[
  {
    "path": ".dockerignore",
    "content": "CHANGELOG.md\nREADME.md\n\nnode_modules\n.github/\nmac/\nwin-wpf/\nwebsite/\nlinux/\ngo.work\ngo.work.sum\n"
  },
  {
    "path": ".editorconfig",
    "content": "# http://editorconfig.org/\n\nroot = true\n\n[*]\nend_of_line = lf\ninsert_final_newline = true\ntrim_trailing_whitespace = true\ncharset = utf-8\n\n[*.go]\nindent_style = tab\n\n[*.{js,jsx,json,html}]\nindent_style = space\nindent_size = 4\n\n[{package.json,.eslintrc.json}]\nindent_size = 2\n\n[i18n/**.json]\nindent_size = 2\n\n[Makefile]\nindent_style = tab\n\n[*.scss]\nindent_style = space\nindent_size = 4\n"
  },
  {
    "path": ".gitattributes",
    "content": "website/** linguist-documentation\nserver/swagger/** linguist-generated\n"
  },
  {
    "path": ".github/CODEOWNERS",
    "content": ""
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: 'Bug: '\nlabels: Bug, Triage\nassignees: ''\n\n---\n\n## Steps to reproduce the behavior\n\n1. Go to ...\n2. Select  ...\n3. Scroll down to ...\n4. See error\n\n## Expected behavior\n\nA clear and concise description of what you expected to happen.\n\n## Screenshots (optional)\n\nIf applicable, add screenshots or a screen recording to elaborate on the problem.\n\n## Edition and Platform\n\n - Edition: Personal Desktop / Personal Server / Mattermost Boards (plugin)\n - Version: [e.g. v0.15.0]\n - Browser and OS: [e.g. Chrome on Mac, Edge on Windows]\n\n## Additional context (optional)\n\nAdd any other context about the problem here, and any notes about the severity:\n* Sev 1: Affects critical functionality without a workaround\n* Sev 2: Affects major functionality with a difficult or non-obvious workaround\n* Sev 3: Affects minor, non-critical functionality\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/doc_improvement.md",
    "content": "---\nname: Documentation Request\nabout: Request improvement to our documentation\ntitle: 'Doc: '\nlabels: Documentation, Triage\nassignees: ''\n\n---\n\n## Summary\n\nConcisely summarize improvement to documentation requested.\n\n## Link to documentation page\n\nIf applicable, link to the documentation page and/or section where you feel the improvement could be added. E.g. `https://docs.mattermost.com/boards/accessing-boards.html`\n\n## (Optional) Additional context and/or screenshot\n\nAdd additional context and/or a screenshot of the product feature you'd like explained in documentation.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/enhancement.md",
    "content": "---\nname: Enhancement/Feature Idea\nabout: Suggest a new capability\ntitle: 'Feature Idea: '\nlabels: Enhancement, Triage\nassignees: ''\n\n---\n\n## Summary\n\nWhat the new capability is.\n\n## How important this is to me and why\n\nImportance: High/Medium/Low\n\nUse cases:\n1.\n2.\n3.\n\n## Additional context/similar features\n\nAny examples of good implementations of this capability.\n"
  },
  {
    "path": ".github/codeql/codeql-config.yml",
    "content": "name: \"CodeQL config\"\n\nquery-filters:\n  - exclude:\n      problem.severity:\n        - warning\n        - recommendation\n  - exclude:\n      id: go/log-injection\n\npaths-ignore:\n  - 'server/swagger/**/*.html'\n  - 'website/**/*.html'\n  - '**/*_test.go'\n  - 'webapp/cypress/**'\n  - '**/*.test.*'\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"github-actions\"\n    directories:\n      - \"**/*\"\n    reviewers:\n      - \"mattermost/cloud-sre\"\n    open-pull-requests-limit: 5\n    groups:\n      Github Actions updates:\n        applies-to: version-updates\n        dependency-type: production\n    schedule:\n      # Check for updates to GitHub Actions every week\n      day: \"monday\"\n      time: \"09:00\"\n      interval: \"weekly\""
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: Check-in tests\n\non:\n  push:\n    branches:\n      - 'main'\n      - 'releases-**'\n  pull_request:\n  workflow_dispatch:\n\nenv:\n BRANCH_NAME: ${{ github.head_ref || github.ref_name }}\n EXCLUDE_ENTERPRISE: true\n\njobs:\n\n  ci-ubuntu-server:\n    runs-on: ubuntu-22.04\n\n    strategy:\n      matrix:\n        db:\n          - sqlite\n          - mysql\n          - mariadb\n          - postgres\n\n    steps:\n    - name: Checkout\n      uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683\n\n    - name: Set up Go\n      uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5\n      with:\n        go-version-file: server/go.mod\n\n    - name: \"Test server: ${{matrix['db']}}\"\n      run: make server-test-${{matrix['db']}}\n\n  ci-ubuntu-webapp:\n    runs-on: ubuntu-22.04\n    steps:\n    - name: Checkout\n      uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683\n      with:\n        path: \"focalboard\"\n        \n    - name: npm ci\n      run: cd focalboard/webapp && npm ci && cd -\n\n    - name: Set up Go\n      uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5\n      with:\n        go-version-file: focalboard/server/go.mod\n\n    - name: Setup Node\n      uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020\n      with:\n        node-version-file: focalboard/webapp/.nvmrc\n\n    - name: Build Linux server\n      run: cd focalboard; make server-linux-package\n\n    - name: Copy server binary for Cypress\n      run: cp focalboard/bin/linux/focalboard-server focalboard/bin/\n\n    - name: Upload server package\n      uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02\n      with:\n        name: focalboard-server-linux-amd64.tar.gz\n        path: ${{ github.workspace }}/focalboard/dist/focalboard-server-linux-amd64.tar.gz\n\n    - name: Lint & test webapp\n      run: cd focalboard; make webapp-ci\n\n  ci-windows-server:\n    runs-on: windows-2022\n\n    strategy:\n      matrix:\n        db:\n          - sqlite\n\n    steps:\n    - name: Checkout\n      uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683\n      with:\n        path: \"focalboard\"\n\n    - name: Set up Go\n      uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5\n      with:\n        go-version-file: focalboard/server/go.mod\n\n    - name: \"Test server (minimum): ${{matrix['db']}}\"\n      run: cd focalboard; make server-test-mini-${{matrix['db']}}\n\n  ci-mac-server:\n    runs-on: macos-15\n\n    strategy:\n      matrix:\n        db:\n          - sqlite\n\n    steps:\n    - name: Checkout\n      uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683\n      with:\n        path: \"focalboard\"\n\n    - name: Set up Go\n      uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5\n      with:\n        go-version-file: focalboard/server/go.mod\n\n    - name: \"Test server (minimum): ${{matrix['db']}}\"\n      run: cd focalboard; make server-test-mini-${{matrix['db']}}\n"
  },
  {
    "path": ".github/workflows/codeql-analysis.yml",
    "content": "name: \"CodeQL\"\n\non:\n  push:\n    branches: [ main, release-** ]\n  pull_request:\n    # The branches below must be a subset of the branches above\n    branches: [ main, release-** ]\n  schedule:\n    - cron: '30 4 * * 0'\n    \npermissions:\n  contents: read\n\njobs:\n  analyze:\n    permissions:\n      security-events: write  # for github/codeql-action/autobuild to send a status report\n    name: Analyze\n    runs-on: ubuntu-22.04\n\n    strategy:\n      fail-fast: false\n      matrix:\n        language: [ 'go', 'javascript' ]\n\n    steps:\n    - name: Checkout repository\n      uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683\n\n    # Initializes the CodeQL tools for scanning.\n    - name: Initialize CodeQL\n      uses: github/codeql-action/init@fca7ace96b7d713c7035871441bd52efbe39e27e\n      with:\n        languages: ${{ matrix.language }}\n        debug: false\n        config-file: ./.github/codeql/codeql-config.yml              \n    \n    # Autobuild attempts to build any compiled languages\n    - name: Autobuild\n      uses: github/codeql-action/autobuild@fca7ace96b7d713c7035871441bd52efbe39e27e    \n\n    # Perform Analysis\n    - name: Perform CodeQL Analysis\n      uses: github/codeql-action/analyze@fca7ace96b7d713c7035871441bd52efbe39e27e    \n"
  },
  {
    "path": ".github/workflows/dev-release.yml",
    "content": "name: Dev-Release\n\non:\n  push:\n    branches: [ main, release-** ]\n  pull_request:\n    branches: [ main, release-** ]\n  workflow_dispatch:\n\nenv:\n BRANCH_NAME: ${{ github.head_ref || github.ref_name }}\n EXCLUDE_ENTERPRISE: true\n\njobs:\n\n  ubuntu:\n    runs-on: ubuntu-22.04\n    steps:\n    - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683\n      with:\n        path: \"focalboard\"\n\n    - name: Replace token 1 server\n      run: sed -i -e \"s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g\" ${{ github.workspace }}/focalboard/server/services/telemetry/telemetry.go\n  \n    - name: Replace token 2 server\n      run: sed -i -e \"s,placeholder_rudder_key,${{ secrets.RUDDER_DEV_KEY }},g\" ${{ github.workspace }}/focalboard/server/services/telemetry/telemetry.go\n   \n    - name: npm ci\n      run: cd focalboard/webapp; npm ci --no-optional\n\n    - name: Set up Go\n      uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5\n      with:\n        go-version: 1.21\n\n    - name: Setup Node\n      uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020\n      with:\n        node-version: 20.11.0\n\n    - name: apt-get update\n      run: sudo apt-get update\n\n    - name: apt-get install -y libgtk-3-dev\n      run: sudo apt-get install -y libgtk-3-dev\n\n    - name: apt-get install -y libwebkit2gtk-4.0-dev\n      run: sudo apt-get install -y libwebkit2gtk-4.0-dev\n\n    - name: Build Linux server and app\n      run: cd focalboard/; make server-linux-package linux-app\n      env:\n        BUILD_NUMBER: ${{ github.run_id }}\n\n    - name: Upload server package\n      uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02\n      with:\n        name: focalboard-server-linux-amd64.tar.gz\n        path: ${{ github.workspace }}/focalboard/dist/focalboard-server-linux-amd64.tar.gz\n\n    - name: Upload app package\n      uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02\n      with:\n        name: focalboard-linux.tar.gz\n        path: ${{ github.workspace }}/focalboard/linux/dist/focalboard-linux.tar.gz\n\n  macos:\n    runs-on: macos-15\n\n    steps:\n\n    - name: Checkout\n      uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683\n      with:\n        path: \"focalboard\"\n    - name: Replace token 1 server\n      run: sed -i -e \"s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g\" ${{ github.workspace }}/focalboard/server/services/telemetry/telemetry.go\n\n    - name: Replace token 2 server\n      run: sed -i -e \"s,placeholder_rudder_key,${{ secrets.RUDDER_DEV_KEY }},g\" ${{ github.workspace }}/focalboard/server/services/telemetry/telemetry.go\n\n    - name: npm ci\n      run: cd focalboard/webapp; npm ci --no-optional\n\n    - name: Set up Go\n      uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5\n      with:\n        go-version: 1.21\n\n    - name: List Xcode versions\n      run: ls -n /Applications/ | grep Xcode*\n\n    - name: Build macOS\n      run: cd focalboard; make mac-app\n      env:\n        DEVELOPER_DIR: /Applications/Xcode_16.0.app/Contents/Developer\n        BUILD_NUMBER: ${{ github.run_id }}\n\n    - name: Upload macOS package\n      uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02\n      with:\n        name: focalboard-mac.zip\n        path: ${{ github.workspace }}/focalboard/mac/dist/focalboard-mac.zip\n\n  windows:\n    runs-on: windows-2022\n\n    steps:\n    - name: Checkout\n      uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683\n      with:\n        path: \"focalboard\"\n    - name: Replace token 1 server\n      run: sed -i -e \"s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g\" ${{ github.workspace }}/focalboard/server/services/telemetry/telemetry.go\n\n    - name: Add msbuild to PATH\n      uses: microsoft/setup-msbuild@v1.3\n\n    - name: npm ci\n      run: cd focalboard/webapp; npm ci --no-optional\n\n    - name: Set up Go\n      uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5\n      with:\n        go-version: 1.21\n\n    - name: Setup NuGet\n      uses: nuget/setup-nuget@323ab0502cd38fdc493335025a96c8fdb0edc71f\n      with:\n        nuget-version: '5.x'\n\n    - name: NuGet Restore\n      run: nuget restore focalboard\\win-wpf\\Focalboard.sln\n\n    - name: Build Windows WPF app\n      run: cd focalboard; make win-wpf-app\n      env:\n        BUILD_NUMBER: ${{ github.run_id }}\n\n    - name: Upload app msix package\n      uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02\n      with:\n        name: focalboard.msix\n        path: ${{ github.workspace }}/focalboard/win-wpf/focalboard.msix\n\n    - name: Upload app zip package\n      uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02\n      with:\n        name: focalboard-win.zip\n        path: ${{ github.workspace }}/focalboard/win-wpf/dist/focalboard-win.zip\n"
  },
  {
    "path": ".github/workflows/lint-server.yml",
    "content": "name: golangci-lint\n\non:\n  push:\n    branches: [ main, release-** ]\n  pull_request:\n    branches: [ main, release-** ]\n  workflow_dispatch:\n\nenv:\n BRANCH_NAME: ${{ github.head_ref || github.ref_name }}\n EXCLUDE_ENTERPRISE: true\n\njobs:\n  down-migrations:\n    runs-on: ubuntu-22.04\n    steps:\n      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683\n        with:\n          path: \"focalboard\"\n      - name: assert that down migrations are SELECT 1 scripts\n        run: |\n          cd focalboard\n          echo 'SELECT 1;' > downmigration\n          for file in server/services/store/sqlstore/migrations/*.down.sql; do diff -Bw downmigration $file; done\n\n  golangci:\n    name: plugin\n    runs-on: ubuntu-22.04\n    steps:\n      - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5\n        with:\n          go-version: 1.21\n      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683\n        with:\n          path: \"focalboard\"\n      - name: set up golangci-lint\n        run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.59.0\n      - name: lint\n        run: |\n          cd focalboard\n          make server-lint\n"
  },
  {
    "path": ".github/workflows/prod-release.yml",
    "content": "name: Production-Release\n\non: workflow_dispatch\n\nenv:\n EXCLUDE_ENTERPRISE: true\n BRANCH_NAME: ${{ github.head_ref || github.ref_name }}\n\njobs:\n\n  ubuntu:\n    runs-on: ubuntu-22.04\n\n    steps:\n    - name: Checkout\n      uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683\n      with:\n        path: \"focalboard\"\n\n    - name: Replace token 1 server\n      run: sed -i -e \"s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g\" ${{ github.workspace }}/focalboard/server/services/telemetry/telemetry.go\n\n    - name: Replace token 2 server\n      run: sed -i -e \"s,placeholder_rudder_key,${{ secrets.RUDDER_PROD_KEY }},g\" ${{ github.workspace }}/focalboard/server/services/telemetry/telemetry.go\n\n    - name: npm ci\n      run: cd focalboard/webapp; npm ci --no-optional\n\n    - name: Set up Go\n      uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5\n      with:\n        go-version: 1.21\n\n    - name: Setup Node\n      uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020\n      with:\n        node-version: 20.11.0\n\n    - name: apt-get update\n      run: sudo apt-get update\n\n    - name: apt-get install -y libgtk-3-dev\n      run: sudo apt-get install -y libgtk-3-dev\n\n    - name: apt-get install -y libwebkit2gtk-4.0-dev\n      run: sudo apt-get install -y libwebkit2gtk-4.0-dev\n\n    - name: Build Linux server and app\n      run: cd focalboard; make server-linux-package linux-app\n      env:\n        BUILD_NUMBER: ${{ github.run_id }}\n\n    - name: Upload server package\n      uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02\n      with:\n        name: focalboard-server-linux-amd64.tar.gz\n        path: ${{ github.workspace }}/focalboard/dist/focalboard-server-linux-amd64.tar.gz\n\n    - name: Upload app package\n      uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02\n      with:\n        name: focalboard-linux.tar.gz\n        path: ${{ github.workspace }}/focalboard/linux/dist/focalboard-linux.tar.gz\n\n  macos:\n    runs-on: macos-15\n\n    steps:\n\n    - name: Checkout\n      uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683\n      with:\n        path: \"focalboard\"\n\n    - name: Replace token 1 server\n      run: sed -i -e \"s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g\" ${{ github.workspace }}/focalboard/server/services/telemetry/telemetry.go\n\n    - name: Replace token 2 server\n      run: sed -i -e \"s,placeholder_rudder_key,${{ secrets.RUDDER_PROD_KEY }},g\" ${{ github.workspace }}/focalboard/server/services/telemetry/telemetry.go\n\n    - name: npm ci\n      run: cd focalboard/webapp; npm ci --no-optional\n\n    - name: Set up Go\n      uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5\n      with:\n        go-version: 1.21\n\n    - name: List Xcode versions\n      run: ls -n /Applications/ | grep Xcode*\n\n    - name: Build macOS\n      run: cd focalboard; make mac-app\n      env:\n        DEVELOPER_DIR: /Applications/Xcode_16.0.app/Contents/Developer\n        BUILD_NUMBER: ${{ github.run_id }}\n\n    - name: Upload macOS package\n      uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02\n      with:\n        name: focalboard-mac.zip\n        path: ${{ github.workspace }}/focalboard/mac/dist/focalboard-mac.zip\n\n  windows:\n    runs-on: windows-2025\n\n    steps:\n    - name: Checkout\n      uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683\n      with:\n        path: \"focalboard\"\n\n    - name: Replace token 1 server\n      run: sed -i -e \"s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g\" ${{ github.workspace }}/focalboard/server/services/telemetry/telemetry.go\n\n    - name: Replace token 2 server\n      run: sed -i -e \"s,placeholder_rudder_key,${{ secrets.RUDDER_PROD_KEY }},g\" ${{ github.workspace }}/focalboard/server/services/telemetry/telemetry.go\n\n    - name: Add msbuild to PATH\n      uses: microsoft/setup-msbuild@v1.3\n\n    - name: npm ci\n      run: cd focalboard/webapp; npm ci --no-optional\n\n    - name: Set up Go\n      uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5\n      with:\n        go-version: 1.21\n\n    - name: Setup NuGet\n      uses: nuget/setup-nuget@323ab0502cd38fdc493335025a96c8fdb0edc71f\n      with:\n        nuget-version: '5.x'\n\n    - name: NuGet Restore\n      run: nuget restore focalboard\\win-wpf\\Focalboard.sln\n\n    - name: Build Windows WPF app\n      run: cd focalboard; make win-wpf-app\n      env:\n        BUILD_NUMBER: ${{ github.run_id }}\n\n    - name: Upload app msix package\n      uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02\n      with:\n        name: focalboard.msix\n        path: ${{ github.workspace }}/focalboard/win-wpf/focalboard.msix\n\n    - name: Upload app zip package\n      uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02\n      with:\n        name: focalboard-win.zip\n        path: ${{ github.workspace }}/focalboard/win-wpf/dist/focalboard-win.zip\n  \n"
  },
  {
    "path": ".github/workflows/scorecards-analysis.yml",
    "content": "name: Scorecards supply-chain security\non:\n  # Only the default branch is supported.\n  branch_protection_rule:\n  schedule:\n    - cron: '38 10 * * 2'\n  push:\n    branches: [ main ]\n\n# Declare default permissions as read only.\npermissions: read-all\n\njobs:\n  analysis:\n    name: Scorecards analysis\n    runs-on: ubuntu-22.04\n    permissions:\n      # Needed to upload the results to code-scanning dashboard.\n      security-events: write\n      actions: read\n      contents: read\n\n    steps:\n      - name: \"Checkout code\"\n        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683\n        with:\n          persist-credentials: false\n\n      - name: \"Run analysis\"\n        uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2\n        with:\n          results_file: results.sarif\n          results_format: sarif\n          # Read-only PAT token. To create it,\n          # follow the steps in https://github.com/ossf/scorecard-action#pat-token-creation.\n          repo_token: ${{ secrets.SCORECARD_READ_TOKEN }}\n          # Publish the results to enable scorecard badges. For more details, see\n          # https://github.com/ossf/scorecard-action#publishing-results.\n          # For private repositories, `publish_results` will automatically be set to `false`,\n          # regardless of the value entered here.\n          publish_results: true\n\n      # Upload the results as artifacts (optional).\n      - name: \"Upload artifact\"\n        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02\n        with:\n          name: SARIF file\n          path: results.sarif\n          retention-days: 5\n\n      # Upload the results to GitHub's code scanning dashboard.\n      - name: \"Upload to code-scanning\"\n        uses: github/codeql-action/upload-sarif@fca7ace96b7d713c7035871441bd52efbe39e27e\n        with:\n          sarif_file: results.sarif\n"
  },
  {
    "path": ".gitignore",
    "content": "# Created by https://www.gitignore.io/api/node\n\n### Node ###\n# Logs\nlogs\n*.log\nnpm-debug.log*\n\n# Runtime data\npids\n*.pid\n*.seed\n\n# OS Files\n.DS_Store\n\n# VSCode project files\n.vscode\n*.code-workspace\n\n# golang\ngo.work \ngo.work.sum\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n.nyc_output\n\n# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (http://nodejs.org/api/addons.html)\nbuild/Release\n\n# Environment files \n.env\n\n# Dependency directory\n# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git\nnode_modules\ndist\npack\npackage\nbin\ndebug\n__debug_bin\nfiles\nocto*.db\nfocalboard*.db\n*.boardarchive\n.eslintcache\n.vscode/settings.json\n# config.json is copied from app-config.json in the Makefile\nmac/resources/config.json\nmac/temp\nmac/dist\nmac/*.xcodeproj/**/xcuserdata\nlinux/bin\nlinux/dist\nlinux/temp\nwin/temp\nwin/dist\nwebapp/cypress/screenshots\nwebapp/cypress/videos\nserver/swagger/clients\nserver/vendor\n.idea\ndocker/certs\ndocker/data\nserver/**/*.coverage\n"
  },
  {
    "path": ".gitlab-ci.yml",
    "content": "stages:\n  - build\n  - s3\n\nvariables:\n  BUILD: \"yes\"\n  IMAGE_BUILDER: $CI_REGISTRY/mattermost/ci/images/builder:go-1.19.5-node-16.15.0-1\n\ninclude:\n  - project: mattermost/ci/focalboard\n    ref: main\n    file: private.yml\n"
  },
  {
    "path": ".gitpod.yml",
    "content": "mainConfiguration: https://github.com/mattermost/mattermost-gitpod-config\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Focalboard Changelog\n\nFocalboard is an open source, self-hosted alternative to Trello, Notion, Asana and Jira for project management. We help individuals and teams define, organize, track and manage their work.\n\nThis changelog summarizes updates to our open source project. You can also find the [latest releases and release notes on GitHub here](https://github.com/mattermost/focalboard/releases).\n\n## v0.15 Release - March, 2022\n* Onboarding tour. Thanks @harshilsharma63! @jespino!\n* Channel intro link to Boards. Thanks @sbishel!\n* Improved share board UI. Thanks @sbishel!\n* New error pages. Thanks @wiggin77! @asaadmahmood!\n* In-app links to import Help Docs. Thanks @justinegeffen! @sbishel!\n* Toggle to hide empty groups for TableView. Thanks @vish9812!\n* Removed transactions from sqlite backend to prevent locking issues. Thanks @jespino!\n* Update readme with accurate Linux standalone app build instructions. Thanks @wiggin77!\n* Change wrapping in React.memo. Thanks @kamre!\n* Don't use property value for key construction. Thanks @kamre!\n* Updated comment box alignment. Thanks @asaadmahmood!\n* Show \"(Deleted User)\" instead of UUID when user not found. Thanks @akkivasu!\n\n## v0.14 Release - February, 2022\n* Template selector dialog. Thanks @jespino!\n* New standard templates. Thanks @wiggin77!\n* Archive file format has changed and now supports images. Thanks @wiggin77!\n* Card badges. Thanks @kamre!\n* URL property improvement. Thanks @kamre!\n* GIF support in card descriptions. Thanks @asaadmahmood!\n* Add links to linode. Thanks @ChaseKnowlden!\n* Add `chown` for nobody in the docker run example. Thanks @K3UL!\n* Fixed Mac M1 chip build. Thanks @jpaldeano!\n* Removed link to deleted css file. Thanks @kamre!\n* Fixed typo in config.toml. Thanks @krmh04!\n\n## v0.12 Release - January, 2022\n* Change notifications. Thanks @wiggin77!\n* Person avatars. Thanks @asaadmahmood!\n* Updated comment sort order. Thanks @asaadmahmood!\n\n## v0.11 Release - December, 2021\n* Calendar view. Thanks @sbishel!\n* `@mention` autocomplete. Thanks @hahmadia!\n\n## v0.10 Release - November, 2021\n* @mention notifications. Thanks @wiggin77!\n* Board calculations. Thanks @harshilsharma63!\n* Unfurl card previews in posts. Thanks @hahmadia!\n* Plus many, many contributions from Hacktoberfest and beyond, including from: @jufab, @kamre, @Johennes, @nishantwrp, @tiago154, @DeeJayBro, @CuriousCorrelation, @prakharporwal, @donno2048, @anchepiece, @puerco, @adithyaakrishna, @JenyaFTW, @ivernus, @grsky360, @b4sen, @naresh1205, @JtheBAB, @ssensalo, @berkeka, @yedamao, @Prassud, @NakulChauhan2001, @achie27, @crspeller, @sahil9001, @alauregaillard, @igordsm, @rafaeelaudibert, @kaakaa, @Sayanta66, @Bhavin789, @Shahzayb, @kayazeren, @fcoiuri, @tsabi, @DeviousLab, @leosunmo, @xMicky24GIT, @majidsajadi, @marcvelasco, and @aloks98. Sorry if we missed anyone in this list!\n\n## v0.9 Release - August, 2021\n* New date range property type. Thanks @sbishel!\n* Changed the urls to use routes instead of query parameters. Thanks @jespino!\n* Add clear button to value selectors. Thanks @jespino!\n* Fix auto-size columns in FireFox. Thanks @kamre!\n* Fix comments not appearing in readonly view. Thanks @harshilsharma63!\n* Multi-line card titles. Thanks @kamre!\n* Add unit tests for sqlstore. Thanks @yedamao!\n* Add makefile documentation. Thanks @Szymongib!\n\n## v0.8 Release - July, 2021\n* CreatedBy property. Thanks @harshilsharma63!\n* Fix dragged card order. Thanks @kamre!\n* Date format user setting. Thanks @darkLord19!\n* Add property tooltip in board view. Thanks @ditsemto!\n* Fix plugin links. Thanks @N3rdP1um23!\n* Add MySQL documentation. Thanks @ctlaltdieliet and @3l0w!\n* RPC API support. Thanks @agnivade!\n\n## v0.7.0 Release - June, 2021\n* Multi-select property type. Thanks @hahmadia!\n* Checkbox property type. Thanks @mickmister!\n* Person property type. Thanks @harshilsharma63!\n* Grouped table view. Thanks @sbishel!\n* Export individual boards. Thanks @hahmadia!\n* Focalboard can now be built as a Mattermost plugin! Thanks @mgdelacroix and @jespino!\n* Improved read-only fields display. Thanks @Johennes!\n* Improved logging. Thanks @wiggin77!\n* Prometheus metrics. Thanks @spirosoik!\n* Mac: Open window by clicking on the dock icon. Thanks @Johennes!\n* Additional unit tests. Thanks @matheusmosca!\n* Fixed Linux app caret display. Thanks @fritsstegmann!\n* Added CodeQL check. Thanks @srkgupta!\n\n## v0.6.7 Release - May, 2021\n\n* Key Updates:\n    * Added Todoist import script. Thanks @harshilsharma63!\n    * Added MySql database support. Thanks @jespino!\n    * Added URL and phone number properties. Thanks @BharatKalluri!\n    * Added documentation for share board. Thanks @haardikdharma10!\n    * Persist Mac app settings. Thanks @Johennes!\n    * Improved board sorting without leading emoji. Thanks @Johennes!\n    * Added Prettier linting for SCSS. Thanks @signalwerk!\n    * Improved table headers. Thanks @sbishel!\n    * Disable unused Mac tab menu. Thanks @@haardikdharma10!\n    * Fixed server lint issues. Thanks @harshilsharma63!\n    * Updated open button. Thanks @arjitc!\n\n## v0.6.5 Release - April 19, 2021\n\n* Key Updates:\n  * Focalboard now available on DockerHub at https://hub.docker.com/r/mattermost/focalboard. [#91](https://github.com/mattermost/focalboard/issues/91) Thanks @jwilander @obbardc!\n  * You can now contribute translations to Focalboard on https://translate.mattermost.com/. Thanks @jespino!\n  * Added German language translation. Thanks @svelle!\n  * Added Japanese language translation. Thanks @kaakaa!\n  * Added French language translation. Thanks @CyrilLD!\n  * Added Occitan language translation. Thanks Quentin PAGÈS!\n  * Added Dutch language translation. Thanks Tom De Moor!\n  * Added Turkish language translation. Thanks Abdullah Musab!\n  * Added Chinese language translation. Thanks Yao Xie and toto6038!\n  * Added Russian language translation. Thanks Edward Smirnov!\n  * Add Dockerfile to run service in a container. [#76](https://github.com/mattermost/focalboard/pull/76) Thanks @proffalken!\n  * Add docker-compose to run the whole service in containers. [#105](https://github.com/mattermost/focalboard/pull/105) Thanks @jbutler992!\n  * Added Gallery view.\n  * Added Checkbox content type.\n  * Added Selected cards duplication with Ctrl+D.\n  * Added Search shortcut (Ctrl+Shift+F).\n* Requested Contributions\n  * Add more frontend unit test coverage. [#126](https://github.com/mattermost/focalboard/pull/126) Thanks @renjithgr!\n  * [GH-40](https://github.com/mattermost/focalboard/issues/40) - Add property type email [#84](https://github.com/mattermost/focalboard/pull/84). Thanks @renjithgr!\n\n## v0.6.1 Release - March 15, 2021\n\n* Focalboard Personal Desktop is now live in the App Stores:\n    * [Mac App Store](https://apps.apple.com/app/apple-store/id1556908618?pt=2114704&ct=changelog&mt=8)\n    * [Microsoft App Store](https://www.microsoft.com/store/apps/9NLN2T0SX9VF?cid=changelog)\n* Added [Windows native app (WPF)](https://github.com/mattermost/focalboard/tree/main/win-wpf) support\n* Added [Swagger / OpenAPI definition and documentation](https://htmlpreview.github.io/?https://github.com/mattermost/focalboard/blob/main/server/swagger/docs/html/index.html)\n* Added [Import scripts for Trello, Asana, and Notion](https://github.com/mattermost/focalboard/tree/main/import)\n* Added [Developer Tips and Tricks article](https://www.focalboard.com/contribute/getting-started/dev-tips/).\n* Added Security improvements:\n\t* [Single-user session token](https://github.com/mattermost/focalboard/commit/0fe96ad7ed3b0c3a68c9a5889b34b764782f9266)\n\t* [CSRF prevention with X-Requested-With header](https://github.com/mattermost/focalboard/commit/43c656c9a440e12f87b61d66654ed3d9873b1620)\n\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Disclaimer\n\n> [!WARNING]\n> **Effective September 15th, 2023, Mattermost, Inc. staff are no longer reviewing or merging pull requests for either Focalboard or the Mattermost Boards plugin in this repository (`mattermost/focalboard`). We encourage the community to fork this repository for continued development and contributions.**\n>\n> The reason behind these changes is to focus Mattermost developer resources on improving the platform’s performance and core features to ensure Mattermost continues being resilient, stable, and best-in-breed for critical operations.\n>\n> ️💡 [Learn more](https://forum.mattermost.com/t/upcoming-product-changes-to-boards-and-various-plugins/16669)\n\n## Past maintainers\n\n- **Scott Bishel**: [@sbishel](https://github.com/sbishel)\n- **Jesús Espino**: [@jespino](https://github.com/jespino)\n- **Doug Lauder**: [@wiggin77](https://github.com/wiggin77)\n- **Miguel de la Cruz**: [@mgdelacroix](https://github.com/mgdelacroix)\n- **Harshil Sharma**: [@harshilsharma63](https://github.com/harshilsharma63)\n- **Chen Lim**: [@chenilim](https://github.com/chenilim)\n- **Ogi Marušić**: [@ogi-m](https://github.com/ogi-m)\n- **Winson Wu**: [@wuwinson](https://github.com/wuwinson)\n- **Justine Geffen**: [@justinegeffen](https://github.com/justinegeffen)\n"
  },
  {
    "path": "Dockerfile.build",
    "content": "# This Dockerfile is used to build Focalboard for Linux. It builds all the parts inside the image\n# and the last stage just holds the package which is then copied back to the host.\n#\n# docker buildx build -f Dockerfile.build --no-cache --platform linux/amd64 -t focalboard-build:dirty --output out .\n# docker buildx build -f Dockerfile.build --no-cache --platform linux/arm64 -t focalboard-build:dirty --output out .\n#\n# Afterwards the packages can be found in the ./out folder.\n\n# build frontend\nFROM node:16.3.0@sha256:ca6daf1543242acb0ca59ff425509eab7defb9452f6ae07c156893db06c7a9a4 AS frontend\n\nWORKDIR /webapp\nCOPY webapp .\n\n### 'CPPFLAGS=\"-DPNG_ARM_NEON_OPT=0\"' Needed To Avoid Bug Described in: https://github.com/imagemin/optipng-bin/issues/118#issuecomment-1019838562\n### Can be Removed when Ticket will be Closed\nRUN CPPFLAGS=\"-DPNG_ARM_NEON_OPT=0\" npm install --no-optional && \\\n    npm run pack\n\n# build backend and package\nFROM golang:1.18.3@sha256:b203dc573d81da7b3176264bfa447bd7c10c9347689be40540381838d75eebef AS backend\n\nCOPY . .\nCOPY --from=frontend /webapp/pack webapp/pack\n\nARG TARGETARCH\n\n# RUN apt-get update && apt-get install libgtk-3-dev libwebkit2gtk-4.0-dev -y\nRUN EXCLUDE_PLUGIN=true EXCLUDE_SERVER=true EXCLUDE_ENTERPRISE=true make server-linux arch=${TARGETARCH}\nRUN make server-linux-package-docker arch=${TARGETARCH}\n\n# Copy package back to host\nFROM scratch AS dist\nARG TARGETARCH\nCOPY --from=backend /go/dist/focalboard-server-linux-${TARGETARCH}.tar.gz .\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "Mattermost Licensing\n\nSOFTWARE LICENSING\n\nYou are licensed to use compiled versions of Focalboard produced by Mattermost, Inc. under an MIT LICENSE\n\n-\tSee MIT-COMPILED-LICENSE.md included in compiled versions for details\n\nYou may be licensed to use source code to create compiled versions not produced by Mattermost, Inc. in one of two ways:\n\n1. Under the Free Software Foundation’s GNU AGPL v.3.0, subject to the exceptions outlined in this policy; or\n2. Under a commercial license available from Mattermost, Inc. by contacting commercial@mattermost.com\n\nYou are licensed to use the source code in Admin Tools and Configuration Files (webapp/html-templates/, app-config.json,\nconfig.json, webapp/i18n/, server/model/, plugin/ and all subdirectories thereof) under the Apache License v2.0.\n\nWe promise that we will not enforce the copyleft provisions in AGPL v3.0 against you if your application (a) does not\nlink to Focalboard directly, but exclusively uses Focalboard's Admin Tools and Configuration Files, and\n(b) you have not modified, added to or adapted the source code of Focalboard in a way that results in the creation of\na “modified version” or “work based on” Focalboard as these terms are defined in the AGPL v3.0 license.\n\nMATTERMOST TRADEMARK GUIDELINES\n\nYour use of the mark Mattermost is subject to Mattermost, Inc's prior written approval and our organization’s Trademark\nStandards of Use at http://www.mattermost.org/trademark-standards-of-use/. For trademark approval or any questions\nyou have about using these trademarks, please email trademark@mattermost.com\n\n------------------------------------------------------------------------------------------------------------------------------\n\n                               Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\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------------------------------------------------------------------------------\n\nThe software is released under the terms of the GNU Affero General Public\nLicense, version 3.\n\n                    GNU AFFERO GENERAL PUBLIC LICENSE\n                       Version 3, 19 November 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU Affero General Public License is a free, copyleft license for\nsoftware and other kinds of works, specifically designed to ensure\ncooperation with the community in the case of network server software.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nour General Public Licenses are intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  Developers that use our General Public Licenses protect your rights\nwith two steps: (1) assert copyright on the software, and (2) offer\nyou this License which gives you legal permission to copy, distribute\nand/or modify the software.\n\n  A secondary benefit of defending all users' freedom is that\nimprovements made in alternate versions of the program, if they\nreceive widespread use, become available for other developers to\nincorporate.  Many developers of free software are heartened and\nencouraged by the resulting cooperation.  However, in the case of\nsoftware used on network servers, this result may fail to come about.\nThe GNU General Public License permits making a modified version and\nletting the public access it on a server without ever releasing its\nsource code to the public.\n\n  The GNU Affero General Public License is designed specifically to\nensure that, in such cases, the modified source code becomes available\nto the community.  It requires the operator of a network server to\nprovide the source code of the modified version running there to the\nusers of that server.  Therefore, public use of a modified version, on\na publicly accessible server, gives the public access to the source\ncode of the modified version.\n\n  An older license, called the Affero General Public License and\npublished by Affero, was designed to accomplish similar goals.  This is\na different license, not a version of the Affero GPL, but Affero has\nreleased a new version of the Affero GPL which permits relicensing under\nthis license.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU Affero General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Remote Network Interaction; Use with the GNU General Public License.\n\n  Notwithstanding any other provision of this License, if you modify the\nProgram, your modified version must prominently offer all users\ninteracting with it remotely through a computer network (if your version\nsupports such interaction) an opportunity to receive the Corresponding\nSource of your version by providing access to the Corresponding Source\nfrom a network server at no charge, through some standard or customary\nmeans of facilitating copying of software.  This Corresponding Source\nshall include the Corresponding Source for any work covered by version 3\nof the GNU General Public License that is incorporated pursuant to the\nfollowing paragraph.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the work with which it is combined will remain governed by version\n3 of the GNU General Public License.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU Affero General Public License from time to time.  Such new versions\nwill be similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU Affero General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU Affero General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU Affero General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU Affero General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU Affero General Public License for more details.\n\n    You should have received a copy of the GNU Affero General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If your software can interact with users remotely through a computer\nnetwork, you should also make sure that it provides a way for users to\nget its source.  For example, if your program is a web application, its\ninterface could display a \"Source\" link that leads users to an archive\nof the code.  There are many ways you could offer source, and different\nsolutions will be better for different programs; see section 13 for the\nspecific requirements.\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU AGPL, see\n<http://www.gnu.org/licenses/>.\n"
  },
  {
    "path": "Makefile",
    "content": ".PHONY: prebuild clean cleanall ci server server-mac server-linux server-win server-linux-package generate watch-server webapp mac-app win-app-wpf linux-app modd-precheck templates-archive\n\nPACKAGE_FOLDER = focalboard\n\n# Build Flags\nBUILD_NUMBER ?= $(BUILD_NUMBER:)\nBUILD_DATE = $(shell date -u)\nBUILD_HASH = $(shell git rev-parse HEAD)\n# If we don't set the build number it defaults to dev\nifeq ($(BUILD_NUMBER),)\n\tBUILD_NUMBER := dev\n\tBUILD_DATE := n/a\nendif\n\nBUILD_TAGS += json1 sqlite3\n\nLDFLAGS += -X \"github.com/mattermost/focalboard/server/model.BuildNumber=$(BUILD_NUMBER)\"\nLDFLAGS += -X \"github.com/mattermost/focalboard/server/model.BuildDate=$(BUILD_DATE)\"\nLDFLAGS += -X \"github.com/mattermost/focalboard/server/model.BuildHash=$(BUILD_HASH)\"\n\nRACE = -race\n\nifeq ($(OS),Windows_NT)\n\tRACE := ''\nendif\n\n# MAC cpu architecture\nifeq ($(shell uname -m),arm64)\n\tMAC_GO_ARCH := arm64\nelse\n\tMAC_GO_ARCH := amd64\nendif\n\nall: webapp server ## Build server and webapp.\n\nprebuild: ## Run prebuild actions (install dependencies etc.).\n\tcd webapp; npm install\n\nci: webapp-ci server-test ## Simulate CI, locally.\n\ntemplates-archive: ## Build templates archive file\n\tcd server/assets/build-template-archive; go run -tags '$(BUILD_TAGS)' main.go --dir=\"../templates-boardarchive\" --out=\"../templates.boardarchive\"\n\nserver: ## Build server for local environment.\n\t$(eval LDFLAGS += -X \"github.com/mattermost/focalboard/server/model.Edition=dev\")\n\tcd server; go build -ldflags '$(LDFLAGS)' -tags '$(BUILD_TAGS)' -o ../bin/focalboard-server ./main\n\nserver-mac: ## Build server for Mac.\n\tmkdir -p bin/mac\n\t$(eval LDFLAGS += -X \"github.com/mattermost/focalboard/server/model.Edition=mac\")\nifeq ($(FB_PROD),)\n\tcd server; env GOOS=darwin GOARCH=$(MAC_GO_ARCH) go build -ldflags '$(LDFLAGS)' -tags '$(BUILD_TAGS)' -o ../bin/mac/focalboard-server ./main\nelse\n# Always build x86 for production, to work on both Apple Silicon and legacy Macs\n\tcd server; env GOOS=darwin GOARCH=amd64 CGO_ENABLED=1 go build -ldflags '$(LDFLAGS)' -tags '$(BUILD_TAGS)' -o ../bin/mac/focalboard-server ./main\nendif\n\nserver-linux: ## Build server for Linux.\n\tmkdir -p bin/linux\n\t$(eval LDFLAGS += -X \"github.com/mattermost/focalboard/server/model.Edition=linux\")\n\tcd server; env GOOS=linux GOARCH=$(arch) go build -ldflags '$(LDFLAGS)' -tags '$(BUILD_TAGS)' -o ../bin/linux/focalboard-server ./main\n\nserver-docker: ## Build server for Docker Architectures.\n\tmkdir -p bin/docker\n\t$(eval LDFLAGS += -X \"github.com/mattermost/focalboard/server/model.Edition=linux\")\n\tcd server; env GOOS=$(os) GOARCH=$(arch) go build -ldflags '$(LDFLAGS)' -tags '$(BUILD_TAGS)' -o ../bin/docker/focalboard-server ./main\n\nserver-win: ## Build server for Windows.\n\t$(eval LDFLAGS += -X \"github.com/mattermost/focalboard/server/model.Edition=win\")\n\tcd server; env GOOS=windows GOARCH=amd64 go build -ldflags '$(LDFLAGS)' -tags '$(BUILD_TAGS)' -o ../bin/win/focalboard-server.exe ./main\n\nserver-dll: ## Build server as Windows DLL.\n\t$(eval LDFLAGS += -X \"github.com/mattermost/focalboard/server/model.Edition=win\")\n\tcd server; env GOOS=windows GOARCH=amd64 go build -ldflags '$(LDFLAGS)' -tags '$(BUILD_TAGS)' -buildmode=c-shared -o ../bin/win-dll/focalboard-server.dll ./main\n\nserver-linux-package: server-linux webapp\n\trm -rf package\n\tmkdir -p package/${PACKAGE_FOLDER}/bin\n\tcp bin/linux/focalboard-server package/${PACKAGE_FOLDER}/bin\n\tcp -R webapp/pack package/${PACKAGE_FOLDER}/pack\n\tcp server-config.json package/${PACKAGE_FOLDER}/config.json\n\tcp NOTICE.txt package/${PACKAGE_FOLDER}\n\tcp webapp/NOTICE.txt package/${PACKAGE_FOLDER}/webapp-NOTICE.txt\n\tmkdir -p dist\n\tcd package && tar -czvf ../dist/focalboard-server-linux-amd64.tar.gz ${PACKAGE_FOLDER}\n\trm -rf package\n\nserver-linux-package-docker:\n\trm -rf package\n\tmkdir -p package/${PACKAGE_FOLDER}/bin\n\tcp bin/linux/focalboard-server package/${PACKAGE_FOLDER}/bin\n\tcp -R webapp/pack package/${PACKAGE_FOLDER}/pack\n\tcp server-config.json package/${PACKAGE_FOLDER}/config.json\n\tcp NOTICE.txt package/${PACKAGE_FOLDER}\n\tcp webapp/NOTICE.txt package/${PACKAGE_FOLDER}/webapp-NOTICE.txt\n\tmkdir -p dist\n\tcd package && tar -czvf ../dist/focalboard-server-linux-$(arch).tar.gz ${PACKAGE_FOLDER}\n\trm -rf package\n\ngenerate: ## Install and run code generators.\n\tcd server; go install github.com/golang/mock/mockgen@v1.6.0\n\tcd server; go generate ./...\n\nserver-lint: ## Run linters on server code.\n\t@if ! [ -x \"$$(command -v golangci-lint)\" ]; then \\\n\t\techo \"golangci-lint is not installed. Please see https://github.com/golangci/golangci-lint#install-golangci-lint for installation instructions.\"; \\\n\t\texit 1; \\\n\tfi;\n\tcd server; golangci-lint run ./...\n\nmodd-precheck:\n\t@if ! [ -x \"$$(command -v modd)\" ]; then \\\n\t\techo \"modd is not installed. Please see https://github.com/cortesi/modd#install for installation instructions\"; \\\n\t\texit 1; \\\n\tfi; \\\n\nwatch: modd-precheck ## Run both server and webapp watching for changes\n\tenv FOCALBOARD_BUILD_TAGS='$(BUILD_TAGS)' modd\n\nwatch-single-user: modd-precheck ## Run both server and webapp in single user mode watching for changes\n\tenv FOCALBOARDSERVER_ARGS=--single-user FOCALBOARD_BUILD_TAGS='$(BUILD_TAGS)' modd\n\nwatch-server-test: modd-precheck ## Run server tests watching for changes\n\tenv FOCALBOARD_BUILD_TAGS='$(BUILD_TAGS)' modd -f modd-servertest.conf\n\nserver-test: server-test-sqlite server-test-mysql server-test-mariadb server-test-postgres ## Run server tests\n\nserver-test-sqlite: export FOCALBOARD_UNIT_TESTING=1\n\nserver-test-sqlite: ## Run server tests using sqlite\n\tcd server; go test -tags '$(BUILD_TAGS)' -race -v -coverpkg=./... -coverprofile=server-sqlite-profile.coverage -count=1 -timeout=30m ./...\n\tcd server; go tool cover -func server-sqlite-profile.coverage\n\nserver-test-mini-sqlite: export FOCALBOARD_UNIT_TESTING=1\n\nserver-test-mini-sqlite: ## Run server tests using sqlite\n\tcd server/integrationtests; go test -tags '$(BUILD_TAGS)' $(RACE) -v -count=1 -timeout=30m ./...\n\nserver-test-mysql: export FOCALBOARD_UNIT_TESTING=1\nserver-test-mysql: export FOCALBOARD_STORE_TEST_DB_TYPE=mysql\nserver-test-mysql: export FOCALBOARD_STORE_TEST_DOCKER_PORT=44446\n\nserver-test-mysql: ## Run server tests using mysql\n\t@echo Starting docker container for mysql\n\tdocker compose -f ./docker-testing/docker-compose-mysql.yml down -v --remove-orphans\n\tdocker compose -f ./docker-testing/docker-compose-mysql.yml run start_dependencies\n\tcd server; go test -tags '$(BUILD_TAGS)' -race -v -coverpkg=./... -coverprofile=server-mysql-profile.coverage -count=1 -timeout=30m ./...\n\tcd server; go tool cover -func server-mysql-profile.coverage\n\tdocker compose -f ./docker-testing/docker-compose-mysql.yml down -v --remove-orphans\n\nserver-test-mariadb: export FOCALBOARD_UNIT_TESTING=1\nserver-test-mariadb: export FOCALBOARD_STORE_TEST_DB_TYPE=mariadb\nserver-test-mariadb: export FOCALBOARD_STORE_TEST_DOCKER_PORT=44445\n\nserver-test-mariadb: templates-archive ## Run server tests using mysql\n\t@echo Starting docker container for mariadb\n\tdocker compose -f ./docker-testing/docker-compose-mariadb.yml down -v --remove-orphans\n\tdocker compose -f ./docker-testing/docker-compose-mariadb.yml run start_dependencies\n\tcd server; go test -tags '$(BUILD_TAGS)' -race -v -coverpkg=./... -coverprofile=server-mariadb-profile.coverage -count=1 -timeout=30m ./...\n\tcd server; go tool cover -func server-mariadb-profile.coverage\n\tdocker compose -f ./docker-testing/docker-compose-mariadb.yml down -v --remove-orphans\n\nserver-test-postgres: export FOCALBOARD_UNIT_TESTING=1\nserver-test-postgres: export FOCALBOARD_STORE_TEST_DB_TYPE=postgres\nserver-test-postgres: export FOCALBOARD_STORE_TEST_DOCKER_PORT=44447\n\nserver-test-postgres: ## Run server tests using postgres\n\t@echo Starting docker container for postgres\n\tdocker compose -f ./docker-testing/docker-compose-postgres.yml down -v --remove-orphans\n\tdocker compose -f ./docker-testing/docker-compose-postgres.yml run start_dependencies\n\tcd server; go test -tags '$(BUILD_TAGS)' -race -v -coverpkg=./... -coverprofile=server-postgres-profile.coverage -count=1 -timeout=30m ./...\n\tcd server; go tool cover -func server-postgres-profile.coverage\n\tdocker compose -f ./docker-testing/docker-compose-postgres.yml down -v --remove-orphans\n\nwebapp: ## Build webapp.\n\tcd webapp; npm run pack\n\nwebapp-ci: ## Webapp CI: linting & testing.\n\tcd webapp; npm run check\n\tcd webapp; npm run test\n\tcd webapp; npm run cypress:ci\n\nwebapp-test: ## jest tests for webapp\n\tcd webapp; npm run test\n\nmac-app: server-mac webapp ## Build Mac application.\n\trm -rf mac/temp\n\trm -rf mac/dist\n\trm -rf mac/resources/bin\n\trm -rf mac/resources/pack\n\tmkdir -p mac/resources/bin\n\tcp bin/mac/focalboard-server mac/resources/bin/focalboard-server\n\tcp app-config.json mac/resources/config.json\n\tcp -R webapp/pack mac/resources/pack\n\tmkdir -p mac/temp\n\txcodebuild archive -workspace mac/Focalboard.xcworkspace -scheme Focalboard -archivePath mac/temp/focalboard.xcarchive CODE_SIGN_IDENTITY=\"\" CODE_SIGNING_REQUIRED=\"NO\" CODE_SIGNING_ALLOWED=\"NO\" \\\n\t\t|| { echo \"xcodebuild failed, did you install the full Xcode and not just the CLI tools?\"; exit 1; }\n\tmkdir -p mac/dist\n\tcp -R mac/temp/focalboard.xcarchive/Products/Applications/Focalboard.app mac/dist/\n\t# xcodebuild -exportArchive -archivePath mac/temp/focalboard.xcarchive -exportPath mac/dist -exportOptionsPlist mac/export.plist\n\tcp NOTICE.txt mac/dist\n\tcp webapp/NOTICE.txt mac/dist/webapp-NOTICE.txt\n\tcd mac/dist; zip -r focalboard-mac.zip Focalboard.app MIT-COMPILED-LICENSE.md NOTICE.txt webapp-NOTICE.txt\n\nwin-wpf-app: server-dll webapp ## Build Windows WPF application.\n\tcd win-wpf && ./build.bat\n\tcd win-wpf && ./package.bat\n\tcd win-wpf && ./package-zip.bat\n\nlinux-app: webapp ## Build Linux application.\n\trm -rf linux/temp\n\trm -rf linux/dist\n\tmkdir -p linux/dist\n\tmkdir -p linux/temp/focalboard-app\n\tcp app-config.json linux/temp/focalboard-app/config.json\n\tcp NOTICE.txt linux/temp/focalboard-app/\n\tcp webapp/NOTICE.txt linux/temp/focalboard-app/webapp-NOTICE.txt\n\tcp -R webapp/pack linux/temp/focalboard-app/pack\n\tcd linux; make build\n\tcp -R linux/bin/focalboard-app linux/temp/focalboard-app/\n\tcd linux/temp; tar -zcf ../dist/focalboard-linux.tar.gz focalboard-app\n\trm -rf linux/temp\n\nswagger: ## Generate swagger API spec and clients based on it.\n\tmkdir -p server/swagger/docs\n\tmkdir -p server/swagger/clients\n\tcd server && swagger generate spec -m -o ./swagger/swagger.yml\n\n\tcd server/swagger && openapi-generator generate -i swagger.yml -g html2 -o docs/html\n\tcd server/swagger && openapi-generator generate -i swagger.yml -g go -o clients/go\n\tcd server/swagger && openapi-generator generate -i swagger.yml -g javascript -o clients/javascript\n\tcd server/swagger && openapi-generator generate -i swagger.yml -g typescript-fetch -o clients/typescript\n\tcd server/swagger && openapi-generator generate -i swagger.yml -g swift5 -o clients/swift\n\tcd server/swagger && openapi-generator generate -i swagger.yml -g python -o clients/python\n\nclean: ## Clean build artifacts.\n\trm -rf bin\n\trm -rf dist\n\trm -rf webapp/pack\n\trm -rf mac/temp\n\trm -rf mac/dist\n\trm -rf linux/dist\n\trm -rf win-wpf/msix\n\trm -f win-wpf/focalboard.msix\n\ncleanall: clean ## Clean all build artifacts and dependencies.\n\trm -rf webapp/node_modules\n\n## Help documentatin à la https://marmelab.com/blog/2016/02/29/auto-documented-makefile.html\nhelp:\n\t@grep -E '^[0-9a-zA-Z_-]+:.*?## .*$$' ./Makefile | sort | awk 'BEGIN {FS = \":.*?## \"}; {printf \"\\033[36m%-30s\\033[0m %s\\n\", $$1, $$2}'\n"
  },
  {
    "path": "NOTICE.txt",
    "content": "Focalboard\n© 2015-present Mattermost, Inc.  All Rights Reserved.  See LICENSE.txt for license information.\n\nNOTICES:\n--------\n\nThis document includes a list of open source components used in Focalboard, including those that have been modified.\n\n-----\n\n## Go\n\nThis product uses the Go programming language by the Go authors.\n\n* HOMEPAGE:\n   * https://golang.org\n\n* LICENSE: BSD-style\n\nCopyright (c) 2009 The Go Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n   * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n   * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n   * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n---\n\n## Masterminds/squirrel\n\nThis product contains 'squirrel' by GitHub user \"Masterminds\".\n\nFluent SQL generation for golang\n\n* HOMEPAGE:\n  * https://github.com/Masterminds/squirrel\n\n* LICENSE: MIT\n\nSquirrel\nThe Masterminds\nCopyright (C) 2014-2015, Lann Martin\nCopyright (C) 2015-2016, Google\nCopyright (C) 2015, Matt Farina and Matt Butcher\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n---\n\n## golang-migrate/migrate\n\nThis product contains 'migrate' by GitHub user \"golang-migrate\".\n\nDatabase migrations. CLI and Golang library.\n\n* HOMEPAGE:\n  * https://github.com/golang-migrate/migrate\n\n* LICENSE: MIT\n\nThe MIT License (MIT)\n\nOriginal Work\nCopyright (c) 2016 Matthias Kadenbach\nhttps://github.com/mattes/migrate\n\nModified Work\nCopyright (c) 2018 Dale Hui\nhttps://github.com/golang-migrate/migrate\n\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n---\n\n## x/tools\n\nThis product contains 'tools' by The Go Authors.\n\n[mirror] Go tools\n\n* HOMEPAGE:\n  * https://github.com/golang/tools\n\n* LICENSE: BSD-3-Clause\n\nCopyright (c) 2009 The Go Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n   * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n   * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n   * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n---\n\n## x/crypto\n\nThis product contains 'crypto' by The Go Authors.\n\n[mirror] Go supplementary cryptography libraries\n\n* HOMEPAGE:\n  * https://github.com/golang/crypto\n\n* LICENSE: BSD-3-Clause\n\nCopyright (c) 2009 The Go Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n   * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n   * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n   * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n---\n\n## gorilla/handlers\n\nThis product contains 'handlers' by Gorilla web toolkit.\n\nA collection of useful handlers for Go's net/http package.\n\n* HOMEPAGE:\n  * https://github.com/gorilla/handlers\n\n* LICENSE: BSD-2-Clause\n\nCopyright (c) 2013 The Gorilla Handlers Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, 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\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n---\n\n## gorilla/mux\n\nThis product contains 'mux' by Gorilla web toolkit.\n\nA powerful URL router and dispatcher for golang.\n\n* HOMEPAGE:\n  * https://github.com/gorilla/mux\n\n* LICENSE: BSD-3-Clause\n\nCopyright (c) 2012-2018 The Gorilla Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n\t * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n\t * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n\t * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n---\n\n## gorilla/websocket\n\nThis product contains 'websocket' by Gorilla web toolkit.\n\nA WebSocket implementation for Go.\n\n* HOMEPAGE:\n  * https://github.com/gorilla/websocket\n\n* LICENSE: BSD-2-Clause\n\nCopyright (c) 2013 The Gorilla WebSocket Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, 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\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n---\n\n## zserge/lorca\n\nThis product contains 'lorca' by GitHub user \"zserge\".\n\nA very small library to build modern HTML5 desktop apps in Go.\n\n* HOMEPAGE:\n  * https://github.com/zserge/lorca\n\n* LICENSE: MIT\n\nMIT License\n\nCopyright (c) 2018 Serge Zaitsev\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n---\n\n## webview/webview\n\nThis product contains 'webview' by GitHub user \"webview\".\n\nTiny cross-platform webview library for C/C++/Golang.\n\n* HOMEPAGE:\n  * https://github.com/webview/webview\n\n* LICENSE: MIT\n\nMIT License\n\nCopyright (c) 2017 Serge Zaitsev\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n---\n\n## lib/pq\n\nThis product contains 'pq' by GitHub user \"lib\".\n\nPure Go Postgres driver for database/sql\n\n* HOMEPAGE:\n  * https://github.com/lib/pq\n\n* LICENSE: MIT\n\nCopyright (c) 2011-2013, 'pq' Contributors\nPortions Copyright (C) 2011 Blake Mizerany\n\nPermission 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\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE 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\nThis product contains 'viper' by GitHub user \"spf13\".\n\nGo configuration with fangs\n\n* HOMEPAGE:\n  * https://github.com/spf13/viper\n\n* LICENSE: MIT\n\nCopyright (c) 2014 Steve Francia\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n---\n\nThis product contains 'uuid' by Google Inc.\n\nGo package for UUIDs based on RFC 4122 and DCE 1.1: Authentication and Security Services.\n\n* HOMEPAGE:\n  * https://github.com/google/uuid\n\n* LICENSE: BSD-3-Clause\n\nCopyright (c) 2009,2014 Google Inc. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n   * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n   * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n   * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n---\n\n## pkg/errors\n\nThis product contains 'errors' by GitHub user \"pkg\".\n\nSimple error handling primitives\n\n* HOMEPAGE:\n  * https://github.com/pkg/errors\n\n* LICENSE: BSD-2-Clause\n\nCopyright (c) 2015, Dave Cheney <dave@cheney.net>\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, 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\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n---\n\n## rudderlabs/analytics-go\n\nThis product contains 'analytics-go' by Segment, Inc.\n\nA toolkit with common assertions and mocks that plays nicely with the standard library\n\n* HOMEPAGE:\n  * https://github.com/rudderlabs/analytics-go\n\n* LICENSE: MIT\n\nThe MIT License (MIT)\n\nCopyright (c) 2016 Segment, Inc.\n\nPermission 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\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE 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\n## gonutz/w32\n\nThis product contains 'testify' by Stretchr, Inc..\n\nA wrapper of Windows APIs for the Go Programming Language.\n\n* HOMEPAGE:\n  * https://github.com/gonutz/w32\n\n* LICENSE: BSD-style\n\nCopyright (c) 2010-2012 The w32 Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions\nare met:\n1. Redistributions of source code must retain the above copyright\n   notice, this list of conditions and the following disclaimer.\n2. Redistributions in binary form must reproduce the above copyright\n   notice, this list of conditions and the following disclaimer in the\n   documentation and/or other materials provided with the distribution.\n3. The names of the authors may not be used to endorse or promote products\n   derived from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR\nIMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\nOF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\nIN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,\nINCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\nNOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF\nTHIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n---\n\n## stretchr/testify\n\nThis product contains 'testify' by Stretchr, Inc..\n\nA toolkit with common assertions and mocks that plays nicely with the standard library\n\n* HOMEPAGE:\n  * https://github.com/stretchr/testify\n\n* LICENSE: MIT\n\nMIT License\n\nCopyright (c) 2012-2020 Mat Ryer, Tyler Bunnell and contributors.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n---\n\n## mattn/go-sqlite3\n\nThis product contains 'go-sqlite3' by GitHub user \"mattn\".\n\nsqlite3 driver for go using database/sql\n\n* HOMEPAGE:\n  * https://github.com/mattn/go-sqlite3\n\n* LICENSE: MIT\n\nThe MIT License (MIT)\n\nCopyright (c) 2014 Yasuhiro Matsumoto\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n---\n\n## zap\n\nThis product contains 'zap' by Uber Technologies, Inc.\n\nBlazing fast, structured, leveled logging in Go.\n\n* HOMEPAGE:\n  * https://github.com/uber-go/zap\n\n* LICENSE: MIT\n\nCopyright (c) 2016-2017 Uber Technologies, Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n---\n\n## nicksnyder/go-i18n\n\nThis product contains 'go-i18n' by GitHub user 'nicksnyder'.\n\nTranslate your Go program into multiple languages.\n\n* HOMEPAGE:\n  * https://github.com/nicksnyder/go-i18n\n\n* LICENSE: MIT\n\nCopyright (c) 2014 Nick Snyder https://github.com/nicksnyder\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n---\n\n## niemeyer/pretty\n\nThis product contains 'pretty' by GitHub user \"niemeyer\"\n\nPretty printing for Go values\n\n* HOMEPAGE:\n  * https://github.com/niemeyer/pretty\n\n* LICENSE: MIT\n\nCopyright 2012 Keith Rarick\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n---\n\n## wiggin77/merror\n\nThis product contains 'merror' by GitHub user 'wiggin77'.\n\nMultiple Error aggregator for Go.\n\n* HOMEPAGE:\n  * https://github.com/wiggin77/merror\n\n* LICENSE: MIT\n\nMIT License\n\nCopyright (c) 2018 wiggin77\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "> [!WARNING]\n> This repository is currently not maintained. If you're interested in becoming a maintainer please [let us know here](https://github.com/mattermost-community/focalboard/issues/5038).\n>\n> This repository only contains standalone Focalboard. If you're looking for the Mattermost plugin please see [mattermost/mattermost-plugin-boards](https://github.com/mattermost/mattermost-plugin-boards).\n>\n\n# Focalboard\n\n![CI Status](https://github.com/mattermost/focalboard/actions/workflows/ci.yml/badge.svg)\n![CodeQL](https://github.com/mattermost/focalboard/actions/workflows/codeql-analysis.yml/badge.svg)\n![Dev Release](https://github.com/mattermost/focalboard/actions/workflows/dev-release.yml/badge.svg)\n![Prod Release](https://github.com/mattermost/focalboard/actions/workflows/prod-release.yml/badge.svg)\n\n![Focalboard](website/site/static/img/hero.jpg)\n\nFocalboard is an open source, multilingual, self-hosted project management tool that's an alternative to Trello, Notion, and Asana.\n\nIt helps define, organize, track and manage work across individuals and teams. Focalboard comes in two editions:\n\n* **[Personal Desktop](https://www.focalboard.com/docs/personal-edition/desktop/)**: A standalone, single-user [macOS](https://apps.apple.com/app/apple-store/id1556908618?pt=2114704&ct=website&mt=8), [Windows](https://www.microsoft.com/store/apps/9NLN2T0SX9VF?cid=website), or [Linux](https://www.focalboard.com/download/personal-edition/desktop/#linux-desktop) desktop app for your own todos and personal projects.\n\n* **[Personal Server](https://www.focalboard.com/download/personal-edition/ubuntu/)**: A standalone, multi-user server for development and personal use.\n\n## Try Focalboard\n\n### Personal Desktop (Windows, Mac or Linux Desktop)\n\n* **Windows**: Download from the [Windows App Store](https://www.microsoft.com/store/productId/9NLN2T0SX9VF) or download `focalboard-win.zip` from the [latest release](https://github.com/mattermost/focalboard/releases), unpack, and run `Focalboard.exe`.\n* **Mac**: Download from the [Mac App Store](https://apps.apple.com/us/app/focalboard-insiders/id1556908618?mt=12).\n* **Linux Desktop**: Download `focalboard-linux.tar.gz` from the [latest release](https://github.com/mattermost/focalboard/releases), unpack, and open `focalboard-app`.\n\n### Personal Server\n\n**Ubuntu**: You can download and run the compiled Focalboard **Personal Server** on Ubuntu by following [our latest install guide](https://www.focalboard.com/download/personal-edition/ubuntu/).\n\n### API Docs\n\nBoards API docs can be found over at <https://htmlpreview.github.io/?https://github.com/mattermost/focalboard/blob/main/server/swagger/docs/html/index.html>\n\n### Getting started\n\nOur [developer guide](https://developers.mattermost.com/contribute/focalboard/personal-server-setup-guide) has detailed instructions on how to set up your development environment for the **Personal Server**. You can also join the [~Focalboard community channel](https://community.mattermost.com/core/channels/focalboard) to connect with other developers.\n\nCreate an `.env` file in the focalboard directory that contains:\n\n```\nEXCLUDE_ENTERPRISE=\"1\"\n```\n\nTo build the server:\n\n```\nmake prebuild\nmake\n```\n\nTo run the server:\n\n```\n ./bin/focalboard-server\n```\n\nThen navigate your browser to [`http://localhost:8000`](http://localhost:8000) to access your Focalboard server. The port is configured in `config.json`.\n\nOnce the server is running, you can rebuild just the web app via `make webapp` in a separate terminal window. Reload your browser to see the changes.\n\n### Building and running standalone desktop apps\n\nYou can build standalone apps that package the server to run locally against SQLite:\n\n* **Windows**:\n  * *Requires Windows 10, [Windows 10 SDK](https://developer.microsoft.com/en-us/windows/downloads/sdk-archive/) 10.0.19041.0, and .NET 4.8 developer pack*\n  * Open a `git-bash` prompt.\n  * Run `make prebuild`\n  * The above prebuild step needs to be run only when you make changes to or want to install your npm dependencies, etc.\n  * Once the prebuild is completed, you can keep repeating the below steps to build the app & see the changes.\n  * Run `make win-wpf-app`\n  * Run `cd win-wpf/msix && focalboard.exe`\n* **Mac**:\n  * *Requires macOS 11.3+ and Xcode 13.2.1+*\n  * Run `make prebuild`\n  * The above prebuild step needs to be run only when you make changes to or want to install your npm dependencies, etc.\n  * Once the prebuild is completed, you can keep repeating the below steps to build the app & see the changes.\n  * Run `make mac-app`\n  * Run `open mac/dist/Focalboard.app`\n* **Linux**:\n  * *Tested on Ubuntu 18.04*\n  * Install `webgtk` dependencies\n    * Run `sudo apt-get install libgtk-3-dev`\n    * Run `sudo apt-get install libwebkit2gtk-4.0-dev`\n  * Run `make prebuild`\n  * The above prebuild step needs to be run only when you make changes to or want to install your npm dependencies, etc.\n  * Once the prebuild is completed, you can keep repeating the below steps to build the app & see the changes.\n  * Run `make linux-app`\n  * Uncompress `linux/dist/focalboard-linux.tar.gz` to a directory of your choice\n  * Run `focalboard-app` from the directory you have chosen\n* **Docker**:\n  * To run it locally from offical image:\n    * `docker run -it -p 80:8000 mattermost/focalboard`\n  * To build it for your current architecture:\n    * `docker build -f docker/Dockerfile .`\n  * To build it for a custom architecture (experimental):\n    * `docker build -f docker/Dockerfile --platform linux/arm64 .`\n\nCross-compilation currently isn't fully supported, so please build on the appropriate platform. Refer to the GitHub Actions workflows (`build-mac.yml`, `build-win.yml`, `build-ubuntu.yml`) for the detailed list of steps on each platform.\n\n### Unit testing\n\nBefore checking in commits, run `make ci`, which is similar to the `.gitlab-ci.yml` workflow and includes:\n\n* **Server unit tests**: `make server-test`\n* **Web app ESLint**: `cd webapp; npm run check`\n* **Web app unit tests**: `cd webapp; npm run test`\n* **Web app UI tests**: `cd webapp; npm run cypress:ci`\n\n### Staying informed\n\n* **Changes**: See the [CHANGELOG](CHANGELOG.md) for the latest updates\n* **Bug Reports**: [File a bug report](https://github.com/mattermost/focalboard/issues/new?assignees=&labels=bug&template=bug_report.md&title=)\n* **Chat**: Join the [~Focalboard community channel](https://community.mattermost.com/core/channels/focalboard)\n"
  },
  {
    "path": "SECURITY.md",
    "content": "Security\n========\n\nSafety and data security is of the utmost priority for the Mattermost community. If you are a security researcher and have discovered a security vulnerability in our codebase, we would appreciate your help in disclosing it to us in a responsible manner.\n\nReporting security issues\n-------------------------\n\n**Please do not use GitHub issues for security-sensitive communication.**\n\nSecurity issues in the community test server, any of the open source codebases maintained by Mattermost, or any of our commercial offerings should be reported via email to [responsibledisclosure@mattermost.com](mailto:responsibledisclosure@mattermost.com). Mattermost is committed to working together with researchers and keeping them updated throughout the patching process. Researchers who responsibly report valid security issues will be publicly credited for their efforts (if they so choose).\n\nFor a more detailed description of the disclosure process and a list of researchers who have previously contributed to the disclosure program, see [Report a Security Vulnerability](https://mattermost.com/security-vulnerability-report/) on the Mattermost website.\n\nSecurity updates\n----------------\n\nMattermost has a mandatory upgrade policy, and updates are only provided for the latest release. Critical updates are delivered as dot releases. Details on security updates are announced 30 days after the availability of the update.\n\nFor more details about the security content of past releases, see the [Security Updates](https://mattermost.com/security-updates/) page on the Mattermost website. For timely notifications about new security updates, subscribe to the [Security Bulletins Mailing List](https://about.mattermost.com/security-bulletin).\n\nContributing to this policy\n---------------------------\n\nIf you have feedback or suggestions on improving this policy document, please [create an issue](https://github.com/mattermost/focalboard/issues/new/choose).\n"
  },
  {
    "path": "app-config.json",
    "content": "{\n\t\"serverRoot\": \"http://localhost:8088\",\n\t\"port\": 8088,\n\t\"dbtype\": \"sqlite3\",\n\t\"dbconfig\": \"./focalboard.db\",\n\t\"useSSL\": false,\n\t\"webpath\": \"./pack\",\n\t\"filespath\": \"./files\",\n\t\"telemetry\": true,\n\t\"localOnly\": true\n}\n"
  },
  {
    "path": "config.json",
    "content": "{\n\t\"serverRoot\": \"http://localhost:8000\",\n\t\"port\": 8000,\n\t\"dbtype\": \"sqlite3\",\n\t\"dbconfig\": \"./focalboard.db?_busy_timeout=5000\",\n\t\"dbpingattempts\": 5,\n\t\"dbtableprefix\": \"\",\n\t\"postgres_dbconfig\": \"dbname=focalboard sslmode=disable\",\n\t\"useSSL\": false,\n\t\"webpath\": \"./webapp/pack\",\n\t\"filesdriver\": \"local\",\n\t\"filespath\": \"./files\",\n\t\"telemetry\": true,\n\t\"prometheusaddress\": \":9092\",\n\t\"webhook_update\": [],\n\t\"session_expire_time\": 2592000,\n\t\"session_refresh_time\": 18000,\n\t\"localOnly\": false,\n\t\"enableLocalMode\": true,\n\t\"localModeSocketLocation\": \"/var/tmp/focalboard_local.socket\",\n\t\"authMode\": \"native\",\n\t\"logging_cfg_file\": \"\",\n\t\"audit_cfg_file\": \"\",\n\t\"enablePublicSharedBoards\": false\n}\n"
  },
  {
    "path": "docker/Dockerfile",
    "content": "### Webapp build\nFROM node:16.3.0@sha256:ca6daf1543242acb0ca59ff425509eab7defb9452f6ae07c156893db06c7a9a4 as nodebuild\n\nWORKDIR /webapp\nADD webapp/ /webapp\n\n### 'CPPFLAGS=\"-DPNG_ARM_NEON_OPT=0\"' Needed To Avoid Bug Described in: https://github.com/imagemin/optipng-bin/issues/118#issuecomment-1019838562\n### Can be Removed when Ticket will be Closed\nRUN CPPFLAGS=\"-DPNG_ARM_NEON_OPT=0\" npm install --no-optional && \\\n    npm run pack\n\n### Go build\nFROM golang:1.18.3@sha256:b203dc573d81da7b3176264bfa447bd7c10c9347689be40540381838d75eebef AS gobuild\n\nWORKDIR /go/src/focalboard\nADD . /go/src/focalboard\n\n# Get target architecture \nARG TARGETOS\nARG TARGETARCH  \n\nRUN EXCLUDE_PLUGIN=true EXCLUDE_SERVER=true EXCLUDE_ENTERPRISE=true make server-docker os=${TARGETOS} arch=${TARGETARCH}\n\n## Final image\nFROM debian:buster-slim@sha256:5b0b1a9a54651bbe9d4d3ee96bbda2b2a1da3d2fa198ddebbced46dfdca7f216\n\nRUN mkdir -p /opt/focalboard/data/files\nRUN chown -R nobody:nogroup /opt/focalboard\n\nWORKDIR /opt/focalboard\n\nCOPY --from=nodebuild --chown=nobody:nogroup /webapp/pack pack/\nCOPY --from=gobuild --chown=nobody:nogroup /go/src/focalboard/bin/docker/focalboard-server bin/\nCOPY --from=gobuild --chown=nobody:nogroup /go/src/focalboard/LICENSE.txt LICENSE.txt\nCOPY --from=gobuild --chown=nobody:nogroup /go/src/focalboard/docker/server_config.json config.json\n\nUSER nobody\n\nEXPOSE 8000/tcp\n\nEXPOSE 8000/tcp 9092/tcp\n\nVOLUME /opt/focalboard/data\n\nCMD [\"/opt/focalboard/bin/focalboard-server\"]\n"
  },
  {
    "path": "docker/README.md",
    "content": "# Deploy Focalboard with Docker\n\n## Docker\n\nThe Dockerfile gives a quick and easy way to build the latest Focalboard server and deploy it locally. In the example below,\nthe Focalboard database and files will be persisted in a named volumed called `fbdata`.\n\nFrom the Focalboard project root directory:\n\n```bash\ndocker build -f docker/Dockerfile -t focalboard .\ndocker run -it -v \"fbdata:/opt/focalboard/data\" -p 80:8000 focalboard\n```\n\nOpen a browser to [localhost](http://localhost) to start\n\n## Alternative architectures\n\nFrom the Focalboard project root directory:\n\n```bash\ndocker build -f docker/Dockerfile --platform linux/arm64 -t focalboard .\ndocker run -it -v \"fbdata:/opt/focalboard/data\" -p 80:8000 focalboard\n```\n\n## Docker-Compose\n\nDocker-Compose provides the option to automate the build and run step, or even include some of the steps from the [personal server setup](https://www.focalboard.com/download/personal-edition/ubuntu/).\n\nTo start the server, change directory to `focalboard/docker` and run:\n\n```bash\ndocker-compose up\n```\n\nThis will automatically build the focalboard image and start it with the http port mapping. These examples also create a persistent named volume called `fbdata`.\n\nTo run Focalboard with a nginx proxy and a postgres backend, change directory to `focalboard/docker` and run:\n\n```bash\ndocker-compose -f docker-compose-db-nginx.yml up\n```\n"
  },
  {
    "path": "docker/config.json",
    "content": "{\n    \"serverRoot\": \"http://localhost:8000\",\n    \"port\": 8000,\n    \"dbtype\": \"postgres\",\n    \"dbconfig\": \"postgres://boardsuser:boardsuser-password@focalboard-db/boards?sslmode=disable&connect_timeout=10\",\n    \"postgres_dbconfig\": \"dbname=boards sslmode=disable\",\n    \"useSSL\": false,\n    \"webpath\": \"./pack\",\n    \"filespath\": \"./data/files\",\n    \"telemetry\": true,\n    \"prometheusaddress\": \":9092\",\n    \"session_expire_time\": 2592000,\n    \"session_refresh_time\": 18000,\n    \"localOnly\": false,\n    \"enableLocalMode\": true,\n    \"localModeSocketLocation\": \"/var/tmp/focalboard_local.socket\"\n}\n"
  },
  {
    "path": "docker/docker-compose-db-nginx.yml",
    "content": "version: \"3\"\n\nservices:\n  app:\n    build:\n      context: ../\n      dockerfile: docker/Dockerfile\n    container_name: focalboard\n    depends_on:\n      - focalboard-db\n    expose:\n      - 8000\n    environment:\n      - VIRTUAL_HOST=localhost\n      - VIRTUAL_PORT=8000\n      - VIRTUAL_PROTO=http\n    volumes:\n      - \"./config.json:/opt/focalboard/config.json\"\n      - fbdata:/opt/focalboard/data\n    restart: always\n    networks:\n      - proxy\n      - default\n\n  proxy:\n    image: jwilder/nginx-proxy:latest\n    container_name: focalboard-proxy\n    restart: always\n    ports:\n      - 80:80\n    volumes:\n      - /var/run/docker.sock:/tmp/docker.sock:ro\n    networks:\n      - proxy\n\n  focalboard-db:\n    image: postgres:latest\n    container_name: focalboard-postgres\n    restart: always\n    volumes:\n      - pgdata:/var/lib/postgresql/data\n    environment:\n      POSTGRES_DB: boards\n      POSTGRES_USER: boardsuser\n      POSTGRES_PASSWORD: boardsuser-password\n\nvolumes:\n  fbdata:\n  pgdata:\n\nnetworks:\n  proxy:\n\n"
  },
  {
    "path": "docker/docker-compose.yml",
    "content": "version: \"3\"\nservices:\n  app:\n    build:\n      context: ../\n      dockerfile: docker/Dockerfile\n    container_name: focalboard\n    volumes:\n      - fbdata:/opt/focalboard/data\n    ports:\n      - 80:8000\n    environment:\n      - VIRTUAL_HOST=focalboard.local\n      - VIRTUAL_PORT=8000\n\nvolumes:\n  fbdata:"
  },
  {
    "path": "docker/server_config.json",
    "content": "{\n  \"serverRoot\": \"http://localhost:8000\",\n  \"port\": 8000,\n  \"dbtype\": \"sqlite3\",\n  \"dbconfig\": \"./data/focalboard.db\",\n  \"postgres_dbconfig\": \"dbname=focalboard sslmode=disable\",\n  \"useSSL\": false,\n  \"webpath\": \"./pack\",\n  \"filespath\": \"./data/files\",\n  \"telemetry\": true,\n  \"session_expire_time\": 2592000,\n  \"session_refresh_time\": 18000,\n  \"localOnly\": false,\n  \"enableLocalMode\": true,\n  \"localModeSocketLocation\": \"/var/tmp/focalboard_local.socket\"\n}\n"
  },
  {
    "path": "docker-testing/docker-compose-mariadb.yml",
    "content": "version: '2.4'\nservices:\n  mariadb:\n    image: \"mariadb:10.9.3\"\n    restart: always\n    environment:\n      MARIADB_ROOT_HOST: \"%\"\n      MARIADB_ROOT_PASSWORD: mostest\n      MARIADB_PASSWORD: mostest\n      MARIADB_USER: mmuser\n    healthcheck:\n      test: [\"CMD\", \"mariadb-admin\", \"ping\", \"-h\", \"localhost\", \"-u\", \"mmuser\", \"-pmostest\"]\n      interval: 5s\n      timeout: 10s\n      retries: 3\n    tmpfs: /var/lib/mariadb\n    ports:\n      - 44445:3306\n\n  start_dependencies:\n    image: mattermost/mattermost-wait-for-dep:latest\n    depends_on:\n      - mariadb\n    command: mariadb:3306\n"
  },
  {
    "path": "docker-testing/docker-compose-mysql.yml",
    "content": "version: '2.4'\nservices:\n  mysql:\n    image: \"mysql/mysql-server:8.0.32\"\n    restart: always\n    environment:\n      MYSQL_ROOT_HOST: \"%\"\n      MYSQL_ROOT_PASSWORD: mostest\n      MYSQL_PASSWORD: mostest\n      MYSQL_USER: mmuser\n    healthcheck:\n      test: [\"CMD\", \"mysqladmin\" ,\"ping\", \"-h\", \"localhost\"]\n      interval: 5s\n      timeout: 10s\n      retries: 3\n    tmpfs: /var/lib/mysql\n    ports:\n      - 44446:3306\n\n  start_dependencies:\n    image: mattermost/mattermost-wait-for-dep:latest\n    depends_on:\n      - mysql\n    command: mysql\n\n"
  },
  {
    "path": "docker-testing/docker-compose-postgres.yml",
    "content": "version: '2.4'\nservices:\n  postgres:\n    image: \"postgres:10\"\n    restart: always\n    environment:\n      POSTGRES_USER: mmuser\n      POSTGRES_PASSWORD: mostest\n    healthcheck:\n      test: [ \"CMD\", \"pg_isready\", \"-h\", \"localhost\" ]\n      interval: 5s\n      timeout: 10s\n      retries: 3\n    tmpfs: /var/lib/postgresql/data\n    ports:\n      - 44447:5432\n\n  start_dependencies:\n    image: mattermost/mattermost-wait-for-dep:latest\n    depends_on:\n      - postgres\n    command: postgres:5432\n\n"
  },
  {
    "path": "docs/README.md",
    "content": "# Disclaimer\n\n> [!WARNING]\n> **Effective September 15th, 2023, Mattermost, Inc. staff are no longer reviewing or merging pull requests for either Focalboard or the Mattermost Boards plugin in this repository (`mattermost/focalboard`). We encourage the community to fork this repository for continued development and contributions.**\n>\n> The reason behind these changes is to focus Mattermost developer resources on improving the platform’s performance and core features to ensure Mattermost continues being resilient, stable, and best-in-breed for critical operations.\n>\n> ️💡 [Learn more](https://forum.mattermost.com/t/upcoming-product-changes-to-boards-and-various-plugins/16669)\n"
  },
  {
    "path": "docs/_config.yml",
    "content": "title: Focalboard Developers\ngoogle_analytics: UA-64458817-2\ntheme: jekyll-theme-architect"
  },
  {
    "path": "docs/code-review.md",
    "content": "# Code Review Checklist\n\nCurrently, all changes to the product must be reviewed by a [core committer](core-committers.md).\n\n## If you are a community member seeking a review\n\n1. Submit your pull request (PR).\n    * Follow the [contribution checklist](../contribution-checklist/).\n2. Wait for a reviewer to be assigned.\n    * Product managers are on the lookout for new pull requests and usually handle this for you automatically.\n    * If you have been working alongside a core committer, feel free to message them for help.\n    * When in doubt, ask for help in the [Focalboard](https://community.mattermost.com/core/channels/focalboard) channel on our community server.\n    * If you are still stuck, please message Chen Lim (@chenilim on GitHub).\n3. [Wait for a review](#if-you-are-awaiting-a-review).\n    * Expect some interaction with at least one reviewer within 5 business days (weekdays, Monday through Friday, excluding [statutory holidays](https://docs.mattermost.com/process/working-at-mattermost.html#holidays)).\n    * Keep in mind that core committers are geographically distributed around the world and likely in a different time zone than your own.\n    * If no interaction has occurred after 5 business days, please [at-mention](https://github.blog/2011-03-23-mention-somebody-they-re-notified/) a reviewer with a comment on your pull request.\n4. Make any necessary changes.\n    * If a reviewer requests changes, your pull request will disappear from their queue of reviews.\n    * Once you've addressed the concerns, please at-mention the reviewer with a comment on your pull request.\n5. Wait for your code to be merged.\n    * Larger pull requests may require more time to review.\n    * Once all reviewers have approved your changes, they will handle merging your code.\n\n## If you are awaiting a review\n\n1. Wait patiently for reviews to complete.\n    * Expect some interaction with each of your reviewers within 5 business days.\n    * There is no need to explicitly mention them on the pull request or to explicitly reach out on our community server.\n2. Make any necessary changes.\n    * If a reviewer requests changes, your pull request will disappear from their queue of reviews.\n    * Once you've addressed the concerns, assign them as a reviewer again to put your pull request back in their queue.\n\n## If you are a core committer asked to give a review\n\n1. Respond promptly to requested reviews.\n    * Assume the requested review is urgent and blocking unless explicitly stated otherwise.\n    * Try to interact with the author within 2 business days.\n    * Configure the GitHub plugin to automate notifications.\n    * Review your outstanding requested reviews daily to avoid blocking authors.\n    * Prioritize earlier milestones when reviewing to help with the release process.\n    * Responding quickly doesn't necessarily mean reviewing quickly! Just don't leave the author hanging.\n2. Feel free to clarify expectations with the author.\n    * If the code is experimental, they may need only a cursory glance and thumbs up to proceed with productizing their changes.\n    * If the review is large or complex, additional time may be required to complete your review. Be upfront with the author.\n    * If you are not comfortable reviewing the code, avoid \"rubber stamping\" the review. Be honest with the author and ask them to consider another core committer.\n3. Never rush a review.\n    * Take the time necessary to review the code thoroughly.\n    * Don't be afraid to ask for changes repeatedly until all concerns are addressed.\n    * Feel free to challenge assumptions and timelines. Rushing a change into a patch release may cause more harm than good.\n4. Avoid leaving a review hanging.\n    * Try to accept or reject the review instead of just leaving comments.\n5. Merge the pull request.\n    * Do not merge if there are outstanding changes requested.\n    * Merge the pull request, and delete the branch if not from a fork.\n"
  },
  {
    "path": "docs/contribution-checklist.md",
    "content": "# Contribution Checklist\n\nThanks for your interest in contributing code!\n\nFollow this checklist for submitting a pull request (PR):\n\n1. You've signed the [Contributor License Agreement](http://www.mattermost.org/mattermost-contributor-agreement/), so you can be added to the Mattermost [Approved Contributor List](https://docs.google.com/spreadsheets/d/1NTCeG-iL_VS9bFqtmHSfwETo5f-8MQ7oMDE5IUYJi_Y/pubhtml?gid=0&single=true).\n2. Your ticket is a Help Wanted GitHub issue for the project you're contributing to.\n    - If not, follow the process [here](contributions-without-ticket.md).\n3. Your code is thoroughly tested, including appropriate unit tests, and manual testing.\n4. If applicable, user interface strings are included in the localization file ([en.json](https://github.com/mattermost/focalboard/blob/main/webapp/i18n/en.json))\n    - In the webapp folder, run `npm run i18n-extract` to generate the new/updated strings.\n5. The PR is submitted against the `main` branch from your fork.\n6. The PR title begins with the GitHub Ticket ID (e.g. `[GH-394]`) and the summary template is filled out.\n\nOnce submitted, the automated build process must pass in order for the PR to be accepted. Any errors or failures need to be addressed in order for the PR to be accepted. Next, the PR goes through [code review](code-review.md). To learn about the review process for each project, read the [CONTRIBUTING.md](https://github.com/mattermost/focalboard/blob/main/CONTRIBUTING.md) file of that GitHub repository.\n"
  },
  {
    "path": "docs/contributions-without-ticket.md",
    "content": "# Contributions Without Ticket\n\nContributions for minor corrections and improvements without a corresponding `Help Wanted` ticket are welcome. For example, a pull request for a bug or incremental improvement, with less than 20 lines of code change, is usually accepted if the change to existing behaviour is minor.\n\nAll pull requests submitted without a corresponding ticket will first be reviewed by a core team product manager. Some examples of minor corrections and improvements include:\n\n- [Fix a formatting error in help text](https://github.com/mattermost/mattermost-server/pull/5640)\n- [Fix success typo in Makefile](https://github.com/mattermost/mattermost-server/pull/5809)\n- [Fix broken Cancel button in Edit Webhooks screen](https://github.com/mattermost/mattermost-server/pull/5612)\n- [Fix Android app crashing when saving user notification settings](https://github.com/mattermost/mattermost-mobile/pull/364)\n- [Fix recent mentions search not working](https://github.com/mattermost/mattermost-server/pull/5878)\n\n**Note:** For pull requests greater than 20 lines of code, a `Help Wanted` ticket should be opened by the core team. This helps ensure that everything going into the project aligns with a unified vision. Core committers who review the PR are entitled to reject it if there isn't a `Help Wanted` ticket and feel it significantly changes behavior or user expectations.\n\nThe best way to discuss opening a `Help Wanted` ticket with the core team is by [starting a conversation in Contributors channel](https://community.mattermost.com/core/channels/focalboard) or [opening an issue in the GitHub repository](https://github.com/mattermost/focalboard/issues/new).\n"
  },
  {
    "path": "docs/core-committers.md",
    "content": "# Core Committers\n\nA core committer is a maintainer on the Focalboard project who has merge access to the repositories. They are responsible for reviewing pull requests, cultivating the developer community, and guiding the technical vision of Focalboard. If you have a question or need some help, these are the people to ask.\n\n## Core Committers\n\nBelow is the list of core committers working on Focalboard:\n\n- **<a name=\"scott.bishel\">Scott Bishel</a>**\n    - @scott.bishel on [community.mattermost.com](https://community.mattermost.com/core/messages/@scott.bishel) and [@sbishel](https://github.com/sbishel) on GitHub\n- **<a name=\"jesús.espino\">Jesús Espino</a>**\n    - @jesus.espino on [community.mattermost.com](https://community.mattermost.com/core/messages/@jesus.espino) and [@jespino](https://github.com/jespino) on GitHub\n- **<a name=\"doug.lauder\">Doug Lauder</a>**\n    - @doug.lauder on [community.mattermost.com](https://community.mattermost.com/core/messages/@doug.lauder) and [@wiggin77](https://github.com/wiggin77) on GitHub\n- **<a name=\"miguel.delacruz\">Miguel de la Cruz</a>**\n    - @miguel.delacruz on [community.mattermost.com](https://community.mattermost.com/core/messages/@miguel.delacruz) and [@mgdelacroix](https://github.com/mgdelacroix) on GitHub\n- **<a name=\"harshil.sharma\">Harshil Sharma</a>**\n    - @harshil.sharma on [community.mattermost.com](https://community.mattermost.com/core/messages/@harshil.sharma) and [@harshilsharma63](https://github.com/harshilsharma63) on GitHub\n- **<a name=\"chen.lim\">Chen Lim</a>**\n    - @chen-i.lim on [community.mattermost.com](https://community.mattermost.com/core/messages/@chen-i.lim) and [@chenilim](https://github.com/chenilim) on GitHub\n"
  },
  {
    "path": "docs/dev-tips.md",
    "content": "# Developer Tips and Tricks\n\nThese tips and tricks apply to developing the standalone Personal Server of Focalboard. For most features, this is the easiest way to get started working against code that ships across editions.\n\nFor working with the Focalboard plugin, refer to the [Focalboard Plugin Developer's Guide](focalboard-dev-guide.md).\n\n## Installation prerequisites\n\nCheck that you have recent versions of the basic dependencies installed:\n* [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)\n    * On Windows, install [Git for Windows](https://gitforwindows.org/) and use the git-bash terminal shell\n* [Go](https://golang.org/doc/install)\n* [Node](https://nodejs.org/en/download/) (v10+) and [npm](https://www.npmjs.com/get-npm)\n\nOn Windows:\n* Install [Mingw64](https://chocolatey.org/packages/mingw) via [Chocolatey](https://chocolatey.org/)\n\nOn macOS, to build the Mac app:\n* Install [Xcode](https://apps.apple.com/us/app/xcode/id497799835?mt=12) (v12+)\n* Install the Xcode commandline tools, via the IDE or run `xcode-select --install`\n\nOn Linux, to build the Linux app:\n* `sudo apt-get install libgtk-3-dev`\n* `sudo apt-get install libwebkit2gtk-4.0-dev`\n* `sudo apt-get install autoconf dh-autoreconf`\n\n## Fork and clone the project source code\n\nFork the [Focalboard GitHub repo](https://github.com/mattermost/focalboard), and clone it locally.\n\n## Build and run from the terminal\n\nFollow the steps in the [main readme file](https://github.com/mattermost/focalboard#building-the-server). In summary, to build and run the server:\n\n```\nmake prebuild\nmake\n ./bin/focalboard-server\n```\n\nThen open a browser to `http://localhost:8000` to access it. The port is configured in `config.json`.\n\nOnce the server is running, you can rebuild just the webapp with `make webapp` (in a separate terminal window), then reload the browser.\n\n## VSCode setup\n\nHere's a recommended dev-test loop using VSCode:\n* Open a bash terminal window to the project folder\n* Run `make prebuild` to npm install\n    * Do this again when dependencies change in `webapp/package.json`\n* Run `cd webapp && npm run watchdev`\n    * This will auto-build the webapp on file changes\n* Open VSCode\n    * Install the [Go](https://marketplace.visualstudio.com/items?itemName=golang.Go) and [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) VSCode extensions if you haven't already\n* Hit F5 and select `Go: Launch Server`\n    * Or, press `Cmd+P` and type `debug <space>` and pick the option\n* Open a browser to `http://localhost:8000`\n    * The port is configured in `config.json`\n\nYou can now edit the webapp code and refresh the browser to see your changes.\n\n## Debugging the web app\n\nYou can use your favorite browser to debug the webapp code. With Chrome, open the dev tools with `Cmd+Alt+I` (`Ctrl+Alt+I` in Windows).\n* `npm run watchdev` builds the dev package, which includes source maps from js to typescript\n* In the Chrome devtools source tab, press `Cmd+P` to jump to a source file\n\nAs a starting point, add a breakpoint to the `render()` function in `BoardPage.tsx`, then refresh the browser to walk through page rendering.\n\n## Debugging the server\n\nDebug the Go code in VSCode. This is set up automatically when you launch the server from there.\n\nTo start, add a breakpoint to `handleGetBlocks()` in `server/api/api.go`, then refresh the browser to see how data is retrieved.\n\n## Localization/Internationalization/Translation\n\nWe use `i18n` to localize the web app. Localized string generally use `intl.formatMessage`. When adding or modifying localized strings, run `npm run i18n-extract` in `webapp` to rebuild `webapp/i18n/en.json`.\n\nTranslated strings are stored in other json files under `webapp/i18n`, e.g. `es.json` for Spanish.\n\n## Database\n\nBy default, data is stored in a sqlite database `focalboard.db`. You can view and edit this directly using `sqlite3 focalboard.db` from bash.\n\n## Unit tests\n\nBefore checking-in commits, run: `make ci`, which is simlar to the ci.yml workflow and includes:\n* Server unit tests: `make server-test`\n* Webapp eslint: `cd webapp; npm run check`\n* Webapp unit tests: `make webapp-test`\n* Webapp UI tests: `cd webapp; npm run cypress:ci`\n\n## Running into problems or have questions?\n\nIf you run into any issues with the steps here, or have any general questions, please don't hesitate to reach out either on [GitHub](https://github.com/mattermost/focalboard) or our [Mattermost community channel](https://community.mattermost.com/core/channels/focalboard).\n\nWe welcome everyone, and appreciate any feedback.\n\nglhf! :)\n"
  },
  {
    "path": "docs/focalboard-dev-guide.md",
    "content": "# Focalboard Plugin Developer's Guide\n\n**Important**: Effective September 15th, 2023, Mattermost Boards transitions to being fully community supported as the Focalboard Plugin. Mattermost will no longer be maintaining this plugin - this includes bug fixes and feature additions. Instead, the plugin is open-sourced and made available indefinitely for community contributions in GitHub.\n\nTo build your own version of it:\n1. Build [mattermost-plugin](https://github.com/mattermost/focalboard/tree/main/mattermost-plugin) in the [Focalboard repo](https://github.com/mattermost/focalboard)\n2. Upload it as a [custom plugin to your Mattermost server](https://developers.mattermost.com/integrate/admin-guide/admin-plugins-beta/#custom-plugins)\n\nHere are the steps in more detail:\n\n### Building the Focalboard plugin\n\nFork the [Focalboard repo](https://github.com/mattermost/focalboard), clone it locally, and follow the steps in the readme to set up your dev environment.\n\nInstall dependencies:\n```\n# First-time setup dependencies\ncd mattermost-plugin/webapp\nnpm install --no-optional\ncd ../..\nmake prebuild\n```\n\nBuild the plugin:\n```\n# Build webapp\nmake webapp\n\n# Build plugin\ncd mattermost-plugin\nmake dist\n```\n\nRefer to the [dev-release.yml](https://github.com/mattermost/focalboard/blob/main/.github/workflows/dev-release.yml#L168) workflow for the up-to-date commands that are run as part of CI.\n\n### Uploading the plugin to your server\n\nYou can manually upload the plugin to your Mattermost Server:\n1. Enable [custom plugins](https://developers.mattermost.com/integrate/admin-guide/admin-plugins-beta/#custom-plugins) by setting `PluginSettings > EnableUploads` to `true` in the Mattermost `config.json` file\n2. Navigate to **System Console > Plugins > Management** and upload the generated `.tar.gz` package in your `mattermost-plugin/dist` directory\n3. Enable it (if needed)\n\nAlternatively, you can install Mattermost locally and use `make deploy` to auto-deploy it for you:\n\nFirst, build and run Mattermost locally:\n1. Follow the [Mattermost Developers Guide](https://developers.mattermost.com/contribute/server/developer-setup/) to set up your environment\n  * In particuler, make sure Docker is set up and running\n2. Fork [mattermost-webapp](https://github.com/mattermost/mattermost-webapp), clone it locally, and `make build`\n3. Fork [mattermost-server](https://github.com/mattermost/mattermost-server) and clone it locally\n3. Run `make config-reset` to generate the `config/config.json` file\n4. Edit `config/config.json`:\n  * Set `ServiceSettings > SiteURL` to `http://localhost:8065` ([docs](https://docs.mattermost.com/configure/configuration-settings.html#site-url))\n  * Set `ServiceSettings > EnableLocalMode` to `true` ([docs](https://docs.mattermost.com/configure/configuration-settings.html#enable-local-mode))\n  * Set `PluginSettings > EnableUploads` to `true` ([docs](https://developers.mattermost.com/integrate/admin-guide/admin-plugins-beta/#custom-plugins))\n5. Add an ENV var `MM_SERVICESETTINGS_SITEURL` with the same site URL used in the config\n6. Run `make run-server` in Mattermost\n\nNow, to build and deploy the plugin:\n1. Clone / fork [mattermost/focalboard](https://github.com/mattermost/focalboard)\n2. Install the dependencies (see above)\n3. Run:\n```\nmake webapp\ncd mattermost-plugin\nmake deploy\n```\n"
  },
  {
    "path": "docs/index.md",
    "content": "# Focalboard Plugin Documentation\n\nWelcome to the Focalboard plugin project! We're very glad you want to check it out and perhaps contribute code to this project in GitHub.\n\n## Install the plugin\n\nVisit the [Mattermost Developer Documentation](https://developers.mattermost.com/integrate/plugins/using-and-managing-plugins/#custom-plugins) for details on how to install and enable the Focalboard plugin in your self-hosted Mattermost instance.\n\n## Enable the plugin\n\nOnce you've installed the Focalboard plugin, you can enable the plugin in the Mattermost System Console by going to **Plugins > Plugin Management**, and selecting the **Enable** option for the Focalboard plugin.\n\n## Contribute to the Focalboard plugin project\n\nFollow these simple steps to contribute:\n\n1. [Fork the Focalboard repo](https://github.com/mattermost/focalboard), clone it locally, and follow the steps in the README to build. Read the [Focalboard Developer's Guide](focalboard-dev-guide.md) and the [developer tips & tricks](dev-tips.md) documentation to get started.\n\n2. Find [help wanted tickets that are up for grabs in GitHub](https://github.com/mattermost/focalboard/issues?q=is%3Aopen+is%3Aissue+label%3A%22Up+for+grabs%22). Comment to let everyone know you’re working on it, and to allow a core contributor to assign the issue to you. If there’s no ticket for what you want to work on see [contributions without a ticket](contributions-without-ticket.md).\n\n3. When your changes are ready, run through our [checklist for pull requests](contribution-checklist.md). Note that if it’s your first contribution, there is a standard [CLA](https://www.mattermost.org/mattermost-contributor-agreement/) to sign.\n\n## Just ask if you need help!\n\nYou can find us on our [public Focalboard channel](https://community.mattermost.com/core/channels/focalboard) on the Mattermost community server. Also feel free to [file a bug](https://github.com/mattermost/focalboard/issues/new/choose) for any issues you run into, or [start a discussion](https://github.com/mattermost/focalboard/discussions).\n\nWe're glad ❤️ you're here! Good luck and have fun!\n"
  },
  {
    "path": "experiments/webext/.gitignore",
    "content": ".parcel-cache\nweb-ext-artifacts\n"
  },
  {
    "path": "experiments/webext/.parcelrc",
    "content": "{\n  \"extends\": \"@parcel/config-webextension\"\n}\n"
  },
  {
    "path": "experiments/webext/README.md",
    "content": "# Focalboard Web Clipper Browser Extension ✂️\n\nThis is the Focalboard Web Clipper browser extension. It aims at supporting various use cases around converting web content from your browser directly into Focalboard cards.\n\n⚠️ **Warning:** The extension is currently in an early and experimental state. Use it at your own risk only. Don't expect any eye candy.\n\n## Status\n\nThe extension currently is in a proof-of-concept state with minimal functionality. The only supported use case at the time is building a read-later list. Things that work:\n\n- Logging in to the Focalboard server from the extension settings\n- Selecting a board to capture cards into from the extension settings\n- Saving websites (title & URL) into cards from a page action (like e.g. Pocket does it)\n\nOnly Firefox was tested so far but polyfills have already been enabled so there's a good chance that it'll work in Chrome and maybe even Safari, too.\n\n### Next Steps\n\nWe're really at the very beginning here so there's a lot to be done. Notable tasks include:\n\n- Improve the React code by extracting components\n- Style the options and popup pages to mimic the look and feel of Focalboard\n- Replace the logo with something better (the current one was snatched from the Focalboard Windows app)\n- Link to the extension's options page from page action error messages\n- Clip parts of a website into image attachments on cards\n- Extract website content in reader mode into card descriptions\n- Optimise the logic for finding the first URL property (currently the whole board subtree has to be requested because there is no other API available)\n- Add some tests\n- Test the extension on Chrome / Safari and add infrastructure to facilitate this in future (e.g. `.web-ext-config.js`)\n- Add an onboarding (displayed after first install) and upboarding (displayed after update) page\n- Distribute the extension via the various browser add-on stores (ok, maybe too early 😜)\n\n## Hacking\n\nFirst, install dependencies with\n\n```\n$ npm i\n```\n\nYou can then compile and bundle the code with\n\n```\n$ npm run watchdev\n```\n\nThis will write output into `dist/dev/` and automatically recompile and bundle on any source change.\n\nTo run the extension in a separate Firefox instance, use\n\n```\n$ npm run servedev\n```\n\nNote that in the above commands you can substitue `dev` with `prod` to build and run the extension with production settings.\n\n## Distribution\n\nTo build a distributable ZIP archive, run\n\n```\n$ npm run build\n```\n\nThe archive will be placed into the `web-ext-artifacts` folder.\n"
  },
  {
    "path": "experiments/webext/manifest.json",
    "content": "{\n  \"manifest_version\": 2,\n  \"name\": \"Focalboard Web Clipper\",\n  \"version\": \"0.1.0\",\n  \"description\": \"Save websites directly into Focalboard\",\n  \"icons\": {\n    \"48\": \"icons/48.png\",\n    \"96\": \"icons/96.png\"\n  },\n  \"page_action\": {\n    \"browser_style\": true,\n    \"default_icon\": {\n      \"19\": \"icons/19.png\",\n      \"38\": \"icons/38.png\"\n    },\n    \"default_title\": \"Save to Focalboard\",\n    \"default_popup\": \"src/views/popup.html\",\n    \"show_matches\": [\"<all_urls>\"]\n  },\n  \"options_ui\": {\n    \"page\": \"src/views/options.html\",\n    \"browser_style\": true\n  },\n  \"web_accessible_resources\": [],\n  \"permissions\": [\n    \"<all_urls>\",\n    \"storage\"\n  ],\n  \"browser_specific_settings\": {\n    \"gecko\": {\n      \"id\": \"focalboard-web-clipper@mattermost.com\"\n    }\n  }\n}\n"
  },
  {
    "path": "experiments/webext/package.json",
    "content": "{\n  \"name\": \"focalboard-web-clipper\",\n  \"version\": \"0.0.0\",\n  \"targets\": {\n    \"dev\": {\n      \"sourceMap\": {\n        \"inline\": true,\n        \"inlineSources\": true\n      }\n    },\n    \"prod\": {}\n  },\n  \"scripts\": {\n    \"watchdev\": \"parcel watch manifest.json --target dev\",\n    \"servedev\": \"web-ext run -s dist/dev/\",\n    \"watchprod\": \"parcel watch manifest.json --target prod\",\n    \"serveprod\": \"web-ext run -s dist/prod/\",\n    \"build\": \"parcel build manifest.json --target prod && web-ext build -s dist/prod/\"\n  },\n  \"devDependencies\": {\n    \"@parcel/config-webextension\": \"^2.0.0\",\n    \"@parcel/transformer-sass\": \"^2.0.0\",\n    \"@types/react\": \"^17.0.32\",\n    \"@types/react-dom\": \"^17.0.10\",\n    \"parcel\": \"^2.0.0\",\n    \"react\": \"^17.0.2\",\n    \"react-dom\": \"^17.0.2\",\n    \"typescript\": \"^4.4.4\",\n    \"web-ext\": \"^6.4.0\",\n    \"webextension-polyfill-ts\": \"^0.26.0\"\n  }\n}\n"
  },
  {
    "path": "experiments/webext/src/utils/Board.ts",
    "content": "interface BoardFields {\n  isTemplate: boolean\n}\n\nexport default interface Board {\n  id: string\n  title: string\n  fields: BoardFields\n}\n"
  },
  {
    "path": "experiments/webext/src/utils/networking.ts",
    "content": "// Copyright (c) 2021-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport Board from \"../utils/Board\"\n\ndeclare global {\n  interface Window {\n      msCrypto: Crypto\n  }\n}\n\nasync function request(method: string, host: string, resource: string, body: any, token: string | null) {\n  const response = await fetch(`${host}/api/v2/${resource}`, {\n    'credentials': 'include',\n    'headers': {\n      'Accept': 'application/json',\n      'Content-Type': 'application/json',\n      'X-Requested-With': 'XMLHttpRequest',\n      'Authorization': token ? `Bearer ${token}` : null\n    } as HeadersInit,\n    'body': body ? JSON.stringify(body) : null,\n    'method': method\n  })\n\n  const json = await response.json()\n\n  if (json.error) {\n    throw json.error\n  }\n\n  return json\n}\n\nexport async function logIn(host: string, username: string, password: string) {\n  const json = await request('POST', host, 'login', { username: username, password: password, type: 'normal' }, null)\n  return json.token\n}\n\nexport async function getBoards(host: string, token: string) {\n  const json = await request('GET', host, 'workspaces/0/blocks?type=board', null, token) as Board[]\n  return json.filter(board => !board.isTemplate)\n}\n\nexport async function findUrlPropertyId(host: string, token: string, boardId: string) {\n  const json = await request('GET', host, `workspaces/0/blocks/${boardId}/subtree`, null, token)\n  for (let obj of json) {\n    if (obj.type === 'board') {\n      for (let property of obj.fields.cardProperties) {\n        if (property.type === 'url') {\n          return property.id\n        }\n      }\n      break // Only one board in subtree, no need to continue\n    }\n  }\n  return null\n}\n\nexport async function createCard(host: string, token: string, boardId: string, urlPropertyId: string, title: string, url: string) {\n  let properties = {} as any\n  if (urlPropertyId) {\n    properties[urlPropertyId] = url\n  }\n  const card = {\n    id: createGuid(),\n    schema: 1,\n    workspaceId: '',\n    parentId: boardId,\n    rootId: boardId,\n    createdBy: '',\n    modifiedBy: '',\n    type: 'card',\n    fields: {\n      icon: null,\n      properties: properties,\n      contentOrder: [],\n      isTemplate: false\n    },\n    title: title,\n    createAt: Date.now(),\n    updateAt: Date.now(),\n    deleteAt: 0\n  }\n  await request('POST', host, 'workspaces/0/blocks', [card], token)\n}\n\nfunction createGuid(): string {\n  const data = randomArray(16)\n  return '7' + base32encode(data, false)\n}\n\nfunction randomArray(size: number): Uint8Array {\n  const crypto = window.crypto || window.msCrypto\n  const rands = new Uint8Array(size)\n  if (crypto && crypto.getRandomValues) {\n      crypto.getRandomValues(rands)\n  } else {\n      for (let i = 0; i < size; i++) {\n          rands[i] = Math.floor((Math.random() * 255))\n      }\n  }\n  return rands\n}\n\nconst base32Alphabet = 'ybndrfg8ejkmcpqxot1uwisza345h769'\n\nfunction base32encode(data: Int8Array | Uint8Array | Uint8ClampedArray, pad: boolean): string {\n  const dview = new DataView(data.buffer, data.byteOffset, data.byteLength)\n  let bits = 0\n  let value = 0\n  let output = ''\n\n  // adapted from https://github.com/LinusU/base32-encode\n  for (let i = 0; i < dview.byteLength; i++) {\n      value = (value << 8) | dview.getUint8(i)\n      bits += 8\n\n      while (bits >= 5) {\n          output += base32Alphabet[(value >>> (bits - 5)) & 31]\n          bits -= 5\n      }\n  }\n  if (bits > 0) {\n      output += base32Alphabet[(value << (5 - bits)) & 31]\n  }\n  if (pad) {\n      while ((output.length % 8) !== 0) {\n          output += '='\n      }\n  }\n  return output\n}\n"
  },
  {
    "path": "experiments/webext/src/utils/settings.ts",
    "content": "// Copyright (c) 2021-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport browser from 'webextension-polyfill'\n\ninterface Settings {\n  host: string | null\n  username: string | null\n  token: string | null\n  boardId: string | null\n}\n \nexport function loadSettings(): Settings {\n  return browser.storage.sync.get(['host', 'username', 'token', 'boardId'])\n}\n\nexport function storeSettings(host: string, username: string, token: string | null, boardId: string | null) {\n  console.log(`storing host ${host}`)\n  return browser.storage.sync.set({ host: host, username: username, token: token, boardId: boardId })\n}\n\nexport function storeToken(value: string | null) {\n  return browser.storage.sync.set({ token: value })\n}\n\nexport function storeBoardId(value: string | null) {\n  return browser.storage.sync.set({ boardId: value })\n}\n  "
  },
  {
    "path": "experiments/webext/src/views/OptionsApp.scss",
    "content": "/* Copyright (c) 2021-present Mattermost, Inc. All Rights Reserved. */\n/* See LICENSE.txt for license information. */\n\n.OptionsApp {\n  label {\n    display: block;\n    font-size: 90%;\n    font-style: italic;\n  }\n  \n  input[type=text], input[type=password], select {\n    width: 20em;\n    max-width: 100%;\n    margin-bottom: 1em;\n  }\n  \n  input[type=submit] {\n    display: block;\n  }\n  \n  .status {\n    margin: 1em 0 1em 0;\n\n    .in-progress {\n      background-color: grey;\n      padding: 0.5em;\n    }\n\n    .success {\n      background-color: lightgreen;\n      padding: 0.5em;\n    }\n    \n    .error {\n      background-color: lightpink;\n      padding: 0.5em;\n    }\n  }\n  \n}\n"
  },
  {
    "path": "experiments/webext/src/views/OptionsApp.tsx",
    "content": "// Copyright (c) 2021-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React, { ChangeEvent, MouseEvent, useEffect, useState } from \"react\"\n\nimport Board from \"../utils/Board\"\nimport { getBoards, logIn } from \"../utils/networking\";\nimport { loadSettings, storeSettings, storeToken, storeBoardId } from \"../utils/settings\";\n\nimport \"./OptionsApp.scss\"\n\nexport default function OptionsApp() {\n  const [host, setHost] = useState('')\n  const [username, setUsername] = useState('')\n  const [password, setPassword] = useState('')\n  const [token, setToken] = useState('')\n  const [boards, setBoards] = useState([] as Board[])\n  const [boardId, setBoardId] = useState(null as string | null)\n  const [inProgress, setInProgress] = useState(false)\n  const [error, setError] = useState(null as string | null)\n\n  useEffect(() => {\n    async function initialiseBoards() {\n      const settings = await loadSettings()\n      if (settings.host) {\n        setHost(settings.host)\n      }\n      if (settings.username) {\n        setUsername(settings.username)\n      }\n      if (settings.token) {\n        setToken(settings.token)\n      }\n      if (settings.boardId) {\n        setBoardId(settings.boardId)\n      }\n      if (!settings.host || !settings.username || !settings.token) {\n        setError('Unauthenticated')\n        return\n      }\n      setInProgress(true)\n      try {\n        setBoards(await getBoards(settings.host, settings.token))\n      } catch (error) {\n        setError(`${error}`)\n      } finally {\n        setInProgress(false)\n      }\n    }\n    initialiseBoards();\n  }, [])\n\n  function onAuthenticateButtonClicked(event: MouseEvent) {\n    authenticate(host, username, password) \n    event.preventDefault()\n    event.stopPropagation()\n  }\n  \n  async function authenticate(host: string, username: string, password: string) {\n    storeSettings(host, username, null, null)\n    setBoards([])\n    setBoardId(null)\n    setInProgress(true)\n    setError(null)\n    try {\n      const token = await logIn(host, username, password)\n      storeToken(token)\n      setToken(token)\n      setBoards(await getBoards(host, token))\n      const select = document.querySelector('select') as any\n      select.value = null\n    } catch (error) {\n      setError(`${error}`)\n    } finally {\n      setInProgress(false)\n    }\n  }\n\n  function onBoardSelectionChanged(event: ChangeEvent) {\n    const id = (event.target as HTMLSelectElement).value\n    storeBoardId(id)\n    setBoardId(id)\n    event.preventDefault()\n    event.stopPropagation()\n  }\n\n  return <div className=\"OptionsApp\">\n    <label>Focalboard host</label>\n    <input type=\"text\" value={host} onChange={e => setHost(e.target.value)}/>\n    <label>Username</label>\n    <input type=\"text\" value={username} onChange={e => setUsername(e.target.value)}/>\n    <label>Password</label>\n    <input type=\"password\" value={password} onChange={e => setPassword(e.target.value)}/>\n    <input type=\"submit\" value=\"Authenticate\" onClick={onAuthenticateButtonClicked}/>\n    <div className=\"status\">\n      {inProgress && <div className=\"in-progress\">\n        Connecting to Focalboard server...\n      </div>}\n      {!inProgress && !error && <div className=\"success\">\n        Token: <span>{token}</span>\n      </div>}\n      {!inProgress && error && <div className=\"error\">{error}</div>}\n    </div>\n    <br/>\n    <br/>\n    <label>Board</label>\n    <select onChange={onBoardSelectionChanged}>\n      {boards.map(board => <option value={board.id} selected={board.id === boardId}>{board.title}</option>)}\n    </select>\n  </div>\n}\n"
  },
  {
    "path": "experiments/webext/src/views/PopupApp.scss",
    "content": "/* Copyright (c) 2021-present Mattermost, Inc. All Rights Reserved. */\n/* See LICENSE.txt for license information. */\n\n.PopupApp {\n  .status {\n    .in-progress {\n      background-color: grey;\n      padding: 1em;\n    }\n    \n    .success {\n      background-color: lightgreen;\n      padding: 1em;\n    }\n    \n    .error {\n      background-color: lightpink;\n      padding: 1em;\n    }\n  }\n}\n"
  },
  {
    "path": "experiments/webext/src/views/PopupApp.tsx",
    "content": "// Copyright (c) 2021-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React, { useEffect, useState } from \"react\"\n\nimport browser from 'webextension-polyfill'\n\nimport { createCard, findUrlPropertyId } from \"../utils/networking\";\nimport { loadSettings } from \"../utils/settings\";\n\nimport \"./PopupApp.scss\"\n\nexport default function OptionsApp() {\n  const [board, setBoard] = useState('')\n  const [inProgress, setInProgress] = useState(false)\n  const [error, setError] = useState(null as string | null)\n\n  useEffect(() => {\n    async function createCardFromCurrentTab() {\n      const settings = await loadSettings()\n      if (!settings.host || !settings.token) {\n        setError('Looks like you\\'re unauthenticated. Please configure the extension\\'s settings first.')\n        return\n      }\n      if (!settings.boardId) {\n        setError('Looks like you haven\\'t selected a board to save to yet. Please configure the extension\\'s settings first.')\n        return\n      }\n      setInProgress(true)\n      try {\n        const tabs = await browser.tabs.query({ active: true, currentWindow: true })\n        const urlPropertyId = await findUrlPropertyId(settings.host as string, settings.token as string, settings.boardId as string)\n        await createCard(settings.host as string, settings.token as string, settings.boardId as string, urlPropertyId, tabs[0].title, tabs[0].url)    \n        setBoard(`${settings.host}/${settings.boardId}`)    \n      } catch (error) {\n        setError(`${error}`)\n      } finally {\n        setInProgress(false)\n      }\n    }\n    createCardFromCurrentTab();\n  }, [])\n  \n  return <div className=\"PopupApp\">\n    <div className=\"status\">\n      {inProgress && <div className=\"in-progress\">\n        Saving to Focalboard...\n      </div>}\n      {!inProgress && !error && <div className=\"success\">\n        Saved to <a href={board} target=\"_blank\">board</a>\n      </div>}\n      {!inProgress && error && <div className=\"error\">{error}</div>}\n    </div>\n  </div>\n}\n"
  },
  {
    "path": "experiments/webext/src/views/options.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\"/>\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"options.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "experiments/webext/src/views/options.tsx",
    "content": "// Copyright (c) 2021-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from \"react\"\nimport ReactDOM from \"react-dom\"\n\nimport OptionsApp from \"./OptionsApp\"\n\nconst app = document.getElementById(\"app\")\nReactDOM.render(<OptionsApp/>, app)\n"
  },
  {
    "path": "experiments/webext/src/views/popup.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\"/>\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"popup.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "experiments/webext/src/views/popup.tsx",
    "content": "// Copyright (c) 2021-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from \"react\"\nimport ReactDOM from \"react-dom\"\n\nimport PopupApp from \"./PopupApp\"\n\nconst app = document.getElementById(\"app\")\nReactDOM.render(<PopupApp/>, app)\n"
  },
  {
    "path": "experiments/webext/tsconfig.json",
    "content": "{\n\t\"compilerOptions\": {\n\t\t\"jsx\": \"react\",\n\t\t\"target\": \"es2019\",\n\t\t\"module\": \"commonjs\",\n\t\t\"esModuleInterop\": true,\n\t\t\"noImplicitAny\": true,\n\t\t\"strict\": true,\n\t\t\"strictNullChecks\": true,\n\t\t\"forceConsistentCasingInFileNames\": true,\n\t\t\"sourceMap\": true,\n\t\t\"allowJs\": true,\n\t\t\"resolveJsonModule\": true,\n\t\t\"incremental\": false,\n\t\t\"outDir\": \"./dist\",\n\t\t\"moduleResolution\": \"node\"\n\t},\n\t\"include\": [\n\t\t\".\"\n\t],\n\t\"exclude\": [\n        \".git\",\n\t\t\"**/node_modules/*\",\n\t\t\"dist\",\n\t\t\"pack\"\n    ]\n}\n"
  },
  {
    "path": "import/README.md",
    "content": "# Import scripts\n\nThis subfolder contains scripts to import data from other systems. It is at an early stage. At present, there are examples of basic importing from the following:\n* Trello\n* Asana\n* Notion\n* Jira\n* Todoist\n* Nextcloud Deck\n\n[Contribute code](https://mattermost.github.io/focalboard/) to expand this.\n"
  },
  {
    "path": "import/asana/.eslintrc.json",
    "content": "{\n  \"extends\": [\n  ],\n  \"plugins\": [\n  ],\n  \"parser\": \"@typescript-eslint/parser\",\n  \"env\": {\n    \"jest\": true\n  },\n  \"settings\": {\n    \"import/resolver\": \"webpack\",\n    \"react\": {\n        \"pragma\": \"React\",\n        \"version\": \"detect\"\n    }\n  },\n  \"rules\": {\n    \"no-unused-expressions\": 0,\n    \"eol-last\": [\"error\", \"always\"],\n    \"import/no-unresolved\": 2,\n    \"no-undefined\": 0,\n    \"react/jsx-filename-extension\": 0,\n    \"max-nested-callbacks\": [\"error\", {\"max\": 5}]\n  },\n  \"overrides\": [\n    {\n      \"files\": [\"**/*.tsx\", \"**/*.ts\"],\n      \"extends\": [\n        \"plugin:@typescript-eslint/recommended\"\n      ],\n      \"rules\": {\n        \"import/no-unresolved\": 0, // ts handles this better\n\t\t\"camelcase\": 0,\n\t\t\"semi\": \"off\",\n        \"@typescript-eslint/naming-convention\": [\n          2,\n          {\n            \"selector\": \"function\",\n            \"format\": [\"camelCase\", \"PascalCase\"]\n          },\n          {\n            \"selector\": \"variable\",\n            \"format\": [\"camelCase\", \"PascalCase\", \"UPPER_CASE\"]\n          },\n          {\n            \"selector\": \"parameter\",\n            \"format\": [\"camelCase\", \"PascalCase\"],\n            \"leadingUnderscore\": \"allow\"\n          },\n          {\n            \"selector\": \"typeLike\",\n            \"format\": [\"PascalCase\"]\n          }\n        ],\n        \"@typescript-eslint/no-non-null-assertion\": 0,\n        \"@typescript-eslint/no-unused-vars\": [\n          2,\n          {\n            \"vars\": \"all\",\n            \"args\": \"after-used\"\n          }\n        ],\n        \"@typescript-eslint/no-var-requires\": 0,\n        \"@typescript-eslint/no-empty-function\": 0,\n        \"@typescript-eslint/prefer-interface\": 0,\n        \"@typescript-eslint/explicit-function-return-type\": 0,\n        \"@typescript-eslint/semi\": [2, \"never\"],\n\t\t\"@typescript-eslint/indent\": [\n          2,\n          4,\n          {\n            \"SwitchCase\": 0\n          }\n        ],\n        \"no-use-before-define\": \"off\",\n        \"@typescript-eslint/no-use-before-define\": [\n          2,\n          {\n            \"classes\": false,\n            \"functions\": false,\n            \"variables\": false\n          }\n        ],\n        \"no-useless-constructor\": 0,\n        \"@typescript-eslint/no-useless-constructor\": 2,\n        \"react/jsx-filename-extension\": 0\n      }\n    },\n    {\n      \"files\": [\"tests/**\", \"**/*.test.*\"],\n      \"env\": {\n        \"jest\": true\n      },\n      \"rules\": {\n        \"func-names\": 0,\n        \"global-require\": 0,\n        \"new-cap\": 0,\n        \"prefer-arrow-callback\": 0,\n        \"no-import-assign\": 0\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "import/asana/.gitignore",
    "content": "test\n\n"
  },
  {
    "path": "import/asana/README.md",
    "content": "# Asana importer\n\nThis node app converts an Asana json archive into a Focalboard archive. To use:\n1. From the Asana Board Menu (dropdown next to board title), select `Export / Print`, and `JSON`\n2. Save it locally, e.g. to `asana.json`\n3. Run `npm install` from within `focalboard/webapp`\n4. Run `npm install` from within `focalboard/import/asana`\n5. Run `npx ts-node importAsana.ts -i <asana.json> -o archive.boardarchive`\n6. In Focalboard, click `Settings`, then `Import archive` and select `archive.boardarchive`\n\n## Import scope\n\nCurrently, the script imports all cards from a single board, including their section (column) membership, names, and notes. [Contribute code](https://mattermost.github.io/focalboard/) to expand this.\n"
  },
  {
    "path": "import/asana/asana.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n// Generated by https://quicktype.io\n//\n// To change quicktype's target language, run command:\n//\n//   \"Set quicktype target language\"\n\nexport interface Asana {\n    data: Datum[];\n}\n\nexport interface Datum {\n    gid:              string;\n    assignee:         null;\n    assignee_status:  AssigneeStatus;\n    completed:        boolean;\n    completed_at:     null;\n    created_at:       string;\n    custom_fields:    CustomField[];\n    due_at:           null;\n    due_on:           null;\n    followers:        Workspace[];\n    hearted:          boolean;\n    hearts:           any[];\n    liked:            boolean;\n    likes:            any[];\n    memberships:      Membership[];\n    modified_at:      string;\n    name:             string;\n    notes:            string;\n    num_hearts:       number;\n    num_likes:        number;\n    parent:           Workspace | null;\n    permalink_url:    string;\n    projects:         Workspace[];\n    resource_type:    WorkspaceResourceType;\n    start_on:         null;\n    subtasks:         Datum[];\n    tags:             any[];\n    resource_subtype: ResourceSubtype;\n    workspace:        Workspace;\n}\n\nexport enum AssigneeStatus {\n    Upcoming = \"upcoming\",\n}\n\nexport interface CustomField {\n    gid:              string;\n    enabled:          boolean;\n    enum_options:     Enum[];\n    enum_value:       Enum | null;\n    name:             CustomFieldName;\n    created_by:       null;\n    resource_subtype: Type;\n    resource_type:    CustomFieldResourceType;\n    type:             Type;\n}\n\nexport interface Enum {\n    gid:           string;\n    color:         Color;\n    enabled:       boolean;\n    name:          EnumOptionName;\n    resource_type: EnumOptionResourceType;\n}\n\nexport enum Color {\n    Blue = \"blue\",\n    BlueGreen = \"blue-green\",\n    CoolGray = \"cool-gray\",\n    Orange = \"orange\",\n    Red = \"red\",\n    Yellow = \"yellow\",\n    YellowOrange = \"yellow-orange\",\n}\n\nexport enum EnumOptionName {\n    Deferred = \"Deferred\",\n    Done = \"Done\",\n    High = \"High\",\n    InProgress = \"In Progress\",\n    Low = \"Low\",\n    Medium = \"Medium\",\n    NotStarted = \"Not Started\",\n    Waiting = \"Waiting\",\n}\n\nexport enum EnumOptionResourceType {\n    EnumOption = \"enum_option\",\n}\n\nexport enum CustomFieldName {\n    Priority = \"Priority\",\n    TaskProgress = \"Task Progress\",\n}\n\nexport enum Type {\n    Enum = \"enum\",\n}\n\nexport enum CustomFieldResourceType {\n    CustomField = \"custom_field\",\n}\n\nexport interface Workspace {\n    gid:           string;\n    name:          string;\n    resource_type: WorkspaceResourceType;\n}\n\nexport enum WorkspaceResourceType {\n    Project = \"project\",\n    Section = \"section\",\n    Task = \"task\",\n    User = \"user\",\n    Workspace = \"workspace\",\n}\n\nexport interface Membership {\n    project: Workspace;\n    section: Workspace;\n}\n\nexport enum ResourceSubtype {\n    DefaultTask = \"default_task\",\n}\n"
  },
  {
    "path": "import/asana/importAsana.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport * as fs from 'fs'\nimport minimist from 'minimist'\nimport {exit} from 'process'\nimport {ArchiveUtils} from '../util/archive'\nimport {Block} from '../../webapp/src/blocks/block'\nimport {Board} from '../../webapp/src/blocks/board'\nimport {IPropertyOption, IPropertyTemplate, createBoard} from '../../webapp/src/blocks/board'\nimport {createBoardView} from '../../webapp/src/blocks/boardView'\nimport {createCard} from '../../webapp/src/blocks/card'\nimport {createTextBlock} from '../../webapp/src/blocks/textBlock'\nimport {Asana, Workspace} from './asana'\nimport {Utils} from './utils'\n\n// HACKHACK: To allow Utils.CreateGuid to work\n(global.window as any) = {}\n\nconst optionColors = [\n    // 'propColorDefault',\n    'propColorGray',\n    'propColorBrown',\n    'propColorOrange',\n    'propColorYellow',\n    'propColorGreen',\n    'propColorBlue',\n    'propColorPurple',\n    'propColorPink',\n    'propColorRed',\n]\nlet optionColorIndex = 0\n\nfunction main() {\n    const args: minimist.ParsedArgs = minimist(process.argv.slice(2))\n\n    const inputFile = args['i']\n    const outputFile = args['o'] || 'archive.boardarchive'\n\n    if (!inputFile) {\n        showHelp()\n    }\n\n    if (!fs.existsSync(inputFile)) {\n        console.error(`File not found: ${inputFile}`)\n        exit(2)\n    }\n\n    // Read input\n    const inputData = fs.readFileSync(inputFile, 'utf-8')\n    const input = JSON.parse(inputData) as Asana\n\n    // Convert\n    const [boards, blocks] = convert(input)\n\n    // Save output\n    // TODO: Stream output\n    const outputData = ArchiveUtils.buildBlockArchive(boards, blocks)\n    fs.writeFileSync(outputFile, outputData)\n\n    console.log(`Exported to ${outputFile}`)\n}\n\nfunction getProjects(input: Asana): Workspace[] {\n    const projectMap = new Map<string, Workspace>()\n\n    input.data.forEach(datum => {\n        datum.projects.forEach(project => {\n            if (!projectMap.get(project.gid)) {\n                projectMap.set(project.gid, project)\n            }\n        })\n    })\n\n    return [...projectMap.values()]\n}\n\nfunction getSections(input: Asana, projectId: string): Workspace[] {\n    const sectionMap = new Map<string, Workspace>()\n\n    input.data.forEach(datum => {\n        const membership = datum.memberships.find(o => o.project.gid === projectId)\n        if (membership) {\n            if (!sectionMap.get(membership.section.gid)) {\n                sectionMap.set(membership.section.gid, membership.section)\n            }\n        }\n    })\n\n    return [...sectionMap.values()]\n}\n\nfunction convert(input: Asana): [Board[], Block[]] {\n    const projects = getProjects(input)\n    if (projects.length < 1) {\n        console.error('No projects found')\n        return [[],[]]\n    }\n\n    // TODO: Handle multiple projects\n    const project = projects[0]\n\n    const boards: Board[] = []\n    const blocks: Block[] = []\n\n    // Board\n    const board = createBoard()\n    console.log(`Board: ${project.name}`)\n    board.title = project.name\n\n    // Convert sections (columns) to a Select property\n    const optionIdMap = new Map<string, string>()\n    const options: IPropertyOption[] = []\n    const sections = getSections(input, project.gid)\n    sections.forEach(section => {\n        const optionId = Utils.createGuid()\n        optionIdMap.set(section.gid, optionId)\n        const color = optionColors[optionColorIndex % optionColors.length]\n        optionColorIndex += 1\n        const option: IPropertyOption = {\n            id: optionId,\n            value: section.name,\n            color,\n        }\n        options.push(option)\n    })\n\n    const cardProperty: IPropertyTemplate = {\n        id: Utils.createGuid(),\n        name: 'Section',\n        type: 'select',\n        options\n    }\n    board.cardProperties = [cardProperty]\n    boards.push(board)\n\n    // Board view\n    const view = createBoardView()\n    view.title = 'Board View'\n    view.fields.viewType = 'board'\n    view.parentId = board.id\n    view.boardId = board.id\n    blocks.push(view)\n\n    // Cards\n    input.data.forEach(card => {\n        console.log(`Card: ${card.name}`)\n\n        const outCard = createCard()\n        outCard.title = card.name\n        outCard.boardId = board.id\n        outCard.parentId = board.id\n\n        // Map lists to Select property options\n        const membership = card.memberships.find(o => o.project.gid === project.gid)\n        if (membership) {\n            const optionId = optionIdMap.get(membership.section.gid)\n            if (optionId) {\n                outCard.fields.properties[cardProperty.id] = optionId\n            } else {\n                console.warn(`Invalid idList: ${membership.section.gid} for card: ${card.name}`)\n            }\n        } else {\n            console.warn(`Missing idList for card: ${card.name}`)\n        }\n\n        blocks.push(outCard)\n\n        if (card.notes) {\n            // console.log(`\\t${card.notes}`)\n            const text = createTextBlock()\n            text.title = card.notes\n            text.parentId = outCard.id\n            text.boardId = board.id\n            blocks.push(text)\n\n            outCard.fields.contentOrder = [text.id]\n        }\n    })\n\n    console.log('')\n    console.log(`Found ${input.data.length} card(s).`)\n\n    return [boards, blocks]\n}\n\nfunction showHelp() {\n    console.log('import -i <input.json> -o [output.boardarchive]')\n    exit(1)\n}\n\nmain()\n"
  },
  {
    "path": "import/asana/package.json",
    "content": "{\n  \"name\": \"focalboard-asana-importer\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"description\": \"\",\n  \"main\": \"importAsana.js\",\n  \"scripts\": {\n    \"lint\": \"eslint --ext .tsx,.ts . --quiet --cache\",\n    \"fix\": \"eslint --ext .tsx,.ts . --quiet --fix --cache\",\n    \"test\": \"ts-node importAsana.ts -i test/asana.json -o test/asana-import.focalboard\",\n    \"debug:test\": \"node --inspect=5858 -r ts-node/register importAsana.ts -i test/asana.json -o test/asana-import.focalboard\"\n  },\n  \"keywords\": [],\n  \"author\": \"\",\n  \"devDependencies\": {\n    \"@types/minimist\": \"^1.2.1\",\n    \"@types/node\": \"^14.14.28\",\n    \"@typescript-eslint/eslint-plugin\": \"^4.15.0\",\n    \"@typescript-eslint/parser\": \"^4.15.0\",\n    \"eslint\": \"^7.20.0\",\n    \"ts-node\": \"^9.1.1\",\n    \"typescript\": \"^4.1.5\"\n  },\n  \"dependencies\": {\n    \"minimist\": \"^1.2.6\"\n  }\n}\n"
  },
  {
    "path": "import/asana/tsconfig.json",
    "content": "{\n\t\"compilerOptions\": {\n\t\t\"jsx\": \"react\",\n\t\t\"target\": \"es2019\",\n\t\t\"module\": \"commonjs\",\n\t\t\"esModuleInterop\": true,\n\t\t\"noImplicitAny\": true,\n\t\t\"strict\": true,\n\t\t\"strictNullChecks\": true,\n\t\t\"forceConsistentCasingInFileNames\": true,\n\t\t\"sourceMap\": true,\n\t\t\"allowJs\": true,\n\t\t\"resolveJsonModule\": true,\n\t\t\"incremental\": false,\n\t\t\"outDir\": \"./dist\",\n\t\t\"moduleResolution\": \"node\"\n\t},\n\t\"include\": [\n\t\t\".\"\n\t],\n\t\"exclude\": [\n        \".git\",\n\t\t\"**/node_modules/*\",\n\t\t\"dist\",\n\t\t\"pack\"\n    ]\n}\n"
  },
  {
    "path": "import/asana/utils.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport * as crypto from 'crypto'\n\nclass Utils {\n    static createGuid(): string {\n        function randomDigit() {\n            if (crypto && crypto.randomBytes) {\n                const rands = crypto.randomBytes(1)\n                return (rands[0] % 16).toString(16)\n            }\n\n            return (Math.floor((Math.random() * 16))).toString(16)\n        }\n        return 'xxxxxxxx-xxxx-4xxx-8xxx-xxxxxxxxxxxx'.replace(/x/g, randomDigit)\n    }\n}\n\nexport { Utils }\n"
  },
  {
    "path": "import/jira/.gitignore",
    "content": "test"
  },
  {
    "path": "import/jira/README.md",
    "content": "# Jira importer\n\nThis node app converts a Jira xml export into a Focalboard archive. To use:\n1. Open Jira advanced search, and search for all the items to export\n2. Select `Export`, then `Export XML`\n3. Save it locally, e.g. to `jira_export.xml`\n4. Run `npm install` from within `focalboard/webapp`\n5. Run `npm install` from within `focalboard/import/jira`\n6. Run `npx ts-node importJira.ts -i <path-to-jira.xml> -o archive.boardarchive` (also from within `focalboard/import/jira`)\n7. In Focalboard, click `Settings`, then `Import archive` and select `archive.boardarchive`\n\n## Import scope and known limitations\n\nCurrently, the script imports each item as a card into a single board. Note that Jira XML export is limited to 1000 issues at a time.\n\nUsers are imported as Select properties, with the name of the user.\n\nThe following aren't currently imported:\n* Custom properties\n* Comments\n* Embedded files\n\n[Contribute code](https://mattermost.github.io/focalboard/) to expand this.\n"
  },
  {
    "path": "import/jira/importJira.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport minimist from 'minimist'\nimport {run} from './jiraImporter'\n\nasync function main() {\n    const args: minimist.ParsedArgs = minimist(process.argv.slice(2))\n\n    const inputFile = args['i']\n    const outputFile = args['o'] || 'archive.boardarchive'\n\n    return run(inputFile, outputFile)\n}\n\nmain()\n"
  },
  {
    "path": "import/jira/jiraImporter.test.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {run} from './jiraImporter'\nimport * as fs from 'fs'\nimport {ArchiveUtils} from '../util/archive'\n\nconst inputFile = './test/jira-export.xml'\nconst outputFile = './test/jira.focalboard'\n\ndescribe('import from Jira', () => {\n    test('import', async () => {\n        const blockCount = await run(inputFile, outputFile)\n        expect(blockCount === 4)\n    })\n\n    test('import was complete', async () => {\n        const archiveData = fs.readFileSync(outputFile, 'utf-8')\n        const blocks = ArchiveUtils.parseBlockArchive(archiveData)\n\n        console.debug(blocks)\n\n\n        blocks.forEach(block => {\n            console.log(block.title)\n        })\n\n        expect(blocks).toEqual(\n            expect.arrayContaining([\n                expect.objectContaining({\n                    title: 'Board View',\n                    type: 'view'\n                }),\n                expect.objectContaining({\n                    title: 'Investigate feature area',\n                    type: 'card'\n                }),\n                expect.objectContaining({\n                    title: 'Investigate feature',\n                    type: 'card'\n                }),\n            ])\n        )\n    })\n\n    afterAll(() => {\n        fs.rmSync(outputFile)\n    });\n})\n"
  },
  {
    "path": "import/jira/jiraImporter.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport * as fs from 'fs'\nimport {exit} from 'process'\nimport {ArchiveUtils} from '../util/archive'\nimport {Block} from '../../webapp/src/blocks/block'\nimport {Board} from '../../webapp/src/blocks/board'\nimport {IPropertyOption, IPropertyTemplate, createBoard} from '../../webapp/src/blocks/board'\nimport {createBoardView} from '../../webapp/src/blocks/boardView'\nimport {Card, createCard} from '../../webapp/src/blocks/card'\nimport {createTextBlock} from '../../webapp/src/blocks/textBlock'\nimport {Utils} from './utils'\nimport xml2js, {ParserOptions} from 'xml2js'\nimport TurndownService from 'turndown'\n\n// HACKHACK: To allow Utils.CreateGuid to work\n(global.window as any) = {}\n\nconst optionColors = [\n    'propColorGray',\n    'propColorBrown',\n    'propColorOrange',\n    'propColorYellow',\n    'propColorGreen',\n    'propColorBlue',\n    'propColorPurple',\n    'propColorPink',\n    'propColorRed',\n]\nlet optionColorIndex = 0\n\nvar turndownService = new TurndownService()\n\nasync function run(inputFile: string, outputFile: string): Promise<number> {\n    console.log(`input: ${inputFile}`)\n    console.log(`output: ${outputFile}`)\n\n    if (!inputFile) {\n        showHelp()\n    }\n\n    if (!fs.existsSync(inputFile)) {\n        console.error(`File not found: ${inputFile}`)\n        exit(2)\n    }\n\n    // Read input\n\tconsole.log(`Reading ${inputFile}`)\n    const inputData = fs.readFileSync(inputFile, 'utf-8')\n\n\tif (!inputData) {\n        console.error(`Unable to read data from file: ${inputFile}`)\n        exit(2)\n    }\n\n\tconsole.log(`Read ${Math.round(inputData.length / 1024)} KB`)\n\n    const parserOptions: ParserOptions = {\n        explicitArray: false\n    }\n\tconst parser = new xml2js.Parser(parserOptions);\n\tconst input = await parser.parseStringPromise(inputData)\n\n\tif (!input?.rss?.channel) {\n        console.error(`No channels in xml: ${inputFile}`)\n        exit(2)\n    }\n    const channel = input.rss.channel\n    const items = channel.item\n\n\t// console.dir(items);\n\n    // Convert\n    const [boards, blocks] = convert(items)\n\n    // Save output\n    // TODO: Stream output\n    const outputData = ArchiveUtils.buildBlockArchive(boards, blocks)\n    fs.writeFileSync(outputFile, outputData)\n    console.log(`Exported ${blocks.length} block(s) to ${outputFile}`)\n\n    return blocks.length\n}\n\nfunction convert(items: any[]): [Board[], Block[]] {\n    const boards: Board[] = []\n    const blocks: Block[] = []\n\n    // Board\n    const board = createBoard()\n    board.title = 'Jira import'\n\n    // Compile standard properties\n    board.cardProperties = []\n\n    const priorityProperty = buildCardPropertyFromValues('Priority', items.map(o => o.priority?._))\n    board.cardProperties.push(priorityProperty)\n\n    const statusProperty = buildCardPropertyFromValues('Status', items.map(o => o.status?._))\n    board.cardProperties.push(statusProperty)\n\n    const resolutionProperty = buildCardPropertyFromValues('Resolution', items.map(o => o.resolution?._))\n    board.cardProperties.push(resolutionProperty)\n\n    const typeProperty = buildCardPropertyFromValues('Type', items.map(o => o.type?._))\n    board.cardProperties.push(typeProperty)\n\n    const assigneeProperty = buildCardPropertyFromValues('Assignee', items.map(o => o.assignee?._))\n    board.cardProperties.push(assigneeProperty)\n\n    const reporterProperty = buildCardPropertyFromValues('Reporter', items.map(o => o.reporter?._))\n    board.cardProperties.push(reporterProperty)\n\n    const originalUrlProperty: IPropertyTemplate = {\n        id: Utils.createGuid(),\n        name: 'Original URL',\n        type: 'url',\n        options: []\n    }\n    board.cardProperties.push(originalUrlProperty)\n\n    const createdDateProperty: IPropertyTemplate = {\n        id: Utils.createGuid(),\n        name: 'Created Date',\n        type: 'date',\n        options: []\n    }\n    board.cardProperties.push(createdDateProperty)\n\n    boards.push(board)\n\n    // Board view\n    const view = createBoardView()\n    view.title = 'Board View'\n    view.fields.viewType = 'board'\n    view.boardId = board.id\n    view.parentId = board.id\n    blocks.push(view)\n\n    for (const item of items) {\n        console.log(\n            `Item: ${item.summary}, ` +\n            `priority: ${item.priority?._}, ` +\n            `status: ${item.status?._}, ` +\n            `type: ${item.type?._}`)\n\n        const card = createCard()\n        card.title = item.summary\n        card.boardId = board.id\n        card.parentId = board.id\n\n        // Map standard properties\n        if (item.priority?._) { setSelectProperty(card, priorityProperty, item.priority._) }\n        if (item.status?._) { setSelectProperty(card, statusProperty, item.status._) }\n        if (item.resolution?._) { setSelectProperty(card, resolutionProperty, item.resolution._) }\n        if (item.type?._) { setSelectProperty(card, typeProperty, item.type._) }\n        if (item.assignee?._) { setSelectProperty(card, assigneeProperty, item.assignee._) }\n        if (item.reporter?._) { setSelectProperty(card, reporterProperty, item.reporter._) }\n\n        if (item.link) { setProperty(card, originalUrlProperty.id, item.link)}\n        if (item.created) {\n            const dateInMs = Date.parse(item.created)\n            setProperty(card, createdDateProperty.id, dateInMs.toString())\n        }\n\n        // TODO: Map custom properties\n\n        if (item.description) {\n            const description = turndownService.turndown(item.description)\n            console.log(`\\t${description}`)\n            const text = createTextBlock()\n            text.title = description\n            text.boardId = board.id\n            text.parentId = card.id\n            blocks.push(text)\n\n            card.fields.contentOrder = [text.id]\n        }\n\n        blocks.push(card)\n    }\n\n    return [boards, blocks]\n}\n\nfunction buildCardPropertyFromValues(propertyName: string, allValues: string[]) {\n    const options: IPropertyOption[] = []\n\n    // Remove empty and duplicate values\n    const values = allValues.\n        filter(o => !!o).\n        filter((x, y) => allValues.indexOf(x) == y);\n\n    for (const value of values) {\n        const optionId = Utils.createGuid()\n        const color = optionColors[optionColorIndex % optionColors.length]\n        optionColorIndex += 1\n        const option: IPropertyOption = {\n            id: optionId,\n            value,\n            color,\n        }\n        options.push(option)\n    }\n\n    const cardProperty: IPropertyTemplate = {\n        id: Utils.createGuid(),\n        name: propertyName,\n        type: 'select',\n        options\n    }\n\n    console.log(`Property: ${propertyName}, values: ${values}`)\n\n    return cardProperty\n}\n\nfunction setSelectProperty(card: Card, cardProperty: IPropertyTemplate, propertyValue: string) {\n    const option = optionForPropertyValue(cardProperty, propertyValue)\n    if (option) {\n        card.fields.properties[cardProperty.id] = option.id\n    }\n}\n\nfunction setProperty(card: Card, cardPropertyId: string, propertyValue: string) {\n    card.fields.properties[cardPropertyId] = propertyValue\n}\n\nfunction optionForPropertyValue(cardProperty: IPropertyTemplate, propertyValue: string): IPropertyOption | null {\n    const option = cardProperty.options.find(o => o.value === propertyValue)\n    if (!option) {\n        console.error(`Property value not found: ${propertyValue}`)\n        return null\n    }\n\n    return option\n}\n\nfunction showHelp() {\n    console.log('import -i <input.xml> -o [output.boardarchive]')\n    exit(1)\n}\n\nexport { run }\n"
  },
  {
    "path": "import/jira/package.json",
    "content": "{\n  \"name\": \"focalboard-jira-importer\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"description\": \"\",\n  \"main\": \"importJira.js\",\n  \"scripts\": {\n    \"lint\": \"eslint --ext .tsx,.ts . --quiet --cache\",\n    \"fix\": \"eslint --ext .tsx,.ts . --quiet --fix --cache\",\n    \"test\": \"jest\",\n    \"testRun\": \"ts-node importJira.ts -i test/jira_export.xml -o test/jira-import.focalboard\",\n    \"debug:test\": \"node --inspect=5858 -r ts-node/register importJira.ts -i test/jira_export.xml -o test/jira-import.focalboard\"\n  },\n  \"keywords\": [],\n  \"author\": \"\",\n  \"jest\": {\n    \"globals\": {\n      \"ts-jest\": {\n        \"tsconfig\": \"./tsconfig.json\"\n      }\n    },\n    \"transform\": {\n      \"^.+\\\\.tsx?$\": \"ts-jest\"\n    },\n    \"collectCoverage\": true,\n    \"collectCoverageFrom\": [\n      \"*.{ts,tsx,js,jsx}\",\n      \"!test/**\"\n    ]\n  },\n  \"devDependencies\": {\n    \"@types/jest\": \"^27.0.2\",\n    \"@types/minimist\": \"^1.2.1\",\n    \"@types/node\": \"^14.14.28\",\n    \"@types/turndown\": \"^5.0.1\",\n    \"@types/xml2js\": \"^0.4.9\",\n    \"@typescript-eslint/eslint-plugin\": \"^4.15.0\",\n    \"@typescript-eslint/parser\": \"^4.15.0\",\n    \"eslint\": \"^7.20.0\",\n    \"jest\": \"^27.3.1\",\n    \"ts-jest\": \"^27.0.7\",\n    \"ts-node\": \"^9.1.1\",\n    \"typescript\": \"^4.1.5\"\n  },\n  \"dependencies\": {\n    \"minimist\": \"^1.2.6\",\n    \"turndown\": \"^7.1.1\",\n    \"xml2js\": \"^0.4.23\"\n  }\n}\n"
  },
  {
    "path": "import/jira/tsconfig.json",
    "content": "{\n\t\"compilerOptions\": {\n\t\t\"jsx\": \"react\",\n\t\t\"target\": \"es2019\",\n\t\t\"module\": \"commonjs\",\n\t\t\"esModuleInterop\": true,\n\t\t\"noImplicitAny\": true,\n\t\t\"strict\": true,\n\t\t\"strictNullChecks\": true,\n\t\t\"forceConsistentCasingInFileNames\": true,\n\t\t\"sourceMap\": true,\n\t\t\"allowJs\": true,\n\t\t\"resolveJsonModule\": true,\n\t\t\"incremental\": false,\n\t\t\"outDir\": \"./dist\",\n\t\t\"moduleResolution\": \"node\"\n\t},\n\t\"include\": [\n\t\t\".\"\n\t],\n\t\"exclude\": [\n        \".git\",\n\t\t\"**/node_modules/*\",\n\t\t\"dist\",\n\t\t\"pack\"\n    ]\n}\n"
  },
  {
    "path": "import/jira/utils.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport * as crypto from 'crypto'\n\nclass Utils {\n    static createGuid(): string {\n        function randomDigit() {\n            if (crypto && crypto.randomBytes) {\n                const rands = crypto.randomBytes(1)\n                return (rands[0] % 16).toString(16)\n            }\n\n            return (Math.floor((Math.random() * 16))).toString(16)\n        }\n        return 'xxxxxxxx-xxxx-4xxx-8xxx-xxxxxxxxxxxx'.replace(/x/g, randomDigit)\n    }\n}\n\nexport { Utils }\n"
  },
  {
    "path": "import/nextcloud-deck/.eslintrc.json",
    "content": "{\n  \"extends\": [\n  ],\n  \"plugins\": [\n  ],\n  \"parser\": \"@typescript-eslint/parser\",\n  \"env\": {\n    \"jest\": true\n  },\n  \"settings\": {\n    \"import/resolver\": \"webpack\",\n    \"react\": {\n        \"pragma\": \"React\",\n        \"version\": \"detect\"\n    }\n  },\n  \"rules\": {\n    \"no-unused-expressions\": 0,\n    \"eol-last\": [\"error\", \"always\"],\n    \"import/no-unresolved\": 2,\n    \"no-undefined\": 0,\n    \"react/jsx-filename-extension\": 0,\n    \"max-nested-callbacks\": [\"error\", {\"max\": 5}]\n  },\n  \"overrides\": [\n    {\n      \"files\": [\"**/*.tsx\", \"**/*.ts\"],\n      \"extends\": [\n        \"plugin:@typescript-eslint/recommended\"\n      ],\n      \"rules\": {\n        \"import/no-unresolved\": 0, // ts handles this better\n\t\t\"camelcase\": 0,\n\t\t\"semi\": \"off\",\n        \"@typescript-eslint/naming-convention\": [\n          2,\n          {\n            \"selector\": \"function\",\n            \"format\": [\"camelCase\", \"PascalCase\"]\n          },\n          {\n            \"selector\": \"variable\",\n            \"format\": [\"camelCase\", \"PascalCase\", \"UPPER_CASE\"]\n          },\n          {\n            \"selector\": \"parameter\",\n            \"format\": [\"camelCase\", \"PascalCase\"],\n            \"leadingUnderscore\": \"allow\"\n          },\n          {\n            \"selector\": \"typeLike\",\n            \"format\": [\"PascalCase\"]\n          }\n        ],\n        \"@typescript-eslint/no-non-null-assertion\": 0,\n        \"@typescript-eslint/no-unused-vars\": [\n          2,\n          {\n            \"vars\": \"all\",\n            \"args\": \"after-used\"\n          }\n        ],\n        \"@typescript-eslint/no-var-requires\": 0,\n        \"@typescript-eslint/no-empty-function\": 0,\n        \"@typescript-eslint/prefer-interface\": 0,\n        \"@typescript-eslint/explicit-function-return-type\": 0,\n        \"@typescript-eslint/semi\": [2, \"never\"],\n\t\t\"@typescript-eslint/indent\": [\n          2,\n          4,\n          {\n            \"SwitchCase\": 0\n          }\n        ],\n        \"no-use-before-define\": \"off\",\n        \"@typescript-eslint/no-use-before-define\": [\n          2,\n          {\n            \"classes\": false,\n            \"functions\": false,\n            \"variables\": false\n          }\n        ],\n        \"no-useless-constructor\": 0,\n        \"@typescript-eslint/no-useless-constructor\": 2,\n        \"react/jsx-filename-extension\": 0\n      }\n    },\n    {\n      \"files\": [\"tests/**\", \"**/*.test.*\"],\n      \"env\": {\n        \"jest\": true\n      },\n      \"rules\": {\n        \"func-names\": 0,\n        \"global-require\": 0,\n        \"new-cap\": 0,\n        \"prefer-arrow-callback\": 0,\n        \"no-import-assign\": 0\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "import/nextcloud-deck/.gitignore",
    "content": "test\noutput.focalboard\n"
  },
  {
    "path": "import/nextcloud-deck/README.md",
    "content": "# Nextcloud Deck importer\n\nThis node app converts data from a Nextcloud Server with the [app Deck](https://apps.nextcloud.com/apps/deck) installed into a Focalboard archive. To use:\n\n1. Run `npm install` from within `focalboard/webapp`\n2. Run `npm install` from within `focalboard/import/nextcloud-deck`\n3. Run `npx ts-node importDeck.ts -o archive.boardarchive` (also from within `focalboard/import/nextcloud-deck`)\n   1. Enter URL and credentials (can also be provided via cli arguments)\n   2. Enter ID of the board to convert\n4. In Focalboard, click `Settings`, then `Import archive` and select `archive.boardarchive`\n\n## Import scope\n\nCurrently, the script imports all cards from a single board, including their stacks (column) membership, labels, names, descriptions, duedate and comments. [Contribute code](https://mattermost.github.io/focalboard/) to expand this.\n\n\n"
  },
  {
    "path": "import/nextcloud-deck/deck.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n/* eslint-disable @typescript-eslint/no-empty-interface */\n// Generated by https://quicktype.io\n//\n// To change quicktype's target language, run command:\n//\n//   \"Set quicktype target language\"\nimport fetch from 'node-fetch'\n\n\n// Types\n\nexport interface Board {\n    title: string;\n    owner: User;\n    color: string;\n    archived: boolean;\n    labels: Label[];\n    acl: any[];\n    permissions: {\n        PERMISSION_READ: boolean;\n        PERMISSION_EDIT: boolean;\n        PERMISSION_MANAGE: boolean;\n        PERMISSION_SHARE: boolean;\n    };\n    users: User[];\n    shared: number;\n    deletedAt: number;\n    id: number;\n    lastModified: number;\n}\n\nexport interface Stack {\n    title: string;\n    boardId: number;\n    deletedAt: number;\n    lastModified: number;\n    cards: Card[];\n    order: number;\n    id: number;\n}\n\nexport interface Card {\n    title: string;\n    description: string;\n    stackId: number;\n    type: \"plain\";\n    lastModified: number;\n    createdAt: number;\n    labels: Label[];\n    assignedUsers: unknown;\n    attachments: unknown;\n    attachmentCount: unknown;\n    owner: string;\n    order: number;\n    archived: boolean;\n    duedate: string;\n    deletedAt: number;\n    commentsUnread: number;\n    commentsCount: number;\n    comments?: Comment[]\n    id: number;\n    overdue: number;\n}\n\nexport interface CommentResponse {\n    ocs: {\n        meta: {\n            status: string;\n            statuscode: number;\n            message: string;\n        };\n        data: Comment[];\n    };\n}\n\nexport interface Comment {\n    id: number;\n    objectId: number;\n    message: string;\n    actorId: string;\n    actorType: string;\n    actorDisplayName: string;\n    creationDateTime: string;\n    mentions: [\n        {\n            mentionId: string;\n            mentionType: string;\n            mentionDisplayName: string;\n        }\n    ];\n    replyTo?: Comment;\n}\n\nexport interface Label {\n    title: string;\n    color: string;\n    cardId: any;\n    id: number;\n}\n\nexport interface User {\n    primaryKey: string;\n    uid: string;\n    displayname: string;\n}\n\nexport interface NextcloudDeckClientConfig {\n    url: string\n    auth: Auth\n}\n\nexport interface Auth {\n    username: string\n    password: string\n}\n\n// api\n\nexport const defaultHeaders = {\n    \"OCS-APIRequest\": \"true\",\n    \"Content-Type\": \"application/json\",\n    \"Accept\": \"application/json\",\n}\n\nexport class NextcloudDeckClient {\n\n    config: NextcloudDeckClientConfig\n\n    /**\n     * Create a new Nextcloud Deck client\n     */\n    constructor(config: NextcloudDeckClientConfig) {\n        this.config = config\n    }\n\n    async fetchWrapper(path: string): Promise<any> {\n        const response = await fetch(`${this.config.url}/index.php/apps/deck/api/v1.0/${path}`,\n            {\n                method: \"GET\",\n                headers: {\n                    ...defaultHeaders,\n                    \"Authorization\": 'Basic ' + Buffer.from(`${this.config.auth.username}:${this.config.auth.password}`).toString('base64')\n                }\n            })\n        if (!response.ok) {\n            throw new Error(`Request failed with info: ${await response.text()}`)\n        }\n        return await response.json()\n    }\n    async fetchWrapperOCS(path: string): Promise<any> {\n        const response = await fetch(`${this.config.url}/ocs/v2.php/apps/deck/api/v1.0/${path}`,\n            {\n                method: \"GET\",\n                headers: {\n                    ...defaultHeaders,\n                    \"Authorization\": 'Basic ' + Buffer.from(`${this.config.auth.username}:${this.config.auth.password}`).toString('base64')\n                }\n            })\n        if (!response.ok) {\n            throw new Error(`Request failed with info: ${await response.text()}`)\n        }\n        return await response.json()\n    }\n\n    async getBoards(): Promise<Board[]> {\n        return await this.fetchWrapper('boards')\n    }\n\n    async getBoardDetails(boardId: number): Promise<Board> {\n        return await this.fetchWrapper(`boards/${boardId}`)\n\n    }\n\n    async getStacks(boardId: number): Promise<Stack[]> {\n        return await this.fetchWrapper(`boards/${boardId}/stacks`)\n\n    }\n\n    async getStacksArchived(boardId: number): Promise<Stack[]> {\n        return await this.fetchWrapper(`boards/${boardId}/stacks/archived`)\n\n    }\n\n    async getStackDetails(boardId: number, stackId: number): Promise<Stack> {\n        return await this.fetchWrapper(`boards/${boardId}/stacks/${stackId}`)\n\n    }\n\n    async getCardDetails(boardId: number, stackId: number, cardId: number): Promise<Card[]> {\n        return await this.fetchWrapper(`boards/${boardId}/stacks/${stackId}/cards/${cardId}`)\n\n    }\n\n    async getComments(cardId: number): Promise<Comment[]> {\n        const response = await this.fetchWrapperOCS(`cards/${cardId}/comments`) as CommentResponse\n        return response.ocs.data\n    }\n}\n\n"
  },
  {
    "path": "import/nextcloud-deck/importDeck.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport * as fs from 'fs'\nimport minimist from 'minimist'\nimport {exit} from 'process'\nimport {ArchiveUtils} from '../util/archive'\nimport {Block} from '../../webapp/src/blocks/block'\nimport {Board as FBBoard} from '../../webapp/src/blocks/board'\nimport {IPropertyOption, IPropertyTemplate, createBoard} from '../../webapp/src/blocks/board'\nimport {createBoardView} from '../../webapp/src/blocks/boardView'\nimport {createCard} from '../../webapp/src/blocks/card'\nimport {createTextBlock} from '../../webapp/src/blocks/textBlock'\nimport {NextcloudDeckClient, Stack, Board} from './deck'\nimport {Utils} from './utils'\nimport readline from 'readline-sync'\nimport {createCommentBlock} from '../../webapp/src/blocks/commentBlock'\n\n\n// HACKHACK: To allow Utils.CreateGuid to work\n(global.window as any) = {}\n\nconst optionColors = [\n    // 'propColorDefault',\n    'propColorGray',\n    'propColorBrown',\n    'propColorOrange',\n    'propColorYellow',\n    'propColorGreen',\n    'propColorBlue',\n    'propColorPurple',\n    'propColorPink',\n    'propColorRed',\n]\n\nasync function main() {\n    const args: minimist.ParsedArgs = minimist(process.argv.slice(2))\n\n    console.log(\"Transform a nextcloud deck into a mattermost Board.\")\n\n    if (args['h'] || args['help']) {\n        showHelp()\n    }\n\n    // Get Options\n    const url = args['url'] ?? readline.question('Nextcloud URL: ')\n    const username = args['u'] ?? readline.question('Username: ')\n    const password = args['p'] ?? readline.question('Password: ', {hideEchoBack: true})\n    const boardIdString = args['b']\n\n    const outputFile = args['o'] || 'archive.boardarchive'\n\n    // Create Client\n    const deckClient = new NextcloudDeckClient({auth: {username, password}, url})\n\n    // Select board (Either from cli or by interactive selection)\n    const boardId = boardIdString ? parseInt(boardIdString) : await selectBoard(deckClient)\n\n    // Get Data\n    const board = await deckClient.getBoardDetails(boardId)\n    const stacks = await Promise.all((await deckClient.getStacks(boardId)).map(async s => {\n        return {\n            ...s,\n            cards: await Promise.all(s.cards.map(async c => {\n                if (c.commentsCount > 0) {\n                    c.comments = await deckClient.getComments(c.id)\n                }\n                return c\n            }))\n        }\n    }))\n\n    // Convert\n    const [boards, blocks] = convert(board, stacks)\n\n    // // Save output\n    const outputData = ArchiveUtils.buildBlockArchive(boards, blocks)\n    fs.writeFileSync(outputFile, outputData)\n\n    console.log(`Exported to ${outputFile}`)\n}\n\nasync function selectBoard(deckClient: NextcloudDeckClient): Promise<number> {\n    console.log(\"\\nAvailable boards for this user:\")\n    const boards = await deckClient.getBoards()\n    boards.forEach(b => console.log(`\\t${b.id}: ${b.title} (${b.owner.uid})`))\n    return readline.questionInt(\"Enter Board ID: \")\n}\n\nfunction convert(deckBoard: Board, stacks: Stack[]): [FBBoard[], Block[]] {\n    const boards: FBBoard[] = []\n    const blocks: Block[] = []\n\n    // Board\n    const board = createBoard()\n    console.log(`Board: ${deckBoard.title}`)\n    board.title = deckBoard.title\n\n    let colorIndex = 0\n    // Convert stacks (columns) to a Select property\n    const stackOptionsIdMap = new Map<number, string>()\n    const stackOptions: IPropertyOption[] = []\n    stacks.forEach(stack => {\n        const optionId = Utils.createGuid()\n        stackOptionsIdMap.set(stack.id, optionId)\n        const color = optionColors[colorIndex % optionColors.length]\n        colorIndex += 1\n        const option: IPropertyOption = {\n            id: optionId,\n            value: stack.title,\n            color,\n        }\n        stackOptions.push(option)\n    })\n    const stackProperty: IPropertyTemplate = {\n        id: Utils.createGuid(),\n        name: 'List',\n        type: 'select',\n        options: stackOptions\n    }\n\n    // Convert labels (tags) to a Select property\n    const labelOptionsIdMap = new Map<number, string>()\n    const labelOptions: IPropertyOption[] = []\n    deckBoard.labels.forEach(label => {\n        const optionId = Utils.createGuid()\n        labelOptionsIdMap.set(label.id, optionId)\n        const color = optionColors[colorIndex % optionColors.length]\n        colorIndex += 1\n        const option: IPropertyOption = {\n            id: optionId,\n            value: label.title,\n            color,\n        }\n        labelOptions.push(option)\n    })\n    const labelProperty: IPropertyTemplate = {\n        id: Utils.createGuid(),\n        name: 'Label',\n        type: 'multiSelect',\n        options: labelOptions\n    }\n    const dueDateProperty: IPropertyTemplate = {\n        id: Utils.createGuid(),\n        name: 'Due Date',\n        type: 'date',\n        options: []\n    }\n\n    board.cardProperties = [stackProperty, labelProperty, dueDateProperty]\n    boards.push(board)\n\n    // Board view\n    const view = createBoardView()\n    view.title = 'Board View'\n    view.fields.viewType = 'board'\n    view.boardId = board.id\n    view.parentId = board.id\n    blocks.push(view)\n\n    // Cards\n    stacks.forEach(stack =>\n        stack.cards.forEach(\n            card => {\n                console.log(`Card: ${card.title}`)\n\n                const outCard = createCard()\n                outCard.title = card.title\n                outCard.boardId = board.id\n                outCard.parentId = board.id\n\n                // Map Stacks to Select property options\n                const stackOptionId = stackOptionsIdMap.get(card.stackId)\n                if (stackOptionId) {\n                    outCard.fields.properties[stackProperty.id] = stackOptionId\n                } else {\n                    console.warn(`Invalid idList: ${card.stackId} for card: ${card.title}`)\n                }\n                // Map Labels to Multiselect options\n                outCard.fields.properties[labelProperty.id] = card.labels?.map(label => labelOptionsIdMap.get(label.id)).filter((id): id is string => !!id)\n\n                // Add duedate\n                if (card.duedate) {\n                    const duedate = new Date(card.duedate)\n                    outCard.fields.properties[dueDateProperty.id] = `{\\\"from\\\":${duedate.getTime()}}`\n                }\n\n                blocks.push(outCard)\n\n                // Description\n                if (card.description) {\n                    const text = createTextBlock()\n                    text.title = card.description\n                    text.boardId = board.id\n                    text.parentId = outCard.id\n                    blocks.push(text)\n\n                    outCard.fields.contentOrder = [text.id]\n                }\n\n                // Add Comments (Author cannot be determined since uid's are different)\n                card.comments?.forEach(comment => {\n                    const commentBlock = createCommentBlock()\n                    commentBlock.title = comment.message\n                    commentBlock.boardId = board.id\n                    commentBlock.parentId = outCard.id\n                    blocks.push(commentBlock)\n                })\n\n            })\n    )\n    console.log('')\n    console.log(`Transformed Board ${deckBoard.title} into ${blocks.length} blocks.`)\n\n    return [boards, blocks]\n}\n\nfunction showHelp() {\n    console.log('import [--url <nextcloud-url>] [-u <username>] [-p <password>] [-o <output-path>]')\n    exit(1)\n}\n\nmain()\n"
  },
  {
    "path": "import/nextcloud-deck/package.json",
    "content": "{\n  \"name\": \"focalboard-nextcloud-deck-importer\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"description\": \"\",\n  \"main\": \"importDeck.js\",\n  \"scripts\": {\n    \"lint\": \"eslint --ext .tsx,.ts . --quiet --cache\",\n    \"fix\": \"eslint --ext .tsx,.ts . --quiet --fix --cache\",\n    \"test\": \"ts-node importTrello.ts -i test/trello.json -o test/trello-import.focalboard\",\n    \"debug:test\": \"node --inspect=5858 -r ts-node/register importTrello.ts -i test/trello.json -o test/trello-import.focalboard\"\n  },\n  \"keywords\": [],\n  \"author\": \"\",\n  \"devDependencies\": {\n    \"@types/minimist\": \"^1.2.1\",\n    \"@types/node\": \"^14.14.28\",\n    \"@types/node-fetch\": \"^2.5.0\",\n    \"@types/readline-sync\": \"^1.4.4\",\n    \"@typescript-eslint/eslint-plugin\": \"^4.15.0\",\n    \"@typescript-eslint/parser\": \"^4.15.0\",\n    \"eslint\": \"^7.20.0\",\n    \"minimist\": \"^1.2.6\",\n    \"node-fetch\": \"^2.6.7\",\n    \"readline-sync\": \"^1.4.10\",\n    \"ts-node\": \"^10.4.0\",\n    \"typescript\": \"^4.5.5\"\n  }\n}\n"
  },
  {
    "path": "import/nextcloud-deck/tsconfig.json",
    "content": "{\n\t\"compilerOptions\": {\n\t\t\"jsx\": \"react\",\n\t\t\"target\": \"es2019\",\n\t\t\"module\": \"commonjs\",\n\t\t\"esModuleInterop\": true,\n\t\t\"noImplicitAny\": true,\n\t\t\"strict\": true,\n\t\t\"strictNullChecks\": true,\n\t\t\"forceConsistentCasingInFileNames\": true,\n\t\t\"sourceMap\": true,\n\t\t\"allowJs\": true,\n\t\t\"resolveJsonModule\": true,\n\t\t\"incremental\": false,\n\t\t\"outDir\": \"./dist\",\n\t\t\"moduleResolution\": \"node\"\n\t},\n\t\"include\": [\n\t\t\".\"\n\t],\n\t\"exclude\": [\n        \".git\",\n\t\t\"**/node_modules/*\",\n\t\t\"dist\",\n\t\t\"pack\"\n    ]\n}\n"
  },
  {
    "path": "import/nextcloud-deck/utils.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport * as crypto from 'crypto'\n\nclass Utils {\n    static createGuid(): string {\n        function randomDigit() {\n            if (crypto && crypto.randomBytes) {\n                const rands = crypto.randomBytes(1)\n                return (rands[0] % 16).toString(16)\n            }\n\n            return (Math.floor((Math.random() * 16))).toString(16)\n        }\n        return 'xxxxxxxx-xxxx-4xxx-8xxx-xxxxxxxxxxxx'.replace(/x/g, randomDigit)\n    }\n}\n\nexport { Utils }\n"
  },
  {
    "path": "import/notion/.eslintrc.json",
    "content": "{\n  \"extends\": [\n  ],\n  \"plugins\": [\n  ],\n  \"parser\": \"@typescript-eslint/parser\",\n  \"env\": {\n    \"jest\": true\n  },\n  \"settings\": {\n    \"import/resolver\": \"webpack\",\n    \"react\": {\n        \"pragma\": \"React\",\n        \"version\": \"detect\"\n    }\n  },\n  \"rules\": {\n    \"no-unused-expressions\": 0,\n    \"eol-last\": [\"error\", \"always\"],\n    \"import/no-unresolved\": 2,\n    \"no-undefined\": 0,\n    \"react/jsx-filename-extension\": 0,\n    \"max-nested-callbacks\": [\"error\", {\"max\": 5}]\n  },\n  \"overrides\": [\n    {\n      \"files\": [\"**/*.tsx\", \"**/*.ts\"],\n      \"extends\": [\n        \"plugin:@typescript-eslint/recommended\"\n      ],\n      \"rules\": {\n        \"import/no-unresolved\": 0, // ts handles this better\n\t\t\"camelcase\": 0,\n\t\t\"semi\": \"off\",\n        \"@typescript-eslint/naming-convention\": [\n          2,\n          {\n            \"selector\": \"function\",\n            \"format\": [\"camelCase\", \"PascalCase\"]\n          },\n          {\n            \"selector\": \"variable\",\n            \"format\": [\"camelCase\", \"PascalCase\", \"UPPER_CASE\"]\n          },\n          {\n            \"selector\": \"parameter\",\n            \"format\": [\"camelCase\", \"PascalCase\"],\n            \"leadingUnderscore\": \"allow\"\n          },\n          {\n            \"selector\": \"typeLike\",\n            \"format\": [\"PascalCase\"]\n          }\n        ],\n        \"@typescript-eslint/no-non-null-assertion\": 0,\n        \"@typescript-eslint/no-unused-vars\": [\n          2,\n          {\n            \"vars\": \"all\",\n            \"args\": \"after-used\"\n          }\n        ],\n        \"@typescript-eslint/no-var-requires\": 0,\n        \"@typescript-eslint/no-empty-function\": 0,\n        \"@typescript-eslint/prefer-interface\": 0,\n        \"@typescript-eslint/explicit-function-return-type\": 0,\n        \"@typescript-eslint/semi\": [2, \"never\"],\n\t\t\"@typescript-eslint/indent\": [\n          2,\n          4,\n          {\n            \"SwitchCase\": 0\n          }\n        ],\n        \"no-use-before-define\": \"off\",\n        \"@typescript-eslint/no-use-before-define\": [\n          2,\n          {\n            \"classes\": false,\n            \"functions\": false,\n            \"variables\": false\n          }\n        ],\n        \"no-useless-constructor\": 0,\n        \"@typescript-eslint/no-useless-constructor\": 2,\n        \"react/jsx-filename-extension\": 0\n      }\n    },\n    {\n      \"files\": [\"tests/**\", \"**/*.test.*\"],\n      \"env\": {\n        \"jest\": true\n      },\n      \"rules\": {\n        \"func-names\": 0,\n        \"global-require\": 0,\n        \"new-cap\": 0,\n        \"prefer-arrow-callback\": 0,\n        \"no-import-assign\": 0\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "import/notion/.gitignore",
    "content": "test\n"
  },
  {
    "path": "import/notion/README.md",
    "content": "# Notion importer\n\nThis node app converts a Notion CSV and markdown export into a Focalboard archive. To use:\n1. From a Notion Board, open the ... menu at the top right\n2. Select `Export`, pick `Markdown & CSV` as the export format, select true to include subpages.\n3. Save it locally, and unzip the folder e.g. to `notion-export`\n4. Run `npm install` from within `focalboard/webapp`\n5. Run `npm install` from within `focalboard/import/notion`\n6. Run `npx ts-node importNotion.ts -i <path to the notion-export folder> -o archive.boardarchive`\n7. In Focalboard, click `Settings`, then `Import archive` and select `archive.boardarchive`\n\n## Import scope\n\nCurrently, the script imports all cards from a single board, including their properties and markdown content.\n\nThe Notion export format does not preserve property types, so the script currently imports all card properties as a Select type. You can change the type after importing into Focalboard.\n\n[Contribute code](https://mattermost.github.io/focalboard/) to expand this.\n"
  },
  {
    "path": "import/notion/importNotion.ts",
    "content": "import csv from 'csvtojson'\nimport * as fs from 'fs'\nimport minimist from 'minimist'\nimport path from 'path'\nimport {exit} from 'process'\nimport {ArchiveUtils} from '../util/archive'\nimport {Block} from '../../webapp/src/blocks/block'\nimport {Board} from '../../webapp/src/blocks/board'\nimport {IPropertyTemplate, createBoard} from '../../webapp/src/blocks/board'\nimport {createBoardView} from '../../webapp/src/blocks/boardView'\nimport {createCard} from '../../webapp/src/blocks/card'\nimport {createTextBlock} from '../../webapp/src/blocks/textBlock'\nimport {Utils} from './utils'\n\n// HACKHACK: To allow Utils.CreateGuid to work\n(global.window as any) = {}\n\nlet markdownFolder: string\n\nconst optionColors = [\n    // 'propColorDefault',\n    'propColorGray',\n    'propColorBrown',\n    'propColorOrange',\n    'propColorYellow',\n    'propColorGreen',\n    'propColorBlue',\n    'propColorPurple',\n    'propColorPink',\n    'propColorRed',\n]\nlet optionColorIndex = 0\n\nasync function main() {\n    const args: minimist.ParsedArgs = minimist(process.argv.slice(2))\n\n    const inputFolder = args['i']\n    const outputFile = args['o'] || 'archive.boardarchive'\n\n    if (!inputFolder) {\n        showHelp()\n    }\n\n    if (!fs.existsSync(inputFolder)){\n        console.log(`Folder not found: ${inputFolder}`)\n        exit(2)\n    }\n\n    const inputFile = getCsvFilePath(inputFolder)\n    if (!inputFile) {\n        console.log(`.csv file not found in folder: ${inputFolder}`)\n        exit(2)\n    }\n\n    console.log(`inputFile: ${inputFile}`)\n\n    // Read input\n    const input = await csv().fromFile(inputFile)\n\n    console.log(`Read ${input.length} rows.`)\n\n    console.log(input)\n\n    const basename = path.basename(inputFile, '.csv')\n    const components = basename.split(' ')\n    components.pop()\n    const title = components.join(' ')\n\n    console.log(`title: ${title}`)\n\n    markdownFolder = path.join(inputFolder, basename)\n\n    // Convert\n    const [boards, blocks] = convert(input, title)\n\n    // Save output\n    // TODO: Stream output\n    const outputData = ArchiveUtils.buildBlockArchive(boards, blocks)\n    fs.writeFileSync(outputFile, outputData)\n\n    console.log(`Exported to ${outputFile}`)\n}\n\nfunction getCsvFilePath(inputFolder: string): string | undefined {\n    const files = fs.readdirSync(inputFolder)\n    const file = files.find(o => path.extname(o).toLowerCase() === '.csv')\n\n    return file ? path.join(inputFolder, file) : undefined\n}\n\nfunction getMarkdown(cardTitle: string): string | undefined {\n    if (!fs.existsSync(markdownFolder)){ return undefined}\n    const files = fs.readdirSync(markdownFolder)\n    const file = files.find((o) => {\n        const basename = path.basename(o)\n        const components = basename.split(' ')\n        const fileCardTitle = components.slice(0, components.length-1).join(' ')\n        if (fileCardTitle === cardTitle) {\n            return o\n        }\n    })\n\n    if (file) {\n        const filePath = path.join(markdownFolder, file)\n        const markdown = fs.readFileSync(filePath, 'utf-8')\n\n        // TODO: Remove header from markdown, which repets card title and properties\n        return markdown\n    }\n\n    return undefined\n}\n\nfunction getColumns(input: any[]) {\n    const row = input[0]\n    const keys = Object.keys(row)\n    // The first key (column) is the card title\n    return keys.slice(1)\n}\n\nfunction convert(input: any[], title: string): [Board[], Block[]] {\n    const boards: Board[] = []\n    const blocks: Block[] = []\n\n    // Board\n    const board = createBoard()\n    console.log(`Board: ${title}`)\n    board.title = title\n\n    // Each column is a card property\n    const columns = getColumns(input)\n    columns.forEach(column => {\n        const cardProperty: IPropertyTemplate = {\n            id: Utils.createGuid(),\n            name: column,\n            type: 'select',\n            options: []\n        }\n        board.cardProperties.push(cardProperty)\n    })\n\n    // Set all column types to select\n    // TODO: Detect column type\n    boards.push(board)\n\n    // Board view\n    const view = createBoardView()\n    view.title = 'Board View'\n    view.fields.viewType = 'board'\n    view.boardId = board.id\n    view.parentId = board.id\n    blocks.push(view)\n\n    // Cards\n    input.forEach(row => {\n        const keys = Object.keys(row)\n        console.log(keys)\n        if (keys.length < 1) {\n            console.error(`Expected at least one column`)\n            return blocks\n        }\n\n        const titleKey = keys[0]\n        const title = row[titleKey]\n\n        console.log(`Card: ${title}`)\n\n        const outCard = createCard()\n        outCard.title = title\n        outCard.boardId = board.id\n        outCard.parentId = board.id\n\n        // Card properties, skip first key which is the title\n        for (const key of keys.slice(1)) {\n            const value = row[key]\n            if (!value) {\n                // Skip empty values\n                continue\n            }\n\n            const cardProperty = board.cardProperties.find((o) => o.name === key)!\n            let option = cardProperty.options.find((o) => o.value === value)\n            if (!option) {\n                const color = optionColors[optionColorIndex % optionColors.length]\n                optionColorIndex += 1\n                option = {\n                    id: Utils.createGuid(),\n                    value,\n                    color: color,\n                }\n                cardProperty.options.push(option)\n            }\n\n            outCard.fields.properties[cardProperty.id] = option.id\n        }\n\n        blocks.push(outCard)\n\n        // Card notes from markdown\n        const markdown = getMarkdown(title)\n        if (markdown) {\n            console.log(`Markdown: ${markdown.length} bytes`)\n            const text = createTextBlock()\n            text.title = markdown\n            text.boardId = board.id\n            text.parentId = outCard.id\n            blocks.push(text)\n\n            outCard.fields.contentOrder = [text.id]\n        }\n    })\n\n    console.log('')\n    console.log(`Found ${input.length} card(s).`)\n\n    return [boards, blocks]\n}\n\nfunction showHelp() {\n    console.log('import -i <input.json> -o [output.boardarchive]')\n    exit(1)\n}\n\nmain()\n"
  },
  {
    "path": "import/notion/package.json",
    "content": "{\n  \"name\": \"focalboard-notion-importer\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"description\": \"\",\n  \"main\": \"importNotion.js\",\n  \"scripts\": {\n    \"lint\": \"eslint --ext .tsx,.ts . --quiet --cache\",\n    \"fix\": \"eslint --ext .tsx,.ts . --quiet --fix --cache\",\n    \"test\": \"ts-node importNotion.ts -i test/export -o test/notion-import.focalboard\",\n    \"debug:test\": \"node --inspect=5858 -r ts-node/register importNotion.ts -i test/export -o test/notion-import.focalboard\"\n  },\n  \"keywords\": [],\n  \"author\": \"\",\n  \"devDependencies\": {\n    \"@types/minimist\": \"^1.2.1\",\n    \"@types/node\": \"^14.14.28\",\n    \"@typescript-eslint/eslint-plugin\": \"^4.15.0\",\n    \"@typescript-eslint/parser\": \"^4.15.0\",\n    \"eslint\": \"^7.20.0\",\n    \"ts-node\": \"^9.1.1\",\n    \"typescript\": \"^4.1.5\"\n  },\n  \"dependencies\": {\n    \"csvtojson\": \"^2.0.10\",\n    \"minimist\": \"^1.2.6\"\n  }\n}\n"
  },
  {
    "path": "import/notion/tsconfig.json",
    "content": "{\n\t\"compilerOptions\": {\n\t\t\"jsx\": \"react\",\n\t\t\"target\": \"es2019\",\n\t\t\"module\": \"commonjs\",\n\t\t\"esModuleInterop\": true,\n\t\t\"noImplicitAny\": true,\n\t\t\"strict\": true,\n\t\t\"strictNullChecks\": true,\n\t\t\"forceConsistentCasingInFileNames\": true,\n\t\t\"sourceMap\": true,\n\t\t\"allowJs\": true,\n\t\t\"resolveJsonModule\": true,\n\t\t\"incremental\": false,\n\t\t\"outDir\": \"./dist\",\n\t\t\"moduleResolution\": \"node\"\n\t},\n\t\"include\": [\n\t\t\".\"\n\t],\n\t\"exclude\": [\n        \".git\",\n\t\t\"**/node_modules/*\",\n\t\t\"dist\",\n\t\t\"pack\"\n    ]\n}\n"
  },
  {
    "path": "import/notion/utils.ts",
    "content": "import * as crypto from 'crypto'\n\nclass Utils {\n    static createGuid(): string {\n        function randomDigit() {\n            if (crypto && crypto.randomBytes) {\n                const rands = crypto.randomBytes(1)\n                return (rands[0] % 16).toString(16)\n            }\n\n            return (Math.floor((Math.random() * 16))).toString(16)\n        }\n        return 'xxxxxxxx-xxxx-4xxx-8xxx-xxxxxxxxxxxx'.replace(/x/g, randomDigit)\n    }\n}\n\nexport { Utils }\n"
  },
  {
    "path": "import/todoist/.eslintrc.json",
    "content": "{\n  \"extends\": [\n  ],\n  \"plugins\": [\n  ],\n  \"parser\": \"@typescript-eslint/parser\",\n  \"env\": {\n    \"jest\": true\n  },\n  \"settings\": {\n    \"import/resolver\": \"webpack\",\n    \"react\": {\n        \"pragma\": \"React\",\n        \"version\": \"detect\"\n    }\n  },\n  \"rules\": {\n    \"no-unused-expressions\": 0,\n    \"eol-last\": [\"error\", \"always\"],\n    \"import/no-unresolved\": 2,\n    \"no-undefined\": 0,\n    \"react/jsx-filename-extension\": 0,\n    \"max-nested-callbacks\": [\"error\", {\"max\": 5}]\n  },\n  \"overrides\": [\n    {\n      \"files\": [\"**/*.tsx\", \"**/*.ts\"],\n      \"extends\": [\n        \"plugin:@typescript-eslint/recommended\"\n      ],\n      \"rules\": {\n        \"import/no-unresolved\": 0, // ts handles this better\n\t\t\"camelcase\": 0,\n\t\t\"semi\": \"off\",\n        \"@typescript-eslint/naming-convention\": [\n          2,\n          {\n            \"selector\": \"function\",\n            \"format\": [\"camelCase\", \"PascalCase\"]\n          },\n          {\n            \"selector\": \"variable\",\n            \"format\": [\"camelCase\", \"PascalCase\", \"UPPER_CASE\"]\n          },\n          {\n            \"selector\": \"parameter\",\n            \"format\": [\"camelCase\", \"PascalCase\"],\n            \"leadingUnderscore\": \"allow\"\n          },\n          {\n            \"selector\": \"typeLike\",\n            \"format\": [\"PascalCase\"]\n          }\n        ],\n        \"@typescript-eslint/no-non-null-assertion\": 0,\n        \"@typescript-eslint/no-unused-vars\": [\n          2,\n          {\n            \"vars\": \"all\",\n            \"args\": \"after-used\"\n          }\n        ],\n        \"@typescript-eslint/no-var-requires\": 0,\n        \"@typescript-eslint/no-empty-function\": 0,\n        \"@typescript-eslint/prefer-interface\": 0,\n        \"@typescript-eslint/explicit-function-return-type\": 0,\n        \"@typescript-eslint/semi\": [2, \"never\"],\n\t\t\"@typescript-eslint/indent\": [\n          2,\n          4,\n          {\n            \"SwitchCase\": 0\n          }\n        ],\n        \"no-use-before-define\": \"off\",\n        \"@typescript-eslint/no-use-before-define\": [\n          2,\n          {\n            \"classes\": false,\n            \"functions\": false,\n            \"variables\": false\n          }\n        ],\n        \"no-useless-constructor\": 0,\n        \"@typescript-eslint/no-useless-constructor\": 2,\n        \"react/jsx-filename-extension\": 0\n      }\n    },\n    {\n      \"files\": [\"tests/**\", \"**/*.test.*\"],\n      \"env\": {\n        \"jest\": true\n      },\n      \"rules\": {\n        \"func-names\": 0,\n        \"global-require\": 0,\n        \"new-cap\": 0,\n        \"prefer-arrow-callback\": 0,\n        \"no-import-assign\": 0\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "import/todoist/.gitignore",
    "content": "test\n"
  },
  {
    "path": "import/todoist/README.md",
    "content": "# Todoist importer\n\nThis node app converts a Todoist json archive into a Focalboard archive. To use:\n1. Visit the open source Todoist data export service at https://darekkay.com/todoist-export/.\n1. Select `JSON (all data)` in **Export As** option.\n1. Uncheck the **Archived** option if checked.\n1. Click on **Authorize and Backup**. This wil take you to your Todoist account. Follow the instructions on screen.\n1. Note the name and location of the downloaded *json* file.\n3. Run `npm install` from within `focalboard/webapp`\n4. Run `npm install` from within `focalboard/import/todoist`\n5. Run `npx ts-node importTodoist.ts -i <path-to-todoist.json> -o archive.boardarchive` (also from within `focalboard/import/todoist`)\n6. In Focalboard, click `Settings`, then `Import archive` and select `archive.boardarchive`\n"
  },
  {
    "path": "import/todoist/importTodoist.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport * as fs from 'fs'\nimport minimist from 'minimist'\nimport {exit} from 'process'\nimport {ArchiveUtils} from '../util/archive'\nimport {Block} from '../../webapp/src/blocks/block'\nimport {Board} from '../../webapp/src/blocks/board'\nimport {IPropertyOption, IPropertyTemplate, createBoard} from '../../webapp/src/blocks/board'\nimport {createBoardView} from '../../webapp/src/blocks/boardView'\nimport {createCard} from '../../webapp/src/blocks/card'\nimport {createTextBlock} from '../../webapp/src/blocks/textBlock'\nimport {Item, Project, Section, Todoist} from './todoist'\nimport {Utils} from './utils'\n\n// HACKHACK: To allow Utils.CreateGuid to work\n(global.window as any) = {}\n\nconst optionColors = [\n    // 'propColorDefault',\n    'propColorGray',\n    'propColorBrown',\n    'propColorOrange',\n    'propColorYellow',\n    'propColorGreen',\n    'propColorBlue',\n    'propColorPurple',\n    'propColorPink',\n    'propColorRed',\n]\nconst defaultSections = ['No Status', 'Next Up', 'In Progress', 'Completed', 'Archived'].map(title => {\n    return {\n        id: Utils.createGuid(),\n        name: title,\n    } as Section\n})\nlet noStatusSectionID: any\n\nlet optionColorIndex = 0\n\nfunction main() {\n    const args: minimist.ParsedArgs = minimist(process.argv.slice(2))\n\n    const inputFile = args['i']\n    const outputFile = args['o'] || 'archive.boardarchive'\n\n    if (!inputFile) {\n        showHelp()\n    }\n\n    if (!fs.existsSync(inputFile)) {\n        console.error(`File not found: ${inputFile}`)\n        exit(2)\n    }\n\n    // Read input\n    const inputData = fs.readFileSync(inputFile, 'utf-8')\n    const input = JSON.parse(inputData) as Todoist\n\n    const boards = [] as Board[]\n    const blocks = [] as Block[]\n\n    input.projects.forEach(project => {\n        const [brds, blks] = convert(input, project)\n        boards.push(...brds)\n        blocks.push(...blks)\n    })\n\n    // Save output\n    // TODO: Stream output\n    const outputData = ArchiveUtils.buildBlockArchive(boards, blocks)\n    fs.writeFileSync(outputFile, outputData)\n\n    console.log(`Exported to ${outputFile}`)\n}\n\nfunction convert(input: Todoist, project: Project): [Board[], Block[]] {\n    const boards: Board[] = []\n    const blocks: Block[] = []\n\n    if (project.name === 'Inbox') {\n        return [boards, blocks]\n    }\n\n    // Board\n    const board = createBoard()\n    console.log(`Board: ${project.name}`)\n    board.title = project.name\n    board.description = project.name\n\n    // Convert lists (columns) to a Select property\n    const optionIdMap = new Map<string, string>()\n    const options: IPropertyOption[] = []\n\n    const columns = getProjectColumns(input, project)\n    console.log(\"columns: \" + JSON.stringify(columns))\n\n    columns.forEach(list => {\n        const optionId = Utils.createGuid()\n        if (list.name === 'No Status') {\n            noStatusSectionID = list.id\n        }\n\n        optionIdMap.set(String(list.id), optionId)\n        const color = optionColors[optionColorIndex % optionColors.length]\n        optionColorIndex += 1\n        const option: IPropertyOption = {\n            id: optionId,\n            value: list.name,\n            color,\n        }\n        options.push(option)\n    })\n\n    const cardProperty: IPropertyTemplate = {\n        id: Utils.createGuid(),\n        name: 'List',\n        type: 'select',\n        options\n    }\n    board.cardProperties = [cardProperty]\n    boards.push(board)\n\n    // Board view\n    const view = createBoardView()\n    view.title = 'Board View'\n    view.fields.viewType = 'board'\n    view.boardId = board.id\n    view.parentId = board.id\n    blocks.push(view)\n\n    // Cards\n    const cards = getProjectCards(input, project)\n    cards.forEach(card => {\n        const outCard = createCard()\n        outCard.title = card.content\n        outCard.boardId = board.id\n        outCard.parentId = board.id\n\n        // Map lists to Select property options\n        const cardSectionId = card.section_id ? card.section_id : noStatusSectionID\n        const optionId = optionIdMap.get(String(cardSectionId))\n\n        if (optionId) {\n            outCard.fields.properties[cardProperty.id] = optionId\n        } else {\n            console.warn(`Invalid idList: ${cardSectionId} for card: ${card.content}`)\n        }\n\n        blocks.push(outCard)\n\n        // console.log(`\\t${card.desc}`)\n        const text = createTextBlock()\n        text.title = getCardDescription(input, card).join('\\n\\n')\n        text.boardId = board.id\n        text.parentId = outCard.id\n        blocks.push(text)\n\n        outCard.fields.contentOrder = [text.id]\n    })\n\n    return [boards, blocks]\n}\n\nfunction getProjectColumns(input: Todoist, project: Project): Array<Section> {\n    const sections = [{\n        id: noStatusSectionID,\n        name: 'No Section',\n    } as Section]\n    sections.push(...input.sections.filter(section => section.project_id === project.id))\n\n    return sections.length > 1 ? sections : defaultSections\n}\n\nfunction getProjectCards(input: Todoist, project: Project): Array<Item> {\n    return input.items.filter(item => item.project_id === project.id)\n}\n\nfunction getCardDescription(input: Todoist, item: Item): Array<string> {\n    return input.notes.filter(note => note.item_id === item.id).map(item => {\n        let description = \"\"\n\n        if (item.content) {\n            description = item.content\n        }\n\n        if (item.file_attachment) {\n            description += `\\n\\nAttachment: [${item.file_attachment.title}](${item.file_attachment.url})`\n        }\n\n        return description\n    })\n}\n\nfunction showHelp() {\n    exit(1)\n}\n\nmain()\n"
  },
  {
    "path": "import/todoist/package.json",
    "content": "{\n  \"name\": \"focalboard-todoist-importer\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"description\": \"\",\n  \"main\": \"importTodoist.ts\",\n  \"scripts\": {\n    \"lint\": \"eslint --ext .tsx,.ts . --quiet --cache\",\n    \"fix\": \"eslint --ext .tsx,.ts . --quiet --fix --cache\",\n    \"test\": \"ts-node importTodoist.ts -i test/todoist.json -o test/todoist-import.focalboard\",\n    \"debug:test\": \"node --inspect=5858 -r ts-node/register importTodoist.ts -i test/todoist.json -o test/todoist-import.focalboard\"\n  },\n  \"keywords\": [],\n  \"author\": \"\",\n  \"devDependencies\": {\n    \"@types/minimist\": \"^1.2.1\",\n    \"@types/node\": \"^14.14.28\",\n    \"@typescript-eslint/eslint-plugin\": \"^4.15.0\",\n    \"@typescript-eslint/parser\": \"^4.15.0\",\n    \"eslint\": \"^7.20.0\",\n    \"ts-node\": \"^9.1.1\",\n    \"typescript\": \"^4.1.5\"\n  },\n  \"dependencies\": {\n    \"minimist\": \"^1.2.6\"\n  }\n}\n"
  },
  {
    "path": "import/todoist/todoist.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n/* eslint-disable @typescript-eslint/no-empty-interface */\n// Generated by https://quicktype.io\n\nexport interface Todoist {\n    collaborator_states:             any[];\n    collaborators:                   any[];\n    day_orders:                      DayOrders;\n    day_orders_timestamp:            string;\n    due_exceptions:                  any[];\n    filters:                         Filter[];\n    full_sync:                       boolean;\n    incomplete_item_ids:             any[];\n    incomplete_project_ids:          any[];\n    items:                           Item[];\n    labels:                          any[];\n    live_notifications:              any[];\n    live_notifications_last_read_id: number;\n    locations:                       any[];\n    notes:                           Note[];\n    project_notes:                   any[];\n    projects:                        Project[];\n    reminders:                       any[];\n    sections:                        Section[];\n    stats:                           Stats;\n    sync_token:                      string;\n    temp_id_mapping:                 DayOrders;\n    tooltips:                        Tooltips;\n    user:                            User;\n    user_plan_limits:                UserPlanLimits;\n    user_settings:                   UserSettings;\n    view_options:                    any[];\n}\n\nexport interface DayOrders {\n}\n\nexport interface Filter {\n    color:       number;\n    id:          number;\n    is_deleted:  number;\n    is_favorite: number;\n    item_order:  number;\n    name:        string;\n    query:       string;\n}\n\nexport interface Item {\n    added_by_uid:    number;\n    assigned_by_uid: null;\n    checked:         number;\n    child_order:     number;\n    collapsed:       number;\n    content:         string;\n    date_added:      Date;\n    date_completed:  null;\n    day_order:       number;\n    due:             Due | null;\n    has_more_notes:  boolean;\n    id:              number;\n    in_history:      number;\n    is_deleted:      number;\n    labels:          any[];\n    parent_id:       number | null;\n    priority:        number;\n    project_id:      number;\n    responsible_uid: null;\n    section_id:      number;\n    sync_id:         null;\n    user_id:         number;\n}\n\nexport interface Due {\n    date:         Date;\n    is_recurring: boolean;\n    lang:         string;\n    string:       string;\n    timezone:     null;\n}\n\nexport interface Note {\n    content:         string;\n    file_attachment: FileAttachment | null;\n    id:              number;\n    is_deleted:      number;\n    item_id:         number;\n    posted:          Date;\n    posted_uid:      number;\n    project_id:      number;\n    reactions:       null;\n    uids_to_notify:  null;\n}\n\nexport interface FileAttachment {\n    description:   string;\n    favicon:       string;\n    image:         string;\n    image_height:  number;\n    image_width:   number;\n    resource_type: string;\n    site_name:     string;\n    title:         string;\n    url:           string;\n}\n\nexport interface Project {\n    child_order:    number;\n    collapsed:      number;\n    color:          number;\n    has_more_notes: boolean;\n    id:             number;\n    inbox_project?: boolean;\n    is_archived:    number;\n    is_deleted:     number;\n    is_favorite:    number;\n    name:           string;\n    parent_id:      null;\n    shared:         boolean;\n    sync_id:        null;\n}\n\nexport interface Section {\n    collapsed:     boolean;\n    date_added:    Date;\n    date_archived: null;\n    id:            number | string;\n    is_archived:   boolean;\n    is_deleted:    boolean;\n    name:          string;\n    project_id:    number;\n    section_order: number;\n    sync_id:       null;\n    user_id:       number;\n}\n\nexport interface Stats {\n    completed_count: number;\n    days_items:      DaysItem[];\n    week_items:      WeekItem[];\n}\n\nexport interface DaysItem {\n    date:            Date;\n    total_completed: number;\n}\n\nexport interface WeekItem {\n    from:            Date;\n    to:              Date;\n    total_completed: number;\n}\n\nexport interface Tooltips {\n    scheduled: string[];\n    seen:      string[];\n}\n\nexport interface User {\n    auto_reminder:           number;\n    business_account_id:     null;\n    daily_goal:              number;\n    date_format:             number;\n    dateist_inline_disabled: boolean;\n    dateist_lang:            null;\n    days_off:                number[];\n    default_reminder:        string;\n    email:                   string;\n    features:                Features;\n    full_name:               string;\n    id:                      number;\n    image_id:                null;\n    inbox_project:           number;\n    is_biz_admin:            boolean;\n    is_premium:              boolean;\n    join_date:               Date;\n    karma:                   number;\n    karma_trend:             string;\n    lang:                    string;\n    mobile_host:             null;\n    mobile_number:           null;\n    next_week:               number;\n    premium_until:           null;\n    share_limit:             number;\n    sort_order:              number;\n    start_day:               number;\n    start_page:              string;\n    theme:                   number;\n    time_format:             number;\n    tz_info:                 TzInfo;\n    unique_prefix:           number;\n    websocket_url:           string;\n    weekly_goal:             number;\n}\n\nexport interface Features {\n    beta:                    number;\n    dateist_inline_disabled: boolean;\n    dateist_lang:            null;\n    has_push_reminders:      boolean;\n    karma_disabled:          boolean;\n    karma_vacation:          boolean;\n    restriction:             number;\n}\n\nexport interface TzInfo {\n    gmt_string: string;\n    hours:      number;\n    is_dst:     number;\n    minutes:    number;\n    timezone:   string;\n}\n\nexport interface UserPlanLimits {\n    current: Current;\n    next:    Current;\n}\n\nexport interface Current {\n    activity_log:           boolean;\n    activity_log_limit:     number;\n    automatic_backups:      boolean;\n    calendar_feeds:         boolean;\n    comments:               boolean;\n    completed_tasks:        boolean;\n    customization_color:    boolean;\n    email_forwarding:       boolean;\n    filters:                boolean;\n    labels:                 boolean;\n    max_collaborators:      number;\n    max_filters:            number;\n    max_labels:             number;\n    max_projects:           number;\n    max_reminders_location: number;\n    max_reminders_time:     number;\n    max_sections:           number;\n    max_tasks:              number;\n    plan_name:              string;\n    reminders:              boolean;\n    templates:              boolean;\n    upload_limit_mb:        number;\n    uploads:                boolean;\n    weekly_trends:          boolean;\n}\n\nexport interface UserSettings {\n    completed_sound_desktop: boolean;\n    completed_sound_mobile:  boolean;\n    legacy_pricing:          boolean;\n    reminder_desktop:        boolean;\n    reminder_email:          boolean;\n    reminder_push:           boolean;\n    sound_on_completed:      boolean;\n}\n"
  },
  {
    "path": "import/todoist/tsconfig.json",
    "content": "{\n\t\"compilerOptions\": {\n\t\t\"jsx\": \"react\",\n\t\t\"target\": \"es2019\",\n\t\t\"module\": \"commonjs\",\n\t\t\"esModuleInterop\": true,\n\t\t\"noImplicitAny\": true,\n\t\t\"strict\": true,\n\t\t\"strictNullChecks\": true,\n\t\t\"forceConsistentCasingInFileNames\": true,\n\t\t\"sourceMap\": true,\n\t\t\"allowJs\": true,\n\t\t\"resolveJsonModule\": true,\n\t\t\"incremental\": false,\n\t\t\"outDir\": \"./dist\",\n\t\t\"moduleResolution\": \"node\"\n\t},\n\t\"include\": [\n\t\t\".\"\n\t],\n\t\"exclude\": [\n        \".git\",\n\t\t\"**/node_modules/*\",\n\t\t\"dist\",\n\t\t\"pack\"\n    ]\n}\n"
  },
  {
    "path": "import/todoist/utils.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport * as crypto from 'crypto'\n\nclass Utils {\n    static createGuid(): string {\n        function randomDigit() {\n            if (crypto && crypto.randomBytes) {\n                const rands = crypto.randomBytes(1)\n                return (rands[0] % 16).toString(16)\n            }\n\n            return (Math.floor((Math.random() * 16))).toString(16)\n        }\n        return 'xxxxxxxx-xxxx-4xxx-8xxx-xxxxxxxxxxxx'.replace(/x/g, randomDigit)\n    }\n}\n\nexport { Utils }\n"
  },
  {
    "path": "import/trello/.eslintrc.json",
    "content": "{\n  \"extends\": [\n  ],\n  \"plugins\": [\n  ],\n  \"parser\": \"@typescript-eslint/parser\",\n  \"env\": {\n    \"jest\": true\n  },\n  \"settings\": {\n    \"import/resolver\": \"webpack\",\n    \"react\": {\n        \"pragma\": \"React\",\n        \"version\": \"detect\"\n    }\n  },\n  \"rules\": {\n    \"no-unused-expressions\": 0,\n    \"eol-last\": [\"error\", \"always\"],\n    \"import/no-unresolved\": 2,\n    \"no-undefined\": 0,\n    \"react/jsx-filename-extension\": 0,\n    \"max-nested-callbacks\": [\"error\", {\"max\": 5}]\n  },\n  \"overrides\": [\n    {\n      \"files\": [\"**/*.tsx\", \"**/*.ts\"],\n      \"extends\": [\n        \"plugin:@typescript-eslint/recommended\"\n      ],\n      \"rules\": {\n        \"import/no-unresolved\": 0, // ts handles this better\n\t\t\"camelcase\": 0,\n\t\t\"semi\": \"off\",\n        \"@typescript-eslint/naming-convention\": [\n          2,\n          {\n            \"selector\": \"function\",\n            \"format\": [\"camelCase\", \"PascalCase\"]\n          },\n          {\n            \"selector\": \"variable\",\n            \"format\": [\"camelCase\", \"PascalCase\", \"UPPER_CASE\"]\n          },\n          {\n            \"selector\": \"parameter\",\n            \"format\": [\"camelCase\", \"PascalCase\"],\n            \"leadingUnderscore\": \"allow\"\n          },\n          {\n            \"selector\": \"typeLike\",\n            \"format\": [\"PascalCase\"]\n          }\n        ],\n        \"@typescript-eslint/no-non-null-assertion\": 0,\n        \"@typescript-eslint/no-unused-vars\": [\n          2,\n          {\n            \"vars\": \"all\",\n            \"args\": \"after-used\"\n          }\n        ],\n        \"@typescript-eslint/no-var-requires\": 0,\n        \"@typescript-eslint/no-empty-function\": 0,\n        \"@typescript-eslint/prefer-interface\": 0,\n        \"@typescript-eslint/explicit-function-return-type\": 0,\n        \"@typescript-eslint/semi\": [2, \"never\"],\n\t\t\"@typescript-eslint/indent\": [\n          2,\n          4,\n          {\n            \"SwitchCase\": 0\n          }\n        ],\n        \"no-use-before-define\": \"off\",\n        \"@typescript-eslint/no-use-before-define\": [\n          2,\n          {\n            \"classes\": false,\n            \"functions\": false,\n            \"variables\": false\n          }\n        ],\n        \"no-useless-constructor\": 0,\n        \"@typescript-eslint/no-useless-constructor\": 2,\n        \"react/jsx-filename-extension\": 0\n      }\n    },\n    {\n      \"files\": [\"tests/**\", \"**/*.test.*\"],\n      \"env\": {\n        \"jest\": true\n      },\n      \"rules\": {\n        \"func-names\": 0,\n        \"global-require\": 0,\n        \"new-cap\": 0,\n        \"prefer-arrow-callback\": 0,\n        \"no-import-assign\": 0\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "import/trello/.gitignore",
    "content": "test"
  },
  {
    "path": "import/trello/README.md",
    "content": "# Trello importer\n\nThis node app converts a Trello json archive into a Focalboard archive. To use:\n1. From the Trello Board Menu, `...Show Menu` on right\n2. Select `More`, then `Print and Export`, and `Export to JSON`\n3. Save it locally, e.g. to `trello.json`\n4. Run `npm install` from within `focalboard/webapp`\n5. Run `npm install` from within `focalboard/import/trello`\n6. Run `npx ts-node importTrello.ts -i <path-to-trello.json> -o archive.boardarchive` (also from within `focalboard/import/trello`)\n7. In Focalboard, click `Settings`, then `Import archive` and select `archive.boardarchive`\n\n## Import scope\n\nCurrently, the script imports all cards from a single board, including their list (column) membership, names, and descriptions. [Contribute code](https://mattermost.github.io/focalboard/) to expand this.\n\n\n"
  },
  {
    "path": "import/trello/importTrello.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport * as fs from 'fs'\nimport minimist from 'minimist'\nimport {exit} from 'process'\nimport {ArchiveUtils} from '../util/archive'\nimport {Block} from '../../webapp/src/blocks/block'\nimport {Board} from '../../webapp/src/blocks/board'\nimport {IPropertyOption, IPropertyTemplate, createBoard} from '../../webapp/src/blocks/board'\nimport {createBoardView} from '../../webapp/src/blocks/boardView'\nimport {createCard} from '../../webapp/src/blocks/card'\nimport {createTextBlock} from '../../webapp/src/blocks/textBlock'\nimport {createCheckboxBlock} from '../../webapp/src/blocks/checkboxBlock'\nimport {Trello} from './trello'\nimport {Utils} from './utils'\n\n// HACKHACK: To allow Utils.CreateGuid to work\n(global.window as any) = {}\n\nconst optionColors = [\n    // 'propColorDefault',\n    'propColorGray',\n    'propColorBrown',\n    'propColorOrange',\n    'propColorYellow',\n    'propColorGreen',\n    'propColorBlue',\n    'propColorPurple',\n    'propColorPink',\n    'propColorRed',\n]\nlet optionColorIndex = 0\n\nfunction main() {\n    const args: minimist.ParsedArgs = minimist(process.argv.slice(2))\n\n    const inputFile = args['i']\n    const outputFile = args['o'] || 'archive.boardarchive'\n\n    if (!inputFile) {\n        showHelp()\n    }\n\n    if (!fs.existsSync(inputFile)) {\n        console.error(`File not found: ${inputFile}`)\n        exit(2)\n    }\n\n    // Read input\n    const inputData = fs.readFileSync(inputFile, 'utf-8')\n    const input = JSON.parse(inputData) as Trello\n\n    // Convert\n    const [boards, blocks] = convert(input)\n\n    // Save output\n    // TODO: Stream output\n    const outputData = ArchiveUtils.buildBlockArchive(boards, blocks)\n    fs.writeFileSync(outputFile, outputData)\n\n    console.log(`Exported to ${outputFile}`)\n}\n\nfunction convert(input: Trello): [Board[], Block[]] {\n    const boards: Board[] = []\n    const blocks: Block[] = []\n\n    // Board\n    const board = createBoard()\n    console.log(`Board: ${input.name}`)\n    board.title = input.name\n    board.description = input.desc\n\n    // Convert lists (columns) to a Select property\n    const optionIdMap = new Map<string, string>()\n    const options: IPropertyOption[] = []\n    input.lists.forEach(list => {\n        const optionId = Utils.createGuid()\n        optionIdMap.set(list.id, optionId)\n        const color = optionColors[optionColorIndex % optionColors.length]\n        optionColorIndex += 1\n        const option: IPropertyOption = {\n            id: optionId,\n            value: list.name,\n            color,\n        }\n        options.push(option)\n    })\n\n    const cardProperty: IPropertyTemplate = {\n        id: Utils.createGuid(),\n        name: 'List',\n        type: 'select',\n        options\n    }\n    board.cardProperties = [cardProperty]\n    boards.push(board)\n\n    // Board view\n    const view = createBoardView()\n    view.title = 'Board View'\n    view.fields.viewType = 'board'\n    view.boardId = board.id\n    view.parentId = board.id\n    blocks.push(view)\n\n    // Cards\n    input.cards.forEach(card => {\n        console.log(`Card: ${card.name}`)\n\n        const outCard = createCard()\n        outCard.title = card.name\n        outCard.boardId = board.id\n        outCard.parentId = board.id\n\n        // Map lists to Select property options\n        if (card.idList) {\n            const optionId = optionIdMap.get(card.idList)\n            if (optionId) {\n                outCard.fields.properties[cardProperty.id] = optionId\n            } else {\n                console.warn(`Invalid idList: ${card.idList} for card: ${card.name}`)\n            }\n        } else {\n            console.warn(`Missing idList for card: ${card.name}`)\n        }\n\n        blocks.push(outCard)\n\n        if (card.desc) {\n            // console.log(`\\t${card.desc}`)\n            const text = createTextBlock()\n            text.title = card.desc\n            text.boardId = board.id\n            text.parentId = outCard.id\n            blocks.push(text)\n\n            outCard.fields.contentOrder = [text.id]\n        }\n\n        // Add Checklists\n        if (card.idChecklists && card.idChecklists.length > 0) {\n            card.idChecklists.forEach(checklistID => {\n                const lookup = input.checklists.find(e => e.id === checklistID)\n                if (lookup) {\n                    lookup.checkItems.forEach(trelloCheckBox=> {\n                        const checkBlock = createCheckboxBlock()\n                        checkBlock.title = trelloCheckBox.name\n                        if (trelloCheckBox.state === 'complete') {\n                            checkBlock.fields.value = true\n                        } else {\n                            checkBlock.fields.value = false\n                        }\n                        checkBlock.boardId = board.id\n                        checkBlock.parentId = outCard.id\n                        blocks.push(checkBlock)\n\n                        outCard.fields.contentOrder.push(checkBlock.id)\n                    })\n                }\n            })\n        }\n    })\n\n    console.log('')\n    console.log(`Found ${input.cards.length} card(s).`)\n\n    return [boards, blocks]\n}\n\nfunction showHelp() {\n    console.log('import -i <input.json> -o [output.boardarchive]')\n    exit(1)\n}\n\nmain()\n"
  },
  {
    "path": "import/trello/package.json",
    "content": "{\n  \"name\": \"focalboard-trello-importer\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"description\": \"\",\n  \"main\": \"importTrello.js\",\n  \"scripts\": {\n    \"lint\": \"eslint --ext .tsx,.ts . --quiet --cache\",\n    \"fix\": \"eslint --ext .tsx,.ts . --quiet --fix --cache\",\n    \"test\": \"ts-node importTrello.ts -i test/trello.json -o test/trello-import.focalboard\",\n    \"debug:test\": \"node --inspect=5858 -r ts-node/register importTrello.ts -i test/trello.json -o test/trello-import.focalboard\"\n  },\n  \"keywords\": [],\n  \"author\": \"\",\n  \"devDependencies\": {\n    \"@types/minimist\": \"^1.2.1\",\n    \"@types/node\": \"^14.14.28\",\n    \"@typescript-eslint/eslint-plugin\": \"^4.15.0\",\n    \"@typescript-eslint/parser\": \"^4.15.0\",\n    \"eslint\": \"^7.20.0\",\n    \"ts-node\": \"^9.1.1\",\n    \"typescript\": \"^4.1.5\"\n  },\n  \"dependencies\": {\n    \"minimist\": \"^1.2.6\"\n  }\n}\n"
  },
  {
    "path": "import/trello/trello.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n/* eslint-disable @typescript-eslint/no-empty-interface */\n// Generated by https://quicktype.io\n//\n// To change quicktype's target language, run command:\n//\n//   \"Set quicktype target language\"\n\nexport interface Trello {\n    id:                IDBoardEnum;\n    name:              BoardName;\n    desc:              string;\n    descData:          null;\n    closed:            boolean;\n    idOrganization:    null;\n    shortLink:         ShortLink;\n    powerUps:          any[];\n    dateLastActivity:  string;\n    idTags:            any[];\n    datePluginDisable: null;\n    creationMethod:    null;\n    idBoardSource:     string;\n    idMemberCreator:   null;\n    idEnterprise:      null;\n    pinned:            boolean;\n    starred:           boolean;\n    url:               string;\n    shortUrl:          string;\n    enterpriseOwned:   boolean;\n    premiumFeatures:   any[];\n    ixUpdate:          string;\n    limits:            TrelloLimits;\n    prefs:             Prefs;\n    subscribed:        boolean;\n    templateGallery:   null;\n    dateLastView:      string;\n    labelNames:        LabelNames;\n    actions:           Action[];\n    cards:             CardElement[];\n    labels:            Label[];\n    lists:             List[];\n    members:           MemberElement[];\n    checklists:        ChecklistElement[];\n    customFields:      CustomFieldElement[];\n    memberships:       Membership[];\n    pluginData:        PluginDatum[];\n}\n\nexport interface Action {\n    id:              string;\n    idMemberCreator: IDMemberCreator;\n    data:            Data;\n    type:            string;\n    date:            string;\n    appCreator:      AppCreator | null;\n    limits:          ActionLimits;\n    memberCreator:   MemberCreatorClass;\n    member?:         MemberCreatorClass;\n}\n\nexport interface AppCreator {\n    id:   string;\n    name: string;\n    icon: Icon;\n}\n\nexport interface Icon {\n    url: string;\n}\n\nexport interface Data {\n    old?:             Old;\n    customField?:     DataCustomField;\n    customFieldItem?: CustomFieldItem;\n    board:            Board;\n    card?:            DataCard;\n    list?:            ListClass;\n    listBefore?:      ListClass;\n    listAfter?:       ListClass;\n    idMember?:        IDMemberCreator;\n    member?:          ListClass;\n    fromCopy?:        boolean;\n    cardSource?:      Board;\n    deactivated?:     boolean;\n    text?:            string;\n    checklist?:       ListClass;\n    checkItem?:       DataCheckItem;\n    boardSource?:     BoardSource;\n}\n\nexport interface Board {\n    id:        IDBoardEnum;\n    name:      BoardName;\n    shortLink: ShortLink;\n    idShort?:  number;\n}\n\nexport enum IDBoardEnum {\n    The5F4800F49696D280D52Bb2Ff = \"5f4800f49696d280d52bb2ff\",\n    The5F58E6144A949F4C1879A32A = \"5f58e6144a949f4c1879a32a\",\n}\n\nexport enum BoardName {\n    AgileSprintBoard = \"Agile Sprint Board\",\n    StandardTask = \"Standard Task\",\n}\n\nexport enum ShortLink {\n    WduAIKhy = \"wduAiKhy\",\n    ZCbHMXU8 = \"ZCbHMxU8\",\n}\n\nexport interface BoardSource {\n    id: string;\n}\n\nexport interface DataCard {\n    id:           string;\n    name:         string;\n    idShort:      number;\n    shortLink:    string;\n    pos?:         number;\n    idList?:      IDMemberCreator;\n    due?:         string;\n    dueReminder?: number;\n    cover?:       OldCover;\n    desc?:        string;\n}\n\nexport interface OldCover {\n    color:                null | string;\n    idAttachment:         null;\n    idUploadedBackground: IDUploadedBackground | null;\n    size:                 MemberType;\n    brightness:           Brightness;\n    url?:                 string;\n}\n\nexport enum Brightness {\n    Dark = \"dark\",\n    Light = \"light\",\n}\n\nexport enum IDUploadedBackground {\n    The5F46Cbb00E54E3660C1A7B22 = \"5f46cbb00e54e3660c1a7b22\",\n    The5F46Cbe1C839Ef48989Cd124 = \"5f46cbe1c839ef48989cd124\",\n    The5F46Cbe8B0A6Bb3B7F91A0B8 = \"5f46cbe8b0a6bb3b7f91a0b8\",\n}\n\nexport enum MemberType {\n    Full = \"full\",\n    Normal = \"normal\",\n}\n\nexport enum IDMemberCreator {\n    The5F4800A4621Dfe2935798972 = \"5f4800a4621dfe2935798972\",\n    The5F4800F49696D280D52Bb300 = \"5f4800f49696d280d52bb300\",\n    The5F4800F49696D280D52Bb301 = \"5f4800f49696d280d52bb301\",\n    The5F4800F49696D280D52Bb302 = \"5f4800f49696d280d52bb302\",\n    The5F4800F49696D280D52Bb303 = \"5f4800f49696d280d52bb303\",\n    The5F4800F49696D280D52Bb304 = \"5f4800f49696d280d52bb304\",\n    The5F480131E778365Be477Add3 = \"5f480131e778365be477add3\",\n}\n\nexport interface DataCheckItem {\n    id:    string;\n    name:  string;\n    state: string;\n}\n\nexport interface ListClass {\n    id:   IDMemberCreator;\n    name: FullNameEnum;\n}\n\nexport enum FullNameEnum {\n    Backlog = \"Backlog\",\n    Checklist = \"Checklist\",\n    JohnSmith = \"John Smith\",\n    InProgress = \"In Progress\",\n    SprintBacklog = \"Sprint Backlog\",\n    The8217SprintComplete = \"8.2.17 Sprint - Complete\",\n    The8917SprintComplete = \"8.9.17 Sprint - Complete\",\n}\n\nexport interface DataCustomField {\n    id:    IDCustomFieldEnum;\n    name:  string;\n    type?: string;\n}\n\nexport enum IDCustomFieldEnum {\n    The5F4802F5905B9A640C49Be08 = \"5f4802f5905b9a640c49be08\",\n    The5F480309D1D96A703F2F3143 = \"5f480309d1d96a703f2f3143\",\n}\n\nexport interface CustomFieldItem {\n    id:            string;\n    value:         CustomFieldItemValue;\n    idCustomField: IDCustomFieldEnum;\n    idModel:       string;\n    modelType:     ModelType;\n}\n\nexport enum ModelType {\n    Card = \"card\",\n}\n\nexport interface CustomFieldItemValue {\n    number?:  string;\n    checked?: string;\n}\n\nexport interface Old {\n    value?:       OldValue | null;\n    pos?:         number;\n    idList?:      IDMemberCreator;\n    name?:        string;\n    due?:         null;\n    dueReminder?: null;\n    cover?:       OldCover;\n    desc?:        string;\n}\n\nexport interface OldValue {\n    number: string;\n}\n\nexport interface ActionLimits {\n    reactions?: Reactions;\n}\n\nexport interface Reactions {\n    perAction:       PerBoard;\n    uniquePerAction: PerBoard;\n}\n\nexport interface PerBoard {\n    status:    Status;\n    disableAt: number;\n    warnAt:    number;\n}\n\nexport enum Status {\n    Ok = \"ok\",\n}\n\nexport interface MemberCreatorClass {\n    id:                 IDMemberCreator;\n    username:           Username;\n    activityBlocked:    boolean;\n    avatarHash:         AvatarHash;\n    avatarUrl:          string;\n    fullName:           FullNameEnum;\n    idMemberReferrer:   null;\n    initials:           Initials;\n    nonPublic:          NonPublic;\n    nonPublicAvailable: boolean;\n}\n\nexport enum AvatarHash {\n    Ea6D6D7Da6B79Dc0Cf31301Bc672487F = \"ea6d6d7da6b79dc0cf31301bc672487f\",\n}\n\nexport enum Initials {\n    Cl = \"CL\",\n}\n\nexport interface NonPublic {\n    fullName:   FullNameEnum;\n    initials:   Initials;\n    avatarHash: null;\n}\n\nexport enum Username {\n    johnsmith = \"johnsmith\",\n}\n\nexport interface CardElement {\n    id:                    string;\n    address:               null;\n    checkItemStates:       null;\n    closed:                boolean;\n    coordinates:           null;\n    creationMethod:        null;\n    dateLastActivity:      string;\n    desc:                  string;\n    descData:              DescDataClass | null;\n    dueReminder:           number | null;\n    idBoard:               IDBoardEnum;\n    idLabels:              string[];\n    idList:                IDMemberCreator;\n    idMembersVoted:        any[];\n    idShort:               number;\n    idAttachmentCover:     null;\n    locationName:          null;\n    manualCoverAttachment: boolean;\n    name:                  string;\n    pos:                   number;\n    shortLink:             string;\n    isTemplate:            boolean;\n    cardRole:              null;\n    badges:                Badges;\n    dueComplete:           boolean;\n    due:                   null | string;\n    email:                 string;\n    idChecklists:          string[];\n    idMembers:             IDMemberCreator[];\n    labels:                Label[];\n    limits:                CardLimits;\n    shortUrl:              string;\n    start:                 null;\n    subscribed:            boolean;\n    url:                   string;\n    cover:                 PurpleCover;\n    attachments:           Attachment[];\n    pluginData:            any[];\n    customFieldItems:      CustomFieldItem[];\n}\n\nexport interface Attachment {\n    bytes:     number | null;\n    date:      string;\n    edgeColor: null | string;\n    idMember:  string;\n    isUpload:  boolean;\n    mimeType:  null | string;\n    name:      string;\n    previews:  Scaled[];\n    url:       string;\n    pos:       number;\n    id:        string;\n    fileName?: string;\n}\n\nexport interface Scaled {\n    _id:    string;\n    id:     string;\n    scaled: boolean;\n    url:    string;\n    bytes:  number;\n    height: number;\n    width:  number;\n}\n\nexport interface Badges {\n    attachmentsByType:     AttachmentsByType;\n    location:              boolean;\n    votes:                 number;\n    viewingMemberVoted:    boolean;\n    subscribed:            boolean;\n    fogbugz:               string;\n    checkItems:            number;\n    checkItemsChecked:     number;\n    checkItemsEarliestDue: null;\n    comments:              number;\n    attachments:           number;\n    description:           boolean;\n    due:                   null | string;\n    dueComplete:           boolean;\n    start:                 null;\n}\n\nexport interface AttachmentsByType {\n    trello: TrelloClass;\n}\n\nexport interface TrelloClass {\n    board: number;\n    card:  number;\n}\n\nexport interface PurpleCover {\n    idAttachment:         null;\n    color:                null;\n    idUploadedBackground: IDUploadedBackground | null;\n    size:                 MemberType;\n    brightness:           Brightness;\n    idPlugin:             null;\n    scaled?:              Scaled[];\n    edgeColor?:           string;\n    sharedSourceUrl?:     string;\n}\n\nexport interface DescDataClass {\n    emoji: Emoji;\n}\n\nexport interface Emoji {\n}\n\nexport interface Label {\n    id:      string;\n    idBoard: IDBoardEnum;\n    name:    string;\n    color:   string;\n}\n\nexport interface CardLimits {\n    attachments: Stickers;\n    checklists:  Stickers;\n    stickers:    Stickers;\n}\n\nexport interface Stickers {\n    perCard: PerBoard;\n}\n\nexport interface ChecklistElement {\n    id:             string;\n    name:           FullNameEnum;\n    idCard:         string;\n    pos:            number;\n    creationMethod: null;\n    idBoard:        IDBoardEnum;\n    limits:         ChecklistLimits;\n    checkItems:     CheckItemElement[];\n}\n\nexport interface CheckItemElement {\n    idChecklist: string;\n    state:       string;\n    id:          string;\n    name:        string;\n    nameData:    DescDataClass | null;\n    pos:         number;\n    due:         null;\n    idMember:    null | string;\n}\n\nexport interface ChecklistLimits {\n    checkItems: CheckItems;\n}\n\nexport interface CheckItems {\n    perChecklist: PerBoard;\n}\n\nexport interface CustomFieldElement {\n    id:               IDCustomFieldEnum;\n    idModel:          IDBoardEnum;\n    modelType:        string;\n    fieldGroup:       string;\n    display:          Display;\n    name:             string;\n    pos:              number;\n    type:             string;\n    isSuggestedField: boolean;\n}\n\nexport interface Display {\n    cardFront: boolean;\n}\n\nexport interface LabelNames {\n    green:  string;\n    yellow: string;\n    orange: string;\n    red:    string;\n    purple: string;\n    blue:   string;\n    sky:    string;\n    lime:   string;\n    pink:   string;\n    black:  string;\n}\n\nexport interface TrelloLimits {\n    attachments:        Attachments;\n    boards:             Boards;\n    cards:              PurpleCards;\n    checklists:         Attachments;\n    checkItems:         CheckItems;\n    customFields:       CustomFields;\n    customFieldOptions: CustomFieldOptions;\n    labels:             CustomFields;\n    lists:              Lists;\n    stickers:           Stickers;\n    reactions:          Reactions;\n}\n\nexport interface Attachments {\n    perBoard: PerBoard;\n    perCard:  PerBoard;\n}\n\nexport interface Boards {\n    totalMembersPerBoard: PerBoard;\n}\n\nexport interface PurpleCards {\n    openPerBoard:  PerBoard;\n    openPerList:   PerBoard;\n    totalPerBoard: PerBoard;\n    totalPerList:  PerBoard;\n}\n\nexport interface CustomFieldOptions {\n    perField: PerBoard;\n}\n\nexport interface CustomFields {\n    perBoard: PerBoard;\n}\n\nexport interface Lists {\n    openPerBoard:  PerBoard;\n    totalPerBoard: PerBoard;\n}\n\nexport interface List {\n    id:             IDMemberCreator;\n    name:           FullNameEnum;\n    closed:         boolean;\n    pos:            number;\n    softLimit:      null;\n    creationMethod: null;\n    idBoard:        IDBoardEnum;\n    limits:         ListLimits;\n    subscribed:     boolean;\n}\n\nexport interface ListLimits {\n    cards: FluffyCards;\n}\n\nexport interface FluffyCards {\n    openPerList:  PerBoard;\n    totalPerList: PerBoard;\n}\n\nexport interface MemberElement {\n    id:                       IDMemberCreator;\n    bio:                      string;\n    bioData:                  null;\n    confirmed:                boolean;\n    memberType:               MemberType;\n    username:                 Username;\n    activityBlocked:          boolean;\n    avatarHash:               AvatarHash;\n    avatarUrl:                string;\n    fullName:                 FullNameEnum;\n    idEnterprise:             null;\n    idEnterprisesDeactivated: any[];\n    idMemberReferrer:         null;\n    idPremOrgsAdmin:          any[];\n    initials:                 Initials;\n    nonPublic:                NonPublic;\n    nonPublicAvailable:       boolean;\n    products:                 any[];\n    url:                      string;\n    status:                   string;\n}\n\nexport interface Membership {\n    id:          string;\n    idMember:    IDMemberCreator;\n    memberType:  string;\n    unconfirmed: boolean;\n    deactivated: boolean;\n}\n\nexport interface PluginDatum {\n    id:       string;\n    idPlugin: string;\n    scope:    string;\n    idModel:  IDBoardEnum;\n    value:    string;\n    access:   string;\n}\n\nexport interface Prefs {\n    permissionLevel:       string;\n    hideVotes:             boolean;\n    voting:                string;\n    comments:              string;\n    invitations:           string;\n    selfJoin:              boolean;\n    cardCovers:            boolean;\n    isTemplate:            boolean;\n    cardAging:             string;\n    calendarFeedEnabled:   boolean;\n    background:            string;\n    backgroundImage:       string;\n    backgroundImageScaled: BackgroundImageScaled[];\n    backgroundTile:        boolean;\n    backgroundBrightness:  Brightness;\n    backgroundBottomColor: string;\n    backgroundTopColor:    string;\n    canBePublic:           boolean;\n    canBeEnterprise:       boolean;\n    canBeOrg:              boolean;\n    canBePrivate:          boolean;\n    canInvite:             boolean;\n}\n\nexport interface BackgroundImageScaled {\n    width:  number;\n    height: number;\n    url:    string;\n}\n"
  },
  {
    "path": "import/trello/tsconfig.json",
    "content": "{\n\t\"compilerOptions\": {\n\t\t\"jsx\": \"react\",\n\t\t\"target\": \"es2019\",\n\t\t\"module\": \"commonjs\",\n\t\t\"esModuleInterop\": true,\n\t\t\"noImplicitAny\": true,\n\t\t\"strict\": true,\n\t\t\"strictNullChecks\": true,\n\t\t\"forceConsistentCasingInFileNames\": true,\n\t\t\"sourceMap\": true,\n\t\t\"allowJs\": true,\n\t\t\"resolveJsonModule\": true,\n\t\t\"incremental\": false,\n\t\t\"outDir\": \"./dist\",\n\t\t\"moduleResolution\": \"node\"\n\t},\n\t\"include\": [\n\t\t\".\"\n\t],\n\t\"exclude\": [\n        \".git\",\n\t\t\"**/node_modules/*\",\n\t\t\"dist\",\n\t\t\"pack\"\n    ]\n}\n"
  },
  {
    "path": "import/trello/utils.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport * as crypto from 'crypto'\n\nclass Utils {\n    static createGuid(): string {\n        function randomDigit() {\n            if (crypto && crypto.randomBytes) {\n                const rands = crypto.randomBytes(1)\n                return (rands[0] % 16).toString(16)\n            }\n\n            return (Math.floor((Math.random() * 16))).toString(16)\n        }\n        return 'xxxxxxxx-xxxx-4xxx-8xxx-xxxxxxxxxxxx'.replace(/x/g, randomDigit)\n    }\n}\n\nexport { Utils }\n"
  },
  {
    "path": "import/util/archive.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {Block} from '../../webapp/src/blocks/block'\nimport {Board} from '../../webapp/src/blocks/board'\n\ninterface ArchiveHeader {\n    version: number\n    date: number\n}\n\n// This schema allows the expansion of additional line types in the future\ninterface ArchiveLine {\n    type: string,\n    data: unknown,\n}\n\ninterface BlockArchiveLine extends ArchiveLine {\n    type: 'block',\n    data: Block\n}\n\ninterface BoardArchiveLine extends ArchiveLine {\n    type: 'board',\n    data: Board\n}\n\nclass ArchiveUtils {\n    static buildBlockArchive(boards: readonly Board[], blocks: readonly Block[]): string {\n        const header: ArchiveHeader = {\n            version: 1,\n            date: Date.now(),\n        }\n\n        const headerString = JSON.stringify(header)\n        let content = headerString + '\\n'\n\n        for (const board of boards) {\n            const line: BoardArchiveLine = {\n                type: 'board',\n                data: board,\n            }\n            const lineString = JSON.stringify(line)\n            content += lineString\n            content += '\\n'\n        }\n\n        for (const block of blocks) {\n            const line: BlockArchiveLine = {\n                type: 'block',\n                data: block,\n            }\n            const lineString = JSON.stringify(line)\n            content += lineString\n            content += '\\n'\n        }\n\n        return content\n    }\n\n    static parseBlockArchive(contents: string): Block[] {\n        const blocks: Block[] = []\n        const allLineStrings = contents.split('\\n')\n        if (allLineStrings.length >= 2) {\n            const headerString = allLineStrings[0]\n            const header = JSON.parse(headerString) as ArchiveHeader\n            if (header.date && header.version >= 1) {\n                const lineStrings = allLineStrings.slice(1)\n                let lineNum = 2\n                for (const lineString of lineStrings) {\n                    if (!lineString) {\n                        // Ignore empty lines, e.g. last line\n                        continue\n                    }\n                    const line = JSON.parse(lineString) as ArchiveLine\n                    if (!line || !line.type || !line.data) {\n                        throw new Error(`ERROR parsing line ${lineNum}`)\n                    }\n                    switch (line.type) {\n                    case 'block': {\n                        const blockLine = line as BlockArchiveLine\n                        const block = blockLine.data\n                        blocks.push(block)\n                        break\n                    }\n                    }\n\n                    lineNum += 1\n                }\n            } else {\n                throw new Error('ERROR parsing header')\n            }\n        }\n\n        return blocks\n    }\n}\n\nexport {ArchiveHeader, ArchiveLine, BlockArchiveLine, ArchiveUtils}"
  },
  {
    "path": "linux/Makefile",
    "content": ".PHONY: run\n\nrun:\n\tgo run -tags \"json1 sqlite3\" ./main.go\n\nbuild:\n\tmkdir -p bin\n\tgo build -tags \"json1 sqlite3\" -o bin/focalboard-app\n"
  },
  {
    "path": "linux/go.mod",
    "content": "module github.com/mattermost/focalboard/linux\n\ngo 1.21\n\ntoolchain go1.21.8\n\nreplace github.com/mattermost/focalboard/server => ../server\n\nrequire (\n\tgithub.com/google/uuid v1.6.0\n\tgithub.com/mattermost/focalboard/server v0.0.0-20230104182634-f909c2552e37\n\tgithub.com/mattermost/mattermost/server/public v0.1.3\n\tgithub.com/webview/webview v0.0.0-20220314230258-a2b7746141c3\n)\n\nrequire (\n\tfilippo.io/edwards25519 v1.1.0 // indirect\n\tgithub.com/Masterminds/squirrel v1.5.4 // indirect\n\tgithub.com/beorn7/perks v1.0.1 // indirect\n\tgithub.com/blang/semver/v4 v4.0.0 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.3.0 // indirect\n\tgithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect\n\tgithub.com/dustin/go-humanize v1.0.1 // indirect\n\tgithub.com/dyatlov/go-opengraph/opengraph v0.0.0-20220524092352-606d7b1e5f8a // indirect\n\tgithub.com/fatih/color v1.17.0 // indirect\n\tgithub.com/francoispqt/gojay v1.2.13 // indirect\n\tgithub.com/fsnotify/fsnotify v1.7.0 // indirect\n\tgithub.com/go-asn1-ber/asn1-ber v1.5.7 // indirect\n\tgithub.com/go-sql-driver/mysql v1.8.1 // indirect\n\tgithub.com/goccy/go-json v0.10.2 // indirect\n\tgithub.com/golang/protobuf v1.5.4 // indirect\n\tgithub.com/gorilla/mux v1.8.1 // indirect\n\tgithub.com/gorilla/websocket v1.5.1 // indirect\n\tgithub.com/hashicorp/errwrap v1.1.0 // indirect\n\tgithub.com/hashicorp/go-hclog v1.6.3 // indirect\n\tgithub.com/hashicorp/go-multierror v1.1.1 // indirect\n\tgithub.com/hashicorp/go-plugin v1.6.1 // indirect\n\tgithub.com/hashicorp/golang-lru/v2 v2.0.7 // indirect\n\tgithub.com/hashicorp/hcl v1.0.0 // indirect\n\tgithub.com/hashicorp/yamux v0.1.1 // indirect\n\tgithub.com/klauspost/compress v1.17.8 // indirect\n\tgithub.com/klauspost/cpuid/v2 v2.2.7 // indirect\n\tgithub.com/krolaw/zipstream v0.0.0-20180621105154-0a2661891f94 // indirect\n\tgithub.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect\n\tgithub.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect\n\tgithub.com/lib/pq v1.10.9 // indirect\n\tgithub.com/magiconair/properties v1.8.7 // indirect\n\tgithub.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404 // indirect\n\tgithub.com/mattermost/ldap v0.0.0-20231116144001-0f480c025956 // indirect\n\tgithub.com/mattermost/logr/v2 v2.0.21 // indirect\n\tgithub.com/mattermost/mattermost/server/v8 v8.0.0-20240529104128-9d30a62c9471 // indirect\n\tgithub.com/mattermost/morph v1.1.0 // indirect\n\tgithub.com/mattn/go-colorable v0.1.13 // indirect\n\tgithub.com/mattn/go-isatty v0.0.20 // indirect\n\tgithub.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect\n\tgithub.com/minio/md5-simd v1.1.2 // indirect\n\tgithub.com/minio/minio-go/v7 v7.0.70 // indirect\n\tgithub.com/mitchellh/go-testing-interface v1.14.1 // indirect\n\tgithub.com/mitchellh/mapstructure v1.5.0 // indirect\n\tgithub.com/ncruces/go-strftime v0.1.9 // indirect\n\tgithub.com/oklog/run v1.1.0 // indirect\n\tgithub.com/pborman/uuid v1.2.1 // indirect\n\tgithub.com/pelletier/go-toml v1.9.5 // indirect\n\tgithub.com/pelletier/go-toml/v2 v2.2.2 // indirect\n\tgithub.com/philhofer/fwd v1.1.2 // indirect\n\tgithub.com/pkg/errors v0.9.1 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect\n\tgithub.com/prometheus/client_golang v1.19.1 // indirect\n\tgithub.com/prometheus/client_model v0.6.1 // indirect\n\tgithub.com/prometheus/common v0.53.0 // indirect\n\tgithub.com/prometheus/procfs v0.15.0 // indirect\n\tgithub.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect\n\tgithub.com/rivo/uniseg v0.4.7 // indirect\n\tgithub.com/rs/xid v1.5.0 // indirect\n\tgithub.com/rudderlabs/analytics-go v3.3.3+incompatible // indirect\n\tgithub.com/sagikazarmark/locafero v0.4.0 // indirect\n\tgithub.com/sagikazarmark/slog-shim v0.1.0 // indirect\n\tgithub.com/segmentio/backo-go v1.1.0 // indirect\n\tgithub.com/sirupsen/logrus v1.9.3 // indirect\n\tgithub.com/sourcegraph/conc v0.3.0 // indirect\n\tgithub.com/spf13/afero v1.11.0 // indirect\n\tgithub.com/spf13/cast v1.6.0 // indirect\n\tgithub.com/spf13/pflag v1.0.5 // indirect\n\tgithub.com/spf13/viper v1.18.2 // indirect\n\tgithub.com/stretchr/objx v0.5.2 // indirect\n\tgithub.com/stretchr/testify v1.9.0 // indirect\n\tgithub.com/subosito/gotenv v1.6.0 // indirect\n\tgithub.com/tidwall/gjson v1.17.1 // indirect\n\tgithub.com/tidwall/match v1.1.1 // indirect\n\tgithub.com/tidwall/pretty v1.2.1 // indirect\n\tgithub.com/tinylib/msgp v1.1.9 // indirect\n\tgithub.com/vmihailenco/msgpack/v5 v5.4.1 // indirect\n\tgithub.com/vmihailenco/tagparser/v2 v2.0.0 // indirect\n\tgithub.com/wiggin77/merror v1.0.5 // indirect\n\tgithub.com/wiggin77/srslog v1.0.1 // indirect\n\tgithub.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect\n\tgithub.com/yuin/goldmark v1.7.1 // indirect\n\tgo.uber.org/multierr v1.11.0 // indirect\n\tgolang.org/x/crypto v0.23.0 // indirect\n\tgolang.org/x/exp v0.0.0-20240529005216-23cca8864a10 // indirect\n\tgolang.org/x/net v0.25.0 // indirect\n\tgolang.org/x/sys v0.20.0 // indirect\n\tgolang.org/x/text v0.15.0 // indirect\n\tgoogle.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect\n\tgoogle.golang.org/grpc v1.64.0 // indirect\n\tgoogle.golang.org/protobuf v1.34.1 // indirect\n\tgopkg.in/ini.v1 v1.67.0 // indirect\n\tgopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect\n\tgopkg.in/yaml.v2 v2.4.0 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n\tmodernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b // indirect\n\tmodernc.org/libc v1.50.9 // indirect\n\tmodernc.org/mathutil v1.6.0 // indirect\n\tmodernc.org/memory v1.8.0 // indirect\n\tmodernc.org/sqlite v1.29.10 // indirect\n\tmodernc.org/strutil v1.2.0 // indirect\n\tmodernc.org/token v1.1.0 // indirect\n)\n"
  },
  {
    "path": "linux/go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=\ndmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=\ndmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=\ndmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=\ndmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=\nfilippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=\nfilippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=\ngit.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM=\ngithub.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=\ngithub.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=\ngithub.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=\ngithub.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=\ngithub.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=\ngithub.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=\ngithub.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=\ngithub.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=\ngithub.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA=\ngithub.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8=\ngithub.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=\ngithub.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=\ngithub.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=\ngithub.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=\ngithub.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=\ngithub.com/dyatlov/go-opengraph/opengraph v0.0.0-20220524092352-606d7b1e5f8a h1:etIrTD8BQqzColk9nKRusM9um5+1q0iOEJLqfBMIK64=\ngithub.com/dyatlov/go-opengraph/opengraph v0.0.0-20220524092352-606d7b1e5f8a/go.mod h1:emQhSYTXqB0xxjLITTw4EaWZ+8IIQYw+kx9GqNUKdLg=\ngithub.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=\ngithub.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=\ngithub.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=\ngithub.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=\ngithub.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=\ngithub.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=\ngithub.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=\ngithub.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=\ngithub.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=\ngithub.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=\ngithub.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=\ngithub.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=\ngithub.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=\ngithub.com/go-asn1-ber/asn1-ber v1.5.7 h1:DTX+lbVTWaTw1hQ+PbZPlnDZPEIs0SS/GCZAl535dDk=\ngithub.com/go-asn1-ber/asn1-ber v1.5.7/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=\ngithub.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=\ngithub.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=\ngithub.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=\ngithub.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=\ngithub.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=\ngithub.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=\ngithub.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=\ngithub.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=\ngithub.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=\ngithub.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=\ngithub.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=\ngithub.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=\ngithub.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=\ngithub.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=\ngithub.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=\ngithub.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=\ngithub.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=\ngithub.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=\ngithub.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=\ngithub.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=\ngithub.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=\ngithub.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=\ngithub.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=\ngithub.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=\ngithub.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=\ngithub.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=\ngithub.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=\ngithub.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=\ngithub.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=\ngithub.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=\ngithub.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=\ngithub.com/hashicorp/go-plugin v1.6.1 h1:P7MR2UP6gNKGPp+y7EZw2kOiq4IR9WiqLvp0XOsVdwI=\ngithub.com/hashicorp/go-plugin v1.6.1/go.mod h1:XPHFku2tFo3o3QKFgSYo+cghcUhw1NA1hZyMK0PWAw0=\ngithub.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=\ngithub.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=\ngithub.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=\ngithub.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=\ngithub.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=\ngithub.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=\ngithub.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=\ngithub.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c=\ngithub.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo=\ngithub.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=\ngithub.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=\ngithub.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=\ngithub.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=\ngithub.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=\ngithub.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=\ngithub.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=\ngithub.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/krolaw/zipstream v0.0.0-20180621105154-0a2661891f94 h1:+AIlO01SKT9sfWU5CLWi0cfHc7dQwgGz3FhFRzXLoMg=\ngithub.com/krolaw/zipstream v0.0.0-20180621105154-0a2661891f94/go.mod h1:TcE3PIIkVWbP/HjhRAafgCjRKvDOi086iqp9VkNX/ng=\ngithub.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw=\ngithub.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o=\ngithub.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk=\ngithub.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw=\ngithub.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=\ngithub.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=\ngithub.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=\ngithub.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=\ngithub.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=\ngithub.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=\ngithub.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404 h1:Khvh6waxG1cHc4Cz5ef9n3XVCxRWpAKUtqg9PJl5+y8=\ngithub.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404/go.mod h1:RyS7FDNQlzF1PsjbJWHRI35exqaKGSO9qD4iv8QjE34=\ngithub.com/mattermost/ldap v0.0.0-20231116144001-0f480c025956 h1:Y1Tu/swM31pVwwb2BTCsOdamENjjWCI6qmfHLbk6OZI=\ngithub.com/mattermost/ldap v0.0.0-20231116144001-0f480c025956/go.mod h1:SRl30Lb7/QoYyohYeVBuqYvvmXSZJxZgiV3Zf6VbxjI=\ngithub.com/mattermost/logr/v2 v2.0.21 h1:CMHsP+nrbRlEC4g7BwOk1GAnMtHkniFhlSQPXy52be4=\ngithub.com/mattermost/logr/v2 v2.0.21/go.mod h1:kZkB/zqKL9e+RY5gB3vGpsyenC+TpuiOenjMkvJJbzc=\ngithub.com/mattermost/mattermost/server/public v0.1.3 h1:A3hQ3rNCwHfKAVxe7Hk3Zd9p2pyUKRmxtRPnkWP5SFM=\ngithub.com/mattermost/mattermost/server/public v0.1.3/go.mod h1:PDPb/iqzJJ5ZvK/m70oDF55AXN/cOvVFj96Yu4e6j+Q=\ngithub.com/mattermost/mattermost/server/v8 v8.0.0-20240529104128-9d30a62c9471 h1:LxlvPGImhPoZ16qJtZHfooqfIG2UGsbcIRNiTqQ/5Is=\ngithub.com/mattermost/mattermost/server/v8 v8.0.0-20240529104128-9d30a62c9471/go.mod h1:qQjPPGKiugHw6Tunlmq3cVDkKFFbgtMxIvyNJoN+p3Y=\ngithub.com/mattermost/morph v1.1.0 h1:Q9vrJbeM3s2jfweGheq12EFIzdNp9a/6IovcbvOQ6Cw=\ngithub.com/mattermost/morph v1.1.0/go.mod h1:gD+EaqX2UMyyuzmF4PFh4r33XneQ8Nzi+0E8nXjMa3A=\ngithub.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=\ngithub.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=\ngithub.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=\ngithub.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=\ngithub.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=\ngithub.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=\ngithub.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=\ngithub.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=\ngithub.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\ngithub.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=\ngithub.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=\ngithub.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=\ngithub.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=\ngithub.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=\ngithub.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=\ngithub.com/minio/minio-go/v7 v7.0.70 h1:1u9NtMgfK1U42kUxcsl5v0yj6TEOPR497OAQxpJnn2g=\ngithub.com/minio/minio-go/v7 v7.0.70/go.mod h1:4yBA8v80xGA30cfM3fz0DKYMXunWl/AV/6tWEs9ryzo=\ngithub.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU=\ngithub.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8=\ngithub.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=\ngithub.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=\ngithub.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=\ngithub.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=\ngithub.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=\ngithub.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA=\ngithub.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU=\ngithub.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=\ngithub.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw=\ngithub.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=\ngithub.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=\ngithub.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=\ngithub.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=\ngithub.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=\ngithub.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=\ngithub.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw=\ngithub.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0=\ngithub.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=\ngithub.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=\ngithub.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=\ngithub.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=\ngithub.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=\ngithub.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=\ngithub.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=\ngithub.com/prometheus/common v0.53.0 h1:U2pL9w9nmJwJDa4qqLQ3ZaePJ6ZTwt7cMD3AG3+aLCE=\ngithub.com/prometheus/common v0.53.0/go.mod h1:BrxBKv3FWBIGXw89Mg1AeBq7FSyRzXWI3l3e7W3RN5U=\ngithub.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=\ngithub.com/prometheus/procfs v0.15.0 h1:A82kmvXJq2jTu5YUhSGNlYoxh85zLnKgPz4bMZgI5Ek=\ngithub.com/prometheus/procfs v0.15.0/go.mod h1:Y0RJ/Y5g5wJpkTisOtqwDSo4HwhGmLB4VQSw2sQJLHk=\ngithub.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=\ngithub.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=\ngithub.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=\ngithub.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=\ngithub.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=\ngithub.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=\ngithub.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=\ngithub.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=\ngithub.com/rudderlabs/analytics-go v3.3.3+incompatible h1:OG0XlKoXfr539e2t1dXtTB+Gr89uFW+OUNQBVhHIIBY=\ngithub.com/rudderlabs/analytics-go v3.3.3+incompatible/go.mod h1:LF8/ty9kUX4PTY3l5c97K3nZZaX5Hwsvt+NBaRL/f30=\ngithub.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=\ngithub.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=\ngithub.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=\ngithub.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=\ngithub.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=\ngithub.com/segmentio/backo-go v1.1.0 h1:cJIfHQUdmLsd8t9IXqf5J8SdrOMn9vMa7cIvOavHAhc=\ngithub.com/segmentio/backo-go v1.1.0/go.mod h1:ckenwdf+v/qbyhVdNPWHnqh2YdJBED1O9cidYyM5J18=\ngithub.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=\ngithub.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=\ngithub.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=\ngithub.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=\ngithub.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=\ngithub.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=\ngithub.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=\ngithub.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=\ngithub.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=\ngithub.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=\ngithub.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=\ngithub.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=\ngithub.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=\ngithub.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=\ngithub.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=\ngithub.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=\ngithub.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=\ngithub.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=\ngithub.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=\ngithub.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=\ngithub.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=\ngithub.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=\ngithub.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=\ngithub.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=\ngithub.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=\ngithub.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=\ngithub.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=\ngithub.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=\ngithub.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=\ngithub.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=\ngithub.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=\ngithub.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=\ngithub.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=\ngithub.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=\ngithub.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=\ngithub.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\ngithub.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=\ngithub.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=\ngithub.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=\ngithub.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=\ngithub.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\ngithub.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=\ngithub.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=\ngithub.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=\ngithub.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=\ngithub.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=\ngithub.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=\ngithub.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=\ngithub.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=\ngithub.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=\ngithub.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=\ngithub.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=\ngithub.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=\ngithub.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=\ngithub.com/tinylib/msgp v1.1.9 h1:SHf3yoO2sGA0veCJeCBYLHuttAVFHGm2RHgNodW7wQU=\ngithub.com/tinylib/msgp v1.1.9/go.mod h1:BCXGB54lDD8qUEPmiG0cQQUANC4IUQyB2ItS2UDlO/k=\ngithub.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=\ngithub.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=\ngithub.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=\ngithub.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=\ngithub.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=\ngithub.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=\ngithub.com/webview/webview v0.0.0-20220314230258-a2b7746141c3 h1:8joKgFslmiNmyA0Cvw/xgkdKZOYiXKsHlnG5OWmJEHA=\ngithub.com/webview/webview v0.0.0-20220314230258-a2b7746141c3/go.mod h1:rpXAuuHgyEJb6kXcXldlkOjU6y4x+YcASKKXJNUhh0Y=\ngithub.com/wiggin77/merror v1.0.5 h1:P+lzicsn4vPMycAf2mFf7Zk6G9eco5N+jB1qJ2XW3ME=\ngithub.com/wiggin77/merror v1.0.5/go.mod h1:H2ETSu7/bPE0Ymf4bEwdUoo73OOEkdClnoRisfw0Nm0=\ngithub.com/wiggin77/srslog v1.0.1 h1:gA2XjSMy3DrRdX9UqLuDtuVAAshb8bE1NhX1YK0Qe+8=\ngithub.com/wiggin77/srslog v1.0.1/go.mod h1:fehkyYDq1QfuYn60TDPu9YdY2bB85VUW2mvN1WynEls=\ngithub.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c h1:3lbZUMbMiGUW/LMkfsEABsc5zNT9+b1CvsJx47JzJ8g=\ngithub.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c/go.mod h1:UrdRz5enIKZ63MEE3IF9l2/ebyx59GyGgPi+tICQdmM=\ngithub.com/yuin/goldmark v1.7.1 h1:3bajkSilaCbjdKVsKdZjZCLBNPL9pYzrCakKaf4U49U=\ngithub.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=\ngo.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=\ngo.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=\ngo.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=\ngo4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=\ngolang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=\ngolang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=\ngolang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20240529005216-23cca8864a10 h1:vpzMC/iZhYFAjJzHU0Cfuq+w1vLLsF2vLkDrPjzKYck=\ngolang.org/x/exp v0.0.0-20240529005216-23cca8864a10/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=\ngolang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=\ngolang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=\ngolang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=\ngolang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=\ngolang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=\ngolang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=\ngolang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=\ngolang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=\ngolang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=\ngolang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=\ngoogle.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=\ngoogle.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=\ngoogle.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=\ngoogle.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 h1:Zy9XzmMEflZ/MAaA7vNcoebnRAld7FsPW1EeBB7V0m8=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=\ngoogle.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=\ngoogle.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=\ngoogle.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=\ngoogle.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=\ngoogle.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=\ngoogle.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=\ngopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=\ngopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=\ngopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=\ngopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=\ngopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=\ngopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngrpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=\nhonnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nmodernc.org/cc/v4 v4.21.2 h1:dycHFB/jDc3IyacKipCNSDrjIC0Lm1hyoWOZTRR20Lk=\nmodernc.org/cc/v4 v4.21.2/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=\nmodernc.org/ccgo/v4 v4.17.8 h1:yyWBf2ipA0Y9GGz/MmCmi3EFpKgeS7ICrAFes+suEbs=\nmodernc.org/ccgo/v4 v4.17.8/go.mod h1:buJnJ6Fn0tyAdP/dqePbrrvLyr6qslFfTbFrCuaYvtA=\nmodernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=\nmodernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=\nmodernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=\nmodernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=\nmodernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b h1:BnN1t+pb1cy61zbvSUV7SeI0PwosMhlAEi/vBY4qxp8=\nmodernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=\nmodernc.org/libc v1.50.9 h1:hIWf1uz55lorXQhfoEoezdUHjxzuO6ceshET/yWjSjk=\nmodernc.org/libc v1.50.9/go.mod h1:15P6ublJ9FJR8YQCGy8DeQ2Uwur7iW9Hserr/T3OFZE=\nmodernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=\nmodernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=\nmodernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=\nmodernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=\nmodernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=\nmodernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=\nmodernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=\nmodernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=\nmodernc.org/sqlite v1.29.10 h1:3u93dz83myFnMilBGCOLbr+HjklS6+5rJLx4q86RDAg=\nmodernc.org/sqlite v1.29.10/go.mod h1:ItX2a1OVGgNsFh6Dv60JQvGfJfTPHPVpV6DF59akYOA=\nmodernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=\nmodernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=\nmodernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=\nmodernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=\nsourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=\nsourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=\n"
  },
  {
    "path": "linux/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"net\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path\"\n\t\"path/filepath\"\n\t\"runtime\"\n\n\t\"github.com/google/uuid\"\n\t\"github.com/mattermost/focalboard/server/server\"\n\t\"github.com/mattermost/focalboard/server/services/config\"\n\t\"github.com/mattermost/focalboard/server/services/permissions/localpermissions\"\n\t\"github.com/webview/webview\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nvar sessionToken string = \"su-\" + uuid.New().String()\n\nfunc getFreePort() (int, error) {\n\taddr, err := net.ResolveTCPAddr(\"tcp\", \"localhost:0\")\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tl, err := net.ListenTCP(\"tcp\", addr)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tdefer l.Close()\n\treturn l.Addr().(*net.TCPAddr).Port, nil\n}\n\nfunc runServer(port int) (*server.Server, error) {\n\tlogger, _ := mlog.NewLogger()\n\n\texecutable, _ := os.Executable()\n\texecutableDir, _ := filepath.EvalSymlinks(filepath.Dir(executable))\n\n\tconfig := &config.Configuration{\n\t\tServerRoot:              fmt.Sprintf(\"http://localhost:%d\", port),\n\t\tPort:                    port,\n\t\tDBType:                  \"sqlite3\",\n\t\tDBConfigString:          path.Join(executableDir, \"focalboard.db\"),\n\t\tUseSSL:                  false,\n\t\tSecureCookie:            true,\n\t\tWebPath:                 path.Join(executableDir, \"pack\"),\n\t\tFilesDriver:             \"local\",\n\t\tFilesPath:               path.Join(executableDir, \"focalboard_files\"),\n\t\tTelemetry:               true,\n\t\tWebhookUpdate:           []string{},\n\t\tSessionExpireTime:       259200000000,\n\t\tSessionRefreshTime:      18000,\n\t\tLocalOnly:               false,\n\t\tEnableLocalMode:         false,\n\t\tLocalModeSocketLocation: \"\",\n\t\tAuthMode:                \"native\",\n\t}\n\n\tsingleUser := len(sessionToken) > 0\n\tdb, err := server.NewStore(config, singleUser, logger)\n\tif err != nil {\n\t\tfmt.Println(\"ERROR INITIALIZING THE SERVER STORE\", err)\n\t\treturn nil, err\n\t}\n\n\tpermissionsService := localpermissions.New(db, logger)\n\n\tparams := server.Params{\n\t\tCfg:                config,\n\t\tSingleUserToken:    sessionToken,\n\t\tDBStore:            db,\n\t\tLogger:             logger,\n\t\tServerID:           \"\",\n\t\tWSAdapter:          nil,\n\t\tNotifyBackends:     nil,\n\t\tPermissionsService: permissionsService,\n\t}\n\n\tserver, err := server.New(params)\n\tif err != nil {\n\t\tfmt.Println(\"ERROR INITIALIZING THE SERVER\", err)\n\t\treturn nil, err\n\t}\n\terr = server.Start()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn server, nil\n\n}\n\nfunc openBrowser(url string) {\n\tvar err error\n\n\tswitch runtime.GOOS {\n\tcase \"linux\":\n\t\terr = exec.Command(\"xdg-open\", url).Start()\n\tcase \"windows\":\n\t\terr = exec.Command(\"rundll32\", \"url.dll,FileProtocolHandler\", url).Start()\n\tcase \"darwin\":\n\t\terr = exec.Command(\"open\", url).Start()\n\tdefault:\n\t\terr = fmt.Errorf(\"unsupported platform\")\n\t}\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n\nfunc main() {\n\tdebug := true\n\tw := webview.New(debug)\n\tdefer w.Destroy()\n\tport, err := getFreePort()\n\tif err != nil {\n\t\tlog.Println(\"Failed to open a free port\")\n\t\tlog.Fatal(err)\n\t}\n\tserver, err := runServer(port)\n\tif err != nil {\n\t\tlog.Println(\"Failed to start the server\")\n\t\tlog.Fatal(err)\n\t}\n\n\tw.SetTitle(\"Focalboard\")\n\tw.SetSize(1024, 768, webview.HintNone)\n\n\tscript := fmt.Sprintf(\"localStorage.setItem('focalboardSessionId', '%s');\", sessionToken)\n\tw.Init(script)\n\n\tw.Navigate(fmt.Sprintf(\"http://localhost:%d\", port))\n\tw.Bind(\"openInNewBrowser\", openBrowser)\n\tw.Init(`\ndocument.addEventListener('click', function (e) {\n    let a = e.target.closest('a[target=\"_blank\"]');\n    if (a) {\n\t    openInNewBrowser(a.getAttribute('href'));\n\t}\n});\n`)\n\tw.Run()\n\tserver.Shutdown()\n}\n"
  },
  {
    "path": "mac/Focalboard/AppDelegate.swift",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport Cocoa\n\n@NSApplicationMain\nclass AppDelegate: NSObject, NSApplicationDelegate {\n\tstatic let serverStartedNotification = NSNotification.Name(\"serverStarted\")\n\n\tprivate var serverProcess: Process?\n\tprivate weak var whatsnewWindow: NSWindow?\n\n\tvar isServerStarted: Bool {\n\t\tget { return serverProcess != nil }\n\t}\n\n\tvar serverPort = 8088\n\tvar sessionToken: String = \"\"\n\n\tfunc applicationDidFinishLaunching(_ aNotification: Notification) {\n\t\tcopyResources()\n\t\tstartServer()\n\n\t\tshowWhatsNewDialogIfNeeded()\n\n\t\tNotificationCenter.default.post(name: AppDelegate.serverStartedNotification, object: nil)\n\t}\n\n\tfunc applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {\n\t\tguard flag else {\n\t\t\topenNewWindow(nil)\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t}\n\n\tfunc applicationWillTerminate(_ aNotification: Notification) {\n\t\tstopServer()\n\t}\n\n\t@IBAction func openNewWindow(_ sender: Any?) {\n\t\tlet mainStoryBoard = NSStoryboard(name: \"Main\", bundle: nil)\n\t\tlet windowController = mainStoryBoard.instantiateController(withIdentifier: \"WindowController\") as! NSWindowController\n\t\twindowController.showWindow(self)\n\t}\n\n\tprivate func showWhatsNewDialogIfNeeded() {\n\t\tif Globals.currentWhatsNewVersion > 0 && Globals.currentWhatsNewVersion < Globals.WhatsNewVersion {\n\t\t\tGlobals.currentWhatsNewVersion = Globals.WhatsNewVersion\n\t\t\tshowWhatsNew(self)\n\t\t}\n\t}\n\n\t@IBAction func showWhatsNew(_: AnyObject) {\n\t\tif let whatsnewWindow = self.whatsnewWindow {\n\t\t\twhatsnewWindow.close()\n\t\t\tself.whatsnewWindow = nil\n\t\t}\n\n\t\tlet controller: WhatsNewViewController = NSStoryboard.main!.instantiateController(withIdentifier: \"WhatsNewViewController\") as! WhatsNewViewController\n\t\tlet window = NSWindow(contentViewController: controller)\n\t\tself.whatsnewWindow = window\n\n\t\twindow.makeKeyAndOrderFront(self)\n\t\tlet vc = NSWindowController(window: window)\n\t\tvc.showWindow(self)\n\t}\n\n\t@IBAction func getCloudServer(_: AnyObject) {\n\t\tGlobals.openGetCloudServerUrl()\n\t}\n\t\n\tprivate func webFolder() -> URL {\n\t\tlet url = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!\n\t\treturn url.appendingPathComponent(\"Focalboard\").appendingPathComponent(\"server\")\n\t}\n\n\tprivate func copyResources() {\n\t\tlet destBaseUrl = webFolder()\n\t\tdo {\n\t\t\ttry FileManager.default.createDirectory(atPath: destBaseUrl.path, withIntermediateDirectories: true, attributes: nil)\n\t\t} catch {\n\t\t\tNSLog(\"copyResources createDirectory ERROR\")\n\t\t}\n\t\tcopyResource(destBaseUrl: destBaseUrl, resourceRelativePath: \"pack\")\n\t\tcopyResource(destBaseUrl: destBaseUrl, resourceRelativePath: \"config.json\")\n\n\t\tNSLog(\"copyResources OK\")\n\t}\n\n\tprivate func copyResource(destBaseUrl: URL, resourceRelativePath: String, fileExtension: String = \"\") {\n\t\tlet srcUrl = Bundle.main.url(forResource: \"resources/\" + resourceRelativePath, withExtension: fileExtension)!\n\t\tlet destUrl = destBaseUrl.appendingPathComponent(resourceRelativePath)\n\n\t\tlet fileManager = FileManager.default\n\t\tif fileManager.fileExists(atPath: destUrl.path) {\n\t\t\tdo {\n\t\t\t\ttry fileManager.removeItem(at: destUrl)\n\t\t\t} catch {\n\t\t\t\tNSLog(\"copyResource removeItem ERROR\")\n\t\t\t}\n\t\t}\n\n\t\tdo {\n\t\t\ttry fileManager.copyItem(at: srcUrl, to: destUrl)\n\t\t} catch {\n\t\t\tNSLog(\"copyResource copyItem ERROR\")\n\t\t\treturn\n\t\t}\n\t}\n\n    private func generateSessionToken() -> String {\n        let bytesCount = 16\n        var randomNumber = \"\"\n\t\tvar randomBytes = [UInt8](repeating: 0, count: bytesCount)\n\n        let status = SecRandomCopyBytes(kSecRandomDefault, bytesCount, &randomBytes)\n\t\tif status != errSecSuccess {\n\t\t\tfatalError(\"SecRandomCopyBytes ERROR: \\(status)\")\n\t\t}\n\t\trandomNumber = randomBytes.map({String(format: \"%02hhx\", $0)}).joined(separator: \"\")\n\n        return \"su-\" + randomNumber\n    }\n\n\tprivate func getFreePort() {\n\t\tif PortUtils.isPortFree(in_port_t(serverPort)) {\n\t\t\treturn\n\t\t}\n\n\t\tserverPort = Int(PortUtils.getFreePort())\n\t}\n\n\tprivate func startServer() {\n\t\tgetFreePort()\n\t\tsessionToken = generateSessionToken()\n\n\t\tlet cwdUrl = webFolder()\n\t\tlet executablePath = Bundle.main.path(forResource: \"resources/bin/focalboard-server\", ofType: \"\")\n\n\t\tlet pid = ProcessInfo.processInfo.processIdentifier\n\t\tNSLog(\"pid: \\(pid)\")\n\t\tlet serverProcess = Process()\n\t\tserverProcess.currentDirectoryPath = cwdUrl.path\n\t\tserverProcess.arguments = [\"-monitorpid\", \"\\(pid)\", \"-port\", \"\\(serverPort)\", \"-single-user\"]\n\t\tserverProcess.environment = [\"FOCALBOARD_SINGLE_USER_TOKEN\": sessionToken]\n\t\tserverProcess.launchPath = executablePath\n\t\tserverProcess.launch()\n\t\tself.serverProcess = serverProcess\n\n\t\tNSLog(\"startServer OK\")\n\t\tNSLog(\"cwd: \\(cwdUrl)\")\n\t}\n\n\tprivate func stopServer() {\n\t\tguard let serverProcess = self.serverProcess else { return }\n\n\t\tserverProcess.terminate()\n\t\tself.serverProcess = nil\n\t\tNSLog(\"stopServer OK\")\n\t}\n}\n"
  },
  {
    "path": "mac/Focalboard/Assets.xcassets/AccentColor.colorset/Contents.json",
    "content": "{\n  \"colors\" : [\n    {\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "mac/Focalboard/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"16x16\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"16x16\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"32x32\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"32x32\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"128x128\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"128x128\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"256x256\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"256x256\"\n    },\n    {\n      \"filename\" : \"focalboard-512.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"512x512\"\n    },\n    {\n      \"filename\" : \"focalboard-1024.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"512x512\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "mac/Focalboard/Assets.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "mac/Focalboard/AutoSaveWindowController.swift",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport Cocoa\n\nclass AutoSaveWindowController: NSWindowController {\n\n\toverride func windowDidLoad() {\n\t\tsuper.windowDidLoad()\n\n\t\t// Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.\n\t\tself.windowFrameAutosaveName = NSWindow.FrameAutosaveName(\"AutoSaveWindow\")\n\t}\n}\n"
  },
  {
    "path": "mac/Focalboard/Base.lproj/Main.storyboard",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB\" version=\"3.0\" toolsVersion=\"20037\" targetRuntime=\"MacOSX.Cocoa\" propertyAccessControl=\"none\" useAutolayout=\"YES\" initialViewController=\"B8D-0N-5wS\">\n    <dependencies>\n        <deployment identifier=\"macosx\"/>\n        <plugIn identifier=\"com.apple.InterfaceBuilder.CocoaPlugin\" version=\"20037\"/>\n        <plugIn identifier=\"com.apple.WebKit2IBPlugin\" version=\"20037\"/>\n        <capability name=\"documents saved in the Xcode 8 format\" minToolsVersion=\"8.0\"/>\n    </dependencies>\n    <scenes>\n        <!--Application-->\n        <scene sceneID=\"JPo-4y-FX3\">\n            <objects>\n                <application id=\"hnw-xV-0zn\" sceneMemberID=\"viewController\">\n                    <menu key=\"mainMenu\" title=\"Main Menu\" systemMenu=\"main\" id=\"AYu-sK-qS6\">\n                        <items>\n                            <menuItem title=\"Focalboard\" id=\"1Xt-HY-uBw\">\n                                <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                <menu key=\"submenu\" title=\"Focalboard\" systemMenu=\"apple\" id=\"uQy-DD-JDr\">\n                                    <items>\n                                        <menuItem title=\"About Focalboard\" id=\"5kV-Vb-QxS\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <connections>\n                                                <action selector=\"orderFrontStandardAboutPanel:\" target=\"Ady-hI-5gd\" id=\"Exp-CZ-Vem\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"What's New\" id=\"zoN-7K-mck\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <connections>\n                                                <action selector=\"showWhatsNew:\" target=\"Ady-hI-5gd\" id=\"dlB-6q-7bK\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Get Mattermost Boards server\" id=\"8YW-NP-PAh\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <connections>\n                                                <action selector=\"getCloudServer:\" target=\"Ady-hI-5gd\" id=\"OvY-fG-Tqt\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem isSeparatorItem=\"YES\" id=\"VOq-y0-SEH\"/>\n                                        <menuItem title=\"Preferences…\" keyEquivalent=\",\" id=\"BOF-NM-1cW\"/>\n                                        <menuItem isSeparatorItem=\"YES\" id=\"wFC-TO-SCJ\"/>\n                                        <menuItem title=\"Services\" id=\"NMo-om-nkz\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <menu key=\"submenu\" title=\"Services\" systemMenu=\"services\" id=\"hz9-B4-Xy5\"/>\n                                        </menuItem>\n                                        <menuItem isSeparatorItem=\"YES\" id=\"4je-JR-u6R\"/>\n                                        <menuItem title=\"Hide Focalboard\" keyEquivalent=\"h\" id=\"Olw-nP-bQN\">\n                                            <connections>\n                                                <action selector=\"hide:\" target=\"Ady-hI-5gd\" id=\"PnN-Uc-m68\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Hide Others\" keyEquivalent=\"h\" id=\"Vdr-fp-XzO\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\" option=\"YES\" command=\"YES\"/>\n                                            <connections>\n                                                <action selector=\"hideOtherApplications:\" target=\"Ady-hI-5gd\" id=\"VT4-aY-XCT\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Show All\" id=\"Kd2-mp-pUS\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <connections>\n                                                <action selector=\"unhideAllApplications:\" target=\"Ady-hI-5gd\" id=\"Dhg-Le-xox\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem isSeparatorItem=\"YES\" id=\"kCx-OE-vgT\"/>\n                                        <menuItem title=\"Quit Focalboard\" keyEquivalent=\"q\" id=\"4sb-4s-VLi\">\n                                            <connections>\n                                                <action selector=\"terminate:\" target=\"Ady-hI-5gd\" id=\"Te7-pn-YzF\"/>\n                                            </connections>\n                                        </menuItem>\n                                    </items>\n                                </menu>\n                            </menuItem>\n                            <menuItem title=\"File\" id=\"dMs-cI-mzQ\">\n                                <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                <menu key=\"submenu\" title=\"File\" id=\"bib-Uj-vzu\">\n                                    <items>\n                                        <menuItem title=\"New\" keyEquivalent=\"n\" id=\"Was-JA-tGl\">\n                                            <connections>\n                                                <action selector=\"openNewWindow:\" target=\"Ady-hI-5gd\" id=\"srC-wx-gm5\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem isSeparatorItem=\"YES\" id=\"m54-Is-iLE\"/>\n                                        <menuItem title=\"Home\" keyEquivalent=\"H\" id=\"Zh7-Qp-1cL\">\n                                            <connections>\n                                                <action selector=\"navigateToHome:\" target=\"Ady-hI-5gd\" id=\"UDk-FQ-NU4\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Close\" keyEquivalent=\"w\" id=\"DVo-aG-piG\">\n                                            <connections>\n                                                <action selector=\"performClose:\" target=\"Ady-hI-5gd\" id=\"HmO-Ls-i7Q\"/>\n                                            </connections>\n                                        </menuItem>\n                                    </items>\n                                </menu>\n                            </menuItem>\n                            <menuItem title=\"Edit\" id=\"5QF-Oa-p0T\">\n                                <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                <menu key=\"submenu\" title=\"Edit\" id=\"W48-6f-4Dl\">\n                                    <items>\n                                        <menuItem title=\"Undo\" keyEquivalent=\"z\" id=\"dRJ-4n-Yzg\">\n                                            <connections>\n                                                <action selector=\"undo:\" target=\"Ady-hI-5gd\" id=\"M6e-cu-g7V\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Redo\" keyEquivalent=\"Z\" id=\"6dh-zS-Vam\">\n                                            <connections>\n                                                <action selector=\"redo:\" target=\"Ady-hI-5gd\" id=\"oIA-Rs-6OD\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem isSeparatorItem=\"YES\" id=\"WRV-NI-Exz\"/>\n                                        <menuItem title=\"Cut\" keyEquivalent=\"x\" id=\"uRl-iY-unG\">\n                                            <connections>\n                                                <action selector=\"cut:\" target=\"Ady-hI-5gd\" id=\"YJe-68-I9s\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Copy\" keyEquivalent=\"c\" id=\"x3v-GG-iWU\">\n                                            <connections>\n                                                <action selector=\"copy:\" target=\"Ady-hI-5gd\" id=\"G1f-GL-Joy\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Paste\" keyEquivalent=\"v\" id=\"gVA-U4-sdL\">\n                                            <connections>\n                                                <action selector=\"paste:\" target=\"Ady-hI-5gd\" id=\"UvS-8e-Qdg\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Paste and Match Style\" keyEquivalent=\"V\" id=\"WeT-3V-zwk\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\" option=\"YES\" command=\"YES\"/>\n                                            <connections>\n                                                <action selector=\"pasteAsPlainText:\" target=\"Ady-hI-5gd\" id=\"cEh-KX-wJQ\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Delete\" id=\"pa3-QI-u2k\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <connections>\n                                                <action selector=\"delete:\" target=\"Ady-hI-5gd\" id=\"0Mk-Ml-PaM\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Select All\" keyEquivalent=\"a\" id=\"Ruw-6m-B2m\">\n                                            <connections>\n                                                <action selector=\"selectAll:\" target=\"Ady-hI-5gd\" id=\"VNm-Mi-diN\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem isSeparatorItem=\"YES\" id=\"uyl-h8-XO2\"/>\n                                        <menuItem title=\"Find\" id=\"4EN-yA-p0u\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <menu key=\"submenu\" title=\"Find\" id=\"1b7-l0-nxx\">\n                                                <items>\n                                                    <menuItem title=\"Find…\" tag=\"1\" keyEquivalent=\"f\" id=\"Xz5-n4-O0W\">\n                                                        <connections>\n                                                            <action selector=\"performFindPanelAction:\" target=\"Ady-hI-5gd\" id=\"cD7-Qs-BN4\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Find and Replace…\" tag=\"12\" keyEquivalent=\"f\" id=\"YEy-JH-Tfz\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\" option=\"YES\" command=\"YES\"/>\n                                                        <connections>\n                                                            <action selector=\"performFindPanelAction:\" target=\"Ady-hI-5gd\" id=\"WD3-Gg-5AJ\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Find Next\" tag=\"2\" keyEquivalent=\"g\" id=\"q09-fT-Sye\">\n                                                        <connections>\n                                                            <action selector=\"performFindPanelAction:\" target=\"Ady-hI-5gd\" id=\"NDo-RZ-v9R\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Find Previous\" tag=\"3\" keyEquivalent=\"G\" id=\"OwM-mh-QMV\">\n                                                        <connections>\n                                                            <action selector=\"performFindPanelAction:\" target=\"Ady-hI-5gd\" id=\"HOh-sY-3ay\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Use Selection for Find\" tag=\"7\" keyEquivalent=\"e\" id=\"buJ-ug-pKt\">\n                                                        <connections>\n                                                            <action selector=\"performFindPanelAction:\" target=\"Ady-hI-5gd\" id=\"U76-nv-p5D\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Jump to Selection\" keyEquivalent=\"j\" id=\"S0p-oC-mLd\">\n                                                        <connections>\n                                                            <action selector=\"centerSelectionInVisibleArea:\" target=\"Ady-hI-5gd\" id=\"IOG-6D-g5B\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                </items>\n                                            </menu>\n                                        </menuItem>\n                                        <menuItem title=\"Spelling and Grammar\" id=\"Dv1-io-Yv7\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <menu key=\"submenu\" title=\"Spelling\" id=\"3IN-sU-3Bg\">\n                                                <items>\n                                                    <menuItem title=\"Show Spelling and Grammar\" keyEquivalent=\":\" id=\"HFo-cy-zxI\">\n                                                        <connections>\n                                                            <action selector=\"showGuessPanel:\" target=\"Ady-hI-5gd\" id=\"vFj-Ks-hy3\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Check Document Now\" keyEquivalent=\";\" id=\"hz2-CU-CR7\">\n                                                        <connections>\n                                                            <action selector=\"checkSpelling:\" target=\"Ady-hI-5gd\" id=\"fz7-VC-reM\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem isSeparatorItem=\"YES\" id=\"bNw-od-mp5\"/>\n                                                    <menuItem title=\"Check Spelling While Typing\" id=\"rbD-Rh-wIN\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"toggleContinuousSpellChecking:\" target=\"Ady-hI-5gd\" id=\"7w6-Qz-0kB\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Check Grammar With Spelling\" id=\"mK6-2p-4JG\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"toggleGrammarChecking:\" target=\"Ady-hI-5gd\" id=\"muD-Qn-j4w\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Correct Spelling Automatically\" id=\"78Y-hA-62v\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"toggleAutomaticSpellingCorrection:\" target=\"Ady-hI-5gd\" id=\"2lM-Qi-WAP\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                </items>\n                                            </menu>\n                                        </menuItem>\n                                        <menuItem title=\"Substitutions\" id=\"9ic-FL-obx\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <menu key=\"submenu\" title=\"Substitutions\" id=\"FeM-D8-WVr\">\n                                                <items>\n                                                    <menuItem title=\"Show Substitutions\" id=\"z6F-FW-3nz\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"orderFrontSubstitutionsPanel:\" target=\"Ady-hI-5gd\" id=\"oku-mr-iSq\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem isSeparatorItem=\"YES\" id=\"gPx-C9-uUO\"/>\n                                                    <menuItem title=\"Smart Copy/Paste\" id=\"9yt-4B-nSM\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"toggleSmartInsertDelete:\" target=\"Ady-hI-5gd\" id=\"3IJ-Se-DZD\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Smart Quotes\" id=\"hQb-2v-fYv\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"toggleAutomaticQuoteSubstitution:\" target=\"Ady-hI-5gd\" id=\"ptq-xd-QOA\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Smart Dashes\" id=\"rgM-f4-ycn\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"toggleAutomaticDashSubstitution:\" target=\"Ady-hI-5gd\" id=\"oCt-pO-9gS\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Smart Links\" id=\"cwL-P1-jid\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"toggleAutomaticLinkDetection:\" target=\"Ady-hI-5gd\" id=\"Gip-E3-Fov\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Data Detectors\" id=\"tRr-pd-1PS\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"toggleAutomaticDataDetection:\" target=\"Ady-hI-5gd\" id=\"R1I-Nq-Kbl\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Text Replacement\" id=\"HFQ-gK-NFA\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"toggleAutomaticTextReplacement:\" target=\"Ady-hI-5gd\" id=\"DvP-Fe-Py6\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                </items>\n                                            </menu>\n                                        </menuItem>\n                                        <menuItem title=\"Transformations\" id=\"2oI-Rn-ZJC\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <menu key=\"submenu\" title=\"Transformations\" id=\"c8a-y6-VQd\">\n                                                <items>\n                                                    <menuItem title=\"Make Upper Case\" id=\"vmV-6d-7jI\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"uppercaseWord:\" target=\"Ady-hI-5gd\" id=\"sPh-Tk-edu\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Make Lower Case\" id=\"d9M-CD-aMd\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"lowercaseWord:\" target=\"Ady-hI-5gd\" id=\"iUZ-b5-hil\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Capitalize\" id=\"UEZ-Bs-lqG\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"capitalizeWord:\" target=\"Ady-hI-5gd\" id=\"26H-TL-nsh\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                </items>\n                                            </menu>\n                                        </menuItem>\n                                        <menuItem title=\"Speech\" id=\"xrE-MZ-jX0\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <menu key=\"submenu\" title=\"Speech\" id=\"3rS-ZA-NoH\">\n                                                <items>\n                                                    <menuItem title=\"Start Speaking\" id=\"Ynk-f8-cLZ\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"startSpeaking:\" target=\"Ady-hI-5gd\" id=\"654-Ng-kyl\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Stop Speaking\" id=\"Oyz-dy-DGm\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"stopSpeaking:\" target=\"Ady-hI-5gd\" id=\"dX8-6p-jy9\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                </items>\n                                            </menu>\n                                        </menuItem>\n                                    </items>\n                                </menu>\n                            </menuItem>\n                            <menuItem title=\"Format\" id=\"jxT-CU-nIS\">\n                                <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                <menu key=\"submenu\" title=\"Format\" id=\"GEO-Iw-cKr\">\n                                    <items>\n                                        <menuItem title=\"Font\" id=\"Gi5-1S-RQB\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <menu key=\"submenu\" title=\"Font\" systemMenu=\"font\" id=\"aXa-aM-Jaq\">\n                                                <items>\n                                                    <menuItem title=\"Show Fonts\" keyEquivalent=\"t\" id=\"Q5e-8K-NDq\">\n                                                        <connections>\n                                                            <action selector=\"orderFrontFontPanel:\" target=\"YLy-65-1bz\" id=\"WHr-nq-2xA\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Bold\" tag=\"2\" keyEquivalent=\"b\" id=\"GB9-OM-e27\">\n                                                        <connections>\n                                                            <action selector=\"addFontTrait:\" target=\"YLy-65-1bz\" id=\"hqk-hr-sYV\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Italic\" tag=\"1\" keyEquivalent=\"i\" id=\"Vjx-xi-njq\">\n                                                        <connections>\n                                                            <action selector=\"addFontTrait:\" target=\"YLy-65-1bz\" id=\"IHV-OB-c03\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Underline\" keyEquivalent=\"u\" id=\"WRG-CD-K1S\">\n                                                        <connections>\n                                                            <action selector=\"underline:\" target=\"Ady-hI-5gd\" id=\"FYS-2b-JAY\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem isSeparatorItem=\"YES\" id=\"5gT-KC-WSO\"/>\n                                                    <menuItem title=\"Bigger\" tag=\"3\" keyEquivalent=\"+\" id=\"Ptp-SP-VEL\">\n                                                        <connections>\n                                                            <action selector=\"modifyFont:\" target=\"YLy-65-1bz\" id=\"Uc7-di-UnL\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Smaller\" tag=\"4\" keyEquivalent=\"-\" id=\"i1d-Er-qST\">\n                                                        <connections>\n                                                            <action selector=\"modifyFont:\" target=\"YLy-65-1bz\" id=\"HcX-Lf-eNd\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem isSeparatorItem=\"YES\" id=\"kx3-Dk-x3B\"/>\n                                                    <menuItem title=\"Kern\" id=\"jBQ-r6-VK2\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <menu key=\"submenu\" title=\"Kern\" id=\"tlD-Oa-oAM\">\n                                                            <items>\n                                                                <menuItem title=\"Use Default\" id=\"GUa-eO-cwY\">\n                                                                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                                    <connections>\n                                                                        <action selector=\"useStandardKerning:\" target=\"Ady-hI-5gd\" id=\"6dk-9l-Ckg\"/>\n                                                                    </connections>\n                                                                </menuItem>\n                                                                <menuItem title=\"Use None\" id=\"cDB-IK-hbR\">\n                                                                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                                    <connections>\n                                                                        <action selector=\"turnOffKerning:\" target=\"Ady-hI-5gd\" id=\"U8a-gz-Maa\"/>\n                                                                    </connections>\n                                                                </menuItem>\n                                                                <menuItem title=\"Tighten\" id=\"46P-cB-AYj\">\n                                                                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                                    <connections>\n                                                                        <action selector=\"tightenKerning:\" target=\"Ady-hI-5gd\" id=\"hr7-Nz-8ro\"/>\n                                                                    </connections>\n                                                                </menuItem>\n                                                                <menuItem title=\"Loosen\" id=\"ogc-rX-tC1\">\n                                                                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                                    <connections>\n                                                                        <action selector=\"loosenKerning:\" target=\"Ady-hI-5gd\" id=\"8i4-f9-FKE\"/>\n                                                                    </connections>\n                                                                </menuItem>\n                                                            </items>\n                                                        </menu>\n                                                    </menuItem>\n                                                    <menuItem title=\"Ligatures\" id=\"o6e-r0-MWq\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <menu key=\"submenu\" title=\"Ligatures\" id=\"w0m-vy-SC9\">\n                                                            <items>\n                                                                <menuItem title=\"Use Default\" id=\"agt-UL-0e3\">\n                                                                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                                    <connections>\n                                                                        <action selector=\"useStandardLigatures:\" target=\"Ady-hI-5gd\" id=\"7uR-wd-Dx6\"/>\n                                                                    </connections>\n                                                                </menuItem>\n                                                                <menuItem title=\"Use None\" id=\"J7y-lM-qPV\">\n                                                                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                                    <connections>\n                                                                        <action selector=\"turnOffLigatures:\" target=\"Ady-hI-5gd\" id=\"iX2-gA-Ilz\"/>\n                                                                    </connections>\n                                                                </menuItem>\n                                                                <menuItem title=\"Use All\" id=\"xQD-1f-W4t\">\n                                                                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                                    <connections>\n                                                                        <action selector=\"useAllLigatures:\" target=\"Ady-hI-5gd\" id=\"KcB-kA-TuK\"/>\n                                                                    </connections>\n                                                                </menuItem>\n                                                            </items>\n                                                        </menu>\n                                                    </menuItem>\n                                                    <menuItem title=\"Baseline\" id=\"OaQ-X3-Vso\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <menu key=\"submenu\" title=\"Baseline\" id=\"ijk-EB-dga\">\n                                                            <items>\n                                                                <menuItem title=\"Use Default\" id=\"3Om-Ey-2VK\">\n                                                                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                                    <connections>\n                                                                        <action selector=\"unscript:\" target=\"Ady-hI-5gd\" id=\"0vZ-95-Ywn\"/>\n                                                                    </connections>\n                                                                </menuItem>\n                                                                <menuItem title=\"Superscript\" id=\"Rqc-34-cIF\">\n                                                                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                                    <connections>\n                                                                        <action selector=\"superscript:\" target=\"Ady-hI-5gd\" id=\"3qV-fo-wpU\"/>\n                                                                    </connections>\n                                                                </menuItem>\n                                                                <menuItem title=\"Subscript\" id=\"I0S-gh-46l\">\n                                                                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                                    <connections>\n                                                                        <action selector=\"subscript:\" target=\"Ady-hI-5gd\" id=\"Q6W-4W-IGz\"/>\n                                                                    </connections>\n                                                                </menuItem>\n                                                                <menuItem title=\"Raise\" id=\"2h7-ER-AoG\">\n                                                                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                                    <connections>\n                                                                        <action selector=\"raiseBaseline:\" target=\"Ady-hI-5gd\" id=\"4sk-31-7Q9\"/>\n                                                                    </connections>\n                                                                </menuItem>\n                                                                <menuItem title=\"Lower\" id=\"1tx-W0-xDw\">\n                                                                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                                    <connections>\n                                                                        <action selector=\"lowerBaseline:\" target=\"Ady-hI-5gd\" id=\"OF1-bc-KW4\"/>\n                                                                    </connections>\n                                                                </menuItem>\n                                                            </items>\n                                                        </menu>\n                                                    </menuItem>\n                                                    <menuItem isSeparatorItem=\"YES\" id=\"Ndw-q3-faq\"/>\n                                                    <menuItem title=\"Show Colors\" keyEquivalent=\"C\" id=\"bgn-CT-cEk\">\n                                                        <connections>\n                                                            <action selector=\"orderFrontColorPanel:\" target=\"Ady-hI-5gd\" id=\"mSX-Xz-DV3\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem isSeparatorItem=\"YES\" id=\"iMs-zA-UFJ\"/>\n                                                    <menuItem title=\"Copy Style\" keyEquivalent=\"c\" id=\"5Vv-lz-BsD\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\" option=\"YES\" command=\"YES\"/>\n                                                        <connections>\n                                                            <action selector=\"copyFont:\" target=\"Ady-hI-5gd\" id=\"GJO-xA-L4q\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Paste Style\" keyEquivalent=\"v\" id=\"vKC-jM-MkH\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\" option=\"YES\" command=\"YES\"/>\n                                                        <connections>\n                                                            <action selector=\"pasteFont:\" target=\"Ady-hI-5gd\" id=\"JfD-CL-leO\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                </items>\n                                            </menu>\n                                        </menuItem>\n                                        <menuItem title=\"Text\" id=\"Fal-I4-PZk\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <menu key=\"submenu\" title=\"Text\" id=\"d9c-me-L2H\">\n                                                <items>\n                                                    <menuItem title=\"Align Left\" keyEquivalent=\"{\" id=\"ZM1-6Q-yy1\">\n                                                        <connections>\n                                                            <action selector=\"alignLeft:\" target=\"Ady-hI-5gd\" id=\"zUv-R1-uAa\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Center\" keyEquivalent=\"|\" id=\"VIY-Ag-zcb\">\n                                                        <connections>\n                                                            <action selector=\"alignCenter:\" target=\"Ady-hI-5gd\" id=\"spX-mk-kcS\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Justify\" id=\"J5U-5w-g23\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"alignJustified:\" target=\"Ady-hI-5gd\" id=\"ljL-7U-jND\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Align Right\" keyEquivalent=\"}\" id=\"wb2-vD-lq4\">\n                                                        <connections>\n                                                            <action selector=\"alignRight:\" target=\"Ady-hI-5gd\" id=\"r48-bG-YeY\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem isSeparatorItem=\"YES\" id=\"4s2-GY-VfK\"/>\n                                                    <menuItem title=\"Writing Direction\" id=\"H1b-Si-o9J\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <menu key=\"submenu\" title=\"Writing Direction\" id=\"8mr-sm-Yjd\">\n                                                            <items>\n                                                                <menuItem title=\"Paragraph\" enabled=\"NO\" id=\"ZvO-Gk-QUH\">\n                                                                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                                </menuItem>\n                                                                <menuItem id=\"YGs-j5-SAR\">\n                                                                    <string key=\"title\">\tDefault</string>\n                                                                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                                    <connections>\n                                                                        <action selector=\"makeBaseWritingDirectionNatural:\" target=\"Ady-hI-5gd\" id=\"qtV-5e-UBP\"/>\n                                                                    </connections>\n                                                                </menuItem>\n                                                                <menuItem id=\"Lbh-J2-qVU\">\n                                                                    <string key=\"title\">\tLeft to Right</string>\n                                                                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                                    <connections>\n                                                                        <action selector=\"makeBaseWritingDirectionLeftToRight:\" target=\"Ady-hI-5gd\" id=\"S0X-9S-QSf\"/>\n                                                                    </connections>\n                                                                </menuItem>\n                                                                <menuItem id=\"jFq-tB-4Kx\">\n                                                                    <string key=\"title\">\tRight to Left</string>\n                                                                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                                    <connections>\n                                                                        <action selector=\"makeBaseWritingDirectionRightToLeft:\" target=\"Ady-hI-5gd\" id=\"5fk-qB-AqJ\"/>\n                                                                    </connections>\n                                                                </menuItem>\n                                                                <menuItem isSeparatorItem=\"YES\" id=\"swp-gr-a21\"/>\n                                                                <menuItem title=\"Selection\" enabled=\"NO\" id=\"cqv-fj-IhA\">\n                                                                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                                </menuItem>\n                                                                <menuItem id=\"Nop-cj-93Q\">\n                                                                    <string key=\"title\">\tDefault</string>\n                                                                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                                    <connections>\n                                                                        <action selector=\"makeTextWritingDirectionNatural:\" target=\"Ady-hI-5gd\" id=\"lPI-Se-ZHp\"/>\n                                                                    </connections>\n                                                                </menuItem>\n                                                                <menuItem id=\"BgM-ve-c93\">\n                                                                    <string key=\"title\">\tLeft to Right</string>\n                                                                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                                    <connections>\n                                                                        <action selector=\"makeTextWritingDirectionLeftToRight:\" target=\"Ady-hI-5gd\" id=\"caW-Bv-w94\"/>\n                                                                    </connections>\n                                                                </menuItem>\n                                                                <menuItem id=\"RB4-Sm-HuC\">\n                                                                    <string key=\"title\">\tRight to Left</string>\n                                                                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                                    <connections>\n                                                                        <action selector=\"makeTextWritingDirectionRightToLeft:\" target=\"Ady-hI-5gd\" id=\"EXD-6r-ZUu\"/>\n                                                                    </connections>\n                                                                </menuItem>\n                                                            </items>\n                                                        </menu>\n                                                    </menuItem>\n                                                    <menuItem isSeparatorItem=\"YES\" id=\"fKy-g9-1gm\"/>\n                                                    <menuItem title=\"Show Ruler\" id=\"vLm-3I-IUL\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"toggleRuler:\" target=\"Ady-hI-5gd\" id=\"FOx-HJ-KwY\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Copy Ruler\" keyEquivalent=\"c\" id=\"MkV-Pr-PK5\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\" control=\"YES\" command=\"YES\"/>\n                                                        <connections>\n                                                            <action selector=\"copyRuler:\" target=\"Ady-hI-5gd\" id=\"71i-fW-3W2\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Paste Ruler\" keyEquivalent=\"v\" id=\"LVM-kO-fVI\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\" control=\"YES\" command=\"YES\"/>\n                                                        <connections>\n                                                            <action selector=\"pasteRuler:\" target=\"Ady-hI-5gd\" id=\"cSh-wd-qM2\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                </items>\n                                            </menu>\n                                        </menuItem>\n                                    </items>\n                                </menu>\n                            </menuItem>\n                            <menuItem title=\"View\" id=\"H8h-7b-M4v\">\n                                <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                <menu key=\"submenu\" title=\"View\" id=\"HyV-fh-RgO\">\n                                    <items>\n                                        <menuItem title=\"Show Toolbar\" keyEquivalent=\"t\" id=\"snW-S8-Cw5\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\" option=\"YES\" command=\"YES\"/>\n                                            <connections>\n                                                <action selector=\"toggleToolbarShown:\" target=\"Ady-hI-5gd\" id=\"BXY-wc-z0C\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Customize Toolbar…\" id=\"1UK-8n-QPP\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <connections>\n                                                <action selector=\"runToolbarCustomizationPalette:\" target=\"Ady-hI-5gd\" id=\"pQI-g3-MTW\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem isSeparatorItem=\"YES\" id=\"hB3-LF-h0Y\"/>\n                                        <menuItem title=\"Diagnostics Info\" id=\"oQB-FY-dcB\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <connections>\n                                                <action selector=\"showDiagnosticsInfo:\" target=\"Ady-hI-5gd\" id=\"hkZ-Xn-3T1\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem isSeparatorItem=\"YES\" id=\"dRF-4R-egy\"/>\n                                        <menuItem title=\"Show Sidebar\" keyEquivalent=\"s\" id=\"kIP-vf-haE\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\" control=\"YES\" command=\"YES\"/>\n                                            <connections>\n                                                <action selector=\"toggleSidebar:\" target=\"Ady-hI-5gd\" id=\"iwa-gc-5KM\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Enter Full Screen\" keyEquivalent=\"f\" id=\"4J7-dP-txa\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\" control=\"YES\" command=\"YES\"/>\n                                            <connections>\n                                                <action selector=\"toggleFullScreen:\" target=\"Ady-hI-5gd\" id=\"dU3-MA-1Rq\"/>\n                                            </connections>\n                                        </menuItem>\n                                    </items>\n                                </menu>\n                            </menuItem>\n                            <menuItem title=\"Window\" id=\"aUF-d1-5bR\">\n                                <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                <menu key=\"submenu\" title=\"Window\" systemMenu=\"window\" id=\"Td7-aD-5lo\">\n                                    <items>\n                                        <menuItem title=\"Minimize\" keyEquivalent=\"m\" id=\"OY7-WF-poV\">\n                                            <connections>\n                                                <action selector=\"performMiniaturize:\" target=\"Ady-hI-5gd\" id=\"VwT-WD-YPe\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Zoom\" keyEquivalent=\"m\" id=\"R4o-n2-Eq4\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\" shift=\"YES\" command=\"YES\"/>\n                                            <connections>\n                                                <action selector=\"performZoom:\" target=\"Ady-hI-5gd\" id=\"DIl-cC-cCs\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem isSeparatorItem=\"YES\" id=\"eu3-7i-yIM\"/>\n                                        <menuItem title=\"Bring All to Front\" id=\"LE2-aR-0XJ\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <connections>\n                                                <action selector=\"arrangeInFront:\" target=\"Ady-hI-5gd\" id=\"DRN-fu-gQh\"/>\n                                            </connections>\n                                        </menuItem>\n                                    </items>\n                                </menu>\n                            </menuItem>\n                            <menuItem title=\"Help\" id=\"wpr-3q-Mcd\">\n                                <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                <menu key=\"submenu\" title=\"Help\" systemMenu=\"help\" id=\"F2S-fz-NVQ\">\n                                    <items>\n                                        <menuItem title=\"Focalboard Help\" keyEquivalent=\"?\" id=\"FKE-Sm-Kum\">\n                                            <connections>\n                                                <action selector=\"showHelp:\" target=\"Ady-hI-5gd\" id=\"y7X-2Q-9no\"/>\n                                            </connections>\n                                        </menuItem>\n                                    </items>\n                                </menu>\n                            </menuItem>\n                        </items>\n                    </menu>\n                    <connections>\n                        <outlet property=\"delegate\" destination=\"Voe-Tx-rLC\" id=\"PrD-fu-P6m\"/>\n                    </connections>\n                </application>\n                <customObject id=\"Voe-Tx-rLC\" customClass=\"AppDelegate\" customModule=\"Focalboard\" customModuleProvider=\"target\"/>\n                <customObject id=\"YLy-65-1bz\" customClass=\"NSFontManager\"/>\n                <customObject id=\"Ady-hI-5gd\" userLabel=\"First Responder\" customClass=\"NSResponder\" sceneMemberID=\"firstResponder\"/>\n            </objects>\n            <point key=\"canvasLocation\" x=\"75\" y=\"0.0\"/>\n        </scene>\n        <!--Window Controller-->\n        <scene sceneID=\"R2V-B0-nI4\">\n            <objects>\n                <windowController storyboardIdentifier=\"WindowController\" id=\"B8D-0N-5wS\" customClass=\"AutoSaveWindowController\" customModule=\"Focalboard\" customModuleProvider=\"target\" sceneMemberID=\"viewController\">\n                    <window key=\"window\" title=\"Focalboard\" allowsToolTipsWhenApplicationIsInactive=\"NO\" autorecalculatesKeyViewLoop=\"NO\" releasedWhenClosed=\"NO\" visibleAtLaunch=\"NO\" animationBehavior=\"default\" tabbingMode=\"disallowed\" id=\"IQv-IB-iLA\">\n                        <windowStyleMask key=\"styleMask\" titled=\"YES\" closable=\"YES\" miniaturizable=\"YES\" resizable=\"YES\"/>\n                        <windowPositionMask key=\"initialPositionMask\" leftStrut=\"YES\" rightStrut=\"YES\" topStrut=\"YES\" bottomStrut=\"YES\"/>\n                        <rect key=\"contentRect\" x=\"196\" y=\"240\" width=\"800\" height=\"600\"/>\n                        <rect key=\"screenRect\" x=\"0.0\" y=\"0.0\" width=\"2560\" height=\"1417\"/>\n                        <value key=\"minSize\" type=\"size\" width=\"800\" height=\"600\"/>\n                        <connections>\n                            <outlet property=\"delegate\" destination=\"B8D-0N-5wS\" id=\"98r-iN-zZc\"/>\n                        </connections>\n                    </window>\n                    <connections>\n                        <segue destination=\"XfG-lQ-9wD\" kind=\"relationship\" relationship=\"window.shadowedContentViewController\" id=\"cq2-FE-JQM\"/>\n                    </connections>\n                </windowController>\n                <customObject id=\"Oky-zY-oP4\" userLabel=\"First Responder\" customClass=\"NSResponder\" sceneMemberID=\"firstResponder\"/>\n            </objects>\n            <point key=\"canvasLocation\" x=\"75\" y=\"250\"/>\n        </scene>\n        <!--View Controller-->\n        <scene sceneID=\"hIz-AP-VOD\">\n            <objects>\n                <viewController storyboardIdentifier=\"ViewController\" id=\"XfG-lQ-9wD\" customClass=\"ViewController\" customModule=\"Focalboard\" customModuleProvider=\"target\" sceneMemberID=\"viewController\">\n                    <view key=\"view\" id=\"m2S-Jp-Qdl\">\n                        <rect key=\"frame\" x=\"0.0\" y=\"0.0\" width=\"1000\" height=\"600\"/>\n                        <autoresizingMask key=\"autoresizingMask\"/>\n                        <subviews>\n                            <wkWebView wantsLayer=\"YES\" translatesAutoresizingMaskIntoConstraints=\"NO\" id=\"7ZQ-vZ-SCO\" customClass=\"CustomWKWebView\" customModule=\"Focalboard\" customModuleProvider=\"target\">\n                                <rect key=\"frame\" x=\"0.0\" y=\"0.0\" width=\"1000\" height=\"600\"/>\n                                <wkWebViewConfiguration key=\"configuration\">\n                                    <audiovisualMediaTypes key=\"mediaTypesRequiringUserActionForPlayback\" none=\"YES\"/>\n                                    <wkPreferences key=\"preferences\"/>\n                                </wkWebViewConfiguration>\n                            </wkWebView>\n                        </subviews>\n                        <constraints>\n                            <constraint firstItem=\"7ZQ-vZ-SCO\" firstAttribute=\"leading\" secondItem=\"m2S-Jp-Qdl\" secondAttribute=\"leading\" id=\"8Mz-F1-x67\"/>\n                            <constraint firstAttribute=\"bottom\" secondItem=\"7ZQ-vZ-SCO\" secondAttribute=\"bottom\" id=\"EAF-OW-flM\"/>\n                            <constraint firstAttribute=\"trailing\" secondItem=\"7ZQ-vZ-SCO\" secondAttribute=\"trailing\" id=\"oP2-SC-KDC\"/>\n                            <constraint firstItem=\"7ZQ-vZ-SCO\" firstAttribute=\"top\" secondItem=\"m2S-Jp-Qdl\" secondAttribute=\"top\" id=\"pla-0h-Dvy\"/>\n                        </constraints>\n                    </view>\n                    <connections>\n                        <outlet property=\"webView\" destination=\"7ZQ-vZ-SCO\" id=\"xwW-9M-8ie\"/>\n                    </connections>\n                </viewController>\n                <customObject id=\"rPt-NT-nkU\" userLabel=\"First Responder\" customClass=\"NSResponder\" sceneMemberID=\"firstResponder\"/>\n            </objects>\n            <point key=\"canvasLocation\" x=\"75\" y=\"1006\"/>\n        </scene>\n        <!--What's New-->\n        <scene sceneID=\"xJu-Zl-eo4\">\n            <objects>\n                <viewController title=\"What's New\" storyboardIdentifier=\"WhatsNewViewController\" id=\"aYX-zT-OJj\" customClass=\"WhatsNewViewController\" customModule=\"Focalboard\" customModuleProvider=\"target\" sceneMemberID=\"viewController\">\n                    <view key=\"view\" id=\"fy7-95-Eie\">\n                        <rect key=\"frame\" x=\"0.0\" y=\"0.0\" width=\"614\" height=\"492\"/>\n                        <autoresizingMask key=\"autoresizingMask\"/>\n                        <subviews>\n                            <scrollView borderType=\"none\" horizontalLineScroll=\"10\" horizontalPageScroll=\"10\" verticalLineScroll=\"10\" verticalPageScroll=\"10\" hasHorizontalScroller=\"NO\" translatesAutoresizingMaskIntoConstraints=\"NO\" id=\"baY-7A-lBA\">\n                                <rect key=\"frame\" x=\"20\" y=\"54\" width=\"574\" height=\"418\"/>\n                                <clipView key=\"contentView\" drawsBackground=\"NO\" copiesOnScroll=\"NO\" id=\"qB2-dj-jVl\">\n                                    <rect key=\"frame\" x=\"0.0\" y=\"0.0\" width=\"559\" height=\"418\"/>\n                                    <autoresizingMask key=\"autoresizingMask\" widthSizable=\"YES\" heightSizable=\"YES\"/>\n                                    <subviews>\n                                        <textView editable=\"NO\" importsGraphics=\"NO\" richText=\"NO\" verticallyResizable=\"YES\" smartInsertDelete=\"YES\" id=\"f3s-Q5-6IG\">\n                                            <rect key=\"frame\" x=\"0.0\" y=\"0.0\" width=\"559\" height=\"418\"/>\n                                            <autoresizingMask key=\"autoresizingMask\" widthSizable=\"YES\" heightSizable=\"YES\"/>\n                                            <color key=\"textColor\" name=\"textColor\" catalog=\"System\" colorSpace=\"catalog\"/>\n                                            <color key=\"backgroundColor\" name=\"textBackgroundColor\" catalog=\"System\" colorSpace=\"catalog\"/>\n                                            <size key=\"minSize\" width=\"559\" height=\"418\"/>\n                                            <size key=\"maxSize\" width=\"636\" height=\"10000000\"/>\n                                            <attributedString key=\"textStorage\">\n                                                <fragment content=\"Welcome to Focalboard!\">\n                                                    <attributes>\n                                                        <color key=\"NSColor\" name=\"textColor\" catalog=\"System\" colorSpace=\"catalog\"/>\n                                                        <font key=\"NSFont\" size=\"18\" name=\"Helvetica-Bold\"/>\n                                                        <paragraphStyle key=\"NSParagraphStyle\" alignment=\"left\" lineBreakMode=\"wordWrapping\" baseWritingDirection=\"natural\" tighteningFactorForTruncation=\"0.0\" allowsDefaultTighteningForTruncation=\"NO\">\n                                                            <tabStops>\n                                                                <textTab alignment=\"left\" location=\"36\">\n                                                                    <options/>\n                                                                </textTab>\n                                                                <textTab alignment=\"left\" location=\"72\">\n                                                                    <options/>\n                                                                </textTab>\n                                                                <textTab alignment=\"left\" location=\"108\">\n                                                                    <options/>\n                                                                </textTab>\n                                                                <textTab alignment=\"left\" location=\"144\">\n                                                                    <options/>\n                                                                </textTab>\n                                                                <textTab alignment=\"left\" location=\"180\">\n                                                                    <options/>\n                                                                </textTab>\n                                                                <textTab alignment=\"left\" location=\"216\">\n                                                                    <options/>\n                                                                </textTab>\n                                                                <textTab alignment=\"left\" location=\"252\">\n                                                                    <options/>\n                                                                </textTab>\n                                                                <textTab alignment=\"left\" location=\"288\">\n                                                                    <options/>\n                                                                </textTab>\n                                                                <textTab alignment=\"left\" location=\"324\">\n                                                                    <options/>\n                                                                </textTab>\n                                                                <textTab alignment=\"left\" location=\"360\">\n                                                                    <options/>\n                                                                </textTab>\n                                                                <textTab alignment=\"left\" location=\"396\">\n                                                                    <options/>\n                                                                </textTab>\n                                                                <textTab alignment=\"left\" location=\"432\">\n                                                                    <options/>\n                                                                </textTab>\n                                                            </tabStops>\n                                                        </paragraphStyle>\n                                                    </attributes>\n                                                </fragment>\n                                                <fragment>\n                                                    <string key=\"content\" base64-UTF8=\"YES\">\nCg\n</string>\n                                                    <attributes>\n                                                        <color key=\"NSColor\" name=\"textColor\" catalog=\"System\" colorSpace=\"catalog\"/>\n                                                        <font key=\"NSFont\" size=\"12\" name=\"Helvetica\"/>\n                                                        <paragraphStyle key=\"NSParagraphStyle\" alignment=\"left\" lineBreakMode=\"wordWrapping\" baseWritingDirection=\"natural\" tighteningFactorForTruncation=\"0.0\" allowsDefaultTighteningForTruncation=\"NO\">\n                                                            <tabStops>\n                                                                <textTab alignment=\"left\" location=\"36\">\n                                                                    <options/>\n                                                                </textTab>\n                                                                <textTab alignment=\"left\" location=\"72\">\n                                                                    <options/>\n                                                                </textTab>\n                                                                <textTab alignment=\"left\" location=\"108\">\n                                                                    <options/>\n                                                                </textTab>\n                                                                <textTab alignment=\"left\" location=\"144\">\n                                                                    <options/>\n                                                                </textTab>\n                                                                <textTab alignment=\"left\" location=\"180\">\n                                                                    <options/>\n                                                                </textTab>\n                                                                <textTab alignment=\"left\" location=\"216\">\n                                                                    <options/>\n                                                                </textTab>\n                                                                <textTab alignment=\"left\" location=\"252\">\n                                                                    <options/>\n                                                                </textTab>\n                                                                <textTab alignment=\"left\" location=\"288\">\n                                                                    <options/>\n                                                                </textTab>\n                                                                <textTab alignment=\"left\" location=\"324\">\n                                                                    <options/>\n                                                                </textTab>\n                                                                <textTab alignment=\"left\" location=\"360\">\n                                                                    <options/>\n                                                                </textTab>\n                                                                <textTab alignment=\"left\" location=\"396\">\n                                                                    <options/>\n                                                                </textTab>\n                                                                <textTab alignment=\"left\" location=\"432\">\n                                                                    <options/>\n                                                                </textTab>\n                                                            </tabStops>\n                                                        </paragraphStyle>\n                                                    </attributes>\n                                                </fragment>\n                                                <fragment>\n                                                    <string key=\"content\" base64-UTF8=\"YES\">\nCg\n</string>\n                                                    <attributes>\n                                                        <color key=\"NSColor\" name=\"textColor\" catalog=\"System\" colorSpace=\"catalog\"/>\n                                                        <font key=\"NSFont\" size=\"14\" name=\"Helvetica\"/>\n                                                        <paragraphStyle key=\"NSParagraphStyle\" alignment=\"left\" lineBreakMode=\"wordWrapping\" baseWritingDirection=\"natural\" tighteningFactorForTruncation=\"0.0\" allowsDefaultTighteningForTruncation=\"NO\">\n                                                            <tabStops>\n                                                                <textTab alignment=\"left\" location=\"36\">\n                                                                    <options/>\n                                                                </textTab>\n                                                                <textTab alignment=\"left\" location=\"72\">\n                                                                    <options/>\n                                                                </textTab>\n                                                                <textTab alignment=\"left\" location=\"108\">\n                                                                    <options/>\n                                                                </textTab>\n                                                                <textTab alignment=\"left\" location=\"144\">\n                                                                    <options/>\n                                                                </textTab>\n                                                                <textTab alignment=\"left\" location=\"180\">\n                                                                    <options/>\n                                                                </textTab>\n                                                                <textTab alignment=\"left\" location=\"216\">\n                                                                    <options/>\n                                                                </textTab>\n                                                                <textTab alignment=\"left\" location=\"252\">\n                                                                    <options/>\n                                                                </textTab>\n                                                                <textTab alignment=\"left\" location=\"288\">\n                                                                    <options/>\n                                                                </textTab>\n                                                                <textTab alignment=\"left\" location=\"324\">\n                                                                    <options/>\n                                                                </textTab>\n                                                                <textTab alignment=\"left\" location=\"360\">\n                                                                    <options/>\n                                                                </textTab>\n                                                                <textTab alignment=\"left\" location=\"396\">\n                                                                    <options/>\n                                                                </textTab>\n                                                                <textTab alignment=\"left\" location=\"432\">\n                                                                    <options/>\n                                                                </textTab>\n                                                            </tabStops>\n                                                        </paragraphStyle>\n                                                    </attributes>\n                                                </fragment>\n                                                <fragment>\n                                                    <string key=\"content\">Thank you contributors!\n \nIf you like what you see, please consider taking a moment to rate this version in the App Store. Your positive ratings make a real difference for us.\n\nThanks so much,\n\n-- The Focalboard developers and community\n</string>\n                                                    <attributes>\n                                                        <color key=\"NSColor\" name=\"textColor\" catalog=\"System\" colorSpace=\"catalog\"/>\n                                                        <font key=\"NSFont\" textStyle=\"body\" name=\".SFNS-Regular\"/>\n                                                        <paragraphStyle key=\"NSParagraphStyle\" alignment=\"left\" lineBreakMode=\"wordWrapping\" baseWritingDirection=\"natural\" tighteningFactorForTruncation=\"0.0\" allowsDefaultTighteningForTruncation=\"NO\">\n                                                            <tabStops>\n                                                                <textTab alignment=\"left\" location=\"36\">\n                                                                    <options/>\n                                                                </textTab>\n                                                                <textTab alignment=\"left\" location=\"72\">\n                                                                    <options/>\n                                                                </textTab>\n                                                                <textTab alignment=\"left\" location=\"108\">\n                                                                    <options/>\n                                                                </textTab>\n                                                                <textTab alignment=\"left\" location=\"144\">\n                                                                    <options/>\n                                                                </textTab>\n                                                                <textTab alignment=\"left\" location=\"180\">\n                                                                    <options/>\n                                                                </textTab>\n                                                                <textTab alignment=\"left\" location=\"216\">\n                                                                    <options/>\n                                                                </textTab>\n                                                                <textTab alignment=\"left\" location=\"252\">\n                                                                    <options/>\n                                                                </textTab>\n                                                                <textTab alignment=\"left\" location=\"288\">\n                                                                    <options/>\n                                                                </textTab>\n                                                                <textTab alignment=\"left\" location=\"324\">\n                                                                    <options/>\n                                                                </textTab>\n                                                                <textTab alignment=\"left\" location=\"360\">\n                                                                    <options/>\n                                                                </textTab>\n                                                                <textTab alignment=\"left\" location=\"396\">\n                                                                    <options/>\n                                                                </textTab>\n                                                                <textTab alignment=\"left\" location=\"432\">\n                                                                    <options/>\n                                                                </textTab>\n                                                            </tabStops>\n                                                        </paragraphStyle>\n                                                    </attributes>\n                                                </fragment>\n                                            </attributedString>\n                                            <color key=\"insertionPointColor\" name=\"textColor\" catalog=\"System\" colorSpace=\"catalog\"/>\n                                        </textView>\n                                    </subviews>\n                                </clipView>\n                                <scroller key=\"horizontalScroller\" hidden=\"YES\" wantsLayer=\"YES\" verticalHuggingPriority=\"750\" horizontal=\"YES\" id=\"tBj-g3-APN\">\n                                    <rect key=\"frame\" x=\"-100\" y=\"-100\" width=\"225\" height=\"15\"/>\n                                    <autoresizingMask key=\"autoresizingMask\"/>\n                                </scroller>\n                                <scroller key=\"verticalScroller\" wantsLayer=\"YES\" verticalHuggingPriority=\"750\" horizontal=\"NO\" id=\"7bj-hR-AiH\">\n                                    <rect key=\"frame\" x=\"559\" y=\"0.0\" width=\"15\" height=\"418\"/>\n                                    <autoresizingMask key=\"autoresizingMask\"/>\n                                </scroller>\n                            </scrollView>\n                            <button verticalHuggingPriority=\"750\" translatesAutoresizingMaskIntoConstraints=\"NO\" id=\"WZS-cI-X3m\">\n                                <rect key=\"frame\" x=\"13\" y=\"7\" width=\"314\" height=\"32\"/>\n                                <constraints>\n                                    <constraint firstAttribute=\"width\" relation=\"greaterThanOrEqual\" constant=\"300\" id=\"tZN-oK-pDS\"/>\n                                </constraints>\n                                <buttonCell key=\"cell\" type=\"push\" title=\"Setup free Mattermost Boards server\" bezelStyle=\"rounded\" alignment=\"center\" borderStyle=\"border\" imageScaling=\"proportionallyDown\" inset=\"2\" id=\"XJY-Vn-Z2s\">\n                                    <behavior key=\"behavior\" pushIn=\"YES\" lightByBackground=\"YES\" lightByGray=\"YES\"/>\n                                    <font key=\"font\" metaFont=\"system\"/>\n                                </buttonCell>\n                                <connections>\n                                    <action selector=\"cloudButtonClicked:\" target=\"Fag-zV-Usm\" id=\"ju3-LN-Zq9\"/>\n                                </connections>\n                            </button>\n                            <button verticalHuggingPriority=\"750\" translatesAutoresizingMaskIntoConstraints=\"NO\" id=\"dYB-Yp-UHf\">\n                                <rect key=\"frame\" x=\"500\" y=\"7\" width=\"89\" height=\"32\"/>\n                                <constraints>\n                                    <constraint firstAttribute=\"width\" constant=\"75\" id=\"vxR-kQ-YQg\"/>\n                                </constraints>\n                                <buttonCell key=\"cell\" type=\"push\" title=\"Ok\" bezelStyle=\"rounded\" alignment=\"center\" borderStyle=\"border\" imageScaling=\"proportionallyDown\" inset=\"2\" id=\"Cvr-aY-V7D\">\n                                    <behavior key=\"behavior\" pushIn=\"YES\" lightByBackground=\"YES\" lightByGray=\"YES\"/>\n                                    <font key=\"font\" metaFont=\"system\"/>\n                                </buttonCell>\n                                <connections>\n                                    <action selector=\"closeButtonClicked:\" target=\"aYX-zT-OJj\" id=\"run-tB-6Tb\"/>\n                                </connections>\n                            </button>\n                            <button verticalHuggingPriority=\"750\" translatesAutoresizingMaskIntoConstraints=\"NO\" id=\"eyp-ee-dxf\">\n                                <rect key=\"frame\" x=\"325\" y=\"7\" width=\"164\" height=\"32\"/>\n                                <constraints>\n                                    <constraint firstAttribute=\"width\" constant=\"150\" id=\"Zzi-QI-UrX\"/>\n                                </constraints>\n                                <buttonCell key=\"cell\" type=\"push\" title=\"Rate Focalboard\" bezelStyle=\"rounded\" alignment=\"center\" borderStyle=\"border\" imageScaling=\"proportionallyDown\" inset=\"2\" id=\"7Kt-FU-UP3\">\n                                    <behavior key=\"behavior\" pushIn=\"YES\" lightByBackground=\"YES\" lightByGray=\"YES\"/>\n                                    <font key=\"font\" metaFont=\"system\"/>\n                                </buttonCell>\n                                <connections>\n                                    <action selector=\"rateButtonClicked:\" target=\"aYX-zT-OJj\" id=\"QqZ-am-cnJ\"/>\n                                </connections>\n                            </button>\n                        </subviews>\n                        <constraints>\n                            <constraint firstItem=\"WZS-cI-X3m\" firstAttribute=\"top\" secondItem=\"baY-7A-lBA\" secondAttribute=\"bottom\" constant=\"20\" symbolic=\"YES\" id=\"29y-HQ-Ik9\"/>\n                            <constraint firstItem=\"WZS-cI-X3m\" firstAttribute=\"leading\" secondItem=\"fy7-95-Eie\" secondAttribute=\"leading\" constant=\"20\" symbolic=\"YES\" id=\"EMs-BU-zTE\"/>\n                            <constraint firstAttribute=\"bottom\" secondItem=\"eyp-ee-dxf\" secondAttribute=\"bottom\" constant=\"14\" id=\"Kvc-VM-O1W\"/>\n                            <constraint firstAttribute=\"trailing\" secondItem=\"dYB-Yp-UHf\" secondAttribute=\"trailing\" constant=\"32\" id=\"SQ6-Hs-a1n\"/>\n                            <constraint firstItem=\"eyp-ee-dxf\" firstAttribute=\"top\" secondItem=\"baY-7A-lBA\" secondAttribute=\"bottom\" constant=\"20\" symbolic=\"YES\" id=\"UIA-ji-xOe\"/>\n                            <constraint firstAttribute=\"trailing\" secondItem=\"baY-7A-lBA\" secondAttribute=\"trailing\" constant=\"20\" id=\"cRi-Tx-jtT\"/>\n                            <constraint firstItem=\"eyp-ee-dxf\" firstAttribute=\"leading\" secondItem=\"WZS-cI-X3m\" secondAttribute=\"trailing\" constant=\"12\" symbolic=\"YES\" id=\"eKo-Qk-lxl\"/>\n                            <constraint firstAttribute=\"bottom\" secondItem=\"dYB-Yp-UHf\" secondAttribute=\"bottom\" constant=\"14\" id=\"grP-Cj-Euz\"/>\n                            <constraint firstItem=\"baY-7A-lBA\" firstAttribute=\"leading\" secondItem=\"fy7-95-Eie\" secondAttribute=\"leading\" constant=\"20\" id=\"mxh-wg-iXP\"/>\n                            <constraint firstItem=\"baY-7A-lBA\" firstAttribute=\"top\" secondItem=\"fy7-95-Eie\" secondAttribute=\"top\" constant=\"20\" id=\"oAR-aZ-gNJ\"/>\n                            <constraint firstItem=\"dYB-Yp-UHf\" firstAttribute=\"leading\" relation=\"greaterThanOrEqual\" secondItem=\"eyp-ee-dxf\" secondAttribute=\"trailing\" constant=\"20\" id=\"yW0-QS-r8G\"/>\n                        </constraints>\n                    </view>\n                    <connections>\n                        <outlet property=\"cloudButton\" destination=\"WZS-cI-X3m\" id=\"4ry-4a-zGW\"/>\n                        <outlet property=\"rateButton\" destination=\"eyp-ee-dxf\" id=\"cvh-x3-TPU\"/>\n                        <outlet property=\"textView\" destination=\"f3s-Q5-6IG\" id=\"nbt-zY-bAo\"/>\n                    </connections>\n                </viewController>\n                <customObject id=\"Fag-zV-Usm\" userLabel=\"First Responder\" customClass=\"NSResponder\" sceneMemberID=\"firstResponder\"/>\n            </objects>\n            <point key=\"canvasLocation\" x=\"-118\" y=\"1661\"/>\n        </scene>\n    </scenes>\n</document>\n"
  },
  {
    "path": "mac/Focalboard/CustomWKWebView.swift",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport Foundation\nimport WebKit\n\nclass CustomWKWebView : WKWebView {\n\toverride func performKeyEquivalent(with event: NSEvent) -> Bool {\n\t\tif (event.modifierFlags.contains(.command) && event.characters?.lowercased() != \"z\") ||\n\t\t\tevent.modifierFlags.contains(.control) ||\n\t\t\tevent.modifierFlags.contains(.option) {\n\t\t\treturn super.performKeyEquivalent(with: event)\n\t\t}\n\n\t\tsuper.performKeyEquivalent(with: event)\n\t\t// Return true to prevent a \"funk\" error sound\n\t\treturn true\n\t}\n}\n"
  },
  {
    "path": "mac/Focalboard/DownloadHandler.swift",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport Foundation\nimport WebKit\n\nclass DownloadHandler: NSObject, WKDownloadDelegate {\n\tfunc download(_ download: WKDownload, decideDestinationUsing response: URLResponse, suggestedFilename: String, completionHandler: @escaping (URL?) -> Void) {\n\t\tDispatchQueue.main.async {\n\t\t\t// Let user select location of file\n\t\t\tlet savePanel = NSSavePanel()\n\t\t\tsavePanel.canCreateDirectories = true\n\t\t\tsavePanel.nameFieldStringValue = suggestedFilename\n\t\t\t// BUGBUG: Specifying the allowedFileTypes causes Catalina to hang / error out\n\t\t\t//savePanel.allowedFileTypes = [\".boardsarchive\"]\n\t\t\tsavePanel.begin { (result) in\n\t\t\t\tif result.rawValue == NSApplication.ModalResponse.OK.rawValue,\n\t\t\t\t   let fileUrl = savePanel.url {\n\t\t\t\t\tif (FileManager.default.fileExists(atPath: fileUrl.path)) {\n\t\t\t\t\t\t// HACKHACK: WKWebView doesn't appear to overwrite files, so delete exsiting files first\n\t\t\t\t\t\tdo {\n\t\t\t\t\t\t\ttry FileManager.default.removeItem(at: fileUrl)\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\tlet alert = NSAlert()\n\t\t\t\t\t\t\talert.messageText = \"Unable to replace \\(fileUrl.path)\"\n\t\t\t\t\t\t\talert.addButton(withTitle: \"OK\")\n\t\t\t\t\t\t\talert.alertStyle = .warning\n\t\t\t\t\t\t\talert.runModal()\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tcompletionHandler(fileUrl)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tfunc downloadDidFinish(_ download: WKDownload) {\n\t\tNSLog(\"downloadDidFinish\")\n\t}\n}\n"
  },
  {
    "path": "mac/Focalboard/Focalboard.entitlements",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>com.apple.security.app-sandbox</key>\n\t<true/>\n\t<key>com.apple.security.files.user-selected.read-write</key>\n\t<true/>\n\t<key>com.apple.security.network.client</key>\n\t<true/>\n\t<key>com.apple.security.network.server</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "mac/Focalboard/Globals.swift",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport Foundation\nimport Cocoa\n\nclass Globals {\n\tstatic let ProductVersion = 70000\n\tstatic let WhatsNewVersion = 70000\n\n\tstatic var currentWhatsNewVersion: Int {\n\t\tget { return UserDefaults.standard.integer(forKey: \"whatsNewVersion\") }\n\t\tset { UserDefaults.standard.setValue(newValue, forKey: \"whatsNewVersion\") }\n\t}\n\n\tstatic func openGetCloudServerUrl() {\n\t\tlet url = URL(string: \"https://mattermost.com/sign-up/?utm_source=focalboard&utm_campaign=focalboardapp\")!\n\t\tNSWorkspace.shared.open(url)\n\t}\n}\n"
  },
  {
    "path": "mac/Focalboard/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>$(DEVELOPMENT_LANGUAGE)</string>\n\t<key>CFBundleExecutable</key>\n\t<string>$(EXECUTABLE_NAME)</string>\n\t<key>CFBundleIconFile</key>\n\t<string></string>\n\t<key>CFBundleIdentifier</key>\n\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>$(PRODUCT_NAME)</string>\n\t<key>CFBundlePackageType</key>\n\t<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>$(MARKETING_VERSION)</string>\n\t<key>CFBundleVersion</key>\n\t<string>$(CURRENT_PROJECT_VERSION)</string>\n\t<key>ITSAppUsesNonExemptEncryption</key>\n\t<false/>\n\t<key>LSApplicationCategoryType</key>\n\t<string>public.app-category.productivity</string>\n\t<key>LSFileQuarantineEnabled</key>\n\t<true/>\n\t<key>LSMinimumSystemVersion</key>\n\t<string>$(MACOSX_DEPLOYMENT_TARGET)</string>\n\t<key>NSAppTransportSecurity</key>\n\t<dict>\n\t\t<key>NSAllowsArbitraryLoads</key>\n\t\t<true/>\n\t</dict>\n\t<key>NSHumanReadableCopyright</key>\n\t<string>Copyright © 2021 Mattermost, Inc. All rights reserved.</string>\n\t<key>NSMainStoryboardFile</key>\n\t<string>Main</string>\n\t<key>NSPrincipalClass</key>\n\t<string>NSApplication</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "mac/Focalboard/Inherit.entitlements",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>com.apple.security.app-sandbox</key>\n\t<true/>\n\t<key>com.apple.security.inherit</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "mac/Focalboard/PortUtils.swift",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport Foundation\n\nclass PortUtils {\n\tstatic func isPortFree(_ port: in_port_t) -> Bool {\n\t\tlet socketFileDescriptor = socket(AF_INET, SOCK_STREAM, 0)\n\t\tif socketFileDescriptor == -1 {\n\t\t\treturn false\n\t\t}\n\n\t\tvar addr = sockaddr_in()\n\t\tlet sizeOfSockkAddr = MemoryLayout<sockaddr_in>.size\n\t\taddr.sin_len = __uint8_t(sizeOfSockkAddr)\n\t\taddr.sin_family = sa_family_t(AF_INET)\n\t\taddr.sin_port = Int(OSHostByteOrder()) == OSLittleEndian ? _OSSwapInt16(port) : port\n\t\taddr.sin_addr = in_addr(s_addr: inet_addr(\"127.0.0.1\"))\n\t\taddr.sin_zero = (0, 0, 0, 0, 0, 0, 0, 0)\n\t\tvar bind_addr = sockaddr()\n\t\tmemcpy(&bind_addr, &addr, Int(sizeOfSockkAddr))\n\n\t\tif Darwin.bind(socketFileDescriptor, &bind_addr, socklen_t(sizeOfSockkAddr)) == -1 {\n\t\t\trelease(socket: socketFileDescriptor)\n\t\t\treturn false\n\t\t}\n\t\tif listen(socketFileDescriptor, SOMAXCONN ) == -1 {\n\t\t\trelease(socket: socketFileDescriptor)\n\t\t\treturn false\n\t\t}\n\t\trelease(socket: socketFileDescriptor)\n\t\treturn true\n\t}\n\n\tprivate static func release(socket: Int32) {\n\t\tDarwin.shutdown(socket, SHUT_RDWR)\n\t\tclose(socket)\n\t}\n\n\tstatic func getFreePort() -> in_port_t {\n\t\tvar portNum: in_port_t = 0\n\t\tfor i in 50000..<65000 {\n\t\t\tlet isFree = isPortFree(in_port_t(i))\n\t\t\tif isFree {\n\t\t\t\tportNum = in_port_t(i)\n\t\t\t\treturn portNum\n\t\t\t}\n\t\t}\n\n\t\treturn in_port_t(0)\n\t}\n}\n"
  },
  {
    "path": "mac/Focalboard/ViewController.swift",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport Cocoa\nimport WebKit\n\nclass ViewController:\n\tNSViewController,\n\tWKUIDelegate,\n\tWKNavigationDelegate,\n\tWKScriptMessageHandler {\n\t@IBOutlet var webView: CustomWKWebView!\n\tprivate var didLoad = false\n\tprivate var refreshWebViewOnLoad = true\n\tprivate let downloadHandler = DownloadHandler()\n\n\toverride func viewDidLoad() {\n\t\tsuper.viewDidLoad()\n\n\t\tNSLog(\"viewDidLoad\")\n\n\t\twebView.navigationDelegate = self\n\t\twebView.uiDelegate = self\n\t\twebView.isHidden = true\n\t\twebView.configuration.userContentController.add(self, name: \"nativeApp\")\n\n\t\tclearWebViewCache()\n\n\t\t// Load the home page if the server was started, otherwise wait until it has\n\t\tlet appDelegate = NSApplication.shared.delegate as! AppDelegate\n\t\tif (appDelegate.isServerStarted) {\n\t\t\tupdateSessionTokenAndUserSettings()\n\t\t\tloadHomepage()\n\t\t}\n\n\t\t// Do any additional setup after loading the view.\n\t\tNotificationCenter.default.addObserver(self, selector: #selector(onServerStarted), name: AppDelegate.serverStartedNotification, object: nil)\n\t}\n\n\toverride func viewDidAppear() {\n\t\tsuper.viewDidAppear()\n\t\tself.view.window?.makeFirstResponder(self.webView)\n\t}\n\n\toverride var representedObject: Any? {\n\t\tdidSet {\n\t\t\t// Update the view, if already loaded.\n\t\t}\n\t}\n\n\tprivate func clearWebViewCache() {\n\t\tlet websiteDataTypes = NSSet(array: [WKWebsiteDataTypeDiskCache, WKWebsiteDataTypeMemoryCache])\n\t\tlet date = Date(timeIntervalSince1970: 0)\n\t\tWKWebsiteDataStore.default().removeData(ofTypes: websiteDataTypes as! Set<String>, modifiedSince: date, completionHandler:{ })\n\t}\n\n\t@IBAction func showDiagnosticsInfo(_ sender: NSObject) {\n\t\tlet appDelegate = NSApplication.shared.delegate as! AppDelegate\n\n\t\tlet alert: NSAlert = NSAlert()\n\t\talert.messageText = \"Diagnostics info\"\n\t\talert.informativeText = \"Port: \\(appDelegate.serverPort)\"\n\t\talert.alertStyle = .informational\n\t\talert.addButton(withTitle: \"OK\")\n\t\talert.runModal()\n\t}\n\n\t@objc func onServerStarted() {\n\t\tNSLog(\"onServerStarted\")\n\t\tDispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {\n\t\t\tself.updateSessionTokenAndUserSettings()\n\t\t\tself.loadHomepage()\n\t\t}\n\t}\n\n\tprivate func updateSessionTokenAndUserSettings() {\n\t\tlet appDelegate = NSApplication.shared.delegate as! AppDelegate\n\t\tlet sessionTokenScript = WKUserScript(\n\t\t\tsource: \"localStorage.setItem('focalboardSessionId', '\\(appDelegate.sessionToken)');\",\n\t\t\tinjectionTime: .atDocumentStart,\n\t\t\tforMainFrameOnly: true\n\t\t)\n\t\tlet blob = UserDefaults.standard.string(forKey: \"localStorage\") ?? \"\"\n\t\tlet userSettingsScript = WKUserScript(\n\t\t\tsource: \"const NativeApp = { settingsBlob: \\\"\\(blob)\\\" };\",\n\t\t\tinjectionTime: .atDocumentStart,\n\t\t\tforMainFrameOnly: true\n\t\t)\n\t\twebView.configuration.userContentController.removeAllUserScripts()\n\t\twebView.configuration.userContentController.addUserScript(sessionTokenScript)\n\t\twebView.configuration.userContentController.addUserScript(userSettingsScript)\n\t}\n\n\tprivate func loadHomepage() {\n\t\tNSLog(\"loadHomepage\")\n\t\tlet appDelegate = NSApplication.shared.delegate as! AppDelegate\n\t\tlet port = appDelegate.serverPort\n\t\tlet url = URL(string: \"http://localhost:\\(port)/\")!\n\t\tlet request = URLRequest(url: url)\n\t\trefreshWebViewOnLoad = true\n\t\twebView.load(request)\n\t}\n\n\tfunc webView(_ webView: WKWebView, runOpenPanelWith parameters: WKOpenPanelParameters, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping ([URL]?) -> Void) {\n\t\tNSLog(\"webView runOpenPanel\")\n\t\tlet openPanel = NSOpenPanel()\n\t\topenPanel.canChooseFiles = true\n\t\topenPanel.begin { (result) in\n\t\t\tif result == NSApplication.ModalResponse.OK {\n\t\t\t\tif let url = openPanel.url {\n\t\t\t\t\tcompletionHandler([url])\n\t\t\t\t}\n\t\t\t} else if result == NSApplication.ModalResponse.cancel {\n\t\t\t\tcompletionHandler(nil)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Handle downloads\n\t\n\tfunc webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, preferences: WKWebpagePreferences, decisionHandler: @escaping (WKNavigationActionPolicy, WKWebpagePreferences) -> Void) {\n\t\tif navigationAction.shouldPerformDownload {\n\t\t\tdecisionHandler(.download, preferences)\n\t\t} else {\n\t\t\tdecisionHandler(.allow, preferences)\n\t\t}\n\t}\n\n\tfunc webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {\n\t\tif navigationResponse.canShowMIMEType {\n\t\t\tdecisionHandler(.allow)\n\t\t} else {\n\t\t\tdecisionHandler(.download)\n\t\t}\n\t}\n\n\tfunc webView(_ webView: WKWebView, navigationAction: WKNavigationAction, didBecome download: WKDownload) {\n\t\tdownload.delegate = downloadHandler\n\t}\n\n\tfunc webView(_ webView: WKWebView, navigationResponse: WKNavigationResponse, didBecome download: WKDownload) {\n\t\tdownload.delegate = downloadHandler\n\t}\n\n\tfunc webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {\n\t\tNSLog(\"webView didFinish navigation: \\(webView.url?.absoluteString ?? \"\")\")\n\t\t// Disable right-click menu\n\t\twebView.evaluateJavaScript(\"document.body.setAttribute('oncontextmenu', 'event.preventDefault();');\", completionHandler: nil)\n\t\twebView.isHidden = false\n\t\tdidLoad = true\n\n\t\t// HACKHACK: Fix WebView initial rendering artifacts\n\t\tif (refreshWebViewOnLoad) {\n\t\t\trefreshWebViewOnLoad = false\n\t\t\tDispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: {\n\t\t\t\tself.refreshWebView()\n\t\t\t})\n\t\t}\n\t}\n\n\t// HACKHACK: Fix WebView initial rendering artifacts\n\tprivate func refreshWebView() {\n\t\tlet frame = self.webView.frame\n\t\tvar frame2 = frame\n\t\tframe2.size.height += 1\n\t\tself.webView.frame = frame2\n\t\tself.webView.frame = frame\n\t}\n\n\tfunc webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {\n\t\tNSLog(\"webView didFailProvisionalNavigation, error: \\(error.localizedDescription)\")\n\t\tif (!didLoad) {\n\t\t\tDispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {\n\t\t\t\tself.updateSessionTokenAndUserSettings()\n\t\t\t\tself.loadHomepage()\n\t\t\t}\n\t\t}\n\t}\n\n\tfunc webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {\n\t\twebView.isHidden = false\n\t}\n\n\tfunc webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {\n\t\tif let frame = navigationAction.targetFrame,\n\t\t\tframe.isMainFrame {\n\t\t\treturn nil\n\t\t}\n\t\t// for _blank target or non-mainFrame target, open in default browser\n\t\tif let url = navigationAction.request.url {\n\t\t\tNSWorkspace.shared.open(url)\n\t\t}\n\t\treturn nil\n\t}\n\n\t@IBAction func navigateToHome(_ sender: NSObject) {\n\t\tloadHomepage()\n\t}\n\n\tfunc userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {\n\t\tguard\n\t\t\tlet body = message.body as? [AnyHashable: Any],\n\t\t\tlet type = body[\"type\"] as? String,\n\t\t\tlet blob = body[\"settingsBlob\"] as? String\n\t\telse {\n\t\t\tNSLog(\"Received unexpected script message \\(message.body)\")\n\t\t\treturn\n\t\t}\n\t\tNSLog(\"Received script message \\(type)\")\n\t\tswitch type {\n\t\tcase \"didImportUserSettings\":\n\t\t\tNSLog(\"Imported user settings keys \\(body[\"keys\"] ?? \"?\")\")\n\t\tcase \"didNotImportUserSettings\":\n\t\t\tbreak\n\t\tcase \"didChangeUserSettings\":\n\t\t\tUserDefaults.standard.set(blob, forKey: \"localStorage\")\n\t\t\tNSLog(\"Persisted user settings after change for key \\(body[\"key\"] ?? \"?\")\")\n\t\tdefault:\n\t\t\tNSLog(\"Received script message of unknown type \\(type)\")\n\t\t}\n\t\tif let settings = Data(base64Encoded: blob).flatMap({ try? JSONSerialization.jsonObject(with: $0, options: []) }) {\n\t\t\tNSLog(\"Current user settings: \\(settings)\")\n\t\t}\n\t}\n}\n\n"
  },
  {
    "path": "mac/Focalboard/WhatsNewViewController.swift",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport Cocoa\n\nclass WhatsNewViewController:\n\tNSViewController {\n\t@IBOutlet var textView: NSTextView!\n\t@IBOutlet var rateButton: NSButton!\n\t@IBOutlet var cloudButton: NSButton!\n\n\toverride func viewDidLoad() {\n\t\tsuper.viewDidLoad()\n\t\tloadText()\n\t}\n\n\tprivate func loadText() {\n\t\tguard let fileUrl = Bundle.main.url(forResource: \"whatsnew\", withExtension: \"txt\") else { assertionFailure(\"whatsnew\"); return }\n\t\tguard let text = try? String(contentsOf: fileUrl, encoding: .utf8) else { assertionFailure(\"whatsnew\"); return }\n\n\t\ttextView.string = text\n\t\ttextView.textStorage?.font = NSFont.systemFont(ofSize: 13)\n\t\ttextView.textStorage?.foregroundColor = NSColor.textColor\n\t}\n\n\t@IBAction func rateButtonClicked(_ sender: Any) {\n\t\tlet url = URL(string: \"macappstore://itunes.apple.com/app/id1556908618?action=write-review\")!\n\t\tNSWorkspace.shared.open(url)\n\t\tview.window?.close()\n\t}\n\n\t@IBAction func cloudButtonClicked(_ sender: Any) {\n\t\tGlobals.openGetCloudServerUrl()\n\t}\n\n\t@IBAction func closeButtonClicked(_ sender: Any) {\n\t\tview.window?.close()\n\t}\n}\n"
  },
  {
    "path": "mac/Focalboard/whatsnew.txt",
    "content": "Welcome to Focalboard v7.2!\n\nMattermost Boards is now availalbe as a Cloud service. Set up your free server via the button below to collaborate with your team.\n\nMattermost Boards combines all the features of Focalboard with real-time collaboration, calls, and playbooks.\n\nYou can export boards from Focalboard and import them into Mattermost Boards, and pick up where you left off.\n\nThank you contributors! Recent improvements include:\n\n* Calendar view. Thanks @sbishel!\n\nIf you like what you see, please consider taking a moment to rate this version in the App Store. Your positive ratings make a real difference for us.\n\nThanks so much,\n\n-- The Focalboard developers and community\n"
  },
  {
    "path": "mac/Focalboard.xcodeproj/project.pbxproj",
    "content": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 50;\n\tobjects = {\n\n/* Begin PBXBuildFile section */\n\t\t8014951C261598D600A51700 /* PortUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8014951B261598D600A51700 /* PortUtils.swift */; };\n\t\t804E57FC27441B6B008526F0 /* whatsnew.txt in Resources */ = {isa = PBXBuildFile; fileRef = 804E57FB27441B6B008526F0 /* whatsnew.txt */; };\n\t\t80672A8B27BAFEBA00257B8C /* DownloadHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80672A8A27BAFEBA00257B8C /* DownloadHandler.swift */; };\n\t\t80D6DEBB252E13CB00AEED9E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80D6DEBA252E13CB00AEED9E /* AppDelegate.swift */; };\n\t\t80D6DEBD252E13CB00AEED9E /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80D6DEBC252E13CB00AEED9E /* ViewController.swift */; };\n\t\t80D6DEBF252E13CD00AEED9E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 80D6DEBE252E13CD00AEED9E /* Assets.xcassets */; };\n\t\t80D6DEC2252E13CD00AEED9E /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 80D6DEC0252E13CD00AEED9E /* Main.storyboard */; };\n\t\t80D6DECE252E13CD00AEED9E /* FocalboardTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80D6DECD252E13CD00AEED9E /* FocalboardTests.swift */; };\n\t\t80D6DED9252E13CD00AEED9E /* FocalboardUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80D6DED8252E13CD00AEED9E /* FocalboardUITests.swift */; };\n\t\t80D6DEEA252E15D100AEED9E /* resources in Resources */ = {isa = PBXBuildFile; fileRef = 80D6DEE9252E15D100AEED9E /* resources */; };\n\t\t80D6DF18252F9BDE00AEED9E /* AutoSaveWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80D6DF17252F9BDE00AEED9E /* AutoSaveWindowController.swift */; };\n\t\t80F174B72788C1A2000A9EEA /* CustomWKWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80F174B62788C1A2000A9EEA /* CustomWKWebView.swift */; };\n\t\t80F8BF502624E1BB00FF3943 /* WhatsNewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80F8BF4F2624E1BB00FF3943 /* WhatsNewViewController.swift */; };\n\t\t80F8BF582624EB0C00FF3943 /* Globals.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80F8BF572624EB0C00FF3943 /* Globals.swift */; };\n/* End PBXBuildFile section */\n\n/* Begin PBXContainerItemProxy section */\n\t\t80D6DECA252E13CD00AEED9E /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 80D6DEAF252E13CB00AEED9E /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 80D6DEB6252E13CB00AEED9E;\n\t\t\tremoteInfo = Focalboard;\n\t\t};\n\t\t80D6DED5252E13CD00AEED9E /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 80D6DEAF252E13CB00AEED9E /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 80D6DEB6252E13CB00AEED9E;\n\t\t\tremoteInfo = Focalboard;\n\t\t};\n/* End PBXContainerItemProxy section */\n\n/* Begin PBXFileReference section */\n\t\t8014951B261598D600A51700 /* PortUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PortUtils.swift; sourceTree = \"<group>\"; };\n\t\t804E57FB27441B6B008526F0 /* whatsnew.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = whatsnew.txt; sourceTree = \"<group>\"; };\n\t\t80672A8A27BAFEBA00257B8C /* DownloadHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadHandler.swift; sourceTree = \"<group>\"; };\n\t\t80D6DEB7252E13CB00AEED9E /* Focalboard.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Focalboard.app; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t80D6DEBA252E13CB00AEED9E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = \"<group>\"; };\n\t\t80D6DEBC252E13CB00AEED9E /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = \"<group>\"; };\n\t\t80D6DEBE252E13CD00AEED9E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = \"<group>\"; };\n\t\t80D6DEC1252E13CD00AEED9E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = \"<group>\"; };\n\t\t80D6DEC3252E13CD00AEED9E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = \"<group>\"; };\n\t\t80D6DEC4252E13CD00AEED9E /* Focalboard.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Focalboard.entitlements; sourceTree = \"<group>\"; };\n\t\t80D6DEC9252E13CD00AEED9E /* FocalboardTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FocalboardTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t80D6DECD252E13CD00AEED9E /* FocalboardTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FocalboardTests.swift; sourceTree = \"<group>\"; };\n\t\t80D6DECF252E13CD00AEED9E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = \"<group>\"; };\n\t\t80D6DED4252E13CD00AEED9E /* FocalboardUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FocalboardUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t80D6DED8252E13CD00AEED9E /* FocalboardUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FocalboardUITests.swift; sourceTree = \"<group>\"; };\n\t\t80D6DEDA252E13CD00AEED9E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = \"<group>\"; };\n\t\t80D6DEE9252E15D100AEED9E /* resources */ = {isa = PBXFileReference; lastKnownFileType = folder; path = resources; sourceTree = \"<group>\"; };\n\t\t80D6DF17252F9BDE00AEED9E /* AutoSaveWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoSaveWindowController.swift; sourceTree = \"<group>\"; };\n\t\t80D6DF1C25324A4F00AEED9E /* Inherit.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Inherit.entitlements; sourceTree = \"<group>\"; };\n\t\t80F174B62788C1A2000A9EEA /* CustomWKWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomWKWebView.swift; sourceTree = \"<group>\"; };\n\t\t80F8BF4F2624E1BB00FF3943 /* WhatsNewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WhatsNewViewController.swift; sourceTree = \"<group>\"; };\n\t\t80F8BF572624EB0C00FF3943 /* Globals.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Globals.swift; sourceTree = \"<group>\"; };\n/* End PBXFileReference section */\n\n/* Begin PBXFrameworksBuildPhase section */\n\t\t80D6DEB4252E13CB00AEED9E /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t80D6DEC6252E13CD00AEED9E /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t80D6DED1252E13CD00AEED9E /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXFrameworksBuildPhase section */\n\n/* Begin PBXGroup section */\n\t\t80D6DEAE252E13CB00AEED9E = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t80D6DEB9252E13CB00AEED9E /* Focalboard */,\n\t\t\t\t80D6DECC252E13CD00AEED9E /* FocalboardTests */,\n\t\t\t\t80D6DEE9252E15D100AEED9E /* resources */,\n\t\t\t\t80D6DED7252E13CD00AEED9E /* FocalboardUITests */,\n\t\t\t\t80D6DEB8252E13CB00AEED9E /* Products */,\n\t\t\t);\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t80D6DEB8252E13CB00AEED9E /* Products */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t80D6DEB7252E13CB00AEED9E /* Focalboard.app */,\n\t\t\t\t80D6DEC9252E13CD00AEED9E /* FocalboardTests.xctest */,\n\t\t\t\t80D6DED4252E13CD00AEED9E /* FocalboardUITests.xctest */,\n\t\t\t);\n\t\t\tname = Products;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t80D6DEB9252E13CB00AEED9E /* Focalboard */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t80D6DEBA252E13CB00AEED9E /* AppDelegate.swift */,\n\t\t\t\t80F8BF572624EB0C00FF3943 /* Globals.swift */,\n\t\t\t\t8014951B261598D600A51700 /* PortUtils.swift */,\n\t\t\t\t80D6DEBC252E13CB00AEED9E /* ViewController.swift */,\n\t\t\t\t80672A8A27BAFEBA00257B8C /* DownloadHandler.swift */,\n\t\t\t\t80F174B62788C1A2000A9EEA /* CustomWKWebView.swift */,\n\t\t\t\t80F8BF4F2624E1BB00FF3943 /* WhatsNewViewController.swift */,\n\t\t\t\t804E57FB27441B6B008526F0 /* whatsnew.txt */,\n\t\t\t\t80D6DF17252F9BDE00AEED9E /* AutoSaveWindowController.swift */,\n\t\t\t\t80D6DEBE252E13CD00AEED9E /* Assets.xcassets */,\n\t\t\t\t80D6DEC0252E13CD00AEED9E /* Main.storyboard */,\n\t\t\t\t80D6DEC3252E13CD00AEED9E /* Info.plist */,\n\t\t\t\t80D6DEC4252E13CD00AEED9E /* Focalboard.entitlements */,\n\t\t\t\t80D6DF1C25324A4F00AEED9E /* Inherit.entitlements */,\n\t\t\t);\n\t\t\tpath = Focalboard;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t80D6DECC252E13CD00AEED9E /* FocalboardTests */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t80D6DECD252E13CD00AEED9E /* FocalboardTests.swift */,\n\t\t\t\t80D6DECF252E13CD00AEED9E /* Info.plist */,\n\t\t\t);\n\t\t\tpath = FocalboardTests;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t80D6DED7252E13CD00AEED9E /* FocalboardUITests */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t80D6DED8252E13CD00AEED9E /* FocalboardUITests.swift */,\n\t\t\t\t80D6DEDA252E13CD00AEED9E /* Info.plist */,\n\t\t\t);\n\t\t\tpath = FocalboardUITests;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXGroup section */\n\n/* Begin PBXNativeTarget section */\n\t\t80D6DEB6252E13CB00AEED9E /* Focalboard */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 80D6DEDD252E13CD00AEED9E /* Build configuration list for PBXNativeTarget \"Focalboard\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t80D6DEB3252E13CB00AEED9E /* Sources */,\n\t\t\t\t80D6DEB4252E13CB00AEED9E /* Frameworks */,\n\t\t\t\t80D6DEB5252E13CB00AEED9E /* Resources */,\n\t\t\t\t80D6DF1D25324A8100AEED9E /* Run Script */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tname = Focalboard;\n\t\t\tproductName = Focalboard;\n\t\t\tproductReference = 80D6DEB7252E13CB00AEED9E /* Focalboard.app */;\n\t\t\tproductType = \"com.apple.product-type.application\";\n\t\t};\n\t\t80D6DEC8252E13CD00AEED9E /* FocalboardTests */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 80D6DEE0252E13CD00AEED9E /* Build configuration list for PBXNativeTarget \"FocalboardTests\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t80D6DEC5252E13CD00AEED9E /* Sources */,\n\t\t\t\t80D6DEC6252E13CD00AEED9E /* Frameworks */,\n\t\t\t\t80D6DEC7252E13CD00AEED9E /* Resources */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t\t80D6DECB252E13CD00AEED9E /* PBXTargetDependency */,\n\t\t\t);\n\t\t\tname = FocalboardTests;\n\t\t\tproductName = FocalboardTests;\n\t\t\tproductReference = 80D6DEC9252E13CD00AEED9E /* FocalboardTests.xctest */;\n\t\t\tproductType = \"com.apple.product-type.bundle.unit-test\";\n\t\t};\n\t\t80D6DED3252E13CD00AEED9E /* FocalboardUITests */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 80D6DEE3252E13CD00AEED9E /* Build configuration list for PBXNativeTarget \"FocalboardUITests\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t80D6DED0252E13CD00AEED9E /* Sources */,\n\t\t\t\t80D6DED1252E13CD00AEED9E /* Frameworks */,\n\t\t\t\t80D6DED2252E13CD00AEED9E /* Resources */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t\t80D6DED6252E13CD00AEED9E /* PBXTargetDependency */,\n\t\t\t);\n\t\t\tname = FocalboardUITests;\n\t\t\tproductName = FocalboardUITests;\n\t\t\tproductReference = 80D6DED4252E13CD00AEED9E /* FocalboardUITests.xctest */;\n\t\t\tproductType = \"com.apple.product-type.bundle.ui-testing\";\n\t\t};\n/* End PBXNativeTarget section */\n\n/* Begin PBXProject section */\n\t\t80D6DEAF252E13CB00AEED9E /* Project object */ = {\n\t\t\tisa = PBXProject;\n\t\t\tattributes = {\n\t\t\t\tLastSwiftUpdateCheck = 1200;\n\t\t\t\tLastUpgradeCheck = 1200;\n\t\t\t\tTargetAttributes = {\n\t\t\t\t\t80D6DEB6252E13CB00AEED9E = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 12.0;\n\t\t\t\t\t};\n\t\t\t\t\t80D6DEC8252E13CD00AEED9E = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 12.0;\n\t\t\t\t\t\tTestTargetID = 80D6DEB6252E13CB00AEED9E;\n\t\t\t\t\t};\n\t\t\t\t\t80D6DED3252E13CD00AEED9E = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 12.0;\n\t\t\t\t\t\tTestTargetID = 80D6DEB6252E13CB00AEED9E;\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t\tbuildConfigurationList = 80D6DEB2252E13CB00AEED9E /* Build configuration list for PBXProject \"Focalboard\" */;\n\t\t\tcompatibilityVersion = \"Xcode 9.3\";\n\t\t\tdevelopmentRegion = en;\n\t\t\thasScannedForEncodings = 0;\n\t\t\tknownRegions = (\n\t\t\t\ten,\n\t\t\t\tBase,\n\t\t\t);\n\t\t\tmainGroup = 80D6DEAE252E13CB00AEED9E;\n\t\t\tproductRefGroup = 80D6DEB8252E13CB00AEED9E /* Products */;\n\t\t\tprojectDirPath = \"\";\n\t\t\tprojectRoot = \"\";\n\t\t\ttargets = (\n\t\t\t\t80D6DEB6252E13CB00AEED9E /* Focalboard */,\n\t\t\t\t80D6DEC8252E13CD00AEED9E /* FocalboardTests */,\n\t\t\t\t80D6DED3252E13CD00AEED9E /* FocalboardUITests */,\n\t\t\t);\n\t\t};\n/* End PBXProject section */\n\n/* Begin PBXResourcesBuildPhase section */\n\t\t80D6DEB5252E13CB00AEED9E /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t80D6DEBF252E13CD00AEED9E /* Assets.xcassets in Resources */,\n\t\t\t\t80D6DEEA252E15D100AEED9E /* resources in Resources */,\n\t\t\t\t80D6DEC2252E13CD00AEED9E /* Main.storyboard in Resources */,\n\t\t\t\t804E57FC27441B6B008526F0 /* whatsnew.txt in Resources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t80D6DEC7252E13CD00AEED9E /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t80D6DED2252E13CD00AEED9E /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXResourcesBuildPhase section */\n\n/* Begin PBXShellScriptBuildPhase section */\n\t\t80D6DF1D25324A8100AEED9E /* Run Script */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t);\n\t\t\tname = \"Run Script\";\n\t\t\toutputFileListPaths = (\n\t\t\t);\n\t\t\toutputPaths = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"# Un-comment this to enable codesign\\n# /usr/bin/codesign --force --timestamp --options runtime --sign \\\"$CODE_SIGN_IDENTITY\\\" -i \\\"com.mattermost.focalboardServer\\\" --entitlement \\\"$PROJECT_DIR/Focalboard/Inherit.entitlements\\\" \\\"$BUILD_DIR/$CONFIGURATION/$EXECUTABLE_FOLDER_PATH/../Resources/resources/bin/focalboard-server\\\"\\n\";\n\t\t};\n/* End PBXShellScriptBuildPhase section */\n\n/* Begin PBXSourcesBuildPhase section */\n\t\t80D6DEB3252E13CB00AEED9E /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t8014951C261598D600A51700 /* PortUtils.swift in Sources */,\n\t\t\t\t80F8BF502624E1BB00FF3943 /* WhatsNewViewController.swift in Sources */,\n\t\t\t\t80F174B72788C1A2000A9EEA /* CustomWKWebView.swift in Sources */,\n\t\t\t\t80F8BF582624EB0C00FF3943 /* Globals.swift in Sources */,\n\t\t\t\t80672A8B27BAFEBA00257B8C /* DownloadHandler.swift in Sources */,\n\t\t\t\t80D6DF18252F9BDE00AEED9E /* AutoSaveWindowController.swift in Sources */,\n\t\t\t\t80D6DEBD252E13CB00AEED9E /* ViewController.swift in Sources */,\n\t\t\t\t80D6DEBB252E13CB00AEED9E /* AppDelegate.swift in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t80D6DEC5252E13CD00AEED9E /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t80D6DECE252E13CD00AEED9E /* FocalboardTests.swift in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t80D6DED0252E13CD00AEED9E /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t80D6DED9252E13CD00AEED9E /* FocalboardUITests.swift in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXSourcesBuildPhase section */\n\n/* Begin PBXTargetDependency section */\n\t\t80D6DECB252E13CD00AEED9E /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = 80D6DEB6252E13CB00AEED9E /* Focalboard */;\n\t\t\ttargetProxy = 80D6DECA252E13CD00AEED9E /* PBXContainerItemProxy */;\n\t\t};\n\t\t80D6DED6252E13CD00AEED9E /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = 80D6DEB6252E13CB00AEED9E /* Focalboard */;\n\t\t\ttargetProxy = 80D6DED5252E13CD00AEED9E /* PBXContainerItemProxy */;\n\t\t};\n/* End PBXTargetDependency section */\n\n/* Begin PBXVariantGroup section */\n\t\t80D6DEC0252E13CD00AEED9E /* Main.storyboard */ = {\n\t\t\tisa = PBXVariantGroup;\n\t\t\tchildren = (\n\t\t\t\t80D6DEC1252E13CD00AEED9E /* Base */,\n\t\t\t);\n\t\t\tname = Main.storyboard;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXVariantGroup section */\n\n/* Begin XCBuildConfiguration section */\n\t\t80D6DEDB252E13CD00AEED9E /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++14\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = dwarf;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_TESTABILITY = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGCC_DYNAMIC_NO_PIC = NO;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_OPTIMIZATION_LEVEL = 0;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 11.3;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tONLY_ACTIVE_ARCH = YES;\n\t\t\t\tSDKROOT = macosx;\n\t\t\t\tSWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-Onone\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t80D6DEDC252E13CD00AEED9E /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++14\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 11.3;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tSDKROOT = macosx;\n\t\t\t\tSWIFT_COMPILATION_MODE = wholemodule;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-O\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t80D6DEDE252E13CD00AEED9E /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = Focalboard/Focalboard.entitlements;\n\t\t\t\tCODE_SIGN_IDENTITY = \"-\";\n\t\t\t\tCODE_SIGN_STYLE = Manual;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = 33;\n\t\t\t\tDEVELOPMENT_TEAM = HFP57A3MYB;\n\t\t\t\tENABLE_HARDENED_RUNTIME = YES;\n\t\t\t\tINFOPLIST_FILE = Focalboard/Info.plist;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/../Frameworks\",\n\t\t\t\t);\n\t\t\t\tMARKETING_VERSION = 7.3;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.mattermost.focalboard;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tPROVISIONING_PROFILE_SPECIFIER = \"\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t80D6DEDF252E13CD00AEED9E /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = Focalboard/Focalboard.entitlements;\n\t\t\t\tCODE_SIGN_IDENTITY = \"-\";\n\t\t\t\tCODE_SIGN_STYLE = Manual;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = 33;\n\t\t\t\tDEVELOPMENT_TEAM = HFP57A3MYB;\n\t\t\t\tENABLE_HARDENED_RUNTIME = YES;\n\t\t\t\tINFOPLIST_FILE = Focalboard/Info.plist;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/../Frameworks\",\n\t\t\t\t);\n\t\t\t\tMARKETING_VERSION = 7.3;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.mattermost.focalboard;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tPROVISIONING_PROFILE_SPECIFIER = \"\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t80D6DEE1252E13CD00AEED9E /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;\n\t\t\t\tBUNDLE_LOADER = \"$(TEST_HOST)\";\n\t\t\t\tCODE_SIGN_STYLE = Manual;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tDEVELOPMENT_TEAM = UQ8HT4Q2XM;\n\t\t\t\tINFOPLIST_FILE = FocalboardTests/Info.plist;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/../Frameworks\",\n\t\t\t\t\t\"@loader_path/../Frameworks\",\n\t\t\t\t);\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 10.15;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.mattermost.focalboardTests;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTEST_HOST = \"$(BUILT_PRODUCTS_DIR)/Focalboard.app/Contents/MacOS/Focalboard\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t80D6DEE2252E13CD00AEED9E /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;\n\t\t\t\tBUNDLE_LOADER = \"$(TEST_HOST)\";\n\t\t\t\tCODE_SIGN_STYLE = Manual;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tDEVELOPMENT_TEAM = UQ8HT4Q2XM;\n\t\t\t\tINFOPLIST_FILE = FocalboardTests/Info.plist;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/../Frameworks\",\n\t\t\t\t\t\"@loader_path/../Frameworks\",\n\t\t\t\t);\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 10.15;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.mattermost.focalboardTests;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTEST_HOST = \"$(BUILT_PRODUCTS_DIR)/Focalboard.app/Contents/MacOS/Focalboard\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t80D6DEE4252E13CD00AEED9E /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;\n\t\t\t\tCODE_SIGN_STYLE = Manual;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tDEVELOPMENT_TEAM = UQ8HT4Q2XM;\n\t\t\t\tINFOPLIST_FILE = FocalboardUITests/Info.plist;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/../Frameworks\",\n\t\t\t\t\t\"@loader_path/../Frameworks\",\n\t\t\t\t);\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.mattermost.focalboardUITests;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTEST_TARGET_NAME = Focalboard;\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t80D6DEE5252E13CD00AEED9E /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;\n\t\t\t\tCODE_SIGN_STYLE = Manual;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tDEVELOPMENT_TEAM = UQ8HT4Q2XM;\n\t\t\t\tINFOPLIST_FILE = FocalboardUITests/Info.plist;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/../Frameworks\",\n\t\t\t\t\t\"@loader_path/../Frameworks\",\n\t\t\t\t);\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.mattermost.focalboardUITests;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTEST_TARGET_NAME = Focalboard;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n/* End XCBuildConfiguration section */\n\n/* Begin XCConfigurationList section */\n\t\t80D6DEB2252E13CB00AEED9E /* Build configuration list for PBXProject \"Focalboard\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t80D6DEDB252E13CD00AEED9E /* Debug */,\n\t\t\t\t80D6DEDC252E13CD00AEED9E /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t80D6DEDD252E13CD00AEED9E /* Build configuration list for PBXNativeTarget \"Focalboard\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t80D6DEDE252E13CD00AEED9E /* Debug */,\n\t\t\t\t80D6DEDF252E13CD00AEED9E /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t80D6DEE0252E13CD00AEED9E /* Build configuration list for PBXNativeTarget \"FocalboardTests\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t80D6DEE1252E13CD00AEED9E /* Debug */,\n\t\t\t\t80D6DEE2252E13CD00AEED9E /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t80D6DEE3252E13CD00AEED9E /* Build configuration list for PBXNativeTarget \"FocalboardUITests\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t80D6DEE4252E13CD00AEED9E /* Debug */,\n\t\t\t\t80D6DEE5252E13CD00AEED9E /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n/* End XCConfigurationList section */\n\t};\n\trootObject = 80D6DEAF252E13CB00AEED9E /* Project object */;\n}\n"
  },
  {
    "path": "mac/Focalboard.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"self:\">\n   </FileRef>\n</Workspace>\n"
  },
  {
    "path": "mac/Focalboard.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>IDEDidComputeMac32BitWarning</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "mac/Focalboard.xcodeproj/xcshareddata/xcschemes/Focalboard.xcscheme",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1220\"\n   version = \"1.3\">\n   <BuildAction\n      parallelizeBuildables = \"YES\"\n      buildImplicitDependencies = \"YES\">\n      <BuildActionEntries>\n         <BuildActionEntry\n            buildForTesting = \"YES\"\n            buildForRunning = \"YES\"\n            buildForProfiling = \"YES\"\n            buildForArchiving = \"YES\"\n            buildForAnalyzing = \"YES\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"80D6DEB6252E13CB00AEED9E\"\n               BuildableName = \"Focalboard.app\"\n               BlueprintName = \"Focalboard\"\n               ReferencedContainer = \"container:Focalboard.xcodeproj\">\n            </BuildableReference>\n         </BuildActionEntry>\n      </BuildActionEntries>\n   </BuildAction>\n   <TestAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\">\n      <Testables>\n      </Testables>\n   </TestAction>\n   <LaunchAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      launchStyle = \"0\"\n      useCustomWorkingDirectory = \"NO\"\n      ignoresPersistentStateOnLaunch = \"NO\"\n      debugDocumentVersioning = \"YES\"\n      debugServiceExtension = \"internal\"\n      allowLocationSimulation = \"YES\">\n      <BuildableProductRunnable\n         runnableDebuggingMode = \"0\">\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"80D6DEB6252E13CB00AEED9E\"\n            BuildableName = \"Focalboard.app\"\n            BlueprintName = \"Focalboard\"\n            ReferencedContainer = \"container:Focalboard.xcodeproj\">\n         </BuildableReference>\n      </BuildableProductRunnable>\n   </LaunchAction>\n   <ProfileAction\n      buildConfiguration = \"Release\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\"\n      savedToolIdentifier = \"\"\n      useCustomWorkingDirectory = \"NO\"\n      debugDocumentVersioning = \"YES\">\n      <BuildableProductRunnable\n         runnableDebuggingMode = \"0\">\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"80D6DEB6252E13CB00AEED9E\"\n            BuildableName = \"Focalboard.app\"\n            BlueprintName = \"Focalboard\"\n            ReferencedContainer = \"container:Focalboard.xcodeproj\">\n         </BuildableReference>\n      </BuildableProductRunnable>\n   </ProfileAction>\n   <AnalyzeAction\n      buildConfiguration = \"Debug\">\n   </AnalyzeAction>\n   <ArchiveAction\n      buildConfiguration = \"Release\"\n      revealArchiveInOrganizer = \"YES\">\n   </ArchiveAction>\n</Scheme>\n"
  },
  {
    "path": "mac/Focalboard.xcworkspace/contents.xcworkspacedata",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"container:Focalboard.xcodeproj\">\n   </FileRef>\n</Workspace>\n"
  },
  {
    "path": "mac/Focalboard.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>IDEDidComputeMac32BitWarning</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "mac/FocalboardTests/FocalboardTests.swift",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport XCTest\n@testable import Focalboard\n\nclass FocalboardTests: XCTestCase {\n\n    override func setUpWithError() throws {\n        // Put setup code here. This method is called before the invocation of each test method in the class.\n    }\n\n    override func tearDownWithError() throws {\n        // Put teardown code here. This method is called after the invocation of each test method in the class.\n    }\n\n    func testExample() throws {\n        // This is an example of a functional test case.\n        // Use XCTAssert and related functions to verify your tests produce the correct results.\n    }\n\n    func testPerformanceExample() throws {\n        // This is an example of a performance test case.\n        self.measure {\n            // Put the code you want to measure the time of here.\n        }\n    }\n\n}\n"
  },
  {
    "path": "mac/FocalboardTests/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>$(DEVELOPMENT_LANGUAGE)</string>\n\t<key>CFBundleExecutable</key>\n\t<string>$(EXECUTABLE_NAME)</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>$(PRODUCT_NAME)</string>\n\t<key>CFBundlePackageType</key>\n\t<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>1.0</string>\n\t<key>CFBundleVersion</key>\n\t<string>1</string>\n\t<key>NSAppTransportSecurity</key>\n\t<dict>\n\t\t<key>NSAllowsArbitraryLoads</key>\n\t\t<true/>\n\t\t<key>NSExceptionDomains</key>\n\t\t<dict>\n\t\t\t<key>127.0.0.1</key>\n\t\t\t<dict>\n\t\t\t\t<key>NSExceptionAllowsInsecureHTTPLoads</key>\n\t\t\t\t<true/>\n\t\t\t</dict>\n\t\t\t<key>localhost</key>\n\t\t\t<dict>\n\t\t\t\t<key>NSExceptionAllowsInsecureHTTPLoads</key>\n\t\t\t\t<true/>\n\t\t\t</dict>\n\t\t</dict>\n\t</dict>\n</dict>\n</plist>\n"
  },
  {
    "path": "mac/FocalboardUITests/FocalboardUITests.swift",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport XCTest\n\nclass FocalboardUITests: XCTestCase {\n\n    override func setUpWithError() throws {\n        // Put setup code here. This method is called before the invocation of each test method in the class.\n\n        // In UI tests it is usually best to stop immediately when a failure occurs.\n        continueAfterFailure = false\n\n        // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.\n    }\n\n    override func tearDownWithError() throws {\n        // Put teardown code here. This method is called after the invocation of each test method in the class.\n    }\n\n    func testExample() throws {\n        // UI tests must launch the application that they test.\n        let app = XCUIApplication()\n        app.launch()\n\n        // Use recording to get started writing UI tests.\n        // Use XCTAssert and related functions to verify your tests produce the correct results.\n    }\n\n    func testLaunchPerformance() throws {\n        if #available(macOS 10.15, iOS 13.0, tvOS 13.0, *) {\n            // This measures how long it takes to launch your application.\n            measure(metrics: [XCTApplicationLaunchMetric()]) {\n                XCUIApplication().launch()\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "mac/FocalboardUITests/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>$(DEVELOPMENT_LANGUAGE)</string>\n\t<key>CFBundleExecutable</key>\n\t<string>$(EXECUTABLE_NAME)</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>$(PRODUCT_NAME)</string>\n\t<key>CFBundlePackageType</key>\n\t<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>1.0</string>\n\t<key>CFBundleVersion</key>\n\t<string>1</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "mac/README.md",
    "content": "# Focalboard Mac Personal Desktop\n\nThis folder contains the code for the Mac Personal Desktop. It packages a lightweight Swift Mac App with the Mac build of the server, and the webapp. The server is run in a single-user mode.\n\n## Debugging in Xcode\n\nOpen `Focalboard.xcworkspace` in Xcode to debug it.\n\nTo debug the client webapp:\n1. Run the Focalboard desktop app from Xcode\n2. Open Safari\n3. Enable Safari's [developer tools]\n4. Select the Focalboard app from the develop menu, under your computer's name\n\n### Testing the single-user server\n\nYou can also run the server in single-user mode and connect to it via a browser:\n\n1. Run `FOCALBOARD_SINGLE_USER_TOKEN=testtest make watch-single-user`\n  * This runs the server with the `-single-user` flag\n  * Alternatively, select `Go: Launch Single-user Server` from VSCode's run and debug options\n2. Open a browser to `http://localhost:8000`\n3. Open the browser developer tools to Application \\ Local Storage \\ localhost:8000\n4. Set `focalboardSessionId` to `testtest`\n5. Navigate to `http://localhost:8000`\n"
  },
  {
    "path": "mac/export.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t <key>compileBitcode</key>\n\t <true/>\n\t <key>method</key>\n\t <string>development</string>\n\t <key>signingStyle</key>\n\t <string>automatic</string>\n\t <key>stripSwiftSymbols</key>\n\t <true/>\n\t <key>thinning</key>\n\t <string>&lt;none&gt;</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "modd-servertest.conf",
    "content": "**/*.go {\n    prep: cd server && go test -tags \"$FOCALBOARD_BUILD_TAGS\" -race -v ./...\n}\n"
  },
  {
    "path": "modd.conf",
    "content": "**/*.go !**/*_test.go {\n    prep: cd server && go build -tags \"$FOCALBOARD_BUILD_TAGS\" -o ../bin/focalboard-server ./main\n    daemon +sigterm: ./bin/focalboard-server $FOCALBOARDSERVER_ARGS\n}\n\n{\n    daemon: cd webapp && npm run watchdev\n}\n"
  },
  {
    "path": "noticegen/Readme.md",
    "content": "# Notice.txt File Configuration\n\nWe are automatically generating Notice.txt by using first-level dependencies of the project. The related pipeline uses `config.yaml` stored in this folder.\n\n\n## Configuration\n\nSample:\n\n```\ntitle: \"Mattermost Playbooks\"\ncopyright: \"©2015-present Mattermost, Inc.  All Rights Reserved.  See LICENSE for license information.\"\ndescription: \"This document includes a list of open source components used in Mattermost Playbooks, including those that have been modified.\"\nsearch:\n  - \"go.mod\"\n  - \"client/go.mod\"\ndependencies: []\ndevDependencies: []\n```\n\n| Field | Type   | Purpose |\n| :--   | :--    | :--     |\n| title | string | Field content will be used as a title of the application. See first line of `NOTICE.txt` file. |\n| copyright | string | Field content will be used as a copyright message. See second line of `NOTICE.txt` file. |\n| description | string | Field content will be used as notice file description. See third line of `NOTICE.txt` file. |\n| dependencies | array | If any dependency name mentioned, it will be automatically added even if it is not a first-level dependency. |\n| devDependencies | array | If any dependency name mentioned, it will be added when it is referenced in devDependency section. |\n| search | array | Pipeline will search for package.json/go.mod files mentioned here. Globstar format is supported ie. `x/**/go.mod`. |\n"
  },
  {
    "path": "noticegen/config.yaml",
    "content": "---\n\ntitle: \"Mattermost Focalboard\"\ncopyright: \"©2015-present Mattermost,Inc. All Rights Reserved. See LICENSE for license information.\"\ndescription: \"This document includes a list of open source components used in Mattermost Focalboard, including those that have been modified.\"\nsearch:\n  - \"server/go.mod\"\n  - \"linux/go.mod\"\ndependencies: []\ndevDependencies: []\n\n...\n"
  },
  {
    "path": "pull_request_template.md",
    "content": "<!-- Thank you for contributing a pull request! Here are a few tips to help you:\n\n1. If this is your first contribution, make sure you've read the Contribution Checklist https://developers.mattermost.com/contribute/getting-started/contribution-checklist/\n2. Read our blog post about \"Submitting Great PRs\" https://developers.mattermost.com/blog/2019-01-24-submitting-great-prs\n3. Take a look at other repository specific documentation at https://developers.mattermost.com/contribute\n\nAssign two reviewers for this pull request from the names suggested. If no names are suggested or you are not sure who to assign, set `Core Focalboard` as the reviewer.\n-->\n\n#### Summary\n<!--\nA description of what this pull request does, as well as QA test steps (if applicable and if not already added to the ticket).\n-->\n\n#### Ticket Link\n<!--\nIf this pull request addresses a Github issue, please link the relevant issue, e.g.\n\n  Fixes https://github.com/mattermost/focalboard/issues/XXXXX\n\nOtherwise, link the JIRA ticket.\n-->\n"
  },
  {
    "path": "responsible_disclosure_policy.md",
    "content": "# Responsible Disclosure Policy\n\nSafety and data security are of utmost priority for the Focalboard community. If you are a security researcher and have discovered a security vulnerability in our codebase, we appreciate your help in disclosing it to us in a responsible manner.\n\nPlease contact us at `chen [at] mattermost.com` to report any security vulnerabilities found in our open source codebase.\n\nPlease refrain from requesting compensation for reporting vulnerabilities.\n\nWe will acknowledge receipt of your vulnerability report and send you regular updates about our progress.\n\nIf your report is reproducible as an exploit and results in a change to the codebase or documentation of a Focalboard product, we will–-at your option–-publicly acknowledge your responsible disclosure.\n\nAfter a fix is made, we ask security researchers to wait 30 days after a release before announcing the specific details of a vulnerability, and to provide Focalboard with a link to any such announcements. In releases containing security fixes, Focalboard announces an update is available, acknowledges the contributions of security researchers, and it withholds specific details until 30 days after availability to give time for the community to apply updates.\n\nYou are not allowed to search for vulnerabilities on any instance of Focalboard hosted by the team, users, or customers with the exception of non-disruptive testing on the community test server mentioned above.\n\nFocalboard is open source software, you can install a copy yourself and test against that.\n\nMany thanks to the security researchers who have responsibly contributed their findings to make the Focalboard code base more secure (listed by number of contributions, then alphabetically).\n\nSecurity Research Hall of Fame:\n\n- [To be announced]\n"
  },
  {
    "path": "server/.golangci.yml",
    "content": "run:\n  timeout: 5m\n  modules-download-mode: readonly\n  skip-dirs:\n    - services/store/sqlstore/migrations\n\nlinters-settings:\n  gofmt:\n    simplify: true\n  govet:\n    check-shadowing: true\n    enable-all: true\n    disable:\n      - fieldalignment\n  lll:\n    line-length: 180\n  dupl:\n    threshold: 200\n  revive:\n    enableAllRules: true\n    rules:\n      - name: exported\n        disabled: true\n\nlinters:\n  disable-all: true\n  enable:\n    - gofmt\n    - goimports\n    - ineffassign\n    - unparam\n    - errcheck\n    - govet\n    - bodyclose\n    - durationcheck\n    - errorlint\n    - exhaustive\n    - exportloopref\n    - gosec\n    - makezero\n    - staticcheck\n    - prealloc\n    - asciicheck\n    - dogsled\n    - dupl\n    - goconst\n    - gocritic\n    - godot\n    - err113\n    - goheader\n    - revive\n    - nakedret\n    - gomodguard\n    - goprintffuncname\n    - gosimple\n    - lll\n    - misspell\n    - nolintlint\n    - stylecheck\n    - typecheck\n    - unconvert\n    - unused\n    - whitespace\n    - gocyclo\n"
  },
  {
    "path": "server/admin-scripts/reset-password.sh",
    "content": "#!/bin/bash\n\nif [[ $# < 2 ]] ; then\n    echo 'reset-password.sh <username> <new password>'\n    exit 1\nfi\n\ncurl --unix-socket /var/tmp/focalboard_local.socket http://localhost/api/v2/admin/users/$1/password -X POST -H 'Content-Type: application/json' -d '{ \"password\": \"'$2'\" }'\n"
  },
  {
    "path": "server/api/admin.go",
    "content": "package api\n\nimport (\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/gorilla/mux\"\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/audit\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\ntype AdminSetPasswordData struct {\n\tPassword string `json:\"password\"`\n}\n\nfunc (a *API) handleAdminSetPassword(w http.ResponseWriter, r *http.Request) {\n\tvars := mux.Vars(r)\n\tusername := vars[\"username\"]\n\n\trequestBody, err := io.ReadAll(r.Body)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tvar requestData AdminSetPasswordData\n\terr = json.Unmarshal(requestBody, &requestData)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"adminSetPassword\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelAuth, auditRec)\n\tauditRec.AddMeta(\"username\", username)\n\n\tif !strings.Contains(requestData.Password, \"\") {\n\t\ta.errorResponse(w, r, model.NewErrBadRequest(\"password is required\"))\n\t\treturn\n\t}\n\n\terr = a.app.UpdateUserPassword(username, requestData.Password)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\ta.logger.Debug(\"AdminSetPassword, username: %s\", mlog.String(\"username\", username))\n\n\tjsonStringResponse(w, http.StatusOK, \"{}\")\n\tauditRec.Success()\n}\n"
  },
  {
    "path": "server/api/api.go",
    "content": "package api\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"runtime/debug\"\n\n\t\"github.com/gorilla/mux\"\n\t\"github.com/mattermost/focalboard/server/app\"\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/audit\"\n\t\"github.com/mattermost/focalboard/server/services/permissions\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nconst (\n\tHeaderRequestedWith    = \"X-Requested-With\"\n\tHeaderRequestedWithXML = \"XMLHttpRequest\"\n\tUploadFormFileKey      = \"file\"\n\tTrue                   = \"true\"\n\n\tErrorNoTeamCode    = 1000\n\tErrorNoTeamMessage = \"No team\"\n)\n\nvar (\n\tErrHandlerPanic = errors.New(\"http handler panic\")\n)\n\n// ----------------------------------------------------------------------------------------------------\n// REST APIs\n\ntype API struct {\n\tapp             *app.App\n\tauthService     string\n\tpermissions     permissions.PermissionsService\n\tsingleUserToken string\n\tMattermostAuth  bool\n\tlogger          mlog.LoggerIFace\n\taudit           *audit.Audit\n}\n\nfunc NewAPI(\n\tapp *app.App,\n\tsingleUserToken string,\n\tauthService string,\n\tpermissions permissions.PermissionsService,\n\tlogger mlog.LoggerIFace,\n\taudit *audit.Audit,\n) *API {\n\treturn &API{\n\t\tapp:             app,\n\t\tsingleUserToken: singleUserToken,\n\t\tauthService:     authService,\n\t\tpermissions:     permissions,\n\t\tlogger:          logger,\n\t\taudit:           audit,\n\t}\n}\n\nfunc (a *API) RegisterRoutes(r *mux.Router) {\n\tapiv2 := r.PathPrefix(\"/api/v2\").Subrouter()\n\tapiv2.Use(a.panicHandler)\n\tapiv2.Use(a.requireCSRFToken)\n\n\t/* ToDo:\n\tapiv3 := r.PathPrefix(\"/api/v3\").Subrouter()\n\tapiv3.Use(a.panicHandler)\n\tapiv3.Use(a.requireCSRFToken)\n\t*/\n\n\t// V2 routes (ToDo: migrate these to V3 when ready to ship V3)\n\ta.registerUsersRoutes(apiv2)\n\ta.registerAuthRoutes(apiv2)\n\ta.registerMembersRoutes(apiv2)\n\ta.registerCategoriesRoutes(apiv2)\n\ta.registerSharingRoutes(apiv2)\n\ta.registerTeamsRoutes(apiv2)\n\ta.registerAchivesRoutes(apiv2)\n\ta.registerSubscriptionsRoutes(apiv2)\n\ta.registerFilesRoutes(apiv2)\n\ta.registerOnboardingRoutes(apiv2)\n\ta.registerSearchRoutes(apiv2)\n\ta.registerConfigRoutes(apiv2)\n\ta.registerBoardsAndBlocksRoutes(apiv2)\n\ta.registerChannelsRoutes(apiv2)\n\ta.registerTemplatesRoutes(apiv2)\n\ta.registerBoardsRoutes(apiv2)\n\ta.registerBlocksRoutes(apiv2)\n\ta.registerContentBlocksRoutes(apiv2)\n\ta.registerStatisticsRoutes(apiv2)\n\ta.registerComplianceRoutes(apiv2)\n\n\t// V3 routes\n\ta.registerCardsRoutes(apiv2)\n\n\t// System routes are outside the /api/v2 path\n\ta.registerSystemRoutes(r)\n}\n\nfunc (a *API) RegisterAdminRoutes(r *mux.Router) {\n\tr.HandleFunc(\"/api/v2/admin/users/{username}/password\", a.adminRequired(a.handleAdminSetPassword)).Methods(\"POST\")\n}\n\nfunc getUserID(r *http.Request) string {\n\tctx := r.Context()\n\tsession, ok := ctx.Value(sessionContextKey).(*model.Session)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn session.UserID\n}\n\nfunc (a *API) panicHandler(next http.Handler) http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tdefer func() {\n\t\t\tif p := recover(); p != nil {\n\t\t\t\ta.logger.Error(\"Http handler panic\",\n\t\t\t\t\tmlog.Any(\"panic\", p),\n\t\t\t\t\tmlog.String(\"stack\", string(debug.Stack())),\n\t\t\t\t\tmlog.String(\"uri\", r.URL.Path),\n\t\t\t\t)\n\t\t\t\ta.errorResponse(w, r, ErrHandlerPanic)\n\t\t\t}\n\t\t}()\n\n\t\tnext.ServeHTTP(w, r)\n\t})\n}\n\nfunc (a *API) requireCSRFToken(next http.Handler) http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tif !a.checkCSRFToken(r) {\n\t\t\ta.logger.Error(\"checkCSRFToken FAILED\")\n\t\t\ta.errorResponse(w, r, model.NewErrBadRequest(\"checkCSRFToken FAILED\"))\n\t\t\treturn\n\t\t}\n\n\t\tnext.ServeHTTP(w, r)\n\t})\n}\n\nfunc (a *API) checkCSRFToken(r *http.Request) bool {\n\ttoken := r.Header.Get(HeaderRequestedWith)\n\treturn token == HeaderRequestedWithXML\n}\n\nfunc (a *API) hasValidReadTokenForBoard(r *http.Request, boardID string) bool {\n\tquery := r.URL.Query()\n\treadToken := query.Get(\"read_token\")\n\n\tif len(readToken) < 1 {\n\t\treturn false\n\t}\n\n\tisValid, err := a.app.IsValidReadToken(boardID, readToken)\n\tif err != nil {\n\t\ta.logger.Error(\"IsValidReadTokenForBoard ERROR\", mlog.Err(err))\n\t\treturn false\n\t}\n\n\treturn isValid\n}\n\nfunc (a *API) userIsGuest(userID string) (bool, error) {\n\tif a.singleUserToken != \"\" {\n\t\treturn false, nil\n\t}\n\treturn a.app.UserIsGuest(userID)\n}\n\n// Response helpers\n\nfunc (a *API) errorResponse(w http.ResponseWriter, r *http.Request, err error) {\n\ta.logger.Error(err.Error())\n\terrorResponse := model.ErrorResponse{Error: err.Error()}\n\n\tswitch {\n\tcase model.IsErrBadRequest(err):\n\t\terrorResponse.ErrorCode = http.StatusBadRequest\n\tcase model.IsErrUnauthorized(err):\n\t\terrorResponse.ErrorCode = http.StatusUnauthorized\n\tcase model.IsErrForbidden(err):\n\t\terrorResponse.ErrorCode = http.StatusForbidden\n\tcase model.IsErrNotFound(err):\n\t\terrorResponse.ErrorCode = http.StatusNotFound\n\tcase model.IsErrRequestEntityTooLarge(err):\n\t\terrorResponse.ErrorCode = http.StatusRequestEntityTooLarge\n\tcase model.IsErrNotImplemented(err):\n\t\terrorResponse.ErrorCode = http.StatusNotImplemented\n\tdefault:\n\t\ta.logger.Error(\"API ERROR\",\n\t\t\tmlog.Int(\"code\", http.StatusInternalServerError),\n\t\t\tmlog.Err(err),\n\t\t\tmlog.String(\"api\", r.URL.Path),\n\t\t)\n\t\terrorResponse.Error = \"internal server error\"\n\t\terrorResponse.ErrorCode = http.StatusInternalServerError\n\t}\n\n\tsetResponseHeader(w, \"Content-Type\", \"application/json\")\n\tdata, err := json.Marshal(errorResponse)\n\tif err != nil {\n\t\tdata = []byte(\"{}\")\n\t}\n\n\tw.WriteHeader(errorResponse.ErrorCode)\n\t_, _ = w.Write(data)\n}\n\nfunc stringResponse(w http.ResponseWriter, message string) {\n\tsetResponseHeader(w, \"Content-Type\", \"text/plain\")\n\t_, _ = fmt.Fprint(w, message)\n}\n\nfunc jsonStringResponse(w http.ResponseWriter, code int, message string) { //nolint:unparam\n\tsetResponseHeader(w, \"Content-Type\", \"application/json\")\n\tw.WriteHeader(code)\n\tfmt.Fprint(w, message)\n}\n\nfunc jsonBytesResponse(w http.ResponseWriter, code int, json []byte) { //nolint:unparam\n\tsetResponseHeader(w, \"Content-Type\", \"application/json\")\n\tw.WriteHeader(code)\n\t_, _ = w.Write(json)\n}\n\nfunc setResponseHeader(w http.ResponseWriter, key string, value string) { //nolint:unparam\n\theader := w.Header()\n\tif header == nil {\n\t\treturn\n\t}\n\theader.Set(key, value)\n}\n"
  },
  {
    "path": "server/api/api_test.go",
    "content": "package api\n\nimport (\n\t\"database/sql\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestErrorResponse(t *testing.T) {\n\ttestAPI := API{logger: mlog.CreateConsoleTestLogger(t)}\n\n\ttestCases := []struct {\n\t\tName         string\n\t\tError        error\n\t\tResponseCode int\n\t\tResponseBody string\n\t}{\n\t\t// bad request\n\t\t{\"ErrBadRequest\", model.NewErrBadRequest(\"bad field\"), http.StatusBadRequest, \"bad field\"},\n\t\t{\"ErrViewsLimitReached\", model.ErrViewsLimitReached, http.StatusBadRequest, \"limit reached\"},\n\t\t{\"ErrAuthParam\", model.NewErrAuthParam(\"password is required\"), http.StatusBadRequest, \"password is required\"},\n\t\t{\"ErrInvalidCategory\", model.NewErrInvalidCategory(\"open\"), http.StatusBadRequest, \"open\"},\n\t\t{\"ErrBoardMemberIsLastAdmin\", model.ErrBoardMemberIsLastAdmin, http.StatusBadRequest, \"no admins\"},\n\t\t{\"ErrBoardIDMismatch\", model.ErrBoardIDMismatch, http.StatusBadRequest, \"Board IDs do not match\"},\n\t\t{\"ErrBlockTitleSizeLimitExceeded\", model.ErrBlockTitleSizeLimitExceeded, http.StatusBadRequest, \"block title size limit exceeded\"},\n\t\t{\"ErrBlockFieldsSizeLimitExceeded\", model.ErrBlockFieldsSizeLimitExceeded, http.StatusBadRequest, \"block fields size limit exceeded\"},\n\n\t\t// unauthorized\n\t\t{\"ErrUnauthorized\", model.NewErrUnauthorized(\"not enough permissions\"), http.StatusUnauthorized, \"not enough permissions\"},\n\n\t\t// forbidden\n\t\t{\"ErrForbidden\", model.NewErrForbidden(\"not enough permissions\"), http.StatusForbidden, \"not enough permissions\"},\n\t\t{\"ErrPermission\", model.NewErrPermission(\"not enough permissions\"), http.StatusForbidden, \"not enough permissions\"},\n\t\t{\"ErrPatchUpdatesLimitedCards\", model.ErrPatchUpdatesLimitedCards, http.StatusForbidden, \"cards that are limited\"},\n\t\t{\"ErrCategoryPermissionDenied\", model.ErrCategoryPermissionDenied, http.StatusForbidden, \"doesn't belong to user\"},\n\n\t\t// not found\n\t\t{\"ErrNotFound\", model.NewErrNotFound(\"board\"), http.StatusNotFound, \"board\"},\n\t\t{\"ErrNotAllFound\", model.NewErrNotAllFound(\"block\", []string{\"1\", \"2\"}), http.StatusNotFound, \"not all instances of {block} in {1, 2} found\"},\n\t\t{\"sql.ErrNoRows\", sql.ErrNoRows, http.StatusNotFound, \"rows\"},\n\t\t{\"ErrNotFound\", model.ErrCategoryDeleted, http.StatusNotFound, \"category is deleted\"},\n\n\t\t// request entity too large\n\t\t{\"ErrRequestEntityTooLarge\", model.ErrRequestEntityTooLarge, http.StatusRequestEntityTooLarge, \"entity too large\"},\n\n\t\t// not implemented\n\t\t{\"ErrNotFound\", model.ErrInsufficientLicense, http.StatusNotImplemented, \"appropriate license required\"},\n\t\t{\"ErrNotImplemented\", model.NewErrNotImplemented(\"not implemented in plugin mode\"), http.StatusNotImplemented, \"plugin mode\"},\n\n\t\t// internal server error\n\t\t{\"Any other error\", ErrHandlerPanic, http.StatusInternalServerError, \"internal server error\"},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(fmt.Sprintf(\"%s should be a %d code\", tc.Name, tc.ResponseCode), func(t *testing.T) {\n\t\t\tr := httptest.NewRequest(http.MethodGet, \"/test\", nil)\n\t\t\tw := httptest.NewRecorder()\n\n\t\t\ttestAPI.errorResponse(w, r, tc.Error)\n\t\t\tres := w.Result()\n\n\t\t\trequire.Equal(t, tc.ResponseCode, res.StatusCode)\n\t\t\trequire.Equal(t, \"application/json\", res.Header.Get(\"Content-Type\"))\n\t\t\tb, rErr := io.ReadAll(res.Body)\n\t\t\trequire.NoError(t, rErr)\n\t\t\tres.Body.Close()\n\t\t\trequire.Contains(t, string(b), tc.ResponseBody)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "server/api/archive.go",
    "content": "package api\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/gorilla/mux\"\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/audit\"\n\n\tmmModel \"github.com/mattermost/mattermost/server/public/model\"\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nconst (\n\tarchiveExtension = \".boardarchive\"\n)\n\nfunc (a *API) registerAchivesRoutes(r *mux.Router) {\n\t// Archive APIs\n\tr.HandleFunc(\"/boards/{boardID}/archive/export\", a.sessionRequired(a.handleArchiveExportBoard)).Methods(\"GET\")\n\tr.HandleFunc(\"/teams/{teamID}/archive/import\", a.sessionRequired(a.handleArchiveImport)).Methods(\"POST\")\n\tr.HandleFunc(\"/teams/{teamID}/archive/export\", a.sessionRequired(a.handleArchiveExportTeam)).Methods(\"GET\")\n}\n\nfunc (a *API) handleArchiveExportBoard(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation GET /boards/{boardID}/archive/export archiveExportBoard\n\t//\n\t// Exports an archive of all blocks for one boards.\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: boardID\n\t//   in: path\n\t//   description: Id of board to export\n\t//   required: true\n\t//   type: string\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     content:\n\t//       application-octet-stream:\n\t//         type: string\n\t//         format: binary\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tvars := mux.Vars(r)\n\tboardID := vars[\"boardID\"]\n\tuserID := getUserID(r)\n\n\t// check user has permission to board\n\tif !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionViewBoard) {\n\t\t// if this user has `manage_system` permission and there is a license with the compliance\n\t\t// feature enabled, then we will allow the export.\n\t\tlicense := a.app.GetLicense()\n\t\tif !a.permissions.HasPermissionTo(userID, mmModel.PermissionManageSystem) || license == nil || !(*license.Features.Compliance) {\n\t\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to board\"))\n\t\t\treturn\n\t\t}\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"archiveExportBoard\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelRead, auditRec)\n\tauditRec.AddMeta(\"BoardID\", boardID)\n\n\tboard, err := a.app.GetBoard(boardID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\topts := model.ExportArchiveOptions{\n\t\tTeamID:   board.TeamID,\n\t\tBoardIDs: []string{board.ID},\n\t}\n\n\tfilename := fmt.Sprintf(\"archive-%s%s\", time.Now().Format(\"2006-01-02\"), archiveExtension)\n\tw.Header().Set(\"Content-Type\", \"application/octet-stream\")\n\tw.Header().Set(\"Content-Disposition\", \"attachment; filename=\"+filename)\n\tw.Header().Set(\"Content-Transfer-Encoding\", \"binary\")\n\n\tif err := a.app.ExportArchive(w, opts); err != nil {\n\t\ta.errorResponse(w, r, err)\n\t}\n\n\tauditRec.Success()\n}\n\nfunc (a *API) handleArchiveImport(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation POST /teams/{teamID}/archive/import archiveImport\n\t//\n\t// Import an archive of boards.\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// consumes:\n\t// - multipart/form-data\n\t// parameters:\n\t// - name: teamID\n\t//   in: path\n\t//   description: Team ID\n\t//   required: true\n\t//   type: string\n\t// - name: file\n\t//   in: formData\n\t//   description: archive file to import\n\t//   required: true\n\t//   type: file\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tctx := r.Context()\n\tsession, _ := ctx.Value(sessionContextKey).(*model.Session)\n\tuserID := session.UserID\n\n\tvars := mux.Vars(r)\n\tteamID := vars[\"teamID\"]\n\n\tif !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to create board\"))\n\t\treturn\n\t}\n\n\tisGuest, err := a.userIsGuest(userID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\tif isGuest {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to create board\"))\n\t\treturn\n\t}\n\n\tfile, handle, err := r.FormFile(UploadFormFileKey)\n\tif err != nil {\n\t\tfmt.Fprintf(w, \"%v\", err)\n\t\treturn\n\t}\n\tdefer file.Close()\n\n\tauditRec := a.makeAuditRecord(r, \"import\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelModify, auditRec)\n\tauditRec.AddMeta(\"filename\", handle.Filename)\n\tauditRec.AddMeta(\"size\", handle.Size)\n\n\topt := model.ImportArchiveOptions{\n\t\tTeamID:     teamID,\n\t\tModifiedBy: userID,\n\t}\n\n\tif err := a.app.ImportArchive(file, opt); err != nil {\n\t\ta.logger.Debug(\"Error importing archive\",\n\t\t\tmlog.String(\"team_id\", teamID),\n\t\t\tmlog.Err(err),\n\t\t)\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tjsonStringResponse(w, http.StatusOK, \"{}\")\n\tauditRec.Success()\n}\n\nfunc (a *API) handleArchiveExportTeam(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation GET /teams/{teamID}/archive/export archiveExportTeam\n\t//\n\t// Exports an archive of all blocks for all the boards in a team.\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: teamID\n\t//   in: path\n\t//   description: Id of team\n\t//   required: true\n\t//   type: string\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     content:\n\t//       application-octet-stream:\n\t//         type: string\n\t//         format: binary\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\tif a.MattermostAuth {\n\t\ta.errorResponse(w, r, model.NewErrNotImplemented(\"not permitted in plugin mode\"))\n\t\treturn\n\t}\n\n\tvars := mux.Vars(r)\n\tteamID := vars[\"teamID\"]\n\n\tctx := r.Context()\n\tsession, _ := ctx.Value(sessionContextKey).(*model.Session)\n\tuserID := session.UserID\n\n\tauditRec := a.makeAuditRecord(r, \"archiveExportTeam\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelRead, auditRec)\n\tauditRec.AddMeta(\"TeamID\", teamID)\n\n\tisGuest, err := a.userIsGuest(userID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tboards, err := a.app.GetBoardsForUserAndTeam(userID, teamID, !isGuest)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\tids := []string{}\n\tfor _, board := range boards {\n\t\tids = append(ids, board.ID)\n\t}\n\n\topts := model.ExportArchiveOptions{\n\t\tTeamID:   teamID,\n\t\tBoardIDs: ids,\n\t}\n\n\tfilename := fmt.Sprintf(\"archive-%s%s\", time.Now().Format(\"2006-01-02\"), archiveExtension)\n\tw.Header().Set(\"Content-Type\", \"application/octet-stream\")\n\tw.Header().Set(\"Content-Disposition\", \"attachment; filename=\"+filename)\n\tw.Header().Set(\"Content-Transfer-Encoding\", \"binary\")\n\n\tif err := a.app.ExportArchive(w, opts); err != nil {\n\t\ta.errorResponse(w, r, err)\n\t}\n\n\tauditRec.Success()\n}\n"
  },
  {
    "path": "server/api/audit.go",
    "content": "package api\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/audit\"\n)\n\n// makeAuditRecord creates an audit record pre-populated with data from the request.\nfunc (a *API) makeAuditRecord(r *http.Request, event string, initialStatus string) *audit.Record { //nolint:unparam\n\tctx := r.Context()\n\tvar sessionID string\n\tvar userID string\n\tif session, ok := ctx.Value(sessionContextKey).(*model.Session); ok {\n\t\tsessionID = session.ID\n\t\tuserID = session.UserID\n\t}\n\n\tteamID := \"unknown\"\n\trec := &audit.Record{\n\t\tAPIPath:   r.URL.Path,\n\t\tEvent:     event,\n\t\tStatus:    initialStatus,\n\t\tUserID:    userID,\n\t\tSessionID: sessionID,\n\t\tClient:    r.UserAgent(),\n\t\tIPAddress: r.RemoteAddr,\n\t\tMeta:      []audit.Meta{{K: audit.KeyTeamID, V: teamID}},\n\t}\n\n\treturn rec\n}\n"
  },
  {
    "path": "server/api/auth.go",
    "content": "package api\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/gorilla/mux\"\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/audit\"\n\t\"github.com/mattermost/focalboard/server/services/auth\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nfunc (a *API) registerAuthRoutes(r *mux.Router) {\n\t// personal-server specific routes. These are not needed in plugin mode.\n\tr.HandleFunc(\"/login\", a.handleLogin).Methods(\"POST\")\n\tr.HandleFunc(\"/logout\", a.sessionRequired(a.handleLogout)).Methods(\"POST\")\n\tr.HandleFunc(\"/register\", a.handleRegister).Methods(\"POST\")\n\tr.HandleFunc(\"/teams/{teamID}/regenerate_signup_token\", a.sessionRequired(a.handlePostTeamRegenerateSignupToken)).Methods(\"POST\")\n\tr.HandleFunc(\"/users/{userID}/changepassword\", a.sessionRequired(a.handleChangePassword)).Methods(\"POST\")\n}\n\nfunc (a *API) handleLogin(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation POST /login login\n\t//\n\t// Login user\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: body\n\t//   in: body\n\t//   description: Login request\n\t//   required: true\n\t//   schema:\n\t//     \"$ref\": \"#/definitions/LoginRequest\"\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/LoginResponse\"\n\t//   '401':\n\t//     description: invalid login\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\t//   '500':\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\tif a.MattermostAuth {\n\t\ta.errorResponse(w, r, model.NewErrNotImplemented(\"not permitted in plugin mode\"))\n\t\treturn\n\t}\n\n\tif len(a.singleUserToken) > 0 {\n\t\t// Not permitted in single-user mode\n\t\ta.errorResponse(w, r, model.NewErrUnauthorized(\"not permitted in single-user mode\"))\n\t\treturn\n\t}\n\n\trequestBody, err := io.ReadAll(r.Body)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tvar loginData model.LoginRequest\n\terr = json.Unmarshal(requestBody, &loginData)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"login\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelAuth, auditRec)\n\tauditRec.AddMeta(\"username\", loginData.Username)\n\tauditRec.AddMeta(\"type\", loginData.Type)\n\n\tif loginData.Type == \"normal\" {\n\t\ttoken, err := a.app.Login(loginData.Username, loginData.Email, loginData.Password, loginData.MfaToken)\n\t\tif err != nil {\n\t\t\ta.errorResponse(w, r, model.NewErrUnauthorized(\"incorrect login\"))\n\t\t\treturn\n\t\t}\n\t\tjson, err := json.Marshal(model.LoginResponse{Token: token})\n\t\tif err != nil {\n\t\t\ta.errorResponse(w, r, err)\n\t\t\treturn\n\t\t}\n\n\t\tjsonBytesResponse(w, http.StatusOK, json)\n\t\tauditRec.Success()\n\t\treturn\n\t}\n\n\ta.errorResponse(w, r, model.NewErrBadRequest(\"invalid login type\"))\n}\n\nfunc (a *API) handleLogout(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation POST /logout logout\n\t//\n\t// Logout user\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//   '500':\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\tif a.MattermostAuth {\n\t\ta.errorResponse(w, r, model.NewErrNotImplemented(\"not permitted in plugin mode\"))\n\t\treturn\n\t}\n\n\tif len(a.singleUserToken) > 0 {\n\t\t// Not permitted in single-user mode\n\t\ta.errorResponse(w, r, model.NewErrUnauthorized(\"not permitted in single-user mode\"))\n\t\treturn\n\t}\n\n\tctx := r.Context()\n\n\tsession := ctx.Value(sessionContextKey).(*model.Session)\n\n\tauditRec := a.makeAuditRecord(r, \"logout\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelAuth, auditRec)\n\tauditRec.AddMeta(\"userID\", session.UserID)\n\n\tif err := a.app.Logout(session.ID); err != nil {\n\t\ta.errorResponse(w, r, model.NewErrUnauthorized(\"incorrect logout\"))\n\t\treturn\n\t}\n\n\tauditRec.AddMeta(\"sessionID\", session.ID)\n\n\tjsonStringResponse(w, http.StatusOK, \"{}\")\n\tauditRec.Success()\n}\n\nfunc (a *API) handleRegister(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation POST /register register\n\t//\n\t// Register new user\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: body\n\t//   in: body\n\t//   description: Register request\n\t//   required: true\n\t//   schema:\n\t//     \"$ref\": \"#/definitions/RegisterRequest\"\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//   '401':\n\t//     description: invalid registration token\n\t//   '500':\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\tif a.MattermostAuth {\n\t\ta.errorResponse(w, r, model.NewErrNotImplemented(\"not permitted in plugin mode\"))\n\t\treturn\n\t}\n\n\tif len(a.singleUserToken) > 0 {\n\t\t// Not permitted in single-user mode\n\t\ta.errorResponse(w, r, model.NewErrUnauthorized(\"not permitted in single-user mode\"))\n\t\treturn\n\t}\n\n\trequestBody, err := io.ReadAll(r.Body)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tvar registerData model.RegisterRequest\n\terr = json.Unmarshal(requestBody, &registerData)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\tregisterData.Email = strings.TrimSpace(registerData.Email)\n\tregisterData.Username = strings.TrimSpace(registerData.Username)\n\n\t// Validate token\n\tif len(registerData.Token) > 0 {\n\t\tteam, err2 := a.app.GetRootTeam()\n\t\tif err2 != nil {\n\t\t\ta.errorResponse(w, r, err2)\n\t\t\treturn\n\t\t}\n\n\t\tif registerData.Token != team.SignupToken {\n\t\t\ta.errorResponse(w, r, model.NewErrUnauthorized(\"invalid token\"))\n\t\t\treturn\n\t\t}\n\t} else {\n\t\t// No signup token, check if no active users\n\t\tuserCount, err2 := a.app.GetRegisteredUserCount()\n\t\tif err2 != nil {\n\t\t\ta.errorResponse(w, r, err2)\n\t\t\treturn\n\t\t}\n\t\tif userCount > 0 {\n\t\t\ta.errorResponse(w, r, model.NewErrUnauthorized(\"no sign-up token and user(s) already exist\"))\n\t\t\treturn\n\t\t}\n\t}\n\n\tif err = registerData.IsValid(); err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"register\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelAuth, auditRec)\n\tauditRec.AddMeta(\"username\", registerData.Username)\n\n\terr = a.app.RegisterUser(registerData.Username, registerData.Email, registerData.Password)\n\tif err != nil {\n\t\ta.errorResponse(w, r, model.NewErrBadRequest(err.Error()))\n\t\treturn\n\t}\n\n\tjsonStringResponse(w, http.StatusOK, \"{}\")\n\tauditRec.Success()\n}\n\nfunc (a *API) handleChangePassword(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation POST /users/{userID}/changepassword changePassword\n\t//\n\t// Change a user's password\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: userID\n\t//   in: path\n\t//   description: User ID\n\t//   required: true\n\t//   type: string\n\t// - name: body\n\t//   in: body\n\t//   description: Change password request\n\t//   required: true\n\t//   schema:\n\t//     \"$ref\": \"#/definitions/ChangePasswordRequest\"\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//   '400':\n\t//     description: invalid request\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\t//   '500':\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\tif a.MattermostAuth {\n\t\ta.errorResponse(w, r, model.NewErrNotImplemented(\"not permitted in plugin mode\"))\n\t\treturn\n\t}\n\n\tif len(a.singleUserToken) > 0 {\n\t\t// Not permitted in single-user mode\n\t\ta.errorResponse(w, r, model.NewErrUnauthorized(\"not permitted in single-user mode\"))\n\t\treturn\n\t}\n\n\tvars := mux.Vars(r)\n\tuserID := vars[\"userID\"]\n\n\trequestBody, err := io.ReadAll(r.Body)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tvar requestData model.ChangePasswordRequest\n\tif err = json.Unmarshal(requestBody, &requestData); err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tif err = requestData.IsValid(); err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"changePassword\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelAuth, auditRec)\n\n\tif err = a.app.ChangePassword(userID, requestData.OldPassword, requestData.NewPassword); err != nil {\n\t\ta.errorResponse(w, r, model.NewErrBadRequest(err.Error()))\n\t\treturn\n\t}\n\n\tjsonStringResponse(w, http.StatusOK, \"{}\")\n\tauditRec.Success()\n}\n\nfunc (a *API) sessionRequired(handler func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) {\n\treturn a.attachSession(handler, true)\n}\n\nfunc (a *API) attachSession(handler func(w http.ResponseWriter, r *http.Request), required bool) func(w http.ResponseWriter, r *http.Request) {\n\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\ttoken, _ := auth.ParseAuthTokenFromRequest(r)\n\n\t\ta.logger.Debug(`attachSession`, mlog.Bool(\"single_user\", len(a.singleUserToken) > 0))\n\t\tif len(a.singleUserToken) > 0 {\n\t\t\tif required && (token != a.singleUserToken) {\n\t\t\t\ta.errorResponse(w, r, model.NewErrUnauthorized(\"invalid single user token\"))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tnow := utils.GetMillis()\n\t\t\tsession := &model.Session{\n\t\t\t\tID:          model.SingleUser,\n\t\t\t\tToken:       token,\n\t\t\t\tUserID:      model.SingleUser,\n\t\t\t\tAuthService: a.authService,\n\t\t\t\tProps:       map[string]interface{}{},\n\t\t\t\tCreateAt:    now,\n\t\t\t\tUpdateAt:    now,\n\t\t\t}\n\t\t\tctx := context.WithValue(r.Context(), sessionContextKey, session)\n\t\t\thandler(w, r.WithContext(ctx))\n\t\t\treturn\n\t\t}\n\n\t\tif a.MattermostAuth && r.Header.Get(\"Mattermost-User-Id\") != \"\" {\n\t\t\tuserID := r.Header.Get(\"Mattermost-User-Id\")\n\t\t\tnow := utils.GetMillis()\n\t\t\tsession := &model.Session{\n\t\t\t\tID:          userID,\n\t\t\t\tToken:       userID,\n\t\t\t\tUserID:      userID,\n\t\t\t\tAuthService: a.authService,\n\t\t\t\tProps:       map[string]interface{}{},\n\t\t\t\tCreateAt:    now,\n\t\t\t\tUpdateAt:    now,\n\t\t\t}\n\n\t\t\tctx := context.WithValue(r.Context(), sessionContextKey, session)\n\t\t\thandler(w, r.WithContext(ctx))\n\t\t\treturn\n\t\t}\n\n\t\tsession, err := a.app.GetSession(token)\n\t\tif err != nil {\n\t\t\tif required {\n\t\t\t\ta.errorResponse(w, r, model.NewErrUnauthorized(err.Error()))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\thandler(w, r)\n\t\t\treturn\n\t\t}\n\n\t\tauthService := session.AuthService\n\t\tif authService != a.authService {\n\t\t\tmsg := `Session authService mismatch`\n\t\t\ta.logger.Error(msg,\n\t\t\t\tmlog.String(\"sessionID\", session.ID),\n\t\t\t\tmlog.String(\"want\", a.authService),\n\t\t\t\tmlog.String(\"got\", authService),\n\t\t\t)\n\t\t\ta.errorResponse(w, r, model.NewErrUnauthorized(msg))\n\t\t\treturn\n\t\t}\n\n\t\tctx := context.WithValue(r.Context(), sessionContextKey, session)\n\t\thandler(w, r.WithContext(ctx))\n\t}\n}\n\nfunc (a *API) adminRequired(handler func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) {\n\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\t// Currently, admin APIs require local unix connections\n\t\tconn := GetContextConn(r)\n\t\tif _, isUnix := conn.(*net.UnixConn); !isUnix {\n\t\t\ta.errorResponse(w, r, model.NewErrUnauthorized(\"not a local unix connection\"))\n\t\t\treturn\n\t\t}\n\n\t\thandler(w, r)\n\t}\n}\n"
  },
  {
    "path": "server/api/blocks.go",
    "content": "package api\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strconv\"\n\n\t\"github.com/gorilla/mux\"\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/audit\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nfunc (a *API) registerBlocksRoutes(r *mux.Router) {\n\t// Blocks APIs\n\tr.HandleFunc(\"/boards/{boardID}/blocks\", a.attachSession(a.handleGetBlocks, false)).Methods(\"GET\")\n\tr.HandleFunc(\"/boards/{boardID}/blocks\", a.sessionRequired(a.handlePostBlocks)).Methods(\"POST\")\n\tr.HandleFunc(\"/boards/{boardID}/blocks\", a.sessionRequired(a.handlePatchBlocks)).Methods(\"PATCH\")\n\tr.HandleFunc(\"/boards/{boardID}/blocks/{blockID}\", a.sessionRequired(a.handleDeleteBlock)).Methods(\"DELETE\")\n\tr.HandleFunc(\"/boards/{boardID}/blocks/{blockID}\", a.sessionRequired(a.handlePatchBlock)).Methods(\"PATCH\")\n\tr.HandleFunc(\"/boards/{boardID}/blocks/{blockID}/undelete\", a.sessionRequired(a.handleUndeleteBlock)).Methods(\"POST\")\n\tr.HandleFunc(\"/boards/{boardID}/blocks/{blockID}/duplicate\", a.sessionRequired(a.handleDuplicateBlock)).Methods(\"POST\")\n}\n\nfunc (a *API) handleGetBlocks(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation GET /boards/{boardID}/blocks getBlocks\n\t//\n\t// Returns blocks\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: boardID\n\t//   in: path\n\t//   description: Board ID\n\t//   required: true\n\t//   type: string\n\t// - name: parent_id\n\t//   in: query\n\t//   description: ID of parent block, omit to specify all blocks\n\t//   required: false\n\t//   type: string\n\t// - name: type\n\t//   in: query\n\t//   description: Type of blocks to return, omit to specify all types\n\t//   required: false\n\t//   type: string\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       type: array\n\t//       items:\n\t//         \"$ref\": \"#/definitions/Block\"\n\t//   '404':\n\t//     description: board not found\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tquery := r.URL.Query()\n\tparentID := query.Get(\"parent_id\")\n\tblockType := query.Get(\"type\")\n\tall := query.Get(\"all\")\n\tblockID := query.Get(\"block_id\")\n\tboardID := mux.Vars(r)[\"boardID\"]\n\n\tuserID := getUserID(r)\n\n\thasValidReadToken := a.hasValidReadTokenForBoard(r, boardID)\n\tif userID == \"\" && !hasValidReadToken {\n\t\ta.errorResponse(w, r, model.NewErrUnauthorized(\"access denied to board\"))\n\t\treturn\n\t}\n\n\tboard, err := a.app.GetBoard(boardID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tif !hasValidReadToken {\n\t\tif board.IsTemplate && board.Type == model.BoardTypeOpen {\n\t\t\tif board.TeamID != model.GlobalTeamID && !a.permissions.HasPermissionToTeam(userID, board.TeamID, model.PermissionViewTeam) {\n\t\t\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to board template\"))\n\t\t\t\treturn\n\t\t\t}\n\t\t} else {\n\t\t\tif !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionViewBoard) {\n\t\t\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to board\"))\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tif board.IsTemplate {\n\t\t\tvar isGuest bool\n\t\t\tisGuest, err = a.userIsGuest(userID)\n\t\t\tif err != nil {\n\t\t\t\ta.errorResponse(w, r, err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif isGuest {\n\t\t\t\ta.errorResponse(w, r, model.NewErrPermission(\"guest are not allowed to get board templates\"))\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"getBlocks\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelRead, auditRec)\n\tauditRec.AddMeta(\"boardID\", boardID)\n\tauditRec.AddMeta(\"parentID\", parentID)\n\tauditRec.AddMeta(\"blockType\", blockType)\n\tauditRec.AddMeta(\"all\", all)\n\tauditRec.AddMeta(\"blockID\", blockID)\n\n\tvar blocks []*model.Block\n\tvar block *model.Block\n\tswitch {\n\tcase all != \"\":\n\t\tblocks, err = a.app.GetBlocksForBoard(boardID)\n\t\tif err != nil {\n\t\t\ta.errorResponse(w, r, err)\n\t\t\treturn\n\t\t}\n\tcase blockID != \"\":\n\t\tblock, err = a.app.GetBlockByID(blockID)\n\t\tif err != nil {\n\t\t\ta.errorResponse(w, r, err)\n\t\t\treturn\n\t\t}\n\t\tif block.BoardID != boardID {\n\t\t\tmessage := fmt.Sprintf(\"block ID=%s on BoardID=%s\", block.ID, boardID)\n\t\t\ta.errorResponse(w, r, model.NewErrNotFound(message))\n\t\t\treturn\n\t\t}\n\n\t\tblocks = append(blocks, block)\n\tdefault:\n\t\tblocks, err = a.app.GetBlocks(boardID, parentID, blockType)\n\t\tif err != nil {\n\t\t\ta.errorResponse(w, r, err)\n\t\t\treturn\n\t\t}\n\t}\n\n\ta.logger.Debug(\"GetBlocks\",\n\t\tmlog.String(\"boardID\", boardID),\n\t\tmlog.String(\"parentID\", parentID),\n\t\tmlog.String(\"blockType\", blockType),\n\t\tmlog.String(\"blockID\", blockID),\n\t\tmlog.Int(\"block_count\", len(blocks)),\n\t)\n\n\tjson, err := json.Marshal(blocks)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tjsonBytesResponse(w, http.StatusOK, json)\n\n\tauditRec.AddMeta(\"blockCount\", len(blocks))\n\tauditRec.Success()\n}\n\nfunc (a *API) handlePostBlocks(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation POST /boards/{boardID}/blocks updateBlocks\n\t//\n\t// Insert blocks. The specified IDs will only be used to link\n\t// blocks with existing ones, the rest will be replaced by server\n\t// generated IDs\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: boardID\n\t//   in: path\n\t//   description: Board ID\n\t//   required: true\n\t//   type: string\n\t// - name: disable_notify\n\t//   in: query\n\t//   description: Disables notifications (for bulk inserting)\n\t//   required: false\n\t//   type: bool\n\t// - name: Body\n\t//   in: body\n\t//   description: array of blocks to insert or update\n\t//   required: true\n\t//   schema:\n\t//     type: array\n\t//     items:\n\t//       \"$ref\": \"#/definitions/Block\"\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       items:\n\t//         $ref: '#/definitions/Block'\n\t//       type: array\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tboardID := mux.Vars(r)[\"boardID\"]\n\tuserID := getUserID(r)\n\n\tval := r.URL.Query().Get(\"disable_notify\")\n\tdisableNotify := val == True\n\n\trequestBody, err := io.ReadAll(r.Body)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tvar blocks []*model.Block\n\n\terr = json.Unmarshal(requestBody, &blocks)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\thasComments := false\n\thasContents := false\n\tfor _, block := range blocks {\n\t\t// Error checking\n\t\tif len(block.Type) < 1 {\n\t\t\tmessage := fmt.Sprintf(\"missing type for block id %s\", block.ID)\n\t\t\ta.errorResponse(w, r, model.NewErrBadRequest(message))\n\t\t\treturn\n\t\t}\n\n\t\tif block.Type == model.TypeComment {\n\t\t\thasComments = true\n\t\t} else {\n\t\t\thasContents = true\n\t\t}\n\n\t\tif block.CreateAt < 1 {\n\t\t\tmessage := fmt.Sprintf(\"invalid createAt for block id %s\", block.ID)\n\t\t\ta.errorResponse(w, r, model.NewErrBadRequest(message))\n\t\t\treturn\n\t\t}\n\n\t\tif block.UpdateAt < 1 {\n\t\t\tmessage := fmt.Sprintf(\"invalid UpdateAt for block id %s\", block.ID)\n\t\t\ta.errorResponse(w, r, model.NewErrBadRequest(message))\n\t\t\treturn\n\t\t}\n\n\t\tif block.BoardID != boardID {\n\t\t\tmessage := fmt.Sprintf(\"invalid BoardID for block id %s\", block.ID)\n\t\t\ta.errorResponse(w, r, model.NewErrBadRequest(message))\n\t\t\treturn\n\t\t}\n\t}\n\n\tif hasContents {\n\t\tif !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardCards) {\n\t\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to make board changes\"))\n\t\t\treturn\n\t\t}\n\t}\n\tif hasComments {\n\t\tif !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionCommentBoardCards) {\n\t\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to post card comments\"))\n\t\t\treturn\n\t\t}\n\t}\n\n\tblocks = model.GenerateBlockIDs(blocks, a.logger)\n\n\tauditRec := a.makeAuditRecord(r, \"postBlocks\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelModify, auditRec)\n\tauditRec.AddMeta(\"disable_notify\", disableNotify)\n\n\tctx := r.Context()\n\tsession := ctx.Value(sessionContextKey).(*model.Session)\n\n\tmodel.StampModificationMetadata(userID, blocks, auditRec)\n\n\t// this query param exists when creating template from board, or board from template\n\tsourceBoardID := r.URL.Query().Get(\"sourceBoardID\")\n\tif sourceBoardID != \"\" {\n\t\tif updateFileIDsErr := a.app.CopyAndUpdateCardFiles(sourceBoardID, userID, blocks, false); updateFileIDsErr != nil {\n\t\t\ta.errorResponse(w, r, updateFileIDsErr)\n\t\t\treturn\n\t\t}\n\t}\n\n\tnewBlocks, err := a.app.InsertBlocksAndNotify(blocks, session.UserID, disableNotify)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\ta.logger.Debug(\"POST Blocks\",\n\t\tmlog.Int(\"block_count\", len(blocks)),\n\t\tmlog.Bool(\"disable_notify\", disableNotify),\n\t)\n\n\tjson, err := json.Marshal(newBlocks)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tjsonBytesResponse(w, http.StatusOK, json)\n\n\tauditRec.AddMeta(\"blockCount\", len(blocks))\n\tauditRec.Success()\n}\n\nfunc (a *API) handleDeleteBlock(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation DELETE /boards/{boardID}/blocks/{blockID} deleteBlock\n\t//\n\t// Deletes a block\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: boardID\n\t//   in: path\n\t//   description: Board ID\n\t//   required: true\n\t//   type: string\n\t// - name: blockID\n\t//   in: path\n\t//   description: ID of block to delete\n\t//   required: true\n\t//   type: string\n\t// - name: disable_notify\n\t//   in: query\n\t//   description: Disables notifications (for bulk deletion)\n\t//   required: false\n\t//   type: bool\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//   '404':\n\t//     description: block not found\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tuserID := getUserID(r)\n\tvars := mux.Vars(r)\n\tboardID := vars[\"boardID\"]\n\tblockID := vars[\"blockID\"]\n\n\tval := r.URL.Query().Get(\"disable_notify\")\n\tdisableNotify := val == True\n\n\tif !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardCards) {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to make board changes\"))\n\t\treturn\n\t}\n\n\tblock, err := a.app.GetBlockByID(blockID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\tif block.BoardID != boardID {\n\t\tmessage := fmt.Sprintf(\"block ID=%s on BoardID=%s\", block.ID, boardID)\n\t\ta.errorResponse(w, r, model.NewErrNotFound(message))\n\t\treturn\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"deleteBlock\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelModify, auditRec)\n\tauditRec.AddMeta(\"boardID\", boardID)\n\tauditRec.AddMeta(\"blockID\", blockID)\n\n\terr = a.app.DeleteBlockAndNotify(blockID, userID, disableNotify)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\ta.logger.Debug(\"DELETE Block\", mlog.String(\"boardID\", boardID), mlog.String(\"blockID\", blockID))\n\tjsonStringResponse(w, http.StatusOK, \"{}\")\n\n\tauditRec.Success()\n}\n\nfunc (a *API) handleUndeleteBlock(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation POST /boards/{boardID}/blocks/{blockID}/undelete undeleteBlock\n\t//\n\t// Undeletes a block\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: boardID\n\t//   in: path\n\t//   description: Board ID\n\t//   required: true\n\t//   type: string\n\t// - name: blockID\n\t//   in: path\n\t//   description: ID of block to undelete\n\t//   required: true\n\t//   type: string\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/BlockPatch\"\n\t//   '404':\n\t//     description: block not found\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tctx := r.Context()\n\tsession := ctx.Value(sessionContextKey).(*model.Session)\n\tuserID := session.UserID\n\n\tvars := mux.Vars(r)\n\tblockID := vars[\"blockID\"]\n\tboardID := vars[\"boardID\"]\n\n\tboard, err := a.app.GetBoard(boardID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tblock, err := a.app.GetLastBlockHistoryEntry(blockID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tif board.ID != block.BoardID {\n\t\tmessage := fmt.Sprintf(\"block ID=%s on BoardID=%s\", block.ID, board.ID)\n\t\ta.errorResponse(w, r, model.NewErrNotFound(message))\n\t\treturn\n\t}\n\n\tif !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardCards) {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to modify board members\"))\n\t\treturn\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"undeleteBlock\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelModify, auditRec)\n\tauditRec.AddMeta(\"blockID\", blockID)\n\n\tundeletedBlock, err := a.app.UndeleteBlock(blockID, userID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tundeletedBlockData, err := json.Marshal(undeletedBlock)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\ta.logger.Debug(\"UNDELETE Block\", mlog.String(\"blockID\", blockID))\n\tjsonBytesResponse(w, http.StatusOK, undeletedBlockData)\n\n\tauditRec.Success()\n}\n\nfunc (a *API) handlePatchBlock(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation PATCH /boards/{boardID}/blocks/{blockID} patchBlock\n\t//\n\t// Partially updates a block\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: boardID\n\t//   in: path\n\t//   description: Board ID\n\t//   required: true\n\t//   type: string\n\t// - name: blockID\n\t//   in: path\n\t//   description: ID of block to patch\n\t//   required: true\n\t//   type: string\n\t// - name: disable_notify\n\t//   in: query\n\t//   description: Disables notifications (for bulk patching)\n\t//   required: false\n\t//   type: bool\n\t// - name: Body\n\t//   in: body\n\t//   description: block patch to apply\n\t//   required: true\n\t//   schema:\n\t//     \"$ref\": \"#/definitions/BlockPatch\"\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//   '404':\n\t//     description: block not found\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tuserID := getUserID(r)\n\tvars := mux.Vars(r)\n\tboardID := vars[\"boardID\"]\n\tblockID := vars[\"blockID\"]\n\n\tval := r.URL.Query().Get(\"disable_notify\")\n\tdisableNotify := val == True\n\n\tif !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardCards) {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to make board changes\"))\n\t\treturn\n\t}\n\n\tblock, err := a.app.GetBlockByID(blockID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\tif block.BoardID != boardID {\n\t\tmessage := fmt.Sprintf(\"block ID=%s on BoardID=%s\", block.ID, boardID)\n\t\ta.errorResponse(w, r, model.NewErrNotFound(message))\n\t\treturn\n\t}\n\n\trequestBody, err := io.ReadAll(r.Body)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tvar patch *model.BlockPatch\n\terr = json.Unmarshal(requestBody, &patch)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"patchBlock\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelModify, auditRec)\n\tauditRec.AddMeta(\"boardID\", boardID)\n\tauditRec.AddMeta(\"blockID\", blockID)\n\n\tif _, err = a.app.PatchBlockAndNotify(blockID, patch, userID, disableNotify); err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\ta.logger.Debug(\"PATCH Block\", mlog.String(\"boardID\", boardID), mlog.String(\"blockID\", blockID))\n\tjsonStringResponse(w, http.StatusOK, \"{}\")\n\n\tauditRec.Success()\n}\n\nfunc (a *API) handlePatchBlocks(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation PATCH /boards/{boardID}/blocks/ patchBlocks\n\t//\n\t// Partially updates batch of blocks\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: boardID\n\t//   in: path\n\t//   description: Workspace ID\n\t//   required: true\n\t//   type: string\n\t// - name: disable_notify\n\t//   in: query\n\t//   description: Disables notifications (for bulk patching)\n\t//   required: false\n\t//   type: bool\n\t// - name: Body\n\t//   in: body\n\t//   description: block Ids and block patches to apply\n\t//   required: true\n\t//   schema:\n\t//     \"$ref\": \"#/definitions/BlockPatchBatch\"\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tctx := r.Context()\n\tsession := ctx.Value(sessionContextKey).(*model.Session)\n\tuserID := session.UserID\n\n\tvars := mux.Vars(r)\n\tteamID := vars[\"teamID\"]\n\n\tval := r.URL.Query().Get(\"disable_notify\")\n\tdisableNotify := val == True\n\n\trequestBody, err := io.ReadAll(r.Body)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tvar patches *model.BlockPatchBatch\n\terr = json.Unmarshal(requestBody, &patches)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"patchBlocks\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelModify, auditRec)\n\tfor i := range patches.BlockIDs {\n\t\tauditRec.AddMeta(\"block_\"+strconv.FormatInt(int64(i), 10), patches.BlockIDs[i])\n\t}\n\n\tfor _, blockID := range patches.BlockIDs {\n\t\tvar block *model.Block\n\t\tblock, err = a.app.GetBlockByID(blockID)\n\t\tif err != nil {\n\t\t\ta.errorResponse(w, r, model.NewErrForbidden(\"access denied to make board changes\"))\n\t\t\treturn\n\t\t}\n\t\tif !a.permissions.HasPermissionToBoard(userID, block.BoardID, model.PermissionManageBoardCards) {\n\t\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to make board changesa\"))\n\t\t\treturn\n\t\t}\n\t}\n\n\terr = a.app.PatchBlocksAndNotify(teamID, patches, userID, disableNotify)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\ta.logger.Debug(\"PATCH Blocks\", mlog.String(\"patches\", strconv.Itoa(len(patches.BlockIDs))))\n\tjsonStringResponse(w, http.StatusOK, \"{}\")\n\n\tauditRec.Success()\n}\n\nfunc (a *API) handleDuplicateBlock(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation POST /boards/{boardID}/blocks/{blockID}/duplicate duplicateBlock\n\t//\n\t// Returns the new created blocks\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: boardID\n\t//   in: path\n\t//   description: Board ID\n\t//   required: true\n\t//   type: string\n\t// - name: blockID\n\t//   in: path\n\t//   description: Block ID\n\t//   required: true\n\t//   type: string\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       type: array\n\t//       items:\n\t//         \"$ref\": \"#/definitions/Block\"\n\t//   '404':\n\t//     description: board or block not found\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tboardID := mux.Vars(r)[\"boardID\"]\n\tblockID := mux.Vars(r)[\"blockID\"]\n\tuserID := getUserID(r)\n\tquery := r.URL.Query()\n\tasTemplate := query.Get(\"asTemplate\")\n\n\tboard, err := a.app.GetBoard(boardID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tif userID == \"\" {\n\t\ta.errorResponse(w, r, model.NewErrUnauthorized(\"access denied to board\"))\n\t\treturn\n\t}\n\n\tblock, err := a.app.GetBlockByID(blockID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tif board.ID != block.BoardID {\n\t\tmessage := fmt.Sprintf(\"block ID=%s on BoardID=%s\", block.ID, board.ID)\n\t\ta.errorResponse(w, r, model.NewErrNotFound(message))\n\t\treturn\n\t}\n\n\tif block.Type == model.TypeComment {\n\t\tif !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionCommentBoardCards) {\n\t\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to comment on board cards\"))\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tif !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardCards) {\n\t\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to modify board cards\"))\n\t\t\treturn\n\t\t}\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"duplicateBlock\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelRead, auditRec)\n\tauditRec.AddMeta(\"boardID\", boardID)\n\tauditRec.AddMeta(\"blockID\", blockID)\n\n\ta.logger.Debug(\"DuplicateBlock\",\n\t\tmlog.String(\"boardID\", boardID),\n\t\tmlog.String(\"blockID\", blockID),\n\t)\n\n\tblocks, err := a.app.DuplicateBlock(boardID, blockID, userID, asTemplate == True)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tdata, err := json.Marshal(blocks)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\t// response\n\tjsonBytesResponse(w, http.StatusOK, data)\n\n\tauditRec.Success()\n}\n"
  },
  {
    "path": "server/api/boards.go",
    "content": "package api\n\nimport (\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\n\t\"github.com/gorilla/mux\"\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/audit\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nfunc (a *API) registerBoardsRoutes(r *mux.Router) {\n\tr.HandleFunc(\"/teams/{teamID}/boards\", a.sessionRequired(a.handleGetBoards)).Methods(\"GET\")\n\tr.HandleFunc(\"/boards\", a.sessionRequired(a.handleCreateBoard)).Methods(\"POST\")\n\tr.HandleFunc(\"/boards/{boardID}\", a.attachSession(a.handleGetBoard, false)).Methods(\"GET\")\n\tr.HandleFunc(\"/boards/{boardID}\", a.sessionRequired(a.handlePatchBoard)).Methods(\"PATCH\")\n\tr.HandleFunc(\"/boards/{boardID}\", a.sessionRequired(a.handleDeleteBoard)).Methods(\"DELETE\")\n\tr.HandleFunc(\"/boards/{boardID}/duplicate\", a.sessionRequired(a.handleDuplicateBoard)).Methods(\"POST\")\n\tr.HandleFunc(\"/boards/{boardID}/undelete\", a.sessionRequired(a.handleUndeleteBoard)).Methods(\"POST\")\n\tr.HandleFunc(\"/boards/{boardID}/metadata\", a.sessionRequired(a.handleGetBoardMetadata)).Methods(\"GET\")\n}\n\nfunc (a *API) handleGetBoards(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation GET /teams/{teamID}/boards getBoards\n\t//\n\t// Returns team boards\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: teamID\n\t//   in: path\n\t//   description: Team ID\n\t//   required: true\n\t//   type: string\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       type: array\n\t//       items:\n\t//         \"$ref\": \"#/definitions/Board\"\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tteamID := mux.Vars(r)[\"teamID\"]\n\tuserID := getUserID(r)\n\n\tif !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to team\"))\n\t\treturn\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"getBoards\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelRead, auditRec)\n\tauditRec.AddMeta(\"teamID\", teamID)\n\n\tisGuest, err := a.userIsGuest(userID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\t// retrieve boards list\n\tboards, err := a.app.GetBoardsForUserAndTeam(userID, teamID, !isGuest)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\ta.logger.Debug(\"GetBoards\",\n\t\tmlog.String(\"teamID\", teamID),\n\t\tmlog.Int(\"boardsCount\", len(boards)),\n\t)\n\n\tdata, err := json.Marshal(boards)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\t// response\n\tjsonBytesResponse(w, http.StatusOK, data)\n\n\tauditRec.AddMeta(\"boardsCount\", len(boards))\n\tauditRec.Success()\n}\n\nfunc (a *API) handleCreateBoard(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation POST /boards createBoard\n\t//\n\t// Creates a new board\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: Body\n\t//   in: body\n\t//   description: the board to create\n\t//   required: true\n\t//   schema:\n\t//     \"$ref\": \"#/definitions/Board\"\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       $ref: '#/definitions/Board'\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tuserID := getUserID(r)\n\n\trequestBody, err := io.ReadAll(r.Body)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tvar newBoard *model.Board\n\tif err = json.Unmarshal(requestBody, &newBoard); err != nil {\n\t\ta.errorResponse(w, r, model.NewErrBadRequest(err.Error()))\n\t\treturn\n\t}\n\n\tif newBoard.Type == model.BoardTypeOpen {\n\t\tif !a.permissions.HasPermissionToTeam(userID, newBoard.TeamID, model.PermissionCreatePublicChannel) {\n\t\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to create public boards\"))\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tif !a.permissions.HasPermissionToTeam(userID, newBoard.TeamID, model.PermissionCreatePrivateChannel) {\n\t\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to create private boards\"))\n\t\t\treturn\n\t\t}\n\t}\n\n\tisGuest, err := a.userIsGuest(userID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\tif isGuest {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to create board\"))\n\t\treturn\n\t}\n\n\tif err = newBoard.IsValid(); err != nil {\n\t\ta.errorResponse(w, r, model.NewErrBadRequest(err.Error()))\n\t\treturn\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"createBoard\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelModify, auditRec)\n\tauditRec.AddMeta(\"teamID\", newBoard.TeamID)\n\tauditRec.AddMeta(\"boardType\", newBoard.Type)\n\n\t// create board\n\tboard, err := a.app.CreateBoard(newBoard, userID, true)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\ta.logger.Debug(\"CreateBoard\",\n\t\tmlog.String(\"teamID\", board.TeamID),\n\t\tmlog.String(\"boardID\", board.ID),\n\t\tmlog.String(\"boardType\", string(board.Type)),\n\t\tmlog.String(\"userID\", userID),\n\t)\n\n\tdata, err := json.Marshal(board)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\t// response\n\tjsonBytesResponse(w, http.StatusOK, data)\n\n\tauditRec.Success()\n}\n\nfunc (a *API) handleGetBoard(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation GET /boards/{boardID} getBoard\n\t//\n\t// Returns a board\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: boardID\n\t//   in: path\n\t//   description: Board ID\n\t//   required: true\n\t//   type: string\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/Board\"\n\t//   '404':\n\t//     description: board not found\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tboardID := mux.Vars(r)[\"boardID\"]\n\tuserID := getUserID(r)\n\n\thasValidReadToken := a.hasValidReadTokenForBoard(r, boardID)\n\tif userID == \"\" && !hasValidReadToken {\n\t\ta.errorResponse(w, r, model.NewErrUnauthorized(\"access denied to board\"))\n\t\treturn\n\t}\n\n\tboard, err := a.app.GetBoard(boardID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tif !hasValidReadToken {\n\t\tif board.Type == model.BoardTypePrivate {\n\t\t\tif !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionViewBoard) {\n\t\t\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to board\"))\n\t\t\t\treturn\n\t\t\t}\n\t\t} else {\n\t\t\tvar isGuest bool\n\t\t\tisGuest, err = a.userIsGuest(userID)\n\t\t\tif err != nil {\n\t\t\t\ta.errorResponse(w, r, err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif isGuest {\n\t\t\t\tif !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionViewBoard) {\n\t\t\t\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to board\"))\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif !a.permissions.HasPermissionToTeam(userID, board.TeamID, model.PermissionViewTeam) {\n\t\t\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to board\"))\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"getBoard\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelRead, auditRec)\n\tauditRec.AddMeta(\"boardID\", boardID)\n\n\ta.logger.Debug(\"GetBoard\",\n\t\tmlog.String(\"boardID\", boardID),\n\t)\n\n\tdata, err := json.Marshal(board)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\t// response\n\tjsonBytesResponse(w, http.StatusOK, data)\n\n\tauditRec.Success()\n}\n\nfunc (a *API) handlePatchBoard(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation PATCH /boards/{boardID} patchBoard\n\t//\n\t// Partially updates a board\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: boardID\n\t//   in: path\n\t//   description: Board ID\n\t//   required: true\n\t//   type: string\n\t// - name: Body\n\t//   in: body\n\t//   description: board patch to apply\n\t//   required: true\n\t//   schema:\n\t//     \"$ref\": \"#/definitions/BoardPatch\"\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       $ref: '#/definitions/Board'\n\t//   '404':\n\t//     description: board not found\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tboardID := mux.Vars(r)[\"boardID\"]\n\tif _, err := a.app.GetBoard(boardID); err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tuserID := getUserID(r)\n\n\trequestBody, err := io.ReadAll(r.Body)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tvar patch *model.BoardPatch\n\tif err = json.Unmarshal(requestBody, &patch); err != nil {\n\t\ta.errorResponse(w, r, model.NewErrBadRequest(err.Error()))\n\t\treturn\n\t}\n\n\tif err = patch.IsValid(); err != nil {\n\t\ta.errorResponse(w, r, model.NewErrBadRequest(err.Error()))\n\t\treturn\n\t}\n\n\tif !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardProperties) {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to modifying board properties\"))\n\t\treturn\n\t}\n\n\tif patch.Type != nil || patch.MinimumRole != nil {\n\t\tif !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardType) {\n\t\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to modifying board type\"))\n\t\t\treturn\n\t\t}\n\t}\n\tif patch.ChannelID != nil {\n\t\tif !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardRoles) {\n\t\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to modifying board access\"))\n\t\t\treturn\n\t\t}\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"patchBoard\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelModify, auditRec)\n\tauditRec.AddMeta(\"boardID\", boardID)\n\tauditRec.AddMeta(\"userID\", userID)\n\n\t// patch board\n\tupdatedBoard, err := a.app.PatchBoard(patch, boardID, userID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\ta.logger.Debug(\"PatchBoard\",\n\t\tmlog.String(\"boardID\", boardID),\n\t\tmlog.String(\"userID\", userID),\n\t)\n\n\tdata, err := json.Marshal(updatedBoard)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\t// response\n\tjsonBytesResponse(w, http.StatusOK, data)\n\n\tauditRec.Success()\n}\n\nfunc (a *API) handleDeleteBoard(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation DELETE /boards/{boardID} deleteBoard\n\t//\n\t// Removes a board\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: boardID\n\t//   in: path\n\t//   description: Board ID\n\t//   required: true\n\t//   type: string\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//   '404':\n\t//     description: board not found\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tboardID := mux.Vars(r)[\"boardID\"]\n\tuserID := getUserID(r)\n\n\t// Check if board exists\n\tif _, err := a.app.GetBoard(boardID); err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tif !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionDeleteBoard) {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to delete board\"))\n\t\treturn\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"deleteBoard\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelModify, auditRec)\n\tauditRec.AddMeta(\"boardID\", boardID)\n\n\tif err := a.app.DeleteBoard(boardID, userID); err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\ta.logger.Debug(\"DELETE Board\", mlog.String(\"boardID\", boardID))\n\tjsonStringResponse(w, http.StatusOK, \"{}\")\n\n\tauditRec.Success()\n}\n\nfunc (a *API) handleDuplicateBoard(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation POST /boards/{boardID}/duplicate duplicateBoard\n\t//\n\t// Returns the new created board and all the blocks\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: boardID\n\t//   in: path\n\t//   description: Board ID\n\t//   required: true\n\t//   type: string\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       $ref: '#/definitions/BoardsAndBlocks'\n\t//   '404':\n\t//     description: board not found\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tboardID := mux.Vars(r)[\"boardID\"]\n\tuserID := getUserID(r)\n\tquery := r.URL.Query()\n\tasTemplate := query.Get(\"asTemplate\")\n\ttoTeam := query.Get(\"toTeam\")\n\n\tif userID == \"\" {\n\t\ta.errorResponse(w, r, model.NewErrUnauthorized(\"access denied to board\"))\n\t\treturn\n\t}\n\n\tboard, err := a.app.GetBoard(boardID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tif toTeam == \"\" {\n\t\ttoTeam = board.TeamID\n\t}\n\n\tif toTeam == \"\" && !a.permissions.HasPermissionToTeam(userID, board.TeamID, model.PermissionViewTeam) {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to team\"))\n\t\treturn\n\t}\n\n\tif toTeam != \"\" && !a.permissions.HasPermissionToTeam(userID, toTeam, model.PermissionViewTeam) {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to team\"))\n\t\treturn\n\t}\n\n\tif board.IsTemplate && board.Type == model.BoardTypeOpen {\n\t\tif board.TeamID != model.GlobalTeamID && !a.permissions.HasPermissionToTeam(userID, board.TeamID, model.PermissionViewTeam) {\n\t\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to board\"))\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tif !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionViewBoard) {\n\t\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to board\"))\n\t\t\treturn\n\t\t}\n\t}\n\n\tisGuest, err := a.userIsGuest(userID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\tif isGuest {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to create board\"))\n\t\treturn\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"duplicateBoard\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelRead, auditRec)\n\tauditRec.AddMeta(\"boardID\", boardID)\n\n\ta.logger.Debug(\"DuplicateBoard\",\n\t\tmlog.String(\"boardID\", boardID),\n\t)\n\n\tboardsAndBlocks, _, err := a.app.DuplicateBoard(boardID, userID, toTeam, asTemplate == True)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tdata, err := json.Marshal(boardsAndBlocks)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\t// response\n\tjsonBytesResponse(w, http.StatusOK, data)\n\n\tauditRec.Success()\n}\n\nfunc (a *API) handleUndeleteBoard(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation POST /boards/{boardID}/undelete undeleteBoard\n\t//\n\t// Undeletes a board\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: boardID\n\t//   in: path\n\t//   description: ID of board to undelete\n\t//   required: true\n\t//   type: string\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tctx := r.Context()\n\tsession := ctx.Value(sessionContextKey).(*model.Session)\n\tuserID := session.UserID\n\n\tvars := mux.Vars(r)\n\tboardID := vars[\"boardID\"]\n\n\tauditRec := a.makeAuditRecord(r, \"undeleteBoard\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelModify, auditRec)\n\tauditRec.AddMeta(\"boardID\", boardID)\n\n\tif !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionDeleteBoard) {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to undelete board\"))\n\t\treturn\n\t}\n\n\terr := a.app.UndeleteBoard(boardID, userID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\ta.logger.Debug(\"UNDELETE Board\", mlog.String(\"boardID\", boardID))\n\tjsonStringResponse(w, http.StatusOK, \"{}\")\n\n\tauditRec.Success()\n}\n\nfunc (a *API) handleGetBoardMetadata(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation GET /boards/{boardID}/metadata getBoardMetadata\n\t//\n\t// Returns a board's metadata\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: boardID\n\t//   in: path\n\t//   description: Board ID\n\t//   required: true\n\t//   type: string\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/BoardMetadata\"\n\t//   '404':\n\t//     description: board not found\n\t//   '501':\n\t//     description: required license not found\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tboardID := mux.Vars(r)[\"boardID\"]\n\tuserID := getUserID(r)\n\n\tboard, boardMetadata, err := a.app.GetBoardMetadata(boardID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\tif board == nil || boardMetadata == nil {\n\t\ta.errorResponse(w, r, model.NewErrNotFound(\"board metadata BoardID=\"+boardID))\n\t\treturn\n\t}\n\n\tif board.Type == model.BoardTypePrivate {\n\t\tif !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionViewBoard) {\n\t\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to board\"))\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tif !a.permissions.HasPermissionToTeam(userID, board.TeamID, model.PermissionViewTeam) {\n\t\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to board\"))\n\t\t\treturn\n\t\t}\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"getBoardMetadata\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelRead, auditRec)\n\tauditRec.AddMeta(\"boardID\", boardID)\n\n\tdata, err := json.Marshal(boardMetadata)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\t// response\n\tjsonBytesResponse(w, http.StatusOK, data)\n\n\tauditRec.Success()\n}\n"
  },
  {
    "path": "server/api/boards_and_blocks.go",
    "content": "package api\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\t\"github.com/gorilla/mux\"\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/audit\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nfunc (a *API) registerBoardsAndBlocksRoutes(r *mux.Router) {\n\t// BoardsAndBlocks APIs\n\tr.HandleFunc(\"/boards-and-blocks\", a.sessionRequired(a.handleCreateBoardsAndBlocks)).Methods(\"POST\")\n\tr.HandleFunc(\"/boards-and-blocks\", a.sessionRequired(a.handlePatchBoardsAndBlocks)).Methods(\"PATCH\")\n\tr.HandleFunc(\"/boards-and-blocks\", a.sessionRequired(a.handleDeleteBoardsAndBlocks)).Methods(\"DELETE\")\n}\n\nfunc (a *API) handleCreateBoardsAndBlocks(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation POST /boards-and-blocks insertBoardsAndBlocks\n\t//\n\t// Creates new boards and blocks\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: Body\n\t//   in: body\n\t//   description: the boards and blocks to create\n\t//   required: true\n\t//   schema:\n\t//     \"$ref\": \"#/definitions/BoardsAndBlocks\"\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       $ref: '#/definitions/BoardsAndBlocks'\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tuserID := getUserID(r)\n\n\trequestBody, err := io.ReadAll(r.Body)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tvar newBab *model.BoardsAndBlocks\n\tif err = json.Unmarshal(requestBody, &newBab); err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tif len(newBab.Boards) == 0 {\n\t\ta.errorResponse(w, r, model.NewErrBadRequest(\"at least one board is required\"))\n\t\treturn\n\t}\n\n\tteamID := \"\"\n\tboardIDs := map[string]bool{}\n\tfor _, board := range newBab.Boards {\n\t\tboardIDs[board.ID] = true\n\n\t\tif teamID == \"\" {\n\t\t\tteamID = board.TeamID\n\t\t\tcontinue\n\t\t}\n\n\t\tif teamID != board.TeamID {\n\t\t\ta.errorResponse(w, r, model.NewErrBadRequest(\"cannot create boards for multiple teams\"))\n\t\t\treturn\n\t\t}\n\n\t\tif board.ID == \"\" {\n\t\t\ta.errorResponse(w, r, model.NewErrBadRequest(\"boards need an ID to be referenced from the blocks\"))\n\t\t\treturn\n\t\t}\n\t}\n\n\tif !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to board template\"))\n\t\treturn\n\t}\n\n\tisGuest, err := a.userIsGuest(userID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\tif isGuest {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to create board\"))\n\t\treturn\n\t}\n\n\tfor _, block := range newBab.Blocks {\n\t\t// Error checking\n\t\tif len(block.Type) < 1 {\n\t\t\tmessage := fmt.Sprintf(\"missing type for block id %s\", block.ID)\n\t\t\ta.errorResponse(w, r, model.NewErrBadRequest(message))\n\t\t\treturn\n\t\t}\n\n\t\tif block.CreateAt < 1 {\n\t\t\tmessage := fmt.Sprintf(\"invalid createAt for block id %s\", block.ID)\n\t\t\ta.errorResponse(w, r, model.NewErrBadRequest(message))\n\t\t\treturn\n\t\t}\n\n\t\tif block.UpdateAt < 1 {\n\t\t\tmessage := fmt.Sprintf(\"invalid UpdateAt for block id %s\", block.ID)\n\t\t\ta.errorResponse(w, r, model.NewErrBadRequest(message))\n\t\t\treturn\n\t\t}\n\n\t\tif !boardIDs[block.BoardID] {\n\t\t\tmessage := fmt.Sprintf(\"invalid BoardID %s (not exists in the created boards)\", block.BoardID)\n\t\t\ta.errorResponse(w, r, model.NewErrBadRequest(message))\n\t\t\treturn\n\t\t}\n\t}\n\n\t// IDs of boards and blocks are used to confirm that they're\n\t// linked and then regenerated by the server\n\tnewBab, err = model.GenerateBoardsAndBlocksIDs(newBab, a.logger)\n\tif err != nil {\n\t\ta.errorResponse(w, r, model.NewErrBadRequest(err.Error()))\n\t\treturn\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"createBoardsAndBlocks\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelModify, auditRec)\n\tauditRec.AddMeta(\"teamID\", teamID)\n\tauditRec.AddMeta(\"userID\", userID)\n\tauditRec.AddMeta(\"boardsCount\", len(newBab.Boards))\n\tauditRec.AddMeta(\"blocksCount\", len(newBab.Blocks))\n\n\t// create boards and blocks\n\tbab, err := a.app.CreateBoardsAndBlocks(newBab, userID, true)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\ta.logger.Debug(\"CreateBoardsAndBlocks\",\n\t\tmlog.String(\"teamID\", teamID),\n\t\tmlog.String(\"userID\", userID),\n\t\tmlog.Int(\"boardCount\", len(bab.Boards)),\n\t\tmlog.Int(\"blockCount\", len(bab.Blocks)),\n\t)\n\n\tdata, err := json.Marshal(bab)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\t// response\n\tjsonBytesResponse(w, http.StatusOK, data)\n\n\tauditRec.Success()\n}\n\nfunc (a *API) handlePatchBoardsAndBlocks(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation PATCH /boards-and-blocks patchBoardsAndBlocks\n\t//\n\t// Patches a set of related boards and blocks\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: Body\n\t//   in: body\n\t//   description: the patches for the boards and blocks\n\t//   required: true\n\t//   schema:\n\t//     \"$ref\": \"#/definitions/PatchBoardsAndBlocks\"\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       $ref: '#/definitions/BoardsAndBlocks'\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tuserID := getUserID(r)\n\n\trequestBody, err := io.ReadAll(r.Body)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tvar pbab *model.PatchBoardsAndBlocks\n\tif err = json.Unmarshal(requestBody, &pbab); err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tif err = pbab.IsValid(); err != nil {\n\t\ta.errorResponse(w, r, model.NewErrBadRequest(err.Error()))\n\t\treturn\n\t}\n\n\tteamID := \"\"\n\tboardIDMap := map[string]bool{}\n\tfor i, boardID := range pbab.BoardIDs {\n\t\tboardIDMap[boardID] = true\n\t\tpatch := pbab.BoardPatches[i]\n\n\t\tif err = patch.IsValid(); err != nil {\n\t\t\ta.errorResponse(w, r, model.NewErrBadRequest(err.Error()))\n\t\t\treturn\n\t\t}\n\n\t\tif !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardProperties) {\n\t\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to modifying board properties\"))\n\t\t\treturn\n\t\t}\n\n\t\tif patch.Type != nil || patch.MinimumRole != nil {\n\t\t\tif !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardType) {\n\t\t\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to modifying board type\"))\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tboard, err2 := a.app.GetBoard(boardID)\n\t\tif err2 != nil {\n\t\t\ta.errorResponse(w, r, err2)\n\t\t\treturn\n\t\t}\n\n\t\tif teamID == \"\" {\n\t\t\tteamID = board.TeamID\n\t\t}\n\t\tif teamID != board.TeamID {\n\t\t\ta.errorResponse(w, r, model.NewErrBadRequest(\"mismatched team ID\"))\n\t\t\treturn\n\t\t}\n\t}\n\n\tfor _, blockID := range pbab.BlockIDs {\n\t\tblock, err2 := a.app.GetBlockByID(blockID)\n\t\tif err2 != nil {\n\t\t\ta.errorResponse(w, r, err2)\n\t\t\treturn\n\t\t}\n\n\t\tif _, ok := boardIDMap[block.BoardID]; !ok {\n\t\t\ta.errorResponse(w, r, model.NewErrBadRequest(\"missing BoardID=\"+block.BoardID))\n\t\t\treturn\n\t\t}\n\n\t\tif !a.permissions.HasPermissionToBoard(userID, block.BoardID, model.PermissionManageBoardCards) {\n\t\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to modifying cards\"))\n\t\t\treturn\n\t\t}\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"patchBoardsAndBlocks\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelModify, auditRec)\n\tauditRec.AddMeta(\"boardsCount\", len(pbab.BoardIDs))\n\tauditRec.AddMeta(\"blocksCount\", len(pbab.BlockIDs))\n\n\tbab, err := a.app.PatchBoardsAndBlocks(pbab, userID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\ta.logger.Debug(\"PATCH BoardsAndBlocks\",\n\t\tmlog.Int(\"boardsCount\", len(pbab.BoardIDs)),\n\t\tmlog.Int(\"blocksCount\", len(pbab.BlockIDs)),\n\t)\n\n\tdata, err := json.Marshal(bab)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\t// response\n\tjsonBytesResponse(w, http.StatusOK, data)\n\n\tauditRec.Success()\n}\n\nfunc (a *API) handleDeleteBoardsAndBlocks(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation DELETE /boards-and-blocks deleteBoardsAndBlocks\n\t//\n\t// Deletes boards and blocks\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: Body\n\t//   in: body\n\t//   description: the boards and blocks to delete\n\t//   required: true\n\t//   schema:\n\t//     \"$ref\": \"#/definitions/DeleteBoardsAndBlocks\"\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tuserID := getUserID(r)\n\n\trequestBody, err := io.ReadAll(r.Body)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tvar dbab *model.DeleteBoardsAndBlocks\n\tif err = json.Unmarshal(requestBody, &dbab); err != nil {\n\t\ta.errorResponse(w, r, model.NewErrBadRequest(err.Error()))\n\t\treturn\n\t}\n\n\t// user must have permission to delete all the boards, and that\n\t// would include the permission to manage their blocks\n\tteamID := \"\"\n\tboardIDMap := map[string]bool{}\n\tfor _, boardID := range dbab.Boards {\n\t\tboardIDMap[boardID] = true\n\t\t// all boards in the request should belong to the same team\n\t\tboard, err := a.app.GetBoard(boardID)\n\t\tif err != nil {\n\t\t\ta.errorResponse(w, r, err)\n\t\t\treturn\n\t\t}\n\t\tif teamID == \"\" {\n\t\t\tteamID = board.TeamID\n\t\t}\n\t\tif teamID != board.TeamID {\n\t\t\ta.errorResponse(w, r, model.NewErrBadRequest(\"all boards should belong to the same team\"))\n\t\t\treturn\n\t\t}\n\n\t\t// permission check\n\t\tif !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionDeleteBoard) {\n\t\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to delete board\"))\n\t\t\treturn\n\t\t}\n\t}\n\n\tfor _, blockID := range dbab.Blocks {\n\t\tblock, err2 := a.app.GetBlockByID(blockID)\n\t\tif err2 != nil {\n\t\t\ta.errorResponse(w, r, err2)\n\t\t\treturn\n\t\t}\n\n\t\tif _, ok := boardIDMap[block.BoardID]; !ok {\n\t\t\ta.errorResponse(w, r, model.NewErrBadRequest(\"missing BoardID=\"+block.BoardID))\n\t\t\treturn\n\t\t}\n\n\t\tif !a.permissions.HasPermissionToBoard(userID, block.BoardID, model.PermissionManageBoardCards) {\n\t\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to modifying cards\"))\n\t\t\treturn\n\t\t}\n\t}\n\n\tif err := dbab.IsValid(); err != nil {\n\t\ta.errorResponse(w, r, model.NewErrBadRequest(err.Error()))\n\t\treturn\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"deleteBoardsAndBlocks\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelModify, auditRec)\n\tauditRec.AddMeta(\"boardsCount\", len(dbab.Boards))\n\tauditRec.AddMeta(\"blocksCount\", len(dbab.Blocks))\n\n\tif err := a.app.DeleteBoardsAndBlocks(dbab, userID); err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\ta.logger.Debug(\"DELETE BoardsAndBlocks\",\n\t\tmlog.Int(\"boardsCount\", len(dbab.Boards)),\n\t\tmlog.Int(\"blocksCount\", len(dbab.Blocks)),\n\t)\n\n\t// response\n\tjsonStringResponse(w, http.StatusOK, \"{}\")\n\tauditRec.Success()\n}\n"
  },
  {
    "path": "server/api/cards.go",
    "content": "package api\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strconv\"\n\n\t\"github.com/gorilla/mux\"\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/audit\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nconst (\n\tdefaultPage    = \"0\"\n\tdefaultPerPage = \"100\"\n)\n\nfunc (a *API) registerCardsRoutes(r *mux.Router) {\n\t// Cards APIs\n\tr.HandleFunc(\"/boards/{boardID}/cards\", a.sessionRequired(a.handleCreateCard)).Methods(\"POST\")\n\tr.HandleFunc(\"/boards/{boardID}/cards\", a.sessionRequired(a.handleGetCards)).Methods(\"GET\")\n\tr.HandleFunc(\"/cards/{cardID}\", a.sessionRequired(a.handlePatchCard)).Methods(\"PATCH\")\n\tr.HandleFunc(\"/cards/{cardID}\", a.sessionRequired(a.handleGetCard)).Methods(\"GET\")\n}\n\nfunc (a *API) handleCreateCard(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation POST /boards/{boardID}/cards createCard\n\t//\n\t// Creates a new card for the specified board.\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: boardID\n\t//   in: path\n\t//   description: Board ID\n\t//   required: true\n\t//   type: string\n\t// - name: Body\n\t//   in: body\n\t//   description: the card to create\n\t//   required: true\n\t//   schema:\n\t//     \"$ref\": \"#/definitions/Card\"\n\t// - name: disable_notify\n\t//   in: query\n\t//   description: Disables notifications (for bulk data inserting)\n\t//   required: false\n\t//   type: bool\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       $ref: '#/definitions/Card'\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tuserID := getUserID(r)\n\tboardID := mux.Vars(r)[\"boardID\"]\n\n\tval := r.URL.Query().Get(\"disable_notify\")\n\tdisableNotify := val == True\n\n\trequestBody, err := io.ReadAll(r.Body)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tvar newCard *model.Card\n\tif err = json.Unmarshal(requestBody, &newCard); err != nil {\n\t\ta.errorResponse(w, r, model.NewErrBadRequest(err.Error()))\n\t\treturn\n\t}\n\n\tif !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardCards) {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to create card\"))\n\t\treturn\n\t}\n\n\tif newCard.BoardID != \"\" && newCard.BoardID != boardID {\n\t\ta.errorResponse(w, r, model.ErrBoardIDMismatch)\n\t\treturn\n\t}\n\n\tnewCard.PopulateWithBoardID(boardID)\n\tif err = newCard.CheckValid(); err != nil {\n\t\ta.errorResponse(w, r, model.NewErrBadRequest(err.Error()))\n\t\treturn\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"createCard\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelModify, auditRec)\n\tauditRec.AddMeta(\"boardID\", boardID)\n\n\t// create card\n\tcard, err := a.app.CreateCard(newCard, boardID, userID, disableNotify)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\ta.logger.Debug(\"CreateCard\",\n\t\tmlog.String(\"boardID\", boardID),\n\t\tmlog.String(\"cardID\", card.ID),\n\t\tmlog.String(\"userID\", userID),\n\t)\n\n\tdata, err := json.Marshal(card)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\t// response\n\tjsonBytesResponse(w, http.StatusOK, data)\n\n\tauditRec.Success()\n}\n\nfunc (a *API) handleGetCards(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation GET /boards/{boardID}/cards getCards\n\t//\n\t// Fetches cards for the specified board.\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: boardID\n\t//   in: path\n\t//   description: Board ID\n\t//   required: true\n\t//   type: string\n\t// - name: page\n\t//   in: query\n\t//   description: The page to select (default=0)\n\t//   required: false\n\t//   type: integer\n\t// - name: per_page\n\t//   in: query\n\t//   description: Number of cards to return per page(default=100)\n\t//   required: false\n\t//   type: integer\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       type: array\n\t//       items:\n\t//         \"$ref\": \"#/definitions/Card\"\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\tuserID := getUserID(r)\n\tboardID := mux.Vars(r)[\"boardID\"]\n\n\tquery := r.URL.Query()\n\tstrPage := query.Get(\"page\")\n\tstrPerPage := query.Get(\"per_page\")\n\n\tif !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionViewBoard) {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to fetch cards\"))\n\t\treturn\n\t}\n\n\tif strPage == \"\" {\n\t\tstrPage = defaultPage\n\t}\n\tif strPerPage == \"\" {\n\t\tstrPerPage = defaultPerPage\n\t}\n\n\tpage, err := strconv.Atoi(strPage)\n\tif err != nil {\n\t\tmessage := fmt.Sprintf(\"invalid `page` parameter: %s\", err)\n\t\ta.errorResponse(w, r, model.NewErrBadRequest(message))\n\t}\n\n\tperPage, err := strconv.Atoi(strPerPage)\n\tif err != nil {\n\t\tmessage := fmt.Sprintf(\"invalid `per_page` parameter: %s\", err)\n\t\ta.errorResponse(w, r, model.NewErrBadRequest(message))\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"getCards\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelRead, auditRec)\n\tauditRec.AddMeta(\"boardID\", boardID)\n\tauditRec.AddMeta(\"page\", page)\n\tauditRec.AddMeta(\"per_page\", perPage)\n\n\tcards, err := a.app.GetCardsForBoard(boardID, page, perPage)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\ta.logger.Debug(\"GetCards\",\n\t\tmlog.String(\"boardID\", boardID),\n\t\tmlog.String(\"userID\", userID),\n\t\tmlog.Int(\"page\", page),\n\t\tmlog.Int(\"per_page\", perPage),\n\t\tmlog.Int(\"count\", len(cards)),\n\t)\n\n\tdata, err := json.Marshal(cards)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\t// response\n\tjsonBytesResponse(w, http.StatusOK, data)\n\n\tauditRec.Success()\n}\n\nfunc (a *API) handlePatchCard(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation PATCH /cards/{cardID}/cards patchCard\n\t//\n\t// Patches the specified card.\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: cardID\n\t//   in: path\n\t//   description: Card ID\n\t//   required: true\n\t//   type: string\n\t// - name: Body\n\t//   in: body\n\t//   description: the card patch\n\t//   required: true\n\t//   schema:\n\t//     \"$ref\": \"#/definitions/CardPatch\"\n\t// - name: disable_notify\n\t//   in: query\n\t//   description: Disables notifications (for bulk data patching)\n\t//   required: false\n\t//   type: bool\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       $ref: '#/definitions/Card'\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tuserID := getUserID(r)\n\tcardID := mux.Vars(r)[\"cardID\"]\n\n\tval := r.URL.Query().Get(\"disable_notify\")\n\tdisableNotify := val == True\n\n\trequestBody, err := io.ReadAll(r.Body)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tcard, err := a.app.GetCardByID(cardID)\n\tif err != nil {\n\t\tmessage := fmt.Sprintf(\"could not fetch card %s: %s\", cardID, err)\n\t\ta.errorResponse(w, r, model.NewErrBadRequest(message))\n\t\treturn\n\t}\n\n\tif !a.permissions.HasPermissionToBoard(userID, card.BoardID, model.PermissionManageBoardCards) {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to patch card\"))\n\t\treturn\n\t}\n\n\tvar patch *model.CardPatch\n\tif err = json.Unmarshal(requestBody, &patch); err != nil {\n\t\ta.errorResponse(w, r, model.NewErrBadRequest(err.Error()))\n\t\treturn\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"patchCard\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelModify, auditRec)\n\tauditRec.AddMeta(\"boardID\", card.BoardID)\n\tauditRec.AddMeta(\"cardID\", card.ID)\n\n\t// patch card\n\tcardPatched, err := a.app.PatchCard(patch, card.ID, userID, disableNotify)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\ta.logger.Debug(\"PatchCard\",\n\t\tmlog.String(\"boardID\", cardPatched.BoardID),\n\t\tmlog.String(\"cardID\", cardPatched.ID),\n\t\tmlog.String(\"userID\", userID),\n\t)\n\n\tdata, err := json.Marshal(cardPatched)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\t// response\n\tjsonBytesResponse(w, http.StatusOK, data)\n\n\tauditRec.Success()\n}\n\nfunc (a *API) handleGetCard(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation GET /cards/{cardID} getCard\n\t//\n\t// Fetches the specified card.\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: cardID\n\t//   in: path\n\t//   description: Card ID\n\t//   required: true\n\t//   type: string\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       $ref: '#/definitions/Card'\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tuserID := getUserID(r)\n\tcardID := mux.Vars(r)[\"cardID\"]\n\n\tcard, err := a.app.GetCardByID(cardID)\n\tif err != nil {\n\t\tmessage := fmt.Sprintf(\"could not fetch card %s: %s\", cardID, err)\n\t\ta.errorResponse(w, r, model.NewErrBadRequest(message))\n\t\treturn\n\t}\n\n\tif !a.permissions.HasPermissionToBoard(userID, card.BoardID, model.PermissionManageBoardCards) {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to fetch card\"))\n\t\treturn\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"getCard\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelRead, auditRec)\n\tauditRec.AddMeta(\"boardID\", card.BoardID)\n\tauditRec.AddMeta(\"cardID\", card.ID)\n\n\ta.logger.Debug(\"GetCard\",\n\t\tmlog.String(\"boardID\", card.BoardID),\n\t\tmlog.String(\"cardID\", card.ID),\n\t\tmlog.String(\"userID\", userID),\n\t)\n\n\tdata, err := json.Marshal(card)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\t// response\n\tjsonBytesResponse(w, http.StatusOK, data)\n\n\tauditRec.Success()\n}\n"
  },
  {
    "path": "server/api/categories.go",
    "content": "package api\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\t\"github.com/gorilla/mux\"\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/audit\"\n)\n\nfunc (a *API) registerCategoriesRoutes(r *mux.Router) {\n\t// Category APIs\n\tr.HandleFunc(\"/teams/{teamID}/categories\", a.sessionRequired(a.handleCreateCategory)).Methods(http.MethodPost)\n\tr.HandleFunc(\"/teams/{teamID}/categories/reorder\", a.sessionRequired(a.handleReorderCategories)).Methods(http.MethodPut)\n\tr.HandleFunc(\"/teams/{teamID}/categories/{categoryID}\", a.sessionRequired(a.handleUpdateCategory)).Methods(http.MethodPut)\n\tr.HandleFunc(\"/teams/{teamID}/categories/{categoryID}\", a.sessionRequired(a.handleDeleteCategory)).Methods(http.MethodDelete)\n\tr.HandleFunc(\"/teams/{teamID}/categories\", a.sessionRequired(a.handleGetUserCategoryBoards)).Methods(http.MethodGet)\n\tr.HandleFunc(\"/teams/{teamID}/categories/{categoryID}/boards/reorder\", a.sessionRequired(a.handleReorderCategoryBoards)).Methods(http.MethodPut)\n\tr.HandleFunc(\"/teams/{teamID}/categories/{categoryID}/boards/{boardID}\", a.sessionRequired(a.handleUpdateCategoryBoard)).Methods(http.MethodPost)\n\tr.HandleFunc(\"/teams/{teamID}/categories/{categoryID}/boards/{boardID}/hide\", a.sessionRequired(a.handleHideBoard)).Methods(http.MethodPut)\n\tr.HandleFunc(\"/teams/{teamID}/categories/{categoryID}/boards/{boardID}/unhide\", a.sessionRequired(a.handleUnhideBoard)).Methods(http.MethodPut)\n}\n\nfunc (a *API) handleCreateCategory(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation POST /teams/{teamID}/categories createCategory\n\t//\n\t// Create a category for boards\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: teamID\n\t//   in: path\n\t//   description: Team ID\n\t//   required: true\n\t//   type: string\n\t// - name: Body\n\t//   in: body\n\t//   description: category to create\n\t//   required: true\n\t//   schema:\n\t//     \"$ref\": \"#/definitions/Category\"\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/Category\"\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\trequestBody, err := io.ReadAll(r.Body)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tvar category model.Category\n\n\terr = json.Unmarshal(requestBody, &category)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"createCategory\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelModify, auditRec)\n\n\tctx := r.Context()\n\tsession := ctx.Value(sessionContextKey).(*model.Session)\n\n\t// user can only create category for themselves\n\tif category.UserID != session.UserID {\n\t\tmessage := fmt.Sprintf(\"userID %s and category userID %s mismatch\", session.UserID, category.UserID)\n\t\ta.errorResponse(w, r, model.NewErrBadRequest(message))\n\t\treturn\n\t}\n\n\tvars := mux.Vars(r)\n\tteamID := vars[\"teamID\"]\n\n\tif category.TeamID != teamID {\n\t\ta.errorResponse(w, r, model.NewErrBadRequest(\"teamID mismatch\"))\n\t\treturn\n\t}\n\n\tif !a.permissions.HasPermissionToTeam(session.UserID, teamID, model.PermissionViewTeam) {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to team\"))\n\t\treturn\n\t}\n\n\tcreatedCategory, err := a.app.CreateCategory(&category)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tdata, err := json.Marshal(createdCategory)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tjsonBytesResponse(w, http.StatusOK, data)\n\tauditRec.AddMeta(\"categoryID\", createdCategory.ID)\n\tauditRec.Success()\n}\n\nfunc (a *API) handleUpdateCategory(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation PUT /teams/{teamID}/categories/{categoryID} updateCategory\n\t//\n\t// Create a category for boards\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: teamID\n\t//   in: path\n\t//   description: Team ID\n\t//   required: true\n\t//   type: string\n\t// - name: categoryID\n\t//   in: path\n\t//   description: Category ID\n\t//   required: true\n\t//   type: string\n\t// - name: Body\n\t//   in: body\n\t//   description: category to update\n\t//   required: true\n\t//   schema:\n\t//     \"$ref\": \"#/definitions/Category\"\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/Category\"\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tvars := mux.Vars(r)\n\tcategoryID := vars[\"categoryID\"]\n\n\trequestBody, err := io.ReadAll(r.Body)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tvar category model.Category\n\terr = json.Unmarshal(requestBody, &category)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"updateCategory\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelModify, auditRec)\n\n\tif categoryID != category.ID {\n\t\ta.errorResponse(w, r, model.NewErrBadRequest(\"categoryID mismatch in patch and body\"))\n\t\treturn\n\t}\n\n\tctx := r.Context()\n\tsession := ctx.Value(sessionContextKey).(*model.Session)\n\n\t// user can only update category for themselves\n\tif category.UserID != session.UserID {\n\t\ta.errorResponse(w, r, model.NewErrBadRequest(\"user ID mismatch in session and category\"))\n\t\treturn\n\t}\n\n\tteamID := vars[\"teamID\"]\n\tif category.TeamID != teamID {\n\t\ta.errorResponse(w, r, model.NewErrBadRequest(\"teamID mismatch\"))\n\t\treturn\n\t}\n\n\tif !a.permissions.HasPermissionToTeam(session.UserID, teamID, model.PermissionViewTeam) {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to team\"))\n\t\treturn\n\t}\n\n\tupdatedCategory, err := a.app.UpdateCategory(&category)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tdata, err := json.Marshal(updatedCategory)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tjsonBytesResponse(w, http.StatusOK, data)\n\tauditRec.Success()\n}\n\nfunc (a *API) handleDeleteCategory(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation DELETE /teams/{teamID}/categories/{categoryID} deleteCategory\n\t//\n\t// Delete a category\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: teamID\n\t//   in: path\n\t//   description: Team ID\n\t//   required: true\n\t//   type: string\n\t// - name: categoryID\n\t//   in: path\n\t//   description: Category ID\n\t//   required: true\n\t//   type: string\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tctx := r.Context()\n\tsession := ctx.Value(sessionContextKey).(*model.Session)\n\tvars := mux.Vars(r)\n\n\tuserID := session.UserID\n\tteamID := vars[\"teamID\"]\n\tcategoryID := vars[\"categoryID\"]\n\n\tauditRec := a.makeAuditRecord(r, \"deleteCategory\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelModify, auditRec)\n\n\tif !a.permissions.HasPermissionToTeam(session.UserID, teamID, model.PermissionViewTeam) {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to team\"))\n\t\treturn\n\t}\n\n\tdeletedCategory, err := a.app.DeleteCategory(categoryID, userID, teamID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tdata, err := json.Marshal(deletedCategory)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tjsonBytesResponse(w, http.StatusOK, data)\n\tauditRec.Success()\n}\n\nfunc (a *API) handleGetUserCategoryBoards(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation GET /teams/{teamID}/categories getUserCategoryBoards\n\t//\n\t// Gets the user's board categories\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: teamID\n\t//   in: path\n\t//   description: Team ID\n\t//   required: true\n\t//   type: string\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       items:\n\t//         \"$ref\": \"#/definitions/CategoryBoards\"\n\t//       type: array\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tctx := r.Context()\n\tsession := ctx.Value(sessionContextKey).(*model.Session)\n\tuserID := session.UserID\n\n\tvars := mux.Vars(r)\n\tteamID := vars[\"teamID\"]\n\n\tauditRec := a.makeAuditRecord(r, \"getUserCategoryBoards\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelModify, auditRec)\n\n\tif !a.permissions.HasPermissionToTeam(session.UserID, teamID, model.PermissionViewTeam) {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to team\"))\n\t\treturn\n\t}\n\n\tcategoryBlocks, err := a.app.GetUserCategoryBoards(userID, teamID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tdata, err := json.Marshal(categoryBlocks)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tjsonBytesResponse(w, http.StatusOK, data)\n\tauditRec.Success()\n}\n\nfunc (a *API) handleUpdateCategoryBoard(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation POST /teams/{teamID}/categories/{categoryID}/boards/{boardID} updateCategoryBoard\n\t//\n\t// Set the category of a board\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: teamID\n\t//   in: path\n\t//   description: Team ID\n\t//   required: true\n\t//   type: string\n\t// - name: categoryID\n\t//   in: path\n\t//   description: Category ID\n\t//   required: true\n\t//   type: string\n\t// - name: boardID\n\t//   in: path\n\t//   description: Board ID\n\t//   required: true\n\t//   type: string\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tauditRec := a.makeAuditRecord(r, \"updateCategoryBoard\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelModify, auditRec)\n\n\tvars := mux.Vars(r)\n\tcategoryID := vars[\"categoryID\"]\n\tboardID := vars[\"boardID\"]\n\tteamID := vars[\"teamID\"]\n\n\tctx := r.Context()\n\tsession := ctx.Value(sessionContextKey).(*model.Session)\n\tuserID := session.UserID\n\n\tif !a.permissions.HasPermissionToTeam(session.UserID, teamID, model.PermissionViewTeam) {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to team\"))\n\t\treturn\n\t}\n\n\t// TODO: Check the category and the team matches\n\terr := a.app.AddUpdateUserCategoryBoard(teamID, userID, categoryID, []string{boardID})\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tjsonBytesResponse(w, http.StatusOK, []byte(\"ok\"))\n\tauditRec.Success()\n}\n\nfunc (a *API) handleReorderCategories(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation PUT /teams/{teamID}/categories/reorder handleReorderCategories\n\t//\n\t// Updated sidebar category order\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: teamID\n\t//   in: path\n\t//   description: Team ID\n\t//   required: true\n\t//   type: string\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tvars := mux.Vars(r)\n\tteamID := vars[\"teamID\"]\n\n\tctx := r.Context()\n\tsession := ctx.Value(sessionContextKey).(*model.Session)\n\tuserID := session.UserID\n\n\tif !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to category\"))\n\t\treturn\n\t}\n\n\trequestBody, err := io.ReadAll(r.Body)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tvar newCategoryOrder []string\n\n\terr = json.Unmarshal(requestBody, &newCategoryOrder)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"reorderCategories\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelModify, auditRec)\n\n\tauditRec.AddMeta(\"TeamID\", teamID)\n\tauditRec.AddMeta(\"CategoryCount\", len(newCategoryOrder))\n\n\tupdatedCategoryOrder, err := a.app.ReorderCategories(userID, teamID, newCategoryOrder)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tdata, err := json.Marshal(updatedCategoryOrder)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tjsonBytesResponse(w, http.StatusOK, data)\n\tauditRec.Success()\n}\n\nfunc (a *API) handleReorderCategoryBoards(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation PUT /teams/{teamID}/categories/{categoryID}/boards/reorder handleReorderCategoryBoards\n\t//\n\t// Updates order of boards inside a sidebar category\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: teamID\n\t//   in: path\n\t//   description: Team ID\n\t//   required: true\n\t//   type: string\n\t// - name: categoryID\n\t//   in: path\n\t//   description: Category ID\n\t//   required: true\n\t//   type: string\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tvars := mux.Vars(r)\n\tteamID := vars[\"teamID\"]\n\tcategoryID := vars[\"categoryID\"]\n\n\tctx := r.Context()\n\tsession := ctx.Value(sessionContextKey).(*model.Session)\n\tuserID := session.UserID\n\n\tif !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to category\"))\n\t\treturn\n\t}\n\n\tcategory, err := a.app.GetCategory(categoryID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tif category.UserID != userID {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to category\"))\n\t\treturn\n\t}\n\n\trequestBody, err := io.ReadAll(r.Body)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tvar newBoardsOrder []string\n\terr = json.Unmarshal(requestBody, &newBoardsOrder)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"reorderCategoryBoards\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelModify, auditRec)\n\n\tupdatedBoardsOrder, err := a.app.ReorderCategoryBoards(userID, teamID, categoryID, newBoardsOrder)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tdata, err := json.Marshal(updatedBoardsOrder)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tjsonBytesResponse(w, http.StatusOK, data)\n\tauditRec.Success()\n}\n\nfunc (a *API) handleHideBoard(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation POST /teams/{teamID}/categories/{categoryID}/boards/{boardID}/hide hideBoard\n\t//\n\t// Hide the specified board for the user\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: teamID\n\t//   in: path\n\t//   description: Team ID\n\t//   required: true\n\t//   type: string\n\t// - name: categoryID\n\t//   in: path\n\t//   description: Category ID to which the board to be hidden belongs to\n\t//   required: true\n\t//   type: string\n\t// - name: boardID\n\t//   in: path\n\t//   description: ID of board to be hidden\n\t//   required: true\n\t//   type: string\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/Category\"\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tuserID := getUserID(r)\n\tvars := mux.Vars(r)\n\tteamID := vars[\"teamID\"]\n\tboardID := vars[\"boardID\"]\n\tcategoryID := vars[\"categoryID\"]\n\n\tif !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to category\"))\n\t\treturn\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"hideBoard\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelModify, auditRec)\n\tauditRec.AddMeta(\"board_id\", boardID)\n\tauditRec.AddMeta(\"team_id\", teamID)\n\tauditRec.AddMeta(\"category_id\", categoryID)\n\n\tif err := a.app.SetBoardVisibility(teamID, userID, categoryID, boardID, false); err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tjsonStringResponse(w, http.StatusOK, \"{}\")\n\tauditRec.Success()\n}\n\nfunc (a *API) handleUnhideBoard(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation POST /teams/{teamID}/categories/{categoryID}/boards/{boardID}/hide unhideBoard\n\t//\n\t// Unhides the specified board for the user\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: teamID\n\t//   in: path\n\t//   description: Team ID\n\t//   required: true\n\t//   type: string\n\t// - name: categoryID\n\t//   in: path\n\t//   description: Category ID to which the board to be unhidden belongs to\n\t//   required: true\n\t//   type: string\n\t// - name: boardID\n\t//   in: path\n\t//   description: ID of board to be unhidden\n\t//   required: true\n\t//   type: string\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/Category\"\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tuserID := getUserID(r)\n\tvars := mux.Vars(r)\n\tteamID := vars[\"teamID\"]\n\tboardID := vars[\"boardID\"]\n\tcategoryID := vars[\"categoryID\"]\n\n\tif !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to category\"))\n\t\treturn\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"unhideBoard\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelModify, auditRec)\n\tauditRec.AddMeta(\"boardID\", boardID)\n\n\tif err := a.app.SetBoardVisibility(teamID, userID, categoryID, boardID, true); err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tjsonStringResponse(w, http.StatusOK, \"{}\")\n\tauditRec.Success()\n}\n"
  },
  {
    "path": "server/api/channels.go",
    "content": "package api\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/gorilla/mux\"\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/audit\"\n\n\tmm_model \"github.com/mattermost/mattermost/server/public/model\"\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nfunc (a *API) registerChannelsRoutes(r *mux.Router) {\n\tr.HandleFunc(\"/teams/{teamID}/channels/{channelID}\", a.sessionRequired(a.handleGetChannel)).Methods(\"GET\")\n}\n\nfunc (a *API) handleGetChannel(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation GET /teams/{teamID}/channels/{channelID} getChannel\n\t//\n\t// Returns the requested channel\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: teamID\n\t//   in: path\n\t//   description: Team ID\n\t//   required: true\n\t//   type: string\n\t// - name: channelID\n\t//   in: path\n\t//   description: Channel ID\n\t//   required: true\n\t//   type: string\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       type: array\n\t//       items:\n\t//         \"$ref\": \"#/definitions/Channel\"\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tif !a.MattermostAuth {\n\t\ta.errorResponse(w, r, model.NewErrNotImplemented(\"not permitted in standalone mode\"))\n\t\treturn\n\t}\n\n\tteamID := mux.Vars(r)[\"teamID\"]\n\tchannelID := mux.Vars(r)[\"channelID\"]\n\tuserID := getUserID(r)\n\n\tif !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to team\"))\n\t\treturn\n\t}\n\n\tif !a.permissions.HasPermissionToChannel(userID, channelID, model.PermissionReadChannel) {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to channel\"))\n\t\treturn\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"getChannel\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelRead, auditRec)\n\tauditRec.AddMeta(\"teamID\", teamID)\n\tauditRec.AddMeta(\"channelID\", teamID)\n\n\tchannel, err := a.app.GetChannel(teamID, channelID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\ta.logger.Debug(\"GetChannel\",\n\t\tmlog.String(\"teamID\", teamID),\n\t\tmlog.String(\"channelID\", channelID),\n\t)\n\n\tif channel.TeamId != teamID {\n\t\tif channel.Type != mm_model.ChannelTypeDirect && channel.Type != mm_model.ChannelTypeGroup {\n\t\t\tmessage := fmt.Sprintf(\"channel ID=%s on TeamID=%s\", channel.Id, teamID)\n\t\t\ta.errorResponse(w, r, model.NewErrNotFound(message))\n\t\t\treturn\n\t\t}\n\t}\n\n\tdata, err := json.Marshal(channel)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\t// response\n\tjsonBytesResponse(w, http.StatusOK, data)\n\n\tauditRec.Success()\n}\n"
  },
  {
    "path": "server/api/compliance.go",
    "content": "package api\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strconv\"\n\n\t\"github.com/gorilla/mux\"\n\t\"github.com/mattermost/focalboard/server/model\"\n\n\tmm_model \"github.com/mattermost/mattermost/server/public/model\"\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nconst (\n\tcomplianceDefaultPage    = \"0\"\n\tcomplianceDefaultPerPage = \"60\"\n)\n\nfunc (a *API) registerComplianceRoutes(r *mux.Router) {\n\t// Compliance APIs\n\tr.HandleFunc(\"/admin/boards\", a.sessionRequired(a.handleGetBoardsForCompliance)).Methods(\"GET\")\n\tr.HandleFunc(\"/admin/boards_history\", a.sessionRequired(a.handleGetBoardsComplianceHistory)).Methods(\"GET\")\n\tr.HandleFunc(\"/admin/blocks_history\", a.sessionRequired(a.handleGetBlocksComplianceHistory)).Methods(\"GET\")\n}\n\nfunc (a *API) handleGetBoardsForCompliance(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation GET /admin/boards getBoardsForCompliance\n\t//\n\t// Returns boards for a specific team, or all teams.\n\t//\n\t// Requires a license that includes Compliance feature. Caller must have `manage_system` permissions.\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: team_id\n\t//   in: query\n\t//   description: Team ID. If empty then boards across all teams are included.\n\t//   required: false\n\t//   type: string\n\t// - name: page\n\t//   in: query\n\t//   description: The page to select (default=0)\n\t//   required: false\n\t//   type: integer\n\t// - name: per_page\n\t//   in: query\n\t//   description: Number of boards to return per page(default=60)\n\t//   required: false\n\t//   type: integer\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       type: object\n\t//       items:\n\t//         \"$ref\": \"#/definitions/BoardsComplianceResponse\"\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tquery := r.URL.Query()\n\tteamID := query.Get(\"team_id\")\n\tstrPage := query.Get(\"page\")\n\tstrPerPage := query.Get(\"per_page\")\n\n\t// check for permission `manage_system`\n\tuserID := getUserID(r)\n\tif !a.permissions.HasPermissionTo(userID, mm_model.PermissionManageSystem) {\n\t\ta.errorResponse(w, r, model.NewErrUnauthorized(\"access denied Compliance Export getAllBoards\"))\n\t\treturn\n\t}\n\n\t// check for valid license feature: compliance\n\tlicense := a.app.GetLicense()\n\tif license == nil || !(*license.Features.Compliance) {\n\t\ta.errorResponse(w, r, model.NewErrNotImplemented(\"insufficient license Compliance Export getAllBoards\"))\n\t\treturn\n\t}\n\n\t// check for valid team if specified\n\tif teamID != \"\" {\n\t\t_, err := a.app.GetTeam(teamID)\n\t\tif err != nil {\n\t\t\ta.errorResponse(w, r, model.NewErrBadRequest(\"invalid team id: \"+teamID))\n\t\t\treturn\n\t\t}\n\t}\n\n\tif strPage == \"\" {\n\t\tstrPage = complianceDefaultPage\n\t}\n\tif strPerPage == \"\" {\n\t\tstrPerPage = complianceDefaultPerPage\n\t}\n\tpage, err := strconv.Atoi(strPage)\n\tif err != nil {\n\t\tmessage := fmt.Sprintf(\"invalid `page` parameter: %s\", err)\n\t\ta.errorResponse(w, r, model.NewErrBadRequest(message))\n\t\treturn\n\t}\n\tperPage, err := strconv.Atoi(strPerPage)\n\tif err != nil {\n\t\tmessage := fmt.Sprintf(\"invalid `per_page` parameter: %s\", err)\n\t\ta.errorResponse(w, r, model.NewErrBadRequest(message))\n\t\treturn\n\t}\n\n\topts := model.QueryBoardsForComplianceOptions{\n\t\tTeamID:  teamID,\n\t\tPage:    page,\n\t\tPerPage: perPage,\n\t}\n\n\tboards, more, err := a.app.GetBoardsForCompliance(opts)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\ta.logger.Debug(\"GetBoardsForCompliance\",\n\t\tmlog.String(\"teamID\", teamID),\n\t\tmlog.Int(\"boardsCount\", len(boards)),\n\t\tmlog.Bool(\"hasNext\", more),\n\t)\n\n\tresponse := model.BoardsComplianceResponse{\n\t\tHasNext: more,\n\t\tResults: boards,\n\t}\n\tdata, err := json.Marshal(response)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tjsonBytesResponse(w, http.StatusOK, data)\n}\n\nfunc (a *API) handleGetBoardsComplianceHistory(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation GET /admin/boards_history getBoardsComplianceHistory\n\t//\n\t// Returns boards histories for a specific team, or all teams.\n\t//\n\t// Requires a license that includes Compliance feature. Caller must have `manage_system` permissions.\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: modified_since\n\t//   in: query\n\t//   description: Filters for boards modified since timestamp; Unix time in milliseconds\n\t//   required: true\n\t//   type: integer\n\t// - name: include_deleted\n\t//   in: query\n\t//   description: When true then deleted boards are included. Default=false\n\t//   required: false\n\t//   type: boolean\n\t// - name: team_id\n\t//   in: query\n\t//   description: Team ID. If empty then board histories across all teams are included\n\t//   required: false\n\t//   type: string\n\t// - name: page\n\t//   in: query\n\t//   description: The page to select (default=0)\n\t//   required: false\n\t//   type: integer\n\t// - name: per_page\n\t//   in: query\n\t//   description: Number of board histories to return per page (default=60)\n\t//   required: false\n\t//   type: integer\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       type: object\n\t//       items:\n\t//         \"$ref\": \"#/definitions/BoardsComplianceHistoryResponse\"\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tquery := r.URL.Query()\n\tstrModifiedSince := query.Get(\"modified_since\") // required, everything else optional\n\tincludeDeleted := query.Get(\"include_deleted\") == \"true\"\n\tstrPage := query.Get(\"page\")\n\tstrPerPage := query.Get(\"per_page\")\n\tteamID := query.Get(\"team_id\")\n\n\tif strModifiedSince == \"\" {\n\t\ta.errorResponse(w, r, model.NewErrBadRequest(\"`modified_since` parameter required\"))\n\t\treturn\n\t}\n\n\t// check for permission `manage_system`\n\tuserID := getUserID(r)\n\tif !a.permissions.HasPermissionTo(userID, mm_model.PermissionManageSystem) {\n\t\ta.errorResponse(w, r, model.NewErrUnauthorized(\"access denied Compliance Export getBoardsHistory\"))\n\t\treturn\n\t}\n\n\t// check for valid license feature: compliance\n\tlicense := a.app.GetLicense()\n\tif license == nil || !(*license.Features.Compliance) {\n\t\ta.errorResponse(w, r, model.NewErrNotImplemented(\"insufficient license Compliance Export getBoardsHistory\"))\n\t\treturn\n\t}\n\n\t// check for valid team if specified\n\tif teamID != \"\" {\n\t\t_, err := a.app.GetTeam(teamID)\n\t\tif err != nil {\n\t\t\ta.errorResponse(w, r, model.NewErrBadRequest(\"invalid team id: \"+teamID))\n\t\t\treturn\n\t\t}\n\t}\n\n\tif strPage == \"\" {\n\t\tstrPage = complianceDefaultPage\n\t}\n\tif strPerPage == \"\" {\n\t\tstrPerPage = complianceDefaultPerPage\n\t}\n\tpage, err := strconv.Atoi(strPage)\n\tif err != nil {\n\t\tmessage := fmt.Sprintf(\"invalid `page` parameter: %s\", err)\n\t\ta.errorResponse(w, r, model.NewErrBadRequest(message))\n\t\treturn\n\t}\n\tperPage, err := strconv.Atoi(strPerPage)\n\tif err != nil {\n\t\tmessage := fmt.Sprintf(\"invalid `per_page` parameter: %s\", err)\n\t\ta.errorResponse(w, r, model.NewErrBadRequest(message))\n\t\treturn\n\t}\n\tmodifiedSince, err := strconv.ParseInt(strModifiedSince, 10, 64)\n\tif err != nil {\n\t\tmessage := fmt.Sprintf(\"invalid `modified_since` parameter: %s\", err)\n\t\ta.errorResponse(w, r, model.NewErrBadRequest(message))\n\t\treturn\n\t}\n\n\topts := model.QueryBoardsComplianceHistoryOptions{\n\t\tModifiedSince:  modifiedSince,\n\t\tIncludeDeleted: includeDeleted,\n\t\tTeamID:         teamID,\n\t\tPage:           page,\n\t\tPerPage:        perPage,\n\t}\n\n\tboards, more, err := a.app.GetBoardsComplianceHistory(opts)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\ta.logger.Debug(\"GetBoardsComplianceHistory\",\n\t\tmlog.String(\"teamID\", teamID),\n\t\tmlog.Int(\"boardsCount\", len(boards)),\n\t\tmlog.Bool(\"hasNext\", more),\n\t)\n\n\tresponse := model.BoardsComplianceHistoryResponse{\n\t\tHasNext: more,\n\t\tResults: boards,\n\t}\n\tdata, err := json.Marshal(response)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tjsonBytesResponse(w, http.StatusOK, data)\n}\n\nfunc (a *API) handleGetBlocksComplianceHistory(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation GET /admin/blocks_history getBlocksComplianceHistory\n\t//\n\t// Returns block histories for a specific team, specific board, or all teams and boards.\n\t//\n\t// Requires a license that includes Compliance feature. Caller must have `manage_system` permissions.\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: modified_since\n\t//   in: query\n\t//   description: Filters for boards modified since timestamp; Unix time in milliseconds\n\t//   required: true\n\t//   type: integer\n\t// - name: include_deleted\n\t//   in: query\n\t//   description: When true then deleted boards are included. Default=false\n\t//   required: false\n\t//   type: boolean\n\t// - name: team_id\n\t//   in: query\n\t//   description: Team ID. If empty then block histories across all teams are included\n\t//   required: false\n\t//   type: string\n\t// - name: board_id\n\t//   in: query\n\t//   description: Board ID. If empty then block histories for all boards are included\n\t//   required: false\n\t//   type: string\n\t// - name: page\n\t//   in: query\n\t//   description: The page to select (default=0)\n\t//   required: false\n\t//   type: integer\n\t// - name: per_page\n\t//   in: query\n\t//   description: Number of block histories to return per page (default=60)\n\t//   required: false\n\t//   type: integer\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       type: object\n\t//       items:\n\t//         \"$ref\": \"#/definitions/BlocksComplianceHistoryResponse\"\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tquery := r.URL.Query()\n\tstrModifiedSince := query.Get(\"modified_since\") // required, everything else optional\n\tincludeDeleted := query.Get(\"include_deleted\") == \"true\"\n\tstrPage := query.Get(\"page\")\n\tstrPerPage := query.Get(\"per_page\")\n\tteamID := query.Get(\"team_id\")\n\tboardID := query.Get(\"board_id\")\n\n\tif strModifiedSince == \"\" {\n\t\ta.errorResponse(w, r, model.NewErrBadRequest(\"`modified_since` parameter required\"))\n\t\treturn\n\t}\n\n\t// check for permission `manage_system`\n\tuserID := getUserID(r)\n\tif !a.permissions.HasPermissionTo(userID, mm_model.PermissionManageSystem) {\n\t\ta.errorResponse(w, r, model.NewErrUnauthorized(\"access denied Compliance Export getBlocksHistory\"))\n\t\treturn\n\t}\n\n\t// check for valid license feature: compliance\n\tlicense := a.app.GetLicense()\n\tif license == nil || !(*license.Features.Compliance) {\n\t\ta.errorResponse(w, r, model.NewErrNotImplemented(\"insufficient license Compliance Export getBlocksHistory\"))\n\t\treturn\n\t}\n\n\t// check for valid team if specified\n\tif teamID != \"\" {\n\t\t_, err := a.app.GetTeam(teamID)\n\t\tif err != nil {\n\t\t\ta.errorResponse(w, r, model.NewErrBadRequest(\"invalid team id: \"+teamID))\n\t\t\treturn\n\t\t}\n\t}\n\n\t// check for valid board if specified\n\tif boardID != \"\" {\n\t\t_, err := a.app.GetBoard(boardID)\n\t\tif err != nil {\n\t\t\ta.errorResponse(w, r, model.NewErrBadRequest(\"invalid board id: \"+boardID))\n\t\t\treturn\n\t\t}\n\t}\n\n\tif strPage == \"\" {\n\t\tstrPage = complianceDefaultPage\n\t}\n\tif strPerPage == \"\" {\n\t\tstrPerPage = complianceDefaultPerPage\n\t}\n\tpage, err := strconv.Atoi(strPage)\n\tif err != nil {\n\t\tmessage := fmt.Sprintf(\"invalid `page` parameter: %s\", err)\n\t\ta.errorResponse(w, r, model.NewErrBadRequest(message))\n\t\treturn\n\t}\n\tperPage, err := strconv.Atoi(strPerPage)\n\tif err != nil {\n\t\tmessage := fmt.Sprintf(\"invalid `per_page` parameter: %s\", err)\n\t\ta.errorResponse(w, r, model.NewErrBadRequest(message))\n\t\treturn\n\t}\n\tmodifiedSince, err := strconv.ParseInt(strModifiedSince, 10, 64)\n\tif err != nil {\n\t\tmessage := fmt.Sprintf(\"invalid `modified_since` parameter: %s\", err)\n\t\ta.errorResponse(w, r, model.NewErrBadRequest(message))\n\t\treturn\n\t}\n\n\topts := model.QueryBlocksComplianceHistoryOptions{\n\t\tModifiedSince:  modifiedSince,\n\t\tIncludeDeleted: includeDeleted,\n\t\tTeamID:         teamID,\n\t\tBoardID:        boardID,\n\t\tPage:           page,\n\t\tPerPage:        perPage,\n\t}\n\n\tblocks, more, err := a.app.GetBlocksComplianceHistory(opts)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\ta.logger.Debug(\"GetBlocksComplianceHistory\",\n\t\tmlog.String(\"teamID\", teamID),\n\t\tmlog.String(\"boardID\", boardID),\n\t\tmlog.Int(\"blocksCount\", len(blocks)),\n\t\tmlog.Bool(\"hasNext\", more),\n\t)\n\n\tresponse := model.BlocksComplianceHistoryResponse{\n\t\tHasNext: more,\n\t\tResults: blocks,\n\t}\n\tdata, err := json.Marshal(response)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tjsonBytesResponse(w, http.StatusOK, data)\n}\n"
  },
  {
    "path": "server/api/config.go",
    "content": "package api\n\nimport (\n\t\"encoding/json\"\n\t\"net/http\"\n\n\t\"github.com/gorilla/mux\"\n)\n\nfunc (a *API) registerConfigRoutes(r *mux.Router) {\n\t// Config APIs\n\tr.HandleFunc(\"/clientConfig\", a.getClientConfig).Methods(\"GET\")\n}\n\nfunc (a *API) getClientConfig(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation GET /clientConfig getClientConfig\n\t//\n\t// Returns the client configuration\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ClientConfig\"\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tclientConfig := a.app.GetClientConfig()\n\n\tconfigData, err := json.Marshal(clientConfig)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\tjsonBytesResponse(w, http.StatusOK, configData)\n}\n"
  },
  {
    "path": "server/api/content_blocks.go",
    "content": "package api\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/gorilla/mux\"\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/audit\"\n)\n\nfunc (a *API) registerContentBlocksRoutes(r *mux.Router) {\n\t// Blocks APIs\n\tr.HandleFunc(\"/content-blocks/{blockID}/moveto/{where}/{dstBlockID}\", a.sessionRequired(a.handleMoveBlockTo)).Methods(\"POST\")\n}\n\nfunc (a *API) handleMoveBlockTo(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation POST /content-blocks/{blockID}/move/{where}/{dstBlockID} moveBlockTo\n\t//\n\t// Move a block after another block in the parent card\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: blockID\n\t//   in: path\n\t//   description: Block ID\n\t//   required: true\n\t//   type: string\n\t// - name: where\n\t//   in: path\n\t//   description: Relative location respect destination block (after or before)\n\t//   required: true\n\t//   type: string\n\t// - name: dstBlockID\n\t//   in: path\n\t//   description: Destination Block ID\n\t//   required: true\n\t//   type: string\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       type: array\n\t//       items:\n\t//         \"$ref\": \"#/definitions/Block\"\n\t//   '404':\n\t//     description: board or block not found\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tblockID := mux.Vars(r)[\"blockID\"]\n\tdstBlockID := mux.Vars(r)[\"dstBlockID\"]\n\twhere := mux.Vars(r)[\"where\"]\n\tuserID := getUserID(r)\n\n\tblock, err := a.app.GetBlockByID(blockID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tdstBlock, err := a.app.GetBlockByID(dstBlockID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tif where != \"after\" && where != \"before\" {\n\t\ta.errorResponse(w, r, model.NewErrBadRequest(\"invalid where parameter, use before or after\"))\n\t\treturn\n\t}\n\n\tif userID == \"\" {\n\t\ta.errorResponse(w, r, model.NewErrUnauthorized(\"access denied to board\"))\n\t\treturn\n\t}\n\n\tif !a.permissions.HasPermissionToBoard(userID, block.BoardID, model.PermissionManageBoardCards) {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to modify board cards\"))\n\t\treturn\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"moveBlockTo\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelModify, auditRec)\n\tauditRec.AddMeta(\"blockID\", blockID)\n\tauditRec.AddMeta(\"dstBlockID\", dstBlockID)\n\n\terr = a.app.MoveContentBlock(block, dstBlock, where, userID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\t// response\n\tjsonStringResponse(w, http.StatusOK, \"{}\")\n\n\tauditRec.Success()\n}\n"
  },
  {
    "path": "server/api/context.go",
    "content": "package api\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"net/http\"\n)\n\ntype contextKey int\n\nconst (\n\thttpConnContextKey contextKey = iota\n\tsessionContextKey\n)\n\n// SetContextConn stores the connection in the request context.\nfunc SetContextConn(ctx context.Context, c net.Conn) context.Context {\n\treturn context.WithValue(ctx, httpConnContextKey, c)\n}\n\n// GetContextConn gets the stored connection from the request context.\nfunc GetContextConn(r *http.Request) net.Conn {\n\tvalue := r.Context().Value(httpConnContextKey)\n\tif value == nil {\n\t\treturn nil\n\t}\n\n\treturn value.(net.Conn)\n}\n"
  },
  {
    "path": "server/api/files.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage api\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/mattermost/focalboard/server/app\"\n\n\t\"github.com/gorilla/mux\"\n\t\"github.com/mattermost/focalboard/server/model\"\n\n\t\"github.com/mattermost/focalboard/server/services/audit\"\n\n\tmmModel \"github.com/mattermost/mattermost/server/public/model\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nvar UnsafeContentTypes = [...]string{\n\t\"application/javascript\",\n\t\"application/ecmascript\",\n\t\"text/javascript\",\n\t\"text/ecmascript\",\n\t\"application/x-javascript\",\n\t\"text/html\",\n}\n\nvar MediaContentTypes = [...]string{\n\t\"image/jpeg\",\n\t\"image/png\",\n\t\"image/bmp\",\n\t\"image/gif\",\n\t\"image/tiff\",\n\t\"video/avi\",\n\t\"video/mpeg\",\n\t\"video/mp4\",\n\t\"audio/mpeg\",\n\t\"audio/wav\",\n}\n\n// FileUploadResponse is the response to a file upload\n// swagger:model\ntype FileUploadResponse struct {\n\t// The FileID to retrieve the uploaded file\n\t// required: true\n\tFileID string `json:\"fileId\"`\n}\n\nfunc FileUploadResponseFromJSON(data io.Reader) (*FileUploadResponse, error) {\n\tvar fileUploadResponse FileUploadResponse\n\n\tif err := json.NewDecoder(data).Decode(&fileUploadResponse); err != nil {\n\t\treturn nil, err\n\t}\n\treturn &fileUploadResponse, nil\n}\n\nfunc FileInfoResponseFromJSON(data io.Reader) (*mmModel.FileInfo, error) {\n\tvar fileInfo mmModel.FileInfo\n\n\tif err := json.NewDecoder(data).Decode(&fileInfo); err != nil {\n\t\treturn nil, err\n\t}\n\treturn &fileInfo, nil\n}\n\nfunc (a *API) registerFilesRoutes(r *mux.Router) {\n\t// Files API\n\tr.HandleFunc(\"/files/teams/{teamID}/{boardID}/{filename}\", a.attachSession(a.handleServeFile, false)).Methods(\"GET\")\n\tr.HandleFunc(\"/files/teams/{teamID}/{boardID}/{filename}/info\", a.attachSession(a.getFileInfo, false)).Methods(\"GET\")\n\tr.HandleFunc(\"/teams/{teamID}/{boardID}/files\", a.sessionRequired(a.handleUploadFile)).Methods(\"POST\")\n}\n\nfunc (a *API) handleServeFile(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation GET /files/teams/{teamID}/{boardID}/{filename} getFile\n\t//\n\t// Returns the contents of an uploaded file\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// - image/jpg\n\t// - image/png\n\t// - image/gif\n\t// parameters:\n\t// - name: teamID\n\t//   in: path\n\t//   description: Team ID\n\t//   required: true\n\t//   type: string\n\t// - name: boardID\n\t//   in: path\n\t//   description: Board ID\n\t//   required: true\n\t//   type: string\n\t// - name: filename\n\t//   in: path\n\t//   description: name of the file\n\t//   required: true\n\t//   type: string\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//   '404':\n\t//     description: file not found\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tvars := mux.Vars(r)\n\tboardID := vars[\"boardID\"]\n\tfilename := vars[\"filename\"]\n\tuserID := getUserID(r)\n\n\thasValidReadToken := a.hasValidReadTokenForBoard(r, boardID)\n\tif userID == \"\" && !hasValidReadToken {\n\t\ta.errorResponse(w, r, model.NewErrUnauthorized(\"access denied to board\"))\n\t\treturn\n\t}\n\n\tif !hasValidReadToken && !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionViewBoard) {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to board\"))\n\t\treturn\n\t}\n\n\tboard, err := a.app.GetBoard(boardID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"getFile\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelRead, auditRec)\n\tauditRec.AddMeta(\"boardID\", boardID)\n\tauditRec.AddMeta(\"teamID\", board.TeamID)\n\tauditRec.AddMeta(\"filename\", filename)\n\n\tfileInfo, fileReader, err := a.app.GetFile(board.TeamID, boardID, filename)\n\tif err != nil && !model.IsErrNotFound(err) {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tif errors.Is(err, app.ErrFileNotFound) && board.ChannelID != \"\" {\n\t\t// prior to moving from workspaces to teams, the filepath was constructed from\n\t\t// workspaceID, which is the channel ID in plugin mode.\n\t\t// If a file is not found from team ID as we tried above, try looking for it via\n\t\t// channel ID.\n\t\tfileReader, err = a.app.GetFileReader(board.ChannelID, boardID, filename)\n\t\tif err != nil {\n\t\t\ta.errorResponse(w, r, err)\n\t\t\treturn\n\t\t}\n\t\t// move file to team location\n\t\t// nothing to do if there is an error\n\t\t_ = a.app.MoveFile(board.ChannelID, board.TeamID, boardID, filename)\n\t}\n\n\tif err != nil {\n\t\t// if err is still not nil then it is an error other than `not found` so we must\n\t\t// return the error to the requestor.  fileReader and Fileinfo are nil in this case.\n\t\ta.errorResponse(w, r, err)\n\t}\n\n\tdefer fileReader.Close()\n\n\tmimeType := \"\"\n\tvar fileSize int64\n\tif fileInfo != nil {\n\t\tmimeType = fileInfo.MimeType\n\t\tfileSize = fileInfo.Size\n\t}\n\twriteFileResponse(filename, mimeType, fileSize, time.Now(), \"\", fileReader, false, w, r)\n\tauditRec.Success()\n}\n\nfunc writeFileResponse(filename string, contentType string, contentSize int64,\n\tlastModification time.Time, webserverMode string, fileReader io.ReadSeeker, forceDownload bool, w http.ResponseWriter, r *http.Request) {\n\tw.Header().Set(\"Cache-Control\", \"private, no-cache\")\n\tw.Header().Set(\"X-Content-Type-Options\", \"nosniff\")\n\n\tif contentSize > 0 {\n\t\tcontentSizeStr := strconv.Itoa(int(contentSize))\n\t\tif webserverMode == \"gzip\" {\n\t\t\tw.Header().Set(\"X-Uncompressed-Content-Length\", contentSizeStr)\n\t\t} else {\n\t\t\tw.Header().Set(\"Content-Length\", contentSizeStr)\n\t\t}\n\t}\n\n\tif contentType == \"\" {\n\t\tcontentType = \"application/octet-stream\"\n\t} else {\n\t\tfor _, unsafeContentType := range UnsafeContentTypes {\n\t\t\tif strings.HasPrefix(contentType, unsafeContentType) {\n\t\t\t\tcontentType = \"text/plain\"\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tw.Header().Set(\"Content-Type\", contentType)\n\n\tvar toDownload bool\n\tif forceDownload {\n\t\ttoDownload = true\n\t} else {\n\t\tisMediaType := false\n\n\t\tfor _, mediaContentType := range MediaContentTypes {\n\t\t\tif strings.HasPrefix(contentType, mediaContentType) {\n\t\t\t\tisMediaType = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\ttoDownload = !isMediaType\n\t}\n\n\tfilename = url.PathEscape(filename)\n\n\tif toDownload {\n\t\tw.Header().Set(\"Content-Disposition\", \"attachment;filename=\\\"\"+filename+\"\\\"; filename*=UTF-8''\"+filename)\n\t} else {\n\t\tw.Header().Set(\"Content-Disposition\", \"inline;filename=\\\"\"+filename+\"\\\"; filename*=UTF-8''\"+filename)\n\t}\n\n\t// prevent file links from being embedded in iframes\n\tw.Header().Set(\"X-Frame-Options\", \"DENY\")\n\tw.Header().Set(\"Content-Security-Policy\", \"Frame-ancestors 'none'\")\n\n\thttp.ServeContent(w, r, filename, lastModification, fileReader)\n}\n\nfunc (a *API) getFileInfo(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation GET /files/teams/{teamID}/{boardID}/{filename}/info getFile\n\t//\n\t// Returns the metadata of an uploaded file\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: teamID\n\t//   in: path\n\t//   description: Team ID\n\t//   required: true\n\t//   type: string\n\t// - name: boardID\n\t//   in: path\n\t//   description: Board ID\n\t//   required: true\n\t//   type: string\n\t// - name: filename\n\t//   in: path\n\t//   description: name of the file\n\t//   required: true\n\t//   type: string\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//   '404':\n\t//     description: file not found\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tvars := mux.Vars(r)\n\tboardID := vars[\"boardID\"]\n\tteamID := vars[\"teamID\"]\n\tfilename := vars[\"filename\"]\n\tuserID := getUserID(r)\n\n\thasValidReadToken := a.hasValidReadTokenForBoard(r, boardID)\n\tif userID == \"\" && !hasValidReadToken {\n\t\ta.errorResponse(w, r, model.NewErrUnauthorized(\"access denied to board\"))\n\t\treturn\n\t}\n\n\tif !hasValidReadToken && !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionViewBoard) {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to board\"))\n\t\treturn\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"getFile\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelRead, auditRec)\n\tauditRec.AddMeta(\"boardID\", boardID)\n\tauditRec.AddMeta(\"teamID\", teamID)\n\tauditRec.AddMeta(\"filename\", filename)\n\n\tfileInfo, err := a.app.GetFileInfo(filename)\n\tif err != nil && !model.IsErrNotFound(err) {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tdata, err := json.Marshal(fileInfo)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tjsonBytesResponse(w, http.StatusOK, data)\n}\n\nfunc (a *API) handleUploadFile(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation POST /teams/{teamID}/boards/{boardID}/files uploadFile\n\t//\n\t// Upload a binary file, attached to a root block\n\t//\n\t// ---\n\t// consumes:\n\t// - multipart/form-data\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: teamID\n\t//   in: path\n\t//   description: ID of the team\n\t//   required: true\n\t//   type: string\n\t// - name: boardID\n\t//   in: path\n\t//   description: Board ID\n\t//   required: true\n\t//   type: string\n\t// - name: uploaded file\n\t//   in: formData\n\t//   type: file\n\t//   description: The file to upload\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/FileUploadResponse\"\n\t//   '404':\n\t//     description: board not found\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tvars := mux.Vars(r)\n\tboardID := vars[\"boardID\"]\n\tuserID := getUserID(r)\n\n\tif !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardCards) {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to make board changes\"))\n\t\treturn\n\t}\n\n\tboard, err := a.app.GetBoard(boardID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tif a.app.GetConfig().MaxFileSize > 0 {\n\t\tr.Body = http.MaxBytesReader(w, r.Body, a.app.GetConfig().MaxFileSize)\n\t}\n\n\tfile, handle, err := r.FormFile(UploadFormFileKey)\n\tif err != nil {\n\t\tif strings.HasSuffix(err.Error(), \"http: request body too large\") {\n\t\t\ta.errorResponse(w, r, model.ErrRequestEntityTooLarge)\n\t\t\treturn\n\t\t}\n\t\ta.errorResponse(w, r, model.NewErrBadRequest(err.Error()))\n\t\treturn\n\t}\n\tdefer file.Close()\n\n\tauditRec := a.makeAuditRecord(r, \"uploadFile\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelModify, auditRec)\n\tauditRec.AddMeta(\"boardID\", boardID)\n\tauditRec.AddMeta(\"teamID\", board.TeamID)\n\tauditRec.AddMeta(\"filename\", handle.Filename)\n\n\tfileID, err := a.app.SaveFile(file, board.TeamID, boardID, handle.Filename, board.IsTemplate)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\ta.logger.Debug(\"uploadFile\",\n\t\tmlog.String(\"filename\", handle.Filename),\n\t\tmlog.String(\"fileID\", fileID),\n\t)\n\tdata, err := json.Marshal(FileUploadResponse{FileID: fileID})\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tjsonBytesResponse(w, http.StatusOK, data)\n\n\tauditRec.AddMeta(\"fileID\", fileID)\n\tauditRec.Success()\n}\n"
  },
  {
    "path": "server/api/members.go",
    "content": "package api\n\nimport (\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\n\t\"github.com/gorilla/mux\"\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/audit\"\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nfunc (a *API) registerMembersRoutes(r *mux.Router) {\n\t// Member APIs\n\tr.HandleFunc(\"/boards/{boardID}/members\", a.sessionRequired(a.handleGetMembersForBoard)).Methods(\"GET\")\n\tr.HandleFunc(\"/boards/{boardID}/members\", a.sessionRequired(a.handleAddMember)).Methods(\"POST\")\n\tr.HandleFunc(\"/boards/{boardID}/members/{userID}\", a.sessionRequired(a.handleUpdateMember)).Methods(\"PUT\")\n\tr.HandleFunc(\"/boards/{boardID}/members/{userID}\", a.sessionRequired(a.handleDeleteMember)).Methods(\"DELETE\")\n\tr.HandleFunc(\"/boards/{boardID}/join\", a.sessionRequired(a.handleJoinBoard)).Methods(\"POST\")\n\tr.HandleFunc(\"/boards/{boardID}/leave\", a.sessionRequired(a.handleLeaveBoard)).Methods(\"POST\")\n}\n\nfunc (a *API) handleGetMembersForBoard(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation GET /boards/{boardID}/members getMembersForBoard\n\t//\n\t// Returns the members of the board\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: boardID\n\t//   in: path\n\t//   description: Board ID\n\t//   required: true\n\t//   type: string\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       type: array\n\t//       items:\n\t//         \"$ref\": \"#/definitions/BoardMember\"\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tboardID := mux.Vars(r)[\"boardID\"]\n\tuserID := getUserID(r)\n\n\tif !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionViewBoard) {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to board members\"))\n\t\treturn\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"getMembersForBoard\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelModify, auditRec)\n\tauditRec.AddMeta(\"boardID\", boardID)\n\n\tmembers, err := a.app.GetMembersForBoard(boardID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\ta.logger.Debug(\"GetMembersForBoard\",\n\t\tmlog.String(\"boardID\", boardID),\n\t\tmlog.Int(\"membersCount\", len(members)),\n\t)\n\n\tdata, err := json.Marshal(members)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\t// response\n\tjsonBytesResponse(w, http.StatusOK, data)\n\n\tauditRec.Success()\n}\n\nfunc (a *API) handleAddMember(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation POST /boards/{boardID}/members addMember\n\t//\n\t// Adds a new member to a board\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: boardID\n\t//   in: path\n\t//   description: Board ID\n\t//   required: true\n\t//   type: string\n\t// - name: Body\n\t//   in: body\n\t//   description: membership to replace the current one with\n\t//   required: true\n\t//   schema:\n\t//     \"$ref\": \"#/definitions/BoardMember\"\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       $ref: '#/definitions/BoardMember'\n\t//   '404':\n\t//     description: board not found\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tboardID := mux.Vars(r)[\"boardID\"]\n\tuserID := getUserID(r)\n\n\tboard, err := a.app.GetBoard(boardID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tif !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardRoles) &&\n\t\t!(board.Type == model.BoardTypeOpen && a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardProperties)) {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to modify board members\"))\n\t\treturn\n\t}\n\n\trequestBody, err := io.ReadAll(r.Body)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tvar reqBoardMember *model.BoardMember\n\tif err = json.Unmarshal(requestBody, &reqBoardMember); err != nil {\n\t\ta.errorResponse(w, r, model.NewErrBadRequest(err.Error()))\n\t\treturn\n\t}\n\n\tif reqBoardMember.UserID == \"\" {\n\t\ta.errorResponse(w, r, model.NewErrBadRequest(\"empty userID\"))\n\t\treturn\n\t}\n\n\tif !a.permissions.HasPermissionToTeam(reqBoardMember.UserID, board.TeamID, model.PermissionViewTeam) {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to team\"))\n\t\treturn\n\t}\n\n\tnewBoardMember := &model.BoardMember{\n\t\tUserID:          reqBoardMember.UserID,\n\t\tBoardID:         boardID,\n\t\tSchemeEditor:    reqBoardMember.SchemeEditor,\n\t\tSchemeAdmin:     reqBoardMember.SchemeAdmin,\n\t\tSchemeViewer:    reqBoardMember.SchemeViewer,\n\t\tSchemeCommenter: reqBoardMember.SchemeCommenter,\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"addMember\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelModify, auditRec)\n\tauditRec.AddMeta(\"boardID\", boardID)\n\tauditRec.AddMeta(\"addedUserID\", reqBoardMember.UserID)\n\n\tmember, err := a.app.AddMemberToBoard(newBoardMember)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\ta.logger.Debug(\"AddMember\",\n\t\tmlog.String(\"boardID\", board.ID),\n\t\tmlog.String(\"addedUserID\", reqBoardMember.UserID),\n\t)\n\n\tdata, err := json.Marshal(member)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\t// response\n\tjsonBytesResponse(w, http.StatusOK, data)\n\n\tauditRec.Success()\n}\n\nfunc (a *API) handleJoinBoard(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation POST /boards/{boardID}/join joinBoard\n\t//\n\t// Become a member of a board\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: boardID\n\t//   in: path\n\t//   description: Board ID\n\t//   required: true\n\t//   type: string\n\t// - name: allow_admin\n\t//   in: path\n\t//   description: allows admin users to join private boards\n\t//   required: false\n\t//   type: boolean\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       $ref: '#/definitions/BoardMember'\n\t//   '404':\n\t//     description: board not found\n\t//   '403':\n\t//     description: access denied\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tquery := r.URL.Query()\n\tallowAdmin := query.Has(\"allow_admin\")\n\n\tuserID := getUserID(r)\n\tif userID == \"\" {\n\t\ta.errorResponse(w, r, model.NewErrBadRequest(\"missing user ID\"))\n\t\treturn\n\t}\n\n\tboardID := mux.Vars(r)[\"boardID\"]\n\tboard, err := a.app.GetBoard(boardID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tisAdmin := false\n\tif board.Type != model.BoardTypeOpen {\n\t\tif !allowAdmin || !a.permissions.HasPermissionToTeam(userID, board.TeamID, model.PermissionManageTeam) {\n\t\t\ta.errorResponse(w, r, model.NewErrPermission(\"cannot join a non Open board\"))\n\t\t\treturn\n\t\t}\n\t\tisAdmin = true\n\t}\n\n\tif !a.permissions.HasPermissionToTeam(userID, board.TeamID, model.PermissionViewTeam) {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to team\"))\n\t\treturn\n\t}\n\n\tisGuest, err := a.userIsGuest(userID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\tif isGuest {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"guests not allowed to join boards\"))\n\t\treturn\n\t}\n\n\tnewBoardMember := &model.BoardMember{\n\t\tUserID:          userID,\n\t\tBoardID:         boardID,\n\t\tSchemeAdmin:     board.MinimumRole == model.BoardRoleAdmin || isAdmin,\n\t\tSchemeEditor:    board.MinimumRole == model.BoardRoleNone || board.MinimumRole == model.BoardRoleEditor,\n\t\tSchemeCommenter: board.MinimumRole == model.BoardRoleCommenter,\n\t\tSchemeViewer:    board.MinimumRole == model.BoardRoleViewer,\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"joinBoard\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelModify, auditRec)\n\tauditRec.AddMeta(\"boardID\", boardID)\n\tauditRec.AddMeta(\"addedUserID\", userID)\n\n\tmember, err := a.app.AddMemberToBoard(newBoardMember)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\ta.logger.Debug(\"JoinBoard\",\n\t\tmlog.String(\"boardID\", board.ID),\n\t\tmlog.String(\"addedUserID\", userID),\n\t)\n\n\tdata, err := json.Marshal(member)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\t// response\n\tjsonBytesResponse(w, http.StatusOK, data)\n\n\tauditRec.Success()\n}\n\nfunc (a *API) handleLeaveBoard(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation POST /boards/{boardID}/leave leaveBoard\n\t//\n\t// Remove your own membership from a board\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: boardID\n\t//   in: path\n\t//   description: Board ID\n\t//   required: true\n\t//   type: string\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//   '404':\n\t//     description: board not found\n\t//   '403':\n\t//     description: access denied\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tuserID := getUserID(r)\n\tif userID == \"\" {\n\t\ta.errorResponse(w, r, model.NewErrBadRequest(\"invalid session\"))\n\t\treturn\n\t}\n\n\tboardID := mux.Vars(r)[\"boardID\"]\n\n\tif !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionViewBoard) {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to board\"))\n\t\treturn\n\t}\n\n\tboard, err := a.app.GetBoard(boardID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"leaveBoard\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelModify, auditRec)\n\tauditRec.AddMeta(\"boardID\", boardID)\n\tauditRec.AddMeta(\"addedUserID\", userID)\n\n\terr = a.app.DeleteBoardMember(boardID, userID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\ta.logger.Debug(\"LeaveBoard\",\n\t\tmlog.String(\"boardID\", board.ID),\n\t\tmlog.String(\"addedUserID\", userID),\n\t)\n\n\tjsonStringResponse(w, http.StatusOK, \"{}\")\n\n\tauditRec.Success()\n}\n\nfunc (a *API) handleUpdateMember(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation PUT /boards/{boardID}/members/{userID} updateMember\n\t//\n\t// Updates a board member\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: boardID\n\t//   in: path\n\t//   description: Board ID\n\t//   required: true\n\t//   type: string\n\t// - name: userID\n\t//   in: path\n\t//   description: User ID\n\t//   required: true\n\t//   type: string\n\t// - name: Body\n\t//   in: body\n\t//   description: membership to replace the current one with\n\t//   required: true\n\t//   schema:\n\t//     \"$ref\": \"#/definitions/BoardMember\"\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       $ref: '#/definitions/BoardMember'\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tboardID := mux.Vars(r)[\"boardID\"]\n\tparamsUserID := mux.Vars(r)[\"userID\"]\n\tuserID := getUserID(r)\n\n\trequestBody, err := io.ReadAll(r.Body)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tvar reqBoardMember *model.BoardMember\n\tif err = json.Unmarshal(requestBody, &reqBoardMember); err != nil {\n\t\ta.errorResponse(w, r, model.NewErrBadRequest(err.Error()))\n\t\treturn\n\t}\n\n\tnewBoardMember := &model.BoardMember{\n\t\tUserID:          paramsUserID,\n\t\tBoardID:         boardID,\n\t\tSchemeAdmin:     reqBoardMember.SchemeAdmin,\n\t\tSchemeEditor:    reqBoardMember.SchemeEditor,\n\t\tSchemeCommenter: reqBoardMember.SchemeCommenter,\n\t\tSchemeViewer:    reqBoardMember.SchemeViewer,\n\t}\n\n\tisGuest, err := a.userIsGuest(paramsUserID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tif isGuest {\n\t\tnewBoardMember.SchemeAdmin = false\n\t}\n\n\tif !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardRoles) {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to modify board members\"))\n\t\treturn\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"patchMember\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelModify, auditRec)\n\tauditRec.AddMeta(\"boardID\", boardID)\n\tauditRec.AddMeta(\"patchedUserID\", paramsUserID)\n\n\tmember, err := a.app.UpdateBoardMember(newBoardMember)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\ta.logger.Debug(\"PatchMember\",\n\t\tmlog.String(\"boardID\", boardID),\n\t\tmlog.String(\"patchedUserID\", paramsUserID),\n\t)\n\n\tdata, err := json.Marshal(member)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\t// response\n\tjsonBytesResponse(w, http.StatusOK, data)\n\n\tauditRec.Success()\n}\n\nfunc (a *API) handleDeleteMember(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation DELETE /boards/{boardID}/members/{userID} deleteMember\n\t//\n\t// Deletes a member from a board\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: boardID\n\t//   in: path\n\t//   description: Board ID\n\t//   required: true\n\t//   type: string\n\t// - name: userID\n\t//   in: path\n\t//   description: User ID\n\t//   required: true\n\t//   type: string\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//   '404':\n\t//     description: board not found\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tboardID := mux.Vars(r)[\"boardID\"]\n\tparamsUserID := mux.Vars(r)[\"userID\"]\n\tuserID := getUserID(r)\n\n\tif _, err := a.app.GetBoard(boardID); err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tif !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardRoles) {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to modify board members\"))\n\t\treturn\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"deleteMember\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelModify, auditRec)\n\tauditRec.AddMeta(\"boardID\", boardID)\n\tauditRec.AddMeta(\"addedUserID\", paramsUserID)\n\n\tdeleteErr := a.app.DeleteBoardMember(boardID, paramsUserID)\n\tif deleteErr != nil {\n\t\ta.errorResponse(w, r, deleteErr)\n\t\treturn\n\t}\n\n\ta.logger.Debug(\"DeleteMember\",\n\t\tmlog.String(\"boardID\", boardID),\n\t\tmlog.String(\"addedUserID\", paramsUserID),\n\t)\n\n\t// response\n\tjsonStringResponse(w, http.StatusOK, \"{}\")\n\n\tauditRec.Success()\n}\n"
  },
  {
    "path": "server/api/onboarding.go",
    "content": "package api\n\nimport (\n\t\"encoding/json\"\n\t\"net/http\"\n\n\t\"github.com/gorilla/mux\"\n\t\"github.com/mattermost/focalboard/server/model\"\n)\n\nfunc (a *API) registerOnboardingRoutes(r *mux.Router) {\n\t// Onboarding tour endpoints APIs\n\tr.HandleFunc(\"/teams/{teamID}/onboard\", a.sessionRequired(a.handleOnboard)).Methods(http.MethodPost)\n}\n\nfunc (a *API) handleOnboard(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation POST /team/{teamID}/onboard onboard\n\t//\n\t// Onboards a user on Boards.\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: teamID\n\t//   in: path\n\t//   description: Team ID\n\t//   required: true\n\t//   type: string\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       type: object\n\t//       properties:\n\t//         teamID:\n\t//           type: string\n\t//           description: Team ID\n\t//         boardID:\n\t//           type: string\n\t//           description: Board ID\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\tteamID := mux.Vars(r)[\"teamID\"]\n\tuserID := getUserID(r)\n\n\tif !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to create board\"))\n\t\treturn\n\t}\n\n\tisGuest, err := a.userIsGuest(userID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\tif isGuest {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to create board\"))\n\t\treturn\n\t}\n\n\tteamID, boardID, err := a.app.PrepareOnboardingTour(userID, teamID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tresponse := map[string]string{\n\t\t\"teamID\":  teamID,\n\t\t\"boardID\": boardID,\n\t}\n\tdata, err := json.Marshal(response)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tjsonBytesResponse(w, http.StatusOK, data)\n}\n"
  },
  {
    "path": "server/api/search.go",
    "content": "package api\n\nimport (\n\t\"encoding/json\"\n\t\"net/http\"\n\n\t\"github.com/gorilla/mux\"\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/audit\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nfunc (a *API) registerSearchRoutes(r *mux.Router) {\n\tr.HandleFunc(\"/teams/{teamID}/channels\", a.sessionRequired(a.handleSearchMyChannels)).Methods(\"GET\")\n\tr.HandleFunc(\"/teams/{teamID}/boards/search\", a.sessionRequired(a.handleSearchBoards)).Methods(\"GET\")\n\tr.HandleFunc(\"/teams/{teamID}/boards/search/linkable\", a.sessionRequired(a.handleSearchLinkableBoards)).Methods(\"GET\")\n\tr.HandleFunc(\"/boards/search\", a.sessionRequired(a.handleSearchAllBoards)).Methods(\"GET\")\n}\n\nfunc (a *API) handleSearchMyChannels(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation GET /teams/{teamID}/channels searchMyChannels\n\t//\n\t// Returns the user available channels\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: teamID\n\t//   in: path\n\t//   description: Team ID\n\t//   required: true\n\t//   type: string\n\t// - name: search\n\t//   in: query\n\t//   description: string to filter channels list\n\t//   required: false\n\t//   type: string\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       type: array\n\t//       items:\n\t//         \"$ref\": \"#/definitions/Channel\"\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tif !a.MattermostAuth {\n\t\ta.errorResponse(w, r, model.NewErrNotImplemented(\"not permitted in standalone mode\"))\n\t\treturn\n\t}\n\n\tquery := r.URL.Query()\n\tsearchQuery := query.Get(\"search\")\n\n\tteamID := mux.Vars(r)[\"teamID\"]\n\tuserID := getUserID(r)\n\n\tif !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to team\"))\n\t\treturn\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"searchMyChannels\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelRead, auditRec)\n\tauditRec.AddMeta(\"teamID\", teamID)\n\n\tchannels, err := a.app.SearchUserChannels(teamID, userID, searchQuery)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\ta.logger.Debug(\"GetUserChannels\",\n\t\tmlog.String(\"teamID\", teamID),\n\t\tmlog.Int(\"channelsCount\", len(channels)),\n\t)\n\n\tdata, err := json.Marshal(channels)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\t// response\n\tjsonBytesResponse(w, http.StatusOK, data)\n\n\tauditRec.AddMeta(\"channelsCount\", len(channels))\n\tauditRec.Success()\n}\n\nfunc (a *API) handleSearchBoards(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation GET /teams/{teamID}/boards/search searchBoards\n\t//\n\t// Returns the boards that match with a search term in the team\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: teamID\n\t//   in: path\n\t//   description: Team ID\n\t//   required: true\n\t//   type: string\n\t// - name: q\n\t//   in: query\n\t//   description: The search term. Must have at least one character\n\t//   required: true\n\t//   type: string\n\t// - name: field\n\t//   in: query\n\t//   description: The field to search on for search term. Can be `title`, `property_name`. Defaults to `title`\n\t//   required: false\n\t//   type: string\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       type: array\n\t//       items:\n\t//         \"$ref\": \"#/definitions/Board\"\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tvar err error\n\tteamID := mux.Vars(r)[\"teamID\"]\n\tterm := r.URL.Query().Get(\"q\")\n\tsearchFieldText := r.URL.Query().Get(\"field\")\n\tsearchField := model.BoardSearchFieldTitle\n\tif searchFieldText != \"\" {\n\t\tsearchField, err = model.BoardSearchFieldFromString(searchFieldText)\n\t\tif err != nil {\n\t\t\ta.errorResponse(w, r, model.NewErrBadRequest(err.Error()))\n\t\t\treturn\n\t\t}\n\t}\n\tuserID := getUserID(r)\n\n\tif !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to team\"))\n\t\treturn\n\t}\n\n\tif len(term) == 0 {\n\t\tjsonStringResponse(w, http.StatusOK, \"[]\")\n\t\treturn\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"searchBoards\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelRead, auditRec)\n\tauditRec.AddMeta(\"teamID\", teamID)\n\n\tisGuest, err := a.userIsGuest(userID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\t// retrieve boards list\n\tboards, err := a.app.SearchBoardsForUser(term, searchField, userID, !isGuest)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\ta.logger.Debug(\"SearchBoards\",\n\t\tmlog.String(\"teamID\", teamID),\n\t\tmlog.Int(\"boardsCount\", len(boards)),\n\t)\n\n\tdata, err := json.Marshal(boards)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\t// response\n\tjsonBytesResponse(w, http.StatusOK, data)\n\n\tauditRec.AddMeta(\"boardsCount\", len(boards))\n\tauditRec.Success()\n}\n\nfunc (a *API) handleSearchLinkableBoards(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation GET /teams/{teamID}/boards/search/linkable searchLinkableBoards\n\t//\n\t// Returns the boards that match with a search term in the team and the\n\t// user has permission to manage members\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: teamID\n\t//   in: path\n\t//   description: Team ID\n\t//   required: true\n\t//   type: string\n\t// - name: q\n\t//   in: query\n\t//   description: The search term. Must have at least one character\n\t//   required: true\n\t//   type: string\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       type: array\n\t//       items:\n\t//         \"$ref\": \"#/definitions/Board\"\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tif !a.MattermostAuth {\n\t\ta.errorResponse(w, r, model.NewErrNotImplemented(\"not permitted in standalone mode\"))\n\t\treturn\n\t}\n\n\tteamID := mux.Vars(r)[\"teamID\"]\n\tterm := r.URL.Query().Get(\"q\")\n\tuserID := getUserID(r)\n\n\tif !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to team\"))\n\t\treturn\n\t}\n\n\tif len(term) == 0 {\n\t\tjsonStringResponse(w, http.StatusOK, \"[]\")\n\t\treturn\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"searchLinkableBoards\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelRead, auditRec)\n\tauditRec.AddMeta(\"teamID\", teamID)\n\n\t// retrieve boards list\n\tboards, err := a.app.SearchBoardsForUserInTeam(teamID, term, userID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tlinkableBoards := []*model.Board{}\n\tfor _, board := range boards {\n\t\tif a.permissions.HasPermissionToBoard(userID, board.ID, model.PermissionManageBoardRoles) {\n\t\t\tlinkableBoards = append(linkableBoards, board)\n\t\t}\n\t}\n\n\ta.logger.Debug(\"SearchLinkableBoards\",\n\t\tmlog.String(\"teamID\", teamID),\n\t\tmlog.Int(\"boardsCount\", len(linkableBoards)),\n\t)\n\n\tdata, err := json.Marshal(linkableBoards)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\t// response\n\tjsonBytesResponse(w, http.StatusOK, data)\n\n\tauditRec.AddMeta(\"boardsCount\", len(linkableBoards))\n\tauditRec.Success()\n}\n\nfunc (a *API) handleSearchAllBoards(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation GET /boards/search searchAllBoards\n\t//\n\t// Returns the boards that match with a search term\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: q\n\t//   in: query\n\t//   description: The search term. Must have at least one character\n\t//   required: true\n\t//   type: string\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       type: array\n\t//       items:\n\t//         \"$ref\": \"#/definitions/Board\"\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tterm := r.URL.Query().Get(\"q\")\n\tuserID := getUserID(r)\n\n\tif len(term) == 0 {\n\t\tjsonStringResponse(w, http.StatusOK, \"[]\")\n\t\treturn\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"searchAllBoards\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelRead, auditRec)\n\n\tisGuest, err := a.userIsGuest(userID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\t// retrieve boards list\n\tboards, err := a.app.SearchBoardsForUser(term, model.BoardSearchFieldTitle, userID, !isGuest)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\ta.logger.Debug(\"SearchAllBoards\",\n\t\tmlog.Int(\"boardsCount\", len(boards)),\n\t)\n\n\tdata, err := json.Marshal(boards)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\t// response\n\tjsonBytesResponse(w, http.StatusOK, data)\n\n\tauditRec.AddMeta(\"boardsCount\", len(boards))\n\tauditRec.Success()\n}\n"
  },
  {
    "path": "server/api/sharing.go",
    "content": "package api\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"io\"\n\t\"net/http\"\n\n\t\"github.com/gorilla/mux\"\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/audit\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nvar ErrTurningOnSharing = errors.New(\"turning on sharing for board failed, see log for details\")\n\nfunc (a *API) registerSharingRoutes(r *mux.Router) {\n\t// Sharing APIs\n\tr.HandleFunc(\"/boards/{boardID}/sharing\", a.sessionRequired(a.handlePostSharing)).Methods(\"POST\")\n\tr.HandleFunc(\"/boards/{boardID}/sharing\", a.sessionRequired(a.handleGetSharing)).Methods(\"GET\")\n}\n\nfunc (a *API) handleGetSharing(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation GET /boards/{boardID}/sharing getSharing\n\t//\n\t// Returns sharing information for a board\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: boardID\n\t//   in: path\n\t//   description: Board ID\n\t//   required: true\n\t//   type: string\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/Sharing\"\n\t//   '404':\n\t//     description: board not found\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tvars := mux.Vars(r)\n\tboardID := vars[\"boardID\"]\n\n\tuserID := getUserID(r)\n\tif !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionShareBoard) {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to sharing the board\"))\n\t\treturn\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"getSharing\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelRead, auditRec)\n\tauditRec.AddMeta(\"boardID\", boardID)\n\n\tsharing, err := a.app.GetSharing(boardID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tsharingData, err := json.Marshal(sharing)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tjsonBytesResponse(w, http.StatusOK, sharingData)\n\n\ta.logger.Debug(\"GET sharing\",\n\t\tmlog.String(\"boardID\", boardID),\n\t\tmlog.String(\"shareID\", sharing.ID),\n\t\tmlog.Bool(\"enabled\", sharing.Enabled),\n\t)\n\tauditRec.AddMeta(\"shareID\", sharing.ID)\n\tauditRec.AddMeta(\"enabled\", sharing.Enabled)\n\tauditRec.Success()\n}\n\nfunc (a *API) handlePostSharing(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation POST /boards/{boardID}/sharing postSharing\n\t//\n\t// Sets sharing information for a board\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: boardID\n\t//   in: path\n\t//   description: Board ID\n\t//   required: true\n\t//   type: string\n\t// - name: Body\n\t//   in: body\n\t//   description: sharing information for a root block\n\t//   required: true\n\t//   schema:\n\t//     \"$ref\": \"#/definitions/Sharing\"\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tboardID := mux.Vars(r)[\"boardID\"]\n\n\tuserID := getUserID(r)\n\tif !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionShareBoard) {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to sharing the board\"))\n\t\treturn\n\t}\n\n\trequestBody, err := io.ReadAll(r.Body)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tvar sharing model.Sharing\n\terr = json.Unmarshal(requestBody, &sharing)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\t// Stamp boardID from the URL\n\tsharing.ID = boardID\n\n\tauditRec := a.makeAuditRecord(r, \"postSharing\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelModify, auditRec)\n\tauditRec.AddMeta(\"shareID\", sharing.ID)\n\tauditRec.AddMeta(\"enabled\", sharing.Enabled)\n\n\t// Stamp ModifiedBy\n\tmodifiedBy := userID\n\tif userID == model.SingleUser {\n\t\tmodifiedBy = \"\"\n\t}\n\tsharing.ModifiedBy = modifiedBy\n\n\tif userID == model.SingleUser {\n\t\tuserID = \"\"\n\t}\n\n\tif !a.app.GetClientConfig().EnablePublicSharedBoards {\n\t\ta.logger.Warn(\n\t\t\t\"Attempt to turn on sharing for board via API failed, sharing off in configuration.\",\n\t\t\tmlog.String(\"boardID\", sharing.ID),\n\t\t\tmlog.String(\"userID\", userID))\n\t\ta.errorResponse(w, r, ErrTurningOnSharing)\n\t\treturn\n\t}\n\n\tsharing.ModifiedBy = userID\n\n\terr = a.app.UpsertSharing(sharing)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tjsonStringResponse(w, http.StatusOK, \"{}\")\n\n\ta.logger.Debug(\"POST sharing\", mlog.String(\"sharingID\", sharing.ID))\n\tauditRec.Success()\n}\n"
  },
  {
    "path": "server/api/statistics.go",
    "content": "package api\n\nimport (\n\t\"encoding/json\"\n\t\"net/http\"\n\n\t\"github.com/gorilla/mux\"\n\t\"github.com/mattermost/focalboard/server/model\"\n\tmmModel \"github.com/mattermost/mattermost/server/public/model\"\n)\n\nfunc (a *API) registerStatisticsRoutes(r *mux.Router) {\n\t// statistics\n\tr.HandleFunc(\"/statistics\", a.sessionRequired(a.handleStatistics)).Methods(\"GET\")\n}\n\nfunc (a *API) handleStatistics(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation GET /statistics handleStatistics\n\t//\n\t// Fetches the statistic  of the server.\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//         \"$ref\": \"#/definitions/BoardStatistics\"\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\tif !a.MattermostAuth {\n\t\ta.errorResponse(w, r, model.NewErrNotImplemented(\"not permitted in standalone mode\"))\n\t\treturn\n\t}\n\n\t// user must have right to access analytics\n\tuserID := getUserID(r)\n\tif !a.permissions.HasPermissionTo(userID, mmModel.PermissionGetAnalytics) {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied System Statistics\"))\n\t\treturn\n\t}\n\n\tboardCount, err := a.app.GetBoardCount()\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\tcardCount, err := a.app.GetUsedCardsCount()\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tstats := model.BoardsStatistics{\n\t\tBoards: int(boardCount),\n\t\tCards:  cardCount,\n\t}\n\tdata, err := json.Marshal(stats)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tjsonBytesResponse(w, http.StatusOK, data)\n}\n"
  },
  {
    "path": "server/api/subscriptions.go",
    "content": "package api\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\t\"github.com/gorilla/mux\"\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/audit\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nfunc (a *API) registerSubscriptionsRoutes(r *mux.Router) {\n\t// Subscription APIs\n\tr.HandleFunc(\"/subscriptions\", a.sessionRequired(a.handleCreateSubscription)).Methods(\"POST\")\n\tr.HandleFunc(\"/subscriptions/{blockID}/{subscriberID}\", a.sessionRequired(a.handleDeleteSubscription)).Methods(\"DELETE\")\n\tr.HandleFunc(\"/subscriptions/{subscriberID}\", a.sessionRequired(a.handleGetSubscriptions)).Methods(\"GET\")\n}\n\n// subscriptions\n\nfunc (a *API) handleCreateSubscription(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation POST /subscriptions createSubscription\n\t//\n\t// Creates a subscription to a block for a user. The user will receive change notifications for the block.\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: Body\n\t//   in: body\n\t//   description: subscription definition\n\t//   required: true\n\t//   schema:\n\t//     \"$ref\": \"#/definitions/Subscription\"\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//         \"$ref\": \"#/definitions/User\"\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\trequestBody, err := io.ReadAll(r.Body)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tvar sub model.Subscription\n\n\tif err = json.Unmarshal(requestBody, &sub); err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tif err = sub.IsValid(); err != nil {\n\t\ta.errorResponse(w, r, model.NewErrBadRequest(err.Error()))\n\t\treturn\n\t}\n\n\tctx := r.Context()\n\tsession := ctx.Value(sessionContextKey).(*model.Session)\n\n\tauditRec := a.makeAuditRecord(r, \"createSubscription\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelModify, auditRec)\n\tauditRec.AddMeta(\"subscriber_id\", sub.SubscriberID)\n\tauditRec.AddMeta(\"block_id\", sub.BlockID)\n\n\t// User can only create subscriptions for themselves (for now)\n\tif session.UserID != sub.SubscriberID {\n\t\ta.errorResponse(w, r, model.NewErrBadRequest(\"userID and subscriberID mismatch\"))\n\t\treturn\n\t}\n\n\t// check for valid block\n\t_, bErr := a.app.GetBlockByID(sub.BlockID)\n\tif bErr != nil {\n\t\tmessage := fmt.Sprintf(\"invalid blockID: %s\", bErr)\n\t\ta.errorResponse(w, r, model.NewErrBadRequest(message))\n\t\treturn\n\t}\n\n\tsubNew, err := a.app.CreateSubscription(&sub)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\ta.logger.Debug(\"CREATE subscription\",\n\t\tmlog.String(\"subscriber_id\", subNew.SubscriberID),\n\t\tmlog.String(\"block_id\", subNew.BlockID),\n\t)\n\n\tjson, err := json.Marshal(subNew)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tjsonBytesResponse(w, http.StatusOK, json)\n\tauditRec.Success()\n}\n\nfunc (a *API) handleDeleteSubscription(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation DELETE /subscriptions/{blockID}/{subscriberID} deleteSubscription\n\t//\n\t// Deletes a subscription a user has for a a block. The user will no longer receive change notifications for the block.\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: blockID\n\t//   in: path\n\t//   description: Block ID\n\t//   required: true\n\t//   type: string\n\t// - name: subscriberID\n\t//   in: path\n\t//   description: Subscriber ID\n\t//   required: true\n\t//   type: string\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tctx := r.Context()\n\tsession := ctx.Value(sessionContextKey).(*model.Session)\n\n\tvars := mux.Vars(r)\n\tblockID := vars[\"blockID\"]\n\tsubscriberID := vars[\"subscriberID\"]\n\n\tauditRec := a.makeAuditRecord(r, \"deleteSubscription\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelModify, auditRec)\n\tauditRec.AddMeta(\"block_id\", blockID)\n\tauditRec.AddMeta(\"subscriber_id\", subscriberID)\n\n\t// User can only delete subscriptions for themselves\n\tif session.UserID != subscriberID {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied\"))\n\t\treturn\n\t}\n\n\tif _, err := a.app.DeleteSubscription(blockID, subscriberID); err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\ta.logger.Debug(\"DELETE subscription\",\n\t\tmlog.String(\"blockID\", blockID),\n\t\tmlog.String(\"subscriberID\", subscriberID),\n\t)\n\tjsonStringResponse(w, http.StatusOK, \"{}\")\n\n\tauditRec.Success()\n}\n\nfunc (a *API) handleGetSubscriptions(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation GET /subscriptions/{subscriberID} getSubscriptions\n\t//\n\t// Gets subscriptions for a user.\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: subscriberID\n\t//   in: path\n\t//   description: Subscriber ID\n\t//   required: true\n\t//   type: string\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       type: array\n\t//       items:\n\t//         \"$ref\": \"#/definitions/User\"\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\tctx := r.Context()\n\tsession := ctx.Value(sessionContextKey).(*model.Session)\n\n\tvars := mux.Vars(r)\n\tsubscriberID := vars[\"subscriberID\"]\n\n\tauditRec := a.makeAuditRecord(r, \"getSubscriptions\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelRead, auditRec)\n\tauditRec.AddMeta(\"subscriber_id\", subscriberID)\n\n\t// User can only get subscriptions for themselves (for now)\n\tif session.UserID != subscriberID {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied\"))\n\t\treturn\n\t}\n\n\tsubs, err := a.app.GetSubscriptions(subscriberID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\ta.logger.Debug(\"GET subscriptions\",\n\t\tmlog.String(\"subscriberID\", subscriberID),\n\t\tmlog.Int(\"count\", len(subs)),\n\t)\n\n\tjson, err := json.Marshal(subs)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\tjsonBytesResponse(w, http.StatusOK, json)\n\n\tauditRec.AddMeta(\"subscription_count\", len(subs))\n\tauditRec.Success()\n}\n"
  },
  {
    "path": "server/api/system.go",
    "content": "package api\n\nimport (\n\t\"encoding/json\"\n\t\"net/http\"\n\n\t\"github.com/gorilla/mux\"\n)\n\nfunc (a *API) registerSystemRoutes(r *mux.Router) {\n\t// System APIs\n\tr.HandleFunc(\"/hello\", a.handleHello).Methods(\"GET\")\n\tr.HandleFunc(\"/ping\", a.handlePing).Methods(\"GET\")\n}\n\nfunc (a *API) handleHello(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation GET /hello hello\n\t//\n\t// Responds with `Hello` if the web service is running.\n\t//\n\t// ---\n\t// produces:\n\t// - text/plain\n\t// responses:\n\t//   '200':\n\t//     description: success\n\tstringResponse(w, \"Hello\")\n}\n\nfunc (a *API) handlePing(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation GET /ping ping\n\t//\n\t// Responds with server metadata if the web service is running.\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// responses:\n\t//   '200':\n\t//     description: success\n\tserverMetadata := a.app.GetServerMetadata()\n\n\tif a.singleUserToken != \"\" {\n\t\tserverMetadata.SKU = \"personal_desktop\"\n\t}\n\n\tif serverMetadata.Edition == \"plugin\" {\n\t\tserverMetadata.SKU = \"suite\"\n\t}\n\n\tbytes, err := json.Marshal(serverMetadata)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t}\n\n\tjsonStringResponse(w, 200, string(bytes))\n}\n"
  },
  {
    "path": "server/api/system_test.go",
    "content": "package api\n\nimport (\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"runtime\"\n\t\"testing\"\n\n\t\"github.com/mattermost/focalboard/server/app\"\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nfunc TestHello(t *testing.T) {\n\ttestAPI := API{logger: mlog.CreateConsoleTestLogger(t)}\n\n\tt.Run(\"Returns 'Hello' on success\", func(t *testing.T) {\n\t\trequest, _ := http.NewRequest(http.MethodGet, \"/hello\", nil)\n\t\tresponse := httptest.NewRecorder()\n\n\t\ttestAPI.handleHello(response, request)\n\n\t\tgot := response.Body.String()\n\t\twant := \"Hello\"\n\n\t\tif got != want {\n\t\t\tt.Errorf(\"got %q want %q\", got, want)\n\t\t}\n\n\t\tif response.Code != http.StatusOK {\n\t\t\tt.Errorf(\"got HTTP %d want %d\", response.Code, http.StatusOK)\n\t\t}\n\t})\n}\n\nfunc TestPing(t *testing.T) {\n\ttestAPI := API{logger: mlog.CreateConsoleTestLogger(t)}\n\n\tt.Run(\"Returns metadata on success\", func(t *testing.T) {\n\t\trequest, _ := http.NewRequest(http.MethodGet, \"/ping\", nil)\n\t\tresponse := httptest.NewRecorder()\n\n\t\ttestAPI.handlePing(response, request)\n\n\t\tvar got app.ServerMetadata\n\t\terr := json.NewDecoder(response.Body).Decode(&got)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unable to JSON decode response body %q\", response.Body)\n\t\t}\n\n\t\twant := app.ServerMetadata{\n\t\t\tVersion:     model.CurrentVersion,\n\t\t\tBuildNumber: model.BuildNumber,\n\t\t\tBuildDate:   model.BuildDate,\n\t\t\tCommit:      model.BuildHash,\n\t\t\tEdition:     model.Edition,\n\t\t\tDBType:      \"\",\n\t\t\tDBVersion:   \"\",\n\t\t\tOSType:      runtime.GOOS,\n\t\t\tOSArch:      runtime.GOARCH,\n\t\t\tSKU:         \"personal_server\",\n\t\t}\n\n\t\tif got != want {\n\t\t\tt.Errorf(\"got %q want %q\", got, want)\n\t\t}\n\n\t\tif response.Code != http.StatusOK {\n\t\t\tt.Errorf(\"got HTTP %d want %d\", response.Code, http.StatusOK)\n\t\t}\n\t})\n\n\tt.Run(\"Sets SKU to 'personal_desktop' when in single-user mode\", func(t *testing.T) {\n\t\ttestAPI.singleUserToken = \"abc-123-xyz-456\"\n\t\trequest, _ := http.NewRequest(http.MethodGet, \"/ping\", nil)\n\t\tresponse := httptest.NewRecorder()\n\n\t\ttestAPI.handlePing(response, request)\n\n\t\tvar got app.ServerMetadata\n\t\terr := json.NewDecoder(response.Body).Decode(&got)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unable to JSON decode response body %q\", response.Body)\n\t\t}\n\n\t\twant := app.ServerMetadata{\n\t\t\tVersion:     model.CurrentVersion,\n\t\t\tBuildNumber: model.BuildNumber,\n\t\t\tBuildDate:   model.BuildDate,\n\t\t\tCommit:      model.BuildHash,\n\t\t\tEdition:     model.Edition,\n\t\t\tDBType:      \"\",\n\t\t\tDBVersion:   \"\",\n\t\t\tOSType:      runtime.GOOS,\n\t\t\tOSArch:      runtime.GOARCH,\n\t\t\tSKU:         \"personal_desktop\",\n\t\t}\n\n\t\tif got != want {\n\t\t\tt.Errorf(\"got %q want %q\", got, want)\n\t\t}\n\n\t\tif response.Code != http.StatusOK {\n\t\t\tt.Errorf(\"got HTTP %d want %d\", response.Code, http.StatusOK)\n\t\t}\n\t})\n\n\tt.Run(\"Sets SKU to 'suite' when in plugin mode\", func(t *testing.T) {\n\t\tmodel.Edition = \"plugin\"\n\t\trequest, _ := http.NewRequest(http.MethodGet, \"/ping\", nil)\n\t\tresponse := httptest.NewRecorder()\n\n\t\ttestAPI.handlePing(response, request)\n\n\t\tvar got app.ServerMetadata\n\t\terr := json.NewDecoder(response.Body).Decode(&got)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unable to JSON decode response body %q\", response.Body)\n\t\t}\n\n\t\twant := app.ServerMetadata{\n\t\t\tVersion:     model.CurrentVersion,\n\t\t\tBuildNumber: model.BuildNumber,\n\t\t\tBuildDate:   model.BuildDate,\n\t\t\tCommit:      model.BuildHash,\n\t\t\tEdition:     \"plugin\",\n\t\t\tDBType:      \"\",\n\t\t\tDBVersion:   \"\",\n\t\t\tOSType:      runtime.GOOS,\n\t\t\tOSArch:      runtime.GOARCH,\n\t\t\tSKU:         \"suite\",\n\t\t}\n\n\t\tif got != want {\n\t\t\tt.Errorf(\"got %q want %q\", got, want)\n\t\t}\n\n\t\tif response.Code != http.StatusOK {\n\t\t\tt.Errorf(\"got HTTP %d want %d\", response.Code, http.StatusOK)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "server/api/teams.go",
    "content": "package api\n\nimport (\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\n\t\"github.com/gorilla/mux\"\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/audit\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n)\n\nfunc (a *API) registerTeamsRoutes(r *mux.Router) {\n\t// Team APIs\n\tr.HandleFunc(\"/teams\", a.sessionRequired(a.handleGetTeams)).Methods(\"GET\")\n\tr.HandleFunc(\"/teams/{teamID}\", a.sessionRequired(a.handleGetTeam)).Methods(\"GET\")\n\tr.HandleFunc(\"/teams/{teamID}/users\", a.sessionRequired(a.handleGetTeamUsers)).Methods(\"GET\")\n\tr.HandleFunc(\"/teams/{teamID}/users\", a.sessionRequired(a.handleGetTeamUsersByID)).Methods(\"POST\")\n\tr.HandleFunc(\"/teams/{teamID}/archive/export\", a.sessionRequired(a.handleArchiveExportTeam)).Methods(\"GET\")\n}\n\nfunc (a *API) handleGetTeams(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation GET /teams getTeams\n\t//\n\t// Returns information of all the teams\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       type: array\n\t//       items:\n\t//         \"$ref\": \"#/definitions/Team\"\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tuserID := getUserID(r)\n\n\tteams, err := a.app.GetTeamsForUser(userID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"getTeams\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelRead, auditRec)\n\tauditRec.AddMeta(\"teamCount\", len(teams))\n\n\tdata, err := json.Marshal(teams)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tjsonBytesResponse(w, http.StatusOK, data)\n\tauditRec.Success()\n}\n\nfunc (a *API) handleGetTeam(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation GET /teams/{teamID} getTeam\n\t//\n\t// Returns information of the root team\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: teamID\n\t//   in: path\n\t//   description: Team ID\n\t//   required: true\n\t//   type: string\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/Team\"\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tvars := mux.Vars(r)\n\tteamID := vars[\"teamID\"]\n\tuserID := getUserID(r)\n\n\tif !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to team\"))\n\t\treturn\n\t}\n\n\tvar team *model.Team\n\tvar err error\n\n\tif a.MattermostAuth {\n\t\tteam, err = a.app.GetTeam(teamID)\n\t\tif model.IsErrNotFound(err) {\n\t\t\ta.errorResponse(w, r, model.NewErrUnauthorized(\"invalid team\"))\n\t\t}\n\t\tif err != nil {\n\t\t\ta.errorResponse(w, r, err)\n\t\t}\n\t} else {\n\t\tteam, err = a.app.GetRootTeam()\n\t\tif err != nil {\n\t\t\ta.errorResponse(w, r, err)\n\t\t\treturn\n\t\t}\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"getTeam\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelRead, auditRec)\n\tauditRec.AddMeta(\"resultTeamID\", team.ID)\n\n\tdata, err := json.Marshal(team)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tjsonBytesResponse(w, http.StatusOK, data)\n\tauditRec.Success()\n}\n\nfunc (a *API) handlePostTeamRegenerateSignupToken(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation POST /teams/{teamID}/regenerate_signup_token regenerateSignupToken\n\t//\n\t// Regenerates the signup token for the root team\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: teamID\n\t//   in: path\n\t//   description: Team ID\n\t//   required: true\n\t//   type: string\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\tif a.MattermostAuth {\n\t\ta.errorResponse(w, r, model.NewErrNotImplemented(\"not permitted in plugin mode\"))\n\t\treturn\n\t}\n\n\tteam, err := a.app.GetRootTeam()\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"regenerateSignupToken\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelModify, auditRec)\n\n\tteam.SignupToken = utils.NewID(utils.IDTypeToken)\n\n\tif err = a.app.UpsertTeamSignupToken(*team); err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tjsonStringResponse(w, http.StatusOK, \"{}\")\n\tauditRec.Success()\n}\n\nfunc (a *API) handleGetTeamUsers(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation GET /teams/{teamID}/users getTeamUsers\n\t//\n\t// Returns team users\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: teamID\n\t//   in: path\n\t//   description: Team ID\n\t//   required: true\n\t//   type: string\n\t// - name: search\n\t//   in: query\n\t//   description: string to filter users list\n\t//   required: false\n\t//   type: string\n\t// - name: exclude_bots\n\t//   in: query\n\t//   description: exclude bot users\n\t//   required: false\n\t//   type: boolean\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       type: array\n\t//       items:\n\t//         \"$ref\": \"#/definitions/User\"\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tvars := mux.Vars(r)\n\tteamID := vars[\"teamID\"]\n\tuserID := getUserID(r)\n\tquery := r.URL.Query()\n\tsearchQuery := query.Get(\"search\")\n\texcludeBots := r.URL.Query().Get(\"exclude_bots\") == True\n\n\tif !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to team\"))\n\t\treturn\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"getUsers\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelRead, auditRec)\n\n\tisGuest, err := a.userIsGuest(userID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\tasGuestUser := \"\"\n\tif isGuest {\n\t\tasGuestUser = userID\n\t}\n\n\tusers, err := a.app.SearchTeamUsers(teamID, searchQuery, asGuestUser, excludeBots)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tdata, err := json.Marshal(users)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tjsonBytesResponse(w, http.StatusOK, data)\n\n\tauditRec.AddMeta(\"userCount\", len(users))\n\tauditRec.Success()\n}\n\nfunc (a *API) handleGetTeamUsersByID(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation POST /teams/{teamID}/users getTeamUsersByID\n\t//\n\t// Returns a user[]\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: teamID\n\t//   in: path\n\t//   description: Team ID\n\t//   required: true\n\t//   type: string\n\t// - name: Body\n\t//   in: body\n\t//   description: []UserIDs to return\n\t//   required: true\n\t//   type: []string\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       type: array\n\t//       items:\n\t//         \"$ref\": \"#/definitions/User\"\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\trequestBody, err := io.ReadAll(r.Body)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tvar userIDs []string\n\tif err = json.Unmarshal(requestBody, &userIDs); err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"getTeamUsersByID\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelRead, auditRec)\n\n\tvars := mux.Vars(r)\n\tteamID := vars[\"teamID\"]\n\tuserID := getUserID(r)\n\n\tif !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to team\"))\n\t\treturn\n\t}\n\n\tvar users []*model.User\n\tvar error error\n\n\tif len(userIDs) == 0 {\n\t\ta.errorResponse(w, r, model.NewErrBadRequest(\"User IDs are empty\"))\n\t\treturn\n\t}\n\n\tif userIDs[0] == model.SingleUser {\n\t\tws, _ := a.app.GetRootTeam()\n\t\tnow := utils.GetMillis()\n\t\tuser := &model.User{\n\t\t\tID:       model.SingleUser,\n\t\t\tUsername: model.SingleUser,\n\t\t\tEmail:    model.SingleUser,\n\t\t\tCreateAt: ws.UpdateAt,\n\t\t\tUpdateAt: now,\n\t\t}\n\t\tusers = append(users, user)\n\t} else {\n\t\tusers, error = a.app.GetUsersList(userIDs)\n\t\tif error != nil {\n\t\t\ta.errorResponse(w, r, error)\n\t\t\treturn\n\t\t}\n\n\t\tfor i, u := range users {\n\t\t\tif a.permissions.HasPermissionToTeam(u.ID, teamID, model.PermissionManageTeam) {\n\t\t\t\tusers[i].Permissions = append(users[i].Permissions, model.PermissionManageTeam.Id)\n\t\t\t}\n\t\t\tif a.permissions.HasPermissionTo(u.ID, model.PermissionManageSystem) {\n\t\t\t\tusers[i].Permissions = append(users[i].Permissions, model.PermissionManageSystem.Id)\n\t\t\t}\n\t\t}\n\t}\n\n\tusersList, err := json.Marshal(users)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tjsonStringResponse(w, http.StatusOK, string(usersList))\n\tauditRec.Success()\n}\n"
  },
  {
    "path": "server/api/templates.go",
    "content": "package api\n\nimport (\n\t\"encoding/json\"\n\t\"net/http\"\n\n\t\"github.com/gorilla/mux\"\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/audit\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nfunc (a *API) registerTemplatesRoutes(r *mux.Router) {\n\tr.HandleFunc(\"/teams/{teamID}/templates\", a.sessionRequired(a.handleGetTemplates)).Methods(\"GET\")\n}\n\nfunc (a *API) handleGetTemplates(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation GET /teams/{teamID}/templates getTemplates\n\t//\n\t// Returns team templates\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: teamID\n\t//   in: path\n\t//   description: Team ID\n\t//   required: true\n\t//   type: string\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       type: array\n\t//       items:\n\t//         \"$ref\": \"#/definitions/Board\"\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tteamID := mux.Vars(r)[\"teamID\"]\n\tuserID := getUserID(r)\n\n\tif teamID != model.GlobalTeamID && !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to team\"))\n\t\treturn\n\t}\n\n\tisGuest, err := a.userIsGuest(userID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\tif isGuest {\n\t\ta.errorResponse(w, r, model.NewErrPermission(\"access denied to templates\"))\n\t\treturn\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"getTemplates\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelRead, auditRec)\n\tauditRec.AddMeta(\"teamID\", teamID)\n\n\t// retrieve boards list\n\tboards, err := a.app.GetTemplateBoards(teamID, userID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tresults := []*model.Board{}\n\tfor _, board := range boards {\n\t\tif board.Type == model.BoardTypeOpen {\n\t\t\tresults = append(results, board)\n\t\t} else if a.permissions.HasPermissionToBoard(userID, board.ID, model.PermissionViewBoard) {\n\t\t\tresults = append(results, board)\n\t\t}\n\t}\n\n\ta.logger.Debug(\"GetTemplates\",\n\t\tmlog.String(\"teamID\", teamID),\n\t\tmlog.Int(\"boardsCount\", len(results)),\n\t)\n\n\tdata, err := json.Marshal(results)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\t// response\n\tjsonBytesResponse(w, http.StatusOK, data)\n\n\tauditRec.AddMeta(\"templatesCount\", len(results))\n\tauditRec.Success()\n}\n"
  },
  {
    "path": "server/api/users.go",
    "content": "package api\n\nimport (\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\n\t\"github.com/gorilla/mux\"\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/audit\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n)\n\nfunc (a *API) registerUsersRoutes(r *mux.Router) {\n\t// Users APIs\n\tr.HandleFunc(\"/users\", a.sessionRequired(a.handleGetUsersList)).Methods(\"POST\")\n\tr.HandleFunc(\"/users/me\", a.sessionRequired(a.handleGetMe)).Methods(\"GET\")\n\tr.HandleFunc(\"/users/me/memberships\", a.sessionRequired(a.handleGetMyMemberships)).Methods(\"GET\")\n\tr.HandleFunc(\"/users/{userID}\", a.sessionRequired(a.handleGetUser)).Methods(\"GET\")\n\tr.HandleFunc(\"/users/{userID}/config\", a.sessionRequired(a.handleUpdateUserConfig)).Methods(http.MethodPut)\n\tr.HandleFunc(\"/users/me/config\", a.sessionRequired(a.handleGetUserPreferences)).Methods(http.MethodGet)\n}\n\nfunc (a *API) handleGetUsersList(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation POST /users getUsersList\n\t//\n\t// Returns a user[]\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: userID\n\t//   in: path\n\t//   description: User ID\n\t//   required: true\n\t//   type: string\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/User\"\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\trequestBody, err := io.ReadAll(r.Body)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tvar userIDs []string\n\tif err = json.Unmarshal(requestBody, &userIDs); err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tauditRec := a.makeAuditRecord(r, \"getUsersList\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelAuth, auditRec)\n\n\tvar users []*model.User\n\tvar error error\n\n\tif len(userIDs) == 0 {\n\t\ta.errorResponse(w, r, model.NewErrBadRequest(\"User IDs are empty\"))\n\t\treturn\n\t}\n\n\tif userIDs[0] == model.SingleUser {\n\t\tws, _ := a.app.GetRootTeam()\n\t\tnow := utils.GetMillis()\n\t\tuser := &model.User{\n\t\t\tID:       model.SingleUser,\n\t\t\tUsername: model.SingleUser,\n\t\t\tEmail:    model.SingleUser,\n\t\t\tCreateAt: ws.UpdateAt,\n\t\t\tUpdateAt: now,\n\t\t}\n\t\tusers = append(users, user)\n\t} else {\n\t\tusers, error = a.app.GetUsersList(userIDs)\n\t\tif error != nil {\n\t\t\ta.errorResponse(w, r, error)\n\t\t\treturn\n\t\t}\n\t}\n\n\tctx := r.Context()\n\tsession := ctx.Value(sessionContextKey).(*model.Session)\n\tisSystemAdmin := a.permissions.HasPermissionTo(session.UserID, model.PermissionManageSystem)\n\n\tsanitizedUsers := make([]*model.User, 0)\n\tfor _, user := range users {\n\t\tcanSeeUser, err2 := a.app.CanSeeUser(session.UserID, user.ID)\n\t\tif err2 != nil {\n\t\t\ta.errorResponse(w, r, err2)\n\t\t\treturn\n\t\t}\n\t\tif !canSeeUser {\n\t\t\tcontinue\n\t\t}\n\t\tif user.ID == session.UserID {\n\t\t\tuser.Sanitize(map[string]bool{})\n\t\t} else {\n\t\t\ta.app.SanitizeProfile(user, isSystemAdmin)\n\t\t}\n\t\tsanitizedUsers = append(sanitizedUsers, user)\n\t}\n\n\tusersList, err := json.Marshal(sanitizedUsers)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tjsonStringResponse(w, http.StatusOK, string(usersList))\n\tauditRec.Success()\n}\n\nfunc (a *API) handleGetMe(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation GET /users/me getMe\n\t//\n\t// Returns the currently logged-in user\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: teamID\n\t//   in: path\n\t//   description: Team ID\n\t//   required: false\n\t//   type: string\n\t// - name: channelID\n\t//   in: path\n\t//   description: Channel ID\n\t//   required: false\n\t//   type: string\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/User\"\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\tquery := r.URL.Query()\n\tteamID := query.Get(\"teamID\")\n\tchannelID := query.Get(\"channelID\")\n\n\tuserID := getUserID(r)\n\n\tvar user *model.User\n\tvar err error\n\n\tauditRec := a.makeAuditRecord(r, \"getMe\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelRead, auditRec)\n\n\tif userID == model.SingleUser {\n\t\tws, _ := a.app.GetRootTeam()\n\t\tnow := utils.GetMillis()\n\t\tuser = &model.User{\n\t\t\tID:       model.SingleUser,\n\t\t\tUsername: model.SingleUser,\n\t\t\tEmail:    model.SingleUser,\n\t\t\tCreateAt: ws.UpdateAt,\n\t\t\tUpdateAt: now,\n\t\t}\n\t} else {\n\t\tuser, err = a.app.GetUser(userID)\n\t\tif err != nil {\n\t\t\t// ToDo: wrap with an invalid token error\n\t\t\ta.errorResponse(w, r, err)\n\t\t\treturn\n\t\t}\n\t}\n\n\tif teamID != \"\" && a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionManageTeam) {\n\t\tuser.Permissions = append(user.Permissions, model.PermissionManageTeam.Id)\n\t}\n\tif a.permissions.HasPermissionTo(userID, model.PermissionManageSystem) {\n\t\tuser.Permissions = append(user.Permissions, model.PermissionManageSystem.Id)\n\t}\n\tif channelID != \"\" && a.permissions.HasPermissionToChannel(userID, channelID, model.PermissionCreatePost) {\n\t\tuser.Permissions = append(user.Permissions, model.PermissionCreatePost.Id)\n\t}\n\n\tuser.Sanitize(map[string]bool{})\n\tuserData, err := json.Marshal(user)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\tjsonBytesResponse(w, http.StatusOK, userData)\n\n\tauditRec.AddMeta(\"userID\", user.ID)\n\tauditRec.Success()\n}\n\nfunc (a *API) handleGetMyMemberships(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation GET /users/me/memberships getMyMemberships\n\t//\n\t// Returns the currently users board memberships\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       type: array\n\t//       items:\n\t//         \"$ref\": \"#/definitions/BoardMember\"\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tuserID := getUserID(r)\n\n\tauditRec := a.makeAuditRecord(r, \"getMyBoardMemberships\", audit.Fail)\n\tauditRec.AddMeta(\"userID\", userID)\n\tdefer a.audit.LogRecord(audit.LevelRead, auditRec)\n\n\tmembers, err := a.app.GetMembersForUser(userID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tmembersData, err := json.Marshal(members)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tjsonBytesResponse(w, http.StatusOK, membersData)\n\n\tauditRec.Success()\n}\n\nfunc (a *API) handleGetUser(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation GET /users/{userID} getUser\n\t//\n\t// Returns a user\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: userID\n\t//   in: path\n\t//   description: User ID\n\t//   required: true\n\t//   type: string\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/User\"\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tvars := mux.Vars(r)\n\tuserID := vars[\"userID\"]\n\n\tauditRec := a.makeAuditRecord(r, \"postBlocks\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelRead, auditRec)\n\tauditRec.AddMeta(\"userID\", userID)\n\n\tuser, err := a.app.GetUser(userID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tctx := r.Context()\n\tsession := ctx.Value(sessionContextKey).(*model.Session)\n\n\tcanSeeUser, err := a.app.CanSeeUser(session.UserID, userID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\tif !canSeeUser {\n\t\ta.errorResponse(w, r, model.NewErrNotFound(\"user ID=\"+userID))\n\t\treturn\n\t}\n\n\tif userID == session.UserID {\n\t\tuser.Sanitize(map[string]bool{})\n\t} else {\n\t\ta.app.SanitizeProfile(user, a.permissions.HasPermissionTo(session.UserID, model.PermissionManageSystem))\n\t}\n\n\tuserData, err := json.Marshal(user)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tjsonBytesResponse(w, http.StatusOK, userData)\n\tauditRec.Success()\n}\n\nfunc (a *API) handleUpdateUserConfig(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation PATCH /users/{userID}/config updateUserConfig\n\t//\n\t// Updates user config\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// parameters:\n\t// - name: userID\n\t//   in: path\n\t//   description: User ID\n\t//   required: true\n\t//   type: string\n\t// - name: Body\n\t//   in: body\n\t//   description: User config patch to apply\n\t//   required: true\n\t//   schema:\n\t//     \"$ref\": \"#/definitions/UserPreferencesPatch\"\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\trequestBody, err := io.ReadAll(r.Body)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tvar patch *model.UserPreferencesPatch\n\terr = json.Unmarshal(requestBody, &patch)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tvars := mux.Vars(r)\n\tuserID := vars[\"userID\"]\n\n\tctx := r.Context()\n\tsession := ctx.Value(sessionContextKey).(*model.Session)\n\n\tauditRec := a.makeAuditRecord(r, \"updateUserConfig\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelModify, auditRec)\n\n\t// a user can update only own config\n\tif userID != session.UserID {\n\t\ta.errorResponse(w, r, model.NewErrForbidden(\"\"))\n\t\treturn\n\t}\n\n\tupdatedConfig, err := a.app.UpdateUserConfig(userID, *patch)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tdata, err := json.Marshal(updatedConfig)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tjsonBytesResponse(w, http.StatusOK, data)\n\tauditRec.Success()\n}\n\nfunc (a *API) handleGetUserPreferences(w http.ResponseWriter, r *http.Request) {\n\t// swagger:operation GET /users/me/config getUserConfig\n\t//\n\t// Returns an array of user preferences\n\t//\n\t// ---\n\t// produces:\n\t// - application/json\n\t// security:\n\t// - BearerAuth: []\n\t// responses:\n\t//   '200':\n\t//     description: success\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/Preferences\"\n\t//   default:\n\t//     description: internal error\n\t//     schema:\n\t//       \"$ref\": \"#/definitions/ErrorResponse\"\n\n\tuserID := getUserID(r)\n\n\tauditRec := a.makeAuditRecord(r, \"getUserConfig\", audit.Fail)\n\tdefer a.audit.LogRecord(audit.LevelRead, auditRec)\n\n\tpreferences, err := a.app.GetUserPreferences(userID)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tdata, err := json.Marshal(preferences)\n\tif err != nil {\n\t\ta.errorResponse(w, r, err)\n\t\treturn\n\t}\n\n\tjsonBytesResponse(w, http.StatusOK, data)\n\tauditRec.Success()\n}\n"
  },
  {
    "path": "server/app/app.go",
    "content": "package app\n\nimport (\n\t\"io\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/mattermost/focalboard/server/auth\"\n\t\"github.com/mattermost/focalboard/server/services/config\"\n\t\"github.com/mattermost/focalboard/server/services/metrics\"\n\t\"github.com/mattermost/focalboard/server/services/notify\"\n\t\"github.com/mattermost/focalboard/server/services/permissions\"\n\t\"github.com/mattermost/focalboard/server/services/store\"\n\t\"github.com/mattermost/focalboard/server/services/webhook\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n\t\"github.com/mattermost/focalboard/server/ws\"\n\n\tmm_model \"github.com/mattermost/mattermost/server/public/model\"\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n\t\"github.com/mattermost/mattermost/server/v8/platform/shared/filestore\"\n)\n\nconst (\n\tblockChangeNotifierQueueSize       = 1000\n\tblockChangeNotifierPoolSize        = 10\n\tblockChangeNotifierShutdownTimeout = time.Second * 10\n)\n\ntype servicesAPI interface {\n\tGetUsersFromProfiles(options *mm_model.UserGetOptions) ([]*mm_model.User, error)\n}\n\ntype ReadCloseSeeker = filestore.ReadCloseSeeker\n\ntype fileBackend interface {\n\tReader(path string) (ReadCloseSeeker, error)\n\tFileExists(path string) (bool, error)\n\tCopyFile(oldPath, newPath string) error\n\tMoveFile(oldPath, newPath string) error\n\tWriteFile(fr io.Reader, path string) (int64, error)\n\tRemoveFile(path string) error\n}\n\ntype Services struct {\n\tAuth             *auth.Auth\n\tStore            store.Store\n\tFilesBackend     fileBackend\n\tWebhook          *webhook.Client\n\tMetrics          *metrics.Metrics\n\tNotifications    *notify.Service\n\tLogger           mlog.LoggerIFace\n\tPermissions      permissions.PermissionsService\n\tSkipTemplateInit bool\n\tServicesAPI      servicesAPI\n}\n\ntype App struct {\n\tconfig              *config.Configuration\n\tstore               store.Store\n\tauth                *auth.Auth\n\twsAdapter           ws.Adapter\n\tfilesBackend        fileBackend\n\twebhook             *webhook.Client\n\tmetrics             *metrics.Metrics\n\tnotifications       *notify.Service\n\tlogger              mlog.LoggerIFace\n\tpermissions         permissions.PermissionsService\n\tblockChangeNotifier *utils.CallbackQueue\n\tservicesAPI         servicesAPI\n\n\tcardLimitMux sync.RWMutex\n\tcardLimit    int\n}\n\nfunc (a *App) SetConfig(config *config.Configuration) {\n\ta.config = config\n}\n\nfunc (a *App) GetConfig() *config.Configuration {\n\treturn a.config\n}\n\nfunc New(config *config.Configuration, wsAdapter ws.Adapter, services Services) *App {\n\tapp := &App{\n\t\tconfig:              config,\n\t\tstore:               services.Store,\n\t\tauth:                services.Auth,\n\t\twsAdapter:           wsAdapter,\n\t\tfilesBackend:        services.FilesBackend,\n\t\twebhook:             services.Webhook,\n\t\tmetrics:             services.Metrics,\n\t\tnotifications:       services.Notifications,\n\t\tlogger:              services.Logger,\n\t\tpermissions:         services.Permissions,\n\t\tblockChangeNotifier: utils.NewCallbackQueue(\"blockChangeNotifier\", blockChangeNotifierQueueSize, blockChangeNotifierPoolSize, services.Logger),\n\t\tservicesAPI:         services.ServicesAPI,\n\t}\n\tapp.initialize(services.SkipTemplateInit)\n\treturn app\n}\n\nfunc (a *App) CardLimit() int {\n\ta.cardLimitMux.RLock()\n\tdefer a.cardLimitMux.RUnlock()\n\treturn a.cardLimit\n}\n\nfunc (a *App) SetCardLimit(cardLimit int) {\n\ta.cardLimitMux.Lock()\n\tdefer a.cardLimitMux.Unlock()\n\ta.cardLimit = cardLimit\n}\n\nfunc (a *App) GetLicense() *mm_model.License {\n\treturn a.store.GetLicense()\n}\n"
  },
  {
    "path": "server/app/app_test.go",
    "content": "package app\n\nimport (\n\t\"testing\"\n\n\t\"github.com/mattermost/focalboard/server/services/config\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestSetConfig(t *testing.T) {\n\tth, tearDown := SetupTestHelper(t)\n\tdefer tearDown()\n\n\tt.Run(\"Test Update Config\", func(t *testing.T) {\n\t\trequire.False(t, th.App.config.EnablePublicSharedBoards)\n\t\tnewConfiguration := config.Configuration{}\n\t\tnewConfiguration.EnablePublicSharedBoards = true\n\t\tth.App.SetConfig(&newConfiguration)\n\n\t\trequire.True(t, th.App.config.EnablePublicSharedBoards)\n\t})\n}\n"
  },
  {
    "path": "server/app/auth.go",
    "content": "package app\n\nimport (\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/auth\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n\n\t\"github.com/pkg/errors\"\n)\n\nconst (\n\tDaysPerMonth     = 30\n\tDaysPerWeek      = 7\n\tHoursPerDay      = 24\n\tMinutesPerHour   = 60\n\tSecondsPerMinute = 60\n)\n\n// GetSession Get a user active session and refresh the session if is needed.\nfunc (a *App) GetSession(token string) (*model.Session, error) {\n\treturn a.auth.GetSession(token)\n}\n\n// IsValidReadToken validates the read token for a block.\nfunc (a *App) IsValidReadToken(boardID string, readToken string) (bool, error) {\n\treturn a.auth.IsValidReadToken(boardID, readToken)\n}\n\n// GetRegisteredUserCount returns the number of registered users.\nfunc (a *App) GetRegisteredUserCount() (int, error) {\n\treturn a.store.GetRegisteredUserCount()\n}\n\n// GetDailyActiveUsers returns the number of daily active users.\nfunc (a *App) GetDailyActiveUsers() (int, error) {\n\tsecondsAgo := int64(SecondsPerMinute * MinutesPerHour * HoursPerDay)\n\treturn a.store.GetActiveUserCount(secondsAgo)\n}\n\n// GetWeeklyActiveUsers returns the number of weekly active users.\nfunc (a *App) GetWeeklyActiveUsers() (int, error) {\n\tsecondsAgo := int64(SecondsPerMinute * MinutesPerHour * HoursPerDay * DaysPerWeek)\n\treturn a.store.GetActiveUserCount(secondsAgo)\n}\n\n// GetMonthlyActiveUsers returns the number of monthly active users.\nfunc (a *App) GetMonthlyActiveUsers() (int, error) {\n\tsecondsAgo := int64(SecondsPerMinute * MinutesPerHour * HoursPerDay * DaysPerMonth)\n\treturn a.store.GetActiveUserCount(secondsAgo)\n}\n\n// GetUser gets an existing active user by id.\nfunc (a *App) GetUser(id string) (*model.User, error) {\n\tif len(id) < 1 {\n\t\treturn nil, errors.New(\"no user ID\")\n\t}\n\n\tuser, err := a.store.GetUserByID(id)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"unable to find user\")\n\t}\n\treturn user, nil\n}\n\nfunc (a *App) GetUsersList(userIDs []string) ([]*model.User, error) {\n\tif len(userIDs) == 0 {\n\t\treturn nil, errors.New(\"No User IDs\")\n\t}\n\n\tusers, err := a.store.GetUsersList(userIDs, a.config.ShowEmailAddress, a.config.ShowFullName)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"unable to find users\")\n\t}\n\treturn users, nil\n}\n\n// Login create a new user session if the authentication data is valid.\nfunc (a *App) Login(username, email, password, mfaToken string) (string, error) {\n\tvar user *model.User\n\tif username != \"\" {\n\t\tvar err error\n\t\tuser, err = a.store.GetUserByUsername(username)\n\t\tif err != nil && !model.IsErrNotFound(err) {\n\t\t\ta.metrics.IncrementLoginFailCount(1)\n\t\t\treturn \"\", errors.Wrap(err, \"invalid username or password\")\n\t\t}\n\t}\n\n\tif user == nil && email != \"\" {\n\t\tvar err error\n\t\tuser, err = a.store.GetUserByEmail(email)\n\t\tif err != nil && model.IsErrNotFound(err) {\n\t\t\ta.metrics.IncrementLoginFailCount(1)\n\t\t\treturn \"\", errors.Wrap(err, \"invalid username or password\")\n\t\t}\n\t}\n\n\tif user == nil {\n\t\ta.metrics.IncrementLoginFailCount(1)\n\t\treturn \"\", errors.New(\"invalid username or password\")\n\t}\n\n\tif !auth.ComparePassword(user.Password, password) {\n\t\ta.metrics.IncrementLoginFailCount(1)\n\t\ta.logger.Debug(\"Invalid password for user\", mlog.String(\"userID\", user.ID))\n\t\treturn \"\", errors.New(\"invalid username or password\")\n\t}\n\n\tauthService := user.AuthService\n\tif authService == \"\" {\n\t\tauthService = \"native\"\n\t}\n\n\tsession := model.Session{\n\t\tID:          utils.NewID(utils.IDTypeSession),\n\t\tToken:       utils.NewID(utils.IDTypeToken),\n\t\tUserID:      user.ID,\n\t\tAuthService: authService,\n\t\tProps:       map[string]interface{}{},\n\t}\n\terr := a.store.CreateSession(&session)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"unable to create session\")\n\t}\n\n\ta.metrics.IncrementLoginCount(1)\n\n\t// TODO: MFA verification\n\treturn session.Token, nil\n}\n\n// Logout invalidates the user session.\nfunc (a *App) Logout(sessionID string) error {\n\terr := a.store.DeleteSession(sessionID)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"unable to delete the session\")\n\t}\n\n\ta.metrics.IncrementLogoutCount(1)\n\n\treturn nil\n}\n\n// RegisterUser creates a new user if the provided data is valid.\nfunc (a *App) RegisterUser(username, email, password string) error {\n\tvar user *model.User\n\tif username != \"\" {\n\t\tvar err error\n\t\tuser, err = a.store.GetUserByUsername(username)\n\t\tif err != nil && !model.IsErrNotFound(err) {\n\t\t\treturn err\n\t\t}\n\t\tif user != nil {\n\t\t\treturn errors.New(\"The username already exists\")\n\t\t}\n\t}\n\n\tif user == nil && email != \"\" {\n\t\tvar err error\n\t\tuser, err = a.store.GetUserByEmail(email)\n\t\tif err != nil && !model.IsErrNotFound(err) {\n\t\t\treturn err\n\t\t}\n\t\tif user != nil {\n\t\t\treturn errors.New(\"The email already exists\")\n\t\t}\n\t}\n\n\t// TODO: Move this into the config\n\tpasswordSettings := auth.PasswordSettings{\n\t\tMinimumLength: 6,\n\t}\n\n\terr := auth.IsPasswordValid(password, passwordSettings)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"Invalid password\")\n\t}\n\n\t_, err = a.store.CreateUser(&model.User{\n\t\tID:          utils.NewID(utils.IDTypeUser),\n\t\tUsername:    username,\n\t\tEmail:       email,\n\t\tPassword:    auth.HashPassword(password),\n\t\tMfaSecret:   \"\",\n\t\tAuthService: a.config.AuthMode,\n\t\tAuthData:    \"\",\n\t})\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"Unable to create the new user\")\n\t}\n\n\treturn nil\n}\n\nfunc (a *App) UpdateUserPassword(username, password string) error {\n\terr := a.store.UpdateUserPassword(username, auth.HashPassword(password))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (a *App) ChangePassword(userID, oldPassword, newPassword string) error {\n\tvar user *model.User\n\tif userID != \"\" {\n\t\tvar err error\n\t\tuser, err = a.store.GetUserByID(userID)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"invalid username or password\")\n\t\t}\n\t}\n\n\tif user == nil {\n\t\treturn errors.New(\"invalid username or password\")\n\t}\n\n\tif !auth.ComparePassword(user.Password, oldPassword) {\n\t\ta.logger.Debug(\"Invalid password for user\", mlog.String(\"userID\", user.ID))\n\t\treturn errors.New(\"invalid username or password\")\n\t}\n\n\terr := a.store.UpdateUserPasswordByID(userID, auth.HashPassword(newPassword))\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"unable to update password\")\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "server/app/auth_test.go",
    "content": "package app\n\nimport (\n\t\"testing\"\n\n\t\"github.com/golang/mock/gomock\"\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/auth\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nvar mockUser = &model.User{\n\tID:       utils.NewID(utils.IDTypeUser),\n\tUsername: \"testUsername\",\n\tEmail:    \"testEmail\",\n\tPassword: auth.HashPassword(\"testPassword\"),\n}\n\nfunc TestLogin(t *testing.T) {\n\tth, tearDown := SetupTestHelper(t)\n\tdefer tearDown()\n\n\ttestcases := []struct {\n\t\ttitle    string\n\t\tuserName string\n\t\temail    string\n\t\tpassword string\n\t\tmfa      string\n\t\tisError  bool\n\t}{\n\t\t{\"fail, missing login information\", \"\", \"\", \"\", \"\", true},\n\t\t{\"fail, invalid username\", \"badUsername\", \"\", \"\", \"\", true},\n\t\t{\"fail, invalid email\", \"\", \"badEmail\", \"\", \"\", true},\n\t\t{\"fail, invalid password\", \"testUsername\", \"\", \"badPassword\", \"\", true},\n\t\t{\"success, using username\", \"testUsername\", \"\", \"testPassword\", \"\", false},\n\t\t{\"success, using email\", \"\", \"testEmail\", \"testPassword\", \"\", false},\n\t}\n\n\tth.Store.EXPECT().GetUserByUsername(\"badUsername\").Return(nil, errors.New(\"Bad Username\"))\n\tth.Store.EXPECT().GetUserByEmail(\"badEmail\").Return(nil, errors.New(\"Bad Email\"))\n\tth.Store.EXPECT().GetUserByUsername(\"testUsername\").Return(mockUser, nil).Times(2)\n\tth.Store.EXPECT().GetUserByEmail(\"testEmail\").Return(mockUser, nil)\n\tth.Store.EXPECT().CreateSession(gomock.Any()).Return(nil).Times(2)\n\n\tfor _, test := range testcases {\n\t\tt.Run(test.title, func(t *testing.T) {\n\t\t\ttoken, err := th.App.Login(test.userName, test.email, test.password, test.mfa)\n\t\t\tif test.isError {\n\t\t\t\trequire.Error(t, err)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.NotNil(t, token)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGetUser(t *testing.T) {\n\tth, tearDown := SetupTestHelper(t)\n\tdefer tearDown()\n\n\ttestcases := []struct {\n\t\ttitle   string\n\t\tid      string\n\t\tisError bool\n\t}{\n\t\t{\"fail, missing id\", \"\", true},\n\t\t{\"fail, invalid id\", \"badID\", true},\n\t\t{\"success\", \"goodID\", false},\n\t}\n\n\tth.Store.EXPECT().GetUserByID(\"badID\").Return(nil, errors.New(\"Bad Id\"))\n\tth.Store.EXPECT().GetUserByID(\"goodID\").Return(mockUser, nil)\n\n\tfor _, test := range testcases {\n\t\tt.Run(test.title, func(t *testing.T) {\n\t\t\ttoken, err := th.App.GetUser(test.id)\n\t\t\tif test.isError {\n\t\t\t\trequire.Error(t, err)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.NotNil(t, token)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestRegisterUser(t *testing.T) {\n\tth, tearDown := SetupTestHelper(t)\n\tdefer tearDown()\n\n\ttestcases := []struct {\n\t\ttitle    string\n\t\tuserName string\n\t\temail    string\n\t\tpassword string\n\t\tisError  bool\n\t}{\n\t\t{\"fail, missing login information\", \"\", \"\", \"\", true},\n\t\t{\"fail, username exists\", \"existingUsername\", \"\", \"\", true},\n\t\t{\"fail, email exists\", \"\", \"existingEmail\", \"\", true},\n\t\t{\"fail, invalid password\", \"newUsername\", \"\", \"test\", true},\n\t\t{\"success, using email\", \"\", \"newEmail\", \"testPassword\", false},\n\t}\n\n\tth.Store.EXPECT().GetUserByUsername(\"existingUsername\").Return(mockUser, nil)\n\tth.Store.EXPECT().GetUserByUsername(\"newUsername\").Return(mockUser, errors.New(\"user not found\"))\n\tth.Store.EXPECT().GetUserByEmail(\"existingEmail\").Return(mockUser, nil)\n\tth.Store.EXPECT().GetUserByEmail(\"newEmail\").Return(nil, model.NewErrNotFound(\"user\"))\n\tth.Store.EXPECT().CreateUser(gomock.Any()).Return(nil, nil)\n\n\tfor _, test := range testcases {\n\t\tt.Run(test.title, func(t *testing.T) {\n\t\t\terr := th.App.RegisterUser(test.userName, test.email, test.password)\n\t\t\tif test.isError {\n\t\t\t\trequire.Error(t, err)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUpdateUserPassword(t *testing.T) {\n\tth, tearDown := SetupTestHelper(t)\n\tdefer tearDown()\n\n\ttestcases := []struct {\n\t\ttitle    string\n\t\tuserName string\n\t\tpassword string\n\t\tisError  bool\n\t}{\n\t\t{\"fail, missing login information\", \"\", \"\", true},\n\t\t{\"fail, invalid username\", \"badUsername\", \"\", true},\n\t\t{\"success, username\", \"testUsername\", \"testPassword\", false},\n\t}\n\n\tth.Store.EXPECT().UpdateUserPassword(\"\", gomock.Any()).Return(errors.New(\"user not found\"))\n\tth.Store.EXPECT().UpdateUserPassword(\"badUsername\", gomock.Any()).Return(errors.New(\"user not found\"))\n\tth.Store.EXPECT().UpdateUserPassword(\"testUsername\", gomock.Any()).Return(nil)\n\n\tfor _, test := range testcases {\n\t\tt.Run(test.title, func(t *testing.T) {\n\t\t\terr := th.App.UpdateUserPassword(test.userName, test.password)\n\t\t\tif test.isError {\n\t\t\t\trequire.Error(t, err)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestChangePassword(t *testing.T) {\n\tth, tearDown := SetupTestHelper(t)\n\tdefer tearDown()\n\n\ttestcases := []struct {\n\t\ttitle       string\n\t\tuserName    string\n\t\toldPassword string\n\t\tpassword    string\n\t\tisError     bool\n\t}{\n\t\t{\"fail, missing login information\", \"\", \"\", \"\", true},\n\t\t{\"fail, invalid userId\", \"badID\", \"\", \"\", true},\n\t\t{\"fail, invalid password\", mockUser.ID, \"wrongPassword\", \"newPassword\", true},\n\t\t{\"success, using username\", mockUser.ID, \"testPassword\", \"newPassword\", false},\n\t}\n\n\tth.Store.EXPECT().GetUserByID(\"badID\").Return(nil, errors.New(\"userID not found\"))\n\tth.Store.EXPECT().GetUserByID(mockUser.ID).Return(mockUser, nil).Times(2)\n\tth.Store.EXPECT().UpdateUserPasswordByID(mockUser.ID, gomock.Any()).Return(nil)\n\n\tfor _, test := range testcases {\n\t\tt.Run(test.title, func(t *testing.T) {\n\t\t\terr := th.App.ChangePassword(test.userName, test.oldPassword, test.password)\n\t\t\tif test.isError {\n\t\t\t\trequire.Error(t, err)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "server/app/blocks.go",
    "content": "package app\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/notify\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nvar ErrBlocksFromMultipleBoards = errors.New(\"the block set contain blocks from multiple boards\")\n\nfunc (a *App) GetBlocks(boardID, parentID string, blockType string) ([]*model.Block, error) {\n\tif boardID == \"\" {\n\t\treturn []*model.Block{}, nil\n\t}\n\n\tif blockType != \"\" && parentID != \"\" {\n\t\treturn a.store.GetBlocksWithParentAndType(boardID, parentID, blockType)\n\t}\n\n\tif blockType != \"\" {\n\t\treturn a.store.GetBlocksWithType(boardID, blockType)\n\t}\n\n\treturn a.store.GetBlocksWithParent(boardID, parentID)\n}\n\nfunc (a *App) DuplicateBlock(boardID string, blockID string, userID string, asTemplate bool) ([]*model.Block, error) {\n\tboard, err := a.GetBoard(boardID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif board == nil {\n\t\treturn nil, fmt.Errorf(\"cannot fetch board %s for DuplicateBlock: %w\", boardID, err)\n\t}\n\n\tblocks, err := a.store.DuplicateBlock(boardID, blockID, userID, asTemplate)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = a.CopyAndUpdateCardFiles(boardID, userID, blocks, asTemplate)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ta.blockChangeNotifier.Enqueue(func() error {\n\t\tfor _, block := range blocks {\n\t\t\ta.wsAdapter.BroadcastBlockChange(board.TeamID, block)\n\t\t}\n\t\treturn nil\n\t})\n\n\treturn blocks, err\n}\n\nfunc (a *App) PatchBlock(blockID string, blockPatch *model.BlockPatch, modifiedByID string) (*model.Block, error) {\n\treturn a.PatchBlockAndNotify(blockID, blockPatch, modifiedByID, false)\n}\n\nfunc (a *App) PatchBlockAndNotify(blockID string, blockPatch *model.BlockPatch, modifiedByID string, disableNotify bool) (*model.Block, error) {\n\toldBlock, err := a.store.GetBlock(blockID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tboard, err := a.store.GetBoard(oldBlock.BoardID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = a.store.PatchBlock(blockID, blockPatch, modifiedByID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ta.metrics.IncrementBlocksPatched(1)\n\tblock, err := a.store.GetBlock(blockID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ta.blockChangeNotifier.Enqueue(func() error {\n\t\t// broadcast on websocket\n\t\ta.wsAdapter.BroadcastBlockChange(board.TeamID, block)\n\n\t\t// broadcast on webhooks\n\t\ta.webhook.NotifyUpdate(block)\n\n\t\t// send notifications\n\t\tif !disableNotify {\n\t\t\ta.notifyBlockChanged(notify.Update, block, oldBlock, modifiedByID)\n\t\t}\n\t\treturn nil\n\t})\n\treturn block, nil\n}\n\nfunc (a *App) PatchBlocks(teamID string, blockPatches *model.BlockPatchBatch, modifiedByID string) error {\n\treturn a.PatchBlocksAndNotify(teamID, blockPatches, modifiedByID, false)\n}\n\nfunc (a *App) PatchBlocksAndNotify(teamID string, blockPatches *model.BlockPatchBatch, modifiedByID string, disableNotify bool) error {\n\toldBlocks, err := a.store.GetBlocksByIDs(blockPatches.BlockIDs)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif err := a.store.PatchBlocks(blockPatches, modifiedByID); err != nil {\n\t\treturn err\n\t}\n\n\ta.blockChangeNotifier.Enqueue(func() error {\n\t\ta.metrics.IncrementBlocksPatched(len(oldBlocks))\n\t\tfor i, blockID := range blockPatches.BlockIDs {\n\t\t\tnewBlock, err := a.store.GetBlock(blockID)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\ta.wsAdapter.BroadcastBlockChange(teamID, newBlock)\n\t\t\ta.webhook.NotifyUpdate(newBlock)\n\t\t\tif !disableNotify {\n\t\t\t\ta.notifyBlockChanged(notify.Update, newBlock, oldBlocks[i], modifiedByID)\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t})\n\treturn nil\n}\n\nfunc (a *App) InsertBlock(block *model.Block, modifiedByID string) error {\n\treturn a.InsertBlockAndNotify(block, modifiedByID, false)\n}\n\nfunc (a *App) InsertBlockAndNotify(block *model.Block, modifiedByID string, disableNotify bool) error {\n\tboard, bErr := a.store.GetBoard(block.BoardID)\n\tif bErr != nil {\n\t\treturn bErr\n\t}\n\n\terr := a.store.InsertBlock(block, modifiedByID)\n\tif err == nil {\n\t\ta.blockChangeNotifier.Enqueue(func() error {\n\t\t\ta.wsAdapter.BroadcastBlockChange(board.TeamID, block)\n\t\t\ta.metrics.IncrementBlocksInserted(1)\n\t\t\ta.webhook.NotifyUpdate(block)\n\t\t\tif !disableNotify {\n\t\t\t\ta.notifyBlockChanged(notify.Add, block, nil, modifiedByID)\n\t\t\t}\n\t\t\treturn nil\n\t\t})\n\t}\n\n\treturn err\n}\n\nfunc (a *App) InsertBlocks(blocks []*model.Block, modifiedByID string) ([]*model.Block, error) {\n\treturn a.InsertBlocksAndNotify(blocks, modifiedByID, false)\n}\n\nfunc (a *App) InsertBlocksAndNotify(blocks []*model.Block, modifiedByID string, disableNotify bool) ([]*model.Block, error) {\n\tif len(blocks) == 0 {\n\t\treturn []*model.Block{}, nil\n\t}\n\n\t// all blocks must belong to the same board\n\tboardID := blocks[0].BoardID\n\tfor _, block := range blocks {\n\t\tif block.BoardID != boardID {\n\t\t\treturn nil, ErrBlocksFromMultipleBoards\n\t\t}\n\t}\n\n\tboard, err := a.store.GetBoard(boardID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tneedsNotify := make([]*model.Block, 0, len(blocks))\n\tfor i := range blocks {\n\t\terr := a.store.InsertBlock(blocks[i], modifiedByID)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tneedsNotify = append(needsNotify, blocks[i])\n\n\t\ta.wsAdapter.BroadcastBlockChange(board.TeamID, blocks[i])\n\t\ta.metrics.IncrementBlocksInserted(1)\n\t}\n\n\ta.blockChangeNotifier.Enqueue(func() error {\n\t\tfor _, b := range needsNotify {\n\t\t\tblock := b\n\t\t\ta.webhook.NotifyUpdate(block)\n\t\t\tif !disableNotify {\n\t\t\t\ta.notifyBlockChanged(notify.Add, block, nil, modifiedByID)\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t})\n\n\treturn blocks, nil\n}\n\nfunc (a *App) GetBlockByID(blockID string) (*model.Block, error) {\n\treturn a.store.GetBlock(blockID)\n}\n\nfunc (a *App) DeleteBlock(blockID string, modifiedBy string) error {\n\treturn a.DeleteBlockAndNotify(blockID, modifiedBy, false)\n}\n\nfunc (a *App) DeleteBlockAndNotify(blockID string, modifiedBy string, disableNotify bool) error {\n\tblock, err := a.store.GetBlock(blockID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tboard, err := a.store.GetBoard(block.BoardID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif block == nil {\n\t\t// deleting non-existing block not considered an error\n\t\treturn nil\n\t}\n\n\terr = a.store.DeleteBlock(blockID, modifiedBy)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ta.blockChangeNotifier.Enqueue(func() error {\n\t\ta.wsAdapter.BroadcastBlockDelete(board.TeamID, blockID, block.BoardID)\n\t\ta.metrics.IncrementBlocksDeleted(1)\n\t\tif !disableNotify {\n\t\t\ta.notifyBlockChanged(notify.Delete, block, block, modifiedBy)\n\t\t}\n\t\treturn nil\n\t})\n\n\treturn nil\n}\n\nfunc (a *App) GetLastBlockHistoryEntry(blockID string) (*model.Block, error) {\n\tblocks, err := a.store.GetBlockHistory(blockID, model.QueryBlockHistoryOptions{Limit: 1, Descending: true})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(blocks) == 0 {\n\t\treturn nil, nil\n\t}\n\treturn blocks[0], nil\n}\n\nfunc (a *App) UndeleteBlock(blockID string, modifiedBy string) (*model.Block, error) {\n\tblocks, err := a.store.GetBlockHistory(blockID, model.QueryBlockHistoryOptions{Limit: 1, Descending: true})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(blocks) == 0 {\n\t\t// undeleting non-existing block not considered an error\n\t\treturn nil, nil\n\t}\n\n\terr = a.store.UndeleteBlock(blockID, modifiedBy)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tblock, err := a.store.GetBlock(blockID)\n\tif model.IsErrNotFound(err) {\n\t\ta.logger.Error(\"Error loading the block after a successful undelete, not propagating through websockets or notifications\", mlog.String(\"blockID\", blockID))\n\t\treturn nil, err\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tboard, err := a.store.GetBoard(block.BoardID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ta.blockChangeNotifier.Enqueue(func() error {\n\t\ta.wsAdapter.BroadcastBlockChange(board.TeamID, block)\n\t\ta.metrics.IncrementBlocksInserted(1)\n\t\ta.webhook.NotifyUpdate(block)\n\t\ta.notifyBlockChanged(notify.Add, block, nil, modifiedBy)\n\n\t\treturn nil\n\t})\n\n\treturn block, nil\n}\n\nfunc (a *App) GetBlockCountsByType() (map[string]int64, error) {\n\treturn a.store.GetBlockCountsByType()\n}\n\nfunc (a *App) GetBlocksForBoard(boardID string) ([]*model.Block, error) {\n\treturn a.store.GetBlocksForBoard(boardID)\n}\n\nfunc (a *App) notifyBlockChanged(action notify.Action, block *model.Block, oldBlock *model.Block, modifiedByID string) {\n\t// don't notify if notifications service disabled, or block change is generated via system user.\n\tif a.notifications == nil || modifiedByID == model.SystemUserID {\n\t\treturn\n\t}\n\n\t// find card and board for the changed block.\n\tboard, card, err := a.getBoardAndCard(block)\n\tif err != nil {\n\t\ta.logger.Error(\"Error notifying for block change; cannot determine board or card\", mlog.Err(err))\n\t\treturn\n\t}\n\n\tboardMember, _ := a.GetMemberForBoard(board.ID, modifiedByID)\n\tif boardMember == nil {\n\t\t// create temporary guest board member\n\t\tboardMember = &model.BoardMember{\n\t\t\tBoardID: board.ID,\n\t\t\tUserID:  modifiedByID,\n\t\t}\n\t}\n\n\tevt := notify.BlockChangeEvent{\n\t\tAction:       action,\n\t\tTeamID:       board.TeamID,\n\t\tBoard:        board,\n\t\tCard:         card,\n\t\tBlockChanged: block,\n\t\tBlockOld:     oldBlock,\n\t\tModifiedBy:   boardMember,\n\t}\n\ta.notifications.BlockChanged(evt)\n}\n\nconst (\n\tmaxSearchDepth = 50\n)\n\n// getBoardAndCard returns the first parent of type `card` its board for the specified block.\n// `board` and/or `card` may return nil without error if the block does not belong to a board or card.\nfunc (a *App) getBoardAndCard(block *model.Block) (board *model.Board, card *model.Block, err error) {\n\tboard, err = a.store.GetBoard(block.BoardID)\n\tif err != nil {\n\t\treturn board, card, err\n\t}\n\n\tvar count int // don't let invalid blocks hierarchy cause infinite loop.\n\titer := block\n\tfor {\n\t\tcount++\n\t\tif card == nil && iter.Type == model.TypeCard {\n\t\t\tcard = iter\n\t\t}\n\n\t\tif iter.ParentID == \"\" || (board != nil && card != nil) || count > maxSearchDepth {\n\t\t\tbreak\n\t\t}\n\n\t\titer, err = a.store.GetBlock(iter.ParentID)\n\t\tif model.IsErrNotFound(err) {\n\t\t\treturn board, card, nil\n\t\t}\n\t\tif err != nil {\n\t\t\treturn board, card, err\n\t\t}\n\t}\n\treturn board, card, nil\n}\n"
  },
  {
    "path": "server/app/blocks_test.go",
    "content": "package app\n\nimport (\n\t\"database/sql\"\n\t\"testing\"\n\n\t\"github.com/golang/mock/gomock\"\n\t\"github.com/stretchr/testify/require\"\n\n\tmmModel \"github.com/mattermost/mattermost/server/public/model\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n)\n\ntype blockError struct {\n\tmsg string\n}\n\nfunc (be blockError) Error() string {\n\treturn be.msg\n}\n\nfunc TestInsertBlock(t *testing.T) {\n\tth, tearDown := SetupTestHelper(t)\n\tdefer tearDown()\n\n\tt.Run(\"success scenario\", func(t *testing.T) {\n\t\tboardID := testBoardID\n\t\tblock := &model.Block{BoardID: boardID}\n\t\tboard := &model.Board{ID: boardID}\n\t\tth.Store.EXPECT().GetBoard(boardID).Return(board, nil)\n\t\tth.Store.EXPECT().InsertBlock(block, \"user-id-1\").Return(nil)\n\t\tth.Store.EXPECT().GetMembersForBoard(boardID).Return([]*model.BoardMember{}, nil)\n\t\terr := th.App.InsertBlock(block, \"user-id-1\")\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"error scenario\", func(t *testing.T) {\n\t\tboardID := testBoardID\n\t\tblock := &model.Block{BoardID: boardID}\n\t\tboard := &model.Board{ID: boardID}\n\t\tth.Store.EXPECT().GetBoard(boardID).Return(board, nil)\n\t\tth.Store.EXPECT().InsertBlock(block, \"user-id-1\").Return(blockError{\"error\"})\n\t\terr := th.App.InsertBlock(block, \"user-id-1\")\n\t\trequire.Error(t, err, \"error\")\n\t})\n}\n\nfunc TestPatchBlocks(t *testing.T) {\n\tth, tearDown := SetupTestHelper(t)\n\tdefer tearDown()\n\n\tt.Run(\"patchBlocks success scenario\", func(t *testing.T) {\n\t\tblockPatches := model.BlockPatchBatch{\n\t\t\tBlockIDs: []string{\"block1\"},\n\t\t\tBlockPatches: []model.BlockPatch{\n\t\t\t\t{Title: mmModel.NewString(\"new title\")},\n\t\t\t},\n\t\t}\n\n\t\tblock1 := &model.Block{ID: \"block1\"}\n\t\tth.Store.EXPECT().GetBlocksByIDs([]string{\"block1\"}).Return([]*model.Block{block1}, nil)\n\t\tth.Store.EXPECT().PatchBlocks(gomock.Eq(&blockPatches), gomock.Eq(\"user-id-1\")).Return(nil)\n\t\tth.Store.EXPECT().GetBlock(\"block1\").Return(block1, nil)\n\t\t// this call comes from the WS server notification\n\t\tth.Store.EXPECT().GetMembersForBoard(gomock.Any()).Times(1)\n\t\terr := th.App.PatchBlocks(\"team-id\", &blockPatches, \"user-id-1\")\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"patchBlocks error scenario\", func(t *testing.T) {\n\t\tblockPatches := model.BlockPatchBatch{BlockIDs: []string{}}\n\t\tth.Store.EXPECT().GetBlocksByIDs([]string{}).Return(nil, sql.ErrNoRows)\n\t\terr := th.App.PatchBlocks(\"team-id\", &blockPatches, \"user-id-1\")\n\t\trequire.ErrorIs(t, err, sql.ErrNoRows)\n\t})\n\n\tt.Run(\"cloud limit error scenario\", func(t *testing.T) {\n\t\tt.Skipf(\"The Cloud Limits feature has been disabled\")\n\n\t\tth.App.SetCardLimit(5)\n\n\t\tfakeLicense := &mmModel.License{\n\t\t\tFeatures: &mmModel.Features{Cloud: mmModel.NewBool(true)},\n\t\t}\n\n\t\tblockPatches := model.BlockPatchBatch{\n\t\t\tBlockIDs: []string{\"block1\"},\n\t\t\tBlockPatches: []model.BlockPatch{\n\t\t\t\t{Title: mmModel.NewString(\"new title\")},\n\t\t\t},\n\t\t}\n\n\t\tblock1 := &model.Block{\n\t\t\tID:       \"block1\",\n\t\t\tType:     model.TypeCard,\n\t\t\tParentID: \"board-id\",\n\t\t\tBoardID:  \"board-id\",\n\t\t\tUpdateAt: 100,\n\t\t}\n\n\t\tboard1 := &model.Board{\n\t\t\tID:   \"board-id\",\n\t\t\tType: model.BoardTypeOpen,\n\t\t}\n\n\t\tth.Store.EXPECT().GetBlocksByIDs([]string{\"block1\"}).Return([]*model.Block{block1}, nil)\n\t\tth.Store.EXPECT().GetBoard(\"board-id\").Return(board1, nil)\n\t\tth.Store.EXPECT().GetLicense().Return(fakeLicense)\n\t\tth.Store.EXPECT().GetCardLimitTimestamp().Return(int64(150), nil)\n\t\terr := th.App.PatchBlocks(\"team-id\", &blockPatches, \"user-id-1\")\n\t\trequire.ErrorIs(t, err, model.ErrPatchUpdatesLimitedCards)\n\t})\n}\n\nfunc TestDeleteBlock(t *testing.T) {\n\tth, tearDown := SetupTestHelper(t)\n\tdefer tearDown()\n\n\tt.Run(\"success scenario\", func(t *testing.T) {\n\t\tboardID := testBoardID\n\t\tboard := &model.Board{ID: boardID}\n\t\tblock := &model.Block{\n\t\t\tID:      \"block-id\",\n\t\t\tBoardID: board.ID,\n\t\t}\n\t\tth.Store.EXPECT().GetBlock(gomock.Eq(\"block-id\")).Return(block, nil)\n\t\tth.Store.EXPECT().DeleteBlock(gomock.Eq(\"block-id\"), gomock.Eq(\"user-id-1\")).Return(nil)\n\t\tth.Store.EXPECT().GetBoard(gomock.Eq(testBoardID)).Return(board, nil)\n\t\tth.Store.EXPECT().GetMembersForBoard(boardID).Return([]*model.BoardMember{}, nil)\n\t\terr := th.App.DeleteBlock(\"block-id\", \"user-id-1\")\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"error scenario\", func(t *testing.T) {\n\t\tboardID := testBoardID\n\t\tboard := &model.Board{ID: boardID}\n\t\tblock := &model.Block{\n\t\t\tID:      \"block-id\",\n\t\t\tBoardID: board.ID,\n\t\t}\n\t\tth.Store.EXPECT().GetBlock(gomock.Eq(\"block-id\")).Return(block, nil)\n\t\tth.Store.EXPECT().DeleteBlock(gomock.Eq(\"block-id\"), gomock.Eq(\"user-id-1\")).Return(blockError{\"error\"})\n\t\tth.Store.EXPECT().GetBoard(gomock.Eq(testBoardID)).Return(board, nil)\n\t\terr := th.App.DeleteBlock(\"block-id\", \"user-id-1\")\n\t\trequire.Error(t, err, \"error\")\n\t})\n}\n\nfunc TestUndeleteBlock(t *testing.T) {\n\tth, tearDown := SetupTestHelper(t)\n\tdefer tearDown()\n\n\tt.Run(\"success scenario\", func(t *testing.T) {\n\t\tboardID := testBoardID\n\t\tboard := &model.Board{ID: boardID}\n\t\tblock := &model.Block{\n\t\t\tID:      \"block-id\",\n\t\t\tBoardID: board.ID,\n\t\t}\n\t\tth.Store.EXPECT().GetBlockHistory(\n\t\t\tgomock.Eq(\"block-id\"),\n\t\t\tgomock.Eq(model.QueryBlockHistoryOptions{Limit: 1, Descending: true}),\n\t\t).Return([]*model.Block{block}, nil)\n\t\tth.Store.EXPECT().UndeleteBlock(gomock.Eq(\"block-id\"), gomock.Eq(\"user-id-1\")).Return(nil)\n\t\tth.Store.EXPECT().GetBlock(gomock.Eq(\"block-id\")).Return(block, nil)\n\t\tth.Store.EXPECT().GetBoard(boardID).Return(board, nil)\n\t\tth.Store.EXPECT().GetMembersForBoard(boardID).Return([]*model.BoardMember{}, nil)\n\t\t_, err := th.App.UndeleteBlock(\"block-id\", \"user-id-1\")\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"error scenario\", func(t *testing.T) {\n\t\tblock := &model.Block{\n\t\t\tID: \"block-id\",\n\t\t}\n\t\tth.Store.EXPECT().GetBlockHistory(\n\t\t\tgomock.Eq(\"block-id\"),\n\t\t\tgomock.Eq(model.QueryBlockHistoryOptions{Limit: 1, Descending: true}),\n\t\t).Return([]*model.Block{block}, nil)\n\t\tth.Store.EXPECT().UndeleteBlock(gomock.Eq(\"block-id\"), gomock.Eq(\"user-id-1\")).Return(blockError{\"error\"})\n\t\t_, err := th.App.UndeleteBlock(\"block-id\", \"user-id-1\")\n\t\trequire.Error(t, err, \"error\")\n\t})\n}\n\nfunc TestInsertBlocks(t *testing.T) {\n\tth, tearDown := SetupTestHelper(t)\n\tdefer tearDown()\n\n\tt.Run(\"success scenario\", func(t *testing.T) {\n\t\tboardID := testBoardID\n\t\tblock := &model.Block{BoardID: boardID}\n\t\tboard := &model.Board{ID: boardID}\n\t\tth.Store.EXPECT().GetBoard(boardID).Return(board, nil)\n\t\tth.Store.EXPECT().InsertBlock(block, \"user-id-1\").Return(nil)\n\t\tth.Store.EXPECT().GetMembersForBoard(boardID).Return([]*model.BoardMember{}, nil)\n\t\t_, err := th.App.InsertBlocks([]*model.Block{block}, \"user-id-1\")\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"error scenario\", func(t *testing.T) {\n\t\tboardID := testBoardID\n\t\tblock := &model.Block{BoardID: boardID}\n\t\tboard := &model.Board{ID: boardID}\n\t\tth.Store.EXPECT().GetBoard(boardID).Return(board, nil)\n\t\tth.Store.EXPECT().InsertBlock(block, \"user-id-1\").Return(blockError{\"error\"})\n\t\t_, err := th.App.InsertBlocks([]*model.Block{block}, \"user-id-1\")\n\t\trequire.Error(t, err, \"error\")\n\t})\n\n\tt.Run(\"create view within limits\", func(t *testing.T) {\n\t\tt.Skipf(\"The Cloud Limits feature has been disabled\")\n\n\t\tboardID := testBoardID\n\t\tblock := &model.Block{\n\t\t\tType:     model.TypeView,\n\t\t\tParentID: \"parent_id\",\n\t\t\tBoardID:  boardID,\n\t\t}\n\t\tboard := &model.Board{ID: boardID}\n\t\tth.Store.EXPECT().GetBoard(boardID).Return(board, nil)\n\t\tth.Store.EXPECT().InsertBlock(block, \"user-id-1\").Return(nil)\n\t\tth.Store.EXPECT().GetMembersForBoard(boardID).Return([]*model.BoardMember{}, nil)\n\n\t\t// setting up mocks for limits\n\t\tfakeLicense := &mmModel.License{\n\t\t\tFeatures: &mmModel.Features{Cloud: mmModel.NewBool(true)},\n\t\t}\n\t\tth.Store.EXPECT().GetLicense().Return(fakeLicense)\n\n\t\tth.Store.EXPECT().GetUsedCardsCount().Return(1, nil)\n\t\tth.Store.EXPECT().GetCardLimitTimestamp().Return(int64(1), nil)\n\t\tth.Store.EXPECT().GetBlocksWithParentAndType(\"test-board-id\", \"parent_id\", \"view\").Return([]*model.Block{{}}, nil)\n\n\t\t_, err := th.App.InsertBlocks([]*model.Block{block}, \"user-id-1\")\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"create view exceeding limits\", func(t *testing.T) {\n\t\tt.Skipf(\"The Cloud Limits feature has been disabled\")\n\n\t\tboardID := testBoardID\n\t\tblock := &model.Block{\n\t\t\tType:     model.TypeView,\n\t\t\tParentID: \"parent_id\",\n\t\t\tBoardID:  boardID,\n\t\t}\n\t\tboard := &model.Board{ID: boardID}\n\t\tth.Store.EXPECT().GetBoard(boardID).Return(board, nil)\n\n\t\t// setting up mocks for limits\n\t\tfakeLicense := &mmModel.License{\n\t\t\tFeatures: &mmModel.Features{Cloud: mmModel.NewBool(true)},\n\t\t}\n\t\tth.Store.EXPECT().GetLicense().Return(fakeLicense)\n\n\t\tth.Store.EXPECT().GetUsedCardsCount().Return(1, nil)\n\t\tth.Store.EXPECT().GetCardLimitTimestamp().Return(int64(1), nil)\n\t\tth.Store.EXPECT().GetBlocksWithParentAndType(\"test-board-id\", \"parent_id\", \"view\").Return([]*model.Block{{}, {}}, nil)\n\n\t\t_, err := th.App.InsertBlocks([]*model.Block{block}, \"user-id-1\")\n\t\trequire.Error(t, err)\n\t})\n\n\tt.Run(\"creating multiple views, reaching limit in the process\", func(t *testing.T) {\n\t\tt.Skipf(\"Will be fixed soon\")\n\n\t\tboardID := testBoardID\n\t\tview1 := &model.Block{\n\t\t\tType:     model.TypeView,\n\t\t\tParentID: \"parent_id\",\n\t\t\tBoardID:  boardID,\n\t\t}\n\n\t\tview2 := &model.Block{\n\t\t\tType:     model.TypeView,\n\t\t\tParentID: \"parent_id\",\n\t\t\tBoardID:  boardID,\n\t\t}\n\n\t\tboard := &model.Board{ID: boardID}\n\t\tth.Store.EXPECT().GetBoard(boardID).Return(board, nil)\n\t\tth.Store.EXPECT().InsertBlock(view1, \"user-id-1\").Return(nil).Times(2)\n\t\tth.Store.EXPECT().GetMembersForBoard(boardID).Return([]*model.BoardMember{}, nil).Times(2)\n\n\t\t// setting up mocks for limits\n\t\tfakeLicense := &mmModel.License{\n\t\t\tFeatures: &mmModel.Features{Cloud: mmModel.NewBool(true)},\n\t\t}\n\t\tth.Store.EXPECT().GetLicense().Return(fakeLicense).Times(2)\n\n\t\tth.Store.EXPECT().GetUsedCardsCount().Return(1, nil).Times(2)\n\t\tth.Store.EXPECT().GetCardLimitTimestamp().Return(int64(1), nil).Times(2)\n\t\tth.Store.EXPECT().GetBlocksWithParentAndType(\"test-board-id\", \"parent_id\", \"view\").Return([]*model.Block{{}}, nil).Times(2)\n\n\t\t_, err := th.App.InsertBlocks([]*model.Block{view1, view2}, \"user-id-1\")\n\t\trequire.Error(t, err)\n\t})\n}\n"
  },
  {
    "path": "server/app/boards.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage app\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/notify\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nvar (\n\tErrNewBoardCannotHaveID = errors.New(\"new board cannot have an ID\")\n)\n\nconst linkBoardMessage = \"@%s linked the board [%s](%s) with this channel\"\nconst unlinkBoardMessage = \"@%s unlinked the board [%s](%s) with this channel\"\n\nvar errNoDefaultCategoryFound = errors.New(\"no default category found for user\")\n\nfunc (a *App) GetBoard(boardID string) (*model.Board, error) {\n\tboard, err := a.store.GetBoard(boardID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn board, nil\n}\n\nfunc (a *App) GetBoardCount() (int64, error) {\n\treturn a.store.GetBoardCount()\n}\n\nfunc (a *App) GetBoardMetadata(boardID string) (*model.Board, *model.BoardMetadata, error) {\n\tlicense := a.store.GetLicense()\n\tif license == nil || !(*license.Features.Compliance) {\n\t\treturn nil, nil, model.ErrInsufficientLicense\n\t}\n\n\tboard, err := a.GetBoard(boardID)\n\tif model.IsErrNotFound(err) {\n\t\t// Board may have been deleted, retrieve most recent history instead\n\t\tboard, err = a.getBoardHistory(boardID, true)\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\t}\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tearliestTime, _, err := a.getBoardDescendantModifiedInfo(boardID, false)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tlatestTime, lastModifiedBy, err := a.getBoardDescendantModifiedInfo(boardID, true)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tboardMetadata := model.BoardMetadata{\n\t\tBoardID:                 boardID,\n\t\tDescendantFirstUpdateAt: earliestTime,\n\t\tDescendantLastUpdateAt:  latestTime,\n\t\tCreatedBy:               board.CreatedBy,\n\t\tLastModifiedBy:          lastModifiedBy,\n\t}\n\treturn board, &boardMetadata, nil\n}\n\n// getBoardForBlock returns the board that owns the specified block.\nfunc (a *App) getBoardForBlock(blockID string) (*model.Board, error) {\n\tblock, err := a.GetBlockByID(blockID)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"cannot get block %s: %w\", blockID, err)\n\t}\n\n\tboard, err := a.GetBoard(block.BoardID)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"cannot get board %s: %w\", block.BoardID, err)\n\t}\n\n\treturn board, nil\n}\n\nfunc (a *App) getBoardHistory(boardID string, latest bool) (*model.Board, error) {\n\topts := model.QueryBoardHistoryOptions{\n\t\tLimit:      1,\n\t\tDescending: latest,\n\t}\n\tboards, err := a.store.GetBoardHistory(boardID, opts)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not get history for board: %w\", err)\n\t}\n\tif len(boards) == 0 {\n\t\treturn nil, nil\n\t}\n\n\treturn boards[0], nil\n}\n\nfunc (a *App) getBoardDescendantModifiedInfo(boardID string, latest bool) (int64, string, error) {\n\tboard, err := a.getBoardHistory(boardID, latest)\n\tif err != nil {\n\t\treturn 0, \"\", err\n\t}\n\tif board == nil {\n\t\treturn 0, \"\", fmt.Errorf(\"history not found for board: %w\", err)\n\t}\n\n\tvar timestamp int64\n\tmodifiedBy := board.ModifiedBy\n\tif latest {\n\t\ttimestamp = board.UpdateAt\n\t} else {\n\t\ttimestamp = board.CreateAt\n\t}\n\n\t// use block_history to fetch blocks in case they were deleted and no longer exist in blocks table.\n\topts := model.QueryBlockHistoryOptions{\n\t\tLimit:      1,\n\t\tDescending: latest,\n\t}\n\tblocks, err := a.store.GetBlockHistoryDescendants(boardID, opts)\n\tif err != nil {\n\t\treturn 0, \"\", fmt.Errorf(\"could not get blocks history descendants for board: %w\", err)\n\t}\n\tif len(blocks) > 0 {\n\t\t// Compare the board history info with the descendant block info, if it exists\n\t\tblock := blocks[0]\n\t\tif latest && block.UpdateAt > timestamp {\n\t\t\ttimestamp = block.UpdateAt\n\t\t\tmodifiedBy = block.ModifiedBy\n\t\t} else if !latest && block.CreateAt < timestamp {\n\t\t\ttimestamp = block.CreateAt\n\t\t\tmodifiedBy = block.ModifiedBy\n\t\t}\n\t}\n\treturn timestamp, modifiedBy, nil\n}\n\nfunc (a *App) setBoardCategoryFromSource(sourceBoardID, destinationBoardID, userID, teamID string, asTemplate bool) error {\n\t// find source board's category ID for the user\n\tuserCategoryBoards, err := a.GetUserCategoryBoards(userID, teamID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar destinationCategoryID string\n\n\tfor _, categoryBoard := range userCategoryBoards {\n\t\tfor _, metadata := range categoryBoard.BoardMetadata {\n\t\t\tif metadata.BoardID == sourceBoardID {\n\t\t\t\t// category found!\n\t\t\t\tdestinationCategoryID = categoryBoard.ID\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif destinationCategoryID == \"\" {\n\t\t// if source board is not mapped to a category for this user,\n\t\t// then move new board to default category\n\t\tif !asTemplate {\n\t\t\treturn a.addBoardsToDefaultCategory(userID, teamID, []*model.Board{{ID: destinationBoardID}})\n\t\t} else {\n\t\t\treturn nil\n\t\t}\n\t}\n\n\t// now that we have source board's category,\n\t// we send destination board to the same category\n\treturn a.AddUpdateUserCategoryBoard(teamID, userID, destinationCategoryID, []string{destinationBoardID})\n}\n\nfunc (a *App) DuplicateBoard(boardID, userID, toTeam string, asTemplate bool) (*model.BoardsAndBlocks, []*model.BoardMember, error) {\n\tbab, members, err := a.store.DuplicateBoard(boardID, userID, toTeam, asTemplate)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\t// copy any file attachments from the duplicated blocks.\n\terr = a.CopyAndUpdateCardFiles(boardID, userID, bab.Blocks, asTemplate)\n\tif err != nil {\n\t\tdbab := model.NewDeleteBoardsAndBlocksFromBabs(bab)\n\t\tif dErr := a.store.DeleteBoardsAndBlocks(dbab, userID); dErr != nil {\n\t\t\ta.logger.Error(\"Cannot delete board after duplication error when updating block's file info\", mlog.String(\"boardID\", bab.Boards[0].ID), mlog.Err(dErr))\n\t\t}\n\t\treturn nil, nil, fmt.Errorf(\"could not patch file IDs while duplicating board %s: %w\", boardID, err)\n\t}\n\n\tif !asTemplate {\n\t\tfor _, board := range bab.Boards {\n\t\t\tif categoryErr := a.setBoardCategoryFromSource(boardID, board.ID, userID, toTeam, asTemplate); categoryErr != nil {\n\t\t\t\treturn nil, nil, categoryErr\n\t\t\t}\n\t\t}\n\t}\n\n\ta.blockChangeNotifier.Enqueue(func() error {\n\t\tteamID := \"\"\n\t\tfor _, board := range bab.Boards {\n\t\t\tteamID = board.TeamID\n\t\t\ta.wsAdapter.BroadcastBoardChange(teamID, board)\n\t\t}\n\t\tfor _, block := range bab.Blocks {\n\t\t\tblk := block\n\t\t\ta.wsAdapter.BroadcastBlockChange(teamID, blk)\n\t\t\ta.notifyBlockChanged(notify.Add, blk, nil, userID)\n\t\t}\n\t\tfor _, member := range members {\n\t\t\ta.wsAdapter.BroadcastMemberChange(teamID, member.BoardID, member)\n\t\t}\n\t\treturn nil\n\t})\n\n\treturn bab, members, err\n}\n\nfunc (a *App) GetBoardsForUserAndTeam(userID, teamID string, includePublicBoards bool) ([]*model.Board, error) {\n\treturn a.store.GetBoardsForUserAndTeam(userID, teamID, includePublicBoards)\n}\n\nfunc (a *App) GetTemplateBoards(teamID, userID string) ([]*model.Board, error) {\n\treturn a.store.GetTemplateBoards(teamID, userID)\n}\n\nfunc (a *App) CreateBoard(board *model.Board, userID string, addMember bool) (*model.Board, error) {\n\tif board.ID != \"\" {\n\t\treturn nil, ErrNewBoardCannotHaveID\n\t}\n\tboard.ID = utils.NewID(utils.IDTypeBoard)\n\n\tvar newBoard *model.Board\n\tvar member *model.BoardMember\n\tvar err error\n\tif addMember {\n\t\tnewBoard, member, err = a.store.InsertBoardWithAdmin(board, userID)\n\t} else {\n\t\tnewBoard, err = a.store.InsertBoard(board, userID)\n\t}\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ta.blockChangeNotifier.Enqueue(func() error {\n\t\ta.wsAdapter.BroadcastBoardChange(newBoard.TeamID, newBoard)\n\n\t\tif newBoard.ChannelID != \"\" {\n\t\t\tmembers, err := a.GetMembersForBoard(board.ID)\n\t\t\tif err != nil {\n\t\t\t\ta.logger.Error(\"Unable to get the board members\", mlog.Err(err))\n\t\t\t}\n\t\t\tfor _, member := range members {\n\t\t\t\ta.wsAdapter.BroadcastMemberChange(newBoard.TeamID, member.BoardID, member)\n\t\t\t}\n\t\t} else if addMember {\n\t\t\ta.wsAdapter.BroadcastMemberChange(newBoard.TeamID, newBoard.ID, member)\n\t\t}\n\t\treturn nil\n\t})\n\n\tif !board.IsTemplate {\n\t\tif err := a.addBoardsToDefaultCategory(userID, newBoard.TeamID, []*model.Board{newBoard}); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn newBoard, nil\n}\n\nfunc (a *App) addBoardsToDefaultCategory(userID, teamID string, boards []*model.Board) error {\n\tuserCategoryBoards, err := a.GetUserCategoryBoards(userID, teamID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdefaultCategoryID := \"\"\n\tfor _, categoryBoard := range userCategoryBoards {\n\t\tif categoryBoard.Name == defaultCategoryBoards {\n\t\t\tdefaultCategoryID = categoryBoard.ID\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif defaultCategoryID == \"\" {\n\t\treturn fmt.Errorf(\"%w userID: %s\", errNoDefaultCategoryFound, userID)\n\t}\n\n\tboardIDs := make([]string, len(boards))\n\tfor i := range boards {\n\t\tboardIDs[i] = boards[i].ID\n\t}\n\n\tif err := a.AddUpdateUserCategoryBoard(teamID, userID, defaultCategoryID, boardIDs); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (a *App) PatchBoard(patch *model.BoardPatch, boardID, userID string) (*model.Board, error) {\n\tvar oldChannelID string\n\tvar isTemplate bool\n\tvar oldMembers []*model.BoardMember\n\n\tif patch.Type != nil || patch.ChannelID != nil {\n\t\ttestChannel := \"\"\n\t\tif patch.ChannelID != nil && *patch.ChannelID == \"\" {\n\t\t\tvar err error\n\t\t\toldMembers, err = a.GetMembersForBoard(boardID)\n\t\t\tif err != nil {\n\t\t\t\ta.logger.Error(\"Unable to get the board members\", mlog.Err(err))\n\t\t\t}\n\t\t} else if patch.ChannelID != nil && *patch.ChannelID != \"\" {\n\t\t\ttestChannel = *patch.ChannelID\n\t\t}\n\n\t\tboard, err := a.store.GetBoard(boardID)\n\t\tif model.IsErrNotFound(err) {\n\t\t\treturn nil, model.NewErrNotFound(\"board ID=\" + boardID)\n\t\t}\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\toldChannelID = board.ChannelID\n\t\tisTemplate = board.IsTemplate\n\t\tif testChannel == \"\" {\n\t\t\ttestChannel = oldChannelID\n\t\t}\n\n\t\tif testChannel != \"\" {\n\t\t\tif !a.permissions.HasPermissionToChannel(userID, testChannel, model.PermissionCreatePost) {\n\t\t\t\treturn nil, model.NewErrPermission(\"access denied to channel\")\n\t\t\t}\n\t\t}\n\t}\n\n\tupdatedBoard, err := a.store.PatchBoard(boardID, patch, userID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Post message to channel if linked/unlinked\n\tif patch.ChannelID != nil {\n\t\tvar username string\n\n\t\tuser, err := a.store.GetUserByID(userID)\n\t\tif err != nil {\n\t\t\ta.logger.Error(\"Unable to get the board updater\", mlog.Err(err))\n\t\t\tusername = \"unknown\"\n\t\t} else {\n\t\t\tusername = user.Username\n\t\t}\n\n\t\tboardLink := utils.MakeBoardLink(a.config.ServerRoot, updatedBoard.TeamID, updatedBoard.ID)\n\t\ttitle := updatedBoard.Title\n\t\tif title == \"\" {\n\t\t\ttitle = \"Untitled board\" // todo: localize this when server has i18n\n\t\t}\n\t\tif *patch.ChannelID != \"\" {\n\t\t\ta.postChannelMessage(fmt.Sprintf(linkBoardMessage, username, title, boardLink), updatedBoard.ChannelID)\n\t\t} else if *patch.ChannelID == \"\" {\n\t\t\ta.postChannelMessage(fmt.Sprintf(unlinkBoardMessage, username, title, boardLink), oldChannelID)\n\t\t}\n\t}\n\n\t// Broadcast Messages to affected users\n\ta.blockChangeNotifier.Enqueue(func() error {\n\t\ta.wsAdapter.BroadcastBoardChange(updatedBoard.TeamID, updatedBoard)\n\n\t\tif patch.ChannelID != nil {\n\t\t\tif *patch.ChannelID != \"\" {\n\t\t\t\tmembers, err := a.GetMembersForBoard(updatedBoard.ID)\n\t\t\t\tif err != nil {\n\t\t\t\t\ta.logger.Error(\"Unable to get the board members\", mlog.Err(err))\n\t\t\t\t}\n\t\t\t\tfor _, member := range members {\n\t\t\t\t\tif member.Synthetic {\n\t\t\t\t\t\ta.wsAdapter.BroadcastMemberChange(updatedBoard.TeamID, member.BoardID, member)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfor _, oldMember := range oldMembers {\n\t\t\t\t\tif oldMember.Synthetic {\n\t\t\t\t\t\ta.wsAdapter.BroadcastMemberDelete(updatedBoard.TeamID, boardID, oldMember.UserID)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif patch.Type != nil && isTemplate {\n\t\t\tmembers, err := a.GetMembersForBoard(updatedBoard.ID)\n\t\t\tif err != nil {\n\t\t\t\ta.logger.Error(\"Unable to get the board members\", mlog.Err(err))\n\t\t\t}\n\t\t\ta.broadcastTeamUsers(updatedBoard.TeamID, updatedBoard.ID, *patch.Type, members)\n\t\t}\n\t\treturn nil\n\t})\n\n\treturn updatedBoard, nil\n}\n\nfunc (a *App) postChannelMessage(message, channelID string) {\n\terr := a.store.PostMessage(message, \"\", channelID)\n\tif err != nil {\n\t\ta.logger.Error(\"Unable to post the link message to channel\", mlog.Err(err))\n\t}\n}\n\n// broadcastTeamUsers notifies the members of a team when a template changes its type\n// from public to private or viceversa.\nfunc (a *App) broadcastTeamUsers(teamID, boardID string, boardType model.BoardType, members []*model.BoardMember) {\n\tusers, err := a.GetTeamUsers(teamID, \"\")\n\tif err != nil {\n\t\ta.logger.Error(\"Unable to get the team users\", mlog.Err(err))\n\t}\n\tfor _, user := range users {\n\t\tisMember := false\n\t\tfor _, member := range members {\n\t\t\tif member.UserID == user.ID {\n\t\t\t\tisMember = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif !isMember {\n\t\t\tif boardType == model.BoardTypePrivate {\n\t\t\t\ta.wsAdapter.BroadcastMemberDelete(teamID, boardID, user.ID)\n\t\t\t} else if boardType == model.BoardTypeOpen {\n\t\t\t\ta.wsAdapter.BroadcastMemberChange(teamID, boardID, &model.BoardMember{UserID: user.ID, BoardID: boardID, SchemeViewer: true, Synthetic: true})\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (a *App) DeleteBoard(boardID, userID string) error {\n\tboard, err := a.store.GetBoard(boardID)\n\tif model.IsErrNotFound(err) {\n\t\treturn nil\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif err := a.store.DeleteBoard(boardID, userID); err != nil {\n\t\treturn err\n\t}\n\n\ta.blockChangeNotifier.Enqueue(func() error {\n\t\ta.wsAdapter.BroadcastBoardDelete(board.TeamID, boardID)\n\t\treturn nil\n\t})\n\n\treturn nil\n}\n\nfunc (a *App) GetMembersForBoard(boardID string) ([]*model.BoardMember, error) {\n\tmembers, err := a.store.GetMembersForBoard(boardID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tboard, err := a.store.GetBoard(boardID)\n\tif err != nil && !model.IsErrNotFound(err) {\n\t\treturn nil, err\n\t}\n\tif board != nil {\n\t\tfor i, m := range members {\n\t\t\tif !m.SchemeAdmin {\n\t\t\t\tif a.permissions.HasPermissionToTeam(m.UserID, board.TeamID, model.PermissionManageTeam) {\n\t\t\t\t\tmembers[i].SchemeAdmin = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn members, nil\n}\n\nfunc (a *App) GetMembersForUser(userID string) ([]*model.BoardMember, error) {\n\tmembers, err := a.store.GetMembersForUser(userID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor i, m := range members {\n\t\tif !m.SchemeAdmin {\n\t\t\tboard, err := a.store.GetBoard(m.BoardID)\n\t\t\tif err != nil && !model.IsErrNotFound(err) {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tif board != nil {\n\t\t\t\tif a.permissions.HasPermissionToTeam(m.UserID, board.TeamID, model.PermissionManageTeam) {\n\t\t\t\t\t// if system/team admin\n\t\t\t\t\tmembers[i].SchemeAdmin = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn members, nil\n}\n\nfunc (a *App) GetMemberForBoard(boardID string, userID string) (*model.BoardMember, error) {\n\treturn a.store.GetMemberForBoard(boardID, userID)\n}\n\nfunc (a *App) AddMemberToBoard(member *model.BoardMember) (*model.BoardMember, error) {\n\tboard, err := a.store.GetBoard(member.BoardID)\n\tif model.IsErrNotFound(err) {\n\t\treturn nil, nil\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\texistingMembership, err := a.store.GetMemberForBoard(member.BoardID, member.UserID)\n\tif err != nil && !model.IsErrNotFound(err) {\n\t\treturn nil, err\n\t}\n\n\tif existingMembership != nil && !existingMembership.Synthetic {\n\t\treturn existingMembership, nil\n\t}\n\n\tnewMember, err := a.store.SaveMember(member)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif !newMember.SchemeAdmin {\n\t\tif board != nil {\n\t\t\tif a.permissions.HasPermissionToTeam(newMember.UserID, board.TeamID, model.PermissionManageTeam) {\n\t\t\t\tnewMember.SchemeAdmin = true\n\t\t\t}\n\t\t}\n\t}\n\n\tif !board.IsTemplate {\n\t\tif err = a.addBoardsToDefaultCategory(member.UserID, board.TeamID, []*model.Board{board}); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\ta.blockChangeNotifier.Enqueue(func() error {\n\t\ta.wsAdapter.BroadcastMemberChange(board.TeamID, member.BoardID, member)\n\t\treturn nil\n\t})\n\n\treturn newMember, nil\n}\n\nfunc (a *App) UpdateBoardMember(member *model.BoardMember) (*model.BoardMember, error) {\n\tboard, bErr := a.store.GetBoard(member.BoardID)\n\tif model.IsErrNotFound(bErr) {\n\t\treturn nil, nil\n\t}\n\tif bErr != nil {\n\t\treturn nil, bErr\n\t}\n\n\toldMember, err := a.store.GetMemberForBoard(member.BoardID, member.UserID)\n\tif model.IsErrNotFound(err) {\n\t\treturn nil, nil\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// if we're updating an admin, we need to check that there is at\n\t// least still another admin on the board\n\tif oldMember.SchemeAdmin && !member.SchemeAdmin {\n\t\tisLastAdmin, err2 := a.isLastAdmin(member.UserID, member.BoardID)\n\t\tif err2 != nil {\n\t\t\treturn nil, err2\n\t\t}\n\t\tif isLastAdmin {\n\t\t\treturn nil, model.ErrBoardMemberIsLastAdmin\n\t\t}\n\t}\n\n\tnewMember, err := a.store.SaveMember(member)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ta.blockChangeNotifier.Enqueue(func() error {\n\t\ta.wsAdapter.BroadcastMemberChange(board.TeamID, member.BoardID, member)\n\t\treturn nil\n\t})\n\n\treturn newMember, nil\n}\n\nfunc (a *App) isLastAdmin(userID, boardID string) (bool, error) {\n\tmembers, err := a.store.GetMembersForBoard(boardID)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tfor _, m := range members {\n\t\tif m.SchemeAdmin && m.UserID != userID {\n\t\t\treturn false, nil\n\t\t}\n\t}\n\treturn true, nil\n}\n\nfunc (a *App) DeleteBoardMember(boardID, userID string) error {\n\tboard, bErr := a.store.GetBoard(boardID)\n\tif model.IsErrNotFound(bErr) {\n\t\treturn nil\n\t}\n\tif bErr != nil {\n\t\treturn bErr\n\t}\n\n\toldMember, err := a.store.GetMemberForBoard(boardID, userID)\n\tif model.IsErrNotFound(err) {\n\t\treturn nil\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// if we're removing an admin, we need to check that there is at\n\t// least still another admin on the board\n\tif oldMember.SchemeAdmin {\n\t\tisLastAdmin, err := a.isLastAdmin(userID, boardID)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif isLastAdmin {\n\t\t\treturn model.ErrBoardMemberIsLastAdmin\n\t\t}\n\t}\n\n\tif err := a.store.DeleteMember(boardID, userID); err != nil {\n\t\treturn err\n\t}\n\n\ta.blockChangeNotifier.Enqueue(func() error {\n\t\tif syntheticMember, _ := a.GetMemberForBoard(boardID, userID); syntheticMember != nil {\n\t\t\ta.wsAdapter.BroadcastMemberChange(board.TeamID, boardID, syntheticMember)\n\t\t} else {\n\t\t\ta.wsAdapter.BroadcastMemberDelete(board.TeamID, boardID, userID)\n\t\t}\n\t\treturn nil\n\t})\n\n\treturn nil\n}\n\nfunc (a *App) SearchBoardsForUser(term string, searchField model.BoardSearchField, userID string, includePublicBoards bool) ([]*model.Board, error) {\n\treturn a.store.SearchBoardsForUser(term, searchField, userID, includePublicBoards)\n}\n\nfunc (a *App) SearchBoardsForUserInTeam(teamID, term, userID string) ([]*model.Board, error) {\n\treturn a.store.SearchBoardsForUserInTeam(teamID, term, userID)\n}\n\nfunc (a *App) UndeleteBoard(boardID string, modifiedBy string) error {\n\tboards, err := a.store.GetBoardHistory(boardID, model.QueryBoardHistoryOptions{Limit: 1, Descending: true})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif len(boards) == 0 {\n\t\t// undeleting non-existing board not considered an error\n\t\treturn nil\n\t}\n\n\terr = a.store.UndeleteBoard(boardID, modifiedBy)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tboard, err := a.store.GetBoard(boardID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif board == nil {\n\t\ta.logger.Error(\"Error loading the board after undelete, not propagating through websockets or notifications\")\n\t\treturn nil\n\t}\n\n\ta.blockChangeNotifier.Enqueue(func() error {\n\t\ta.wsAdapter.BroadcastBoardChange(board.TeamID, board)\n\t\treturn nil\n\t})\n\n\treturn nil\n}\n"
  },
  {
    "path": "server/app/boards_and_blocks.go",
    "content": "package app\n\nimport (\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/notify\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nfunc (a *App) CreateBoardsAndBlocks(bab *model.BoardsAndBlocks, userID string, addMember bool) (*model.BoardsAndBlocks, error) {\n\tvar newBab *model.BoardsAndBlocks\n\tvar members []*model.BoardMember\n\tvar err error\n\n\tif addMember {\n\t\tnewBab, members, err = a.store.CreateBoardsAndBlocksWithAdmin(bab, userID)\n\t} else {\n\t\tnewBab, err = a.store.CreateBoardsAndBlocks(bab, userID)\n\t}\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// all new boards should belong to the same team\n\tteamID := newBab.Boards[0].TeamID\n\n\t// This can be synchronous because this action is not common\n\tfor _, board := range newBab.Boards {\n\t\ta.wsAdapter.BroadcastBoardChange(teamID, board)\n\t}\n\n\tfor _, block := range newBab.Blocks {\n\t\tb := block\n\t\ta.wsAdapter.BroadcastBlockChange(teamID, b)\n\t\ta.metrics.IncrementBlocksInserted(1)\n\t\ta.webhook.NotifyUpdate(b)\n\t\ta.notifyBlockChanged(notify.Add, b, nil, userID)\n\t}\n\n\tif addMember {\n\t\tfor _, member := range members {\n\t\t\ta.wsAdapter.BroadcastMemberChange(teamID, member.BoardID, member)\n\t\t}\n\t}\n\n\tfor _, board := range newBab.Boards {\n\t\tif !board.IsTemplate {\n\t\t\tif err := a.addBoardsToDefaultCategory(userID, board.TeamID, []*model.Board{board}); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn newBab, nil\n}\n\nfunc (a *App) PatchBoardsAndBlocks(pbab *model.PatchBoardsAndBlocks, userID string) (*model.BoardsAndBlocks, error) {\n\toldBlocks, err := a.store.GetBlocksByIDs(pbab.BlockIDs)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\toldBlocksMap := map[string]*model.Block{}\n\tfor _, block := range oldBlocks {\n\t\toldBlocksMap[block.ID] = block\n\t}\n\n\tbab, err := a.store.PatchBoardsAndBlocks(pbab, userID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ta.blockChangeNotifier.Enqueue(func() error {\n\t\tteamID := bab.Boards[0].TeamID\n\n\t\tfor _, block := range bab.Blocks {\n\t\t\toldBlock, ok := oldBlocksMap[block.ID]\n\t\t\tif !ok {\n\t\t\t\ta.logger.Error(\"Error notifying for block change on patch boards and blocks; cannot get old block\", mlog.String(\"blockID\", block.ID))\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tb := block\n\t\t\ta.metrics.IncrementBlocksPatched(1)\n\t\t\ta.wsAdapter.BroadcastBlockChange(teamID, b)\n\t\t\ta.webhook.NotifyUpdate(b)\n\t\t\ta.notifyBlockChanged(notify.Update, b, oldBlock, userID)\n\t\t}\n\n\t\tfor _, board := range bab.Boards {\n\t\t\ta.wsAdapter.BroadcastBoardChange(board.TeamID, board)\n\t\t}\n\t\treturn nil\n\t})\n\n\treturn bab, nil\n}\n\nfunc (a *App) DeleteBoardsAndBlocks(dbab *model.DeleteBoardsAndBlocks, userID string) error {\n\tfirstBoard, err := a.store.GetBoard(dbab.Boards[0])\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// we need the block entity to notify of the block changes, so we\n\t// fetch and store the blocks first\n\tblocks := []*model.Block{}\n\tfor _, blockID := range dbab.Blocks {\n\t\tblock, err := a.store.GetBlock(blockID)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tblocks = append(blocks, block)\n\t}\n\n\tif err := a.store.DeleteBoardsAndBlocks(dbab, userID); err != nil {\n\t\treturn err\n\t}\n\n\ta.blockChangeNotifier.Enqueue(func() error {\n\t\tfor _, block := range blocks {\n\t\t\ta.wsAdapter.BroadcastBlockDelete(firstBoard.TeamID, block.ID, block.BoardID)\n\t\t\ta.metrics.IncrementBlocksDeleted(1)\n\t\t\ta.notifyBlockChanged(notify.Update, block, block, userID)\n\t\t}\n\n\t\tfor _, boardID := range dbab.Boards {\n\t\t\ta.wsAdapter.BroadcastBoardDelete(firstBoard.TeamID, boardID)\n\t\t}\n\t\treturn nil\n\t})\n\n\treturn nil\n}\n"
  },
  {
    "path": "server/app/boards_test.go",
    "content": "package app\n\nimport (\n\t\"testing\"\n\n\t\"github.com/mattermost/focalboard/server/utils\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/stretchr/testify/mock\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestAddMemberToBoard(t *testing.T) {\n\tth, tearDown := SetupTestHelper(t)\n\tdefer tearDown()\n\n\tt.Run(\"base case\", func(t *testing.T) {\n\t\tconst boardID = \"board_id_1\"\n\t\tconst userID = \"user_id_1\"\n\n\t\tboardMember := &model.BoardMember{\n\t\t\tBoardID:      boardID,\n\t\t\tUserID:       userID,\n\t\t\tSchemeEditor: true,\n\t\t}\n\n\t\tth.Store.EXPECT().GetBoard(boardID).Return(&model.Board{\n\t\t\tID:     \"board_id_1\",\n\t\t\tTeamID: \"team_id_1\",\n\t\t}, nil)\n\n\t\tth.Store.EXPECT().GetMemberForBoard(boardID, userID).Return(nil, nil)\n\n\t\tth.Store.EXPECT().SaveMember(mock.MatchedBy(func(i interface{}) bool {\n\t\t\tp := i.(*model.BoardMember)\n\t\t\treturn p.BoardID == boardID && p.UserID == userID\n\t\t})).Return(&model.BoardMember{\n\t\t\tBoardID: boardID,\n\t\t}, nil)\n\n\t\t// for WS change broadcast\n\t\tth.Store.EXPECT().GetMembersForBoard(boardID).Return([]*model.BoardMember{}, nil)\n\n\t\tth.Store.EXPECT().GetUserCategoryBoards(\"user_id_1\", \"team_id_1\").Return([]model.CategoryBoards{\n\t\t\t{\n\t\t\t\tCategory: model.Category{\n\t\t\t\t\tID:   \"default_category_id\",\n\t\t\t\t\tName: \"Boards\",\n\t\t\t\t\tType: \"system\",\n\t\t\t\t},\n\t\t\t},\n\t\t}, nil).Times(2)\n\t\tth.Store.EXPECT().AddUpdateCategoryBoard(\"user_id_1\", \"default_category_id\", []string{\"board_id_1\"}).Return(nil)\n\n\t\taddedBoardMember, err := th.App.AddMemberToBoard(boardMember)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, boardID, addedBoardMember.BoardID)\n\t})\n\n\tt.Run(\"return existing non-synthetic membership if any\", func(t *testing.T) {\n\t\tconst boardID = \"board_id_1\"\n\t\tconst userID = \"user_id_1\"\n\n\t\tboardMember := &model.BoardMember{\n\t\t\tBoardID:      boardID,\n\t\t\tUserID:       userID,\n\t\t\tSchemeEditor: true,\n\t\t}\n\n\t\tth.Store.EXPECT().GetBoard(boardID).Return(&model.Board{\n\t\t\tTeamID: \"team_id_1\",\n\t\t}, nil)\n\n\t\tth.Store.EXPECT().GetMemberForBoard(boardID, userID).Return(&model.BoardMember{\n\t\t\tUserID:    userID,\n\t\t\tBoardID:   boardID,\n\t\t\tSynthetic: false,\n\t\t}, nil)\n\n\t\taddedBoardMember, err := th.App.AddMemberToBoard(boardMember)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, boardID, addedBoardMember.BoardID)\n\t})\n\n\tt.Run(\"should convert synthetic membership into natural membership\", func(t *testing.T) {\n\t\tconst boardID = \"board_id_1\"\n\t\tconst userID = \"user_id_1\"\n\n\t\tboardMember := &model.BoardMember{\n\t\t\tBoardID:      boardID,\n\t\t\tUserID:       userID,\n\t\t\tSchemeEditor: true,\n\t\t}\n\n\t\tth.Store.EXPECT().GetBoard(boardID).Return(&model.Board{\n\t\t\tID:     \"board_id_1\",\n\t\t\tTeamID: \"team_id_1\",\n\t\t}, nil)\n\n\t\tth.Store.EXPECT().GetMemberForBoard(boardID, userID).Return(&model.BoardMember{\n\t\t\tUserID:    userID,\n\t\t\tBoardID:   boardID,\n\t\t\tSynthetic: true,\n\t\t}, nil)\n\n\t\tth.Store.EXPECT().SaveMember(mock.MatchedBy(func(i interface{}) bool {\n\t\t\tp := i.(*model.BoardMember)\n\t\t\treturn p.BoardID == boardID && p.UserID == userID\n\t\t})).Return(&model.BoardMember{\n\t\t\tUserID:    userID,\n\t\t\tBoardID:   boardID,\n\t\t\tSynthetic: false,\n\t\t}, nil)\n\n\t\t// for WS change broadcast\n\t\tth.Store.EXPECT().GetMembersForBoard(boardID).Return([]*model.BoardMember{}, nil)\n\n\t\tth.Store.EXPECT().GetUserCategoryBoards(\"user_id_1\", \"team_id_1\").Return([]model.CategoryBoards{\n\t\t\t{\n\t\t\t\tCategory: model.Category{\n\t\t\t\t\tID:   \"default_category_id\",\n\t\t\t\t\tName: \"Boards\",\n\t\t\t\t\tType: \"system\",\n\t\t\t\t},\n\t\t\t},\n\t\t}, nil).Times(2)\n\t\tth.Store.EXPECT().AddUpdateCategoryBoard(\"user_id_1\", \"default_category_id\", []string{\"board_id_1\"}).Return(nil)\n\t\tth.API.EXPECT().HasPermissionToTeam(\"user_id_1\", \"team_id_1\", model.PermissionManageTeam).Return(false).Times(1)\n\n\t\taddedBoardMember, err := th.App.AddMemberToBoard(boardMember)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, boardID, addedBoardMember.BoardID)\n\t})\n}\n\nfunc TestPatchBoard(t *testing.T) {\n\tth, tearDown := SetupTestHelper(t)\n\tdefer tearDown()\n\n\tt.Run(\"base case, title patch\", func(t *testing.T) {\n\t\tconst boardID = \"board_id_1\"\n\t\tconst userID = \"user_id_1\"\n\t\tconst teamID = \"team_id_1\"\n\n\t\tpatchTitle := \"Patched Title\"\n\t\tpatch := &model.BoardPatch{\n\t\t\tTitle: &patchTitle,\n\t\t}\n\n\t\tth.Store.EXPECT().PatchBoard(boardID, patch, userID).Return(\n\t\t\t&model.Board{\n\t\t\t\tID:     boardID,\n\t\t\t\tTeamID: teamID,\n\t\t\t\tTitle:  patchTitle,\n\t\t\t},\n\t\t\tnil)\n\n\t\t// for WS BroadcastBoardChange\n\t\tth.Store.EXPECT().GetMembersForBoard(boardID).Return([]*model.BoardMember{}, nil).Times(1)\n\n\t\tpatchedBoard, err := th.App.PatchBoard(patch, boardID, userID)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, patchTitle, patchedBoard.Title)\n\t})\n\n\tt.Run(\"patch type open, no users\", func(t *testing.T) {\n\t\tconst boardID = \"board_id_1\"\n\t\tconst userID = \"user_id_2\"\n\t\tconst teamID = \"team_id_1\"\n\n\t\tpatchType := model.BoardTypeOpen\n\t\tpatch := &model.BoardPatch{\n\t\t\tType: &patchType,\n\t\t}\n\n\t\t// Type not nil, will cause board to be reteived\n\t\t// to check isTemplate\n\t\tth.Store.EXPECT().GetBoard(boardID).Return(&model.Board{\n\t\t\tID:         boardID,\n\t\t\tTeamID:     teamID,\n\t\t\tIsTemplate: true,\n\t\t}, nil).Times(2)\n\n\t\t// Type not null will retrieve team members\n\t\tth.Store.EXPECT().GetUsersByTeam(teamID, \"\", false, false).Return([]*model.User{}, nil)\n\t\tth.Store.EXPECT().GetUserByID(userID).Return(&model.User{ID: userID, Username: \"UserName\"}, nil)\n\n\t\tth.Store.EXPECT().PatchBoard(boardID, patch, userID).Return(\n\t\t\t&model.Board{\n\t\t\t\tID:     boardID,\n\t\t\t\tTeamID: teamID,\n\t\t\t},\n\t\t\tnil)\n\n\t\t// Should call GetMembersForBoard 2 times\n\t\t// - for WS BroadcastBoardChange\n\t\t// - for AddTeamMembers check\n\t\tth.Store.EXPECT().GetMembersForBoard(boardID).Return([]*model.BoardMember{}, nil).Times(2)\n\n\t\tpatchedBoard, err := th.App.PatchBoard(patch, boardID, userID)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, boardID, patchedBoard.ID)\n\t})\n\n\tt.Run(\"patch type private, no users\", func(t *testing.T) {\n\t\tconst boardID = \"board_id_1\"\n\t\tconst userID = \"user_id_2\"\n\t\tconst teamID = \"team_id_1\"\n\n\t\tpatchType := model.BoardTypePrivate\n\t\tpatch := &model.BoardPatch{\n\t\t\tType: &patchType,\n\t\t}\n\n\t\t// Type not nil, will cause board to be reteived\n\t\t// to check isTemplate\n\t\tth.Store.EXPECT().GetBoard(boardID).Return(&model.Board{\n\t\t\tID:         boardID,\n\t\t\tTeamID:     teamID,\n\t\t\tIsTemplate: true,\n\t\t}, nil).Times(2)\n\n\t\t// Type not null will retrieve team members\n\t\tth.Store.EXPECT().GetUsersByTeam(teamID, \"\", false, false).Return([]*model.User{}, nil)\n\n\t\tth.Store.EXPECT().PatchBoard(boardID, patch, userID).Return(\n\t\t\t&model.Board{\n\t\t\t\tID:     boardID,\n\t\t\t\tTeamID: teamID,\n\t\t\t},\n\t\t\tnil)\n\n\t\t// Should call GetMembersForBoard 2 times\n\t\t// - for WS BroadcastBoardChange\n\t\t// - for AddTeamMembers check\n\t\tth.Store.EXPECT().GetMembersForBoard(boardID).Return([]*model.BoardMember{}, nil).Times(2)\n\n\t\tpatchedBoard, err := th.App.PatchBoard(patch, boardID, userID)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, boardID, patchedBoard.ID)\n\t})\n\n\tt.Run(\"patch type open, single user\", func(t *testing.T) {\n\t\tconst boardID = \"board_id_1\"\n\t\tconst userID = \"user_id_2\"\n\t\tconst teamID = \"team_id_1\"\n\n\t\tpatchType := model.BoardTypeOpen\n\t\tpatch := &model.BoardPatch{\n\t\t\tType: &patchType,\n\t\t}\n\n\t\t// Type not nil, will cause board to be reteived\n\t\t// to check isTemplate\n\t\tth.Store.EXPECT().GetBoard(boardID).Return(&model.Board{\n\t\t\tID:         boardID,\n\t\t\tTeamID:     teamID,\n\t\t\tIsTemplate: true,\n\t\t}, nil).Times(2)\n\t\t// Type not null will retrieve team members\n\t\tth.Store.EXPECT().GetUsersByTeam(teamID, \"\", false, false).Return([]*model.User{{ID: userID}}, nil)\n\n\t\tth.Store.EXPECT().PatchBoard(boardID, patch, userID).Return(\n\t\t\t&model.Board{\n\t\t\t\tID:     boardID,\n\t\t\t\tTeamID: teamID,\n\t\t\t},\n\t\t\tnil)\n\n\t\t// Should call GetMembersForBoard 3 times\n\t\t// for WS BroadcastBoardChange\n\t\t// for AddTeamMembers check\n\t\t// for WS BroadcastMemberChange\n\t\tth.Store.EXPECT().GetMembersForBoard(boardID).Return([]*model.BoardMember{}, nil).Times(3)\n\n\t\tpatchedBoard, err := th.App.PatchBoard(patch, boardID, userID)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, boardID, patchedBoard.ID)\n\t})\n\n\tt.Run(\"patch type private, single user\", func(t *testing.T) {\n\t\tconst boardID = \"board_id_1\"\n\t\tconst userID = \"user_id_2\"\n\t\tconst teamID = \"team_id_1\"\n\n\t\tpatchType := model.BoardTypePrivate\n\t\tpatch := &model.BoardPatch{\n\t\t\tType: &patchType,\n\t\t}\n\n\t\t// Type not nil, will cause board to be reteived\n\t\t// to check isTemplate\n\t\tth.Store.EXPECT().GetBoard(boardID).Return(&model.Board{\n\t\t\tID:         boardID,\n\t\t\tTeamID:     teamID,\n\t\t\tIsTemplate: true,\n\t\t}, nil).Times(2)\n\t\t// Type not null will retrieve team members\n\t\tth.Store.EXPECT().GetUsersByTeam(teamID, \"\", false, false).Return([]*model.User{{ID: userID}}, nil)\n\n\t\tth.Store.EXPECT().PatchBoard(boardID, patch, userID).Return(\n\t\t\t&model.Board{\n\t\t\t\tID:     boardID,\n\t\t\t\tTeamID: teamID,\n\t\t\t},\n\t\t\tnil)\n\n\t\t// Should call GetMembersForBoard 3 times\n\t\t// for WS BroadcastBoardChange\n\t\t// for AddTeamMembers check\n\t\t// for WS BroadcastMemberChange\n\t\tth.Store.EXPECT().GetMembersForBoard(boardID).Return([]*model.BoardMember{}, nil).Times(3)\n\n\t\tpatchedBoard, err := th.App.PatchBoard(patch, boardID, userID)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, boardID, patchedBoard.ID)\n\t})\n\n\tt.Run(\"patch type open, user with member\", func(t *testing.T) {\n\t\tconst boardID = \"board_id_1\"\n\t\tconst userID = \"user_id_2\"\n\t\tconst teamID = \"team_id_1\"\n\n\t\tpatchType := model.BoardTypeOpen\n\t\tpatch := &model.BoardPatch{\n\t\t\tType: &patchType,\n\t\t}\n\n\t\t// Type not nil, will cause board to be reteived\n\t\t// to check isTemplate\n\t\tth.Store.EXPECT().GetBoard(boardID).Return(&model.Board{\n\t\t\tID:         boardID,\n\t\t\tTeamID:     teamID,\n\t\t\tIsTemplate: true,\n\t\t}, nil).Times(3)\n\n\t\tth.API.EXPECT().HasPermissionToTeam(userID, teamID, model.PermissionManageTeam).Return(false).Times(1)\n\n\t\t// Type not null will retrieve team members\n\t\tth.Store.EXPECT().GetUsersByTeam(teamID, \"\", false, false).Return([]*model.User{{ID: userID}}, nil)\n\n\t\tth.Store.EXPECT().PatchBoard(boardID, patch, userID).Return(\n\t\t\t&model.Board{\n\t\t\t\tID:     boardID,\n\t\t\t\tTeamID: teamID,\n\t\t\t},\n\t\t\tnil)\n\n\t\t// Should call GetMembersForBoard 2 times\n\t\t// for WS BroadcastBoardChange\n\t\t// for AddTeamMembers check\n\t\t// We are returning the user as a direct Board Member, so BroadcastMemberDelete won't be called\n\t\tth.Store.EXPECT().GetMembersForBoard(boardID).Return([]*model.BoardMember{{BoardID: boardID, UserID: userID, SchemeEditor: true}}, nil).Times(2)\n\n\t\tpatchedBoard, err := th.App.PatchBoard(patch, boardID, userID)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, boardID, patchedBoard.ID)\n\t})\n\n\tt.Run(\"patch type private, user with member\", func(t *testing.T) {\n\t\tconst boardID = \"board_id_1\"\n\t\tconst userID = \"user_id_2\"\n\t\tconst teamID = \"team_id_1\"\n\n\t\tpatchType := model.BoardTypePrivate\n\t\tpatch := &model.BoardPatch{\n\t\t\tType: &patchType,\n\t\t}\n\n\t\t// Type not nil, will cause board to be reteived\n\t\t// to check isTemplate\n\t\tth.Store.EXPECT().GetBoard(boardID).Return(&model.Board{\n\t\t\tID:         boardID,\n\t\t\tTeamID:     teamID,\n\t\t\tIsTemplate: true,\n\t\t\tChannelID:  \"\",\n\t\t}, nil).Times(1)\n\n\t\tth.API.EXPECT().HasPermissionToTeam(userID, teamID, model.PermissionManageTeam).Return(false).Times(1)\n\n\t\t// Type not null will retrieve team members\n\t\tth.Store.EXPECT().GetUsersByTeam(teamID, \"\", false, false).Return([]*model.User{{ID: userID}}, nil)\n\n\t\tth.Store.EXPECT().PatchBoard(boardID, patch, userID).Return(\n\t\t\t&model.Board{\n\t\t\t\tID:     boardID,\n\t\t\t\tTeamID: teamID,\n\t\t\t},\n\t\t\tnil)\n\n\t\t// Should call GetMembersForBoard 2 times\n\t\t// for WS BroadcastBoardChange\n\t\t// for AddTeamMembers check\n\t\t// We are returning the user as a direct Board Member, so BroadcastMemberDelete won't be called\n\t\tth.Store.EXPECT().GetMembersForBoard(boardID).Return([]*model.BoardMember{{BoardID: boardID, UserID: userID, SchemeEditor: true}}, nil).Times(2)\n\n\t\tpatchedBoard, err := th.App.PatchBoard(patch, boardID, userID)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, boardID, patchedBoard.ID)\n\t})\n\n\tt.Run(\"patch type channel, user without post permissions\", func(t *testing.T) {\n\t\tconst boardID = \"board_id_1\"\n\t\tconst userID = \"user_id_2\"\n\t\tconst teamID = \"team_id_1\"\n\n\t\tchannelID := \"myChannel\"\n\t\tpatchType := model.BoardTypeOpen\n\t\tpatch := &model.BoardPatch{\n\t\t\tType:      &patchType,\n\t\t\tChannelID: &channelID,\n\t\t}\n\n\t\t// Type not nil, will cause board to be reteived\n\t\t// to check isTemplate\n\t\tth.Store.EXPECT().GetBoard(boardID).Return(&model.Board{\n\t\t\tID:         boardID,\n\t\t\tTeamID:     teamID,\n\t\t\tIsTemplate: true,\n\t\t}, nil).Times(1)\n\n\t\tth.API.EXPECT().HasPermissionToChannel(userID, channelID, model.PermissionCreatePost).Return(false).Times(1)\n\t\t_, err := th.App.PatchBoard(patch, boardID, userID)\n\t\trequire.Error(t, err)\n\t})\n\n\tt.Run(\"patch type channel, user with post permissions\", func(t *testing.T) {\n\t\tconst boardID = \"board_id_1\"\n\t\tconst userID = \"user_id_2\"\n\t\tconst teamID = \"team_id_1\"\n\n\t\tchannelID := \"myChannel\"\n\t\tpatch := &model.BoardPatch{\n\t\t\tChannelID: &channelID,\n\t\t}\n\n\t\t// Type not nil, will cause board to be reteived\n\t\t// to check isTemplate\n\t\tth.Store.EXPECT().GetBoard(boardID).Return(&model.Board{\n\t\t\tID:     boardID,\n\t\t\tTeamID: teamID,\n\t\t}, nil).Times(2)\n\n\t\tth.API.EXPECT().HasPermissionToChannel(userID, channelID, model.PermissionCreatePost).Return(true).Times(1)\n\n\t\tth.Store.EXPECT().PatchBoard(boardID, patch, userID).Return(\n\t\t\t&model.Board{\n\t\t\t\tID:     boardID,\n\t\t\t\tTeamID: teamID,\n\t\t\t},\n\t\t\tnil)\n\n\t\t// Should call GetMembersForBoard 2 times\n\t\t// - for WS BroadcastBoardChange\n\t\t// - for AddTeamMembers check\n\t\tth.Store.EXPECT().GetMembersForBoard(boardID).Return([]*model.BoardMember{}, nil).Times(2)\n\n\t\tth.Store.EXPECT().PostMessage(utils.Anything, \"\", \"\").Times(1)\n\n\t\tpatchedBoard, err := th.App.PatchBoard(patch, boardID, userID)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, boardID, patchedBoard.ID)\n\t})\n\n\tt.Run(\"patch type remove channel, user without post permissions\", func(t *testing.T) {\n\t\tconst boardID = \"board_id_1\"\n\t\tconst userID = \"user_id_2\"\n\t\tconst teamID = \"team_id_1\"\n\n\t\tconst channelID = \"myChannel\"\n\t\tclearChannel := \"\"\n\t\tpatchType := model.BoardTypeOpen\n\t\tpatch := &model.BoardPatch{\n\t\t\tType:      &patchType,\n\t\t\tChannelID: &clearChannel,\n\t\t}\n\n\t\t// Type not nil, will cause board to be reteived\n\t\t// to check isTemplate\n\t\tth.Store.EXPECT().GetBoard(boardID).Return(&model.Board{\n\t\t\tID:         boardID,\n\t\t\tTeamID:     teamID,\n\t\t\tIsTemplate: true,\n\t\t\tChannelID:  channelID,\n\t\t}, nil).Times(2)\n\n\t\tth.API.EXPECT().HasPermissionToChannel(userID, channelID, model.PermissionCreatePost).Return(false).Times(1)\n\n\t\tth.API.EXPECT().HasPermissionToTeam(userID, teamID, model.PermissionManageTeam).Return(false).Times(1)\n\t\t// Should call GetMembersForBoard 2 times\n\t\t// for WS BroadcastBoardChange\n\t\t// for AddTeamMembers check\n\t\t// We are returning the user as a direct Board Member, so BroadcastMemberDelete won't be called\n\t\tth.Store.EXPECT().GetMembersForBoard(boardID).Return([]*model.BoardMember{{BoardID: boardID, UserID: userID, SchemeEditor: true}}, nil).Times(1)\n\n\t\t_, err := th.App.PatchBoard(patch, boardID, userID)\n\t\trequire.Error(t, err)\n\t})\n}\n\nfunc TestGetBoardCount(t *testing.T) {\n\tth, tearDown := SetupTestHelper(t)\n\tdefer tearDown()\n\n\tt.Run(\"base case\", func(t *testing.T) {\n\t\tboardCount := int64(100)\n\t\tth.Store.EXPECT().GetBoardCount().Return(boardCount, nil)\n\n\t\tcount, err := th.App.GetBoardCount()\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, boardCount, count)\n\t})\n}\n\nfunc TestBoardCategory(t *testing.T) {\n\tth, tearDown := SetupTestHelper(t)\n\tdefer tearDown()\n\n\tt.Run(\"no boards default category exists\", func(t *testing.T) {\n\t\tth.Store.EXPECT().GetUserCategoryBoards(\"user_id\", \"team_id\").Return([]model.CategoryBoards{\n\t\t\t{\n\t\t\t\tCategory: model.Category{ID: \"category_id_1\", Name: \"Category 1\"},\n\t\t\t\tBoardMetadata: []model.CategoryBoardMetadata{\n\t\t\t\t\t{BoardID: \"board_id_1\"},\n\t\t\t\t\t{BoardID: \"board_id_2\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tCategory: model.Category{ID: \"category_id_2\", Name: \"Category 2\"},\n\t\t\t\tBoardMetadata: []model.CategoryBoardMetadata{\n\t\t\t\t\t{BoardID: \"board_id_3\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tCategory:      model.Category{ID: \"category_id_3\", Name: \"Category 3\"},\n\t\t\t\tBoardMetadata: []model.CategoryBoardMetadata{},\n\t\t\t},\n\t\t}, nil).Times(1)\n\n\t\t// when this function is called the second time, the default category is created\n\t\tth.Store.EXPECT().GetUserCategoryBoards(\"user_id\", \"team_id\").Return([]model.CategoryBoards{\n\t\t\t{\n\t\t\t\tCategory: model.Category{ID: \"category_id_1\", Name: \"Category 1\"},\n\t\t\t\tBoardMetadata: []model.CategoryBoardMetadata{\n\t\t\t\t\t{BoardID: \"board_id_1\"},\n\t\t\t\t\t{BoardID: \"board_id_2\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tCategory: model.Category{ID: \"category_id_2\", Name: \"Category 2\"},\n\t\t\t\tBoardMetadata: []model.CategoryBoardMetadata{\n\t\t\t\t\t{BoardID: \"board_id_3\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tCategory:      model.Category{ID: \"category_id_3\", Name: \"Category 3\"},\n\t\t\t\tBoardMetadata: []model.CategoryBoardMetadata{},\n\t\t\t},\n\t\t\t{\n\t\t\t\tCategory: model.Category{ID: \"default_category_id\", Type: model.CategoryTypeSystem, Name: \"Boards\"},\n\t\t\t},\n\t\t}, nil).Times(1)\n\n\t\tth.Store.EXPECT().CreateCategory(utils.Anything).Return(nil)\n\t\tth.Store.EXPECT().GetCategory(utils.Anything).Return(&model.Category{\n\t\t\tID:   \"default_category_id\",\n\t\t\tName: \"Boards\",\n\t\t}, nil)\n\t\tth.Store.EXPECT().GetMembersForUser(\"user_id\").Return([]*model.BoardMember{}, nil)\n\t\tth.Store.EXPECT().GetBoardsForUserAndTeam(\"user_id\", \"team_id\", false).Return([]*model.Board{}, nil)\n\t\tth.Store.EXPECT().AddUpdateCategoryBoard(\"user_id\", \"default_category_id\", []string{\n\t\t\t\"board_id_1\",\n\t\t\t\"board_id_2\",\n\t\t\t\"board_id_3\",\n\t\t}).Return(nil)\n\n\t\tboards := []*model.Board{\n\t\t\t{ID: \"board_id_1\"},\n\t\t\t{ID: \"board_id_2\"},\n\t\t\t{ID: \"board_id_3\"},\n\t\t}\n\n\t\terr := th.App.addBoardsToDefaultCategory(\"user_id\", \"team_id\", boards)\n\t\tassert.NoError(t, err)\n\t})\n}\n\nfunc TestDuplicateBoard(t *testing.T) {\n\tth, tearDown := SetupTestHelper(t)\n\tdefer tearDown()\n\n\tt.Run(\"base case\", func(t *testing.T) {\n\t\tboard := &model.Board{\n\t\t\tID:    \"board_id_2\",\n\t\t\tTitle: \"Duplicated Board\",\n\t\t}\n\n\t\tblock := &model.Block{\n\t\t\tID:   \"block_id_1\",\n\t\t\tType: \"image\",\n\t\t}\n\n\t\tth.Store.EXPECT().DuplicateBoard(\"board_id_1\", \"user_id_1\", \"team_id_1\", false).Return(\n\t\t\t&model.BoardsAndBlocks{\n\t\t\t\tBoards: []*model.Board{\n\t\t\t\t\tboard,\n\t\t\t\t},\n\t\t\t\tBlocks: []*model.Block{\n\t\t\t\t\tblock,\n\t\t\t\t},\n\t\t\t},\n\t\t\t[]*model.BoardMember{},\n\t\t\tnil,\n\t\t)\n\n\t\tth.Store.EXPECT().GetBoard(\"board_id_1\").Return(&model.Board{}, nil)\n\n\t\tth.Store.EXPECT().GetUserCategoryBoards(\"user_id_1\", \"team_id_1\").Return([]model.CategoryBoards{\n\t\t\t{\n\t\t\t\tCategory: model.Category{\n\t\t\t\t\tID:   \"category_id_1\",\n\t\t\t\t\tName: \"Boards\",\n\t\t\t\t\tType: \"system\",\n\t\t\t\t},\n\t\t\t},\n\t\t}, nil).Times(3)\n\n\t\tth.Store.EXPECT().AddUpdateCategoryBoard(\"user_id_1\", \"category_id_1\", utils.Anything).Return(nil)\n\n\t\t// for WS change broadcast\n\t\tth.Store.EXPECT().GetMembersForBoard(utils.Anything).Return([]*model.BoardMember{}, nil).Times(2)\n\n\t\tbab, members, err := th.App.DuplicateBoard(\"board_id_1\", \"user_id_1\", \"team_id_1\", false)\n\t\tassert.NoError(t, err)\n\t\tassert.NotNil(t, bab)\n\t\tassert.NotNil(t, members)\n\t})\n\n\tt.Run(\"duplicating board as template should not set it's category\", func(t *testing.T) {\n\t\tboard := &model.Board{\n\t\t\tID:    \"board_id_2\",\n\t\t\tTitle: \"Duplicated Board\",\n\t\t}\n\n\t\tblock := &model.Block{\n\t\t\tID:   \"block_id_1\",\n\t\t\tType: \"image\",\n\t\t}\n\n\t\tth.Store.EXPECT().DuplicateBoard(\"board_id_1\", \"user_id_1\", \"team_id_1\", true).Return(\n\t\t\t&model.BoardsAndBlocks{\n\t\t\t\tBoards: []*model.Board{\n\t\t\t\t\tboard,\n\t\t\t\t},\n\t\t\t\tBlocks: []*model.Block{\n\t\t\t\t\tblock,\n\t\t\t\t},\n\t\t\t},\n\t\t\t[]*model.BoardMember{},\n\t\t\tnil,\n\t\t)\n\n\t\tth.Store.EXPECT().GetBoard(\"board_id_1\").Return(&model.Board{}, nil)\n\n\t\t// for WS change broadcast\n\t\tth.Store.EXPECT().GetMembersForBoard(utils.Anything).Return([]*model.BoardMember{}, nil).Times(2)\n\n\t\tbab, members, err := th.App.DuplicateBoard(\"board_id_1\", \"user_id_1\", \"team_id_1\", true)\n\t\tassert.NoError(t, err)\n\t\tassert.NotNil(t, bab)\n\t\tassert.NotNil(t, members)\n\t})\n}\n\nfunc TestGetMembersForBoard(t *testing.T) {\n\tth, tearDown := SetupTestHelper(t)\n\tdefer tearDown()\n\n\tconst boardID = \"board_id_1\"\n\tconst userID = \"user_id_1\"\n\tconst teamID = \"team_id_1\"\n\n\tth.Store.EXPECT().GetMembersForBoard(boardID).Return([]*model.BoardMember{\n\t\t{\n\t\t\tBoardID:      boardID,\n\t\t\tUserID:       userID,\n\t\t\tSchemeEditor: true,\n\t\t},\n\t}, nil).Times(3)\n\tth.Store.EXPECT().GetBoard(boardID).Return(nil, nil).Times(1)\n\tt.Run(\"-base case\", func(t *testing.T) {\n\t\tmembers, err := th.App.GetMembersForBoard(boardID)\n\t\tassert.NoError(t, err)\n\t\tassert.NotNil(t, members)\n\t\tassert.False(t, members[0].SchemeAdmin)\n\t})\n\n\tboard := &model.Board{\n\t\tID:     boardID,\n\t\tTeamID: teamID,\n\t}\n\tth.Store.EXPECT().GetBoard(boardID).Return(board, nil).Times(2)\n\tth.API.EXPECT().HasPermissionToTeam(userID, teamID, model.PermissionManageTeam).Return(false).Times(1)\n\n\tt.Run(\"-team check false \", func(t *testing.T) {\n\t\tmembers, err := th.App.GetMembersForBoard(boardID)\n\t\tassert.NoError(t, err)\n\t\tassert.NotNil(t, members)\n\n\t\tassert.False(t, members[0].SchemeAdmin)\n\t})\n\n\tth.API.EXPECT().HasPermissionToTeam(userID, teamID, model.PermissionManageTeam).Return(true).Times(1)\n\tt.Run(\"-team check true\", func(t *testing.T) {\n\t\tmembers, err := th.App.GetMembersForBoard(boardID)\n\t\tassert.NoError(t, err)\n\t\tassert.NotNil(t, members)\n\n\t\tassert.True(t, members[0].SchemeAdmin)\n\t})\n}\n\nfunc TestGetMembersForUser(t *testing.T) {\n\tth, tearDown := SetupTestHelper(t)\n\tdefer tearDown()\n\n\tconst boardID = \"board_id_1\"\n\tconst userID = \"user_id_1\"\n\tconst teamID = \"team_id_1\"\n\n\tth.Store.EXPECT().GetMembersForUser(userID).Return([]*model.BoardMember{\n\t\t{\n\t\t\tBoardID:      boardID,\n\t\t\tUserID:       userID,\n\t\t\tSchemeEditor: true,\n\t\t},\n\t}, nil).Times(3)\n\tth.Store.EXPECT().GetBoard(boardID).Return(nil, nil)\n\tt.Run(\"-base case\", func(t *testing.T) {\n\t\tmembers, err := th.App.GetMembersForUser(userID)\n\t\tassert.NoError(t, err)\n\t\tassert.NotNil(t, members)\n\t\tassert.False(t, members[0].SchemeAdmin)\n\t})\n\n\tboard := &model.Board{\n\t\tID:     boardID,\n\t\tTeamID: teamID,\n\t}\n\tth.Store.EXPECT().GetBoard(boardID).Return(board, nil).Times(2)\n\n\tth.API.EXPECT().HasPermissionToTeam(userID, teamID, model.PermissionManageTeam).Return(false).Times(1)\n\tt.Run(\"-team check false \", func(t *testing.T) {\n\t\tmembers, err := th.App.GetMembersForUser(userID)\n\t\tassert.NoError(t, err)\n\t\tassert.NotNil(t, members)\n\n\t\tassert.False(t, members[0].SchemeAdmin)\n\t})\n\n\tth.API.EXPECT().HasPermissionToTeam(userID, teamID, model.PermissionManageTeam).Return(true).Times(1)\n\tt.Run(\"-team check true\", func(t *testing.T) {\n\t\tmembers, err := th.App.GetMembersForUser(userID)\n\t\tassert.NoError(t, err)\n\t\tassert.NotNil(t, members)\n\n\t\tassert.True(t, members[0].SchemeAdmin)\n\t})\n}\n"
  },
  {
    "path": "server/app/cards.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage app\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n)\n\nfunc (a *App) CreateCard(card *model.Card, boardID string, userID string, disableNotify bool) (*model.Card, error) {\n\t// Convert the card struct to a block and insert the block.\n\tnow := utils.GetMillis()\n\n\tcard.ID = utils.NewID(utils.IDTypeCard)\n\tcard.BoardID = boardID\n\tcard.CreatedBy = userID\n\tcard.ModifiedBy = userID\n\tcard.CreateAt = now\n\tcard.UpdateAt = now\n\tcard.DeleteAt = 0\n\n\tblock := model.Card2Block(card)\n\n\tnewBlocks, err := a.InsertBlocksAndNotify([]*model.Block{block}, userID, disableNotify)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"cannot create card: %w\", err)\n\t}\n\n\tnewCard, err := model.Block2Card(newBlocks[0])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn newCard, nil\n}\n\nfunc (a *App) GetCardsForBoard(boardID string, page int, perPage int) ([]*model.Card, error) {\n\topts := model.QueryBlocksOptions{\n\t\tBoardID:   boardID,\n\t\tBlockType: model.TypeCard,\n\t\tPage:      page,\n\t\tPerPage:   perPage,\n\t}\n\n\tblocks, err := a.store.GetBlocks(opts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcards := make([]*model.Card, 0, len(blocks))\n\tfor _, blk := range blocks {\n\t\tb := blk\n\t\tif card, err := model.Block2Card(b); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"Block2Card fail: %w\", err)\n\t\t} else {\n\t\t\tcards = append(cards, card)\n\t\t}\n\t}\n\treturn cards, nil\n}\n\nfunc (a *App) PatchCard(cardPatch *model.CardPatch, cardID string, userID string, disableNotify bool) (*model.Card, error) {\n\tblockPatch, err := model.CardPatch2BlockPatch(cardPatch)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tnewBlock, err := a.PatchBlockAndNotify(cardID, blockPatch, userID, disableNotify)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"cannot patch card %s: %w\", cardID, err)\n\t}\n\n\tnewCard, err := model.Block2Card(newBlock)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn newCard, nil\n}\n\nfunc (a *App) GetCardByID(cardID string) (*model.Card, error) {\n\tcardBlock, err := a.GetBlockByID(cardID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcard, err := model.Block2Card(cardBlock)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn card, nil\n}\n"
  },
  {
    "path": "server/app/cards_test.go",
    "content": "package app\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/golang/mock/gomock\"\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestCreateCard(t *testing.T) {\n\tth, tearDown := SetupTestHelper(t)\n\tdefer tearDown()\n\n\tboard := &model.Board{\n\t\tID: utils.NewID(utils.IDTypeBoard),\n\t}\n\tuserID := utils.NewID(utils.IDTypeUser)\n\n\tprops := makeProps(3)\n\n\tcard := &model.Card{\n\t\tBoardID:      board.ID,\n\t\tCreatedBy:    userID,\n\t\tModifiedBy:   userID,\n\t\tTitle:        \"test card\",\n\t\tContentOrder: []string{utils.NewID(utils.IDTypeBlock), utils.NewID(utils.IDTypeBlock)},\n\t\tProperties:   props,\n\t}\n\tblock := model.Card2Block(card)\n\n\tt.Run(\"success scenario\", func(t *testing.T) {\n\t\tth.Store.EXPECT().GetBoard(board.ID).Return(board, nil)\n\t\tth.Store.EXPECT().InsertBlock(gomock.AssignableToTypeOf(reflect.TypeOf(block)), userID).Return(nil)\n\t\tth.Store.EXPECT().GetMembersForBoard(board.ID).Return([]*model.BoardMember{}, nil)\n\n\t\tnewCard, err := th.App.CreateCard(card, board.ID, userID, false)\n\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, card.BoardID, newCard.BoardID)\n\t\trequire.Equal(t, card.Title, newCard.Title)\n\t\trequire.Equal(t, card.ContentOrder, newCard.ContentOrder)\n\t\trequire.EqualValues(t, card.Properties, newCard.Properties)\n\t})\n\n\tt.Run(\"error scenario\", func(t *testing.T) {\n\t\tth.Store.EXPECT().GetBoard(board.ID).Return(board, nil)\n\t\tth.Store.EXPECT().InsertBlock(gomock.AssignableToTypeOf(reflect.TypeOf(block)), userID).Return(blockError{\"error\"})\n\n\t\tnewCard, err := th.App.CreateCard(card, board.ID, userID, false)\n\n\t\trequire.Error(t, err, \"error\")\n\t\trequire.Nil(t, newCard)\n\t})\n}\n\nfunc TestGetCards(t *testing.T) {\n\tth, tearDown := SetupTestHelper(t)\n\tdefer tearDown()\n\n\tboard := &model.Board{\n\t\tID: utils.NewID(utils.IDTypeBoard),\n\t}\n\n\tconst cardCount = 25\n\n\t// make some cards\n\tblocks := make([]*model.Block, 0, cardCount)\n\tfor i := 0; i < cardCount; i++ {\n\t\tcard := &model.Block{\n\t\t\tID:       utils.NewID(utils.IDTypeBlock),\n\t\t\tParentID: board.ID,\n\t\t\tSchema:   1,\n\t\t\tType:     model.TypeCard,\n\t\t\tTitle:    fmt.Sprintf(\"card %d\", i),\n\t\t\tBoardID:  board.ID,\n\t\t}\n\t\tblocks = append(blocks, card)\n\t}\n\n\tt.Run(\"success scenario\", func(t *testing.T) {\n\t\topts := model.QueryBlocksOptions{\n\t\t\tBoardID:   board.ID,\n\t\t\tBlockType: model.TypeCard,\n\t\t}\n\n\t\tth.Store.EXPECT().GetBlocks(opts).Return(blocks, nil)\n\n\t\tcards, err := th.App.GetCardsForBoard(board.ID, 0, 0)\n\t\trequire.NoError(t, err)\n\t\tassert.Len(t, cards, cardCount)\n\t})\n\n\tt.Run(\"error scenario\", func(t *testing.T) {\n\t\topts := model.QueryBlocksOptions{\n\t\t\tBoardID:   board.ID,\n\t\t\tBlockType: model.TypeCard,\n\t\t}\n\n\t\tth.Store.EXPECT().GetBlocks(opts).Return(nil, blockError{\"error\"})\n\n\t\tcards, err := th.App.GetCardsForBoard(board.ID, 0, 0)\n\t\trequire.Error(t, err)\n\t\trequire.Nil(t, cards)\n\t})\n}\n\nfunc TestPatchCard(t *testing.T) {\n\tth, tearDown := SetupTestHelper(t)\n\tdefer tearDown()\n\n\tboard := &model.Board{\n\t\tID: utils.NewID(utils.IDTypeBoard),\n\t}\n\tuserID := utils.NewID(utils.IDTypeUser)\n\n\tprops := makeProps(3)\n\n\tcard := &model.Card{\n\t\tBoardID:      board.ID,\n\t\tCreatedBy:    userID,\n\t\tModifiedBy:   userID,\n\t\tTitle:        \"test card for patch\",\n\t\tContentOrder: []string{utils.NewID(utils.IDTypeBlock), utils.NewID(utils.IDTypeBlock)},\n\t\tProperties:   copyProps(props),\n\t}\n\n\tnewTitle := \"patched\"\n\tnewIcon := \"😀\"\n\tnewContentOrder := reverse(card.ContentOrder)\n\n\tcardPatch := &model.CardPatch{\n\t\tTitle:             &newTitle,\n\t\tContentOrder:      &newContentOrder,\n\t\tIcon:              &newIcon,\n\t\tUpdatedProperties: modifyProps(props),\n\t}\n\n\tt.Run(\"success scenario\", func(t *testing.T) {\n\t\texpectedPatchedCard := cardPatch.Patch(card)\n\t\texpectedPatchedBlock := model.Card2Block(expectedPatchedCard)\n\n\t\tvar blockPatch *model.BlockPatch\n\t\tth.Store.EXPECT().GetBoard(board.ID).Return(board, nil)\n\t\tth.Store.EXPECT().PatchBlock(card.ID, gomock.AssignableToTypeOf(reflect.TypeOf(blockPatch)), userID).Return(nil)\n\t\tth.Store.EXPECT().GetMembersForBoard(board.ID).Return([]*model.BoardMember{}, nil)\n\t\tth.Store.EXPECT().GetBlock(card.ID).Return(expectedPatchedBlock, nil).AnyTimes()\n\n\t\tpatchedCard, err := th.App.PatchCard(cardPatch, card.ID, userID, false)\n\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, board.ID, patchedCard.BoardID)\n\t\trequire.Equal(t, newTitle, patchedCard.Title)\n\t\trequire.Equal(t, newIcon, patchedCard.Icon)\n\t\trequire.Equal(t, newContentOrder, patchedCard.ContentOrder)\n\t\trequire.EqualValues(t, expectedPatchedCard.Properties, patchedCard.Properties)\n\t})\n\n\tt.Run(\"error scenario\", func(t *testing.T) {\n\t\tvar blockPatch *model.BlockPatch\n\t\tth.Store.EXPECT().GetBoard(board.ID).Return(board, nil)\n\t\tth.Store.EXPECT().PatchBlock(card.ID, gomock.AssignableToTypeOf(reflect.TypeOf(blockPatch)), userID).Return(blockError{\"error\"})\n\n\t\tpatchedCard, err := th.App.PatchCard(cardPatch, card.ID, userID, false)\n\n\t\trequire.Error(t, err, \"error\")\n\t\trequire.Nil(t, patchedCard)\n\t})\n}\n\nfunc TestGetCard(t *testing.T) {\n\tth, tearDown := SetupTestHelper(t)\n\tdefer tearDown()\n\n\tboardID := utils.NewID(utils.IDTypeBoard)\n\tuserID := utils.NewID(utils.IDTypeUser)\n\tprops := makeProps(5)\n\tcontentOrder := []string{utils.NewID(utils.IDTypeUser), utils.NewID(utils.IDTypeUser)}\n\tfields := make(map[string]any)\n\tfields[\"contentOrder\"] = contentOrder\n\tfields[\"properties\"] = props\n\tfields[\"icon\"] = \"😀\"\n\tfields[\"isTemplate\"] = true\n\n\tblock := &model.Block{\n\t\tID:         utils.NewID(utils.IDTypeBlock),\n\t\tParentID:   boardID,\n\t\tType:       model.TypeCard,\n\t\tTitle:      \"test card\",\n\t\tBoardID:    boardID,\n\t\tFields:     fields,\n\t\tCreatedBy:  userID,\n\t\tModifiedBy: userID,\n\t}\n\n\tt.Run(\"success scenario\", func(t *testing.T) {\n\t\tth.Store.EXPECT().GetBlock(block.ID).Return(block, nil)\n\n\t\tcard, err := th.App.GetCardByID(block.ID)\n\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, boardID, card.BoardID)\n\t\trequire.Equal(t, block.Title, card.Title)\n\t\trequire.Equal(t, \"😀\", card.Icon)\n\t\trequire.Equal(t, true, card.IsTemplate)\n\t\trequire.Equal(t, contentOrder, card.ContentOrder)\n\t\trequire.EqualValues(t, props, card.Properties)\n\t})\n\n\tt.Run(\"not found\", func(t *testing.T) {\n\t\tbogusID := utils.NewID(utils.IDTypeBlock)\n\t\tth.Store.EXPECT().GetBlock(bogusID).Return(nil, model.NewErrNotFound(bogusID))\n\n\t\tcard, err := th.App.GetCardByID(bogusID)\n\n\t\trequire.Error(t, err, \"error\")\n\t\trequire.True(t, model.IsErrNotFound(err))\n\t\trequire.Nil(t, card)\n\t})\n\n\tt.Run(\"error scenario\", func(t *testing.T) {\n\t\tth.Store.EXPECT().GetBlock(block.ID).Return(nil, blockError{\"error\"})\n\n\t\tcard, err := th.App.GetCardByID(block.ID)\n\n\t\trequire.Error(t, err, \"error\")\n\t\trequire.Nil(t, card)\n\t})\n}\n\n// reverse is a helper function to copy and reverse a slice of strings.\nfunc reverse(src []string) []string {\n\tout := make([]string, 0, len(src))\n\tfor i := len(src) - 1; i >= 0; i-- {\n\t\tout = append(out, src[i])\n\t}\n\treturn out\n}\n\nfunc makeProps(count int) map[string]any {\n\tprops := make(map[string]any)\n\tfor i := 0; i < count; i++ {\n\t\tprops[utils.NewID(utils.IDTypeBlock)] = utils.NewID(utils.IDTypeBlock)\n\t}\n\treturn props\n}\n\nfunc copyProps(m map[string]any) map[string]any {\n\tout := make(map[string]any)\n\tfor k, v := range m {\n\t\tout[k] = v\n\t}\n\treturn out\n}\n\nfunc modifyProps(m map[string]any) map[string]any {\n\tout := make(map[string]any)\n\tfor k := range m {\n\t\tout[k] = utils.NewID(utils.IDTypeBlock)\n\t}\n\treturn out\n}\n"
  },
  {
    "path": "server/app/category.go",
    "content": "package app\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n)\n\nvar errCategoryNotFound = errors.New(\"category ID specified in input does not exist for user\")\nvar errCategoriesLengthMismatch = errors.New(\"cannot update category order, passed list of categories different size than in database\")\nvar ErrCannotDeleteSystemCategory = errors.New(\"cannot delete a system category\")\nvar ErrCannotUpdateSystemCategory = errors.New(\"cannot update a system category\")\n\nfunc (a *App) GetCategory(categoryID string) (*model.Category, error) {\n\treturn a.store.GetCategory(categoryID)\n}\n\nfunc (a *App) CreateCategory(category *model.Category) (*model.Category, error) {\n\tcategory.Hydrate()\n\tif err := category.IsValid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err := a.store.CreateCategory(*category); err != nil {\n\t\treturn nil, err\n\t}\n\n\tcreatedCategory, err := a.store.GetCategory(category.ID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tgo func() {\n\t\ta.wsAdapter.BroadcastCategoryChange(*createdCategory)\n\t}()\n\n\treturn createdCategory, nil\n}\n\nfunc (a *App) UpdateCategory(category *model.Category) (*model.Category, error) {\n\tcategory.Hydrate()\n\n\tif err := category.IsValid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// verify if category belongs to the user\n\texistingCategory, err := a.store.GetCategory(category.ID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif existingCategory.DeleteAt != 0 {\n\t\treturn nil, model.ErrCategoryDeleted\n\t}\n\n\tif existingCategory.UserID != category.UserID {\n\t\treturn nil, model.ErrCategoryPermissionDenied\n\t}\n\n\tif existingCategory.TeamID != category.TeamID {\n\t\treturn nil, model.ErrCategoryPermissionDenied\n\t}\n\n\t// in case type was defaulted above, set to existingCategory.Type\n\tcategory.Type = existingCategory.Type\n\tif existingCategory.Type == model.CategoryTypeSystem {\n\t\t// You cannot rename or delete a system category,\n\t\t// So restoring its name and undeleting it if set so.\n\t\tcategory.Name = existingCategory.Name\n\t\tcategory.DeleteAt = 0\n\t}\n\n\tcategory.UpdateAt = utils.GetMillis()\n\tif err = category.IsValid(); err != nil {\n\t\treturn nil, err\n\t}\n\tif err = a.store.UpdateCategory(*category); err != nil {\n\t\treturn nil, err\n\t}\n\n\tupdatedCategory, err := a.store.GetCategory(category.ID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tgo func() {\n\t\ta.wsAdapter.BroadcastCategoryChange(*updatedCategory)\n\t}()\n\n\treturn updatedCategory, nil\n}\n\nfunc (a *App) DeleteCategory(categoryID, userID, teamID string) (*model.Category, error) {\n\texistingCategory, err := a.store.GetCategory(categoryID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// category is already deleted. This avoids\n\t// overriding the original deleted at timestamp\n\tif existingCategory.DeleteAt != 0 {\n\t\treturn existingCategory, nil\n\t}\n\n\t// verify if category belongs to the user\n\tif existingCategory.UserID != userID {\n\t\treturn nil, model.ErrCategoryPermissionDenied\n\t}\n\n\t// verify if category belongs to the team\n\tif existingCategory.TeamID != teamID {\n\t\treturn nil, model.NewErrInvalidCategory(\"category doesn't belong to the team\")\n\t}\n\n\tif existingCategory.Type == model.CategoryTypeSystem {\n\t\treturn nil, ErrCannotDeleteSystemCategory\n\t}\n\n\tif err = a.moveBoardsToDefaultCategory(userID, teamID, categoryID); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err = a.store.DeleteCategory(categoryID, userID, teamID); err != nil {\n\t\treturn nil, err\n\t}\n\n\tdeletedCategory, err := a.store.GetCategory(categoryID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tgo func() {\n\t\ta.wsAdapter.BroadcastCategoryChange(*deletedCategory)\n\t}()\n\n\treturn deletedCategory, nil\n}\n\nfunc (a *App) moveBoardsToDefaultCategory(userID, teamID, sourceCategoryID string) error {\n\t// we need a list of boards associated to this category\n\t// so we can move them to user's default Boards category\n\tcategoryBoards, err := a.GetUserCategoryBoards(userID, teamID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar sourceCategoryBoards *model.CategoryBoards\n\tdefaultCategoryID := \"\"\n\n\t// iterate user's categories to find the source category\n\t// and the default category.\n\t// We need source category to get the list of its board\n\t// and the default category to know its ID to\n\t// move source category's boards to.\n\tfor i := range categoryBoards {\n\t\tif categoryBoards[i].ID == sourceCategoryID {\n\t\t\tsourceCategoryBoards = &categoryBoards[i]\n\t\t}\n\n\t\tif categoryBoards[i].Name == defaultCategoryBoards {\n\t\t\tdefaultCategoryID = categoryBoards[i].ID\n\t\t}\n\n\t\t// if both categories are found, no need to iterate furthur.\n\t\tif sourceCategoryBoards != nil && defaultCategoryID != \"\" {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif sourceCategoryBoards == nil {\n\t\treturn errCategoryNotFound\n\t}\n\n\tif defaultCategoryID == \"\" {\n\t\treturn fmt.Errorf(\"moveBoardsToDefaultCategory: %w\", errNoDefaultCategoryFound)\n\t}\n\n\tboardIDs := make([]string, len(sourceCategoryBoards.BoardMetadata))\n\tfor i := range sourceCategoryBoards.BoardMetadata {\n\t\tboardIDs[i] = sourceCategoryBoards.BoardMetadata[i].BoardID\n\t}\n\n\tif err := a.AddUpdateUserCategoryBoard(teamID, userID, defaultCategoryID, boardIDs); err != nil {\n\t\treturn fmt.Errorf(\"moveBoardsToDefaultCategory: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc (a *App) ReorderCategories(userID, teamID string, newCategoryOrder []string) ([]string, error) {\n\tif err := a.verifyNewCategoriesMatchExisting(userID, teamID, newCategoryOrder); err != nil {\n\t\treturn nil, err\n\t}\n\n\tnewOrder, err := a.store.ReorderCategories(userID, teamID, newCategoryOrder)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tgo func() {\n\t\ta.wsAdapter.BroadcastCategoryReorder(teamID, userID, newOrder)\n\t}()\n\n\treturn newOrder, nil\n}\n\nfunc (a *App) verifyNewCategoriesMatchExisting(userID, teamID string, newCategoryOrder []string) error {\n\texistingCategories, err := a.store.GetUserCategories(userID, teamID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif len(newCategoryOrder) != len(existingCategories) {\n\t\treturn fmt.Errorf(\n\t\t\t\"%w length new categories: %d, length existing categories: %d, userID: %s, teamID: %s\",\n\t\t\terrCategoriesLengthMismatch,\n\t\t\tlen(newCategoryOrder),\n\t\t\tlen(existingCategories),\n\t\t\tuserID,\n\t\t\tteamID,\n\t\t)\n\t}\n\n\texistingCategoriesMap := map[string]bool{}\n\tfor _, category := range existingCategories {\n\t\texistingCategoriesMap[category.ID] = true\n\t}\n\n\tfor _, newCategoryID := range newCategoryOrder {\n\t\tif _, found := existingCategoriesMap[newCategoryID]; !found {\n\t\t\treturn fmt.Errorf(\n\t\t\t\t\"%w specified category ID: %s, userID: %s, teamID: %s\",\n\t\t\t\terrCategoryNotFound,\n\t\t\t\tnewCategoryID,\n\t\t\t\tuserID,\n\t\t\t\tteamID,\n\t\t\t)\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "server/app/category_boards.go",
    "content": "package app\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n)\n\nconst defaultCategoryBoards = \"Boards\"\n\nvar errCategoryBoardsLengthMismatch = errors.New(\"cannot update category boards order, passed list of categories boards different size than in database\")\nvar errBoardNotFoundInCategory = errors.New(\"specified board ID not found in specified category ID\")\nvar errBoardMembershipNotFound = errors.New(\"board membership not found for user's board\")\n\nfunc (a *App) GetUserCategoryBoards(userID, teamID string) ([]model.CategoryBoards, error) {\n\tcategoryBoards, err := a.store.GetUserCategoryBoards(userID, teamID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcreatedCategoryBoards, err := a.createDefaultCategoriesIfRequired(categoryBoards, userID, teamID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcategoryBoards = append(categoryBoards, createdCategoryBoards...)\n\treturn categoryBoards, nil\n}\n\nfunc (a *App) createDefaultCategoriesIfRequired(existingCategoryBoards []model.CategoryBoards, userID, teamID string) ([]model.CategoryBoards, error) {\n\tcreatedCategories := []model.CategoryBoards{}\n\n\tboardsCategoryExist := false\n\tfor _, categoryBoard := range existingCategoryBoards {\n\t\tif categoryBoard.Name == defaultCategoryBoards {\n\t\t\tboardsCategoryExist = true\n\t\t}\n\t}\n\n\tif !boardsCategoryExist {\n\t\tcreatedCategoryBoards, err := a.createBoardsCategory(userID, teamID, existingCategoryBoards)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tcreatedCategories = append(createdCategories, *createdCategoryBoards)\n\t}\n\n\treturn createdCategories, nil\n}\n\nfunc (a *App) createBoardsCategory(userID, teamID string, existingCategoryBoards []model.CategoryBoards) (*model.CategoryBoards, error) {\n\t// create the category\n\tcategory := model.Category{\n\t\tName:      defaultCategoryBoards,\n\t\tUserID:    userID,\n\t\tTeamID:    teamID,\n\t\tCollapsed: false,\n\t\tType:      model.CategoryTypeSystem,\n\t\tSortOrder: len(existingCategoryBoards) * model.CategoryBoardsSortOrderGap,\n\t}\n\tcreatedCategory, err := a.CreateCategory(&category)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"createBoardsCategory default category creation failed: %w\", err)\n\t}\n\n\t// once the category is created, we need to move all boards which do not\n\t// belong to any category, into this category.\n\n\tboardMembers, err := a.GetMembersForUser(userID)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"createBoardsCategory error fetching user's board memberships: %w\", err)\n\t}\n\n\tboardMemberByBoardID := map[string]*model.BoardMember{}\n\tfor _, boardMember := range boardMembers {\n\t\tboardMemberByBoardID[boardMember.BoardID] = boardMember\n\t}\n\n\tcreatedCategoryBoards := &model.CategoryBoards{\n\t\tCategory:      *createdCategory,\n\t\tBoardMetadata: []model.CategoryBoardMetadata{},\n\t}\n\n\t// get user's current team's baords\n\tuserTeamBoards, err := a.GetBoardsForUserAndTeam(userID, teamID, false)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"createBoardsCategory error fetching user's team's boards: %w\", err)\n\t}\n\n\tboardIDsToAdd := []string{}\n\n\tfor _, board := range userTeamBoards {\n\t\tboardMembership, ok := boardMemberByBoardID[board.ID]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"createBoardsCategory: %w\", errBoardMembershipNotFound)\n\t\t}\n\n\t\t// boards with implicit access (aka synthetic membership),\n\t\t// should show up in LHS only when openign them explicitelly.\n\t\t// So we don't process any synthetic membership boards\n\t\t// and only add boards with explicit access to, to the the LHS,\n\t\t// for example, if a user explicitelly added another user to a board.\n\t\tif boardMembership.Synthetic {\n\t\t\tcontinue\n\t\t}\n\n\t\tbelongsToCategory := false\n\n\t\tfor _, categoryBoard := range existingCategoryBoards {\n\t\t\tfor _, metadata := range categoryBoard.BoardMetadata {\n\t\t\t\tif metadata.BoardID == board.ID {\n\t\t\t\t\tbelongsToCategory = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// stop looking into other categories if\n\t\t\t// the board was found in a category\n\t\t\tif belongsToCategory {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif !belongsToCategory {\n\t\t\tboardIDsToAdd = append(boardIDsToAdd, board.ID)\n\t\t\tnewBoardMetadata := model.CategoryBoardMetadata{\n\t\t\t\tBoardID: board.ID,\n\t\t\t\tHidden:  false,\n\t\t\t}\n\t\t\tcreatedCategoryBoards.BoardMetadata = append(createdCategoryBoards.BoardMetadata, newBoardMetadata)\n\t\t}\n\t}\n\n\tif len(boardIDsToAdd) > 0 {\n\t\tif err := a.AddUpdateUserCategoryBoard(teamID, userID, createdCategory.ID, boardIDsToAdd); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"createBoardsCategory failed to add category-less board to the default category, defaultCategoryID: %s, error: %w\", createdCategory.ID, err)\n\t\t}\n\t}\n\n\treturn createdCategoryBoards, nil\n}\n\nfunc (a *App) AddUpdateUserCategoryBoard(teamID, userID, categoryID string, boardIDs []string) error {\n\tif len(boardIDs) == 0 {\n\t\treturn nil\n\t}\n\n\terr := a.store.AddUpdateCategoryBoard(userID, categoryID, boardIDs)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tuserCategoryBoards, err := a.GetUserCategoryBoards(userID, teamID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar updatedCategory *model.CategoryBoards\n\tfor i := range userCategoryBoards {\n\t\tif userCategoryBoards[i].ID == categoryID {\n\t\t\tupdatedCategory = &userCategoryBoards[i]\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif updatedCategory == nil {\n\t\treturn errCategoryNotFound\n\t}\n\n\twsPayload := make([]*model.BoardCategoryWebsocketData, len(updatedCategory.BoardMetadata))\n\ti := 0\n\tfor _, categoryBoardMetadata := range updatedCategory.BoardMetadata {\n\t\twsPayload[i] = &model.BoardCategoryWebsocketData{\n\t\t\tBoardID:    categoryBoardMetadata.BoardID,\n\t\t\tCategoryID: categoryID,\n\t\t\tHidden:     categoryBoardMetadata.Hidden,\n\t\t}\n\t\ti++\n\t}\n\n\ta.blockChangeNotifier.Enqueue(func() error {\n\t\ta.wsAdapter.BroadcastCategoryBoardChange(\n\t\t\tteamID,\n\t\t\tuserID,\n\t\t\twsPayload,\n\t\t)\n\t\treturn nil\n\t})\n\n\treturn nil\n}\n\nfunc (a *App) ReorderCategoryBoards(userID, teamID, categoryID string, newBoardsOrder []string) ([]string, error) {\n\tif err := a.verifyNewCategoryBoardsMatchExisting(userID, teamID, categoryID, newBoardsOrder); err != nil {\n\t\treturn nil, err\n\t}\n\n\tnewOrder, err := a.store.ReorderCategoryBoards(categoryID, newBoardsOrder)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tgo func() {\n\t\ta.wsAdapter.BroadcastCategoryBoardsReorder(teamID, userID, categoryID, newOrder)\n\t}()\n\n\treturn newOrder, nil\n}\n\nfunc (a *App) verifyNewCategoryBoardsMatchExisting(userID, teamID, categoryID string, newBoardsOrder []string) error {\n\t// this function is to ensure that we don't miss specifying\n\t// all boards of the category while reordering.\n\texistingCategoryBoards, err := a.GetUserCategoryBoards(userID, teamID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar targetCategoryBoards *model.CategoryBoards\n\tfor i := range existingCategoryBoards {\n\t\tif existingCategoryBoards[i].Category.ID == categoryID {\n\t\t\ttargetCategoryBoards = &existingCategoryBoards[i]\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif targetCategoryBoards == nil {\n\t\treturn fmt.Errorf(\"%w categoryID: %s\", errCategoryNotFound, categoryID)\n\t}\n\n\tif len(targetCategoryBoards.BoardMetadata) != len(newBoardsOrder) {\n\t\treturn fmt.Errorf(\n\t\t\t\"%w length new category boards: %d, length existing category boards: %d, userID: %s, teamID: %s, categoryID: %s\",\n\t\t\terrCategoryBoardsLengthMismatch,\n\t\t\tlen(newBoardsOrder),\n\t\t\tlen(targetCategoryBoards.BoardMetadata),\n\t\t\tuserID,\n\t\t\tteamID,\n\t\t\tcategoryID,\n\t\t)\n\t}\n\n\texistingBoardMap := map[string]bool{}\n\tfor _, metadata := range targetCategoryBoards.BoardMetadata {\n\t\texistingBoardMap[metadata.BoardID] = true\n\t}\n\n\tfor _, boardID := range newBoardsOrder {\n\t\tif _, found := existingBoardMap[boardID]; !found {\n\t\t\treturn fmt.Errorf(\n\t\t\t\t\"%w board ID: %s, category ID: %s, userID: %s, teamID: %s\",\n\t\t\t\terrBoardNotFoundInCategory,\n\t\t\t\tboardID,\n\t\t\t\tcategoryID,\n\t\t\t\tuserID,\n\t\t\t\tteamID,\n\t\t\t)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (a *App) SetBoardVisibility(teamID, userID, categoryID, boardID string, visible bool) error {\n\tif err := a.store.SetBoardVisibility(userID, categoryID, boardID, visible); err != nil {\n\t\treturn fmt.Errorf(\"SetBoardVisibility: failed to update board visibility: %w\", err)\n\t}\n\n\ta.wsAdapter.BroadcastCategoryBoardChange(teamID, userID, []*model.BoardCategoryWebsocketData{\n\t\t{\n\t\t\tBoardID:    boardID,\n\t\t\tCategoryID: categoryID,\n\t\t\tHidden:     !visible,\n\t\t},\n\t})\n\n\treturn nil\n}\n"
  },
  {
    "path": "server/app/category_boards_test.go",
    "content": "package app\n\nimport (\n\t\"testing\"\n\n\t\"github.com/mattermost/focalboard/server/utils\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestGetUserCategoryBoards(t *testing.T) {\n\tth, tearDown := SetupTestHelper(t)\n\tdefer tearDown()\n\n\tt.Run(\"user had no default category and had boards\", func(t *testing.T) {\n\t\tth.Store.EXPECT().GetUserCategoryBoards(\"user_id\", \"team_id\").Return([]model.CategoryBoards{}, nil).Times(1)\n\t\tth.Store.EXPECT().GetUserCategoryBoards(\"user_id\", \"team_id\").Return([]model.CategoryBoards{\n\t\t\t{\n\t\t\t\tCategory: model.Category{\n\t\t\t\t\tID:   \"boards_category_id\",\n\t\t\t\t\tType: model.CategoryTypeSystem,\n\t\t\t\t\tName: \"Boards\",\n\t\t\t\t},\n\t\t\t},\n\t\t}, nil).Times(1)\n\t\tth.Store.EXPECT().CreateCategory(utils.Anything).Return(nil)\n\t\tth.Store.EXPECT().GetCategory(utils.Anything).Return(&model.Category{\n\t\t\tID:   \"boards_category_id\",\n\t\t\tName: \"Boards\",\n\t\t}, nil)\n\n\t\tboard1 := &model.Board{\n\t\t\tID: \"board_id_1\",\n\t\t}\n\n\t\tboard2 := &model.Board{\n\t\t\tID: \"board_id_2\",\n\t\t}\n\n\t\tboard3 := &model.Board{\n\t\t\tID: \"board_id_3\",\n\t\t}\n\n\t\tth.Store.EXPECT().GetBoardsForUserAndTeam(\"user_id\", \"team_id\", false).Return([]*model.Board{board1, board2, board3}, nil)\n\n\t\tth.Store.EXPECT().GetMembersForUser(\"user_id\").Return([]*model.BoardMember{\n\t\t\t{\n\t\t\t\tBoardID:   \"board_id_1\",\n\t\t\t\tSynthetic: false,\n\t\t\t},\n\t\t\t{\n\t\t\t\tBoardID:   \"board_id_2\",\n\t\t\t\tSynthetic: false,\n\t\t\t},\n\t\t\t{\n\t\t\t\tBoardID:   \"board_id_3\",\n\t\t\t\tSynthetic: false,\n\t\t\t},\n\t\t}, nil)\n\t\tth.Store.EXPECT().GetBoard(utils.Anything).Return(nil, nil).Times(3)\n\t\tth.Store.EXPECT().AddUpdateCategoryBoard(\"user_id\", \"boards_category_id\", []string{\"board_id_1\", \"board_id_2\", \"board_id_3\"}).Return(nil)\n\n\t\tcategoryBoards, err := th.App.GetUserCategoryBoards(\"user_id\", \"team_id\")\n\t\tassert.NoError(t, err)\n\t\tassert.Equal(t, 1, len(categoryBoards))\n\t\tassert.Equal(t, \"Boards\", categoryBoards[0].Name)\n\t\tassert.Equal(t, 3, len(categoryBoards[0].BoardMetadata))\n\t\tassert.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: \"board_id_1\", Hidden: false})\n\t\tassert.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: \"board_id_2\", Hidden: false})\n\t\tassert.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: \"board_id_3\", Hidden: false})\n\t})\n\n\tt.Run(\"user had no default category BUT had no boards\", func(t *testing.T) {\n\t\tth.Store.EXPECT().GetUserCategoryBoards(\"user_id\", \"team_id\").Return([]model.CategoryBoards{}, nil)\n\t\tth.Store.EXPECT().CreateCategory(utils.Anything).Return(nil)\n\t\tth.Store.EXPECT().GetCategory(utils.Anything).Return(&model.Category{\n\t\t\tID:   \"boards_category_id\",\n\t\t\tName: \"Boards\",\n\t\t}, nil)\n\n\t\tth.Store.EXPECT().GetMembersForUser(\"user_id\").Return([]*model.BoardMember{}, nil)\n\t\tth.Store.EXPECT().GetBoardsForUserAndTeam(\"user_id\", \"team_id\", false).Return([]*model.Board{}, nil)\n\n\t\tcategoryBoards, err := th.App.GetUserCategoryBoards(\"user_id\", \"team_id\")\n\t\tassert.NoError(t, err)\n\t\tassert.Equal(t, 1, len(categoryBoards))\n\t\tassert.Equal(t, \"Boards\", categoryBoards[0].Name)\n\t\tassert.Equal(t, 0, len(categoryBoards[0].BoardMetadata))\n\t})\n\n\tt.Run(\"user already had a default Boards category with boards in it\", func(t *testing.T) {\n\t\tth.Store.EXPECT().GetUserCategoryBoards(\"user_id\", \"team_id\").Return([]model.CategoryBoards{\n\t\t\t{\n\t\t\t\tCategory: model.Category{Name: \"Boards\"},\n\t\t\t\tBoardMetadata: []model.CategoryBoardMetadata{\n\t\t\t\t\t{BoardID: \"board_id_1\", Hidden: false},\n\t\t\t\t\t{BoardID: \"board_id_2\", Hidden: false},\n\t\t\t\t},\n\t\t\t},\n\t\t}, nil)\n\n\t\tcategoryBoards, err := th.App.GetUserCategoryBoards(\"user_id\", \"team_id\")\n\t\tassert.NoError(t, err)\n\t\tassert.Equal(t, 1, len(categoryBoards))\n\t\tassert.Equal(t, \"Boards\", categoryBoards[0].Name)\n\t\tassert.Equal(t, 2, len(categoryBoards[0].BoardMetadata))\n\t})\n}\n\nfunc TestCreateBoardsCategory(t *testing.T) {\n\tth, tearDown := SetupTestHelper(t)\n\tdefer tearDown()\n\n\tt.Run(\"user doesn't have any boards - implicit or explicit\", func(t *testing.T) {\n\t\tth.Store.EXPECT().CreateCategory(utils.Anything).Return(nil)\n\t\tth.Store.EXPECT().GetCategory(utils.Anything).Return(&model.Category{\n\t\t\tID:   \"boards_category_id\",\n\t\t\tType: \"system\",\n\t\t\tName: \"Boards\",\n\t\t}, nil)\n\t\tth.Store.EXPECT().GetBoardsForUserAndTeam(\"user_id\", \"team_id\", false).Return([]*model.Board{}, nil)\n\t\tth.Store.EXPECT().GetMembersForUser(\"user_id\").Return([]*model.BoardMember{}, nil)\n\n\t\texistingCategoryBoards := []model.CategoryBoards{}\n\t\tboardsCategory, err := th.App.createBoardsCategory(\"user_id\", \"team_id\", existingCategoryBoards)\n\t\tassert.NoError(t, err)\n\t\tassert.NotNil(t, boardsCategory)\n\t\tassert.Equal(t, \"Boards\", boardsCategory.Name)\n\t\tassert.Equal(t, 0, len(boardsCategory.BoardMetadata))\n\t})\n\n\tt.Run(\"user has implicit access to some board\", func(t *testing.T) {\n\t\tth.Store.EXPECT().CreateCategory(utils.Anything).Return(nil)\n\t\tth.Store.EXPECT().GetCategory(utils.Anything).Return(&model.Category{\n\t\t\tID:   \"boards_category_id\",\n\t\t\tType: \"system\",\n\t\t\tName: \"Boards\",\n\t\t}, nil)\n\t\tth.Store.EXPECT().GetBoardsForUserAndTeam(\"user_id\", \"team_id\", false).Return([]*model.Board{}, nil)\n\t\tth.Store.EXPECT().GetMembersForUser(\"user_id\").Return([]*model.BoardMember{\n\t\t\t{\n\t\t\t\tBoardID:   \"board_id_1\",\n\t\t\t\tSynthetic: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tBoardID:   \"board_id_2\",\n\t\t\t\tSynthetic: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tBoardID:   \"board_id_3\",\n\t\t\t\tSynthetic: true,\n\t\t\t},\n\t\t}, nil)\n\t\tth.Store.EXPECT().GetBoard(utils.Anything).Return(nil, nil).Times(3)\n\n\t\texistingCategoryBoards := []model.CategoryBoards{}\n\t\tboardsCategory, err := th.App.createBoardsCategory(\"user_id\", \"team_id\", existingCategoryBoards)\n\t\tassert.NoError(t, err)\n\t\tassert.NotNil(t, boardsCategory)\n\t\tassert.Equal(t, \"Boards\", boardsCategory.Name)\n\n\t\t// there should still be no boards in the default category as\n\t\t// the user had only implicit access to boards\n\t\tassert.Equal(t, 0, len(boardsCategory.BoardMetadata))\n\t})\n\n\tt.Run(\"user has explicit access to some board\", func(t *testing.T) {\n\t\tth.Store.EXPECT().CreateCategory(utils.Anything).Return(nil)\n\t\tth.Store.EXPECT().GetCategory(utils.Anything).Return(&model.Category{\n\t\t\tID:   \"boards_category_id\",\n\t\t\tType: \"system\",\n\t\t\tName: \"Boards\",\n\t\t}, nil)\n\n\t\tboard1 := &model.Board{\n\t\t\tID: \"board_id_1\",\n\t\t}\n\t\tboard2 := &model.Board{\n\t\t\tID: \"board_id_2\",\n\t\t}\n\t\tboard3 := &model.Board{\n\t\t\tID: \"board_id_3\",\n\t\t}\n\t\tth.Store.EXPECT().GetBoardsForUserAndTeam(\"user_id\", \"team_id\", false).Return([]*model.Board{board1, board2, board3}, nil)\n\t\tth.Store.EXPECT().GetMembersForUser(\"user_id\").Return([]*model.BoardMember{\n\t\t\t{\n\t\t\t\tBoardID:   \"board_id_1\",\n\t\t\t\tSynthetic: false,\n\t\t\t},\n\t\t\t{\n\t\t\t\tBoardID:   \"board_id_2\",\n\t\t\t\tSynthetic: false,\n\t\t\t},\n\t\t\t{\n\t\t\t\tBoardID:   \"board_id_3\",\n\t\t\t\tSynthetic: false,\n\t\t\t},\n\t\t}, nil)\n\t\tth.Store.EXPECT().GetBoard(utils.Anything).Return(nil, nil).Times(3)\n\t\tth.Store.EXPECT().AddUpdateCategoryBoard(\"user_id\", \"boards_category_id\", []string{\"board_id_1\", \"board_id_2\", \"board_id_3\"}).Return(nil)\n\n\t\tth.Store.EXPECT().GetUserCategoryBoards(\"user_id\", \"team_id\").Return([]model.CategoryBoards{\n\t\t\t{\n\t\t\t\tCategory: model.Category{\n\t\t\t\t\tType: model.CategoryTypeSystem,\n\t\t\t\t\tID:   \"boards_category_id\",\n\t\t\t\t\tName: \"Boards\",\n\t\t\t\t},\n\t\t\t},\n\t\t}, nil)\n\n\t\texistingCategoryBoards := []model.CategoryBoards{}\n\t\tboardsCategory, err := th.App.createBoardsCategory(\"user_id\", \"team_id\", existingCategoryBoards)\n\t\tassert.NoError(t, err)\n\t\tassert.NotNil(t, boardsCategory)\n\t\tassert.Equal(t, \"Boards\", boardsCategory.Name)\n\n\t\t// since user has explicit access to three boards,\n\t\t// they should all end up in the default category\n\t\tassert.Equal(t, 3, len(boardsCategory.BoardMetadata))\n\t})\n\n\tt.Run(\"user has both implicit and explicit access to some board\", func(t *testing.T) {\n\t\tth.Store.EXPECT().CreateCategory(utils.Anything).Return(nil)\n\t\tth.Store.EXPECT().GetCategory(utils.Anything).Return(&model.Category{\n\t\t\tID:   \"boards_category_id\",\n\t\t\tType: \"system\",\n\t\t\tName: \"Boards\",\n\t\t}, nil)\n\n\t\tboard1 := &model.Board{\n\t\t\tID: \"board_id_1\",\n\t\t}\n\t\tth.Store.EXPECT().GetBoardsForUserAndTeam(\"user_id\", \"team_id\", false).Return([]*model.Board{board1}, nil)\n\t\tth.Store.EXPECT().GetMembersForUser(\"user_id\").Return([]*model.BoardMember{\n\t\t\t{\n\t\t\t\tBoardID:   \"board_id_1\",\n\t\t\t\tSynthetic: false,\n\t\t\t},\n\t\t\t{\n\t\t\t\tBoardID:   \"board_id_2\",\n\t\t\t\tSynthetic: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tBoardID:   \"board_id_3\",\n\t\t\t\tSynthetic: true,\n\t\t\t},\n\t\t}, nil)\n\t\tth.Store.EXPECT().GetBoard(utils.Anything).Return(nil, nil).Times(3)\n\t\tth.Store.EXPECT().AddUpdateCategoryBoard(\"user_id\", \"boards_category_id\", []string{\"board_id_1\"}).Return(nil)\n\n\t\tth.Store.EXPECT().GetUserCategoryBoards(\"user_id\", \"team_id\").Return([]model.CategoryBoards{\n\t\t\t{\n\t\t\t\tCategory: model.Category{\n\t\t\t\t\tType: model.CategoryTypeSystem,\n\t\t\t\t\tID:   \"boards_category_id\",\n\t\t\t\t\tName: \"Boards\",\n\t\t\t\t},\n\t\t\t},\n\t\t}, nil)\n\n\t\texistingCategoryBoards := []model.CategoryBoards{}\n\t\tboardsCategory, err := th.App.createBoardsCategory(\"user_id\", \"team_id\", existingCategoryBoards)\n\t\tassert.NoError(t, err)\n\t\tassert.NotNil(t, boardsCategory)\n\t\tassert.Equal(t, \"Boards\", boardsCategory.Name)\n\n\t\t// there was only one explicit board access,\n\t\t// and so only that one should end up in the\n\t\t// default category\n\t\tassert.Equal(t, 1, len(boardsCategory.BoardMetadata))\n\t})\n}\n\nfunc TestReorderCategoryBoards(t *testing.T) {\n\tth, tearDown := SetupTestHelper(t)\n\tdefer tearDown()\n\n\tt.Run(\"base case\", func(t *testing.T) {\n\t\tth.Store.EXPECT().GetUserCategoryBoards(\"user_id\", \"team_id\").Return([]model.CategoryBoards{\n\t\t\t{\n\t\t\t\tCategory: model.Category{ID: \"category_id_1\", Name: \"Category 1\"},\n\t\t\t\tBoardMetadata: []model.CategoryBoardMetadata{\n\t\t\t\t\t{BoardID: \"board_id_1\", Hidden: false},\n\t\t\t\t\t{BoardID: \"board_id_2\", Hidden: false},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tCategory: model.Category{ID: \"category_id_2\", Name: \"Boards\", Type: \"system\"},\n\t\t\t\tBoardMetadata: []model.CategoryBoardMetadata{\n\t\t\t\t\t{BoardID: \"board_id_3\", Hidden: false},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tCategory:      model.Category{ID: \"category_id_3\", Name: \"Category 3\"},\n\t\t\t\tBoardMetadata: []model.CategoryBoardMetadata{},\n\t\t\t},\n\t\t}, nil)\n\n\t\tth.Store.EXPECT().ReorderCategoryBoards(\"category_id_1\", []string{\"board_id_2\", \"board_id_1\"}).Return([]string{\"board_id_2\", \"board_id_1\"}, nil)\n\n\t\tnewOrder, err := th.App.ReorderCategoryBoards(\"user_id\", \"team_id\", \"category_id_1\", []string{\"board_id_2\", \"board_id_1\"})\n\t\tassert.NoError(t, err)\n\t\tassert.Equal(t, 2, len(newOrder))\n\t\tassert.Equal(t, \"board_id_2\", newOrder[0])\n\t\tassert.Equal(t, \"board_id_1\", newOrder[1])\n\t})\n\n\tt.Run(\"not specifying all boards\", func(t *testing.T) {\n\t\tth.Store.EXPECT().GetUserCategoryBoards(\"user_id\", \"team_id\").Return([]model.CategoryBoards{\n\t\t\t{\n\t\t\t\tCategory: model.Category{ID: \"category_id_1\", Name: \"Category 1\"},\n\t\t\t\tBoardMetadata: []model.CategoryBoardMetadata{\n\t\t\t\t\t{BoardID: \"board_id_1\", Hidden: false},\n\t\t\t\t\t{BoardID: \"board_id_2\", Hidden: false},\n\t\t\t\t\t{BoardID: \"board_id_3\", Hidden: false},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tCategory: model.Category{ID: \"category_id_2\", Name: \"Boards\", Type: \"system\"},\n\t\t\t\tBoardMetadata: []model.CategoryBoardMetadata{\n\t\t\t\t\t{BoardID: \"board_id_3\", Hidden: false},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tCategory:      model.Category{ID: \"category_id_3\", Name: \"Category 3\"},\n\t\t\t\tBoardMetadata: []model.CategoryBoardMetadata{},\n\t\t\t},\n\t\t}, nil)\n\n\t\tnewOrder, err := th.App.ReorderCategoryBoards(\"user_id\", \"team_id\", \"category_id_1\", []string{\"board_id_2\", \"board_id_1\"})\n\t\tassert.Error(t, err)\n\t\tassert.Nil(t, newOrder)\n\t})\n}\n"
  },
  {
    "path": "server/app/category_test.go",
    "content": "package app\n\nimport (\n\t\"testing\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestCreateCategory(t *testing.T) {\n\tth, tearDown := SetupTestHelper(t)\n\tdefer tearDown()\n\n\tt.Run(\"base case\", func(t *testing.T) {\n\t\tth.Store.EXPECT().CreateCategory(utils.Anything).Return(nil)\n\n\t\tth.Store.EXPECT().GetCategory(utils.Anything).Return(&model.Category{\n\t\t\tID: \"category_id_1\",\n\t\t}, nil)\n\n\t\tcategory := &model.Category{\n\t\t\tName:   \"Category\",\n\t\t\tUserID: \"user_id\",\n\t\t\tTeamID: \"team_id\",\n\t\t\tType:   \"custom\",\n\t\t}\n\t\tcreatedCategory, err := th.App.CreateCategory(category)\n\t\tassert.NotNil(t, createdCategory)\n\t\tassert.NoError(t, err)\n\t})\n\n\tt.Run(\"creating invalid category\", func(t *testing.T) {\n\t\tcategory := &model.Category{\n\t\t\tName:   \"\", // empty name shouldn't be allowed\n\t\t\tUserID: \"user_id\",\n\t\t\tTeamID: \"team_id\",\n\t\t\tType:   \"custom\",\n\t\t}\n\t\tcreatedCategory, err := th.App.CreateCategory(category)\n\t\tassert.Nil(t, createdCategory)\n\t\tassert.Error(t, err)\n\n\t\tcategory.Name = \"Name\"\n\t\tcategory.UserID = \"\" // empty creator user id shouldn't be allowed\n\t\tcreatedCategory, err = th.App.CreateCategory(category)\n\t\tassert.Nil(t, createdCategory)\n\t\tassert.Error(t, err)\n\n\t\tcategory.UserID = \"user_id\"\n\t\tcategory.TeamID = \"\" // empty TeamID shouldn't be allowed\n\t\tcreatedCategory, err = th.App.CreateCategory(category)\n\t\tassert.Nil(t, createdCategory)\n\t\tassert.Error(t, err)\n\n\t\tcategory.Type = \"invalid\" // unknown type shouldn't be allowed\n\t\tcreatedCategory, err = th.App.CreateCategory(category)\n\t\tassert.Nil(t, createdCategory)\n\t\tassert.Error(t, err)\n\t})\n}\n\nfunc TestUpdateCategory(t *testing.T) {\n\tth, tearDown := SetupTestHelper(t)\n\tdefer tearDown()\n\n\tt.Run(\"base case\", func(t *testing.T) {\n\t\tth.Store.EXPECT().GetCategory(utils.Anything).Return(&model.Category{\n\t\t\tID:     \"category_id_1\",\n\t\t\tName:   \"Category\",\n\t\t\tTeamID: \"team_id_1\",\n\t\t\tUserID: \"user_id_1\",\n\t\t\tType:   \"custom\",\n\t\t}, nil)\n\n\t\tth.Store.EXPECT().UpdateCategory(utils.Anything).Return(nil)\n\t\tth.Store.EXPECT().GetCategory(\"category_id_1\").Return(&model.Category{\n\t\t\tID:   \"category_id_1\",\n\t\t\tName: \"Category\",\n\t\t}, nil)\n\n\t\tcategory := &model.Category{\n\t\t\tID:     \"category_id_1\",\n\t\t\tName:   \"Category\",\n\t\t\tUserID: \"user_id_1\",\n\t\t\tTeamID: \"team_id_1\",\n\t\t\tType:   \"custom\",\n\t\t}\n\t\tupdatedCategory, err := th.App.UpdateCategory(category)\n\t\tassert.NotNil(t, updatedCategory)\n\t\tassert.NoError(t, err)\n\t})\n\n\tt.Run(\"updating invalid category\", func(t *testing.T) {\n\t\tth.Store.EXPECT().GetCategory(utils.Anything).Return(&model.Category{\n\t\t\tID:     \"category_id_1\",\n\t\t\tName:   \"Category\",\n\t\t\tTeamID: \"team_id_1\",\n\t\t\tUserID: \"user_id_1\",\n\t\t\tType:   \"custom\",\n\t\t}, nil)\n\n\t\tcategory := &model.Category{\n\t\t\tID:     \"category_id_1\",\n\t\t\tName:   \"Name\",\n\t\t\tUserID: \"user_id\",\n\t\t\tTeamID: \"team_id\",\n\t\t\tType:   \"custom\",\n\t\t}\n\n\t\tcategory.ID = \"\"\n\t\tcreatedCategory, err := th.App.UpdateCategory(category)\n\t\tassert.Nil(t, createdCategory)\n\t\tassert.Error(t, err)\n\n\t\tcategory.ID = \"category_id_1\"\n\t\tcategory.Name = \"\"\n\t\tcreatedCategory, err = th.App.UpdateCategory(category)\n\t\tassert.Nil(t, createdCategory)\n\t\tassert.Error(t, err)\n\n\t\tcategory.Name = \"Name\"\n\t\tcategory.UserID = \"\" // empty creator user id shouldn't be allowed\n\t\tcreatedCategory, err = th.App.UpdateCategory(category)\n\t\tassert.Nil(t, createdCategory)\n\t\tassert.Error(t, err)\n\n\t\tcategory.UserID = \"user_id\"\n\t\tcategory.TeamID = \"\" // empty TeamID shouldn't be allowed\n\t\tcreatedCategory, err = th.App.UpdateCategory(category)\n\t\tassert.Nil(t, createdCategory)\n\t\tassert.Error(t, err)\n\n\t\tcategory.Type = \"invalid\" // unknown type shouldn't be allowed\n\t\tcreatedCategory, err = th.App.UpdateCategory(category)\n\t\tassert.Nil(t, createdCategory)\n\t\tassert.Error(t, err)\n\t})\n\n\tt.Run(\"trying to update someone else's category\", func(t *testing.T) {\n\t\tth.Store.EXPECT().GetCategory(utils.Anything).Return(&model.Category{\n\t\t\tID:     \"category_id_1\",\n\t\t\tName:   \"Category\",\n\t\t\tTeamID: \"team_id_1\",\n\t\t\tUserID: \"user_id_1\",\n\t\t\tType:   \"custom\",\n\t\t}, nil)\n\n\t\tcategory := &model.Category{\n\t\t\tID:     \"category_id_1\",\n\t\t\tName:   \"Category\",\n\t\t\tUserID: \"user_id_2\",\n\t\t\tTeamID: \"team_id_1\",\n\t\t\tType:   \"custom\",\n\t\t}\n\t\tupdatedCategory, err := th.App.UpdateCategory(category)\n\t\tassert.Nil(t, updatedCategory)\n\t\tassert.Error(t, err)\n\t})\n\n\tt.Run(\"trying to update some other team's category\", func(t *testing.T) {\n\t\tth.Store.EXPECT().GetCategory(utils.Anything).Return(&model.Category{\n\t\t\tID:     \"category_id_1\",\n\t\t\tName:   \"Category\",\n\t\t\tTeamID: \"team_id_1\",\n\t\t\tUserID: \"user_id_1\",\n\t\t\tType:   \"custom\",\n\t\t}, nil)\n\n\t\tcategory := &model.Category{\n\t\t\tID:     \"category_id_1\",\n\t\t\tName:   \"Category\",\n\t\t\tUserID: \"user_id_1\",\n\t\t\tTeamID: \"team_id_2\",\n\t\t\tType:   \"custom\",\n\t\t}\n\t\tupdatedCategory, err := th.App.UpdateCategory(category)\n\t\tassert.Nil(t, updatedCategory)\n\t\tassert.Error(t, err)\n\t})\n\n\tt.Run(\"should not be allowed to rename system category\", func(t *testing.T) {\n\t\tth.Store.EXPECT().GetCategory(utils.Anything).Return(&model.Category{\n\t\t\tID:     \"category_id_1\",\n\t\t\tName:   \"Category\",\n\t\t\tTeamID: \"team_id_1\",\n\t\t\tUserID: \"user_id_1\",\n\t\t\tType:   \"system\",\n\t\t}, nil).Times(1)\n\n\t\tth.Store.EXPECT().UpdateCategory(utils.Anything).Return(nil)\n\n\t\tth.Store.EXPECT().GetCategory(utils.Anything).Return(&model.Category{\n\t\t\tID:        \"category_id_1\",\n\t\t\tName:      \"Category\",\n\t\t\tTeamID:    \"team_id_1\",\n\t\t\tUserID:    \"user_id_1\",\n\t\t\tType:      \"system\",\n\t\t\tCollapsed: true,\n\t\t}, nil).Times(1)\n\n\t\tcategory := &model.Category{\n\t\t\tID:     \"category_id_1\",\n\t\t\tName:   \"Updated Name\",\n\t\t\tUserID: \"user_id_1\",\n\t\t\tTeamID: \"team_id_1\",\n\t\t\tType:   \"system\",\n\t\t}\n\t\tupdatedCategory, err := th.App.UpdateCategory(category)\n\t\tassert.NotNil(t, updatedCategory)\n\t\tassert.NoError(t, err)\n\t\tassert.Equal(t, \"Category\", updatedCategory.Name)\n\t})\n\n\tt.Run(\"should be allowed to collapse and expand any category type\", func(t *testing.T) {\n\t\tth.Store.EXPECT().GetCategory(utils.Anything).Return(&model.Category{\n\t\t\tID:        \"category_id_1\",\n\t\t\tName:      \"Category\",\n\t\t\tTeamID:    \"team_id_1\",\n\t\t\tUserID:    \"user_id_1\",\n\t\t\tType:      \"system\",\n\t\t\tCollapsed: false,\n\t\t}, nil).Times(1)\n\n\t\tth.Store.EXPECT().UpdateCategory(utils.Anything).Return(nil)\n\n\t\tth.Store.EXPECT().GetCategory(utils.Anything).Return(&model.Category{\n\t\t\tID:        \"category_id_1\",\n\t\t\tName:      \"Category\",\n\t\t\tTeamID:    \"team_id_1\",\n\t\t\tUserID:    \"user_id_1\",\n\t\t\tType:      \"system\",\n\t\t\tCollapsed: true,\n\t\t}, nil).Times(1)\n\n\t\tcategory := &model.Category{\n\t\t\tID:        \"category_id_1\",\n\t\t\tName:      \"Updated Name\",\n\t\t\tUserID:    \"user_id_1\",\n\t\t\tTeamID:    \"team_id_1\",\n\t\t\tType:      \"system\",\n\t\t\tCollapsed: true,\n\t\t}\n\t\tupdatedCategory, err := th.App.UpdateCategory(category)\n\t\tassert.NotNil(t, updatedCategory)\n\t\tassert.NoError(t, err)\n\t\tassert.Equal(t, \"Category\", updatedCategory.Name, \"The name should have not been updated\")\n\t\tassert.True(t, updatedCategory.Collapsed)\n\t})\n}\n\nfunc TestDeleteCategory(t *testing.T) {\n\tth, tearDown := SetupTestHelper(t)\n\tdefer tearDown()\n\n\tt.Run(\"base case\", func(t *testing.T) {\n\t\tth.Store.EXPECT().GetCategory(\"category_id_1\").Return(&model.Category{\n\t\t\tID:       \"category_id_1\",\n\t\t\tDeleteAt: 0,\n\t\t\tUserID:   \"user_id_1\",\n\t\t\tTeamID:   \"team_id_1\",\n\t\t\tType:     \"custom\",\n\t\t}, nil)\n\n\t\tth.Store.EXPECT().DeleteCategory(\"category_id_1\", \"user_id_1\", \"team_id_1\").Return(nil)\n\n\t\tth.Store.EXPECT().GetCategory(\"category_id_1\").Return(&model.Category{\n\t\t\tDeleteAt: 10000,\n\t\t}, nil)\n\n\t\tth.Store.EXPECT().GetUserCategoryBoards(\"user_id_1\", \"team_id_1\").Return([]model.CategoryBoards{\n\t\t\t{\n\t\t\t\tCategory: model.Category{\n\t\t\t\t\tID:       \"category_id_default\",\n\t\t\t\t\tDeleteAt: 0,\n\t\t\t\t\tUserID:   \"user_id_1\",\n\t\t\t\t\tTeamID:   \"team_id_1\",\n\t\t\t\t\tType:     \"default\",\n\t\t\t\t\tName:     \"Boards\",\n\t\t\t\t},\n\t\t\t\tBoardMetadata: []model.CategoryBoardMetadata{},\n\t\t\t},\n\t\t\t{\n\t\t\t\tCategory: model.Category{\n\t\t\t\t\tID:       \"category_id_1\",\n\t\t\t\t\tDeleteAt: 0,\n\t\t\t\t\tUserID:   \"user_id_1\",\n\t\t\t\t\tTeamID:   \"team_id_1\",\n\t\t\t\t\tType:     \"custom\",\n\t\t\t\t\tName:     \"Category 1\",\n\t\t\t\t},\n\t\t\t\tBoardMetadata: []model.CategoryBoardMetadata{},\n\t\t\t},\n\t\t}, nil)\n\n\t\tdeletedCategory, err := th.App.DeleteCategory(\"category_id_1\", \"user_id_1\", \"team_id_1\")\n\t\tassert.NotNil(t, deletedCategory)\n\t\tassert.NoError(t, err)\n\t})\n\n\tt.Run(\"trying to delete already deleted category\", func(t *testing.T) {\n\t\tth.Store.EXPECT().GetCategory(\"category_id_1\").Return(&model.Category{\n\t\t\tID:       \"category_id_1\",\n\t\t\tDeleteAt: 1000,\n\t\t\tUserID:   \"user_id_1\",\n\t\t\tTeamID:   \"team_id_1\",\n\t\t\tType:     \"custom\",\n\t\t}, nil)\n\n\t\tdeletedCategory, err := th.App.DeleteCategory(\"category_id_1\", \"user_id_1\", \"team_id_1\")\n\t\tassert.NotNil(t, deletedCategory)\n\t\tassert.NoError(t, err)\n\t})\n\n\tt.Run(\"trying to delete system category\", func(t *testing.T) {\n\t\tth.Store.EXPECT().GetCategory(\"category_id_1\").Return(&model.Category{\n\t\t\tID:       \"category_id_1\",\n\t\t\tDeleteAt: 0,\n\t\t\tUserID:   \"user_id_1\",\n\t\t\tTeamID:   \"team_id_1\",\n\t\t\tType:     \"system\",\n\t\t}, nil)\n\n\t\tdeletedCategory, err := th.App.DeleteCategory(\"category_id_1\", \"user_id_1\", \"team_id_1\")\n\t\tassert.Nil(t, deletedCategory)\n\t\tassert.Error(t, err)\n\t})\n}\n\nfunc TestMoveBoardsToDefaultCategory(t *testing.T) {\n\tth, tearDown := SetupTestHelper(t)\n\tdefer tearDown()\n\n\tt.Run(\"When default category already exists\", func(t *testing.T) {\n\t\tth.Store.EXPECT().GetUserCategoryBoards(\"user_id\", \"team_id\").Return([]model.CategoryBoards{\n\t\t\t{\n\t\t\t\tCategory: model.Category{\n\t\t\t\t\tID:   \"category_id_1\",\n\t\t\t\t\tName: \"Boards\",\n\t\t\t\t\tType: \"system\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tCategory: model.Category{\n\t\t\t\t\tID:   \"category_id_2\",\n\t\t\t\t\tName: \"Custom Category 1\",\n\t\t\t\t\tType: \"custom\",\n\t\t\t\t},\n\t\t\t},\n\t\t}, nil)\n\n\t\terr := th.App.moveBoardsToDefaultCategory(\"user_id\", \"team_id\", \"category_id_2\")\n\t\tassert.NoError(t, err)\n\t})\n\n\tt.Run(\"When default category doesn't already exists\", func(t *testing.T) {\n\t\tth.Store.EXPECT().GetUserCategoryBoards(\"user_id\", \"team_id\").Return([]model.CategoryBoards{\n\t\t\t{\n\t\t\t\tCategory: model.Category{\n\t\t\t\t\tID:   \"category_id_2\",\n\t\t\t\t\tName: \"Custom Category 1\",\n\t\t\t\t\tType: \"custom\",\n\t\t\t\t},\n\t\t\t},\n\t\t}, nil)\n\n\t\tth.Store.EXPECT().CreateCategory(utils.Anything).Return(nil)\n\t\tth.Store.EXPECT().GetCategory(utils.Anything).Return(&model.Category{\n\t\t\tID:   \"default_category_id\",\n\t\t\tName: \"Boards\",\n\t\t\tType: \"system\",\n\t\t}, nil)\n\t\tth.Store.EXPECT().GetMembersForUser(\"user_id\").Return([]*model.BoardMember{}, nil)\n\t\tth.Store.EXPECT().GetBoardsForUserAndTeam(\"user_id\", \"team_id\", false).Return([]*model.Board{}, nil)\n\n\t\terr := th.App.moveBoardsToDefaultCategory(\"user_id\", \"team_id\", \"category_id_2\")\n\t\tassert.NoError(t, err)\n\t})\n}\n\nfunc TestReorderCategories(t *testing.T) {\n\tth, tearDown := SetupTestHelper(t)\n\tdefer tearDown()\n\n\tt.Run(\"base case\", func(t *testing.T) {\n\t\tth.Store.EXPECT().GetUserCategories(\"user_id\", \"team_id\").Return([]model.Category{\n\t\t\t{\n\t\t\t\tID:   \"category_id_1\",\n\t\t\t\tName: \"Boards\",\n\t\t\t\tType: \"system\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tID:   \"category_id_2\",\n\t\t\t\tName: \"Category 2\",\n\t\t\t\tType: \"custom\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tID:   \"category_id_3\",\n\t\t\t\tName: \"Category 3\",\n\t\t\t\tType: \"custom\",\n\t\t\t},\n\t\t}, nil)\n\n\t\tth.Store.EXPECT().ReorderCategories(\"user_id\", \"team_id\", []string{\"category_id_2\", \"category_id_3\", \"category_id_1\"}).\n\t\t\tReturn([]string{\"category_id_2\", \"category_id_3\", \"category_id_1\"}, nil)\n\n\t\tnewOrder, err := th.App.ReorderCategories(\"user_id\", \"team_id\", []string{\"category_id_2\", \"category_id_3\", \"category_id_1\"})\n\t\tassert.NoError(t, err)\n\t\tassert.Equal(t, 3, len(newOrder))\n\t})\n\n\tt.Run(\"not specifying all categories should fail\", func(t *testing.T) {\n\t\tth.Store.EXPECT().GetUserCategories(\"user_id\", \"team_id\").Return([]model.Category{\n\t\t\t{\n\t\t\t\tID:   \"category_id_1\",\n\t\t\t\tName: \"Boards\",\n\t\t\t\tType: \"system\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tID:   \"category_id_2\",\n\t\t\t\tName: \"Category 2\",\n\t\t\t\tType: \"custom\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tID:   \"category_id_3\",\n\t\t\t\tName: \"Category 3\",\n\t\t\t\tType: \"custom\",\n\t\t\t},\n\t\t}, nil)\n\n\t\tnewOrder, err := th.App.ReorderCategories(\"user_id\", \"team_id\", []string{\"category_id_2\", \"category_id_3\"})\n\t\tassert.Error(t, err)\n\t\tassert.Nil(t, newOrder)\n\t})\n}\n\nfunc TestVerifyNewCategoriesMatchExisting(t *testing.T) {\n\tth, tearDown := SetupTestHelper(t)\n\tdefer tearDown()\n\n\tt.Run(\"base case\", func(t *testing.T) {\n\t\tth.Store.EXPECT().GetUserCategories(\"user_id\", \"team_id\").Return([]model.Category{\n\t\t\t{\n\t\t\t\tID:   \"category_id_1\",\n\t\t\t\tName: \"Boards\",\n\t\t\t\tType: \"system\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tID:   \"category_id_2\",\n\t\t\t\tName: \"Category 2\",\n\t\t\t\tType: \"custom\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tID:   \"category_id_3\",\n\t\t\t\tName: \"Category 3\",\n\t\t\t\tType: \"custom\",\n\t\t\t},\n\t\t}, nil)\n\n\t\terr := th.App.verifyNewCategoriesMatchExisting(\"user_id\", \"team_id\", []string{\n\t\t\t\"category_id_2\",\n\t\t\t\"category_id_3\",\n\t\t\t\"category_id_1\",\n\t\t})\n\t\tassert.NoError(t, err)\n\t})\n\n\tt.Run(\"different category counts\", func(t *testing.T) {\n\t\tth.Store.EXPECT().GetUserCategories(\"user_id\", \"team_id\").Return([]model.Category{\n\t\t\t{\n\t\t\t\tID:   \"category_id_1\",\n\t\t\t\tName: \"Boards\",\n\t\t\t\tType: \"system\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tID:   \"category_id_2\",\n\t\t\t\tName: \"Category 2\",\n\t\t\t\tType: \"custom\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tID:   \"category_id_3\",\n\t\t\t\tName: \"Category 3\",\n\t\t\t\tType: \"custom\",\n\t\t\t},\n\t\t}, nil)\n\n\t\terr := th.App.verifyNewCategoriesMatchExisting(\"user_id\", \"team_id\", []string{\n\t\t\t\"category_id_2\",\n\t\t\t\"category_id_3\",\n\t\t})\n\t\tassert.Error(t, err)\n\t})\n}\n"
  },
  {
    "path": "server/app/clientConfig.go",
    "content": "package app\n\nimport (\n\t\"github.com/mattermost/focalboard/server/model\"\n)\n\nfunc (a *App) GetClientConfig() *model.ClientConfig {\n\treturn &model.ClientConfig{\n\t\tTelemetry:                a.config.Telemetry,\n\t\tTelemetryID:              a.config.TelemetryID,\n\t\tEnablePublicSharedBoards: a.config.EnablePublicSharedBoards,\n\t\tTeammateNameDisplay:      a.config.TeammateNameDisplay,\n\t\tFeatureFlags:             a.config.FeatureFlags,\n\t\tMaxFileSize:              a.config.MaxFileSize,\n\t}\n}\n"
  },
  {
    "path": "server/app/clientConfig_test.go",
    "content": "package app\n\nimport (\n\t\"testing\"\n\n\t\"github.com/mattermost/focalboard/server/services/config\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestGetClientConfig(t *testing.T) {\n\tth, tearDown := SetupTestHelper(t)\n\tdefer tearDown()\n\n\tt.Run(\"Test Get Client Config\", func(t *testing.T) {\n\t\tnewConfiguration := config.Configuration{}\n\t\tnewConfiguration.Telemetry = true\n\t\tnewConfiguration.TelemetryID = \"abcde\"\n\t\tnewConfiguration.EnablePublicSharedBoards = true\n\t\tnewConfiguration.TeammateNameDisplay = \"username\"\n\t\tth.App.SetConfig(&newConfiguration)\n\n\t\tclientConfig := th.App.GetClientConfig()\n\t\trequire.True(t, clientConfig.EnablePublicSharedBoards)\n\t\trequire.True(t, clientConfig.Telemetry)\n\t\trequire.Equal(t, \"abcde\", clientConfig.TelemetryID)\n\t\trequire.Equal(t, \"username\", clientConfig.TeammateNameDisplay)\n\t})\n}\n"
  },
  {
    "path": "server/app/compliance.go",
    "content": "package app\n\nimport \"github.com/mattermost/focalboard/server/model\"\n\nfunc (a *App) GetBoardsForCompliance(opts model.QueryBoardsForComplianceOptions) ([]*model.Board, bool, error) {\n\treturn a.store.GetBoardsForCompliance(opts)\n}\n\nfunc (a *App) GetBoardsComplianceHistory(opts model.QueryBoardsComplianceHistoryOptions) ([]*model.BoardHistory, bool, error) {\n\treturn a.store.GetBoardsComplianceHistory(opts)\n}\n\nfunc (a *App) GetBlocksComplianceHistory(opts model.QueryBlocksComplianceHistoryOptions) ([]*model.BlockHistory, bool, error) {\n\treturn a.store.GetBlocksComplianceHistory(opts)\n}\n"
  },
  {
    "path": "server/app/content_blocks.go",
    "content": "package app\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/pkg/errors\"\n)\n\nfunc (a *App) MoveContentBlock(block *model.Block, dstBlock *model.Block, where string, userID string) error {\n\tif block.ParentID != dstBlock.ParentID {\n\t\tmessage := fmt.Sprintf(\"not matching parent %s and %s\", block.ParentID, dstBlock.ParentID)\n\t\treturn model.NewErrBadRequest(message)\n\t}\n\n\tcard, err := a.GetBlockByID(block.ParentID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcontentOrderData, ok := card.Fields[\"contentOrder\"]\n\tvar contentOrder []interface{}\n\tif ok {\n\t\tcontentOrder = contentOrderData.([]interface{})\n\t}\n\n\tnewContentOrder := []interface{}{}\n\tfoundDst := false\n\tfoundSrc := false\n\tfor _, id := range contentOrder {\n\t\tstringID, ok := id.(string)\n\t\tif !ok {\n\t\t\tnewContentOrder = append(newContentOrder, id)\n\t\t\tcontinue\n\t\t}\n\n\t\tif dstBlock.ID == stringID {\n\t\t\tfoundDst = true\n\t\t\tif where == \"after\" {\n\t\t\t\tnewContentOrder = append(newContentOrder, id)\n\t\t\t\tnewContentOrder = append(newContentOrder, block.ID)\n\t\t\t} else {\n\t\t\t\tnewContentOrder = append(newContentOrder, block.ID)\n\t\t\t\tnewContentOrder = append(newContentOrder, id)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tif block.ID == stringID {\n\t\t\tfoundSrc = true\n\t\t\tcontinue\n\t\t}\n\n\t\tnewContentOrder = append(newContentOrder, id)\n\t}\n\n\tif !foundSrc {\n\t\tmessage := fmt.Sprintf(\"source block %s not found\", block.ID)\n\t\treturn model.NewErrBadRequest(message)\n\t}\n\n\tif !foundDst {\n\t\tmessage := fmt.Sprintf(\"destination block %s not found\", dstBlock.ID)\n\t\treturn model.NewErrBadRequest(message)\n\t}\n\n\tpatch := &model.BlockPatch{\n\t\tUpdatedFields: map[string]interface{}{\n\t\t\t\"contentOrder\": newContentOrder,\n\t\t},\n\t}\n\n\t_, err = a.PatchBlock(block.ParentID, patch, userID)\n\tif errors.Is(err, model.ErrPatchUpdatesLimitedCards) {\n\t\treturn err\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "server/app/content_blocks_test.go",
    "content": "package app\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/golang/mock/gomock\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n)\n\ntype contentOrderMatcher struct {\n\tcontentOrder []string\n}\n\nfunc NewContentOrderMatcher(contentOrder []string) contentOrderMatcher {\n\treturn contentOrderMatcher{contentOrder}\n}\n\nfunc (com contentOrderMatcher) Matches(x interface{}) bool {\n\tpatch, ok := x.(*model.BlockPatch)\n\tif !ok {\n\t\treturn false\n\t}\n\n\tcontentOrderData, ok := patch.UpdatedFields[\"contentOrder\"]\n\tif !ok {\n\t\treturn false\n\t}\n\n\tcontentOrder, ok := contentOrderData.([]interface{})\n\tif !ok {\n\t\treturn false\n\t}\n\n\tif len(contentOrder) != len(com.contentOrder) {\n\t\treturn false\n\t}\n\n\tfor i := range contentOrder {\n\t\tif contentOrder[i] != com.contentOrder[i] {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc (com contentOrderMatcher) String() string {\n\treturn fmt.Sprint(&model.BlockPatch{UpdatedFields: map[string]interface{}{\"contentOrder\": com.contentOrder}})\n}\n\nfunc TestMoveContentBlock(t *testing.T) {\n\tth, tearDown := SetupTestHelper(t)\n\tdefer tearDown()\n\n\tttCases := []struct {\n\t\tname                 string\n\t\tsrcBlock             model.Block\n\t\tdstBlock             model.Block\n\t\tparentBlock          *model.Block\n\t\twhere                string\n\t\tuserID               string\n\t\tmockPatch            bool\n\t\tmockPatchError       error\n\t\terrorMessage         string\n\t\texpectedContentOrder []string\n\t}{\n\t\t{\n\t\t\tname:         \"not matching parents\",\n\t\t\tsrcBlock:     model.Block{ID: \"test-1\", ParentID: \"test-card\"},\n\t\t\tdstBlock:     model.Block{ID: \"test-2\", ParentID: \"other-test-card\"},\n\t\t\tparentBlock:  nil,\n\t\t\twhere:        \"after\",\n\t\t\tuserID:       \"user-id\",\n\t\t\terrorMessage: \"not matching parent test-card and other-test-card\",\n\t\t},\n\t\t{\n\t\t\tname:         \"parent not found\",\n\t\t\tsrcBlock:     model.Block{ID: \"test-1\", ParentID: \"invalid-card\"},\n\t\t\tdstBlock:     model.Block{ID: \"test-2\", ParentID: \"invalid-card\"},\n\t\t\tparentBlock:  &model.Block{ID: \"invalid-card\"},\n\t\t\twhere:        \"after\",\n\t\t\tuserID:       \"user-id\",\n\t\t\terrorMessage: \"{test} not found\",\n\t\t},\n\t\t{\n\t\t\tname:         \"valid parent without content order\",\n\t\t\tsrcBlock:     model.Block{ID: \"test-1\", ParentID: \"test-card\"},\n\t\t\tdstBlock:     model.Block{ID: \"test-2\", ParentID: \"test-card\"},\n\t\t\tparentBlock:  &model.Block{ID: \"test-card\"},\n\t\t\twhere:        \"after\",\n\t\t\tuserID:       \"user-id\",\n\t\t\terrorMessage: \"source block test-1 not found\",\n\t\t},\n\t\t{\n\t\t\tname:         \"valid parent with content order but without test-1 in it\",\n\t\t\tsrcBlock:     model.Block{ID: \"test-1\", ParentID: \"test-card\"},\n\t\t\tdstBlock:     model.Block{ID: \"test-2\", ParentID: \"test-card\"},\n\t\t\tparentBlock:  &model.Block{ID: \"test-card\", Fields: map[string]interface{}{\"contentOrder\": []interface{}{\"test-2\"}}},\n\t\t\twhere:        \"after\",\n\t\t\tuserID:       \"user-id\",\n\t\t\terrorMessage: \"source block test-1 not found\",\n\t\t},\n\t\t{\n\t\t\tname:         \"valid parent with content order but without test-2 in it\",\n\t\t\tsrcBlock:     model.Block{ID: \"test-1\", ParentID: \"test-card\"},\n\t\t\tdstBlock:     model.Block{ID: \"test-2\", ParentID: \"test-card\"},\n\t\t\tparentBlock:  &model.Block{ID: \"test-card\", Fields: map[string]interface{}{\"contentOrder\": []interface{}{\"test-1\"}}},\n\t\t\twhere:        \"after\",\n\t\t\tuserID:       \"user-id\",\n\t\t\terrorMessage: \"destination block test-2 not found\",\n\t\t},\n\t\t{\n\t\t\tname:           \"valid request but fail on patchparent with content order\",\n\t\t\tsrcBlock:       model.Block{ID: \"test-1\", ParentID: \"test-card\"},\n\t\t\tdstBlock:       model.Block{ID: \"test-2\", ParentID: \"test-card\"},\n\t\t\tparentBlock:    &model.Block{ID: \"test-card\", Fields: map[string]interface{}{\"contentOrder\": []interface{}{\"test-1\", \"test-2\"}}},\n\t\t\twhere:          \"after\",\n\t\t\tuserID:         \"user-id\",\n\t\t\tmockPatch:      true,\n\t\t\tmockPatchError: errors.New(\"test error\"),\n\t\t\terrorMessage:   \"test error\",\n\t\t},\n\t\t{\n\t\t\tname:                 \"valid request with not real change\",\n\t\t\tsrcBlock:             model.Block{ID: \"test-2\", ParentID: \"test-card\"},\n\t\t\tdstBlock:             model.Block{ID: \"test-1\", ParentID: \"test-card\"},\n\t\t\tparentBlock:          &model.Block{ID: \"test-card\", Fields: map[string]interface{}{\"contentOrder\": []interface{}{\"test-1\", \"test-2\", \"test-3\"}}, BoardID: \"test-board\"},\n\t\t\twhere:                \"after\",\n\t\t\tuserID:               \"user-id\",\n\t\t\tmockPatch:            true,\n\t\t\terrorMessage:         \"\",\n\t\t\texpectedContentOrder: []string{\"test-1\", \"test-2\", \"test-3\"},\n\t\t},\n\t\t{\n\t\t\tname:                 \"valid request changing order with before\",\n\t\t\tsrcBlock:             model.Block{ID: \"test-2\", ParentID: \"test-card\"},\n\t\t\tdstBlock:             model.Block{ID: \"test-1\", ParentID: \"test-card\"},\n\t\t\tparentBlock:          &model.Block{ID: \"test-card\", Fields: map[string]interface{}{\"contentOrder\": []interface{}{\"test-1\", \"test-2\", \"test-3\"}}, BoardID: \"test-board\"},\n\t\t\twhere:                \"before\",\n\t\t\tuserID:               \"user-id\",\n\t\t\tmockPatch:            true,\n\t\t\terrorMessage:         \"\",\n\t\t\texpectedContentOrder: []string{\"test-2\", \"test-1\", \"test-3\"},\n\t\t},\n\t\t{\n\t\t\tname:                 \"valid request changing order with after\",\n\t\t\tsrcBlock:             model.Block{ID: \"test-1\", ParentID: \"test-card\"},\n\t\t\tdstBlock:             model.Block{ID: \"test-2\", ParentID: \"test-card\"},\n\t\t\tparentBlock:          &model.Block{ID: \"test-card\", Fields: map[string]interface{}{\"contentOrder\": []interface{}{\"test-1\", \"test-2\", \"test-3\"}}, BoardID: \"test-board\"},\n\t\t\twhere:                \"after\",\n\t\t\tuserID:               \"user-id\",\n\t\t\tmockPatch:            true,\n\t\t\terrorMessage:         \"\",\n\t\t\texpectedContentOrder: []string{\"test-2\", \"test-1\", \"test-3\"},\n\t\t},\n\t}\n\n\tfor _, tc := range ttCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ttc := tc\n\t\t\tif tc.parentBlock != nil {\n\t\t\t\tif tc.parentBlock.ID == \"invalid-card\" {\n\t\t\t\t\tth.Store.EXPECT().GetBlock(tc.srcBlock.ParentID).Return(nil, model.NewErrNotFound(\"test\"))\n\t\t\t\t} else {\n\t\t\t\t\tth.Store.EXPECT().GetBlock(tc.parentBlock.ID).Return(tc.parentBlock, nil)\n\t\t\t\t\tif tc.mockPatch {\n\t\t\t\t\t\tif tc.mockPatchError != nil {\n\t\t\t\t\t\t\tth.Store.EXPECT().GetBlock(tc.parentBlock.ID).Return(nil, tc.mockPatchError)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tth.Store.EXPECT().GetBlock(tc.parentBlock.ID).Return(tc.parentBlock, nil)\n\t\t\t\t\t\t\tth.Store.EXPECT().PatchBlock(tc.parentBlock.ID, NewContentOrderMatcher(tc.expectedContentOrder), gomock.Eq(\"user-id\")).Return(nil)\n\t\t\t\t\t\t\tth.Store.EXPECT().GetBlock(tc.parentBlock.ID).Return(tc.parentBlock, nil)\n\t\t\t\t\t\t\tth.Store.EXPECT().GetBoard(tc.parentBlock.BoardID).Return(&model.Board{ID: \"test-board\"}, nil)\n\t\t\t\t\t\t\t// this call comes from the WS server notification\n\t\t\t\t\t\t\tth.Store.EXPECT().GetMembersForBoard(gomock.Any()).Times(1)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\terr := th.App.MoveContentBlock(&tc.srcBlock, &tc.dstBlock, tc.where, tc.userID)\n\t\t\tif tc.errorMessage == \"\" {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t} else {\n\t\t\t\trequire.EqualError(t, err, tc.errorMessage)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "server/app/export.go",
    "content": "package app\n\nimport (\n\t\"archive/zip\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/wiggin77/merror\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nvar (\n\tnewline = []byte{'\\n'}\n)\n\nfunc (a *App) ExportArchive(w io.Writer, opt model.ExportArchiveOptions) (errs error) {\n\tboards, err := a.getBoardsForArchive(opt.BoardIDs)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tmerr := merror.New()\n\tdefer func() {\n\t\terrs = merr.ErrorOrNil()\n\t}()\n\n\t// wrap the writer in a zip.\n\tzw := zip.NewWriter(w)\n\tdefer func() {\n\t\tmerr.Append(zw.Close())\n\t}()\n\n\tif err := a.writeArchiveVersion(zw); err != nil {\n\t\tmerr.Append(err)\n\t\treturn\n\t}\n\n\tfor _, board := range boards {\n\t\tif err := a.writeArchiveBoard(zw, board, opt); err != nil {\n\t\t\tmerr.Append(fmt.Errorf(\"cannot export board %s: %w\", board.ID, err))\n\t\t\treturn\n\t\t}\n\t}\n\treturn nil\n}\n\n// writeArchiveVersion writes a version file to the zip.\nfunc (a *App) writeArchiveVersion(zw *zip.Writer) error {\n\tarchiveHeader := model.ArchiveHeader{\n\t\tVersion: archiveVersion,\n\t\tDate:    model.GetMillis(),\n\t}\n\tb, _ := json.Marshal(&archiveHeader)\n\n\tw, err := zw.Create(\"version.json\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"cannot write archive header: %w\", err)\n\t}\n\n\tif _, err := w.Write(b); err != nil {\n\t\treturn fmt.Errorf(\"cannot write archive header: %w\", err)\n\t}\n\treturn nil\n}\n\n// writeArchiveBoard writes a single board to the archive in a zip directory.\nfunc (a *App) writeArchiveBoard(zw *zip.Writer, board model.Board, opt model.ExportArchiveOptions) error {\n\t// create a directory per board\n\tw, err := zw.Create(board.ID + \"/board.jsonl\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// write the board block first\n\tif err = a.writeArchiveBoardLine(w, board); err != nil {\n\t\treturn err\n\t}\n\n\tvar files []string\n\t// write the board's blocks\n\t// TODO: paginate this\n\tblocks, err := a.GetBlocksForBoard(board.ID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, block := range blocks {\n\t\tif err = a.writeArchiveBlockLine(w, block); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif block.Type == model.TypeImage || block.Type == model.TypeAttachment {\n\t\t\tfilename, err2 := extractFilename(block)\n\t\t\tif err2 != nil {\n\t\t\t\treturn err2\n\t\t\t}\n\t\t\tfiles = append(files, filename)\n\t\t}\n\t}\n\n\tboardMembers, err := a.GetMembersForBoard(board.ID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, boardMember := range boardMembers {\n\t\tif err = a.writeArchiveBoardMemberLine(w, boardMember); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// write the files\n\tfor _, filename := range files {\n\t\tif err := a.writeArchiveFile(zw, filename, board.ID, opt); err != nil {\n\t\t\treturn fmt.Errorf(\"cannot write file %s to archive: %w\", filename, err)\n\t\t}\n\t}\n\treturn nil\n}\n\n// writeArchiveBoardMemberLine writes a single boardMember to the archive.\nfunc (a *App) writeArchiveBoardMemberLine(w io.Writer, boardMember *model.BoardMember) error {\n\tbm, err := json.Marshal(&boardMember)\n\tif err != nil {\n\t\treturn err\n\t}\n\tline := model.ArchiveLine{\n\t\tType: \"boardMember\",\n\t\tData: bm,\n\t}\n\n\tbm, err = json.Marshal(&line)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = w.Write(bm)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = w.Write(newline)\n\treturn err\n}\n\n// writeArchiveBlockLine writes a single block to the archive.\nfunc (a *App) writeArchiveBlockLine(w io.Writer, block *model.Block) error {\n\tb, err := json.Marshal(&block)\n\tif err != nil {\n\t\treturn err\n\t}\n\tline := model.ArchiveLine{\n\t\tType: \"block\",\n\t\tData: b,\n\t}\n\n\tb, err = json.Marshal(&line)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = w.Write(b)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// jsonl files need a newline\n\t_, err = w.Write(newline)\n\treturn err\n}\n\n// writeArchiveBlockLine writes a single block to the archive.\nfunc (a *App) writeArchiveBoardLine(w io.Writer, board model.Board) error {\n\tb, err := json.Marshal(&board)\n\tif err != nil {\n\t\treturn err\n\t}\n\tline := model.ArchiveLine{\n\t\tType: \"board\",\n\t\tData: b,\n\t}\n\n\tb, err = json.Marshal(&line)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = w.Write(b)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// jsonl files need a newline\n\t_, err = w.Write(newline)\n\treturn err\n}\n\n// writeArchiveFile writes a single file to the archive.\nfunc (a *App) writeArchiveFile(zw *zip.Writer, filename string, boardID string, opt model.ExportArchiveOptions) error {\n\tdest, err := zw.Create(boardID + \"/\" + filename)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, fileReader, err := a.GetFile(opt.TeamID, boardID, filename)\n\tif err != nil && !model.IsErrNotFound(err) {\n\t\treturn err\n\t}\n\tif err != nil {\n\t\t// just log this; image file is missing but we'll still export an equivalent board\n\t\ta.logger.Error(\"image file missing for export\",\n\t\t\tmlog.String(\"filename\", filename),\n\t\t\tmlog.String(\"team_id\", opt.TeamID),\n\t\t\tmlog.String(\"board_id\", boardID),\n\t\t)\n\t\treturn nil\n\t}\n\tdefer fileReader.Close()\n\n\t_, err = io.Copy(dest, fileReader)\n\treturn err\n}\n\n// getBoardsForArchive fetches all the specified boards.\nfunc (a *App) getBoardsForArchive(boardIDs []string) ([]model.Board, error) {\n\tboards := make([]model.Board, 0, len(boardIDs))\n\n\tfor _, id := range boardIDs {\n\t\tb, err := a.GetBoard(id)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"could not fetch board %s: %w\", id, err)\n\t\t}\n\n\t\tboards = append(boards, *b)\n\t}\n\treturn boards, nil\n}\n\nfunc extractFilename(block *model.Block) (string, error) {\n\tf, ok := block.Fields[\"fileId\"]\n\tif !ok {\n\t\tf, ok = block.Fields[\"attachmentId\"]\n\t\tif !ok {\n\t\t\treturn \"\", model.ErrInvalidImageBlock\n\t\t}\n\t}\n\n\tfilename, ok := f.(string)\n\tif !ok {\n\t\treturn \"\", model.ErrInvalidImageBlock\n\t}\n\treturn filename, nil\n}\n"
  },
  {
    "path": "server/app/files.go",
    "content": "package app\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\tmm_model \"github.com/mattermost/mattermost/server/public/model\"\n\n\t\"github.com/mattermost/focalboard/server/utils\"\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n\t\"github.com/mattermost/mattermost/server/v8/platform/shared/filestore\"\n)\n\nconst emptyString = \"empty\"\n\nvar errEmptyFilename = errors.New(\"IsFileArchived: empty filename not allowed\")\nvar ErrFileNotFound = errors.New(\"file not found\")\n\nfunc (a *App) SaveFile(reader io.Reader, teamID, boardID, filename string, asTemplate bool) (string, error) {\n\t// NOTE: File extension includes the dot\n\tfileExtension := strings.ToLower(filepath.Ext(filename))\n\tif fileExtension == \".jpeg\" {\n\t\tfileExtension = \".jpg\"\n\t}\n\n\tcreatedFilename := utils.NewID(utils.IDTypeNone)\n\tnewFileName := fmt.Sprintf(`%s%s`, createdFilename, fileExtension)\n\tif asTemplate {\n\t\tnewFileName = filename\n\t}\n\tfilePath := getDestinationFilePath(asTemplate, teamID, boardID, newFileName)\n\n\tfileSize, appErr := a.filesBackend.WriteFile(reader, filePath)\n\tif appErr != nil {\n\t\treturn \"\", fmt.Errorf(\"unable to store the file in the files storage: %w\", appErr)\n\t}\n\n\tfileInfo := model.NewFileInfo(filename)\n\tfileInfo.Id = getFileInfoID(createdFilename)\n\tfileInfo.Path = filePath\n\tfileInfo.Size = fileSize\n\n\terr := a.store.SaveFileInfo(fileInfo)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn newFileName, nil\n}\n\nfunc (a *App) GetFileInfo(filename string) (*mm_model.FileInfo, error) {\n\tif len(filename) == 0 {\n\t\treturn nil, errEmptyFilename\n\t}\n\n\t// filename is in the format 7<some-alphanumeric-string>.<extension>\n\t// we want to extract the <some-alphanumeric-string> part of this as this\n\t// will be the fileinfo id.\n\tfileInfoID := getFileInfoID(strings.Split(filename, \".\")[0])\n\n\tfileInfo, err := a.store.GetFileInfo(fileInfoID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn fileInfo, nil\n}\n\nfunc (a *App) GetFile(teamID, rootID, fileName string) (*mm_model.FileInfo, filestore.ReadCloseSeeker, error) {\n\tfileInfo, filePath, err := a.GetFilePath(teamID, rootID, fileName)\n\tif err != nil {\n\t\ta.logger.Error(\"GetFile: Failed to GetFilePath.\", mlog.String(\"Team\", teamID), mlog.String(\"board\", rootID), mlog.String(\"filename\", fileName), mlog.Err(err))\n\t\treturn nil, nil, err\n\t}\n\n\texists, err := a.filesBackend.FileExists(filePath)\n\tif err != nil {\n\t\ta.logger.Error(\"GetFile: Failed to check if file exists as path. \", mlog.String(\"Path\", filePath), mlog.Err(err))\n\t\treturn nil, nil, err\n\t}\n\n\tif !exists {\n\t\treturn nil, nil, ErrFileNotFound\n\t}\n\n\treader, err := a.filesBackend.Reader(filePath)\n\tif err != nil {\n\t\ta.logger.Error(\"GetFile: Failed to get file reader of existing file at path\", mlog.String(\"Path\", filePath), mlog.Err(err))\n\t\treturn nil, nil, err\n\t}\n\n\treturn fileInfo, reader, nil\n}\n\nfunc (a *App) GetFilePath(teamID, rootID, fileName string) (*mm_model.FileInfo, string, error) {\n\tfileInfo, err := a.GetFileInfo(fileName)\n\tif err != nil && !model.IsErrNotFound(err) {\n\t\treturn nil, \"\", err\n\t}\n\n\tvar filePath string\n\n\tif fileInfo != nil && fileInfo.Path != \"\" && fileInfo.Path != emptyString {\n\t\tfilePath = fileInfo.Path\n\t} else {\n\t\tfilePath = filepath.Join(teamID, rootID, fileName)\n\t}\n\n\treturn fileInfo, filePath, nil\n}\n\nfunc getDestinationFilePath(isTemplate bool, teamID, boardID, filename string) string {\n\t// if saving a file for a template, save using the \"old method\" that is /teamID/boardID/fileName\n\t// this will prevent template files from being deleted by DataRetention,\n\t// which deletes all files inside the \"date\" subdirectory\n\tif isTemplate {\n\t\treturn filepath.Join(teamID, boardID, filename)\n\t}\n\treturn filepath.Join(utils.GetBaseFilePath(), filename)\n}\n\nfunc getFileInfoID(fileName string) string {\n\t// Boards ids are 27 characters long with a prefix character.\n\t// removing the prefix, returns the 26 character uuid\n\treturn fileName[1:]\n}\n\nfunc (a *App) GetFileReader(teamID, rootID, filename string) (filestore.ReadCloseSeeker, error) {\n\tfilePath := filepath.Join(teamID, rootID, filename)\n\texists, err := a.filesBackend.FileExists(filePath)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t// FIXUP: Check the deprecated old location\n\tif teamID == \"0\" && !exists {\n\t\toldExists, err2 := a.filesBackend.FileExists(filename)\n\t\tif err2 != nil {\n\t\t\treturn nil, err2\n\t\t}\n\t\tif oldExists {\n\t\t\terr2 := a.filesBackend.MoveFile(filename, filePath)\n\t\t\tif err2 != nil {\n\t\t\t\ta.logger.Error(\"ERROR moving file\",\n\t\t\t\t\tmlog.String(\"old\", filename),\n\t\t\t\t\tmlog.String(\"new\", filePath),\n\t\t\t\t\tmlog.Err(err2),\n\t\t\t\t)\n\t\t\t} else {\n\t\t\t\ta.logger.Debug(\"Moved file\",\n\t\t\t\t\tmlog.String(\"old\", filename),\n\t\t\t\t\tmlog.String(\"new\", filePath),\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t} else if !exists {\n\t\treturn nil, ErrFileNotFound\n\t}\n\n\treader, err := a.filesBackend.Reader(filePath)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn reader, nil\n}\n\nfunc (a *App) MoveFile(channelID, teamID, boardID, filename string) error {\n\toldPath := filepath.Join(channelID, boardID, filename)\n\tnewPath := filepath.Join(teamID, boardID, filename)\n\terr := a.filesBackend.MoveFile(oldPath, newPath)\n\tif err != nil {\n\t\ta.logger.Error(\"ERROR moving file\",\n\t\t\tmlog.String(\"old\", oldPath),\n\t\t\tmlog.String(\"new\", newPath),\n\t\t\tmlog.Err(err),\n\t\t)\n\t\treturn err\n\t}\n\treturn nil\n}\nfunc (a *App) CopyAndUpdateCardFiles(boardID, userID string, blocks []*model.Block, asTemplate bool) error {\n\tnewFileNames, err := a.CopyCardFiles(boardID, blocks, asTemplate)\n\tif err != nil {\n\t\ta.logger.Error(\"Could not copy files while duplicating board\", mlog.String(\"BoardID\", boardID), mlog.Err(err))\n\t}\n\n\t// blocks now has updated file ids for any blocks containing files.  We need to update the database for them.\n\tblockIDs := make([]string, 0)\n\tblockPatches := make([]model.BlockPatch, 0)\n\tfor _, block := range blocks {\n\t\tif block.Type == model.TypeImage || block.Type == model.TypeAttachment {\n\t\t\tif fileID, ok := block.Fields[\"fileId\"].(string); ok {\n\t\t\t\tblockIDs = append(blockIDs, block.ID)\n\t\t\t\tblockPatches = append(blockPatches, model.BlockPatch{\n\t\t\t\t\tUpdatedFields: map[string]interface{}{\n\t\t\t\t\t\t\"fileId\": newFileNames[fileID],\n\t\t\t\t\t},\n\t\t\t\t\tDeletedFields: []string{\"attachmentId\"},\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\ta.logger.Debug(\"Duplicate boards patching file IDs\", mlog.Int(\"count\", len(blockIDs)))\n\n\tif len(blockIDs) != 0 {\n\t\tpatches := &model.BlockPatchBatch{\n\t\t\tBlockIDs:     blockIDs,\n\t\t\tBlockPatches: blockPatches,\n\t\t}\n\t\tif err := a.store.PatchBlocks(patches, userID); err != nil {\n\t\t\treturn fmt.Errorf(\"could not patch file IDs while duplicating board %s: %w\", boardID, err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (a *App) CopyCardFiles(sourceBoardID string, copiedBlocks []*model.Block, asTemplate bool) (map[string]string, error) {\n\t// Images attached in cards have a path comprising the card's board ID.\n\t// When we create a template from this board, we need to copy the files\n\t// with the new board ID in path.\n\t// Not doing so causing images in templates (and boards created from this\n\t// template) to fail to load.\n\n\t// look up ID of source sourceBoard, which may be different than the blocks.\n\tsourceBoard, err := a.GetBoard(sourceBoardID)\n\tif err != nil || sourceBoard == nil {\n\t\treturn nil, fmt.Errorf(\"cannot fetch source board %s for CopyCardFiles: %w\", sourceBoardID, err)\n\t}\n\n\tvar destBoard *model.Board\n\tnewFileNames := make(map[string]string)\n\tfor _, block := range copiedBlocks {\n\t\tif block.Type != model.TypeImage && block.Type != model.TypeAttachment {\n\t\t\tcontinue\n\t\t}\n\n\t\tfileID, isOk := block.Fields[\"fileId\"].(string)\n\t\tif !isOk {\n\t\t\tfileID, isOk = block.Fields[\"attachmentId\"].(string)\n\t\t\tif !isOk {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\t// create unique filename\n\t\text := filepath.Ext(fileID)\n\t\tfileInfoID := utils.NewID(utils.IDTypeNone)\n\t\tdestFilename := fileInfoID + ext\n\n\t\tif destBoard == nil || block.BoardID != destBoard.ID {\n\t\t\tdestBoard = sourceBoard\n\t\t\tif block.BoardID != destBoard.ID {\n\t\t\t\tdestBoard, err = a.GetBoard(block.BoardID)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, fmt.Errorf(\"cannot fetch destination board %s for CopyCardFiles: %w\", sourceBoardID, err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// GetFilePath will retrieve the correct path\n\t\t// depending on whether FileInfo table is used for the file.\n\t\tfileInfo, sourceFilePath, err := a.GetFilePath(sourceBoard.TeamID, sourceBoard.ID, fileID)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"cannot fetch destination board %s for CopyCardFiles: %w\", sourceBoardID, err)\n\t\t}\n\t\tdestinationFilePath := getDestinationFilePath(asTemplate, destBoard.TeamID, destBoard.ID, destFilename)\n\n\t\tif fileInfo == nil {\n\t\t\tfileInfo = model.NewFileInfo(destFilename)\n\t\t}\n\t\tfileInfo.Id = getFileInfoID(fileInfoID)\n\t\tfileInfo.Path = destinationFilePath\n\t\terr = a.store.SaveFileInfo(fileInfo)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"CopyCardFiles: cannot create fileinfo: %w\", err)\n\t\t}\n\n\t\ta.logger.Debug(\n\t\t\t\"Copying card file\",\n\t\t\tmlog.String(\"sourceFilePath\", sourceFilePath),\n\t\t\tmlog.String(\"destinationFilePath\", destinationFilePath),\n\t\t)\n\n\t\tif err := a.filesBackend.CopyFile(sourceFilePath, destinationFilePath); err != nil {\n\t\t\ta.logger.Error(\n\t\t\t\t\"CopyCardFiles failed to copy file\",\n\t\t\t\tmlog.String(\"sourceFilePath\", sourceFilePath),\n\t\t\t\tmlog.String(\"destinationFilePath\", destinationFilePath),\n\t\t\t\tmlog.Err(err),\n\t\t\t)\n\t\t}\n\t\tnewFileNames[fileID] = destFilename\n\t}\n\n\treturn newFileNames, nil\n}\n"
  },
  {
    "path": "server/app/files_test.go",
    "content": "package app\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/golang/mock/gomock\"\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\tmm_model \"github.com/mattermost/mattermost/server/public/model\"\n\t\"github.com/mattermost/mattermost/server/public/plugin/plugintest/mock\"\n\t\"github.com/mattermost/mattermost/server/v8/platform/shared/filestore\"\n\t\"github.com/mattermost/mattermost/server/v8/platform/shared/filestore/mocks\"\n)\n\nconst (\n\ttestFileName = \"temp-file-name\"\n\ttestBoardID  = \"test-board-id\"\n\ttestPath     = \"/path/to/file/fileName.txt\"\n)\n\nvar errDummy = errors.New(\"hello\")\n\ntype TestError struct{}\n\nfunc (err *TestError) Error() string { return \"Mocked File backend error\" }\n\nfunc TestGetFileReader(t *testing.T) {\n\ttestFilePath := filepath.Join(\"1\", \"test-board-id\", \"temp-file-name\")\n\n\tth, _ := SetupTestHelper(t)\n\tmockedReadCloseSeek := &mocks.ReadCloseSeeker{}\n\tt.Run(\"should get file reader from filestore successfully\", func(t *testing.T) {\n\t\tmockedFileBackend := &mocks.FileBackend{}\n\t\tth.App.filesBackend = mockedFileBackend\n\t\treaderFunc := func(path string) filestore.ReadCloseSeeker {\n\t\t\treturn mockedReadCloseSeek\n\t\t}\n\n\t\treaderErrorFunc := func(path string) error {\n\t\t\treturn nil\n\t\t}\n\n\t\tfileExistsFunc := func(path string) bool {\n\t\t\treturn true\n\t\t}\n\n\t\tfileExistsErrorFunc := func(path string) error {\n\t\t\treturn nil\n\t\t}\n\n\t\tmockedFileBackend.On(\"Reader\", testFilePath).Return(readerFunc, readerErrorFunc)\n\t\tmockedFileBackend.On(\"FileExists\", testFilePath).Return(fileExistsFunc, fileExistsErrorFunc)\n\t\tactual, _ := th.App.GetFileReader(\"1\", testBoardID, testFileName)\n\t\tassert.Equal(t, mockedReadCloseSeek, actual)\n\t})\n\n\tt.Run(\"should get error from filestore when file exists return error\", func(t *testing.T) {\n\t\tmockedFileBackend := &mocks.FileBackend{}\n\t\tth.App.filesBackend = mockedFileBackend\n\t\tmockedError := &TestError{}\n\t\treaderFunc := func(path string) filestore.ReadCloseSeeker {\n\t\t\treturn mockedReadCloseSeek\n\t\t}\n\n\t\treaderErrorFunc := func(path string) error {\n\t\t\treturn nil\n\t\t}\n\n\t\tfileExistsFunc := func(path string) bool {\n\t\t\treturn false\n\t\t}\n\n\t\tfileExistsErrorFunc := func(path string) error {\n\t\t\treturn mockedError\n\t\t}\n\n\t\tmockedFileBackend.On(\"Reader\", testFilePath).Return(readerFunc, readerErrorFunc)\n\t\tmockedFileBackend.On(\"FileExists\", testFilePath).Return(fileExistsFunc, fileExistsErrorFunc)\n\t\tactual, err := th.App.GetFileReader(\"1\", testBoardID, testFileName)\n\t\tassert.Error(t, err, mockedError)\n\t\tassert.Nil(t, actual)\n\t})\n\n\tt.Run(\"should return error, if get reader from file backend returns error\", func(t *testing.T) {\n\t\tmockedFileBackend := &mocks.FileBackend{}\n\t\tth.App.filesBackend = mockedFileBackend\n\t\tmockedError := &TestError{}\n\t\treaderFunc := func(path string) filestore.ReadCloseSeeker {\n\t\t\treturn nil\n\t\t}\n\n\t\treaderErrorFunc := func(path string) error {\n\t\t\treturn mockedError\n\t\t}\n\n\t\tfileExistsFunc := func(path string) bool {\n\t\t\treturn false\n\t\t}\n\n\t\tfileExistsErrorFunc := func(path string) error {\n\t\t\treturn nil\n\t\t}\n\n\t\tmockedFileBackend.On(\"Reader\", testFilePath).Return(readerFunc, readerErrorFunc)\n\t\tmockedFileBackend.On(\"FileExists\", testFilePath).Return(fileExistsFunc, fileExistsErrorFunc)\n\t\tactual, err := th.App.GetFileReader(\"1\", testBoardID, testFileName)\n\t\tassert.Error(t, err, mockedError)\n\t\tassert.Nil(t, actual)\n\t})\n\n\tt.Run(\"should move file from old filepath to new filepath, if file doesnot exists in new filepath and workspace id is 0\", func(t *testing.T) {\n\t\tfilePath := filepath.Join(\"0\", \"test-board-id\", \"temp-file-name\")\n\t\tworkspaceid := \"0\"\n\t\tmockedFileBackend := &mocks.FileBackend{}\n\t\tth.App.filesBackend = mockedFileBackend\n\t\treaderFunc := func(path string) filestore.ReadCloseSeeker {\n\t\t\treturn mockedReadCloseSeek\n\t\t}\n\n\t\treaderErrorFunc := func(path string) error {\n\t\t\treturn nil\n\t\t}\n\n\t\tfileExistsFunc := func(path string) bool {\n\t\t\t// return true for old path\n\t\t\treturn path == testFileName\n\t\t}\n\n\t\tfileExistsErrorFunc := func(path string) error {\n\t\t\treturn nil\n\t\t}\n\n\t\tmoveFileFunc := func(oldFileName, newFileName string) error {\n\t\t\treturn nil\n\t\t}\n\n\t\tmockedFileBackend.On(\"FileExists\", filePath).Return(fileExistsFunc, fileExistsErrorFunc)\n\t\tmockedFileBackend.On(\"FileExists\", testFileName).Return(fileExistsFunc, fileExistsErrorFunc)\n\t\tmockedFileBackend.On(\"MoveFile\", testFileName, filePath).Return(moveFileFunc)\n\t\tmockedFileBackend.On(\"Reader\", filePath).Return(readerFunc, readerErrorFunc)\n\n\t\tactual, _ := th.App.GetFileReader(workspaceid, testBoardID, testFileName)\n\t\tassert.Equal(t, mockedReadCloseSeek, actual)\n\t})\n\n\tt.Run(\"should return file reader, if file doesnot exists in new filepath and old file path\", func(t *testing.T) {\n\t\tfilePath := filepath.Join(\"0\", \"test-board-id\", \"temp-file-name\")\n\t\tfileName := testFileName\n\t\tworkspaceid := \"0\"\n\t\tmockedFileBackend := &mocks.FileBackend{}\n\t\tth.App.filesBackend = mockedFileBackend\n\t\treaderFunc := func(path string) filestore.ReadCloseSeeker {\n\t\t\treturn mockedReadCloseSeek\n\t\t}\n\n\t\treaderErrorFunc := func(path string) error {\n\t\t\treturn nil\n\t\t}\n\n\t\tfileExistsFunc := func(path string) bool {\n\t\t\t// return true for old path\n\t\t\treturn false\n\t\t}\n\n\t\tfileExistsErrorFunc := func(path string) error {\n\t\t\treturn nil\n\t\t}\n\n\t\tmoveFileFunc := func(oldFileName, newFileName string) error {\n\t\t\treturn nil\n\t\t}\n\n\t\tmockedFileBackend.On(\"FileExists\", filePath).Return(fileExistsFunc, fileExistsErrorFunc)\n\t\tmockedFileBackend.On(\"FileExists\", testFileName).Return(fileExistsFunc, fileExistsErrorFunc)\n\t\tmockedFileBackend.On(\"MoveFile\", fileName, filePath).Return(moveFileFunc)\n\t\tmockedFileBackend.On(\"Reader\", filePath).Return(readerFunc, readerErrorFunc)\n\n\t\tactual, _ := th.App.GetFileReader(workspaceid, testBoardID, testFileName)\n\t\tassert.Equal(t, mockedReadCloseSeek, actual)\n\t})\n}\n\nfunc TestSaveFile(t *testing.T) {\n\tth, _ := SetupTestHelper(t)\n\tmockedReadCloseSeek := &mocks.ReadCloseSeeker{}\n\tt.Run(\"should save file to file store using file backend\", func(t *testing.T) {\n\t\tfileName := \"temp-file-name.txt\"\n\t\tmockedFileBackend := &mocks.FileBackend{}\n\t\tth.App.filesBackend = mockedFileBackend\n\t\tth.Store.EXPECT().SaveFileInfo(gomock.Any()).Return(nil)\n\n\t\twriteFileFunc := func(reader io.Reader, path string) int64 {\n\t\t\tpaths := strings.Split(path, string(os.PathSeparator))\n\t\t\tassert.Equal(t, \"boards\", paths[0])\n\t\t\tassert.Equal(t, time.Now().Format(\"20060102\"), paths[1])\n\t\t\tfileName = paths[2]\n\t\t\treturn int64(10)\n\t\t}\n\n\t\twriteFileErrorFunc := func(reader io.Reader, filePath string) error {\n\t\t\treturn nil\n\t\t}\n\n\t\tmockedFileBackend.On(\"WriteFile\", mockedReadCloseSeek, mock.Anything).Return(writeFileFunc, writeFileErrorFunc)\n\t\tactual, err := th.App.SaveFile(mockedReadCloseSeek, \"1\", testBoardID, fileName, false)\n\t\tassert.Equal(t, fileName, actual)\n\t\tassert.Nil(t, err)\n\t})\n\n\tt.Run(\"should save .jpeg file as jpg file to file store using file backend\", func(t *testing.T) {\n\t\tfileName := \"temp-file-name.jpeg\"\n\t\tmockedFileBackend := &mocks.FileBackend{}\n\t\tth.App.filesBackend = mockedFileBackend\n\t\tth.Store.EXPECT().SaveFileInfo(gomock.Any()).Return(nil)\n\n\t\twriteFileFunc := func(reader io.Reader, path string) int64 {\n\t\t\tpaths := strings.Split(path, string(os.PathSeparator))\n\t\t\tassert.Equal(t, \"boards\", paths[0])\n\t\t\tassert.Equal(t, time.Now().Format(\"20060102\"), paths[1])\n\t\t\tassert.Equal(t, \"jpg\", strings.Split(paths[2], \".\")[1])\n\t\t\treturn int64(10)\n\t\t}\n\n\t\twriteFileErrorFunc := func(reader io.Reader, filePath string) error {\n\t\t\treturn nil\n\t\t}\n\n\t\tmockedFileBackend.On(\"WriteFile\", mockedReadCloseSeek, mock.Anything).Return(writeFileFunc, writeFileErrorFunc)\n\t\tactual, err := th.App.SaveFile(mockedReadCloseSeek, \"1\", \"test-board-id\", fileName, false)\n\t\tassert.Nil(t, err)\n\t\tassert.NotNil(t, actual)\n\t})\n\n\tt.Run(\"should return error when fileBackend.WriteFile returns error\", func(t *testing.T) {\n\t\tfileName := \"temp-file-name.jpeg\"\n\t\tmockedFileBackend := &mocks.FileBackend{}\n\t\tth.App.filesBackend = mockedFileBackend\n\t\tmockedError := &TestError{}\n\n\t\twriteFileFunc := func(reader io.Reader, path string) int64 {\n\t\t\tpaths := strings.Split(path, string(os.PathSeparator))\n\t\t\tassert.Equal(t, \"boards\", paths[0])\n\t\t\tassert.Equal(t, time.Now().Format(\"20060102\"), paths[1])\n\t\t\tassert.Equal(t, \"jpg\", strings.Split(paths[2], \".\")[1])\n\t\t\treturn int64(10)\n\t\t}\n\n\t\twriteFileErrorFunc := func(reader io.Reader, filePath string) error {\n\t\t\treturn mockedError\n\t\t}\n\n\t\tmockedFileBackend.On(\"WriteFile\", mockedReadCloseSeek, mock.Anything).Return(writeFileFunc, writeFileErrorFunc)\n\t\tactual, err := th.App.SaveFile(mockedReadCloseSeek, \"1\", \"test-board-id\", fileName, false)\n\t\tassert.Equal(t, \"\", actual)\n\t\tassert.Equal(t, \"unable to store the file in the files storage: Mocked File backend error\", err.Error())\n\t})\n}\n\nfunc TestGetFileInfo(t *testing.T) {\n\tth, _ := SetupTestHelper(t)\n\n\tt.Run(\"should return file info\", func(t *testing.T) {\n\t\tfileInfo := &mm_model.FileInfo{\n\t\t\tId:       \"file_info_id\",\n\t\t\tArchived: false,\n\t\t}\n\n\t\tth.Store.EXPECT().GetFileInfo(\"filename\").Return(fileInfo, nil).Times(2)\n\n\t\tfetchedFileInfo, err := th.App.GetFileInfo(\"Afilename\")\n\t\tassert.NoError(t, err)\n\t\tassert.Equal(t, \"file_info_id\", fetchedFileInfo.Id)\n\t\tassert.False(t, fetchedFileInfo.Archived)\n\n\t\tfetchedFileInfo, err = th.App.GetFileInfo(\"Afilename.txt\")\n\t\tassert.NoError(t, err)\n\t\tassert.Equal(t, \"file_info_id\", fetchedFileInfo.Id)\n\t\tassert.False(t, fetchedFileInfo.Archived)\n\t})\n\n\tt.Run(\"should return archived file info\", func(t *testing.T) {\n\t\tfileInfo := &mm_model.FileInfo{\n\t\t\tId:       \"file_info_id\",\n\t\t\tArchived: true,\n\t\t}\n\n\t\tth.Store.EXPECT().GetFileInfo(\"filename\").Return(fileInfo, nil)\n\n\t\tfetchedFileInfo, err := th.App.GetFileInfo(\"Afilename\")\n\t\tassert.NoError(t, err)\n\t\tassert.Equal(t, \"file_info_id\", fetchedFileInfo.Id)\n\t\tassert.True(t, fetchedFileInfo.Archived)\n\t})\n\n\tt.Run(\"should return archived file infoerror\", func(t *testing.T) {\n\t\tth.Store.EXPECT().GetFileInfo(\"filename\").Return(nil, errDummy)\n\n\t\tfetchedFileInfo, err := th.App.GetFileInfo(\"Afilename\")\n\t\tassert.Error(t, err)\n\t\tassert.Nil(t, fetchedFileInfo)\n\t})\n}\n\nfunc TestGetFile(t *testing.T) {\n\tth, _ := SetupTestHelper(t)\n\tt.Run(\"happy path, no errors\", func(t *testing.T) {\n\t\tth.Store.EXPECT().GetFileInfo(\"fileInfoID\").Return(&mm_model.FileInfo{\n\t\t\tId:   \"fileInfoID\",\n\t\t\tPath: testPath,\n\t\t}, nil)\n\n\t\tmockedFileBackend := &mocks.FileBackend{}\n\t\tth.App.filesBackend = mockedFileBackend\n\t\tmockedReadCloseSeek := &mocks.ReadCloseSeeker{}\n\t\treaderFunc := func(path string) filestore.ReadCloseSeeker {\n\t\t\treturn mockedReadCloseSeek\n\t\t}\n\n\t\treaderErrorFunc := func(path string) error {\n\t\t\treturn nil\n\t\t}\n\t\tmockedFileBackend.On(\"Reader\", testPath).Return(readerFunc, readerErrorFunc)\n\t\tmockedFileBackend.On(\"FileExists\", testPath).Return(true, nil)\n\n\t\tfileInfo, seeker, err := th.App.GetFile(\"teamID\", \"boardID\", \"7fileInfoID.txt\")\n\t\tassert.NoError(t, err)\n\t\tassert.NotNil(t, fileInfo)\n\t\tassert.NotNil(t, seeker)\n\t})\n\n\tt.Run(\"when GetFilePath() throws error\", func(t *testing.T) {\n\t\tth.Store.EXPECT().GetFileInfo(\"fileInfoID\").Return(nil, errDummy)\n\n\t\tfileInfo, seeker, err := th.App.GetFile(\"teamID\", \"boardID\", \"7fileInfoID.txt\")\n\t\tassert.Error(t, err)\n\t\tassert.Nil(t, fileInfo)\n\t\tassert.Nil(t, seeker)\n\t})\n\n\tt.Run(\"when FileExists returns false\", func(t *testing.T) {\n\t\tth.Store.EXPECT().GetFileInfo(\"fileInfoID\").Return(&mm_model.FileInfo{\n\t\t\tId:   \"fileInfoID\",\n\t\t\tPath: testPath,\n\t\t}, nil)\n\n\t\tmockedFileBackend := &mocks.FileBackend{}\n\t\tth.App.filesBackend = mockedFileBackend\n\t\tmockedFileBackend.On(\"FileExists\", testPath).Return(false, nil)\n\n\t\tfileInfo, seeker, err := th.App.GetFile(\"teamID\", \"boardID\", \"7fileInfoID.txt\")\n\t\tassert.Error(t, err)\n\t\tassert.Nil(t, fileInfo)\n\t\tassert.Nil(t, seeker)\n\t})\n\tt.Run(\"when FileReader throws error\", func(t *testing.T) {\n\t\tth.Store.EXPECT().GetFileInfo(\"fileInfoID\").Return(&mm_model.FileInfo{\n\t\t\tId:   \"fileInfoID\",\n\t\t\tPath: testPath,\n\t\t}, nil)\n\n\t\tmockedFileBackend := &mocks.FileBackend{}\n\t\tth.App.filesBackend = mockedFileBackend\n\t\tmockedFileBackend.On(\"Reader\", testPath).Return(nil, errDummy)\n\t\tmockedFileBackend.On(\"FileExists\", testPath).Return(true, nil)\n\n\t\tfileInfo, seeker, err := th.App.GetFile(\"teamID\", \"boardID\", \"7fileInfoID.txt\")\n\t\tassert.Error(t, err)\n\t\tassert.Nil(t, fileInfo)\n\t\tassert.Nil(t, seeker)\n\t})\n}\n\nfunc TestGetFilePath(t *testing.T) {\n\tth, _ := SetupTestHelper(t)\n\n\tt.Run(\"when FileInfo exists\", func(t *testing.T) {\n\t\tth.Store.EXPECT().GetFileInfo(\"fileInfoID\").Return(&mm_model.FileInfo{\n\t\t\tId:   \"fileInfoID\",\n\t\t\tPath: testPath,\n\t\t}, nil)\n\n\t\tfileInfo, filePath, err := th.App.GetFilePath(\"teamID\", \"boardID\", \"7fileInfoID.txt\")\n\t\tassert.NoError(t, err)\n\t\tassert.NotNil(t, fileInfo)\n\t\tassert.Equal(t, testPath, filePath)\n\t})\n\n\tt.Run(\"when FileInfo doesn't exist\", func(t *testing.T) {\n\t\tth.Store.EXPECT().GetFileInfo(\"fileInfoID\").Return(nil, nil)\n\n\t\tfileInfo, filePath, err := th.App.GetFilePath(\"teamID\", \"boardID\", \"7fileInfoID.txt\")\n\t\tassert.NoError(t, err)\n\t\tassert.Nil(t, fileInfo)\n\t\tassert.Equal(t, \"teamID/boardID/7fileInfoID.txt\", filePath)\n\t})\n\n\tt.Run(\"when FileInfo exists but FileInfo.Path is not set\", func(t *testing.T) {\n\t\tth.Store.EXPECT().GetFileInfo(\"fileInfoID\").Return(&mm_model.FileInfo{\n\t\t\tId:   \"fileInfoID\",\n\t\t\tPath: \"\",\n\t\t}, nil)\n\n\t\tfileInfo, filePath, err := th.App.GetFilePath(\"teamID\", \"boardID\", \"7fileInfoID.txt\")\n\t\tassert.NoError(t, err)\n\t\tassert.NotNil(t, fileInfo)\n\t\tassert.Equal(t, \"teamID/boardID/7fileInfoID.txt\", filePath)\n\t})\n}\n\nfunc TestCopyCard(t *testing.T) {\n\tth, _ := SetupTestHelper(t)\n\timageBlock := &model.Block{\n\t\tID:         \"imageBlock\",\n\t\tParentID:   \"c3zqnh6fsu3f4mr6hzq9hizwske\",\n\t\tCreatedBy:  \"6k6ynxdp47dujjhhojw9nqhmyh\",\n\t\tModifiedBy: \"6k6ynxdp47dujjhhojw9nqhmyh\",\n\t\tSchema:     1,\n\t\tType:       \"image\",\n\t\tTitle:      \"\",\n\t\tFields:     map[string]interface{}{\"fileId\": \"7fileName.jpg\"},\n\t\tCreateAt:   1680725585250,\n\t\tUpdateAt:   1680725585250,\n\t\tDeleteAt:   0,\n\t\tBoardID:    \"boardID\",\n\t}\n\tt.Run(\"Board doesn't exist\", func(t *testing.T) {\n\t\tth.Store.EXPECT().GetBoard(\"boardID\").Return(nil, errDummy)\n\t\t_, err := th.App.CopyCardFiles(\"boardID\", []*model.Block{}, false)\n\t\tassert.Error(t, err)\n\t})\n\n\tt.Run(\"Board exists, image block, with FileInfo\", func(t *testing.T) {\n\t\tfileInfo := &mm_model.FileInfo{\n\t\t\tId:   \"imageBlock\",\n\t\t\tPath: testPath,\n\t\t}\n\t\tth.Store.EXPECT().GetBoard(\"boardID\").Return(&model.Board{\n\t\t\tID:         \"boardID\",\n\t\t\tIsTemplate: false,\n\t\t}, nil)\n\t\tth.Store.EXPECT().GetFileInfo(\"fileName\").Return(fileInfo, nil)\n\t\tth.Store.EXPECT().SaveFileInfo(fileInfo).Return(nil)\n\n\t\tmockedFileBackend := &mocks.FileBackend{}\n\t\tth.App.filesBackend = mockedFileBackend\n\t\tmockedFileBackend.On(\"CopyFile\", mock.Anything, mock.Anything).Return(nil)\n\n\t\tupdatedFileNames, err := th.App.CopyCardFiles(\"boardID\", []*model.Block{imageBlock}, false)\n\t\tassert.NoError(t, err)\n\t\tassert.Equal(t, \"7fileName.jpg\", imageBlock.Fields[\"fileId\"])\n\t\tassert.NotNil(t, updatedFileNames[\"7fileName.jpg\"])\n\t\tassert.NotNil(t, updatedFileNames[imageBlock.Fields[\"fileId\"].(string)])\n\t})\n\n\tt.Run(\"Board exists, attachment block, with FileInfo\", func(t *testing.T) {\n\t\tattachmentBlock := &model.Block{\n\t\t\tID:         \"attachmentBlock\",\n\t\t\tParentID:   \"c3zqnh6fsu3f4mr6hzq9hizwske\",\n\t\t\tCreatedBy:  \"6k6ynxdp47dujjhhojw9nqhmyh\",\n\t\t\tModifiedBy: \"6k6ynxdp47dujjhhojw9nqhmyh\",\n\t\t\tSchema:     1,\n\t\t\tType:       \"attachment\",\n\t\t\tTitle:      \"\",\n\t\t\tFields:     map[string]interface{}{\"fileId\": \"7fileName.jpg\"},\n\t\t\tCreateAt:   1680725585250,\n\t\t\tUpdateAt:   1680725585250,\n\t\t\tDeleteAt:   0,\n\t\t\tBoardID:    \"boardID\",\n\t\t}\n\n\t\tfileInfo := &mm_model.FileInfo{\n\t\t\tId:   \"attachmentBlock\",\n\t\t\tPath: testPath,\n\t\t}\n\t\tth.Store.EXPECT().GetBoard(\"boardID\").Return(&model.Board{\n\t\t\tID:         \"boardID\",\n\t\t\tIsTemplate: false,\n\t\t}, nil)\n\t\tth.Store.EXPECT().GetFileInfo(\"fileName\").Return(fileInfo, nil)\n\t\tth.Store.EXPECT().SaveFileInfo(fileInfo).Return(nil)\n\n\t\tmockedFileBackend := &mocks.FileBackend{}\n\t\tth.App.filesBackend = mockedFileBackend\n\t\tmockedFileBackend.On(\"CopyFile\", mock.Anything, mock.Anything).Return(nil)\n\n\t\tupdatedFileNames, err := th.App.CopyCardFiles(\"boardID\", []*model.Block{attachmentBlock}, false)\n\t\tassert.NoError(t, err)\n\t\tassert.NotNil(t, updatedFileNames[imageBlock.Fields[\"fileId\"].(string)])\n\t})\n\n\tt.Run(\"Board exists, image block, without FileInfo\", func(t *testing.T) {\n\t\tth.Store.EXPECT().GetBoard(\"boardID\").Return(&model.Board{\n\t\t\tID:         \"boardID\",\n\t\t\tIsTemplate: false,\n\t\t}, nil)\n\t\tth.Store.EXPECT().GetFileInfo(gomock.Any()).Return(nil, nil)\n\t\tth.Store.EXPECT().SaveFileInfo(gomock.Any()).Return(nil)\n\n\t\tmockedFileBackend := &mocks.FileBackend{}\n\t\tth.App.filesBackend = mockedFileBackend\n\t\tmockedFileBackend.On(\"CopyFile\", mock.Anything, mock.Anything).Return(nil)\n\n\t\tupdatedFileNames, err := th.App.CopyCardFiles(\"boardID\", []*model.Block{imageBlock}, false)\n\t\tassert.NoError(t, err)\n\t\tassert.NotNil(t, imageBlock.Fields[\"fileId\"].(string))\n\t\tassert.NotNil(t, updatedFileNames[imageBlock.Fields[\"fileId\"].(string)])\n\t})\n}\n\nfunc TestCopyAndUpdateCardFiles(t *testing.T) {\n\tth, _ := SetupTestHelper(t)\n\timageBlock := &model.Block{\n\t\tID:         \"imageBlock\",\n\t\tParentID:   \"c3zqnh6fsu3f4mr6hzq9hizwske\",\n\t\tCreatedBy:  \"6k6ynxdp47dujjhhojw9nqhmyh\",\n\t\tModifiedBy: \"6k6ynxdp47dujjhhojw9nqhmyh\",\n\t\tSchema:     1,\n\t\tType:       \"image\",\n\t\tTitle:      \"\",\n\t\tFields:     map[string]interface{}{\"fileId\": \"7fileName.jpg\"},\n\t\tCreateAt:   1680725585250,\n\t\tUpdateAt:   1680725585250,\n\t\tDeleteAt:   0,\n\t\tBoardID:    \"boardID\",\n\t}\n\n\tt.Run(\"Board exists, image block, with FileInfo\", func(t *testing.T) {\n\t\tfileInfo := &mm_model.FileInfo{\n\t\t\tId:   \"imageBlock\",\n\t\t\tPath: testPath,\n\t\t}\n\t\tth.Store.EXPECT().GetBoard(\"boardID\").Return(&model.Board{\n\t\t\tID:         \"boardID\",\n\t\t\tIsTemplate: false,\n\t\t}, nil)\n\t\tth.Store.EXPECT().GetFileInfo(\"fileName\").Return(fileInfo, nil)\n\t\tth.Store.EXPECT().SaveFileInfo(fileInfo).Return(nil)\n\t\tth.Store.EXPECT().PatchBlocks(gomock.Any(), \"userID\").Return(nil)\n\n\t\tmockedFileBackend := &mocks.FileBackend{}\n\t\tth.App.filesBackend = mockedFileBackend\n\t\tmockedFileBackend.On(\"CopyFile\", mock.Anything, mock.Anything).Return(nil)\n\n\t\terr := th.App.CopyAndUpdateCardFiles(\"boardID\", \"userID\", []*model.Block{imageBlock}, false)\n\t\tassert.NoError(t, err)\n\n\t\tassert.NotEqual(t, testPath, imageBlock.Fields[\"fileId\"])\n\t})\n}\n"
  },
  {
    "path": "server/app/helper_test.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\npackage app\n\nimport (\n\t\"testing\"\n\n\t\"github.com/golang/mock/gomock\"\n\n\t\"github.com/mattermost/focalboard/server/auth\"\n\t\"github.com/mattermost/focalboard/server/services/config\"\n\t\"github.com/mattermost/focalboard/server/services/metrics\"\n\t\"github.com/mattermost/focalboard/server/services/permissions/mmpermissions\"\n\tmmpermissionsMocks \"github.com/mattermost/focalboard/server/services/permissions/mmpermissions/mocks\"\n\tpermissionsMocks \"github.com/mattermost/focalboard/server/services/permissions/mocks\"\n\t\"github.com/mattermost/focalboard/server/services/store/mockstore\"\n\t\"github.com/mattermost/focalboard/server/services/webhook\"\n\t\"github.com/mattermost/focalboard/server/ws\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n\t\"github.com/mattermost/mattermost/server/v8/platform/shared/filestore/mocks\"\n)\n\ntype TestHelper struct {\n\tApp          *App\n\tStore        *mockstore.MockStore\n\tFilesBackend *mocks.FileBackend\n\tlogger       mlog.LoggerIFace\n\tAPI          *mmpermissionsMocks.MockAPI\n}\n\nfunc SetupTestHelper(t *testing.T) (*TestHelper, func()) {\n\tctrl := gomock.NewController(t)\n\tcfg := config.Configuration{}\n\tstore := mockstore.NewMockStore(ctrl)\n\tfilesBackend := &mocks.FileBackend{}\n\tauth := auth.New(&cfg, store, nil)\n\tlogger, _ := mlog.NewLogger()\n\tsessionToken := \"TESTTOKEN\"\n\twsserver := ws.NewServer(auth, sessionToken, false, logger, store)\n\twebhook := webhook.NewClient(&cfg, logger)\n\tmetricsService := metrics.NewMetrics(metrics.InstanceInfo{})\n\n\tmockStore := permissionsMocks.NewMockStore(ctrl)\n\tmockAPI := mmpermissionsMocks.NewMockAPI(ctrl)\n\tpermissions := mmpermissions.New(mockStore, mockAPI, mlog.CreateConsoleTestLogger(t))\n\n\tappServices := Services{\n\t\tAuth:             auth,\n\t\tStore:            store,\n\t\tFilesBackend:     filesBackend,\n\t\tWebhook:          webhook,\n\t\tMetrics:          metricsService,\n\t\tLogger:           logger,\n\t\tSkipTemplateInit: true,\n\t\tPermissions:      permissions,\n\t}\n\tapp2 := New(&cfg, wsserver, appServices)\n\n\ttearDown := func() {\n\t\tapp2.Shutdown()\n\t\tif logger != nil {\n\t\t\t_ = logger.Shutdown()\n\t\t}\n\t}\n\n\treturn &TestHelper{\n\t\tApp:          app2,\n\t\tStore:        store,\n\t\tFilesBackend: filesBackend,\n\t\tlogger:       logger,\n\t\tAPI:          mockAPI,\n\t}, tearDown\n}\n"
  },
  {
    "path": "server/app/import.go",
    "content": "package app\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"path\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/krolaw/zipstream\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nconst (\n\tarchiveVersion    = 2\n\tlegacyFileBegin   = \"{\\\"version\\\":1\"\n\timportMaxFileSize = 1024 * 1024 * 70\n)\n\nvar (\n\terrBlockIsNotABoard  = errors.New(\"block is not a board\")\n\terrSizeLimitExceeded = errors.New(\"size limit exceeded\")\n)\n\n// ImportArchive imports an archive containing zero or more boards, plus all\n// associated content, including cards, content blocks, views, and images.\n//\n// Archives are ZIP files containing a `version.json` file and zero or more\n// directories, each containing a `board.jsonl` and zero or more image files.\nfunc (a *App) ImportArchive(r io.Reader, opt model.ImportArchiveOptions) error {\n\t// peek at the first bytes to see if this is a legacy archive format\n\tbr := bufio.NewReader(r)\n\tpeek, err := br.Peek(len(legacyFileBegin))\n\tif err == nil && string(peek) == legacyFileBegin {\n\t\ta.logger.Debug(\"importing legacy archive\")\n\t\t_, errImport := a.ImportBoardJSONL(br, opt)\n\n\t\treturn errImport\n\t}\n\n\tzr := zipstream.NewReader(br)\n\n\tboardMap := make(map[string]*model.Board) // maps old board ids to new\n\tfileMap := make(map[string]string)        // maps old fileIds to new\n\n\tfor {\n\t\thdr, err := zr.Next()\n\t\tif err != nil {\n\t\t\tif errors.Is(err, io.EOF) {\n\t\t\t\ta.fixImagesAttachments(boardMap, fileMap, opt.TeamID, opt.ModifiedBy)\n\t\t\t\ta.logger.Debug(\"import archive - done\", mlog.Int(\"boards_imported\", len(boardMap)))\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn err\n\t\t}\n\n\t\tdir, filename := filepath.Split(hdr.Name)\n\t\tdir = path.Clean(dir)\n\n\t\tswitch filename {\n\t\tcase \"version.json\":\n\t\t\tver, errVer := parseVersionFile(zr)\n\t\t\tif errVer != nil {\n\t\t\t\treturn errVer\n\t\t\t}\n\t\t\tif ver != archiveVersion {\n\t\t\t\treturn model.NewErrUnsupportedArchiveVersion(ver, archiveVersion)\n\t\t\t}\n\t\tcase \"board.jsonl\":\n\t\t\tboard, err := a.ImportBoardJSONL(zr, opt)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"cannot import board %s: %w\", dir, err)\n\t\t\t}\n\t\t\tboardMap[dir] = board\n\t\tdefault:\n\t\t\t// import file/image;  dir is the old board id\n\t\t\tboard, ok := boardMap[dir]\n\t\t\tif !ok {\n\t\t\t\ta.logger.Warn(\"skipping orphan image in archive\",\n\t\t\t\t\tmlog.String(\"dir\", dir),\n\t\t\t\t\tmlog.String(\"filename\", filename),\n\t\t\t\t)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tnewFileName, err := a.SaveFile(zr, opt.TeamID, board.ID, filename, board.IsTemplate)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"cannot import file %s for board %s: %w\", filename, dir, err)\n\t\t\t}\n\t\t\tfileMap[filename] = newFileName\n\n\t\t\ta.logger.Debug(\"import archive file\",\n\t\t\t\tmlog.String(\"TeamID\", opt.TeamID),\n\t\t\t\tmlog.String(\"boardID\", board.ID),\n\t\t\t\tmlog.String(\"filename\", filename),\n\t\t\t\tmlog.String(\"newFileName\", newFileName),\n\t\t\t)\n\t\t}\n\t}\n}\n\n// Update image and attachment blocks.\nfunc (a *App) fixImagesAttachments(boardMap map[string]*model.Board, fileMap map[string]string, teamID string, userID string) {\n\tblockIDs := make([]string, 0)\n\tblockPatches := make([]model.BlockPatch, 0)\n\tfor _, board := range boardMap {\n\t\tif board.IsTemplate {\n\t\t\tcontinue\n\t\t}\n\n\t\topts := model.QueryBlocksOptions{\n\t\t\tBoardID: board.ID,\n\t\t}\n\t\tnewBlocks, err := a.store.GetBlocks(opts)\n\t\tif err != nil {\n\t\t\ta.logger.Info(\"cannot retrieve imported blocks for board\", mlog.String(\"BoardID\", board.ID), mlog.Err(err))\n\t\t\treturn\n\t\t}\n\n\t\tfor _, block := range newBlocks {\n\t\t\tif block.Type == \"image\" || block.Type == \"attachment\" {\n\t\t\t\tfieldName := \"fileId\"\n\t\t\t\toldID := block.Fields[fieldName]\n\t\t\t\tblockIDs = append(blockIDs, block.ID)\n\n\t\t\t\tblockPatches = append(blockPatches, model.BlockPatch{\n\t\t\t\t\tUpdatedFields: map[string]interface{}{\n\t\t\t\t\t\tfieldName: fileMap[oldID.(string)],\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\tblockPatchBatch := model.BlockPatchBatch{BlockIDs: blockIDs, BlockPatches: blockPatches}\n\t\terr = a.PatchBlocks(teamID, &blockPatchBatch, userID)\n\t\tif err != nil {\n\t\t\ta.logger.Info(\"Error patching blocks for image import\", mlog.Err(err))\n\t\t}\n\t}\n}\n\n// ImportBoardJSONL imports a JSONL file containing blocks for one board. The resulting\n// board id is returned.\nfunc (a *App) ImportBoardJSONL(r io.Reader, opt model.ImportArchiveOptions) (*model.Board, error) {\n\t// TODO: Stream this once `model.GenerateBlockIDs` can take a stream of blocks.\n\t//       We don't want to load the whole file in memory, even though it's a single board.\n\tboardsAndBlocks := &model.BoardsAndBlocks{\n\t\tBlocks: make([]*model.Block, 0, 10),\n\t\tBoards: make([]*model.Board, 0, 10),\n\t}\n\tlineReader := &io.LimitedReader{R: r, N: importMaxFileSize + 1}\n\tscanner := bufio.NewScanner(lineReader)\n\n\tuserID := opt.ModifiedBy\n\tif userID == model.SingleUser {\n\t\tuserID = \"\"\n\t}\n\tnow := utils.GetMillis()\n\tvar boardID string\n\tvar boardMembers []*model.BoardMember\n\n\tlineNum := 1\n\tfirstLine := true\n\tfor scanner.Scan() {\n\t\tif lineReader.N <= 0 {\n\t\t\treturn nil, fmt.Errorf(\"error parsing archive line %d: %w\", lineNum, errSizeLimitExceeded)\n\t\t}\n\n\t\tline := bytes.TrimSpace(scanner.Bytes())\n\t\tif len(line) != 0 {\n\t\t\tvar skip bool\n\t\t\tif firstLine {\n\t\t\t\t// first line might be a header tag (old archive format)\n\t\t\t\tif strings.HasPrefix(string(line), legacyFileBegin) {\n\t\t\t\t\tskip = true\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif !skip {\n\t\t\t\tvar archiveLine model.ArchiveLine\n\t\t\t\tif err := json.Unmarshal(line, &archiveLine); err != nil {\n\t\t\t\t\treturn nil, fmt.Errorf(\"error parsing archive line %d: %w\", lineNum, err)\n\t\t\t\t}\n\n\t\t\t\t// first line must be a board\n\t\t\t\tif firstLine && archiveLine.Type == \"block\" {\n\t\t\t\t\tarchiveLine.Type = \"board_block\"\n\t\t\t\t}\n\n\t\t\t\tswitch archiveLine.Type {\n\t\t\t\tcase \"board\":\n\t\t\t\t\tvar board model.Board\n\t\t\t\t\tif err2 := json.Unmarshal(archiveLine.Data, &board); err2 != nil {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"invalid board in archive line %d: %w\", lineNum, err2)\n\t\t\t\t\t}\n\t\t\t\t\tboard.ModifiedBy = userID\n\t\t\t\t\tboard.UpdateAt = now\n\t\t\t\t\tboard.TeamID = opt.TeamID\n\t\t\t\t\tboardsAndBlocks.Boards = append(boardsAndBlocks.Boards, &board)\n\t\t\t\t\tboardID = board.ID\n\t\t\t\tcase \"board_block\":\n\t\t\t\t\t// legacy archives encoded boards as blocks; we need to convert them to real boards.\n\t\t\t\t\tvar block *model.Block\n\t\t\t\t\tif err2 := json.Unmarshal(archiveLine.Data, &block); err2 != nil {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"invalid board block in archive line %d: %w\", lineNum, err2)\n\t\t\t\t\t}\n\t\t\t\t\tblock.ModifiedBy = userID\n\t\t\t\t\tblock.UpdateAt = now\n\t\t\t\t\tboard, err := a.blockToBoard(block, opt)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"cannot convert archive line %d to block: %w\", lineNum, err)\n\t\t\t\t\t}\n\t\t\t\t\tboardsAndBlocks.Boards = append(boardsAndBlocks.Boards, board)\n\t\t\t\t\tboardID = board.ID\n\t\t\t\tcase \"block\":\n\t\t\t\t\tvar block *model.Block\n\t\t\t\t\tif err2 := json.Unmarshal(archiveLine.Data, &block); err2 != nil {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"invalid block in archive line %d: %w\", lineNum, err2)\n\t\t\t\t\t}\n\t\t\t\t\tblock.ModifiedBy = userID\n\t\t\t\t\tblock.UpdateAt = now\n\t\t\t\t\tblock.BoardID = boardID\n\t\t\t\t\tboardsAndBlocks.Blocks = append(boardsAndBlocks.Blocks, block)\n\t\t\t\tcase \"boardMember\":\n\t\t\t\t\tvar boardMember *model.BoardMember\n\t\t\t\t\tif err2 := json.Unmarshal(archiveLine.Data, &boardMember); err2 != nil {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"invalid board Member in archive line %d: %w\", lineNum, err2)\n\t\t\t\t\t}\n\t\t\t\t\tboardMembers = append(boardMembers, boardMember)\n\t\t\t\tdefault:\n\t\t\t\t\treturn nil, model.NewErrUnsupportedArchiveLineType(lineNum, archiveLine.Type)\n\t\t\t\t}\n\t\t\t\tfirstLine = false\n\t\t\t}\n\t\t}\n\t}\n\n\tif errRead := scanner.Err(); errRead != nil {\n\t\treturn nil, fmt.Errorf(\"error reading archive line %d: %w\", lineNum, errRead)\n\t}\n\n\t// loop to remove the people how are not part of the team and system\n\tfor i := len(boardMembers) - 1; i >= 0; i-- {\n\t\tif _, err := a.GetUser(boardMembers[i].UserID); err != nil {\n\t\t\tboardMembers = append(boardMembers[:i], boardMembers[i+1:]...)\n\t\t}\n\t}\n\n\ta.fixBoardsandBlocks(boardsAndBlocks, opt)\n\n\tvar err error\n\tboardsAndBlocks, err = model.GenerateBoardsAndBlocksIDs(boardsAndBlocks, a.logger)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error generating archive block IDs: %w\", err)\n\t}\n\n\tboardsAndBlocks, err = a.CreateBoardsAndBlocks(boardsAndBlocks, opt.ModifiedBy, false)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error inserting archive blocks: %w\", err)\n\t}\n\n\t// add users to all the new boards (if not the fake system user).\n\tfor _, board := range boardsAndBlocks.Boards {\n\t\t// make sure an admin user gets added\n\t\tadminMember := &model.BoardMember{\n\t\t\tBoardID:     board.ID,\n\t\t\tUserID:      opt.ModifiedBy,\n\t\t\tSchemeAdmin: true,\n\t\t}\n\t\tif _, err2 := a.AddMemberToBoard(adminMember); err2 != nil {\n\t\t\treturn nil, fmt.Errorf(\"cannot add adminMember to board: %w\", err2)\n\t\t}\n\t\tfor _, boardMember := range boardMembers {\n\t\t\tbm := &model.BoardMember{\n\t\t\t\tBoardID:         board.ID,\n\t\t\t\tUserID:          boardMember.UserID,\n\t\t\t\tRoles:           boardMember.Roles,\n\t\t\t\tMinimumRole:     boardMember.MinimumRole,\n\t\t\t\tSchemeAdmin:     boardMember.SchemeAdmin,\n\t\t\t\tSchemeEditor:    boardMember.SchemeEditor,\n\t\t\t\tSchemeCommenter: boardMember.SchemeCommenter,\n\t\t\t\tSchemeViewer:    boardMember.SchemeViewer,\n\t\t\t\tSynthetic:       boardMember.Synthetic,\n\t\t\t}\n\t\t\tif _, err2 := a.AddMemberToBoard(bm); err2 != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"cannot add member to board: %w\", err2)\n\t\t\t}\n\t\t}\n\t}\n\n\t// find new board id\n\tfor _, board := range boardsAndBlocks.Boards {\n\t\treturn board, nil\n\t}\n\treturn nil, fmt.Errorf(\"missing board in archive: %w\", model.ErrInvalidBoardBlock)\n}\n\n// fixBoardsandBlocks allows the caller of `ImportArchive` to modify or filters boards and blocks being\n// imported via callbacks.\nfunc (a *App) fixBoardsandBlocks(boardsAndBlocks *model.BoardsAndBlocks, opt model.ImportArchiveOptions) {\n\tif opt.BlockModifier == nil && opt.BoardModifier == nil {\n\t\treturn\n\t}\n\n\tmodInfoCache := make(map[string]interface{})\n\tmodBoards := make([]*model.Board, 0, len(boardsAndBlocks.Boards))\n\tmodBlocks := make([]*model.Block, 0, len(boardsAndBlocks.Blocks))\n\n\tfor _, board := range boardsAndBlocks.Boards {\n\t\tb := *board\n\t\tif opt.BoardModifier != nil && !opt.BoardModifier(&b, modInfoCache) {\n\t\t\ta.logger.Debug(\"skipping insert board per board modifier\",\n\t\t\t\tmlog.String(\"boardID\", board.ID),\n\t\t\t)\n\t\t\tcontinue\n\t\t}\n\t\tmodBoards = append(modBoards, &b)\n\t}\n\n\tfor _, block := range boardsAndBlocks.Blocks {\n\t\tb := block\n\t\tif opt.BlockModifier != nil && !opt.BlockModifier(b, modInfoCache) {\n\t\t\ta.logger.Debug(\"skipping insert block per block modifier\",\n\t\t\t\tmlog.String(\"blockID\", block.ID),\n\t\t\t)\n\t\t\tcontinue\n\t\t}\n\t\tmodBlocks = append(modBlocks, b)\n\t}\n\n\tboardsAndBlocks.Boards = modBoards\n\tboardsAndBlocks.Blocks = modBlocks\n}\n\n// blockToBoard converts a `model.Block` to `model.Board`. Legacy archive formats encode boards as blocks\n// and need conversion during import.\nfunc (a *App) blockToBoard(block *model.Block, opt model.ImportArchiveOptions) (*model.Board, error) {\n\tif block.Type != model.TypeBoard {\n\t\treturn nil, errBlockIsNotABoard\n\t}\n\n\tboard := &model.Board{\n\t\tID:             block.ID,\n\t\tTeamID:         opt.TeamID,\n\t\tCreatedBy:      block.CreatedBy,\n\t\tModifiedBy:     block.ModifiedBy,\n\t\tType:           model.BoardTypePrivate,\n\t\tTitle:          block.Title,\n\t\tCreateAt:       block.CreateAt,\n\t\tUpdateAt:       block.UpdateAt,\n\t\tDeleteAt:       block.DeleteAt,\n\t\tProperties:     make(map[string]interface{}),\n\t\tCardProperties: make([]map[string]interface{}, 0),\n\t}\n\n\tif icon, ok := stringValue(block.Fields, \"icon\"); ok {\n\t\tboard.Icon = icon\n\t}\n\tif description, ok := stringValue(block.Fields, \"description\"); ok {\n\t\tboard.Description = description\n\t}\n\tif showDescription, ok := boolValue(block.Fields, \"showDescription\"); ok {\n\t\tboard.ShowDescription = showDescription\n\t}\n\tif isTemplate, ok := boolValue(block.Fields, \"isTemplate\"); ok {\n\t\tboard.IsTemplate = isTemplate\n\t}\n\tif templateVer, ok := intValue(block.Fields, \"templateVer\"); ok {\n\t\tboard.TemplateVersion = templateVer\n\t}\n\tif properties, ok := mapValue(block.Fields, \"properties\"); ok {\n\t\tboard.Properties = properties\n\t}\n\tif cardProperties, ok := arrayMapsValue(block.Fields, \"cardProperties\"); ok {\n\t\tboard.CardProperties = cardProperties\n\t}\n\treturn board, nil\n}\n\nfunc stringValue(m map[string]interface{}, key string) (string, bool) {\n\tv, ok := m[key]\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\ts, ok := v.(string)\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\treturn s, true\n}\n\nfunc boolValue(m map[string]interface{}, key string) (bool, bool) {\n\tv, ok := m[key]\n\tif !ok {\n\t\treturn false, false\n\t}\n\tb, ok := v.(bool)\n\tif !ok {\n\t\treturn false, false\n\t}\n\treturn b, true\n}\n\nfunc intValue(m map[string]interface{}, key string) (int, bool) {\n\tv, ok := m[key]\n\tif !ok {\n\t\treturn 0, false\n\t}\n\ti, ok := v.(int)\n\tif !ok {\n\t\treturn 0, false\n\t}\n\treturn i, true\n}\n\nfunc mapValue(m map[string]interface{}, key string) (map[string]interface{}, bool) {\n\tv, ok := m[key]\n\tif !ok {\n\t\treturn nil, false\n\t}\n\tmm, ok := v.(map[string]interface{})\n\tif !ok {\n\t\treturn nil, false\n\t}\n\treturn mm, true\n}\n\nfunc arrayMapsValue(m map[string]interface{}, key string) ([]map[string]interface{}, bool) {\n\tv, ok := m[key]\n\tif !ok {\n\t\treturn nil, false\n\t}\n\tai, ok := v.([]interface{})\n\tif !ok {\n\t\treturn nil, false\n\t}\n\n\tarr := make([]map[string]interface{}, 0, len(ai))\n\tfor _, mi := range ai {\n\t\tmm, ok := mi.(map[string]interface{})\n\t\tif !ok {\n\t\t\treturn nil, false\n\t\t}\n\t\tarr = append(arr, mm)\n\t}\n\treturn arr, true\n}\n\nfunc parseVersionFile(r io.Reader) (int, error) {\n\tfile, err := io.ReadAll(r)\n\tif err != nil {\n\t\treturn 0, fmt.Errorf(\"cannot read version.json: %w\", err)\n\t}\n\n\tvar header model.ArchiveHeader\n\tif err := json.Unmarshal(file, &header); err != nil {\n\t\treturn 0, fmt.Errorf(\"cannot parse version.json: %w\", err)\n\t}\n\treturn header.Version, nil\n}\n"
  },
  {
    "path": "server/app/import_test.go",
    "content": "package app\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/mattermost/focalboard/server/utils\"\n\n\t\"github.com/golang/mock/gomock\"\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestApp_ImportArchive(t *testing.T) {\n\tth, tearDown := SetupTestHelper(t)\n\tdefer tearDown()\n\n\tboard := &model.Board{\n\t\tID:     \"d14b9df9-1f31-4732-8a64-92bc7162cd28\",\n\t\tTeamID: \"test-team\",\n\t\tTitle:  \"Cross-Functional Project Plan\",\n\t}\n\n\tblock := &model.Block{\n\t\tID:       \"2c1873e0-1484-407d-8b2c-3c3b5a2a9f9e\",\n\t\tParentID: board.ID,\n\t\tType:     model.TypeView,\n\t\tBoardID:  board.ID,\n\t}\n\n\tbabs := &model.BoardsAndBlocks{\n\t\tBoards: []*model.Board{board},\n\t\tBlocks: []*model.Block{block},\n\t}\n\n\tboardMember := &model.BoardMember{\n\t\tBoardID: board.ID,\n\t\tUserID:  \"user\",\n\t}\n\n\tt.Run(\"import asana archive\", func(t *testing.T) {\n\t\tr := bytes.NewReader([]byte(asana))\n\t\topts := model.ImportArchiveOptions{\n\t\t\tTeamID:     \"test-team\",\n\t\t\tModifiedBy: \"user\",\n\t\t}\n\n\t\tth.Store.EXPECT().CreateBoardsAndBlocks(gomock.AssignableToTypeOf(&model.BoardsAndBlocks{}), \"user\").Return(babs, nil)\n\t\tth.Store.EXPECT().GetMembersForBoard(board.ID).AnyTimes().Return([]*model.BoardMember{boardMember}, nil)\n\t\tth.Store.EXPECT().GetBoard(board.ID).Return(board, nil)\n\t\tth.Store.EXPECT().GetMemberForBoard(board.ID, \"user\").Return(boardMember, nil)\n\t\tth.Store.EXPECT().GetUserCategoryBoards(\"user\", \"test-team\").Return([]model.CategoryBoards{\n\t\t\t{\n\t\t\t\tCategory: model.Category{\n\t\t\t\t\tType: \"default\",\n\t\t\t\t\tName: \"Boards\",\n\t\t\t\t\tID:   \"boards_category_id\",\n\t\t\t\t},\n\t\t\t},\n\t\t}, nil)\n\t\tth.Store.EXPECT().GetUserCategoryBoards(\"user\", \"test-team\")\n\t\tth.Store.EXPECT().CreateCategory(utils.Anything).Return(nil)\n\t\tth.Store.EXPECT().GetCategory(utils.Anything).Return(&model.Category{\n\t\t\tID:   \"boards_category_id\",\n\t\t\tName: \"Boards\",\n\t\t}, nil)\n\t\tth.Store.EXPECT().GetBoardsForUserAndTeam(\"user\", \"test-team\", false).Return([]*model.Board{}, nil)\n\t\tth.Store.EXPECT().GetMembersForUser(\"user\").Return([]*model.BoardMember{}, nil)\n\t\tth.Store.EXPECT().AddUpdateCategoryBoard(\"user\", utils.Anything, utils.Anything).Return(nil)\n\n\t\terr := th.App.ImportArchive(r, opts)\n\t\trequire.NoError(t, err, \"import archive should not fail\")\n\t})\n\n\tt.Run(\"import board archive\", func(t *testing.T) {\n\t\tr := bytes.NewReader([]byte(boardArchive))\n\t\topts := model.ImportArchiveOptions{\n\t\t\tTeamID:     \"test-team\",\n\t\t\tModifiedBy: \"f1tydgc697fcbp8ampr6881jea\",\n\t\t}\n\n\t\tbm1 := &model.BoardMember{\n\t\t\tBoardID: board.ID,\n\t\t\tUserID:  \"f1tydgc697fcbp8ampr6881jea\",\n\t\t}\n\n\t\tbm2 := &model.BoardMember{\n\t\t\tBoardID: board.ID,\n\t\t\tUserID:  \"hxxzooc3ff8cubsgtcmpn8733e\",\n\t\t}\n\n\t\tbm3 := &model.BoardMember{\n\t\t\tBoardID: board.ID,\n\t\t\tUserID:  \"nto73edn5ir6ifimo5a53y1dwa\",\n\t\t}\n\n\t\tuser1 := &model.User{\n\t\t\tID: \"f1tydgc697fcbp8ampr6881jea\",\n\t\t}\n\n\t\tuser2 := &model.User{\n\t\t\tID: \"hxxzooc3ff8cubsgtcmpn8733e\",\n\t\t}\n\n\t\tuser3 := &model.User{\n\t\t\tID: \"nto73edn5ir6ifimo5a53y1dwa\",\n\t\t}\n\n\t\tth.Store.EXPECT().CreateBoardsAndBlocks(gomock.AssignableToTypeOf(&model.BoardsAndBlocks{}), \"f1tydgc697fcbp8ampr6881jea\").Return(babs, nil)\n\t\tth.Store.EXPECT().GetMembersForBoard(board.ID).AnyTimes().Return([]*model.BoardMember{bm1, bm2, bm3}, nil)\n\t\tth.Store.EXPECT().GetUserCategoryBoards(\"f1tydgc697fcbp8ampr6881jea\", \"test-team\").Return([]model.CategoryBoards{}, nil)\n\t\tth.Store.EXPECT().GetUserCategoryBoards(\"f1tydgc697fcbp8ampr6881jea\", \"test-team\").Return([]model.CategoryBoards{\n\t\t\t{\n\t\t\t\tCategory: model.Category{\n\t\t\t\t\tID:   \"boards_category_id\",\n\t\t\t\t\tName: \"Boards\",\n\t\t\t\t\tType: model.CategoryTypeSystem,\n\t\t\t\t},\n\t\t\t},\n\t\t}, nil)\n\t\tth.Store.EXPECT().CreateCategory(utils.Anything).Return(nil)\n\t\tth.Store.EXPECT().GetCategory(utils.Anything).Return(&model.Category{\n\t\t\tID:   \"boards_category_id\",\n\t\t\tName: \"Boards\",\n\t\t}, nil)\n\t\tth.Store.EXPECT().GetMembersForUser(\"f1tydgc697fcbp8ampr6881jea\").Return([]*model.BoardMember{}, nil)\n\t\tth.Store.EXPECT().GetBoardsForUserAndTeam(\"f1tydgc697fcbp8ampr6881jea\", \"test-team\", false).Return([]*model.Board{}, nil)\n\t\tth.Store.EXPECT().AddUpdateCategoryBoard(\"f1tydgc697fcbp8ampr6881jea\", utils.Anything, utils.Anything).Return(nil)\n\t\tth.Store.EXPECT().GetBoard(board.ID).AnyTimes().Return(board, nil)\n\t\tth.Store.EXPECT().GetMemberForBoard(board.ID, \"f1tydgc697fcbp8ampr6881jea\").AnyTimes().Return(bm1, nil)\n\t\tth.Store.EXPECT().GetMemberForBoard(board.ID, \"hxxzooc3ff8cubsgtcmpn8733e\").AnyTimes().Return(bm2, nil)\n\t\tth.Store.EXPECT().GetMemberForBoard(board.ID, \"nto73edn5ir6ifimo5a53y1dwa\").AnyTimes().Return(bm3, nil)\n\t\tth.Store.EXPECT().GetUserByID(\"f1tydgc697fcbp8ampr6881jea\").AnyTimes().Return(user1, nil)\n\t\tth.Store.EXPECT().GetUserByID(\"hxxzooc3ff8cubsgtcmpn8733e\").AnyTimes().Return(user2, nil)\n\t\tth.Store.EXPECT().GetUserByID(\"nto73edn5ir6ifimo5a53y1dwa\").AnyTimes().Return(user3, nil)\n\n\t\tnewBoard, err := th.App.ImportBoardJSONL(r, opts)\n\t\trequire.NoError(t, err, \"import archive should not fail\")\n\t\trequire.Equal(t, board.ID, newBoard.ID, \"Board ID should be same\")\n\t})\n\n\tt.Run(\"fix image and attachment\", func(t *testing.T) {\n\t\tboardMap := map[string]*model.Board{\n\t\t\t\"test\": board,\n\t\t}\n\n\t\tfileMap := map[string]string{\n\t\t\t\"oldFileName1.jpg\": \"newFileName1.jpg\",\n\t\t\t\"oldFileName2.jpg\": \"newFileName2.jpg\",\n\t\t}\n\n\t\timageBlock := &model.Block{\n\t\t\tID:         \"blockID-1\",\n\t\t\tParentID:   \"c3zqnh6fsu3f4mr6hzq9hizwske\",\n\t\t\tCreatedBy:  \"6k6ynxdp47dujjhhojw9nqhmyh\",\n\t\t\tModifiedBy: \"6k6ynxdp47dujjhhojw9nqhmyh\",\n\t\t\tSchema:     1,\n\t\t\tType:       \"image\",\n\t\t\tTitle:      \"\",\n\t\t\tFields:     map[string]interface{}{\"fileId\": \"oldFileName1.jpg\"},\n\t\t\tCreateAt:   1680725585250,\n\t\t\tUpdateAt:   1680725585250,\n\t\t\tDeleteAt:   0,\n\t\t\tBoardID:    \"board-id\",\n\t\t}\n\n\t\tattachmentBlock := &model.Block{\n\t\t\tID:         \"blockID-2\",\n\t\t\tParentID:   \"c3zqnh6fsu3f4mr6hzq9hizwske\",\n\t\t\tCreatedBy:  \"6k6ynxdp47dujjhhojw9nqhmyh\",\n\t\t\tModifiedBy: \"6k6ynxdp47dujjhhojw9nqhmyh\",\n\t\t\tSchema:     1,\n\t\t\tType:       \"attachment\",\n\t\t\tTitle:      \"\",\n\t\t\tFields:     map[string]interface{}{\"fileId\": \"oldFileName2.jpg\"},\n\t\t\tCreateAt:   1680725585250,\n\t\t\tUpdateAt:   1680725585250,\n\t\t\tDeleteAt:   0,\n\t\t\tBoardID:    \"board-id\",\n\t\t}\n\n\t\tblockIDs := []string{\"blockID-1\", \"blockID-2\"}\n\n\t\tblockPatch := model.BlockPatch{\n\t\t\tUpdatedFields: map[string]interface{}{\"fileId\": \"newFileName1.jpg\"},\n\t\t}\n\n\t\tblockPatch2 := model.BlockPatch{\n\t\t\tUpdatedFields: map[string]interface{}{\"fileId\": \"newFileName2.jpg\"},\n\t\t}\n\n\t\tblockPatches := []model.BlockPatch{blockPatch, blockPatch2}\n\n\t\tblockPatchesBatch := model.BlockPatchBatch{BlockIDs: blockIDs, BlockPatches: blockPatches}\n\n\t\topts := model.QueryBlocksOptions{\n\t\t\tBoardID: board.ID,\n\t\t}\n\t\tth.Store.EXPECT().GetBlocks(opts).Return([]*model.Block{imageBlock, attachmentBlock}, nil)\n\t\tth.Store.EXPECT().GetBlocksByIDs(blockIDs).Return([]*model.Block{imageBlock, attachmentBlock}, nil)\n\t\tth.Store.EXPECT().GetBlock(blockIDs[0]).Return(imageBlock, nil)\n\t\tth.Store.EXPECT().GetBlock(blockIDs[1]).Return(attachmentBlock, nil)\n\t\tth.Store.EXPECT().GetMembersForBoard(\"board-id\").AnyTimes().Return([]*model.BoardMember{}, nil)\n\n\t\tth.Store.EXPECT().PatchBlocks(&blockPatchesBatch, \"my-userid\")\n\t\tth.App.fixImagesAttachments(boardMap, fileMap, \"test-team\", \"my-userid\")\n\t})\n}\n\n//nolint:lll\nconst asana = `{\"version\":1,\"date\":1614714686842}\n{\"type\":\"block\",\"data\":{\"id\":\"d14b9df9-1f31-4732-8a64-92bc7162cd28\",\"fields\":{\"icon\":\"\",\"description\":\"\",\"cardProperties\":[{\"id\":\"3bdcbaeb-bc78-4884-8531-a0323b74676a\",\"name\":\"Section\",\"type\":\"select\",\"options\":[{\"id\":\"d8d94ef1-5e74-40bb-8be5-fc0eb3f47732\",\"value\":\"Planning\",\"color\":\"propColorGray\"},{\"id\":\"454559bb-b788-4ff6-873e-04def8491d2c\",\"value\":\"Milestones\",\"color\":\"propColorBrown\"},{\"id\":\"deaab476-c690-48df-828f-725b064dc476\",\"value\":\"Next steps\",\"color\":\"propColorOrange\"},{\"id\":\"2138305a-3157-461c-8bbe-f19ebb55846d\",\"value\":\"Comms Plan\",\"color\":\"propColorYellow\"}]}]},\"createAt\":1614714686836,\"updateAt\":1614714686836,\"deleteAt\":0,\"schema\":1,\"parentId\":\"\",\"rootId\":\"d14b9df9-1f31-4732-8a64-92bc7162cd28\",\"modifiedBy\":\"\",\"type\":\"board\",\"title\":\"Cross-Functional Project Plan\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"2c1873e0-1484-407d-8b2c-3c3b5a2a9f9e\",\"fields\":{\"sortOptions\":[],\"visiblePropertyIds\":[],\"visibleOptionIds\":[],\"hiddenOptionIds\":[],\"filter\":{\"operation\":\"and\",\"filters\":[]},\"cardOrder\":[],\"columnWidths\":{},\"viewType\":\"board\"},\"createAt\":1614714686840,\"updateAt\":1614714686840,\"deleteAt\":0,\"schema\":1,\"parentId\":\"d14b9df9-1f31-4732-8a64-92bc7162cd28\",\"rootId\":\"d14b9df9-1f31-4732-8a64-92bc7162cd28\",\"modifiedBy\":\"\",\"type\":\"view\",\"title\":\"Board View\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"520c332b-adf5-4a32-88ab-43655c8b6aa2\",\"fields\":{\"icon\":\"\",\"properties\":{\"3bdcbaeb-bc78-4884-8531-a0323b74676a\":\"d8d94ef1-5e74-40bb-8be5-fc0eb3f47732\"},\"contentOrder\":[\"deb3966c-6d56-43b1-8e95-36806877ce81\"]},\"createAt\":1614714686841,\"updateAt\":1614714686841,\"deleteAt\":0,\"schema\":1,\"parentId\":\"d14b9df9-1f31-4732-8a64-92bc7162cd28\",\"rootId\":\"d14b9df9-1f31-4732-8a64-92bc7162cd28\",\"modifiedBy\":\"\",\"type\":\"card\",\"title\":\"[READ ME] - Instructions for using this template\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"deb3966c-6d56-43b1-8e95-36806877ce81\",\"fields\":{},\"createAt\":1614714686841,\"updateAt\":1614714686841,\"deleteAt\":0,\"schema\":1,\"parentId\":\"520c332b-adf5-4a32-88ab-43655c8b6aa2\",\"rootId\":\"d14b9df9-1f31-4732-8a64-92bc7162cd28\",\"modifiedBy\":\"\",\"type\":\"text\",\"title\":\"This project template is set up in List View with sections and Asana-created Custom Fields to help you track your team's work. We've provided some example content in this template to get you started, but you should add tasks, change task names, add more Custom Fields, and change any other info to make this project your own.\\n\\nSend feedback about this template: https://asa.na/templatesfeedback\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"be791f66-a5e5-4408-82f6-cb1280f5bc45\",\"fields\":{\"icon\":\"\",\"properties\":{\"3bdcbaeb-bc78-4884-8531-a0323b74676a\":\"d8d94ef1-5e74-40bb-8be5-fc0eb3f47732\"},\"contentOrder\":[\"2688b31f-e7ff-4de1-87ae-d4b5570f8712\"]},\"createAt\":1614714686841,\"updateAt\":1614714686841,\"deleteAt\":0,\"schema\":1,\"parentId\":\"d14b9df9-1f31-4732-8a64-92bc7162cd28\",\"rootId\":\"d14b9df9-1f31-4732-8a64-92bc7162cd28\",\"modifiedBy\":\"\",\"type\":\"card\",\"title\":\"Redesign the landing page of our website\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"2688b31f-e7ff-4de1-87ae-d4b5570f8712\",\"fields\":{},\"createAt\":1614714686841,\"updateAt\":1614714686841,\"deleteAt\":0,\"schema\":1,\"parentId\":\"be791f66-a5e5-4408-82f6-cb1280f5bc45\",\"rootId\":\"d14b9df9-1f31-4732-8a64-92bc7162cd28\",\"modifiedBy\":\"\",\"type\":\"text\",\"title\":\"Redesign the landing page to focus on the main persona.\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"98f74948-1700-4a3c-8cc2-8bb632499def\",\"fields\":{\"icon\":\"\",\"properties\":{\"3bdcbaeb-bc78-4884-8531-a0323b74676a\":\"454559bb-b788-4ff6-873e-04def8491d2c\"},\"contentOrder\":[]},\"createAt\":1614714686841,\"updateAt\":1614714686841,\"deleteAt\":0,\"schema\":1,\"parentId\":\"d14b9df9-1f31-4732-8a64-92bc7162cd28\",\"rootId\":\"d14b9df9-1f31-4732-8a64-92bc7162cd28\",\"modifiedBy\":\"\",\"type\":\"card\",\"title\":\"[EXAMPLE TASK] Consider trying a new email marketing service\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"142fba5d-05e6-4865-83d9-b3f54d9de96e\",\"fields\":{\"icon\":\"\",\"properties\":{\"3bdcbaeb-bc78-4884-8531-a0323b74676a\":\"454559bb-b788-4ff6-873e-04def8491d2c\"},\"contentOrder\":[]},\"createAt\":1614714686841,\"updateAt\":1614714686841,\"deleteAt\":0,\"schema\":1,\"parentId\":\"d14b9df9-1f31-4732-8a64-92bc7162cd28\",\"rootId\":\"d14b9df9-1f31-4732-8a64-92bc7162cd28\",\"modifiedBy\":\"\",\"type\":\"card\",\"title\":\"[EXAMPLE TASK] Budget finalization\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"ca6670b1-b034-4e42-8971-c659b478b9e0\",\"fields\":{\"icon\":\"\",\"properties\":{\"3bdcbaeb-bc78-4884-8531-a0323b74676a\":\"deaab476-c690-48df-828f-725b064dc476\"},\"contentOrder\":[]},\"createAt\":1614714686841,\"updateAt\":1614714686841,\"deleteAt\":0,\"schema\":1,\"parentId\":\"d14b9df9-1f31-4732-8a64-92bc7162cd28\",\"rootId\":\"d14b9df9-1f31-4732-8a64-92bc7162cd28\",\"modifiedBy\":\"\",\"type\":\"card\",\"title\":\"[EXAMPLE TASK] Find a venue for the holiday party\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"db1dd596-0999-4741-8b05-72ca8e438e31\",\"fields\":{\"icon\":\"\",\"properties\":{\"3bdcbaeb-bc78-4884-8531-a0323b74676a\":\"deaab476-c690-48df-828f-725b064dc476\"},\"contentOrder\":[]},\"createAt\":1614714686841,\"updateAt\":1614714686841,\"deleteAt\":0,\"schema\":1,\"parentId\":\"d14b9df9-1f31-4732-8a64-92bc7162cd28\",\"rootId\":\"d14b9df9-1f31-4732-8a64-92bc7162cd28\",\"modifiedBy\":\"\",\"type\":\"card\",\"title\":\"[EXAMPLE TASK] Approve campaign copy\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"16861c05-f31f-46af-8429-80a87b5aa93a\",\"fields\":{\"icon\":\"\",\"properties\":{\"3bdcbaeb-bc78-4884-8531-a0323b74676a\":\"2138305a-3157-461c-8bbe-f19ebb55846d\"},\"contentOrder\":[]},\"createAt\":1614714686841,\"updateAt\":1614714686841,\"deleteAt\":0,\"schema\":1,\"parentId\":\"d14b9df9-1f31-4732-8a64-92bc7162cd28\",\"rootId\":\"d14b9df9-1f31-4732-8a64-92bc7162cd28\",\"modifiedBy\":\"\",\"type\":\"card\",\"title\":\"[EXAMPLE TASK] Send out updated attendee list\"}}\n`\n\n//nolint:lll\nconst boardArchive = `{\"type\":\"board\",\"data\":{\"id\":\"bfoi6yy6pa3yzika53spj7pq9ee\",\"teamId\":\"wsmqbtwb5jb35jb3mtp85c8a9h\",\"channelId\":\"\",\"createdBy\":\"nto73edn5ir6ifimo5a53y1dwa\",\"modifiedBy\":\"nto73edn5ir6ifimo5a53y1dwa\",\"type\":\"P\",\"minimumRole\":\"\",\"title\":\"Custom\",\"description\":\"\",\"icon\":\"\",\"showDescription\":false,\"isTemplate\":false,\"templateVersion\":0,\"properties\":{},\"cardProperties\":[{\"id\":\"aonihehbifijmx56aqzu3cc7w1r\",\"name\":\"Status\",\"options\":[],\"type\":\"select\"},{\"id\":\"aohjkzt769rxhtcz1o9xcoce5to\",\"name\":\"Person\",\"options\":[],\"type\":\"person\"}],\"createAt\":1672750481591,\"updateAt\":1672750481591,\"deleteAt\":0}}\n{\"type\":\"block\",\"data\":{\"id\":\"ckpc3b1dp3pbw7bqntfryy9jbzo\",\"parentId\":\"bjaqxtbyqz3bu7pgyddpgpms74a\",\"createdBy\":\"nto73edn5ir6ifimo5a53y1dwa\",\"modifiedBy\":\"nto73edn5ir6ifimo5a53y1dwa\",\"schema\":1,\"type\":\"card\",\"title\":\"Test\",\"fields\":{\"contentOrder\":[],\"icon\":\"\",\"isTemplate\":false,\"properties\":{\"aohjkzt769rxhtcz1o9xcoce5to\":\"hxxzooc3ff8cubsgtcmpn8733e\"}},\"createAt\":1672750481612,\"updateAt\":1672845003530,\"deleteAt\":0,\"boardId\":\"bfoi6yy6pa3yzika53spj7pq9ee\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"v7tdajwpm47r3u8duedk89bhxar\",\"parentId\":\"bpypang3a3errqstj1agx9kuqay\",\"createdBy\":\"nto73edn5ir6ifimo5a53y1dwa\",\"modifiedBy\":\"nto73edn5ir6ifimo5a53y1dwa\",\"schema\":1,\"type\":\"view\",\"title\":\"Board view\",\"fields\":{\"cardOrder\":[\"crsyw7tbr3pnjznok6ppngmmyya\",\"c5titiemp4pgaxbs4jksgybbj4y\"],\"collapsedOptionIds\":[],\"columnCalculations\":{},\"columnWidths\":{},\"defaultTemplateId\":\"\",\"filter\":{\"filters\":[],\"operation\":\"and\"},\"hiddenOptionIds\":[],\"kanbanCalculations\":{},\"sortOptions\":[],\"viewType\":\"board\",\"visibleOptionIds\":[],\"visiblePropertyIds\":[\"aohjkzt769rxhtcz1o9xcoce5to\"]},\"createAt\":1672750481626,\"updateAt\":1672750481626,\"deleteAt\":0,\"boardId\":\"bfoi6yy6pa3yzika53spj7pq9ee\"}}\n{\"type\":\"boardMember\",\"data\":{\"boardId\":\"bfoi6yy6pa3yzika53spj7pq9ee\",\"userId\":\"f1tydgc697fcbp8ampr6881jea\",\"roles\":\"\",\"minimumRole\":\"\",\"schemeAdmin\":false,\"schemeEditor\":false,\"schemeCommenter\":false,\"schemeViewer\":true,\"synthetic\":false}}\n{\"type\":\"boardMember\",\"data\":{\"boardId\":\"bfoi6yy6pa3yzika53spj7pq9ee\",\"userId\":\"hxxzooc3ff8cubsgtcmpn8733e\",\"roles\":\"\",\"minimumRole\":\"\",\"schemeAdmin\":false,\"schemeEditor\":false,\"schemeCommenter\":false,\"schemeViewer\":true,\"synthetic\":false}}\n{\"type\":\"boardMember\",\"data\":{\"boardId\":\"bfoi6yy6pa3yzika53spj7pq9ee\",\"userId\":\"nto73edn5ir6ifimo5a53y1dwa\",\"roles\":\"\",\"minimumRole\":\"\",\"schemeAdmin\":true,\"schemeEditor\":false,\"schemeCommenter\":false,\"schemeViewer\":false,\"synthetic\":false}}\n`\n"
  },
  {
    "path": "server/app/initialize.go",
    "content": "package app\n\nimport (\n\t\"context\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\n// initialize is called when the App is first created.\nfunc (a *App) initialize(skipTemplateInit bool) {\n\tif !skipTemplateInit {\n\t\tif err := a.InitTemplates(); err != nil {\n\t\t\ta.logger.Error(`InitializeTemplates failed`, mlog.Err(err))\n\t\t}\n\t}\n}\n\nfunc (a *App) Shutdown() {\n\tif a.blockChangeNotifier != nil {\n\t\tctx, cancel := context.WithTimeout(context.Background(), blockChangeNotifierShutdownTimeout)\n\t\tdefer cancel()\n\t\tif !a.blockChangeNotifier.Shutdown(ctx) {\n\t\t\ta.logger.Warn(\"blockChangeNotifier shutdown timed out\")\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "server/app/onboarding.go",
    "content": "package app\n\nimport (\n\t\"errors\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n)\n\nconst (\n\tKeyOnboardingTourStarted  = \"onboardingTourStarted\"\n\tKeyOnboardingTourCategory = \"tourCategory\"\n\tKeyOnboardingTourStep     = \"onboardingTourStep\"\n\n\tValueOnboardingFirstStep    = \"0\"\n\tValueTourCategoryOnboarding = \"onboarding\"\n\n\tWelcomeBoardTitle = \"Welcome to Boards!\"\n)\n\nvar (\n\terrUnableToFindWelcomeBoard = errors.New(\"unable to find welcome board in newly created blocks\")\n\terrCannotCreateBoard        = errors.New(\"new board wasn't created\")\n)\n\nfunc (a *App) PrepareOnboardingTour(userID string, teamID string) (string, string, error) {\n\t// copy the welcome board into this workspace\n\tboardID, err := a.createWelcomeBoard(userID, teamID)\n\tif err != nil {\n\t\treturn \"\", \"\", err\n\t}\n\n\t// set user's tour state to initial state\n\tuserPreferencesPatch := model.UserPreferencesPatch{\n\t\tUpdatedFields: map[string]string{\n\t\t\tKeyOnboardingTourStarted:  \"1\",\n\t\t\tKeyOnboardingTourStep:     ValueOnboardingFirstStep,\n\t\t\tKeyOnboardingTourCategory: ValueTourCategoryOnboarding,\n\t\t},\n\t}\n\tif _, err := a.store.PatchUserPreferences(userID, userPreferencesPatch); err != nil {\n\t\treturn \"\", \"\", err\n\t}\n\n\treturn teamID, boardID, nil\n}\n\nfunc (a *App) getOnboardingBoardID() (string, error) {\n\tboards, err := a.store.GetTemplateBoards(model.GlobalTeamID, \"\")\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tvar onboardingBoardID string\n\tfor _, block := range boards {\n\t\tif block.Title == WelcomeBoardTitle && block.TeamID == model.GlobalTeamID {\n\t\t\tonboardingBoardID = block.ID\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif onboardingBoardID == \"\" {\n\t\treturn \"\", errUnableToFindWelcomeBoard\n\t}\n\n\treturn onboardingBoardID, nil\n}\n\nfunc (a *App) createWelcomeBoard(userID, teamID string) (string, error) {\n\tonboardingBoardID, err := a.getOnboardingBoardID()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tbab, _, err := a.DuplicateBoard(onboardingBoardID, userID, teamID, false)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tif len(bab.Boards) != 1 {\n\t\treturn \"\", errCannotCreateBoard\n\t}\n\n\t// need variable for this to\n\t// get reference for board patch\n\tnewType := model.BoardTypePrivate\n\n\tpatch := &model.BoardPatch{\n\t\tType: &newType,\n\t}\n\n\tif _, err := a.PatchBoard(patch, bab.Boards[0].ID, userID); err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn bab.Boards[0].ID, nil\n}\n"
  },
  {
    "path": "server/app/onboarding_test.go",
    "content": "package app\n\nimport (\n\t\"testing\"\n\n\t\"github.com/mattermost/focalboard/server/utils\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nconst (\n\ttestTeamID = \"team_id\"\n)\n\nfunc TestPrepareOnboardingTour(t *testing.T) {\n\tth, tearDown := SetupTestHelper(t)\n\tdefer tearDown()\n\n\tt.Run(\"base case\", func(t *testing.T) {\n\t\tteamID := testTeamID\n\t\tuserID := \"user_id_1\"\n\t\twelcomeBoard := model.Board{\n\t\t\tID:         \"board_id_1\",\n\t\t\tTitle:      \"Welcome to Boards!\",\n\t\t\tTeamID:     \"0\",\n\t\t\tIsTemplate: true,\n\t\t}\n\n\t\tth.Store.EXPECT().GetTemplateBoards(\"0\", \"\").Return([]*model.Board{&welcomeBoard}, nil)\n\t\tth.Store.EXPECT().DuplicateBoard(welcomeBoard.ID, userID, teamID, false).Return(&model.BoardsAndBlocks{Boards: []*model.Board{\n\t\t\t{\n\t\t\t\tID:         \"board_id_2\",\n\t\t\t\tTitle:      \"Welcome to Boards!\",\n\t\t\t\tTeamID:     \"0\",\n\t\t\t\tIsTemplate: true,\n\t\t\t},\n\t\t}},\n\t\t\tnil, nil)\n\t\tth.Store.EXPECT().GetMembersForBoard(welcomeBoard.ID).Return([]*model.BoardMember{}, nil).Times(2)\n\t\tth.Store.EXPECT().GetMembersForBoard(\"board_id_2\").Return([]*model.BoardMember{}, nil).Times(1)\n\t\tth.Store.EXPECT().GetBoard(welcomeBoard.ID).Return(&welcomeBoard, nil).Times(2)\n\t\tth.Store.EXPECT().GetBoard(\"board_id_2\").Return(&welcomeBoard, nil).Times(1)\n\t\tth.Store.EXPECT().GetUsersByTeam(\"0\", \"\", false, false).Return([]*model.User{}, nil)\n\n\t\tprivateWelcomeBoard := model.Board{\n\t\t\tID:         \"board_id_1\",\n\t\t\tTitle:      \"Welcome to Boards!\",\n\t\t\tTeamID:     \"0\",\n\t\t\tIsTemplate: true,\n\t\t\tType:       model.BoardTypePrivate,\n\t\t}\n\t\tnewType := model.BoardTypePrivate\n\t\tth.Store.EXPECT().PatchBoard(\"board_id_2\", &model.BoardPatch{Type: &newType}, \"user_id_1\").Return(&privateWelcomeBoard, nil)\n\t\tth.Store.EXPECT().GetMembersForUser(\"user_id_1\").Return([]*model.BoardMember{}, nil)\n\n\t\tuserPreferencesPatch := model.UserPreferencesPatch{\n\t\t\tUpdatedFields: map[string]string{\n\t\t\t\tKeyOnboardingTourStarted:  \"1\",\n\t\t\t\tKeyOnboardingTourStep:     ValueOnboardingFirstStep,\n\t\t\t\tKeyOnboardingTourCategory: ValueTourCategoryOnboarding,\n\t\t\t},\n\t\t}\n\n\t\tth.Store.EXPECT().PatchUserPreferences(userID, userPreferencesPatch).Return(nil, nil)\n\t\tth.Store.EXPECT().GetUserCategoryBoards(userID, \"team_id\").Return([]model.CategoryBoards{}, nil).Times(1)\n\n\t\t// when this is called the second time, the default category is created so we need to include that in the response list\n\t\tth.Store.EXPECT().GetUserCategoryBoards(userID, \"team_id\").Return([]model.CategoryBoards{\n\t\t\t{\n\t\t\t\tCategory: model.Category{ID: \"boards_category_id\", Name: \"Boards\"},\n\t\t\t},\n\t\t}, nil).Times(2)\n\n\t\tth.Store.EXPECT().CreateCategory(utils.Anything).Return(nil).Times(1)\n\t\tth.Store.EXPECT().GetCategory(utils.Anything).Return(&model.Category{\n\t\t\tID:   \"boards_category\",\n\t\t\tName: \"Boards\",\n\t\t}, nil)\n\t\tth.Store.EXPECT().GetBoardsForUserAndTeam(\"user_id_1\", teamID, false).Return([]*model.Board{}, nil)\n\t\tth.Store.EXPECT().AddUpdateCategoryBoard(\"user_id_1\", \"boards_category_id\", []string{\"board_id_2\"}).Return(nil)\n\n\t\tteamID, boardID, err := th.App.PrepareOnboardingTour(userID, teamID)\n\t\tassert.NoError(t, err)\n\t\tassert.Equal(t, testTeamID, teamID)\n\t\tassert.NotEmpty(t, boardID)\n\t})\n}\n\nfunc TestCreateWelcomeBoard(t *testing.T) {\n\tth, tearDown := SetupTestHelper(t)\n\tdefer tearDown()\n\n\tt.Run(\"base case\", func(t *testing.T) {\n\t\tteamID := testTeamID\n\t\tuserID := \"user_id_1\"\n\t\twelcomeBoard := model.Board{\n\t\t\tID:         \"board_id_1\",\n\t\t\tTitle:      \"Welcome to Boards!\",\n\t\t\tTeamID:     \"0\",\n\t\t\tIsTemplate: true,\n\t\t}\n\t\tth.Store.EXPECT().GetTemplateBoards(\"0\", \"\").Return([]*model.Board{&welcomeBoard}, nil)\n\t\tth.Store.EXPECT().DuplicateBoard(welcomeBoard.ID, userID, teamID, false).\n\t\t\tReturn(&model.BoardsAndBlocks{Boards: []*model.Board{&welcomeBoard}}, nil, nil)\n\t\tth.Store.EXPECT().GetMembersForBoard(welcomeBoard.ID).Return([]*model.BoardMember{}, nil).Times(3)\n\t\tth.Store.EXPECT().GetBoard(welcomeBoard.ID).Return(&welcomeBoard, nil).AnyTimes()\n\t\tth.Store.EXPECT().GetUsersByTeam(\"0\", \"\", false, false).Return([]*model.User{}, nil)\n\n\t\tprivateWelcomeBoard := model.Board{\n\t\t\tID:         \"board_id_1\",\n\t\t\tTitle:      \"Welcome to Boards!\",\n\t\t\tTeamID:     \"0\",\n\t\t\tIsTemplate: true,\n\t\t\tType:       model.BoardTypePrivate,\n\t\t}\n\t\tnewType := model.BoardTypePrivate\n\t\tth.Store.EXPECT().PatchBoard(\"board_id_1\", &model.BoardPatch{Type: &newType}, \"user_id_1\").Return(&privateWelcomeBoard, nil)\n\t\tth.Store.EXPECT().GetUserCategoryBoards(userID, \"team_id\").Return([]model.CategoryBoards{\n\t\t\t{\n\t\t\t\tCategory: model.Category{ID: \"boards_category_id\", Name: \"Boards\"},\n\t\t\t},\n\t\t}, nil).Times(3)\n\t\tth.Store.EXPECT().AddUpdateCategoryBoard(\"user_id_1\", \"boards_category_id\", []string{\"board_id_1\"}).Return(nil)\n\n\t\tboardID, err := th.App.createWelcomeBoard(userID, teamID)\n\t\tassert.Nil(t, err)\n\t\tassert.NotEmpty(t, boardID)\n\t})\n\n\tt.Run(\"template doesn't contain a board\", func(t *testing.T) {\n\t\tteamID := testTeamID\n\t\tth.Store.EXPECT().GetTemplateBoards(\"0\", \"\").Return([]*model.Board{}, nil)\n\t\tboardID, err := th.App.createWelcomeBoard(\"user_id_1\", teamID)\n\t\tassert.Error(t, err)\n\t\tassert.Empty(t, boardID)\n\t})\n\n\tt.Run(\"template doesn't contain the welcome board\", func(t *testing.T) {\n\t\tteamID := testTeamID\n\t\twelcomeBoard := model.Board{\n\t\t\tID:         \"board_id_1\",\n\t\t\tTitle:      \"Other template\",\n\t\t\tTeamID:     teamID,\n\t\t\tIsTemplate: true,\n\t\t}\n\t\tth.Store.EXPECT().GetTemplateBoards(\"0\", \"\").Return([]*model.Board{&welcomeBoard}, nil)\n\t\tboardID, err := th.App.createWelcomeBoard(\"user_id_1\", \"workspace_id_1\")\n\t\tassert.Error(t, err)\n\t\tassert.Empty(t, boardID)\n\t})\n}\n\nfunc TestGetOnboardingBoardID(t *testing.T) {\n\tth, tearDown := SetupTestHelper(t)\n\tdefer tearDown()\n\n\tt.Run(\"base case\", func(t *testing.T) {\n\t\twelcomeBoard := model.Board{\n\t\t\tID:         \"board_id_1\",\n\t\t\tTitle:      \"Welcome to Boards!\",\n\t\t\tTeamID:     \"0\",\n\t\t\tIsTemplate: true,\n\t\t}\n\t\tth.Store.EXPECT().GetTemplateBoards(\"0\", \"\").Return([]*model.Board{&welcomeBoard}, nil)\n\n\t\tonboardingBoardID, err := th.App.getOnboardingBoardID()\n\t\tassert.NoError(t, err)\n\t\tassert.Equal(t, \"board_id_1\", onboardingBoardID)\n\t})\n\n\tt.Run(\"no blocks found\", func(t *testing.T) {\n\t\tth.Store.EXPECT().GetTemplateBoards(\"0\", \"\").Return([]*model.Board{}, nil)\n\n\t\tonboardingBoardID, err := th.App.getOnboardingBoardID()\n\t\tassert.Error(t, err)\n\t\tassert.Empty(t, onboardingBoardID)\n\t})\n\n\tt.Run(\"onboarding board doesn't exists\", func(t *testing.T) {\n\t\twelcomeBoard := model.Board{\n\t\t\tID:         \"board_id_1\",\n\t\t\tTitle:      \"Other template\",\n\t\t\tTeamID:     \"0\",\n\t\t\tIsTemplate: true,\n\t\t}\n\t\tth.Store.EXPECT().GetTemplateBoards(\"0\", \"\").Return([]*model.Board{&welcomeBoard}, nil)\n\n\t\tonboardingBoardID, err := th.App.getOnboardingBoardID()\n\t\tassert.Error(t, err)\n\t\tassert.Empty(t, onboardingBoardID)\n\t})\n}\n"
  },
  {
    "path": "server/app/permissions.go",
    "content": "package app\n\nimport (\n\tmm_model \"github.com/mattermost/mattermost/server/public/model\"\n)\n\nfunc (a *App) HasPermissionToBoard(userID, boardID string, permission *mm_model.Permission) bool {\n\treturn a.permissions.HasPermissionToBoard(userID, boardID, permission)\n}\n"
  },
  {
    "path": "server/app/server_metadata.go",
    "content": "package app\n\nimport (\n\t\"runtime\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n)\n\ntype ServerMetadata struct {\n\tVersion     string `json:\"version\"`\n\tBuildNumber string `json:\"build_number\"`\n\tBuildDate   string `json:\"build_date\"`\n\tCommit      string `json:\"commit\"`\n\tEdition     string `json:\"edition\"`\n\tDBType      string `json:\"db_type\"`\n\tDBVersion   string `json:\"db_version\"`\n\tOSType      string `json:\"os_type\"`\n\tOSArch      string `json:\"os_arch\"`\n\tSKU         string `json:\"sku\"`\n}\n\nfunc (a *App) GetServerMetadata() *ServerMetadata {\n\tvar dbType string\n\tvar dbVersion string\n\tif a != nil && a.store != nil {\n\t\tdbType = a.store.DBType()\n\t\tdbVersion = a.store.DBVersion()\n\t}\n\n\treturn &ServerMetadata{\n\t\tVersion:     model.CurrentVersion,\n\t\tBuildNumber: model.BuildNumber,\n\t\tBuildDate:   model.BuildDate,\n\t\tCommit:      model.BuildHash,\n\t\tEdition:     model.Edition,\n\t\tDBType:      dbType,\n\t\tDBVersion:   dbVersion,\n\t\tOSType:      runtime.GOOS,\n\t\tOSArch:      runtime.GOARCH,\n\t\tSKU:         \"personal_server\",\n\t}\n}\n"
  },
  {
    "path": "server/app/server_metadata_test.go",
    "content": "package app\n\nimport (\n\t\"reflect\"\n\t\"runtime\"\n\t\"testing\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n)\n\nfunc TestGetServerMetadata(t *testing.T) {\n\tth, tearDown := SetupTestHelper(t)\n\tdefer tearDown()\n\n\tth.Store.EXPECT().DBType().Return(\"TEST_DB_TYPE\")\n\tth.Store.EXPECT().DBVersion().Return(\"TEST_DB_VERSION\")\n\n\tt.Run(\"Get Server Metadata\", func(t *testing.T) {\n\t\tgot := th.App.GetServerMetadata()\n\t\twant := &ServerMetadata{\n\t\t\tVersion:     model.CurrentVersion,\n\t\t\tBuildNumber: model.BuildNumber,\n\t\t\tBuildDate:   model.BuildDate,\n\t\t\tCommit:      model.BuildHash,\n\t\t\tEdition:     model.Edition,\n\t\t\tDBType:      \"TEST_DB_TYPE\",\n\t\t\tDBVersion:   \"TEST_DB_VERSION\",\n\t\t\tOSType:      runtime.GOOS,\n\t\t\tOSArch:      runtime.GOARCH,\n\t\t\tSKU:         \"personal_server\",\n\t\t}\n\n\t\tif !reflect.DeepEqual(got, want) {\n\t\t\tt.Errorf(\"got: %q, want: %q\", got, want)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "server/app/sharing.go",
    "content": "package app\n\nimport (\n\t\"github.com/mattermost/focalboard/server/model\"\n)\n\nfunc (a *App) GetSharing(boardID string) (*model.Sharing, error) {\n\tsharing, err := a.store.GetSharing(boardID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn sharing, nil\n}\n\nfunc (a *App) UpsertSharing(sharing model.Sharing) error {\n\treturn a.store.UpsertSharing(sharing)\n}\n"
  },
  {
    "path": "server/app/sharing_test.go",
    "content": "package app\n\nimport (\n\t\"database/sql\"\n\t\"testing\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestGetSharing(t *testing.T) {\n\tth, tearDown := SetupTestHelper(t)\n\tdefer tearDown()\n\n\tt.Run(\"should get a sharing successfully\", func(t *testing.T) {\n\t\twant := &model.Sharing{\n\t\t\tID:         utils.NewID(utils.IDTypeBlock),\n\t\t\tEnabled:    true,\n\t\t\tToken:      \"token\",\n\t\t\tModifiedBy: \"otherid\",\n\t\t\tUpdateAt:   utils.GetMillis(),\n\t\t}\n\t\tth.Store.EXPECT().GetSharing(\"test-id\").Return(want, nil)\n\n\t\tresult, err := th.App.GetSharing(\"test-id\")\n\t\trequire.NoError(t, err)\n\n\t\trequire.Equal(t, result, want)\n\t\trequire.NotNil(t, th.App)\n\t})\n\n\tt.Run(\"should fail to get a sharing\", func(t *testing.T) {\n\t\tth.Store.EXPECT().GetSharing(\"test-id\").Return(\n\t\t\tnil,\n\t\t\terrors.New(\"sharing not found\"),\n\t\t)\n\t\tresult, err := th.App.GetSharing(\"test-id\")\n\n\t\trequire.Nil(t, result)\n\t\trequire.Error(t, err)\n\t\trequire.Equal(t, \"sharing not found\", err.Error())\n\t})\n\n\tt.Run(\"should return a not found error\", func(t *testing.T) {\n\t\tth.Store.EXPECT().GetSharing(\"test-id\").Return(\n\t\t\tnil,\n\t\t\tsql.ErrNoRows,\n\t\t)\n\t\tresult, err := th.App.GetSharing(\"test-id\")\n\t\trequire.Error(t, err)\n\t\trequire.True(t, model.IsErrNotFound(err))\n\t\trequire.Nil(t, result)\n\t})\n}\n\nfunc TestUpsertSharing(t *testing.T) {\n\tth, tearDown := SetupTestHelper(t)\n\tdefer tearDown()\n\n\tsharing := model.Sharing{\n\t\tID:         utils.NewID(utils.IDTypeBlock),\n\t\tEnabled:    true,\n\t\tToken:      \"token\",\n\t\tModifiedBy: \"otherid\",\n\t\tUpdateAt:   utils.GetMillis(),\n\t}\n\n\tt.Run(\"should success to upsert sharing\", func(t *testing.T) {\n\t\tth.Store.EXPECT().UpsertSharing(sharing).Return(nil)\n\t\terr := th.App.UpsertSharing(sharing)\n\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"should fail to upsert a sharing\", func(t *testing.T) {\n\t\tth.Store.EXPECT().UpsertSharing(sharing).Return(errors.New(\"sharing not found\"))\n\t\terr := th.App.UpsertSharing(sharing)\n\n\t\trequire.Error(t, err)\n\t\trequire.Equal(t, \"sharing not found\", err.Error())\n\t})\n}\n"
  },
  {
    "path": "server/app/statistics.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage app\n\nfunc (a *App) GetUsedCardsCount() (int, error) {\n\treturn a.store.GetUsedCardsCount()\n}\n"
  },
  {
    "path": "server/app/subscriptions.go",
    "content": "package app\n\nimport (\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nfunc (a *App) CreateSubscription(sub *model.Subscription) (*model.Subscription, error) {\n\tsub, err := a.store.CreateSubscription(sub)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ta.notifySubscriptionChanged(sub)\n\n\treturn sub, nil\n}\n\nfunc (a *App) DeleteSubscription(blockID string, subscriberID string) (*model.Subscription, error) {\n\tsub, err := a.store.GetSubscription(blockID, subscriberID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := a.store.DeleteSubscription(blockID, subscriberID); err != nil {\n\t\treturn nil, err\n\t}\n\tsub.DeleteAt = utils.GetMillis()\n\ta.notifySubscriptionChanged(sub)\n\n\treturn sub, nil\n}\n\nfunc (a *App) GetSubscriptions(subscriberID string) ([]*model.Subscription, error) {\n\treturn a.store.GetSubscriptions(subscriberID)\n}\n\nfunc (a *App) notifySubscriptionChanged(subscription *model.Subscription) {\n\tif a.notifications == nil {\n\t\treturn\n\t}\n\n\tboard, err := a.getBoardForBlock(subscription.BlockID)\n\tif err != nil {\n\t\ta.logger.Error(\"Error notifying subscription change\",\n\t\t\tmlog.String(\"subscriber_id\", subscription.SubscriberID),\n\t\t\tmlog.String(\"block_id\", subscription.BlockID),\n\t\t\tmlog.Err(err),\n\t\t)\n\t}\n\ta.wsAdapter.BroadcastSubscriptionChange(board.TeamID, subscription)\n}\n"
  },
  {
    "path": "server/app/teams.go",
    "content": "package app\n\nimport (\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nfunc (a *App) GetRootTeam() (*model.Team, error) {\n\tteamID := \"0\"\n\tteam, _ := a.store.GetTeam(teamID)\n\tif team == nil {\n\t\tteam = &model.Team{\n\t\t\tID:          teamID,\n\t\t\tSignupToken: utils.NewID(utils.IDTypeToken),\n\t\t}\n\t\terr := a.store.UpsertTeamSignupToken(*team)\n\t\tif err != nil {\n\t\t\ta.logger.Error(\"Unable to initialize team\", mlog.Err(err))\n\t\t\treturn nil, err\n\t\t}\n\n\t\tteam, err = a.store.GetTeam(teamID)\n\t\tif err != nil {\n\t\t\ta.logger.Error(\"Unable to get initialized team\", mlog.Err(err))\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.logger.Info(\"initialized team\")\n\t}\n\n\treturn team, nil\n}\n\nfunc (a *App) GetTeam(id string) (*model.Team, error) {\n\tteam, err := a.store.GetTeam(id)\n\tif model.IsErrNotFound(err) {\n\t\treturn nil, nil\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn team, nil\n}\n\nfunc (a *App) GetTeamsForUser(userID string) ([]*model.Team, error) {\n\treturn a.store.GetTeamsForUser(userID)\n}\n\nfunc (a *App) DoesUserHaveTeamAccess(userID string, teamID string) bool {\n\treturn a.auth.DoesUserHaveTeamAccess(userID, teamID)\n}\n\nfunc (a *App) UpsertTeamSettings(team model.Team) error {\n\treturn a.store.UpsertTeamSettings(team)\n}\n\nfunc (a *App) UpsertTeamSignupToken(team model.Team) error {\n\treturn a.store.UpsertTeamSignupToken(team)\n}\n\nfunc (a *App) GetTeamCount() (int64, error) {\n\treturn a.store.GetTeamCount()\n}\n"
  },
  {
    "path": "server/app/teams_test.go",
    "content": "package app\n\nimport (\n\t\"database/sql\"\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/golang/mock/gomock\"\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nvar errInvalidTeam = errors.New(\"invalid team id\")\n\nvar mockTeam = &model.Team{\n\tID:    \"mock-team-id\",\n\tTitle: \"MockTeam\",\n}\n\nvar errUpsertSignupToken = errors.New(\"upsert error\")\n\nfunc TestGetRootTeam(t *testing.T) {\n\tvar newRootTeam = &model.Team{\n\t\tID:    \"0\",\n\t\tTitle: \"NewRootTeam\",\n\t}\n\n\ttestCases := []struct {\n\t\ttitle                    string\n\t\tteamToReturnBeforeUpsert *model.Team\n\t\tteamToReturnAfterUpsert  *model.Team\n\t\tisError                  bool\n\t}{\n\t\t{\n\t\t\t\"Success, Return new root team, when root team returned by mockstore is nil\",\n\t\t\tnil,\n\t\t\tnewRootTeam,\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"Success, Return existing root team, when root team returned by mockstore is notnil\",\n\t\t\tnewRootTeam,\n\t\t\tnil,\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"Fail, Return nil, when root team returned by mockstore is nil, and upsert new root team fails\",\n\t\t\tnil,\n\t\t\tnil,\n\t\t\ttrue,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.title, func(t *testing.T) {\n\t\t\tth, tearDown := SetupTestHelper(t)\n\t\t\tdefer tearDown()\n\t\t\tth.Store.EXPECT().GetTeam(\"0\").Return(tc.teamToReturnBeforeUpsert, nil)\n\t\t\tif tc.teamToReturnBeforeUpsert == nil {\n\t\t\t\tth.Store.EXPECT().UpsertTeamSignupToken(gomock.Any()).DoAndReturn(\n\t\t\t\t\tfunc(arg0 model.Team) error {\n\t\t\t\t\t\tif tc.isError {\n\t\t\t\t\t\t\treturn errUpsertSignupToken\n\t\t\t\t\t\t}\n\t\t\t\t\t\tth.Store.EXPECT().GetTeam(\"0\").Return(tc.teamToReturnAfterUpsert, nil)\n\t\t\t\t\t\treturn nil\n\t\t\t\t\t})\n\t\t\t}\n\t\t\trootTeam, err := th.App.GetRootTeam()\n\n\t\t\tif tc.isError {\n\t\t\t\trequire.Error(t, err)\n\t\t\t} else {\n\t\t\t\tassert.NotNil(t, rootTeam.ID)\n\t\t\t\tassert.NotNil(t, rootTeam.SignupToken)\n\t\t\t\tassert.Equal(t, \"\", rootTeam.ModifiedBy)\n\t\t\t\tassert.Equal(t, int64(0), rootTeam.UpdateAt)\n\t\t\t\tassert.Equal(t, \"NewRootTeam\", rootTeam.Title)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.NotNil(t, rootTeam)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGetTeam(t *testing.T) {\n\tth, tearDown := SetupTestHelper(t)\n\tdefer tearDown()\n\n\ttestCases := []struct {\n\t\ttitle   string\n\t\tteamID  string\n\t\tisError bool\n\t}{\n\t\t{\n\t\t\t\"Success, Return new root team, when team returned by mockstore is not nil\",\n\t\t\t\"mock-team-id\",\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"Success, Return nil, when get team returns an sql error\",\n\t\t\t\"team-not-available-id\",\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"Fail, Return nil, when get team by mockstore returns an error\",\n\t\t\t\"invalid-team-id\",\n\t\t\ttrue,\n\t\t},\n\t}\n\n\tth.Store.EXPECT().GetTeam(\"mock-team-id\").Return(mockTeam, nil)\n\tth.Store.EXPECT().GetTeam(\"invalid-team-id\").Return(nil, errInvalidTeam)\n\tth.Store.EXPECT().GetTeam(\"team-not-available-id\").Return(nil, sql.ErrNoRows)\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.title, func(t *testing.T) {\n\t\t\tt.Log(tc.title)\n\t\t\tteam, err := th.App.GetTeam(tc.teamID)\n\n\t\t\tif tc.isError {\n\t\t\t\trequire.Error(t, err)\n\t\t\t} else if tc.teamID != \"team-not-available-id\" {\n\t\t\t\tassert.NotNil(t, team.ID)\n\t\t\t\tassert.NotNil(t, team.SignupToken)\n\t\t\t\tassert.Equal(t, \"mock-team-id\", team.ID)\n\t\t\t\tassert.Equal(t, \"\", team.ModifiedBy)\n\t\t\t\tassert.Equal(t, int64(0), team.UpdateAt)\n\t\t\t\tassert.Equal(t, \"MockTeam\", team.Title)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.NotNil(t, team)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamOperations(t *testing.T) {\n\tth, tearDown := SetupTestHelper(t)\n\tdefer tearDown()\n\n\tth.Store.EXPECT().UpsertTeamSettings(*mockTeam).Return(nil)\n\tth.Store.EXPECT().UpsertTeamSignupToken(*mockTeam).Return(nil)\n\tth.Store.EXPECT().GetTeamCount().Return(int64(10), nil)\n\n\terrUpsertTeamSettings := th.App.UpsertTeamSettings(*mockTeam)\n\tassert.NoError(t, errUpsertTeamSettings)\n\n\terrUpsertTeamSignupToken := th.App.UpsertTeamSignupToken(*mockTeam)\n\tassert.NoError(t, errUpsertTeamSignupToken)\n\n\tcount, errGetTeamCount := th.App.GetTeamCount()\n\tassert.NoError(t, errGetTeamCount)\n\tassert.Equal(t, int64(10), count)\n}\n"
  },
  {
    "path": "server/app/templates.go",
    "content": "package app\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/mattermost/focalboard/server/assets\"\n\t\"github.com/mattermost/focalboard/server/model\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nconst (\n\tdefaultTemplateVersion = 6 // bump this number to force default templates to be re-imported\n)\n\nfunc (a *App) InitTemplates() error {\n\t_, err := a.initializeTemplates()\n\treturn err\n}\n\n// initializeTemplates imports default templates if the boards table is empty.\nfunc (a *App) initializeTemplates() (bool, error) {\n\tboards, err := a.store.GetTemplateBoards(model.GlobalTeamID, \"\")\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"cannot initialize templates: %w\", err)\n\t}\n\n\ta.logger.Debug(\"Fetched template boards\", mlog.Int(\"count\", len(boards)))\n\n\tisNeeded, reason := a.isInitializationNeeded(boards)\n\tif !isNeeded {\n\t\ta.logger.Debug(\"Template import not needed, skipping\")\n\t\treturn false, nil\n\t}\n\n\ta.logger.Debug(\"Importing new default templates\",\n\t\tmlog.String(\"reason\", reason),\n\t\tmlog.Int(\"size\", len(assets.DefaultTemplatesArchive)),\n\t)\n\n\t// Remove in case of newer Templates\n\tif err = a.store.RemoveDefaultTemplates(boards); err != nil {\n\t\treturn false, fmt.Errorf(\"cannot remove old template boards: %w\", err)\n\t}\n\n\tr := bytes.NewReader(assets.DefaultTemplatesArchive)\n\n\topt := model.ImportArchiveOptions{\n\t\tTeamID:        model.GlobalTeamID,\n\t\tModifiedBy:    model.SystemUserID,\n\t\tBlockModifier: fixTemplateBlock,\n\t\tBoardModifier: fixTemplateBoard,\n\t}\n\tif err = a.ImportArchive(r, opt); err != nil {\n\t\treturn false, fmt.Errorf(\"cannot initialize global templates for team %s: %w\", model.GlobalTeamID, err)\n\t}\n\treturn true, nil\n}\n\n// isInitializationNeeded returns true if the blocks table contains no default templates,\n// or contains at least one default template with an old version number.\nfunc (a *App) isInitializationNeeded(boards []*model.Board) (bool, string) {\n\tif len(boards) == 0 {\n\t\treturn true, \"no default templates found\"\n\t}\n\n\t// look for any built-in template boards with the wrong version number (or no version #).\n\tfor _, board := range boards {\n\t\t// if not built-in board...skip\n\t\tif board.CreatedBy != model.SystemUserID {\n\t\t\tcontinue\n\t\t}\n\t\tif board.TemplateVersion < defaultTemplateVersion {\n\t\t\treturn true, \"template_version too old\"\n\t\t}\n\t}\n\treturn false, \"\"\n}\n\n// fixTemplateBlock fixes a block to be inserted as part of a template.\nfunc fixTemplateBlock(block *model.Block, cache map[string]interface{}) bool {\n\t// cache contains ids of skipped boards. Ensure their children are skipped as well.\n\tif _, ok := cache[block.BoardID]; ok {\n\t\tcache[block.ID] = struct{}{}\n\t\treturn false\n\t}\n\n\tif _, ok := cache[block.ParentID]; ok {\n\t\tcache[block.ID] = struct{}{}\n\t\treturn false\n\t}\n\treturn true\n}\n\n// fixTemplateBoard fixes a board to be inserted as part of a template.\nfunc fixTemplateBoard(board *model.Board, cache map[string]interface{}) bool {\n\t// filter out template blocks; we only want the non-template\n\t// blocks which we will turn into default template blocks.\n\tif board.IsTemplate {\n\t\tcache[board.ID] = struct{}{}\n\t\treturn false\n\t}\n\n\t// remove '(NEW)' from title & force template flag\n\tboard.Title = strings.ReplaceAll(board.Title, \"(NEW)\", \"\")\n\tboard.IsTemplate = true\n\tboard.TemplateVersion = defaultTemplateVersion\n\tboard.Type = model.BoardTypeOpen\n\treturn true\n}\n"
  },
  {
    "path": "server/app/templates_test.go",
    "content": "package app\n\nimport (\n\t\"testing\"\n\n\t\"github.com/golang/mock/gomock\"\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/mattermost/mattermost/server/public/plugin/plugintest/mock\"\n)\n\nfunc TestApp_initializeTemplates(t *testing.T) {\n\tboard := &model.Board{\n\t\tID:              utils.NewID(utils.IDTypeBoard),\n\t\tTeamID:          model.GlobalTeamID,\n\t\tType:            model.BoardTypeOpen,\n\t\tTitle:           \"test board\",\n\t\tIsTemplate:      true,\n\t\tTemplateVersion: defaultTemplateVersion,\n\t}\n\n\tblock := &model.Block{\n\t\tID:       utils.NewID(utils.IDTypeBlock),\n\t\tParentID: board.ID,\n\t\tBoardID:  board.ID,\n\t\tType:     model.TypeText,\n\t\tTitle:    \"test text\",\n\t}\n\n\tboardsAndBlocks := &model.BoardsAndBlocks{\n\t\tBoards: []*model.Board{board},\n\t\tBlocks: []*model.Block{block},\n\t}\n\n\tboardMember := &model.BoardMember{\n\t\tBoardID: board.ID,\n\t\tUserID:  \"test-user\",\n\t}\n\n\tt.Run(\"Needs template init\", func(t *testing.T) {\n\t\tth, tearDown := SetupTestHelper(t)\n\t\tdefer tearDown()\n\n\t\tth.Store.EXPECT().GetTemplateBoards(model.GlobalTeamID, \"\").Return([]*model.Board{}, nil)\n\t\tth.Store.EXPECT().RemoveDefaultTemplates([]*model.Board{}).Return(nil)\n\t\tth.Store.EXPECT().CreateBoardsAndBlocks(gomock.Any(), gomock.Any()).AnyTimes().Return(boardsAndBlocks, nil)\n\t\tth.Store.EXPECT().GetMembersForBoard(board.ID).AnyTimes().Return([]*model.BoardMember{}, nil)\n\t\tth.Store.EXPECT().GetBoard(board.ID).AnyTimes().Return(board, nil)\n\t\tth.Store.EXPECT().GetMemberForBoard(gomock.Any(), gomock.Any()).AnyTimes().Return(boardMember, nil)\n\t\tth.Store.EXPECT().SaveFileInfo(gomock.Any()).Return(nil).AnyTimes()\n\n\t\tth.FilesBackend.On(\"WriteFile\", mock.Anything, mock.Anything).Return(int64(1), nil)\n\n\t\tdone, err := th.App.initializeTemplates()\n\t\trequire.NoError(t, err, \"initializeTemplates should not error\")\n\t\trequire.True(t, done, \"initialization was needed\")\n\t})\n\n\tt.Run(\"Skip template init\", func(t *testing.T) {\n\t\tth, tearDown := SetupTestHelper(t)\n\t\tdefer tearDown()\n\n\t\tth.Store.EXPECT().GetTemplateBoards(model.GlobalTeamID, \"\").Return([]*model.Board{board}, nil)\n\n\t\tdone, err := th.App.initializeTemplates()\n\t\trequire.NoError(t, err, \"initializeTemplates should not error\")\n\t\trequire.False(t, done, \"initialization was not needed\")\n\t})\n}\n"
  },
  {
    "path": "server/app/user.go",
    "content": "package app\n\nimport (\n\t\"github.com/mattermost/focalboard/server/model\"\n\tmmModel \"github.com/mattermost/mattermost/server/public/model\"\n)\n\nfunc (a *App) GetTeamUsers(teamID string, asGuestID string) ([]*model.User, error) {\n\treturn a.store.GetUsersByTeam(teamID, asGuestID, a.config.ShowEmailAddress, a.config.ShowFullName)\n}\n\nfunc (a *App) SearchTeamUsers(teamID string, searchQuery string, asGuestID string, excludeBots bool) ([]*model.User, error) {\n\tusers, err := a.store.SearchUsersByTeam(teamID, searchQuery, asGuestID, excludeBots, a.config.ShowEmailAddress, a.config.ShowFullName)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor i, u := range users {\n\t\tif a.permissions.HasPermissionToTeam(u.ID, teamID, model.PermissionManageTeam) {\n\t\t\tusers[i].Permissions = append(users[i].Permissions, model.PermissionManageTeam.Id)\n\t\t}\n\t\tif a.permissions.HasPermissionTo(u.ID, model.PermissionManageSystem) {\n\t\t\tusers[i].Permissions = append(users[i].Permissions, model.PermissionManageSystem.Id)\n\t\t}\n\t}\n\treturn users, nil\n}\n\nfunc (a *App) UpdateUserConfig(userID string, patch model.UserPreferencesPatch) ([]mmModel.Preference, error) {\n\tupdatedPreferences, err := a.store.PatchUserPreferences(userID, patch)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn updatedPreferences, nil\n}\n\nfunc (a *App) GetUserPreferences(userID string) ([]mmModel.Preference, error) {\n\treturn a.store.GetUserPreferences(userID)\n}\n\nfunc (a *App) UserIsGuest(userID string) (bool, error) {\n\tuser, err := a.store.GetUserByID(userID)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treturn user.IsGuest, nil\n}\n\nfunc (a *App) CanSeeUser(seerUser string, seenUser string) (bool, error) {\n\tisGuest, err := a.UserIsGuest(seerUser)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tif isGuest {\n\t\thasSharedChannels, err := a.store.CanSeeUser(seerUser, seenUser)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\treturn hasSharedChannels, nil\n\t}\n\treturn true, nil\n}\n\nfunc (a *App) SearchUserChannels(teamID string, userID string, query string) ([]*mmModel.Channel, error) {\n\tchannels, err := a.store.SearchUserChannels(teamID, userID, query)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar writeableChannels []*mmModel.Channel\n\tfor _, channel := range channels {\n\t\tif a.permissions.HasPermissionToChannel(userID, channel.Id, model.PermissionCreatePost) {\n\t\t\twriteableChannels = append(writeableChannels, channel)\n\t\t}\n\t}\n\treturn writeableChannels, nil\n}\n\nfunc (a *App) GetChannel(teamID string, channelID string) (*mmModel.Channel, error) {\n\treturn a.store.GetChannel(teamID, channelID)\n}\n\nfunc (a *App) SanitizeProfile(user *model.User, isAdmin bool) {\n\toptions := map[string]bool{}\n\tif isAdmin {\n\t\toptions[\"fullname\"] = true\n\t\toptions[\"email\"] = true\n\t} else {\n\t\toptions[\"fullname\"] = a.config.ShowFullName\n\t\toptions[\"email\"] = a.config.ShowEmailAddress\n\t}\n\tuser.Sanitize(options)\n}\n"
  },
  {
    "path": "server/app/user_test.go",
    "content": "package app\n\nimport (\n\t\"testing\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\tmmModel \"github.com/mattermost/mattermost/server/public/model\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestSearchUsers(t *testing.T) {\n\tth, tearDown := SetupTestHelper(t)\n\tdefer tearDown()\n\tth.App.config.ShowEmailAddress = false\n\tth.App.config.ShowFullName = false\n\n\tteamID := \"team-id-1\"\n\tuserID := \"user-id-1\"\n\n\tt.Run(\"return empty users\", func(t *testing.T) {\n\t\tth.Store.EXPECT().SearchUsersByTeam(teamID, \"\", \"\", true, false, false).Return([]*model.User{}, nil)\n\n\t\tusers, err := th.App.SearchTeamUsers(teamID, \"\", \"\", true)\n\t\tassert.NoError(t, err)\n\t\tassert.Equal(t, 0, len(users))\n\t})\n\n\tt.Run(\"return user\", func(t *testing.T) {\n\t\tth.Store.EXPECT().SearchUsersByTeam(teamID, \"\", \"\", true, false, false).Return([]*model.User{{ID: userID}}, nil)\n\t\tth.API.EXPECT().HasPermissionToTeam(userID, teamID, model.PermissionManageTeam).Return(false).Times(1)\n\t\tth.API.EXPECT().HasPermissionTo(userID, model.PermissionManageSystem).Return(false).Times(1)\n\n\t\tusers, err := th.App.SearchTeamUsers(teamID, \"\", \"\", true)\n\t\tassert.NoError(t, err)\n\t\tassert.Equal(t, 1, len(users))\n\t\tassert.Equal(t, 0, len(users[0].Permissions))\n\t})\n\n\tt.Run(\"return team admin\", func(t *testing.T) {\n\t\tth.Store.EXPECT().SearchUsersByTeam(teamID, \"\", \"\", true, false, false).Return([]*model.User{{ID: userID}}, nil)\n\t\tth.App.config.ShowEmailAddress = false\n\t\tth.App.config.ShowFullName = false\n\t\tth.API.EXPECT().HasPermissionToTeam(userID, teamID, model.PermissionManageTeam).Return(true).Times(1)\n\t\tth.API.EXPECT().HasPermissionTo(userID, model.PermissionManageSystem).Return(false).Times(1)\n\n\t\tusers, err := th.App.SearchTeamUsers(teamID, \"\", \"\", true)\n\t\tassert.NoError(t, err)\n\t\tassert.Equal(t, 1, len(users))\n\t\tassert.Equal(t, users[0].Permissions[0], model.PermissionManageTeam.Id)\n\t})\n\n\tt.Run(\"return system admin\", func(t *testing.T) {\n\t\tth.Store.EXPECT().SearchUsersByTeam(teamID, \"\", \"\", true, false, false).Return([]*model.User{{ID: userID}}, nil)\n\t\tth.App.config.ShowEmailAddress = false\n\t\tth.App.config.ShowFullName = false\n\t\tth.API.EXPECT().HasPermissionToTeam(userID, teamID, model.PermissionManageTeam).Return(true).Times(1)\n\t\tth.API.EXPECT().HasPermissionTo(userID, model.PermissionManageSystem).Return(true).Times(1)\n\n\t\tusers, err := th.App.SearchTeamUsers(teamID, \"\", \"\", true)\n\t\tassert.NoError(t, err)\n\t\tassert.Equal(t, 1, len(users))\n\t\tassert.Equal(t, users[0].Permissions[0], model.PermissionManageTeam.Id)\n\t\tassert.Equal(t, users[0].Permissions[1], model.PermissionManageSystem.Id)\n\t})\n\n\tt.Run(\"test user channels\", func(t *testing.T) {\n\t\tchannelID := \"Channel1\"\n\t\tth.Store.EXPECT().SearchUserChannels(teamID, userID, \"\").Return([]*mmModel.Channel{{Id: channelID}}, nil)\n\t\tth.API.EXPECT().HasPermissionToChannel(userID, channelID, model.PermissionCreatePost).Return(true).Times(1)\n\n\t\tchannels, err := th.App.SearchUserChannels(teamID, userID, \"\")\n\t\tassert.NoError(t, err)\n\t\tassert.Equal(t, 1, len(channels))\n\t})\n\n\tt.Run(\"test user channels- no permissions\", func(t *testing.T) {\n\t\tchannelID := \"Channel1\"\n\t\tth.Store.EXPECT().SearchUserChannels(teamID, userID, \"\").Return([]*mmModel.Channel{{Id: channelID}}, nil)\n\t\tth.API.EXPECT().HasPermissionToChannel(userID, channelID, model.PermissionCreatePost).Return(false).Times(1)\n\n\t\tchannels, err := th.App.SearchUserChannels(teamID, userID, \"\")\n\t\tassert.NoError(t, err)\n\t\tassert.Equal(t, 0, len(channels))\n\t})\n}\n"
  },
  {
    "path": "server/assets/assets.go",
    "content": "package assets\n\nimport (\n\t_ \"embed\"\n)\n\n// DefaultTemplatesArchive is an embedded archive file containing the default\n// templates to be imported to team 0.\n// This archive is generated with `make templates-archive`\n//\n//go:embed templates.boardarchive\nvar DefaultTemplatesArchive []byte\n"
  },
  {
    "path": "server/assets/build-template-archive/main.go",
    "content": "package main\n\nimport (\n\t\"archive/zip\"\n\t\"encoding/json\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n)\n\nconst (\n\tdefArchiveFilename = \"templates.boardarchive\"\n\tversionFilename    = \"version.json\"\n\tboardFilename      = \"board.jsonl\"\n\tminArchiveVersion  = 2\n\tmaxArchiveVersion  = 2\n)\n\ntype archiveVersion struct {\n\tVersion int   `json:\"version\"`\n\tDate    int64 `json:\"date\"`\n}\n\ntype appConfig struct {\n\tdir     string\n\tout     string\n\tverbose bool\n}\n\nfunc main() {\n\tcfg := appConfig{}\n\n\tflag.StringVar(&cfg.dir, \"dir\", \"\", \"source directory of templates\")\n\tflag.StringVar(&cfg.out, \"out\", defArchiveFilename, \"output filename\")\n\tflag.BoolVar(&cfg.verbose, \"verbose\", false, \"enable verbose output\")\n\tflag.Parse()\n\n\tif cfg.dir == \"\" {\n\t\tflag.Usage()\n\t\tos.Exit(-1)\n\t}\n\n\tvar code int\n\tif err := build(cfg); err != nil {\n\t\tcode = -1\n\t\tfmt.Fprintf(os.Stderr, \"error creating archive: %v\\n\", err)\n\t} else if cfg.verbose {\n\t\tfmt.Fprintf(os.Stdout, \"archive created: %s\\n\", cfg.out)\n\t}\n\n\tos.Exit(code)\n}\n\nfunc build(cfg appConfig) (err error) {\n\tversion, err := getVersionFile(cfg)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// create the output archive zip file\n\tarchiveFile, err := os.Create(cfg.out)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error creating %s: %w\", cfg.out, err)\n\t}\n\tarchiveZip := zip.NewWriter(archiveFile)\n\tdefer func() {\n\t\tif err2 := archiveZip.Close(); err2 != nil {\n\t\t\tif err == nil {\n\t\t\t\terr = fmt.Errorf(\"error closing zip %s: %w\", cfg.out, err2)\n\t\t\t}\n\t\t}\n\t\tif err2 := archiveFile.Close(); err2 != nil {\n\t\t\tif err == nil {\n\t\t\t\terr = fmt.Errorf(\"error closing %s: %w\", cfg.out, err2)\n\t\t\t}\n\t\t}\n\t}()\n\n\t// write the version file\n\tv, err := archiveZip.Create(versionFilename)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error creating %s: %w\", cfg.out, err)\n\t}\n\tif _, err = v.Write(version); err != nil {\n\t\treturn fmt.Errorf(\"error writing %s: %w\", cfg.out, err)\n\t}\n\n\t// each board is a subdirectory; write each to the archive\n\tfiles, err := os.ReadDir(cfg.dir)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error reading directory %s: %w\", cfg.dir, err)\n\t}\n\tfor _, f := range files {\n\t\tif !f.IsDir() {\n\t\t\tif f.Name() != versionFilename && cfg.verbose {\n\t\t\t\tfmt.Fprintf(os.Stdout, \"skipping non-directory %s\\n\", f.Name())\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tif err = writeBoard(archiveZip, f.Name(), cfg); err != nil {\n\t\t\treturn fmt.Errorf(\"error writing board %s: %w\", f.Name(), err)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc getVersionFile(cfg appConfig) ([]byte, error) {\n\tpath := filepath.Join(cfg.dir, versionFilename)\n\tbuf, err := os.ReadFile(path)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"cannot read %s: %w\", path, err)\n\t}\n\n\tvar version archiveVersion\n\tif err := json.Unmarshal(buf, &version); err != nil {\n\t\treturn nil, fmt.Errorf(\"cannot parse %s: %w\", path, err)\n\t}\n\n\tif version.Version < minArchiveVersion || version.Version > maxArchiveVersion {\n\t\treturn nil, errUnsupportedVersion{Min: minArchiveVersion, Max: maxArchiveVersion, Got: version.Version}\n\t}\n\n\treturn buf, nil\n}\n\nfunc writeBoard(w *zip.Writer, boardID string, cfg appConfig) error {\n\t// copy the board's jsonl file first.  BoardID is also the directory name.\n\tsrcPath := filepath.Join(cfg.dir, boardID, boardFilename)\n\tdestPath := filepath.Join(boardID, boardFilename)\n\tif err := writeFile(w, srcPath, destPath, cfg); err != nil {\n\t\treturn err\n\t}\n\n\tboardPath := filepath.Join(cfg.dir, boardID)\n\tfiles, err := os.ReadDir(boardPath)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error reading board directory %s: %w\", cfg.dir, err)\n\t}\n\tfor _, f := range files {\n\t\tif f.IsDir() {\n\t\t\tif cfg.verbose {\n\t\t\t\tfmt.Fprintf(os.Stdout, \"skipping directory %s\\n\", f.Name())\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tif f.Name() == boardFilename {\n\t\t\tcontinue\n\t\t}\n\n\t\tsrcPath = filepath.Join(cfg.dir, boardID, f.Name())\n\t\tdestPath = filepath.Join(boardID, f.Name())\n\t\tif err = writeFile(w, srcPath, destPath, cfg); err != nil {\n\t\t\treturn fmt.Errorf(\"error writing %s: %w\", destPath, err)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc writeFile(w *zip.Writer, srcPath string, destPath string, cfg appConfig) (err error) {\n\tinFile, err := os.Open(srcPath)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error reading %s: %w\", srcPath, err)\n\t}\n\tdefer inFile.Close()\n\n\toutFile, err := w.Create(destPath)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error creating %s: %w\", destPath, err)\n\t}\n\tsize, err := io.Copy(outFile, inFile)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error writing %s: %w\", destPath, err)\n\t}\n\n\tif cfg.verbose {\n\t\tfmt.Fprintf(os.Stdout, \"%s written (%d bytes)\\n\", destPath, size)\n\t}\n\n\treturn nil\n}\n\ntype errUnsupportedVersion struct {\n\tMin int\n\tMax int\n\tGot int\n}\n\nfunc (e errUnsupportedVersion) Error() string {\n\treturn fmt.Sprintf(\"unsupported archive version; require between %d and %d inclusive, got %d\", e.Min, e.Max, e.Got)\n}\n"
  },
  {
    "path": "server/assets/templates-boardarchive/b7wnw9awd4pnefryhq51apbzb4c/board.jsonl",
    "content": "{\"type\":\"block\",\"data\":{\"id\":\"b7wnw9awd4pnefryhq51apbzb4c\",\"parentId\":\"\",\"rootId\":\"b7wnw9awd4pnefryhq51apbzb4c\",\"createdBy\":\"mweioqznbife7p7aee7dr4wcxo\",\"modifiedBy\":\"mweioqznbife7p7aee7dr4wcxo\",\"schema\":1,\"type\":\"board\",\"title\":\"Meeting Agenda (NEW)\",\"fields\":{\"cardProperties\":[{\"id\":\"d777ba3b-8728-40d1-87a6-59406bbbbfb0\",\"name\":\"Status\",\"options\":[{\"color\":\"propColorPink\",\"id\":\"34eb9c25-d5bf-49d9-859e-f74f4e0030e7\",\"value\":\"To Discuss 💬\"},{\"color\":\"propColorYellow\",\"id\":\"d37a61f4-f332-4db9-8b2d-5e0a91aa20ed\",\"value\":\"Revisit Later ⏳\"},{\"color\":\"propColorGreen\",\"id\":\"dabadd9b-adf1-4d9f-8702-805ac6cef602\",\"value\":\"Done / Archived 📦\"}],\"type\":\"select\"},{\"id\":\"4cf1568d-530f-4028-8ffd-bdc65249187e\",\"name\":\"Priority\",\"options\":[{\"color\":\"propColorRed\",\"id\":\"8b05c83e-a44a-4d04-831e-97f01d8e2003\",\"value\":\"1. High\"},{\"color\":\"propColorYellow\",\"id\":\"b1abafbf-a038-4a19-8b68-56e0fd2319f7\",\"value\":\"2. Medium\"},{\"color\":\"propColorGray\",\"id\":\"2491ffaa-eb55-417b-8aff-4bd7d4136613\",\"value\":\"3. Low\"}],\"type\":\"select\"},{\"id\":\"aw4w63xhet79y9gueqzzeiifdoe\",\"name\":\"Created by\",\"options\":[],\"type\":\"createdBy\"},{\"id\":\"a6ux19353xcwfqg9k1inqg5sg4w\",\"name\":\"Created time\",\"options\":[],\"type\":\"createdTime\"}],\"description\":\"Use this template for recurring meeting agendas, like team meetings and 1:1's. To use this board:\\n* Participants queue new items to discuss under \\\"To Discuss\\\"\\n* Go through items during the meeting\\n* Move items to Done or Revisit Later as needed\",\"icon\":\"🍩\",\"isTemplate\":false,\"showDescription\":true},\"createAt\":1641497047916,\"updateAt\":1643788318628,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"cgwagmaw6gin7xcq7nwew8rsynr\",\"parentId\":\"b7wnw9awd4pnefryhq51apbzb4c\",\"rootId\":\"b7wnw9awd4pnefryhq51apbzb4c\",\"createdBy\":\"mweioqznbife7p7aee7dr4wcxo\",\"modifiedBy\":\"mweioqznbife7p7aee7dr4wcxo\",\"schema\":1,\"type\":\"card\",\"title\":\"Team Schedule\",\"fields\":{\"contentOrder\":[\"a4t1p1pbxbtnnu8p8e538o8369a\",\"7b7hsbkm6sifqfqi4gstxxaz7my\",\"aoqz1pydxbtnzdcs4ehcuys6cuc\",\"7b3njq5m3n78hdpe4bimzr34fic\",\"73dzfgistnbgzuekc6c8irou9wy\",\"7z4cjur4ybbfibgmydhfct4jdke\"],\"icon\":\"⏰\",\"isTemplate\":false,\"properties\":{\"4cf1568d-530f-4028-8ffd-bdc65249187e\":\"8b05c83e-a44a-4d04-831e-97f01d8e2003\",\"d777ba3b-8728-40d1-87a6-59406bbbbfb0\":\"34eb9c25-d5bf-49d9-859e-f74f4e0030e7\"}},\"createAt\":1641497048246,\"updateAt\":1643788318628,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"chki1tsudciyiiffrkqbcmp71rh\",\"parentId\":\"b7wnw9awd4pnefryhq51apbzb4c\",\"rootId\":\"b7wnw9awd4pnefryhq51apbzb4c\",\"createdBy\":\"mweioqznbife7p7aee7dr4wcxo\",\"modifiedBy\":\"mweioqznbife7p7aee7dr4wcxo\",\"schema\":1,\"type\":\"card\",\"title\":\"Video production\",\"fields\":{\"contentOrder\":[\"a9ti13dqo8jfmjdmg97f5umfdyw\",\"717fa85sx3f8f8m81f771s9hmwr\",\"a4se5s4ozx3ry8ec57w6z6jpk7y\",\"7n37rxrn9uffdzrfi1xajotzjey\",\"7ifofmuwjzbdzppfxgtuai4i47h\",\"7cfc4fkpz53gn9frciz9kui4p1c\"],\"icon\":\"📹\",\"isTemplate\":false,\"properties\":{\"4cf1568d-530f-4028-8ffd-bdc65249187e\":\"b1abafbf-a038-4a19-8b68-56e0fd2319f7\",\"d777ba3b-8728-40d1-87a6-59406bbbbfb0\":\"34eb9c25-d5bf-49d9-859e-f74f4e0030e7\"}},\"createAt\":1641497048092,\"updateAt\":1643788318629,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"cmt5usr1mw3fom886t34ekjquay\",\"parentId\":\"b7wnw9awd4pnefryhq51apbzb4c\",\"rootId\":\"b7wnw9awd4pnefryhq51apbzb4c\",\"createdBy\":\"mweioqznbife7p7aee7dr4wcxo\",\"modifiedBy\":\"mweioqznbife7p7aee7dr4wcxo\",\"schema\":1,\"type\":\"card\",\"title\":\"Offsite plans\",\"fields\":{\"contentOrder\":[\"aw53ugkfq8pyi9fjh9j6i4kdeiw\",\"7ni9593iz3pnb7xitoz3guwq5gh\",\"agjkcro3x7irbxedyxrn8iuerrr\",\"75zkot1f3sjb7ifysuzijitw91y\",\"7is5m8apdu3g53c8f6cz6sq7bmh\",\"7xsmzscbqn3ftudzqbb4w1q7t7e\"],\"icon\":\"🚙\",\"isTemplate\":false,\"properties\":{\"4cf1568d-530f-4028-8ffd-bdc65249187e\":\"8b05c83e-a44a-4d04-831e-97f01d8e2003\",\"d777ba3b-8728-40d1-87a6-59406bbbbfb0\":\"dabadd9b-adf1-4d9f-8702-805ac6cef602\"}},\"createAt\":1641497048336,\"updateAt\":1643788318629,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"cnqsbzg4b7brfddtyh7fc66atrw\",\"parentId\":\"b7wnw9awd4pnefryhq51apbzb4c\",\"rootId\":\"b7wnw9awd4pnefryhq51apbzb4c\",\"createdBy\":\"mweioqznbife7p7aee7dr4wcxo\",\"modifiedBy\":\"mweioqznbife7p7aee7dr4wcxo\",\"schema\":1,\"type\":\"card\",\"title\":\"Social Media Strategy\",\"fields\":{\"contentOrder\":[\"ao57n1fbtmt8q8bfk8ieqgzqt3a\",\"76h9y996sdj8sbrbpqjo9d8cwto\",\"aco8iu5jp7jbyzmzegwxkeusgzr\",\"7y6zcyofmsfrbt899ts1ixr3iey\",\"7hudywfzcwirkpcp1p5jhsfs83r\",\"7jzw67ngdgtns8mstsg9g614oac\"],\"icon\":\"🎉\",\"isTemplate\":false,\"properties\":{\"4cf1568d-530f-4028-8ffd-bdc65249187e\":\"b1abafbf-a038-4a19-8b68-56e0fd2319f7\",\"d777ba3b-8728-40d1-87a6-59406bbbbfb0\":\"d37a61f4-f332-4db9-8b2d-5e0a91aa20ed\"}},\"createAt\":1641497048417,\"updateAt\":1643788318629,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"vfs8sj79dt7n75bomn46fybxmfo\",\"parentId\":\"b7wnw9awd4pnefryhq51apbzb4c\",\"rootId\":\"b7wnw9awd4pnefryhq51apbzb4c\",\"createdBy\":\"mweioqznbife7p7aee7dr4wcxo\",\"modifiedBy\":\"mweioqznbife7p7aee7dr4wcxo\",\"schema\":1,\"type\":\"view\",\"title\":\"Discussion Items\",\"fields\":{\"cardOrder\":[\"cjpkiya33qsagr4f9hrdwhgiajc\"],\"collapsedOptionIds\":[],\"columnCalculations\":{},\"columnWidths\":{},\"defaultTemplateId\":\"\",\"filter\":{\"filters\":[],\"operation\":\"and\"},\"groupById\":\"d777ba3b-8728-40d1-87a6-59406bbbbfb0\",\"hiddenOptionIds\":[\"\"],\"kanbanCalculations\":{},\"sortOptions\":[{\"propertyId\":\"4cf1568d-530f-4028-8ffd-bdc65249187e\",\"reversed\":false}],\"viewType\":\"board\",\"visibleOptionIds\":[],\"visiblePropertyIds\":[\"4cf1568d-530f-4028-8ffd-bdc65249187e\"]},\"createAt\":1641497048501,\"updateAt\":1643788318629,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"73dzfgistnbgzuekc6c8irou9wy\",\"parentId\":\"cgwagmaw6gin7xcq7nwew8rsynr\",\"rootId\":\"b7wnw9awd4pnefryhq51apbzb4c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"\",\"fields\":{\"value\":false},\"createAt\":1641586451774,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7b3njq5m3n78hdpe4bimzr34fic\",\"parentId\":\"cgwagmaw6gin7xcq7nwew8rsynr\",\"rootId\":\"b7wnw9awd4pnefryhq51apbzb4c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"\",\"fields\":{\"value\":false},\"createAt\":1641586448934,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7b7hsbkm6sifqfqi4gstxxaz7my\",\"parentId\":\"cgwagmaw6gin7xcq7nwew8rsynr\",\"rootId\":\"b7wnw9awd4pnefryhq51apbzb4c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"divider\",\"title\":\"\",\"fields\":{},\"createAt\":1641586358664,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7z4cjur4ybbfibgmydhfct4jdke\",\"parentId\":\"cgwagmaw6gin7xcq7nwew8rsynr\",\"rootId\":\"b7wnw9awd4pnefryhq51apbzb4c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"\",\"fields\":{\"value\":false},\"createAt\":1641586454130,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"a4t1p1pbxbtnnu8p8e538o8369a\",\"parentId\":\"cgwagmaw6gin7xcq7nwew8rsynr\",\"rootId\":\"b7wnw9awd4pnefryhq51apbzb4c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Notes\\n*[Add meeting notes here]*\",\"fields\":{},\"createAt\":1641586355777,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"aoqz1pydxbtnzdcs4ehcuys6cuc\",\"parentId\":\"cgwagmaw6gin7xcq7nwew8rsynr\",\"rootId\":\"b7wnw9awd4pnefryhq51apbzb4c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Action Items\",\"fields\":{},\"createAt\":1641586443526,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"766mkfhc4u7dxzcc36nhfpmm5fy\",\"parentId\":\"ch798q5ucefyobf5bymgqjt4f3h\",\"rootId\":\"b7wnw9awd4pnefryhq51apbzb4c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"divider\",\"title\":\"\",\"fields\":{},\"createAt\":1641586677789,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"76w5qigi5ufgktcmmnw9ze88w5w\",\"parentId\":\"ch798q5ucefyobf5bymgqjt4f3h\",\"rootId\":\"b7wnw9awd4pnefryhq51apbzb4c\",\"createdBy\":\"mweioqznbife7p7aee7dr4wcxo\",\"modifiedBy\":\"mweioqznbife7p7aee7dr4wcxo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"\",\"fields\":{\"value\":false},\"createAt\":1641497389096,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"79wi7osb3utd3mjt9x57h7wpqfa\",\"parentId\":\"ch798q5ucefyobf5bymgqjt4f3h\",\"rootId\":\"b7wnw9awd4pnefryhq51apbzb4c\",\"createdBy\":\"mweioqznbife7p7aee7dr4wcxo\",\"modifiedBy\":\"mweioqznbife7p7aee7dr4wcxo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"\",\"fields\":{\"value\":false},\"createAt\":1641497390990,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7un1ccdg7qi8j3gxmkx5y3d9nhr\",\"parentId\":\"ch798q5ucefyobf5bymgqjt4f3h\",\"rootId\":\"b7wnw9awd4pnefryhq51apbzb4c\",\"createdBy\":\"mweioqznbife7p7aee7dr4wcxo\",\"modifiedBy\":\"mweioqznbife7p7aee7dr4wcxo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"\",\"fields\":{\"value\":false},\"createAt\":1641497382984,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"as3orhrci6tnutp5etbh6bzbgdy\",\"parentId\":\"ch798q5ucefyobf5bymgqjt4f3h\",\"rootId\":\"b7wnw9awd4pnefryhq51apbzb4c\",\"createdBy\":\"mweioqznbife7p7aee7dr4wcxo\",\"modifiedBy\":\"mweioqznbife7p7aee7dr4wcxo\",\"schema\":1,\"type\":\"text\",\"title\":\"# Action Items\",\"fields\":{},\"createAt\":1641497371429,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"axyitfq8ae38qictgcw34cmwueh\",\"parentId\":\"ch798q5ucefyobf5bymgqjt4f3h\",\"rootId\":\"b7wnw9awd4pnefryhq51apbzb4c\",\"createdBy\":\"mweioqznbife7p7aee7dr4wcxo\",\"modifiedBy\":\"mweioqznbife7p7aee7dr4wcxo\",\"schema\":1,\"type\":\"text\",\"title\":\"# Notes\\n*[Add meeting notes here]*\",\"fields\":{},\"createAt\":1641497348992,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"717fa85sx3f8f8m81f771s9hmwr\",\"parentId\":\"chki1tsudciyiiffrkqbcmp71rh\",\"rootId\":\"b7wnw9awd4pnefryhq51apbzb4c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"divider\",\"title\":\"\",\"fields\":{},\"createAt\":1641586368705,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7cfc4fkpz53gn9frciz9kui4p1c\",\"parentId\":\"chki1tsudciyiiffrkqbcmp71rh\",\"rootId\":\"b7wnw9awd4pnefryhq51apbzb4c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"\",\"fields\":{\"value\":false},\"createAt\":1641586479058,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7ifofmuwjzbdzppfxgtuai4i47h\",\"parentId\":\"chki1tsudciyiiffrkqbcmp71rh\",\"rootId\":\"b7wnw9awd4pnefryhq51apbzb4c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"\",\"fields\":{\"value\":false},\"createAt\":1641586476646,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7n37rxrn9uffdzrfi1xajotzjey\",\"parentId\":\"chki1tsudciyiiffrkqbcmp71rh\",\"rootId\":\"b7wnw9awd4pnefryhq51apbzb4c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"\",\"fields\":{\"value\":false},\"createAt\":1641586469805,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"a4se5s4ozx3ry8ec57w6z6jpk7y\",\"parentId\":\"chki1tsudciyiiffrkqbcmp71rh\",\"rootId\":\"b7wnw9awd4pnefryhq51apbzb4c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Action Items\",\"fields\":{},\"createAt\":1641586462602,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"a9ti13dqo8jfmjdmg97f5umfdyw\",\"parentId\":\"chki1tsudciyiiffrkqbcmp71rh\",\"rootId\":\"b7wnw9awd4pnefryhq51apbzb4c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Notes\\n*[Add meeting notes here]*\",\"fields\":{},\"createAt\":1641586365342,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"75zkot1f3sjb7ifysuzijitw91y\",\"parentId\":\"cmt5usr1mw3fom886t34ekjquay\",\"rootId\":\"b7wnw9awd4pnefryhq51apbzb4c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"\",\"fields\":{\"value\":false},\"createAt\":1641586514173,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7is5m8apdu3g53c8f6cz6sq7bmh\",\"parentId\":\"cmt5usr1mw3fom886t34ekjquay\",\"rootId\":\"b7wnw9awd4pnefryhq51apbzb4c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"\",\"fields\":{\"value\":false},\"createAt\":1641586516563,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7ni9593iz3pnb7xitoz3guwq5gh\",\"parentId\":\"cmt5usr1mw3fom886t34ekjquay\",\"rootId\":\"b7wnw9awd4pnefryhq51apbzb4c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"divider\",\"title\":\"\",\"fields\":{},\"createAt\":1641586383504,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7xsmzscbqn3ftudzqbb4w1q7t7e\",\"parentId\":\"cmt5usr1mw3fom886t34ekjquay\",\"rootId\":\"b7wnw9awd4pnefryhq51apbzb4c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"\",\"fields\":{\"value\":false},\"createAt\":1641586518624,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"agjkcro3x7irbxedyxrn8iuerrr\",\"parentId\":\"cmt5usr1mw3fom886t34ekjquay\",\"rootId\":\"b7wnw9awd4pnefryhq51apbzb4c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Action Items\",\"fields\":{},\"createAt\":1641586506048,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"aw53ugkfq8pyi9fjh9j6i4kdeiw\",\"parentId\":\"cmt5usr1mw3fom886t34ekjquay\",\"rootId\":\"b7wnw9awd4pnefryhq51apbzb4c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Notes\\n*[Add meeting notes here]*\",\"fields\":{},\"createAt\":1641586380592,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"76h9y996sdj8sbrbpqjo9d8cwto\",\"parentId\":\"cnqsbzg4b7brfddtyh7fc66atrw\",\"rootId\":\"b7wnw9awd4pnefryhq51apbzb4c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"divider\",\"title\":\"\",\"fields\":{},\"createAt\":1641586375619,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7hudywfzcwirkpcp1p5jhsfs83r\",\"parentId\":\"cnqsbzg4b7brfddtyh7fc66atrw\",\"rootId\":\"b7wnw9awd4pnefryhq51apbzb4c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"\",\"fields\":{\"value\":false},\"createAt\":1641586495344,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7jzw67ngdgtns8mstsg9g614oac\",\"parentId\":\"cnqsbzg4b7brfddtyh7fc66atrw\",\"rootId\":\"b7wnw9awd4pnefryhq51apbzb4c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"\",\"fields\":{\"value\":false},\"createAt\":1641586497433,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7y6zcyofmsfrbt899ts1ixr3iey\",\"parentId\":\"cnqsbzg4b7brfddtyh7fc66atrw\",\"rootId\":\"b7wnw9awd4pnefryhq51apbzb4c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"\",\"fields\":{\"value\":false},\"createAt\":1641586492877,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"aco8iu5jp7jbyzmzegwxkeusgzr\",\"parentId\":\"cnqsbzg4b7brfddtyh7fc66atrw\",\"rootId\":\"b7wnw9awd4pnefryhq51apbzb4c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Action Items\",\"fields\":{},\"createAt\":1641586487881,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"ao57n1fbtmt8q8bfk8ieqgzqt3a\",\"parentId\":\"cnqsbzg4b7brfddtyh7fc66atrw\",\"rootId\":\"b7wnw9awd4pnefryhq51apbzb4c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Notes\\n*[Add meeting notes here]*\",\"fields\":{},\"createAt\":1641586373252,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n"
  },
  {
    "path": "server/assets/templates-boardarchive/bbkpwdj8x17bdpdqd176n8ctoua/board.jsonl",
    "content": "{\"type\":\"board\",\"data\":{\"id\":\"bbkpwdj8x17bdpdqd176n8ctoua\",\"teamId\":\"qghzt68dq7bopgqamcnziq69ao\",\"channelId\":\"\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"type\":\"P\",\"minimumRole\":\"\",\"title\":\"Sales Pipeline CRM\",\"description\":\"Use this template to grow and keep track of your sales opportunities.\",\"icon\":\"📈\",\"showDescription\":true,\"isTemplate\":false,\"templateVersion\":0,\"properties\":{},\"cardProperties\":[{\"id\":\"a5hwxjsmkn6bak6r7uea5bx1kwc\",\"name\":\"Status\",\"options\":[{\"color\":\"propColorGray\",\"id\":\"akj61wc9yxdwyw3t6m8igyf9d5o\",\"value\":\"Lead\"},{\"color\":\"propColorYellow\",\"id\":\"aic89a5xox4wbppi6mbyx6ujsda\",\"value\":\"Qualified\"},{\"color\":\"propColorBrown\",\"id\":\"ah6ehh43rwj88jy4awensin8pcw\",\"value\":\"Meeting\"},{\"color\":\"propColorPurple\",\"id\":\"aprhd96zwi34o9cs4xyr3o9sf3c\",\"value\":\"Proposal\"},{\"color\":\"propColorOrange\",\"id\":\"axesd74yuxtbmw1sbk8ufax7z3a\",\"value\":\"Negotiation\"},{\"color\":\"propColorRed\",\"id\":\"a5txuiubumsmrs8gsd5jz5gc1oa\",\"value\":\"Lost\"},{\"color\":\"propColorGreen\",\"id\":\"acm9q494bcthyoqzmfogxxy5czy\",\"value\":\"Closed 🏆\"}],\"type\":\"select\"},{\"id\":\"aoheuj1f3mu6eehygr45fxa144y\",\"name\":\"Account Owner\",\"options\":[],\"type\":\"multiPerson\"},{\"id\":\"aro91wme9kfaie5ceu9qasmtcnw\",\"name\":\"Priority\",\"options\":[{\"color\":\"propColorRed\",\"id\":\"apjnaggwixchfxwiatfh7ey7uno\",\"value\":\"High 🔥\"},{\"color\":\"propColorYellow\",\"id\":\"apiswzj7uiwbh87z8dw8c6mturw\",\"value\":\"Medium\"},{\"color\":\"propColorBrown\",\"id\":\"auu9bfzqeuruyjwzzqgz7q8apuw\",\"value\":\"Low\"}],\"type\":\"select\"},{\"id\":\"ainpw47babwkpyj77ic4b9zq9xr\",\"name\":\"Company\",\"options\":[],\"type\":\"text\"},{\"id\":\"ahf43e44h3y8ftanqgzno9z7q7w\",\"name\":\"Estimated Value\",\"options\":[],\"type\":\"number\"},{\"id\":\"amahgyn9n4twaapg3jyxb6y4jic\",\"name\":\"Territory\",\"options\":[{\"color\":\"propColorBrown\",\"id\":\"ar6t1ttcumgfuqugg5o4g4mzrza\",\"value\":\"Western US\"},{\"color\":\"propColorGreen\",\"id\":\"asbwojkm7zb4ohrtij97jkdfgwe\",\"value\":\"Mountain West / Central US\"},{\"color\":\"propColorGray\",\"id\":\"aw8ppwtcrm8iwopdadje3ni196w\",\"value\":\"Mid-Atlantic / Southeast\"},{\"color\":\"propColorBlue\",\"id\":\"aafwyza5iwdcwcyfyj6bp7emufw\",\"value\":\"Northeast US / Canada\"},{\"color\":\"propColorPink\",\"id\":\"agw8rcb9uxyt3c7g6tq3r65fgqe\",\"value\":\"Eastern Europe\"},{\"color\":\"propColorPurple\",\"id\":\"as5bk6afoaaa7caewe1zc391sce\",\"value\":\"Central Europe / Africa\"},{\"color\":\"propColorYellow\",\"id\":\"a8fj94bka8z9t6p95qd3hn6t5re\",\"value\":\"Middle East\"},{\"color\":\"propColorOrange\",\"id\":\"arpxa3faaou9trt4zx5sh435gne\",\"value\":\"UK\"},{\"color\":\"propColorRed\",\"id\":\"azdidd5wze4kcxf8neefj3ctkyr\",\"value\":\"Asia\"},{\"color\":\"propColorGray\",\"id\":\"a4jn5mhqs3thknqf5opykntgsnc\",\"value\":\"Australia\"},{\"color\":\"propColorBrown\",\"id\":\"afjbgrecb7hp5owj7xh8u4w33tr\",\"value\":\"Latin America\"}],\"type\":\"select\"},{\"id\":\"abru6tz8uebdxy4skheqidh7zxy\",\"name\":\"Email\",\"options\":[],\"type\":\"email\"},{\"id\":\"a1438fbbhjeffkexmcfhnx99o1h\",\"name\":\"Phone\",\"options\":[],\"type\":\"phone\"},{\"id\":\"auhf91pm85f73swwidi4wid8jqe\",\"name\":\"Last Contact Date\",\"options\":[],\"type\":\"date\"},{\"id\":\"adtf1151chornmihz4xbgbk9exa\",\"name\":\"Expected Close\",\"options\":[],\"type\":\"date\"},{\"id\":\"aejo5tcmq54bauuueem9wc4fw4y\",\"name\":\"Close Probability\",\"options\":[],\"type\":\"text\"},{\"id\":\"amba7ot98fh7hwsx8jdcfst5g7h\",\"name\":\"Created Date\",\"options\":[],\"type\":\"createdTime\"}],\"createAt\":1667509277974,\"updateAt\":1667511890353,\"deleteAt\":0}}\n{\"type\":\"block\",\"data\":{\"id\":\"v76ciioz6ujd49phimp5jzomsww\",\"parentId\":\"\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"view\",\"title\":\"All Contacts\",\"fields\":{\"cardOrder\":[\"cyt3qdus94pg3fkxq4ojebyd5fr\",\"chew1d7kc3py3pj51qyqaiz6ade\",\"c91bktnpajfrrdpxs7ck1h7ziwh\",\"c77c6z9k9oigdpbocg8kxi7h8ah\",\"c9ciauq49ifdntc99rnehkkshpr\"],\"collapsedOptionIds\":[],\"columnCalculations\":{},\"columnWidths\":{\"__title\":240,\"a1438fbbhjeffkexmcfhnx99o1h\":151,\"a5hwxjsmkn6bak6r7uea5bx1kwc\":132,\"abru6tz8uebdxy4skheqidh7zxy\":247,\"adtf1151chornmihz4xbgbk9exa\":125,\"aejo5tcmq54bauuueem9wc4fw4y\":127,\"ahf43e44h3y8ftanqgzno9z7q7w\":129,\"ainpw47babwkpyj77ic4b9zq9xr\":157,\"amahgyn9n4twaapg3jyxb6y4jic\":224,\"amba7ot98fh7hwsx8jdcfst5g7h\":171,\"aoheuj1f3mu6eehygr45fxa144y\":130,\"auhf91pm85f73swwidi4wid8jqe\":157},\"defaultTemplateId\":\"cphg5tyix4irsipkcp9ujaj3gwh\",\"filter\":{\"filters\":[],\"operation\":\"and\"},\"hiddenOptionIds\":[],\"kanbanCalculations\":{},\"sortOptions\":[],\"viewType\":\"table\",\"visibleOptionIds\":[],\"visiblePropertyIds\":[\"a5hwxjsmkn6bak6r7uea5bx1kwc\",\"aoheuj1f3mu6eehygr45fxa144y\",\"aro91wme9kfaie5ceu9qasmtcnw\",\"ainpw47babwkpyj77ic4b9zq9xr\",\"ahf43e44h3y8ftanqgzno9z7q7w\",\"amahgyn9n4twaapg3jyxb6y4jic\",\"abru6tz8uebdxy4skheqidh7zxy\",\"a1438fbbhjeffkexmcfhnx99o1h\",\"auhf91pm85f73swwidi4wid8jqe\",\"adtf1151chornmihz4xbgbk9exa\",\"aejo5tcmq54bauuueem9wc4fw4y\",\"amba7ot98fh7hwsx8jdcfst5g7h\"]},\"createAt\":1667513494864,\"updateAt\":1667513802156,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"va9qcbagmdbfwb8xq5hawbq1a4r\",\"parentId\":\"\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"view\",\"title\":\"Pipeline Tracker\",\"fields\":{\"cardOrder\":[],\"collapsedOptionIds\":[],\"columnCalculations\":{},\"columnWidths\":{},\"defaultTemplateId\":\"\",\"filter\":{\"filters\":[],\"operation\":\"and\"},\"hiddenOptionIds\":[\"\"],\"kanbanCalculations\":{},\"sortOptions\":[],\"viewType\":\"board\",\"visibleOptionIds\":[\"akj61wc9yxdwyw3t6m8igyf9d5o\",\"aic89a5xox4wbppi6mbyx6ujsda\",\"ah6ehh43rwj88jy4awensin8pcw\",\"aprhd96zwi34o9cs4xyr3o9sf3c\",\"axesd74yuxtbmw1sbk8ufax7z3a\",\"a5txuiubumsmrs8gsd5jz5gc1oa\",\"acm9q494bcthyoqzmfogxxy5czy\"],\"visiblePropertyIds\":[\"aro91wme9kfaie5ceu9qasmtcnw\",\"amahgyn9n4twaapg3jyxb6y4jic\"]},\"createAt\":1667513379646,\"updateAt\":1667513589086,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"c77c6z9k9oigdpbocg8kxi7h8ah\",\"parentId\":\"bbkpwdj8x17bdpdqd176n8ctoua\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Jonathan Frazier\",\"fields\":{\"contentOrder\":[\"a7hc9n8oz47gybkxj4ssnwgi7ky\",\"a4bminunz1j8p3go9ixxdxpi4no\",\"71ibw3rrac7gcmgr4f16st7fz1c\",\"736fwfii9t7nafekshdjc6y4rge\",\"78aiw1o1wzibzzbiuo4e78p4pdr\",\"7ei858uzb9jye8yqo7j5nq1knaa\",\"7bai1o5z5fibiuxs7i9i8tti87w\",\"7cg1mxma4fjb67xmh1p7fyxekro\",\"76ry4rpfhq7ykprpmbidxdjr33o\",\"77gckzfpcmjb1bysnnqs7cnzseo\",\"7biw71wn9nfdgxd7fbh9un68zrc\",\"7iz6fjou66i8muqnhzb9pocff3e\"],\"icon\":\"🙎‍♂️\",\"isTemplate\":false,\"properties\":{\"a1438fbbhjeffkexmcfhnx99o1h\":\"(999) 123-5678\",\"a5hwxjsmkn6bak6r7uea5bx1kwc\":\"a5txuiubumsmrs8gsd5jz5gc1oa\",\"abru6tz8uebdxy4skheqidh7zxy\":\"jonathan.frazier@email.com\",\"aejo5tcmq54bauuueem9wc4fw4y\":\"0%\",\"ahf43e44h3y8ftanqgzno9z7q7w\":\"$800,000\",\"ainpw47babwkpyj77ic4b9zq9xr\":\"Ositions Inc.\",\"amahgyn9n4twaapg3jyxb6y4jic\":\"as5bk6afoaaa7caewe1zc391sce\",\"aro91wme9kfaie5ceu9qasmtcnw\":\"apiswzj7uiwbh87z8dw8c6mturw\",\"auhf91pm85f73swwidi4wid8jqe\":\"{\\\"from\\\":1669118400000}\"}},\"createAt\":1667513212844,\"updateAt\":1667513367839,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"c91bktnpajfrrdpxs7ck1h7ziwh\",\"parentId\":\"bbkpwdj8x17bdpdqd176n8ctoua\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Richard Guzman\",\"fields\":{\"contentOrder\":[\"a43kn6138w7boimnp5xe1khezjc\",\"ab6q8dsqh7ifhmk14ow4m9ytj3e\",\"73p7qyd8h13nq5fk54rqgbee7or\",\"7sqafho6jofdtjk5byn3yskq5ry\",\"7q6pi4f9dbtrpzbcm65hg9useso\",\"7976uafbzbjrmjya983z5bweesy\",\"7nu71kxnutbd6fdnmzjfbrczinw\",\"7j3q9i5a337ym3fdnigx3ifrhoh\",\"7jap7w5js9bfazgsa59skmocmhw\",\"7mndskgucj3g18ys7c6wjpub78o\",\"7kwmrfpx8pir5ieg5w8orbtq8ba\",\"7mwxoycnpq7nhix7r5x3wtmqd3h\"],\"icon\":\"👨‍💼\",\"isTemplate\":false,\"properties\":{\"a1438fbbhjeffkexmcfhnx99o1h\":\"(222) 123-1234\",\"a5hwxjsmkn6bak6r7uea5bx1kwc\":\"axesd74yuxtbmw1sbk8ufax7z3a\",\"abru6tz8uebdxy4skheqidh7zxy\":\"richard.guzman@email.com\",\"adtf1151chornmihz4xbgbk9exa\":\"{\\\"from\\\":1681992000000}\",\"aejo5tcmq54bauuueem9wc4fw4y\":\"80%\",\"ahf43e44h3y8ftanqgzno9z7q7w\":\"$3,200,000\",\"ainpw47babwkpyj77ic4b9zq9xr\":\"Afformance Ltd.\",\"amahgyn9n4twaapg3jyxb6y4jic\":\"ar6t1ttcumgfuqugg5o4g4mzrza\",\"aro91wme9kfaie5ceu9qasmtcnw\":\"apjnaggwixchfxwiatfh7ey7uno\",\"auhf91pm85f73swwidi4wid8jqe\":\"{\\\"from\\\":1667476800000}\"}},\"createAt\":1667512379637,\"updateAt\":1667512604683,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"c9ciauq49ifdntc99rnehkkshpr\",\"parentId\":\"bbkpwdj8x17bdpdqd176n8ctoua\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Byron Cole\",\"fields\":{\"contentOrder\":[\"a4wwsynhjafb4dgbubda18ho3fr\",\"a1ag6b4hwkibbbbxdmse74cw3ur\",\"767qdn4uhbbrb8gyq4x7w1rfcoc\",\"7ad16jbhbcpro78cueumekyqjyy\",\"7xbj8zr1jxfnxfkyfyccb84ddeo\",\"7sggexapxebb1zk9oqta6gcwsda\",\"7y3ncauhatfrg7nzyr67twe36wc\",\"74ach4ckw53grfygwp8m6wbj4ya\",\"7agc943grqtgidb3e49dkqumrce\",\"7owy1izqn1if55r5hc3fgu8fada\",\"7zcbwgrw5apd4frn6uxd386rktc\",\"7zijtxs3enjy5frzc4zb6937b3w\"],\"icon\":\"🤵\",\"isTemplate\":false,\"properties\":{\"a1438fbbhjeffkexmcfhnx99o1h\":\"(333) 123-1234\",\"a5hwxjsmkn6bak6r7uea5bx1kwc\":\"acm9q494bcthyoqzmfogxxy5czy\",\"abru6tz8uebdxy4skheqidh7zxy\":\"byron.cole@email.com\",\"adtf1151chornmihz4xbgbk9exa\":\"{\\\"from\\\":1667563200000}\",\"aejo5tcmq54bauuueem9wc4fw4y\":\"100%\",\"ahf43e44h3y8ftanqgzno9z7q7w\":\"$500,000\",\"ainpw47babwkpyj77ic4b9zq9xr\":\"Helx Industries\",\"amahgyn9n4twaapg3jyxb6y4jic\":\"aafwyza5iwdcwcyfyj6bp7emufw\",\"aro91wme9kfaie5ceu9qasmtcnw\":\"apjnaggwixchfxwiatfh7ey7uno\",\"auhf91pm85f73swwidi4wid8jqe\":\"{\\\"from\\\":1667822400000}\"}},\"createAt\":1667512692248,\"updateAt\":1667512904723,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"chew1d7kc3py3pj51qyqaiz6ade\",\"parentId\":\"bbkpwdj8x17bdpdqd176n8ctoua\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Caitlyn Russel\",\"fields\":{\"contentOrder\":[\"atgpetmwdubb5jkugcb6jm9pzyo\",\"acod8woq6zjbzmc1hz8qkfxyi1h\",\"7s47d7rzh4pnw5rcnpjysxg6duh\",\"7s9smhppjff87tndwawqwdmfryo\",\"7t3ib1amo7fgzbmhg4tkzqustcy\",\"7s5bgtoajcbnd8rrc5bxzabdcyw\",\"7nze85jfmobfm8j8xfmrbdwyrfa\",\"7jrwix8rkbtb5bdek79mtat8w1c\",\"7c1iwiqsi1iddpfzqisbkubjxhh\",\"7tp1rgey147nnfjuose7418oioh\",\"7ftxm79a1e7nuxpb913aqphoqbo\",\"799cbodnfr3ydfjp53die7egd1e\"],\"icon\":\"🧑‍💼\",\"isTemplate\":false,\"properties\":{\"a1438fbbhjeffkexmcfhnx99o1h\":\"(111) 123-1234\",\"a5hwxjsmkn6bak6r7uea5bx1kwc\":\"ah6ehh43rwj88jy4awensin8pcw\",\"abru6tz8uebdxy4skheqidh7zxy\":\"caitlyn.russel@email.com\",\"adtf1151chornmihz4xbgbk9exa\":\"{\\\"from\\\":1689336000000}\",\"aejo5tcmq54bauuueem9wc4fw4y\":\"20%\",\"ahf43e44h3y8ftanqgzno9z7q7w\":\"$250,000\",\"ainpw47babwkpyj77ic4b9zq9xr\":\"Liminary Corp.\",\"amahgyn9n4twaapg3jyxb6y4jic\":\"aafwyza5iwdcwcyfyj6bp7emufw\",\"aro91wme9kfaie5ceu9qasmtcnw\":\"apiswzj7uiwbh87z8dw8c6mturw\",\"auhf91pm85f73swwidi4wid8jqe\":\"{\\\"from\\\":1668168000000}\"}},\"createAt\":1667509567800,\"updateAt\":1667512683024,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"cphg5tyix4irsipkcp9ujaj3gwh\",\"parentId\":\"bbkpwdj8x17bdpdqd176n8ctoua\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"New Prospect\",\"fields\":{\"contentOrder\":[\"atw8pz7bqgp877pd5714jbpqrsh\",\"azwoek6rwfpfqiruig13owyyagr\",\"71735rqboe3rkxypssssddjykkc\",\"7jgf4cownfiy7xpaznxdsnyze9a\",\"74khjujy4hir4zmer4hkj1gcckh\",\"768ut9xkqipgf9fk6ub146spu5e\",\"7jryotoo5wig9bdt3kh1fmgm5qw\",\"7p7hz5ky15jgrirb64533xzsquo\",\"7c9cy5ohjd3b85xkee539zw9owh\",\"7dsynp6qf8tdtjpcqsxfyuqyzmo\",\"7kxzdhjtx8pdazm7bufusybwygo\",\"7h1zyk7thz7gx3r5degq6qorjay\"],\"icon\":\"👤\",\"isTemplate\":true,\"properties\":{\"a5hwxjsmkn6bak6r7uea5bx1kwc\":\"akj61wc9yxdwyw3t6m8igyf9d5o\"}},\"createAt\":1667513652330,\"updateAt\":1667513749765,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"cyt3qdus94pg3fkxq4ojebyd5fr\",\"parentId\":\"bbkpwdj8x17bdpdqd176n8ctoua\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Shelby Olson\",\"fields\":{\"contentOrder\":[\"ay46giuhekby5tmrnw4abnz97pw\",\"awugzceoocjdnmn1ab6srb5cc6r\",\"7bncmywm3h38s7ndpcu9sytfffy\",\"7zmoekhdb4i848p313eh1okp78c\",\"7xxy1eewp8jdxpcouho8jq7ed4w\",\"7rgodjyks6jrtprbeizusduat4c\",\"7u5qxs77u57bc8bd33ug5aa91rw\",\"7jexsw3nutb8m3x6eyqio7gtcxr\",\"7abw4xifxubn1urheakij9kjc5e\",\"7r47h1d8fjfrpzkfzyxha44wrqe\",\"7yq4oh69547rm9mp3eqg9zqzoxw\",\"7yhpeqyesfif188x4pabwurnw4o\"],\"icon\":\"🙎‍♀️\",\"isTemplate\":false,\"properties\":{\"a1438fbbhjeffkexmcfhnx99o1h\":\"(111) 321-5678\",\"a5hwxjsmkn6bak6r7uea5bx1kwc\":\"akj61wc9yxdwyw3t6m8igyf9d5o\",\"abru6tz8uebdxy4skheqidh7zxy\":\"shelby.olson@email.com\",\"ahf43e44h3y8ftanqgzno9z7q7w\":\"$30,000\",\"ainpw47babwkpyj77ic4b9zq9xr\":\"Kadera Global\",\"amahgyn9n4twaapg3jyxb6y4jic\":\"ar6t1ttcumgfuqugg5o4g4mzrza\",\"aro91wme9kfaie5ceu9qasmtcnw\":\"auu9bfzqeuruyjwzzqgz7q8apuw\",\"auhf91pm85f73swwidi4wid8jqe\":\"{\\\"from\\\":1669291200000}\"}},\"createAt\":1667512982640,\"updateAt\":1667513171727,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"vg19cqh9bnbfq5edwq4kep3ssxr\",\"parentId\":\"bzwb99zf498tsm7mjqbiy7g81ze\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"view\",\"title\":\"Open Deals\",\"fields\":{\"cardOrder\":[\"chew1d7kc3py3pj51qyqaiz6ade\"],\"collapsedOptionIds\":[],\"columnCalculations\":{},\"columnWidths\":{},\"defaultTemplateId\":\"\",\"filter\":{\"filters\":[{\"condition\":\"includes\",\"propertyId\":\"a5hwxjsmkn6bak6r7uea5bx1kwc\",\"values\":[\"akj61wc9yxdwyw3t6m8igyf9d5o\",\"aic89a5xox4wbppi6mbyx6ujsda\",\"ah6ehh43rwj88jy4awensin8pcw\",\"aprhd96zwi34o9cs4xyr3o9sf3c\",\"axesd74yuxtbmw1sbk8ufax7z3a\"]}],\"operation\":\"and\"},\"groupById\":\"aro91wme9kfaie5ceu9qasmtcnw\",\"hiddenOptionIds\":[],\"kanbanCalculations\":{},\"sortOptions\":[],\"viewType\":\"board\",\"visibleOptionIds\":[\"apjnaggwixchfxwiatfh7ey7uno\",\"apiswzj7uiwbh87z8dw8c6mturw\",\"auu9bfzqeuruyjwzzqgz7q8apuw\",\"\"],\"visiblePropertyIds\":[]},\"createAt\":1667509277984,\"updateAt\":1667513521431,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"71ibw3rrac7gcmgr4f16st7fz1c\",\"parentId\":\"c77c6z9k9oigdpbocg8kxi7h8ah\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Send initial email\",\"fields\":{\"value\":true},\"createAt\":1667513212852,\"updateAt\":1667513212852,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"736fwfii9t7nafekshdjc6y4rge\",\"parentId\":\"c77c6z9k9oigdpbocg8kxi7h8ah\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Send follow-up email\",\"fields\":{\"value\":true},\"createAt\":1667513212861,\"updateAt\":1667513341391,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"76ry4rpfhq7ykprpmbidxdjr33o\",\"parentId\":\"c77c6z9k9oigdpbocg8kxi7h8ah\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Send proposal\",\"fields\":{\"value\":true},\"createAt\":1667513212920,\"updateAt\":1667513348088,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"77gckzfpcmjb1bysnnqs7cnzseo\",\"parentId\":\"c77c6z9k9oigdpbocg8kxi7h8ah\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Finalize contract\",\"fields\":{},\"createAt\":1667513212930,\"updateAt\":1667513212930,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"78aiw1o1wzibzzbiuo4e78p4pdr\",\"parentId\":\"c77c6z9k9oigdpbocg8kxi7h8ah\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Schedule initial sales call\",\"fields\":{\"value\":true},\"createAt\":1667513212869,\"updateAt\":1667513342078,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7bai1o5z5fibiuxs7i9i8tti87w\",\"parentId\":\"c77c6z9k9oigdpbocg8kxi7h8ah\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Schedule demo\",\"fields\":{\"value\":true},\"createAt\":1667513212887,\"updateAt\":1667513344670,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7biw71wn9nfdgxd7fbh9un68zrc\",\"parentId\":\"c77c6z9k9oigdpbocg8kxi7h8ah\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Hand-off to customer success\",\"fields\":{},\"createAt\":1667513212939,\"updateAt\":1667513212939,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7cg1mxma4fjb67xmh1p7fyxekro\",\"parentId\":\"c77c6z9k9oigdpbocg8kxi7h8ah\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Follow up after demo\",\"fields\":{\"value\":true},\"createAt\":1667513212912,\"updateAt\":1667513345694,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7ei858uzb9jye8yqo7j5nq1knaa\",\"parentId\":\"c77c6z9k9oigdpbocg8kxi7h8ah\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Schedule follow-up sales call\",\"fields\":{\"value\":true},\"createAt\":1667513212878,\"updateAt\":1667513343116,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7iz6fjou66i8muqnhzb9pocff3e\",\"parentId\":\"c77c6z9k9oigdpbocg8kxi7h8ah\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Post-sales follow up\",\"fields\":{},\"createAt\":1667513212947,\"updateAt\":1667513212947,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"a4bminunz1j8p3go9ixxdxpi4no\",\"parentId\":\"c77c6z9k9oigdpbocg8kxi7h8ah\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Checklist\",\"fields\":{},\"createAt\":1667513212903,\"updateAt\":1667513212903,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"a7hc9n8oz47gybkxj4ssnwgi7ky\",\"parentId\":\"c77c6z9k9oigdpbocg8kxi7h8ah\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Notes\\nLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Duis fermentum aliquet massa in ornare. Pellentesque mollis nisl efficitur, eleifend nisi congue, scelerisque nunc. Aliquam lorem quam, commodo id nunc nec, congue bibendum velit. Vivamus sed mattis libero, et iaculis diam. Suspendisse euismod hendrerit nisl, quis ornare ipsum gravida in.\",\"fields\":{},\"createAt\":1667513212895,\"updateAt\":1667513212895,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"73p7qyd8h13nq5fk54rqgbee7or\",\"parentId\":\"c91bktnpajfrrdpxs7ck1h7ziwh\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Send initial email\",\"fields\":{\"value\":true},\"createAt\":1667512379656,\"updateAt\":1667512968074,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7976uafbzbjrmjya983z5bweesy\",\"parentId\":\"c91bktnpajfrrdpxs7ck1h7ziwh\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Schedule follow-up sales call\",\"fields\":{\"value\":true},\"createAt\":1667512379686,\"updateAt\":1667512970061,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7j3q9i5a337ym3fdnigx3ifrhoh\",\"parentId\":\"c91bktnpajfrrdpxs7ck1h7ziwh\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Follow up after demo\",\"fields\":{\"value\":true},\"createAt\":1667512379752,\"updateAt\":1667512975240,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7jap7w5js9bfazgsa59skmocmhw\",\"parentId\":\"c91bktnpajfrrdpxs7ck1h7ziwh\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Send proposal\",\"fields\":{\"value\":true},\"createAt\":1667512379772,\"updateAt\":1667512975857,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7kwmrfpx8pir5ieg5w8orbtq8ba\",\"parentId\":\"c91bktnpajfrrdpxs7ck1h7ziwh\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Hand-off to customer success\",\"fields\":{},\"createAt\":1667512379805,\"updateAt\":1667512379805,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7mndskgucj3g18ys7c6wjpub78o\",\"parentId\":\"c91bktnpajfrrdpxs7ck1h7ziwh\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Finalize contract\",\"fields\":{},\"createAt\":1667512379792,\"updateAt\":1667512379792,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7mwxoycnpq7nhix7r5x3wtmqd3h\",\"parentId\":\"c91bktnpajfrrdpxs7ck1h7ziwh\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Post-sales follow up\",\"fields\":{},\"createAt\":1667512379814,\"updateAt\":1667512379814,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7nu71kxnutbd6fdnmzjfbrczinw\",\"parentId\":\"c91bktnpajfrrdpxs7ck1h7ziwh\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Schedule demo\",\"fields\":{\"value\":true},\"createAt\":1667512379695,\"updateAt\":1667512973476,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7q6pi4f9dbtrpzbcm65hg9useso\",\"parentId\":\"c91bktnpajfrrdpxs7ck1h7ziwh\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Schedule initial sales call\",\"fields\":{\"value\":true},\"createAt\":1667512379677,\"updateAt\":1667512969519,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7sqafho6jofdtjk5byn3yskq5ry\",\"parentId\":\"c91bktnpajfrrdpxs7ck1h7ziwh\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Send follow-up email\",\"fields\":{\"value\":true},\"createAt\":1667512379668,\"updateAt\":1667512968798,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"a43kn6138w7boimnp5xe1khezjc\",\"parentId\":\"c91bktnpajfrrdpxs7ck1h7ziwh\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Notes\\nLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Duis fermentum aliquet massa in ornare. Pellentesque mollis nisl efficitur, eleifend nisi congue, scelerisque nunc. Aliquam lorem quam, commodo id nunc nec, congue bibendum velit. Vivamus sed mattis libero, et iaculis diam. Suspendisse euismod hendrerit nisl, quis ornare ipsum gravida in.\",\"fields\":{},\"createAt\":1667512379704,\"updateAt\":1667512379704,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"ab6q8dsqh7ifhmk14ow4m9ytj3e\",\"parentId\":\"c91bktnpajfrrdpxs7ck1h7ziwh\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Checklist\",\"fields\":{},\"createAt\":1667512379729,\"updateAt\":1667512379728,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"74ach4ckw53grfygwp8m6wbj4ya\",\"parentId\":\"c9ciauq49ifdntc99rnehkkshpr\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Follow up after demo\",\"fields\":{\"value\":true},\"createAt\":1667512692319,\"updateAt\":1667512917248,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"767qdn4uhbbrb8gyq4x7w1rfcoc\",\"parentId\":\"c9ciauq49ifdntc99rnehkkshpr\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Send initial email\",\"fields\":{\"value\":true},\"createAt\":1667512692257,\"updateAt\":1667512911931,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7ad16jbhbcpro78cueumekyqjyy\",\"parentId\":\"c9ciauq49ifdntc99rnehkkshpr\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Send follow-up email\",\"fields\":{\"value\":true},\"createAt\":1667512692265,\"updateAt\":1667512912836,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7agc943grqtgidb3e49dkqumrce\",\"parentId\":\"c9ciauq49ifdntc99rnehkkshpr\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Send proposal\",\"fields\":{\"value\":true},\"createAt\":1667512692327,\"updateAt\":1667512919194,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7owy1izqn1if55r5hc3fgu8fada\",\"parentId\":\"c9ciauq49ifdntc99rnehkkshpr\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Finalize contract\",\"fields\":{\"value\":true},\"createAt\":1667512692335,\"updateAt\":1667512920115,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7sggexapxebb1zk9oqta6gcwsda\",\"parentId\":\"c9ciauq49ifdntc99rnehkkshpr\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Schedule follow-up sales call\",\"fields\":{\"value\":true},\"createAt\":1667512692283,\"updateAt\":1667512914481,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7xbj8zr1jxfnxfkyfyccb84ddeo\",\"parentId\":\"c9ciauq49ifdntc99rnehkkshpr\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Schedule initial sales call\",\"fields\":{\"value\":true},\"createAt\":1667512692273,\"updateAt\":1667512913567,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7y3ncauhatfrg7nzyr67twe36wc\",\"parentId\":\"c9ciauq49ifdntc99rnehkkshpr\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Schedule demo\",\"fields\":{\"value\":true},\"createAt\":1667512692292,\"updateAt\":1667512915496,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7zcbwgrw5apd4frn6uxd386rktc\",\"parentId\":\"c9ciauq49ifdntc99rnehkkshpr\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Hand-off to customer success\",\"fields\":{\"value\":true},\"createAt\":1667512692344,\"updateAt\":1667512920721,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7zijtxs3enjy5frzc4zb6937b3w\",\"parentId\":\"c9ciauq49ifdntc99rnehkkshpr\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Post-sales follow up\",\"fields\":{\"value\":true},\"createAt\":1667512692353,\"updateAt\":1667512922687,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"a1ag6b4hwkibbbbxdmse74cw3ur\",\"parentId\":\"c9ciauq49ifdntc99rnehkkshpr\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Checklist\",\"fields\":{},\"createAt\":1667512692310,\"updateAt\":1667512692310,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"a4wwsynhjafb4dgbubda18ho3fr\",\"parentId\":\"c9ciauq49ifdntc99rnehkkshpr\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Notes\\nLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Duis fermentum aliquet massa in ornare. Pellentesque mollis nisl efficitur, eleifend nisi congue, scelerisque nunc. Aliquam lorem quam, commodo id nunc nec, congue bibendum velit. Vivamus sed mattis libero, et iaculis diam. Suspendisse euismod hendrerit nisl, quis ornare ipsum gravida in.\",\"fields\":{},\"createAt\":1667512692301,\"updateAt\":1667512692301,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"799cbodnfr3ydfjp53die7egd1e\",\"parentId\":\"chew1d7kc3py3pj51qyqaiz6ade\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Post-sales follow up\",\"fields\":{},\"createAt\":1667512344379,\"updateAt\":1667512354748,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7c1iwiqsi1iddpfzqisbkubjxhh\",\"parentId\":\"chew1d7kc3py3pj51qyqaiz6ade\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Send proposal\",\"fields\":{},\"createAt\":1667512215518,\"updateAt\":1667512224971,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7ftxm79a1e7nuxpb913aqphoqbo\",\"parentId\":\"chew1d7kc3py3pj51qyqaiz6ade\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Hand-off to customer success\",\"fields\":{},\"createAt\":1667512251753,\"updateAt\":1667512267186,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7jrwix8rkbtb5bdek79mtat8w1c\",\"parentId\":\"chew1d7kc3py3pj51qyqaiz6ade\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Follow up after demo\",\"fields\":{},\"createAt\":1667512204105,\"updateAt\":1667512287236,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7nze85jfmobfm8j8xfmrbdwyrfa\",\"parentId\":\"chew1d7kc3py3pj51qyqaiz6ade\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Schedule demo\",\"fields\":{\"value\":true},\"createAt\":1667510597027,\"updateAt\":1667512961521,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7s47d7rzh4pnw5rcnpjysxg6duh\",\"parentId\":\"chew1d7kc3py3pj51qyqaiz6ade\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Send initial email\",\"fields\":{\"value\":true},\"createAt\":1667510557630,\"updateAt\":1667512956967,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7s5bgtoajcbnd8rrc5bxzabdcyw\",\"parentId\":\"chew1d7kc3py3pj51qyqaiz6ade\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Schedule follow-up sales call\",\"fields\":{\"value\":true},\"createAt\":1667510586823,\"updateAt\":1667512960547,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7s9smhppjff87tndwawqwdmfryo\",\"parentId\":\"chew1d7kc3py3pj51qyqaiz6ade\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Send follow-up email\",\"fields\":{\"value\":true},\"createAt\":1667510564441,\"updateAt\":1667512958081,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7t3ib1amo7fgzbmhg4tkzqustcy\",\"parentId\":\"chew1d7kc3py3pj51qyqaiz6ade\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Schedule initial sales call\",\"fields\":{\"value\":true},\"createAt\":1667510573106,\"updateAt\":1667512959302,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7tp1rgey147nnfjuose7418oioh\",\"parentId\":\"chew1d7kc3py3pj51qyqaiz6ade\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Finalize contract\",\"fields\":{},\"createAt\":1667512225170,\"updateAt\":1667512251543,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"acod8woq6zjbzmc1hz8qkfxyi1h\",\"parentId\":\"chew1d7kc3py3pj51qyqaiz6ade\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Checklist\",\"fields\":{},\"createAt\":1667512186838,\"updateAt\":1667512192833,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"atgpetmwdubb5jkugcb6jm9pzyo\",\"parentId\":\"chew1d7kc3py3pj51qyqaiz6ade\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Notes\\nLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Duis fermentum aliquet massa in ornare. Pellentesque mollis nisl efficitur, eleifend nisi congue, scelerisque nunc. Aliquam lorem quam, commodo id nunc nec, congue bibendum velit. Vivamus sed mattis libero, et iaculis diam. Suspendisse euismod hendrerit nisl, quis ornare ipsum gravida in.\",\"fields\":{},\"createAt\":1667512110036,\"updateAt\":1667512180024,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"71735rqboe3rkxypssssddjykkc\",\"parentId\":\"cphg5tyix4irsipkcp9ujaj3gwh\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Send initial email\",\"fields\":{\"value\":false},\"createAt\":1667513652337,\"updateAt\":1667513703739,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"74khjujy4hir4zmer4hkj1gcckh\",\"parentId\":\"cphg5tyix4irsipkcp9ujaj3gwh\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Schedule initial sales call\",\"fields\":{\"value\":false},\"createAt\":1667513652354,\"updateAt\":1667513652354,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"768ut9xkqipgf9fk6ub146spu5e\",\"parentId\":\"cphg5tyix4irsipkcp9ujaj3gwh\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Schedule follow-up sales call\",\"fields\":{\"value\":false},\"createAt\":1667513652368,\"updateAt\":1667513652368,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7c9cy5ohjd3b85xkee539zw9owh\",\"parentId\":\"cphg5tyix4irsipkcp9ujaj3gwh\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Send proposal\",\"fields\":{},\"createAt\":1667513652448,\"updateAt\":1667513652448,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7dsynp6qf8tdtjpcqsxfyuqyzmo\",\"parentId\":\"cphg5tyix4irsipkcp9ujaj3gwh\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Finalize contract\",\"fields\":{},\"createAt\":1667513652464,\"updateAt\":1667513652464,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7h1zyk7thz7gx3r5degq6qorjay\",\"parentId\":\"cphg5tyix4irsipkcp9ujaj3gwh\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Post-sales follow up\",\"fields\":{},\"createAt\":1667513652495,\"updateAt\":1667513652495,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7jgf4cownfiy7xpaznxdsnyze9a\",\"parentId\":\"cphg5tyix4irsipkcp9ujaj3gwh\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Send follow-up email\",\"fields\":{\"value\":false},\"createAt\":1667513652344,\"updateAt\":1667513652344,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7jryotoo5wig9bdt3kh1fmgm5qw\",\"parentId\":\"cphg5tyix4irsipkcp9ujaj3gwh\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Schedule demo\",\"fields\":{\"value\":false},\"createAt\":1667513652384,\"updateAt\":1667513652384,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7kxzdhjtx8pdazm7bufusybwygo\",\"parentId\":\"cphg5tyix4irsipkcp9ujaj3gwh\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Hand-off to customer success\",\"fields\":{},\"createAt\":1667513652486,\"updateAt\":1667513652486,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7p7hz5ky15jgrirb64533xzsquo\",\"parentId\":\"cphg5tyix4irsipkcp9ujaj3gwh\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Follow up after demo\",\"fields\":{},\"createAt\":1667513652428,\"updateAt\":1667513652428,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"atw8pz7bqgp877pd5714jbpqrsh\",\"parentId\":\"cphg5tyix4irsipkcp9ujaj3gwh\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Notes\\n[Enter notes here...]\",\"fields\":{},\"createAt\":1667513652402,\"updateAt\":1667513741067,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"azwoek6rwfpfqiruig13owyyagr\",\"parentId\":\"cphg5tyix4irsipkcp9ujaj3gwh\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Checklist\",\"fields\":{},\"createAt\":1667513652416,\"updateAt\":1667513652416,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"777h45bs9xffj3ecpe9ti9jqdar\",\"parentId\":\"ct59gu9j4cpnrtjcpyn3a5okdqa\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Hand-off to customer success\",\"fields\":{},\"createAt\":1667513758151,\"updateAt\":1667513758151,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"78sze8whs5i8htgtsuwqc81agjr\",\"parentId\":\"ct59gu9j4cpnrtjcpyn3a5okdqa\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Send initial email\",\"fields\":{\"value\":false},\"createAt\":1667513758058,\"updateAt\":1667513758058,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7brzhoqztxpyg8jtqrd7c6dqtie\",\"parentId\":\"ct59gu9j4cpnrtjcpyn3a5okdqa\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Post-sales follow up\",\"fields\":{},\"createAt\":1667513758161,\"updateAt\":1667513758161,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7dejdqngn43rp3phmg64ditmyrr\",\"parentId\":\"ct59gu9j4cpnrtjcpyn3a5okdqa\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Finalize contract\",\"fields\":{},\"createAt\":1667513758142,\"updateAt\":1667513758142,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7dpbn45wo63r97fgb5356od9jyr\",\"parentId\":\"ct59gu9j4cpnrtjcpyn3a5okdqa\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Send follow-up email\",\"fields\":{\"value\":false},\"createAt\":1667513758067,\"updateAt\":1667513758067,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7gku1jfqfppb358a3q6y1b8sb7a\",\"parentId\":\"ct59gu9j4cpnrtjcpyn3a5okdqa\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Schedule follow-up sales call\",\"fields\":{\"value\":false},\"createAt\":1667513758086,\"updateAt\":1667513758086,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7p6xqywuxwifsfb67bc5zbhu1ny\",\"parentId\":\"ct59gu9j4cpnrtjcpyn3a5okdqa\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Send proposal\",\"fields\":{},\"createAt\":1667513758133,\"updateAt\":1667513758133,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7qfj7p7xb4fgp9y4a8sp13ixiny\",\"parentId\":\"ct59gu9j4cpnrtjcpyn3a5okdqa\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Follow up after demo\",\"fields\":{},\"createAt\":1667513758124,\"updateAt\":1667513758124,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7xrfub5nuxtb5pbuwrwtjbtekdw\",\"parentId\":\"ct59gu9j4cpnrtjcpyn3a5okdqa\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Schedule initial sales call\",\"fields\":{\"value\":false},\"createAt\":1667513758077,\"updateAt\":1667513758077,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7ynnwzqecx7f8t8yci1htkikude\",\"parentId\":\"ct59gu9j4cpnrtjcpyn3a5okdqa\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Schedule demo\",\"fields\":{\"value\":false},\"createAt\":1667513758096,\"updateAt\":1667513758096,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"as8xstqmiobdr7ykjc4rb9pfcdh\",\"parentId\":\"ct59gu9j4cpnrtjcpyn3a5okdqa\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Checklist\",\"fields\":{},\"createAt\":1667513758114,\"updateAt\":1667513758114,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"azxp9y8hk33guugjq6iba7whj6h\",\"parentId\":\"ct59gu9j4cpnrtjcpyn3a5okdqa\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Notes\\n[Enter notes here...]\",\"fields\":{},\"createAt\":1667513758104,\"updateAt\":1667513758104,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7abw4xifxubn1urheakij9kjc5e\",\"parentId\":\"cyt3qdus94pg3fkxq4ojebyd5fr\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Send proposal\",\"fields\":{},\"createAt\":1667512982703,\"updateAt\":1667512982703,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7bncmywm3h38s7ndpcu9sytfffy\",\"parentId\":\"cyt3qdus94pg3fkxq4ojebyd5fr\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Send initial email\",\"fields\":{\"value\":true},\"createAt\":1667512982648,\"updateAt\":1667512982648,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7jexsw3nutb8m3x6eyqio7gtcxr\",\"parentId\":\"cyt3qdus94pg3fkxq4ojebyd5fr\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Follow up after demo\",\"fields\":{},\"createAt\":1667512982697,\"updateAt\":1667512982697,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7r47h1d8fjfrpzkfzyxha44wrqe\",\"parentId\":\"cyt3qdus94pg3fkxq4ojebyd5fr\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Finalize contract\",\"fields\":{},\"createAt\":1667512982712,\"updateAt\":1667512982712,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7rgodjyks6jrtprbeizusduat4c\",\"parentId\":\"cyt3qdus94pg3fkxq4ojebyd5fr\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Schedule follow-up sales call\",\"fields\":{\"value\":false},\"createAt\":1667512982669,\"updateAt\":1667513178427,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7u5qxs77u57bc8bd33ug5aa91rw\",\"parentId\":\"cyt3qdus94pg3fkxq4ojebyd5fr\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Schedule demo\",\"fields\":{\"value\":false},\"createAt\":1667512982675,\"updateAt\":1667513176256,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7xxy1eewp8jdxpcouho8jq7ed4w\",\"parentId\":\"cyt3qdus94pg3fkxq4ojebyd5fr\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Schedule initial sales call\",\"fields\":{\"value\":false},\"createAt\":1667512982661,\"updateAt\":1667513177889,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7yhpeqyesfif188x4pabwurnw4o\",\"parentId\":\"cyt3qdus94pg3fkxq4ojebyd5fr\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Post-sales follow up\",\"fields\":{},\"createAt\":1667512982725,\"updateAt\":1667512982725,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7yq4oh69547rm9mp3eqg9zqzoxw\",\"parentId\":\"cyt3qdus94pg3fkxq4ojebyd5fr\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Hand-off to customer success\",\"fields\":{},\"createAt\":1667512982718,\"updateAt\":1667512982718,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7zmoekhdb4i848p313eh1okp78c\",\"parentId\":\"cyt3qdus94pg3fkxq4ojebyd5fr\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Send follow-up email\",\"fields\":{\"value\":false},\"createAt\":1667512982655,\"updateAt\":1667513179761,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"awugzceoocjdnmn1ab6srb5cc6r\",\"parentId\":\"cyt3qdus94pg3fkxq4ojebyd5fr\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Checklist\",\"fields\":{},\"createAt\":1667512982690,\"updateAt\":1667512982690,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"ay46giuhekby5tmrnw4abnz97pw\",\"parentId\":\"cyt3qdus94pg3fkxq4ojebyd5fr\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Notes\\nLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Duis fermentum aliquet massa in ornare. Pellentesque mollis nisl efficitur, eleifend nisi congue, scelerisque nunc. Aliquam lorem quam, commodo id nunc nec, congue bibendum velit. Vivamus sed mattis libero, et iaculis diam. Suspendisse euismod hendrerit nisl, quis ornare ipsum gravida in.\",\"fields\":{},\"createAt\":1667512982683,\"updateAt\":1667512982683,\"deleteAt\":0,\"boardId\":\"bbkpwdj8x17bdpdqd176n8ctoua\"}}\n"
  },
  {
    "path": "server/assets/templates-boardarchive/bbn1888mprfrm5fjw9f1je9x3xo/board.jsonl",
    "content": "{\"type\":\"block\",\"data\":{\"id\":\"bbn1888mprfrm5fjw9f1je9x3xo\",\"parentId\":\"\",\"rootId\":\"bbn1888mprfrm5fjw9f1je9x3xo\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"board\",\"title\":\"Personal Tasks (NEW)\",\"fields\":{\"cardProperties\":[{\"id\":\"a9zf59u8x1rf4ywctpcqama7tio\",\"name\":\"Occurrence\",\"options\":[{\"color\":\"propColorBlue\",\"id\":\"an51dnkenmoog9cetapbc4uyt3y\",\"value\":\"Daily\"},{\"color\":\"propColorOrange\",\"id\":\"afpy8s7i45frggprmfsqngsocqh\",\"value\":\"Weekly\"},{\"color\":\"propColorPurple\",\"id\":\"aj4jyekqqssatjcq7r7chmy19ey\",\"value\":\"Monthly\"}],\"type\":\"select\"},{\"id\":\"abthng7baedhhtrwsdodeuincqy\",\"name\":\"Completed\",\"options\":[],\"type\":\"checkbox\"}],\"description\":\"Use this template to organize your life and track your personal tasks.\",\"icon\":\"✔️\",\"isTemplate\":false,\"showDescription\":true},\"createAt\":1640281433899,\"updateAt\":1643788318628,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"c5xamko6rpibhje3bjreenon7ce\",\"parentId\":\"bbn1888mprfrm5fjw9f1je9x3xo\",\"rootId\":\"bbn1888mprfrm5fjw9f1je9x3xo\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Pay bills\",\"fields\":{\"contentOrder\":[\"7gwsf4uxtftgjt841zgwydxeere\",\"7j6rbt87htj83bbssod76iumsja\",\"7fjacjgfxjfrf3psxc46wwsgqdo\"],\"icon\":\"🔌\",\"isTemplate\":false,\"properties\":{\"a9zf59u8x1rf4ywctpcqama7tio\":\"aj4jyekqqssatjcq7r7chmy19ey\",\"abthng7baedhhtrwsdodeuincqy\":\"true\"}},\"createAt\":1640366942078,\"updateAt\":1643788318630,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"co6a88h6og3dm3kkub64kyb71jw\",\"parentId\":\"bbn1888mprfrm5fjw9f1je9x3xo\",\"rootId\":\"bbn1888mprfrm5fjw9f1je9x3xo\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Buy groceries\",\"fields\":{\"contentOrder\":[\"amd9sbzwrkpdspkisato6ajmzby\",\"7r749xjm5pfnuib18sefxwezc4o\",\"7zhat99shridtfntr97ek5j7yho\",\"7imjjx8fazty8fcjzkns464nupy\",\"7cbjz6bszwprnby56gfgzqehexc\",\"76x8gh63upjdnm8uso3nja7gjqh\",\"7z6ho1e3dibg6mki7jug84yxpja\"],\"icon\":\"🛒\",\"isTemplate\":false,\"properties\":{\"a9zf59u8x1rf4ywctpcqama7tio\":\"afpy8s7i45frggprmfsqngsocqh\"}},\"createAt\":1640365957059,\"updateAt\":1643788318630,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"cr7gz7sempbfqpq7sign4jaeyxc\",\"parentId\":\"bbn1888mprfrm5fjw9f1je9x3xo\",\"rootId\":\"bbn1888mprfrm5fjw9f1je9x3xo\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Go for a walk\",\"fields\":{\"contentOrder\":[\"a6b44enuiwpgszm1wt6og1mshqa\",\"aumtoywd8wjy7udm4ntcib4ckpo\",\"75gpszxg6difjmf1j3f5edj3w7a\"],\"icon\":\"👣\",\"isTemplate\":false,\"properties\":{\"a9zf59u8x1rf4ywctpcqama7tio\":\"an51dnkenmoog9cetapbc4uyt3y\"}},\"createAt\":1640281433950,\"updateAt\":1643788318630,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"cx7cki81xppd3pdgnyktwbgtzer\",\"parentId\":\"bbn1888mprfrm5fjw9f1je9x3xo\",\"rootId\":\"bbn1888mprfrm5fjw9f1je9x3xo\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Feed Fluffy\",\"fields\":{\"contentOrder\":[\"as5kdrix3ibd3jrnqzz94dcqqba\"],\"icon\":\"🐱\",\"isTemplate\":false,\"properties\":{\"a9zf59u8x1rf4ywctpcqama7tio\":\"an51dnkenmoog9cetapbc4uyt3y\"}},\"createAt\":1640281433850,\"updateAt\":1643788318630,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"czowhma7rnpgb3eczbqo3t7fijo\",\"parentId\":\"bbn1888mprfrm5fjw9f1je9x3xo\",\"rootId\":\"bbn1888mprfrm5fjw9f1je9x3xo\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Gardening\",\"fields\":{\"contentOrder\":[],\"icon\":\"🌳\",\"isTemplate\":false,\"properties\":{\"a9zf59u8x1rf4ywctpcqama7tio\":\"afpy8s7i45frggprmfsqngsocqh\"}},\"createAt\":1640281433750,\"updateAt\":1643788318630,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"vjq4piq89kbds5x5zq39zww7joo\",\"parentId\":\"bbn1888mprfrm5fjw9f1je9x3xo\",\"rootId\":\"bbn1888mprfrm5fjw9f1je9x3xo\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"view\",\"title\":\"List View\",\"fields\":{\"cardOrder\":[],\"collapsedOptionIds\":[],\"columnCalculations\":{},\"columnWidths\":{\"__title\":280},\"defaultTemplateId\":\"\",\"filter\":{\"filters\":[],\"operation\":\"and\"},\"hiddenOptionIds\":[],\"kanbanCalculations\":{},\"sortOptions\":[],\"viewType\":\"table\",\"visibleOptionIds\":[],\"visiblePropertyIds\":[\"a9zf59u8x1rf4ywctpcqama7tio\",\"abthng7baedhhtrwsdodeuincqy\"]},\"createAt\":1641247999081,\"updateAt\":1643788318630,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"vyeipq97iqbfjtd6fgcbxg6xbme\",\"parentId\":\"bbn1888mprfrm5fjw9f1je9x3xo\",\"rootId\":\"bbn1888mprfrm5fjw9f1je9x3xo\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"view\",\"title\":\"Board View\",\"fields\":{\"cardOrder\":[\"co6a88h6og3dm3kkub64kyb71jw\",\"c5xamko6rpibhje3bjreenon7ce\",\"cr7gz7sempbfqpq7sign4jaeyxc\",\"cx7cki81xppd3pdgnyktwbgtzer\",\"czowhma7rnpgb3eczbqo3t7fijo\"],\"collapsedOptionIds\":[],\"columnCalculations\":{},\"columnWidths\":{},\"defaultTemplateId\":\"\",\"filter\":{\"filters\":[],\"operation\":\"and\"},\"groupById\":\"a9zf59u8x1rf4ywctpcqama7tio\",\"hiddenOptionIds\":[],\"kanbanCalculations\":{},\"sortOptions\":[],\"viewType\":\"board\",\"visibleOptionIds\":[\"an51dnkenmoog9cetapbc4uyt3y\",\"afpy8s7i45frggprmfsqngsocqh\",\"aj4jyekqqssatjcq7r7chmy19ey\",\"\"],\"visiblePropertyIds\":[\"a9zf59u8x1rf4ywctpcqama7tio\"]},\"createAt\":1640281433698,\"updateAt\":1643788318630,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7fjacjgfxjfrf3psxc46wwsgqdo\",\"parentId\":\"c5xamko6rpibhje3bjreenon7ce\",\"rootId\":\"bbn1888mprfrm5fjw9f1je9x3xo\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Utilities\",\"fields\":{\"value\":true},\"createAt\":1640367568655,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7gwsf4uxtftgjt841zgwydxeere\",\"parentId\":\"c5xamko6rpibhje3bjreenon7ce\",\"rootId\":\"bbn1888mprfrm5fjw9f1je9x3xo\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Mobile phone\",\"fields\":{\"value\":true},\"createAt\":1640367517692,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7j6rbt87htj83bbssod76iumsja\",\"parentId\":\"c5xamko6rpibhje3bjreenon7ce\",\"rootId\":\"bbn1888mprfrm5fjw9f1je9x3xo\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Internet\",\"fields\":{\"value\":true},\"createAt\":1640367560684,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"76x8gh63upjdnm8uso3nja7gjqh\",\"parentId\":\"co6a88h6og3dm3kkub64kyb71jw\",\"rootId\":\"bbn1888mprfrm5fjw9f1je9x3xo\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Cereal\",\"fields\":{\"value\":false},\"createAt\":1640366017886,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7cbjz6bszwprnby56gfgzqehexc\",\"parentId\":\"co6a88h6og3dm3kkub64kyb71jw\",\"rootId\":\"bbn1888mprfrm5fjw9f1je9x3xo\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Butter\",\"fields\":{\"value\":false},\"createAt\":1640365985683,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7imjjx8fazty8fcjzkns464nupy\",\"parentId\":\"co6a88h6og3dm3kkub64kyb71jw\",\"rootId\":\"bbn1888mprfrm5fjw9f1je9x3xo\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Bread\",\"fields\":{\"value\":false},\"createAt\":1640365983209,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7r749xjm5pfnuib18sefxwezc4o\",\"parentId\":\"co6a88h6og3dm3kkub64kyb71jw\",\"rootId\":\"bbn1888mprfrm5fjw9f1je9x3xo\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Milk\",\"fields\":{\"value\":false},\"createAt\":1640365978720,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7z6ho1e3dibg6mki7jug84yxpja\",\"parentId\":\"co6a88h6og3dm3kkub64kyb71jw\",\"rootId\":\"bbn1888mprfrm5fjw9f1je9x3xo\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Bananas\",\"fields\":{\"value\":false},\"createAt\":1640367364568,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7zhat99shridtfntr97ek5j7yho\",\"parentId\":\"co6a88h6og3dm3kkub64kyb71jw\",\"rootId\":\"bbn1888mprfrm5fjw9f1je9x3xo\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Eggs\",\"fields\":{\"value\":false},\"createAt\":1640365980953,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"amd9sbzwrkpdspkisato6ajmzby\",\"parentId\":\"co6a88h6og3dm3kkub64kyb71jw\",\"rootId\":\"bbn1888mprfrm5fjw9f1je9x3xo\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Grocery list\",\"fields\":{},\"createAt\":1640367228497,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"75gpszxg6difjmf1j3f5edj3w7a\",\"parentId\":\"cr7gz7sempbfqpq7sign4jaeyxc\",\"rootId\":\"bbn1888mprfrm5fjw9f1je9x3xo\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"image\",\"title\":\"\",\"fields\":{\"fileId\":\"76fwrj36hptg6dywka4k5mt3sph.png\"},\"createAt\":1640368278060,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"a6b44enuiwpgszm1wt6og1mshqa\",\"parentId\":\"cr7gz7sempbfqpq7sign4jaeyxc\",\"rootId\":\"bbn1888mprfrm5fjw9f1je9x3xo\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Goal\\nWalk at least 10,000 steps every day.\",\"fields\":{},\"createAt\":1640367836067,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"aumtoywd8wjy7udm4ntcib4ckpo\",\"parentId\":\"cr7gz7sempbfqpq7sign4jaeyxc\",\"rootId\":\"bbn1888mprfrm5fjw9f1je9x3xo\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Route\",\"fields\":{},\"createAt\":1640368155600,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"as5kdrix3ibd3jrnqzz94dcqqba\",\"parentId\":\"cx7cki81xppd3pdgnyktwbgtzer\",\"rootId\":\"bbn1888mprfrm5fjw9f1je9x3xo\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"\",\"fields\":{},\"createAt\":1640368933239,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n"
  },
  {
    "path": "server/assets/templates-boardarchive/bc41mwxg9ybb69pn9j5zna6d36c/board.jsonl",
    "content": "{\"type\":\"block\",\"data\":{\"id\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"parentId\":\"\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"board\",\"title\":\"Project Tasks (NEW)\",\"fields\":{\"cardProperties\":[{\"id\":\"a972dc7a-5f4c-45d2-8044-8c28c69717f1\",\"name\":\"Status\",\"options\":[{\"color\":\"propColorBlue\",\"id\":\"ayz81h9f3dwp7rzzbdebesc7ute\",\"value\":\"Not Started\"},{\"color\":\"propColorYellow\",\"id\":\"ar6b8m3jxr3asyxhr8iucdbo6yc\",\"value\":\"In Progress\"},{\"color\":\"propColorRed\",\"id\":\"afi4o5nhnqc3smtzs1hs3ij34dh\",\"value\":\"Blocked\"},{\"color\":\"propColorGreen\",\"id\":\"adeo5xuwne3qjue83fcozekz8ko\",\"value\":\"Completed 🙌\"},{\"color\":\"propColorBrown\",\"id\":\"ahpyxfnnrzynsw3im1psxpkgtpe\",\"value\":\"Archived\"}],\"type\":\"select\"},{\"id\":\"d3d682bf-e074-49d9-8df5-7320921c2d23\",\"name\":\"Priority\",\"options\":[{\"color\":\"propColorRed\",\"id\":\"d3bfb50f-f569-4bad-8a3a-dd15c3f60101\",\"value\":\"1. High 🔥\"},{\"color\":\"propColorYellow\",\"id\":\"87f59784-b859-4c24-8ebe-17c766e081dd\",\"value\":\"2. Medium\"},{\"color\":\"propColorGray\",\"id\":\"98a57627-0f76-471d-850d-91f3ed9fd213\",\"value\":\"3. Low\"}],\"type\":\"select\"},{\"id\":\"axkhqa4jxr3jcqe4k87g8bhmary\",\"name\":\"Assignee\",\"options\":[],\"type\":\"person\"},{\"id\":\"a8daz81s4xjgke1ww6cwik5w7ye\",\"name\":\"Estimated Hours\",\"options\":[],\"type\":\"number\"},{\"id\":\"a3zsw7xs8sxy7atj8b6totp3mby\",\"name\":\"Due Date\",\"options\":[],\"type\":\"date\"},{\"id\":\"a7gdnz8ff8iyuqmzddjgmgo9ery\",\"name\":\"Created By\",\"options\":[],\"type\":\"createdBy\"},{\"id\":\"2a5da320-735c-4093-8787-f56e15cdfeed\",\"name\":\"Date Created\",\"options\":[],\"type\":\"createdTime\"}],\"description\":\"Use this template to stay on top of your project tasks and progress.\",\"icon\":\"🎯\",\"isTemplate\":false,\"showDescription\":true},\"createAt\":1640281242611,\"updateAt\":1643788318628,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"c68gyx34srjgjxmrs1z8pj7nbce\",\"parentId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Identify dependencies\",\"fields\":{\"contentOrder\":[\"akqkae666a7bnbgib4ykbexjjey\",\"7b1h5q66pkig4mp948z635dejxy\",\"aepujbmb347ye9j7uikbk3oajqh\",\"76q9tmzey4byqdpimsdxeg1gx3h\",\"79qbaadiuwjgujnz9tgqmmkaaqo\",\"7msorzdb7r3rk3qjncmdxhpqz5o\",\"7izro8efd1irwpepfph4uz56bgh\"],\"icon\":\"🔗\",\"isTemplate\":false,\"properties\":{\"a8daz81s4xjgke1ww6cwik5w7ye\":\"16\",\"a972dc7a-5f4c-45d2-8044-8c28c69717f1\":\"ayz81h9f3dwp7rzzbdebesc7ute\",\"d3d682bf-e074-49d9-8df5-7320921c2d23\":\"98a57627-0f76-471d-850d-91f3ed9fd213\"}},\"createAt\":1640364405240,\"updateAt\":1643788318630,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"c6w7rxrootfdw7j4fsftc5gsyoo\",\"parentId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Define project scope\",\"fields\":{\"contentOrder\":[\"ags74nq3isiywmmkkg8h4tbxcfh\",\"7q7rkcbuqwfffjgrk57yjkydnry\",\"a66dncm7qppd4tjo9886d5bbsaa\",\"7jy54jqerhbnj7r4efpuk3g4cda\",\"716fy9hw4p38a5mf8rq5ap6txoo\",\"7opf3hssh6pn9zyy6toh53r49iw\",\"7g1qskptj9i8gimg1aynyqtnwka\"],\"icon\":\"🔬\",\"isTemplate\":false,\"properties\":{\"a8daz81s4xjgke1ww6cwik5w7ye\":\"32\",\"a972dc7a-5f4c-45d2-8044-8c28c69717f1\":\"ar6b8m3jxr3asyxhr8iucdbo6yc\",\"d3d682bf-e074-49d9-8df5-7320921c2d23\":\"87f59784-b859-4c24-8ebe-17c766e081dd\"}},\"createAt\":1640364532461,\"updateAt\":1643788318630,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"cdwqxf4b3utbbxdrgbwtmk9y9eo\",\"parentId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Requirements sign-off\",\"fields\":{\"contentOrder\":[\"aags5e9sbbfnqtrtf39hoopbxme\",\"7kriyyuos4pgg8k6t8fkcsa7bde\",\"adw7awe3ucp8g781dfq7yw6kfur\",\"7xk7xg6yonbn88fpkihigzn8whr\",\"7b9uyiog56jr1zgonbutxfd7w3c\",\"7r3ua3e7w3jrmpqdngzqs74i1go\",\"76hsxtocpnbnrijxqcfccfkyo1e\"],\"icon\":\"🖋️\",\"isTemplate\":false,\"properties\":{\"a8daz81s4xjgke1ww6cwik5w7ye\":\"8\",\"a972dc7a-5f4c-45d2-8044-8c28c69717f1\":\"ayz81h9f3dwp7rzzbdebesc7ute\",\"d3d682bf-e074-49d9-8df5-7320921c2d23\":\"d3bfb50f-f569-4bad-8a3a-dd15c3f60101\"}},\"createAt\":1640281242441,\"updateAt\":1643788318630,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"cfk8kwmuhcfd8m8qicz5aqw4mar\",\"parentId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Project budget approval\",\"fields\":{\"contentOrder\":[\"a9h4kfaurrprepefrw95i1raoxr\",\"7btyuex8nji8jxn9yieaxgwoe6h\",\"a34hy46bu8bngxcxpz9woui4afa\",\"7ekrgkgq67fdofn9gskpe19bkrc\",\"7ygi1kq3683ya5ydfttuc5rhasr\",\"7qmjyww91rj8a38dsgu5b5wu7hr\",\"7qmmpepfm4byqjqo9m16yp7m3no\"],\"icon\":\"💵\",\"isTemplate\":false,\"properties\":{\"a8daz81s4xjgke1ww6cwik5w7ye\":\"16\",\"a972dc7a-5f4c-45d2-8044-8c28c69717f1\":\"ayz81h9f3dwp7rzzbdebesc7ute\",\"d3d682bf-e074-49d9-8df5-7320921c2d23\":\"d3bfb50f-f569-4bad-8a3a-dd15c3f60101\"}},\"createAt\":1640281242677,\"updateAt\":1643788318630,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"ckcntrrmcjbywpciau57gw5suoo\",\"parentId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Conduct market analysis\",\"fields\":{\"contentOrder\":[\"a6gowxxpgijgip8qzrsp5rmjwqy\",\"771bq4ja3ejfwbgaq78cdpgmjih\",\"asdoj8ffhcirh3x3iys3joeox9o\",\"7k975b49ni7yrfn3nqg7q4x4wde\",\"7e9aj57zouidozb8sf8e1wybywe\",\"71dm4jiu43byubx7pukjiy19pay\",\"719y6x4tkiigd9nwarn1e6ek7ic\"],\"icon\":\"📈\",\"isTemplate\":false,\"properties\":{\"a8daz81s4xjgke1ww6cwik5w7ye\":\"40\",\"a972dc7a-5f4c-45d2-8044-8c28c69717f1\":\"ar6b8m3jxr3asyxhr8iucdbo6yc\",\"d3d682bf-e074-49d9-8df5-7320921c2d23\":\"87f59784-b859-4c24-8ebe-17c766e081dd\"}},\"createAt\":1640281242851,\"updateAt\":1643788318630,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"vcuoise4b8jn1ffzujfuacymmmr\",\"parentId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"view\",\"title\":\"Project Priorities\",\"fields\":{\"cardOrder\":[],\"collapsedOptionIds\":[],\"columnCalculations\":{},\"columnWidths\":{},\"defaultTemplateId\":\"\",\"filter\":{\"filters\":[],\"operation\":\"and\"},\"groupById\":\"d3d682bf-e074-49d9-8df5-7320921c2d23\",\"hiddenOptionIds\":[],\"kanbanCalculations\":{\"\":{\"calculation\":\"sum\",\"propertyId\":\"a8daz81s4xjgke1ww6cwik5w7ye\"},\"87f59784-b859-4c24-8ebe-17c766e081dd\":{\"calculation\":\"sum\",\"propertyId\":\"a8daz81s4xjgke1ww6cwik5w7ye\"},\"98a57627-0f76-471d-850d-91f3ed9fd213\":{\"calculation\":\"sum\",\"propertyId\":\"a8daz81s4xjgke1ww6cwik5w7ye\"},\"d3bfb50f-f569-4bad-8a3a-dd15c3f60101\":{\"calculation\":\"sum\",\"propertyId\":\"a8daz81s4xjgke1ww6cwik5w7ye\"}},\"sortOptions\":[],\"viewType\":\"board\",\"visibleOptionIds\":[\"d3bfb50f-f569-4bad-8a3a-dd15c3f60101\",\"87f59784-b859-4c24-8ebe-17c766e081dd\",\"98a57627-0f76-471d-850d-91f3ed9fd213\",\"\"],\"visiblePropertyIds\":[\"a972dc7a-5f4c-45d2-8044-8c28c69717f1\",\"a8daz81s4xjgke1ww6cwik5w7ye\"]},\"createAt\":1640281242551,\"updateAt\":1643788318630,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"vey61xzc6u38ptnpjqaik6ap91e\",\"parentId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"view\",\"title\":\"Progress Tracker\",\"fields\":{\"cardOrder\":[\"cfk8kwmuhcfd8m8qicz5aqw4mar\",\"cdwqxf4b3utbbxdrgbwtmk9y9eo\",\"c68gyx34srjgjxmrs1z8pj7nbce\",\"ckcntrrmcjbywpciau57gw5suoo\",\"c6w7rxrootfdw7j4fsftc5gsyoo\",\"coxnjt3ro1in19dd1e3awdt338r\"],\"collapsedOptionIds\":[],\"columnCalculations\":{},\"columnWidths\":{},\"defaultTemplateId\":\"\",\"filter\":{\"filters\":[],\"operation\":\"and\"},\"groupById\":\"a972dc7a-5f4c-45d2-8044-8c28c69717f1\",\"hiddenOptionIds\":[],\"kanbanCalculations\":{\"\":{\"calculation\":\"sum\",\"propertyId\":\"a8daz81s4xjgke1ww6cwik5w7ye\"},\"adeo5xuwne3qjue83fcozekz8ko\":{\"calculation\":\"sum\",\"propertyId\":\"a8daz81s4xjgke1ww6cwik5w7ye\"},\"afi4o5nhnqc3smtzs1hs3ij34dh\":{\"calculation\":\"sum\",\"propertyId\":\"a8daz81s4xjgke1ww6cwik5w7ye\"},\"ahpyxfnnrzynsw3im1psxpkgtpe\":{\"calculation\":\"sum\",\"propertyId\":\"a8daz81s4xjgke1ww6cwik5w7ye\"},\"ar6b8m3jxr3asyxhr8iucdbo6yc\":{\"calculation\":\"sum\",\"propertyId\":\"a8daz81s4xjgke1ww6cwik5w7ye\"},\"ayz81h9f3dwp7rzzbdebesc7ute\":{\"calculation\":\"sum\",\"propertyId\":\"a8daz81s4xjgke1ww6cwik5w7ye\"}},\"sortOptions\":[],\"viewType\":\"board\",\"visibleOptionIds\":[\"ayz81h9f3dwp7rzzbdebesc7ute\",\"ar6b8m3jxr3asyxhr8iucdbo6yc\",\"afi4o5nhnqc3smtzs1hs3ij34dh\",\"adeo5xuwne3qjue83fcozekz8ko\",\"ahpyxfnnrzynsw3im1psxpkgtpe\",\"\"],\"visiblePropertyIds\":[\"d3d682bf-e074-49d9-8df5-7320921c2d23\",\"a8daz81s4xjgke1ww6cwik5w7ye\"]},\"createAt\":1640281242788,\"updateAt\":1643788318630,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"vfztxwjnegbdh38nfccu3bq1auc\",\"parentId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"view\",\"title\":\"Task Overview\",\"fields\":{\"cardOrder\":[\"c6w7rxrootfdw7j4fsftc5gsyoo\",\"ckcntrrmcjbywpciau57gw5suoo\",\"c68gyx34srjgjxmrs1z8pj7nbce\",\"cfk8kwmuhcfd8m8qicz5aqw4mar\",\"cdwqxf4b3utbbxdrgbwtmk9y9eo\",\"cz8p8gofakfby8kzz83j97db8ph\",\"ce1jm5q5i54enhuu4h3kkay1hcc\"],\"collapsedOptionIds\":[],\"columnCalculations\":{\"a8daz81s4xjgke1ww6cwik5w7ye\":\"sum\"},\"columnWidths\":{\"2a5da320-735c-4093-8787-f56e15cdfeed\":196,\"__title\":280,\"a8daz81s4xjgke1ww6cwik5w7ye\":139,\"a972dc7a-5f4c-45d2-8044-8c28c69717f1\":141,\"d3d682bf-e074-49d9-8df5-7320921c2d23\":110},\"defaultTemplateId\":\"czw9es1e89fdpjr7cqptr1xq7qh\",\"filter\":{\"filters\":[],\"operation\":\"and\"},\"groupById\":\"\",\"hiddenOptionIds\":[],\"kanbanCalculations\":{},\"sortOptions\":[],\"viewType\":\"table\",\"visibleOptionIds\":[],\"visiblePropertyIds\":[\"a972dc7a-5f4c-45d2-8044-8c28c69717f1\",\"d3d682bf-e074-49d9-8df5-7320921c2d23\",\"2a5da320-735c-4093-8787-f56e15cdfeed\",\"a3zsw7xs8sxy7atj8b6totp3mby\",\"axkhqa4jxr3jcqe4k87g8bhmary\",\"a7gdnz8ff8iyuqmzddjgmgo9ery\",\"a8daz81s4xjgke1ww6cwik5w7ye\"]},\"createAt\":1640281242734,\"updateAt\":1643788318630,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"vi49i1138jpnbiqhyd81beme9zy\",\"parentId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"view\",\"title\":\"Task Calendar\",\"fields\":{\"cardOrder\":[],\"collapsedOptionIds\":[],\"columnCalculations\":{},\"columnWidths\":{},\"dateDisplayPropertyId\":\"a3zsw7xs8sxy7atj8b6totp3mby\",\"defaultTemplateId\":\"\",\"filter\":{\"filters\":[],\"operation\":\"and\"},\"hiddenOptionIds\":[],\"kanbanCalculations\":{},\"sortOptions\":[],\"viewType\":\"calendar\",\"visibleOptionIds\":[],\"visiblePropertyIds\":[\"__title\"]},\"createAt\":1640361708030,\"updateAt\":1643788318630,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"76q9tmzey4byqdpimsdxeg1gx3h\",\"parentId\":\"c68gyx34srjgjxmrs1z8pj7nbce\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"[Subtask 1]\",\"fields\":{\"value\":false},\"createAt\":1641247437494,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"79qbaadiuwjgujnz9tgqmmkaaqo\",\"parentId\":\"c68gyx34srjgjxmrs1z8pj7nbce\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"[Subtask 2]\",\"fields\":{\"value\":false},\"createAt\":1641247440946,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7b1h5q66pkig4mp948z635dejxy\",\"parentId\":\"c68gyx34srjgjxmrs1z8pj7nbce\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"divider\",\"title\":\"\",\"fields\":{},\"createAt\":1641247334696,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7izro8efd1irwpepfph4uz56bgh\",\"parentId\":\"c68gyx34srjgjxmrs1z8pj7nbce\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"...\",\"fields\":{\"value\":false},\"createAt\":1641247447937,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7msorzdb7r3rk3qjncmdxhpqz5o\",\"parentId\":\"c68gyx34srjgjxmrs1z8pj7nbce\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"[Subtask 3]\",\"fields\":{\"value\":false},\"createAt\":1641247445214,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"aepujbmb347ye9j7uikbk3oajqh\",\"parentId\":\"c68gyx34srjgjxmrs1z8pj7nbce\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Checklist\",\"fields\":{},\"createAt\":1641247378401,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"akqkae666a7bnbgib4ykbexjjey\",\"parentId\":\"c68gyx34srjgjxmrs1z8pj7nbce\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Description\\n*[Brief description of this task]*\",\"fields\":{},\"createAt\":1641247332262,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"716fy9hw4p38a5mf8rq5ap6txoo\",\"parentId\":\"c6w7rxrootfdw7j4fsftc5gsyoo\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"[Subtask 2]\",\"fields\":{\"value\":false},\"createAt\":1641247170396,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7g1qskptj9i8gimg1aynyqtnwka\",\"parentId\":\"c6w7rxrootfdw7j4fsftc5gsyoo\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"...\",\"fields\":{\"value\":false},\"createAt\":1641247182126,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7jy54jqerhbnj7r4efpuk3g4cda\",\"parentId\":\"c6w7rxrootfdw7j4fsftc5gsyoo\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"[Subtask 1]\",\"fields\":{\"value\":false},\"createAt\":1641247156773,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7opf3hssh6pn9zyy6toh53r49iw\",\"parentId\":\"c6w7rxrootfdw7j4fsftc5gsyoo\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"[Subtask 3]\",\"fields\":{\"value\":false},\"createAt\":1641247176917,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7q7rkcbuqwfffjgrk57yjkydnry\",\"parentId\":\"c6w7rxrootfdw7j4fsftc5gsyoo\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"divider\",\"title\":\"\",\"fields\":{},\"createAt\":1641247131586,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"a66dncm7qppd4tjo9886d5bbsaa\",\"parentId\":\"c6w7rxrootfdw7j4fsftc5gsyoo\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Checklist\",\"fields\":{},\"createAt\":1641247135038,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"ags74nq3isiywmmkkg8h4tbxcfh\",\"parentId\":\"c6w7rxrootfdw7j4fsftc5gsyoo\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Description\\n*[Brief description of this task]*\",\"fields\":{},\"createAt\":1641247112211,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"76hsxtocpnbnrijxqcfccfkyo1e\",\"parentId\":\"cdwqxf4b3utbbxdrgbwtmk9y9eo\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"...\",\"fields\":{\"value\":false},\"createAt\":1641247486848,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7b9uyiog56jr1zgonbutxfd7w3c\",\"parentId\":\"cdwqxf4b3utbbxdrgbwtmk9y9eo\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"[Subtask 2]\",\"fields\":{\"value\":false},\"createAt\":1641247480724,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7kriyyuos4pgg8k6t8fkcsa7bde\",\"parentId\":\"cdwqxf4b3utbbxdrgbwtmk9y9eo\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"divider\",\"title\":\"\",\"fields\":{},\"createAt\":1641247352753,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7r3ua3e7w3jrmpqdngzqs74i1go\",\"parentId\":\"cdwqxf4b3utbbxdrgbwtmk9y9eo\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"[Subtask 3]\",\"fields\":{\"value\":false},\"createAt\":1641247483695,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7xk7xg6yonbn88fpkihigzn8whr\",\"parentId\":\"cdwqxf4b3utbbxdrgbwtmk9y9eo\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"[Subtask 1]\",\"fields\":{\"value\":false},\"createAt\":1641247478297,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"aags5e9sbbfnqtrtf39hoopbxme\",\"parentId\":\"cdwqxf4b3utbbxdrgbwtmk9y9eo\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Description\\n*[Brief description of this task]*\",\"fields\":{},\"createAt\":1641247350239,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"adw7awe3ucp8g781dfq7yw6kfur\",\"parentId\":\"cdwqxf4b3utbbxdrgbwtmk9y9eo\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Checklist\",\"fields\":{},\"createAt\":1641247399161,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7btyuex8nji8jxn9yieaxgwoe6h\",\"parentId\":\"cfk8kwmuhcfd8m8qicz5aqw4mar\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"divider\",\"title\":\"\",\"fields\":{},\"createAt\":1641247342345,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7ekrgkgq67fdofn9gskpe19bkrc\",\"parentId\":\"cfk8kwmuhcfd8m8qicz5aqw4mar\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"[Subtask 1]\",\"fields\":{\"value\":false},\"createAt\":1641247459230,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7qmjyww91rj8a38dsgu5b5wu7hr\",\"parentId\":\"cfk8kwmuhcfd8m8qicz5aqw4mar\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"[Subtask 3]\",\"fields\":{\"value\":false},\"createAt\":1641247464903,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7qmmpepfm4byqjqo9m16yp7m3no\",\"parentId\":\"cfk8kwmuhcfd8m8qicz5aqw4mar\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"...\",\"fields\":{\"value\":false},\"createAt\":1641247468228,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7ygi1kq3683ya5ydfttuc5rhasr\",\"parentId\":\"cfk8kwmuhcfd8m8qicz5aqw4mar\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"[Subtask 2]\",\"fields\":{\"value\":false},\"createAt\":1641247461754,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"a34hy46bu8bngxcxpz9woui4afa\",\"parentId\":\"cfk8kwmuhcfd8m8qicz5aqw4mar\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Checklist\",\"fields\":{},\"createAt\":1641247389505,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"a9h4kfaurrprepefrw95i1raoxr\",\"parentId\":\"cfk8kwmuhcfd8m8qicz5aqw4mar\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Description\\n*[Brief description of this task]*\",\"fields\":{},\"createAt\":1641247339781,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"719y6x4tkiigd9nwarn1e6ek7ic\",\"parentId\":\"ckcntrrmcjbywpciau57gw5suoo\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"...\",\"fields\":{\"value\":false},\"createAt\":1641247428974,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"71dm4jiu43byubx7pukjiy19pay\",\"parentId\":\"ckcntrrmcjbywpciau57gw5suoo\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"[Subtask 3]\",\"fields\":{\"value\":false},\"createAt\":1641247425545,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"771bq4ja3ejfwbgaq78cdpgmjih\",\"parentId\":\"ckcntrrmcjbywpciau57gw5suoo\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"divider\",\"title\":\"\",\"fields\":{},\"createAt\":1641247327922,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7e9aj57zouidozb8sf8e1wybywe\",\"parentId\":\"ckcntrrmcjbywpciau57gw5suoo\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"[Subtask 2]\",\"fields\":{\"value\":false},\"createAt\":1641247421647,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7k975b49ni7yrfn3nqg7q4x4wde\",\"parentId\":\"ckcntrrmcjbywpciau57gw5suoo\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"[Subtask 1]\",\"fields\":{\"value\":false},\"createAt\":1641247417179,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"a6gowxxpgijgip8qzrsp5rmjwqy\",\"parentId\":\"ckcntrrmcjbywpciau57gw5suoo\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Description\\n*[Brief description of this task]*\",\"fields\":{},\"createAt\":1641247325247,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"asdoj8ffhcirh3x3iys3joeox9o\",\"parentId\":\"ckcntrrmcjbywpciau57gw5suoo\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Checklist\",\"fields\":{},\"createAt\":1641247365651,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"73a715h3xkiye9jj9px3daujgpa\",\"parentId\":\"czw9es1e89fdpjr7cqptr1xq7qh\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"[Subtask 3]\",\"fields\":{\"value\":false},\"createAt\":1641247243580,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"75afimcsuqby6xxq39wiae9obme\",\"parentId\":\"czw9es1e89fdpjr7cqptr1xq7qh\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"[Subtask 2]\",\"fields\":{\"value\":false},\"createAt\":1641247239940,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7dodh1pgw73yq78pgtmk3ckc9fr\",\"parentId\":\"czw9es1e89fdpjr7cqptr1xq7qh\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"divider\",\"title\":\"\",\"fields\":{},\"createAt\":1641247212754,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7ttgtruigcbfzdmxkhmzt6kp6dh\",\"parentId\":\"czw9es1e89fdpjr7cqptr1xq7qh\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"[Subtask 1]\",\"fields\":{\"value\":false},\"createAt\":1641247226415,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7u7mmiit57b8i8gsp6mc6x7h9he\",\"parentId\":\"czw9es1e89fdpjr7cqptr1xq7qh\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"...\",\"fields\":{\"value\":false},\"createAt\":1641247248372,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"adxx8y691qf8btg7w8mx6x78w9y\",\"parentId\":\"czw9es1e89fdpjr7cqptr1xq7qh\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Description\\n*[Brief description of this task]*\",\"fields\":{},\"createAt\":1641247210152,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"afatxnq346jbcin9iisryo38grr\",\"parentId\":\"czw9es1e89fdpjr7cqptr1xq7qh\",\"rootId\":\"bc41mwxg9ybb69pn9j5zna6d36c\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Checklist\",\"fields\":{},\"createAt\":1641247215942,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n"
  },
  {
    "path": "server/assets/templates-boardarchive/bcm39o11e4ib8tye8mt6iyuec9o/board.jsonl",
    "content": "{\"type\":\"board\",\"data\":{\"id\":\"bcm39o11e4ib8tye8mt6iyuec9o\",\"teamId\":\"qghzt68dq7bopgqamcnziq69ao\",\"channelId\":\"\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"type\":\"P\",\"minimumRole\":\"\",\"title\":\"Company Goals \\u0026 OKRs\",\"description\":\"Use this template to plan your company goals and OKRs more efficiently.\",\"icon\":\"⛳\",\"showDescription\":true,\"isTemplate\":false,\"templateVersion\":0,\"properties\":{},\"cardProperties\":[{\"id\":\"a6amddgmrzakw66cidqzgk6p4ge\",\"name\":\"Objective\",\"options\":[{\"color\":\"propColorGreen\",\"id\":\"auw3afh3kfhrfgmjr8muiz137jy\",\"value\":\"Grow Revenue\"},{\"color\":\"propColorOrange\",\"id\":\"apqfjst8massbjjhpcsjs3y1yqa\",\"value\":\"Delight Customers\"},{\"color\":\"propColorPurple\",\"id\":\"ao9b5pxyt7tkgdohzh9oaustdhr\",\"value\":\"Drive Product Adoption\"}],\"type\":\"select\"},{\"id\":\"a17ryhi1jfsboxkwkztwawhmsxe\",\"name\":\"Status\",\"options\":[{\"color\":\"propColorGray\",\"id\":\"a6robxx81diugpjq5jkezz3j1fo\",\"value\":\"Not Started\"},{\"color\":\"propColorBlue\",\"id\":\"a8nukezwwmknqwjsygg7eaxs9te\",\"value\":\"In Progress\"},{\"color\":\"propColorYellow\",\"id\":\"apnt1f7na9rzgk1rt49keg7xbiy\",\"value\":\"At Risk\"},{\"color\":\"propColorRed\",\"id\":\"axbz3m1amss335wzwf9s7pqjzxr\",\"value\":\"Missed\"},{\"color\":\"propColorGreen\",\"id\":\"abzfwnn6rmtfzyq5hg8uqmpsncy\",\"value\":\"Complete 🙌\"}],\"type\":\"select\"},{\"id\":\"azzbawji5bksj69sekcs4srm1ky\",\"name\":\"Department\",\"options\":[{\"color\":\"propColorBrown\",\"id\":\"aw5i7hmpadn6mbwbz955ubarhme\",\"value\":\"Engineering\"},{\"color\":\"propColorBlue\",\"id\":\"afkxpcjqjypu7hhar7banxau91h\",\"value\":\"Product\"},{\"color\":\"propColorOrange\",\"id\":\"aehoa17cz18rqnrf75g7dwhphpr\",\"value\":\"Marketing\"},{\"color\":\"propColorGreen\",\"id\":\"agrfeaoj7d8p5ianw5iaf3191ae\",\"value\":\"Sales\"},{\"color\":\"propColorYellow\",\"id\":\"agm9p6gcq15ueuzqq3wd4be39wy\",\"value\":\"Support\"},{\"color\":\"propColorPink\",\"id\":\"aucop7kw6xwodcix6zzojhxih6r\",\"value\":\"Design\"},{\"color\":\"propColorPurple\",\"id\":\"afust91f3g8ht368mkn5x9tgf1o\",\"value\":\"Finance\"},{\"color\":\"propColorGray\",\"id\":\"acocxxwjurud1jixhp7nowdig7y\",\"value\":\"Human Resources\"}],\"type\":\"select\"},{\"id\":\"adp5ft3kgz7r5iqq3tnwg551der\",\"name\":\"Priority\",\"options\":[{\"color\":\"propColorRed\",\"id\":\"a8zg3rjtf4swh7smsjxpsn743rh\",\"value\":\"P1 🔥\"},{\"color\":\"propColorYellow\",\"id\":\"as555ipyzopjjpfb5rjtssecw5e\",\"value\":\"P2\"},{\"color\":\"propColorGray\",\"id\":\"a1ts3ftyr8nocsicui98c89uxjy\",\"value\":\"P3\"}],\"type\":\"select\"},{\"id\":\"aqxyzkdrs4egqf7yk866ixkaojc\",\"name\":\"Quarter\",\"options\":[{\"color\":\"propColorBlue\",\"id\":\"ahfbn1jsmhydym33ygxwg5jt3kh\",\"value\":\"Q1\"},{\"color\":\"propColorBrown\",\"id\":\"awfu37js3fomfkkczm1zppac57a\",\"value\":\"Q2\"},{\"color\":\"propColorGreen\",\"id\":\"anruuoyez51r3yjxuoc8zoqnwaw\",\"value\":\"Q3\"},{\"color\":\"propColorPurple\",\"id\":\"acb6dqqs6yson7bbzx6jk9bghjh\",\"value\":\"Q4\"}],\"type\":\"select\"},{\"id\":\"adu6mebzpibq6mgcswk69xxmnqe\",\"name\":\"Due Date\",\"options\":[],\"type\":\"date\"},{\"id\":\"asope3bddhm4gpsng5cfu4hf6rh\",\"name\":\"Assignee\",\"options\":[],\"type\":\"multiPerson\"},{\"id\":\"ajwxp866f9obs1kutfwaa5ru7fe\",\"name\":\"Target\",\"options\":[],\"type\":\"number\"},{\"id\":\"azqnyswk6s1boiwuthscm78qwuo\",\"name\":\"Actual\",\"options\":[],\"type\":\"number\"},{\"id\":\"ahz3fmjnaguec8hce7xq3h5cjdr\",\"name\":\"Completion (%)\",\"options\":[],\"type\":\"text\"},{\"id\":\"a17bfcgnzmkwhziwa4tr38kiw5r\",\"name\":\"Note\",\"options\":[],\"type\":\"text\"}],\"createAt\":1667430124226,\"updateAt\":1667431508571,\"deleteAt\":0}}\n{\"type\":\"block\",\"data\":{\"id\":\"vangk4cpd5fgpbr7635tx6oxg7c\",\"parentId\":\"\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"view\",\"title\":\"By Quarter\",\"fields\":{\"cardOrder\":[],\"collapsedOptionIds\":[],\"columnCalculations\":{},\"columnWidths\":{\"__title\":452,\"a17ryhi1jfsboxkwkztwawhmsxe\":148,\"a6amddgmrzakw66cidqzgk6p4ge\":230,\"azzbawji5bksj69sekcs4srm1ky\":142},\"defaultTemplateId\":\"\",\"filter\":{\"filters\":[],\"operation\":\"and\"},\"groupById\":\"aqxyzkdrs4egqf7yk866ixkaojc\",\"hiddenOptionIds\":[\"\"],\"kanbanCalculations\":{},\"sortOptions\":[],\"viewType\":\"table\",\"visibleOptionIds\":[],\"visiblePropertyIds\":[\"a6amddgmrzakw66cidqzgk6p4ge\",\"a17ryhi1jfsboxkwkztwawhmsxe\",\"azzbawji5bksj69sekcs4srm1ky\",\"adp5ft3kgz7r5iqq3tnwg551der\",\"aqxyzkdrs4egqf7yk866ixkaojc\",\"adu6mebzpibq6mgcswk69xxmnqe\",\"asope3bddhm4gpsng5cfu4hf6rh\",\"ajwxp866f9obs1kutfwaa5ru7fe\",\"azqnyswk6s1boiwuthscm78qwuo\",\"ahz3fmjnaguec8hce7xq3h5cjdr\",\"a17bfcgnzmkwhziwa4tr38kiw5r\"]},\"createAt\":1667431291178,\"updateAt\":1667431333436,\"deleteAt\":0,\"boardId\":\"bcm39o11e4ib8tye8mt6iyuec9o\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"vr1jnxkxi8pf9z83fhr4qbsbxao\",\"parentId\":\"\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"view\",\"title\":\"By Objectives\",\"fields\":{\"cardOrder\":[],\"collapsedOptionIds\":[],\"columnCalculations\":{},\"columnWidths\":{\"__title\":387,\"a17ryhi1jfsboxkwkztwawhmsxe\":134,\"a6amddgmrzakw66cidqzgk6p4ge\":183,\"aqxyzkdrs4egqf7yk866ixkaojc\":100},\"defaultTemplateId\":\"\",\"filter\":{\"filters\":[],\"operation\":\"and\"},\"groupById\":\"a6amddgmrzakw66cidqzgk6p4ge\",\"hiddenOptionIds\":[\"\"],\"kanbanCalculations\":{},\"sortOptions\":[],\"viewType\":\"table\",\"visibleOptionIds\":[],\"visiblePropertyIds\":[\"a6amddgmrzakw66cidqzgk6p4ge\",\"a17ryhi1jfsboxkwkztwawhmsxe\",\"azzbawji5bksj69sekcs4srm1ky\",\"adp5ft3kgz7r5iqq3tnwg551der\",\"aqxyzkdrs4egqf7yk866ixkaojc\",\"adu6mebzpibq6mgcswk69xxmnqe\",\"asope3bddhm4gpsng5cfu4hf6rh\",\"ajwxp866f9obs1kutfwaa5ru7fe\",\"azqnyswk6s1boiwuthscm78qwuo\",\"ahz3fmjnaguec8hce7xq3h5cjdr\",\"a17bfcgnzmkwhziwa4tr38kiw5r\"]},\"createAt\":1667431221976,\"updateAt\":1667431420460,\"deleteAt\":0,\"boardId\":\"bcm39o11e4ib8tye8mt6iyuec9o\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"c3m6mgymw978wjecydz16io868h\",\"parentId\":\"bcm39o11e4ib8tye8mt6iyuec9o\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Improve customer NPS score\",\"fields\":{\"contentOrder\":[],\"icon\":\"💯\",\"isTemplate\":false,\"properties\":{\"a17ryhi1jfsboxkwkztwawhmsxe\":\"a8nukezwwmknqwjsygg7eaxs9te\",\"a6amddgmrzakw66cidqzgk6p4ge\":\"apqfjst8massbjjhpcsjs3y1yqa\",\"adp5ft3kgz7r5iqq3tnwg551der\":\"as555ipyzopjjpfb5rjtssecw5e\",\"ahz3fmjnaguec8hce7xq3h5cjdr\":\"82%\",\"ajwxp866f9obs1kutfwaa5ru7fe\":\"8.5\",\"aqxyzkdrs4egqf7yk866ixkaojc\":\"anruuoyez51r3yjxuoc8zoqnwaw\",\"azqnyswk6s1boiwuthscm78qwuo\":\"7\",\"azzbawji5bksj69sekcs4srm1ky\":\"agm9p6gcq15ueuzqq3wd4be39wy\"}},\"createAt\":1667430924551,\"updateAt\":1667430962900,\"deleteAt\":0,\"boardId\":\"bcm39o11e4ib8tye8mt6iyuec9o\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"ce9u86wofitrb5ns4qp5w1ij1nh\",\"parentId\":\"bcm39o11e4ib8tye8mt6iyuec9o\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Generate more Marketing Qualified Leads (MQLs)\",\"fields\":{\"contentOrder\":[],\"icon\":\"🛣️\",\"isTemplate\":false,\"properties\":{\"a17ryhi1jfsboxkwkztwawhmsxe\":\"a8nukezwwmknqwjsygg7eaxs9te\",\"a6amddgmrzakw66cidqzgk6p4ge\":\"auw3afh3kfhrfgmjr8muiz137jy\",\"adp5ft3kgz7r5iqq3tnwg551der\":\"as555ipyzopjjpfb5rjtssecw5e\",\"ahz3fmjnaguec8hce7xq3h5cjdr\":\"65%\",\"ajwxp866f9obs1kutfwaa5ru7fe\":\"100\",\"aqxyzkdrs4egqf7yk866ixkaojc\":\"ahfbn1jsmhydym33ygxwg5jt3kh\",\"azqnyswk6s1boiwuthscm78qwuo\":\"65\",\"azzbawji5bksj69sekcs4srm1ky\":\"aehoa17cz18rqnrf75g7dwhphpr\"}},\"createAt\":1667430791375,\"updateAt\":1667430832892,\"deleteAt\":0,\"boardId\":\"bcm39o11e4ib8tye8mt6iyuec9o\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"cjkscjjex6fg8i8aa3umxof9wfc\",\"parentId\":\"bcm39o11e4ib8tye8mt6iyuec9o\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Increase customer retention\",\"fields\":{\"contentOrder\":[],\"icon\":\"😀\",\"isTemplate\":false,\"properties\":{\"a17ryhi1jfsboxkwkztwawhmsxe\":\"a8nukezwwmknqwjsygg7eaxs9te\",\"a6amddgmrzakw66cidqzgk6p4ge\":\"apqfjst8massbjjhpcsjs3y1yqa\",\"adp5ft3kgz7r5iqq3tnwg551der\":\"a8zg3rjtf4swh7smsjxpsn743rh\",\"ahz3fmjnaguec8hce7xq3h5cjdr\":\"66%\",\"ajwxp866f9obs1kutfwaa5ru7fe\":\"90% customer retention rate\",\"aqxyzkdrs4egqf7yk866ixkaojc\":\"acb6dqqs6yson7bbzx6jk9bghjh\",\"azqnyswk6s1boiwuthscm78qwuo\":\"60%\",\"azzbawji5bksj69sekcs4srm1ky\":\"afkxpcjqjypu7hhar7banxau91h\"}},\"createAt\":1667430973987,\"updateAt\":1667431007817,\"deleteAt\":0,\"boardId\":\"bcm39o11e4ib8tye8mt6iyuec9o\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"ckxdhpf5bhf8i7n13fgbs4155ec\",\"parentId\":\"bcm39o11e4ib8tye8mt6iyuec9o\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Hit company global sales target\",\"fields\":{\"contentOrder\":[],\"icon\":\"💰\",\"isTemplate\":false,\"properties\":{\"a17ryhi1jfsboxkwkztwawhmsxe\":\"a6robxx81diugpjq5jkezz3j1fo\",\"a6amddgmrzakw66cidqzgk6p4ge\":\"auw3afh3kfhrfgmjr8muiz137jy\",\"adp5ft3kgz7r5iqq3tnwg551der\":\"a8zg3rjtf4swh7smsjxpsn743rh\",\"ahz3fmjnaguec8hce7xq3h5cjdr\":\"15%\",\"ajwxp866f9obs1kutfwaa5ru7fe\":\"50MM\",\"aqxyzkdrs4egqf7yk866ixkaojc\":\"awfu37js3fomfkkczm1zppac57a\",\"azqnyswk6s1boiwuthscm78qwuo\":\"7.5MM\",\"azzbawji5bksj69sekcs4srm1ky\":\"agrfeaoj7d8p5ianw5iaf3191ae\"}},\"createAt\":1667430875599,\"updateAt\":1667430909496,\"deleteAt\":0,\"boardId\":\"bcm39o11e4ib8tye8mt6iyuec9o\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"cn1x4niym7tnpjg61jf1su67wcr\",\"parentId\":\"bcm39o11e4ib8tye8mt6iyuec9o\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Increase user signups by 30%\",\"fields\":{\"contentOrder\":[],\"icon\":\"💳\",\"isTemplate\":false,\"properties\":{\"a17ryhi1jfsboxkwkztwawhmsxe\":\"a6robxx81diugpjq5jkezz3j1fo\",\"a6amddgmrzakw66cidqzgk6p4ge\":\"ao9b5pxyt7tkgdohzh9oaustdhr\",\"adp5ft3kgz7r5iqq3tnwg551der\":\"as555ipyzopjjpfb5rjtssecw5e\",\"ahz3fmjnaguec8hce7xq3h5cjdr\":\"0%\",\"ajwxp866f9obs1kutfwaa5ru7fe\":\"1,000\",\"aqxyzkdrs4egqf7yk866ixkaojc\":\"acb6dqqs6yson7bbzx6jk9bghjh\",\"azqnyswk6s1boiwuthscm78qwuo\":\"0\",\"azzbawji5bksj69sekcs4srm1ky\":\"afkxpcjqjypu7hhar7banxau91h\"}},\"createAt\":1667431085923,\"updateAt\":1667431132757,\"deleteAt\":0,\"boardId\":\"bcm39o11e4ib8tye8mt6iyuec9o\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"cpa534b5natgmunis8u1ixb55pw\",\"parentId\":\"bcm39o11e4ib8tye8mt6iyuec9o\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Add 10 new customers in the EU\",\"fields\":{\"contentOrder\":[],\"icon\":\"🌍\",\"isTemplate\":false,\"properties\":{\"a17ryhi1jfsboxkwkztwawhmsxe\":\"apnt1f7na9rzgk1rt49keg7xbiy\",\"a6amddgmrzakw66cidqzgk6p4ge\":\"auw3afh3kfhrfgmjr8muiz137jy\",\"adp5ft3kgz7r5iqq3tnwg551der\":\"a1ts3ftyr8nocsicui98c89uxjy\",\"ahz3fmjnaguec8hce7xq3h5cjdr\":\"30%\",\"ajwxp866f9obs1kutfwaa5ru7fe\":\"10\",\"aqxyzkdrs4egqf7yk866ixkaojc\":\"acb6dqqs6yson7bbzx6jk9bghjh\",\"azqnyswk6s1boiwuthscm78qwuo\":\"3\",\"azzbawji5bksj69sekcs4srm1ky\":\"agrfeaoj7d8p5ianw5iaf3191ae\"}},\"createAt\":1667430190782,\"updateAt\":1667430844747,\"deleteAt\":0,\"boardId\":\"bcm39o11e4ib8tye8mt6iyuec9o\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"cq4krpnzqqfne3khfyhnn3c6r5r\",\"parentId\":\"bcm39o11e4ib8tye8mt6iyuec9o\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Launch 3 key features\",\"fields\":{\"contentOrder\":[],\"icon\":\"🚀\",\"isTemplate\":false,\"properties\":{\"a17ryhi1jfsboxkwkztwawhmsxe\":\"apnt1f7na9rzgk1rt49keg7xbiy\",\"a6amddgmrzakw66cidqzgk6p4ge\":\"ao9b5pxyt7tkgdohzh9oaustdhr\",\"adp5ft3kgz7r5iqq3tnwg551der\":\"a8zg3rjtf4swh7smsjxpsn743rh\",\"ahz3fmjnaguec8hce7xq3h5cjdr\":\"33%\",\"ajwxp866f9obs1kutfwaa5ru7fe\":\"3\",\"aqxyzkdrs4egqf7yk866ixkaojc\":\"anruuoyez51r3yjxuoc8zoqnwaw\",\"azqnyswk6s1boiwuthscm78qwuo\":\"1\",\"azzbawji5bksj69sekcs4srm1ky\":\"aw5i7hmpadn6mbwbz955ubarhme\"}},\"createAt\":1667431144882,\"updateAt\":1667431177540,\"deleteAt\":0,\"boardId\":\"bcm39o11e4ib8tye8mt6iyuec9o\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"cugiq6j98utg1zdekbpjpufo51y\",\"parentId\":\"bcm39o11e4ib8tye8mt6iyuec9o\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Reduce bug backlog by 50%\",\"fields\":{\"contentOrder\":[],\"icon\":\"🐞\",\"isTemplate\":false,\"properties\":{\"a17ryhi1jfsboxkwkztwawhmsxe\":\"abzfwnn6rmtfzyq5hg8uqmpsncy\",\"a6amddgmrzakw66cidqzgk6p4ge\":\"apqfjst8massbjjhpcsjs3y1yqa\",\"adp5ft3kgz7r5iqq3tnwg551der\":\"a1ts3ftyr8nocsicui98c89uxjy\",\"ahz3fmjnaguec8hce7xq3h5cjdr\":\"100%\",\"ajwxp866f9obs1kutfwaa5ru7fe\":\"75\",\"aqxyzkdrs4egqf7yk866ixkaojc\":\"awfu37js3fomfkkczm1zppac57a\",\"azqnyswk6s1boiwuthscm78qwuo\":\"75\",\"azzbawji5bksj69sekcs4srm1ky\":\"aw5i7hmpadn6mbwbz955ubarhme\"}},\"createAt\":1667431018282,\"updateAt\":1667431070950,\"deleteAt\":0,\"boardId\":\"bcm39o11e4ib8tye8mt6iyuec9o\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"vx4ng6gtakbntt8k98znkzszc1a\",\"parentId\":\"bm4ubx56krp4zwyfcqh7nxiigbr\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"view\",\"title\":\"Departments\",\"fields\":{\"cardOrder\":[\"cpa534b5natgmunis8u1ixb55pw\"],\"collapsedOptionIds\":[],\"columnCalculations\":{},\"columnWidths\":{},\"defaultTemplateId\":\"\",\"filter\":{\"filters\":[],\"operation\":\"and\"},\"groupById\":\"azzbawji5bksj69sekcs4srm1ky\",\"hiddenOptionIds\":[\"\"],\"kanbanCalculations\":{},\"sortOptions\":[],\"viewType\":\"board\",\"visibleOptionIds\":[\"aw5i7hmpadn6mbwbz955ubarhme\",\"afkxpcjqjypu7hhar7banxau91h\",\"aehoa17cz18rqnrf75g7dwhphpr\",\"agrfeaoj7d8p5ianw5iaf3191ae\",\"agm9p6gcq15ueuzqq3wd4be39wy\",\"aucop7kw6xwodcix6zzojhxih6r\",\"afust91f3g8ht368mkn5x9tgf1o\",\"acocxxwjurud1jixhp7nowdig7y\"],\"visiblePropertyIds\":[]},\"createAt\":1667430124232,\"updateAt\":1667431286030,\"deleteAt\":0,\"boardId\":\"bcm39o11e4ib8tye8mt6iyuec9o\"}}\n"
  },
  {
    "path": "server/assets/templates-boardarchive/bd65qbzuqupfztpg31dgwgwm5ga/board.jsonl",
    "content": "{\"type\":\"block\",\"data\":{\"id\":\"bd65qbzuqupfztpg31dgwgwm5ga\",\"parentId\":\"\",\"rootId\":\"bd65qbzuqupfztpg31dgwgwm5ga\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"board\",\"title\":\"Personal Goals (NEW)\",\"fields\":{\"cardProperties\":[{\"id\":\"af6fcbb8-ca56-4b73-83eb-37437b9a667d\",\"name\":\"Status\",\"options\":[{\"color\":\"propColorRed\",\"id\":\"bf52bfe6-ac4c-4948-821f-83eaa1c7b04a\",\"value\":\"To Do\"},{\"color\":\"propColorYellow\",\"id\":\"77c539af-309c-4db1-8329-d20ef7e9eacd\",\"value\":\"Doing\"},{\"color\":\"propColorGreen\",\"id\":\"98bdea27-0cce-4cde-8dc6-212add36e63a\",\"value\":\"Done 🙌\"}],\"type\":\"select\"},{\"id\":\"d9725d14-d5a8-48e5-8de1-6f8c004a9680\",\"name\":\"Category\",\"options\":[{\"color\":\"propColorPurple\",\"id\":\"3245a32d-f688-463b-87f4-8e7142c1b397\",\"value\":\"Life Skills\"},{\"color\":\"propColorGreen\",\"id\":\"80be816c-fc7a-4928-8489-8b02180f4954\",\"value\":\"Finance\"},{\"color\":\"propColorOrange\",\"id\":\"ffb3f951-b47f-413b-8f1d-238666728008\",\"value\":\"Health\"}],\"type\":\"select\"},{\"id\":\"d6b1249b-bc18-45fc-889e-bec48fce80ef\",\"name\":\"Target\",\"options\":[{\"color\":\"propColorBlue\",\"id\":\"9a090e33-b110-4268-8909-132c5002c90e\",\"value\":\"Q1\"},{\"color\":\"propColorBrown\",\"id\":\"0a82977f-52bf-457b-841b-e2b7f76fb525\",\"value\":\"Q2\"},{\"color\":\"propColorGreen\",\"id\":\"6e7139e4-5358-46bb-8c01-7b029a57b80a\",\"value\":\"Q3\"},{\"color\":\"propColorPurple\",\"id\":\"d5371c63-66bf-4468-8738-c4dc4bea4843\",\"value\":\"Q4\"}],\"type\":\"select\"},{\"id\":\"ajy6xbebzopojaenbnmfpgtdwso\",\"name\":\"Due Date\",\"options\":[],\"type\":\"date\"}],\"description\":\"Use this template to set and accomplish new personal goals.\",\"icon\":\"⛰️\",\"isTemplate\":false,\"showDescription\":true},\"createAt\":1641246775089,\"updateAt\":1643788318628,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"c76haqhzin78q5dkfko7kwhbjjh\",\"parentId\":\"bd65qbzuqupfztpg31dgwgwm5ga\",\"rootId\":\"bd65qbzuqupfztpg31dgwgwm5ga\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Start a daily journal\",\"fields\":{\"contentOrder\":[],\"icon\":\"✍️\",\"isTemplate\":false,\"properties\":{\"af6fcbb8-ca56-4b73-83eb-37437b9a667d\":\"bf52bfe6-ac4c-4948-821f-83eaa1c7b04a\",\"d6b1249b-bc18-45fc-889e-bec48fce80ef\":\"0a82977f-52bf-457b-841b-e2b7f76fb525\",\"d9725d14-d5a8-48e5-8de1-6f8c004a9680\":\"3245a32d-f688-463b-87f4-8e7142c1b397\"}},\"createAt\":1641246774828,\"updateAt\":1643788318630,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"ca3byfg7iq3g8zjpg1t8hwa6ekh\",\"parentId\":\"bd65qbzuqupfztpg31dgwgwm5ga\",\"rootId\":\"bd65qbzuqupfztpg31dgwgwm5ga\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Run 3 times a week\",\"fields\":{\"contentOrder\":[],\"icon\":\"🏃\",\"isTemplate\":false,\"properties\":{\"af6fcbb8-ca56-4b73-83eb-37437b9a667d\":\"bf52bfe6-ac4c-4948-821f-83eaa1c7b04a\",\"d6b1249b-bc18-45fc-889e-bec48fce80ef\":\"6e7139e4-5358-46bb-8c01-7b029a57b80a\",\"d9725d14-d5a8-48e5-8de1-6f8c004a9680\":\"ffb3f951-b47f-413b-8f1d-238666728008\"}},\"createAt\":1641246775039,\"updateAt\":1643788318630,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"ckng5n1ag5f8m5gfdifn7ijof9y\",\"parentId\":\"bd65qbzuqupfztpg31dgwgwm5ga\",\"rootId\":\"bd65qbzuqupfztpg31dgwgwm5ga\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Learn to paint\",\"fields\":{\"contentOrder\":[],\"icon\":\"🎨\",\"isTemplate\":false,\"properties\":{\"af6fcbb8-ca56-4b73-83eb-37437b9a667d\":\"77c539af-309c-4db1-8329-d20ef7e9eacd\",\"d6b1249b-bc18-45fc-889e-bec48fce80ef\":\"9a090e33-b110-4268-8909-132c5002c90e\",\"d9725d14-d5a8-48e5-8de1-6f8c004a9680\":\"3245a32d-f688-463b-87f4-8e7142c1b397\"}},\"createAt\":1641246774928,\"updateAt\":1643788318630,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"cw9zofoi6dj8x7x8r6ypebpwpuc\",\"parentId\":\"bd65qbzuqupfztpg31dgwgwm5ga\",\"rootId\":\"bd65qbzuqupfztpg31dgwgwm5ga\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Open retirement account\",\"fields\":{\"contentOrder\":[],\"icon\":\"🏦\",\"isTemplate\":false,\"properties\":{\"af6fcbb8-ca56-4b73-83eb-37437b9a667d\":\"bf52bfe6-ac4c-4948-821f-83eaa1c7b04a\",\"d6b1249b-bc18-45fc-889e-bec48fce80ef\":\"0a82977f-52bf-457b-841b-e2b7f76fb525\",\"d9725d14-d5a8-48e5-8de1-6f8c004a9680\":\"80be816c-fc7a-4928-8489-8b02180f4954\"}},\"createAt\":1641246774987,\"updateAt\":1643788318630,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"v9sj7oekk1jr1pemtf9rps7fate\",\"parentId\":\"bd65qbzuqupfztpg31dgwgwm5ga\",\"rootId\":\"bd65qbzuqupfztpg31dgwgwm5ga\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"view\",\"title\":\"By Status\",\"fields\":{\"cardOrder\":[],\"collapsedOptionIds\":[],\"columnCalculations\":{},\"columnWidths\":{},\"defaultTemplateId\":\"\",\"filter\":{\"filters\":[],\"operation\":\"and\"},\"groupById\":\"af6fcbb8-ca56-4b73-83eb-37437b9a667d\",\"hiddenOptionIds\":[],\"kanbanCalculations\":{},\"sortOptions\":[],\"viewType\":\"board\",\"visibleOptionIds\":[\"bf52bfe6-ac4c-4948-821f-83eaa1c7b04a\",\"77c539af-309c-4db1-8329-d20ef7e9eacd\",\"98bdea27-0cce-4cde-8dc6-212add36e63a\",\"\"],\"visiblePropertyIds\":[\"d9725d14-d5a8-48e5-8de1-6f8c004a9680\",\"d6b1249b-bc18-45fc-889e-bec48fce80ef\"]},\"createAt\":1641246774878,\"updateAt\":1643788318630,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"vrpmc8r6nj7fcmdkp18cpcekzco\",\"parentId\":\"bd65qbzuqupfztpg31dgwgwm5ga\",\"rootId\":\"bd65qbzuqupfztpg31dgwgwm5ga\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"view\",\"title\":\"Calendar View\",\"fields\":{\"cardOrder\":[],\"collapsedOptionIds\":[],\"columnCalculations\":{},\"columnWidths\":{},\"dateDisplayPropertyId\":\"ajy6xbebzopojaenbnmfpgtdwso\",\"defaultTemplateId\":\"\",\"filter\":{\"filters\":[],\"operation\":\"and\"},\"hiddenOptionIds\":[],\"kanbanCalculations\":{},\"sortOptions\":[],\"viewType\":\"calendar\",\"visibleOptionIds\":[],\"visiblePropertyIds\":[\"__title\"]},\"createAt\":1641247726340,\"updateAt\":1643788318630,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"vw9mbn66j97dwb8jhqiq7zuum5e\",\"parentId\":\"bd65qbzuqupfztpg31dgwgwm5ga\",\"rootId\":\"bd65qbzuqupfztpg31dgwgwm5ga\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"view\",\"title\":\"By Date\",\"fields\":{\"cardOrder\":[],\"collapsedOptionIds\":[],\"columnCalculations\":{},\"columnWidths\":{},\"defaultTemplateId\":\"\",\"filter\":{\"filters\":[],\"operation\":\"and\"},\"groupById\":\"d6b1249b-bc18-45fc-889e-bec48fce80ef\",\"hiddenOptionIds\":[],\"kanbanCalculations\":{},\"sortOptions\":[],\"viewType\":\"board\",\"visibleOptionIds\":[\"9a090e33-b110-4268-8909-132c5002c90e\",\"0a82977f-52bf-457b-841b-e2b7f76fb525\",\"6e7139e4-5358-46bb-8c01-7b029a57b80a\",\"d5371c63-66bf-4468-8738-c4dc4bea4843\",\"\"],\"visiblePropertyIds\":[\"d9725d14-d5a8-48e5-8de1-6f8c004a9680\"]},\"createAt\":1641246775139,\"updateAt\":1643788318631,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n"
  },
  {
    "path": "server/assets/templates-boardarchive/bgi1yqiis8t8xdqxgnet8ebutky/board.jsonl",
    "content": "{\"type\":\"board\",\"data\":{\"id\":\"bgi1yqiis8t8xdqxgnet8ebutky\",\"teamId\":\"qghzt68dq7bopgqamcnziq69ao\",\"channelId\":\"\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"type\":\"P\",\"minimumRole\":\"\",\"title\":\"Sprint Planner \",\"description\":\"Use this template to plan your sprints and manage your releases more efficiently.\",\"icon\":\"🗓️\",\"showDescription\":true,\"isTemplate\":false,\"templateVersion\":4,\"properties\":{},\"cardProperties\":[{\"id\":\"50117d52-bcc7-4750-82aa-831a351c44a0\",\"name\":\"Status\",\"options\":[{\"color\":\"propColorGray\",\"id\":\"aft5bzo7h9aspqgrx3jpy5tzrer\",\"value\":\"Not Started\"},{\"color\":\"propColorOrange\",\"id\":\"abrfos7e7eczk9rqw6y5abadm1y\",\"value\":\"Next Up\"},{\"color\":\"propColorBlue\",\"id\":\"ax8wzbka5ahs3zziji3pp4qp9mc\",\"value\":\"In Progress\"},{\"color\":\"propColorYellow\",\"id\":\"atabdfbdmjh83136d5e5oysxybw\",\"value\":\"In Review\"},{\"color\":\"propColorPink\",\"id\":\"ace1bzypd586kkyhcht5qqd9eca\",\"value\":\"Approved\"},{\"color\":\"propColorRed\",\"id\":\"aay656c9m1hzwxc9ch5ftymh3nw\",\"value\":\"Blocked\"},{\"color\":\"propColorGreen\",\"id\":\"a6ghze4iy441qhsh3eijnc8hwze\",\"value\":\"Complete 🙌\"}],\"type\":\"select\"},{\"id\":\"20717ad3-5741-4416-83f1-6f133fff3d11\",\"name\":\"Type\",\"options\":[{\"color\":\"propColorYellow\",\"id\":\"424ea5e3-9aa1-4075-8c5c-01b44b66e634\",\"value\":\"Epic ⛰\"},{\"color\":\"propColorGray\",\"id\":\"a5yxq8rbubrpnoommfwqmty138h\",\"value\":\"Feature 🏗\"},{\"color\":\"propColorOrange\",\"id\":\"apht1nt5ryukdmxkh6fkfn6rgoy\",\"value\":\"User Story 📖\"},{\"color\":\"propColorGreen\",\"id\":\"aiycbuo3dr5k4xxbfr7coem8ono\",\"value\":\"Task ⛏\"},{\"color\":\"propColorRed\",\"id\":\"aomnawq4551cbbzha9gxnmb3z5w\",\"value\":\"Bug 🐞\"}],\"type\":\"select\"},{\"id\":\"60985f46-3e41-486e-8213-2b987440ea1c\",\"name\":\"Sprint\",\"options\":[{\"color\":\"propColorBrown\",\"id\":\"c01676ca-babf-4534-8be5-cce2287daa6c\",\"value\":\"Sprint 1\"},{\"color\":\"propColorPurple\",\"id\":\"ed4a5340-460d-461b-8838-2c56e8ee59fe\",\"value\":\"Sprint 2\"},{\"color\":\"propColorBlue\",\"id\":\"14892380-1a32-42dd-8034-a0cea32bc7e6\",\"value\":\"Sprint 3\"}],\"type\":\"select\"},{\"id\":\"f7f3ad42-b31a-4ac2-81f0-28ea80c5b34e\",\"name\":\"Priority\",\"options\":[{\"color\":\"propColorRed\",\"id\":\"cb8ecdac-38be-4d36-8712-c4d58cc8a8e9\",\"value\":\"P1 🔥\"},{\"color\":\"propColorYellow\",\"id\":\"e6a7f297-4440-4783-8ab3-3af5ba62ca11\",\"value\":\"P2\"},{\"color\":\"propColorGray\",\"id\":\"c62172ea-5da7-4dec-8186-37267d8ee9a7\",\"value\":\"P3\"}],\"type\":\"select\"},{\"id\":\"aphg37f7zbpuc3bhwhp19s1ribh\",\"name\":\"Assignee\",\"options\":[],\"type\":\"multiPerson\"},{\"id\":\"a4378omyhmgj3bex13sj4wbpfiy\",\"name\":\"Due Date\",\"options\":[],\"type\":\"date\"},{\"id\":\"ai7ajsdk14w7x5s8up3dwir77te\",\"name\":\"Story Points\",\"options\":[],\"type\":\"number\"},{\"id\":\"a1g6i613dpe9oryeo71ex3c86hy\",\"name\":\"Design Link\",\"options\":[],\"type\":\"url\"},{\"id\":\"aeomttrbhhsi8bph31jn84sto6h\",\"name\":\"Created Time\",\"options\":[],\"type\":\"createdTime\"},{\"id\":\"ax9f8so418s6s65hi5ympd93i6a\",\"name\":\"Created By\",\"options\":[],\"type\":\"createdBy\"}],\"createAt\":1657660691136,\"updateAt\":1667496289175,\"deleteAt\":0}}\n{\"type\":\"block\",\"data\":{\"id\":\"vdusd7mmojjy7dqtcews89kbawe\",\"parentId\":\"\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"view\",\"title\":\"By Sprint\",\"fields\":{\"cardOrder\":[],\"collapsedOptionIds\":[],\"columnCalculations\":{\"ai7ajsdk14w7x5s8up3dwir77te\":\"count\"},\"columnWidths\":{\"20717ad3-5741-4416-83f1-6f133fff3d11\":128,\"50117d52-bcc7-4750-82aa-831a351c44a0\":126,\"__title\":280,\"a1g6i613dpe9oryeo71ex3c86hy\":159,\"aeomttrbhhsi8bph31jn84sto6h\":141,\"ax9f8so418s6s65hi5ympd93i6a\":183,\"f7f3ad42-b31a-4ac2-81f0-28ea80c5b34e\":100},\"defaultTemplateId\":\"c9pwabyseiibumq71b9ykxsotqe\",\"filter\":{\"filters\":[],\"operation\":\"and\"},\"groupById\":\"60985f46-3e41-486e-8213-2b987440ea1c\",\"hiddenOptionIds\":[\"\"],\"kanbanCalculations\":{},\"sortOptions\":[],\"viewType\":\"table\",\"visibleOptionIds\":[],\"visiblePropertyIds\":[\"50117d52-bcc7-4750-82aa-831a351c44a0\",\"20717ad3-5741-4416-83f1-6f133fff3d11\",\"60985f46-3e41-486e-8213-2b987440ea1c\",\"f7f3ad42-b31a-4ac2-81f0-28ea80c5b34e\",\"aphg37f7zbpuc3bhwhp19s1ribh\",\"a4378omyhmgj3bex13sj4wbpfiy\",\"ai7ajsdk14w7x5s8up3dwir77te\",\"a1g6i613dpe9oryeo71ex3c86hy\",\"aeomttrbhhsi8bph31jn84sto6h\",\"ax9f8so418s6s65hi5ympd93i6a\"]},\"createAt\":1667495373961,\"updateAt\":1667507826320,\"deleteAt\":0,\"boardId\":\"bgi1yqiis8t8xdqxgnet8ebutky\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"c9pwabyseiibumq71b9ykxsotqe\",\"parentId\":\"bgi1yqiis8t8xdqxgnet8ebutky\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"User Story\",\"fields\":{\"contentOrder\":[\"anfmjd4qmxffj3bckd9nei61ioe\"],\"icon\":\"📖\",\"isTemplate\":true,\"properties\":{\"20717ad3-5741-4416-83f1-6f133fff3d11\":\"apht1nt5ryukdmxkh6fkfn6rgoy\",\"50117d52-bcc7-4750-82aa-831a351c44a0\":\"aft5bzo7h9aspqgrx3jpy5tzrer\"}},\"createAt\":1667496557683,\"updateAt\":1667496593762,\"deleteAt\":0,\"boardId\":\"bgi1yqiis8t8xdqxgnet8ebutky\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"c9rh5kubchfy1tejtiwbsw6z5xr\",\"parentId\":\"bgi1yqiis8t8xdqxgnet8ebutky\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Horizontal scroll issue\",\"fields\":{\"contentOrder\":[\"aiazua9893f8tmgn5jcn476ieay\",\"ayko7csybxpgg7ejnybqoimp6co\",\"7n75owjmi1bfnbcdswmscqpon5r\"],\"icon\":\"〰️\",\"isTemplate\":false,\"properties\":{\"20717ad3-5741-4416-83f1-6f133fff3d11\":\"aomnawq4551cbbzha9gxnmb3z5w\",\"50117d52-bcc7-4750-82aa-831a351c44a0\":\"aft5bzo7h9aspqgrx3jpy5tzrer\",\"60985f46-3e41-486e-8213-2b987440ea1c\":\"ed4a5340-460d-461b-8838-2c56e8ee59fe\",\"ai7ajsdk14w7x5s8up3dwir77te\":\"1\",\"f7f3ad42-b31a-4ac2-81f0-28ea80c5b34e\":\"e6a7f297-4440-4783-8ab3-3af5ba62ca11\"}},\"createAt\":1657660691350,\"updateAt\":1667495472233,\"deleteAt\":0,\"boardId\":\"bgi1yqiis8t8xdqxgnet8ebutky\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"cc98t3whwhbnd5mx4qehmg43wpy\",\"parentId\":\"bgi1yqiis8t8xdqxgnet8ebutky\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Login screen not loading\",\"fields\":{\"contentOrder\":[\"ahaytdn7aajy63dsca6dhmzew6e\",\"awcedibyeufyazxdy6x83wiqtne\",\"73u5teq68rbrsfensjkigjfsk3h\"],\"icon\":\"🖥️\",\"isTemplate\":false,\"properties\":{\"20717ad3-5741-4416-83f1-6f133fff3d11\":\"aomnawq4551cbbzha9gxnmb3z5w\",\"50117d52-bcc7-4750-82aa-831a351c44a0\":\"abrfos7e7eczk9rqw6y5abadm1y\",\"60985f46-3e41-486e-8213-2b987440ea1c\":\"c01676ca-babf-4534-8be5-cce2287daa6c\",\"ai7ajsdk14w7x5s8up3dwir77te\":\"1\",\"f7f3ad42-b31a-4ac2-81f0-28ea80c5b34e\":\"cb8ecdac-38be-4d36-8712-c4d58cc8a8e9\"}},\"createAt\":1657660691556,\"updateAt\":1667495472221,\"deleteAt\":0,\"boardId\":\"bgi1yqiis8t8xdqxgnet8ebutky\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"cfb7jed1iz3ntx8rrcc5pphaixc\",\"parentId\":\"bgi1yqiis8t8xdqxgnet8ebutky\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Move cards across boards\",\"fields\":{\"contentOrder\":[\"aqm83zjjchi8a8nramuatg88cer\"],\"icon\":\"🚚\",\"isTemplate\":false,\"properties\":{\"20717ad3-5741-4416-83f1-6f133fff3d11\":\"a5yxq8rbubrpnoommfwqmty138h\",\"50117d52-bcc7-4750-82aa-831a351c44a0\":\"abrfos7e7eczk9rqw6y5abadm1y\",\"60985f46-3e41-486e-8213-2b987440ea1c\":\"ed4a5340-460d-461b-8838-2c56e8ee59fe\",\"a1g6i613dpe9oryeo71ex3c86hy\":\"https://mattermost.com/boards/\",\"ai7ajsdk14w7x5s8up3dwir77te\":\"2\",\"f7f3ad42-b31a-4ac2-81f0-28ea80c5b34e\":\"e6a7f297-4440-4783-8ab3-3af5ba62ca11\"}},\"createAt\":1657660691670,\"updateAt\":1667495472240,\"deleteAt\":0,\"boardId\":\"bgi1yqiis8t8xdqxgnet8ebutky\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"cg1ausqdw9bdpbgx1aaoas6umaa\",\"parentId\":\"bgi1yqiis8t8xdqxgnet8ebutky\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Cross-team collaboration\",\"fields\":{\"contentOrder\":[\"aanatt8ay8iyj7n4gxstxijiber\"],\"icon\":\"🤝\",\"isTemplate\":false,\"properties\":{\"20717ad3-5741-4416-83f1-6f133fff3d11\":\"424ea5e3-9aa1-4075-8c5c-01b44b66e634\",\"50117d52-bcc7-4750-82aa-831a351c44a0\":\"aft5bzo7h9aspqgrx3jpy5tzrer\",\"60985f46-3e41-486e-8213-2b987440ea1c\":\"14892380-1a32-42dd-8034-a0cea32bc7e6\",\"a1g6i613dpe9oryeo71ex3c86hy\":\"https://mattermost.com/boards/\",\"ai7ajsdk14w7x5s8up3dwir77te\":\"3\",\"f7f3ad42-b31a-4ac2-81f0-28ea80c5b34e\":\"c62172ea-5da7-4dec-8186-37267d8ee9a7\"}},\"createAt\":1657660691791,\"updateAt\":1667495472239,\"deleteAt\":0,\"boardId\":\"bgi1yqiis8t8xdqxgnet8ebutky\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"ci1zytcoz1jrj3qncas3fjc9ruo\",\"parentId\":\"bgi1yqiis8t8xdqxgnet8ebutky\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Bug\",\"fields\":{\"contentOrder\":[\"ajgyboqst3fy1zb989wkuqiaz5o\",\"akzfz1eh8uj87mfkgucgmtdzwzw\",\"7zbiceo9toidtbjya5xxo6fcsow\"],\"icon\":\"🐞\",\"isTemplate\":true,\"properties\":{\"20717ad3-5741-4416-83f1-6f133fff3d11\":\"aomnawq4551cbbzha9gxnmb3z5w\",\"50117d52-bcc7-4750-82aa-831a351c44a0\":\"aft5bzo7h9aspqgrx3jpy5tzrer\"}},\"createAt\":1667507786809,\"updateAt\":1667507806029,\"deleteAt\":0,\"boardId\":\"bgi1yqiis8t8xdqxgnet8ebutky\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"cs7rqsonyr7gofepxn84ui8niyy\",\"parentId\":\"bgi1yqiis8t8xdqxgnet8ebutky\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Standard properties\",\"fields\":{\"contentOrder\":[\"ax5npjmoqo7b87fzjo518ahfdkc\"],\"icon\":\"🏷️\",\"isTemplate\":false,\"properties\":{\"20717ad3-5741-4416-83f1-6f133fff3d11\":\"a5yxq8rbubrpnoommfwqmty138h\",\"50117d52-bcc7-4750-82aa-831a351c44a0\":\"aft5bzo7h9aspqgrx3jpy5tzrer\",\"60985f46-3e41-486e-8213-2b987440ea1c\":\"14892380-1a32-42dd-8034-a0cea32bc7e6\",\"a1g6i613dpe9oryeo71ex3c86hy\":\"https://mattermost.com/boards/\",\"ai7ajsdk14w7x5s8up3dwir77te\":\"3\",\"f7f3ad42-b31a-4ac2-81f0-28ea80c5b34e\":\"e6a7f297-4440-4783-8ab3-3af5ba62ca11\"}},\"createAt\":1657660691454,\"updateAt\":1667495472304,\"deleteAt\":0,\"boardId\":\"bgi1yqiis8t8xdqxgnet8ebutky\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"cxi14orfaajfsjjpgok167kc78y\",\"parentId\":\"bgi1yqiis8t8xdqxgnet8ebutky\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Epic\",\"fields\":{\"contentOrder\":[\"aoer81hcfmt818d1awj3bnntkzh\"],\"icon\":\"🤝\",\"isTemplate\":true,\"properties\":{\"20717ad3-5741-4416-83f1-6f133fff3d11\":\"424ea5e3-9aa1-4075-8c5c-01b44b66e634\",\"50117d52-bcc7-4750-82aa-831a351c44a0\":\"aft5bzo7h9aspqgrx3jpy5tzrer\",\"a1g6i613dpe9oryeo71ex3c86hy\":\"https://mattermost.com/boards/\",\"ai7ajsdk14w7x5s8up3dwir77te\":\"3\",\"f7f3ad42-b31a-4ac2-81f0-28ea80c5b34e\":\"c62172ea-5da7-4dec-8186-37267d8ee9a7\"}},\"createAt\":1667496390689,\"updateAt\":1667496493419,\"deleteAt\":0,\"boardId\":\"bgi1yqiis8t8xdqxgnet8ebutky\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"cxmfbp7wdoifdzdztkrurxe3pgh\",\"parentId\":\"bgi1yqiis8t8xdqxgnet8ebutky\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Global templates\",\"fields\":{\"contentOrder\":[\"a6r3jdde39ibbury8s8zib5prjy\"],\"icon\":\"🖼️\",\"isTemplate\":false,\"properties\":{\"20717ad3-5741-4416-83f1-6f133fff3d11\":\"a5yxq8rbubrpnoommfwqmty138h\",\"50117d52-bcc7-4750-82aa-831a351c44a0\":\"a6ghze4iy441qhsh3eijnc8hwze\",\"60985f46-3e41-486e-8213-2b987440ea1c\":\"c01676ca-babf-4534-8be5-cce2287daa6c\",\"a1g6i613dpe9oryeo71ex3c86hy\":\"https://mattermost.com/boards/\",\"ai7ajsdk14w7x5s8up3dwir77te\":\"2\",\"f7f3ad42-b31a-4ac2-81f0-28ea80c5b34e\":\"e6a7f297-4440-4783-8ab3-3af5ba62ca11\"}},\"createAt\":1657660691245,\"updateAt\":1667496491020,\"deleteAt\":0,\"boardId\":\"bgi1yqiis8t8xdqxgnet8ebutky\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"cxom5chmr5tna9ru4na34dbhmur\",\"parentId\":\"bgi1yqiis8t8xdqxgnet8ebutky\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Feature\",\"fields\":{\"contentOrder\":[\"ad9bf7wpdwbnwbebkptg3puwu4c\"],\"icon\":\"🏗️\",\"isTemplate\":true,\"properties\":{\"20717ad3-5741-4416-83f1-6f133fff3d11\":\"a5yxq8rbubrpnoommfwqmty138h\",\"50117d52-bcc7-4750-82aa-831a351c44a0\":\"aft5bzo7h9aspqgrx3jpy5tzrer\"}},\"createAt\":1667496496593,\"updateAt\":1667496522591,\"deleteAt\":0,\"boardId\":\"bgi1yqiis8t8xdqxgnet8ebutky\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"v4fpda1kk3jgy8ctqyw9ey4fwye\",\"parentId\":\"bgi1yqiis8t8xdqxgnet8ebutky\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"view\",\"title\":\"By Status\",\"fields\":{\"cardOrder\":[\"cxmfbp7wdoifdzdztkrurxe3pgh\",\"c9rh5kubchfy1tejtiwbsw6z5xr\",\"cfb7jed1iz3ntx8rrcc5pphaixc\",\"cc98t3whwhbnd5mx4qehmg43wpy\",\"cs7rqsonyr7gofepxn84ui8niyy\",\"cg1ausqdw9bdpbgx1aaoas6umaa\"],\"collapsedOptionIds\":[],\"columnCalculations\":{},\"columnWidths\":{},\"defaultTemplateId\":\"cidz4imnqhir48brz6e8hxhfrhy\",\"filter\":{\"filters\":[],\"operation\":\"and\"},\"groupById\":\"50117d52-bcc7-4750-82aa-831a351c44a0\",\"hiddenOptionIds\":[\"\"],\"kanbanCalculations\":{},\"sortOptions\":[{\"propertyId\":\"f7f3ad42-b31a-4ac2-81f0-28ea80c5b34e\",\"reversed\":false}],\"viewType\":\"board\",\"visibleOptionIds\":[\"aft5bzo7h9aspqgrx3jpy5tzrer\",\"abrfos7e7eczk9rqw6y5abadm1y\",\"ax8wzbka5ahs3zziji3pp4qp9mc\",\"atabdfbdmjh83136d5e5oysxybw\",\"ace1bzypd586kkyhcht5qqd9eca\",\"aay656c9m1hzwxc9ch5ftymh3nw\",\"a6ghze4iy441qhsh3eijnc8hwze\"],\"visiblePropertyIds\":[\"20717ad3-5741-4416-83f1-6f133fff3d11\",\"60985f46-3e41-486e-8213-2b987440ea1c\",\"f7f3ad42-b31a-4ac2-81f0-28ea80c5b34e\"]},\"createAt\":1657660691994,\"updateAt\":1667496285840,\"deleteAt\":0,\"boardId\":\"bgi1yqiis8t8xdqxgnet8ebutky\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"vj3bern6637nt7c5edfx8qx6b6h\",\"parentId\":\"bgi1yqiis8t8xdqxgnet8ebutky\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"view\",\"title\":\"By Type\",\"fields\":{\"cardOrder\":[\"cc98t3whwhbnd5mx4qehmg43wpy\",\"cfb7jed1iz3ntx8rrcc5pphaixc\",\"cs7rqsonyr7gofepxn84ui8niyy\",\"c9rh5kubchfy1tejtiwbsw6z5xr\",\"cxmfbp7wdoifdzdztkrurxe3pgh\",\"cg1ausqdw9bdpbgx1aaoas6umaa\"],\"collapsedOptionIds\":[],\"columnCalculations\":{},\"columnWidths\":{},\"defaultTemplateId\":\"cidz4imnqhir48brz6e8hxhfrhy\",\"filter\":{\"filters\":[],\"operation\":\"and\"},\"groupById\":\"20717ad3-5741-4416-83f1-6f133fff3d11\",\"hiddenOptionIds\":[\"\"],\"kanbanCalculations\":{},\"sortOptions\":[{\"propertyId\":\"f7f3ad42-b31a-4ac2-81f0-28ea80c5b34e\",\"reversed\":false}],\"viewType\":\"board\",\"visibleOptionIds\":[\"424ea5e3-9aa1-4075-8c5c-01b44b66e634\",\"a5yxq8rbubrpnoommfwqmty138h\",\"apht1nt5ryukdmxkh6fkfn6rgoy\",\"aiycbuo3dr5k4xxbfr7coem8ono\",\"aomnawq4551cbbzha9gxnmb3z5w\"],\"visiblePropertyIds\":[\"20717ad3-5741-4416-83f1-6f133fff3d11\",\"f7f3ad42-b31a-4ac2-81f0-28ea80c5b34e\"]},\"createAt\":1657660691890,\"updateAt\":1667496327144,\"deleteAt\":0,\"boardId\":\"bgi1yqiis8t8xdqxgnet8ebutky\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"anfmjd4qmxffj3bckd9nei61ioe\",\"parentId\":\"c9pwabyseiibumq71b9ykxsotqe\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Description\\n*[Brief description of this task]*\\n\\n## Requirements\\n- *[Requirement 1]*\\n- *[Requirement 2]*\\n- ...\",\"fields\":{},\"createAt\":1667496557692,\"updateAt\":1667496557692,\"deleteAt\":0,\"boardId\":\"bgi1yqiis8t8xdqxgnet8ebutky\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7n75owjmi1bfnbcdswmscqpon5r\",\"parentId\":\"c9rh5kubchfy1tejtiwbsw6z5xr\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"image\",\"title\":\"\",\"fields\":{\"fileId\":\"7tmfu5iqju3n1mdfwi5gru89qmw.png\"},\"createAt\":1657660690017,\"updateAt\":1657660690017,\"deleteAt\":0,\"boardId\":\"bgi1yqiis8t8xdqxgnet8ebutky\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"aiazua9893f8tmgn5jcn476ieay\",\"parentId\":\"c9rh5kubchfy1tejtiwbsw6z5xr\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Steps to reproduce the behavior\\n1. Go to ...\\n2. Select  ...\\n3. Scroll down to ...\\n4. See error\\n\\n## Expected behavior\\n*[A clear and concise description of what you expected to happen.]*\\n\\n## Edition and Platform\\n- Edition: *[e.g. Personal Desktop / Personal Server / Mattermost plugin]*\\n- Version: *[e.g. v0.9.0]*\\n- Browser and OS: *[e.g. Chrome 91 on macOS, Edge 93 on Windows]*\\n\\n## Additional context\\n*[Add any other context about the problem here.]*\",\"fields\":{},\"createAt\":1657660691037,\"updateAt\":1657660691037,\"deleteAt\":0,\"boardId\":\"bgi1yqiis8t8xdqxgnet8ebutky\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"ayko7csybxpgg7ejnybqoimp6co\",\"parentId\":\"c9rh5kubchfy1tejtiwbsw6z5xr\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Screenshots\\n*[If applicable, add screenshots to elaborate on the problem.]*\",\"fields\":{},\"createAt\":1657660690831,\"updateAt\":1657660690831,\"deleteAt\":0,\"boardId\":\"bgi1yqiis8t8xdqxgnet8ebutky\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"73u5teq68rbrsfensjkigjfsk3h\",\"parentId\":\"cc98t3whwhbnd5mx4qehmg43wpy\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"image\",\"title\":\"\",\"fields\":{\"fileId\":\"7b9xk9boj3fbqfm3umeaaizp8qr.png\"},\"createAt\":1657660690116,\"updateAt\":1657660690116,\"deleteAt\":0,\"boardId\":\"bgi1yqiis8t8xdqxgnet8ebutky\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"ahaytdn7aajy63dsca6dhmzew6e\",\"parentId\":\"cc98t3whwhbnd5mx4qehmg43wpy\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Steps to reproduce the behavior\\n1. Go to ...\\n2. Select  ...\\n3. Scroll down to ...\\n4. See error\\n\\n## Expected behavior\\n*[A clear and concise description of what you expected to happen.]*\\n\\n## Edition and Platform\\n- Edition: *[e.g. Personal Desktop / Personal Server / Mattermost plugin]*\\n- Version: *[e.g. v0.9.0]*\\n- Browser and OS: *[e.g. Chrome 91 on macOS, Edge 93 on Windows]*\\n\\n## Additional context\\n*[Add any other context about the problem here.]*\",\"fields\":{},\"createAt\":1657660690422,\"updateAt\":1657660690422,\"deleteAt\":0,\"boardId\":\"bgi1yqiis8t8xdqxgnet8ebutky\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"awcedibyeufyazxdy6x83wiqtne\",\"parentId\":\"cc98t3whwhbnd5mx4qehmg43wpy\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Screenshots\\n*[If applicable, add screenshots to elaborate on the problem.]*\",\"fields\":{},\"createAt\":1657660690318,\"updateAt\":1657660690318,\"deleteAt\":0,\"boardId\":\"bgi1yqiis8t8xdqxgnet8ebutky\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"aqm83zjjchi8a8nramuatg88cer\",\"parentId\":\"cfb7jed1iz3ntx8rrcc5pphaixc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Description\\n*[Brief description of this task]*\\n\\n## Requirements\\n- *[Requirement 1]*\\n- *[Requirement 2]*\\n- ...\",\"fields\":{},\"createAt\":1657660690521,\"updateAt\":1657660690521,\"deleteAt\":0,\"boardId\":\"bgi1yqiis8t8xdqxgnet8ebutky\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"aumokx4tdmjrgxgy4o8s3jow8ha\",\"parentId\":\"cfmk7771httynm8r7rm8cbrmrya\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Steps to reproduce the behavior\\n1. Go to ...\\n2. Select  ...\\n3. Scroll down to ...\\n4. See error\\n\\n## Expected behavior\\n*[A clear and concise description of what you expected to happen.]*\\n\\n## Edition and Platform\\n- Edition: *[e.g. Personal Desktop / Personal Server / Mattermost plugin]*\\n- Version: *[e.g. v0.9.0]*\\n- Browser and OS: *[e.g. Chrome 91 on macOS, Edge 93 on Windows]*\\n\\n## Additional context\\n*[Add any other context about the problem here.]*\",\"fields\":{},\"createAt\":1657729295838,\"updateAt\":1657729295838,\"deleteAt\":0,\"boardId\":\"bgi1yqiis8t8xdqxgnet8ebutky\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"aydywea4hq3rytf3k7a9y4iqtbe\",\"parentId\":\"cfmk7771httynm8r7rm8cbrmrya\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Screenshots\\n*[If applicable, add screenshots to elaborate on the problem.]*\",\"fields\":{},\"createAt\":1657729295724,\"updateAt\":1657729295724,\"deleteAt\":0,\"boardId\":\"bgi1yqiis8t8xdqxgnet8ebutky\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"aanatt8ay8iyj7n4gxstxijiber\",\"parentId\":\"cg1ausqdw9bdpbgx1aaoas6umaa\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Summary\\n*[Brief description of what this epic is about]*\\n\\n## Motivation\\n*[Brief description on why this is needed]*\\n\\n## Acceptance Criteria\\n - *[Criteron 1]*\\n - *[Criteron 2]*\\n - ...\\n\\n## Personas\\n - *[Persona A]*\\n - *[Persona B]*\\n - ...\\n\\n## Reference Materials\\n - *[Links to other relevant documents as needed]*\\n - ...\",\"fields\":{},\"createAt\":1657660690218,\"updateAt\":1657660690218,\"deleteAt\":0,\"boardId\":\"bgi1yqiis8t8xdqxgnet8ebutky\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7zbiceo9toidtbjya5xxo6fcsow\",\"parentId\":\"ci1zytcoz1jrj3qncas3fjc9ruo\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"image\",\"title\":\"\",\"fields\":{\"fileId\":\"7tmfu5iqju3n1mdfwi5gru89qmw.png\"},\"createAt\":1667507786817,\"updateAt\":1667507786817,\"deleteAt\":0,\"boardId\":\"bgi1yqiis8t8xdqxgnet8ebutky\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"ajgyboqst3fy1zb989wkuqiaz5o\",\"parentId\":\"ci1zytcoz1jrj3qncas3fjc9ruo\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Steps to reproduce the behavior\\n1. Go to ...\\n2. Select  ...\\n3. Scroll down to ...\\n4. See error\\n\\n## Expected behavior\\n*[A clear and concise description of what you expected to happen.]*\\n\\n## Edition and Platform\\n- Edition: *[e.g. Personal Desktop / Personal Server / Mattermost plugin]*\\n- Version: *[e.g. v0.9.0]*\\n- Browser and OS: *[e.g. Chrome 91 on macOS, Edge 93 on Windows]*\\n\\n## Additional context\\n*[Add any other context about the problem here.]*\",\"fields\":{},\"createAt\":1667507786830,\"updateAt\":1667507786830,\"deleteAt\":0,\"boardId\":\"bgi1yqiis8t8xdqxgnet8ebutky\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"akzfz1eh8uj87mfkgucgmtdzwzw\",\"parentId\":\"ci1zytcoz1jrj3qncas3fjc9ruo\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Screenshots\\n*[If applicable, add screenshots to elaborate on the problem.]*\",\"fields\":{},\"createAt\":1667507786823,\"updateAt\":1667507786823,\"deleteAt\":0,\"boardId\":\"bgi1yqiis8t8xdqxgnet8ebutky\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"ax5npjmoqo7b87fzjo518ahfdkc\",\"parentId\":\"cs7rqsonyr7gofepxn84ui8niyy\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Description\\n*[Brief description of this task]*\\n\\n## Requirements\\n- *[Requirement 1]*\\n- *[Requirement 2]*\\n- ...\",\"fields\":{},\"createAt\":1657660690723,\"updateAt\":1657660690723,\"deleteAt\":0,\"boardId\":\"bgi1yqiis8t8xdqxgnet8ebutky\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"aoer81hcfmt818d1awj3bnntkzh\",\"parentId\":\"cxi14orfaajfsjjpgok167kc78y\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Summary\\n*[Brief description of what this epic is about]*\\n\\n## Motivation\\n*[Brief description on why this is needed]*\\n\\n## Acceptance Criteria\\n - *[Criteron 1]*\\n - *[Criteron 2]*\\n - ...\\n\\n## Personas\\n - *[Persona A]*\\n - *[Persona B]*\\n - ...\\n\\n## Reference Materials\\n - *[Links to other relevant documents as needed]*\\n - ...\",\"fields\":{},\"createAt\":1667496390699,\"updateAt\":1667496390699,\"deleteAt\":0,\"boardId\":\"bgi1yqiis8t8xdqxgnet8ebutky\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"a6r3jdde39ibbury8s8zib5prjy\",\"parentId\":\"cxmfbp7wdoifdzdztkrurxe3pgh\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Description\\n*[Brief description of this task]*\\n\\n## Requirements\\n- *[Requirement 1]*\\n- *[Requirement 2]*\\n- ...\",\"fields\":{},\"createAt\":1657660690935,\"updateAt\":1657660690935,\"deleteAt\":0,\"boardId\":\"bgi1yqiis8t8xdqxgnet8ebutky\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"ad9bf7wpdwbnwbebkptg3puwu4c\",\"parentId\":\"cxom5chmr5tna9ru4na34dbhmur\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Description\\n*[Brief description of this task]*\\n\\n## Requirements\\n- *[Requirement 1]*\\n- *[Requirement 2]*\\n- ...\",\"fields\":{},\"createAt\":1667496496600,\"updateAt\":1667496496600,\"deleteAt\":0,\"boardId\":\"bgi1yqiis8t8xdqxgnet8ebutky\"}}\n"
  },
  {
    "path": "server/assets/templates-boardarchive/bh4pkixqsjift58e1qy6htrgeay/board.jsonl",
    "content": "{\"type\":\"board\",\"data\":{\"id\":\"bh4pkixqsjift58e1qy6htrgeay\",\"teamId\":\"qghzt68dq7bopgqamcnziq69ao\",\"channelId\":\"\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"type\":\"P\",\"minimumRole\":\"\",\"title\":\"User Research Sessions\",\"description\":\"Use this template to manage and keep track of all your user research sessions.\",\"icon\":\"🔬\",\"showDescription\":true,\"isTemplate\":false,\"templateVersion\":0,\"properties\":{},\"cardProperties\":[{\"id\":\"aaebj5fyx493eezx6ukxiwydgty\",\"name\":\"Status\",\"options\":[{\"color\":\"propColorGray\",\"id\":\"af6hjb3ysuaxbwnfqpby4wwnkdr\",\"value\":\"Backlog 📒\"},{\"color\":\"propColorYellow\",\"id\":\"aotxum1p5bw3xuzqz3ctjw66yww\",\"value\":\"Contacted 📞\"},{\"color\":\"propColorBlue\",\"id\":\"a7yq89whddzob1futao4rxk3yzc\",\"value\":\"Scheduled 📅\"},{\"color\":\"propColorRed\",\"id\":\"aseqq9hrsua56r3s6nbuirj9eec\",\"value\":\"Cancelled 🚫\"},{\"color\":\"propColorGreen\",\"id\":\"ap93ysuzy1xa7z818r6myrn4h4y\",\"value\":\"Completed ✔️\"}],\"type\":\"select\"},{\"id\":\"akrxgi7p7w14fym3gbynb98t9fh\",\"name\":\"Interview Date\",\"options\":[],\"type\":\"date\"},{\"id\":\"atg9qu6oe4bjm8jczzsn71ff5me\",\"name\":\"Product Area\",\"options\":[{\"color\":\"propColorGreen\",\"id\":\"ahn89mqg9u4igk6pdm7333t8i5h\",\"value\":\"Desktop App\"},{\"color\":\"propColorPurple\",\"id\":\"aehc83ffays3gh8myz16a8j7k4e\",\"value\":\"Web App\"},{\"color\":\"propColorBlue\",\"id\":\"a1sxagjgaadym5yrjak6tcup1oa\",\"value\":\"Mobile App\"}],\"type\":\"select\"},{\"id\":\"acjq4t5ymytu8x1f68wkggm7ypc\",\"name\":\"Email\",\"options\":[],\"type\":\"email\"},{\"id\":\"aphio1s5gkmpdbwoxynim7acw3e\",\"name\":\"Interviewer\",\"options\":[],\"type\":\"multiPerson\"},{\"id\":\"aqafzdeekpyncwz7m7i54q3iqqy\",\"name\":\"Recording URL\",\"options\":[],\"type\":\"url\"},{\"id\":\"aify3r761b9w43bqjtskrzi68tr\",\"name\":\"Passcode\",\"options\":[],\"type\":\"text\"}],\"createAt\":1667410119064,\"updateAt\":1667504168497,\"deleteAt\":0}}\n{\"type\":\"block\",\"data\":{\"id\":\"vtibhoxpq67f1xmh8a8kxh39nka\",\"parentId\":\"\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"view\",\"title\":\"All Users\",\"fields\":{\"cardOrder\":[\"ccsa77z7ubbbhbd3jq8xyx4hq8r\"],\"collapsedOptionIds\":[],\"columnCalculations\":{},\"columnWidths\":{\"__title\":280,\"aaebj5fyx493eezx6ukxiwydgty\":146,\"acjq4t5ymytu8x1f68wkggm7ypc\":222,\"akrxgi7p7w14fym3gbynb98t9fh\":131,\"atg9qu6oe4bjm8jczzsn71ff5me\":131},\"defaultTemplateId\":\"\",\"filter\":{\"filters\":[],\"operation\":\"and\"},\"hiddenOptionIds\":[],\"kanbanCalculations\":{},\"sortOptions\":[{\"propertyId\":\"akrxgi7p7w14fym3gbynb98t9fh\",\"reversed\":false}],\"viewType\":\"table\",\"visibleOptionIds\":[],\"visiblePropertyIds\":[\"aaebj5fyx493eezx6ukxiwydgty\",\"akrxgi7p7w14fym3gbynb98t9fh\",\"atg9qu6oe4bjm8jczzsn71ff5me\",\"acjq4t5ymytu8x1f68wkggm7ypc\",\"aphio1s5gkmpdbwoxynim7acw3e\",\"aqafzdeekpyncwz7m7i54q3iqqy\",\"aify3r761b9w43bqjtskrzi68tr\"]},\"createAt\":1667410162023,\"updateAt\":1667410900827,\"deleteAt\":0,\"boardId\":\"bh4pkixqsjift58e1qy6htrgeay\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"ccsa77z7ubbbhbd3jq8xyx4hq8r\",\"parentId\":\"bh4pkixqsjift58e1qy6htrgeay\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Frank Nash\",\"fields\":{\"contentOrder\":[\"aiqaqwzhe1tn9umunms95414kzo\"],\"icon\":\"👨‍💼\",\"isTemplate\":false,\"properties\":{\"aaebj5fyx493eezx6ukxiwydgty\":\"ap93ysuzy1xa7z818r6myrn4h4y\",\"acjq4t5ymytu8x1f68wkggm7ypc\":\"frank.nash@email.com\",\"aify3r761b9w43bqjtskrzi68tr\":\"Password123\",\"akrxgi7p7w14fym3gbynb98t9fh\":\"{\\\"from\\\":1669896000000}\",\"aqafzdeekpyncwz7m7i54q3iqqy\":\"https://user-images.githubusercontent.com/46905241/121941290-ee355280-cd03-11eb-9b9f-f6f524e4103e.gif\",\"atg9qu6oe4bjm8jczzsn71ff5me\":\"aehc83ffays3gh8myz16a8j7k4e\"}},\"createAt\":1667410176348,\"updateAt\":1667410539559,\"deleteAt\":0,\"boardId\":\"bh4pkixqsjift58e1qy6htrgeay\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"cdus79hea7ib6tb6nhic4bzcjbc\",\"parentId\":\"bh4pkixqsjift58e1qy6htrgeay\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Richard Parsons\",\"fields\":{\"contentOrder\":[\"aodtggcnfuby6fq8ehxg4koafgr\"],\"icon\":\"👨‍🦱\",\"isTemplate\":false,\"properties\":{\"aaebj5fyx493eezx6ukxiwydgty\":\"a7yq89whddzob1futao4rxk3yzc\",\"acjq4t5ymytu8x1f68wkggm7ypc\":\"richard.parsons@email.com\",\"aify3r761b9w43bqjtskrzi68tr\":\"Password123\",\"akrxgi7p7w14fym3gbynb98t9fh\":\"{\\\"from\\\":1671019200000}\",\"aqafzdeekpyncwz7m7i54q3iqqy\":\"https://user-images.githubusercontent.com/46905241/121941290-ee355280-cd03-11eb-9b9f-f6f524e4103e.gif\",\"atg9qu6oe4bjm8jczzsn71ff5me\":\"a1sxagjgaadym5yrjak6tcup1oa\"}},\"createAt\":1667410640657,\"updateAt\":1667417523845,\"deleteAt\":0,\"boardId\":\"bh4pkixqsjift58e1qy6htrgeay\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"cewmyr3nbybdombzmi83arq3koo\",\"parentId\":\"bh4pkixqsjift58e1qy6htrgeay\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Claire Hart\",\"fields\":{\"contentOrder\":[\"ahkkrf9xn8tfq9y98to8pbt6qnw\"],\"icon\":\"👩‍🦰\",\"isTemplate\":false,\"properties\":{\"aaebj5fyx493eezx6ukxiwydgty\":\"aseqq9hrsua56r3s6nbuirj9eec\",\"acjq4t5ymytu8x1f68wkggm7ypc\":\"claire.hart@email.com\",\"aify3r761b9w43bqjtskrzi68tr\":\"Password123\",\"akrxgi7p7w14fym3gbynb98t9fh\":\"{\\\"from\\\":1670500800000}\",\"aqafzdeekpyncwz7m7i54q3iqqy\":\"https://user-images.githubusercontent.com/46905241/121941290-ee355280-cd03-11eb-9b9f-f6f524e4103e.gif\",\"atg9qu6oe4bjm8jczzsn71ff5me\":\"ahn89mqg9u4igk6pdm7333t8i5h\"}},\"createAt\":1667410785750,\"updateAt\":1667410805030,\"deleteAt\":0,\"boardId\":\"bh4pkixqsjift58e1qy6htrgeay\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"cix9xfgh48ir55y7fdjftuje3za\",\"parentId\":\"bh4pkixqsjift58e1qy6htrgeay\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Olivia Alsop\",\"fields\":{\"contentOrder\":[\"a8xz4ead8k7budxknwhjxm9n3uc\"],\"icon\":\"👩‍💼\",\"isTemplate\":false,\"properties\":{\"aaebj5fyx493eezx6ukxiwydgty\":\"a7yq89whddzob1futao4rxk3yzc\",\"acjq4t5ymytu8x1f68wkggm7ypc\":\"olivia.alsop@email.com\",\"aify3r761b9w43bqjtskrzi68tr\":\"Password123\",\"akrxgi7p7w14fym3gbynb98t9fh\":\"{\\\"from\\\":1671192000000}\",\"aqafzdeekpyncwz7m7i54q3iqqy\":\"https://user-images.githubusercontent.com/46905241/121941290-ee355280-cd03-11eb-9b9f-f6f524e4103e.gif\",\"atg9qu6oe4bjm8jczzsn71ff5me\":\"a1sxagjgaadym5yrjak6tcup1oa\"}},\"createAt\":1667410730577,\"updateAt\":1667410775912,\"deleteAt\":0,\"boardId\":\"bh4pkixqsjift58e1qy6htrgeay\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"cn3skudjd9tbp5md9bocifnazpw\",\"parentId\":\"bh4pkixqsjift58e1qy6htrgeay\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Bernadette Powell\",\"fields\":{\"contentOrder\":[\"au67hjd7es7y6jkumo4ysrudwfa\"],\"icon\":\"🧑‍💼\",\"isTemplate\":false,\"properties\":{\"aaebj5fyx493eezx6ukxiwydgty\":\"af6hjb3ysuaxbwnfqpby4wwnkdr\",\"acjq4t5ymytu8x1f68wkggm7ypc\":\"bernadette.powell@email.com\"}},\"createAt\":1667410584181,\"updateAt\":1667410629860,\"deleteAt\":0,\"boardId\":\"bh4pkixqsjift58e1qy6htrgeay\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"vqi9zpn3h43bkbfc8c8jc7ci1hr\",\"parentId\":\"bh4pkixqsjift58e1qy6htrgeay\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"view\",\"title\":\"By Date\",\"fields\":{\"cardOrder\":[],\"collapsedOptionIds\":[],\"columnCalculations\":{},\"columnWidths\":{},\"dateDisplayPropertyId\":\"akrxgi7p7w14fym3gbynb98t9fh\",\"defaultTemplateId\":\"\",\"filter\":{\"filters\":[],\"operation\":\"and\"},\"hiddenOptionIds\":[],\"kanbanCalculations\":{},\"sortOptions\":[],\"viewType\":\"calendar\",\"visibleOptionIds\":[],\"visiblePropertyIds\":[\"__title\"]},\"createAt\":1667410845935,\"updateAt\":1667410849497,\"deleteAt\":0,\"boardId\":\"bh4pkixqsjift58e1qy6htrgeay\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"vdbpwgay6bbn8581n39yjiyxrxo\",\"parentId\":\"bixohg18tt11in4qbtinimk974y\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"view\",\"title\":\"By Status\",\"fields\":{\"cardOrder\":[],\"collapsedOptionIds\":[],\"columnCalculations\":{},\"columnWidths\":{},\"defaultTemplateId\":\"\",\"filter\":{\"filters\":[],\"operation\":\"and\"},\"hiddenOptionIds\":[\"\"],\"kanbanCalculations\":{},\"sortOptions\":[],\"viewType\":\"board\",\"visibleOptionIds\":[\"af6hjb3ysuaxbwnfqpby4wwnkdr\",\"aotxum1p5bw3xuzqz3ctjw66yww\",\"a7yq89whddzob1futao4rxk3yzc\",\"aseqq9hrsua56r3s6nbuirj9eec\",\"ap93ysuzy1xa7z818r6myrn4h4y\"],\"visiblePropertyIds\":[]},\"createAt\":1667410119073,\"updateAt\":1667417470776,\"deleteAt\":0,\"boardId\":\"bh4pkixqsjift58e1qy6htrgeay\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"aiqaqwzhe1tn9umunms95414kzo\",\"parentId\":\"ccsa77z7ubbbhbd3jq8xyx4hq8r\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Interview Notes\\n- ...\\n- ...\\n- ... \",\"fields\":{},\"createAt\":1667410410824,\"updateAt\":1667410422875,\"deleteAt\":0,\"boardId\":\"bh4pkixqsjift58e1qy6htrgeay\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"aodtggcnfuby6fq8ehxg4koafgr\",\"parentId\":\"cdus79hea7ib6tb6nhic4bzcjbc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Interview Notes\\n- ...\\n- ...\\n- ... \",\"fields\":{},\"createAt\":1667410640663,\"updateAt\":1667410640663,\"deleteAt\":0,\"boardId\":\"bh4pkixqsjift58e1qy6htrgeay\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"ahkkrf9xn8tfq9y98to8pbt6qnw\",\"parentId\":\"cewmyr3nbybdombzmi83arq3koo\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Interview Notes\\n- ...\\n- ...\\n- ... \",\"fields\":{},\"createAt\":1667410785755,\"updateAt\":1667410785755,\"deleteAt\":0,\"boardId\":\"bh4pkixqsjift58e1qy6htrgeay\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"a8xz4ead8k7budxknwhjxm9n3uc\",\"parentId\":\"cix9xfgh48ir55y7fdjftuje3za\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Interview Notes\\n- ...\\n- ...\\n- ... \",\"fields\":{},\"createAt\":1667410730582,\"updateAt\":1667410730582,\"deleteAt\":0,\"boardId\":\"bh4pkixqsjift58e1qy6htrgeay\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"au67hjd7es7y6jkumo4ysrudwfa\",\"parentId\":\"cn3skudjd9tbp5md9bocifnazpw\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Interview Notes\\n- ...\\n- ...\\n- ... \",\"fields\":{},\"createAt\":1667410584187,\"updateAt\":1667410584187,\"deleteAt\":0,\"boardId\":\"bh4pkixqsjift58e1qy6htrgeay\"}}\n"
  },
  {
    "path": "server/assets/templates-boardarchive/bkqk6hpfx7pbsucue7jan5n1o1o/board.jsonl",
    "content": "{\"type\":\"board\",\"data\":{\"id\":\"bkqk6hpfx7pbsucue7jan5n1o1o\",\"teamId\":\"qghzt68dq7bopgqamcnziq69ao\",\"channelId\":\"\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"type\":\"P\",\"minimumRole\":\"\",\"title\":\"Competitive Analysis\",\"description\":\"Use this template to track and stay ahead of the competition.\",\"icon\":\"🗂️\",\"showDescription\":true,\"isTemplate\":false,\"templateVersion\":0,\"properties\":{},\"cardProperties\":[{\"id\":\"ahzspe59iux8wigra8bg6cg18nc\",\"name\":\"Website\",\"options\":[],\"type\":\"url\"},{\"id\":\"aozntq4go4nkab688j1s7stqtfc\",\"name\":\"Location\",\"options\":[],\"type\":\"text\"},{\"id\":\"aiefo7nh9jwisn8b4cgakowithy\",\"name\":\"Revenue\",\"options\":[],\"type\":\"text\"},{\"id\":\"a6cwaq79b1pdpb97wkanmeyy4er\",\"name\":\"Employees\",\"options\":[],\"type\":\"number\"},{\"id\":\"an1eerzscfxn6awdfajbg41uz3h\",\"name\":\"Founded\",\"options\":[],\"type\":\"text\"},{\"id\":\"a1semdhszu1rq17d7et5ydrqqio\",\"name\":\"Market Position\",\"options\":[{\"color\":\"propColorYellow\",\"id\":\"arfjpz9by5car71tz3behba8yih\",\"value\":\"Leader\"},{\"color\":\"propColorRed\",\"id\":\"abajmr34b8g1916w495xjb35iko\",\"value\":\"Challenger\"},{\"color\":\"propColorBlue\",\"id\":\"abt79uxg5edqojsrrefcnr4eruo\",\"value\":\"Follower\"},{\"color\":\"propColorBrown\",\"id\":\"aipf3qfgjtkheiayjuxrxbpk9wa\",\"value\":\"Nicher\"}],\"type\":\"select\"},{\"id\":\"aapogff3xoa8ym7xf56s87kysda\",\"name\":\"Last updated time\",\"options\":[],\"type\":\"updatedTime\"},{\"id\":\"az3jkw3ynd3mqmart7edypey15e\",\"name\":\"Last updated by\",\"options\":[],\"type\":\"updatedBy\"}],\"createAt\":1667337304886,\"updateAt\":1667352513150,\"deleteAt\":0}}\n{\"type\":\"block\",\"data\":{\"id\":\"vfzq8kedf3bnt7qkrsom658j6io\",\"parentId\":\"\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"view\",\"title\":\"Competitor List\",\"fields\":{\"cardOrder\":[\"c96bjeqk6zjrm5qtyoenexh3f8e\",\"chg7cdun9hjbf5pue6zc1gxm8rw\",\"cn9chs8a4zjyqzqez7qor63s8uc\",\"ctkqr4ce3zjrzur3q4mn47eeuuc\",\"cnam59x954idsxpbbfp8bmsigtr\"],\"collapsedOptionIds\":[],\"columnCalculations\":{},\"columnWidths\":{\"__title\":210,\"a1semdhszu1rq17d7et5ydrqqio\":121,\"aapogff3xoa8ym7xf56s87kysda\":194,\"ahzspe59iux8wigra8bg6cg18nc\":156,\"aiefo7nh9jwisn8b4cgakowithy\":155,\"aozntq4go4nkab688j1s7stqtfc\":151,\"az3jkw3ynd3mqmart7edypey15e\":145},\"defaultTemplateId\":\"\",\"filter\":{\"filters\":[],\"operation\":\"and\"},\"hiddenOptionIds\":[],\"kanbanCalculations\":{},\"sortOptions\":[],\"viewType\":\"table\",\"visibleOptionIds\":[],\"visiblePropertyIds\":[\"ahzspe59iux8wigra8bg6cg18nc\",\"aozntq4go4nkab688j1s7stqtfc\",\"aiefo7nh9jwisn8b4cgakowithy\",\"a6cwaq79b1pdpb97wkanmeyy4er\",\"an1eerzscfxn6awdfajbg41uz3h\",\"a1semdhszu1rq17d7et5ydrqqio\",\"aapogff3xoa8ym7xf56s87kysda\",\"az3jkw3ynd3mqmart7edypey15e\"]},\"createAt\":1667339411936,\"updateAt\":1667399926321,\"deleteAt\":0,\"boardId\":\"bkqk6hpfx7pbsucue7jan5n1o1o\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"vr158bbbsetn5ffm1gebhduhx5a\",\"parentId\":\"\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"view\",\"title\":\"Market Position\",\"fields\":{\"cardOrder\":[\"cip8b4jcomfr7by9gtizebikfke\",\"cacs91js1hb887ds41r6dwnd88c\",\"ca3u8edwrof89i8obxffnz4xw3a\"],\"collapsedOptionIds\":[],\"columnCalculations\":{},\"columnWidths\":{},\"defaultTemplateId\":\"\",\"filter\":{\"filters\":[],\"operation\":\"and\"},\"hiddenOptionIds\":[\"\"],\"kanbanCalculations\":{},\"sortOptions\":[],\"viewType\":\"board\",\"visibleOptionIds\":[\"arfjpz9by5car71tz3behba8yih\",\"abajmr34b8g1916w495xjb35iko\",\"abt79uxg5edqojsrrefcnr4eruo\",\"aipf3qfgjtkheiayjuxrxbpk9wa\"],\"visiblePropertyIds\":[]},\"createAt\":1667351648812,\"updateAt\":1667352684324,\"deleteAt\":0,\"boardId\":\"bkqk6hpfx7pbsucue7jan5n1o1o\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"c96bjeqk6zjrm5qtyoenexh3f8e\",\"parentId\":\"bkqk6hpfx7pbsucue7jan5n1o1o\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Liminary Corp.\",\"fields\":{\"contentOrder\":[\"ainmysbw6xpyczm3p5xayocpm3e\"],\"icon\":\"🌧\",\"isTemplate\":false,\"properties\":{\"a1semdhszu1rq17d7et5ydrqqio\":\"abt79uxg5edqojsrrefcnr4eruo\",\"a6cwaq79b1pdpb97wkanmeyy4er\":\"300\",\"ahzspe59iux8wigra8bg6cg18nc\":\"liminarycorp.com\",\"aiefo7nh9jwisn8b4cgakowithy\":\"$25,000,000\",\"an1eerzscfxn6awdfajbg41uz3h\":\"2017\",\"aozntq4go4nkab688j1s7stqtfc\":\"Toronto, Canada\"}},\"createAt\":1667338157613,\"updateAt\":1667351630721,\"deleteAt\":0,\"boardId\":\"bkqk6hpfx7pbsucue7jan5n1o1o\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"chg7cdun9hjbf5pue6zc1gxm8rw\",\"parentId\":\"bkqk6hpfx7pbsucue7jan5n1o1o\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Helx Industries\",\"fields\":{\"contentOrder\":[\"an5k8g7ntz7bgppfx9cuk9oyaja\"],\"icon\":\"📦\",\"isTemplate\":false,\"properties\":{\"a1semdhszu1rq17d7et5ydrqqio\":\"abt79uxg5edqojsrrefcnr4eruo\",\"a6cwaq79b1pdpb97wkanmeyy4er\":\"650\",\"ahzspe59iux8wigra8bg6cg18nc\":\"helxindustries.com\",\"aiefo7nh9jwisn8b4cgakowithy\":\"$50,000,000\",\"an1eerzscfxn6awdfajbg41uz3h\":\"2009\",\"aozntq4go4nkab688j1s7stqtfc\":\"New York, NY\"}},\"createAt\":1667338444580,\"updateAt\":1667351626493,\"deleteAt\":0,\"boardId\":\"bkqk6hpfx7pbsucue7jan5n1o1o\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"cn9chs8a4zjyqzqez7qor63s8uc\",\"parentId\":\"bkqk6hpfx7pbsucue7jan5n1o1o\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Kadera Global\",\"fields\":{\"contentOrder\":[\"aup87xiwr9bye8fpshibuh156ih\"],\"icon\":\"🛡\",\"isTemplate\":false,\"properties\":{\"a1semdhszu1rq17d7et5ydrqqio\":\"aipf3qfgjtkheiayjuxrxbpk9wa\",\"a6cwaq79b1pdpb97wkanmeyy4er\":\"150\",\"ahzspe59iux8wigra8bg6cg18nc\":\"kaderaglobal.com\",\"aiefo7nh9jwisn8b4cgakowithy\":\"$12,000,000\",\"an1eerzscfxn6awdfajbg41uz3h\":\"2015\",\"aozntq4go4nkab688j1s7stqtfc\":\"Seattle, OR\"}},\"createAt\":1667338227718,\"updateAt\":1667351623847,\"deleteAt\":0,\"boardId\":\"bkqk6hpfx7pbsucue7jan5n1o1o\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"cnam59x954idsxpbbfp8bmsigtr\",\"parentId\":\"bkqk6hpfx7pbsucue7jan5n1o1o\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Ositions Inc.\",\"fields\":{\"contentOrder\":[\"aukdnjj7mw3ggudoe88wmmpgore\"],\"icon\":\"🌃\",\"isTemplate\":false,\"properties\":{\"a1semdhszu1rq17d7et5ydrqqio\":\"abajmr34b8g1916w495xjb35iko\",\"a6cwaq79b1pdpb97wkanmeyy4er\":\"2,700\",\"ahzspe59iux8wigra8bg6cg18nc\":\"ositionsinc.com\",\"aiefo7nh9jwisn8b4cgakowithy\":\"$125,000,000\",\"an1eerzscfxn6awdfajbg41uz3h\":\"2004\",\"aozntq4go4nkab688j1s7stqtfc\":\"Berlin, Germany\"}},\"createAt\":1667337634942,\"updateAt\":1667351619186,\"deleteAt\":0,\"boardId\":\"bkqk6hpfx7pbsucue7jan5n1o1o\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"ctkqr4ce3zjrzur3q4mn47eeuuc\",\"parentId\":\"bkqk6hpfx7pbsucue7jan5n1o1o\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Afformance Ltd.\",\"fields\":{\"contentOrder\":[\"a1c857fph4byaxdqf88kw1wrwyo\"],\"icon\":\"⚡\",\"isTemplate\":false,\"properties\":{\"a1semdhszu1rq17d7et5ydrqqio\":\"arfjpz9by5car71tz3behba8yih\",\"a6cwaq79b1pdpb97wkanmeyy4er\":\"1,800\",\"ahzspe59iux8wigra8bg6cg18nc\":\"afformanceltd.com\",\"aiefo7nh9jwisn8b4cgakowithy\":\"$200,000,000\",\"an1eerzscfxn6awdfajbg41uz3h\":\"2002\",\"aozntq4go4nkab688j1s7stqtfc\":\"Palo Alto, CA\"}},\"createAt\":1667338608746,\"updateAt\":1667351615526,\"deleteAt\":0,\"boardId\":\"bkqk6hpfx7pbsucue7jan5n1o1o\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"ainmysbw6xpyczm3p5xayocpm3e\",\"parentId\":\"c96bjeqk6zjrm5qtyoenexh3f8e\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Summary\\nLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Duis fermentum aliquet massa in ornare. Pellentesque mollis nisl efficitur, eleifend nisi congue, scelerisque nunc. Aliquam lorem quam, commodo id nunc nec, congue bibendum velit. Vivamus sed mattis libero, et iaculis diam. Suspendisse euismod hendrerit nisl, quis ornare ipsum gravida in.\\n## Strengths\\n- ...\\n- ...\\n## Weaknesses\\n- ...\\n- ...\\n## Opportunities\\n- ...\\n- ...\\n## Threats\\n- ...\\n- ...\",\"fields\":{},\"createAt\":1667339042969,\"updateAt\":1667340672945,\"deleteAt\":0,\"boardId\":\"bkqk6hpfx7pbsucue7jan5n1o1o\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"an5k8g7ntz7bgppfx9cuk9oyaja\",\"parentId\":\"chg7cdun9hjbf5pue6zc1gxm8rw\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Summary\\nLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Duis fermentum aliquet massa in ornare. Pellentesque mollis nisl efficitur, eleifend nisi congue, scelerisque nunc. Aliquam lorem quam, commodo id nunc nec, congue bibendum velit. Vivamus sed mattis libero, et iaculis diam. Suspendisse euismod hendrerit nisl, quis ornare ipsum gravida in.\\n## Strengths\\n- ...\\n- ...\\n## Weaknesses\\n- ...\\n- ...\\n## Opportunities\\n- ...\\n- ...\\n## Threats\\n- ...\\n- ...\",\"fields\":{},\"createAt\":1667339057076,\"updateAt\":1667340666993,\"deleteAt\":0,\"boardId\":\"bkqk6hpfx7pbsucue7jan5n1o1o\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"aup87xiwr9bye8fpshibuh156ih\",\"parentId\":\"cn9chs8a4zjyqzqez7qor63s8uc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Summary\\nLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Duis fermentum aliquet massa in ornare. Pellentesque mollis nisl efficitur, eleifend nisi congue, scelerisque nunc. Aliquam lorem quam, commodo id nunc nec, congue bibendum velit. Vivamus sed mattis libero, et iaculis diam. Suspendisse euismod hendrerit nisl, quis ornare ipsum gravida in.\\n\\n## Strengths\\n- ...\\n- ...\\n## Weaknesses\\n- ...\\n- ...\\n## Opportunities\\n- ...\\n- ...\\n## Threats\\n- ...\\n- ...\",\"fields\":{},\"createAt\":1667339054835,\"updateAt\":1667340086915,\"deleteAt\":0,\"boardId\":\"bkqk6hpfx7pbsucue7jan5n1o1o\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"aukdnjj7mw3ggudoe88wmmpgore\",\"parentId\":\"cnam59x954idsxpbbfp8bmsigtr\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Summary\\nLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Duis fermentum aliquet massa in ornare. Pellentesque mollis nisl efficitur, eleifend nisi congue, scelerisque nunc. Aliquam lorem quam, commodo id nunc nec, congue bibendum velit. Vivamus sed mattis libero, et iaculis diam. Suspendisse euismod hendrerit nisl, quis ornare ipsum gravida in.\\n## Strengths\\n- ...\\n- ...\\n\\n## Weaknesses\\n- ...\\n- ...\\n\\n## Opportunities\\n- ...\\n- ...\\n\\n## Threats\\n- ...\\n- ...\",\"fields\":{},\"createAt\":1667339032796,\"updateAt\":1667340679120,\"deleteAt\":0,\"boardId\":\"bkqk6hpfx7pbsucue7jan5n1o1o\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"a1c857fph4byaxdqf88kw1wrwyo\",\"parentId\":\"ctkqr4ce3zjrzur3q4mn47eeuuc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Summary\\nLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Duis fermentum aliquet massa in ornare. Pellentesque mollis nisl efficitur, eleifend nisi congue, scelerisque nunc. Aliquam lorem quam, commodo id nunc nec, congue bibendum velit. Vivamus sed mattis libero, et iaculis diam. Suspendisse euismod hendrerit nisl, quis ornare ipsum gravida in.\\n## Strengths\\n- ...\\n- ...\\n## Weaknesses\\n- ...\\n- ...\\n## Opportunities\\n- ...\\n- ...\\n## Threats\\n- ...\\n- ...\",\"fields\":{},\"createAt\":1667339061925,\"updateAt\":1667340648719,\"deleteAt\":0,\"boardId\":\"bkqk6hpfx7pbsucue7jan5n1o1o\"}}\n"
  },
  {
    "path": "server/assets/templates-boardarchive/brs9cdimfw7fodyi7erqt747rhc/board.jsonl",
    "content": "{\"type\":\"block\",\"data\":{\"id\":\"brs9cdimfw7fodyi7erqt747rhc\",\"parentId\":\"\",\"rootId\":\"brs9cdimfw7fodyi7erqt747rhc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"board\",\"title\":\"Content Calendar (NEW)\",\"fields\":{\"cardProperties\":[{\"id\":\"ae9ar615xoknd8hw8py7mbyr7zo\",\"name\":\"Status\",\"options\":[{\"color\":\"propColorGray\",\"id\":\"awna1nuarjca99m9s4uiy9kwj5h\",\"value\":\"Idea 💡\"},{\"color\":\"propColorOrange\",\"id\":\"a9ana1e9w673o5cp8md4xjjwfto\",\"value\":\"Draft\"},{\"color\":\"propColorPurple\",\"id\":\"apy9dcd7zmand615p3h53zjqxjh\",\"value\":\"In Review\"},{\"color\":\"propColorBlue\",\"id\":\"acri4cm3bmay55f7ksztphmtnga\",\"value\":\"Ready to Publish\"},{\"color\":\"propColorGreen\",\"id\":\"amsowcd9a8e1kid317r7ttw6uzh\",\"value\":\"Published 🎉\"}],\"type\":\"select\"},{\"id\":\"aysx3atqexotgwp5kx6h5i5ancw\",\"name\":\"Type\",\"options\":[{\"color\":\"propColorOrange\",\"id\":\"aywiofmmtd3ofgzj95ysky4pjga\",\"value\":\"Press Release\"},{\"color\":\"propColorGreen\",\"id\":\"apqdgjrmsmx8ngmp7zst51647de\",\"value\":\"Sponsored Post\"},{\"color\":\"propColorPurple\",\"id\":\"a3woynbjnb7j16e74uw3pubrytw\",\"value\":\"Customer Story\"},{\"color\":\"propColorRed\",\"id\":\"aq36k5pkpfcypqb3idw36xdi1fh\",\"value\":\"Product Release\"},{\"color\":\"propColorGray\",\"id\":\"azn66pmk34adygnizjqhgiac4ia\",\"value\":\"Partnership\"},{\"color\":\"propColorBlue\",\"id\":\"aj8y675weso8kpb6eceqbpj4ruw\",\"value\":\"Feature Announcement\"},{\"color\":\"propColorYellow\",\"id\":\"a3xky7ygn14osr1mokerbfah5cy\",\"value\":\"Article\"}],\"type\":\"select\"},{\"id\":\"ab6mbock6styfe6htf815ph1mhw\",\"name\":\"Channel\",\"options\":[{\"color\":\"propColorBrown\",\"id\":\"a8xceonxiu4n3c43szhskqizicr\",\"value\":\"Website\"},{\"color\":\"propColorGreen\",\"id\":\"a3pdzi53kpbd4okzdkz6khi87zo\",\"value\":\"Blog\"},{\"color\":\"propColorOrange\",\"id\":\"a3d9ux4fmi3anyd11kyipfbhwde\",\"value\":\"Email\"},{\"color\":\"propColorRed\",\"id\":\"a8cbbfdwocx73zn3787cx6gacsh\",\"value\":\"Podcast\"},{\"color\":\"propColorPink\",\"id\":\"aigjtpcaxdp7d6kmctrwo1ztaia\",\"value\":\"Print\"},{\"color\":\"propColorBlue\",\"id\":\"af1wsn13muho59e7ghwaogxy5ey\",\"value\":\"Facebook\"},{\"color\":\"propColorGray\",\"id\":\"a47zajfxwhsg6q8m7ppbiqs7jge\",\"value\":\"LinkedIn\"},{\"color\":\"propColorYellow\",\"id\":\"az8o8pfe9hq6s7xaehoqyc3wpyc\",\"value\":\"Twitter\"}],\"type\":\"multiSelect\"},{\"id\":\"ao44fz8nf6z6tuj1x31t9yyehcc\",\"name\":\"Assignee\",\"options\":[],\"type\":\"person\"},{\"id\":\"a39x5cybshwrbjpc3juaakcyj6e\",\"name\":\"Due Date\",\"options\":[],\"type\":\"date\"},{\"id\":\"agqsoiipowmnu9rdwxm57zrehtr\",\"name\":\"Publication Date\",\"options\":[],\"type\":\"date\"},{\"id\":\"ap4e7kdg7eip7j3c3oyiz39eaoc\",\"name\":\"Link\",\"options\":[],\"type\":\"url\"}],\"description\":\"Use this template to plan and organize your editorial content.\",\"icon\":\"📅\",\"isTemplate\":false,\"showDescription\":true},\"createAt\":1641618112737,\"updateAt\":1643788318628,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"c3pxiqf156fnhjfazwwpo79rt6w\",\"parentId\":\"brs9cdimfw7fodyi7erqt747rhc\",\"rootId\":\"brs9cdimfw7fodyi7erqt747rhc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"New Project and Workflow Management Solutions for Developers\",\"fields\":{\"contentOrder\":[\"71qhnzuec6esdi6fnynwpze4xya\",\"aianjmrimwfyr7jiiju1oi77kiw\"],\"icon\":\"🎯\",\"isTemplate\":false,\"properties\":{\"a39x5cybshwrbjpc3juaakcyj6e\":\"{\\\"from\\\":1645790400000}\",\"ab6mbock6styfe6htf815ph1mhw\":[\"a8xceonxiu4n3c43szhskqizicr\",\"a3pdzi53kpbd4okzdkz6khi87zo\",\"a3d9ux4fmi3anyd11kyipfbhwde\"],\"ae9ar615xoknd8hw8py7mbyr7zo\":\"awna1nuarjca99m9s4uiy9kwj5h\",\"ap4e7kdg7eip7j3c3oyiz39eaoc\":\"https://mattermost.com/newsroom/press-releases/mattermost-launches-new-project-and-workflow-management-solutions-for-developers/\",\"aysx3atqexotgwp5kx6h5i5ancw\":\"aywiofmmtd3ofgzj95ysky4pjga\"}},\"createAt\":1641618113009,\"updateAt\":1643788318631,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"cemyj9s9nwtgzieowpufrd1oo5h\",\"parentId\":\"brs9cdimfw7fodyi7erqt747rhc\",\"rootId\":\"brs9cdimfw7fodyi7erqt747rhc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"[Tweet] Mattermost v6.1 includes card @-mention notifications in Boards\",\"fields\":{\"contentOrder\":[\"7i96m7nbsdsex8n6hzuzrmdfjuy\",\"7ed5bwp3gr8yax3mhtuwiaa9gjy\",\"a8egmu8gsqp8dzfk9pgpq5mm4ta\",\"awyawmyjtj3nfffu4aphaqy9bgy\",\"abdasiyq4k7ndtfrdadrias8sjy\",\"71ppnm4bcmbrbpn73nefjkao17r\"],\"icon\":\"🐤\",\"isTemplate\":false,\"properties\":{\"a39x5cybshwrbjpc3juaakcyj6e\":\"{\\\"from\\\":1639051200000}\",\"ab6mbock6styfe6htf815ph1mhw\":[\"az8o8pfe9hq6s7xaehoqyc3wpyc\"],\"ae9ar615xoknd8hw8py7mbyr7zo\":\"a9ana1e9w673o5cp8md4xjjwfto\",\"agqsoiipowmnu9rdwxm57zrehtr\":\"{\\\"from\\\":1637668800000}\",\"ap4e7kdg7eip7j3c3oyiz39eaoc\":\"https://twitter.com/Mattermost/status/1463145633162969097?s=20\",\"aysx3atqexotgwp5kx6h5i5ancw\":\"aj8y675weso8kpb6eceqbpj4ruw\"}},\"createAt\":1641618112896,\"updateAt\":1643788318631,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"cp963ioyx63rz98q8gs19nxxm7w\",\"parentId\":\"brs9cdimfw7fodyi7erqt747rhc\",\"rootId\":\"brs9cdimfw7fodyi7erqt747rhc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Top 10 Must-Have DevOps Tools in 2021\",\"fields\":{\"contentOrder\":[\"7fo1utqc8x1z1z6hzg33hes1ktc\",\"ajm6ykd3633dbxdq6j76wtthbia\"],\"icon\":\"🛠️\",\"isTemplate\":false,\"properties\":{\"a39x5cybshwrbjpc3juaakcyj6e\":\"{\\\"from\\\":1636113600000}\",\"ab6mbock6styfe6htf815ph1mhw\":[\"a8xceonxiu4n3c43szhskqizicr\"],\"ae9ar615xoknd8hw8py7mbyr7zo\":\"a9ana1e9w673o5cp8md4xjjwfto\",\"agqsoiipowmnu9rdwxm57zrehtr\":\"{\\\"from\\\":1637323200000}\",\"ap4e7kdg7eip7j3c3oyiz39eaoc\":\"https://www.toolbox.com/tech/devops/articles/best-devops-tools/\",\"aysx3atqexotgwp5kx6h5i5ancw\":\"a3xky7ygn14osr1mokerbfah5cy\"}},\"createAt\":1641618112796,\"updateAt\":1643788318631,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"crrwzx9z4dfbsiki6suzwj3mqfw\",\"parentId\":\"brs9cdimfw7fodyi7erqt747rhc\",\"rootId\":\"brs9cdimfw7fodyi7erqt747rhc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Unblocking Workflows: The Guide to Developer Productivity\",\"fields\":{\"contentOrder\":[\"77tz16jtz5x73ncs3dxc3fp1d7h\",\"asmp1ztc1gjyh3k8og8yyizu5jy\"],\"icon\":\"💻\",\"isTemplate\":false,\"properties\":{\"a39x5cybshwrbjpc3juaakcyj6e\":\"{\\\"from\\\":1638532800000}\",\"ab6mbock6styfe6htf815ph1mhw\":[\"a3pdzi53kpbd4okzdkz6khi87zo\"],\"ae9ar615xoknd8hw8py7mbyr7zo\":\"apy9dcd7zmand615p3h53zjqxjh\",\"agqsoiipowmnu9rdwxm57zrehtr\":\"{\\\"from\\\":1639483200000}\",\"ap4e7kdg7eip7j3c3oyiz39eaoc\":\"https://mattermost.com/newsroom/press-releases/mattermost-unveils-definitive-report-on-the-state-of-developer-productivity-unblocking-workflows-the-guide-to-developer-productivity-2022-edition/\",\"aysx3atqexotgwp5kx6h5i5ancw\":\"a3xky7ygn14osr1mokerbfah5cy\"}},\"createAt\":1641618112846,\"updateAt\":1643788318631,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"vaiuu5bg4ofdn8j4whttdgtus4w\",\"parentId\":\"brs9cdimfw7fodyi7erqt747rhc\",\"rootId\":\"brs9cdimfw7fodyi7erqt747rhc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"view\",\"title\":\"By Status\",\"fields\":{\"cardOrder\":[null,\"cdbfkd15d6iy18rgx1tskmfsr6c\",\"cn8yofg9rtkgmzgmb5xdi56p3ic\",\"csgsnnywpuqzs5jgq87snk9x17e\",\"cqwaytore5y487wdu8zffppqnea\",null],\"collapsedOptionIds\":[],\"columnCalculations\":{},\"columnWidths\":{},\"defaultTemplateId\":\"cff1jmrxfrirgbeebhr9qd7nida\",\"filter\":{\"filters\":[],\"operation\":\"and\"},\"hiddenOptionIds\":[],\"kanbanCalculations\":{},\"sortOptions\":[],\"viewType\":\"board\",\"visibleOptionIds\":[\"awna1nuarjca99m9s4uiy9kwj5h\",\"a9ana1e9w673o5cp8md4xjjwfto\",\"apy9dcd7zmand615p3h53zjqxjh\",\"acri4cm3bmay55f7ksztphmtnga\",\"amsowcd9a8e1kid317r7ttw6uzh\",\"\"],\"visiblePropertyIds\":[\"ab6mbock6styfe6htf815ph1mhw\"]},\"createAt\":1641618113176,\"updateAt\":1643788318631,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"vgbzazskupjrq7gnrwqqk51adsh\",\"parentId\":\"brs9cdimfw7fodyi7erqt747rhc\",\"rootId\":\"brs9cdimfw7fodyi7erqt747rhc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"view\",\"title\":\"Due Date Calendar\",\"fields\":{\"cardOrder\":[],\"collapsedOptionIds\":[],\"columnCalculations\":{},\"columnWidths\":{},\"dateDisplayPropertyId\":\"a39x5cybshwrbjpc3juaakcyj6e\",\"defaultTemplateId\":\"cff1jmrxfrirgbeebhr9qd7nida\",\"filter\":{\"filters\":[],\"operation\":\"and\"},\"hiddenOptionIds\":[],\"kanbanCalculations\":{},\"sortOptions\":[],\"viewType\":\"calendar\",\"visibleOptionIds\":[],\"visiblePropertyIds\":[\"__title\"]},\"createAt\":1641618113068,\"updateAt\":1643788318631,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"vkk4dm1tnzb8fbmr5gxhibr63te\",\"parentId\":\"brs9cdimfw7fodyi7erqt747rhc\",\"rootId\":\"brs9cdimfw7fodyi7erqt747rhc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"view\",\"title\":\"Publication Calendar\",\"fields\":{\"cardOrder\":[],\"collapsedOptionIds\":[],\"columnCalculations\":{},\"columnWidths\":{},\"dateDisplayPropertyId\":\"agqsoiipowmnu9rdwxm57zrehtr\",\"defaultTemplateId\":\"cff1jmrxfrirgbeebhr9qd7nida\",\"filter\":{\"filters\":[],\"operation\":\"and\"},\"hiddenOptionIds\":[],\"kanbanCalculations\":{},\"sortOptions\":[],\"viewType\":\"calendar\",\"visibleOptionIds\":[],\"visiblePropertyIds\":[\"__title\"]},\"createAt\":1641618113123,\"updateAt\":1643788318631,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"vpsefkithi7gq3rfyignqxa9cze\",\"parentId\":\"brs9cdimfw7fodyi7erqt747rhc\",\"rootId\":\"brs9cdimfw7fodyi7erqt747rhc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"view\",\"title\":\"Content List\",\"fields\":{\"cardOrder\":[],\"collapsedOptionIds\":[],\"columnCalculations\":{},\"columnWidths\":{\"__title\":322,\"ab6mbock6styfe6htf815ph1mhw\":229,\"aysx3atqexotgwp5kx6h5i5ancw\":208},\"defaultTemplateId\":\"cff1jmrxfrirgbeebhr9qd7nida\",\"filter\":{\"filters\":[],\"operation\":\"and\"},\"hiddenOptionIds\":[],\"kanbanCalculations\":{},\"sortOptions\":[{\"propertyId\":\"a39x5cybshwrbjpc3juaakcyj6e\",\"reversed\":false}],\"viewType\":\"table\",\"visibleOptionIds\":[],\"visiblePropertyIds\":[\"ae9ar615xoknd8hw8py7mbyr7zo\",\"aysx3atqexotgwp5kx6h5i5ancw\",\"ab6mbock6styfe6htf815ph1mhw\",\"ao44fz8nf6z6tuj1x31t9yyehcc\",\"a39x5cybshwrbjpc3juaakcyj6e\",\"agqsoiipowmnu9rdwxm57zrehtr\",\"ap4e7kdg7eip7j3c3oyiz39eaoc\"]},\"createAt\":1641618243042,\"updateAt\":1643788318631,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"aianjmrimwfyr7jiiju1oi77kiw\",\"parentId\":\"c3pxiqf156fnhjfazwwpo79rt6w\",\"rootId\":\"brs9cdimfw7fodyi7erqt747rhc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Research\\n- ...\\n- ...\\n\\n## Plan\\n- ...\\n- ...\\n\\n## Notes\\n- ...\\n- ...\",\"fields\":{},\"createAt\":1641618141074,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"71ppnm4bcmbrbpn73nefjkao17r\",\"parentId\":\"cemyj9s9nwtgzieowpufrd1oo5h\",\"rootId\":\"brs9cdimfw7fodyi7erqt747rhc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"image\",\"title\":\"\",\"fields\":{\"fileId\":\"7y5kr8x8ybpnwdykjfuz57rggrh.png\"},\"createAt\":1641618185785,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"a8egmu8gsqp8dzfk9pgpq5mm4ta\",\"parentId\":\"cemyj9s9nwtgzieowpufrd1oo5h\",\"rootId\":\"brs9cdimfw7fodyi7erqt747rhc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Research\\n- ...\\n- ...\\n\\n## Plan\\n- ...\\n- ...\\n\\n## Notes\\n- ...\\n- ...\",\"fields\":{},\"createAt\":1641618157625,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"awyawmyjtj3nfffu4aphaqy9bgy\",\"parentId\":\"cemyj9s9nwtgzieowpufrd1oo5h\",\"rootId\":\"brs9cdimfw7fodyi7erqt747rhc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Media\",\"fields\":{},\"createAt\":1641618160634,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"a4uyug1msrtrkdfy5fwu8shf7so\",\"parentId\":\"cff1jmrxfrirgbeebhr9qd7nida\",\"rootId\":\"brs9cdimfw7fodyi7erqt747rhc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Research\\n- ...\\n- ...\\n\\n## Plan\\n- ...\\n- ...\\n\\n## Notes\\n- ...\\n- ...\",\"fields\":{},\"createAt\":1641618338368,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"abztjcgndkffd3gybef6phr14so\",\"parentId\":\"cff1jmrxfrirgbeebhr9qd7nida\",\"rootId\":\"brs9cdimfw7fodyi7erqt747rhc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Research\\n- ...\\n- ...\\n- ...\\n\\n## Plan\\n- ...\\n- ...\\n- ...\\n\\n## Notes\\n- ...\\n- ...\\n- ...\",\"fields\":{},\"createAt\":1641618112322,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"azczyg4pfj3ysjpxf4hjtu666ne\",\"parentId\":\"cff1jmrxfrirgbeebhr9qd7nida\",\"rootId\":\"brs9cdimfw7fodyi7erqt747rhc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Research\\n- ...\\n- ...\\n\\n## Plan\\n- ...\\n- ...\\n\\n## Notes\\n- ...\\n- ...\",\"fields\":{},\"createAt\":1641618112527,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"ajm6ykd3633dbxdq6j76wtthbia\",\"parentId\":\"cp963ioyx63rz98q8gs19nxxm7w\",\"rootId\":\"brs9cdimfw7fodyi7erqt747rhc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Research\\n- ...\\n- ...\\n\\n## Plan\\n- ...\\n- ...\\n\\n## Notes\\n- ...\\n- ...\",\"fields\":{},\"createAt\":1641618208454,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"asmp1ztc1gjyh3k8og8yyizu5jy\",\"parentId\":\"crrwzx9z4dfbsiki6suzwj3mqfw\",\"rootId\":\"brs9cdimfw7fodyi7erqt747rhc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Research\\n- ...\\n- ...\\n\\n## Plan\\n- ...\\n- ...\\n\\n## Notes\\n- ...\\n- ...\",\"fields\":{},\"createAt\":1641618224780,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n"
  },
  {
    "path": "server/assets/templates-boardarchive/bsjd59qtpbf888mqez3ge77domw/board.jsonl",
    "content": "{\"type\":\"board\",\"data\":{\"id\":\"bsjd59qtpbf888mqez3ge77domw\",\"teamId\":\"qghzt68dq7bopgqamcnziq69ao\",\"channelId\":\"\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"type\":\"P\",\"minimumRole\":\"\",\"title\":\"Team Retrospective\",\"description\":\"Use this template at the end of your project or sprint to identify what worked well and what can be improved for the future.\",\"icon\":\"🧭\",\"showDescription\":true,\"isTemplate\":false,\"templateVersion\":0,\"properties\":{},\"cardProperties\":[{\"id\":\"adjckpdotpgkz7c6wixzw9ipb1e\",\"name\":\"Category\",\"options\":[{\"color\":\"propColorGray\",\"id\":\"aok6pgecm85qe9k5kcphzoe63ma\",\"value\":\"To Discuss 📣\"},{\"color\":\"propColorGreen\",\"id\":\"aq1dwbf661yx337hjcd5q3sbxwa\",\"value\":\"Went Well 👍\"},{\"color\":\"propColorRed\",\"id\":\"ar87yh5xmsswqkxmjq1ipfftfpc\",\"value\":\"Didn't Go Well 🚫\"},{\"color\":\"propColorBlue\",\"id\":\"akj3fkmxq7idma55mdt8sqpumyw\",\"value\":\"Action Items ✅\"}],\"type\":\"select\"},{\"id\":\"aspaay76a5wrnuhtqgm97tt3rer\",\"name\":\"Details\",\"options\":[],\"type\":\"text\"},{\"id\":\"arzsm76s376y7suuhao3tu6efoc\",\"name\":\"Created By\",\"options\":[],\"type\":\"createdBy\"},{\"id\":\"a8anbe5fpa668sryatcdsuuyh8a\",\"name\":\"Created Time\",\"options\":[],\"type\":\"createdTime\"}],\"createAt\":1667494395151,\"updateAt\":1667508014713,\"deleteAt\":0}}\n{\"type\":\"block\",\"data\":{\"id\":\"v56n9ixhhbpn79m6eb4xwpo6dih\",\"parentId\":\"bjbhs6bos3m8zjouf78xceg9nqw\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"view\",\"title\":\"Board view\",\"fields\":{\"cardOrder\":[\"cniwb8xwcqtbstbcm3sdfrr854h\",\"cs4qwpzr65fgttd7364dicskanh\",\"c9s78pzbdg3g4jkcdjqahtnfejc\",\"c8utmazns878jtfgtf7exyi9pee\",\"cnobejmb6bf8e3c1w7em5z4pwyh\"],\"collapsedOptionIds\":[],\"columnCalculations\":{},\"columnWidths\":{},\"defaultTemplateId\":\"\",\"filter\":{\"filters\":[],\"operation\":\"and\"},\"hiddenOptionIds\":[\"\"],\"kanbanCalculations\":{},\"sortOptions\":[],\"viewType\":\"board\",\"visibleOptionIds\":[\"aok6pgecm85qe9k5kcphzoe63ma\",\"aq1dwbf661yx337hjcd5q3sbxwa\",\"ar87yh5xmsswqkxmjq1ipfftfpc\",\"akj3fkmxq7idma55mdt8sqpumyw\"],\"visiblePropertyIds\":[\"aspaay76a5wrnuhtqgm97tt3rer\"]},\"createAt\":1667494395162,\"updateAt\":1667508040536,\"deleteAt\":0,\"boardId\":\"bsjd59qtpbf888mqez3ge77domw\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"c8utmazns878jtfgtf7exyi9pee\",\"parentId\":\"bsjd59qtpbf888mqez3ge77domw\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Tight deadline\",\"fields\":{\"contentOrder\":[],\"icon\":\"📅\",\"isTemplate\":false,\"properties\":{\"adjckpdotpgkz7c6wixzw9ipb1e\":\"ar87yh5xmsswqkxmjq1ipfftfpc\"}},\"createAt\":1667495008197,\"updateAt\":1667495012284,\"deleteAt\":0,\"boardId\":\"bsjd59qtpbf888mqez3ge77domw\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"c9s78pzbdg3g4jkcdjqahtnfejc\",\"parentId\":\"bsjd59qtpbf888mqez3ge77domw\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Team communication\",\"fields\":{\"contentOrder\":[],\"icon\":\"💬\",\"isTemplate\":false,\"properties\":{\"adjckpdotpgkz7c6wixzw9ipb1e\":\"aq1dwbf661yx337hjcd5q3sbxwa\"}},\"createAt\":1667494992121,\"updateAt\":1667494995438,\"deleteAt\":0,\"boardId\":\"bsjd59qtpbf888mqez3ge77domw\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"cniwb8xwcqtbstbcm3sdfrr854h\",\"parentId\":\"bsjd59qtpbf888mqez3ge77domw\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Reschedule planning meeting\",\"fields\":{\"contentOrder\":[],\"icon\":\"🗓️\",\"isTemplate\":false,\"properties\":{\"adjckpdotpgkz7c6wixzw9ipb1e\":\"aok6pgecm85qe9k5kcphzoe63ma\"}},\"createAt\":1667494810631,\"updateAt\":1667495048934,\"deleteAt\":0,\"boardId\":\"bsjd59qtpbf888mqez3ge77domw\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"cnobejmb6bf8e3c1w7em5z4pwyh\",\"parentId\":\"bsjd59qtpbf888mqez3ge77domw\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Schedule more time for testing\",\"fields\":{\"contentOrder\":[],\"icon\":\"🧪\",\"isTemplate\":false,\"properties\":{\"adjckpdotpgkz7c6wixzw9ipb1e\":\"akj3fkmxq7idma55mdt8sqpumyw\"}},\"createAt\":1667495025505,\"updateAt\":1667495032672,\"deleteAt\":0,\"boardId\":\"bsjd59qtpbf888mqez3ge77domw\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"cs4qwpzr65fgttd7364dicskanh\",\"parentId\":\"bsjd59qtpbf888mqez3ge77domw\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Positive user feedback\",\"fields\":{\"contentOrder\":[],\"icon\":\"🥰\",\"isTemplate\":false,\"properties\":{\"adjckpdotpgkz7c6wixzw9ipb1e\":\"aq1dwbf661yx337hjcd5q3sbxwa\"}},\"createAt\":1667494972061,\"updateAt\":1667494978637,\"deleteAt\":0,\"boardId\":\"bsjd59qtpbf888mqez3ge77domw\"}}\n"
  },
  {
    "path": "server/assets/templates-boardarchive/bui5izho7dtn77xg3thkiqprc9r/board.jsonl",
    "content": "{\"type\":\"block\",\"data\":{\"id\":\"bui5izho7dtn77xg3thkiqprc9r\",\"parentId\":\"\",\"rootId\":\"bui5izho7dtn77xg3thkiqprc9r\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"board\",\"title\":\"Roadmap (NEW)\",\"fields\":{\"cardProperties\":[{\"id\":\"50117d52-bcc7-4750-82aa-831a351c44a0\",\"name\":\"Status\",\"options\":[{\"color\":\"propColorGray\",\"id\":\"8c557f69-b0ed-46ec-83a3-8efab9d47ef5\",\"value\":\"Not Started\"},{\"color\":\"propColorYellow\",\"id\":\"ec6d2bc5-df2b-4f77-8479-e59ceb039946\",\"value\":\"In Progress\"},{\"color\":\"propColorGreen\",\"id\":\"849766ba-56a5-48d1-886f-21672f415395\",\"value\":\"Complete 🙌\"}],\"type\":\"select\"},{\"id\":\"20717ad3-5741-4416-83f1-6f133fff3d11\",\"name\":\"Type\",\"options\":[{\"color\":\"propColorYellow\",\"id\":\"424ea5e3-9aa1-4075-8c5c-01b44b66e634\",\"value\":\"Epic ⛰\"},{\"color\":\"propColorGreen\",\"id\":\"6eea96c9-4c61-4968-8554-4b7537e8f748\",\"value\":\"Task 🔨\"},{\"color\":\"propColorRed\",\"id\":\"1fdbb515-edd2-4af5-80fc-437ed2211a49\",\"value\":\"Bug 🐞\"}],\"type\":\"select\"},{\"id\":\"60985f46-3e41-486e-8213-2b987440ea1c\",\"name\":\"Sprint\",\"options\":[{\"color\":\"propColorBrown\",\"id\":\"c01676ca-babf-4534-8be5-cce2287daa6c\",\"value\":\"Sprint 1\"},{\"color\":\"propColorPurple\",\"id\":\"ed4a5340-460d-461b-8838-2c56e8ee59fe\",\"value\":\"Sprint 2\"},{\"color\":\"propColorBlue\",\"id\":\"14892380-1a32-42dd-8034-a0cea32bc7e6\",\"value\":\"Sprint 3\"}],\"type\":\"select\"},{\"id\":\"f7f3ad42-b31a-4ac2-81f0-28ea80c5b34e\",\"name\":\"Priority\",\"options\":[{\"color\":\"propColorRed\",\"id\":\"cb8ecdac-38be-4d36-8712-c4d58cc8a8e9\",\"value\":\"P1 🔥\"},{\"color\":\"propColorYellow\",\"id\":\"e6a7f297-4440-4783-8ab3-3af5ba62ca11\",\"value\":\"P2\"},{\"color\":\"propColorGray\",\"id\":\"c62172ea-5da7-4dec-8186-37267d8ee9a7\",\"value\":\"P3\"}],\"type\":\"select\"},{\"id\":\"aphg37f7zbpuc3bhwhp19s1ribh\",\"name\":\"Assignee\",\"options\":[],\"type\":\"person\"},{\"id\":\"a4378omyhmgj3bex13sj4wbpfiy\",\"name\":\"Due Date\",\"options\":[],\"type\":\"date\"},{\"id\":\"a36o9q1yik6nmar6ri4q4uca7ey\",\"name\":\"Created Date\",\"options\":[],\"type\":\"createdTime\"},{\"id\":\"ai7ajsdk14w7x5s8up3dwir77te\",\"name\":\"Design Link\",\"options\":[],\"type\":\"url\"}],\"description\":\"Use this template to plan your roadmap and manage your releases more efficiently.\",\"icon\":\"🗺️\",\"isTemplate\":false,\"showDescription\":true},\"createAt\":1640363551156,\"updateAt\":1643788318628,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"c3jawn6e4fbr3jctthy9xxkdsqe\",\"parentId\":\"bui5izho7dtn77xg3thkiqprc9r\",\"rootId\":\"bui5izho7dtn77xg3thkiqprc9r\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"App crashing\",\"fields\":{\"contentOrder\":[\"79t7rkiuspeneqi9xurou9tqzwh\",\"a4d68ftemrbfsfykur6eh6nrogh\",\"ae54fbyywubnbtr3s4yhgns4nye\",\"7o9ktgofg37yc7gma9s3jd9bd3a\"],\"icon\":\"📉\",\"isTemplate\":false,\"properties\":{\"20717ad3-5741-4416-83f1-6f133fff3d11\":\"1fdbb515-edd2-4af5-80fc-437ed2211a49\",\"50117d52-bcc7-4750-82aa-831a351c44a0\":\"ec6d2bc5-df2b-4f77-8479-e59ceb039946\",\"60985f46-3e41-486e-8213-2b987440ea1c\":\"c01676ca-babf-4534-8be5-cce2287daa6c\",\"f7f3ad42-b31a-4ac2-81f0-28ea80c5b34e\":\"cb8ecdac-38be-4d36-8712-c4d58cc8a8e9\"}},\"createAt\":1641589357560,\"updateAt\":1643788318631,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"c5trb4319wi8n3x4r4f7f83ytdc\",\"parentId\":\"bui5izho7dtn77xg3thkiqprc9r\",\"rootId\":\"bui5izho7dtn77xg3thkiqprc9r\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Calendar view\",\"fields\":{\"contentOrder\":[\"7df11783ny67mdnognqae31ax6y\",\"ag9rxpgbwqid1mm5hgg8b9yhf6o\"],\"icon\":\"📆\",\"isTemplate\":false,\"properties\":{\"20717ad3-5741-4416-83f1-6f133fff3d11\":\"6eea96c9-4c61-4968-8554-4b7537e8f748\",\"50117d52-bcc7-4750-82aa-831a351c44a0\":\"849766ba-56a5-48d1-886f-21672f415395\",\"60985f46-3e41-486e-8213-2b987440ea1c\":\"c01676ca-babf-4534-8be5-cce2287daa6c\",\"ai7ajsdk14w7x5s8up3dwir77te\":\"https://mattermost.com/boards/\",\"f7f3ad42-b31a-4ac2-81f0-28ea80c5b34e\":\"e6a7f297-4440-4783-8ab3-3af5ba62ca11\"}},\"createAt\":1641590072588,\"updateAt\":1643788318631,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"c9p4bdasriifc7qgihzhjm63ugy\",\"parentId\":\"bui5izho7dtn77xg3thkiqprc9r\",\"rootId\":\"bui5izho7dtn77xg3thkiqprc9r\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Standard templates\",\"fields\":{\"contentOrder\":[\"7uonmjk41nipnrsi6tz8wau5ssh\",\"afz66z155b7fhik9p6opysjneha\"],\"icon\":\"🗺️\",\"isTemplate\":false,\"properties\":{\"20717ad3-5741-4416-83f1-6f133fff3d11\":\"6eea96c9-4c61-4968-8554-4b7537e8f748\",\"50117d52-bcc7-4750-82aa-831a351c44a0\":\"ec6d2bc5-df2b-4f77-8479-e59ceb039946\",\"60985f46-3e41-486e-8213-2b987440ea1c\":\"ed4a5340-460d-461b-8838-2c56e8ee59fe\",\"ai7ajsdk14w7x5s8up3dwir77te\":\"https://mattermost.com/boards/\",\"f7f3ad42-b31a-4ac2-81f0-28ea80c5b34e\":\"e6a7f297-4440-4783-8ab3-3af5ba62ca11\"}},\"createAt\":1641589960934,\"updateAt\":1643788318631,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"chfrdo1nb3p8ofnbftyinr6949o\",\"parentId\":\"bui5izho7dtn77xg3thkiqprc9r\",\"rootId\":\"bui5izho7dtn77xg3thkiqprc9r\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Import / Export\",\"fields\":{\"contentOrder\":[\"aw66wjze7qfr1ukqs8gw53qa5qw\"],\"icon\":\"🚢\",\"isTemplate\":false,\"properties\":{\"20717ad3-5741-4416-83f1-6f133fff3d11\":\"6eea96c9-4c61-4968-8554-4b7537e8f748\",\"50117d52-bcc7-4750-82aa-831a351c44a0\":\"ec6d2bc5-df2b-4f77-8479-e59ceb039946\",\"60985f46-3e41-486e-8213-2b987440ea1c\":\"c01676ca-babf-4534-8be5-cce2287daa6c\",\"ai7ajsdk14w7x5s8up3dwir77te\":\"https://mattermost.com/boards/\",\"f7f3ad42-b31a-4ac2-81f0-28ea80c5b34e\":\"e6a7f297-4440-4783-8ab3-3af5ba62ca11\"}},\"createAt\":1640363550923,\"updateAt\":1643788318631,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"cp1m1wrpfatdxikhwkf58oo5k3o\",\"parentId\":\"bui5izho7dtn77xg3thkiqprc9r\",\"rootId\":\"bui5izho7dtn77xg3thkiqprc9r\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Review API design\",\"fields\":{\"contentOrder\":[\"ahsamufik97nsfxjgx9cs6cmzme\"],\"icon\":\"🛣️\",\"isTemplate\":false,\"properties\":{\"20717ad3-5741-4416-83f1-6f133fff3d11\":\"424ea5e3-9aa1-4075-8c5c-01b44b66e634\",\"50117d52-bcc7-4750-82aa-831a351c44a0\":\"8c557f69-b0ed-46ec-83a3-8efab9d47ef5\",\"60985f46-3e41-486e-8213-2b987440ea1c\":\"14892380-1a32-42dd-8034-a0cea32bc7e6\",\"ai7ajsdk14w7x5s8up3dwir77te\":\"https://mattermost.com/boards/\",\"f7f3ad42-b31a-4ac2-81f0-28ea80c5b34e\":\"c62172ea-5da7-4dec-8186-37267d8ee9a7\"}},\"createAt\":1640363550754,\"updateAt\":1643788318631,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"cqfy6g434pigk3p7j3gq55trq9o\",\"parentId\":\"bui5izho7dtn77xg3thkiqprc9r\",\"rootId\":\"bui5izho7dtn77xg3thkiqprc9r\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Icons don't display\",\"fields\":{\"contentOrder\":[\"axfkn6tuy4igubj3ka99tbymb8o\",\"acbpep9wxdtyg8gg3fi6h1hgoro\",\"7tedfdyq4p7g77dmkrebryh4jor\"],\"icon\":\"💻\",\"isTemplate\":false,\"properties\":{\"20717ad3-5741-4416-83f1-6f133fff3d11\":\"1fdbb515-edd2-4af5-80fc-437ed2211a49\",\"50117d52-bcc7-4750-82aa-831a351c44a0\":\"8c557f69-b0ed-46ec-83a3-8efab9d47ef5\",\"60985f46-3e41-486e-8213-2b987440ea1c\":\"ed4a5340-460d-461b-8838-2c56e8ee59fe\",\"ai7ajsdk14w7x5s8up3dwir77te\":\"https://mattermost.com/boards/\",\"f7f3ad42-b31a-4ac2-81f0-28ea80c5b34e\":\"e6a7f297-4440-4783-8ab3-3af5ba62ca11\"}},\"createAt\":1640363550868,\"updateAt\":1643788318631,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"v1uubwdzrw7fsxnd6pss1dyhh5e\",\"parentId\":\"bui5izho7dtn77xg3thkiqprc9r\",\"rootId\":\"bui5izho7dtn77xg3thkiqprc9r\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"view\",\"title\":\"Calendar View\",\"fields\":{\"cardOrder\":[],\"collapsedOptionIds\":[],\"columnCalculations\":{},\"columnWidths\":{},\"dateDisplayPropertyId\":\"a4378omyhmgj3bex13sj4wbpfiy\",\"defaultTemplateId\":\"\",\"filter\":{\"filters\":[],\"operation\":\"and\"},\"hiddenOptionIds\":[],\"kanbanCalculations\":{},\"sortOptions\":[],\"viewType\":\"calendar\",\"visibleOptionIds\":[],\"visiblePropertyIds\":[\"__title\"]},\"createAt\":1640379248049,\"updateAt\":1643788318631,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"v7n4sc9cre7gsbq9yydsuekpg8a\",\"parentId\":\"bui5izho7dtn77xg3thkiqprc9r\",\"rootId\":\"bui5izho7dtn77xg3thkiqprc9r\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"view\",\"title\":\"Board: Sprints\",\"fields\":{\"cardOrder\":[\"c3jawn6e4fbr3jctthy9xxkdsqe\",\"c5trb4319wi8n3x4r4f7f83ytdc\",\"c9p4bdasriifc7qgihzhjm63ugy\",\"cqfy6g434pigk3p7j3gq55trq9o\",\"chfrdo1nb3p8ofnbftyinr6949o\",\"cp1m1wrpfatdxikhwkf58oo5k3o\"],\"collapsedOptionIds\":[],\"columnCalculations\":{},\"columnWidths\":{},\"defaultTemplateId\":\"\",\"filter\":{\"filters\":[],\"operation\":\"and\"},\"groupById\":\"60985f46-3e41-486e-8213-2b987440ea1c\",\"hiddenOptionIds\":[],\"kanbanCalculations\":{},\"sortOptions\":[{\"propertyId\":\"f7f3ad42-b31a-4ac2-81f0-28ea80c5b34e\",\"reversed\":false}],\"viewType\":\"board\",\"visibleOptionIds\":[\"c01676ca-babf-4534-8be5-cce2287daa6c\",\"ed4a5340-460d-461b-8838-2c56e8ee59fe\",\"14892380-1a32-42dd-8034-a0cea32bc7e6\",\"\"],\"visiblePropertyIds\":[\"20717ad3-5741-4416-83f1-6f133fff3d11\",\"f7f3ad42-b31a-4ac2-81f0-28ea80c5b34e\"]},\"createAt\":1640363550811,\"updateAt\":1643788318631,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"v8sa3mo81d38rbmd8bz4n6dg7qc\",\"parentId\":\"bui5izho7dtn77xg3thkiqprc9r\",\"rootId\":\"bui5izho7dtn77xg3thkiqprc9r\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"view\",\"title\":\"List: Tasks 🔨\",\"fields\":{\"cardOrder\":[],\"collapsedOptionIds\":[],\"columnCalculations\":{},\"columnWidths\":{\"50117d52-bcc7-4750-82aa-831a351c44a0\":139,\"__title\":280},\"defaultTemplateId\":\"\",\"filter\":{\"filters\":[{\"condition\":\"includes\",\"propertyId\":\"20717ad3-5741-4416-83f1-6f133fff3d11\",\"values\":[\"6eea96c9-4c61-4968-8554-4b7537e8f748\"]}],\"operation\":\"and\"},\"hiddenOptionIds\":[],\"kanbanCalculations\":{},\"sortOptions\":[{\"propertyId\":\"50117d52-bcc7-4750-82aa-831a351c44a0\",\"reversed\":true}],\"viewType\":\"table\",\"visibleOptionIds\":[],\"visiblePropertyIds\":[\"50117d52-bcc7-4750-82aa-831a351c44a0\",\"20717ad3-5741-4416-83f1-6f133fff3d11\",\"60985f46-3e41-486e-8213-2b987440ea1c\",\"f7f3ad42-b31a-4ac2-81f0-28ea80c5b34e\"]},\"createAt\":1640363550980,\"updateAt\":1643788318631,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"vi43bqxsho3fmjbu1oa8qafwo4c\",\"parentId\":\"bui5izho7dtn77xg3thkiqprc9r\",\"rootId\":\"bui5izho7dtn77xg3thkiqprc9r\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"view\",\"title\":\"Board: Status\",\"fields\":{\"cardOrder\":[\"c3jawn6e4fbr3jctthy9xxkdsqe\",\"cm4w7cc3aac6s9jdcujbs4j8f4r\",\"c6egh6cpnj137ixdoitsoxq17oo\",\"cct9u78utsdyotmejbmwwg66ihr\",\"cmft87it1q7yebbd51ij9k65xbw\",\"c9fe77j9qcruxf4itzib7ag6f1c\",\"coup7afjknqnzbdwghiwbsq541w\",\"c5ex1hndz8qyc8gx6ofbfeksftc\"],\"collapsedOptionIds\":[],\"columnCalculations\":{},\"columnWidths\":{},\"defaultTemplateId\":\"cidz4imnqhir48brz6e8hxhfrhy\",\"filter\":{\"filters\":[],\"operation\":\"and\"},\"groupById\":\"50117d52-bcc7-4750-82aa-831a351c44a0\",\"hiddenOptionIds\":[],\"kanbanCalculations\":{},\"sortOptions\":[{\"propertyId\":\"f7f3ad42-b31a-4ac2-81f0-28ea80c5b34e\",\"reversed\":false}],\"viewType\":\"board\",\"visibleOptionIds\":[\"8c557f69-b0ed-46ec-83a3-8efab9d47ef5\",\"ec6d2bc5-df2b-4f77-8479-e59ceb039946\",\"849766ba-56a5-48d1-886f-21672f415395\",\"\"],\"visiblePropertyIds\":[\"20717ad3-5741-4416-83f1-6f133fff3d11\",\"60985f46-3e41-486e-8213-2b987440ea1c\",\"f7f3ad42-b31a-4ac2-81f0-28ea80c5b34e\"]},\"createAt\":1640363551099,\"updateAt\":1643788318631,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"vod5de87tz7nxpji31oou4ine3c\",\"parentId\":\"bui5izho7dtn77xg3thkiqprc9r\",\"rootId\":\"bui5izho7dtn77xg3thkiqprc9r\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"view\",\"title\":\"List: Bugs 🐞\",\"fields\":{\"cardOrder\":[],\"collapsedOptionIds\":[],\"columnCalculations\":{},\"columnWidths\":{\"50117d52-bcc7-4750-82aa-831a351c44a0\":145,\"__title\":280},\"defaultTemplateId\":\"\",\"filter\":{\"filters\":[{\"condition\":\"includes\",\"propertyId\":\"20717ad3-5741-4416-83f1-6f133fff3d11\",\"values\":[\"1fdbb515-edd2-4af5-80fc-437ed2211a49\"]}],\"operation\":\"and\"},\"hiddenOptionIds\":[],\"kanbanCalculations\":{},\"sortOptions\":[{\"propertyId\":\"f7f3ad42-b31a-4ac2-81f0-28ea80c5b34e\",\"reversed\":false}],\"viewType\":\"table\",\"visibleOptionIds\":[],\"visiblePropertyIds\":[\"50117d52-bcc7-4750-82aa-831a351c44a0\",\"20717ad3-5741-4416-83f1-6f133fff3d11\",\"60985f46-3e41-486e-8213-2b987440ea1c\",\"f7f3ad42-b31a-4ac2-81f0-28ea80c5b34e\"]},\"createAt\":1640363550690,\"updateAt\":1643788318631,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7o9ktgofg37yc7gma9s3jd9bd3a\",\"parentId\":\"c3jawn6e4fbr3jctthy9xxkdsqe\",\"rootId\":\"bui5izho7dtn77xg3thkiqprc9r\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"image\",\"title\":\"\",\"fields\":{\"fileId\":\"77pe9r4ckbin438ph3f18bpatua.png\"},\"createAt\":1641589687567,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"a4d68ftemrbfsfykur6eh6nrogh\",\"parentId\":\"c3jawn6e4fbr3jctthy9xxkdsqe\",\"rootId\":\"bui5izho7dtn77xg3thkiqprc9r\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Steps to reproduce the behavior\\n1. Go to ...\\n2. Select  ...\\n3. Scroll down to ...\\n4. See error\\n\\n## Expected behavior\\n*[A clear and concise description of what you expected to happen.]*\\n\\n## Edition and Platform\\n- Edition: *[e.g. Personal Desktop / Personal Server / Mattermost plugin]*\\n- Version: *[e.g. v0.9.0]*\\n- Browser and OS: *[e.g. Chrome 91 on macOS, Edge 93 on Windows]*\\n\\n## Additional context\\n*[Add any other context about the problem here.]*\",\"fields\":{},\"createAt\":1641589386414,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"ae54fbyywubnbtr3s4yhgns4nye\",\"parentId\":\"c3jawn6e4fbr3jctthy9xxkdsqe\",\"rootId\":\"bui5izho7dtn77xg3thkiqprc9r\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Screenshots\\n*[If applicable, add screenshots to elaborate on the problem.]*\",\"fields\":{},\"createAt\":1641589472988,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"ag9rxpgbwqid1mm5hgg8b9yhf6o\",\"parentId\":\"c5trb4319wi8n3x4r4f7f83ytdc\",\"rootId\":\"bui5izho7dtn77xg3thkiqprc9r\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Description\\n*[Brief description of this task]*\\n\\n## Requirements\\n- *[Requirement 1]*\\n- *[Requirement 2]*\\n- ...\",\"fields\":{},\"createAt\":1641590081840,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"afz66z155b7fhik9p6opysjneha\",\"parentId\":\"c9p4bdasriifc7qgihzhjm63ugy\",\"rootId\":\"bui5izho7dtn77xg3thkiqprc9r\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Description\\n*[Brief description of this task]*\\n\\n## Requirements\\n- *[Requirement 1]*\\n- *[Requirement 2]*\\n- ...\",\"fields\":{},\"createAt\":1641589969935,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"73dpuy7r9qpfymrp67c9n3krrsc\",\"parentId\":\"cfefgwjke6bbxpjpig618g9bpte\",\"rootId\":\"bui5izho7dtn77xg3thkiqprc9r\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"image\",\"title\":\"\",\"fields\":{\"fileId\":\"7pbp4qg415pbstc6enzeicnu3qh.png\"},\"createAt\":1640379104209,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"aabek71yr1trxmjudty7efncp3r\",\"parentId\":\"cfefgwjke6bbxpjpig618g9bpte\",\"rootId\":\"bui5izho7dtn77xg3thkiqprc9r\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Screenshots\\nIf applicable, add screenshots to elaborate on the problem.\",\"fields\":{},\"createAt\":1640379104369,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"asqzoizq31b81dpyzm1tnm8wyxc\",\"parentId\":\"cfefgwjke6bbxpjpig618g9bpte\",\"rootId\":\"bui5izho7dtn77xg3thkiqprc9r\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Steps to reproduce the behavior\\n\\n1. Go to ...\\n2. Select  ...\\n3. Scroll down to ...\\n4. See error\\n\\n## Expected behavior\\n\\nA clear and concise description of what you expected to happen.\\n\\n## Edition and Platform\\n\\n - Edition: Personal Desktop / Personal Server / Mattermost plugin\\n - Version: [e.g. v0.9.0]\\n - Browser and OS: [e.g. Chrome 91 on macOS, Edge 93 on Windows]\\n\\n## Additional context\\n\\nAdd any other context about the problem here.\",\"fields\":{},\"createAt\":1640379104459,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"auefo9xa6sffatbeqzya56bhebo\",\"parentId\":\"cfefgwjke6bbxpjpig618g9bpte\",\"rootId\":\"bui5izho7dtn77xg3thkiqprc9r\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Steps to reproduce the behavior\\n\\n1. Go to ...\\n2. Select  ...\\n3. Scroll down to ...\\n4. See error\\n\\n## Expected behavior\\n\\n*[A clear and concise description of what you expected to happen.]*\\n\\n## Screenshots\\n\\n*[If applicable, add screenshots to elaborate on the problem.]*\\n\\n## Edition and Platform\\n\\n - Edition: *[e.g. Personal Desktop / Personal Server / Mattermost plugin]*\\n - Version: *[e.g. v0.9.0]*\\n - Browser and OS: *[e.g. Chrome 91 on macOS, Edge 93 on Windows]*\\n\\n## Additional context\\n\\n*[Add any other context about the problem here.]*\",\"fields\":{},\"createAt\":1640379139361,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"aw66wjze7qfr1ukqs8gw53qa5qw\",\"parentId\":\"chfrdo1nb3p8ofnbftyinr6949o\",\"rootId\":\"bui5izho7dtn77xg3thkiqprc9r\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Description\\n*[Brief description of this task]*\\n\\n## Requirements\\n- *[Requirement 1]*\\n- *[Requirement 2]*\\n- ...\",\"fields\":{},\"createAt\":1640380216220,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"anppzrbx3i7b47n17b6jje6e1yc\",\"parentId\":\"cidz4imnqhir48brz6e8hxhfrhy\",\"rootId\":\"bui5izho7dtn77xg3thkiqprc9r\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Description\\n*[Brief description of this task]*\\n\\n## Requirements\\n- *[Requirement 1]*\\n- *[Requirement 2]*\\n- ...\",\"fields\":{},\"createAt\":1640380239894,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"azyfnyszy6jb9iys9izfz1bhbdw\",\"parentId\":\"cidz4imnqhir48brz6e8hxhfrhy\",\"rootId\":\"bui5izho7dtn77xg3thkiqprc9r\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Requirements\\n- [Requirement 1]\\n- [Requirement 2]\\n- ...\",\"fields\":{},\"createAt\":1640380231316,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"ahsamufik97nsfxjgx9cs6cmzme\",\"parentId\":\"cp1m1wrpfatdxikhwkf58oo5k3o\",\"rootId\":\"bui5izho7dtn77xg3thkiqprc9r\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Summary\\n*[Brief description of what this epic is about]*\\n\\n## Motivation\\n*[Brief description on why this is needed]*\\n\\n## Acceptance Criteria\\n - *[Criteron 1]*\\n - *[Criteron 2]*\\n - ...\\n\\n## Personas\\n - *[Persona A]*\\n - *[Persona B]*\\n - ...\\n\\n## Reference Materials\\n - *[Links to other relevant documents as needed]*\\n - ...\",\"fields\":{},\"createAt\":1640380010492,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7tedfdyq4p7g77dmkrebryh4jor\",\"parentId\":\"cqfy6g434pigk3p7j3gq55trq9o\",\"rootId\":\"bui5izho7dtn77xg3thkiqprc9r\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"image\",\"title\":\"\",\"fields\":{\"fileId\":\"7pbp4qg415pbstc6enzeicnu3qh.png\"},\"createAt\":1640379056342,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"acbpep9wxdtyg8gg3fi6h1hgoro\",\"parentId\":\"cqfy6g434pigk3p7j3gq55trq9o\",\"rootId\":\"bui5izho7dtn77xg3thkiqprc9r\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Screenshots\\n*[If applicable, add screenshots to elaborate on the problem.]*\",\"fields\":{},\"createAt\":1640378826029,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"axfkn6tuy4igubj3ka99tbymb8o\",\"parentId\":\"cqfy6g434pigk3p7j3gq55trq9o\",\"rootId\":\"bui5izho7dtn77xg3thkiqprc9r\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Steps to reproduce the behavior\\n1. Go to ...\\n2. Select  ...\\n3. Scroll down to ...\\n4. See error\\n\\n## Expected behavior\\n*[A clear and concise description of what you expected to happen.]*\\n\\n## Edition and Platform\\n- Edition: *[e.g. Personal Desktop / Personal Server / Mattermost plugin]*\\n- Version: *[e.g. v0.9.0]*\\n- Browser and OS: *[e.g. Chrome 91 on macOS, Edge 93 on Windows]*\\n\\n## Additional context\\n*[Add any other context about the problem here.]*\",\"fields\":{},\"createAt\":1640378803642,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"a58i6xsb3abdhm87oezaum6ehhc\",\"parentId\":\"cwrq9ag3p5pgzzy98nfd3wwra1w\",\"rootId\":\"bui5izho7dtn77xg3thkiqprc9r\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Summary\\n*[Brief description of what this epic is about]*\\n## Motivation\\n*[Brief description on why this is needed]*\\n## Acceptance Criteria\\n- *[Criteron 1]*\\n- *[Criteron 2]*\\n- ...\\n## Personas\\n- *[Persona A]*\\n- *[Persona B]*\\n- ...\\n## Reference Materials\\n- *[Links to other relevant documents as needed]*\\n- ...\",\"fields\":{},\"createAt\":1640380125209,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"a799597ibbjb17yxy1c3zjias1w\",\"parentId\":\"cwrq9ag3p5pgzzy98nfd3wwra1w\",\"rootId\":\"bui5izho7dtn77xg3thkiqprc9r\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"## Summary\\n[Brief description of what this epic is about]\\n\\n## Motivation\\n[Brief description on why this is needed]\\n\\n## Acceptance Criteria\\n - [Criteron 1]\\n - [Criteron 2]\\n - ...\\n\\n## Personas\\n - [Persona A]\\n - [Persona B]\\n - ...\\n\\n## Reference Materials\\n - [Links to other relevant documents as needed]\\n - ...\",\"fields\":{},\"createAt\":1640380118322,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n"
  },
  {
    "path": "server/assets/templates-boardarchive/buixxjic3xjfkieees4iafdrznc/board.jsonl",
    "content": "{\"type\":\"block\",\"data\":{\"id\":\"buixxjic3xjfkieees4iafdrznc\",\"parentId\":\"\",\"rootId\":\"buixxjic3xjfkieees4iafdrznc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"board\",\"title\":\"Welcome to Boards!\",\"fields\":{\"cardProperties\":[{\"id\":\"a972dc7a-5f4c-45d2-8044-8c28c69717f1\",\"name\":\"Status\",\"options\":[{\"color\":\"propColorRed\",\"id\":\"amm6wfhnbuxojwssyftgs9dipqe\",\"value\":\"To do 🔥\"},{\"color\":\"propColorYellow\",\"id\":\"af3p8ztcyxgn8wd9z4az7o9tjeh\",\"value\":\"Next up\"},{\"color\":\"propColorPurple\",\"id\":\"ajurey3xkocs1nwx8di5zx6oe7o\",\"value\":\"Later\"},{\"color\":\"propColorGreen\",\"id\":\"agkinkjy5983wsk6kppsujajxqw\",\"value\":\"Completed 🙌\"}],\"type\":\"select\"},{\"id\":\"acypkejeb5yfujhj9te57p9kaxw\",\"name\":\"Priority\",\"options\":[{\"color\":\"propColorOrange\",\"id\":\"aanaehcw3m13jytujsjk5hpf6ry\",\"value\":\"1. High\"},{\"color\":\"propColorBrown\",\"id\":\"ascd7nm9r491ayot8i86g1gmgqw\",\"value\":\"2. Medium\"},{\"color\":\"propColorGray\",\"id\":\"aq6ukoiciyfctgwyhwzpfss8ghe\",\"value\":\"3. Low\"}],\"type\":\"select\"},{\"id\":\"aqh13jabwexjkzr3jqsz1i1syew\",\"name\":\"Assignee\",\"options\":[],\"type\":\"person\"},{\"id\":\"acmg7mz1rr1eykfug4hcdpb1y1o\",\"name\":\"Due Date\",\"options\":[],\"type\":\"date\"},{\"id\":\"amewjwfjrtpu8ha73xsrdmxazxr\",\"name\":\"Reviewed\",\"options\":[],\"type\":\"checkbox\"},{\"id\":\"attzzboqaz6m1sdti5xa7gjnk1e\",\"name\":\"Last updated time\",\"options\":[],\"type\":\"updatedTime\"},{\"id\":\"a4nfnb5xr3txr5xq7y9ho7kyz6c\",\"name\":\"Reference\",\"options\":[],\"type\":\"url\"},{\"id\":\"a9gzwi3dt5n55nddej6zcbhxaeh\",\"name\":\"Created by\",\"options\":[],\"type\":\"createdBy\"}],\"description\":\"Mattermost Boards is an open source project management tool that helps you organize, track, and manage work across teams. Select a card to learn more.\",\"icon\":\"👋\",\"isTemplate\":false,\"showDescription\":true},\"createAt\":1640034759040,\"updateAt\":1643788318628,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"c5ay4q3t1hf8cdcschejip7ybpc\",\"parentId\":\"buixxjic3xjfkieees4iafdrznc\",\"rootId\":\"buixxjic3xjfkieees4iafdrznc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Drag cards\",\"fields\":{\"contentOrder\":[\"apktbgtee5jb8xrnqy3ibiujxew\",\"aefratgmk6j8nzj5fngfrf4k8hw\"],\"icon\":\"🤏\",\"isTemplate\":false,\"properties\":{\"a4nfnb5xr3txr5xq7y9ho7kyz6c\":\"https://docs.mattermost.com/boards/working-with-boards.html#dragging-cards\",\"a972dc7a-5f4c-45d2-8044-8c28c69717f1\":\"ajurey3xkocs1nwx8di5zx6oe7o\",\"acypkejeb5yfujhj9te57p9kaxw\":\"aq6ukoiciyfctgwyhwzpfss8ghe\"}},\"createAt\":1640034759400,\"updateAt\":1643788318631,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"c9h4wpgh1ajyzfdqoyotohtj6oy\",\"parentId\":\"buixxjic3xjfkieees4iafdrznc\",\"rootId\":\"buixxjic3xjfkieees4iafdrznc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Manage tasks with cards\",\"fields\":{\"contentOrder\":[\"a778mcixrm7byzb4mxrixjtrwwa\",\"7mgy47rzyxpdm5c5eod9x5nypea\",\"7tuw1my7b7fnxd8sfyzpz6dd1sc\",\"784uu3ufcgb878ky7hyugmf6xcw\",\"77msur4yswfn65d8qycdyfpfawe\",\"7dh4oncxngj8jb8n59sefmsynac\",\"7nkegq1zimifpmxcrq8ntyothoe\",\"7nb8y7jyoetro8cd36qcju53z8c\",\"7exhjmek1ctbexxt95w5cy1cuwo\",\"7peuyuzgkc3fmzczfjuzseg9ksa\",\"76nwb9tqfsid5jx46yw34itqima\",\"7dy3mcgzgybf1ifa3emgewkzj7e\",\"a5ca6tii33bfw8ba36y1rswq3he\",\"7876od6xhffr6fy69zeogag7eyw\",\"7x7bq9awkatbm5x4docbh5gaw4y\",\"7ghpx9qff43dgtke1rwidmge1ho\",\"7nb8y7jyoetro8cd36qcju53z8c\",\"7hdyxemhbytfm3m83g88djq9nhr\",\"7pgnejxokubbe9kdrxj6g9qa41e\",\"7hw9z6qtx8jyizkmm9g5yq3gxcy\",\"7gk6ooz6npbb8by5rgp9aig7tua\",\"7ayruwskq4b8rte64fiwz493kjo\"],\"icon\":\"☑️\",\"isTemplate\":false,\"properties\":{\"a4nfnb5xr3txr5xq7y9ho7kyz6c\":\"https://docs.mattermost.com/boards/work-with-cards.html\",\"a972dc7a-5f4c-45d2-8044-8c28c69717f1\":\"amm6wfhnbuxojwssyftgs9dipqe\",\"acypkejeb5yfujhj9te57p9kaxw\":\"aanaehcw3m13jytujsjk5hpf6ry\"}},\"createAt\":1640034759460,\"updateAt\":1643788318631,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"cfkikng8egbr878ryaztmpkno4w\",\"parentId\":\"buixxjic3xjfkieees4iafdrznc\",\"rootId\":\"buixxjic3xjfkieees4iafdrznc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Create your own board\",\"fields\":{\"contentOrder\":[\"apedf7fbrspgt3cg8e5worq1gqa\",\"as7511u6t1pdc7fe7zrbzdfg51y\",\"7r9my1yuddbn45dojrfht3neg8c\",\"7eir5gdjxgjbsxpbyp3df4npcze\",\"7cux9rwr1b3rjmxakbipeoxky6h\"],\"icon\":\"📋\",\"isTemplate\":false,\"properties\":{\"a4nfnb5xr3txr5xq7y9ho7kyz6c\":\"https://docs.mattermost.com/boards/working-with-boards.html#adding-new-boards\",\"a972dc7a-5f4c-45d2-8044-8c28c69717f1\":\"amm6wfhnbuxojwssyftgs9dipqe\",\"acypkejeb5yfujhj9te57p9kaxw\":\"aanaehcw3m13jytujsjk5hpf6ry\"}},\"createAt\":1640034759557,\"updateAt\":1643788318631,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"cm8yz355wbtfd7rtpgs655wbr4e\",\"parentId\":\"buixxjic3xjfkieees4iafdrznc\",\"rootId\":\"buixxjic3xjfkieees4iafdrznc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Share a board\",\"fields\":{\"contentOrder\":[\"a5z5po5apabfibkgmkq53dxe9dw\",\"ag791jfbc47gobroeo9ie1afcdo\",\"7r7asyew8d7fyunf4sow8e5iyoc\",\"ad8j3n8tp77bppee3ipjt6odgpe\",\"7w935usqt6pby8qz9x5pxaj7iow\",\"7ogbs8h6q4j8z7ngy1m7eag63nw\",\"7z1jau5qy3jfcxdp5cgq3duk6ne\",\"7hkn59merfbf38gzxf7sabewuma\"],\"icon\":\"📤\",\"isTemplate\":false,\"properties\":{\"a4nfnb5xr3txr5xq7y9ho7kyz6c\":\"https://docs.mattermost.com/boards/sharing-boards.html\",\"a972dc7a-5f4c-45d2-8044-8c28c69717f1\":\"ajurey3xkocs1nwx8di5zx6oe7o\",\"acypkejeb5yfujhj9te57p9kaxw\":\"aq6ukoiciyfctgwyhwzpfss8ghe\"}},\"createAt\":1640034759139,\"updateAt\":1643788318631,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"cmjtfip8a738nbr33shzmgk559o\",\"parentId\":\"buixxjic3xjfkieees4iafdrznc\",\"rootId\":\"buixxjic3xjfkieees4iafdrznc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Create a new card\",\"fields\":{\"contentOrder\":[\"aykjshfjrxpd9zngqruenqn5s7h\",\"adhsx4h5ss7rqdcjt8xyam6xtqc\",\"auow16g4f4tf4z89qrxbg3btxba\",\"7me9p46gbqiyfmfnapi7dyxb5br\",\"76bqrrm8dobr37kttya6jhznjih\"],\"icon\":\"📝\",\"isTemplate\":false,\"properties\":{\"a4nfnb5xr3txr5xq7y9ho7kyz6c\":\"https://docs.mattermost.com/boards/working-with-boards.html#adding-cards\",\"a972dc7a-5f4c-45d2-8044-8c28c69717f1\":\"amm6wfhnbuxojwssyftgs9dipqe\",\"acypkejeb5yfujhj9te57p9kaxw\":\"aanaehcw3m13jytujsjk5hpf6ry\"}},\"createAt\":1640034759755,\"updateAt\":1643788318631,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"cq1gmiwhx4jgd7q9ad9c1icasqr\",\"parentId\":\"buixxjic3xjfkieees4iafdrznc\",\"rootId\":\"buixxjic3xjfkieees4iafdrznc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Share cards on Channels\",\"fields\":{\"contentOrder\":[\"arpj7spx9op8jumm6yfdsxwpeuw\",\"a4z7htb6catgaue5npinux4tmrc\",\"a3qa1n69wc7d1u8krumz9ogcidy\",\"7y5hxcb9zzprzzrqeu675rtnpae\"],\"icon\":\"📮\",\"isTemplate\":false,\"properties\":{\"a4nfnb5xr3txr5xq7y9ho7kyz6c\":\"https://docs.mattermost.com/boards/work-with-cards.html#share-card-previews\",\"a972dc7a-5f4c-45d2-8044-8c28c69717f1\":\"af3p8ztcyxgn8wd9z4az7o9tjeh\",\"acypkejeb5yfujhj9te57p9kaxw\":\"ascd7nm9r491ayot8i86g1gmgqw\"}},\"createAt\":1641487149480,\"updateAt\":1643788318631,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"cse6a9d81tfyd7e34cbmfttbgte\",\"parentId\":\"buixxjic3xjfkieees4iafdrznc\",\"rootId\":\"buixxjic3xjfkieees4iafdrznc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Filter and sort cards\",\"fields\":{\"contentOrder\":[\"a4fz9kcfs9ibj8puk9mux7ac94c\",\"ad9fecctco7ggjjeo9usfpwkfpa\",\"78i8aqjmqtibr7x4okhz6uqquqr\",\"7oz9pp3bkopgfpycph3oqgze8uw\"],\"icon\":\"🎛️\",\"isTemplate\":false,\"properties\":{\"a972dc7a-5f4c-45d2-8044-8c28c69717f1\":\"ajurey3xkocs1nwx8di5zx6oe7o\",\"acypkejeb5yfujhj9te57p9kaxw\":\"aq6ukoiciyfctgwyhwzpfss8ghe\"}},\"createAt\":1640034759298,\"updateAt\":1643788318631,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"cstm8jadnmbds9kooz4tr8sy5wr\",\"parentId\":\"buixxjic3xjfkieees4iafdrznc\",\"rootId\":\"buixxjic3xjfkieees4iafdrznc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Create a new view\",\"fields\":{\"contentOrder\":[\"aozbezukpgif3jpbsq7tahmmp5e\",\"a538fji6kcp89fyhmgaoko7wk6c\",\"7owai1ux3h3gtf8byynfk6hyx1c\",\"7n8jq1dizyfgotby3o91arf1hxh\",\"77y4wffj1ctg7xmm9bx45qn6q6o\",\"7te5jcsrym3by7yxq4um8usj7ao\"],\"icon\":\"👓\",\"isTemplate\":false,\"properties\":{\"a4nfnb5xr3txr5xq7y9ho7kyz6c\":\"https://docs.mattermost.com/boards/working-with-boards.html#adding-new-views\",\"a972dc7a-5f4c-45d2-8044-8c28c69717f1\":\"af3p8ztcyxgn8wd9z4az7o9tjeh\",\"acypkejeb5yfujhj9te57p9kaxw\":\"ascd7nm9r491ayot8i86g1gmgqw\"}},\"createAt\":1640034759508,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"cut8jasi4etbd7mpqpn36fna9ba\",\"parentId\":\"buixxjic3xjfkieees4iafdrznc\",\"rootId\":\"buixxjic3xjfkieees4iafdrznc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"Add new properties\",\"fields\":{\"contentOrder\":[\"afb3pntrwgtrwidfeo1f1dsonqy\",\"ayhk11qsuz789fk8bqae4oz8mro\",\"7gc3z8cf8rirgfyutwoke9nn6jy\",\"76cinqnb6k3dzmfbm9fnc8eofny\",\"79yggmhcyhbgdiej5w4re3k4ssy\"],\"icon\":\"🏷️\",\"isTemplate\":false,\"properties\":{\"a4nfnb5xr3txr5xq7y9ho7kyz6c\":\"https://docs.mattermost.com/boards/work-with-cards.html#add-and-manage-properties\",\"a972dc7a-5f4c-45d2-8044-8c28c69717f1\":\"af3p8ztcyxgn8wd9z4az7o9tjeh\",\"acypkejeb5yfujhj9te57p9kaxw\":\"ascd7nm9r491ayot8i86g1gmgqw\"}},\"createAt\":1640034759239,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"cwf9xw6d6zbgfbbgd6atr336uco\",\"parentId\":\"buixxjic3xjfkieees4iafdrznc\",\"rootId\":\"buixxjic3xjfkieees4iafdrznc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"card\",\"title\":\"@mention teammates\",\"fields\":{\"contentOrder\":[\"akbz4abecginpjgz1etweynd7io\",\"ab6ygnyg757bc9cpm9bkp8aam8e\",\"7mbw9t71hjbrydgzgkqqaoh8usr\",\"78mzk7qpof7ybxpfjn9j9an9gkh\"],\"icon\":\"🔔\",\"isTemplate\":false,\"properties\":{\"a4nfnb5xr3txr5xq7y9ho7kyz6c\":\"https://docs.mattermost.com/boards/work-with-cards.html#mention-people\",\"a972dc7a-5f4c-45d2-8044-8c28c69717f1\":\"ajurey3xkocs1nwx8di5zx6oe7o\",\"acypkejeb5yfujhj9te57p9kaxw\":\"aq6ukoiciyfctgwyhwzpfss8ghe\"}},\"createAt\":1640034759348,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"vkm63rwrg5p8q5mrrk6ghzk3q1r\",\"parentId\":\"buixxjic3xjfkieees4iafdrznc\",\"rootId\":\"buixxjic3xjfkieees4iafdrznc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"view\",\"title\":\"Preview: Table View\",\"fields\":{\"cardOrder\":[],\"collapsedOptionIds\":[],\"columnCalculations\":{},\"columnWidths\":{\"__title\":280,\"a972dc7a-5f4c-45d2-8044-8c28c69717f1\":100,\"acypkejeb5yfujhj9te57p9kaxw\":169},\"defaultTemplateId\":\"\",\"filter\":{\"filters\":[],\"operation\":\"and\"},\"hiddenOptionIds\":[],\"kanbanCalculations\":{},\"sortOptions\":[],\"viewType\":\"table\",\"visibleOptionIds\":[],\"visiblePropertyIds\":[\"a972dc7a-5f4c-45d2-8044-8c28c69717f1\",\"aqh13jabwexjkzr3jqsz1i1syew\",\"acmg7mz1rr1eykfug4hcdpb1y1o\",\"acypkejeb5yfujhj9te57p9kaxw\"]},\"createAt\":1640034759909,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"vmu9ebyngpjb1mkc887m3pfocfa\",\"parentId\":\"buixxjic3xjfkieees4iafdrznc\",\"rootId\":\"buixxjic3xjfkieees4iafdrznc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"view\",\"title\":\"Preview: Calendar View\",\"fields\":{\"cardOrder\":[],\"collapsedOptionIds\":[],\"columnCalculations\":{},\"columnWidths\":{},\"dateDisplayPropertyId\":\"acmg7mz1rr1eykfug4hcdpb1y1o\",\"defaultTemplateId\":\"\",\"filter\":{\"filters\":[],\"operation\":\"and\"},\"hiddenOptionIds\":[],\"kanbanCalculations\":{},\"sortOptions\":[],\"viewType\":\"calendar\",\"visibleOptionIds\":[],\"visiblePropertyIds\":[\"__title\"]},\"createAt\":1641796830370,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"vssisypn6sfbctxy8si7oj3y9io\",\"parentId\":\"buixxjic3xjfkieees4iafdrznc\",\"rootId\":\"buixxjic3xjfkieees4iafdrznc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"view\",\"title\":\"Preview: Gallery View\",\"fields\":{\"cardOrder\":[],\"collapsedOptionIds\":[],\"columnCalculations\":{},\"columnWidths\":{},\"defaultTemplateId\":\"\",\"filter\":{\"filters\":[],\"operation\":\"and\"},\"hiddenOptionIds\":[],\"kanbanCalculations\":{},\"sortOptions\":[],\"viewType\":\"gallery\",\"visibleOptionIds\":[],\"visiblePropertyIds\":[\"__title\"]},\"createAt\":1641791749185,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"vzou1dc41ntn73mkyymka9yrese\",\"parentId\":\"buixxjic3xjfkieees4iafdrznc\",\"rootId\":\"buixxjic3xjfkieees4iafdrznc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"view\",\"title\":\"Onboarding\",\"fields\":{\"cardOrder\":[\"cmjtfip8a738nbr33shzmgk559o\",\"c9h4wpgh1ajyzfdqoyotohtj6oy\",\"cfkikng8egbr878ryaztmpkno4w\",\"cq1gmiwhx4jgd7q9ad9c1icasqr\",\"cut8jasi4etbd7mpqpn36fna9ba\",\"cstm8jadnmbds9kooz4tr8sy5wr\",\"cwf9xw6d6zbgfbbgd6atr336uco\",\"c5ay4q3t1hf8cdcschejip7ybpc\",\"cm8yz355wbtfd7rtpgs655wbr4e\",\"cse6a9d81tfyd7e34cbmfttbgte\"],\"collapsedOptionIds\":[],\"columnCalculations\":{},\"columnWidths\":{},\"defaultTemplateId\":\"\",\"filter\":{\"filters\":[],\"operation\":\"and\"},\"groupById\":\"a972dc7a-5f4c-45d2-8044-8c28c69717f1\",\"hiddenOptionIds\":[\"\"],\"kanbanCalculations\":{},\"sortOptions\":[],\"viewType\":\"board\",\"visibleOptionIds\":[\"aqb5x3pt87dcc9stbk4ofodrpoy\",\"a1mtm777bkagq3iuu7xo9b13qfr\",\"auxbwzptiqzkii5r61uz3ndsy1r\",\"aj9386k1bx8qwmepeuxg3b7z4pw\"],\"visiblePropertyIds\":[]},\"createAt\":1640034759964,\"updateAt\":1643788318632,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"aefratgmk6j8nzj5fngfrf4k8hw\",\"parentId\":\"c5ay4q3t1hf8cdcschejip7ybpc\",\"rootId\":\"buixxjic3xjfkieees4iafdrznc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"Mattermost Boards makes it easy for you to update certain properties on cards through our drag and drop functionality. Simply drag this card from the **Later** column to the **Completed** column to automatically update the status and mark this task as complete.\",\"fields\":{},\"createAt\":1640035135582,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"77msur4yswfn65d8qycdyfpfawe\",\"parentId\":\"c9h4wpgh1ajyzfdqoyotohtj6oy\",\"rootId\":\"buixxjic3xjfkieees4iafdrznc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Assign tasks to teammates\",\"fields\":{\"value\":false},\"createAt\":1641787609114,\"updateAt\":1643788318633,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7ayruwskq4b8rte64fiwz493kjo\",\"parentId\":\"c9h4wpgh1ajyzfdqoyotohtj6oy\",\"rootId\":\"buixxjic3xjfkieees4iafdrznc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Create and manage checklists, like this one... :)\",\"fields\":{\"value\":false},\"createAt\":1641788123369,\"updateAt\":1643788318634,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7dh4oncxngj8jb8n59sefmsynac\",\"parentId\":\"c9h4wpgh1ajyzfdqoyotohtj6oy\",\"rootId\":\"buixxjic3xjfkieees4iafdrznc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Add and update descriptions with Markdown\",\"fields\":{\"value\":false},\"createAt\":1641793564533,\"updateAt\":1643788318634,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7exhjmek1ctbexxt95w5cy1cuwo\",\"parentId\":\"c9h4wpgh1ajyzfdqoyotohtj6oy\",\"rootId\":\"buixxjic3xjfkieees4iafdrznc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Follow cards to get notified on the latest updates\",\"fields\":{\"value\":false},\"createAt\":1641787466530,\"updateAt\":1643788318634,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7mgy47rzyxpdm5c5eod9x5nypea\",\"parentId\":\"c9h4wpgh1ajyzfdqoyotohtj6oy\",\"rootId\":\"buixxjic3xjfkieees4iafdrznc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Set priorities and update statuses\",\"fields\":{\"value\":false},\"createAt\":1641787424191,\"updateAt\":1643788318634,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7nkegq1zimifpmxcrq8ntyothoe\",\"parentId\":\"c9h4wpgh1ajyzfdqoyotohtj6oy\",\"rootId\":\"buixxjic3xjfkieees4iafdrznc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Provide feedback and ask questions via comments\",\"fields\":{\"value\":false},\"createAt\":1642012664514,\"updateAt\":1643788318634,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7peuyuzgkc3fmzczfjuzseg9ksa\",\"parentId\":\"c9h4wpgh1ajyzfdqoyotohtj6oy\",\"rootId\":\"buixxjic3xjfkieees4iafdrznc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"@mention teammates so they can follow, and collaborate on, comments and descriptions\",\"fields\":{\"value\":false},\"createAt\":1641787459538,\"updateAt\":1643788318634,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7tuw1my7b7fnxd8sfyzpz6dd1sc\",\"parentId\":\"c9h4wpgh1ajyzfdqoyotohtj6oy\",\"rootId\":\"buixxjic3xjfkieees4iafdrznc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"checkbox\",\"title\":\"Manage deadlines and milestones\",\"fields\":{\"value\":false},\"createAt\":1641787583043,\"updateAt\":1643788318634,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"a778mcixrm7byzb4mxrixjtrwwa\",\"parentId\":\"c9h4wpgh1ajyzfdqoyotohtj6oy\",\"rootId\":\"buixxjic3xjfkieees4iafdrznc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"Cards allow your entire team to manage and collaborate on a task in one place. Within a card, your team can:\",\"fields\":{},\"createAt\":1641786489535,\"updateAt\":1643788318634,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7cux9rwr1b3rjmxakbipeoxky6h\",\"parentId\":\"cfkikng8egbr878ryaztmpkno4w\",\"rootId\":\"buixxjic3xjfkieees4iafdrznc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"image\",\"title\":\"\",\"fields\":{\"fileId\":\"74uia99m9btr8peydw7oexn37tw.gif\"},\"createAt\":1643638662994,\"updateAt\":1643788318634,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"apedf7fbrspgt3cg8e5worq1gqa\",\"parentId\":\"cfkikng8egbr878ryaztmpkno4w\",\"rootId\":\"buixxjic3xjfkieees4iafdrznc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"A board helps you manage your project, organize tasks, and collaborate with your team all in one place.\",\"fields\":{},\"createAt\":1641797295263,\"updateAt\":1643788318634,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"as7511u6t1pdc7fe7zrbzdfg51y\",\"parentId\":\"cfkikng8egbr878ryaztmpkno4w\",\"rootId\":\"buixxjic3xjfkieees4iafdrznc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"To create your own board, select the \\\"+\\\" on the top of the left hand sidebar. Choose from one of our standard templates and see how they can help you get started with your next project:\\n\\n- **Project Tasks**: Stay on top of your project tasks, track progress, and set priorities. \\n- **Meeting Agenda**: Set your meeting agendas for recurring team meetings and 1:1s.\\n- **Roadmap**: Plan your roadmap and manage your releases more efficiently.\\n- **Personal Tasks**: Organize your life and track your personal tasks.\\n- **Content Calendar**: Plan your editorial content, assign work, and track deadlines.\\n- **Personal Goals**: Set and accomplish new personal goals and milestones.\",\"fields\":{},\"createAt\":1641788827589,\"updateAt\":1643788318634,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7hkn59merfbf38gzxf7sabewuma\",\"parentId\":\"cm8yz355wbtfd7rtpgs655wbr4e\",\"rootId\":\"buixxjic3xjfkieees4iafdrznc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"image\",\"title\":\"\",\"fields\":{\"fileId\":\"7knxbyuiedtdafcgmropgkrtybr.gif\"},\"createAt\":1643638846658,\"updateAt\":1643788318634,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"a5z5po5apabfibkgmkq53dxe9dw\",\"parentId\":\"cm8yz355wbtfd7rtpgs655wbr4e\",\"rootId\":\"buixxjic3xjfkieees4iafdrznc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"Keep stakeholders and customers up-to-date on project progress by sharing your board.\",\"fields\":{},\"createAt\":1642198459390,\"updateAt\":1643788318634,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"ag791jfbc47gobroeo9ie1afcdo\",\"parentId\":\"cm8yz355wbtfd7rtpgs655wbr4e\",\"rootId\":\"buixxjic3xjfkieees4iafdrznc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"To share a board, select **Share** at the top right of the Board view. Copy the link to share the board internally with your team or generate public link that can be accessed by anyone externally.\",\"fields\":{},\"createAt\":1642199464341,\"updateAt\":1643788318634,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"76bqrrm8dobr37kttya6jhznjih\",\"parentId\":\"cmjtfip8a738nbr33shzmgk559o\",\"rootId\":\"buixxjic3xjfkieees4iafdrznc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"image\",\"title\":\"\",\"fields\":{\"fileId\":\"7iw4rxx7jj7bypmdotd9z469cyh.png\"},\"createAt\":1643143198631,\"updateAt\":1643788318634,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"auow16g4f4tf4z89qrxbg3btxba\",\"parentId\":\"cmjtfip8a738nbr33shzmgk559o\",\"rootId\":\"buixxjic3xjfkieees4iafdrznc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"To create a new card, simply do any of the following:\\n- Select \\\"**New**\\\" on the top right header\\n- Select \\\"**+ New**\\\" below any column\\n- Select \\\"**+**\\\" to the right of any columnn header\",\"fields\":{},\"createAt\":1640034820888,\"updateAt\":1643788318634,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"aykjshfjrxpd9zngqruenqn5s7h\",\"parentId\":\"cmjtfip8a738nbr33shzmgk559o\",\"rootId\":\"buixxjic3xjfkieees4iafdrznc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"Mattermost Boards helps you manage and track all your project tasks with **Cards**.\",\"fields\":{},\"createAt\":1641504858080,\"updateAt\":1643788318634,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7y5hxcb9zzprzzrqeu675rtnpae\",\"parentId\":\"cq1gmiwhx4jgd7q9ad9c1icasqr\",\"rootId\":\"buixxjic3xjfkieees4iafdrznc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"image\",\"title\":\"\",\"fields\":{\"fileId\":\"7ek6wbpp19jfoujs1goh6kttbby.gif\"},\"createAt\":1643638818402,\"updateAt\":1643788318634,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"a3qa1n69wc7d1u8krumz9ogcidy\",\"parentId\":\"cq1gmiwhx4jgd7q9ad9c1icasqr\",\"rootId\":\"buixxjic3xjfkieees4iafdrznc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"After you've copied the link, paste it into any channel or Direct Message to share the card. A preview of the card will display within the channel with a link back to the card on Boards.\",\"fields\":{},\"createAt\":1642195798726,\"updateAt\":1643788318634,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"a4z7htb6catgaue5npinux4tmrc\",\"parentId\":\"cq1gmiwhx4jgd7q9ad9c1icasqr\",\"rootId\":\"buixxjic3xjfkieees4iafdrznc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"To share a card, you'll need to copy the card link first. You can:\\n\\n- Open a card and select the options menu button at the top right of the card.\\n- Open the board view and hover your mouse over any card to access the options menu button.\",\"fields\":{},\"createAt\":1642193170054,\"updateAt\":1643788318634,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"arpj7spx9op8jumm6yfdsxwpeuw\",\"parentId\":\"cq1gmiwhx4jgd7q9ad9c1icasqr\",\"rootId\":\"buixxjic3xjfkieees4iafdrznc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"Cards can be linked and shared with teammates directly on Channels. Card previews are displayed when shared on Channels, so your team can discuss work items and get the relevant context without having to switch over to Boards.\",\"fields\":{},\"createAt\":1642193162587,\"updateAt\":1643788318634,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7oz9pp3bkopgfpycph3oqgze8uw\",\"parentId\":\"cse6a9d81tfyd7e34cbmfttbgte\",\"rootId\":\"buixxjic3xjfkieees4iafdrznc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"image\",\"title\":\"\",\"fields\":{\"fileId\":\"7dybb6t8fj3nrdft7nerhuf784y.png\"},\"createAt\":1643142381491,\"updateAt\":1643788318634,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"ad9fecctco7ggjjeo9usfpwkfpa\",\"parentId\":\"cse6a9d81tfyd7e34cbmfttbgte\",\"rootId\":\"buixxjic3xjfkieees4iafdrznc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"Organize and find the cards you're looking for with our filter, sort, and grouping options. From the Board header, you can quickly toggle on different properties, change the group display, set filters, and change how the cards are sorted.\",\"fields\":{},\"createAt\":1640034870185,\"updateAt\":1643788318634,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"7te5jcsrym3by7yxq4um8usj7ao\",\"parentId\":\"cstm8jadnmbds9kooz4tr8sy5wr\",\"rootId\":\"buixxjic3xjfkieees4iafdrznc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"image\",\"title\":\"\",\"fields\":{\"fileId\":\"78jws5m1myf8pufewzkaa6i11sc.gif\"},\"createAt\":1643638721400,\"updateAt\":1643788318634,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"a538fji6kcp89fyhmgaoko7wk6c\",\"parentId\":\"cstm8jadnmbds9kooz4tr8sy5wr\",\"rootId\":\"buixxjic3xjfkieees4iafdrznc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"Views allow your team to visualize the same cards and data from different perspectives, so they can stay up-to-date in the way that works best for them. To add a new view, go to **Add a new view** from the view drop-down, then select from any of the following views:\\n\\n- **Board**: Adds a Kanban board, similar to this one, that allows your team to organize cards in swimlanes grouped by any property of your choosing. This view helps you visualize your project progress.\\n- **Table**: Displays cards in a table format with rows and columns. Use this view to get an overview of all your project tasks. Easily view and compare the state of all properties across all cards without needing to open individual cards.\\n- **Gallery**: Displays cards in a gallery format, so you can manage and organize cards with image attachments.\\n- **Calendar**: Adds a calendar view to easily visualize your cards by dates and keep track of deadlines.\",\"fields\":{},\"createAt\":1640035911505,\"updateAt\":1643788318634,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"79yggmhcyhbgdiej5w4re3k4ssy\",\"parentId\":\"cut8jasi4etbd7mpqpn36fna9ba\",\"rootId\":\"buixxjic3xjfkieees4iafdrznc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"image\",\"title\":\"\",\"fields\":{\"fileId\":\"7d6hrtig3zt8f9cnbo1um5oxx3y.gif\"},\"createAt\":1643638613909,\"updateAt\":1643788318634,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"afb3pntrwgtrwidfeo1f1dsonqy\",\"parentId\":\"cut8jasi4etbd7mpqpn36fna9ba\",\"rootId\":\"buixxjic3xjfkieees4iafdrznc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"Customize cards to fit your needs and track the information most important to you. Boards supports a wide range of fully customizable property types. For example, you can:\\n- Use the **Date** property for things like deadlines or milestones.\\n- Assign owners to tasks with the **Person** property.\\n- Define statuses and priorities with the **Select** property.\\n- Create tags with the **Multi Select** property.\\n- Link cards to webpages with the **URL** property.\",\"fields\":{},\"createAt\":1641611832288,\"updateAt\":1643788318634,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"78mzk7qpof7ybxpfjn9j9an9gkh\",\"parentId\":\"cwf9xw6d6zbgfbbgd6atr336uco\",\"rootId\":\"buixxjic3xjfkieees4iafdrznc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"image\",\"title\":\"\",\"fields\":{\"fileId\":\"74nt9eqzea3ydjjpgjtsxcjgrxc.gif\"},\"createAt\":1643638766210,\"updateAt\":1643788318634,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"ab6ygnyg757bc9cpm9bkp8aam8e\",\"parentId\":\"cwf9xw6d6zbgfbbgd6atr336uco\",\"rootId\":\"buixxjic3xjfkieees4iafdrznc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"To mention a teammate use the **@ symbol with their username** in the comments or description section. They'll get a Direct Message notification via Channels and also be added as a [follower](https://docs.mattermost.com/boards/work-with-cards.html#receive-updates) to the card. \\n\\nWhenever any changes are made to the card, they'll automatically get notified on Channels.\",\"fields\":{},\"createAt\":1642177456022,\"updateAt\":1643788318634,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n{\"type\":\"block\",\"data\":{\"id\":\"akbz4abecginpjgz1etweynd7io\",\"parentId\":\"cwf9xw6d6zbgfbbgd6atr336uco\",\"rootId\":\"buixxjic3xjfkieees4iafdrznc\",\"createdBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"modifiedBy\":\"edrkkih4cinzf8ueeszh6rmfoo\",\"schema\":1,\"type\":\"text\",\"title\":\"Collaborate with teammates directly on each card using @mentions and have all the relevant context in one place.\",\"fields\":{},\"createAt\":1642177002644,\"updateAt\":1643788318634,\"deleteAt\":0,\"workspaceId\":\"855b3j34ojn5p8f36yhu8336fe\"}}\n"
  },
  {
    "path": "server/assets/templates-boardarchive/version.json",
    "content": "{\"version\":2,\"date\":1643788318636}\n"
  },
  {
    "path": "server/auth/auth.go",
    "content": "//go:generate mockgen -destination=mocks/mockauth_interface.go -package mocks . AuthInterface\npackage auth\n\nimport (\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/config\"\n\t\"github.com/mattermost/focalboard/server/services/permissions\"\n\t\"github.com/mattermost/focalboard/server/services/store\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n\t\"github.com/pkg/errors\"\n)\n\ntype AuthInterface interface {\n\tGetSession(token string) (*model.Session, error)\n\tIsValidReadToken(boardID string, readToken string) (bool, error)\n\tDoesUserHaveTeamAccess(userID string, teamID string) bool\n}\n\n// Auth authenticates sessions.\ntype Auth struct {\n\tconfig      *config.Configuration\n\tstore       store.Store\n\tpermissions permissions.PermissionsService\n}\n\n// New returns a new Auth.\nfunc New(config *config.Configuration, store store.Store, permissions permissions.PermissionsService) *Auth {\n\treturn &Auth{config: config, store: store, permissions: permissions}\n}\n\n// GetSession Get a user active session and refresh the session if needed.\nfunc (a *Auth) GetSession(token string) (*model.Session, error) {\n\tif len(token) < 1 {\n\t\treturn nil, errors.New(\"no session token\")\n\t}\n\n\tsession, err := a.store.GetSession(token, a.config.SessionExpireTime)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"unable to get the session for the token\")\n\t}\n\tif session.UpdateAt < (utils.GetMillis() - utils.SecondsToMillis(a.config.SessionRefreshTime)) {\n\t\t_ = a.store.RefreshSession(session)\n\t}\n\treturn session, nil\n}\n\n// IsValidReadToken validates the read token for a board.\nfunc (a *Auth) IsValidReadToken(boardID string, readToken string) (bool, error) {\n\tsharing, err := a.store.GetSharing(boardID)\n\tif model.IsErrNotFound(err) {\n\t\treturn false, nil\n\t}\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tif !a.config.EnablePublicSharedBoards {\n\t\treturn false, errors.New(\"public shared boards disabled\")\n\t}\n\n\tif sharing != nil && (sharing.ID == boardID && sharing.Enabled && sharing.Token == readToken) {\n\t\treturn true, nil\n\t}\n\n\treturn false, nil\n}\n\nfunc (a *Auth) DoesUserHaveTeamAccess(userID string, teamID string) bool {\n\treturn a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam)\n}\n"
  },
  {
    "path": "server/auth/auth_test.go",
    "content": "package auth\n\nimport (\n\t\"testing\"\n\n\t\"github.com/golang/mock/gomock\"\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/config\"\n\t\"github.com/mattermost/focalboard/server/services/permissions/localpermissions\"\n\tmockpermissions \"github.com/mattermost/focalboard/server/services/permissions/mocks\"\n\t\"github.com/mattermost/focalboard/server/services/store/mockstore\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\ntype TestHelper struct {\n\tAuth    *Auth\n\tSession model.Session\n\tStore   *mockstore.MockStore\n}\n\nvar mockSession = &model.Session{\n\tID:       utils.NewID(utils.IDTypeSession),\n\tToken:    \"goodToken\",\n\tUserID:   \"12345\",\n\tCreateAt: utils.GetMillis() - utils.SecondsToMillis(2000),\n\tUpdateAt: utils.GetMillis() - utils.SecondsToMillis(2000),\n}\n\nfunc setupTestHelper(t *testing.T) *TestHelper {\n\tctrl := gomock.NewController(t)\n\tctrlPermissions := gomock.NewController(t)\n\tcfg := config.Configuration{}\n\tmockStore := mockstore.NewMockStore(ctrl)\n\tmockPermissions := mockpermissions.NewMockStore(ctrlPermissions)\n\tlogger, err := mlog.NewLogger()\n\trequire.NoError(t, err)\n\tnewAuth := New(&cfg, mockStore, localpermissions.New(mockPermissions, logger))\n\n\t// called during default template setup for every test\n\tmockStore.EXPECT().GetTemplateBoards(\"0\", \"\").AnyTimes()\n\tmockStore.EXPECT().RemoveDefaultTemplates(gomock.Any()).AnyTimes()\n\tmockStore.EXPECT().InsertBlock(gomock.Any(), gomock.Any()).AnyTimes()\n\n\treturn &TestHelper{\n\t\tAuth:    newAuth,\n\t\tSession: *mockSession,\n\t\tStore:   mockStore,\n\t}\n}\n\nfunc TestGetSession(t *testing.T) {\n\tth := setupTestHelper(t)\n\n\ttestcases := []struct {\n\t\ttitle       string\n\t\ttoken       string\n\t\trefreshTime int64\n\t\tisError     bool\n\t}{\n\t\t{\"fail, no token\", \"\", 0, true},\n\t\t{\"fail, invalid username\", \"badToken\", 0, true},\n\t\t{\"success, good token\", \"goodToken\", 1000, false},\n\t}\n\n\tth.Store.EXPECT().GetSession(\"badToken\", gomock.Any()).Return(nil, errors.New(\"Invalid Token\"))\n\tth.Store.EXPECT().GetSession(\"goodToken\", gomock.Any()).Return(mockSession, nil)\n\tth.Store.EXPECT().RefreshSession(gomock.Any()).Return(nil)\n\n\tfor _, test := range testcases {\n\t\tt.Run(test.title, func(t *testing.T) {\n\t\t\tif test.refreshTime > 0 {\n\t\t\t\tth.Auth.config.SessionRefreshTime = test.refreshTime\n\t\t\t}\n\n\t\t\tsession, err := th.Auth.GetSession(test.token)\n\t\t\tif test.isError {\n\t\t\t\trequire.Error(t, err)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.NotNil(t, session)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestIsValidReadToken(t *testing.T) {\n\t// ToDo: reimplement\n\n\t// th := setupTestHelper(t)\n\n\t// validBlockID := \"testBlockID\"\n\t// mockContainer := store.Container{\n\t// \tTeamID: \"testTeamID\",\n\t// }\n\t// validReadToken := \"testReadToken\"\n\t// mockSharing := model.Sharing{\n\t// \tID:      \"testRootID\",\n\t// \tEnabled: true,\n\t// \tToken:   validReadToken,\n\t// }\n\n\t// testcases := []struct {\n\t// \ttitle     string\n\t// \tcontainer store.Container\n\t// \tblockID   string\n\t// \treadToken string\n\t// \tisError   bool\n\t// \tisSuccess bool\n\t// }{\n\t// \t{\"fail, error GetRootID\", mockContainer, \"badBlock\", \"\", true, false},\n\t// \t{\"fail, rootID not found\", mockContainer, \"goodBlockID\", \"\", false, false},\n\t// \t{\"fail, sharing throws error\", mockContainer, \"goodBlockID2\", \"\", true, false},\n\t// \t{\"fail, bad readToken\", mockContainer, validBlockID, \"invalidReadToken\", false, false},\n\t// \t{\"success\", mockContainer, validBlockID, validReadToken, false, true},\n\t// }\n\n\t// th.Store.EXPECT().GetRootID(gomock.Eq(mockContainer), \"badBlock\").Return(\"\", errors.New(\"invalid block\"))\n\t// th.Store.EXPECT().GetRootID(gomock.Eq(mockContainer), \"goodBlockID\").Return(\"rootNotFound\", nil)\n\t// th.Store.EXPECT().GetRootID(gomock.Eq(mockContainer), \"goodBlockID2\").Return(\"rootError\", nil)\n\t// th.Store.EXPECT().GetRootID(gomock.Eq(mockContainer), validBlockID).Return(\"testRootID\", nil).Times(2)\n\t// th.Store.EXPECT().GetSharing(gomock.Eq(mockContainer), \"rootNotFound\").Return(nil, sql.ErrNoRows)\n\t// th.Store.EXPECT().GetSharing(gomock.Eq(mockContainer), \"rootError\").Return(nil, errors.New(\"another error\"))\n\t// th.Store.EXPECT().GetSharing(gomock.Eq(mockContainer), \"testRootID\").Return(&mockSharing, nil).Times(2)\n\n\t// for _, test := range testcases {\n\t// \tt.Run(test.title, func(t *testing.T) {\n\t// \t\tsuccess, err := th.Auth.IsValidReadToken(test.container, test.blockID, test.readToken)\n\t// \t\tif test.isError {\n\t// \t\t\trequire.Error(t, err)\n\t// \t\t} else {\n\t// \t\t\trequire.NoError(t, err)\n\t// \t\t}\n\t// \t\tif test.isSuccess {\n\t// \t\t\trequire.True(t, success)\n\t// \t\t} else {\n\t// \t\t\trequire.False(t, success)\n\t// \t\t}\n\t// \t})\n\t// }\n}\n"
  },
  {
    "path": "server/auth/mocks/mockauth_interface.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: github.com/mattermost/focalboard/server/auth (interfaces: AuthInterface)\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\treflect \"reflect\"\n\n\tgomock \"github.com/golang/mock/gomock\"\n\tmodel \"github.com/mattermost/focalboard/server/model\"\n)\n\n// MockAuthInterface is a mock of AuthInterface interface.\ntype MockAuthInterface struct {\n\tctrl     *gomock.Controller\n\trecorder *MockAuthInterfaceMockRecorder\n}\n\n// MockAuthInterfaceMockRecorder is the mock recorder for MockAuthInterface.\ntype MockAuthInterfaceMockRecorder struct {\n\tmock *MockAuthInterface\n}\n\n// NewMockAuthInterface creates a new mock instance.\nfunc NewMockAuthInterface(ctrl *gomock.Controller) *MockAuthInterface {\n\tmock := &MockAuthInterface{ctrl: ctrl}\n\tmock.recorder = &MockAuthInterfaceMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockAuthInterface) EXPECT() *MockAuthInterfaceMockRecorder {\n\treturn m.recorder\n}\n\n// DoesUserHaveTeamAccess mocks base method.\nfunc (m *MockAuthInterface) DoesUserHaveTeamAccess(arg0, arg1 string) bool {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"DoesUserHaveTeamAccess\", arg0, arg1)\n\tret0, _ := ret[0].(bool)\n\treturn ret0\n}\n\n// DoesUserHaveTeamAccess indicates an expected call of DoesUserHaveTeamAccess.\nfunc (mr *MockAuthInterfaceMockRecorder) DoesUserHaveTeamAccess(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"DoesUserHaveTeamAccess\", reflect.TypeOf((*MockAuthInterface)(nil).DoesUserHaveTeamAccess), arg0, arg1)\n}\n\n// GetSession mocks base method.\nfunc (m *MockAuthInterface) GetSession(arg0 string) (*model.Session, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetSession\", arg0)\n\tret0, _ := ret[0].(*model.Session)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetSession indicates an expected call of GetSession.\nfunc (mr *MockAuthInterfaceMockRecorder) GetSession(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetSession\", reflect.TypeOf((*MockAuthInterface)(nil).GetSession), arg0)\n}\n\n// IsValidReadToken mocks base method.\nfunc (m *MockAuthInterface) IsValidReadToken(arg0, arg1 string) (bool, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"IsValidReadToken\", arg0, arg1)\n\tret0, _ := ret[0].(bool)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// IsValidReadToken indicates an expected call of IsValidReadToken.\nfunc (mr *MockAuthInterfaceMockRecorder) IsValidReadToken(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"IsValidReadToken\", reflect.TypeOf((*MockAuthInterface)(nil).IsValidReadToken), arg0, arg1)\n}\n"
  },
  {
    "path": "server/client/client.go",
    "content": "package client\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"mime/multipart\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/mattermost/focalboard/server/api\"\n\t\"github.com/mattermost/focalboard/server/model\"\n\n\tmmModel \"github.com/mattermost/mattermost/server/public/model\"\n)\n\nconst (\n\tAPIURLSuffix = \"/api/v2\"\n)\n\ntype RequestReaderError struct {\n\tbuf []byte\n}\n\nfunc (rre RequestReaderError) Error() string {\n\treturn \"payload: \" + string(rre.buf)\n}\n\ntype Response struct {\n\tStatusCode int\n\tError      error\n\tHeader     http.Header\n}\n\nfunc BuildResponse(r *http.Response) *Response {\n\treturn &Response{\n\t\tStatusCode: r.StatusCode,\n\t\tHeader:     r.Header,\n\t}\n}\n\nfunc BuildErrorResponse(r *http.Response, err error) *Response {\n\tstatusCode := 0\n\theader := make(http.Header)\n\tif r != nil {\n\t\tstatusCode = r.StatusCode\n\t\theader = r.Header\n\t}\n\n\treturn &Response{\n\t\tStatusCode: statusCode,\n\t\tError:      err,\n\t\tHeader:     header,\n\t}\n}\n\nfunc closeBody(r *http.Response) {\n\tif r.Body != nil {\n\t\t_, _ = io.Copy(io.Discard, r.Body)\n\t\t_ = r.Body.Close()\n\t}\n}\n\nfunc toJSON(v interface{}) string {\n\tb, _ := json.Marshal(v)\n\treturn string(b)\n}\n\ntype Client struct {\n\tURL        string\n\tAPIURL     string\n\tHTTPClient *http.Client\n\tHTTPHeader map[string]string\n\t// Token if token is empty indicate client is not login yet\n\tToken string\n}\n\nfunc NewClient(url, sessionToken string) *Client {\n\turl = strings.TrimRight(url, \"/\")\n\n\theaders := map[string]string{\n\t\t\"X-Requested-With\": \"XMLHttpRequest\",\n\t}\n\n\treturn &Client{url, url + APIURLSuffix, &http.Client{}, headers, sessionToken}\n}\n\nfunc (c *Client) DoAPIGet(url, etag string) (*http.Response, error) {\n\treturn c.DoAPIRequest(http.MethodGet, c.APIURL+url, \"\", etag)\n}\n\nfunc (c *Client) DoAPIPost(url, data string) (*http.Response, error) {\n\treturn c.DoAPIRequest(http.MethodPost, c.APIURL+url, data, \"\")\n}\n\nfunc (c *Client) DoAPIPatch(url, data string) (*http.Response, error) {\n\treturn c.DoAPIRequest(http.MethodPatch, c.APIURL+url, data, \"\")\n}\n\nfunc (c *Client) DoAPIPut(url, data string) (*http.Response, error) {\n\treturn c.DoAPIRequest(http.MethodPut, c.APIURL+url, data, \"\")\n}\n\nfunc (c *Client) DoAPIDelete(url string, data string) (*http.Response, error) {\n\treturn c.DoAPIRequest(http.MethodDelete, c.APIURL+url, data, \"\")\n}\n\nfunc (c *Client) DoAPIRequest(method, url, data, etag string) (*http.Response, error) {\n\treturn c.doAPIRequestReader(method, url, strings.NewReader(data), etag)\n}\n\ntype requestOption func(r *http.Request)\n\nfunc (c *Client) doAPIRequestReader(method, url string, data io.Reader, _ /* etag */ string, opts ...requestOption) (*http.Response, error) {\n\trq, err := http.NewRequest(method, url, data)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, opt := range opts {\n\t\topt(rq)\n\t}\n\n\tif c.HTTPHeader != nil && len(c.HTTPHeader) > 0 {\n\t\tfor k, v := range c.HTTPHeader {\n\t\t\trq.Header.Set(k, v)\n\t\t}\n\t}\n\n\tif c.Token != \"\" {\n\t\trq.Header.Set(\"Authorization\", \"Bearer \"+c.Token)\n\t}\n\n\trp, err := c.HTTPClient.Do(rq)\n\tif err != nil || rp == nil {\n\t\treturn nil, err\n\t}\n\n\tif rp.StatusCode == http.StatusNotModified {\n\t\treturn rp, nil\n\t}\n\n\tif rp.StatusCode >= http.StatusMultipleChoices {\n\t\tdefer closeBody(rp)\n\t\tb, err := io.ReadAll(rp.Body)\n\t\tif err != nil {\n\t\t\treturn rp, fmt.Errorf(\"error when parsing response with code %d: %w\", rp.StatusCode, err)\n\t\t}\n\t\treturn rp, RequestReaderError{b}\n\t}\n\n\treturn rp, nil\n}\n\nfunc (c *Client) GetTeamRoute(teamID string) string {\n\treturn fmt.Sprintf(\"%s/%s\", c.GetTeamsRoute(), teamID)\n}\n\nfunc (c *Client) GetTeamsRoute() string {\n\treturn \"/teams\"\n}\n\nfunc (c *Client) GetBlockRoute(boardID, blockID string) string {\n\treturn fmt.Sprintf(\"%s/%s\", c.GetBlocksRoute(boardID), blockID)\n}\n\nfunc (c *Client) GetBoardsRoute() string {\n\treturn \"/boards\"\n}\n\nfunc (c *Client) GetBoardRoute(boardID string) string {\n\treturn fmt.Sprintf(\"%s/%s\", c.GetBoardsRoute(), boardID)\n}\n\nfunc (c *Client) GetBoardMetadataRoute(boardID string) string {\n\treturn fmt.Sprintf(\"%s/%s/metadata\", c.GetBoardsRoute(), boardID)\n}\n\nfunc (c *Client) GetJoinBoardRoute(boardID string) string {\n\treturn fmt.Sprintf(\"%s/%s/join\", c.GetBoardsRoute(), boardID)\n}\n\nfunc (c *Client) GetLeaveBoardRoute(boardID string) string {\n\treturn fmt.Sprintf(\"%s/%s/join\", c.GetBoardsRoute(), boardID)\n}\n\nfunc (c *Client) GetBlocksRoute(boardID string) string {\n\treturn fmt.Sprintf(\"%s/blocks\", c.GetBoardRoute(boardID))\n}\n\nfunc (c *Client) GetAllBlocksRoute(boardID string) string {\n\treturn fmt.Sprintf(\"%s/blocks?all=true\", c.GetBoardRoute(boardID))\n}\n\nfunc (c *Client) GetBoardsAndBlocksRoute() string {\n\treturn \"/boards-and-blocks\"\n}\n\nfunc (c *Client) GetCardsRoute() string {\n\treturn \"/cards\"\n}\n\nfunc (c *Client) GetCardRoute(cardID string) string {\n\treturn fmt.Sprintf(\"%s/%s\", c.GetCardsRoute(), cardID)\n}\n\nfunc (c *Client) GetTeam(teamID string) (*model.Team, *Response) {\n\tr, err := c.DoAPIGet(c.GetTeamRoute(teamID), \"\")\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\treturn model.TeamFromJSON(r.Body), BuildResponse(r)\n}\n\nfunc (c *Client) GetBlocksForBoard(boardID string) ([]*model.Block, *Response) {\n\tr, err := c.DoAPIGet(c.GetBlocksRoute(boardID), \"\")\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\treturn model.BlocksFromJSON(r.Body), BuildResponse(r)\n}\n\nfunc (c *Client) GetAllBlocksForBoard(boardID string) ([]*model.Block, *Response) {\n\tr, err := c.DoAPIGet(c.GetAllBlocksRoute(boardID), \"\")\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\treturn model.BlocksFromJSON(r.Body), BuildResponse(r)\n}\n\nconst disableNotifyQueryParam = \"disable_notify=true\"\n\nfunc (c *Client) PatchBlock(boardID, blockID string, blockPatch *model.BlockPatch, disableNotify bool) (bool, *Response) {\n\tvar queryParams string\n\tif disableNotify {\n\t\tqueryParams = \"?\" + disableNotifyQueryParam\n\t}\n\tr, err := c.DoAPIPatch(c.GetBlockRoute(boardID, blockID)+queryParams, toJSON(blockPatch))\n\tif err != nil {\n\t\treturn false, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\treturn true, BuildResponse(r)\n}\n\nfunc (c *Client) DuplicateBoard(boardID string, asTemplate bool, teamID string) (*model.BoardsAndBlocks, *Response) {\n\tqueryParams := \"?asTemplate=false&\"\n\tif asTemplate {\n\t\tqueryParams = \"?asTemplate=true\"\n\t}\n\tif len(teamID) > 0 {\n\t\tqueryParams = queryParams + \"&toTeam=\" + teamID\n\t}\n\tr, err := c.DoAPIPost(c.GetBoardRoute(boardID)+\"/duplicate\"+queryParams, \"\")\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\treturn model.BoardsAndBlocksFromJSON(r.Body), BuildResponse(r)\n}\n\nfunc (c *Client) DuplicateBlock(boardID, blockID string, asTemplate bool) (bool, *Response) {\n\tqueryParams := \"?asTemplate=false\"\n\tif asTemplate {\n\t\tqueryParams = \"?asTemplate=true\"\n\t}\n\tr, err := c.DoAPIPost(c.GetBlockRoute(boardID, blockID)+\"/duplicate\"+queryParams, \"\")\n\tif err != nil {\n\t\treturn false, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\treturn true, BuildResponse(r)\n}\n\nfunc (c *Client) UndeleteBlock(boardID, blockID string) (bool, *Response) {\n\tr, err := c.DoAPIPost(c.GetBlockRoute(boardID, blockID)+\"/undelete\", \"\")\n\tif err != nil {\n\t\treturn false, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\treturn true, BuildResponse(r)\n}\n\nfunc (c *Client) InsertBlocks(boardID string, blocks []*model.Block, disableNotify bool) ([]*model.Block, *Response) {\n\tvar queryParams string\n\tif disableNotify {\n\t\tqueryParams = \"?\" + disableNotifyQueryParam\n\t}\n\tr, err := c.DoAPIPost(c.GetBlocksRoute(boardID)+queryParams, toJSON(blocks))\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\treturn model.BlocksFromJSON(r.Body), BuildResponse(r)\n}\n\nfunc (c *Client) DeleteBlock(boardID, blockID string, disableNotify bool) (bool, *Response) {\n\tvar queryParams string\n\tif disableNotify {\n\t\tqueryParams = \"?\" + disableNotifyQueryParam\n\t}\n\tr, err := c.DoAPIDelete(c.GetBlockRoute(boardID, blockID)+queryParams, \"\")\n\tif err != nil {\n\t\treturn false, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\treturn true, BuildResponse(r)\n}\n\n//\n// Cards\n//\n\nfunc (c *Client) CreateCard(boardID string, card *model.Card, disableNotify bool) (*model.Card, *Response) {\n\tvar queryParams string\n\tif disableNotify {\n\t\tqueryParams = \"?\" + disableNotifyQueryParam\n\t}\n\tr, err := c.DoAPIPost(c.GetBoardRoute(boardID)+\"/cards\"+queryParams, toJSON(card))\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\tvar cardNew *model.Card\n\tif err := json.NewDecoder(r.Body).Decode(&cardNew); err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\n\treturn cardNew, BuildResponse(r)\n}\n\nfunc (c *Client) GetCards(boardID string, page int, perPage int) ([]*model.Card, *Response) {\n\turl := fmt.Sprintf(\"%s/cards?page=%d&per_page=%d\", c.GetBoardRoute(boardID), page, perPage)\n\tr, err := c.DoAPIGet(url, \"\")\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\n\tdefer closeBody(r)\n\n\tvar cards []*model.Card\n\tif err := json.NewDecoder(r.Body).Decode(&cards); err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\n\treturn cards, BuildResponse(r)\n}\n\nfunc (c *Client) PatchCard(cardID string, cardPatch *model.CardPatch, disableNotify bool) (*model.Card, *Response) {\n\tvar queryParams string\n\tif disableNotify {\n\t\tqueryParams = \"?\" + disableNotifyQueryParam\n\t}\n\tr, err := c.DoAPIPatch(c.GetCardRoute(cardID)+queryParams, toJSON(cardPatch))\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\n\tdefer closeBody(r)\n\n\tvar cardNew *model.Card\n\tif err := json.NewDecoder(r.Body).Decode(&cardNew); err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\n\treturn cardNew, BuildResponse(r)\n}\n\nfunc (c *Client) GetCard(cardID string) (*model.Card, *Response) {\n\tr, err := c.DoAPIGet(c.GetCardRoute(cardID), \"\")\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\n\tdefer closeBody(r)\n\n\tvar card *model.Card\n\tif err := json.NewDecoder(r.Body).Decode(&card); err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\n\treturn card, BuildResponse(r)\n}\n\n//\n// Boards and blocks.\n//\n\nfunc (c *Client) CreateBoardsAndBlocks(bab *model.BoardsAndBlocks) (*model.BoardsAndBlocks, *Response) {\n\tr, err := c.DoAPIPost(c.GetBoardsAndBlocksRoute(), toJSON(bab))\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\treturn model.BoardsAndBlocksFromJSON(r.Body), BuildResponse(r)\n}\n\nfunc (c *Client) CreateCategory(category model.Category) (*model.Category, *Response) {\n\tr, err := c.DoAPIPost(c.GetTeamRoute(category.TeamID)+\"/categories\", toJSON(category))\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\treturn model.CategoryFromJSON(r.Body), BuildResponse(r)\n}\n\nfunc (c *Client) DeleteCategory(teamID, categoryID string) *Response {\n\tr, err := c.DoAPIDelete(c.GetTeamRoute(teamID)+\"/categories/\"+categoryID, \"\")\n\tif err != nil {\n\t\treturn BuildErrorResponse(r, err)\n\t}\n\n\tdefer closeBody(r)\n\treturn BuildResponse(r)\n}\n\nfunc (c *Client) UpdateCategoryBoard(teamID, categoryID, boardID string) *Response {\n\tr, err := c.DoAPIPost(fmt.Sprintf(\"%s/categories/%s/boards/%s\", c.GetTeamRoute(teamID), categoryID, boardID), \"\")\n\tif err != nil {\n\t\treturn BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\treturn BuildResponse(r)\n}\n\nfunc (c *Client) GetUserCategoryBoards(teamID string) ([]model.CategoryBoards, *Response) {\n\tr, err := c.DoAPIGet(c.GetTeamRoute(teamID)+\"/categories\", \"\")\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\tvar categoryBoards []model.CategoryBoards\n\t_ = json.NewDecoder(r.Body).Decode(&categoryBoards)\n\treturn categoryBoards, BuildResponse(r)\n}\n\nfunc (c *Client) ReorderCategories(teamID string, newOrder []string) ([]string, *Response) {\n\tr, err := c.DoAPIPut(c.GetTeamRoute(teamID)+\"/categories/reorder\", toJSON(newOrder))\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\tvar updatedCategoryOrder []string\n\t_ = json.NewDecoder(r.Body).Decode(&updatedCategoryOrder)\n\treturn updatedCategoryOrder, BuildResponse(r)\n}\n\nfunc (c *Client) ReorderCategoryBoards(teamID, categoryID string, newOrder []string) ([]string, *Response) {\n\tr, err := c.DoAPIPut(c.GetTeamRoute(teamID)+\"/categories/\"+categoryID+\"/reorder\", toJSON(newOrder))\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\tvar updatedBoardsOrder []string\n\t_ = json.NewDecoder(r.Body).Decode(&updatedBoardsOrder)\n\treturn updatedBoardsOrder, BuildResponse(r)\n}\n\nfunc (c *Client) PatchBoardsAndBlocks(pbab *model.PatchBoardsAndBlocks) (*model.BoardsAndBlocks, *Response) {\n\tr, err := c.DoAPIPatch(c.GetBoardsAndBlocksRoute(), toJSON(pbab))\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\treturn model.BoardsAndBlocksFromJSON(r.Body), BuildResponse(r)\n}\n\nfunc (c *Client) DeleteBoardsAndBlocks(dbab *model.DeleteBoardsAndBlocks) (bool, *Response) {\n\tr, err := c.DoAPIDelete(c.GetBoardsAndBlocksRoute(), toJSON(dbab))\n\tif err != nil {\n\t\treturn false, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\treturn true, BuildResponse(r)\n}\n\n// Sharing\n\nfunc (c *Client) GetSharingRoute(boardID string) string {\n\treturn fmt.Sprintf(\"%s/sharing\", c.GetBoardRoute(boardID))\n}\n\nfunc (c *Client) GetSharing(boardID string) (*model.Sharing, *Response) {\n\tr, err := c.DoAPIGet(c.GetSharingRoute(boardID), \"\")\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\tsharing := model.SharingFromJSON(r.Body)\n\treturn &sharing, BuildResponse(r)\n}\n\nfunc (c *Client) PostSharing(sharing *model.Sharing) (bool, *Response) {\n\tr, err := c.DoAPIPost(c.GetSharingRoute(sharing.ID), toJSON(sharing))\n\tif err != nil {\n\t\treturn false, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\treturn true, BuildResponse(r)\n}\n\nfunc (c *Client) GetRegisterRoute() string {\n\treturn \"/register\"\n}\n\nfunc (c *Client) Register(request *model.RegisterRequest) (bool, *Response) {\n\tr, err := c.DoAPIPost(c.GetRegisterRoute(), toJSON(&request))\n\tif err != nil {\n\t\treturn false, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\treturn true, BuildResponse(r)\n}\n\nfunc (c *Client) GetLoginRoute() string {\n\treturn \"/login\"\n}\n\nfunc (c *Client) Login(request *model.LoginRequest) (*model.LoginResponse, *Response) {\n\tr, err := c.DoAPIPost(c.GetLoginRoute(), toJSON(&request))\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\tdata, err := model.LoginResponseFromJSON(r.Body)\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\n\tif data.Token != \"\" {\n\t\tc.Token = data.Token\n\t}\n\n\treturn data, BuildResponse(r)\n}\n\nfunc (c *Client) GetMeRoute() string {\n\treturn \"/users/me\"\n}\n\nfunc (c *Client) GetMe() (*model.User, *Response) {\n\tr, err := c.DoAPIGet(c.GetMeRoute(), \"\")\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\tme, err := model.UserFromJSON(r.Body)\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\treturn me, BuildResponse(r)\n}\n\nfunc (c *Client) GetUserID() string {\n\tme, _ := c.GetMe()\n\tif me == nil {\n\t\treturn \"\"\n\t}\n\treturn me.ID\n}\n\nfunc (c *Client) GetUserRoute(id string) string {\n\treturn fmt.Sprintf(\"/users/%s\", id)\n}\n\nfunc (c *Client) GetUser(id string) (*model.User, *Response) {\n\tr, err := c.DoAPIGet(c.GetUserRoute(id), \"\")\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\tuser, err := model.UserFromJSON(r.Body)\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\treturn user, BuildResponse(r)\n}\n\nfunc (c *Client) GetUserList(ids []string) ([]model.User, *Response) {\n\tr, err := c.DoAPIPost(\"/users\", toJSON(ids))\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\trequestBody, err := io.ReadAll(r.Body)\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\n\tvar users []model.User\n\terr = json.Unmarshal(requestBody, &users)\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\treturn users, BuildResponse(r)\n}\n\nfunc (c *Client) GetUserChangePasswordRoute(id string) string {\n\treturn fmt.Sprintf(\"/users/%s/changepassword\", id)\n}\n\nfunc (c *Client) UserChangePassword(id string, data *model.ChangePasswordRequest) (bool, *Response) {\n\tr, err := c.DoAPIPost(c.GetUserChangePasswordRoute(id), toJSON(&data))\n\tif err != nil {\n\t\treturn false, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\treturn true, BuildResponse(r)\n}\n\nfunc (c *Client) CreateBoard(board *model.Board) (*model.Board, *Response) {\n\tr, err := c.DoAPIPost(c.GetBoardsRoute(), toJSON(board))\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\treturn model.BoardFromJSON(r.Body), BuildResponse(r)\n}\n\nfunc (c *Client) PatchBoard(boardID string, patch *model.BoardPatch) (*model.Board, *Response) {\n\tr, err := c.DoAPIPatch(c.GetBoardRoute(boardID), toJSON(patch))\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\treturn model.BoardFromJSON(r.Body), BuildResponse(r)\n}\n\nfunc (c *Client) DeleteBoard(boardID string) (bool, *Response) {\n\tr, err := c.DoAPIDelete(c.GetBoardRoute(boardID), \"\")\n\tif err != nil {\n\t\treturn false, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\treturn true, BuildResponse(r)\n}\n\nfunc (c *Client) UndeleteBoard(boardID string) (bool, *Response) {\n\tr, err := c.DoAPIPost(c.GetBoardRoute(boardID)+\"/undelete\", \"\")\n\tif err != nil {\n\t\treturn false, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\treturn true, BuildResponse(r)\n}\n\nfunc (c *Client) GetBoard(boardID, readToken string) (*model.Board, *Response) {\n\turl := c.GetBoardRoute(boardID)\n\tif readToken != \"\" {\n\t\turl += fmt.Sprintf(\"?read_token=%s\", readToken)\n\t}\n\n\tr, err := c.DoAPIGet(url, \"\")\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\treturn model.BoardFromJSON(r.Body), BuildResponse(r)\n}\n\nfunc (c *Client) GetBoardMetadata(boardID, readToken string) (*model.BoardMetadata, *Response) {\n\turl := c.GetBoardMetadataRoute(boardID)\n\tif readToken != \"\" {\n\t\turl += fmt.Sprintf(\"?read_token=%s\", readToken)\n\t}\n\n\tr, err := c.DoAPIGet(url, \"\")\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\treturn model.BoardMetadataFromJSON(r.Body), BuildResponse(r)\n}\n\nfunc (c *Client) GetBoardsForTeam(teamID string) ([]*model.Board, *Response) {\n\tr, err := c.DoAPIGet(c.GetTeamRoute(teamID)+\"/boards\", \"\")\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\treturn model.BoardsFromJSON(r.Body), BuildResponse(r)\n}\n\nfunc (c *Client) SearchBoardsForUser(teamID, term string, field model.BoardSearchField) ([]*model.Board, *Response) {\n\tquery := fmt.Sprintf(\"q=%s&field=%s\", term, field)\n\tr, err := c.DoAPIGet(c.GetTeamRoute(teamID)+\"/boards/search?\"+query, \"\")\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\treturn model.BoardsFromJSON(r.Body), BuildResponse(r)\n}\n\nfunc (c *Client) SearchBoardsForTeam(teamID, term string) ([]*model.Board, *Response) {\n\tr, err := c.DoAPIGet(c.GetTeamRoute(teamID)+\"/boards/search?q=\"+term, \"\")\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\treturn model.BoardsFromJSON(r.Body), BuildResponse(r)\n}\n\nfunc (c *Client) GetMembersForBoard(boardID string) ([]*model.BoardMember, *Response) {\n\tr, err := c.DoAPIGet(c.GetBoardRoute(boardID)+\"/members\", \"\")\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\treturn model.BoardMembersFromJSON(r.Body), BuildResponse(r)\n}\n\nfunc (c *Client) AddMemberToBoard(member *model.BoardMember) (*model.BoardMember, *Response) {\n\tr, err := c.DoAPIPost(c.GetBoardRoute(member.BoardID)+\"/members\", toJSON(member))\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\treturn model.BoardMemberFromJSON(r.Body), BuildResponse(r)\n}\n\nfunc (c *Client) JoinBoard(boardID string) (*model.BoardMember, *Response) {\n\tr, err := c.DoAPIPost(c.GetJoinBoardRoute(boardID), \"\")\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\treturn model.BoardMemberFromJSON(r.Body), BuildResponse(r)\n}\n\nfunc (c *Client) LeaveBoard(boardID string) (*model.BoardMember, *Response) {\n\tr, err := c.DoAPIPost(c.GetLeaveBoardRoute(boardID), \"\")\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\treturn model.BoardMemberFromJSON(r.Body), BuildResponse(r)\n}\n\nfunc (c *Client) UpdateBoardMember(member *model.BoardMember) (*model.BoardMember, *Response) {\n\tr, err := c.DoAPIPut(c.GetBoardRoute(member.BoardID)+\"/members/\"+member.UserID, toJSON(member))\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\treturn model.BoardMemberFromJSON(r.Body), BuildResponse(r)\n}\n\nfunc (c *Client) DeleteBoardMember(member *model.BoardMember) (bool, *Response) {\n\tr, err := c.DoAPIDelete(c.GetBoardRoute(member.BoardID)+\"/members/\"+member.UserID, \"\")\n\tif err != nil {\n\t\treturn false, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\treturn true, BuildResponse(r)\n}\n\nfunc (c *Client) GetTeamUploadFileRoute(teamID, boardID string) string {\n\treturn fmt.Sprintf(\"%s/%s/files\", c.GetTeamRoute(teamID), boardID)\n}\n\nfunc (c *Client) TeamUploadFile(teamID, boardID string, data io.Reader) (*api.FileUploadResponse, *Response) {\n\tbody := &bytes.Buffer{}\n\twriter := multipart.NewWriter(body)\n\tpart, err := writer.CreateFormFile(api.UploadFormFileKey, \"file\")\n\tif err != nil {\n\t\treturn nil, &Response{Error: err}\n\t}\n\tif _, err = io.Copy(part, data); err != nil {\n\t\treturn nil, &Response{Error: err}\n\t}\n\twriter.Close()\n\n\topt := func(r *http.Request) {\n\t\tr.Header.Add(\"Content-Type\", writer.FormDataContentType())\n\t}\n\n\tr, err := c.doAPIRequestReader(http.MethodPost, c.APIURL+c.GetTeamUploadFileRoute(teamID, boardID), body, \"\", opt)\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\tfileUploadResponse, err := api.FileUploadResponseFromJSON(r.Body)\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\n\treturn fileUploadResponse, BuildResponse(r)\n}\n\nfunc (c *Client) TeamUploadFileInfo(teamID, boardID string, fileName string) (*mmModel.FileInfo, *Response) {\n\tr, err := c.DoAPIGet(fmt.Sprintf(\"/files/teams/%s/%s/%s/info\", teamID, boardID, fileName), \"\")\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\tfileInfoResponse, error := api.FileInfoResponseFromJSON(r.Body)\n\tif error != nil {\n\t\treturn nil, BuildErrorResponse(r, error)\n\t}\n\treturn fileInfoResponse, BuildResponse(r)\n}\n\nfunc (c *Client) GetSubscriptionsRoute() string {\n\treturn \"/subscriptions\"\n}\n\nfunc (c *Client) CreateSubscription(sub *model.Subscription) (*model.Subscription, *Response) {\n\tr, err := c.DoAPIPost(c.GetSubscriptionsRoute(), toJSON(&sub))\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\tsubNew, err := model.SubscriptionFromJSON(r.Body)\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\treturn subNew, BuildResponse(r)\n}\n\nfunc (c *Client) DeleteSubscription(blockID string, subscriberID string) *Response {\n\turl := fmt.Sprintf(\"%s/%s/%s\", c.GetSubscriptionsRoute(), blockID, subscriberID)\n\n\tr, err := c.DoAPIDelete(url, \"\")\n\tif err != nil {\n\t\treturn BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\treturn BuildResponse(r)\n}\n\nfunc (c *Client) GetSubscriptions(subscriberID string) ([]*model.Subscription, *Response) {\n\turl := fmt.Sprintf(\"%s/%s\", c.GetSubscriptionsRoute(), subscriberID)\n\n\tr, err := c.DoAPIGet(url, \"\")\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\tvar subs []*model.Subscription\n\terr = json.NewDecoder(r.Body).Decode(&subs)\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\n\treturn subs, BuildResponse(r)\n}\n\nfunc (c *Client) GetTemplatesForTeam(teamID string) ([]*model.Board, *Response) {\n\tr, err := c.DoAPIGet(c.GetTeamRoute(teamID)+\"/templates\", \"\")\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\treturn model.BoardsFromJSON(r.Body), BuildResponse(r)\n}\n\nfunc (c *Client) ExportBoardArchive(boardID string) ([]byte, *Response) {\n\tr, err := c.DoAPIGet(c.GetBoardRoute(boardID)+\"/archive/export\", \"\")\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\tbuf, err := io.ReadAll(r.Body)\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\treturn buf, BuildResponse(r)\n}\n\nfunc (c *Client) ImportArchive(teamID string, data io.Reader) *Response {\n\tbody := &bytes.Buffer{}\n\twriter := multipart.NewWriter(body)\n\tpart, err := writer.CreateFormFile(api.UploadFormFileKey, \"file\")\n\tif err != nil {\n\t\treturn &Response{Error: err}\n\t}\n\tif _, err = io.Copy(part, data); err != nil {\n\t\treturn &Response{Error: err}\n\t}\n\twriter.Close()\n\n\topt := func(r *http.Request) {\n\t\tr.Header.Add(\"Content-Type\", writer.FormDataContentType())\n\t}\n\n\tr, err := c.doAPIRequestReader(http.MethodPost, c.APIURL+c.GetTeamRoute(teamID)+\"/archive/import\", body, \"\", opt)\n\tif err != nil {\n\t\treturn BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\treturn BuildResponse(r)\n}\n\nfunc (c *Client) MoveContentBlock(srcBlockID string, dstBlockID string, where string, userID string) (bool, *Response) {\n\tr, err := c.DoAPIPost(\"/content-blocks/\"+srcBlockID+\"/moveto/\"+where+\"/\"+dstBlockID, \"\")\n\tif err != nil {\n\t\treturn false, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\treturn true, BuildResponse(r)\n}\n\nfunc (c *Client) GetBoardsForCompliance(teamID string, page, perPage int) (*model.BoardsComplianceResponse, *Response) {\n\tquery := fmt.Sprintf(\"?team_id=%s&page=%d&per_page=%d\", teamID, page, perPage)\n\tr, err := c.DoAPIGet(\"/admin/boards\"+query, \"\")\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\tvar res *model.BoardsComplianceResponse\n\terr = json.NewDecoder(r.Body).Decode(&res)\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\n\treturn res, BuildResponse(r)\n}\n\nfunc (c *Client) GetBoardsComplianceHistory(\n\tmodifiedSince int64, includeDeleted bool, teamID string, page, perPage int) (*model.BoardsComplianceHistoryResponse, *Response) {\n\tquery := fmt.Sprintf(\"?modified_since=%d&include_deleted=%t&team_id=%s&page=%d&per_page=%d\",\n\t\tmodifiedSince, includeDeleted, teamID, page, perPage)\n\tr, err := c.DoAPIGet(\"/admin/boards_history\"+query, \"\")\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\tvar res *model.BoardsComplianceHistoryResponse\n\terr = json.NewDecoder(r.Body).Decode(&res)\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\n\treturn res, BuildResponse(r)\n}\n\nfunc (c *Client) GetBlocksComplianceHistory(\n\tmodifiedSince int64, includeDeleted bool, teamID, boardID string, page, perPage int) (*model.BlocksComplianceHistoryResponse, *Response) {\n\tquery := fmt.Sprintf(\"?modified_since=%d&include_deleted=%t&team_id=%s&board_id=%s&page=%d&per_page=%d\",\n\t\tmodifiedSince, includeDeleted, teamID, boardID, page, perPage)\n\tr, err := c.DoAPIGet(\"/admin/blocks_history\"+query, \"\")\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\tdefer closeBody(r)\n\n\tvar res *model.BlocksComplianceHistoryResponse\n\terr = json.NewDecoder(r.Body).Decode(&res)\n\tif err != nil {\n\t\treturn nil, BuildErrorResponse(r, err)\n\t}\n\n\treturn res, BuildResponse(r)\n}\n\nfunc (c *Client) HideBoard(teamID, categoryID, boardID string) *Response {\n\tr, err := c.DoAPIPut(c.GetTeamRoute(teamID)+\"/categories/\"+categoryID+\"/boards/\"+boardID+\"/hide\", \"\")\n\tif err != nil {\n\t\treturn BuildErrorResponse(r, err)\n\t}\n\n\tdefer closeBody(r)\n\treturn BuildResponse(r)\n}\n\nfunc (c *Client) UnhideBoard(teamID, categoryID, boardID string) *Response {\n\tr, err := c.DoAPIPut(c.GetTeamRoute(teamID)+\"/categories/\"+categoryID+\"/boards/\"+boardID+\"/unhide\", \"\")\n\tif err != nil {\n\t\treturn BuildErrorResponse(r, err)\n\t}\n\n\tdefer closeBody(r)\n\treturn BuildResponse(r)\n}\n"
  },
  {
    "path": "server/go.mod",
    "content": "module github.com/mattermost/focalboard/server\n\ngo 1.21\n\ntoolchain go1.21.8\n\nrequire (\n\tgithub.com/Masterminds/squirrel v1.5.4\n\tgithub.com/golang/mock v1.6.0\n\tgithub.com/gorilla/mux v1.8.1\n\tgithub.com/gorilla/websocket v1.5.1\n\tgithub.com/krolaw/zipstream v0.0.0-20180621105154-0a2661891f94\n\tgithub.com/lib/pq v1.10.9\n\tgithub.com/mattermost/logr/v2 v2.0.21\n\tgithub.com/mattermost/mattermost/server/public v0.1.3\n\tgithub.com/mattermost/mattermost/server/v8 v8.0.0-20240529104128-9d30a62c9471\n\tgithub.com/mattermost/morph v1.1.0\n\tgithub.com/mattn/go-sqlite3 v2.0.3+incompatible\n\tgithub.com/mgdelacroix/foundation v0.0.0-20230510073833-0660207768ef\n\tgithub.com/oklog/run v1.1.0\n\tgithub.com/pkg/errors v0.9.1\n\tgithub.com/prometheus/client_golang v1.19.1\n\tgithub.com/rivo/uniseg v0.4.7\n\tgithub.com/rudderlabs/analytics-go v3.3.3+incompatible\n\tgithub.com/sergi/go-diff v1.3.1\n\tgithub.com/spf13/viper v1.18.2\n\tgithub.com/stretchr/testify v1.9.0\n\tgithub.com/wiggin77/merror v1.0.5\n\tgolang.org/x/crypto v0.23.0\n)\n\nrequire (\n\tfilippo.io/edwards25519 v1.1.0 // indirect\n\tgithub.com/beorn7/perks v1.0.1 // indirect\n\tgithub.com/blang/semver/v4 v4.0.0 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.3.0 // indirect\n\tgithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect\n\tgithub.com/dustin/go-humanize v1.0.1 // indirect\n\tgithub.com/dyatlov/go-opengraph/opengraph v0.0.0-20220524092352-606d7b1e5f8a // indirect\n\tgithub.com/fatih/color v1.17.0 // indirect\n\tgithub.com/francoispqt/gojay v1.2.13 // indirect\n\tgithub.com/fsnotify/fsnotify v1.7.0 // indirect\n\tgithub.com/go-asn1-ber/asn1-ber v1.5.7 // indirect\n\tgithub.com/go-sql-driver/mysql v1.8.1 // indirect\n\tgithub.com/goccy/go-json v0.10.2 // indirect\n\tgithub.com/golang/protobuf v1.5.4 // indirect\n\tgithub.com/google/uuid v1.6.0 // indirect\n\tgithub.com/hashicorp/errwrap v1.1.0 // indirect\n\tgithub.com/hashicorp/go-hclog v1.6.3 // indirect\n\tgithub.com/hashicorp/go-multierror v1.1.1 // indirect\n\tgithub.com/hashicorp/go-plugin v1.6.1 // indirect\n\tgithub.com/hashicorp/golang-lru/v2 v2.0.7 // indirect\n\tgithub.com/hashicorp/hcl v1.0.0 // indirect\n\tgithub.com/hashicorp/yamux v0.1.1 // indirect\n\tgithub.com/jmoiron/sqlx v1.4.0 // indirect\n\tgithub.com/klauspost/compress v1.17.8 // indirect\n\tgithub.com/klauspost/cpuid/v2 v2.2.7 // indirect\n\tgithub.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect\n\tgithub.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect\n\tgithub.com/magiconair/properties v1.8.7 // indirect\n\tgithub.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404 // indirect\n\tgithub.com/mattermost/ldap v0.0.0-20231116144001-0f480c025956 // indirect\n\tgithub.com/mattn/go-colorable v0.1.13 // indirect\n\tgithub.com/mattn/go-isatty v0.0.20 // indirect\n\tgithub.com/minio/md5-simd v1.1.2 // indirect\n\tgithub.com/minio/minio-go/v7 v7.0.70 // indirect\n\tgithub.com/mitchellh/go-testing-interface v1.14.1 // indirect\n\tgithub.com/mitchellh/mapstructure v1.5.0 // indirect\n\tgithub.com/ncruces/go-strftime v0.1.9 // indirect\n\tgithub.com/pborman/uuid v1.2.1 // indirect\n\tgithub.com/pelletier/go-toml v1.9.5 // indirect\n\tgithub.com/pelletier/go-toml/v2 v2.2.2 // indirect\n\tgithub.com/philhofer/fwd v1.1.2 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect\n\tgithub.com/prometheus/client_model v0.6.1 // indirect\n\tgithub.com/prometheus/common v0.53.0 // indirect\n\tgithub.com/prometheus/procfs v0.15.0 // indirect\n\tgithub.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect\n\tgithub.com/rs/xid v1.5.0 // indirect\n\tgithub.com/sagikazarmark/locafero v0.4.0 // indirect\n\tgithub.com/sagikazarmark/slog-shim v0.1.0 // indirect\n\tgithub.com/segmentio/backo-go v1.1.0 // indirect\n\tgithub.com/sirupsen/logrus v1.9.3 // indirect\n\tgithub.com/sourcegraph/conc v0.3.0 // indirect\n\tgithub.com/spf13/afero v1.11.0 // indirect\n\tgithub.com/spf13/cast v1.6.0 // indirect\n\tgithub.com/spf13/pflag v1.0.5 // indirect\n\tgithub.com/stretchr/objx v0.5.2 // indirect\n\tgithub.com/subosito/gotenv v1.6.0 // indirect\n\tgithub.com/tidwall/gjson v1.17.1 // indirect\n\tgithub.com/tidwall/match v1.1.1 // indirect\n\tgithub.com/tidwall/pretty v1.2.1 // indirect\n\tgithub.com/tinylib/msgp v1.1.9 // indirect\n\tgithub.com/vmihailenco/msgpack/v5 v5.4.1 // indirect\n\tgithub.com/vmihailenco/tagparser/v2 v2.0.0 // indirect\n\tgithub.com/wiggin77/srslog v1.0.1 // indirect\n\tgithub.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect\n\tgithub.com/yuin/goldmark v1.7.1 // indirect\n\tgo.uber.org/multierr v1.11.0 // indirect\n\tgolang.org/x/exp v0.0.0-20240529005216-23cca8864a10 // indirect\n\tgolang.org/x/net v0.25.0 // indirect\n\tgolang.org/x/sys v0.20.0 // indirect\n\tgolang.org/x/text v0.15.0 // indirect\n\tgoogle.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect\n\tgoogle.golang.org/grpc v1.64.0 // indirect\n\tgoogle.golang.org/protobuf v1.34.1 // indirect\n\tgopkg.in/ini.v1 v1.67.0 // indirect\n\tgopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect\n\tgopkg.in/yaml.v2 v2.4.0 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n\tmodernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b // indirect\n\tmodernc.org/libc v1.50.9 // indirect\n\tmodernc.org/mathutil v1.6.0 // indirect\n\tmodernc.org/memory v1.8.0 // indirect\n\tmodernc.org/sqlite v1.29.10 // indirect\n\tmodernc.org/strutil v1.2.0 // indirect\n\tmodernc.org/token v1.1.0 // indirect\n)\n"
  },
  {
    "path": "server/go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=\ndmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=\ndmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=\ndmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=\ndmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=\nfilippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=\nfilippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=\ngit.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM=\ngithub.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=\ngithub.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=\ngithub.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=\ngithub.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=\ngithub.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=\ngithub.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=\ngithub.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=\ngithub.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=\ngithub.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA=\ngithub.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8=\ngithub.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=\ngithub.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=\ngithub.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=\ngithub.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=\ngithub.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=\ngithub.com/dyatlov/go-opengraph/opengraph v0.0.0-20220524092352-606d7b1e5f8a h1:etIrTD8BQqzColk9nKRusM9um5+1q0iOEJLqfBMIK64=\ngithub.com/dyatlov/go-opengraph/opengraph v0.0.0-20220524092352-606d7b1e5f8a/go.mod h1:emQhSYTXqB0xxjLITTw4EaWZ+8IIQYw+kx9GqNUKdLg=\ngithub.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=\ngithub.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=\ngithub.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=\ngithub.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=\ngithub.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=\ngithub.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=\ngithub.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=\ngithub.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=\ngithub.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=\ngithub.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=\ngithub.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=\ngithub.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=\ngithub.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=\ngithub.com/go-asn1-ber/asn1-ber v1.5.7 h1:DTX+lbVTWaTw1hQ+PbZPlnDZPEIs0SS/GCZAl535dDk=\ngithub.com/go-asn1-ber/asn1-ber v1.5.7/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=\ngithub.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=\ngithub.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=\ngithub.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=\ngithub.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=\ngithub.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=\ngithub.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=\ngithub.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=\ngithub.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=\ngithub.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=\ngithub.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=\ngithub.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=\ngithub.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=\ngithub.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=\ngithub.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=\ngithub.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=\ngithub.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=\ngithub.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=\ngithub.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=\ngithub.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=\ngithub.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=\ngithub.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=\ngithub.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=\ngithub.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=\ngithub.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=\ngithub.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=\ngithub.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=\ngithub.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=\ngithub.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=\ngithub.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=\ngithub.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=\ngithub.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=\ngithub.com/hashicorp/go-plugin v1.6.1 h1:P7MR2UP6gNKGPp+y7EZw2kOiq4IR9WiqLvp0XOsVdwI=\ngithub.com/hashicorp/go-plugin v1.6.1/go.mod h1:XPHFku2tFo3o3QKFgSYo+cghcUhw1NA1hZyMK0PWAw0=\ngithub.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=\ngithub.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=\ngithub.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=\ngithub.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=\ngithub.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=\ngithub.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=\ngithub.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=\ngithub.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c=\ngithub.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo=\ngithub.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=\ngithub.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=\ngithub.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=\ngithub.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=\ngithub.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=\ngithub.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=\ngithub.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=\ngithub.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=\ngithub.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=\ngithub.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/krolaw/zipstream v0.0.0-20180621105154-0a2661891f94 h1:+AIlO01SKT9sfWU5CLWi0cfHc7dQwgGz3FhFRzXLoMg=\ngithub.com/krolaw/zipstream v0.0.0-20180621105154-0a2661891f94/go.mod h1:TcE3PIIkVWbP/HjhRAafgCjRKvDOi086iqp9VkNX/ng=\ngithub.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw=\ngithub.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o=\ngithub.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk=\ngithub.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw=\ngithub.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=\ngithub.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=\ngithub.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=\ngithub.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=\ngithub.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=\ngithub.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=\ngithub.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404 h1:Khvh6waxG1cHc4Cz5ef9n3XVCxRWpAKUtqg9PJl5+y8=\ngithub.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404/go.mod h1:RyS7FDNQlzF1PsjbJWHRI35exqaKGSO9qD4iv8QjE34=\ngithub.com/mattermost/ldap v0.0.0-20231116144001-0f480c025956 h1:Y1Tu/swM31pVwwb2BTCsOdamENjjWCI6qmfHLbk6OZI=\ngithub.com/mattermost/ldap v0.0.0-20231116144001-0f480c025956/go.mod h1:SRl30Lb7/QoYyohYeVBuqYvvmXSZJxZgiV3Zf6VbxjI=\ngithub.com/mattermost/logr/v2 v2.0.21 h1:CMHsP+nrbRlEC4g7BwOk1GAnMtHkniFhlSQPXy52be4=\ngithub.com/mattermost/logr/v2 v2.0.21/go.mod h1:kZkB/zqKL9e+RY5gB3vGpsyenC+TpuiOenjMkvJJbzc=\ngithub.com/mattermost/mattermost/server/public v0.1.3 h1:A3hQ3rNCwHfKAVxe7Hk3Zd9p2pyUKRmxtRPnkWP5SFM=\ngithub.com/mattermost/mattermost/server/public v0.1.3/go.mod h1:PDPb/iqzJJ5ZvK/m70oDF55AXN/cOvVFj96Yu4e6j+Q=\ngithub.com/mattermost/mattermost/server/v8 v8.0.0-20240529104128-9d30a62c9471 h1:LxlvPGImhPoZ16qJtZHfooqfIG2UGsbcIRNiTqQ/5Is=\ngithub.com/mattermost/mattermost/server/v8 v8.0.0-20240529104128-9d30a62c9471/go.mod h1:qQjPPGKiugHw6Tunlmq3cVDkKFFbgtMxIvyNJoN+p3Y=\ngithub.com/mattermost/morph v1.1.0 h1:Q9vrJbeM3s2jfweGheq12EFIzdNp9a/6IovcbvOQ6Cw=\ngithub.com/mattermost/morph v1.1.0/go.mod h1:gD+EaqX2UMyyuzmF4PFh4r33XneQ8Nzi+0E8nXjMa3A=\ngithub.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=\ngithub.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=\ngithub.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=\ngithub.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=\ngithub.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=\ngithub.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=\ngithub.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=\ngithub.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=\ngithub.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\ngithub.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=\ngithub.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=\ngithub.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=\ngithub.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=\ngithub.com/mgdelacroix/foundation v0.0.0-20230510073833-0660207768ef h1:xSk08nuyfWQY5tpJO3qC3eKo8yDyjkIL0hIEMHTYOLI=\ngithub.com/mgdelacroix/foundation v0.0.0-20230510073833-0660207768ef/go.mod h1:ZwobEfNHde7sU2pGybCWEnSlQ2r+MGrHGOKLphHZ42g=\ngithub.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=\ngithub.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=\ngithub.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=\ngithub.com/minio/minio-go/v7 v7.0.70 h1:1u9NtMgfK1U42kUxcsl5v0yj6TEOPR497OAQxpJnn2g=\ngithub.com/minio/minio-go/v7 v7.0.70/go.mod h1:4yBA8v80xGA30cfM3fz0DKYMXunWl/AV/6tWEs9ryzo=\ngithub.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU=\ngithub.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8=\ngithub.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=\ngithub.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=\ngithub.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=\ngithub.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=\ngithub.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=\ngithub.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA=\ngithub.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU=\ngithub.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=\ngithub.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw=\ngithub.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=\ngithub.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=\ngithub.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=\ngithub.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=\ngithub.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=\ngithub.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=\ngithub.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw=\ngithub.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0=\ngithub.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=\ngithub.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=\ngithub.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=\ngithub.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=\ngithub.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=\ngithub.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=\ngithub.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=\ngithub.com/prometheus/common v0.53.0 h1:U2pL9w9nmJwJDa4qqLQ3ZaePJ6ZTwt7cMD3AG3+aLCE=\ngithub.com/prometheus/common v0.53.0/go.mod h1:BrxBKv3FWBIGXw89Mg1AeBq7FSyRzXWI3l3e7W3RN5U=\ngithub.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=\ngithub.com/prometheus/procfs v0.15.0 h1:A82kmvXJq2jTu5YUhSGNlYoxh85zLnKgPz4bMZgI5Ek=\ngithub.com/prometheus/procfs v0.15.0/go.mod h1:Y0RJ/Y5g5wJpkTisOtqwDSo4HwhGmLB4VQSw2sQJLHk=\ngithub.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=\ngithub.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=\ngithub.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=\ngithub.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=\ngithub.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=\ngithub.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=\ngithub.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=\ngithub.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=\ngithub.com/rudderlabs/analytics-go v3.3.3+incompatible h1:OG0XlKoXfr539e2t1dXtTB+Gr89uFW+OUNQBVhHIIBY=\ngithub.com/rudderlabs/analytics-go v3.3.3+incompatible/go.mod h1:LF8/ty9kUX4PTY3l5c97K3nZZaX5Hwsvt+NBaRL/f30=\ngithub.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=\ngithub.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=\ngithub.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=\ngithub.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=\ngithub.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=\ngithub.com/segmentio/backo-go v1.1.0 h1:cJIfHQUdmLsd8t9IXqf5J8SdrOMn9vMa7cIvOavHAhc=\ngithub.com/segmentio/backo-go v1.1.0/go.mod h1:ckenwdf+v/qbyhVdNPWHnqh2YdJBED1O9cidYyM5J18=\ngithub.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=\ngithub.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=\ngithub.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=\ngithub.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=\ngithub.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=\ngithub.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=\ngithub.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=\ngithub.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=\ngithub.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=\ngithub.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=\ngithub.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=\ngithub.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=\ngithub.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=\ngithub.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=\ngithub.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=\ngithub.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=\ngithub.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=\ngithub.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=\ngithub.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=\ngithub.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=\ngithub.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=\ngithub.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=\ngithub.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=\ngithub.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=\ngithub.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=\ngithub.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=\ngithub.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=\ngithub.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=\ngithub.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=\ngithub.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=\ngithub.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=\ngithub.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=\ngithub.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=\ngithub.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=\ngithub.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=\ngithub.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=\ngithub.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=\ngithub.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\ngithub.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=\ngithub.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=\ngithub.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=\ngithub.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=\ngithub.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\ngithub.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=\ngithub.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=\ngithub.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=\ngithub.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=\ngithub.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=\ngithub.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=\ngithub.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=\ngithub.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=\ngithub.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=\ngithub.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=\ngithub.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=\ngithub.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=\ngithub.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=\ngithub.com/tinylib/msgp v1.1.9 h1:SHf3yoO2sGA0veCJeCBYLHuttAVFHGm2RHgNodW7wQU=\ngithub.com/tinylib/msgp v1.1.9/go.mod h1:BCXGB54lDD8qUEPmiG0cQQUANC4IUQyB2ItS2UDlO/k=\ngithub.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=\ngithub.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=\ngithub.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=\ngithub.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=\ngithub.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=\ngithub.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=\ngithub.com/wiggin77/merror v1.0.5 h1:P+lzicsn4vPMycAf2mFf7Zk6G9eco5N+jB1qJ2XW3ME=\ngithub.com/wiggin77/merror v1.0.5/go.mod h1:H2ETSu7/bPE0Ymf4bEwdUoo73OOEkdClnoRisfw0Nm0=\ngithub.com/wiggin77/srslog v1.0.1 h1:gA2XjSMy3DrRdX9UqLuDtuVAAshb8bE1NhX1YK0Qe+8=\ngithub.com/wiggin77/srslog v1.0.1/go.mod h1:fehkyYDq1QfuYn60TDPu9YdY2bB85VUW2mvN1WynEls=\ngithub.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c h1:3lbZUMbMiGUW/LMkfsEABsc5zNT9+b1CvsJx47JzJ8g=\ngithub.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c/go.mod h1:UrdRz5enIKZ63MEE3IF9l2/ebyx59GyGgPi+tICQdmM=\ngithub.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=\ngithub.com/yuin/goldmark v1.7.1 h1:3bajkSilaCbjdKVsKdZjZCLBNPL9pYzrCakKaf4U49U=\ngithub.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=\ngo.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=\ngo.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=\ngo.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=\ngo4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=\ngolang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=\ngolang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=\ngolang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20240529005216-23cca8864a10 h1:vpzMC/iZhYFAjJzHU0Cfuq+w1vLLsF2vLkDrPjzKYck=\ngolang.org/x/exp v0.0.0-20240529005216-23cca8864a10/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=\ngolang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=\ngolang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=\ngolang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=\ngolang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=\ngolang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=\ngolang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=\ngolang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=\ngolang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=\ngolang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=\ngolang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=\ngolang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngoogle.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=\ngoogle.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=\ngoogle.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=\ngoogle.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 h1:Zy9XzmMEflZ/MAaA7vNcoebnRAld7FsPW1EeBB7V0m8=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=\ngoogle.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=\ngoogle.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=\ngoogle.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=\ngoogle.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=\ngoogle.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=\ngoogle.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=\ngopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=\ngopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=\ngopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=\ngopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=\ngopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=\ngopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngrpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=\nhonnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nmodernc.org/cc/v4 v4.21.2 h1:dycHFB/jDc3IyacKipCNSDrjIC0Lm1hyoWOZTRR20Lk=\nmodernc.org/cc/v4 v4.21.2/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=\nmodernc.org/ccgo/v4 v4.17.8 h1:yyWBf2ipA0Y9GGz/MmCmi3EFpKgeS7ICrAFes+suEbs=\nmodernc.org/ccgo/v4 v4.17.8/go.mod h1:buJnJ6Fn0tyAdP/dqePbrrvLyr6qslFfTbFrCuaYvtA=\nmodernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=\nmodernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=\nmodernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=\nmodernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=\nmodernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b h1:BnN1t+pb1cy61zbvSUV7SeI0PwosMhlAEi/vBY4qxp8=\nmodernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=\nmodernc.org/libc v1.50.9 h1:hIWf1uz55lorXQhfoEoezdUHjxzuO6ceshET/yWjSjk=\nmodernc.org/libc v1.50.9/go.mod h1:15P6ublJ9FJR8YQCGy8DeQ2Uwur7iW9Hserr/T3OFZE=\nmodernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=\nmodernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=\nmodernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=\nmodernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=\nmodernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=\nmodernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=\nmodernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=\nmodernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=\nmodernc.org/sqlite v1.29.10 h1:3u93dz83myFnMilBGCOLbr+HjklS6+5rJLx4q86RDAg=\nmodernc.org/sqlite v1.29.10/go.mod h1:ItX2a1OVGgNsFh6Dv60JQvGfJfTPHPVpV6DF59akYOA=\nmodernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=\nmodernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=\nmodernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=\nmodernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=\nsourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=\nsourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=\n"
  },
  {
    "path": "server/go.tools.mod",
    "content": "module github.com/mattermost/focalboard/server\n\ngo 1.19\n\nrequire github.com/golang/mock v1.6.0\n\nrequire (\n\tgithub.com/jteeuwen/go-bindata v3.0.7+incompatible // indirect\n\tgolang.org/x/mod v0.4.2 // indirect\n\tgolang.org/x/sys v0.0.0-20210510120138-977fb7262007 // indirect\n\tgolang.org/x/tools v0.1.1 // indirect\n\tgolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect\n)\n"
  },
  {
    "path": "server/go.tools.sum",
    "content": "github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=\ngithub.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=\ngithub.com/golang/mock v1.5.0 h1:jlYHihg//f7RRwuPfptm04yp4s7O6Kw8EZiVYIGcH0g=\ngithub.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=\ngithub.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=\ngithub.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=\ngithub.com/jteeuwen/go-bindata v3.0.7+incompatible h1:91Uy4d9SYVr1kyTJ15wJsog+esAZZl7JmEfTkwmhJts=\ngithub.com/jteeuwen/go-bindata v3.0.7+incompatible/go.mod h1:JVvhzYOiGBnFSYRyV00iY8q7/0PThjIYav1p9h5dmKs=\ngithub.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=\ngolang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=\ngolang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE=\ngolang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190425150028-36563e24a262 h1:qsl9y/CJx34tuA7QCPNp86JNJe4spst6Ff8MjvPUdPg=\ngolang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e h1:aZzprAO9/8oim3qStq3wc1Xuxx4QmAGriC4VU4ojemQ=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.1.1 h1:wGiQel/hW0NnEkJUk8lbzkX2gFJU6PFxf1v5OlCfuOs=\ngolang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\n"
  },
  {
    "path": "server/integrationtests/blocks_test.go",
    "content": "package integrationtests\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestGetBlocks(t *testing.T) {\n\tth := SetupTestHelperWithToken(t).Start()\n\tdefer th.TearDown()\n\n\tboard := th.CreateBoard(\"team-id\", model.BoardTypeOpen)\n\n\tinitialID1 := utils.NewID(utils.IDTypeBlock)\n\tinitialID2 := utils.NewID(utils.IDTypeBlock)\n\tnewBlocks := []*model.Block{\n\t\t{\n\t\t\tID:       initialID1,\n\t\t\tBoardID:  board.ID,\n\t\t\tCreateAt: 1,\n\t\t\tUpdateAt: 1,\n\t\t\tType:     model.TypeCard,\n\t\t},\n\t\t{\n\t\t\tID:       initialID2,\n\t\t\tBoardID:  board.ID,\n\t\t\tCreateAt: 1,\n\t\t\tUpdateAt: 1,\n\t\t\tType:     model.TypeCard,\n\t\t},\n\t}\n\tnewBlocks, resp := th.Client.InsertBlocks(board.ID, newBlocks, false)\n\trequire.NoError(t, resp.Error)\n\trequire.Len(t, newBlocks, 2)\n\tblockID1 := newBlocks[0].ID\n\tblockID2 := newBlocks[1].ID\n\n\tblocks, resp := th.Client.GetBlocksForBoard(board.ID)\n\trequire.NoError(t, resp.Error)\n\trequire.Len(t, blocks, 2)\n\n\tblockIDs := make([]string, len(blocks))\n\tfor i, b := range blocks {\n\t\tblockIDs[i] = b.ID\n\t}\n\trequire.Contains(t, blockIDs, blockID1)\n\trequire.Contains(t, blockIDs, blockID2)\n}\n\nfunc TestPostBlock(t *testing.T) {\n\tth := SetupTestHelperWithToken(t).Start()\n\tdefer th.TearDown()\n\n\tboard := th.CreateBoard(\"team-id\", model.BoardTypeOpen)\n\n\tvar blockID1 string\n\tvar blockID2 string\n\tvar blockID3 string\n\n\tt.Run(\"Create a single block\", func(t *testing.T) {\n\t\tinitialID1 := utils.NewID(utils.IDTypeBlock)\n\t\tblock := &model.Block{\n\t\t\tID:       initialID1,\n\t\t\tBoardID:  board.ID,\n\t\t\tCreateAt: 1,\n\t\t\tUpdateAt: 1,\n\t\t\tType:     model.TypeCard,\n\t\t\tTitle:    \"New title\",\n\t\t}\n\n\t\tnewBlocks, resp := th.Client.InsertBlocks(board.ID, []*model.Block{block}, false)\n\t\trequire.NoError(t, resp.Error)\n\t\trequire.Len(t, newBlocks, 1)\n\t\tblockID1 = newBlocks[0].ID\n\n\t\tblocks, resp := th.Client.GetBlocksForBoard(board.ID)\n\t\trequire.NoError(t, resp.Error)\n\t\trequire.Len(t, blocks, 1)\n\n\t\tblockIDs := make([]string, len(blocks))\n\t\tfor i, b := range blocks {\n\t\t\tblockIDs[i] = b.ID\n\t\t}\n\t\trequire.Contains(t, blockIDs, blockID1)\n\t})\n\n\tt.Run(\"Create a couple of blocks in the same call\", func(t *testing.T) {\n\t\tinitialID2 := utils.NewID(utils.IDTypeBlock)\n\t\tinitialID3 := utils.NewID(utils.IDTypeBlock)\n\t\tnewBlocks := []*model.Block{\n\t\t\t{\n\t\t\t\tID:       initialID2,\n\t\t\t\tBoardID:  board.ID,\n\t\t\t\tCreateAt: 1,\n\t\t\t\tUpdateAt: 1,\n\t\t\t\tType:     model.TypeCard,\n\t\t\t},\n\t\t\t{\n\t\t\t\tID:       initialID3,\n\t\t\t\tBoardID:  board.ID,\n\t\t\t\tCreateAt: 1,\n\t\t\t\tUpdateAt: 1,\n\t\t\t\tType:     model.TypeCard,\n\t\t\t},\n\t\t}\n\n\t\tnewBlocks, resp := th.Client.InsertBlocks(board.ID, newBlocks, false)\n\t\trequire.NoError(t, resp.Error)\n\t\trequire.Len(t, newBlocks, 2)\n\t\tblockID2 = newBlocks[0].ID\n\t\tblockID3 = newBlocks[1].ID\n\t\trequire.NotEqual(t, initialID2, blockID2)\n\t\trequire.NotEqual(t, initialID3, blockID3)\n\n\t\tblocks, resp := th.Client.GetBlocksForBoard(board.ID)\n\t\trequire.NoError(t, resp.Error)\n\t\trequire.Len(t, blocks, 3)\n\n\t\tblockIDs := make([]string, len(blocks))\n\t\tfor i, b := range blocks {\n\t\t\tblockIDs[i] = b.ID\n\t\t}\n\t\trequire.Contains(t, blockIDs, blockID1)\n\t\trequire.Contains(t, blockIDs, blockID2)\n\t\trequire.Contains(t, blockIDs, blockID3)\n\t})\n\n\tt.Run(\"Update a block should not be possible through the insert endpoint\", func(t *testing.T) {\n\t\tblock := &model.Block{\n\t\t\tID:       blockID1,\n\t\t\tBoardID:  board.ID,\n\t\t\tCreateAt: 1,\n\t\t\tUpdateAt: 20,\n\t\t\tType:     model.TypeCard,\n\t\t\tTitle:    \"Updated title\",\n\t\t}\n\n\t\tnewBlocks, resp := th.Client.InsertBlocks(board.ID, []*model.Block{block}, false)\n\t\trequire.NoError(t, resp.Error)\n\t\trequire.Len(t, newBlocks, 1)\n\t\tblockID4 := newBlocks[0].ID\n\t\trequire.NotEqual(t, blockID1, blockID4)\n\n\t\tblocks, resp := th.Client.GetBlocksForBoard(board.ID)\n\t\trequire.NoError(t, resp.Error)\n\t\trequire.Len(t, blocks, 4)\n\n\t\tvar block4 *model.Block\n\t\tfor _, b := range blocks {\n\t\t\tif b.ID == blockID4 {\n\t\t\t\tblock4 = b\n\t\t\t}\n\t\t}\n\t\trequire.NotNil(t, block4)\n\t\trequire.Equal(t, \"Updated title\", block4.Title)\n\t})\n}\n\nfunc TestPatchBlock(t *testing.T) {\n\tth := SetupTestHelperWithToken(t).Start()\n\tdefer th.TearDown()\n\n\tinitialID := utils.NewID(utils.IDTypeBlock)\n\n\tboard := th.CreateBoard(\"team-id\", model.BoardTypeOpen)\n\ttime.Sleep(10 * time.Millisecond)\n\n\tblock := &model.Block{\n\t\tID:       initialID,\n\t\tBoardID:  board.ID,\n\t\tCreateAt: 1,\n\t\tUpdateAt: 1,\n\t\tType:     model.TypeCard,\n\t\tTitle:    \"New title\",\n\t\tFields:   map[string]interface{}{\"test\": \"test value\", \"test2\": \"test value 2\"},\n\t}\n\n\tnewBlocks, resp := th.Client.InsertBlocks(board.ID, []*model.Block{block}, false)\n\tth.CheckOK(resp)\n\trequire.Len(t, newBlocks, 1)\n\tblockID := newBlocks[0].ID\n\n\tt.Run(\"Patch a block basic field\", func(t *testing.T) {\n\t\tnewTitle := \"Updated title\"\n\t\tblockPatch := &model.BlockPatch{\n\t\t\tTitle: &newTitle,\n\t\t}\n\n\t\t_, resp := th.Client.PatchBlock(board.ID, blockID, blockPatch, false)\n\t\trequire.NoError(t, resp.Error)\n\n\t\tblocks, resp := th.Client.GetBlocksForBoard(board.ID)\n\t\trequire.NoError(t, resp.Error)\n\t\trequire.Len(t, blocks, 1)\n\n\t\tvar updatedBlock *model.Block\n\t\tfor _, b := range blocks {\n\t\t\tif b.ID == blockID {\n\t\t\t\tupdatedBlock = b\n\t\t\t}\n\t\t}\n\t\trequire.NotNil(t, updatedBlock)\n\t\trequire.Equal(t, \"Updated title\", updatedBlock.Title)\n\t})\n\n\tt.Run(\"Patch a block custom fields\", func(t *testing.T) {\n\t\tblockPatch := &model.BlockPatch{\n\t\t\tUpdatedFields: map[string]interface{}{\n\t\t\t\t\"test\":  \"new test value\",\n\t\t\t\t\"test3\": \"new field\",\n\t\t\t},\n\t\t}\n\n\t\t_, resp := th.Client.PatchBlock(board.ID, blockID, blockPatch, false)\n\t\trequire.NoError(t, resp.Error)\n\n\t\tblocks, resp := th.Client.GetBlocksForBoard(board.ID)\n\t\trequire.NoError(t, resp.Error)\n\t\trequire.Len(t, blocks, 1)\n\n\t\tvar updatedBlock *model.Block\n\t\tfor _, b := range blocks {\n\t\t\tif b.ID == blockID {\n\t\t\t\tupdatedBlock = b\n\t\t\t}\n\t\t}\n\t\trequire.NotNil(t, updatedBlock)\n\t\trequire.Equal(t, \"new test value\", updatedBlock.Fields[\"test\"])\n\t\trequire.Equal(t, \"new field\", updatedBlock.Fields[\"test3\"])\n\t})\n\n\tt.Run(\"Patch a block to remove custom fields\", func(t *testing.T) {\n\t\tblockPatch := &model.BlockPatch{\n\t\t\tDeletedFields: []string{\"test\", \"test3\", \"test100\"},\n\t\t}\n\n\t\t_, resp := th.Client.PatchBlock(board.ID, blockID, blockPatch, false)\n\t\trequire.NoError(t, resp.Error)\n\n\t\tblocks, resp := th.Client.GetBlocksForBoard(board.ID)\n\t\trequire.NoError(t, resp.Error)\n\t\trequire.Len(t, blocks, 1)\n\n\t\tvar updatedBlock *model.Block\n\t\tfor _, b := range blocks {\n\t\t\tif b.ID == blockID {\n\t\t\t\tupdatedBlock = b\n\t\t\t}\n\t\t}\n\t\trequire.NotNil(t, updatedBlock)\n\t\trequire.Equal(t, nil, updatedBlock.Fields[\"test\"])\n\t\trequire.Equal(t, \"test value 2\", updatedBlock.Fields[\"test2\"])\n\t\trequire.Equal(t, nil, updatedBlock.Fields[\"test3\"])\n\t})\n}\n\nfunc TestDeleteBlock(t *testing.T) {\n\tth := SetupTestHelperWithToken(t).Start()\n\tdefer th.TearDown()\n\n\tboard := th.CreateBoard(\"team-id\", model.BoardTypeOpen)\n\ttime.Sleep(10 * time.Millisecond)\n\n\tvar blockID string\n\tt.Run(\"Create a block\", func(t *testing.T) {\n\t\tinitialID := utils.NewID(utils.IDTypeBlock)\n\t\tblock := &model.Block{\n\t\t\tID:       initialID,\n\t\t\tBoardID:  board.ID,\n\t\t\tCreateAt: 1,\n\t\t\tUpdateAt: 1,\n\t\t\tType:     model.TypeCard,\n\t\t\tTitle:    \"New title\",\n\t\t}\n\n\t\tnewBlocks, resp := th.Client.InsertBlocks(board.ID, []*model.Block{block}, false)\n\t\trequire.NoError(t, resp.Error)\n\t\trequire.Len(t, newBlocks, 1)\n\t\trequire.NotZero(t, newBlocks[0].ID)\n\t\trequire.NotEqual(t, initialID, newBlocks[0].ID)\n\t\tblockID = newBlocks[0].ID\n\n\t\tblocks, resp := th.Client.GetBlocksForBoard(board.ID)\n\t\trequire.NoError(t, resp.Error)\n\t\trequire.Len(t, blocks, 1)\n\n\t\tblockIDs := make([]string, len(blocks))\n\t\tfor i, b := range blocks {\n\t\t\tblockIDs[i] = b.ID\n\t\t}\n\t\trequire.Contains(t, blockIDs, blockID)\n\t})\n\n\tt.Run(\"Delete a block\", func(t *testing.T) {\n\t\t// this avoids triggering uniqueness constraint of\n\t\t// id,insert_at on block history\n\t\ttime.Sleep(10 * time.Millisecond)\n\n\t\t_, resp := th.Client.DeleteBlock(board.ID, blockID, false)\n\t\trequire.NoError(t, resp.Error)\n\n\t\tblocks, resp := th.Client.GetBlocksForBoard(board.ID)\n\t\trequire.NoError(t, resp.Error)\n\t\trequire.Empty(t, blocks)\n\t})\n}\n\nfunc TestUndeleteBlock(t *testing.T) {\n\tth := SetupTestHelper(t).InitBasic()\n\tdefer th.TearDown()\n\n\tboard := th.CreateBoard(\"team-id\", model.BoardTypeOpen)\n\n\tblocks, resp := th.Client.GetBlocksForBoard(board.ID)\n\trequire.NoError(t, resp.Error)\n\tinitialCount := len(blocks)\n\n\tvar blockID string\n\tt.Run(\"Create a block\", func(t *testing.T) {\n\t\tinitialID := utils.NewID(utils.IDTypeBoard)\n\t\tblock := &model.Block{\n\t\t\tID:       initialID,\n\t\t\tBoardID:  board.ID,\n\t\t\tCreateAt: 1,\n\t\t\tUpdateAt: 1,\n\t\t\tType:     model.TypeBoard,\n\t\t\tTitle:    \"New title\",\n\t\t}\n\n\t\tnewBlocks, resp := th.Client.InsertBlocks(board.ID, []*model.Block{block}, false)\n\t\trequire.NoError(t, resp.Error)\n\t\trequire.Len(t, newBlocks, 1)\n\t\trequire.NotZero(t, newBlocks[0].ID)\n\t\trequire.NotEqual(t, initialID, newBlocks[0].ID)\n\t\tblockID = newBlocks[0].ID\n\n\t\tblocks, resp := th.Client.GetBlocksForBoard(board.ID)\n\t\trequire.NoError(t, resp.Error)\n\t\trequire.Len(t, blocks, initialCount+1)\n\n\t\tblockIDs := make([]string, len(blocks))\n\t\tfor i, b := range blocks {\n\t\t\tblockIDs[i] = b.ID\n\t\t}\n\t\trequire.Contains(t, blockIDs, blockID)\n\t})\n\n\tt.Run(\"Delete a block\", func(t *testing.T) {\n\t\t// this avoids triggering uniqueness constraint of\n\t\t// id,insert_at on block history\n\t\ttime.Sleep(10 * time.Millisecond)\n\n\t\t_, resp := th.Client.DeleteBlock(board.ID, blockID, false)\n\t\trequire.NoError(t, resp.Error)\n\n\t\tblocks, resp := th.Client.GetBlocksForBoard(board.ID)\n\t\trequire.NoError(t, resp.Error)\n\t\trequire.Len(t, blocks, initialCount)\n\t})\n\n\tt.Run(\"Undelete a block\", func(t *testing.T) {\n\t\t// this avoids triggering uniqueness constraint of\n\t\t// id,insert_at on block history\n\t\ttime.Sleep(10 * time.Millisecond)\n\n\t\t_, resp := th.Client.UndeleteBlock(board.ID, blockID)\n\t\trequire.NoError(t, resp.Error)\n\n\t\tblocks, resp := th.Client.GetBlocksForBoard(board.ID)\n\t\trequire.NoError(t, resp.Error)\n\t\trequire.Len(t, blocks, initialCount+1)\n\t})\n\n\tt.Run(\"Try to undelete a block without permissions\", func(t *testing.T) {\n\t\t// this avoids triggering uniqueness constraint of\n\t\t// id,insert_at on block history\n\t\ttime.Sleep(10 * time.Millisecond)\n\n\t\t_, resp := th.Client.DeleteBlock(board.ID, blockID, false)\n\t\trequire.NoError(t, resp.Error)\n\n\t\t_, resp = th.Client2.UndeleteBlock(board.ID, blockID)\n\t\tth.CheckForbidden(resp)\n\n\t\tblocks, resp := th.Client.GetBlocksForBoard(board.ID)\n\t\trequire.NoError(t, resp.Error)\n\t\trequire.Len(t, blocks, initialCount)\n\t})\n}\n"
  },
  {
    "path": "server/integrationtests/board_test.go",
    "content": "package integrationtests\n\nimport (\n\t\"encoding/json\"\n\t\"sort\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/mattermost/focalboard/server/client\"\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestGetBoards(t *testing.T) {\n\tt.Run(\"a non authenticated client should be rejected\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\t\tth.Logout(th.Client)\n\n\t\tteamID := \"0\"\n\t\tnewBoard := &model.Board{\n\t\t\tTeamID: teamID,\n\t\t\tType:   model.BoardTypeOpen,\n\t\t}\n\n\t\tboard, err := th.Server.App().CreateBoard(newBoard, \"user-id\", false)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, board)\n\n\t\tboards, resp := th.Client.GetBoardsForTeam(teamID)\n\t\tth.CheckUnauthorized(resp)\n\t\trequire.Nil(t, boards)\n\t})\n\n\tt.Run(\"should only return the boards that the user is a member of\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tteamID := \"0\"\n\t\totherTeamID := \"other-team-id\"\n\t\tuser1 := th.GetUser1()\n\t\tuser2 := th.GetUser2()\n\n\t\tboard1 := &model.Board{\n\t\t\tTeamID: teamID,\n\t\t\tType:   model.BoardTypeOpen,\n\t\t\tTitle:  \"Board 1\",\n\t\t}\n\t\trBoard1, err := th.Server.App().CreateBoard(board1, user1.ID, true)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, rBoard1)\n\n\t\tboard2 := &model.Board{\n\t\t\tTeamID: teamID,\n\t\t\tType:   model.BoardTypeOpen,\n\t\t\tTitle:  \"Board 2\",\n\t\t}\n\t\trBoard2, err := th.Server.App().CreateBoard(board2, user2.ID, false)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, rBoard2)\n\n\t\tboard3 := &model.Board{\n\t\t\tTeamID: teamID,\n\t\t\tType:   model.BoardTypePrivate,\n\t\t\tTitle:  \"Board 3\",\n\t\t}\n\t\trBoard3, err := th.Server.App().CreateBoard(board3, user1.ID, true)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, rBoard3)\n\n\t\tboard4 := &model.Board{\n\t\t\tTeamID: teamID,\n\t\t\tType:   model.BoardTypePrivate,\n\t\t\tTitle:  \"Board 4\",\n\t\t}\n\t\trBoard4, err := th.Server.App().CreateBoard(board4, user1.ID, false)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, rBoard4)\n\n\t\tboard5 := &model.Board{\n\t\t\tTeamID: teamID,\n\t\t\tType:   model.BoardTypePrivate,\n\t\t\tTitle:  \"Board 5\",\n\t\t}\n\t\trBoard5, err := th.Server.App().CreateBoard(board5, user2.ID, true)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, rBoard5)\n\n\t\tboard6 := &model.Board{\n\t\t\tTeamID: otherTeamID,\n\t\t\tType:   model.BoardTypeOpen,\n\t\t}\n\t\trBoard6, err := th.Server.App().CreateBoard(board6, user1.ID, true)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, rBoard6)\n\n\t\tboards, resp := th.Client.GetBoardsForTeam(teamID)\n\t\tth.CheckOK(resp)\n\t\trequire.NotNil(t, boards)\n\t\trequire.ElementsMatch(t, []*model.Board{\n\t\t\trBoard1,\n\t\t\trBoard2,\n\t\t\trBoard3,\n\t\t}, boards)\n\n\t\tboardsFromOtherTeam, resp := th.Client.GetBoardsForTeam(otherTeamID)\n\t\tth.CheckOK(resp)\n\t\trequire.NotNil(t, boardsFromOtherTeam)\n\t\trequire.Len(t, boardsFromOtherTeam, 1)\n\t\trequire.Equal(t, rBoard6.ID, boardsFromOtherTeam[0].ID)\n\t})\n}\n\nfunc TestCreateBoard(t *testing.T) {\n\tt.Run(\"a non authenticated user should be rejected\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\t\tth.Logout(th.Client)\n\n\t\tnewBoard := &model.Board{\n\t\t\tTitle:  \"board title\",\n\t\t\tType:   model.BoardTypeOpen,\n\t\t\tTeamID: testTeamID,\n\t\t}\n\t\tboard, resp := th.Client.CreateBoard(newBoard)\n\t\tth.CheckUnauthorized(resp)\n\t\trequire.Nil(t, board)\n\t})\n\n\tt.Run(\"create public board\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tme := th.GetUser1()\n\n\t\ttitle := \"board title 1\"\n\t\tteamID := testTeamID\n\t\tnewBoard := &model.Board{\n\t\t\tTitle:  title,\n\t\t\tType:   model.BoardTypeOpen,\n\t\t\tTeamID: teamID,\n\t\t}\n\t\tboard, resp := th.Client.CreateBoard(newBoard)\n\t\tth.CheckOK(resp)\n\t\trequire.NoError(t, resp.Error)\n\t\trequire.NotNil(t, board)\n\t\trequire.NotNil(t, board.ID)\n\t\trequire.Equal(t, title, board.Title)\n\t\trequire.Equal(t, model.BoardTypeOpen, board.Type)\n\t\trequire.Equal(t, teamID, board.TeamID)\n\t\trequire.Equal(t, me.ID, board.CreatedBy)\n\t\trequire.Equal(t, me.ID, board.ModifiedBy)\n\n\t\tt.Run(\"creating a board should make the creator an admin\", func(t *testing.T) {\n\t\t\tmembers, err := th.Server.App().GetMembersForBoard(board.ID)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, members, 1)\n\t\t\trequire.Equal(t, me.ID, members[0].UserID)\n\t\t\trequire.Equal(t, board.ID, members[0].BoardID)\n\t\t\trequire.True(t, members[0].SchemeAdmin)\n\t\t})\n\n\t\tt.Run(\"creator should be able to access the public board and its blocks\", func(t *testing.T) {\n\t\t\trbBoard, resp := th.Client.GetBoard(board.ID, \"\")\n\t\t\tth.CheckOK(resp)\n\t\t\trequire.NotNil(t, rbBoard)\n\t\t\trequire.Equal(t, board, rbBoard)\n\n\t\t\trBlocks, resp := th.Client.GetBlocksForBoard(board.ID)\n\t\t\tth.CheckOK(resp)\n\t\t\trequire.NotNil(t, rBlocks)\n\t\t})\n\n\t\tt.Run(\"A non-member user should be able to access the public board but not its blocks\", func(t *testing.T) {\n\t\t\trbBoard, resp := th.Client2.GetBoard(board.ID, \"\")\n\t\t\tth.CheckOK(resp)\n\t\t\trequire.NotNil(t, rbBoard)\n\t\t\trequire.Equal(t, board, rbBoard)\n\n\t\t\trBlocks, resp := th.Client2.GetBlocksForBoard(board.ID)\n\t\t\tth.CheckForbidden(resp)\n\t\t\trequire.Nil(t, rBlocks)\n\t\t})\n\t})\n\n\tt.Run(\"create private board\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tme := th.GetUser1()\n\n\t\ttitle := \"private board title\"\n\t\tteamID := testTeamID\n\t\tnewBoard := &model.Board{\n\t\t\tTitle:  title,\n\t\t\tType:   model.BoardTypePrivate,\n\t\t\tTeamID: teamID,\n\t\t}\n\t\tboard, resp := th.Client.CreateBoard(newBoard)\n\t\tth.CheckOK(resp)\n\t\trequire.NotNil(t, board)\n\t\trequire.NotNil(t, board.ID)\n\t\trequire.Equal(t, title, board.Title)\n\t\trequire.Equal(t, model.BoardTypePrivate, board.Type)\n\t\trequire.Equal(t, teamID, board.TeamID)\n\t\trequire.Equal(t, me.ID, board.CreatedBy)\n\t\trequire.Equal(t, me.ID, board.ModifiedBy)\n\n\t\tt.Run(\"creating a board should make the creator an admin\", func(t *testing.T) {\n\t\t\tmembers, err := th.Server.App().GetMembersForBoard(board.ID)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, members, 1)\n\t\t\trequire.Equal(t, me.ID, members[0].UserID)\n\t\t\trequire.Equal(t, board.ID, members[0].BoardID)\n\t\t\trequire.True(t, members[0].SchemeAdmin)\n\t\t})\n\n\t\tt.Run(\"creator should be able to access the private board and its blocks\", func(t *testing.T) {\n\t\t\trbBoard, resp := th.Client.GetBoard(board.ID, \"\")\n\t\t\tth.CheckOK(resp)\n\t\t\trequire.NotNil(t, rbBoard)\n\t\t\trequire.Equal(t, board, rbBoard)\n\n\t\t\trBlocks, resp := th.Client.GetBlocksForBoard(board.ID)\n\t\t\tth.CheckOK(resp)\n\t\t\trequire.NotNil(t, rBlocks)\n\t\t})\n\n\t\tt.Run(\"unauthorized user should not be able to access the private board or its blocks\", func(t *testing.T) {\n\t\t\trbBoard, resp := th.Client2.GetBoard(board.ID, \"\")\n\t\t\tth.CheckForbidden(resp)\n\t\t\trequire.Nil(t, rbBoard)\n\n\t\t\trBlocks, resp := th.Client2.GetBlocksForBoard(board.ID)\n\t\t\tth.CheckForbidden(resp)\n\t\t\trequire.Nil(t, rBlocks)\n\t\t})\n\t})\n\n\tt.Run(\"create invalid board\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\ttitle := \"invalid board title\"\n\t\tteamID := testTeamID\n\t\tuser1 := th.GetUser1()\n\n\t\tt.Run(\"invalid board type\", func(t *testing.T) {\n\t\t\tvar invalidBoardType model.BoardType = \"invalid\"\n\t\t\tnewBoard := &model.Board{\n\t\t\t\tTitle:  title,\n\t\t\t\tTeamID: testTeamID,\n\t\t\t\tType:   invalidBoardType,\n\t\t\t}\n\n\t\t\tboard, resp := th.Client.CreateBoard(newBoard)\n\t\t\tth.CheckBadRequest(resp)\n\t\t\trequire.Nil(t, board)\n\n\t\t\tboards, err := th.Server.App().GetBoardsForUserAndTeam(user1.ID, teamID, true)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Empty(t, boards)\n\t\t})\n\n\t\tt.Run(\"no type\", func(t *testing.T) {\n\t\t\tnewBoard := &model.Board{\n\t\t\t\tTitle:  title,\n\t\t\t\tTeamID: teamID,\n\t\t\t}\n\t\t\tboard, resp := th.Client.CreateBoard(newBoard)\n\t\t\tth.CheckBadRequest(resp)\n\t\t\trequire.Nil(t, board)\n\n\t\t\tboards, err := th.Server.App().GetBoardsForUserAndTeam(user1.ID, teamID, true)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Empty(t, boards)\n\t\t})\n\n\t\tt.Run(\"no team ID\", func(t *testing.T) {\n\t\t\tnewBoard := &model.Board{\n\t\t\t\tTitle: title,\n\t\t\t}\n\t\t\tboard, resp := th.Client.CreateBoard(newBoard)\n\t\t\t// the request is unauthorized because the permission\n\t\t\t// check fails on an empty teamID\n\t\t\tth.CheckForbidden(resp)\n\t\t\trequire.Nil(t, board)\n\n\t\t\tboards, err := th.Server.App().GetBoardsForUserAndTeam(user1.ID, teamID, true)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Empty(t, boards)\n\t\t})\n\t})\n}\n\nfunc TestCreateBoardTemplate(t *testing.T) {\n\tt.Run(\"create public board template\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tme := th.GetUser1()\n\n\t\ttitle := \"board template 1\"\n\t\tteamID := testTeamID\n\t\tnewBoard := &model.Board{\n\t\t\tTitle:      title,\n\t\t\tType:       model.BoardTypeOpen,\n\t\t\tTeamID:     teamID,\n\t\t\tIsTemplate: true,\n\t\t}\n\t\tboard, resp := th.Client.CreateBoard(newBoard)\n\t\tth.CheckOK(resp)\n\t\trequire.NoError(t, resp.Error)\n\t\trequire.NotNil(t, board)\n\t\trequire.NotNil(t, board.ID)\n\t\trequire.Equal(t, title, board.Title)\n\t\trequire.Equal(t, model.BoardTypeOpen, board.Type)\n\t\trequire.Equal(t, teamID, board.TeamID)\n\t\trequire.Equal(t, me.ID, board.CreatedBy)\n\t\trequire.Equal(t, me.ID, board.ModifiedBy)\n\n\t\tt.Run(\"creating a board template should make the creator an admin\", func(t *testing.T) {\n\t\t\tmembers, err := th.Server.App().GetMembersForBoard(board.ID)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, members, 1)\n\t\t\trequire.Equal(t, me.ID, members[0].UserID)\n\t\t\trequire.Equal(t, board.ID, members[0].BoardID)\n\t\t\trequire.True(t, members[0].SchemeAdmin)\n\t\t})\n\n\t\tt.Run(\"creator should be able to access the public board template and its blocks\", func(t *testing.T) {\n\t\t\trbBoard, resp := th.Client.GetBoard(board.ID, \"\")\n\t\t\tth.CheckOK(resp)\n\t\t\trequire.NotNil(t, rbBoard)\n\t\t\trequire.Equal(t, board, rbBoard)\n\n\t\t\trBlocks, resp := th.Client.GetBlocksForBoard(board.ID)\n\t\t\tth.CheckOK(resp)\n\t\t\trequire.NotNil(t, rBlocks)\n\t\t})\n\n\t\tt.Run(\"another user should be able to access the public board template and its blocks\", func(t *testing.T) {\n\t\t\trbBoard, resp := th.Client2.GetBoard(board.ID, \"\")\n\t\t\tth.CheckOK(resp)\n\t\t\trequire.NotNil(t, rbBoard)\n\t\t\trequire.Equal(t, board, rbBoard)\n\n\t\t\trBlocks, resp := th.Client2.GetBlocksForBoard(board.ID)\n\t\t\tth.CheckOK(resp)\n\t\t\trequire.NotNil(t, rBlocks)\n\t\t})\n\t})\n\n\tt.Run(\"create private board template\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tme := th.GetUser1()\n\n\t\ttitle := \"private board template title\"\n\t\tteamID := testTeamID\n\t\tnewBoard := &model.Board{\n\t\t\tTitle:      title,\n\t\t\tType:       model.BoardTypePrivate,\n\t\t\tTeamID:     teamID,\n\t\t\tIsTemplate: true,\n\t\t}\n\t\tboard, resp := th.Client.CreateBoard(newBoard)\n\t\tth.CheckOK(resp)\n\t\trequire.NotNil(t, board)\n\t\trequire.NotNil(t, board.ID)\n\t\trequire.Equal(t, title, board.Title)\n\t\trequire.Equal(t, model.BoardTypePrivate, board.Type)\n\t\trequire.Equal(t, teamID, board.TeamID)\n\t\trequire.Equal(t, me.ID, board.CreatedBy)\n\t\trequire.Equal(t, me.ID, board.ModifiedBy)\n\n\t\tt.Run(\"creating a board template should make the creator an admin\", func(t *testing.T) {\n\t\t\tmembers, err := th.Server.App().GetMembersForBoard(board.ID)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, members, 1)\n\t\t\trequire.Equal(t, me.ID, members[0].UserID)\n\t\t\trequire.Equal(t, board.ID, members[0].BoardID)\n\t\t\trequire.True(t, members[0].SchemeAdmin)\n\t\t})\n\n\t\tt.Run(\"creator should be able to access the private board template and its blocks\", func(t *testing.T) {\n\t\t\trbBoard, resp := th.Client.GetBoard(board.ID, \"\")\n\t\t\tth.CheckOK(resp)\n\t\t\trequire.NotNil(t, rbBoard)\n\t\t\trequire.Equal(t, board, rbBoard)\n\n\t\t\trBlocks, resp := th.Client.GetBlocksForBoard(board.ID)\n\t\t\tth.CheckOK(resp)\n\t\t\trequire.NotNil(t, rBlocks)\n\t\t})\n\n\t\tt.Run(\"unauthorized user should not be able to access the private board template or its blocks\", func(t *testing.T) {\n\t\t\trbBoard, resp := th.Client2.GetBoard(board.ID, \"\")\n\t\t\tth.CheckForbidden(resp)\n\t\t\trequire.Nil(t, rbBoard)\n\n\t\t\trBlocks, resp := th.Client2.GetBlocksForBoard(board.ID)\n\t\t\tth.CheckForbidden(resp)\n\t\t\trequire.Nil(t, rBlocks)\n\t\t})\n\t})\n}\n\nfunc TestGetAllBlocksForBoard(t *testing.T) {\n\tth := SetupTestHelperWithToken(t).Start()\n\tdefer th.TearDown()\n\n\tboard := th.CreateBoard(\"board-id\", model.BoardTypeOpen)\n\n\tparentBlockID := utils.NewID(utils.IDTypeBlock)\n\tchildBlockID1 := utils.NewID(utils.IDTypeBlock)\n\tchildBlockID2 := utils.NewID(utils.IDTypeBlock)\n\n\tt.Run(\"Create the block structure\", func(t *testing.T) {\n\t\tnewBlocks := []*model.Block{\n\t\t\t{\n\t\t\t\tID:       parentBlockID,\n\t\t\t\tBoardID:  board.ID,\n\t\t\t\tCreateAt: 1,\n\t\t\t\tUpdateAt: 1,\n\t\t\t\tType:     model.TypeCard,\n\t\t\t},\n\t\t\t{\n\t\t\t\tID:       childBlockID1,\n\t\t\t\tBoardID:  board.ID,\n\t\t\t\tParentID: parentBlockID,\n\t\t\t\tCreateAt: 2,\n\t\t\t\tUpdateAt: 2,\n\t\t\t\tType:     model.TypeCard,\n\t\t\t},\n\t\t\t{\n\t\t\t\tID:       childBlockID2,\n\t\t\t\tBoardID:  board.ID,\n\t\t\t\tParentID: parentBlockID,\n\t\t\t\tCreateAt: 2,\n\t\t\t\tUpdateAt: 2,\n\t\t\t\tType:     model.TypeCard,\n\t\t\t},\n\t\t}\n\n\t\tinsertedBlocks, resp := th.Client.InsertBlocks(board.ID, newBlocks, false)\n\t\trequire.NoError(t, resp.Error)\n\t\trequire.Len(t, insertedBlocks, len(newBlocks))\n\n\t\tinsertedBlockIDs := make([]string, len(insertedBlocks))\n\t\tfor i, b := range insertedBlocks {\n\t\t\tinsertedBlockIDs[i] = b.ID\n\t\t}\n\n\t\tfetchedBlocks, resp := th.Client.GetAllBlocksForBoard(board.ID)\n\t\trequire.NoError(t, resp.Error)\n\t\trequire.Len(t, fetchedBlocks, len(newBlocks))\n\n\t\tfetchedblockIDs := make([]string, len(fetchedBlocks))\n\t\tfor i, b := range fetchedBlocks {\n\t\t\tfetchedblockIDs[i] = b.ID\n\t\t}\n\n\t\tsort.Strings(insertedBlockIDs)\n\t\tsort.Strings(fetchedblockIDs)\n\n\t\trequire.Equal(t, insertedBlockIDs, fetchedblockIDs)\n\t})\n}\n\nfunc TestSearchBoards(t *testing.T) {\n\tt.Run(\"a non authenticated user should be rejected\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\t\tth.Logout(th.Client)\n\n\t\tboards, resp := th.Client.SearchBoardsForTeam(testTeamID, \"term\")\n\t\tth.CheckUnauthorized(resp)\n\t\trequire.Nil(t, boards)\n\t})\n\n\tt.Run(\"all the matching private boards that the user is a member of and all matching public boards should be returned\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tteamID := testTeamID\n\t\tuser1 := th.GetUser1()\n\n\t\tboard1 := &model.Board{\n\t\t\tTitle:  \"public board where user1 is admin\",\n\t\t\tType:   model.BoardTypeOpen,\n\t\t\tTeamID: teamID,\n\t\t}\n\t\trBoard1, err := th.Server.App().CreateBoard(board1, user1.ID, true)\n\t\trequire.NoError(t, err)\n\n\t\tboard2 := &model.Board{\n\t\t\tTitle:  \"public board where user1 is not member\",\n\t\t\tType:   model.BoardTypeOpen,\n\t\t\tTeamID: teamID,\n\t\t}\n\t\trBoard2, err := th.Server.App().CreateBoard(board2, user1.ID, false)\n\t\trequire.NoError(t, err)\n\n\t\tboard3 := &model.Board{\n\t\t\tTitle:  \"private board where user1 is admin\",\n\t\t\tType:   model.BoardTypePrivate,\n\t\t\tTeamID: teamID,\n\t\t}\n\t\trBoard3, err := th.Server.App().CreateBoard(board3, user1.ID, true)\n\t\trequire.NoError(t, err)\n\n\t\tboard4 := &model.Board{\n\t\t\tTitle:  \"private board where user1 is not member\",\n\t\t\tType:   model.BoardTypePrivate,\n\t\t\tTeamID: teamID,\n\t\t}\n\t\t_, err = th.Server.App().CreateBoard(board4, user1.ID, false)\n\t\trequire.NoError(t, err)\n\n\t\tboard5 := &model.Board{\n\t\t\tTitle:  \"private board where user1 is admin, but in other team\",\n\t\t\tType:   model.BoardTypePrivate,\n\t\t\tTeamID: \"other-team-id\",\n\t\t}\n\t\trBoard5, err := th.Server.App().CreateBoard(board5, user1.ID, true)\n\t\trequire.NoError(t, err)\n\n\t\ttestCases := []struct {\n\t\t\tName        string\n\t\t\tClient      *client.Client\n\t\t\tTerm        string\n\t\t\tExpectedIDs []string\n\t\t}{\n\t\t\t{\n\t\t\t\tName:        \"should return all boards where user1 is member or that are public\",\n\t\t\t\tClient:      th.Client,\n\t\t\t\tTerm:        \"board\",\n\t\t\t\tExpectedIDs: []string{rBoard1.ID, rBoard2.ID, rBoard3.ID, rBoard5.ID},\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:        \"matching a full word\",\n\t\t\t\tClient:      th.Client,\n\t\t\t\tTerm:        \"admin\",\n\t\t\t\tExpectedIDs: []string{rBoard1.ID, rBoard3.ID, rBoard5.ID},\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:        \"matching part of the word\",\n\t\t\t\tClient:      th.Client,\n\t\t\t\tTerm:        \"ubli\",\n\t\t\t\tExpectedIDs: []string{rBoard1.ID, rBoard2.ID},\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:        \"case insensitive\",\n\t\t\t\tClient:      th.Client,\n\t\t\t\tTerm:        \"UBLI\",\n\t\t\t\tExpectedIDs: []string{rBoard1.ID, rBoard2.ID},\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:        \"user2 can only see the public boards, as he's not a member of any\",\n\t\t\t\tClient:      th.Client2,\n\t\t\t\tTerm:        \"board\",\n\t\t\t\tExpectedIDs: []string{rBoard1.ID, rBoard2.ID},\n\t\t\t},\n\t\t}\n\n\t\tfor _, tc := range testCases {\n\t\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\t\tboards, resp := tc.Client.SearchBoardsForTeam(teamID, tc.Term)\n\t\t\t\tth.CheckOK(resp)\n\n\t\t\t\tboardIDs := []string{}\n\t\t\t\tfor _, board := range boards {\n\t\t\t\t\tboardIDs = append(boardIDs, board.ID)\n\t\t\t\t}\n\n\t\t\t\trequire.ElementsMatch(t, tc.ExpectedIDs, boardIDs)\n\t\t\t})\n\t\t}\n\t})\n}\n\nfunc TestGetBoard(t *testing.T) {\n\tt.Run(\"a non authenticated user should be rejected\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\t\tth.Logout(th.Client)\n\n\t\tboard, resp := th.Client.GetBoard(\"boar-id\", \"\")\n\t\tth.CheckUnauthorized(resp)\n\t\trequire.Nil(t, board)\n\t})\n\n\tt.Run(\"valid read token should be enough to get the board\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\t\tth.Server.Config().EnablePublicSharedBoards = true\n\n\t\tteamID := testTeamID\n\t\tsharingToken := utils.NewID(utils.IDTypeToken)\n\n\t\tboard := &model.Board{\n\t\t\tTitle:  \"public board where user1 is admin\",\n\t\t\tType:   model.BoardTypeOpen,\n\t\t\tTeamID: teamID,\n\t\t}\n\t\trBoard, err := th.Server.App().CreateBoard(board, th.GetUser1().ID, true)\n\t\trequire.NoError(t, err)\n\n\t\tsharing := &model.Sharing{\n\t\t\tID:       rBoard.ID,\n\t\t\tEnabled:  true,\n\t\t\tToken:    sharingToken,\n\t\t\tUpdateAt: 1,\n\t\t}\n\n\t\tsuccess, resp := th.Client.PostSharing(sharing)\n\t\tth.CheckOK(resp)\n\t\trequire.True(t, success)\n\n\t\t// the client logs out\n\t\tth.Logout(th.Client)\n\n\t\t// we make sure that the client cannot currently retrieve the\n\t\t// board with no session\n\t\tboard, resp = th.Client.GetBoard(rBoard.ID, \"\")\n\t\tth.CheckUnauthorized(resp)\n\t\trequire.Nil(t, board)\n\n\t\t// it should be able to retrieve it with the read token\n\t\tboard, resp = th.Client.GetBoard(rBoard.ID, sharingToken)\n\t\tth.CheckOK(resp)\n\t\trequire.NotNil(t, board)\n\t})\n\n\tt.Run(\"nonexisting board\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tboard, resp := th.Client.GetBoard(\"nonexistent board\", \"\")\n\t\tth.CheckNotFound(resp)\n\t\trequire.Nil(t, board)\n\t})\n\n\tt.Run(\"a user that doesn't have permissions to a private board cannot retrieve it\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tteamID := testTeamID\n\t\tnewBoard := &model.Board{\n\t\t\tType:   model.BoardTypePrivate,\n\t\t\tTeamID: teamID,\n\t\t}\n\t\tboard, err := th.Server.App().CreateBoard(newBoard, th.GetUser1().ID, false)\n\t\trequire.NoError(t, err)\n\n\t\trBoard, resp := th.Client.GetBoard(board.ID, \"\")\n\t\tth.CheckForbidden(resp)\n\t\trequire.Nil(t, rBoard)\n\t})\n\n\tt.Run(\"a user that has permissions to a private board can retrieve it\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tteamID := testTeamID\n\t\tnewBoard := &model.Board{\n\t\t\tType:   model.BoardTypePrivate,\n\t\t\tTeamID: teamID,\n\t\t}\n\t\tboard, err := th.Server.App().CreateBoard(newBoard, th.GetUser1().ID, true)\n\t\trequire.NoError(t, err)\n\n\t\trBoard, resp := th.Client.GetBoard(board.ID, \"\")\n\t\tth.CheckOK(resp)\n\t\trequire.NotNil(t, rBoard)\n\t})\n\n\tt.Run(\"a user that doesn't have permissions to a public board but have them to its team can retrieve it\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tteamID := testTeamID\n\t\tnewBoard := &model.Board{\n\t\t\tTitle:  \"title\",\n\t\t\tType:   model.BoardTypeOpen,\n\t\t\tTeamID: teamID,\n\t\t}\n\t\tboard, err := th.Server.App().CreateBoard(newBoard, th.GetUser1().ID, false)\n\t\trequire.NoError(t, err)\n\n\t\trBoard, resp := th.Client.GetBoard(board.ID, \"\")\n\t\tth.CheckOK(resp)\n\t\trequire.NotNil(t, rBoard)\n\t})\n}\n\nfunc TestGetBoardMetadata(t *testing.T) {\n\tt.Run(\"a non authenticated user should be rejected\", func(t *testing.T) {\n\t\tth := SetupTestHelperWithLicense(t, LicenseEnterprise).InitBasic()\n\t\tdefer th.TearDown()\n\t\tth.Logout(th.Client)\n\n\t\tboardMetadata, resp := th.Client.GetBoardMetadata(\"boar-id\", \"\")\n\t\tth.CheckUnauthorized(resp)\n\t\trequire.Nil(t, boardMetadata)\n\t})\n\n\tt.Run(\"getBoardMetadata query is correct\", func(t *testing.T) {\n\t\tth := SetupTestHelperWithLicense(t, LicenseEnterprise).InitBasic()\n\t\tdefer th.TearDown()\n\t\tth.Server.Config().EnablePublicSharedBoards = true\n\n\t\tteamID := testTeamID\n\n\t\tboard := &model.Board{\n\t\t\tTitle:  \"public board where user1 is admin\",\n\t\t\tType:   model.BoardTypeOpen,\n\t\t\tTeamID: teamID,\n\t\t}\n\t\trBoard, err := th.Server.App().CreateBoard(board, th.GetUser1().ID, true)\n\t\trequire.NoError(t, err)\n\n\t\t// Check metadata\n\t\tboardMetadata, resp := th.Client.GetBoardMetadata(rBoard.ID, \"\")\n\t\tth.CheckOK(resp)\n\t\trequire.NotNil(t, boardMetadata)\n\n\t\trequire.Equal(t, rBoard.CreatedBy, boardMetadata.CreatedBy)\n\t\trequire.Equal(t, rBoard.CreateAt, boardMetadata.DescendantFirstUpdateAt)\n\t\trequire.Equal(t, rBoard.UpdateAt, boardMetadata.DescendantLastUpdateAt)\n\t\trequire.Equal(t, rBoard.ModifiedBy, boardMetadata.LastModifiedBy)\n\n\t\t// Insert card1\n\t\tcard1 := &model.Block{\n\t\t\tID:      \"card1\",\n\t\t\tBoardID: rBoard.ID,\n\t\t\tTitle:   \"Card 1\",\n\t\t}\n\t\ttime.Sleep(20 * time.Millisecond)\n\t\trequire.NoError(t, th.Server.App().InsertBlock(card1, th.GetUser2().ID))\n\t\trCard1, err := th.Server.App().GetBlockByID(card1.ID)\n\t\trequire.NoError(t, err)\n\n\t\t// Check updated metadata\n\t\tboardMetadata, resp = th.Client.GetBoardMetadata(rBoard.ID, \"\")\n\t\tth.CheckOK(resp)\n\t\trequire.NotNil(t, boardMetadata)\n\n\t\trequire.Equal(t, rBoard.CreatedBy, boardMetadata.CreatedBy)\n\t\trequire.Equal(t, rBoard.CreateAt, boardMetadata.DescendantFirstUpdateAt)\n\t\trequire.Equal(t, rCard1.UpdateAt, boardMetadata.DescendantLastUpdateAt)\n\t\trequire.Equal(t, rCard1.ModifiedBy, boardMetadata.LastModifiedBy)\n\n\t\t// Insert card2\n\t\tcard2 := &model.Block{\n\t\t\tID:      \"card2\",\n\t\t\tBoardID: rBoard.ID,\n\t\t\tTitle:   \"Card 2\",\n\t\t}\n\t\ttime.Sleep(20 * time.Millisecond)\n\t\trequire.NoError(t, th.Server.App().InsertBlock(card2, th.GetUser1().ID))\n\t\trCard2, err := th.Server.App().GetBlockByID(card2.ID)\n\t\trequire.NoError(t, err)\n\n\t\t// Check updated metadata\n\t\tboardMetadata, resp = th.Client.GetBoardMetadata(rBoard.ID, \"\")\n\t\tth.CheckOK(resp)\n\t\trequire.NotNil(t, boardMetadata)\n\t\trequire.Equal(t, rBoard.CreatedBy, boardMetadata.CreatedBy)\n\t\trequire.Equal(t, rBoard.CreateAt, boardMetadata.DescendantFirstUpdateAt)\n\t\trequire.Equal(t, rCard2.UpdateAt, boardMetadata.DescendantLastUpdateAt)\n\t\trequire.Equal(t, rCard2.ModifiedBy, boardMetadata.LastModifiedBy)\n\n\t\tt.Run(\"After delete board\", func(t *testing.T) {\n\t\t\t// Delete board\n\t\t\ttime.Sleep(20 * time.Millisecond)\n\t\t\trequire.NoError(t, th.Server.App().DeleteBoard(rBoard.ID, th.GetUser1().ID))\n\n\t\t\t// Check updated metadata\n\t\t\tboardMetadata, resp = th.Client.GetBoardMetadata(rBoard.ID, \"\")\n\t\t\tth.CheckOK(resp)\n\t\t\trequire.NotNil(t, boardMetadata)\n\t\t\trequire.Equal(t, rBoard.CreatedBy, boardMetadata.CreatedBy)\n\t\t\trequire.Equal(t, rBoard.CreateAt, boardMetadata.DescendantFirstUpdateAt)\n\t\t\trequire.Greater(t, boardMetadata.DescendantLastUpdateAt, rCard2.UpdateAt)\n\t\t\trequire.Equal(t, th.GetUser1().ID, boardMetadata.LastModifiedBy)\n\t\t})\n\t})\n\n\tt.Run(\"getBoardMetadata should fail with no license\", func(t *testing.T) {\n\t\tth := SetupTestHelperWithLicense(t, LicenseNone).InitBasic()\n\t\tdefer th.TearDown()\n\t\tth.Server.Config().EnablePublicSharedBoards = true\n\n\t\tteamID := testTeamID\n\n\t\tboard := &model.Board{\n\t\t\tTitle:  \"public board where user1 is admin\",\n\t\t\tType:   model.BoardTypeOpen,\n\t\t\tTeamID: teamID,\n\t\t}\n\t\trBoard, err := th.Server.App().CreateBoard(board, th.GetUser1().ID, true)\n\t\trequire.NoError(t, err)\n\n\t\t// Check metadata\n\t\tboardMetadata, resp := th.Client.GetBoardMetadata(rBoard.ID, \"\")\n\t\tth.CheckNotImplemented(resp)\n\t\trequire.Nil(t, boardMetadata)\n\t})\n\n\tt.Run(\"getBoardMetadata should fail on Professional license\", func(t *testing.T) {\n\t\tth := SetupTestHelperWithLicense(t, LicenseProfessional).InitBasic()\n\t\tdefer th.TearDown()\n\t\tth.Server.Config().EnablePublicSharedBoards = true\n\n\t\tteamID := testTeamID\n\n\t\tboard := &model.Board{\n\t\t\tTitle:  \"public board where user1 is admin\",\n\t\t\tType:   model.BoardTypeOpen,\n\t\t\tTeamID: teamID,\n\t\t}\n\t\trBoard, err := th.Server.App().CreateBoard(board, th.GetUser1().ID, true)\n\t\trequire.NoError(t, err)\n\n\t\t// Check metadata\n\t\tboardMetadata, resp := th.Client.GetBoardMetadata(rBoard.ID, \"\")\n\t\tth.CheckNotImplemented(resp)\n\t\trequire.Nil(t, boardMetadata)\n\t})\n\n\tt.Run(\"valid read token should not get the board metadata\", func(t *testing.T) {\n\t\tth := SetupTestHelperWithLicense(t, LicenseEnterprise).InitBasic()\n\t\tdefer th.TearDown()\n\t\tth.Server.Config().EnablePublicSharedBoards = true\n\n\t\tteamID := testTeamID\n\t\tsharingToken := utils.NewID(utils.IDTypeToken)\n\t\tuserID := th.GetUser1().ID\n\n\t\tboard := &model.Board{\n\t\t\tTitle:  \"public board where user1 is admin\",\n\t\t\tType:   model.BoardTypeOpen,\n\t\t\tTeamID: teamID,\n\t\t}\n\t\trBoard, err := th.Server.App().CreateBoard(board, userID, true)\n\t\trequire.NoError(t, err)\n\n\t\tsharing := &model.Sharing{\n\t\t\tID:       rBoard.ID,\n\t\t\tEnabled:  true,\n\t\t\tToken:    sharingToken,\n\t\t\tUpdateAt: 1,\n\t\t}\n\n\t\tsuccess, resp := th.Client.PostSharing(sharing)\n\t\tth.CheckOK(resp)\n\t\trequire.True(t, success)\n\n\t\t// the client logs out\n\t\tth.Logout(th.Client)\n\n\t\t// we make sure that the client cannot currently retrieve the\n\t\t// board with no session\n\t\tboardMetadata, resp := th.Client.GetBoardMetadata(rBoard.ID, \"\")\n\t\tth.CheckUnauthorized(resp)\n\t\trequire.Nil(t, boardMetadata)\n\n\t\t// it should not be able to retrieve it with the read token either\n\t\tboardMetadata, resp = th.Client.GetBoardMetadata(rBoard.ID, sharingToken)\n\t\tth.CheckUnauthorized(resp)\n\t\trequire.Nil(t, boardMetadata)\n\t})\n}\n\nfunc TestPatchBoard(t *testing.T) {\n\tteamID := testTeamID\n\n\tt.Run(\"a non authenticated user should be rejected\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\t\tth.Logout(th.Client)\n\n\t\tinitialTitle := \"title 1\"\n\t\tnewBoard := &model.Board{\n\t\t\tTitle:  initialTitle,\n\t\t\tType:   model.BoardTypeOpen,\n\t\t\tTeamID: teamID,\n\t\t}\n\t\tboard, err := th.Server.App().CreateBoard(newBoard, \"user-id\", false)\n\t\trequire.NoError(t, err)\n\n\t\tnewTitle := \"a new title 1\"\n\t\tpatch := &model.BoardPatch{Title: &newTitle}\n\n\t\trBoard, resp := th.Client.PatchBoard(board.ID, patch)\n\t\tth.CheckUnauthorized(resp)\n\t\trequire.Nil(t, rBoard)\n\n\t\tdbBoard, err := th.Server.App().GetBoard(board.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, initialTitle, dbBoard.Title)\n\t})\n\n\tt.Run(\"non existing board\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tnewTitle := \"a new title 2\"\n\t\tpatch := &model.BoardPatch{Title: &newTitle}\n\n\t\tboard, resp := th.Client.PatchBoard(\"non-existing-board\", patch)\n\t\tth.CheckNotFound(resp)\n\t\trequire.Nil(t, board)\n\t})\n\n\tt.Run(\"invalid patch on a board with permissions\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tuser1 := th.GetUser1()\n\n\t\tnewBoard := &model.Board{\n\t\t\tTitle:  \"title\",\n\t\t\tType:   model.BoardTypeOpen,\n\t\t\tTeamID: teamID,\n\t\t}\n\t\tboard, err := th.Server.App().CreateBoard(newBoard, user1.ID, true)\n\t\trequire.NoError(t, err)\n\n\t\tvar invalidPatchType model.BoardType = \"invalid\"\n\t\tpatch := &model.BoardPatch{Type: &invalidPatchType}\n\n\t\trBoard, resp := th.Client.PatchBoard(board.ID, patch)\n\t\tth.CheckBadRequest(resp)\n\t\trequire.Nil(t, rBoard)\n\t})\n\n\tt.Run(\"valid patch on a board with permissions\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tuser1 := th.GetUser1()\n\n\t\tinitialTitle := \"title\"\n\t\tnewBoard := &model.Board{\n\t\t\tTitle:  initialTitle,\n\t\t\tType:   model.BoardTypeOpen,\n\t\t\tTeamID: teamID,\n\t\t}\n\t\tboard, err := th.Server.App().CreateBoard(newBoard, user1.ID, true)\n\t\trequire.NoError(t, err)\n\n\t\tnewTitle := \"a new title\"\n\t\tpatch := &model.BoardPatch{Title: &newTitle}\n\n\t\trBoard, resp := th.Client.PatchBoard(board.ID, patch)\n\t\tth.CheckOK(resp)\n\t\trequire.NotNil(t, rBoard)\n\t\trequire.Equal(t, newTitle, rBoard.Title)\n\t})\n\n\tt.Run(\"valid patch on a board without permissions\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tuser1 := th.GetUser1()\n\n\t\tinitialTitle := \"title\"\n\t\tnewBoard := &model.Board{\n\t\t\tTitle:  initialTitle,\n\t\t\tType:   model.BoardTypeOpen,\n\t\t\tTeamID: teamID,\n\t\t}\n\t\tboard, err := th.Server.App().CreateBoard(newBoard, user1.ID, false)\n\t\trequire.NoError(t, err)\n\n\t\tnewTitle := \"a new title\"\n\t\tpatch := &model.BoardPatch{Title: &newTitle}\n\n\t\trBoard, resp := th.Client.PatchBoard(board.ID, patch)\n\t\tth.CheckForbidden(resp)\n\t\trequire.Nil(t, rBoard)\n\n\t\tdbBoard, err := th.Server.App().GetBoard(board.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, initialTitle, dbBoard.Title)\n\t})\n}\n\nfunc TestDeleteBoard(t *testing.T) {\n\tteamID := testTeamID\n\n\tt.Run(\"a non authenticated user should be rejected\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\t\tth.Logout(th.Client)\n\n\t\tnewBoard := &model.Board{\n\t\t\tTitle:  \"title\",\n\t\t\tType:   model.BoardTypeOpen,\n\t\t\tTeamID: teamID,\n\t\t}\n\t\tboard, err := th.Server.App().CreateBoard(newBoard, \"user-id\", false)\n\t\trequire.NoError(t, err)\n\n\t\tsuccess, resp := th.Client.DeleteBoard(board.ID)\n\t\tth.CheckUnauthorized(resp)\n\t\trequire.False(t, success)\n\n\t\tdbBoard, err := th.Server.App().GetBoard(board.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, dbBoard)\n\t})\n\n\tt.Run(\"a user without permissions should be rejected\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tnewBoard := &model.Board{\n\t\t\tTitle:  \"title\",\n\t\t\tType:   model.BoardTypeOpen,\n\t\t\tTeamID: teamID,\n\t\t}\n\t\tboard, err := th.Server.App().CreateBoard(newBoard, \"some-user-id\", false)\n\t\trequire.NoError(t, err)\n\n\t\tsuccess, resp := th.Client.DeleteBoard(board.ID)\n\t\tth.CheckForbidden(resp)\n\t\trequire.False(t, success)\n\n\t\tdbBoard, err := th.Server.App().GetBoard(board.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, dbBoard)\n\t})\n\n\tt.Run(\"non existing board\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tsuccess, resp := th.Client.DeleteBoard(\"non-existing-board\")\n\t\tth.CheckNotFound(resp)\n\t\trequire.False(t, success)\n\t})\n\n\tt.Run(\"an existing board should be correctly deleted\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tnewBoard := &model.Board{\n\t\t\tTitle:  \"title\",\n\t\t\tType:   model.BoardTypeOpen,\n\t\t\tTeamID: teamID,\n\t\t}\n\t\tboard, err := th.Server.App().CreateBoard(newBoard, th.GetUser1().ID, true)\n\t\trequire.NoError(t, err)\n\n\t\tsuccess, resp := th.Client.DeleteBoard(board.ID)\n\t\tth.CheckOK(resp)\n\t\trequire.True(t, success)\n\n\t\tdbBoard, err := th.Server.App().GetBoard(board.ID)\n\t\trequire.Error(t, err)\n\t\trequire.True(t, model.IsErrNotFound(err))\n\t\trequire.Nil(t, dbBoard)\n\t})\n}\n\nfunc TestUndeleteBoard(t *testing.T) {\n\tteamID := testTeamID\n\n\tt.Run(\"a non authenticated user should be rejected\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\t\tth.Logout(th.Client)\n\n\t\tnewBoard := &model.Board{\n\t\t\tTitle:  \"title\",\n\t\t\tType:   model.BoardTypeOpen,\n\t\t\tTeamID: teamID,\n\t\t}\n\t\tboard, err := th.Server.App().CreateBoard(newBoard, \"user-id\", false)\n\t\trequire.NoError(t, err)\n\n\t\ttime.Sleep(1 * time.Millisecond)\n\t\terr = th.Server.App().DeleteBoard(newBoard.ID, \"user-id\")\n\t\trequire.NoError(t, err)\n\n\t\tsuccess, resp := th.Client.UndeleteBoard(board.ID)\n\t\tth.CheckUnauthorized(resp)\n\t\trequire.False(t, success)\n\n\t\tdbBoard, err := th.Server.App().GetBoard(board.ID)\n\t\trequire.Error(t, err)\n\t\trequire.True(t, model.IsErrNotFound(err))\n\t\trequire.Nil(t, dbBoard)\n\t})\n\n\tt.Run(\"a user without membership should be rejected\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tnewBoard := &model.Board{\n\t\t\tTitle:  \"title\",\n\t\t\tType:   model.BoardTypeOpen,\n\t\t\tTeamID: teamID,\n\t\t}\n\t\tboard, err := th.Server.App().CreateBoard(newBoard, \"some-user-id\", false)\n\t\trequire.NoError(t, err)\n\n\t\ttime.Sleep(1 * time.Millisecond)\n\t\terr = th.Server.App().DeleteBoard(newBoard.ID, \"some-user-id\")\n\t\trequire.NoError(t, err)\n\n\t\tsuccess, resp := th.Client.UndeleteBoard(board.ID)\n\t\tth.CheckForbidden(resp)\n\t\trequire.False(t, success)\n\n\t\tdbBoard, err := th.Server.App().GetBoard(board.ID)\n\t\trequire.Error(t, err)\n\t\trequire.True(t, model.IsErrNotFound(err))\n\t\trequire.Nil(t, dbBoard)\n\t})\n\n\tt.Run(\"a user with membership but without permissions should be rejected\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tnewBoard := &model.Board{\n\t\t\tTitle:  \"title\",\n\t\t\tType:   model.BoardTypeOpen,\n\t\t\tTeamID: teamID,\n\t\t}\n\t\tboard, err := th.Server.App().CreateBoard(newBoard, \"some-user-id\", false)\n\t\trequire.NoError(t, err)\n\n\t\tnewUser2Member := &model.BoardMember{\n\t\t\tUserID:       \"user-id\",\n\t\t\tBoardID:      board.ID,\n\t\t\tSchemeEditor: true,\n\t\t}\n\t\t_, err = th.Server.App().AddMemberToBoard(newUser2Member)\n\t\trequire.NoError(t, err)\n\n\t\ttime.Sleep(1 * time.Millisecond)\n\t\terr = th.Server.App().DeleteBoard(newBoard.ID, \"some-user-id\")\n\t\trequire.NoError(t, err)\n\n\t\tsuccess, resp := th.Client.UndeleteBoard(board.ID)\n\t\tth.CheckForbidden(resp)\n\t\trequire.False(t, success)\n\n\t\tdbBoard, err := th.Server.App().GetBoard(board.ID)\n\t\trequire.Error(t, err)\n\t\trequire.True(t, model.IsErrNotFound(err))\n\t\trequire.Nil(t, dbBoard)\n\t})\n\n\tt.Run(\"non existing board\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tsuccess, resp := th.Client.UndeleteBoard(\"non-existing-board\")\n\t\tth.CheckForbidden(resp)\n\t\trequire.False(t, success)\n\t})\n\n\tt.Run(\"an existing deleted board should be correctly undeleted\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tnewBoard := &model.Board{\n\t\t\tTitle:  \"title\",\n\t\t\tType:   model.BoardTypeOpen,\n\t\t\tTeamID: teamID,\n\t\t}\n\t\tboard, err := th.Server.App().CreateBoard(newBoard, th.GetUser1().ID, true)\n\t\trequire.NoError(t, err)\n\n\t\ttime.Sleep(1 * time.Millisecond)\n\t\terr = th.Server.App().DeleteBoard(newBoard.ID, \"user-id\")\n\t\trequire.NoError(t, err)\n\n\t\tsuccess, resp := th.Client.UndeleteBoard(board.ID)\n\t\tth.CheckOK(resp)\n\t\trequire.True(t, success)\n\n\t\tdbBoard, err := th.Server.App().GetBoard(board.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, dbBoard)\n\t})\n}\n\nfunc TestGetMembersForBoard(t *testing.T) {\n\tteamID := testTeamID\n\n\tcreateBoardWithUsers := func(th *TestHelper) *model.Board {\n\t\tuser1 := th.GetUser1()\n\n\t\tnewBoard := &model.Board{\n\t\t\tTitle:  \"title\",\n\t\t\tType:   model.BoardTypeOpen,\n\t\t\tTeamID: teamID,\n\t\t}\n\t\tboard, err := th.Server.App().CreateBoard(newBoard, user1.ID, true)\n\t\trequire.NoError(t, err)\n\n\t\tnewUser2Member := &model.BoardMember{\n\t\t\tUserID:       th.GetUser2().ID,\n\t\t\tBoardID:      board.ID,\n\t\t\tSchemeEditor: true,\n\t\t}\n\t\tuser2Member, err := th.Server.App().AddMemberToBoard(newUser2Member)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, user2Member)\n\n\t\treturn board\n\t}\n\n\tt.Run(\"a non authenticated user should be rejected\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\t\tboard := createBoardWithUsers(th)\n\t\tth.Logout(th.Client)\n\n\t\tmembers, resp := th.Client.GetMembersForBoard(board.ID)\n\t\tth.CheckUnauthorized(resp)\n\t\trequire.Empty(t, members)\n\t})\n\n\tt.Run(\"a user without permissions should be rejected\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\t\tboard := createBoardWithUsers(th)\n\n\t\t_ = th.Server.App().DeleteBoardMember(board.ID, th.GetUser2().ID)\n\n\t\tmembers, resp := th.Client2.GetMembersForBoard(board.ID)\n\t\tth.CheckForbidden(resp)\n\t\trequire.Empty(t, members)\n\t})\n\n\tt.Run(\"non existing board\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tmembers, resp := th.Client.GetMembersForBoard(\"non-existing-board\")\n\t\tth.CheckForbidden(resp)\n\t\trequire.Empty(t, members)\n\t})\n\n\tt.Run(\"should correctly return board members for a valid board\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\t\tboard := createBoardWithUsers(th)\n\n\t\tmembers, resp := th.Client.GetMembersForBoard(board.ID)\n\t\tth.CheckOK(resp)\n\t\trequire.Len(t, members, 2)\n\t})\n}\n\nfunc TestAddMember(t *testing.T) {\n\tteamID := testTeamID\n\n\tt.Run(\"a non authenticated user should be rejected\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\t\tth.Logout(th.Client)\n\n\t\tnewBoard := &model.Board{\n\t\t\tTitle:  \"title\",\n\t\t\tType:   model.BoardTypeOpen,\n\t\t\tTeamID: teamID,\n\t\t}\n\t\tboard, err := th.Server.App().CreateBoard(newBoard, \"user-id\", false)\n\t\trequire.NoError(t, err)\n\n\t\tnewMember := &model.BoardMember{\n\t\t\tUserID:       \"user1\",\n\t\t\tBoardID:      board.ID,\n\t\t\tSchemeEditor: true,\n\t\t}\n\n\t\tmember, resp := th.Client.AddMemberToBoard(newMember)\n\t\tth.CheckUnauthorized(resp)\n\t\trequire.Nil(t, member)\n\t})\n\n\tt.Run(\"a user without permissions should be rejected\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tnewBoard := &model.Board{\n\t\t\tTitle:  \"title\",\n\t\t\tType:   model.BoardTypePrivate,\n\t\t\tTeamID: teamID,\n\t\t}\n\t\tboard, err := th.Server.App().CreateBoard(newBoard, \"user-id\", false)\n\t\trequire.NoError(t, err)\n\n\t\tnewMember := &model.BoardMember{\n\t\t\tUserID:       \"user1\",\n\t\t\tBoardID:      board.ID,\n\t\t\tSchemeEditor: true,\n\t\t}\n\n\t\tmember, resp := th.Client.AddMemberToBoard(newMember)\n\t\tth.CheckForbidden(resp)\n\t\trequire.Nil(t, member)\n\t})\n\n\tt.Run(\"non existing board\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tnewMember := &model.BoardMember{\n\t\t\tUserID:       \"user1\",\n\t\t\tBoardID:      \"non-existing-board-id\",\n\t\t\tSchemeEditor: true,\n\t\t}\n\n\t\tmember, resp := th.Client.AddMemberToBoard(newMember)\n\t\tth.CheckNotFound(resp)\n\t\trequire.Nil(t, member)\n\t})\n\n\tt.Run(\"should correctly add a new member for a valid board\", func(t *testing.T) {\n\t\tt.Run(\"a private board through an admin user\", func(t *testing.T) {\n\t\t\tth := SetupTestHelper(t).InitBasic()\n\t\t\tdefer th.TearDown()\n\n\t\t\tnewBoard := &model.Board{\n\t\t\t\tTitle:  \"title\",\n\t\t\t\tType:   model.BoardTypePrivate,\n\t\t\t\tTeamID: teamID,\n\t\t\t}\n\t\t\tboard, err := th.Server.App().CreateBoard(newBoard, th.GetUser1().ID, true)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tnewMember := &model.BoardMember{\n\t\t\t\tUserID:       th.GetUser2().ID,\n\t\t\t\tBoardID:      board.ID,\n\t\t\t\tSchemeEditor: true,\n\t\t\t}\n\n\t\t\tmember, resp := th.Client.AddMemberToBoard(newMember)\n\t\t\tth.CheckOK(resp)\n\t\t\trequire.Equal(t, newMember.UserID, member.UserID)\n\t\t\trequire.Equal(t, newMember.BoardID, member.BoardID)\n\t\t\trequire.Equal(t, newMember.SchemeAdmin, member.SchemeAdmin)\n\t\t\trequire.Equal(t, newMember.SchemeEditor, member.SchemeEditor)\n\t\t\trequire.False(t, member.SchemeCommenter)\n\t\t\trequire.False(t, member.SchemeViewer)\n\t\t})\n\n\t\tt.Run(\"a public board through a user that is not yet a member\", func(t *testing.T) {\n\t\t\tth := SetupTestHelper(t).InitBasic()\n\t\t\tdefer th.TearDown()\n\n\t\t\tnewBoard := &model.Board{\n\t\t\t\tTitle:  \"title\",\n\t\t\t\tType:   model.BoardTypeOpen,\n\t\t\t\tTeamID: teamID,\n\t\t\t}\n\t\t\tboard, err := th.Server.App().CreateBoard(newBoard, th.GetUser1().ID, true)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tnewMember := &model.BoardMember{\n\t\t\t\tUserID:       th.GetUser2().ID,\n\t\t\t\tBoardID:      board.ID,\n\t\t\t\tSchemeEditor: true,\n\t\t\t}\n\n\t\t\tmember, resp := th.Client2.AddMemberToBoard(newMember)\n\t\t\tth.CheckForbidden(resp)\n\t\t\trequire.Nil(t, member)\n\n\t\t\tmembers, resp := th.Client2.GetMembersForBoard(board.ID)\n\t\t\tth.CheckForbidden(resp)\n\t\t\trequire.Nil(t, members)\n\n\t\t\t// Join board - will become an editor\n\t\t\tmember, resp = th.Client2.JoinBoard(board.ID)\n\t\t\tth.CheckOK(resp)\n\t\t\trequire.NoError(t, resp.Error)\n\t\t\trequire.NotNil(t, member)\n\t\t\trequire.Equal(t, board.ID, member.BoardID)\n\t\t\trequire.Equal(t, th.GetUser2().ID, member.UserID)\n\n\t\t\tmember, resp = th.Client2.AddMemberToBoard(newMember)\n\t\t\tth.CheckOK(resp)\n\t\t\trequire.NotNil(t, member)\n\n\t\t\tmembers, resp = th.Client2.GetMembersForBoard(board.ID)\n\t\t\tth.CheckOK(resp)\n\t\t\trequire.Len(t, members, 2)\n\t\t})\n\n\t\tt.Run(\"should always add a new member as given board role\", func(t *testing.T) {\n\t\t\tth := SetupTestHelper(t).InitBasic()\n\t\t\tdefer th.TearDown()\n\n\t\t\tnewBoard := &model.Board{\n\t\t\t\tTitle:  \"title\",\n\t\t\t\tType:   model.BoardTypePrivate,\n\t\t\t\tTeamID: teamID,\n\t\t\t}\n\t\t\tboard, err := th.Server.App().CreateBoard(newBoard, th.GetUser1().ID, true)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tnewMember := &model.BoardMember{\n\t\t\t\tUserID:          th.GetUser2().ID,\n\t\t\t\tBoardID:         board.ID,\n\t\t\t\tSchemeAdmin:     false,\n\t\t\t\tSchemeEditor:    false,\n\t\t\t\tSchemeCommenter: true,\n\t\t\t}\n\n\t\t\tmember, resp := th.Client.AddMemberToBoard(newMember)\n\t\t\tth.CheckOK(resp)\n\t\t\trequire.Equal(t, newMember.UserID, member.UserID)\n\t\t\trequire.Equal(t, newMember.BoardID, member.BoardID)\n\t\t\trequire.False(t, member.SchemeAdmin)\n\t\t\trequire.False(t, member.SchemeEditor)\n\t\t\trequire.True(t, member.SchemeCommenter)\n\t\t})\n\t})\n\n\tt.Run(\"should do nothing if the member already exists\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tnewBoard := &model.Board{\n\t\t\tTitle:  \"title\",\n\t\t\tType:   model.BoardTypePrivate,\n\t\t\tTeamID: teamID,\n\t\t}\n\t\tboard, err := th.Server.App().CreateBoard(newBoard, th.GetUser1().ID, true)\n\t\trequire.NoError(t, err)\n\n\t\tnewMember := &model.BoardMember{\n\t\t\tUserID:       th.GetUser1().ID,\n\t\t\tBoardID:      board.ID,\n\t\t\tSchemeAdmin:  false,\n\t\t\tSchemeEditor: true,\n\t\t}\n\n\t\tmembers, err := th.Server.App().GetMembersForBoard(board.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, members, 1)\n\t\trequire.True(t, members[0].SchemeAdmin)\n\t\trequire.True(t, members[0].SchemeEditor)\n\n\t\tmember, resp := th.Client.AddMemberToBoard(newMember)\n\t\tth.CheckOK(resp)\n\t\trequire.True(t, member.SchemeAdmin)\n\t\trequire.True(t, member.SchemeEditor)\n\n\t\tmembers, err = th.Server.App().GetMembersForBoard(board.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, members, 1)\n\t\trequire.True(t, members[0].SchemeAdmin)\n\t\trequire.True(t, members[0].SchemeEditor)\n\t})\n}\n\nfunc TestUpdateMember(t *testing.T) {\n\tteamID := testTeamID\n\n\tt.Run(\"a non authenticated user should be rejected\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tnewBoard := &model.Board{\n\t\t\tTitle:  \"title\",\n\t\t\tType:   model.BoardTypeOpen,\n\t\t\tTeamID: teamID,\n\t\t}\n\t\tboard, err := th.Server.App().CreateBoard(newBoard, th.GetUser1().ID, true)\n\t\trequire.NoError(t, err)\n\n\t\tupdatedMember := &model.BoardMember{\n\t\t\tUserID:       th.GetUser1().ID,\n\t\t\tBoardID:      board.ID,\n\t\t\tSchemeEditor: true,\n\t\t}\n\n\t\tth.Logout(th.Client)\n\t\tmember, resp := th.Client.UpdateBoardMember(updatedMember)\n\t\tth.CheckUnauthorized(resp)\n\t\trequire.Nil(t, member)\n\t})\n\n\tt.Run(\"a user without permissions should be rejected\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tnewBoard := &model.Board{\n\t\t\tTitle:  \"title\",\n\t\t\tType:   model.BoardTypeOpen,\n\t\t\tTeamID: teamID,\n\t\t}\n\t\tboard, err := th.Server.App().CreateBoard(newBoard, th.GetUser1().ID, true)\n\t\trequire.NoError(t, err)\n\n\t\tupdatedMember := &model.BoardMember{\n\t\t\tUserID:       th.GetUser1().ID,\n\t\t\tBoardID:      board.ID,\n\t\t\tSchemeEditor: true,\n\t\t}\n\n\t\tmember, resp := th.Client2.UpdateBoardMember(updatedMember)\n\t\tth.CheckForbidden(resp)\n\t\trequire.Nil(t, member)\n\t})\n\n\tt.Run(\"non existing board\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tupdatedMember := &model.BoardMember{\n\t\t\tUserID:       th.GetUser1().ID,\n\t\t\tBoardID:      \"non-existent-board-id\",\n\t\t\tSchemeEditor: true,\n\t\t}\n\n\t\tmember, resp := th.Client.UpdateBoardMember(updatedMember)\n\t\tth.CheckForbidden(resp)\n\t\trequire.Nil(t, member)\n\t})\n\n\tt.Run(\"should correctly update a member for a valid board\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tnewBoard := &model.Board{\n\t\t\tTitle:  \"title\",\n\t\t\tType:   model.BoardTypeOpen,\n\t\t\tTeamID: teamID,\n\t\t}\n\t\tboard, err := th.Server.App().CreateBoard(newBoard, th.GetUser1().ID, true)\n\t\trequire.NoError(t, err)\n\n\t\tnewUser2Member := &model.BoardMember{\n\t\t\tUserID:       th.GetUser2().ID,\n\t\t\tBoardID:      board.ID,\n\t\t\tSchemeEditor: true,\n\t\t}\n\t\tuser2Member, err := th.Server.App().AddMemberToBoard(newUser2Member)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, user2Member)\n\t\trequire.False(t, user2Member.SchemeAdmin)\n\t\trequire.True(t, user2Member.SchemeEditor)\n\n\t\tmemberUpdate := &model.BoardMember{\n\t\t\tUserID:       th.GetUser2().ID,\n\t\t\tBoardID:      board.ID,\n\t\t\tSchemeAdmin:  true,\n\t\t\tSchemeEditor: true,\n\t\t}\n\n\t\tupdatedUser2Member, resp := th.Client.UpdateBoardMember(memberUpdate)\n\t\tth.CheckOK(resp)\n\t\trequire.True(t, updatedUser2Member.SchemeAdmin)\n\t\trequire.True(t, updatedUser2Member.SchemeEditor)\n\t})\n\n\tt.Run(\"should not update a member if that means that a board will not have any admin\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tnewBoard := &model.Board{\n\t\t\tTitle:  \"title\",\n\t\t\tType:   model.BoardTypeOpen,\n\t\t\tTeamID: teamID,\n\t\t}\n\t\tboard, err := th.Server.App().CreateBoard(newBoard, th.GetUser1().ID, true)\n\t\trequire.NoError(t, err)\n\n\t\tmemberUpdate := &model.BoardMember{\n\t\t\tUserID:       th.GetUser1().ID,\n\t\t\tBoardID:      board.ID,\n\t\t\tSchemeEditor: true,\n\t\t}\n\n\t\tupdatedUser1Member, resp := th.Client.UpdateBoardMember(memberUpdate)\n\t\tth.CheckBadRequest(resp)\n\t\trequire.Nil(t, updatedUser1Member)\n\n\t\tmembers, err := th.Server.App().GetMembersForBoard(board.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, members, 1)\n\t\trequire.True(t, members[0].SchemeAdmin)\n\t})\n\n\tt.Run(\"should always disable the admin role on update member if the user is a guest\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\n\t\tnewBoard := &model.Board{\n\t\t\tTitle:  \"title\",\n\t\t\tType:   model.BoardTypeOpen,\n\t\t\tTeamID: teamID,\n\t\t}\n\t\tboard, err := th.Server.App().CreateBoard(newBoard, userAdmin, true)\n\t\trequire.NoError(t, err)\n\n\t\tnewGuestMember := &model.BoardMember{\n\t\t\tUserID:          userGuest,\n\t\t\tBoardID:         board.ID,\n\t\t\tSchemeViewer:    true,\n\t\t\tSchemeCommenter: true,\n\t\t\tSchemeEditor:    true,\n\t\t\tSchemeAdmin:     false,\n\t\t}\n\t\tguestMember, err := th.Server.App().AddMemberToBoard(newGuestMember)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, guestMember)\n\t\trequire.True(t, guestMember.SchemeViewer)\n\t\trequire.True(t, guestMember.SchemeCommenter)\n\t\trequire.True(t, guestMember.SchemeEditor)\n\t\trequire.False(t, guestMember.SchemeAdmin)\n\n\t\tmemberUpdate := &model.BoardMember{\n\t\t\tUserID:          userGuest,\n\t\t\tBoardID:         board.ID,\n\t\t\tSchemeAdmin:     true,\n\t\t\tSchemeViewer:    true,\n\t\t\tSchemeCommenter: true,\n\t\t\tSchemeEditor:    true,\n\t\t}\n\n\t\tupdatedGuestMember, resp := clients.Admin.UpdateBoardMember(memberUpdate)\n\t\tth.CheckOK(resp)\n\t\trequire.True(t, updatedGuestMember.SchemeViewer)\n\t\trequire.True(t, updatedGuestMember.SchemeCommenter)\n\t\trequire.True(t, updatedGuestMember.SchemeEditor)\n\t\trequire.False(t, updatedGuestMember.SchemeAdmin)\n\t})\n}\n\nfunc TestDeleteMember(t *testing.T) {\n\tteamID := testTeamID\n\n\tt.Run(\"a non authenticated user should be rejected\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tnewBoard := &model.Board{\n\t\t\tTitle:  \"title\",\n\t\t\tType:   model.BoardTypeOpen,\n\t\t\tTeamID: teamID,\n\t\t}\n\t\tboard, err := th.Server.App().CreateBoard(newBoard, th.GetUser1().ID, true)\n\t\trequire.NoError(t, err)\n\n\t\tmember := &model.BoardMember{\n\t\t\tUserID:  th.GetUser1().ID,\n\t\t\tBoardID: board.ID,\n\t\t}\n\n\t\tth.Logout(th.Client)\n\t\tsuccess, resp := th.Client.DeleteBoardMember(member)\n\t\tth.CheckUnauthorized(resp)\n\t\trequire.False(t, success)\n\t})\n\n\tt.Run(\"a user without permissions should be rejected\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tnewBoard := &model.Board{\n\t\t\tTitle:  \"title\",\n\t\t\tType:   model.BoardTypeOpen,\n\t\t\tTeamID: teamID,\n\t\t}\n\t\tboard, err := th.Server.App().CreateBoard(newBoard, th.GetUser1().ID, true)\n\t\trequire.NoError(t, err)\n\n\t\tmember := &model.BoardMember{\n\t\t\tUserID:  th.GetUser1().ID,\n\t\t\tBoardID: board.ID,\n\t\t}\n\n\t\tsuccess, resp := th.Client2.DeleteBoardMember(member)\n\t\tth.CheckForbidden(resp)\n\t\trequire.False(t, success)\n\t})\n\n\tt.Run(\"non existing board\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tupdatedMember := &model.BoardMember{\n\t\t\tUserID:  th.GetUser1().ID,\n\t\t\tBoardID: \"non-existent-board-id\",\n\t\t}\n\n\t\tsuccess, resp := th.Client.DeleteBoardMember(updatedMember)\n\t\tth.CheckNotFound(resp)\n\t\trequire.False(t, success)\n\t})\n\n\tt.Run(\"should correctly delete a member for a valid board\", func(t *testing.T) {\n\t\t//nolint:dupl\n\t\tt.Run(\"admin removing a user\", func(t *testing.T) {\n\t\t\tth := SetupTestHelper(t).InitBasic()\n\t\t\tdefer th.TearDown()\n\n\t\t\tnewBoard := &model.Board{\n\t\t\t\tTitle:  \"title\",\n\t\t\t\tType:   model.BoardTypePrivate,\n\t\t\t\tTeamID: teamID,\n\t\t\t}\n\t\t\tboard, err := th.Server.App().CreateBoard(newBoard, th.GetUser1().ID, true)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tnewUser2Member := &model.BoardMember{\n\t\t\t\tUserID:       th.GetUser2().ID,\n\t\t\t\tBoardID:      board.ID,\n\t\t\t\tSchemeEditor: true,\n\t\t\t}\n\t\t\tuser2Member, err := th.Server.App().AddMemberToBoard(newUser2Member)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NotNil(t, user2Member)\n\t\t\trequire.False(t, user2Member.SchemeAdmin)\n\t\t\trequire.True(t, user2Member.SchemeEditor)\n\n\t\t\tmemberToDelete := &model.BoardMember{\n\t\t\t\tUserID:  th.GetUser2().ID,\n\t\t\t\tBoardID: board.ID,\n\t\t\t}\n\n\t\t\tmembers, err := th.Server.App().GetMembersForBoard(board.ID)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, members, 2)\n\n\t\t\tsuccess, resp := th.Client.DeleteBoardMember(memberToDelete)\n\t\t\tth.CheckOK(resp)\n\t\t\trequire.True(t, success)\n\n\t\t\tmembers, err = th.Server.App().GetMembersForBoard(board.ID)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, members, 1)\n\t\t})\n\n\t\t//nolint:dupl\n\t\tt.Run(\"user removing themselves\", func(t *testing.T) {\n\t\t\tth := SetupTestHelper(t).InitBasic()\n\t\t\tdefer th.TearDown()\n\n\t\t\tnewBoard := &model.Board{\n\t\t\t\tTitle:  \"title\",\n\t\t\t\tType:   model.BoardTypePrivate,\n\t\t\t\tTeamID: teamID,\n\t\t\t}\n\t\t\tboard, err := th.Server.App().CreateBoard(newBoard, th.GetUser1().ID, true)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tnewUser2Member := &model.BoardMember{\n\t\t\t\tUserID:       th.GetUser2().ID,\n\t\t\t\tBoardID:      board.ID,\n\t\t\t\tSchemeEditor: true,\n\t\t\t}\n\t\t\tuser2Member, err := th.Server.App().AddMemberToBoard(newUser2Member)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NotNil(t, user2Member)\n\t\t\trequire.False(t, user2Member.SchemeAdmin)\n\t\t\trequire.True(t, user2Member.SchemeEditor)\n\n\t\t\tmemberToDelete := &model.BoardMember{\n\t\t\t\tUserID:  th.GetUser2().ID,\n\t\t\t\tBoardID: board.ID,\n\t\t\t}\n\n\t\t\tmembers, err := th.Server.App().GetMembersForBoard(board.ID)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, members, 2)\n\n\t\t\t// Should fail - must call leave to leave a board\n\t\t\tsuccess, resp := th.Client2.DeleteBoardMember(memberToDelete)\n\t\t\tth.CheckForbidden(resp)\n\t\t\trequire.False(t, success)\n\n\t\t\tmembers, err = th.Server.App().GetMembersForBoard(board.ID)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, members, 2)\n\t\t})\n\n\t\t//nolint:dupl\n\t\tt.Run(\"a non admin user should not be able to remove another user\", func(t *testing.T) {\n\t\t\tth := SetupTestHelper(t).InitBasic()\n\t\t\tdefer th.TearDown()\n\n\t\t\tnewBoard := &model.Board{\n\t\t\t\tTitle:  \"title\",\n\t\t\t\tType:   model.BoardTypePrivate,\n\t\t\t\tTeamID: teamID,\n\t\t\t}\n\t\t\tboard, err := th.Server.App().CreateBoard(newBoard, th.GetUser1().ID, true)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tnewUser2Member := &model.BoardMember{\n\t\t\t\tUserID:       th.GetUser2().ID,\n\t\t\t\tBoardID:      board.ID,\n\t\t\t\tSchemeEditor: true,\n\t\t\t}\n\t\t\tuser2Member, err := th.Server.App().AddMemberToBoard(newUser2Member)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NotNil(t, user2Member)\n\t\t\trequire.False(t, user2Member.SchemeAdmin)\n\t\t\trequire.True(t, user2Member.SchemeEditor)\n\n\t\t\tmemberToDelete := &model.BoardMember{\n\t\t\t\tUserID:  th.GetUser1().ID,\n\t\t\t\tBoardID: board.ID,\n\t\t\t}\n\n\t\t\tmembers, err := th.Server.App().GetMembersForBoard(board.ID)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, members, 2)\n\n\t\t\tsuccess, resp := th.Client2.DeleteBoardMember(memberToDelete)\n\t\t\tth.CheckForbidden(resp)\n\t\t\trequire.False(t, success)\n\n\t\t\tmembers, err = th.Server.App().GetMembersForBoard(board.ID)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, members, 2)\n\t\t})\n\t})\n\n\tt.Run(\"should not delete a member if that means that a board will not have any admin\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tnewBoard := &model.Board{\n\t\t\tTitle:  \"title\",\n\t\t\tType:   model.BoardTypePrivate,\n\t\t\tTeamID: teamID,\n\t\t}\n\t\tboard, err := th.Server.App().CreateBoard(newBoard, th.GetUser1().ID, true)\n\t\trequire.NoError(t, err)\n\n\t\tmemberToDelete := &model.BoardMember{\n\t\t\tUserID:  th.GetUser1().ID,\n\t\t\tBoardID: board.ID,\n\t\t}\n\n\t\tsuccess, resp := th.Client.DeleteBoardMember(memberToDelete)\n\t\tth.CheckBadRequest(resp)\n\t\trequire.False(t, success)\n\n\t\tmembers, err := th.Server.App().GetMembersForBoard(board.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, members, 1)\n\t\trequire.True(t, members[0].SchemeAdmin)\n\t})\n}\n\nfunc TestGetTemplates(t *testing.T) {\n\tt.Run(\"should be able to retrieve built-in templates\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\terr := th.Server.App().InitTemplates()\n\t\trequire.NoError(t, err, \"InitTemplates should not fail\")\n\n\t\tteamID := \"my-team-id\"\n\t\trBoards, resp := th.Client.GetTemplatesForTeam(\"0\")\n\t\tth.CheckOK(resp)\n\t\trequire.NotNil(t, rBoards)\n\t\trequire.GreaterOrEqual(t, len(rBoards), 6)\n\n\t\tt.Log(\"\\n\\n\")\n\t\tfor _, board := range rBoards {\n\t\t\tt.Logf(\"Test get template: %s - %s\\n\", board.Title, board.ID)\n\t\t\trBoard, resp := th.Client.GetBoard(board.ID, \"\")\n\t\t\tth.CheckOK(resp)\n\t\t\trequire.NotNil(t, rBoard)\n\t\t\trequire.Equal(t, board, rBoard)\n\n\t\t\trBlocks, resp := th.Client.GetAllBlocksForBoard(board.ID)\n\t\t\tth.CheckOK(resp)\n\t\t\trequire.NotNil(t, rBlocks)\n\t\t\trequire.Greater(t, len(rBlocks), 0)\n\t\t\tt.Logf(\"Got %d block(s)\\n\", len(rBlocks))\n\n\t\t\trBoardsAndBlock, resp := th.Client.DuplicateBoard(board.ID, false, teamID)\n\t\t\tth.CheckOK(resp)\n\t\t\trequire.NotNil(t, rBoardsAndBlock)\n\t\t\trequire.Greater(t, len(rBoardsAndBlock.Boards), 0)\n\t\t\trequire.Greater(t, len(rBoardsAndBlock.Blocks), 0)\n\n\t\t\trBoard2 := rBoardsAndBlock.Boards[0]\n\t\t\trequire.Contains(t, board.Title, rBoard2.Title)\n\t\t\trequire.False(t, rBoard2.IsTemplate)\n\n\t\t\tt.Logf(\"Duplicate template: %s - %s, %d block(s)\\n\", rBoard2.Title, rBoard2.ID, len(rBoardsAndBlock.Blocks))\n\t\t\trBoard3, resp := th.Client.GetBoard(rBoard2.ID, \"\")\n\t\t\tth.CheckOK(resp)\n\t\t\trequire.NotNil(t, rBoard3)\n\t\t\trequire.Equal(t, rBoard2, rBoard3)\n\n\t\t\trBlocks2, resp := th.Client.GetAllBlocksForBoard(rBoard2.ID)\n\t\t\tth.CheckOK(resp)\n\t\t\trequire.NotNil(t, rBlocks2)\n\t\t\trequire.Equal(t, len(rBoardsAndBlock.Blocks), len(rBlocks2))\n\t\t}\n\t\tt.Log(\"\\n\\n\")\n\t})\n}\n\nfunc TestDuplicateBoard(t *testing.T) {\n\tt.Run(\"create and duplicate public board\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tme := th.GetUser1()\n\n\t\ttitle := \"Public board\"\n\t\tteamID := testTeamID\n\t\tnewBoard := &model.Board{\n\t\t\tTitle:  title,\n\t\t\tType:   model.BoardTypeOpen,\n\t\t\tTeamID: teamID,\n\t\t}\n\t\tboard, resp := th.Client.CreateBoard(newBoard)\n\t\tth.CheckOK(resp)\n\t\trequire.NoError(t, resp.Error)\n\t\trequire.NotNil(t, board)\n\t\trequire.NotNil(t, board.ID)\n\t\trequire.Equal(t, title, board.Title)\n\t\trequire.Equal(t, model.BoardTypeOpen, board.Type)\n\t\trequire.Equal(t, teamID, board.TeamID)\n\t\trequire.Equal(t, me.ID, board.CreatedBy)\n\t\trequire.Equal(t, me.ID, board.ModifiedBy)\n\n\t\tnewBlocks := []*model.Block{\n\t\t\t{\n\t\t\t\tID:       utils.NewID(utils.IDTypeBlock),\n\t\t\t\tBoardID:  board.ID,\n\t\t\t\tCreateAt: 1,\n\t\t\t\tUpdateAt: 1,\n\t\t\t\tTitle:    \"View 1\",\n\t\t\t\tType:     model.TypeView,\n\t\t\t},\n\t\t}\n\n\t\tnewBlocks, resp = th.Client.InsertBlocks(board.ID, newBlocks, false)\n\t\trequire.NoError(t, resp.Error)\n\t\trequire.Len(t, newBlocks, 1)\n\n\t\tnewUserMember := &model.BoardMember{\n\t\t\tUserID:       th.GetUser2().ID,\n\t\t\tBoardID:      board.ID,\n\t\t\tSchemeEditor: true,\n\t\t}\n\t\tth.Client.AddMemberToBoard(newUserMember)\n\n\t\tmembers, err := th.Server.App().GetMembersForBoard(board.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, members, 2)\n\n\t\t// Duplicate the board\n\t\trBoardsAndBlock, resp := th.Client.DuplicateBoard(board.ID, false, teamID)\n\t\tth.CheckOK(resp)\n\t\trequire.NotNil(t, rBoardsAndBlock)\n\t\trequire.Equal(t, len(rBoardsAndBlock.Boards), 1)\n\t\trequire.Equal(t, len(rBoardsAndBlock.Blocks), 1)\n\t\tduplicateBoard := rBoardsAndBlock.Boards[0]\n\t\trequire.Equal(t, duplicateBoard.Type, model.BoardTypePrivate, \"Duplicated board should be private\")\n\n\t\tmembers, err = th.Server.App().GetMembersForBoard(duplicateBoard.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, members, 1, \"Duplicated board should only have one member\")\n\t\trequire.Equal(t, me.ID, members[0].UserID)\n\t\trequire.Equal(t, duplicateBoard.ID, members[0].BoardID)\n\t\trequire.True(t, members[0].SchemeAdmin)\n\t})\n\n\tt.Run(\"create and duplicate public board from a custom category\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tme := th.GetUser1()\n\t\tteamID := testTeamID\n\n\t\tcategory := model.Category{\n\t\t\tName:   \"My Category\",\n\t\t\tUserID: me.ID,\n\t\t\tTeamID: teamID,\n\t\t}\n\t\tcreatedCategory, resp := th.Client.CreateCategory(category)\n\t\tth.CheckOK(resp)\n\t\trequire.NoError(t, resp.Error)\n\t\trequire.NotNil(t, createdCategory)\n\t\trequire.Equal(t, \"My Category\", createdCategory.Name)\n\t\trequire.Equal(t, me.ID, createdCategory.UserID)\n\t\trequire.Equal(t, teamID, createdCategory.TeamID)\n\n\t\ttitle := \"Public board\"\n\t\tnewBoard := &model.Board{\n\t\t\tTitle:  title,\n\t\t\tType:   model.BoardTypeOpen,\n\t\t\tTeamID: teamID,\n\t\t}\n\t\tboard, resp := th.Client.CreateBoard(newBoard)\n\t\tth.CheckOK(resp)\n\t\trequire.NoError(t, resp.Error)\n\t\trequire.NotNil(t, board)\n\t\trequire.NotNil(t, board.ID)\n\t\trequire.Equal(t, title, board.Title)\n\t\trequire.Equal(t, model.BoardTypeOpen, board.Type)\n\t\trequire.Equal(t, teamID, board.TeamID)\n\t\trequire.Equal(t, me.ID, board.CreatedBy)\n\t\trequire.Equal(t, me.ID, board.ModifiedBy)\n\n\t\t// move board to custom category\n\t\tresp = th.Client.UpdateCategoryBoard(teamID, createdCategory.ID, board.ID)\n\t\tth.CheckOK(resp)\n\t\trequire.NoError(t, resp.Error)\n\n\t\tnewBlocks := []*model.Block{\n\t\t\t{\n\t\t\t\tID:       utils.NewID(utils.IDTypeBlock),\n\t\t\t\tBoardID:  board.ID,\n\t\t\t\tCreateAt: 1,\n\t\t\t\tUpdateAt: 1,\n\t\t\t\tTitle:    \"View 1\",\n\t\t\t\tType:     model.TypeView,\n\t\t\t},\n\t\t}\n\n\t\tnewBlocks, resp = th.Client.InsertBlocks(board.ID, newBlocks, false)\n\t\trequire.NoError(t, resp.Error)\n\t\trequire.Len(t, newBlocks, 1)\n\n\t\tnewUserMember := &model.BoardMember{\n\t\t\tUserID:       th.GetUser2().ID,\n\t\t\tBoardID:      board.ID,\n\t\t\tSchemeEditor: true,\n\t\t}\n\t\tth.Client.AddMemberToBoard(newUserMember)\n\n\t\tmembers, err := th.Server.App().GetMembersForBoard(board.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, members, 2)\n\n\t\t// Duplicate the board\n\t\trBoardsAndBlock, resp := th.Client.DuplicateBoard(board.ID, false, teamID)\n\t\tth.CheckOK(resp)\n\t\trequire.NotNil(t, rBoardsAndBlock)\n\t\trequire.Equal(t, len(rBoardsAndBlock.Boards), 1)\n\t\trequire.Equal(t, len(rBoardsAndBlock.Blocks), 1)\n\n\t\tduplicateBoard := rBoardsAndBlock.Boards[0]\n\t\trequire.Equal(t, duplicateBoard.Type, model.BoardTypePrivate, \"Duplicated board should be private\")\n\t\trequire.Equal(t, \"Public board copy\", duplicateBoard.Title)\n\n\t\tmembers, err = th.Server.App().GetMembersForBoard(duplicateBoard.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, members, 1, \"Duplicated board should only have one member\")\n\t\trequire.Equal(t, me.ID, members[0].UserID)\n\t\trequire.Equal(t, duplicateBoard.ID, members[0].BoardID)\n\t\trequire.True(t, members[0].SchemeAdmin)\n\n\t\t// verify duplicated board is in the same custom category\n\t\tuserCategoryBoards, resp := th.Client.GetUserCategoryBoards(teamID)\n\t\tth.CheckOK(resp)\n\t\trequire.NotNil(t, rBoardsAndBlock)\n\n\t\tvar duplicateBoardCategoryID string\n\t\tfor _, categoryBoard := range userCategoryBoards {\n\t\t\tfor _, boardMetadata := range categoryBoard.BoardMetadata {\n\t\t\t\tif boardMetadata.BoardID == duplicateBoard.ID {\n\t\t\t\t\tduplicateBoardCategoryID = categoryBoard.Category.ID\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\trequire.Equal(t, createdCategory.ID, duplicateBoardCategoryID)\n\t})\n}\n\nfunc TestJoinBoard(t *testing.T) {\n\tt.Run(\"create and join public board\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tme := th.GetUser1()\n\n\t\ttitle := \"Test Public board\"\n\t\tteamID := testTeamID\n\t\tnewBoard := &model.Board{\n\t\t\tTitle:  title,\n\t\t\tType:   model.BoardTypeOpen,\n\t\t\tTeamID: teamID,\n\t\t}\n\t\tboard, resp := th.Client.CreateBoard(newBoard)\n\t\tth.CheckOK(resp)\n\t\trequire.NoError(t, resp.Error)\n\t\trequire.NotNil(t, board)\n\t\trequire.NotNil(t, board.ID)\n\t\trequire.Equal(t, title, board.Title)\n\t\trequire.Equal(t, model.BoardTypeOpen, board.Type)\n\t\trequire.Equal(t, teamID, board.TeamID)\n\t\trequire.Equal(t, me.ID, board.CreatedBy)\n\t\trequire.Equal(t, me.ID, board.ModifiedBy)\n\t\trequire.Equal(t, model.BoardRoleNone, board.MinimumRole)\n\n\t\tmember, resp := th.Client2.JoinBoard(board.ID)\n\t\tth.CheckOK(resp)\n\t\trequire.NoError(t, resp.Error)\n\t\trequire.NotNil(t, member)\n\t\trequire.Equal(t, board.ID, member.BoardID)\n\t\trequire.Equal(t, th.GetUser2().ID, member.UserID)\n\n\t\ts, _ := json.MarshalIndent(member, \"\", \"\\t\")\n\t\tt.Log(string(s))\n\t})\n\n\tt.Run(\"create and join public board should match the minimumRole in the membership\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tme := th.GetUser1()\n\n\t\ttitle := \"Public board for commenters\"\n\t\tteamID := testTeamID\n\t\tnewBoard := &model.Board{\n\t\t\tTitle:       title,\n\t\t\tType:        model.BoardTypeOpen,\n\t\t\tTeamID:      teamID,\n\t\t\tMinimumRole: model.BoardRoleCommenter,\n\t\t}\n\t\tboard, resp := th.Client.CreateBoard(newBoard)\n\t\tth.CheckOK(resp)\n\t\trequire.NoError(t, resp.Error)\n\t\trequire.NotNil(t, board)\n\t\trequire.NotNil(t, board.ID)\n\t\trequire.Equal(t, title, board.Title)\n\t\trequire.Equal(t, model.BoardTypeOpen, board.Type)\n\t\trequire.Equal(t, teamID, board.TeamID)\n\t\trequire.Equal(t, me.ID, board.CreatedBy)\n\t\trequire.Equal(t, me.ID, board.ModifiedBy)\n\n\t\tmember, resp := th.Client2.JoinBoard(board.ID)\n\t\tth.CheckOK(resp)\n\t\trequire.NoError(t, resp.Error)\n\t\trequire.NotNil(t, member)\n\t\trequire.Equal(t, board.ID, member.BoardID)\n\t\trequire.Equal(t, th.GetUser2().ID, member.UserID)\n\t\trequire.False(t, member.SchemeAdmin, \"new member should not be admin\")\n\t\trequire.False(t, member.SchemeEditor, \"new member should not be editor\")\n\t\trequire.True(t, member.SchemeCommenter, \"new member should be commenter\")\n\t\trequire.False(t, member.SchemeViewer, \"new member should not be viewer\")\n\n\t\ts, _ := json.MarshalIndent(member, \"\", \"\\t\")\n\t\tt.Log(string(s))\n\t})\n\n\tt.Run(\"create and join public board should match editor role in the membership when MinimumRole is empty\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tme := th.GetUser1()\n\n\t\ttitle := \"Public board for editors\"\n\t\tteamID := testTeamID\n\t\tnewBoard := &model.Board{\n\t\t\tTitle:  title,\n\t\t\tType:   model.BoardTypeOpen,\n\t\t\tTeamID: teamID,\n\t\t}\n\t\tboard, resp := th.Client.CreateBoard(newBoard)\n\t\tth.CheckOK(resp)\n\t\trequire.NoError(t, resp.Error)\n\t\trequire.NotNil(t, board)\n\t\trequire.NotNil(t, board.ID)\n\t\trequire.Equal(t, title, board.Title)\n\t\trequire.Equal(t, model.BoardTypeOpen, board.Type)\n\t\trequire.Equal(t, teamID, board.TeamID)\n\t\trequire.Equal(t, me.ID, board.CreatedBy)\n\t\trequire.Equal(t, me.ID, board.ModifiedBy)\n\n\t\tmember, resp := th.Client2.JoinBoard(board.ID)\n\t\tth.CheckOK(resp)\n\t\trequire.NoError(t, resp.Error)\n\t\trequire.NotNil(t, member)\n\t\trequire.Equal(t, board.ID, member.BoardID)\n\t\trequire.Equal(t, th.GetUser2().ID, member.UserID)\n\t\trequire.False(t, member.SchemeAdmin, \"new member should not be admin\")\n\t\trequire.True(t, member.SchemeEditor, \"new member should be editor\")\n\t\trequire.False(t, member.SchemeCommenter, \"new member should not be commenter\")\n\t\trequire.False(t, member.SchemeViewer, \"new member should not be viewer\")\n\n\t\ts, _ := json.MarshalIndent(member, \"\", \"\\t\")\n\t\tt.Log(string(s))\n\t})\n\n\tt.Run(\"create and join private board (should not succeed)\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tme := th.GetUser1()\n\n\t\ttitle := \"Private board\"\n\t\tteamID := testTeamID\n\t\tnewBoard := &model.Board{\n\t\t\tTitle:  title,\n\t\t\tType:   model.BoardTypePrivate,\n\t\t\tTeamID: teamID,\n\t\t}\n\t\tboard, resp := th.Client.CreateBoard(newBoard)\n\t\tth.CheckOK(resp)\n\t\trequire.NoError(t, resp.Error)\n\t\trequire.NotNil(t, board)\n\t\trequire.NotNil(t, board.ID)\n\t\trequire.Equal(t, title, board.Title)\n\t\trequire.Equal(t, model.BoardTypePrivate, board.Type)\n\t\trequire.Equal(t, teamID, board.TeamID)\n\t\trequire.Equal(t, me.ID, board.CreatedBy)\n\t\trequire.Equal(t, me.ID, board.ModifiedBy)\n\n\t\tmember, resp := th.Client2.JoinBoard(board.ID)\n\t\tth.CheckForbidden(resp)\n\t\trequire.Nil(t, member)\n\t})\n\n\tt.Run(\"join invalid board\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tmember, resp := th.Client2.JoinBoard(\"nonexistent-board-ID\")\n\t\tth.CheckNotFound(resp)\n\t\trequire.Nil(t, member)\n\t})\n}\n"
  },
  {
    "path": "server/integrationtests/boards_and_blocks_test.go",
    "content": "package integrationtests\n\nimport (\n\t\"testing\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestCreateBoardsAndBlocks(t *testing.T) {\n\tteamID := testTeamID\n\n\tt.Run(\"a non authenticated user should be rejected\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).Start()\n\t\tdefer th.TearDown()\n\n\t\tnewBab := &model.BoardsAndBlocks{\n\t\t\tBoards: []*model.Board{},\n\t\t\tBlocks: []*model.Block{},\n\t\t}\n\n\t\tbab, resp := th.Client.CreateBoardsAndBlocks(newBab)\n\t\tth.CheckUnauthorized(resp)\n\t\trequire.Nil(t, bab)\n\t})\n\n\tt.Run(\"invalid boards and blocks\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tt.Run(\"no boards\", func(t *testing.T) {\n\t\t\tnewBab := &model.BoardsAndBlocks{\n\t\t\t\tBoards: []*model.Board{},\n\t\t\t\tBlocks: []*model.Block{\n\t\t\t\t\t{ID: \"block-id\", BoardID: \"board-id\", Type: model.TypeCard},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tbab, resp := th.Client.CreateBoardsAndBlocks(newBab)\n\t\t\tth.CheckBadRequest(resp)\n\t\t\trequire.Nil(t, bab)\n\t\t})\n\n\t\tt.Run(\"no blocks\", func(t *testing.T) {\n\t\t\tnewBab := &model.BoardsAndBlocks{\n\t\t\t\tBoards: []*model.Board{\n\t\t\t\t\t{ID: \"board-id\", TeamID: teamID, Type: model.BoardTypePrivate},\n\t\t\t\t},\n\t\t\t\tBlocks: []*model.Block{},\n\t\t\t}\n\n\t\t\tbab, resp := th.Client.CreateBoardsAndBlocks(newBab)\n\t\t\tth.CheckBadRequest(resp)\n\t\t\trequire.Nil(t, bab)\n\t\t})\n\n\t\tt.Run(\"blocks from nonexistent boards\", func(t *testing.T) {\n\t\t\tnewBab := &model.BoardsAndBlocks{\n\t\t\t\tBoards: []*model.Board{\n\t\t\t\t\t{ID: \"board-id\", TeamID: teamID, Type: model.BoardTypePrivate},\n\t\t\t\t},\n\t\t\t\tBlocks: []*model.Block{\n\t\t\t\t\t{ID: \"block-id\", BoardID: \"nonexistent-board-id\", Type: model.TypeCard, CreateAt: 1, UpdateAt: 1},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tbab, resp := th.Client.CreateBoardsAndBlocks(newBab)\n\t\t\tth.CheckBadRequest(resp)\n\t\t\trequire.Nil(t, bab)\n\t\t})\n\n\t\tt.Run(\"boards with no IDs\", func(t *testing.T) {\n\t\t\tnewBab := &model.BoardsAndBlocks{\n\t\t\t\tBoards: []*model.Board{\n\t\t\t\t\t{ID: \"board-id\", TeamID: teamID, Type: model.BoardTypePrivate},\n\t\t\t\t\t{TeamID: teamID, Type: model.BoardTypePrivate},\n\t\t\t\t},\n\t\t\t\tBlocks: []*model.Block{\n\t\t\t\t\t{ID: \"block-id\", BoardID: \"board-id\", Type: model.TypeCard, CreateAt: 1, UpdateAt: 1},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tbab, resp := th.Client.CreateBoardsAndBlocks(newBab)\n\t\t\tth.CheckBadRequest(resp)\n\t\t\trequire.Nil(t, bab)\n\t\t})\n\n\t\tt.Run(\"boards from different teams\", func(t *testing.T) {\n\t\t\tnewBab := &model.BoardsAndBlocks{\n\t\t\t\tBoards: []*model.Board{\n\t\t\t\t\t{ID: \"board-id-1\", TeamID: \"team-id-1\", Type: model.BoardTypePrivate},\n\t\t\t\t\t{ID: \"board-id-2\", TeamID: \"team-id-2\", Type: model.BoardTypePrivate},\n\t\t\t\t},\n\t\t\t\tBlocks: []*model.Block{\n\t\t\t\t\t{ID: \"block-id\", BoardID: \"board-id-1\", Type: model.TypeCard, CreateAt: 1, UpdateAt: 1},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tbab, resp := th.Client.CreateBoardsAndBlocks(newBab)\n\t\t\tth.CheckBadRequest(resp)\n\t\t\trequire.Nil(t, bab)\n\t\t})\n\n\t\tt.Run(\"creating boards and blocks\", func(t *testing.T) {\n\t\t\tnewBab := &model.BoardsAndBlocks{\n\t\t\t\tBoards: []*model.Board{\n\t\t\t\t\t{ID: \"board-id-1\", Title: \"public board\", TeamID: teamID, Type: model.BoardTypeOpen},\n\t\t\t\t\t{ID: \"board-id-2\", Title: \"private board\", TeamID: teamID, Type: model.BoardTypePrivate},\n\t\t\t\t},\n\t\t\t\tBlocks: []*model.Block{\n\t\t\t\t\t{ID: \"block-id-1\", Title: \"block 1\", BoardID: \"board-id-1\", Type: model.TypeCard, CreateAt: 1, UpdateAt: 1},\n\t\t\t\t\t{ID: \"block-id-2\", Title: \"block 2\", BoardID: \"board-id-2\", Type: model.TypeCard, CreateAt: 1, UpdateAt: 1},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tbab, resp := th.Client.CreateBoardsAndBlocks(newBab)\n\t\t\tth.CheckOK(resp)\n\t\t\trequire.NotNil(t, bab)\n\n\t\t\trequire.Len(t, bab.Boards, 2)\n\t\t\trequire.Len(t, bab.Blocks, 2)\n\n\t\t\t// board 1 should have been created with a new ID, and its\n\t\t\t// block should be there too\n\t\t\tboardsTermPublic, resp := th.Client.SearchBoardsForTeam(teamID, \"public\")\n\t\t\tth.CheckOK(resp)\n\t\t\trequire.Len(t, boardsTermPublic, 1)\n\t\t\tboard1 := boardsTermPublic[0]\n\t\t\trequire.Equal(t, \"public board\", board1.Title)\n\t\t\trequire.Equal(t, model.BoardTypeOpen, board1.Type)\n\t\t\trequire.NotEqual(t, \"board-id-1\", board1.ID)\n\t\t\tblocks1, err := th.Server.App().GetBlocksForBoard(board1.ID)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, blocks1, 1)\n\t\t\trequire.Equal(t, \"block 1\", blocks1[0].Title)\n\n\t\t\t// board 1 should have been created with a new ID, and its\n\t\t\t// block should be there too\n\t\t\tboardsTermPrivate, resp := th.Client.SearchBoardsForTeam(teamID, \"private\")\n\t\t\tth.CheckOK(resp)\n\t\t\trequire.Len(t, boardsTermPrivate, 1)\n\t\t\tboard2 := boardsTermPrivate[0]\n\t\t\trequire.Equal(t, \"private board\", board2.Title)\n\t\t\trequire.Equal(t, model.BoardTypePrivate, board2.Type)\n\t\t\trequire.NotEqual(t, \"board-id-2\", board2.ID)\n\t\t\tblocks2, err := th.Server.App().GetBlocksForBoard(board2.ID)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, blocks2, 1)\n\t\t\trequire.Equal(t, \"block 2\", blocks2[0].Title)\n\n\t\t\t// user should be an admin of both newly created boards\n\t\t\tuser1 := th.GetUser1()\n\t\t\tmembers1, err := th.Server.App().GetMembersForBoard(board1.ID)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, members1, 1)\n\t\t\trequire.Equal(t, user1.ID, members1[0].UserID)\n\t\t\tmembers2, err := th.Server.App().GetMembersForBoard(board2.ID)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, members2, 1)\n\t\t\trequire.Equal(t, user1.ID, members2[0].UserID)\n\t\t})\n\t})\n}\n\nfunc TestPatchBoardsAndBlocks(t *testing.T) {\n\tteamID := \"team-id\"\n\n\tt.Run(\"a non authenticated user should be rejected\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).Start()\n\t\tdefer th.TearDown()\n\n\t\tpbab := &model.PatchBoardsAndBlocks{}\n\n\t\tbab, resp := th.Client.PatchBoardsAndBlocks(pbab)\n\t\tth.CheckUnauthorized(resp)\n\t\trequire.Nil(t, bab)\n\t})\n\n\tt.Run(\"invalid patch boards and blocks\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tuserID := th.GetUser1().ID\n\t\tinitialTitle := \"initial title 1\"\n\t\tnewTitle := \"new title 1\"\n\n\t\tnewBoard1 := &model.Board{\n\t\t\tTitle:  initialTitle,\n\t\t\tTeamID: teamID,\n\t\t\tType:   model.BoardTypeOpen,\n\t\t}\n\t\tboard1, err := th.Server.App().CreateBoard(newBoard1, userID, true)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, board1)\n\n\t\tnewBoard2 := &model.Board{\n\t\t\tTitle:  initialTitle,\n\t\t\tTeamID: teamID,\n\t\t\tType:   model.BoardTypeOpen,\n\t\t}\n\t\tboard2, err := th.Server.App().CreateBoard(newBoard2, userID, true)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, board2)\n\n\t\tnewBlock1 := &model.Block{\n\t\t\tID:      \"block-id-1\",\n\t\t\tBoardID: board1.ID,\n\t\t\tTitle:   initialTitle,\n\t\t}\n\t\trequire.NoError(t, th.Server.App().InsertBlock(newBlock1, userID))\n\t\tblock1, err := th.Server.App().GetBlockByID(\"block-id-1\")\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, block1)\n\n\t\tnewBlock2 := &model.Block{\n\t\t\tID:      \"block-id-2\",\n\t\t\tBoardID: board2.ID,\n\t\t\tTitle:   initialTitle,\n\t\t}\n\t\trequire.NoError(t, th.Server.App().InsertBlock(newBlock2, userID))\n\t\tblock2, err := th.Server.App().GetBlockByID(\"block-id-2\")\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, block2)\n\n\t\tt.Run(\"no board IDs\", func(t *testing.T) {\n\t\t\tpbab := &model.PatchBoardsAndBlocks{\n\t\t\t\tBoardIDs: []string{},\n\t\t\t\tBoardPatches: []*model.BoardPatch{\n\t\t\t\t\t{Title: &newTitle},\n\t\t\t\t\t{Title: &newTitle},\n\t\t\t\t},\n\t\t\t\tBlockIDs: []string{block1.ID, block2.ID},\n\t\t\t\tBlockPatches: []*model.BlockPatch{\n\t\t\t\t\t{Title: &newTitle},\n\t\t\t\t\t{Title: &newTitle},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tbab, resp := th.Client.PatchBoardsAndBlocks(pbab)\n\t\t\tth.CheckBadRequest(resp)\n\t\t\trequire.Nil(t, bab)\n\t\t})\n\n\t\tt.Run(\"missmatch board IDs and patches\", func(t *testing.T) {\n\t\t\tpbab := &model.PatchBoardsAndBlocks{\n\t\t\t\tBoardIDs: []string{board1.ID, board2.ID},\n\t\t\t\tBoardPatches: []*model.BoardPatch{\n\t\t\t\t\t{Title: &newTitle},\n\t\t\t\t},\n\t\t\t\tBlockIDs: []string{block1.ID, block2.ID},\n\t\t\t\tBlockPatches: []*model.BlockPatch{\n\t\t\t\t\t{Title: &newTitle},\n\t\t\t\t\t{Title: &newTitle},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tbab, resp := th.Client.PatchBoardsAndBlocks(pbab)\n\t\t\tth.CheckBadRequest(resp)\n\t\t\trequire.Nil(t, bab)\n\t\t})\n\n\t\tt.Run(\"no block IDs\", func(t *testing.T) {\n\t\t\tpbab := &model.PatchBoardsAndBlocks{\n\t\t\t\tBoardIDs: []string{board1.ID, board2.ID},\n\t\t\t\tBoardPatches: []*model.BoardPatch{\n\t\t\t\t\t{Title: &newTitle},\n\t\t\t\t\t{Title: &newTitle},\n\t\t\t\t},\n\t\t\t\tBlockIDs: []string{},\n\t\t\t\tBlockPatches: []*model.BlockPatch{\n\t\t\t\t\t{Title: &newTitle},\n\t\t\t\t\t{Title: &newTitle},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tbab, resp := th.Client.PatchBoardsAndBlocks(pbab)\n\t\t\tth.CheckBadRequest(resp)\n\t\t\trequire.Nil(t, bab)\n\t\t})\n\n\t\tt.Run(\"missmatch block IDs and patches\", func(t *testing.T) {\n\t\t\tpbab := &model.PatchBoardsAndBlocks{\n\t\t\t\tBoardIDs: []string{board1.ID, board2.ID},\n\t\t\t\tBoardPatches: []*model.BoardPatch{\n\t\t\t\t\t{Title: &newTitle},\n\t\t\t\t\t{Title: &newTitle},\n\t\t\t\t},\n\t\t\t\tBlockIDs: []string{block1.ID, block2.ID},\n\t\t\t\tBlockPatches: []*model.BlockPatch{\n\t\t\t\t\t{Title: &newTitle},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tbab, resp := th.Client.PatchBoardsAndBlocks(pbab)\n\t\t\tth.CheckBadRequest(resp)\n\t\t\trequire.Nil(t, bab)\n\t\t})\n\n\t\tt.Run(\"block that doesn't belong to any board\", func(t *testing.T) {\n\t\t\tpbab := &model.PatchBoardsAndBlocks{\n\t\t\t\tBoardIDs: []string{board1.ID},\n\t\t\t\tBoardPatches: []*model.BoardPatch{\n\t\t\t\t\t{Title: &newTitle},\n\t\t\t\t},\n\t\t\t\tBlockIDs: []string{block1.ID, block2.ID},\n\t\t\t\tBlockPatches: []*model.BlockPatch{\n\t\t\t\t\t{Title: &newTitle},\n\t\t\t\t\t{Title: &newTitle},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tbab, resp := th.Client.PatchBoardsAndBlocks(pbab)\n\t\t\tth.CheckBadRequest(resp)\n\t\t\trequire.Nil(t, bab)\n\t\t})\n\t})\n\n\tt.Run(\"if the user doesn't have permissions for one of the boards, nothing should be updated\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tuserID := th.GetUser1().ID\n\t\tinitialTitle := \"initial title 2\"\n\t\tnewTitle := \"new title 2\"\n\n\t\tnewBoard1 := &model.Board{\n\t\t\tTitle:  initialTitle,\n\t\t\tTeamID: teamID,\n\t\t\tType:   model.BoardTypeOpen,\n\t\t}\n\t\tboard1, err := th.Server.App().CreateBoard(newBoard1, userID, true)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, board1)\n\n\t\tnewBoard2 := &model.Board{\n\t\t\tTitle:  initialTitle,\n\t\t\tTeamID: teamID,\n\t\t\tType:   model.BoardTypeOpen,\n\t\t}\n\t\tboard2, err := th.Server.App().CreateBoard(newBoard2, userID, false)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, board2)\n\n\t\tnewBlock1 := &model.Block{\n\t\t\tID:      \"block-id-1\",\n\t\t\tBoardID: board1.ID,\n\t\t\tTitle:   initialTitle,\n\t\t}\n\t\trequire.NoError(t, th.Server.App().InsertBlock(newBlock1, userID))\n\t\tblock1, err := th.Server.App().GetBlockByID(\"block-id-1\")\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, block1)\n\n\t\tnewBlock2 := &model.Block{\n\t\t\tID:      \"block-id-2\",\n\t\t\tBoardID: board2.ID,\n\t\t\tTitle:   initialTitle,\n\t\t}\n\t\trequire.NoError(t, th.Server.App().InsertBlock(newBlock2, userID))\n\t\tblock2, err := th.Server.App().GetBlockByID(\"block-id-2\")\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, block2)\n\n\t\tpbab := &model.PatchBoardsAndBlocks{\n\t\t\tBoardIDs: []string{board1.ID, board2.ID},\n\t\t\tBoardPatches: []*model.BoardPatch{\n\t\t\t\t{Title: &newTitle},\n\t\t\t\t{Title: &newTitle},\n\t\t\t},\n\t\t\tBlockIDs: []string{block1.ID, block2.ID},\n\t\t\tBlockPatches: []*model.BlockPatch{\n\t\t\t\t{Title: &newTitle},\n\t\t\t\t{Title: &newTitle},\n\t\t\t},\n\t\t}\n\n\t\tbab, resp := th.Client.PatchBoardsAndBlocks(pbab)\n\t\tth.CheckForbidden(resp)\n\t\trequire.Nil(t, bab)\n\t})\n\n\tt.Run(\"boards belonging to different teams should be rejected\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tuserID := th.GetUser1().ID\n\t\tinitialTitle := \"initial title 3\"\n\t\tnewTitle := \"new title 3\"\n\n\t\tnewBoard1 := &model.Board{\n\t\t\tTitle:  initialTitle,\n\t\t\tTeamID: teamID,\n\t\t\tType:   model.BoardTypeOpen,\n\t\t}\n\t\tboard1, err := th.Server.App().CreateBoard(newBoard1, userID, true)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, board1)\n\n\t\tnewBoard2 := &model.Board{\n\t\t\tTitle:  initialTitle,\n\t\t\tTeamID: \"different-team-id\",\n\t\t\tType:   model.BoardTypeOpen,\n\t\t}\n\t\tboard2, err := th.Server.App().CreateBoard(newBoard2, userID, true)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, board2)\n\n\t\tnewBlock1 := &model.Block{\n\t\t\tID:      \"block-id-1\",\n\t\t\tBoardID: board1.ID,\n\t\t\tTitle:   initialTitle,\n\t\t}\n\t\trequire.NoError(t, th.Server.App().InsertBlock(newBlock1, userID))\n\t\tblock1, err := th.Server.App().GetBlockByID(\"block-id-1\")\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, block1)\n\n\t\tnewBlock2 := &model.Block{\n\t\t\tID:      \"block-id-2\",\n\t\t\tBoardID: board2.ID,\n\t\t\tTitle:   initialTitle,\n\t\t}\n\t\trequire.NoError(t, th.Server.App().InsertBlock(newBlock2, userID))\n\t\tblock2, err := th.Server.App().GetBlockByID(\"block-id-2\")\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, block2)\n\n\t\tpbab := &model.PatchBoardsAndBlocks{\n\t\t\tBoardIDs: []string{board1.ID, board2.ID},\n\t\t\tBoardPatches: []*model.BoardPatch{\n\t\t\t\t{Title: &newTitle},\n\t\t\t\t{Title: &newTitle},\n\t\t\t},\n\t\t\tBlockIDs: []string{block1.ID, \"board-id-2\"},\n\t\t\tBlockPatches: []*model.BlockPatch{\n\t\t\t\t{Title: &newTitle},\n\t\t\t\t{Title: &newTitle},\n\t\t\t},\n\t\t}\n\n\t\tbab, resp := th.Client.PatchBoardsAndBlocks(pbab)\n\t\tth.CheckBadRequest(resp)\n\t\trequire.Nil(t, bab)\n\t})\n\n\tt.Run(\"patches should be rejected if one is invalid\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tuserID := th.GetUser1().ID\n\t\tinitialTitle := \"initial title 4\"\n\t\tnewTitle := \"new title 4\"\n\n\t\tnewBoard1 := &model.Board{\n\t\t\tTitle:  initialTitle,\n\t\t\tTeamID: teamID,\n\t\t\tType:   model.BoardTypeOpen,\n\t\t}\n\t\tboard1, err := th.Server.App().CreateBoard(newBoard1, userID, true)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, board1)\n\n\t\tnewBoard2 := &model.Board{\n\t\t\tTitle:  initialTitle,\n\t\t\tTeamID: teamID,\n\t\t\tType:   model.BoardTypeOpen,\n\t\t}\n\t\tboard2, err := th.Server.App().CreateBoard(newBoard2, userID, false)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, board2)\n\n\t\tnewBlock1 := &model.Block{\n\t\t\tID:      \"block-id-1\",\n\t\t\tBoardID: board1.ID,\n\t\t\tTitle:   initialTitle,\n\t\t}\n\t\trequire.NoError(t, th.Server.App().InsertBlock(newBlock1, userID))\n\t\tblock1, err := th.Server.App().GetBlockByID(\"block-id-1\")\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, block1)\n\n\t\tnewBlock2 := &model.Block{\n\t\t\tID:      \"block-id-2\",\n\t\t\tBoardID: board2.ID,\n\t\t\tTitle:   initialTitle,\n\t\t}\n\t\trequire.NoError(t, th.Server.App().InsertBlock(newBlock2, userID))\n\t\tblock2, err := th.Server.App().GetBlockByID(\"block-id-2\")\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, block2)\n\n\t\tvar invalidPatchType model.BoardType = \"invalid\"\n\t\tinvalidPatch := &model.BoardPatch{Type: &invalidPatchType}\n\n\t\tpbab := &model.PatchBoardsAndBlocks{\n\t\t\tBoardIDs: []string{board1.ID, board2.ID},\n\t\t\tBoardPatches: []*model.BoardPatch{\n\t\t\t\t{Title: &newTitle},\n\t\t\t\tinvalidPatch,\n\t\t\t},\n\t\t\tBlockIDs: []string{block1.ID, \"board-id-2\"},\n\t\t\tBlockPatches: []*model.BlockPatch{\n\t\t\t\t{Title: &newTitle},\n\t\t\t\t{Title: &newTitle},\n\t\t\t},\n\t\t}\n\n\t\tbab, resp := th.Client.PatchBoardsAndBlocks(pbab)\n\t\tth.CheckBadRequest(resp)\n\t\trequire.Nil(t, bab)\n\t})\n\n\tt.Run(\"patches should be rejected if there is a block that doesn't belong to the boards being patched\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tuserID := th.GetUser1().ID\n\t\tinitialTitle := \"initial title\"\n\t\tnewTitle := \"new patched title\"\n\n\t\tnewBoard1 := &model.Board{\n\t\t\tTitle:  initialTitle,\n\t\t\tTeamID: teamID,\n\t\t\tType:   model.BoardTypeOpen,\n\t\t}\n\t\tboard1, err := th.Server.App().CreateBoard(newBoard1, userID, true)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, board1)\n\n\t\tnewBoard2 := &model.Board{\n\t\t\tTitle:  initialTitle,\n\t\t\tTeamID: teamID,\n\t\t\tType:   model.BoardTypeOpen,\n\t\t}\n\t\tboard2, err := th.Server.App().CreateBoard(newBoard2, userID, true)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, board2)\n\n\t\tnewBlock1 := &model.Block{\n\t\t\tID:      \"block-id-1\",\n\t\t\tBoardID: board1.ID,\n\t\t\tTitle:   initialTitle,\n\t\t}\n\t\trequire.NoError(t, th.Server.App().InsertBlock(newBlock1, userID))\n\t\tblock1, err := th.Server.App().GetBlockByID(\"block-id-1\")\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, block1)\n\n\t\tnewBlock2 := &model.Block{\n\t\t\tID:      \"block-id-2\",\n\t\t\tBoardID: board2.ID,\n\t\t\tTitle:   initialTitle,\n\t\t}\n\t\trequire.NoError(t, th.Server.App().InsertBlock(newBlock2, userID))\n\t\tblock2, err := th.Server.App().GetBlockByID(\"block-id-2\")\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, block2)\n\n\t\tpbab := &model.PatchBoardsAndBlocks{\n\t\t\tBoardIDs: []string{board1.ID},\n\t\t\tBoardPatches: []*model.BoardPatch{\n\t\t\t\t{Title: &newTitle},\n\t\t\t},\n\t\t\tBlockIDs: []string{block1.ID, block2.ID},\n\t\t\tBlockPatches: []*model.BlockPatch{\n\t\t\t\t{Title: &newTitle},\n\t\t\t\t{Title: &newTitle},\n\t\t\t},\n\t\t}\n\n\t\tbab, resp := th.Client.PatchBoardsAndBlocks(pbab)\n\t\tth.CheckBadRequest(resp)\n\t\trequire.Nil(t, bab)\n\t})\n\n\tt.Run(\"patches should be applied if they're valid and they're related\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tuserID := th.GetUser1().ID\n\t\tinitialTitle := \"initial title\"\n\t\tnewTitle := \"new other title\"\n\n\t\tnewBoard1 := &model.Board{\n\t\t\tTitle:  initialTitle,\n\t\t\tTeamID: teamID,\n\t\t\tType:   model.BoardTypeOpen,\n\t\t}\n\t\tboard1, err := th.Server.App().CreateBoard(newBoard1, userID, true)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, board1)\n\n\t\tnewBoard2 := &model.Board{\n\t\t\tTitle:  initialTitle,\n\t\t\tTeamID: teamID,\n\t\t\tType:   model.BoardTypeOpen,\n\t\t}\n\t\tboard2, err := th.Server.App().CreateBoard(newBoard2, userID, true)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, board2)\n\n\t\tnewBlock1 := &model.Block{\n\t\t\tID:      \"block-id-1\",\n\t\t\tBoardID: board1.ID,\n\t\t\tTitle:   initialTitle,\n\t\t}\n\t\trequire.NoError(t, th.Server.App().InsertBlock(newBlock1, userID))\n\t\tblock1, err := th.Server.App().GetBlockByID(\"block-id-1\")\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, block1)\n\n\t\tnewBlock2 := &model.Block{\n\t\t\tID:      \"block-id-2\",\n\t\t\tBoardID: board2.ID,\n\t\t\tTitle:   initialTitle,\n\t\t}\n\t\trequire.NoError(t, th.Server.App().InsertBlock(newBlock2, userID))\n\t\tblock2, err := th.Server.App().GetBlockByID(\"block-id-2\")\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, block2)\n\n\t\tpbab := &model.PatchBoardsAndBlocks{\n\t\t\tBoardIDs: []string{board1.ID, board2.ID},\n\t\t\tBoardPatches: []*model.BoardPatch{\n\t\t\t\t{Title: &newTitle},\n\t\t\t\t{Title: &newTitle},\n\t\t\t},\n\t\t\tBlockIDs: []string{block1.ID, block2.ID},\n\t\t\tBlockPatches: []*model.BlockPatch{\n\t\t\t\t{Title: &newTitle},\n\t\t\t\t{Title: &newTitle},\n\t\t\t},\n\t\t}\n\n\t\tbab, resp := th.Client.PatchBoardsAndBlocks(pbab)\n\t\tth.CheckOK(resp)\n\t\trequire.NotNil(t, bab)\n\t\trequire.Len(t, bab.Boards, 2)\n\t\trequire.Len(t, bab.Blocks, 2)\n\n\t\t// ensure that the entities have been updated\n\t\trBoard1, err := th.Server.App().GetBoard(board1.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, newTitle, rBoard1.Title)\n\t\trBlock1, err := th.Server.App().GetBlockByID(block1.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, newTitle, rBlock1.Title)\n\n\t\trBoard2, err := th.Server.App().GetBoard(board2.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, newTitle, rBoard2.Title)\n\t\trBlock2, err := th.Server.App().GetBlockByID(block2.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, newTitle, rBlock2.Title)\n\t})\n}\n\nfunc TestDeleteBoardsAndBlocks(t *testing.T) {\n\tteamID := \"team-id\"\n\n\tt.Run(\"a non authenticated user should be rejected\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).Start()\n\t\tdefer th.TearDown()\n\n\t\tdbab := &model.DeleteBoardsAndBlocks{}\n\n\t\tsuccess, resp := th.Client.DeleteBoardsAndBlocks(dbab)\n\t\tth.CheckUnauthorized(resp)\n\t\trequire.False(t, success)\n\t})\n\n\tt.Run(\"invalid delete boards and blocks\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\t// a board and a block are required for the permission checks\n\t\tnewBoard := &model.Board{\n\t\t\tTeamID: teamID,\n\t\t\tType:   model.BoardTypeOpen,\n\t\t}\n\t\tboard, err := th.Server.App().CreateBoard(newBoard, th.GetUser1().ID, true)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, board)\n\n\t\tnewBlock := &model.Block{\n\t\t\tID:      \"block-id-1\",\n\t\t\tBoardID: board.ID,\n\t\t\tTitle:   \"title\",\n\t\t}\n\t\trequire.NoError(t, th.Server.App().InsertBlock(newBlock, th.GetUser1().ID))\n\t\tblock, err := th.Server.App().GetBlockByID(newBlock.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, block)\n\n\t\tt.Run(\"no boards\", func(t *testing.T) {\n\t\t\tdbab := &model.DeleteBoardsAndBlocks{\n\t\t\t\tBlocks: []string{block.ID},\n\t\t\t}\n\n\t\t\tsuccess, resp := th.Client.DeleteBoardsAndBlocks(dbab)\n\t\t\tth.CheckBadRequest(resp)\n\t\t\trequire.False(t, success)\n\t\t})\n\n\t\tt.Run(\"boards from different teams\", func(t *testing.T) {\n\t\t\tnewOtherTeamsBoard := &model.Board{\n\t\t\t\tTeamID: \"another-team-id\",\n\t\t\t\tType:   model.BoardTypeOpen,\n\t\t\t}\n\t\t\totherTeamsBoard, err := th.Server.App().CreateBoard(newOtherTeamsBoard, th.GetUser1().ID, true)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NotNil(t, board)\n\n\t\t\tdbab := &model.DeleteBoardsAndBlocks{\n\t\t\t\tBoards: []string{board.ID, otherTeamsBoard.ID},\n\t\t\t\tBlocks: []string{\"block-id-1\"},\n\t\t\t}\n\n\t\t\tsuccess, resp := th.Client.DeleteBoardsAndBlocks(dbab)\n\t\t\tth.CheckBadRequest(resp)\n\t\t\trequire.False(t, success)\n\t\t})\n\t})\n\n\tt.Run(\"if the user has no permissions to one of the boards, nothing should be deleted\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\t// the user is an admin of the first board\n\t\tnewBoard1 := &model.Board{\n\t\t\tType:   model.BoardTypeOpen,\n\t\t\tTeamID: \"team_id_1\",\n\t\t}\n\t\tboard1, err := th.Server.App().CreateBoard(newBoard1, th.GetUser1().ID, true)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, board1)\n\n\t\t// but not of the second\n\t\tnewBoard2 := &model.Board{\n\t\t\tType:   model.BoardTypeOpen,\n\t\t\tTeamID: \"team_id_1\",\n\t\t}\n\t\tboard2, err := th.Server.App().CreateBoard(newBoard2, th.GetUser1().ID, false)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, board2)\n\n\t\tdbab := &model.DeleteBoardsAndBlocks{\n\t\t\tBoards: []string{board1.ID, board2.ID},\n\t\t\tBlocks: []string{\"block-id-1\"},\n\t\t}\n\n\t\tsuccess, resp := th.Client.DeleteBoardsAndBlocks(dbab)\n\t\tth.CheckForbidden(resp)\n\t\trequire.False(t, success)\n\t})\n\n\tt.Run(\"all boards and blocks should be deleted if the request is correct\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tnewBab := &model.BoardsAndBlocks{\n\t\t\tBoards: []*model.Board{\n\t\t\t\t{ID: \"board-id-1\", Title: \"public board\", TeamID: teamID, Type: model.BoardTypeOpen},\n\t\t\t\t{ID: \"board-id-2\", Title: \"private board\", TeamID: teamID, Type: model.BoardTypePrivate},\n\t\t\t},\n\t\t\tBlocks: []*model.Block{\n\t\t\t\t{ID: \"block-id-1\", Title: \"block 1\", BoardID: \"board-id-1\", Type: model.TypeCard, CreateAt: 1, UpdateAt: 1},\n\t\t\t\t{ID: \"block-id-2\", Title: \"block 2\", BoardID: \"board-id-2\", Type: model.TypeCard, CreateAt: 1, UpdateAt: 1},\n\t\t\t},\n\t\t}\n\n\t\tbab, err := th.Server.App().CreateBoardsAndBlocks(newBab, th.GetUser1().ID, true)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, bab.Boards, 2)\n\t\trequire.Len(t, bab.Blocks, 2)\n\n\t\t// ensure that the entities have been successfully created\n\t\tboard1, err := th.Server.App().GetBoard(\"board-id-1\")\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, board1)\n\t\tblock1, err := th.Server.App().GetBlockByID(\"block-id-1\")\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, block1)\n\n\t\tboard2, err := th.Server.App().GetBoard(\"board-id-2\")\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, board2)\n\t\tblock2, err := th.Server.App().GetBlockByID(\"block-id-2\")\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, block2)\n\n\t\t// call the API to delete boards and blocks\n\t\tdbab := &model.DeleteBoardsAndBlocks{\n\t\t\tBoards: []string{\"board-id-1\", \"board-id-2\"},\n\t\t\tBlocks: []string{\"block-id-1\", \"block-id-2\"},\n\t\t}\n\n\t\tsuccess, resp := th.Client.DeleteBoardsAndBlocks(dbab)\n\t\tth.CheckOK(resp)\n\t\trequire.True(t, success)\n\n\t\t// ensure that the entities have been successfully deleted\n\t\tboard1, err = th.Server.App().GetBoard(\"board-id-1\")\n\t\trequire.Error(t, err)\n\t\trequire.True(t, model.IsErrNotFound(err))\n\t\trequire.Nil(t, board1)\n\t\tblock1, err = th.Server.App().GetBlockByID(\"block-id-1\")\n\t\trequire.Error(t, err)\n\t\trequire.True(t, model.IsErrNotFound(err))\n\t\trequire.Nil(t, block1)\n\n\t\tboard2, err = th.Server.App().GetBoard(\"board-id-2\")\n\t\trequire.Error(t, err)\n\t\trequire.True(t, model.IsErrNotFound(err))\n\t\trequire.Nil(t, board2)\n\t\tblock2, err = th.Server.App().GetBlockByID(\"block-id-2\")\n\t\trequire.Error(t, err)\n\t\trequire.True(t, model.IsErrNotFound(err))\n\t\trequire.Nil(t, block2)\n\t})\n}\n"
  },
  {
    "path": "server/integrationtests/cards_test.go",
    "content": "package integrationtests\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\t\"testing\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestCreateCard(t *testing.T) {\n\tt.Run(\"a non authenticated user should be rejected\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tboard := th.CreateBoard(testTeamID, model.BoardTypeOpen)\n\t\tth.Logout(th.Client)\n\n\t\tcard := &model.Card{\n\t\t\tTitle: \"basic card\",\n\t\t}\n\t\tcardNew, resp := th.Client.CreateCard(board.ID, card, false)\n\t\tth.CheckUnauthorized(resp)\n\t\trequire.Nil(t, cardNew)\n\t})\n\n\tt.Run(\"good\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tboard := th.CreateBoard(testTeamID, model.BoardTypeOpen)\n\t\tcontentOrder := []string{utils.NewID(utils.IDTypeBlock), utils.NewID(utils.IDTypeBlock), utils.NewID(utils.IDTypeBlock)}\n\n\t\tcard := &model.Card{\n\t\t\tTitle:        \"test card 1\",\n\t\t\tIcon:         \"😱\",\n\t\t\tContentOrder: contentOrder,\n\t\t}\n\n\t\tcardNew, resp := th.Client.CreateCard(board.ID, card, false)\n\t\trequire.NoError(t, resp.Error)\n\t\tth.CheckOK(resp)\n\t\trequire.NotNil(t, cardNew)\n\n\t\trequire.Equal(t, board.ID, cardNew.BoardID)\n\t\trequire.Equal(t, \"test card 1\", cardNew.Title)\n\t\trequire.Equal(t, \"😱\", cardNew.Icon)\n\t\trequire.Equal(t, contentOrder, cardNew.ContentOrder)\n\t})\n\n\tt.Run(\"invalid card\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tboard := th.CreateBoard(testTeamID, model.BoardTypeOpen)\n\n\t\tcard := &model.Card{\n\t\t\tTitle: \"too many emoji's\",\n\t\t\tIcon:  \"😱😱😱😱\",\n\t\t}\n\n\t\tcardNew, resp := th.Client.CreateCard(board.ID, card, false)\n\t\trequire.Error(t, resp.Error)\n\t\trequire.Nil(t, cardNew)\n\t})\n}\n\nfunc TestGetCards(t *testing.T) {\n\tth := SetupTestHelper(t).InitBasic()\n\tdefer th.TearDown()\n\n\tboard := th.CreateBoard(testTeamID, model.BoardTypeOpen)\n\tuserID := th.GetUser1().ID\n\n\tconst cardCount = 25\n\n\t// make some cards with content\n\tfor i := 0; i < cardCount; i++ {\n\t\tcard := &model.Card{\n\t\t\tBoardID:    board.ID,\n\t\t\tCreatedBy:  userID,\n\t\t\tModifiedBy: userID,\n\t\t\tTitle:      fmt.Sprintf(\"%d\", i),\n\t\t}\n\t\tcardNew, resp := th.Client.CreateCard(board.ID, card, true)\n\t\tth.CheckOK(resp)\n\n\t\tblocks := make([]*model.Block, 0, 3)\n\t\tfor j := 0; j < 3; j++ {\n\t\t\tnow := model.GetMillis()\n\t\t\tblock := &model.Block{\n\t\t\t\tID:         utils.NewID(utils.IDTypeBlock),\n\t\t\t\tParentID:   cardNew.ID,\n\t\t\t\tCreatedBy:  userID,\n\t\t\t\tModifiedBy: userID,\n\t\t\t\tCreateAt:   now,\n\t\t\t\tUpdateAt:   now,\n\t\t\t\tSchema:     1,\n\t\t\t\tType:       model.TypeText,\n\t\t\t\tTitle:      fmt.Sprintf(\"text %d for card %d\", j, i),\n\t\t\t\tBoardID:    board.ID,\n\t\t\t}\n\t\t\tblocks = append(blocks, block)\n\t\t}\n\t\t_, resp = th.Client.InsertBlocks(board.ID, blocks, true)\n\t\tth.CheckOK(resp)\n\t}\n\n\tt.Run(\"fetch all cards\", func(t *testing.T) {\n\t\tcards, resp := th.Client.GetCards(board.ID, 0, -1)\n\t\tth.CheckOK(resp)\n\t\tassert.Len(t, cards, cardCount)\n\t})\n\n\tt.Run(\"fetch with pagination\", func(t *testing.T) {\n\t\tcardNums := make(map[int]struct{})\n\n\t\t// return first 10\n\t\tcards, resp := th.Client.GetCards(board.ID, 0, 10)\n\t\tth.CheckOK(resp)\n\t\tassert.Len(t, cards, 10)\n\t\tfor _, card := range cards {\n\t\t\tcardNum, err := strconv.Atoi(card.Title)\n\t\t\trequire.NoError(t, err)\n\t\t\tcardNums[cardNum] = struct{}{}\n\t\t}\n\n\t\t// return second 10\n\t\tcards, resp = th.Client.GetCards(board.ID, 1, 10)\n\t\tth.CheckOK(resp)\n\t\tassert.Len(t, cards, 10)\n\t\tfor _, card := range cards {\n\t\t\tcardNum, err := strconv.Atoi(card.Title)\n\t\t\trequire.NoError(t, err)\n\t\t\tcardNums[cardNum] = struct{}{}\n\t\t}\n\n\t\t// return remaining 5\n\t\tcards, resp = th.Client.GetCards(board.ID, 2, 10)\n\t\tth.CheckOK(resp)\n\t\tassert.Len(t, cards, 5)\n\t\tfor _, card := range cards {\n\t\t\tcardNum, err := strconv.Atoi(card.Title)\n\t\t\trequire.NoError(t, err)\n\t\t\tcardNums[cardNum] = struct{}{}\n\t\t}\n\n\t\t// make sure all card numbers were returned\n\t\tassert.Len(t, cardNums, cardCount)\n\t\tfor i := 0; i < cardCount; i++ {\n\t\t\t_, ok := cardNums[i]\n\t\t\tassert.True(t, ok)\n\t\t}\n\t})\n\n\tt.Run(\"a non authenticated user should be rejected\", func(t *testing.T) {\n\t\tth.Logout(th.Client)\n\n\t\tcards, resp := th.Client.GetCards(board.ID, 0, 10)\n\t\tth.CheckUnauthorized(resp)\n\t\trequire.Nil(t, cards)\n\t})\n}\n\nfunc TestPatchCard(t *testing.T) {\n\tt.Run(\"a non authenticated user should be rejected\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\t_, cards := th.CreateBoardAndCards(testTeamID, model.BoardTypeOpen, 1)\n\t\tcard := cards[0]\n\n\t\tth.Logout(th.Client)\n\n\t\tnewTitle := \"another title\"\n\t\tpatch := &model.CardPatch{\n\t\t\tTitle: &newTitle,\n\t\t}\n\n\t\tpatchedCard, resp := th.Client.PatchCard(card.ID, patch, false)\n\t\tth.CheckUnauthorized(resp)\n\t\trequire.Nil(t, patchedCard)\n\t})\n\n\tt.Run(\"good\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tboard, cards := th.CreateBoardAndCards(testTeamID, model.BoardTypeOpen, 1)\n\t\tcard := cards[0]\n\n\t\t// Patch the card\n\t\tnewTitle := \"another title\"\n\t\tnewIcon := \"🐿\"\n\t\tnewContentOrder := reverse(card.ContentOrder)\n\t\tupdatedProps := modifyCardProps(card.Properties)\n\t\tpatch := &model.CardPatch{\n\t\t\tTitle:             &newTitle,\n\t\t\tIcon:              &newIcon,\n\t\t\tContentOrder:      &newContentOrder,\n\t\t\tUpdatedProperties: updatedProps,\n\t\t}\n\n\t\tpatchedCard, resp := th.Client.PatchCard(card.ID, patch, false)\n\n\t\tth.CheckOK(resp)\n\t\trequire.NotNil(t, patchedCard)\n\t\trequire.Equal(t, board.ID, patchedCard.BoardID)\n\t\trequire.Equal(t, newTitle, patchedCard.Title)\n\t\trequire.Equal(t, newIcon, patchedCard.Icon)\n\t\trequire.NotEqual(t, card.ContentOrder, patchedCard.ContentOrder)\n\t\trequire.ElementsMatch(t, card.ContentOrder, patchedCard.ContentOrder)\n\t\trequire.EqualValues(t, updatedProps, patchedCard.Properties)\n\t})\n\n\tt.Run(\"invalid card patch\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\t_, cards := th.CreateBoardAndCards(testTeamID, model.BoardTypeOpen, 1)\n\t\tcard := cards[0]\n\n\t\t// Bad patch  (too many emoji)\n\t\tnewIcon := \"🐿🐿🐿\"\n\t\tpatch := &model.CardPatch{\n\t\t\tIcon: &newIcon,\n\t\t}\n\n\t\tcardNew, resp := th.Client.PatchCard(card.ID, patch, false)\n\t\trequire.Error(t, resp.Error)\n\t\trequire.Nil(t, cardNew)\n\t})\n}\n\nfunc TestGetCard(t *testing.T) {\n\tt.Run(\"a non authenticated user should be rejected\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\t_, cards := th.CreateBoardAndCards(testTeamID, model.BoardTypeOpen, 1)\n\t\tcard := cards[0]\n\n\t\tth.Logout(th.Client)\n\n\t\tcardFetched, resp := th.Client.GetCard(card.ID)\n\t\tth.CheckUnauthorized(resp)\n\t\trequire.Nil(t, cardFetched)\n\t})\n\n\tt.Run(\"good\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tboard, cards := th.CreateBoardAndCards(testTeamID, model.BoardTypeOpen, 1)\n\t\tcard := cards[0]\n\n\t\tcardFetched, resp := th.Client.GetCard(card.ID)\n\n\t\tth.CheckOK(resp)\n\t\trequire.NotNil(t, cardFetched)\n\t\trequire.Equal(t, board.ID, cardFetched.BoardID)\n\t\trequire.Equal(t, card.Title, cardFetched.Title)\n\t\trequire.Equal(t, card.Icon, cardFetched.Icon)\n\t\trequire.Equal(t, card.ContentOrder, cardFetched.ContentOrder)\n\t\trequire.EqualValues(t, card.Properties, cardFetched.Properties)\n\t})\n}\n\n// Helpers.\nfunc reverse(src []string) []string {\n\tout := make([]string, 0, len(src))\n\tfor i := len(src) - 1; i >= 0; i-- {\n\t\tout = append(out, src[i])\n\t}\n\treturn out\n}\n\nfunc modifyCardProps(m map[string]any) map[string]any {\n\tout := make(map[string]any)\n\tfor k := range m {\n\t\tout[k] = utils.NewID(utils.IDTypeBlock)\n\t}\n\treturn out\n}\n"
  },
  {
    "path": "server/integrationtests/clienttestlib.go",
    "content": "package integrationtests\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/mattermost/focalboard/server/client\"\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/server\"\n\t\"github.com/mattermost/focalboard/server/services/auth\"\n\t\"github.com/mattermost/focalboard/server/services/config\"\n\t\"github.com/mattermost/focalboard/server/services/permissions/localpermissions\"\n\t\"github.com/mattermost/focalboard/server/services/permissions/mmpermissions\"\n\t\"github.com/mattermost/focalboard/server/services/store\"\n\t\"github.com/mattermost/focalboard/server/services/store/sqlstore\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n\n\tmmModel \"github.com/mattermost/mattermost/server/public/model\"\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nconst (\n\tuser1Username = \"user1\"\n\tuser2Username = \"user2\"\n\tpassword      = \"Pa$$word\"\n\ttestTeamID    = \"team-id\"\n)\n\nconst (\n\tuserAnon         string = \"anon\"\n\tuserNoTeamMember string = \"no-team-member\"\n\tuserTeamMember   string = \"team-member\"\n\tuserViewer       string = \"viewer\"\n\tuserCommenter    string = \"commenter\"\n\tuserEditor       string = \"editor\"\n\tuserAdmin        string = \"admin\"\n\tuserGuest        string = \"guest\"\n)\n\nvar (\n\tuserAnonID         = userAnon\n\tuserNoTeamMemberID = userNoTeamMember\n\tuserTeamMemberID   = userTeamMember\n\tuserViewerID       = userViewer\n\tuserCommenterID    = userCommenter\n\tuserEditorID       = userEditor\n\tuserAdminID        = userAdmin\n\tuserGuestID        = userGuest\n)\n\ntype LicenseType int\n\nconst (\n\tLicenseNone         LicenseType = iota // 0\n\tLicenseProfessional                    // 1\n\tLicenseEnterprise                      // 2\n)\n\ntype TestHelper struct {\n\tT       *testing.T\n\tServer  *server.Server\n\tClient  *client.Client\n\tClient2 *client.Client\n\n\torigEnvUnitTesting string\n}\n\ntype FakePermissionPluginAPI struct{}\n\nfunc (*FakePermissionPluginAPI) HasPermissionTo(userID string, permission *mmModel.Permission) bool {\n\treturn userID == userAdmin\n}\n\nfunc (*FakePermissionPluginAPI) HasPermissionToTeam(userID string, teamID string, permission *mmModel.Permission) bool {\n\tif permission.Id == model.PermissionManageTeam.Id {\n\t\treturn false\n\t}\n\tif userID == userNoTeamMember {\n\t\treturn false\n\t}\n\tif teamID == \"empty-team\" {\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc (*FakePermissionPluginAPI) HasPermissionToChannel(userID string, channelID string, permission *mmModel.Permission) bool {\n\treturn channelID == \"valid-channel-id\" || channelID == \"valid-channel-id-2\"\n}\n\nfunc getTestConfig() (*config.Configuration, error) {\n\tdbType, connectionString, err := sqlstore.PrepareNewTestDatabase()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tlogging := `\n\t{\n\t\t\"testing\": {\n\t\t\t\"type\": \"console\",\n\t\t\t\"options\": {\n\t\t\t\t\"out\": \"stdout\"\n\t\t\t},\n\t\t\t\"format\": \"plain\",\n\t\t\t\"format_options\": {\n\t\t\t\t\"delim\": \"  \"\n\t\t\t},\n\t\t\t\"levels\": [\n\t\t\t\t{\"id\": 5, \"name\": \"debug\"},\n\t\t\t\t{\"id\": 4, \"name\": \"info\"},\n\t\t\t\t{\"id\": 3, \"name\": \"warn\"},\n\t\t\t\t{\"id\": 2, \"name\": \"error\", \"stacktrace\": true},\n\t\t\t\t{\"id\": 1, \"name\": \"fatal\", \"stacktrace\": true},\n\t\t\t\t{\"id\": 0, \"name\": \"panic\", \"stacktrace\": true}\n\t\t\t]\n\t\t}\n\t}`\n\n\treturn &config.Configuration{\n\t\tServerRoot:        \"http://localhost:8888\",\n\t\tPort:              8888,\n\t\tDBType:            dbType,\n\t\tDBConfigString:    connectionString,\n\t\tDBTablePrefix:     \"test_\",\n\t\tWebPath:           \"./pack\",\n\t\tFilesDriver:       \"local\",\n\t\tFilesPath:         \"./files\",\n\t\tLoggingCfgJSON:    logging,\n\t\tSessionExpireTime: int64(30 * time.Second),\n\t\tAuthMode:          \"native\",\n\t}, nil\n}\n\nfunc newTestServer(singleUserToken string) *server.Server {\n\treturn newTestServerWithLicense(singleUserToken, LicenseNone)\n}\n\nfunc newTestServerWithLicense(singleUserToken string, licenseType LicenseType) *server.Server {\n\tcfg, err := getTestConfig()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tlogger, _ := mlog.NewLogger()\n\tif err = logger.Configure(\"\", cfg.LoggingCfgJSON, nil); err != nil {\n\t\tpanic(err)\n\t}\n\tsingleUser := len(singleUserToken) > 0\n\tinnerStore, err := server.NewStore(cfg, singleUser, logger)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tvar db store.Store\n\n\tswitch licenseType {\n\tcase LicenseProfessional:\n\t\tdb = NewTestProfessionalStore(innerStore)\n\tcase LicenseEnterprise:\n\t\tdb = NewTestEnterpriseStore(innerStore)\n\tcase LicenseNone:\n\t\tfallthrough\n\tdefault:\n\t\tdb = innerStore\n\t}\n\n\tpermissionsService := localpermissions.New(db, logger)\n\n\tparams := server.Params{\n\t\tCfg:                cfg,\n\t\tSingleUserToken:    singleUserToken,\n\t\tDBStore:            db,\n\t\tLogger:             logger,\n\t\tPermissionsService: permissionsService,\n\t}\n\n\tsrv, err := server.New(params)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn srv\n}\n\nfunc NewTestServerPluginMode() *server.Server {\n\tcfg, err := getTestConfig()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tcfg.AuthMode = \"mattermost\"\n\tcfg.EnablePublicSharedBoards = true\n\n\tlogger, _ := mlog.NewLogger()\n\tif err = logger.Configure(\"\", cfg.LoggingCfgJSON, nil); err != nil {\n\t\tpanic(err)\n\t}\n\tinnerStore, err := server.NewStore(cfg, false, logger)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tdb := NewPluginTestStore(innerStore)\n\n\tpermissionsService := mmpermissions.New(db, &FakePermissionPluginAPI{}, logger)\n\n\tparams := server.Params{\n\t\tCfg:                cfg,\n\t\tDBStore:            db,\n\t\tLogger:             logger,\n\t\tPermissionsService: permissionsService,\n\t}\n\n\tsrv, err := server.New(params)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn srv\n}\n\nfunc newTestServerLocalMode() *server.Server {\n\tcfg, err := getTestConfig()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tcfg.EnablePublicSharedBoards = true\n\n\tlogger, _ := mlog.NewLogger()\n\tif err = logger.Configure(\"\", cfg.LoggingCfgJSON, nil); err != nil {\n\t\tpanic(err)\n\t}\n\n\tdb, err := server.NewStore(cfg, false, logger)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tpermissionsService := localpermissions.New(db, logger)\n\n\tparams := server.Params{\n\t\tCfg:                cfg,\n\t\tDBStore:            db,\n\t\tLogger:             logger,\n\t\tPermissionsService: permissionsService,\n\t}\n\n\tsrv, err := server.New(params)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Reduce password has strength for unit tests to dramatically speed up account creation and login\n\tauth.PasswordHashStrength = 4\n\n\treturn srv\n}\n\nfunc SetupTestHelperWithToken(t *testing.T) *TestHelper {\n\torigUnitTesting := os.Getenv(\"FOCALBOARD_UNIT_TESTING\")\n\tos.Setenv(\"FOCALBOARD_UNIT_TESTING\", \"1\")\n\n\tsessionToken := \"TESTTOKEN\"\n\n\tth := &TestHelper{\n\t\tT:                  t,\n\t\torigEnvUnitTesting: origUnitTesting,\n\t}\n\n\tth.Server = newTestServer(sessionToken)\n\tth.Client = client.NewClient(th.Server.Config().ServerRoot, sessionToken)\n\tth.Client2 = client.NewClient(th.Server.Config().ServerRoot, sessionToken)\n\treturn th\n}\n\nfunc SetupTestHelper(t *testing.T) *TestHelper {\n\treturn SetupTestHelperWithLicense(t, LicenseNone)\n}\n\nfunc SetupTestHelperPluginMode(t *testing.T) *TestHelper {\n\torigUnitTesting := os.Getenv(\"FOCALBOARD_UNIT_TESTING\")\n\tos.Setenv(\"FOCALBOARD_UNIT_TESTING\", \"1\")\n\n\tth := &TestHelper{\n\t\tT:                  t,\n\t\torigEnvUnitTesting: origUnitTesting,\n\t}\n\n\tth.Server = NewTestServerPluginMode()\n\tth.Start()\n\treturn th\n}\n\nfunc SetupTestHelperLocalMode(t *testing.T) *TestHelper {\n\torigUnitTesting := os.Getenv(\"FOCALBOARD_UNIT_TESTING\")\n\tos.Setenv(\"FOCALBOARD_UNIT_TESTING\", \"1\")\n\n\tth := &TestHelper{\n\t\tT:                  t,\n\t\torigEnvUnitTesting: origUnitTesting,\n\t}\n\n\tth.Server = newTestServerLocalMode()\n\tth.Start()\n\treturn th\n}\n\nfunc SetupTestHelperWithLicense(t *testing.T, licenseType LicenseType) *TestHelper {\n\torigUnitTesting := os.Getenv(\"FOCALBOARD_UNIT_TESTING\")\n\tos.Setenv(\"FOCALBOARD_UNIT_TESTING\", \"1\")\n\n\tth := &TestHelper{\n\t\tT:                  t,\n\t\torigEnvUnitTesting: origUnitTesting,\n\t}\n\n\tth.Server = newTestServerWithLicense(\"\", licenseType)\n\tth.Client = client.NewClient(th.Server.Config().ServerRoot, \"\")\n\tth.Client2 = client.NewClient(th.Server.Config().ServerRoot, \"\")\n\treturn th\n}\n\n// Start starts the test server and ensures that it's correctly\n// responding to requests before returning.\nfunc (th *TestHelper) Start() *TestHelper {\n\tgo func() {\n\t\tif err := th.Server.Start(); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}()\n\n\tfor {\n\t\tURL := th.Server.Config().ServerRoot\n\t\tth.Server.Logger().Info(\"Polling server\", mlog.String(\"url\", URL))\n\t\tresp, err := http.Get(URL) //nolint:gosec\n\t\tif err != nil {\n\t\t\tth.Server.Logger().Error(\"Polling failed\", mlog.Err(err))\n\t\t\ttime.Sleep(100 * time.Millisecond)\n\t\t\tcontinue\n\t\t}\n\t\tresp.Body.Close()\n\n\t\t// Currently returns 404\n\t\t// if resp.StatusCode != http.StatusOK {\n\t\t// \tth.Server.Logger().Error(\"Not OK\", mlog.Int(\"statusCode\", resp.StatusCode))\n\t\t// \tcontinue\n\t\t// }\n\n\t\t// Reached this point: server is up and running!\n\t\tth.Server.Logger().Info(\"Server ping OK\", mlog.Int(\"statusCode\", resp.StatusCode))\n\n\t\tbreak\n\t}\n\n\treturn th\n}\n\n// InitBasic starts the test server and initializes the clients of the\n// helper, registering them and logging them into the system.\nfunc (th *TestHelper) InitBasic() *TestHelper {\n\t// Reduce password has strength for unit tests to dramatically speed up account creation and login\n\tauth.PasswordHashStrength = 4\n\n\tth.Start()\n\n\t// user1\n\tth.RegisterAndLogin(th.Client, user1Username, \"user1@sample.com\", password, \"\")\n\n\t// get token\n\tteam, resp := th.Client.GetTeam(model.GlobalTeamID)\n\tth.CheckOK(resp)\n\trequire.NotNil(th.T, team)\n\trequire.NotNil(th.T, team.SignupToken)\n\n\t// user2\n\tth.RegisterAndLogin(th.Client2, user2Username, \"user2@sample.com\", password, team.SignupToken)\n\n\treturn th\n}\n\nvar ErrRegisterFail = errors.New(\"register failed\")\n\nfunc (th *TestHelper) TearDown() {\n\tos.Setenv(\"FOCALBOARD_UNIT_TESTING\", th.origEnvUnitTesting)\n\n\tlogger := th.Server.Logger()\n\n\tif l, ok := logger.(*mlog.Logger); ok {\n\t\tdefer func() { _ = l.Shutdown() }()\n\t}\n\n\terr := th.Server.Shutdown()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tos.RemoveAll(th.Server.Config().FilesPath)\n\n\tif err := os.Remove(th.Server.Config().DBConfigString); err == nil {\n\t\tlogger.Debug(\"Removed test database\", mlog.String(\"file\", th.Server.Config().DBConfigString))\n\t}\n}\n\nfunc (th *TestHelper) RegisterAndLogin(client *client.Client, username, email, password, token string) {\n\treq := &model.RegisterRequest{\n\t\tUsername: username,\n\t\tEmail:    email,\n\t\tPassword: password,\n\t\tToken:    token,\n\t}\n\n\tsuccess, resp := th.Client.Register(req)\n\tth.CheckOK(resp)\n\trequire.True(th.T, success)\n\n\tth.Login(client, username, password)\n}\n\nfunc (th *TestHelper) Login(client *client.Client, username, password string) {\n\treq := &model.LoginRequest{\n\t\tType:     \"normal\",\n\t\tUsername: username,\n\t\tPassword: password,\n\t}\n\tdata, resp := client.Login(req)\n\tth.CheckOK(resp)\n\trequire.NotNil(th.T, data)\n}\n\nfunc (th *TestHelper) Login1() {\n\tth.Login(th.Client, user1Username, password)\n}\n\nfunc (th *TestHelper) Login2() {\n\tth.Login(th.Client2, user2Username, password)\n}\n\nfunc (th *TestHelper) Logout(client *client.Client) {\n\tclient.Token = \"\"\n}\n\nfunc (th *TestHelper) Me(client *client.Client) *model.User {\n\tuser, resp := client.GetMe()\n\tth.CheckOK(resp)\n\trequire.NotNil(th.T, user)\n\treturn user\n}\n\nfunc (th *TestHelper) CreateBoard(teamID string, boardType model.BoardType) *model.Board {\n\tnewBoard := &model.Board{\n\t\tTeamID: teamID,\n\t\tType:   boardType,\n\t}\n\tboard, resp := th.Client.CreateBoard(newBoard)\n\tth.CheckOK(resp)\n\treturn board\n}\n\nfunc (th *TestHelper) CreateBoards(teamID string, boardType model.BoardType, count int) []*model.Board {\n\tboards := make([]*model.Board, 0, count)\n\n\tfor i := 0; i < count; i++ {\n\t\tboard := th.CreateBoard(teamID, boardType)\n\t\tboards = append(boards, board)\n\t}\n\treturn boards\n}\n\nfunc (th *TestHelper) CreateCategory(category model.Category) *model.Category {\n\tcat, resp := th.Client.CreateCategory(category)\n\tth.CheckOK(resp)\n\treturn cat\n}\n\nfunc (th *TestHelper) UpdateCategoryBoard(teamID, categoryID, boardID string) {\n\tresponse := th.Client.UpdateCategoryBoard(teamID, categoryID, boardID)\n\tth.CheckOK(response)\n}\n\nfunc (th *TestHelper) CreateBoardAndCards(teamdID string, boardType model.BoardType, numCards int) (*model.Board, []*model.Card) {\n\tboard := th.CreateBoard(teamdID, boardType)\n\tcards := make([]*model.Card, 0, numCards)\n\tfor i := 0; i < numCards; i++ {\n\t\tcard := &model.Card{\n\t\t\tTitle:        fmt.Sprintf(\"test card %d\", i+1),\n\t\t\tContentOrder: []string{utils.NewID(utils.IDTypeBlock), utils.NewID(utils.IDTypeBlock), utils.NewID(utils.IDTypeBlock)},\n\t\t\tIcon:         \"😱\",\n\t\t\tProperties:   th.MakeCardProps(5),\n\t\t}\n\t\tnewCard, resp := th.Client.CreateCard(board.ID, card, true)\n\t\tth.CheckOK(resp)\n\t\tcards = append(cards, newCard)\n\t}\n\treturn board, cards\n}\n\nfunc (th *TestHelper) MakeCardProps(count int) map[string]any {\n\tprops := make(map[string]any)\n\tfor i := 0; i < count; i++ {\n\t\tprops[utils.NewID(utils.IDTypeBlock)] = utils.NewID(utils.IDTypeBlock)\n\t}\n\treturn props\n}\n\nfunc (th *TestHelper) GetUserCategoryBoards(teamID string) []model.CategoryBoards {\n\tcategoryBoards, response := th.Client.GetUserCategoryBoards(teamID)\n\tth.CheckOK(response)\n\treturn categoryBoards\n}\n\nfunc (th *TestHelper) DeleteCategory(teamID, categoryID string) {\n\tresponse := th.Client.DeleteCategory(teamID, categoryID)\n\tth.CheckOK(response)\n}\n\nfunc (th *TestHelper) GetUser1() *model.User {\n\treturn th.Me(th.Client)\n}\n\nfunc (th *TestHelper) GetUser2() *model.User {\n\treturn th.Me(th.Client2)\n}\n\nfunc (th *TestHelper) CheckOK(r *client.Response) {\n\trequire.Equal(th.T, http.StatusOK, r.StatusCode)\n\trequire.NoError(th.T, r.Error)\n}\n\nfunc (th *TestHelper) CheckBadRequest(r *client.Response) {\n\trequire.Equal(th.T, http.StatusBadRequest, r.StatusCode)\n\trequire.Error(th.T, r.Error)\n}\n\nfunc (th *TestHelper) CheckNotFound(r *client.Response) {\n\trequire.Equal(th.T, http.StatusNotFound, r.StatusCode)\n\trequire.Error(th.T, r.Error)\n}\n\nfunc (th *TestHelper) CheckUnauthorized(r *client.Response) {\n\trequire.Equal(th.T, http.StatusUnauthorized, r.StatusCode)\n\trequire.Error(th.T, r.Error)\n}\n\nfunc (th *TestHelper) CheckForbidden(r *client.Response) {\n\trequire.Equal(th.T, http.StatusForbidden, r.StatusCode)\n\trequire.Error(th.T, r.Error)\n}\n\nfunc (th *TestHelper) CheckRequestEntityTooLarge(r *client.Response) {\n\trequire.Equal(th.T, http.StatusRequestEntityTooLarge, r.StatusCode)\n\trequire.Error(th.T, r.Error)\n}\n\nfunc (th *TestHelper) CheckNotImplemented(r *client.Response) {\n\trequire.Equal(th.T, http.StatusNotImplemented, r.StatusCode)\n\trequire.Error(th.T, r.Error)\n}\n"
  },
  {
    "path": "server/integrationtests/compliance_test.go",
    "content": "package integrationtests\n\nimport (\n\t\"math\"\n\t\"os\"\n\t\"strconv\"\n\t\"testing\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nvar (\n\tOneHour int64 = 360000\n\tOneDay  int64 = OneHour * 24\n\tOneYear int64 = OneDay * 365\n)\n\nfunc setupTestHelperForCompliance(t *testing.T, complianceLicense bool) (*TestHelper, Clients) {\n\tos.Setenv(\"FOCALBOARD_UNIT_TESTING_COMPLIANCE\", strconv.FormatBool(complianceLicense))\n\n\tth := SetupTestHelperPluginMode(t)\n\tclients := setupClients(th)\n\n\tth.Client = clients.TeamMember\n\tth.Client2 = clients.TeamMember\n\n\treturn th, clients\n}\n\nfunc TestGetBoardsForCompliance(t *testing.T) {\n\tt.Run(\"missing Features.Compliance license should fail\", func(t *testing.T) {\n\t\tth, clients := setupTestHelperForCompliance(t, false)\n\t\tdefer th.TearDown()\n\n\t\t_ = th.CreateBoards(testTeamID, model.BoardTypeOpen, 2)\n\n\t\tbcr, resp := clients.Admin.GetBoardsForCompliance(testTeamID, 0, 0)\n\n\t\tth.CheckNotImplemented(resp)\n\t\trequire.Nil(t, bcr)\n\t})\n\n\tt.Run(\"a non authenticated user should be rejected\", func(t *testing.T) {\n\t\tth, clients := setupTestHelperForCompliance(t, true)\n\t\tdefer th.TearDown()\n\n\t\t_ = th.CreateBoards(testTeamID, model.BoardTypeOpen, 2)\n\t\tth.Logout(th.Client)\n\n\t\tbcr, resp := clients.Anon.GetBoardsForCompliance(testTeamID, 0, 0)\n\n\t\tth.CheckUnauthorized(resp)\n\t\trequire.Nil(t, bcr)\n\t})\n\n\tt.Run(\"a user without manage_system permission should be rejected\", func(t *testing.T) {\n\t\tth, clients := setupTestHelperForCompliance(t, true)\n\t\tdefer th.TearDown()\n\n\t\t_ = th.CreateBoards(testTeamID, model.BoardTypeOpen, 2)\n\n\t\tbcr, resp := clients.TeamMember.GetBoardsForCompliance(testTeamID, 0, 0)\n\n\t\tth.CheckUnauthorized(resp)\n\t\trequire.Nil(t, bcr)\n\t})\n\n\tt.Run(\"good call\", func(t *testing.T) {\n\t\tth, clients := setupTestHelperForCompliance(t, true)\n\t\tdefer th.TearDown()\n\n\t\tconst count = 10\n\t\t_ = th.CreateBoards(testTeamID, model.BoardTypeOpen, count)\n\n\t\tbcr, resp := clients.Admin.GetBoardsForCompliance(testTeamID, 0, 0)\n\t\tth.CheckOK(resp)\n\t\trequire.False(t, bcr.HasNext)\n\t\trequire.Len(t, bcr.Results, count)\n\t})\n\n\tt.Run(\"pagination\", func(t *testing.T) {\n\t\tth, clients := setupTestHelperForCompliance(t, true)\n\t\tdefer th.TearDown()\n\n\t\tconst count = 20\n\t\tconst perPage = 3\n\t\t_ = th.CreateBoards(testTeamID, model.BoardTypeOpen, count)\n\n\t\tboards := make([]*model.Board, 0, count)\n\t\tpage := 0\n\t\tfor {\n\t\t\tbcr, resp := clients.Admin.GetBoardsForCompliance(testTeamID, page, perPage)\n\t\t\tpage++\n\t\t\tth.CheckOK(resp)\n\t\t\tboards = append(boards, bcr.Results...)\n\t\t\tif !bcr.HasNext {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\trequire.Len(t, boards, count)\n\t\trequire.Equal(t, int(math.Floor((count/perPage)+1)), page)\n\t})\n\n\tt.Run(\"invalid teamID\", func(t *testing.T) {\n\t\tth, clients := setupTestHelperForCompliance(t, true)\n\t\tdefer th.TearDown()\n\n\t\t_ = th.CreateBoards(testTeamID, model.BoardTypeOpen, 2)\n\n\t\tbcr, resp := clients.Admin.GetBoardsForCompliance(utils.NewID(utils.IDTypeTeam), 0, 0)\n\n\t\tth.CheckBadRequest(resp)\n\t\trequire.Nil(t, bcr)\n\t})\n}\n\nfunc TestGetBoardsComplianceHistory(t *testing.T) {\n\tt.Run(\"missing Features.Compliance license should fail\", func(t *testing.T) {\n\t\tth, clients := setupTestHelperForCompliance(t, false)\n\t\tdefer th.TearDown()\n\n\t\t_ = th.CreateBoards(testTeamID, model.BoardTypeOpen, 2)\n\n\t\tbchr, resp := clients.Admin.GetBoardsComplianceHistory(utils.GetMillis()-OneDay, true, testTeamID, 0, 0)\n\n\t\tth.CheckNotImplemented(resp)\n\t\trequire.Nil(t, bchr)\n\t})\n\n\tt.Run(\"a non authenticated user should be rejected\", func(t *testing.T) {\n\t\tth, clients := setupTestHelperForCompliance(t, true)\n\t\tdefer th.TearDown()\n\n\t\t_ = th.CreateBoards(testTeamID, model.BoardTypeOpen, 2)\n\t\tth.Logout(th.Client)\n\n\t\tbchr, resp := clients.Anon.GetBoardsComplianceHistory(utils.GetMillis()-OneDay, true, testTeamID, 0, 0)\n\n\t\tth.CheckUnauthorized(resp)\n\t\trequire.Nil(t, bchr)\n\t})\n\n\tt.Run(\"a user without manage_system permission should be rejected\", func(t *testing.T) {\n\t\tth, clients := setupTestHelperForCompliance(t, true)\n\t\tdefer th.TearDown()\n\n\t\t_ = th.CreateBoards(testTeamID, model.BoardTypeOpen, 2)\n\n\t\tbchr, resp := clients.TeamMember.GetBoardsComplianceHistory(utils.GetMillis()-OneDay, true, testTeamID, 0, 0)\n\n\t\tth.CheckUnauthorized(resp)\n\t\trequire.Nil(t, bchr)\n\t})\n\n\tt.Run(\"good call, exclude deleted\", func(t *testing.T) {\n\t\tth, clients := setupTestHelperForCompliance(t, true)\n\t\tdefer th.TearDown()\n\n\t\tconst count = 10\n\t\tboards := th.CreateBoards(testTeamID, model.BoardTypeOpen, count)\n\n\t\tdeleted, resp := th.Client.DeleteBoard(boards[0].ID)\n\t\tth.CheckOK(resp)\n\t\trequire.True(t, deleted)\n\n\t\tdeleted, resp = th.Client.DeleteBoard(boards[1].ID)\n\t\tth.CheckOK(resp)\n\t\trequire.True(t, deleted)\n\n\t\tbchr, resp := clients.Admin.GetBoardsComplianceHistory(utils.GetMillis()-OneDay, false, testTeamID, 0, 0)\n\t\tth.CheckOK(resp)\n\t\trequire.False(t, bchr.HasNext)\n\t\trequire.Len(t, bchr.Results, count-2) // two boards deleted\n\t})\n\n\tt.Run(\"good call, include deleted\", func(t *testing.T) {\n\t\tth, clients := setupTestHelperForCompliance(t, true)\n\t\tdefer th.TearDown()\n\n\t\tconst count = 10\n\t\tboards := th.CreateBoards(testTeamID, model.BoardTypeOpen, count)\n\n\t\tdeleted, resp := th.Client.DeleteBoard(boards[0].ID)\n\t\tth.CheckOK(resp)\n\t\trequire.True(t, deleted)\n\n\t\tdeleted, resp = th.Client.DeleteBoard(boards[1].ID)\n\t\tth.CheckOK(resp)\n\t\trequire.True(t, deleted)\n\n\t\tbchr, resp := clients.Admin.GetBoardsComplianceHistory(utils.GetMillis()-OneDay, true, testTeamID, 0, 0)\n\t\tth.CheckOK(resp)\n\t\trequire.False(t, bchr.HasNext)\n\t\trequire.Len(t, bchr.Results, count+2) // both deleted boards have 2 history records each\n\t})\n\n\tt.Run(\"pagination\", func(t *testing.T) {\n\t\tth, clients := setupTestHelperForCompliance(t, true)\n\t\tdefer th.TearDown()\n\n\t\tconst count = 20\n\t\tconst perPage = 3\n\t\t_ = th.CreateBoards(testTeamID, model.BoardTypeOpen, count)\n\n\t\tboardHistory := make([]*model.BoardHistory, 0, count)\n\t\tpage := 0\n\t\tfor {\n\t\t\tbchr, resp := clients.Admin.GetBoardsComplianceHistory(utils.GetMillis()-OneDay, true, testTeamID, page, perPage)\n\t\t\tpage++\n\t\t\tth.CheckOK(resp)\n\t\t\tboardHistory = append(boardHistory, bchr.Results...)\n\t\t\tif !bchr.HasNext {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\trequire.Len(t, boardHistory, count)\n\t\trequire.Equal(t, int(math.Floor((count/perPage)+1)), page)\n\t})\n\n\tt.Run(\"invalid teamID\", func(t *testing.T) {\n\t\tth, clients := setupTestHelperForCompliance(t, true)\n\t\tdefer th.TearDown()\n\n\t\t_ = th.CreateBoards(testTeamID, model.BoardTypeOpen, 2)\n\n\t\tbchr, resp := clients.Admin.GetBoardsComplianceHistory(utils.GetMillis()-OneDay, true, utils.NewID(utils.IDTypeTeam), 0, 0)\n\n\t\tth.CheckBadRequest(resp)\n\t\trequire.Nil(t, bchr)\n\t})\n}\n\nfunc TestGetBlocksComplianceHistory(t *testing.T) {\n\tt.Run(\"missing Features.Compliance license should fail\", func(t *testing.T) {\n\t\tth, clients := setupTestHelperForCompliance(t, false)\n\t\tdefer th.TearDown()\n\n\t\tboard, _ := th.CreateBoardAndCards(testTeamID, model.BoardTypeOpen, 2)\n\n\t\tbchr, resp := clients.Admin.GetBlocksComplianceHistory(utils.GetMillis()-OneDay, true, testTeamID, board.ID, 0, 0)\n\n\t\tth.CheckNotImplemented(resp)\n\t\trequire.Nil(t, bchr)\n\t})\n\n\tt.Run(\"a non authenticated user should be rejected\", func(t *testing.T) {\n\t\tth, clients := setupTestHelperForCompliance(t, true)\n\t\tdefer th.TearDown()\n\n\t\tboard, _ := th.CreateBoardAndCards(testTeamID, model.BoardTypeOpen, 2)\n\n\t\tbchr, resp := clients.Anon.GetBlocksComplianceHistory(utils.GetMillis()-OneDay, true, testTeamID, board.ID, 0, 0)\n\n\t\tth.CheckUnauthorized(resp)\n\t\trequire.Nil(t, bchr)\n\t})\n\n\tt.Run(\"a user without manage_system permission should be rejected\", func(t *testing.T) {\n\t\tth, clients := setupTestHelperForCompliance(t, true)\n\t\tdefer th.TearDown()\n\n\t\tboard, _ := th.CreateBoardAndCards(testTeamID, model.BoardTypeOpen, 2)\n\n\t\tbchr, resp := clients.TeamMember.GetBlocksComplianceHistory(utils.GetMillis()-OneDay, true, testTeamID, board.ID, 0, 0)\n\n\t\tth.CheckUnauthorized(resp)\n\t\trequire.Nil(t, bchr)\n\t})\n\n\tt.Run(\"good call, exclude deleted\", func(t *testing.T) {\n\t\tth, clients := setupTestHelperForCompliance(t, true)\n\t\tdefer th.TearDown()\n\n\t\tconst count = 10\n\t\tboard, cards := th.CreateBoardAndCards(testTeamID, model.BoardTypeOpen, count)\n\n\t\tdeleted, resp := th.Client.DeleteBlock(board.ID, cards[0].ID, true)\n\t\tth.CheckOK(resp)\n\t\trequire.True(t, deleted)\n\n\t\tdeleted, resp = th.Client.DeleteBlock(board.ID, cards[1].ID, true)\n\t\tth.CheckOK(resp)\n\t\trequire.True(t, deleted)\n\n\t\tbchr, resp := clients.Admin.GetBlocksComplianceHistory(utils.GetMillis()-OneDay, false, testTeamID, board.ID, 0, 0)\n\t\tth.CheckOK(resp)\n\t\trequire.False(t, bchr.HasNext)\n\t\trequire.Len(t, bchr.Results, count-2) // 2 blocks deleted\n\t})\n\n\tt.Run(\"good call, include deleted\", func(t *testing.T) {\n\t\tth, clients := setupTestHelperForCompliance(t, true)\n\t\tdefer th.TearDown()\n\n\t\tconst count = 10\n\t\tboard, cards := th.CreateBoardAndCards(testTeamID, model.BoardTypeOpen, count)\n\n\t\tdeleted, resp := th.Client.DeleteBlock(board.ID, cards[0].ID, true)\n\t\tth.CheckOK(resp)\n\t\trequire.True(t, deleted)\n\n\t\tdeleted, resp = th.Client.DeleteBlock(board.ID, cards[1].ID, true)\n\t\tth.CheckOK(resp)\n\t\trequire.True(t, deleted)\n\n\t\tbchr, resp := clients.Admin.GetBlocksComplianceHistory(utils.GetMillis()-OneDay, true, testTeamID, board.ID, 0, 0)\n\t\tth.CheckOK(resp)\n\t\trequire.False(t, bchr.HasNext)\n\t\trequire.Len(t, bchr.Results, count+2) // both deleted boards have 2 history records each\n\t})\n\n\tt.Run(\"pagination\", func(t *testing.T) {\n\t\tth, clients := setupTestHelperForCompliance(t, true)\n\t\tdefer th.TearDown()\n\n\t\tconst count = 20\n\t\tconst perPage = 3\n\t\tboard, _ := th.CreateBoardAndCards(testTeamID, model.BoardTypeOpen, count)\n\n\t\tblockHistory := make([]*model.BlockHistory, 0, count)\n\t\tpage := 0\n\t\tfor {\n\t\t\tbchr, resp := clients.Admin.GetBlocksComplianceHistory(utils.GetMillis()-OneDay, true, testTeamID, board.ID, page, perPage)\n\t\t\tpage++\n\t\t\tth.CheckOK(resp)\n\t\t\tblockHistory = append(blockHistory, bchr.Results...)\n\t\t\tif !bchr.HasNext {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\trequire.Len(t, blockHistory, count)\n\t\trequire.Equal(t, int(math.Floor((count/perPage)+1)), page)\n\t})\n\n\tt.Run(\"invalid teamID\", func(t *testing.T) {\n\t\tth, clients := setupTestHelperForCompliance(t, true)\n\t\tdefer th.TearDown()\n\n\t\tboard, _ := th.CreateBoardAndCards(testTeamID, model.BoardTypeOpen, 2)\n\n\t\tbchr, resp := clients.Admin.GetBlocksComplianceHistory(utils.GetMillis()-OneDay, true, utils.NewID(utils.IDTypeTeam), board.ID, 0, 0)\n\n\t\tth.CheckBadRequest(resp)\n\t\trequire.Nil(t, bchr)\n\t})\n\n\tt.Run(\"invalid boardID\", func(t *testing.T) {\n\t\tth, clients := setupTestHelperForCompliance(t, true)\n\t\tdefer th.TearDown()\n\n\t\t_, _ = th.CreateBoardAndCards(testTeamID, model.BoardTypeOpen, 2)\n\n\t\tbchr, resp := clients.Admin.GetBlocksComplianceHistory(utils.GetMillis()-OneDay, true, testTeamID, utils.NewID(utils.IDTypeBoard), 0, 0)\n\n\t\tth.CheckBadRequest(resp)\n\t\trequire.Nil(t, bchr)\n\t})\n}\n"
  },
  {
    "path": "server/integrationtests/content_blocks_test.go",
    "content": "package integrationtests\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestMoveContentBlock(t *testing.T) {\n\tth := SetupTestHelperWithToken(t).Start()\n\tdefer th.TearDown()\n\n\tboard := th.CreateBoard(\"team-id\", model.BoardTypeOpen)\n\n\tcardID1 := utils.NewID(utils.IDTypeBlock)\n\tcardID2 := utils.NewID(utils.IDTypeBlock)\n\tcontentBlockID1 := utils.NewID(utils.IDTypeBlock)\n\tcontentBlockID2 := utils.NewID(utils.IDTypeBlock)\n\tcontentBlockID3 := utils.NewID(utils.IDTypeBlock)\n\tcontentBlockID4 := utils.NewID(utils.IDTypeBlock)\n\tcontentBlockID5 := utils.NewID(utils.IDTypeBlock)\n\tcontentBlockID6 := utils.NewID(utils.IDTypeBlock)\n\n\tcard1 := &model.Block{\n\t\tID:       cardID1,\n\t\tBoardID:  board.ID,\n\t\tCreateAt: 1,\n\t\tUpdateAt: 1,\n\t\tType:     model.TypeCard,\n\t\tFields: map[string]interface{}{\n\t\t\t\"contentOrder\": []string{contentBlockID1, contentBlockID2, contentBlockID3},\n\t\t},\n\t}\n\tcard2 := &model.Block{\n\t\tID:       cardID2,\n\t\tBoardID:  board.ID,\n\t\tCreateAt: 1,\n\t\tUpdateAt: 1,\n\t\tType:     model.TypeCard,\n\t\tFields: map[string]interface{}{\n\t\t\t\"contentOrder\": []string{contentBlockID4, contentBlockID5, contentBlockID6},\n\t\t},\n\t}\n\n\tcontentBlock1 := &model.Block{\n\t\tID:       contentBlockID1,\n\t\tBoardID:  board.ID,\n\t\tCreateAt: 1,\n\t\tUpdateAt: 1,\n\t\tType:     model.TypeCard,\n\t\tParentID: cardID1,\n\t}\n\tcontentBlock2 := &model.Block{\n\t\tID:       contentBlockID2,\n\t\tBoardID:  board.ID,\n\t\tCreateAt: 1,\n\t\tUpdateAt: 1,\n\t\tType:     model.TypeCard,\n\t\tParentID: cardID1,\n\t}\n\tcontentBlock3 := &model.Block{\n\t\tID:       contentBlockID3,\n\t\tBoardID:  board.ID,\n\t\tCreateAt: 1,\n\t\tUpdateAt: 1,\n\t\tType:     model.TypeCard,\n\t\tParentID: cardID1,\n\t}\n\tcontentBlock4 := &model.Block{\n\t\tID:       contentBlockID4,\n\t\tBoardID:  board.ID,\n\t\tCreateAt: 1,\n\t\tUpdateAt: 1,\n\t\tType:     model.TypeCard,\n\t\tParentID: cardID2,\n\t}\n\tcontentBlock5 := &model.Block{\n\t\tID:       contentBlockID5,\n\t\tBoardID:  board.ID,\n\t\tCreateAt: 1,\n\t\tUpdateAt: 1,\n\t\tType:     model.TypeCard,\n\t\tParentID: cardID2,\n\t}\n\tcontentBlock6 := &model.Block{\n\t\tID:       contentBlockID6,\n\t\tBoardID:  board.ID,\n\t\tCreateAt: 1,\n\t\tUpdateAt: 1,\n\t\tType:     model.TypeCard,\n\t\tParentID: cardID2,\n\t}\n\n\tnewBlocks := []*model.Block{\n\t\tcontentBlock1,\n\t\tcontentBlock2,\n\t\tcontentBlock3,\n\t\tcontentBlock4,\n\t\tcontentBlock5,\n\t\tcontentBlock6,\n\t\tcard1,\n\t\tcard2,\n\t}\n\tcreatedBlocks, resp := th.Client.InsertBlocks(board.ID, newBlocks, false)\n\trequire.NoError(t, resp.Error)\n\trequire.Len(t, newBlocks, 8)\n\n\tcontentBlock1.ID = createdBlocks[0].ID\n\tcontentBlock2.ID = createdBlocks[1].ID\n\tcontentBlock3.ID = createdBlocks[2].ID\n\tcontentBlock4.ID = createdBlocks[3].ID\n\tcontentBlock5.ID = createdBlocks[4].ID\n\tcontentBlock6.ID = createdBlocks[5].ID\n\tcard1.ID = createdBlocks[6].ID\n\tcard2.ID = createdBlocks[7].ID\n\n\tttCases := []struct {\n\t\tname                 string\n\t\tsrcBlockID           string\n\t\tdstBlockID           string\n\t\twhere                string\n\t\tuserID               string\n\t\terrorMessage         string\n\t\texpectedContentOrder []interface{}\n\t}{\n\t\t{\n\t\t\tname:                 \"not matching parents\",\n\t\t\tsrcBlockID:           contentBlock1.ID,\n\t\t\tdstBlockID:           contentBlock4.ID,\n\t\t\twhere:                \"after\",\n\t\t\tuserID:               \"user-id\",\n\t\t\terrorMessage:         fmt.Sprintf(\"payload: {\\\"error\\\":\\\"not matching parent %s and %s\\\",\\\"errorCode\\\":400}\", card1.ID, card2.ID),\n\t\t\texpectedContentOrder: []interface{}{contentBlock1.ID, contentBlock2.ID, contentBlock3.ID},\n\t\t},\n\t\t{\n\t\t\tname:                 \"valid request with not real change\",\n\t\t\tsrcBlockID:           contentBlock2.ID,\n\t\t\tdstBlockID:           contentBlock1.ID,\n\t\t\twhere:                \"after\",\n\t\t\tuserID:               \"user-id\",\n\t\t\terrorMessage:         \"\",\n\t\t\texpectedContentOrder: []interface{}{contentBlock1.ID, contentBlock2.ID, contentBlock3.ID},\n\t\t},\n\t\t{\n\t\t\tname:                 \"valid request changing order with before\",\n\t\t\tsrcBlockID:           contentBlock2.ID,\n\t\t\tdstBlockID:           contentBlock1.ID,\n\t\t\twhere:                \"before\",\n\t\t\tuserID:               \"user-id\",\n\t\t\terrorMessage:         \"\",\n\t\t\texpectedContentOrder: []interface{}{contentBlock2.ID, contentBlock1.ID, contentBlock3.ID},\n\t\t},\n\t\t{\n\t\t\tname:                 \"valid request changing order with after\",\n\t\t\tsrcBlockID:           contentBlock1.ID,\n\t\t\tdstBlockID:           contentBlock2.ID,\n\t\t\twhere:                \"after\",\n\t\t\tuserID:               \"user-id\",\n\t\t\terrorMessage:         \"\",\n\t\t\texpectedContentOrder: []interface{}{contentBlock2.ID, contentBlock1.ID, contentBlock3.ID},\n\t\t},\n\t}\n\n\tfor _, tc := range ttCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\t_, resp := th.Client.MoveContentBlock(tc.srcBlockID, tc.dstBlockID, tc.where, tc.userID)\n\t\t\tif tc.errorMessage == \"\" {\n\t\t\t\trequire.NoError(t, resp.Error)\n\t\t\t} else {\n\t\t\t\trequire.EqualError(t, resp.Error, tc.errorMessage)\n\t\t\t}\n\n\t\t\tparent, err := th.Server.App().GetBlockByID(card1.ID)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, parent.Fields[\"contentOrder\"], tc.expectedContentOrder)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "server/integrationtests/export_test.go",
    "content": "package integrationtests\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestExportBoard(t *testing.T) {\n\tt.Run(\"export single board\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tboard := &model.Board{\n\t\t\tID:        utils.NewID(utils.IDTypeBoard),\n\t\t\tTeamID:    \"test-team\",\n\t\t\tTitle:     \"Export Test Board\",\n\t\t\tCreatedBy: th.GetUser1().ID,\n\t\t\tType:      model.BoardTypeOpen,\n\t\t\tCreateAt:  utils.GetMillis(),\n\t\t\tUpdateAt:  utils.GetMillis(),\n\t\t}\n\n\t\tblock := &model.Block{\n\t\t\tID:        utils.NewID(utils.IDTypeCard),\n\t\t\tParentID:  board.ID,\n\t\t\tType:      model.TypeCard,\n\t\t\tBoardID:   board.ID,\n\t\t\tTitle:     \"Test card # for export\",\n\t\t\tCreatedBy: th.GetUser1().ID,\n\t\t\tCreateAt:  utils.GetMillis(),\n\t\t\tUpdateAt:  utils.GetMillis(),\n\t\t}\n\n\t\tbabs := &model.BoardsAndBlocks{\n\t\t\tBoards: []*model.Board{board},\n\t\t\tBlocks: []*model.Block{block},\n\t\t}\n\n\t\tbabs, resp := th.Client.CreateBoardsAndBlocks(babs)\n\t\tth.CheckOK(resp)\n\n\t\t// export the board to an in-memory archive file\n\t\tbuf, resp := th.Client.ExportBoardArchive(babs.Boards[0].ID)\n\t\tth.CheckOK(resp)\n\t\trequire.NotNil(t, buf)\n\n\t\t// import the archive file to team 0\n\t\tresp = th.Client.ImportArchive(model.GlobalTeamID, bytes.NewReader(buf))\n\t\tth.CheckOK(resp)\n\t\trequire.NoError(t, resp.Error)\n\n\t\t// check for test card\n\t\tboardsImported, err := th.Server.App().GetBoardsForUserAndTeam(th.GetUser1().ID, model.GlobalTeamID, true)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, boardsImported, 1)\n\t\tboardImported := boardsImported[0]\n\t\tblocksImported, err := th.Server.App().GetBlocksForBoard(boardImported.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, blocksImported, 1)\n\t\trequire.Equal(t, block.Title, blocksImported[0].Title)\n\t})\n}\n"
  },
  {
    "path": "server/integrationtests/file_test.go",
    "content": "package integrationtests\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestUploadFile(t *testing.T) {\n\tconst (\n\t\ttestTeamID = \"team-id\"\n\t)\n\n\tt.Run(\"a non authenticated user should be rejected\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\t\tth.Logout(th.Client)\n\n\t\tfile, resp := th.Client.TeamUploadFile(testTeamID, \"test-board-id\", bytes.NewBuffer([]byte(\"test\")))\n\t\tth.CheckUnauthorized(resp)\n\t\trequire.Nil(t, file)\n\t})\n\n\tt.Run(\"upload a file to an existing team and board without permissions\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tfile, resp := th.Client.TeamUploadFile(testTeamID, \"not-valid-board\", bytes.NewBuffer([]byte(\"test\")))\n\t\tth.CheckForbidden(resp)\n\t\trequire.Nil(t, file)\n\t})\n\n\tt.Run(\"upload a file to an existing team and board with permissions\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\ttestBoard := th.CreateBoard(testTeamID, model.BoardTypeOpen)\n\t\tfile, resp := th.Client.TeamUploadFile(testTeamID, testBoard.ID, bytes.NewBuffer([]byte(\"test\")))\n\t\tth.CheckOK(resp)\n\t\trequire.NoError(t, resp.Error)\n\t\trequire.NotNil(t, file)\n\t\trequire.NotNil(t, file.FileID)\n\t})\n\n\tt.Run(\"upload a file to an existing team and board with permissions but reaching the MaxFileLimit\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\ttestBoard := th.CreateBoard(testTeamID, model.BoardTypeOpen)\n\n\t\tconfig := th.Server.App().GetConfig()\n\t\tconfig.MaxFileSize = 1\n\t\tth.Server.App().SetConfig(config)\n\n\t\tfile, resp := th.Client.TeamUploadFile(testTeamID, testBoard.ID, bytes.NewBuffer([]byte(\"test\")))\n\t\tth.CheckRequestEntityTooLarge(resp)\n\t\trequire.Nil(t, file)\n\n\t\tconfig.MaxFileSize = 100000\n\t\tth.Server.App().SetConfig(config)\n\n\t\tfile, resp = th.Client.TeamUploadFile(testTeamID, testBoard.ID, bytes.NewBuffer([]byte(\"test\")))\n\t\tth.CheckOK(resp)\n\t\trequire.NoError(t, resp.Error)\n\t\trequire.NotNil(t, file)\n\t\trequire.NotNil(t, file.FileID)\n\t})\n}\n\nfunc TestFileInfo(t *testing.T) {\n\tconst (\n\t\ttestTeamID = \"team-id\"\n\t)\n\n\tt.Run(\"Retrieving file info\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\t\ttestBoard := th.CreateBoard(testTeamID, model.BoardTypeOpen)\n\n\t\tfileInfo, resp := th.Client.TeamUploadFileInfo(testTeamID, testBoard.ID, \"test\")\n\t\tth.CheckOK(resp)\n\t\trequire.NotNil(t, fileInfo)\n\t\trequire.NotNil(t, fileInfo.Id)\n\t})\n}\n"
  },
  {
    "path": "server/integrationtests/permissions_test.go",
    "content": "//nolint:dupl\npackage integrationtests\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/mattermost/focalboard/server/client\"\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype Clients struct {\n\tAnon         *client.Client\n\tNoTeamMember *client.Client\n\tTeamMember   *client.Client\n\tViewer       *client.Client\n\tCommenter    *client.Client\n\tEditor       *client.Client\n\tAdmin        *client.Client\n\tGuest        *client.Client\n}\n\nconst (\n\tmethodPost   = \"POST\"\n\tmethodGet    = \"GET\"\n\tmethodPut    = \"PUT\"\n\tmethodDelete = \"DELETE\"\n\tmethodPatch  = \"PATCH\"\n)\n\ntype TestCase struct {\n\turl                string\n\tmethod             string\n\tbody               string\n\tuserRole           string // userAnon, userNoTeamMember, userTeamMember, userViewer, userCommenter, userEditor, userAdmin or userGuest\n\texpectedStatusCode int\n\ttotalResults       int\n}\n\nfunc (tt TestCase) identifier() string {\n\treturn fmt.Sprintf(\n\t\t\"url: %s method: %s body: %s userRoles: %s expectedStatusCode: %d totalResults: %d\",\n\t\ttt.url,\n\t\ttt.method,\n\t\ttt.body,\n\t\ttt.userRole,\n\t\ttt.expectedStatusCode,\n\t\ttt.totalResults,\n\t)\n}\n\nfunc setupClients(th *TestHelper) Clients {\n\t// user1\n\tclients := Clients{\n\t\tAnon:         client.NewClient(th.Server.Config().ServerRoot, \"\"),\n\t\tNoTeamMember: client.NewClient(th.Server.Config().ServerRoot, \"\"),\n\t\tTeamMember:   client.NewClient(th.Server.Config().ServerRoot, \"\"),\n\t\tViewer:       client.NewClient(th.Server.Config().ServerRoot, \"\"),\n\t\tCommenter:    client.NewClient(th.Server.Config().ServerRoot, \"\"),\n\t\tEditor:       client.NewClient(th.Server.Config().ServerRoot, \"\"),\n\t\tAdmin:        client.NewClient(th.Server.Config().ServerRoot, \"\"),\n\t\tGuest:        client.NewClient(th.Server.Config().ServerRoot, \"\"),\n\t}\n\n\tclients.NoTeamMember.HTTPHeader[\"Mattermost-User-Id\"] = userNoTeamMember\n\tclients.TeamMember.HTTPHeader[\"Mattermost-User-Id\"] = userTeamMember\n\tclients.Viewer.HTTPHeader[\"Mattermost-User-Id\"] = userViewer\n\tclients.Commenter.HTTPHeader[\"Mattermost-User-Id\"] = userCommenter\n\tclients.Editor.HTTPHeader[\"Mattermost-User-Id\"] = userEditor\n\tclients.Admin.HTTPHeader[\"Mattermost-User-Id\"] = userAdmin\n\tclients.Guest.HTTPHeader[\"Mattermost-User-Id\"] = userGuest\n\n\t// For plugin tests, the userID = username\n\tuserAnonID = userAnon\n\tuserNoTeamMemberID = userNoTeamMember\n\tuserTeamMemberID = userTeamMember\n\tuserViewerID = userViewer\n\tuserCommenterID = userCommenter\n\tuserEditorID = userEditor\n\tuserAdminID = userAdmin\n\tuserGuestID = userGuest\n\n\treturn clients\n}\n\nfunc setupLocalClients(th *TestHelper) Clients {\n\tth.Client = client.NewClient(th.Server.Config().ServerRoot, \"\")\n\tth.RegisterAndLogin(th.Client, \"sysadmin\", \"sysadmin@sample.com\", password, \"\")\n\n\tclients := Clients{\n\t\tAnon:         client.NewClient(th.Server.Config().ServerRoot, \"\"),\n\t\tNoTeamMember: client.NewClient(th.Server.Config().ServerRoot, \"\"),\n\t\tTeamMember:   client.NewClient(th.Server.Config().ServerRoot, \"\"),\n\t\tViewer:       client.NewClient(th.Server.Config().ServerRoot, \"\"),\n\t\tCommenter:    client.NewClient(th.Server.Config().ServerRoot, \"\"),\n\t\tEditor:       client.NewClient(th.Server.Config().ServerRoot, \"\"),\n\t\tAdmin:        client.NewClient(th.Server.Config().ServerRoot, \"\"),\n\t\tGuest:        nil,\n\t}\n\n\t// get token\n\tteam, resp := th.Client.GetTeam(model.GlobalTeamID)\n\tth.CheckOK(resp)\n\trequire.NotNil(th.T, team)\n\trequire.NotNil(th.T, team.SignupToken)\n\n\tth.RegisterAndLogin(clients.NoTeamMember, userNoTeamMember, userNoTeamMember+\"@sample.com\", password, team.SignupToken)\n\tuserNoTeamMemberID = clients.NoTeamMember.GetUserID()\n\n\tth.RegisterAndLogin(clients.TeamMember, userTeamMember, userTeamMember+\"@sample.com\", password, team.SignupToken)\n\tuserTeamMemberID = clients.TeamMember.GetUserID()\n\n\tth.RegisterAndLogin(clients.Viewer, userViewer, userViewer+\"@sample.com\", password, team.SignupToken)\n\tuserViewerID = clients.Viewer.GetUserID()\n\n\tth.RegisterAndLogin(clients.Commenter, userCommenter, userCommenter+\"@sample.com\", password, team.SignupToken)\n\tuserCommenterID = clients.Commenter.GetUserID()\n\n\tth.RegisterAndLogin(clients.Editor, userEditor, userEditor+\"@sample.com\", password, team.SignupToken)\n\tuserEditorID = clients.Editor.GetUserID()\n\n\tth.RegisterAndLogin(clients.Admin, userAdmin, userAdmin+\"@sample.com\", password, team.SignupToken)\n\tuserAdminID = clients.Admin.GetUserID()\n\n\treturn clients\n}\n\nfunc toJSON(t *testing.T, obj interface{}) string {\n\tresult, err := json.Marshal(obj)\n\trequire.NoError(t, err)\n\treturn string(result)\n}\n\ntype TestData struct {\n\tpublicBoard     *model.Board\n\tprivateBoard    *model.Board\n\tpublicTemplate  *model.Board\n\tprivateTemplate *model.Board\n}\n\nfunc setupData(t *testing.T, th *TestHelper) TestData {\n\tcustomTemplate1, err := th.Server.App().CreateBoard(\n\t\t&model.Board{Title: \"Custom template 1\", TeamID: \"test-team\", IsTemplate: true, Type: model.BoardTypeOpen, MinimumRole: \"viewer\"},\n\t\tuserAdminID,\n\t\ttrue,\n\t)\n\trequire.NoError(t, err)\n\terr = th.Server.App().InsertBlock(&model.Block{ID: \"block-1\", Title: \"Test\", Type: \"card\", BoardID: customTemplate1.ID, Fields: map[string]interface{}{}}, userAdminID)\n\trequire.NoError(t, err)\n\tcustomTemplate2, err := th.Server.App().CreateBoard(\n\t\t&model.Board{Title: \"Custom template 2\", TeamID: \"test-team\", IsTemplate: true, Type: model.BoardTypePrivate, MinimumRole: \"viewer\"},\n\t\tuserAdminID,\n\t\ttrue)\n\trequire.NoError(t, err)\n\terr = th.Server.App().InsertBlock(&model.Block{ID: \"block-2\", Title: \"Test\", Type: \"card\", BoardID: customTemplate2.ID, Fields: map[string]interface{}{}}, userAdminID)\n\trequire.NoError(t, err)\n\n\tboard1, err := th.Server.App().CreateBoard(&model.Board{Title: \"Board 1\", TeamID: \"test-team\", Type: model.BoardTypeOpen, MinimumRole: \"viewer\"}, userAdminID, true)\n\trequire.NoError(t, err)\n\terr = th.Server.App().InsertBlock(&model.Block{ID: \"block-3\", Title: \"Test\", Type: \"card\", BoardID: board1.ID, Fields: map[string]interface{}{}}, userAdminID)\n\trequire.NoError(t, err)\n\tboard2, err := th.Server.App().CreateBoard(&model.Board{Title: \"Board 2\", TeamID: \"test-team\", Type: model.BoardTypePrivate, MinimumRole: \"viewer\"}, userAdminID, true)\n\trequire.NoError(t, err)\n\n\trBoard2, err := th.Server.App().GetBoard(board2.ID)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, rBoard2)\n\trequire.Equal(t, rBoard2, board2)\n\n\tboardMember, err := th.Server.App().GetMemberForBoard(board2.ID, userAdminID)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, boardMember)\n\trequire.Equal(t, boardMember.UserID, userAdminID)\n\trequire.Equal(t, boardMember.BoardID, board2.ID)\n\n\terr = th.Server.App().InsertBlock(&model.Block{ID: \"block-4\", Title: \"Test\", Type: \"card\", BoardID: board2.ID, Fields: map[string]interface{}{}}, userAdminID)\n\trequire.NoError(t, err)\n\n\terr = th.Server.App().UpsertSharing(model.Sharing{ID: board2.ID, Enabled: true, Token: \"valid\", ModifiedBy: userAdminID, UpdateAt: model.GetMillis()})\n\trequire.NoError(t, err)\n\n\t_, err = th.Server.App().AddMemberToBoard(&model.BoardMember{BoardID: customTemplate1.ID, UserID: userViewerID, SchemeViewer: true})\n\trequire.NoError(t, err)\n\t_, err = th.Server.App().AddMemberToBoard(&model.BoardMember{BoardID: customTemplate2.ID, UserID: userViewerID, SchemeViewer: true})\n\trequire.NoError(t, err)\n\t_, err = th.Server.App().AddMemberToBoard(&model.BoardMember{BoardID: customTemplate1.ID, UserID: userCommenterID, SchemeCommenter: true})\n\trequire.NoError(t, err)\n\t_, err = th.Server.App().AddMemberToBoard(&model.BoardMember{BoardID: customTemplate2.ID, UserID: userCommenterID, SchemeCommenter: true})\n\trequire.NoError(t, err)\n\t_, err = th.Server.App().AddMemberToBoard(&model.BoardMember{BoardID: customTemplate1.ID, UserID: userEditorID, SchemeEditor: true})\n\trequire.NoError(t, err)\n\t_, err = th.Server.App().AddMemberToBoard(&model.BoardMember{BoardID: customTemplate2.ID, UserID: userEditorID, SchemeEditor: true})\n\trequire.NoError(t, err)\n\t_, err = th.Server.App().AddMemberToBoard(&model.BoardMember{BoardID: customTemplate1.ID, UserID: userAdminID, SchemeAdmin: true})\n\trequire.NoError(t, err)\n\t_, err = th.Server.App().AddMemberToBoard(&model.BoardMember{BoardID: customTemplate2.ID, UserID: userAdminID, SchemeAdmin: true})\n\trequire.NoError(t, err)\n\n\t_, err = th.Server.App().AddMemberToBoard(&model.BoardMember{BoardID: board1.ID, UserID: userViewerID, SchemeViewer: true})\n\trequire.NoError(t, err)\n\n\tboardMember, err = th.Server.App().GetMemberForBoard(board1.ID, userViewerID)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, boardMember)\n\trequire.Equal(t, boardMember.UserID, userViewerID)\n\trequire.Equal(t, boardMember.BoardID, board1.ID)\n\n\t_, err = th.Server.App().AddMemberToBoard(&model.BoardMember{BoardID: board2.ID, UserID: userViewerID, SchemeViewer: true})\n\trequire.NoError(t, err)\n\n\tboardMember, err = th.Server.App().GetMemberForBoard(board2.ID, userViewerID)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, boardMember)\n\trequire.Equal(t, boardMember.UserID, userViewerID)\n\trequire.Equal(t, boardMember.BoardID, board2.ID)\n\n\t_, err = th.Server.App().AddMemberToBoard(&model.BoardMember{BoardID: board1.ID, UserID: userCommenterID, SchemeCommenter: true})\n\trequire.NoError(t, err)\n\t_, err = th.Server.App().AddMemberToBoard(&model.BoardMember{BoardID: board2.ID, UserID: userCommenterID, SchemeCommenter: true})\n\trequire.NoError(t, err)\n\t_, err = th.Server.App().AddMemberToBoard(&model.BoardMember{BoardID: board1.ID, UserID: userEditorID, SchemeEditor: true})\n\trequire.NoError(t, err)\n\t_, err = th.Server.App().AddMemberToBoard(&model.BoardMember{BoardID: board2.ID, UserID: userEditorID, SchemeEditor: true})\n\trequire.NoError(t, err)\n\t_, err = th.Server.App().AddMemberToBoard(&model.BoardMember{BoardID: board1.ID, UserID: userAdminID, SchemeAdmin: true})\n\trequire.NoError(t, err)\n\t_, err = th.Server.App().AddMemberToBoard(&model.BoardMember{BoardID: board2.ID, UserID: userAdminID, SchemeAdmin: true})\n\trequire.NoError(t, err)\n\n\t_, err = th.Server.App().AddMemberToBoard(&model.BoardMember{BoardID: board2.ID, UserID: userGuestID, SchemeViewer: true})\n\trequire.NoError(t, err)\n\n\treturn TestData{\n\t\tpublicBoard:     board1,\n\t\tprivateBoard:    board2,\n\t\tpublicTemplate:  customTemplate1,\n\t\tprivateTemplate: customTemplate2,\n\t}\n}\n\nfunc runTestCases(t *testing.T, ttCases []TestCase, testData TestData, clients Clients) {\n\tfor _, tc := range ttCases {\n\t\tt.Run(tc.userRole+\": \"+tc.method+\" \"+tc.url, func(t *testing.T) {\n\t\t\treqClient := clients.Anon\n\t\t\tswitch tc.userRole {\n\t\t\tcase userAnon:\n\t\t\t\treqClient = clients.Anon\n\t\t\tcase userNoTeamMember:\n\t\t\t\treqClient = clients.NoTeamMember\n\t\t\tcase userTeamMember:\n\t\t\t\treqClient = clients.TeamMember\n\t\t\tcase userViewer:\n\t\t\t\treqClient = clients.Viewer\n\t\t\tcase userCommenter:\n\t\t\t\treqClient = clients.Commenter\n\t\t\tcase userEditor:\n\t\t\t\treqClient = clients.Editor\n\t\t\tcase userAdmin:\n\t\t\t\treqClient = clients.Admin\n\t\t\tcase userGuest:\n\t\t\t\tif clients.Guest == nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\treqClient = clients.Guest\n\t\t\t}\n\n\t\t\turl := strings.ReplaceAll(tc.url, \"{PRIVATE_BOARD_ID}\", testData.privateBoard.ID)\n\t\t\turl = strings.ReplaceAll(url, \"{PUBLIC_BOARD_ID}\", testData.publicBoard.ID)\n\t\t\turl = strings.ReplaceAll(url, \"{PUBLIC_TEMPLATE_ID}\", testData.publicTemplate.ID)\n\t\t\turl = strings.ReplaceAll(url, \"{PRIVATE_TEMPLATE_ID}\", testData.privateTemplate.ID)\n\t\t\turl = strings.ReplaceAll(url, \"{USER_ANON_ID}\", userAnonID)\n\t\t\turl = strings.ReplaceAll(url, \"{USER_NO_TEAM_MEMBER_ID}\", userNoTeamMemberID)\n\t\t\turl = strings.ReplaceAll(url, \"{USER_TEAM_MEMBER_ID}\", userTeamMemberID)\n\t\t\turl = strings.ReplaceAll(url, \"{USER_VIEWER_ID}\", userViewerID)\n\t\t\turl = strings.ReplaceAll(url, \"{USER_COMMENTER_ID}\", userCommenterID)\n\t\t\turl = strings.ReplaceAll(url, \"{USER_EDITOR_ID}\", userEditorID)\n\t\t\turl = strings.ReplaceAll(url, \"{USER_ADMIN_ID}\", userAdminID)\n\t\t\turl = strings.ReplaceAll(url, \"{USER_GUEST_ID}\", userGuestID)\n\n\t\t\tif strings.Contains(url, \"{\") || strings.Contains(url, \"}\") {\n\t\t\t\trequire.Fail(t, \"Unreplaced tokens in url\", url, tc.identifier())\n\t\t\t}\n\n\t\t\tvar response *http.Response\n\t\t\tvar err error\n\t\t\tswitch tc.method {\n\t\t\tcase methodGet:\n\t\t\t\tresponse, err = reqClient.DoAPIGet(url, \"\")\n\t\t\t\tdefer response.Body.Close()\n\t\t\tcase methodPost:\n\t\t\t\tresponse, err = reqClient.DoAPIPost(url, tc.body)\n\t\t\t\tdefer response.Body.Close()\n\t\t\tcase methodPatch:\n\t\t\t\tresponse, err = reqClient.DoAPIPatch(url, tc.body)\n\t\t\t\tdefer response.Body.Close()\n\t\t\tcase methodPut:\n\t\t\t\tresponse, err = reqClient.DoAPIPut(url, tc.body)\n\t\t\t\tdefer response.Body.Close()\n\t\t\tcase methodDelete:\n\t\t\t\tresponse, err = reqClient.DoAPIDelete(url, tc.body)\n\t\t\t\tdefer response.Body.Close()\n\t\t\t}\n\n\t\t\trequire.Equal(t, tc.expectedStatusCode, response.StatusCode, tc.identifier())\n\t\t\tif tc.expectedStatusCode >= 200 && tc.expectedStatusCode < 300 {\n\t\t\t\trequire.NoError(t, err, tc.identifier())\n\t\t\t}\n\t\t\tif tc.expectedStatusCode >= 200 && tc.expectedStatusCode < 300 {\n\t\t\t\tbody, err := io.ReadAll(response.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\trequire.Fail(t, err.Error(), tc.identifier())\n\t\t\t\t}\n\t\t\t\tif strings.HasPrefix(string(body), \"[\") {\n\t\t\t\t\tvar data []interface{}\n\t\t\t\t\terr = json.Unmarshal(body, &data)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\trequire.Fail(t, err.Error(), tc.identifier())\n\t\t\t\t\t}\n\t\t\t\t\trequire.Len(t, data, tc.totalResults, tc.identifier())\n\t\t\t\t} else {\n\t\t\t\t\tif tc.totalResults > 0 {\n\t\t\t\t\t\trequire.Equal(t, 1, tc.totalResults)\n\t\t\t\t\t\trequire.Greater(t, len(string(body)), 2, tc.identifier())\n\t\t\t\t\t} else {\n\t\t\t\t\t\trequire.Len(t, string(body), 2, tc.identifier())\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestPermissionsGetTeamBoards(t *testing.T) {\n\tttCases := []TestCase{\n\t\t{\"/teams/test-team/boards\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/teams/test-team/boards\", methodGet, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/teams/test-team/boards\", methodGet, \"\", userTeamMember, http.StatusOK, 1},\n\t\t{\"/teams/test-team/boards\", methodGet, \"\", userViewer, http.StatusOK, 2},\n\t\t{\"/teams/test-team/boards\", methodGet, \"\", userCommenter, http.StatusOK, 2},\n\t\t{\"/teams/test-team/boards\", methodGet, \"\", userEditor, http.StatusOK, 2},\n\t\t{\"/teams/test-team/boards\", methodGet, \"\", userAdmin, http.StatusOK, 2},\n\t\t{\"/teams/test-team/boards\", methodGet, \"\", userGuest, http.StatusOK, 1},\n\t}\n\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases[1].expectedStatusCode = http.StatusOK\n\t\tttCases[1].totalResults = 1\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsSearchTeamBoards(t *testing.T) {\n\tttCases := []TestCase{\n\t\t// Search boards\n\t\t{\"/teams/test-team/boards/search?q=b\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/teams/test-team/boards/search?q=b\", methodGet, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/teams/test-team/boards/search?q=b\", methodGet, \"\", userTeamMember, http.StatusOK, 1},\n\t\t{\"/teams/test-team/boards/search?q=b\", methodGet, \"\", userViewer, http.StatusOK, 2},\n\t\t{\"/teams/test-team/boards/search?q=b\", methodGet, \"\", userCommenter, http.StatusOK, 2},\n\t\t{\"/teams/test-team/boards/search?q=b\", methodGet, \"\", userEditor, http.StatusOK, 2},\n\t\t{\"/teams/test-team/boards/search?q=b\", methodGet, \"\", userAdmin, http.StatusOK, 2},\n\t\t{\"/teams/test-team/boards/search?q=b\", methodGet, \"\", userGuest, http.StatusOK, 1},\n\t}\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases[1].expectedStatusCode = http.StatusOK\n\t\tttCases[1].totalResults = 1\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsSearchTeamLinkableBoards(t *testing.T) {\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases := []TestCase{\n\t\t\t// Search boards\n\t\t\t{\"/teams/test-team/boards/search/linkable?q=b\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/teams/test-team/boards/search/linkable?q=b\", methodGet, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/teams/test-team/boards/search/linkable?q=b\", methodGet, \"\", userTeamMember, http.StatusOK, 0},\n\t\t\t{\"/teams/test-team/boards/search/linkable?q=b\", methodGet, \"\", userViewer, http.StatusOK, 0},\n\t\t\t{\"/teams/test-team/boards/search/linkable?q=b\", methodGet, \"\", userCommenter, http.StatusOK, 0},\n\t\t\t{\"/teams/test-team/boards/search/linkable?q=b\", methodGet, \"\", userEditor, http.StatusOK, 0},\n\t\t\t{\"/teams/test-team/boards/search/linkable?q=b\", methodGet, \"\", userAdmin, http.StatusOK, 2},\n\t\t}\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases := []TestCase{\n\t\t\t// Search boards\n\t\t\t{\"/teams/test-team/boards/search/linkable?q=b\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/teams/test-team/boards/search/linkable?q=b\", methodGet, \"\", userNoTeamMember, http.StatusNotImplemented, 0},\n\t\t\t{\"/teams/test-team/boards/search/linkable?q=b\", methodGet, \"\", userTeamMember, http.StatusNotImplemented, 0},\n\t\t\t{\"/teams/test-team/boards/search/linkable?q=b\", methodGet, \"\", userViewer, http.StatusNotImplemented, 0},\n\t\t\t{\"/teams/test-team/boards/search/linkable?q=b\", methodGet, \"\", userCommenter, http.StatusNotImplemented, 0},\n\t\t\t{\"/teams/test-team/boards/search/linkable?q=b\", methodGet, \"\", userEditor, http.StatusNotImplemented, 0},\n\t\t\t{\"/teams/test-team/boards/search/linkable?q=b\", methodGet, \"\", userAdmin, http.StatusNotImplemented, 0},\n\t\t}\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsGetTeamTemplates(t *testing.T) {\n\textraSetup := func(t *testing.T, th *TestHelper) {\n\t\terr := th.Server.App().InitTemplates()\n\t\trequire.NoError(t, err, \"InitTemplates should succeed\")\n\t}\n\n\tbuiltInTemplateCount := 13\n\n\tttCases := []TestCase{\n\t\t// Get Team Boards\n\t\t{\"/teams/test-team/templates\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/teams/test-team/templates\", methodGet, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/teams/test-team/templates\", methodGet, \"\", userTeamMember, http.StatusOK, 1},\n\t\t{\"/teams/test-team/templates\", methodGet, \"\", userViewer, http.StatusOK, 2},\n\t\t{\"/teams/test-team/templates\", methodGet, \"\", userCommenter, http.StatusOK, 2},\n\t\t{\"/teams/test-team/templates\", methodGet, \"\", userEditor, http.StatusOK, 2},\n\t\t{\"/teams/test-team/templates\", methodGet, \"\", userAdmin, http.StatusOK, 2},\n\t\t{\"/teams/test-team/templates\", methodGet, \"\", userGuest, http.StatusForbidden, 0},\n\t\t// Built-in templates\n\t\t{\"/teams/0/templates\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/teams/0/templates\", methodGet, \"\", userNoTeamMember, http.StatusOK, builtInTemplateCount},\n\t\t{\"/teams/0/templates\", methodGet, \"\", userTeamMember, http.StatusOK, builtInTemplateCount},\n\t\t{\"/teams/0/templates\", methodGet, \"\", userViewer, http.StatusOK, builtInTemplateCount},\n\t\t{\"/teams/0/templates\", methodGet, \"\", userCommenter, http.StatusOK, builtInTemplateCount},\n\t\t{\"/teams/0/templates\", methodGet, \"\", userEditor, http.StatusOK, builtInTemplateCount},\n\t\t{\"/teams/0/templates\", methodGet, \"\", userGuest, http.StatusForbidden, 0},\n\t}\n\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\textraSetup(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\textraSetup(t, th)\n\t\tttCases[1].expectedStatusCode = http.StatusOK\n\t\tttCases[1].totalResults = 1\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsCreateBoard(t *testing.T) {\n\tpublicBoard := toJSON(t, model.Board{Title: \"Board To Create\", TeamID: \"test-team\", Type: model.BoardTypeOpen})\n\tprivateBoard := toJSON(t, model.Board{Title: \"Board To Create\", TeamID: \"test-team\", Type: model.BoardTypeOpen})\n\n\tttCases := []TestCase{\n\t\t// Create Public boards\n\t\t{\"/boards\", methodPost, publicBoard, userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards\", methodPost, publicBoard, userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards\", methodPost, publicBoard, userGuest, http.StatusForbidden, 0},\n\t\t{\"/boards\", methodPost, publicBoard, userTeamMember, http.StatusOK, 1},\n\n\t\t// Create private boards\n\t\t{\"/boards\", methodPost, privateBoard, userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards\", methodPost, privateBoard, userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards\", methodPost, privateBoard, userGuest, http.StatusForbidden, 0},\n\t\t{\"/boards\", methodPost, privateBoard, userTeamMember, http.StatusOK, 1},\n\t}\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases[1].expectedStatusCode = http.StatusOK\n\t\tttCases[1].totalResults = 1\n\t\tttCases[5].expectedStatusCode = http.StatusOK\n\t\tttCases[5].totalResults = 1\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsGetBoard(t *testing.T) {\n\tttCases := []TestCase{\n\t\t{\"/boards/{PRIVATE_BOARD_ID}\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}\", methodGet, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}\", methodGet, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}\", methodGet, \"\", userViewer, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}\", methodGet, \"\", userCommenter, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}\", methodGet, \"\", userEditor, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}\", methodGet, \"\", userAdmin, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}\", methodGet, \"\", userGuest, http.StatusOK, 1},\n\n\t\t{\"/boards/{PUBLIC_BOARD_ID}\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}\", methodGet, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}\", methodGet, \"\", userTeamMember, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}\", methodGet, \"\", userViewer, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}\", methodGet, \"\", userCommenter, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}\", methodGet, \"\", userEditor, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}\", methodGet, \"\", userAdmin, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}\", methodGet, \"\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}\", methodGet, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}\", methodGet, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}\", methodGet, \"\", userViewer, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}\", methodGet, \"\", userCommenter, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}\", methodGet, \"\", userEditor, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}\", methodGet, \"\", userAdmin, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}\", methodGet, \"\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}\", methodGet, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}\", methodGet, \"\", userTeamMember, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}\", methodGet, \"\", userViewer, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}\", methodGet, \"\", userCommenter, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}\", methodGet, \"\", userEditor, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}\", methodGet, \"\", userAdmin, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}\", methodGet, \"\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PRIVATE_BOARD_ID}?read_token=invalid\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}?read_token=valid\", methodGet, \"\", userAnon, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}?read_token=invalid\", methodGet, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}?read_token=valid\", methodGet, \"\", userTeamMember, http.StatusOK, 1},\n\t}\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases[9].expectedStatusCode = http.StatusOK\n\t\tttCases[9].totalResults = 1\n\t\tttCases[25].expectedStatusCode = http.StatusOK\n\t\tttCases[25].totalResults = 1\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsGetBoardPublic(t *testing.T) {\n\tttCases := []TestCase{\n\t\t{\"/boards/{PRIVATE_BOARD_ID}?read_token=invalid\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}?read_token=valid\", methodGet, \"\", userAnon, http.StatusUnauthorized, 1},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}?read_token=invalid\", methodGet, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}?read_token=valid\", methodGet, \"\", userTeamMember, http.StatusForbidden, 1},\n\t}\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tcfg := th.Server.Config()\n\t\tcfg.EnablePublicSharedBoards = false\n\t\tth.Server.UpdateAppConfig()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tcfg := th.Server.Config()\n\t\tcfg.EnablePublicSharedBoards = false\n\t\tth.Server.UpdateAppConfig()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsPatchBoard(t *testing.T) {\n\tttCases := []TestCase{\n\t\t{\"/boards/{PRIVATE_BOARD_ID}\", methodPatch, \"{\\\"title\\\": \\\"test\\\"}\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}\", methodPatch, \"{\\\"title\\\": \\\"test\\\"}\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}\", methodPatch, \"{\\\"title\\\": \\\"test\\\"}\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}\", methodPatch, \"{\\\"title\\\": \\\"test\\\"}\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}\", methodPatch, \"{\\\"title\\\": \\\"test\\\"}\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}\", methodPatch, \"{\\\"title\\\": \\\"test\\\"}\", userEditor, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}\", methodPatch, \"{\\\"title\\\": \\\"test\\\"}\", userAdmin, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}\", methodPatch, \"{\\\"title\\\": \\\"test\\\"}\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PUBLIC_BOARD_ID}\", methodPatch, \"{\\\"title\\\": \\\"test\\\"}\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}\", methodPatch, \"{\\\"title\\\": \\\"test\\\"}\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}\", methodPatch, \"{\\\"title\\\": \\\"test\\\"}\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}\", methodPatch, \"{\\\"title\\\": \\\"test\\\"}\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}\", methodPatch, \"{\\\"title\\\": \\\"test\\\"}\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}\", methodPatch, \"{\\\"title\\\": \\\"test\\\"}\", userEditor, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}\", methodPatch, \"{\\\"title\\\": \\\"test\\\"}\", userAdmin, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}\", methodPatch, \"{\\\"title\\\": \\\"test\\\"}\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}\", methodPatch, \"{\\\"title\\\": \\\"test\\\"}\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}\", methodPatch, \"{\\\"title\\\": \\\"test\\\"}\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}\", methodPatch, \"{\\\"title\\\": \\\"test\\\"}\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}\", methodPatch, \"{\\\"title\\\": \\\"test\\\"}\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}\", methodPatch, \"{\\\"title\\\": \\\"test\\\"}\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}\", methodPatch, \"{\\\"title\\\": \\\"test\\\"}\", userEditor, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}\", methodPatch, \"{\\\"title\\\": \\\"test\\\"}\", userAdmin, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}\", methodPatch, \"{\\\"title\\\": \\\"test\\\"}\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}\", methodPatch, \"{\\\"title\\\": \\\"test\\\"}\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}\", methodPatch, \"{\\\"title\\\": \\\"test\\\"}\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}\", methodPatch, \"{\\\"title\\\": \\\"test\\\"}\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}\", methodPatch, \"{\\\"title\\\": \\\"test\\\"}\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}\", methodPatch, \"{\\\"title\\\": \\\"test\\\"}\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}\", methodPatch, \"{\\\"title\\\": \\\"test\\\"}\", userEditor, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}\", methodPatch, \"{\\\"title\\\": \\\"test\\\"}\", userAdmin, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}\", methodPatch, \"{\\\"title\\\": \\\"test\\\"}\", userGuest, http.StatusForbidden, 0},\n\t}\n\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsPatchBoardType(t *testing.T) {\n\tttCases := []TestCase{\n\t\t{\"/boards/{PRIVATE_BOARD_ID}\", methodPatch, \"{\\\"type\\\": \\\"P\\\"}\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}\", methodPatch, \"{\\\"type\\\": \\\"P\\\"}\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}\", methodPatch, \"{\\\"type\\\": \\\"P\\\"}\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}\", methodPatch, \"{\\\"type\\\": \\\"P\\\"}\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}\", methodPatch, \"{\\\"type\\\": \\\"P\\\"}\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}\", methodPatch, \"{\\\"type\\\": \\\"P\\\"}\", userEditor, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}\", methodPatch, \"{\\\"type\\\": \\\"P\\\"}\", userAdmin, http.StatusOK, 1},\n\n\t\t{\"/boards/{PUBLIC_BOARD_ID}\", methodPatch, \"{\\\"type\\\": \\\"P\\\"}\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}\", methodPatch, \"{\\\"type\\\": \\\"P\\\"}\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}\", methodPatch, \"{\\\"type\\\": \\\"P\\\"}\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}\", methodPatch, \"{\\\"type\\\": \\\"P\\\"}\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}\", methodPatch, \"{\\\"type\\\": \\\"P\\\"}\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}\", methodPatch, \"{\\\"type\\\": \\\"P\\\"}\", userEditor, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}\", methodPatch, \"{\\\"type\\\": \\\"P\\\"}\", userAdmin, http.StatusOK, 1},\n\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}\", methodPatch, \"{\\\"type\\\": \\\"P\\\"}\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}\", methodPatch, \"{\\\"type\\\": \\\"P\\\"}\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}\", methodPatch, \"{\\\"type\\\": \\\"P\\\"}\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}\", methodPatch, \"{\\\"type\\\": \\\"P\\\"}\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}\", methodPatch, \"{\\\"type\\\": \\\"P\\\"}\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}\", methodPatch, \"{\\\"type\\\": \\\"P\\\"}\", userEditor, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}\", methodPatch, \"{\\\"type\\\": \\\"P\\\"}\", userAdmin, http.StatusOK, 1},\n\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}\", methodPatch, \"{\\\"type\\\": \\\"P\\\"}\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}\", methodPatch, \"{\\\"type\\\": \\\"P\\\"}\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}\", methodPatch, \"{\\\"type\\\": \\\"P\\\"}\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}\", methodPatch, \"{\\\"type\\\": \\\"P\\\"}\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}\", methodPatch, \"{\\\"type\\\": \\\"P\\\"}\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}\", methodPatch, \"{\\\"type\\\": \\\"P\\\"}\", userEditor, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}\", methodPatch, \"{\\\"type\\\": \\\"P\\\"}\", userAdmin, http.StatusOK, 1},\n\t}\n\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsPatchBoardMinimumRole(t *testing.T) {\n\tpatch := toJSON(t, map[string]model.BoardRole{\"minimumRole\": model.BoardRoleViewer})\n\tttCases := []TestCase{\n\t\t{\"/boards/{PRIVATE_BOARD_ID}\", methodPatch, patch, userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}\", methodPatch, patch, userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}\", methodPatch, patch, userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}\", methodPatch, patch, userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}\", methodPatch, patch, userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}\", methodPatch, patch, userEditor, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}\", methodPatch, patch, userAdmin, http.StatusOK, 1},\n\n\t\t{\"/boards/{PUBLIC_BOARD_ID}\", methodPatch, patch, userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}\", methodPatch, patch, userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}\", methodPatch, patch, userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}\", methodPatch, patch, userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}\", methodPatch, patch, userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}\", methodPatch, patch, userEditor, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}\", methodPatch, patch, userAdmin, http.StatusOK, 1},\n\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}\", methodPatch, patch, userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}\", methodPatch, patch, userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}\", methodPatch, patch, userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}\", methodPatch, patch, userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}\", methodPatch, patch, userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}\", methodPatch, patch, userEditor, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}\", methodPatch, patch, userAdmin, http.StatusOK, 1},\n\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}\", methodPatch, patch, userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}\", methodPatch, patch, userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}\", methodPatch, patch, userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}\", methodPatch, patch, userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}\", methodPatch, patch, userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}\", methodPatch, patch, userEditor, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}\", methodPatch, patch, userAdmin, http.StatusOK, 1},\n\t}\n\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsPatchBoardChannelId(t *testing.T) {\n\tpatch := toJSON(t, map[string]string{\"channelId\": \"valid-channel-id\"})\n\tttCases := []TestCase{\n\t\t{\"/boards/{PRIVATE_BOARD_ID}\", methodPatch, patch, userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}\", methodPatch, patch, userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}\", methodPatch, patch, userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}\", methodPatch, patch, userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}\", methodPatch, patch, userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}\", methodPatch, patch, userEditor, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}\", methodPatch, patch, userAdmin, http.StatusOK, 1},\n\n\t\t{\"/boards/{PUBLIC_BOARD_ID}\", methodPatch, patch, userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}\", methodPatch, patch, userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}\", methodPatch, patch, userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}\", methodPatch, patch, userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}\", methodPatch, patch, userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}\", methodPatch, patch, userEditor, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}\", methodPatch, patch, userAdmin, http.StatusOK, 1},\n\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}\", methodPatch, patch, userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}\", methodPatch, patch, userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}\", methodPatch, patch, userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}\", methodPatch, patch, userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}\", methodPatch, patch, userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}\", methodPatch, patch, userEditor, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}\", methodPatch, patch, userAdmin, http.StatusOK, 1},\n\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}\", methodPatch, patch, userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}\", methodPatch, patch, userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}\", methodPatch, patch, userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}\", methodPatch, patch, userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}\", methodPatch, patch, userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}\", methodPatch, patch, userEditor, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}\", methodPatch, patch, userAdmin, http.StatusOK, 1},\n\t}\n\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsDeleteBoard(t *testing.T) {\n\tttCases := []TestCase{\n\t\t{\"/boards/{PRIVATE_BOARD_ID}\", methodDelete, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}\", methodDelete, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}\", methodDelete, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}\", methodDelete, \"\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}\", methodDelete, \"\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}\", methodDelete, \"\", userEditor, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}\", methodDelete, \"\", userGuest, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}\", methodDelete, \"\", userAdmin, http.StatusOK, 0},\n\n\t\t{\"/boards/{PUBLIC_BOARD_ID}\", methodDelete, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}\", methodDelete, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}\", methodDelete, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}\", methodDelete, \"\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}\", methodDelete, \"\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}\", methodDelete, \"\", userEditor, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}\", methodDelete, \"\", userGuest, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}\", methodDelete, \"\", userAdmin, http.StatusOK, 0},\n\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}\", methodDelete, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}\", methodDelete, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}\", methodDelete, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}\", methodDelete, \"\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}\", methodDelete, \"\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}\", methodDelete, \"\", userEditor, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}\", methodDelete, \"\", userGuest, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}\", methodDelete, \"\", userAdmin, http.StatusOK, 0},\n\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}\", methodDelete, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}\", methodDelete, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}\", methodDelete, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}\", methodDelete, \"\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}\", methodDelete, \"\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}\", methodDelete, \"\", userEditor, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}\", methodDelete, \"\", userGuest, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}\", methodDelete, \"\", userAdmin, http.StatusOK, 0},\n\t}\n\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsDuplicateBoard(t *testing.T) {\n\t// In same team\n\tttCases := []TestCase{\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/duplicate\", methodPost, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/duplicate\", methodPost, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/duplicate\", methodPost, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/duplicate\", methodPost, \"\", userViewer, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/duplicate\", methodPost, \"\", userCommenter, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/duplicate\", methodPost, \"\", userEditor, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/duplicate\", methodPost, \"\", userAdmin, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/duplicate\", methodPost, \"\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/duplicate\", methodPost, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/duplicate\", methodPost, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/duplicate\", methodPost, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/duplicate\", methodPost, \"\", userViewer, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/duplicate\", methodPost, \"\", userCommenter, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/duplicate\", methodPost, \"\", userEditor, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/duplicate\", methodPost, \"\", userAdmin, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/duplicate\", methodPost, \"\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/duplicate\", methodPost, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/duplicate\", methodPost, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/duplicate\", methodPost, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/duplicate\", methodPost, \"\", userViewer, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/duplicate\", methodPost, \"\", userCommenter, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/duplicate\", methodPost, \"\", userEditor, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/duplicate\", methodPost, \"\", userAdmin, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/duplicate\", methodPost, \"\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/duplicate\", methodPost, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/duplicate\", methodPost, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/duplicate\", methodPost, \"\", userTeamMember, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/duplicate\", methodPost, \"\", userViewer, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/duplicate\", methodPost, \"\", userCommenter, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/duplicate\", methodPost, \"\", userEditor, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/duplicate\", methodPost, \"\", userAdmin, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/duplicate\", methodPost, \"\", userGuest, http.StatusForbidden, 0},\n\t}\n\tt.Run(\"plugin-same-team\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local-same-team\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases[25].expectedStatusCode = http.StatusOK\n\t\tttCases[25].totalResults = 1\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\n\t// In other team\n\tttCases = []TestCase{\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/duplicate?toTeam=other-team\", methodPost, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/duplicate?toTeam=other-team\", methodPost, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/duplicate?toTeam=other-team\", methodPost, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/duplicate?toTeam=other-team\", methodPost, \"\", userViewer, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/duplicate?toTeam=other-team\", methodPost, \"\", userCommenter, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/duplicate?toTeam=other-team\", methodPost, \"\", userEditor, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/duplicate?toTeam=other-team\", methodPost, \"\", userAdmin, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/duplicate?toTeam=other-team\", methodPost, \"\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/duplicate?toTeam=other-team\", methodPost, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/duplicate?toTeam=other-team\", methodPost, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/duplicate?toTeam=other-team\", methodPost, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/duplicate?toTeam=other-team\", methodPost, \"\", userViewer, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/duplicate?toTeam=other-team\", methodPost, \"\", userCommenter, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/duplicate?toTeam=other-team\", methodPost, \"\", userEditor, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/duplicate?toTeam=other-team\", methodPost, \"\", userAdmin, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/duplicate?toTeam=other-team\", methodPost, \"\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/duplicate?toTeam=other-team\", methodPost, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/duplicate?toTeam=other-team\", methodPost, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/duplicate?toTeam=other-team\", methodPost, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/duplicate?toTeam=other-team\", methodPost, \"\", userViewer, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/duplicate?toTeam=other-team\", methodPost, \"\", userCommenter, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/duplicate?toTeam=other-team\", methodPost, \"\", userEditor, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/duplicate?toTeam=other-team\", methodPost, \"\", userAdmin, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/duplicate?toTeam=other-team\", methodPost, \"\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/duplicate?toTeam=other-team\", methodPost, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/duplicate?toTeam=other-team\", methodPost, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/duplicate?toTeam=other-team\", methodPost, \"\", userTeamMember, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/duplicate?toTeam=other-team\", methodPost, \"\", userViewer, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/duplicate?toTeam=other-team\", methodPost, \"\", userCommenter, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/duplicate?toTeam=other-team\", methodPost, \"\", userEditor, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/duplicate?toTeam=other-team\", methodPost, \"\", userAdmin, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/duplicate?toTeam=other-team\", methodPost, \"\", userGuest, http.StatusForbidden, 0},\n\t}\n\tt.Run(\"plugin-other-team\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local-other-team\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases[25].expectedStatusCode = http.StatusOK\n\t\tttCases[25].totalResults = 1\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\n\t// In empty team\n\tttCases = []TestCase{\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/duplicate?toTeam=empty-team\", methodPost, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/duplicate?toTeam=empty-team\", methodPost, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/duplicate?toTeam=empty-team\", methodPost, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/duplicate?toTeam=empty-team\", methodPost, \"\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/duplicate?toTeam=empty-team\", methodPost, \"\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/duplicate?toTeam=empty-team\", methodPost, \"\", userEditor, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/duplicate?toTeam=empty-team\", methodPost, \"\", userAdmin, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/duplicate?toTeam=empty-team\", methodPost, \"\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/duplicate?toTeam=empty-team\", methodPost, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/duplicate?toTeam=empty-team\", methodPost, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/duplicate?toTeam=empty-team\", methodPost, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/duplicate?toTeam=empty-team\", methodPost, \"\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/duplicate?toTeam=empty-team\", methodPost, \"\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/duplicate?toTeam=empty-team\", methodPost, \"\", userEditor, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/duplicate?toTeam=empty-team\", methodPost, \"\", userAdmin, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/duplicate?toTeam=empty-team\", methodPost, \"\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/duplicate?toTeam=empty-team\", methodPost, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/duplicate?toTeam=empty-team\", methodPost, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/duplicate?toTeam=empty-team\", methodPost, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/duplicate?toTeam=empty-team\", methodPost, \"\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/duplicate?toTeam=empty-team\", methodPost, \"\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/duplicate?toTeam=empty-team\", methodPost, \"\", userEditor, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/duplicate?toTeam=empty-team\", methodPost, \"\", userAdmin, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/duplicate?toTeam=empty-team\", methodPost, \"\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/duplicate?toTeam=empty-team\", methodPost, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/duplicate?toTeam=empty-team\", methodPost, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/duplicate?toTeam=empty-team\", methodPost, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/duplicate?toTeam=empty-team\", methodPost, \"\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/duplicate?toTeam=empty-team\", methodPost, \"\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/duplicate?toTeam=empty-team\", methodPost, \"\", userEditor, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/duplicate?toTeam=empty-team\", methodPost, \"\", userAdmin, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/duplicate?toTeam=empty-team\", methodPost, \"\", userGuest, http.StatusForbidden, 0},\n\t}\n\tt.Run(\"plugin-empty-team\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsGetBoardBlocks(t *testing.T) {\n\tttCases := []TestCase{\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks\", methodGet, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks\", methodGet, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks\", methodGet, \"\", userViewer, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks\", methodGet, \"\", userCommenter, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks\", methodGet, \"\", userEditor, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks\", methodGet, \"\", userAdmin, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks\", methodGet, \"\", userGuest, http.StatusOK, 1},\n\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks\", methodGet, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks\", methodGet, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks\", methodGet, \"\", userViewer, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks\", methodGet, \"\", userCommenter, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks\", methodGet, \"\", userEditor, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks\", methodGet, \"\", userAdmin, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks\", methodGet, \"\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks\", methodGet, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks\", methodGet, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks\", methodGet, \"\", userViewer, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks\", methodGet, \"\", userCommenter, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks\", methodGet, \"\", userEditor, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks\", methodGet, \"\", userAdmin, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks\", methodGet, \"\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks\", methodGet, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks\", methodGet, \"\", userTeamMember, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks\", methodGet, \"\", userViewer, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks\", methodGet, \"\", userCommenter, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks\", methodGet, \"\", userEditor, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks\", methodGet, \"\", userAdmin, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks\", methodGet, \"\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks?read_token=invalid\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks?read_token=valid\", methodGet, \"\", userAnon, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks?read_token=invalid\", methodGet, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks?read_token=valid\", methodGet, \"\", userTeamMember, http.StatusOK, 1},\n\t}\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases[25].expectedStatusCode = http.StatusOK\n\t\tttCases[25].totalResults = 1\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsCreateBoardBlocks(t *testing.T) {\n\tttCasesF := func(testData TestData) []TestCase {\n\t\tcounter := 0\n\t\tnewBlockJSON := func(boardID string) string {\n\t\t\tcounter++\n\t\t\treturn toJSON(t, []*model.Block{{\n\t\t\t\tID:       fmt.Sprintf(\"%d\", counter),\n\t\t\t\tTitle:    \"Board To Create\",\n\t\t\t\tBoardID:  boardID,\n\t\t\t\tType:     \"card\",\n\t\t\t\tCreateAt: model.GetMillis(),\n\t\t\t\tUpdateAt: model.GetMillis(),\n\t\t\t}})\n\t\t}\n\n\t\treturn []TestCase{\n\t\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.privateBoard.ID), userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.privateBoard.ID), userNoTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.privateBoard.ID), userTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.privateBoard.ID), userViewer, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.privateBoard.ID), userCommenter, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.privateBoard.ID), userEditor, http.StatusOK, 1},\n\t\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.privateBoard.ID), userAdmin, http.StatusOK, 1},\n\t\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.privateBoard.ID), userGuest, http.StatusForbidden, 0},\n\n\t\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.publicBoard.ID), userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.publicBoard.ID), userNoTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.publicBoard.ID), userTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.publicBoard.ID), userViewer, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.publicBoard.ID), userCommenter, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.publicBoard.ID), userEditor, http.StatusOK, 1},\n\t\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.publicBoard.ID), userAdmin, http.StatusOK, 1},\n\t\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.publicBoard.ID), userGuest, http.StatusForbidden, 0},\n\n\t\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.privateTemplate.ID), userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.privateTemplate.ID), userNoTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.privateTemplate.ID), userTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.privateTemplate.ID), userViewer, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.privateTemplate.ID), userCommenter, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.privateTemplate.ID), userEditor, http.StatusOK, 1},\n\t\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.privateTemplate.ID), userAdmin, http.StatusOK, 1},\n\t\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.privateTemplate.ID), userGuest, http.StatusForbidden, 0},\n\n\t\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.publicTemplate.ID), userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.publicTemplate.ID), userNoTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.publicTemplate.ID), userTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.publicTemplate.ID), userViewer, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.publicTemplate.ID), userCommenter, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.publicTemplate.ID), userEditor, http.StatusOK, 1},\n\t\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.publicTemplate.ID), userAdmin, http.StatusOK, 1},\n\t\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.publicTemplate.ID), userGuest, http.StatusForbidden, 0},\n\t\t}\n\t}\n\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases := ttCasesF(testData)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases := ttCasesF(testData)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsCreateBoardComments(t *testing.T) {\n\tttCasesF := func(testData TestData) []TestCase {\n\t\tcounter := 0\n\t\tnewBlockJSON := func(boardID string) string {\n\t\t\tcounter++\n\t\t\treturn toJSON(t, []*model.Block{{\n\t\t\t\tID:       fmt.Sprintf(\"%d\", counter),\n\t\t\t\tTitle:    \"Comment to create\",\n\t\t\t\tBoardID:  boardID,\n\t\t\t\tType:     model.TypeComment,\n\t\t\t\tCreateAt: model.GetMillis(),\n\t\t\t\tUpdateAt: model.GetMillis(),\n\t\t\t}})\n\t\t}\n\n\t\treturn []TestCase{\n\t\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.privateBoard.ID), userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.privateBoard.ID), userNoTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.privateBoard.ID), userTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.privateBoard.ID), userViewer, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.privateBoard.ID), userCommenter, http.StatusOK, 1},\n\t\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.privateBoard.ID), userEditor, http.StatusOK, 1},\n\t\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.privateBoard.ID), userAdmin, http.StatusOK, 1},\n\n\t\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.publicBoard.ID), userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.publicBoard.ID), userNoTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.publicBoard.ID), userTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.publicBoard.ID), userViewer, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.publicBoard.ID), userCommenter, http.StatusOK, 1},\n\t\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.publicBoard.ID), userEditor, http.StatusOK, 1},\n\t\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.publicBoard.ID), userAdmin, http.StatusOK, 1},\n\n\t\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.privateTemplate.ID), userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.privateTemplate.ID), userNoTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.privateTemplate.ID), userTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.privateTemplate.ID), userViewer, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.privateTemplate.ID), userCommenter, http.StatusOK, 1},\n\t\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.privateTemplate.ID), userEditor, http.StatusOK, 1},\n\t\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.privateTemplate.ID), userAdmin, http.StatusOK, 1},\n\n\t\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.publicTemplate.ID), userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.publicTemplate.ID), userNoTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.publicTemplate.ID), userTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.publicTemplate.ID), userViewer, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.publicTemplate.ID), userCommenter, http.StatusOK, 1},\n\t\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.publicTemplate.ID), userEditor, http.StatusOK, 1},\n\t\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.publicTemplate.ID), userAdmin, http.StatusOK, 1},\n\t\t}\n\t}\n\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases := ttCasesF(testData)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases := ttCasesF(testData)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsPatchBoardBlocks(t *testing.T) {\n\tnewBlocksPatchJSON := func(blockID string) string {\n\t\tnewTitle := \"New Patch Block Title\"\n\t\treturn toJSON(t, model.BlockPatchBatch{\n\t\t\tBlockIDs: []string{blockID},\n\t\t\tBlockPatches: []model.BlockPatch{\n\t\t\t\t{Title: &newTitle},\n\t\t\t},\n\t\t})\n\t}\n\n\tttCases := []TestCase{\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks\", methodPatch, newBlocksPatchJSON(\"block-4\"), userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks\", methodPatch, newBlocksPatchJSON(\"block-4\"), userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks\", methodPatch, newBlocksPatchJSON(\"block-4\"), userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks\", methodPatch, newBlocksPatchJSON(\"block-4\"), userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks\", methodPatch, newBlocksPatchJSON(\"block-4\"), userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks\", methodPatch, newBlocksPatchJSON(\"block-4\"), userEditor, http.StatusOK, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks\", methodPatch, newBlocksPatchJSON(\"block-4\"), userAdmin, http.StatusOK, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks\", methodPatch, newBlocksPatchJSON(\"block-4\"), userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks\", methodPatch, newBlocksPatchJSON(\"block-3\"), userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks\", methodPatch, newBlocksPatchJSON(\"block-3\"), userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks\", methodPatch, newBlocksPatchJSON(\"block-3\"), userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks\", methodPatch, newBlocksPatchJSON(\"block-3\"), userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks\", methodPatch, newBlocksPatchJSON(\"block-3\"), userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks\", methodPatch, newBlocksPatchJSON(\"block-3\"), userEditor, http.StatusOK, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks\", methodPatch, newBlocksPatchJSON(\"block-3\"), userAdmin, http.StatusOK, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks\", methodPatch, newBlocksPatchJSON(\"block-3\"), userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks\", methodPatch, newBlocksPatchJSON(\"block-2\"), userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks\", methodPatch, newBlocksPatchJSON(\"block-2\"), userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks\", methodPatch, newBlocksPatchJSON(\"block-2\"), userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks\", methodPatch, newBlocksPatchJSON(\"block-2\"), userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks\", methodPatch, newBlocksPatchJSON(\"block-2\"), userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks\", methodPatch, newBlocksPatchJSON(\"block-2\"), userEditor, http.StatusOK, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks\", methodPatch, newBlocksPatchJSON(\"block-2\"), userAdmin, http.StatusOK, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks\", methodPatch, newBlocksPatchJSON(\"block-2\"), userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks\", methodPatch, newBlocksPatchJSON(\"block-1\"), userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks\", methodPatch, newBlocksPatchJSON(\"block-1\"), userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks\", methodPatch, newBlocksPatchJSON(\"block-1\"), userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks\", methodPatch, newBlocksPatchJSON(\"block-1\"), userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks\", methodPatch, newBlocksPatchJSON(\"block-1\"), userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks\", methodPatch, newBlocksPatchJSON(\"block-1\"), userEditor, http.StatusOK, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks\", methodPatch, newBlocksPatchJSON(\"block-1\"), userAdmin, http.StatusOK, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks\", methodPatch, newBlocksPatchJSON(\"block-1\"), userGuest, http.StatusForbidden, 0},\n\t}\n\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsPatchBoardBlock(t *testing.T) {\n\tnewTitle := \"New Patch Title\"\n\tpatchJSON := toJSON(t, model.BlockPatch{Title: &newTitle})\n\n\tttCases := []TestCase{\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks/block-4\", methodPatch, patchJSON, userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks/block-4\", methodPatch, patchJSON, userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks/block-4\", methodPatch, patchJSON, userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks/block-4\", methodPatch, patchJSON, userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks/block-4\", methodPatch, patchJSON, userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks/block-4\", methodPatch, patchJSON, userEditor, http.StatusOK, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks/block-4\", methodPatch, patchJSON, userAdmin, http.StatusOK, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks/block-4\", methodPatch, patchJSON, userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks/block-3\", methodPatch, patchJSON, userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks/block-3\", methodPatch, patchJSON, userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks/block-3\", methodPatch, patchJSON, userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks/block-3\", methodPatch, patchJSON, userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks/block-3\", methodPatch, patchJSON, userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks/block-3\", methodPatch, patchJSON, userEditor, http.StatusOK, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks/block-3\", methodPatch, patchJSON, userAdmin, http.StatusOK, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks/block-3\", methodPatch, patchJSON, userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-2\", methodPatch, patchJSON, userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-2\", methodPatch, patchJSON, userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-2\", methodPatch, patchJSON, userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-2\", methodPatch, patchJSON, userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-2\", methodPatch, patchJSON, userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-2\", methodPatch, patchJSON, userEditor, http.StatusOK, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-2\", methodPatch, patchJSON, userAdmin, http.StatusOK, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-2\", methodPatch, patchJSON, userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-1\", methodPatch, patchJSON, userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-1\", methodPatch, patchJSON, userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-1\", methodPatch, patchJSON, userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-1\", methodPatch, patchJSON, userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-1\", methodPatch, patchJSON, userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-1\", methodPatch, patchJSON, userEditor, http.StatusOK, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-1\", methodPatch, patchJSON, userAdmin, http.StatusOK, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-1\", methodPatch, patchJSON, userGuest, http.StatusForbidden, 0},\n\n\t\t// Invalid boardID/blockID combination\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-3\", methodPatch, patchJSON, userAdmin, http.StatusNotFound, 0},\n\t}\n\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsDeleteBoardBlock(t *testing.T) {\n\textraSetup := func(t *testing.T, th *TestHelper, testData TestData) {\n\t\terr := th.Server.App().InsertBlock(&model.Block{ID: \"block-5\", Title: \"Test\", Type: \"card\", BoardID: testData.publicTemplate.ID}, userAdmin)\n\t\trequire.NoError(t, err)\n\t\terr = th.Server.App().InsertBlock(&model.Block{ID: \"block-6\", Title: \"Test\", Type: \"card\", BoardID: testData.privateTemplate.ID}, userAdmin)\n\t\trequire.NoError(t, err)\n\t\terr = th.Server.App().InsertBlock(&model.Block{ID: \"block-7\", Title: \"Test\", Type: \"card\", BoardID: testData.publicBoard.ID}, userAdmin)\n\t\trequire.NoError(t, err)\n\t\terr = th.Server.App().InsertBlock(&model.Block{ID: \"block-8\", Title: \"Test\", Type: \"card\", BoardID: testData.privateBoard.ID}, userAdmin)\n\t\trequire.NoError(t, err)\n\t}\n\n\tttCases := []TestCase{\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks/block-4\", methodDelete, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks/block-4\", methodDelete, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks/block-4\", methodDelete, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks/block-4\", methodDelete, \"\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks/block-4\", methodDelete, \"\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks/block-4\", methodDelete, \"\", userEditor, http.StatusOK, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks/block-8\", methodDelete, \"\", userAdmin, http.StatusOK, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks/block-4\", methodDelete, \"\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks/block-3\", methodDelete, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks/block-3\", methodDelete, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks/block-3\", methodDelete, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks/block-3\", methodDelete, \"\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks/block-3\", methodDelete, \"\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks/block-3\", methodDelete, \"\", userEditor, http.StatusOK, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks/block-7\", methodDelete, \"\", userAdmin, http.StatusOK, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks/block-3\", methodDelete, \"\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-2\", methodDelete, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-2\", methodDelete, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-2\", methodDelete, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-2\", methodDelete, \"\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-2\", methodDelete, \"\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-2\", methodDelete, \"\", userEditor, http.StatusOK, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-6\", methodDelete, \"\", userAdmin, http.StatusOK, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-2\", methodDelete, \"\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-1\", methodDelete, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-1\", methodDelete, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-1\", methodDelete, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-1\", methodDelete, \"\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-1\", methodDelete, \"\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-1\", methodDelete, \"\", userEditor, http.StatusOK, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-5\", methodDelete, \"\", userAdmin, http.StatusOK, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-1\", methodDelete, \"\", userGuest, http.StatusForbidden, 0},\n\n\t\t// Invalid boardID/blockID combination\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-3\", methodDelete, \"\", userAdmin, http.StatusNotFound, 0},\n\t}\n\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\textraSetup(t, th, testData)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\textraSetup(t, th, testData)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsUndeleteBoardBlock(t *testing.T) {\n\textraSetup := func(t *testing.T, th *TestHelper, testData TestData) {\n\t\terr := th.Server.App().InsertBlock(&model.Block{ID: \"block-5\", Title: \"Test\", Type: \"card\", BoardID: testData.publicTemplate.ID}, userAdmin)\n\t\trequire.NoError(t, err)\n\t\terr = th.Server.App().InsertBlock(&model.Block{ID: \"block-6\", Title: \"Test\", Type: \"card\", BoardID: testData.privateTemplate.ID}, userAdmin)\n\t\trequire.NoError(t, err)\n\t\terr = th.Server.App().InsertBlock(&model.Block{ID: \"block-7\", Title: \"Test\", Type: \"card\", BoardID: testData.publicBoard.ID}, userAdmin)\n\t\trequire.NoError(t, err)\n\t\terr = th.Server.App().InsertBlock(&model.Block{ID: \"block-8\", Title: \"Test\", Type: \"card\", BoardID: testData.privateBoard.ID}, userAdmin)\n\t\trequire.NoError(t, err)\n\t\terr = th.Server.App().DeleteBlock(\"block-1\", userAdmin)\n\t\trequire.NoError(t, err)\n\t\terr = th.Server.App().DeleteBlock(\"block-2\", userAdmin)\n\t\trequire.NoError(t, err)\n\t\terr = th.Server.App().DeleteBlock(\"block-3\", userAdmin)\n\t\trequire.NoError(t, err)\n\t\terr = th.Server.App().DeleteBlock(\"block-4\", userAdmin)\n\t\trequire.NoError(t, err)\n\t\terr = th.Server.App().DeleteBlock(\"block-5\", userAdmin)\n\t\trequire.NoError(t, err)\n\t\terr = th.Server.App().DeleteBlock(\"block-6\", userAdmin)\n\t\trequire.NoError(t, err)\n\t\terr = th.Server.App().DeleteBlock(\"block-7\", userAdmin)\n\t\trequire.NoError(t, err)\n\t\terr = th.Server.App().DeleteBlock(\"block-8\", userAdmin)\n\t\trequire.NoError(t, err)\n\t}\n\n\tttCases := []TestCase{\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks/block-4/undelete\", methodPost, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks/block-4/undelete\", methodPost, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks/block-4/undelete\", methodPost, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks/block-4/undelete\", methodPost, \"\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks/block-4/undelete\", methodPost, \"\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks/block-4/undelete\", methodPost, \"\", userEditor, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks/block-8/undelete\", methodPost, \"\", userAdmin, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks/block-4/undelete\", methodPost, \"\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks/block-3/undelete\", methodPost, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks/block-3/undelete\", methodPost, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks/block-3/undelete\", methodPost, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks/block-3/undelete\", methodPost, \"\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks/block-3/undelete\", methodPost, \"\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks/block-3/undelete\", methodPost, \"\", userEditor, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks/block-7/undelete\", methodPost, \"\", userAdmin, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks/block-3/undelete\", methodPost, \"\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-2/undelete\", methodPost, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-2/undelete\", methodPost, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-2/undelete\", methodPost, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-2/undelete\", methodPost, \"\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-2/undelete\", methodPost, \"\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-2/undelete\", methodPost, \"\", userEditor, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-6/undelete\", methodPost, \"\", userAdmin, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-2/undelete\", methodPost, \"\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-1/undelete\", methodPost, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-1/undelete\", methodPost, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-1/undelete\", methodPost, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-1/undelete\", methodPost, \"\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-1/undelete\", methodPost, \"\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-1/undelete\", methodPost, \"\", userEditor, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-5/undelete\", methodPost, \"\", userAdmin, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-1/undelete\", methodPost, \"\", userGuest, http.StatusForbidden, 0},\n\n\t\t// Invalid boardID/blockID combination\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-3/undelete\", methodPost, \"\", userAdmin, http.StatusNotFound, 0},\n\t}\n\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\textraSetup(t, th, testData)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\textraSetup(t, th, testData)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsMoveContentBlock(t *testing.T) {\n\textraSetup := func(t *testing.T, th *TestHelper, testData TestData) {\n\t\terr := th.Server.App().InsertBlock(&model.Block{ID: \"content-1-1\", Title: \"Test\", Type: \"text\", BoardID: testData.publicTemplate.ID, ParentID: \"block-1\"}, userAdmin)\n\t\trequire.NoError(t, err)\n\t\terr = th.Server.App().InsertBlock(&model.Block{ID: \"content-1-2\", Title: \"Test\", Type: \"text\", BoardID: testData.publicTemplate.ID, ParentID: \"block-1\"}, userAdmin)\n\t\trequire.NoError(t, err)\n\t\t_, err = th.Server.App().PatchBlock(\"block-1\", &model.BlockPatch{UpdatedFields: map[string]interface{}{\"contentOrder\": []string{\"content-1-1\", \"content-1-2\"}}}, userAdmin)\n\t\trequire.NoError(t, err)\n\t\terr = th.Server.App().InsertBlock(&model.Block{ID: \"content-2-1\", Title: \"Test\", Type: \"text\", BoardID: testData.privateTemplate.ID, ParentID: \"block-2\"}, userAdmin)\n\t\trequire.NoError(t, err)\n\t\terr = th.Server.App().InsertBlock(&model.Block{ID: \"content-2-2\", Title: \"Test\", Type: \"text\", BoardID: testData.privateTemplate.ID, ParentID: \"block-2\"}, userAdmin)\n\t\trequire.NoError(t, err)\n\t\t_, err = th.Server.App().PatchBlock(\"block-2\", &model.BlockPatch{UpdatedFields: map[string]interface{}{\"contentOrder\": []string{\"content-2-1\", \"content-2-2\"}}}, userAdmin)\n\t\trequire.NoError(t, err)\n\t\terr = th.Server.App().InsertBlock(&model.Block{ID: \"content-3-1\", Title: \"Test\", Type: \"text\", BoardID: testData.publicBoard.ID, ParentID: \"block-3\"}, userAdmin)\n\t\trequire.NoError(t, err)\n\t\terr = th.Server.App().InsertBlock(&model.Block{ID: \"content-3-2\", Title: \"Test\", Type: \"text\", BoardID: testData.publicBoard.ID, ParentID: \"block-3\"}, userAdmin)\n\t\trequire.NoError(t, err)\n\t\t_, err = th.Server.App().PatchBlock(\"block-3\", &model.BlockPatch{UpdatedFields: map[string]interface{}{\"contentOrder\": []string{\"content-3-1\", \"content-3-2\"}}}, userAdmin)\n\t\trequire.NoError(t, err)\n\t\terr = th.Server.App().InsertBlock(&model.Block{ID: \"content-4-1\", Title: \"Test\", Type: \"text\", BoardID: testData.privateBoard.ID, ParentID: \"block-4\"}, userAdmin)\n\t\trequire.NoError(t, err)\n\t\terr = th.Server.App().InsertBlock(&model.Block{ID: \"content-4-2\", Title: \"Test\", Type: \"text\", BoardID: testData.privateBoard.ID, ParentID: \"block-4\"}, userAdmin)\n\t\trequire.NoError(t, err)\n\t\t_, err = th.Server.App().PatchBlock(\"block-4\", &model.BlockPatch{UpdatedFields: map[string]interface{}{\"contentOrder\": []string{\"content-4-1\", \"content-4-2\"}}}, userAdmin)\n\t\trequire.NoError(t, err)\n\t}\n\n\tttCases := []TestCase{\n\t\t{\"/content-blocks/content-4-1/moveto/after/content-4-2\", methodPost, \"{}\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/content-blocks/content-4-1/moveto/after/content-4-2\", methodPost, \"{}\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/content-blocks/content-4-1/moveto/after/content-4-2\", methodPost, \"{}\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/content-blocks/content-4-1/moveto/after/content-4-2\", methodPost, \"{}\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/content-blocks/content-4-1/moveto/after/content-4-2\", methodPost, \"{}\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/content-blocks/content-4-1/moveto/after/content-4-2\", methodPost, \"{}\", userEditor, http.StatusOK, 0},\n\t\t{\"/content-blocks/content-4-1/moveto/after/content-4-2\", methodPost, \"{}\", userAdmin, http.StatusOK, 0},\n\t\t{\"/content-blocks/content-4-1/moveto/after/content-4-2\", methodPost, \"{}\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/content-blocks/content-3-1/moveto/after/content-3-2\", methodPost, \"{}\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/content-blocks/content-3-1/moveto/after/content-3-2\", methodPost, \"{}\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/content-blocks/content-3-1/moveto/after/content-3-2\", methodPost, \"{}\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/content-blocks/content-3-1/moveto/after/content-3-2\", methodPost, \"{}\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/content-blocks/content-3-1/moveto/after/content-3-2\", methodPost, \"{}\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/content-blocks/content-3-1/moveto/after/content-3-2\", methodPost, \"{}\", userEditor, http.StatusOK, 0},\n\t\t{\"/content-blocks/content-3-1/moveto/after/content-3-2\", methodPost, \"{}\", userAdmin, http.StatusOK, 0},\n\t\t{\"/content-blocks/content-3-1/moveto/after/content-3-2\", methodPost, \"{}\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/content-blocks/content-2-1/moveto/after/content-2-2\", methodPost, \"{}\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/content-blocks/content-2-1/moveto/after/content-2-2\", methodPost, \"{}\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/content-blocks/content-2-1/moveto/after/content-2-2\", methodPost, \"{}\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/content-blocks/content-2-1/moveto/after/content-2-2\", methodPost, \"{}\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/content-blocks/content-2-1/moveto/after/content-2-2\", methodPost, \"{}\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/content-blocks/content-2-1/moveto/after/content-2-2\", methodPost, \"{}\", userEditor, http.StatusOK, 0},\n\t\t{\"/content-blocks/content-2-1/moveto/after/content-2-2\", methodPost, \"{}\", userAdmin, http.StatusOK, 0},\n\t\t{\"/content-blocks/content-2-1/moveto/after/content-2-2\", methodPost, \"{}\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/content-blocks/content-1-1/moveto/after/content-1-2\", methodPost, \"{}\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/content-blocks/content-1-1/moveto/after/content-1-2\", methodPost, \"{}\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/content-blocks/content-1-1/moveto/after/content-1-2\", methodPost, \"{}\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/content-blocks/content-1-1/moveto/after/content-1-2\", methodPost, \"{}\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/content-blocks/content-1-1/moveto/after/content-1-2\", methodPost, \"{}\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/content-blocks/content-1-1/moveto/after/content-1-2\", methodPost, \"{}\", userEditor, http.StatusOK, 0},\n\t\t{\"/content-blocks/content-1-1/moveto/after/content-1-2\", methodPost, \"{}\", userAdmin, http.StatusOK, 0},\n\t\t{\"/content-blocks/content-1-1/moveto/after/content-1-2\", methodPost, \"{}\", userGuest, http.StatusForbidden, 0},\n\n\t\t// Invalid srcBlockID/dstBlockID combination\n\t\t{\"/content-blocks/content-1-1/moveto/after/content-2-1\", methodPost, \"{}\", userAdmin, http.StatusBadRequest, 0},\n\t}\n\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\textraSetup(t, th, testData)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\textraSetup(t, th, testData)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsUndeleteBoard(t *testing.T) {\n\textraSetup := func(t *testing.T, th *TestHelper, testData TestData) {\n\t\terr := th.Server.App().DeleteBoard(testData.publicBoard.ID, userAdmin)\n\t\trequire.NoError(t, err)\n\t\terr = th.Server.App().DeleteBoard(testData.privateBoard.ID, userAdmin)\n\t\trequire.NoError(t, err)\n\t\terr = th.Server.App().DeleteBoard(testData.publicTemplate.ID, userAdmin)\n\t\trequire.NoError(t, err)\n\t\terr = th.Server.App().DeleteBoard(testData.privateTemplate.ID, userAdmin)\n\t\trequire.NoError(t, err)\n\t}\n\n\tttCases := []TestCase{\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/undelete\", methodPost, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/undelete\", methodPost, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/undelete\", methodPost, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/undelete\", methodPost, \"\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/undelete\", methodPost, \"\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/undelete\", methodPost, \"\", userEditor, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/undelete\", methodPost, \"\", userAdmin, http.StatusOK, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/undelete\", methodPost, \"\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/undelete\", methodPost, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/undelete\", methodPost, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/undelete\", methodPost, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/undelete\", methodPost, \"\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/undelete\", methodPost, \"\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/undelete\", methodPost, \"\", userEditor, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/undelete\", methodPost, \"\", userAdmin, http.StatusOK, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/undelete\", methodPost, \"\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/undelete\", methodPost, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/undelete\", methodPost, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/undelete\", methodPost, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/undelete\", methodPost, \"\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/undelete\", methodPost, \"\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/undelete\", methodPost, \"\", userEditor, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/undelete\", methodPost, \"\", userAdmin, http.StatusOK, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/undelete\", methodPost, \"\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/undelete\", methodPost, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/undelete\", methodPost, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/undelete\", methodPost, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/undelete\", methodPost, \"\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/undelete\", methodPost, \"\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/undelete\", methodPost, \"\", userEditor, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/undelete\", methodPost, \"\", userAdmin, http.StatusOK, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/undelete\", methodPost, \"\", userGuest, http.StatusForbidden, 0},\n\t}\n\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\textraSetup(t, th, testData)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\textraSetup(t, th, testData)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsDuplicateBoardBlock(t *testing.T) {\n\textraSetup := func(t *testing.T, th *TestHelper, testData TestData) {\n\t\terr := th.Server.App().InsertBlock(&model.Block{ID: \"block-5\", Title: \"Test\", Type: \"card\", BoardID: testData.publicTemplate.ID}, userAdmin)\n\t\trequire.NoError(t, err)\n\t\terr = th.Server.App().InsertBlock(&model.Block{ID: \"block-6\", Title: \"Test\", Type: \"card\", BoardID: testData.privateTemplate.ID}, userAdmin)\n\t\trequire.NoError(t, err)\n\t\terr = th.Server.App().InsertBlock(&model.Block{ID: \"block-7\", Title: \"Test\", Type: \"card\", BoardID: testData.publicBoard.ID}, userAdmin)\n\t\trequire.NoError(t, err)\n\t\terr = th.Server.App().InsertBlock(&model.Block{ID: \"block-8\", Title: \"Test\", Type: \"card\", BoardID: testData.privateBoard.ID}, userAdmin)\n\t\trequire.NoError(t, err)\n\t}\n\n\tttCases := []TestCase{\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks/block-4/duplicate\", methodPost, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks/block-4/duplicate\", methodPost, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks/block-4/duplicate\", methodPost, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks/block-4/duplicate\", methodPost, \"\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks/block-4/duplicate\", methodPost, \"\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks/block-4/duplicate\", methodPost, \"\", userEditor, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks/block-4/duplicate\", methodPost, \"\", userAdmin, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks/block-4/duplicate\", methodPost, \"\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks/block-3/duplicate\", methodPost, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks/block-3/duplicate\", methodPost, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks/block-3/duplicate\", methodPost, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks/block-3/duplicate\", methodPost, \"\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks/block-3/duplicate\", methodPost, \"\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks/block-3/duplicate\", methodPost, \"\", userEditor, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks/block-3/duplicate\", methodPost, \"\", userAdmin, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks/block-3/duplicate\", methodPost, \"\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-2/duplicate\", methodPost, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-2/duplicate\", methodPost, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-2/duplicate\", methodPost, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-2/duplicate\", methodPost, \"\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-2/duplicate\", methodPost, \"\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-2/duplicate\", methodPost, \"\", userEditor, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-2/duplicate\", methodPost, \"\", userAdmin, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks/block-2/duplicate\", methodPost, \"\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-1/duplicate\", methodPost, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-1/duplicate\", methodPost, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-1/duplicate\", methodPost, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-1/duplicate\", methodPost, \"\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-1/duplicate\", methodPost, \"\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-1/duplicate\", methodPost, \"\", userEditor, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-1/duplicate\", methodPost, \"\", userAdmin, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-1/duplicate\", methodPost, \"\", userGuest, http.StatusForbidden, 0},\n\n\t\t// Invalid boardID/blockID combination\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks/block-3/duplicate\", methodPost, \"\", userAdmin, http.StatusNotFound, 0},\n\t}\n\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\textraSetup(t, th, testData)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\textraSetup(t, th, testData)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsGetBoardMembers(t *testing.T) {\n\tttCases := []TestCase{\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/members\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/members\", methodGet, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/members\", methodGet, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/members\", methodGet, \"\", userViewer, http.StatusOK, 5},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/members\", methodGet, \"\", userCommenter, http.StatusOK, 5},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/members\", methodGet, \"\", userEditor, http.StatusOK, 5},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/members\", methodGet, \"\", userAdmin, http.StatusOK, 5},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/members\", methodGet, \"\", userGuest, http.StatusOK, 5},\n\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/members\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/members\", methodGet, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/members\", methodGet, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/members\", methodGet, \"\", userViewer, http.StatusOK, 4},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/members\", methodGet, \"\", userCommenter, http.StatusOK, 4},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/members\", methodGet, \"\", userEditor, http.StatusOK, 4},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/members\", methodGet, \"\", userAdmin, http.StatusOK, 4},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/members\", methodGet, \"\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/members\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/members\", methodGet, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/members\", methodGet, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/members\", methodGet, \"\", userViewer, http.StatusOK, 4},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/members\", methodGet, \"\", userCommenter, http.StatusOK, 4},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/members\", methodGet, \"\", userEditor, http.StatusOK, 4},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/members\", methodGet, \"\", userAdmin, http.StatusOK, 4},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/members\", methodGet, \"\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/members\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/members\", methodGet, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/members\", methodGet, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/members\", methodGet, \"\", userViewer, http.StatusOK, 4},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/members\", methodGet, \"\", userCommenter, http.StatusOK, 4},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/members\", methodGet, \"\", userEditor, http.StatusOK, 4},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/members\", methodGet, \"\", userAdmin, http.StatusOK, 4},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/members\", methodGet, \"\", userGuest, http.StatusForbidden, 0},\n\t}\n\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsCreateBoardMembers(t *testing.T) {\n\tttCasesF := func(testData TestData) []TestCase {\n\t\tboardMemberJSON := func(boardID string) string {\n\t\t\treturn toJSON(t, model.BoardMember{\n\t\t\t\tBoardID:      boardID,\n\t\t\t\tUserID:       userTeamMemberID,\n\t\t\t\tSchemeEditor: true,\n\t\t\t})\n\t\t}\n\n\t\treturn []TestCase{\n\t\t\t{\"/boards/{PRIVATE_BOARD_ID}/members\", methodPost, boardMemberJSON(testData.privateBoard.ID), userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/boards/{PRIVATE_BOARD_ID}/members\", methodPost, boardMemberJSON(testData.privateBoard.ID), userNoTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PRIVATE_BOARD_ID}/members\", methodPost, boardMemberJSON(testData.privateBoard.ID), userTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PRIVATE_BOARD_ID}/members\", methodPost, boardMemberJSON(testData.privateBoard.ID), userViewer, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PRIVATE_BOARD_ID}/members\", methodPost, boardMemberJSON(testData.privateBoard.ID), userCommenter, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PRIVATE_BOARD_ID}/members\", methodPost, boardMemberJSON(testData.privateBoard.ID), userEditor, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PRIVATE_BOARD_ID}/members\", methodPost, boardMemberJSON(testData.privateBoard.ID), userAdmin, http.StatusOK, 1},\n\t\t\t{\"/boards/{PRIVATE_BOARD_ID}/members\", methodPost, boardMemberJSON(testData.privateBoard.ID), userGuest, http.StatusForbidden, 0},\n\n\t\t\t{\"/boards/{PUBLIC_BOARD_ID}/members\", methodPost, boardMemberJSON(testData.publicBoard.ID), userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/boards/{PUBLIC_BOARD_ID}/members\", methodPost, boardMemberJSON(testData.publicBoard.ID), userNoTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PUBLIC_BOARD_ID}/members\", methodPost, boardMemberJSON(testData.publicBoard.ID), userTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PUBLIC_BOARD_ID}/members\", methodPost, boardMemberJSON(testData.publicBoard.ID), userViewer, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PUBLIC_BOARD_ID}/members\", methodPost, boardMemberJSON(testData.publicBoard.ID), userCommenter, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PUBLIC_BOARD_ID}/members\", methodPost, boardMemberJSON(testData.publicBoard.ID), userEditor, http.StatusOK, 1},\n\t\t\t{\"/boards/{PUBLIC_BOARD_ID}/members\", methodPost, boardMemberJSON(testData.publicBoard.ID), userAdmin, http.StatusOK, 1},\n\t\t\t{\"/boards/{PUBLIC_BOARD_ID}/members\", methodPost, boardMemberJSON(testData.publicBoard.ID), userGuest, http.StatusForbidden, 0},\n\n\t\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/members\", methodPost, boardMemberJSON(testData.privateTemplate.ID), userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/members\", methodPost, boardMemberJSON(testData.privateTemplate.ID), userNoTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/members\", methodPost, boardMemberJSON(testData.privateTemplate.ID), userTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/members\", methodPost, boardMemberJSON(testData.privateTemplate.ID), userViewer, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/members\", methodPost, boardMemberJSON(testData.privateTemplate.ID), userCommenter, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/members\", methodPost, boardMemberJSON(testData.privateTemplate.ID), userEditor, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/members\", methodPost, boardMemberJSON(testData.privateTemplate.ID), userAdmin, http.StatusOK, 1},\n\t\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/members\", methodPost, boardMemberJSON(testData.privateTemplate.ID), userGuest, http.StatusForbidden, 0},\n\n\t\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/members\", methodPost, boardMemberJSON(testData.publicTemplate.ID), userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/members\", methodPost, boardMemberJSON(testData.publicTemplate.ID), userNoTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/members\", methodPost, boardMemberJSON(testData.publicTemplate.ID), userTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/members\", methodPost, boardMemberJSON(testData.publicTemplate.ID), userViewer, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/members\", methodPost, boardMemberJSON(testData.publicTemplate.ID), userCommenter, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/members\", methodPost, boardMemberJSON(testData.publicTemplate.ID), userEditor, http.StatusOK, 1},\n\t\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/members\", methodPost, boardMemberJSON(testData.publicTemplate.ID), userAdmin, http.StatusOK, 1},\n\t\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/members\", methodPost, boardMemberJSON(testData.publicTemplate.ID), userGuest, http.StatusForbidden, 0},\n\t\t}\n\t}\n\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases := ttCasesF(testData)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases := ttCasesF(testData)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsUpdateBoardMember(t *testing.T) {\n\tttCasesF := func(testData TestData) []TestCase {\n\t\tboardMemberJSON := func(boardID string) string {\n\t\t\treturn toJSON(t, model.BoardMember{\n\t\t\t\tBoardID:      boardID,\n\t\t\t\tUserID:       userTeamMember,\n\t\t\t\tSchemeEditor: false,\n\t\t\t\tSchemeViewer: true,\n\t\t\t})\n\t\t}\n\n\t\treturn []TestCase{\n\t\t\t{\"/boards/{PRIVATE_BOARD_ID}/members/{USER_VIEWER_ID}\", methodPut, boardMemberJSON(testData.privateBoard.ID), userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/boards/{PRIVATE_BOARD_ID}/members/{USER_VIEWER_ID}\", methodPut, boardMemberJSON(testData.privateBoard.ID), userNoTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PRIVATE_BOARD_ID}/members/{USER_VIEWER_ID}\", methodPut, boardMemberJSON(testData.privateBoard.ID), userTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PRIVATE_BOARD_ID}/members/{USER_VIEWER_ID}\", methodPut, boardMemberJSON(testData.privateBoard.ID), userViewer, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PRIVATE_BOARD_ID}/members/{USER_VIEWER_ID}\", methodPut, boardMemberJSON(testData.privateBoard.ID), userCommenter, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PRIVATE_BOARD_ID}/members/{USER_VIEWER_ID}\", methodPut, boardMemberJSON(testData.privateBoard.ID), userEditor, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PRIVATE_BOARD_ID}/members/{USER_VIEWER_ID}\", methodPut, boardMemberJSON(testData.privateBoard.ID), userAdmin, http.StatusOK, 1},\n\t\t\t{\"/boards/{PRIVATE_BOARD_ID}/members/{USER_VIEWER_ID}\", methodPut, boardMemberJSON(testData.privateBoard.ID), userGuest, http.StatusForbidden, 0},\n\n\t\t\t{\"/boards/{PUBLIC_BOARD_ID}/members/{USER_VIEWER_ID}\", methodPut, boardMemberJSON(testData.publicBoard.ID), userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/boards/{PUBLIC_BOARD_ID}/members/{USER_VIEWER_ID}\", methodPut, boardMemberJSON(testData.publicBoard.ID), userNoTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PUBLIC_BOARD_ID}/members/{USER_VIEWER_ID}\", methodPut, boardMemberJSON(testData.publicBoard.ID), userTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PUBLIC_BOARD_ID}/members/{USER_VIEWER_ID}\", methodPut, boardMemberJSON(testData.publicBoard.ID), userViewer, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PUBLIC_BOARD_ID}/members/{USER_VIEWER_ID}\", methodPut, boardMemberJSON(testData.publicBoard.ID), userCommenter, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PUBLIC_BOARD_ID}/members/{USER_VIEWER_ID}\", methodPut, boardMemberJSON(testData.publicBoard.ID), userEditor, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PUBLIC_BOARD_ID}/members/{USER_VIEWER_ID}\", methodPut, boardMemberJSON(testData.publicBoard.ID), userAdmin, http.StatusOK, 1},\n\t\t\t{\"/boards/{PUBLIC_BOARD_ID}/members/{USER_VIEWER_ID}\", methodPut, boardMemberJSON(testData.publicBoard.ID), userGuest, http.StatusForbidden, 0},\n\n\t\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/members/{USER_VIEWER_ID}\", methodPut, boardMemberJSON(testData.privateTemplate.ID), userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/members/{USER_VIEWER_ID}\", methodPut, boardMemberJSON(testData.privateTemplate.ID), userNoTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/members/{USER_VIEWER_ID}\", methodPut, boardMemberJSON(testData.privateTemplate.ID), userTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/members/{USER_VIEWER_ID}\", methodPut, boardMemberJSON(testData.privateTemplate.ID), userViewer, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/members/{USER_VIEWER_ID}\", methodPut, boardMemberJSON(testData.privateTemplate.ID), userCommenter, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/members/{USER_VIEWER_ID}\", methodPut, boardMemberJSON(testData.privateTemplate.ID), userEditor, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/members/{USER_VIEWER_ID}\", methodPut, boardMemberJSON(testData.privateTemplate.ID), userAdmin, http.StatusOK, 1},\n\t\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/members/{USER_VIEWER_ID}\", methodPut, boardMemberJSON(testData.privateTemplate.ID), userGuest, http.StatusForbidden, 0},\n\n\t\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/members/{USER_VIEWER_ID}\", methodPut, boardMemberJSON(testData.publicTemplate.ID), userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/members/{USER_VIEWER_ID}\", methodPut, boardMemberJSON(testData.publicTemplate.ID), userNoTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/members/{USER_VIEWER_ID}\", methodPut, boardMemberJSON(testData.publicTemplate.ID), userTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/members/{USER_VIEWER_ID}\", methodPut, boardMemberJSON(testData.publicTemplate.ID), userViewer, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/members/{USER_VIEWER_ID}\", methodPut, boardMemberJSON(testData.publicTemplate.ID), userCommenter, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/members/{USER_VIEWER_ID}\", methodPut, boardMemberJSON(testData.publicTemplate.ID), userEditor, http.StatusForbidden, 0},\n\t\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/members/{USER_VIEWER_ID}\", methodPut, boardMemberJSON(testData.publicTemplate.ID), userAdmin, http.StatusOK, 1},\n\t\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/members/{USER_VIEWER_ID}\", methodPut, boardMemberJSON(testData.publicTemplate.ID), userGuest, http.StatusForbidden, 0},\n\n\t\t\t// Invalid boardID/memberID combination\n\t\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/members/{USER_TEAM_MEMBER_ID}\", methodPut, \"\", userAdmin, http.StatusBadRequest, 0},\n\n\t\t\t// Invalid boardID\n\t\t\t{\"/boards/invalid/members/{USER_VIEWER_ID}\", methodPut, \"\", userAdmin, http.StatusBadRequest, 0},\n\n\t\t\t// Invalid memberID\n\t\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/members/invalid\", methodPut, \"\", userAdmin, http.StatusBadRequest, 0},\n\t\t}\n\t}\n\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases := ttCasesF(testData)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases := ttCasesF(testData)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsDeleteBoardMember(t *testing.T) {\n\textraSetup := func(t *testing.T, th *TestHelper, testData TestData) {\n\t\t_, err := th.Server.App().AddMemberToBoard(&model.BoardMember{BoardID: testData.publicBoard.ID, UserID: userTeamMemberID, SchemeViewer: true})\n\t\trequire.NoError(t, err)\n\t\t_, err = th.Server.App().AddMemberToBoard(&model.BoardMember{BoardID: testData.privateBoard.ID, UserID: userTeamMemberID, SchemeViewer: true})\n\t\trequire.NoError(t, err)\n\t\t_, err = th.Server.App().AddMemberToBoard(&model.BoardMember{BoardID: testData.publicTemplate.ID, UserID: userTeamMemberID, SchemeViewer: true})\n\t\trequire.NoError(t, err)\n\t\t_, err = th.Server.App().AddMemberToBoard(&model.BoardMember{BoardID: testData.privateTemplate.ID, UserID: userTeamMemberID, SchemeViewer: true})\n\t\trequire.NoError(t, err)\n\t}\n\n\tttCases := []TestCase{\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/members/{USER_TEAM_MEMBER_ID}\", methodDelete, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/members/{USER_TEAM_MEMBER_ID}\", methodDelete, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/members/{USER_TEAM_MEMBER_ID}\", methodDelete, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/members/{USER_TEAM_MEMBER_ID}\", methodDelete, \"\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/members/{USER_TEAM_MEMBER_ID}\", methodDelete, \"\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/members/{USER_TEAM_MEMBER_ID}\", methodDelete, \"\", userEditor, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/members/{USER_TEAM_MEMBER_ID}\", methodDelete, \"\", userAdmin, http.StatusOK, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/members/{USER_TEAM_MEMBER_ID}\", methodDelete, \"\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/members/{USER_TEAM_MEMBER_ID}\", methodDelete, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/members/{USER_TEAM_MEMBER_ID}\", methodDelete, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/members/{USER_TEAM_MEMBER_ID}\", methodDelete, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/members/{USER_TEAM_MEMBER_ID}\", methodDelete, \"\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/members/{USER_TEAM_MEMBER_ID}\", methodDelete, \"\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/members/{USER_TEAM_MEMBER_ID}\", methodDelete, \"\", userEditor, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/members/{USER_TEAM_MEMBER_ID}\", methodDelete, \"\", userAdmin, http.StatusOK, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/members/{USER_TEAM_MEMBER_ID}\", methodDelete, \"\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/members/{USER_TEAM_MEMBER_ID}\", methodDelete, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/members/{USER_TEAM_MEMBER_ID}\", methodDelete, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/members/{USER_TEAM_MEMBER_ID}\", methodDelete, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/members/{USER_TEAM_MEMBER_ID}\", methodDelete, \"\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/members/{USER_TEAM_MEMBER_ID}\", methodDelete, \"\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/members/{USER_TEAM_MEMBER_ID}\", methodDelete, \"\", userEditor, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/members/{USER_TEAM_MEMBER_ID}\", methodDelete, \"\", userAdmin, http.StatusOK, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/members/{USER_TEAM_MEMBER_ID}\", methodDelete, \"\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/members/{USER_TEAM_MEMBER_ID}\", methodDelete, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/members/{USER_TEAM_MEMBER_ID}\", methodDelete, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/members/{USER_TEAM_MEMBER_ID}\", methodDelete, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/members/{USER_TEAM_MEMBER_ID}\", methodDelete, \"\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/members/{USER_TEAM_MEMBER_ID}\", methodDelete, \"\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/members/{USER_TEAM_MEMBER_ID}\", methodDelete, \"\", userEditor, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/members/{USER_TEAM_MEMBER_ID}\", methodDelete, \"\", userAdmin, http.StatusOK, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/members/{USER_TEAM_MEMBER_ID}\", methodDelete, \"\", userGuest, http.StatusForbidden, 0},\n\n\t\t// Invalid boardID/memberID combination\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/members/{USER_TEAM_MEMBER_ID}\", methodDelete, \"\", userAdmin, http.StatusOK, 0},\n\n\t\t// Invalid boardID\n\t\t{\"/boards/invalid/members/{USER_VIEWER_ID}\", methodDelete, \"\", userAdmin, http.StatusNotFound, 0},\n\n\t\t// Invalid memberID\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/members/invalid\", methodDelete, \"\", userAdmin, http.StatusOK, 0},\n\t}\n\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\textraSetup(t, th, testData)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\textraSetup(t, th, testData)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsJoinBoardAsMember(t *testing.T) {\n\tttCases := []TestCase{\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/join\", methodPost, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/join\", methodPost, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/join\", methodPost, \"\", userTeamMember, http.StatusForbidden, 0},\n\n\t\t// Do we want to forbid already existing members to join to the board or simply return the current membership?\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/join\", methodPost, \"\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/join\", methodPost, \"\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/join\", methodPost, \"\", userEditor, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/join\", methodPost, \"\", userAdmin, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/join\", methodPost, \"\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/join\", methodPost, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/join\", methodPost, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/join\", methodPost, \"\", userTeamMember, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/join\", methodPost, \"\", userViewer, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/join\", methodPost, \"\", userCommenter, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/join\", methodPost, \"\", userEditor, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/join\", methodPost, \"\", userAdmin, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/join\", methodPost, \"\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/join\", methodPost, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/join\", methodPost, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/join\", methodPost, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/join\", methodPost, \"\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/join\", methodPost, \"\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/join\", methodPost, \"\", userEditor, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/join\", methodPost, \"\", userAdmin, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/join\", methodPost, \"\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/join\", methodPost, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/join\", methodPost, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/join\", methodPost, \"\", userTeamMember, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/join\", methodPost, \"\", userViewer, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/join\", methodPost, \"\", userCommenter, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/join\", methodPost, \"\", userEditor, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/join\", methodPost, \"\", userAdmin, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/join\", methodPost, \"\", userGuest, http.StatusForbidden, 0},\n\t}\n\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases[9].expectedStatusCode = http.StatusOK\n\t\tttCases[9].totalResults = 1\n\t\tttCases[25].expectedStatusCode = http.StatusOK\n\t\tttCases[25].totalResults = 1\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsLeaveBoardAsMember(t *testing.T) {\n\textraSetup := func(t *testing.T, th *TestHelper, testData TestData) {\n\t\t_, err := th.Server.App().AddMemberToBoard(&model.BoardMember{BoardID: testData.publicBoard.ID, UserID: \"not-real-user\", SchemeAdmin: true})\n\t\trequire.NoError(t, err)\n\t\t_, err = th.Server.App().AddMemberToBoard(&model.BoardMember{BoardID: testData.privateBoard.ID, UserID: \"not-real-user\", SchemeAdmin: true})\n\t\trequire.NoError(t, err)\n\t\t_, err = th.Server.App().AddMemberToBoard(&model.BoardMember{BoardID: testData.publicTemplate.ID, UserID: \"not-real-user\", SchemeAdmin: true})\n\t\trequire.NoError(t, err)\n\t\t_, err = th.Server.App().AddMemberToBoard(&model.BoardMember{BoardID: testData.privateTemplate.ID, UserID: \"not-real-user\", SchemeAdmin: true})\n\t\trequire.NoError(t, err)\n\t}\n\n\tttCases := []TestCase{\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/leave\", methodPost, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/leave\", methodPost, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/leave\", methodPost, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/leave\", methodPost, \"\", userViewer, http.StatusOK, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/leave\", methodPost, \"\", userCommenter, http.StatusOK, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/leave\", methodPost, \"\", userEditor, http.StatusOK, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/leave\", methodPost, \"\", userAdmin, http.StatusOK, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/leave\", methodPost, \"\", userGuest, http.StatusOK, 0},\n\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/leave\", methodPost, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/leave\", methodPost, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/leave\", methodPost, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/leave\", methodPost, \"\", userViewer, http.StatusOK, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/leave\", methodPost, \"\", userCommenter, http.StatusOK, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/leave\", methodPost, \"\", userEditor, http.StatusOK, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/leave\", methodPost, \"\", userAdmin, http.StatusOK, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/leave\", methodPost, \"\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/leave\", methodPost, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/leave\", methodPost, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/leave\", methodPost, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/leave\", methodPost, \"\", userViewer, http.StatusOK, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/leave\", methodPost, \"\", userCommenter, http.StatusOK, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/leave\", methodPost, \"\", userEditor, http.StatusOK, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/leave\", methodPost, \"\", userAdmin, http.StatusOK, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/leave\", methodPost, \"\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/leave\", methodPost, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/leave\", methodPost, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/leave\", methodPost, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/leave\", methodPost, \"\", userViewer, http.StatusOK, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/leave\", methodPost, \"\", userCommenter, http.StatusOK, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/leave\", methodPost, \"\", userEditor, http.StatusOK, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/leave\", methodPost, \"\", userAdmin, http.StatusOK, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/leave\", methodPost, \"\", userGuest, http.StatusForbidden, 0},\n\t}\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\textraSetup(t, th, testData)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\textraSetup(t, th, testData)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\n\t// Last admin leave should fail\n\textraSetup = func(t *testing.T, th *TestHelper, testData TestData) {\n\t\t_, err := th.Server.App().AddMemberToBoard(&model.BoardMember{BoardID: testData.publicBoard.ID, UserID: userAdminID, SchemeAdmin: true})\n\t\trequire.NoError(t, err)\n\t\t_, err = th.Server.App().AddMemberToBoard(&model.BoardMember{BoardID: testData.privateBoard.ID, UserID: userAdminID, SchemeAdmin: true})\n\t\trequire.NoError(t, err)\n\t\t_, err = th.Server.App().AddMemberToBoard(&model.BoardMember{BoardID: testData.publicTemplate.ID, UserID: userAdminID, SchemeAdmin: true})\n\t\trequire.NoError(t, err)\n\t\t_, err = th.Server.App().AddMemberToBoard(&model.BoardMember{BoardID: testData.privateTemplate.ID, UserID: userAdminID, SchemeAdmin: true})\n\t\trequire.NoError(t, err)\n\n\t\trequire.NoError(t, th.Server.App().DeleteBoardMember(testData.publicBoard.ID, \"not-real-user\"))\n\t\trequire.NoError(t, th.Server.App().DeleteBoardMember(testData.privateBoard.ID, \"not-real-user\"))\n\t\trequire.NoError(t, th.Server.App().DeleteBoardMember(testData.publicTemplate.ID, \"not-real-user\"))\n\t\trequire.NoError(t, th.Server.App().DeleteBoardMember(testData.privateTemplate.ID, \"not-real-user\"))\n\t}\n\n\tttCases = []TestCase{\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/leave\", methodPost, \"\", userAdmin, http.StatusBadRequest, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/leave\", methodPost, \"\", userAdmin, http.StatusBadRequest, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/leave\", methodPost, \"\", userAdmin, http.StatusBadRequest, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/leave\", methodPost, \"\", userAdmin, http.StatusBadRequest, 0},\n\t}\n\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\textraSetup(t, th, testData)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\textraSetup(t, th, testData)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsShareBoard(t *testing.T) {\n\tsharing := toJSON(t, model.Sharing{Enabled: true, Token: \"test-token\"})\n\n\tttCases := []TestCase{\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/sharing\", methodPost, sharing, userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/sharing\", methodPost, sharing, userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/sharing\", methodPost, sharing, userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/sharing\", methodPost, sharing, userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/sharing\", methodPost, sharing, userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/sharing\", methodPost, sharing, userEditor, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/sharing\", methodPost, sharing, userAdmin, http.StatusOK, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/sharing\", methodPost, sharing, userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/sharing\", methodPost, sharing, userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/sharing\", methodPost, sharing, userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/sharing\", methodPost, sharing, userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/sharing\", methodPost, sharing, userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/sharing\", methodPost, sharing, userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/sharing\", methodPost, sharing, userEditor, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/sharing\", methodPost, sharing, userAdmin, http.StatusOK, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/sharing\", methodPost, sharing, userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/sharing\", methodPost, sharing, userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/sharing\", methodPost, sharing, userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/sharing\", methodPost, sharing, userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/sharing\", methodPost, sharing, userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/sharing\", methodPost, sharing, userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/sharing\", methodPost, sharing, userEditor, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/sharing\", methodPost, sharing, userAdmin, http.StatusOK, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/sharing\", methodPost, sharing, userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/sharing\", methodPost, sharing, userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/sharing\", methodPost, sharing, userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/sharing\", methodPost, sharing, userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/sharing\", methodPost, sharing, userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/sharing\", methodPost, sharing, userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/sharing\", methodPost, sharing, userEditor, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/sharing\", methodPost, sharing, userAdmin, http.StatusOK, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/sharing\", methodPost, sharing, userGuest, http.StatusForbidden, 0},\n\t}\n\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsGetSharedBoardInfo(t *testing.T) {\n\tttCases := []TestCase{\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/sharing\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/sharing\", methodGet, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/sharing\", methodGet, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/sharing\", methodGet, \"\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/sharing\", methodGet, \"\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/sharing\", methodGet, \"\", userEditor, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/sharing\", methodGet, \"\", userAdmin, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/sharing\", methodGet, \"\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/sharing\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/sharing\", methodGet, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/sharing\", methodGet, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/sharing\", methodGet, \"\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/sharing\", methodGet, \"\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/sharing\", methodGet, \"\", userEditor, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/sharing\", methodGet, \"\", userAdmin, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/sharing\", methodGet, \"\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/sharing\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/sharing\", methodGet, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/sharing\", methodGet, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/sharing\", methodGet, \"\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/sharing\", methodGet, \"\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/sharing\", methodGet, \"\", userEditor, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/sharing\", methodGet, \"\", userAdmin, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/sharing\", methodGet, \"\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/sharing\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/sharing\", methodGet, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/sharing\", methodGet, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/sharing\", methodGet, \"\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/sharing\", methodGet, \"\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/sharing\", methodGet, \"\", userEditor, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/sharing\", methodGet, \"\", userAdmin, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/sharing\", methodGet, \"\", userGuest, http.StatusForbidden, 0},\n\t}\n\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\n\t\tclients.Admin.PostSharing(&model.Sharing{ID: testData.publicBoard.ID, Enabled: true, Token: \"test-token\"})\n\t\tclients.Admin.PostSharing(&model.Sharing{ID: testData.privateBoard.ID, Enabled: true, Token: \"test-token\"})\n\t\tclients.Admin.PostSharing(&model.Sharing{ID: testData.publicTemplate.ID, Enabled: true, Token: \"test-token\"})\n\t\tclients.Admin.PostSharing(&model.Sharing{ID: testData.privateTemplate.ID, Enabled: true, Token: \"test-token\"})\n\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\n\t\tclients.Admin.PostSharing(&model.Sharing{ID: testData.publicBoard.ID, Enabled: true, Token: \"test-token\"})\n\t\tclients.Admin.PostSharing(&model.Sharing{ID: testData.privateBoard.ID, Enabled: true, Token: \"test-token\"})\n\t\tclients.Admin.PostSharing(&model.Sharing{ID: testData.publicTemplate.ID, Enabled: true, Token: \"test-token\"})\n\t\tclients.Admin.PostSharing(&model.Sharing{ID: testData.privateTemplate.ID, Enabled: true, Token: \"test-token\"})\n\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsListTeams(t *testing.T) {\n\tttCases := []TestCase{\n\t\t{\"/teams\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/teams\", methodGet, \"\", userNoTeamMember, http.StatusOK, 0},\n\t\t{\"/teams\", methodGet, \"\", userTeamMember, http.StatusOK, 2},\n\t\t{\"/teams\", methodGet, \"\", userViewer, http.StatusOK, 2},\n\t\t{\"/teams\", methodGet, \"\", userCommenter, http.StatusOK, 2},\n\t\t{\"/teams\", methodGet, \"\", userEditor, http.StatusOK, 2},\n\t\t{\"/teams\", methodGet, \"\", userAdmin, http.StatusOK, 2},\n\t\t{\"/teams\", methodGet, \"\", userGuest, http.StatusOK, 1},\n\t}\n\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases[1].expectedStatusCode = http.StatusOK\n\t\tfor i := range ttCases {\n\t\t\tttCases[i].totalResults = 1\n\t\t}\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsGetTeam(t *testing.T) {\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases := []TestCase{\n\t\t\t{\"/teams/test-team\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/teams/test-team\", methodGet, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/teams/test-team\", methodGet, \"\", userTeamMember, http.StatusOK, 1},\n\t\t\t{\"/teams/test-team\", methodGet, \"\", userViewer, http.StatusOK, 1},\n\t\t\t{\"/teams/test-team\", methodGet, \"\", userCommenter, http.StatusOK, 1},\n\t\t\t{\"/teams/test-team\", methodGet, \"\", userEditor, http.StatusOK, 1},\n\t\t\t{\"/teams/test-team\", methodGet, \"\", userAdmin, http.StatusOK, 1},\n\t\t\t{\"/teams/test-team\", methodGet, \"\", userGuest, http.StatusOK, 1},\n\n\t\t\t{\"/teams/empty-team\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/teams/empty-team\", methodGet, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/teams/empty-team\", methodGet, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/teams/empty-team\", methodGet, \"\", userViewer, http.StatusForbidden, 0},\n\t\t\t{\"/teams/empty-team\", methodGet, \"\", userCommenter, http.StatusForbidden, 0},\n\t\t\t{\"/teams/empty-team\", methodGet, \"\", userEditor, http.StatusForbidden, 0},\n\t\t\t{\"/teams/empty-team\", methodGet, \"\", userAdmin, http.StatusForbidden, 0},\n\t\t\t{\"/teams/empty-team\", methodGet, \"\", userGuest, http.StatusForbidden, 0},\n\t\t}\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases := []TestCase{\n\t\t\t{\"/teams/test-team\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/teams/test-team\", methodGet, \"\", userNoTeamMember, http.StatusOK, 1},\n\t\t\t{\"/teams/test-team\", methodGet, \"\", userTeamMember, http.StatusOK, 1},\n\t\t\t{\"/teams/test-team\", methodGet, \"\", userViewer, http.StatusOK, 1},\n\t\t\t{\"/teams/test-team\", methodGet, \"\", userCommenter, http.StatusOK, 1},\n\t\t\t{\"/teams/test-team\", methodGet, \"\", userEditor, http.StatusOK, 1},\n\t\t\t{\"/teams/test-team\", methodGet, \"\", userAdmin, http.StatusOK, 1},\n\t\t\t{\"/teams/test-team\", methodGet, \"\", userGuest, http.StatusOK, 1},\n\t\t}\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsRegenerateSignupToken(t *testing.T) {\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases := []TestCase{\n\t\t\t{\"/teams/test-team/regenerate_signup_token\", methodPost, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/teams/test-team/regenerate_signup_token\", methodPost, \"\", userAdmin, http.StatusNotImplemented, 0},\n\n\t\t\t{\"/teams/empty-team/regenerate_signup_token\", methodPost, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/teams/empty-team/regenerate_signup_token\", methodPost, \"\", userAdmin, http.StatusNotImplemented, 0},\n\t\t}\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases := []TestCase{\n\t\t\t{\"/teams/test-team/regenerate_signup_token\", methodPost, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/teams/test-team/regenerate_signup_token\", methodPost, \"\", userAdmin, http.StatusOK, 0},\n\n\t\t\t{\"/teams/empty-team/regenerate_signup_token\", methodPost, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/teams/empty-team/regenerate_signup_token\", methodPost, \"\", userAdmin, http.StatusOK, 0},\n\t\t}\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsGetTeamUsers(t *testing.T) {\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases := []TestCase{\n\t\t\t{\"/teams/test-team/users\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/teams/test-team/users\", methodGet, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/teams/test-team/users\", methodGet, \"\", userTeamMember, http.StatusOK, 6},\n\t\t\t{\"/teams/test-team/users\", methodGet, \"\", userViewer, http.StatusOK, 6},\n\t\t\t{\"/teams/test-team/users\", methodGet, \"\", userCommenter, http.StatusOK, 6},\n\t\t\t{\"/teams/test-team/users\", methodGet, \"\", userEditor, http.StatusOK, 6},\n\t\t\t{\"/teams/test-team/users\", methodGet, \"\", userAdmin, http.StatusOK, 6},\n\t\t\t{\"/teams/test-team/users\", methodGet, \"\", userGuest, http.StatusOK, 5},\n\n\t\t\t{\"/teams/empty-team/users\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/teams/empty-team/users\", methodGet, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/teams/empty-team/users\", methodGet, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/teams/empty-team/users\", methodGet, \"\", userViewer, http.StatusForbidden, 0},\n\t\t\t{\"/teams/empty-team/users\", methodGet, \"\", userCommenter, http.StatusForbidden, 0},\n\t\t\t{\"/teams/empty-team/users\", methodGet, \"\", userEditor, http.StatusForbidden, 0},\n\t\t\t{\"/teams/empty-team/users\", methodGet, \"\", userAdmin, http.StatusForbidden, 0},\n\t\t\t{\"/teams/empty-team/users\", methodGet, \"\", userGuest, http.StatusForbidden, 0},\n\t\t}\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases := []TestCase{\n\t\t\t{\"/teams/test-team/users\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/teams/test-team/users\", methodGet, \"\", userNoTeamMember, http.StatusOK, 7},\n\t\t\t{\"/teams/test-team/users\", methodGet, \"\", userTeamMember, http.StatusOK, 7},\n\t\t\t{\"/teams/test-team/users\", methodGet, \"\", userViewer, http.StatusOK, 7},\n\t\t\t{\"/teams/test-team/users\", methodGet, \"\", userCommenter, http.StatusOK, 7},\n\t\t\t{\"/teams/test-team/users\", methodGet, \"\", userEditor, http.StatusOK, 7},\n\t\t\t{\"/teams/test-team/users\", methodGet, \"\", userAdmin, http.StatusOK, 7},\n\t\t\t{\"/teams/test-team/users\", methodGet, \"\", userGuest, http.StatusOK, 7},\n\t\t}\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsTeamArchiveExport(t *testing.T) {\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases := []TestCase{\n\t\t\t{\"/teams/test-team/archive/export\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/teams/test-team/archive/export\", methodGet, \"\", userAdmin, http.StatusNotImplemented, 0},\n\n\t\t\t{\"/teams/empty-team/archive/export\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/teams/empty-team/archive/export\", methodGet, \"\", userAdmin, http.StatusNotImplemented, 0},\n\t\t}\n\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases := []TestCase{\n\t\t\t{\"/teams/test-team/archive/export\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/teams/test-team/archive/export\", methodGet, \"\", userAdmin, http.StatusOK, 1},\n\n\t\t\t{\"/teams/empty-team/archive/export\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/teams/empty-team/archive/export\", methodGet, \"\", userAdmin, http.StatusOK, 1},\n\t\t}\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsUploadFile(t *testing.T) {\n\tttCases := []TestCase{\n\t\t{\"/teams/test-team/{PRIVATE_BOARD_ID}/files\", methodPost, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/teams/test-team/{PRIVATE_BOARD_ID}/files\", methodPost, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/teams/test-team/{PRIVATE_BOARD_ID}/files\", methodPost, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/teams/test-team/{PRIVATE_BOARD_ID}/files\", methodPost, \"\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/teams/test-team/{PRIVATE_BOARD_ID}/files\", methodPost, \"\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/teams/test-team/{PRIVATE_BOARD_ID}/files\", methodPost, \"\", userEditor, http.StatusBadRequest, 1}, // Not checking the logic, only the permissions\n\t\t{\"/teams/test-team/{PRIVATE_BOARD_ID}/files\", methodPost, \"\", userAdmin, http.StatusBadRequest, 1},  // Not checking the logic, only the permissions\n\t\t{\"/teams/test-team/{PRIVATE_BOARD_ID}/files\", methodPost, \"\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/teams/test-team/{PUBLIC_BOARD_ID}/files\", methodPost, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/teams/test-team/{PUBLIC_BOARD_ID}/files\", methodPost, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/teams/test-team/{PUBLIC_BOARD_ID}/files\", methodPost, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/teams/test-team/{PUBLIC_BOARD_ID}/files\", methodPost, \"\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/teams/test-team/{PUBLIC_BOARD_ID}/files\", methodPost, \"\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/teams/test-team/{PUBLIC_BOARD_ID}/files\", methodPost, \"\", userEditor, http.StatusBadRequest, 1}, // Not checking the logic, only the permissions\n\t\t{\"/teams/test-team/{PUBLIC_BOARD_ID}/files\", methodPost, \"\", userAdmin, http.StatusBadRequest, 1},  // Not checking the logic, only the permissions\n\t\t{\"/teams/test-team/{PUBLIC_BOARD_ID}/files\", methodPost, \"\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/teams/test-team/{PRIVATE_TEMPLATE_ID}/files\", methodPost, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/teams/test-team/{PRIVATE_TEMPLATE_ID}/files\", methodPost, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/teams/test-team/{PRIVATE_TEMPLATE_ID}/files\", methodPost, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/teams/test-team/{PRIVATE_TEMPLATE_ID}/files\", methodPost, \"\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/teams/test-team/{PRIVATE_TEMPLATE_ID}/files\", methodPost, \"\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/teams/test-team/{PRIVATE_TEMPLATE_ID}/files\", methodPost, \"\", userEditor, http.StatusBadRequest, 1}, // Not checking the logic, only the permissions\n\t\t{\"/teams/test-team/{PRIVATE_TEMPLATE_ID}/files\", methodPost, \"\", userAdmin, http.StatusBadRequest, 1},  // Not checking the logic, only the permissions\n\t\t{\"/teams/test-team/{PRIVATE_TEMPLATE_ID}/files\", methodPost, \"\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/teams/test-team/{PUBLIC_TEMPLATE_ID}/files\", methodPost, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/teams/test-team/{PUBLIC_TEMPLATE_ID}/files\", methodPost, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/teams/test-team/{PUBLIC_TEMPLATE_ID}/files\", methodPost, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/teams/test-team/{PUBLIC_TEMPLATE_ID}/files\", methodPost, \"\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/teams/test-team/{PUBLIC_TEMPLATE_ID}/files\", methodPost, \"\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/teams/test-team/{PUBLIC_TEMPLATE_ID}/files\", methodPost, \"\", userEditor, http.StatusBadRequest, 1}, // Not checking the logic, only the permissions\n\t\t{\"/teams/test-team/{PUBLIC_TEMPLATE_ID}/files\", methodPost, \"\", userAdmin, http.StatusBadRequest, 1},  // Not checking the logic, only the permissions\n\t\t{\"/teams/test-team/{PUBLIC_TEMPLATE_ID}/files\", methodPost, \"\", userGuest, http.StatusForbidden, 0},\n\t}\n\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsGetMe(t *testing.T) {\n\tttCases := []TestCase{\n\t\t{\"/users/me\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/users/me\", methodGet, \"\", userNoTeamMember, http.StatusOK, 1},\n\t\t{\"/users/me\", methodGet, \"\", userTeamMember, http.StatusOK, 1},\n\t\t{\"/users/me\", methodGet, \"\", userViewer, http.StatusOK, 1},\n\t\t{\"/users/me\", methodGet, \"\", userCommenter, http.StatusOK, 1},\n\t\t{\"/users/me\", methodGet, \"\", userEditor, http.StatusOK, 1},\n\t\t{\"/users/me\", methodGet, \"\", userAdmin, http.StatusOK, 1},\n\t\t{\"/users/me\", methodGet, \"\", userGuest, http.StatusOK, 1},\n\t}\n\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsGetMyMemberships(t *testing.T) {\n\tttCases := []TestCase{\n\t\t{\"/users/me/memberships\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/users/me/memberships\", methodGet, \"\", userNoTeamMember, http.StatusOK, 0},\n\t\t{\"/users/me/memberships\", methodGet, \"\", userTeamMember, http.StatusOK, 0},\n\t\t{\"/users/me/memberships\", methodGet, \"\", userViewer, http.StatusOK, 4},\n\t\t{\"/users/me/memberships\", methodGet, \"\", userCommenter, http.StatusOK, 4},\n\t\t{\"/users/me/memberships\", methodGet, \"\", userEditor, http.StatusOK, 4},\n\t\t{\"/users/me/memberships\", methodGet, \"\", userAdmin, http.StatusOK, 4},\n\t\t{\"/users/me/memberships\", methodGet, \"\", userGuest, http.StatusOK, 1},\n\t}\n\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsGetUser(t *testing.T) {\n\tttCases := []TestCase{\n\t\t{\"/users/{USER_NO_TEAM_MEMBER_ID}\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/users/{USER_NO_TEAM_MEMBER_ID}\", methodGet, \"\", userNoTeamMember, http.StatusOK, 1},\n\t\t{\"/users/{USER_NO_TEAM_MEMBER_ID}\", methodGet, \"\", userTeamMember, http.StatusOK, 1},\n\t\t{\"/users/{USER_NO_TEAM_MEMBER_ID}\", methodGet, \"\", userViewer, http.StatusOK, 1},\n\t\t{\"/users/{USER_NO_TEAM_MEMBER_ID}\", methodGet, \"\", userCommenter, http.StatusOK, 1},\n\t\t{\"/users/{USER_NO_TEAM_MEMBER_ID}\", methodGet, \"\", userEditor, http.StatusOK, 1},\n\t\t{\"/users/{USER_NO_TEAM_MEMBER_ID}\", methodGet, \"\", userAdmin, http.StatusOK, 1},\n\t\t{\"/users/{USER_NO_TEAM_MEMBER_ID}\", methodGet, \"\", userGuest, http.StatusNotFound, 0},\n\n\t\t{\"/users/{USER_TEAM_MEMBER_ID}\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/users/{USER_TEAM_MEMBER_ID}\", methodGet, \"\", userNoTeamMember, http.StatusOK, 1},\n\t\t{\"/users/{USER_TEAM_MEMBER_ID}\", methodGet, \"\", userTeamMember, http.StatusOK, 1},\n\t\t{\"/users/{USER_TEAM_MEMBER_ID}\", methodGet, \"\", userViewer, http.StatusOK, 1},\n\t\t{\"/users/{USER_TEAM_MEMBER_ID}\", methodGet, \"\", userCommenter, http.StatusOK, 1},\n\t\t{\"/users/{USER_TEAM_MEMBER_ID}\", methodGet, \"\", userEditor, http.StatusOK, 1},\n\t\t{\"/users/{USER_TEAM_MEMBER_ID}\", methodGet, \"\", userAdmin, http.StatusOK, 1},\n\t\t{\"/users/{USER_TEAM_MEMBER_ID}\", methodGet, \"\", userGuest, http.StatusNotFound, 0},\n\n\t\t{\"/users/{USER_VIEWER_ID}\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/users/{USER_VIEWER_ID}\", methodGet, \"\", userNoTeamMember, http.StatusOK, 1},\n\t\t{\"/users/{USER_VIEWER_ID}\", methodGet, \"\", userTeamMember, http.StatusOK, 1},\n\t\t{\"/users/{USER_VIEWER_ID}\", methodGet, \"\", userViewer, http.StatusOK, 1},\n\t\t{\"/users/{USER_VIEWER_ID}\", methodGet, \"\", userCommenter, http.StatusOK, 1},\n\t\t{\"/users/{USER_VIEWER_ID}\", methodGet, \"\", userEditor, http.StatusOK, 1},\n\t\t{\"/users/{USER_VIEWER_ID}\", methodGet, \"\", userAdmin, http.StatusOK, 1},\n\t\t{\"/users/{USER_VIEWER_ID}\", methodGet, \"\", userGuest, http.StatusOK, 1},\n\t}\n\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsUserChangePassword(t *testing.T) {\n\tpostBody := toJSON(t, model.ChangePasswordRequest{\n\t\tOldPassword: password,\n\t\tNewPassword: \"newpa$$word123\",\n\t})\n\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases := []TestCase{\n\t\t\t{\"/users/{USER_ADMIN_ID}/changepassword\", methodPost, postBody, userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/users/{USER_ADMIN_ID}/changepassword\", methodPost, postBody, userAdmin, http.StatusNotImplemented, 0},\n\t\t}\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases := []TestCase{\n\t\t\t{\"/users/{USER_ADMIN_ID}/changepassword\", methodPost, postBody, userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/users/{USER_ADMIN_ID}/changepassword\", methodPost, postBody, userAdmin, http.StatusOK, 0},\n\t\t}\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsUpdateUserConfig(t *testing.T) {\n\tpatch := toJSON(t, model.UserPreferencesPatch{UpdatedFields: map[string]string{\"test\": \"test\"}})\n\n\tttCases := []TestCase{\n\t\t{\"/users/{USER_TEAM_MEMBER_ID}/config\", methodPut, patch, userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/users/{USER_TEAM_MEMBER_ID}/config\", methodPut, patch, userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/users/{USER_TEAM_MEMBER_ID}/config\", methodPut, patch, userTeamMember, http.StatusOK, 1},\n\t\t{\"/users/{USER_TEAM_MEMBER_ID}/config\", methodPut, patch, userViewer, http.StatusForbidden, 0},\n\t\t{\"/users/{USER_TEAM_MEMBER_ID}/config\", methodPut, patch, userCommenter, http.StatusForbidden, 0},\n\t\t{\"/users/{USER_TEAM_MEMBER_ID}/config\", methodPut, patch, userEditor, http.StatusForbidden, 0},\n\t\t{\"/users/{USER_TEAM_MEMBER_ID}/config\", methodPut, patch, userAdmin, http.StatusForbidden, 0},\n\t\t{\"/users/{USER_TEAM_MEMBER_ID}/config\", methodPut, patch, userGuest, http.StatusForbidden, 0},\n\t}\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsCreateBoardsAndBlocks(t *testing.T) {\n\tbab := toJSON(t, model.BoardsAndBlocks{\n\t\tBoards: []*model.Board{{ID: \"test\", Title: \"Test Board\", TeamID: \"test-team\"}},\n\t\tBlocks: []*model.Block{\n\t\t\t{ID: \"test-block\", BoardID: \"test\", Type: \"card\", CreateAt: model.GetMillis(), UpdateAt: model.GetMillis()},\n\t\t},\n\t})\n\n\tttCases := []TestCase{\n\t\t{\"/boards-and-blocks\", methodPost, bab, userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards-and-blocks\", methodPost, bab, userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards-and-blocks\", methodPost, bab, userTeamMember, http.StatusOK, 1},\n\t\t{\"/boards-and-blocks\", methodPost, bab, userViewer, http.StatusOK, 1},\n\t\t{\"/boards-and-blocks\", methodPost, bab, userCommenter, http.StatusOK, 1},\n\t\t{\"/boards-and-blocks\", methodPost, bab, userEditor, http.StatusOK, 1},\n\t\t{\"/boards-and-blocks\", methodPost, bab, userAdmin, http.StatusOK, 1},\n\t\t{\"/boards-and-blocks\", methodPost, bab, userGuest, http.StatusForbidden, 0},\n\t}\n\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases[1].expectedStatusCode = http.StatusOK\n\t\tttCases[1].totalResults = 1\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsUpdateBoardsAndBlocks(t *testing.T) {\n\tttCasesF := func(t *testing.T, testData TestData) []TestCase {\n\t\tnewTitle := \"New Block Title\"\n\t\tbab := toJSON(t, model.PatchBoardsAndBlocks{\n\t\t\tBoardIDs:     []string{testData.publicBoard.ID},\n\t\t\tBoardPatches: []*model.BoardPatch{{Title: &newTitle}},\n\t\t\tBlockIDs:     []string{\"block-3\"},\n\t\t\tBlockPatches: []*model.BlockPatch{{Title: &newTitle}},\n\t\t})\n\n\t\treturn []TestCase{\n\t\t\t{\"/boards-and-blocks\", methodPatch, bab, userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/boards-and-blocks\", methodPatch, bab, userNoTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/boards-and-blocks\", methodPatch, bab, userTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/boards-and-blocks\", methodPatch, bab, userViewer, http.StatusForbidden, 0},\n\t\t\t{\"/boards-and-blocks\", methodPatch, bab, userCommenter, http.StatusForbidden, 0},\n\t\t\t{\"/boards-and-blocks\", methodPatch, bab, userEditor, http.StatusOK, 1},\n\t\t\t{\"/boards-and-blocks\", methodPatch, bab, userAdmin, http.StatusOK, 1},\n\t\t\t{\"/boards-and-blocks\", methodPatch, bab, userGuest, http.StatusForbidden, 0},\n\t\t}\n\t}\n\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases := ttCasesF(t, testData)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases := ttCasesF(t, testData)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\n\t// With type change\n\tttCasesF = func(t *testing.T, testData TestData) []TestCase {\n\t\tnewType := model.BoardTypePrivate\n\t\tnewTitle := \"New Block Title\"\n\t\tbab := toJSON(t, model.PatchBoardsAndBlocks{\n\t\t\tBoardIDs:     []string{testData.publicBoard.ID},\n\t\t\tBoardPatches: []*model.BoardPatch{{Type: &newType}},\n\t\t\tBlockIDs:     []string{\"block-3\"},\n\t\t\tBlockPatches: []*model.BlockPatch{{Title: &newTitle}},\n\t\t})\n\n\t\treturn []TestCase{\n\t\t\t{\"/boards-and-blocks\", methodPatch, bab, userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/boards-and-blocks\", methodPatch, bab, userNoTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/boards-and-blocks\", methodPatch, bab, userTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/boards-and-blocks\", methodPatch, bab, userViewer, http.StatusForbidden, 0},\n\t\t\t{\"/boards-and-blocks\", methodPatch, bab, userCommenter, http.StatusForbidden, 0},\n\t\t\t{\"/boards-and-blocks\", methodPatch, bab, userEditor, http.StatusForbidden, 0},\n\t\t\t{\"/boards-and-blocks\", methodPatch, bab, userAdmin, http.StatusOK, 1},\n\t\t\t{\"/boards-and-blocks\", methodPatch, bab, userGuest, http.StatusForbidden, 0},\n\t\t}\n\t}\n\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases := ttCasesF(t, testData)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases := ttCasesF(t, testData)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsDeleteBoardsAndBlocks(t *testing.T) {\n\tttCasesF := func(t *testing.T, testData TestData) []TestCase {\n\t\tbab := toJSON(t, model.DeleteBoardsAndBlocks{\n\t\t\tBoards: []string{testData.publicBoard.ID},\n\t\t\tBlocks: []string{\"block-3\"},\n\t\t})\n\t\treturn []TestCase{\n\t\t\t{\"/boards-and-blocks\", methodDelete, bab, userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/boards-and-blocks\", methodDelete, bab, userNoTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/boards-and-blocks\", methodDelete, bab, userTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/boards-and-blocks\", methodDelete, bab, userViewer, http.StatusForbidden, 0},\n\t\t\t{\"/boards-and-blocks\", methodDelete, bab, userCommenter, http.StatusForbidden, 0},\n\t\t\t{\"/boards-and-blocks\", methodDelete, bab, userEditor, http.StatusForbidden, 0},\n\t\t\t{\"/boards-and-blocks\", methodDelete, bab, userGuest, http.StatusForbidden, 0},\n\t\t\t{\"/boards-and-blocks\", methodDelete, bab, userAdmin, http.StatusOK, 0},\n\t\t}\n\t}\n\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases := ttCasesF(t, testData)\n\n\t\t_, err := th.Server.App().AddMemberToBoard(&model.BoardMember{BoardID: testData.publicBoard.ID, UserID: userGuestID, SchemeViewer: true})\n\t\trequire.NoError(t, err)\n\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases := ttCasesF(t, testData)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsLogin(t *testing.T) {\n\tloginReq := func(username, password string) string {\n\t\treturn toJSON(t, model.LoginRequest{\n\t\t\tType:     \"normal\",\n\t\t\tUsername: username,\n\t\t\tPassword: password,\n\t\t})\n\t}\n\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases := []TestCase{\n\t\t\t{\"/login\", methodPost, loginReq(userAnon, password), userAnon, http.StatusNotImplemented, 0},\n\t\t\t{\"/login\", methodPost, loginReq(userAdmin, password), userAdmin, http.StatusNotImplemented, 0},\n\t\t}\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases := []TestCase{\n\t\t\t{\"/login\", methodPost, loginReq(userAnon, password), userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/login\", methodPost, loginReq(userAdmin, password), userAdmin, http.StatusOK, 1},\n\t\t}\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsLogout(t *testing.T) {\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases := []TestCase{\n\t\t\t{\"/logout\", methodPost, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/logout\", methodPost, \"\", userAdmin, http.StatusNotImplemented, 0},\n\t\t}\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases := []TestCase{\n\t\t\t{\"/logout\", methodPost, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/logout\", methodPost, \"\", userAdmin, http.StatusOK, 0},\n\t\t}\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsRegister(t *testing.T) {\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases := []TestCase{\n\t\t\t{\"/register\", methodPost, \"\", userAnon, http.StatusNotImplemented, 0},\n\t\t\t{\"/register\", methodPost, \"\", userAdmin, http.StatusNotImplemented, 0},\n\t\t}\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\n\t\tteam, resp := th.Client.GetTeam(model.GlobalTeamID)\n\t\tth.CheckOK(resp)\n\t\trequire.NotNil(th.T, team)\n\t\trequire.NotNil(th.T, team.SignupToken)\n\n\t\tpostData := toJSON(t, model.RegisterRequest{\n\t\t\tUsername: \"newuser\",\n\t\t\tEmail:    \"newuser@test.com\",\n\t\t\tPassword: password,\n\t\t\tToken:    team.SignupToken,\n\t\t})\n\n\t\tttCases := []TestCase{\n\t\t\t{\"/register\", methodPost, postData, userAnon, http.StatusOK, 0},\n\t\t}\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsClientConfig(t *testing.T) {\n\tttCases := []TestCase{\n\t\t{\"/clientConfig\", methodGet, \"\", userAnon, http.StatusOK, 1},\n\t\t{\"/clientConfig\", methodGet, \"\", userAdmin, http.StatusOK, 1},\n\t}\n\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsGetCategories(t *testing.T) {\n\tttCases := []TestCase{\n\t\t{\"/teams/test-team/categories\", methodGet, \"\", userAnon, http.StatusUnauthorized, 1},\n\t\t{\"/teams/test-team/categories\", methodGet, \"\", userNoTeamMember, http.StatusForbidden, 1},\n\t\t{\"/teams/test-team/categories\", methodGet, \"\", userTeamMember, http.StatusOK, 1},\n\t\t{\"/teams/test-team/categories\", methodGet, \"\", userViewer, http.StatusOK, 1},\n\t\t{\"/teams/test-team/categories\", methodGet, \"\", userCommenter, http.StatusOK, 1},\n\t\t{\"/teams/test-team/categories\", methodGet, \"\", userEditor, http.StatusOK, 1},\n\t\t{\"/teams/test-team/categories\", methodGet, \"\", userAdmin, http.StatusOK, 1},\n\t\t{\"/teams/test-team/categories\", methodGet, \"\", userGuest, http.StatusOK, 1},\n\t}\n\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases[1].expectedStatusCode = http.StatusOK\n\t\tttCases[1].totalResults = 1\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsCreateCategory(t *testing.T) {\n\tttCasesF := func() []TestCase {\n\t\tcategory := func(userID string) string {\n\t\t\treturn toJSON(t, model.Category{\n\t\t\t\tName:     \"Test category\",\n\t\t\t\tTeamID:   \"test-team\",\n\t\t\t\tUserID:   userID,\n\t\t\t\tCreateAt: model.GetMillis(),\n\t\t\t\tUpdateAt: model.GetMillis(),\n\t\t\t})\n\t\t}\n\n\t\treturn []TestCase{\n\t\t\t{\"/teams/test-team/categories\", methodPost, category(\"\"), userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/teams/test-team/categories\", methodPost, category(userNoTeamMemberID), userNoTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/teams/test-team/categories\", methodPost, category(userTeamMemberID), userTeamMember, http.StatusOK, 1},\n\t\t\t{\"/teams/test-team/categories\", methodPost, category(userViewerID), userViewer, http.StatusOK, 1},\n\t\t\t{\"/teams/test-team/categories\", methodPost, category(userCommenterID), userCommenter, http.StatusOK, 1},\n\t\t\t{\"/teams/test-team/categories\", methodPost, category(userEditorID), userEditor, http.StatusOK, 1},\n\t\t\t{\"/teams/test-team/categories\", methodPost, category(userAdminID), userAdmin, http.StatusOK, 1},\n\t\t\t{\"/teams/test-team/categories\", methodPost, category(userGuestID), userGuest, http.StatusOK, 1},\n\n\t\t\t{\"/teams/test-team/categories\", methodPost, category(\"other\"), userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/teams/test-team/categories\", methodPost, category(\"other\"), userNoTeamMember, http.StatusBadRequest, 0},\n\t\t\t{\"/teams/test-team/categories\", methodPost, category(\"other\"), userTeamMember, http.StatusBadRequest, 0},\n\t\t\t{\"/teams/test-team/categories\", methodPost, category(\"other\"), userViewer, http.StatusBadRequest, 0},\n\t\t\t{\"/teams/test-team/categories\", methodPost, category(\"other\"), userCommenter, http.StatusBadRequest, 0},\n\t\t\t{\"/teams/test-team/categories\", methodPost, category(\"other\"), userEditor, http.StatusBadRequest, 0},\n\t\t\t{\"/teams/test-team/categories\", methodPost, category(\"other\"), userAdmin, http.StatusBadRequest, 0},\n\t\t\t{\"/teams/test-team/categories\", methodPost, category(\"other\"), userGuest, http.StatusBadRequest, 0},\n\n\t\t\t{\"/teams/other-team/categories\", methodPost, category(\"\"), userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/teams/other-team/categories\", methodPost, category(userNoTeamMemberID), userNoTeamMember, http.StatusBadRequest, 0},\n\t\t\t{\"/teams/other-team/categories\", methodPost, category(userTeamMemberID), userTeamMember, http.StatusBadRequest, 0},\n\t\t\t{\"/teams/other-team/categories\", methodPost, category(userViewerID), userViewer, http.StatusBadRequest, 0},\n\t\t\t{\"/teams/other-team/categories\", methodPost, category(userCommenterID), userCommenter, http.StatusBadRequest, 0},\n\t\t\t{\"/teams/other-team/categories\", methodPost, category(userEditorID), userEditor, http.StatusBadRequest, 0},\n\t\t\t{\"/teams/other-team/categories\", methodPost, category(userAdminID), userAdmin, http.StatusBadRequest, 0},\n\t\t\t{\"/teams/other-team/categories\", methodPost, category(userGuestID), userGuest, http.StatusBadRequest, 0},\n\t\t}\n\t}\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases := ttCasesF()\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases := ttCasesF()\n\t\tttCases[1].expectedStatusCode = http.StatusOK\n\t\tttCases[1].totalResults = 1\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsUpdateCategory(t *testing.T) {\n\tttCasesF := func(extraData map[string]string) []TestCase {\n\t\tcategory := func(userID string, categoryID string) string {\n\t\t\treturn toJSON(t, model.Category{\n\t\t\t\tID:       categoryID,\n\t\t\t\tName:     \"Test category\",\n\t\t\t\tTeamID:   \"test-team\",\n\t\t\t\tUserID:   userID,\n\t\t\t\tCreateAt: model.GetMillis(),\n\t\t\t\tUpdateAt: model.GetMillis(),\n\t\t\t\tType:     \"custom\",\n\t\t\t})\n\t\t}\n\n\t\treturn []TestCase{\n\t\t\t{\"/teams/test-team/categories/any\", methodPut, category(\"\", \"any\"), userAnonID, http.StatusUnauthorized, 0},\n\t\t\t{\"/teams/test-team/categories/\" + extraData[\"noTeamMember\"], methodPut, category(userNoTeamMemberID, extraData[\"noTeamMember\"]), userNoTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/teams/test-team/categories/\" + extraData[\"teamMember\"], methodPut, category(userTeamMemberID, extraData[\"teamMember\"]), userTeamMember, http.StatusOK, 1},\n\t\t\t{\"/teams/test-team/categories/\" + extraData[\"viewer\"], methodPut, category(userViewerID, extraData[\"viewer\"]), userViewer, http.StatusOK, 1},\n\t\t\t{\"/teams/test-team/categories/\" + extraData[\"commenter\"], methodPut, category(userCommenterID, extraData[\"commenter\"]), userCommenter, http.StatusOK, 1},\n\t\t\t{\"/teams/test-team/categories/\" + extraData[\"editor\"], methodPut, category(userEditorID, extraData[\"editor\"]), userEditor, http.StatusOK, 1},\n\t\t\t{\"/teams/test-team/categories/\" + extraData[\"admin\"], methodPut, category(userAdminID, extraData[\"admin\"]), userAdmin, http.StatusOK, 1},\n\t\t\t{\"/teams/test-team/categories/\" + extraData[\"guest\"], methodPut, category(userGuestID, extraData[\"guest\"]), userGuest, http.StatusOK, 1},\n\n\t\t\t{\"/teams/test-team/categories/any\", methodPut, category(\"other\", \"any\"), userAnonID, http.StatusUnauthorized, 0},\n\t\t\t{\"/teams/test-team/categories/\" + extraData[\"noTeamMember\"], methodPut, category(\"other\", extraData[\"noTeamMember\"]), userNoTeamMember, http.StatusBadRequest, 0},\n\t\t\t{\"/teams/test-team/categories/\" + extraData[\"teamMember\"], methodPut, category(\"other\", extraData[\"teamMember\"]), userTeamMember, http.StatusBadRequest, 0},\n\t\t\t{\"/teams/test-team/categories/\" + extraData[\"viewer\"], methodPut, category(\"other\", extraData[\"viewer\"]), userViewer, http.StatusBadRequest, 0},\n\t\t\t{\"/teams/test-team/categories/\" + extraData[\"commenter\"], methodPut, category(\"other\", extraData[\"commenter\"]), userCommenter, http.StatusBadRequest, 0},\n\t\t\t{\"/teams/test-team/categories/\" + extraData[\"editor\"], methodPut, category(\"other\", extraData[\"editor\"]), userEditor, http.StatusBadRequest, 0},\n\t\t\t{\"/teams/test-team/categories/\" + extraData[\"admin\"], methodPut, category(\"other\", extraData[\"admin\"]), userAdmin, http.StatusBadRequest, 0},\n\t\t\t{\"/teams/test-team/categories/\" + extraData[\"guest\"], methodPut, category(\"other\", extraData[\"guest\"]), userGuest, http.StatusBadRequest, 0},\n\n\t\t\t{\"/teams/other-team/categories/any\", methodPut, category(\"\", \"any\"), userAnonID, http.StatusUnauthorized, 0},\n\t\t\t{\"/teams/other-team/categories/\" + extraData[\"noTeamMember\"], methodPut, category(userNoTeamMemberID, extraData[\"noTeamMember\"]), userNoTeamMember, http.StatusBadRequest, 0},\n\t\t\t{\"/teams/other-team/categories/\" + extraData[\"teamMember\"], methodPut, category(userTeamMemberID, extraData[\"teamMember\"]), userTeamMember, http.StatusBadRequest, 0},\n\t\t\t{\"/teams/other-team/categories/\" + extraData[\"viewer\"], methodPut, category(userViewerID, extraData[\"viewer\"]), userViewer, http.StatusBadRequest, 0},\n\t\t\t{\"/teams/other-team/categories/\" + extraData[\"commenter\"], methodPut, category(userCommenterID, extraData[\"commenter\"]), userCommenter, http.StatusBadRequest, 0},\n\t\t\t{\"/teams/other-team/categories/\" + extraData[\"editor\"], methodPut, category(userEditorID, extraData[\"editor\"]), userEditor, http.StatusBadRequest, 0},\n\t\t\t{\"/teams/other-team/categories/\" + extraData[\"admin\"], methodPut, category(userAdminID, extraData[\"admin\"]), userAdmin, http.StatusBadRequest, 0},\n\t\t\t{\"/teams/other-team/categories/\" + extraData[\"guest\"], methodPut, category(userGuestID, extraData[\"guest\"]), userGuest, http.StatusBadRequest, 0},\n\t\t}\n\t}\n\n\textraSetup := func(t *testing.T, th *TestHelper) map[string]string {\n\t\tcategoryNoTeamMember, err := th.Server.App().CreateCategory(\n\t\t\t&model.Category{Name: \"Test category\", TeamID: \"test-team\", UserID: userNoTeamMemberID, CreateAt: model.GetMillis(), UpdateAt: model.GetMillis()},\n\t\t)\n\t\trequire.NoError(t, err)\n\t\tcategoryTeamMember, err := th.Server.App().CreateCategory(\n\t\t\t&model.Category{Name: \"Test category\", TeamID: \"test-team\", UserID: userTeamMemberID, CreateAt: model.GetMillis(), UpdateAt: model.GetMillis()},\n\t\t)\n\t\trequire.NoError(t, err)\n\t\tcategoryViewer, err := th.Server.App().CreateCategory(\n\t\t\t&model.Category{Name: \"Test category\", TeamID: \"test-team\", UserID: userViewerID, CreateAt: model.GetMillis(), UpdateAt: model.GetMillis()},\n\t\t)\n\t\trequire.NoError(t, err)\n\t\tcategoryCommenter, err := th.Server.App().CreateCategory(\n\t\t\t&model.Category{Name: \"Test category\", TeamID: \"test-team\", UserID: userCommenterID, CreateAt: model.GetMillis(), UpdateAt: model.GetMillis()},\n\t\t)\n\t\trequire.NoError(t, err)\n\t\tcategoryEditor, err := th.Server.App().CreateCategory(\n\t\t\t&model.Category{Name: \"Test category\", TeamID: \"test-team\", UserID: userEditorID, CreateAt: model.GetMillis(), UpdateAt: model.GetMillis()},\n\t\t)\n\t\trequire.NoError(t, err)\n\t\tcategoryAdmin, err := th.Server.App().CreateCategory(\n\t\t\t&model.Category{Name: \"Test category\", TeamID: \"test-team\", UserID: userAdminID, CreateAt: model.GetMillis(), UpdateAt: model.GetMillis()},\n\t\t)\n\t\trequire.NoError(t, err)\n\t\tcategoryGuest, err := th.Server.App().CreateCategory(\n\t\t\t&model.Category{Name: \"Test category\", TeamID: \"test-team\", UserID: userGuestID, CreateAt: model.GetMillis(), UpdateAt: model.GetMillis()},\n\t\t)\n\t\trequire.NoError(t, err)\n\t\treturn map[string]string{\n\t\t\t\"noTeamMember\": categoryNoTeamMember.ID,\n\t\t\t\"teamMember\":   categoryTeamMember.ID,\n\t\t\t\"viewer\":       categoryViewer.ID,\n\t\t\t\"commenter\":    categoryCommenter.ID,\n\t\t\t\"editor\":       categoryEditor.ID,\n\t\t\t\"admin\":        categoryAdmin.ID,\n\t\t\t\"guest\":        categoryGuest.ID,\n\t\t}\n\t}\n\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\textraData := extraSetup(t, th)\n\t\tttCases := ttCasesF(extraData)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\textraData := extraSetup(t, th)\n\t\tttCases := ttCasesF(extraData)\n\t\tttCases[1].expectedStatusCode = http.StatusOK\n\t\tttCases[1].totalResults = 1\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsDeleteCategory(t *testing.T) {\n\tttCasesF := func(extraData map[string]string) []TestCase {\n\t\treturn []TestCase{\n\t\t\t{\"/teams/other-team/categories/any\", methodDelete, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/teams/other-team/categories/\" + extraData[\"noTeamMember\"], methodDelete, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/teams/other-team/categories/\" + extraData[\"teamMember\"], methodDelete, \"\", userTeamMember, http.StatusBadRequest, 0},\n\t\t\t{\"/teams/other-team/categories/\" + extraData[\"viewer\"], methodDelete, \"\", userViewer, http.StatusBadRequest, 0},\n\t\t\t{\"/teams/other-team/categories/\" + extraData[\"commenter\"], methodDelete, \"\", userCommenter, http.StatusBadRequest, 0},\n\t\t\t{\"/teams/other-team/categories/\" + extraData[\"editor\"], methodDelete, \"\", userEditor, http.StatusBadRequest, 0},\n\t\t\t{\"/teams/other-team/categories/\" + extraData[\"admin\"], methodDelete, \"\", userAdmin, http.StatusBadRequest, 0},\n\t\t\t{\"/teams/other-team/categories/\" + extraData[\"guest\"], methodDelete, \"\", userGuest, http.StatusBadRequest, 0},\n\n\t\t\t{\"/teams/test-team/categories/any\", methodDelete, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/teams/test-team/categories/\" + extraData[\"noTeamMember\"], methodDelete, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/teams/test-team/categories/\" + extraData[\"teamMember\"], methodDelete, \"\", userTeamMember, http.StatusOK, 1},\n\t\t\t{\"/teams/test-team/categories/\" + extraData[\"viewer\"], methodDelete, \"\", userViewer, http.StatusOK, 1},\n\t\t\t{\"/teams/test-team/categories/\" + extraData[\"commenter\"], methodDelete, \"\", userCommenter, http.StatusOK, 1},\n\t\t\t{\"/teams/test-team/categories/\" + extraData[\"editor\"], methodDelete, \"\", userEditor, http.StatusOK, 1},\n\t\t\t{\"/teams/test-team/categories/\" + extraData[\"admin\"], methodDelete, \"\", userAdmin, http.StatusOK, 1},\n\t\t\t{\"/teams/test-team/categories/\" + extraData[\"guest\"], methodDelete, \"\", userGuest, http.StatusOK, 1},\n\t\t}\n\t}\n\n\textraSetup := func(t *testing.T, th *TestHelper) map[string]string {\n\t\tcategoryNoTeamMember, err := th.Server.App().CreateCategory(\n\t\t\t&model.Category{Name: \"Test category\", TeamID: \"test-team\", UserID: userNoTeamMemberID, CreateAt: model.GetMillis(), UpdateAt: model.GetMillis()},\n\t\t)\n\t\trequire.NoError(t, err)\n\t\tcategoryTeamMember, err := th.Server.App().CreateCategory(\n\t\t\t&model.Category{Name: \"Test category\", TeamID: \"test-team\", UserID: userTeamMemberID, CreateAt: model.GetMillis(), UpdateAt: model.GetMillis()},\n\t\t)\n\t\trequire.NoError(t, err)\n\t\tcategoryViewer, err := th.Server.App().CreateCategory(\n\t\t\t&model.Category{Name: \"Test category\", TeamID: \"test-team\", UserID: userViewerID, CreateAt: model.GetMillis(), UpdateAt: model.GetMillis()},\n\t\t)\n\t\trequire.NoError(t, err)\n\t\tcategoryCommenter, err := th.Server.App().CreateCategory(\n\t\t\t&model.Category{Name: \"Test category\", TeamID: \"test-team\", UserID: userCommenterID, CreateAt: model.GetMillis(), UpdateAt: model.GetMillis()},\n\t\t)\n\t\trequire.NoError(t, err)\n\t\tcategoryEditor, err := th.Server.App().CreateCategory(\n\t\t\t&model.Category{Name: \"Test category\", TeamID: \"test-team\", UserID: userEditorID, CreateAt: model.GetMillis(), UpdateAt: model.GetMillis()},\n\t\t)\n\t\trequire.NoError(t, err)\n\t\tcategoryAdmin, err := th.Server.App().CreateCategory(\n\t\t\t&model.Category{Name: \"Test category\", TeamID: \"test-team\", UserID: userAdminID, CreateAt: model.GetMillis(), UpdateAt: model.GetMillis()},\n\t\t)\n\t\trequire.NoError(t, err)\n\t\tcategoryGuest, err := th.Server.App().CreateCategory(\n\t\t\t&model.Category{Name: \"Test category\", TeamID: \"test-team\", UserID: userGuestID, CreateAt: model.GetMillis(), UpdateAt: model.GetMillis()},\n\t\t)\n\t\trequire.NoError(t, err)\n\t\treturn map[string]string{\n\t\t\t\"noTeamMember\": categoryNoTeamMember.ID,\n\t\t\t\"teamMember\":   categoryTeamMember.ID,\n\t\t\t\"viewer\":       categoryViewer.ID,\n\t\t\t\"commenter\":    categoryCommenter.ID,\n\t\t\t\"editor\":       categoryEditor.ID,\n\t\t\t\"admin\":        categoryAdmin.ID,\n\t\t\t\"guest\":        categoryGuest.ID,\n\t\t}\n\t}\n\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\textraData := extraSetup(t, th)\n\t\tttCases := ttCasesF(extraData)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\textraData := extraSetup(t, th)\n\t\tttCases := ttCasesF(extraData)\n\t\tttCases[1].expectedStatusCode = http.StatusBadRequest\n\t\tttCases[1].totalResults = 0\n\t\tttCases[9].expectedStatusCode = http.StatusOK\n\t\tttCases[9].totalResults = 1\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsUpdateCategoryBoard(t *testing.T) {\n\tttCasesF := func(testData TestData, extraData map[string]string) []TestCase {\n\t\treturn []TestCase{\n\t\t\t{\"/teams/test-team/categories/any/boards/any\", methodPost, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/teams/test-team/categories/\" + extraData[\"noTeamMember\"] + \"/boards/\" + testData.publicBoard.ID, methodPost, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/teams/test-team/categories/\" + extraData[\"teamMember\"] + \"/boards/\" + testData.publicBoard.ID, methodPost, \"\", userTeamMember, http.StatusOK, 0},\n\t\t\t{\"/teams/test-team/categories/\" + extraData[\"viewer\"] + \"/boards/\" + testData.publicBoard.ID, methodPost, \"\", userViewer, http.StatusOK, 0},\n\t\t\t{\"/teams/test-team/categories/\" + extraData[\"commenter\"] + \"/boards/\" + testData.publicBoard.ID, methodPost, \"\", userCommenter, http.StatusOK, 0},\n\t\t\t{\"/teams/test-team/categories/\" + extraData[\"editor\"] + \"/boards/\" + testData.publicBoard.ID, methodPost, \"\", userEditor, http.StatusOK, 0},\n\t\t\t{\"/teams/test-team/categories/\" + extraData[\"admin\"] + \"/boards/\" + testData.publicBoard.ID, methodPost, \"\", userAdmin, http.StatusOK, 0},\n\t\t\t{\"/teams/test-team/categories/\" + extraData[\"guest\"] + \"/boards/\" + testData.publicBoard.ID, methodPost, \"\", userGuest, http.StatusOK, 0},\n\t\t}\n\t}\n\n\textraSetup := func(t *testing.T, th *TestHelper) map[string]string {\n\t\tcategoryNoTeamMember, err := th.Server.App().CreateCategory(\n\t\t\t&model.Category{Name: \"Test category\", TeamID: \"test-team\", UserID: userNoTeamMemberID, CreateAt: model.GetMillis(), UpdateAt: model.GetMillis()},\n\t\t)\n\t\trequire.NoError(t, err)\n\t\tcategoryTeamMember, err := th.Server.App().CreateCategory(\n\t\t\t&model.Category{Name: \"Test category\", TeamID: \"test-team\", UserID: userTeamMemberID, CreateAt: model.GetMillis(), UpdateAt: model.GetMillis()},\n\t\t)\n\t\trequire.NoError(t, err)\n\t\tcategoryViewer, err := th.Server.App().CreateCategory(\n\t\t\t&model.Category{Name: \"Test category\", TeamID: \"test-team\", UserID: userViewerID, CreateAt: model.GetMillis(), UpdateAt: model.GetMillis()},\n\t\t)\n\t\trequire.NoError(t, err)\n\t\tcategoryCommenter, err := th.Server.App().CreateCategory(\n\t\t\t&model.Category{Name: \"Test category\", TeamID: \"test-team\", UserID: userCommenterID, CreateAt: model.GetMillis(), UpdateAt: model.GetMillis()},\n\t\t)\n\t\trequire.NoError(t, err)\n\t\tcategoryEditor, err := th.Server.App().CreateCategory(\n\t\t\t&model.Category{Name: \"Test category\", TeamID: \"test-team\", UserID: userEditorID, CreateAt: model.GetMillis(), UpdateAt: model.GetMillis()},\n\t\t)\n\t\trequire.NoError(t, err)\n\t\tcategoryAdmin, err := th.Server.App().CreateCategory(\n\t\t\t&model.Category{Name: \"Test category\", TeamID: \"test-team\", UserID: userAdminID, CreateAt: model.GetMillis(), UpdateAt: model.GetMillis()},\n\t\t)\n\t\trequire.NoError(t, err)\n\t\tcategoryGuest, err := th.Server.App().CreateCategory(\n\t\t\t&model.Category{Name: \"Test category\", TeamID: \"test-team\", UserID: userGuestID, CreateAt: model.GetMillis(), UpdateAt: model.GetMillis()},\n\t\t)\n\t\trequire.NoError(t, err)\n\t\treturn map[string]string{\n\t\t\t\"noTeamMember\": categoryNoTeamMember.ID,\n\t\t\t\"teamMember\":   categoryTeamMember.ID,\n\t\t\t\"viewer\":       categoryViewer.ID,\n\t\t\t\"commenter\":    categoryCommenter.ID,\n\t\t\t\"editor\":       categoryEditor.ID,\n\t\t\t\"admin\":        categoryAdmin.ID,\n\t\t\t\"guest\":        categoryGuest.ID,\n\t\t}\n\t}\n\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\textraData := extraSetup(t, th)\n\t\tttCases := ttCasesF(testData, extraData)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\textraData := extraSetup(t, th)\n\t\tttCases := ttCasesF(testData, extraData)\n\t\tttCases[1].expectedStatusCode = http.StatusOK\n\t\tttCases[1].totalResults = 0\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsGetFile(t *testing.T) {\n\tttCasesF := func() []TestCase {\n\t\treturn []TestCase{\n\t\t\t{\"/files/teams/test-team/{PRIVATE_BOARD_ID}/{NEW_FILE_ID}\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/files/teams/test-team/{PRIVATE_BOARD_ID}/{NEW_FILE_ID}\", methodGet, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/files/teams/test-team/{PRIVATE_BOARD_ID}/{NEW_FILE_ID}\", methodGet, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/files/teams/test-team/{PRIVATE_BOARD_ID}/{NEW_FILE_ID}\", methodGet, \"\", userViewer, http.StatusOK, 1},\n\t\t\t{\"/files/teams/test-team/{PRIVATE_BOARD_ID}/{NEW_FILE_ID}\", methodGet, \"\", userCommenter, http.StatusOK, 1},\n\t\t\t{\"/files/teams/test-team/{PRIVATE_BOARD_ID}/{NEW_FILE_ID}\", methodGet, \"\", userEditor, http.StatusOK, 1},\n\t\t\t{\"/files/teams/test-team/{PRIVATE_BOARD_ID}/{NEW_FILE_ID}\", methodGet, \"\", userAdmin, http.StatusOK, 1},\n\t\t\t{\"/files/teams/test-team/{PRIVATE_BOARD_ID}/{NEW_FILE_ID}\", methodGet, \"\", userGuest, http.StatusOK, 1},\n\n\t\t\t{\"/files/teams/test-team/{PRIVATE_BOARD_ID}/{NEW_FILE_ID}?read_token=invalid\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/files/teams/test-team/{PRIVATE_BOARD_ID}/{NEW_FILE_ID}?read_token=valid\", methodGet, \"\", userAnon, http.StatusOK, 1},\n\t\t\t{\"/files/teams/test-team/{PRIVATE_BOARD_ID}/{NEW_FILE_ID}?read_token=invalid\", methodGet, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/files/teams/test-team/{PRIVATE_BOARD_ID}/{NEW_FILE_ID}?read_token=valid\", methodGet, \"\", userTeamMember, http.StatusOK, 1},\n\t\t}\n\t}\n\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\n\t\tnewFileID, err := th.Server.App().SaveFile(bytes.NewBuffer([]byte(\"test\")), \"test-team\", testData.privateBoard.ID, \"test.png\", false)\n\t\trequire.NoError(t, err)\n\n\t\tttCases := ttCasesF()\n\t\tfor i, tc := range ttCases {\n\t\t\tttCases[i].url = strings.Replace(tc.url, \"{NEW_FILE_ID}\", newFileID, 1)\n\t\t}\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\n\t\tnewFileID, err := th.Server.App().SaveFile(bytes.NewBuffer([]byte(\"test\")), \"test-team\", testData.privateBoard.ID, \"test.png\", false)\n\t\trequire.NoError(t, err)\n\n\t\tttCases := ttCasesF()\n\t\tfor i, tc := range ttCases {\n\t\t\tttCases[i].url = strings.Replace(tc.url, \"{NEW_FILE_ID}\", newFileID, 1)\n\t\t}\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsCreateSubscription(t *testing.T) {\n\tttCases := func() []TestCase {\n\t\tsubscription := func(userID string) string {\n\t\t\treturn toJSON(t, model.Subscription{\n\t\t\t\tBlockType:      \"card\",\n\t\t\t\tBlockID:        \"block-3\",\n\t\t\t\tSubscriberType: \"user\",\n\t\t\t\tSubscriberID:   userID,\n\t\t\t\tCreateAt:       model.GetMillis(),\n\t\t\t})\n\t\t}\n\t\treturn []TestCase{\n\t\t\t{\"/subscriptions\", methodPost, subscription(\"\"), userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/subscriptions\", methodPost, subscription(userNoTeamMemberID), userNoTeamMember, http.StatusOK, 1},\n\t\t\t{\"/subscriptions\", methodPost, subscription(userTeamMemberID), userTeamMember, http.StatusOK, 1},\n\t\t\t{\"/subscriptions\", methodPost, subscription(userViewerID), userViewer, http.StatusOK, 1},\n\t\t\t{\"/subscriptions\", methodPost, subscription(userCommenterID), userCommenter, http.StatusOK, 1},\n\t\t\t{\"/subscriptions\", methodPost, subscription(userEditorID), userEditor, http.StatusOK, 1},\n\t\t\t{\"/subscriptions\", methodPost, subscription(userAdminID), userAdmin, http.StatusOK, 1},\n\t\t\t{\"/subscriptions\", methodPost, subscription(userGuestID), userGuest, http.StatusOK, 1},\n\t\t}\n\t}\n\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases(), testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases(), testData, clients)\n\t})\n}\n\nfunc TestPermissionsGetSubscriptions(t *testing.T) {\n\tttCases := []TestCase{\n\t\t{\"/subscriptions/{USER_ANON_ID}\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/subscriptions/{USER_NO_TEAM_MEMBER_ID}\", methodGet, \"\", userNoTeamMember, http.StatusOK, 0},\n\t\t{\"/subscriptions/{USER_TEAM_MEMBER_ID}\", methodGet, \"\", userTeamMember, http.StatusOK, 0},\n\t\t{\"/subscriptions/{USER_VIEWER_ID}\", methodGet, \"\", userViewer, http.StatusOK, 0},\n\t\t{\"/subscriptions/{USER_COMMENTER_ID}\", methodGet, \"\", userCommenter, http.StatusOK, 0},\n\t\t{\"/subscriptions/{USER_EDITOR_ID}\", methodGet, \"\", userEditor, http.StatusOK, 0},\n\t\t{\"/subscriptions/{USER_ADMIN_ID}\", methodGet, \"\", userAdmin, http.StatusOK, 0},\n\t\t{\"/subscriptions/{USER_GUEST_ID}\", methodGet, \"\", userGuest, http.StatusOK, 0},\n\n\t\t{\"/subscriptions/other\", methodGet, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/subscriptions/other\", methodGet, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/subscriptions/other\", methodGet, \"\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/subscriptions/other\", methodGet, \"\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/subscriptions/other\", methodGet, \"\", userEditor, http.StatusForbidden, 0},\n\t\t{\"/subscriptions/other\", methodGet, \"\", userAdmin, http.StatusForbidden, 0},\n\t\t{\"/subscriptions/other\", methodGet, \"\", userGuest, http.StatusForbidden, 0},\n\t}\n\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsDeleteSubscription(t *testing.T) {\n\tttCases := []TestCase{\n\t\t{\"/subscriptions/block-3/{USER_ANON_ID}\", methodDelete, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/subscriptions/block-3/{USER_NO_TEAM_MEMBER_ID}\", methodDelete, \"\", userNoTeamMember, http.StatusOK, 0},\n\t\t{\"/subscriptions/block-3/{USER_TEAM_MEMBER_ID}\", methodDelete, \"\", userTeamMember, http.StatusOK, 0},\n\t\t{\"/subscriptions/block-3/{USER_VIEWER_ID}\", methodDelete, \"\", userViewer, http.StatusOK, 0},\n\t\t{\"/subscriptions/block-3/{USER_COMMENTER_ID}\", methodDelete, \"\", userCommenter, http.StatusOK, 0},\n\t\t{\"/subscriptions/block-3/{USER_EDITOR_ID}\", methodDelete, \"\", userEditor, http.StatusOK, 0},\n\t\t{\"/subscriptions/block-3/{USER_ADMIN_ID}\", methodDelete, \"\", userAdmin, http.StatusOK, 0},\n\t\t{\"/subscriptions/block-3/{USER_GUEST_ID}\", methodDelete, \"\", userGuest, http.StatusOK, 0},\n\n\t\t{\"/subscriptions/block-3/other\", methodDelete, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/subscriptions/block-3/other\", methodDelete, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/subscriptions/block-3/other\", methodDelete, \"\", userViewer, http.StatusForbidden, 0},\n\t\t{\"/subscriptions/block-3/other\", methodDelete, \"\", userCommenter, http.StatusForbidden, 0},\n\t\t{\"/subscriptions/block-3/other\", methodDelete, \"\", userEditor, http.StatusForbidden, 0},\n\t\t{\"/subscriptions/block-3/other\", methodDelete, \"\", userAdmin, http.StatusForbidden, 0},\n\t\t{\"/subscriptions/block-3/other\", methodDelete, \"\", userGuest, http.StatusForbidden, 0},\n\t}\n\n\textraSetup := func(t *testing.T, th *TestHelper) {\n\t\t_, err := th.Server.App().CreateSubscription(\n\t\t\t&model.Subscription{BlockType: \"card\", BlockID: \"block-3\", SubscriberType: \"user\", SubscriberID: userNoTeamMemberID, CreateAt: model.GetMillis()},\n\t\t)\n\t\trequire.NoError(t, err)\n\t\t_, err = th.Server.App().CreateSubscription(\n\t\t\t&model.Subscription{BlockType: \"card\", BlockID: \"block-3\", SubscriberType: \"user\", SubscriberID: userTeamMemberID, CreateAt: model.GetMillis()},\n\t\t)\n\t\trequire.NoError(t, err)\n\t\t_, err = th.Server.App().CreateSubscription(\n\t\t\t&model.Subscription{BlockType: \"card\", BlockID: \"block-3\", SubscriberType: \"user\", SubscriberID: userViewerID, CreateAt: model.GetMillis()},\n\t\t)\n\t\trequire.NoError(t, err)\n\t\t_, err = th.Server.App().CreateSubscription(\n\t\t\t&model.Subscription{BlockType: \"card\", BlockID: \"block-3\", SubscriberType: \"user\", SubscriberID: userCommenterID, CreateAt: model.GetMillis()},\n\t\t)\n\t\trequire.NoError(t, err)\n\t\t_, err = th.Server.App().CreateSubscription(\n\t\t\t&model.Subscription{BlockType: \"card\", BlockID: \"block-3\", SubscriberType: \"user\", SubscriberID: userEditorID, CreateAt: model.GetMillis()},\n\t\t)\n\t\trequire.NoError(t, err)\n\t\t_, err = th.Server.App().CreateSubscription(\n\t\t\t&model.Subscription{BlockType: \"card\", BlockID: \"block-3\", SubscriberType: \"user\", SubscriberID: userAdminID, CreateAt: model.GetMillis()},\n\t\t)\n\t\trequire.NoError(t, err)\n\t\t_, err = th.Server.App().CreateSubscription(\n\t\t\t&model.Subscription{BlockType: \"card\", BlockID: \"block-3\", SubscriberType: \"user\", SubscriberID: userGuestID, CreateAt: model.GetMillis()},\n\t\t)\n\t\trequire.NoError(t, err)\n\t\t_, err = th.Server.App().CreateSubscription(\n\t\t\t&model.Subscription{BlockType: \"card\", BlockID: \"block-3\", SubscriberType: \"user\", SubscriberID: \"other\", CreateAt: model.GetMillis()},\n\t\t)\n\t\trequire.NoError(t, err)\n\t}\n\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\textraSetup(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\textraSetup(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsOnboard(t *testing.T) {\n\tttCases := []TestCase{\n\t\t{\"/teams/test-team/onboard\", methodPost, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/teams/test-team/onboard\", methodPost, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/teams/test-team/onboard\", methodPost, \"\", userTeamMember, http.StatusOK, 1},\n\t\t{\"/teams/test-team/onboard\", methodPost, \"\", userViewer, http.StatusOK, 1},\n\t\t{\"/teams/test-team/onboard\", methodPost, \"\", userCommenter, http.StatusOK, 1},\n\t\t{\"/teams/test-team/onboard\", methodPost, \"\", userEditor, http.StatusOK, 1},\n\t\t{\"/teams/test-team/onboard\", methodPost, \"\", userAdmin, http.StatusOK, 1},\n\t\t{\"/teams/test-team/onboard\", methodPost, \"\", userGuest, http.StatusForbidden, 0},\n\t}\n\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\n\t\terr := th.Server.App().InitTemplates()\n\t\trequire.NoError(t, err, \"InitTemplates should not fail\")\n\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\n\t\terr := th.Server.App().InitTemplates()\n\t\trequire.NoError(t, err, \"InitTemplates should not fail\")\n\n\t\tttCases[1].expectedStatusCode = http.StatusOK\n\t\tttCases[1].totalResults = 1\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsBoardArchiveExport(t *testing.T) {\n\tttCases := []TestCase{\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/archive/export\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/archive/export\", methodGet, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/archive/export\", methodGet, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/archive/export\", methodGet, \"\", userViewer, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/archive/export\", methodGet, \"\", userCommenter, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/archive/export\", methodGet, \"\", userEditor, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/archive/export\", methodGet, \"\", userAdmin, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_BOARD_ID}/archive/export\", methodGet, \"\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/archive/export\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/archive/export\", methodGet, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/archive/export\", methodGet, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/archive/export\", methodGet, \"\", userViewer, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/archive/export\", methodGet, \"\", userCommenter, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/archive/export\", methodGet, \"\", userEditor, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/archive/export\", methodGet, \"\", userAdmin, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_BOARD_ID}/archive/export\", methodGet, \"\", userGuest, http.StatusOK, 1},\n\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/archive/export\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/archive/export\", methodGet, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/archive/export\", methodGet, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/archive/export\", methodGet, \"\", userViewer, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/archive/export\", methodGet, \"\", userCommenter, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/archive/export\", methodGet, \"\", userEditor, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/archive/export\", methodGet, \"\", userAdmin, http.StatusOK, 1},\n\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/archive/export\", methodGet, \"\", userGuest, http.StatusForbidden, 0},\n\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/archive/export\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/archive/export\", methodGet, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/archive/export\", methodGet, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/archive/export\", methodGet, \"\", userViewer, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/archive/export\", methodGet, \"\", userCommenter, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/archive/export\", methodGet, \"\", userEditor, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/archive/export\", methodGet, \"\", userAdmin, http.StatusOK, 1},\n\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/archive/export\", methodGet, \"\", userGuest, http.StatusForbidden, 0},\n\t}\n\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsBoardArchiveImport(t *testing.T) {\n\tttCases := []TestCase{\n\t\t{\"/teams/test-team/archive/import\", methodPost, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t{\"/teams/test-team/archive/import\", methodPost, \"\", userNoTeamMember, http.StatusForbidden, 1},\n\t\t{\"/teams/test-team/archive/import\", methodPost, \"\", userTeamMember, http.StatusOK, 1},\n\t\t{\"/teams/test-team/archive/import\", methodPost, \"\", userViewer, http.StatusOK, 1},\n\t\t{\"/teams/test-team/archive/import\", methodPost, \"\", userCommenter, http.StatusOK, 1},\n\t\t{\"/teams/test-team/archive/import\", methodPost, \"\", userEditor, http.StatusOK, 1},\n\t\t{\"/teams/test-team/archive/import\", methodPost, \"\", userAdmin, http.StatusOK, 1},\n\t\t{\"/teams/test-team/archive/import\", methodPost, \"\", userGuest, http.StatusForbidden, 0},\n\t}\n\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases[1].expectedStatusCode = http.StatusOK\n\t\tttCases[1].totalResults = 1\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsMinimumRolesApplied(t *testing.T) {\n\tttCasesF := func(t *testing.T, th *TestHelper, minimumRole model.BoardRole, testData TestData) []TestCase {\n\t\tcounter := 0\n\t\tnewBlockJSON := func(boardID string) string {\n\t\t\tcounter++\n\t\t\treturn toJSON(t, []*model.Block{{\n\t\t\t\tID:       fmt.Sprintf(\"%d\", counter),\n\t\t\t\tTitle:    \"Board To Create\",\n\t\t\t\tBoardID:  boardID,\n\t\t\t\tType:     \"card\",\n\t\t\t\tCreateAt: model.GetMillis(),\n\t\t\t\tUpdateAt: model.GetMillis(),\n\t\t\t}})\n\t\t}\n\t\t_, err := th.Server.App().PatchBoard(&model.BoardPatch{MinimumRole: &minimumRole}, testData.privateBoard.ID, userAdminID)\n\t\trequire.NoError(t, err)\n\t\t_, err = th.Server.App().PatchBoard(&model.BoardPatch{MinimumRole: &minimumRole}, testData.publicBoard.ID, userAdminID)\n\t\trequire.NoError(t, err)\n\t\t_, err = th.Server.App().PatchBoard(&model.BoardPatch{MinimumRole: &minimumRole}, testData.privateTemplate.ID, userAdminID)\n\t\trequire.NoError(t, err)\n\t\t_, err = th.Server.App().PatchBoard(&model.BoardPatch{MinimumRole: &minimumRole}, testData.publicTemplate.ID, userAdminID)\n\t\trequire.NoError(t, err)\n\n\t\tif minimumRole == \"viewer\" || minimumRole == \"commenter\" {\n\t\t\treturn []TestCase{\n\t\t\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.privateBoard.ID), userAnon, http.StatusUnauthorized, 0},\n\t\t\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.privateBoard.ID), userNoTeamMember, http.StatusForbidden, 0},\n\t\t\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.privateBoard.ID), userTeamMember, http.StatusForbidden, 0},\n\t\t\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.privateBoard.ID), userViewer, http.StatusForbidden, 0},\n\t\t\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.privateBoard.ID), userCommenter, http.StatusForbidden, 0},\n\t\t\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.privateBoard.ID), userEditor, http.StatusOK, 1},\n\t\t\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.privateBoard.ID), userAdmin, http.StatusOK, 1},\n\n\t\t\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.publicBoard.ID), userAnon, http.StatusUnauthorized, 0},\n\t\t\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.publicBoard.ID), userNoTeamMember, http.StatusForbidden, 0},\n\t\t\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.publicBoard.ID), userTeamMember, http.StatusForbidden, 0},\n\t\t\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.publicBoard.ID), userViewer, http.StatusForbidden, 0},\n\t\t\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.publicBoard.ID), userCommenter, http.StatusForbidden, 0},\n\t\t\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.publicBoard.ID), userEditor, http.StatusOK, 1},\n\t\t\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.publicBoard.ID), userAdmin, http.StatusOK, 1},\n\n\t\t\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.privateTemplate.ID), userAnon, http.StatusUnauthorized, 0},\n\t\t\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.privateTemplate.ID), userNoTeamMember, http.StatusForbidden, 0},\n\t\t\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.privateTemplate.ID), userTeamMember, http.StatusForbidden, 0},\n\t\t\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.privateTemplate.ID), userViewer, http.StatusForbidden, 0},\n\t\t\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.privateTemplate.ID), userCommenter, http.StatusForbidden, 0},\n\t\t\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.privateTemplate.ID), userEditor, http.StatusOK, 1},\n\t\t\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.privateTemplate.ID), userAdmin, http.StatusOK, 1},\n\n\t\t\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.publicTemplate.ID), userAnon, http.StatusUnauthorized, 0},\n\t\t\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.publicTemplate.ID), userNoTeamMember, http.StatusForbidden, 0},\n\t\t\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.publicTemplate.ID), userTeamMember, http.StatusForbidden, 0},\n\t\t\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.publicTemplate.ID), userViewer, http.StatusForbidden, 0},\n\t\t\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.publicTemplate.ID), userCommenter, http.StatusForbidden, 0},\n\t\t\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.publicTemplate.ID), userEditor, http.StatusOK, 1},\n\t\t\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.publicTemplate.ID), userAdmin, http.StatusOK, 1},\n\t\t\t}\n\t\t} else {\n\t\t\treturn []TestCase{\n\t\t\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.privateBoard.ID), userAnon, http.StatusUnauthorized, 0},\n\t\t\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.privateBoard.ID), userNoTeamMember, http.StatusForbidden, 0},\n\t\t\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.privateBoard.ID), userTeamMember, http.StatusForbidden, 0},\n\t\t\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.privateBoard.ID), userViewer, http.StatusOK, 1},\n\t\t\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.privateBoard.ID), userCommenter, http.StatusOK, 1},\n\t\t\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.privateBoard.ID), userEditor, http.StatusOK, 1},\n\t\t\t\t{\"/boards/{PRIVATE_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.privateBoard.ID), userAdmin, http.StatusOK, 1},\n\n\t\t\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.publicBoard.ID), userAnon, http.StatusUnauthorized, 0},\n\t\t\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.publicBoard.ID), userNoTeamMember, http.StatusForbidden, 0},\n\t\t\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.publicBoard.ID), userTeamMember, http.StatusForbidden, 0},\n\t\t\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.publicBoard.ID), userViewer, http.StatusOK, 1},\n\t\t\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.publicBoard.ID), userCommenter, http.StatusOK, 1},\n\t\t\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.publicBoard.ID), userEditor, http.StatusOK, 1},\n\t\t\t\t{\"/boards/{PUBLIC_BOARD_ID}/blocks\", methodPost, newBlockJSON(testData.publicBoard.ID), userAdmin, http.StatusOK, 1},\n\n\t\t\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.privateTemplate.ID), userAnon, http.StatusUnauthorized, 0},\n\t\t\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.privateTemplate.ID), userNoTeamMember, http.StatusForbidden, 0},\n\t\t\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.privateTemplate.ID), userTeamMember, http.StatusForbidden, 0},\n\t\t\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.privateTemplate.ID), userViewer, http.StatusOK, 1},\n\t\t\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.privateTemplate.ID), userCommenter, http.StatusOK, 1},\n\t\t\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.privateTemplate.ID), userEditor, http.StatusOK, 1},\n\t\t\t\t{\"/boards/{PRIVATE_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.privateTemplate.ID), userAdmin, http.StatusOK, 1},\n\n\t\t\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.publicTemplate.ID), userAnon, http.StatusUnauthorized, 0},\n\t\t\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.publicTemplate.ID), userNoTeamMember, http.StatusForbidden, 0},\n\t\t\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.publicTemplate.ID), userTeamMember, http.StatusForbidden, 0},\n\t\t\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.publicTemplate.ID), userViewer, http.StatusOK, 1},\n\t\t\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.publicTemplate.ID), userCommenter, http.StatusOK, 1},\n\t\t\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.publicTemplate.ID), userEditor, http.StatusOK, 1},\n\t\t\t\t{\"/boards/{PUBLIC_TEMPLATE_ID}/blocks\", methodPost, newBlockJSON(testData.publicTemplate.ID), userAdmin, http.StatusOK, 1},\n\t\t\t}\n\t\t}\n\t}\n\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tt.Run(\"minimum role viewer\", func(t *testing.T) {\n\t\t\tth := SetupTestHelperPluginMode(t)\n\t\t\tdefer th.TearDown()\n\t\t\tclients := setupClients(th)\n\t\t\ttestData := setupData(t, th)\n\t\t\tttCases := ttCasesF(t, th, \"viewer\", testData)\n\t\t\trunTestCases(t, ttCases, testData, clients)\n\t\t})\n\t\tt.Run(\"minimum role commenter\", func(t *testing.T) {\n\t\t\tth := SetupTestHelperPluginMode(t)\n\t\t\tdefer th.TearDown()\n\t\t\tclients := setupClients(th)\n\t\t\ttestData := setupData(t, th)\n\t\t\tttCases := ttCasesF(t, th, \"commenter\", testData)\n\t\t\trunTestCases(t, ttCases, testData, clients)\n\t\t})\n\t\tt.Run(\"minimum role editor\", func(t *testing.T) {\n\t\t\tth := SetupTestHelperPluginMode(t)\n\t\t\tdefer th.TearDown()\n\t\t\tclients := setupClients(th)\n\t\t\ttestData := setupData(t, th)\n\t\t\tttCases := ttCasesF(t, th, \"editor\", testData)\n\t\t\trunTestCases(t, ttCases, testData, clients)\n\t\t})\n\t\tt.Run(\"minimum role admin\", func(t *testing.T) {\n\t\t\tth := SetupTestHelperPluginMode(t)\n\t\t\tdefer th.TearDown()\n\t\t\tclients := setupClients(th)\n\t\t\ttestData := setupData(t, th)\n\t\t\tttCases := ttCasesF(t, th, \"admin\", testData)\n\t\t\trunTestCases(t, ttCases, testData, clients)\n\t\t})\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tt.Run(\"minimum role viewer\", func(t *testing.T) {\n\t\t\tth := SetupTestHelperLocalMode(t)\n\t\t\tdefer th.TearDown()\n\t\t\tclients := setupLocalClients(th)\n\t\t\ttestData := setupData(t, th)\n\t\t\tttCases := ttCasesF(t, th, \"viewer\", testData)\n\t\t\trunTestCases(t, ttCases, testData, clients)\n\t\t})\n\t\tt.Run(\"minimum role commenter\", func(t *testing.T) {\n\t\t\tth := SetupTestHelperLocalMode(t)\n\t\t\tdefer th.TearDown()\n\t\t\tclients := setupLocalClients(th)\n\t\t\ttestData := setupData(t, th)\n\t\t\tttCases := ttCasesF(t, th, \"commenter\", testData)\n\t\t\trunTestCases(t, ttCases, testData, clients)\n\t\t})\n\t\tt.Run(\"minimum role editor\", func(t *testing.T) {\n\t\t\tth := SetupTestHelperLocalMode(t)\n\t\t\tdefer th.TearDown()\n\t\t\tclients := setupLocalClients(th)\n\t\t\ttestData := setupData(t, th)\n\t\t\tttCases := ttCasesF(t, th, \"editor\", testData)\n\t\t\trunTestCases(t, ttCases, testData, clients)\n\t\t})\n\t\tt.Run(\"minimum role admin\", func(t *testing.T) {\n\t\t\tth := SetupTestHelperLocalMode(t)\n\t\t\tdefer th.TearDown()\n\t\t\tclients := setupLocalClients(th)\n\t\t\ttestData := setupData(t, th)\n\t\t\tttCases := ttCasesF(t, th, \"admin\", testData)\n\t\t\trunTestCases(t, ttCases, testData, clients)\n\t\t})\n\t})\n}\n\nfunc TestPermissionsChannels(t *testing.T) {\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases := []TestCase{\n\t\t\t{\"/teams/test-team/channels\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/teams/test-team/channels\", methodGet, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/teams/test-team/channels\", methodGet, \"\", userTeamMember, http.StatusOK, 2},\n\t\t\t{\"/teams/test-team/channels\", methodGet, \"\", userViewer, http.StatusOK, 2},\n\t\t\t{\"/teams/test-team/channels\", methodGet, \"\", userCommenter, http.StatusOK, 2},\n\t\t\t{\"/teams/test-team/channels\", methodGet, \"\", userEditor, http.StatusOK, 2},\n\t\t\t{\"/teams/test-team/channels\", methodGet, \"\", userAdmin, http.StatusOK, 2},\n\t\t}\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases := []TestCase{\n\t\t\t{\"/teams/test-team/channels\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/teams/test-team/channels\", methodGet, \"\", userNoTeamMember, http.StatusNotImplemented, 0},\n\t\t\t{\"/teams/test-team/channels\", methodGet, \"\", userTeamMember, http.StatusNotImplemented, 0},\n\t\t\t{\"/teams/test-team/channels\", methodGet, \"\", userViewer, http.StatusNotImplemented, 0},\n\t\t\t{\"/teams/test-team/channels\", methodGet, \"\", userCommenter, http.StatusNotImplemented, 0},\n\t\t\t{\"/teams/test-team/channels\", methodGet, \"\", userEditor, http.StatusNotImplemented, 0},\n\t\t\t{\"/teams/test-team/channels\", methodGet, \"\", userAdmin, http.StatusNotImplemented, 0},\n\t\t}\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsChannel(t *testing.T) {\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases := []TestCase{\n\t\t\t{\"/teams/test-team/channels/valid-channel-id\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/teams/test-team/channels/valid-channel-id\", methodGet, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/teams/test-team/channels/valid-channel-id\", methodGet, \"\", userTeamMember, http.StatusOK, 1},\n\t\t\t{\"/teams/test-team/channels/valid-channel-id\", methodGet, \"\", userViewer, http.StatusOK, 1},\n\t\t\t{\"/teams/test-team/channels/valid-channel-id\", methodGet, \"\", userCommenter, http.StatusOK, 1},\n\t\t\t{\"/teams/test-team/channels/valid-channel-id\", methodGet, \"\", userEditor, http.StatusOK, 1},\n\t\t\t{\"/teams/test-team/channels/valid-channel-id\", methodGet, \"\", userAdmin, http.StatusOK, 1},\n\n\t\t\t{\"/teams/test-team/channels/not-valid-channel-id\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/teams/test-team/channels/not-valid-channel-id\", methodGet, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/teams/test-team/channels/not-valid-channel-id\", methodGet, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/teams/test-team/channels/not-valid-channel-id\", methodGet, \"\", userViewer, http.StatusForbidden, 0},\n\t\t\t{\"/teams/test-team/channels/not-valid-channel-id\", methodGet, \"\", userCommenter, http.StatusForbidden, 0},\n\t\t\t{\"/teams/test-team/channels/not-valid-channel-id\", methodGet, \"\", userEditor, http.StatusForbidden, 0},\n\t\t\t{\"/teams/test-team/channels/not-valid-channel-id\", methodGet, \"\", userAdmin, http.StatusForbidden, 0},\n\t\t}\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases := []TestCase{\n\t\t\t{\"/teams/test-team/channels/valid-channel-id\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/teams/test-team/channels/valid-channel-id\", methodGet, \"\", userNoTeamMember, http.StatusNotImplemented, 0},\n\t\t\t{\"/teams/test-team/channels/valid-channel-id\", methodGet, \"\", userTeamMember, http.StatusNotImplemented, 0},\n\t\t\t{\"/teams/test-team/channels/valid-channel-id\", methodGet, \"\", userViewer, http.StatusNotImplemented, 0},\n\t\t\t{\"/teams/test-team/channels/valid-channel-id\", methodGet, \"\", userCommenter, http.StatusNotImplemented, 0},\n\t\t\t{\"/teams/test-team/channels/valid-channel-id\", methodGet, \"\", userEditor, http.StatusNotImplemented, 0},\n\t\t\t{\"/teams/test-team/channels/valid-channel-id\", methodGet, \"\", userAdmin, http.StatusNotImplemented, 0},\n\t\t}\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n\nfunc TestPermissionsGetStatistics(t *testing.T) {\n\tt.Run(\"plugin\", func(t *testing.T) {\n\t\tth := SetupTestHelperPluginMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases := []TestCase{\n\t\t\t{\"/statistics\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/statistics\", methodGet, \"\", userNoTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/statistics\", methodGet, \"\", userTeamMember, http.StatusForbidden, 0},\n\t\t\t{\"/statistics\", methodGet, \"\", userViewer, http.StatusForbidden, 0},\n\t\t\t{\"/statistics\", methodGet, \"\", userCommenter, http.StatusForbidden, 0},\n\t\t\t{\"/statistics\", methodGet, \"\", userEditor, http.StatusForbidden, 0},\n\t\t\t{\"/statistics\", methodGet, \"\", userAdmin, http.StatusOK, 1},\n\t\t\t{\"/statistics\", methodGet, \"\", userGuest, http.StatusForbidden, 0},\n\t\t}\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n\tt.Run(\"local\", func(t *testing.T) {\n\t\tth := SetupTestHelperLocalMode(t)\n\t\tdefer th.TearDown()\n\t\tclients := setupLocalClients(th)\n\t\ttestData := setupData(t, th)\n\t\tttCases := []TestCase{\n\t\t\t{\"/statistics\", methodGet, \"\", userAnon, http.StatusUnauthorized, 0},\n\t\t\t{\"/statistics\", methodGet, \"\", userNoTeamMember, http.StatusNotImplemented, 0},\n\t\t\t{\"/statistics\", methodGet, \"\", userTeamMember, http.StatusNotImplemented, 0},\n\t\t\t{\"/statistics\", methodGet, \"\", userViewer, http.StatusNotImplemented, 0},\n\t\t\t{\"/statistics\", methodGet, \"\", userCommenter, http.StatusNotImplemented, 0},\n\t\t\t{\"/statistics\", methodGet, \"\", userEditor, http.StatusNotImplemented, 0},\n\t\t\t{\"/statistics\", methodGet, \"\", userAdmin, http.StatusNotImplemented, 1},\n\t\t\t{\"/statistics\", methodGet, \"\", userGuest, http.StatusForbidden, 0},\n\t\t}\n\t\trunTestCases(t, ttCases, testData, clients)\n\t})\n}\n"
  },
  {
    "path": "server/integrationtests/pluginteststore.go",
    "content": "package integrationtests\n\nimport (\n\t\"errors\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/store\"\n\n\tmmModel \"github.com/mattermost/mattermost/server/public/model\"\n)\n\nvar errTestStore = errors.New(\"plugin test store error\")\n\ntype PluginTestStore struct {\n\tstore.Store\n\tusers     map[string]*model.User\n\ttestTeam  *model.Team\n\totherTeam *model.Team\n\temptyTeam *model.Team\n\tbaseTeam  *model.Team\n}\n\nfunc NewPluginTestStore(innerStore store.Store) *PluginTestStore {\n\treturn &PluginTestStore{\n\t\tStore: innerStore,\n\t\tusers: map[string]*model.User{\n\t\t\t\"no-team-member\": {\n\t\t\t\tID:       \"no-team-member\",\n\t\t\t\tUsername: \"no-team-member\",\n\t\t\t\tEmail:    \"no-team-member@sample.com\",\n\t\t\t\tCreateAt: model.GetMillis(),\n\t\t\t\tUpdateAt: model.GetMillis(),\n\t\t\t},\n\t\t\t\"team-member\": {\n\t\t\t\tID:       \"team-member\",\n\t\t\t\tUsername: \"team-member\",\n\t\t\t\tEmail:    \"team-member@sample.com\",\n\t\t\t\tCreateAt: model.GetMillis(),\n\t\t\t\tUpdateAt: model.GetMillis(),\n\t\t\t},\n\t\t\t\"viewer\": {\n\t\t\t\tID:       \"viewer\",\n\t\t\t\tUsername: \"viewer\",\n\t\t\t\tEmail:    \"viewer@sample.com\",\n\t\t\t\tCreateAt: model.GetMillis(),\n\t\t\t\tUpdateAt: model.GetMillis(),\n\t\t\t},\n\t\t\t\"commenter\": {\n\t\t\t\tID:       \"commenter\",\n\t\t\t\tUsername: \"commenter\",\n\t\t\t\tEmail:    \"commenter@sample.com\",\n\t\t\t\tCreateAt: model.GetMillis(),\n\t\t\t\tUpdateAt: model.GetMillis(),\n\t\t\t},\n\t\t\t\"editor\": {\n\t\t\t\tID:       \"editor\",\n\t\t\t\tUsername: \"editor\",\n\t\t\t\tEmail:    \"editor@sample.com\",\n\t\t\t\tCreateAt: model.GetMillis(),\n\t\t\t\tUpdateAt: model.GetMillis(),\n\t\t\t},\n\t\t\t\"admin\": {\n\t\t\t\tID:       \"admin\",\n\t\t\t\tUsername: \"admin\",\n\t\t\t\tEmail:    \"admin@sample.com\",\n\t\t\t\tCreateAt: model.GetMillis(),\n\t\t\t\tUpdateAt: model.GetMillis(),\n\t\t\t},\n\t\t\t\"guest\": {\n\t\t\t\tID:       \"guest\",\n\t\t\t\tUsername: \"guest\",\n\t\t\t\tEmail:    \"guest@sample.com\",\n\t\t\t\tCreateAt: model.GetMillis(),\n\t\t\t\tUpdateAt: model.GetMillis(),\n\t\t\t\tIsGuest:  true,\n\t\t\t},\n\t\t},\n\t\ttestTeam:  &model.Team{ID: \"test-team\", Title: \"Test Team\"},\n\t\totherTeam: &model.Team{ID: \"other-team\", Title: \"Other Team\"},\n\t\temptyTeam: &model.Team{ID: \"empty-team\", Title: \"Empty Team\"},\n\t\tbaseTeam:  &model.Team{ID: \"0\", Title: \"Base Team\"},\n\t}\n}\n\nfunc (s *PluginTestStore) GetTeam(id string) (*model.Team, error) {\n\tswitch id {\n\tcase \"0\":\n\t\treturn s.baseTeam, nil\n\tcase \"other-team\":\n\t\treturn s.otherTeam, nil\n\tcase \"test-team\", testTeamID:\n\t\treturn s.testTeam, nil\n\tcase \"empty-team\":\n\t\treturn s.emptyTeam, nil\n\t}\n\treturn nil, errTestStore\n}\n\nfunc (s *PluginTestStore) GetTeamsForUser(userID string) ([]*model.Team, error) {\n\tswitch userID {\n\tcase \"no-team-member\":\n\t\treturn []*model.Team{}, nil\n\tcase \"team-member\":\n\t\treturn []*model.Team{s.testTeam, s.otherTeam}, nil\n\tcase \"viewer\":\n\t\treturn []*model.Team{s.testTeam, s.otherTeam}, nil\n\tcase \"commenter\":\n\t\treturn []*model.Team{s.testTeam, s.otherTeam}, nil\n\tcase \"editor\":\n\t\treturn []*model.Team{s.testTeam, s.otherTeam}, nil\n\tcase \"admin\":\n\t\treturn []*model.Team{s.testTeam, s.otherTeam}, nil\n\tcase \"guest\":\n\t\treturn []*model.Team{s.testTeam}, nil\n\t}\n\treturn nil, errTestStore\n}\n\nfunc (s *PluginTestStore) GetUserByID(userID string) (*model.User, error) {\n\tuser := s.users[userID]\n\tif user == nil {\n\t\treturn nil, errTestStore\n\t}\n\treturn user, nil\n}\n\nfunc (s *PluginTestStore) GetUsersList(userIDs []string, showEmail, showName bool) ([]*model.User, error) {\n\tvar users []*model.User\n\tfor _, id := range userIDs {\n\t\tuser := s.users[id]\n\t\tif user != nil {\n\t\t\tusers = append(users, user)\n\t\t}\n\t}\n\treturn users, nil\n}\n\nfunc (s *PluginTestStore) GetUserByEmail(email string) (*model.User, error) {\n\tfor _, user := range s.users {\n\t\tif user.Email == email {\n\t\t\treturn user, nil\n\t\t}\n\t}\n\treturn nil, errTestStore\n}\n\nfunc (s *PluginTestStore) GetUserByUsername(username string) (*model.User, error) {\n\tfor _, user := range s.users {\n\t\tif user.Username == username {\n\t\t\treturn user, nil\n\t\t}\n\t}\n\treturn nil, errTestStore\n}\n\nfunc (s *PluginTestStore) GetUserPreferences(userID string) (mmModel.Preferences, error) {\n\tif userID == userTeamMember {\n\t\treturn mmModel.Preferences{{\n\t\t\tUserId:   userTeamMember,\n\t\t\tCategory: \"focalboard\",\n\t\t\tName:     \"test\",\n\t\t\tValue:    \"test\",\n\t\t}}, nil\n\t}\n\n\treturn nil, errTestStore\n}\n\nfunc (s *PluginTestStore) GetUsersByTeam(teamID string, asGuestID string, showEmail, showName bool) ([]*model.User, error) {\n\tif asGuestID == \"guest\" {\n\t\treturn []*model.User{\n\t\t\ts.users[\"viewer\"],\n\t\t\ts.users[\"commenter\"],\n\t\t\ts.users[\"editor\"],\n\t\t\ts.users[\"admin\"],\n\t\t\ts.users[\"guest\"],\n\t\t}, nil\n\t}\n\n\tswitch {\n\tcase teamID == s.testTeam.ID:\n\t\treturn []*model.User{\n\t\t\ts.users[\"team-member\"],\n\t\t\ts.users[\"viewer\"],\n\t\t\ts.users[\"commenter\"],\n\t\t\ts.users[\"editor\"],\n\t\t\ts.users[\"admin\"],\n\t\t\ts.users[\"guest\"],\n\t\t}, nil\n\tcase teamID == s.otherTeam.ID:\n\t\treturn []*model.User{\n\t\t\ts.users[\"team-member\"],\n\t\t\ts.users[\"viewer\"],\n\t\t\ts.users[\"commenter\"],\n\t\t\ts.users[\"editor\"],\n\t\t\ts.users[\"admin\"],\n\t\t}, nil\n\tcase teamID == s.emptyTeam.ID:\n\t\treturn []*model.User{}, nil\n\t}\n\treturn nil, errTestStore\n}\n\nfunc (s *PluginTestStore) SearchUsersByTeam(teamID string, searchQuery string, asGuestID string, excludeBots bool, showEmail, showName bool) ([]*model.User, error) {\n\tusers := []*model.User{}\n\tteamUsers, err := s.GetUsersByTeam(teamID, asGuestID, showEmail, showName)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, user := range teamUsers {\n\t\tif excludeBots && user.IsBot {\n\t\t\tcontinue\n\t\t}\n\t\tif strings.Contains(user.Username, searchQuery) {\n\t\t\tusers = append(users, user)\n\t\t}\n\t}\n\treturn users, nil\n}\n\nfunc (s *PluginTestStore) CanSeeUser(seerID string, seenID string) (bool, error) {\n\tuser, err := s.GetUserByID(seerID)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tif !user.IsGuest {\n\t\treturn true, nil\n\t}\n\tseerMembers, err := s.GetMembersForUser(seerID)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tseenMembers, err := s.GetMembersForUser(seenID)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tfor _, seerMember := range seerMembers {\n\t\tfor _, seenMember := range seenMembers {\n\t\t\tif seerMember.BoardID == seenMember.BoardID {\n\t\t\t\treturn true, nil\n\t\t\t}\n\t\t}\n\t}\n\treturn false, nil\n}\n\nfunc (s *PluginTestStore) SearchUserChannels(teamID, userID, query string) ([]*mmModel.Channel, error) {\n\treturn []*mmModel.Channel{\n\t\t{\n\t\t\tTeamId:      teamID,\n\t\t\tId:          \"valid-channel-id\",\n\t\t\tDisplayName: \"Valid Channel\",\n\t\t\tName:        \"valid-channel\",\n\t\t},\n\t\t{\n\t\t\tTeamId:      teamID,\n\t\t\tId:          \"valid-channel-id-2\",\n\t\t\tDisplayName: \"Valid Channel 2\",\n\t\t\tName:        \"valid-channel-2\",\n\t\t},\n\t}, nil\n}\n\nfunc (s *PluginTestStore) GetChannel(teamID, channel string) (*mmModel.Channel, error) {\n\tif channel == \"valid-channel-id\" {\n\t\treturn &mmModel.Channel{\n\t\t\tTeamId:      teamID,\n\t\t\tId:          \"valid-channel-id\",\n\t\t\tDisplayName: \"Valid Channel\",\n\t\t\tName:        \"valid-channel\",\n\t\t}, nil\n\t} else if channel == \"valid-channel-id-2\" {\n\t\treturn &mmModel.Channel{\n\t\t\tTeamId:      teamID,\n\t\t\tId:          \"valid-channel-id-2\",\n\t\t\tDisplayName: \"Valid Channel 2\",\n\t\t\tName:        \"valid-channel-2\",\n\t\t}, nil\n\t}\n\treturn nil, errTestStore\n}\n\nfunc (s *PluginTestStore) SearchBoardsForUser(term string, field model.BoardSearchField, userID string, includePublicBoards bool) ([]*model.Board, error) {\n\tboards, err := s.Store.SearchBoardsForUser(term, field, userID, includePublicBoards)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tteams, err := s.GetTeamsForUser(userID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tresultBoards := []*model.Board{}\n\tfor _, board := range boards {\n\t\tfor _, team := range teams {\n\t\t\tif team.ID == board.TeamID {\n\t\t\t\tresultBoards = append(resultBoards, board)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\treturn resultBoards, nil\n}\n\nfunc (s *PluginTestStore) GetLicense() *mmModel.License {\n\tlicense := s.Store.GetLicense()\n\n\tif license == nil {\n\t\tlicense = &mmModel.License{\n\t\t\tId:        mmModel.NewId(),\n\t\t\tStartsAt:  mmModel.GetMillis() - 2629746000, // 1 month\n\t\t\tExpiresAt: mmModel.GetMillis() + 2629746000, //\n\t\t\tIssuedAt:  mmModel.GetMillis() - 2629746000,\n\t\t\tFeatures:  &mmModel.Features{},\n\t\t}\n\t\tlicense.Features.SetDefaults()\n\t}\n\n\tcomplianceLicense := os.Getenv(\"FOCALBOARD_UNIT_TESTING_COMPLIANCE\")\n\tif complianceLicense != \"\" {\n\t\tif val, err := strconv.ParseBool(complianceLicense); err == nil {\n\t\t\tlicense.Features.Compliance = mmModel.NewBool(val)\n\t\t}\n\t}\n\n\treturn license\n}\n"
  },
  {
    "path": "server/integrationtests/sharing_test.go",
    "content": "package integrationtests\n\nimport (\n\t\"testing\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestSharing(t *testing.T) {\n\tth := SetupTestHelper(t).InitBasic()\n\tdefer th.TearDown()\n\n\tvar boardID string\n\ttoken := utils.NewID(utils.IDTypeToken)\n\n\tt.Run(\"an unauthenticated client should not be able to get a sharing\", func(t *testing.T) {\n\t\tth.Logout(th.Client)\n\n\t\tsharing, resp := th.Client.GetSharing(\"board-id\")\n\t\tth.CheckUnauthorized(resp)\n\t\trequire.Nil(t, sharing)\n\t})\n\n\tt.Run(\"Check no initial sharing\", func(t *testing.T) {\n\t\tth.Login1()\n\n\t\tteamID := \"0\"\n\t\tnewBoard := &model.Board{\n\t\t\tTeamID: teamID,\n\t\t\tType:   model.BoardTypeOpen,\n\t\t}\n\n\t\tboard, err := th.Server.App().CreateBoard(newBoard, th.GetUser1().ID, true)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, board)\n\t\tboardID = board.ID\n\n\t\ts, err := th.Server.App().GetSharing(boardID)\n\t\trequire.Error(t, err)\n\t\trequire.True(t, model.IsErrNotFound(err))\n\t\trequire.Nil(t, s)\n\n\t\tsharing, resp := th.Client.GetSharing(boardID)\n\t\tth.CheckNotFound(resp)\n\t\trequire.Nil(t, sharing)\n\t})\n\n\tt.Run(\"POST sharing, config = false\", func(t *testing.T) {\n\t\tsharing := model.Sharing{\n\t\t\tID:       boardID,\n\t\t\tToken:    token,\n\t\t\tEnabled:  true,\n\t\t\tUpdateAt: 1,\n\t\t}\n\n\t\t// it will fail with default config\n\t\tsuccess, resp := th.Client.PostSharing(&sharing)\n\t\trequire.False(t, success)\n\t\trequire.Error(t, resp.Error)\n\n\t\tt.Run(\"GET sharing\", func(t *testing.T) {\n\t\t\tsharing, resp := th.Client.GetSharing(boardID)\n\t\t\t// Expect empty sharing object\n\t\t\tth.CheckNotFound(resp)\n\t\t\trequire.Nil(t, sharing)\n\t\t})\n\t})\n\n\tt.Run(\"POST sharing, config = true\", func(t *testing.T) {\n\t\tth.Server.Config().EnablePublicSharedBoards = true\n\t\tsharing := model.Sharing{\n\t\t\tID:       boardID,\n\t\t\tToken:    token,\n\t\t\tEnabled:  true,\n\t\t\tUpdateAt: 1,\n\t\t}\n\n\t\t// it will succeed with updated config\n\t\tsuccess, resp := th.Client.PostSharing(&sharing)\n\t\trequire.True(t, success)\n\t\trequire.NoError(t, resp.Error)\n\n\t\tt.Run(\"GET sharing\", func(t *testing.T) {\n\t\t\tsharing, resp := th.Client.GetSharing(boardID)\n\t\t\trequire.NoError(t, resp.Error)\n\t\t\trequire.NotNil(t, sharing)\n\t\t\trequire.Equal(t, sharing.ID, boardID)\n\t\t\trequire.True(t, sharing.Enabled)\n\t\t\trequire.Equal(t, sharing.Token, token)\n\t\t})\n\t})\n}\n"
  },
  {
    "path": "server/integrationtests/sidebar_test.go",
    "content": "package integrationtests\n\nimport (\n\t\"testing\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestSidebar(t *testing.T) {\n\tth := SetupTestHelperWithToken(t).Start()\n\tdefer th.TearDown()\n\n\t// we'll create a new board.\n\t// The board should end up in a default \"Boards\" category\n\tboard := th.CreateBoard(\"team-id\", \"O\")\n\n\tcategoryBoards := th.GetUserCategoryBoards(\"team-id\")\n\trequire.Equal(t, 1, len(categoryBoards))\n\trequire.Equal(t, \"Boards\", categoryBoards[0].Name)\n\trequire.Equal(t, 1, len(categoryBoards[0].BoardMetadata))\n\trequire.Equal(t, board.ID, categoryBoards[0].BoardMetadata[0].BoardID)\n\n\t// create a new category, a new board\n\t// and move that board into the new category\n\tboard2 := th.CreateBoard(\"team-id\", \"O\")\n\tcategory := th.CreateCategory(model.Category{\n\t\tName:   \"Category 2\",\n\t\tTeamID: \"team-id\",\n\t\tUserID: \"single-user\",\n\t})\n\tth.UpdateCategoryBoard(\"team-id\", category.ID, board2.ID)\n\n\tcategoryBoards = th.GetUserCategoryBoards(\"team-id\")\n\t// now there should be two categories - boards and the one\n\t// we created just now\n\trequire.Equal(t, 2, len(categoryBoards))\n\n\t// the newly created category should be the first one array\n\t// as new categories end up on top in LHS\n\trequire.Equal(t, \"Category 2\", categoryBoards[0].Name)\n\trequire.Equal(t, 1, len(categoryBoards[0].BoardMetadata))\n\trequire.Equal(t, board2.ID, categoryBoards[0].BoardMetadata[0].BoardID)\n\n\t// now we'll delete the custom category we created, \"Category 2\"\n\t// and all it's boards should get moved to the Boards category\n\tth.DeleteCategory(\"team-id\", category.ID)\n\tcategoryBoards = th.GetUserCategoryBoards(\"team-id\")\n\trequire.Equal(t, 1, len(categoryBoards))\n\trequire.Equal(t, \"Boards\", categoryBoards[0].Name)\n\trequire.Equal(t, 2, len(categoryBoards[0].BoardMetadata))\n\trequire.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: board.ID, Hidden: false})\n\trequire.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: board2.ID, Hidden: false})\n}\n\nfunc TestHideUnhideBoard(t *testing.T) {\n\tth := SetupTestHelperWithToken(t).Start()\n\tdefer th.TearDown()\n\n\t// we'll create a new board.\n\t// The board should end up in a default \"Boards\" category\n\tth.CreateBoard(\"team-id\", \"O\")\n\n\t// the created board should not be hidden\n\tcategoryBoards := th.GetUserCategoryBoards(\"team-id\")\n\trequire.Equal(t, 1, len(categoryBoards))\n\trequire.Equal(t, \"Boards\", categoryBoards[0].Name)\n\trequire.Equal(t, 1, len(categoryBoards[0].BoardMetadata))\n\trequire.False(t, categoryBoards[0].BoardMetadata[0].Hidden)\n\n\t// now we'll hide the board\n\tresponse := th.Client.HideBoard(\"team-id\", categoryBoards[0].ID, categoryBoards[0].BoardMetadata[0].BoardID)\n\tth.CheckOK(response)\n\n\t// verifying if the board has been marked as hidden\n\tcategoryBoards = th.GetUserCategoryBoards(\"team-id\")\n\trequire.True(t, categoryBoards[0].BoardMetadata[0].Hidden)\n\n\t// trying to hide the already hidden board.This should have no effect\n\tresponse = th.Client.HideBoard(\"team-id\", categoryBoards[0].ID, categoryBoards[0].BoardMetadata[0].BoardID)\n\tth.CheckOK(response)\n\tcategoryBoards = th.GetUserCategoryBoards(\"team-id\")\n\trequire.True(t, categoryBoards[0].BoardMetadata[0].Hidden)\n\n\t// now we'll unhide the board\n\tresponse = th.Client.UnhideBoard(\"team-id\", categoryBoards[0].ID, categoryBoards[0].BoardMetadata[0].BoardID)\n\tth.CheckOK(response)\n\n\t// verifying\n\tcategoryBoards = th.GetUserCategoryBoards(\"team-id\")\n\trequire.False(t, categoryBoards[0].BoardMetadata[0].Hidden)\n\n\t// trying to unhide the already visible board.This should have no effect\n\tresponse = th.Client.UnhideBoard(\"team-id\", categoryBoards[0].ID, categoryBoards[0].BoardMetadata[0].BoardID)\n\tth.CheckOK(response)\n\tcategoryBoards = th.GetUserCategoryBoards(\"team-id\")\n\trequire.False(t, categoryBoards[0].BoardMetadata[0].Hidden)\n}\n"
  },
  {
    "path": "server/integrationtests/subscriptions_test.go",
    "content": "package integrationtests\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/mattermost/focalboard/server/client\"\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc createTestSubscriptions(client *client.Client, num int) ([]*model.Subscription, string, error) {\n\tnewSubs := make([]*model.Subscription, 0, num)\n\n\tuser, resp := client.GetMe()\n\tif resp.Error != nil {\n\t\treturn nil, \"\", fmt.Errorf(\"cannot get current user: %w\", resp.Error)\n\t}\n\n\tboard := &model.Board{\n\t\tTeamID:   \"0\",\n\t\tType:     model.BoardTypeOpen,\n\t\tCreateAt: 1,\n\t\tUpdateAt: 1,\n\t}\n\tboard, resp = client.CreateBoard(board)\n\tif resp.Error != nil {\n\t\treturn nil, \"\", fmt.Errorf(\"cannot insert test board block: %w\", resp.Error)\n\t}\n\n\tfor n := 0; n < num; n++ {\n\t\tnewBlock := &model.Block{\n\t\t\tID:       utils.NewID(utils.IDTypeCard),\n\t\t\tBoardID:  board.ID,\n\t\t\tCreateAt: 1,\n\t\t\tUpdateAt: 1,\n\t\t\tType:     model.TypeCard,\n\t\t}\n\n\t\tnewBlocks, resp := client.InsertBlocks(board.ID, []*model.Block{newBlock}, false)\n\t\tif resp.Error != nil {\n\t\t\treturn nil, \"\", fmt.Errorf(\"cannot insert test card block: %w\", resp.Error)\n\t\t}\n\t\tnewBlock = newBlocks[0]\n\n\t\tsub := &model.Subscription{\n\t\t\tBlockType:      newBlock.Type,\n\t\t\tBlockID:        newBlock.ID,\n\t\t\tSubscriberType: model.SubTypeUser,\n\t\t\tSubscriberID:   user.ID,\n\t\t}\n\n\t\tsubNew, resp := client.CreateSubscription(sub)\n\t\tif resp.Error != nil {\n\t\t\treturn nil, \"\", resp.Error\n\t\t}\n\t\tnewSubs = append(newSubs, subNew)\n\t}\n\treturn newSubs, user.ID, nil\n}\n\nfunc TestCreateSubscription(t *testing.T) {\n\tth := SetupTestHelper(t).InitBasic()\n\tdefer th.TearDown()\n\n\tt.Run(\"Create valid subscription\", func(t *testing.T) {\n\t\tsubs, userID, err := createTestSubscriptions(th.Client, 5)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, subs, 5)\n\n\t\t// fetch the newly created subscriptions and compare\n\t\tsubsFound, resp := th.Client.GetSubscriptions(userID)\n\t\trequire.NoError(t, resp.Error)\n\t\trequire.Len(t, subsFound, 5)\n\t\tassert.ElementsMatch(t, subs, subsFound)\n\t})\n\n\tt.Run(\"Create invalid subscription\", func(t *testing.T) {\n\t\tuser, resp := th.Client.GetMe()\n\t\trequire.NoError(t, resp.Error)\n\n\t\tsub := &model.Subscription{\n\t\t\tSubscriberID: user.ID,\n\t\t}\n\t\t_, resp = th.Client.CreateSubscription(sub)\n\t\trequire.Error(t, resp.Error)\n\t})\n\n\tt.Run(\"Create subscription for another user\", func(t *testing.T) {\n\t\tsub := &model.Subscription{\n\t\t\tSubscriberID: utils.NewID(utils.IDTypeUser),\n\t\t}\n\t\t_, resp := th.Client.CreateSubscription(sub)\n\t\trequire.Error(t, resp.Error)\n\t})\n}\n\nfunc TestGetSubscriptions(t *testing.T) {\n\tth := SetupTestHelper(t).InitBasic()\n\tdefer th.TearDown()\n\n\tt.Run(\"Get subscriptions for user\", func(t *testing.T) {\n\t\tmySubs, user1ID, err := createTestSubscriptions(th.Client, 5)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, mySubs, 5)\n\n\t\t// create more subscriptions with different user\n\t\totherSubs, _, err := createTestSubscriptions(th.Client2, 10)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, otherSubs, 10)\n\n\t\t// fetch the newly created subscriptions for current user, making sure only\n\t\t// the ones created for the current user are returned.\n\t\tsubsFound, resp := th.Client.GetSubscriptions(user1ID)\n\t\trequire.NoError(t, resp.Error)\n\t\trequire.Len(t, subsFound, 5)\n\t\tassert.ElementsMatch(t, mySubs, subsFound)\n\t})\n}\n\nfunc TestDeleteSubscription(t *testing.T) {\n\tth := SetupTestHelper(t).InitBasic()\n\tdefer th.TearDown()\n\n\tt.Run(\"Delete valid subscription\", func(t *testing.T) {\n\t\tsubs, userID, err := createTestSubscriptions(th.Client, 3)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, subs, 3)\n\n\t\tresp := th.Client.DeleteSubscription(subs[1].BlockID, userID)\n\t\trequire.NoError(t, resp.Error)\n\n\t\t// fetch the subscriptions and ensure the list is correct\n\t\tsubsFound, resp := th.Client.GetSubscriptions(userID)\n\t\trequire.NoError(t, resp.Error)\n\t\trequire.Len(t, subsFound, 2)\n\n\t\tassert.Contains(t, subsFound, subs[0])\n\t\tassert.Contains(t, subsFound, subs[2])\n\t\tassert.NotContains(t, subsFound, subs[1])\n\t})\n\n\tt.Run(\"Delete invalid subscription\", func(t *testing.T) {\n\t\tuser, resp := th.Client.GetMe()\n\t\trequire.NoError(t, resp.Error)\n\n\t\tresp = th.Client.DeleteSubscription(\"bogus\", user.ID)\n\t\trequire.Error(t, resp.Error)\n\t})\n}\n"
  },
  {
    "path": "server/integrationtests/teststore.go",
    "content": "package integrationtests\n\nimport (\n\t\"github.com/mattermost/focalboard/server/services/store\"\n\n\tmmModel \"github.com/mattermost/mattermost/server/public/model\"\n)\n\ntype TestStore struct {\n\tstore.Store\n\tlicense *mmModel.License\n}\n\nfunc NewTestEnterpriseStore(store store.Store) *TestStore {\n\tusersValue := 10000\n\ttrueValue := true\n\tfalseValue := false\n\tlicense := &mmModel.License{\n\t\tFeatures: &mmModel.Features{\n\t\t\tUsers:                     &usersValue,\n\t\t\tLDAP:                      &trueValue,\n\t\t\tLDAPGroups:                &trueValue,\n\t\t\tMFA:                       &trueValue,\n\t\t\tGoogleOAuth:               &trueValue,\n\t\t\tOffice365OAuth:            &trueValue,\n\t\t\tOpenId:                    &trueValue,\n\t\t\tCompliance:                &trueValue,\n\t\t\tCluster:                   &trueValue,\n\t\t\tMetrics:                   &trueValue,\n\t\t\tMHPNS:                     &trueValue,\n\t\t\tSAML:                      &trueValue,\n\t\t\tElasticsearch:             &trueValue,\n\t\t\tAnnouncement:              &trueValue,\n\t\t\tThemeManagement:           &trueValue,\n\t\t\tEmailNotificationContents: &trueValue,\n\t\t\tDataRetention:             &trueValue,\n\t\t\tMessageExport:             &trueValue,\n\t\t\tCustomPermissionsSchemes:  &trueValue,\n\t\t\tCustomTermsOfService:      &trueValue,\n\t\t\tGuestAccounts:             &trueValue,\n\t\t\tGuestAccountsPermissions:  &trueValue,\n\t\t\tIDLoadedPushNotifications: &trueValue,\n\t\t\tLockTeammateNameDisplay:   &trueValue,\n\t\t\tEnterprisePlugins:         &trueValue,\n\t\t\tAdvancedLogging:           &trueValue,\n\t\t\tCloud:                     &falseValue,\n\t\t\tSharedChannels:            &trueValue,\n\t\t\tRemoteClusterService:      &trueValue,\n\t\t\tFutureFeatures:            &trueValue,\n\t\t},\n\t}\n\n\ttestStore := &TestStore{\n\t\tStore:   store,\n\t\tlicense: license,\n\t}\n\n\treturn testStore\n}\n\nfunc NewTestProfessionalStore(store store.Store) *TestStore {\n\tusersValue := 10000\n\ttrueValue := true\n\tfalseValue := false\n\tlicense := &mmModel.License{\n\t\tFeatures: &mmModel.Features{\n\t\t\tUsers:                     &usersValue,\n\t\t\tLDAP:                      &falseValue,\n\t\t\tLDAPGroups:                &falseValue,\n\t\t\tMFA:                       &trueValue,\n\t\t\tGoogleOAuth:               &trueValue,\n\t\t\tOffice365OAuth:            &trueValue,\n\t\t\tOpenId:                    &trueValue,\n\t\t\tCompliance:                &falseValue,\n\t\t\tCluster:                   &falseValue,\n\t\t\tMetrics:                   &trueValue,\n\t\t\tMHPNS:                     &trueValue,\n\t\t\tSAML:                      &trueValue,\n\t\t\tElasticsearch:             &trueValue,\n\t\t\tAnnouncement:              &trueValue,\n\t\t\tThemeManagement:           &trueValue,\n\t\t\tEmailNotificationContents: &trueValue,\n\t\t\tDataRetention:             &trueValue,\n\t\t\tMessageExport:             &trueValue,\n\t\t\tCustomPermissionsSchemes:  &trueValue,\n\t\t\tCustomTermsOfService:      &trueValue,\n\t\t\tGuestAccounts:             &trueValue,\n\t\t\tGuestAccountsPermissions:  &trueValue,\n\t\t\tIDLoadedPushNotifications: &trueValue,\n\t\t\tLockTeammateNameDisplay:   &trueValue,\n\t\t\tEnterprisePlugins:         &falseValue,\n\t\t\tAdvancedLogging:           &trueValue,\n\t\t\tCloud:                     &falseValue,\n\t\t\tSharedChannels:            &trueValue,\n\t\t\tRemoteClusterService:      &falseValue,\n\t\t\tFutureFeatures:            &trueValue,\n\t\t},\n\t}\n\n\ttestStore := &TestStore{\n\t\tStore:   store,\n\t\tlicense: license,\n\t}\n\n\treturn testStore\n}\n\nfunc (s *TestStore) GetLicense() *mmModel.License {\n\treturn s.license\n}\n"
  },
  {
    "path": "server/integrationtests/user_test.go",
    "content": "package integrationtests\n\nimport (\n\t\"bytes\"\n\t\"crypto/rand\"\n\t\"testing\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nconst (\n\tfakeUsername = \"fakeUsername\"\n\tfakeEmail    = \"mock@test.com\"\n)\n\nfunc TestUserRegister(t *testing.T) {\n\tth := SetupTestHelper(t).Start()\n\tdefer th.TearDown()\n\n\t// register\n\tregisterRequest := &model.RegisterRequest{\n\t\tUsername: fakeUsername,\n\t\tEmail:    fakeEmail,\n\t\tPassword: utils.NewID(utils.IDTypeNone),\n\t}\n\tsuccess, resp := th.Client.Register(registerRequest)\n\trequire.NoError(t, resp.Error)\n\trequire.True(t, success)\n\n\t// register again will fail\n\tsuccess, resp = th.Client.Register(registerRequest)\n\trequire.Error(t, resp.Error)\n\trequire.False(t, success)\n}\n\nfunc TestUserLogin(t *testing.T) {\n\tth := SetupTestHelper(t).Start()\n\tdefer th.TearDown()\n\n\tt.Run(\"with nonexist user\", func(t *testing.T) {\n\t\tloginRequest := &model.LoginRequest{\n\t\t\tType:     \"normal\",\n\t\t\tUsername: \"nonexistuser\",\n\t\t\tEmail:    \"\",\n\t\t\tPassword: utils.NewID(utils.IDTypeNone),\n\t\t}\n\t\tdata, resp := th.Client.Login(loginRequest)\n\t\trequire.Error(t, resp.Error)\n\t\trequire.Nil(t, data)\n\t})\n\n\tt.Run(\"with registered user\", func(t *testing.T) {\n\t\tpassword := utils.NewID(utils.IDTypeNone)\n\t\t// register\n\t\tregisterRequest := &model.RegisterRequest{\n\t\t\tUsername: fakeUsername,\n\t\t\tEmail:    fakeEmail,\n\t\t\tPassword: password,\n\t\t}\n\t\tsuccess, resp := th.Client.Register(registerRequest)\n\t\trequire.NoError(t, resp.Error)\n\t\trequire.True(t, success)\n\n\t\t// login\n\t\tloginRequest := &model.LoginRequest{\n\t\t\tType:     \"normal\",\n\t\t\tUsername: fakeUsername,\n\t\t\tEmail:    fakeEmail,\n\t\t\tPassword: password,\n\t\t}\n\t\tdata, resp := th.Client.Login(loginRequest)\n\t\trequire.NoError(t, resp.Error)\n\t\trequire.NotNil(t, data)\n\t\trequire.NotNil(t, data.Token)\n\t})\n}\n\nfunc TestGetMe(t *testing.T) {\n\tth := SetupTestHelper(t).Start()\n\tdefer th.TearDown()\n\n\tt.Run(\"not login yet\", func(t *testing.T) {\n\t\tme, resp := th.Client.GetMe()\n\t\trequire.Error(t, resp.Error)\n\t\trequire.Nil(t, me)\n\t})\n\n\tt.Run(\"logged in\", func(t *testing.T) {\n\t\t// register\n\t\tpassword := utils.NewID(utils.IDTypeNone)\n\t\tregisterRequest := &model.RegisterRequest{\n\t\t\tUsername: fakeUsername,\n\t\t\tEmail:    fakeEmail,\n\t\t\tPassword: password,\n\t\t}\n\t\tsuccess, resp := th.Client.Register(registerRequest)\n\t\trequire.NoError(t, resp.Error)\n\t\trequire.True(t, success)\n\t\t// login\n\t\tloginRequest := &model.LoginRequest{\n\t\t\tType:     \"normal\",\n\t\t\tUsername: fakeUsername,\n\t\t\tEmail:    fakeEmail,\n\t\t\tPassword: password,\n\t\t}\n\t\tdata, resp := th.Client.Login(loginRequest)\n\t\trequire.NoError(t, resp.Error)\n\t\trequire.NotNil(t, data)\n\t\trequire.NotNil(t, data.Token)\n\n\t\t// get user me\n\t\tme, resp := th.Client.GetMe()\n\t\trequire.NoError(t, resp.Error)\n\t\trequire.NotNil(t, me)\n\t\trequire.Equal(t, \"\", me.Email)\n\t\trequire.Equal(t, registerRequest.Username, me.Username)\n\t})\n}\n\nfunc TestGetUser(t *testing.T) {\n\tth := SetupTestHelper(t).Start()\n\tdefer th.TearDown()\n\n\t// register\n\tpassword := utils.NewID(utils.IDTypeNone)\n\tregisterRequest := &model.RegisterRequest{\n\t\tUsername: fakeUsername,\n\t\tEmail:    fakeEmail,\n\t\tPassword: password,\n\t}\n\tsuccess, resp := th.Client.Register(registerRequest)\n\trequire.NoError(t, resp.Error)\n\trequire.True(t, success)\n\t// login\n\tloginRequest := &model.LoginRequest{\n\t\tType:     \"normal\",\n\t\tUsername: fakeUsername,\n\t\tEmail:    fakeEmail,\n\t\tPassword: password,\n\t}\n\tdata, resp := th.Client.Login(loginRequest)\n\trequire.NoError(t, resp.Error)\n\trequire.NotNil(t, data)\n\trequire.NotNil(t, data.Token)\n\n\tme, resp := th.Client.GetMe()\n\trequire.NoError(t, resp.Error)\n\trequire.NotNil(t, me)\n\n\tt.Run(\"me's id\", func(t *testing.T) {\n\t\tuser, resp := th.Client.GetUser(me.ID)\n\t\trequire.NoError(t, resp.Error)\n\t\trequire.NotNil(t, user)\n\t\trequire.Equal(t, me.ID, user.ID)\n\t\trequire.Equal(t, me.Username, user.Username)\n\t})\n\n\tt.Run(\"nonexist user\", func(t *testing.T) {\n\t\tuser, resp := th.Client.GetUser(\"nonexistid\")\n\t\trequire.Error(t, resp.Error)\n\t\trequire.Nil(t, user)\n\t})\n}\n\nfunc TestGetUserList(t *testing.T) {\n\tth := SetupTestHelperPluginMode(t)\n\tdefer th.TearDown()\n\tclients := setupClients(th)\n\tth.Client = clients.TeamMember\n\tth.Client2 = clients.Editor\n\n\tme, resp := th.Client.GetMe()\n\trequire.NoError(t, resp.Error)\n\trequire.NotNil(t, me)\n\n\tuserID1 := me.ID\n\tuserID2 := th.GetUser2().ID\n\n\t// Admin user should return both\n\treturnUsers, resp := clients.Admin.GetUserList([]string{userID1, userID2})\n\trequire.NoError(t, resp.Error)\n\trequire.NotNil(t, returnUsers)\n\trequire.Equal(t, 2, len(returnUsers))\n\n\t// Guest user should return none\n\treturnUsers2, resp := clients.Guest.GetUserList([]string{userID1, userID2})\n\trequire.NoError(t, resp.Error)\n\trequire.NotNil(t, returnUsers2)\n\trequire.Equal(t, 0, len(returnUsers2))\n\n\tnewBoard := &model.Board{\n\t\tTitle:  \"title\",\n\t\tType:   model.BoardTypeOpen,\n\t\tTeamID: testTeamID,\n\t}\n\tboard, err := th.Server.App().CreateBoard(newBoard, userID1, true)\n\trequire.NoError(t, err)\n\n\t// add Guest as board member\n\tnewGuestMember := &model.BoardMember{\n\t\tUserID:          userGuestID,\n\t\tBoardID:         board.ID,\n\t\tSchemeViewer:    true,\n\t\tSchemeCommenter: true,\n\t\tSchemeEditor:    true,\n\t\tSchemeAdmin:     false,\n\t}\n\tguestMember, err := th.Server.App().AddMemberToBoard(newGuestMember)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, guestMember)\n\n\t// Guest user should now return one of members\n\tguestUsers, resp := clients.Guest.GetUserList([]string{th.GetUser1().ID, th.GetUser2().ID})\n\trequire.NoError(t, resp.Error)\n\trequire.NotNil(t, guestUsers)\n\trequire.Equal(t, 1, len(guestUsers))\n\n\t// add other user as board member\n\tnewBoardMember := &model.BoardMember{\n\t\tUserID:          userID2,\n\t\tBoardID:         board.ID,\n\t\tSchemeViewer:    true,\n\t\tSchemeCommenter: true,\n\t\tSchemeEditor:    true,\n\t\tSchemeAdmin:     false,\n\t}\n\tnewMember, err := th.Server.App().AddMemberToBoard(newBoardMember)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, newMember)\n\n\t// Guest user should now return both\n\tguestUsers, resp = clients.Guest.GetUserList([]string{th.GetUser1().ID, th.GetUser2().ID})\n\trequire.NoError(t, resp.Error)\n\trequire.NotNil(t, guestUsers)\n\trequire.Equal(t, 2, len(guestUsers))\n}\n\nfunc TestUserChangePassword(t *testing.T) {\n\tth := SetupTestHelper(t).Start()\n\tdefer th.TearDown()\n\n\t// register\n\tpassword := utils.NewID(utils.IDTypeNone)\n\tregisterRequest := &model.RegisterRequest{\n\t\tUsername: fakeUsername,\n\t\tEmail:    fakeEmail,\n\t\tPassword: password,\n\t}\n\tsuccess, resp := th.Client.Register(registerRequest)\n\trequire.NoError(t, resp.Error)\n\trequire.True(t, success)\n\t// login\n\tloginRequest := &model.LoginRequest{\n\t\tType:     \"normal\",\n\t\tUsername: fakeUsername,\n\t\tEmail:    fakeEmail,\n\t\tPassword: password,\n\t}\n\tdata, resp := th.Client.Login(loginRequest)\n\trequire.NoError(t, resp.Error)\n\trequire.NotNil(t, data)\n\trequire.NotNil(t, data.Token)\n\n\toriginalMe, resp := th.Client.GetMe()\n\trequire.NoError(t, resp.Error)\n\trequire.NotNil(t, originalMe)\n\n\t// change password\n\tsuccess, resp = th.Client.UserChangePassword(originalMe.ID, &model.ChangePasswordRequest{\n\t\tOldPassword: password,\n\t\tNewPassword: utils.NewID(utils.IDTypeNone),\n\t})\n\trequire.NoError(t, resp.Error)\n\trequire.True(t, success)\n}\n\nfunc randomBytes(t *testing.T, n int) []byte {\n\tbb := make([]byte, n)\n\t_, err := rand.Read(bb)\n\trequire.NoError(t, err)\n\treturn bb\n}\n\nfunc TestTeamUploadFile(t *testing.T) {\n\tt.Run(\"no permission\", func(t *testing.T) { // native auth, but not login\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tteamID := \"0\"\n\t\tboardID := utils.NewID(utils.IDTypeBoard)\n\t\tdata := randomBytes(t, 1024)\n\t\tresult, resp := th.Client.TeamUploadFile(teamID, boardID, bytes.NewReader(data))\n\t\trequire.Error(t, resp.Error)\n\t\trequire.Nil(t, result)\n\t})\n\n\tt.Run(\"a board admin should be able to update a file\", func(t *testing.T) { // single token auth\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tteamID := \"0\"\n\t\tnewBoard := &model.Board{\n\t\t\tType:   model.BoardTypeOpen,\n\t\t\tTeamID: teamID,\n\t\t}\n\t\tboard, resp := th.Client.CreateBoard(newBoard)\n\t\tth.CheckOK(resp)\n\t\trequire.NotNil(t, board)\n\n\t\tdata := randomBytes(t, 1024)\n\t\tresult, resp := th.Client.TeamUploadFile(teamID, board.ID, bytes.NewReader(data))\n\t\tth.CheckOK(resp)\n\t\trequire.NotNil(t, result)\n\t\trequire.NotEmpty(t, result.FileID)\n\t\t// TODO get the uploaded file\n\t})\n\n\tt.Run(\"user that doesn't belong to the board should not be able to upload a file\", func(t *testing.T) {\n\t\tth := SetupTestHelper(t).InitBasic()\n\t\tdefer th.TearDown()\n\n\t\tteamID := \"0\"\n\t\tnewBoard := &model.Board{\n\t\t\tType:   model.BoardTypeOpen,\n\t\t\tTeamID: teamID,\n\t\t}\n\t\tboard, resp := th.Client.CreateBoard(newBoard)\n\t\tth.CheckOK(resp)\n\t\trequire.NotNil(t, board)\n\n\t\tdata := randomBytes(t, 1024)\n\n\t\t// a user that doesn't belong to the board tries to upload the file\n\t\tresult, resp := th.Client2.TeamUploadFile(teamID, board.ID, bytes.NewReader(data))\n\t\tth.CheckForbidden(resp)\n\t\trequire.Nil(t, result)\n\t})\n}\n"
  },
  {
    "path": "server/integrationtests/work_template_test.go",
    "content": "package integrationtests\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\n// This test is there to guarantee that the board templates needed for\n// the work template are present in the default templates.\n// If this fails, you might need to sync with the channels team.\nfunc TestGetTemplatesForWorkTemplate(t *testing.T) {\n\t// map[name]trackingTemplateId\n\tknownInWorkTemplates := map[string]string{\n\t\t\"Company Goals & OKRs\":   \"7ba22ccfdfac391d63dea5c4b8cde0de\",\n\t\t\"Competitive Analysis\":   \"06f4bff367a7c2126fab2380c9dec23c\",\n\t\t\"Content Calendar\":       \"c75fbd659d2258b5183af2236d176ab4\",\n\t\t\"Meeting Agenda \":        \"54fcf9c610f0ac5e4c522c0657c90602\",\n\t\t\"Personal Goals \":        \"7f32dc8d2ae008cfe56554e9363505cc\",\n\t\t\"Personal Tasls \":        \"dfb70c146a4584b8a21837477c7b5431\",\n\t\t\"Project Tasks \":         \"a4ec399ab4f2088b1051c3cdf1dde4c3\",\n\t\t\"Roadmap \":               \"b728c6ca730e2cfc229741c5a4712b65\",\n\t\t\"Sales Pipeline CRM\":     \"ecc250bb7dff0fe02247f1110f097544\",\n\t\t\"Sprint Planner \":        \"99b74e26d2f5d0a9b346d43c0a7bfb09\",\n\t\t\"Team Retrospective\":     \"e4f03181c4ced8edd4d53d33d569a086\",\n\t\t\"User Research Sessions\": \"6c345c7f50f6833f78b7d0f08ce450a3\",\n\t}\n\tth := SetupTestHelper(t).InitBasic()\n\tdefer th.TearDown()\n\n\terr := th.Server.App().InitTemplates()\n\trequire.NoError(t, err, \"InitTemplates should not fail\")\n\n\trBoards, resp := th.Client.GetTemplatesForTeam(\"0\")\n\tth.CheckOK(resp)\n\trequire.NotNil(t, rBoards)\n\n\ttrackingTemplateIDs := []string{}\n\tfor _, board := range rBoards {\n\t\tproperty, _ := board.GetPropertyString(\"trackingTemplateId\")\n\t\tif property != \"\" {\n\t\t\ttrackingTemplateIDs = append(trackingTemplateIDs, property)\n\t\t}\n\t}\n\n\t// make sure all known templates are in trackingTemplateIds\n\tfor name, ttID := range knownInWorkTemplates {\n\t\tfound := false\n\t\tfor _, trackingTemplateID := range trackingTemplateIDs {\n\t\t\tif trackingTemplateID == ttID {\n\t\t\t\tfound = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\trequire.True(t, found, \"trackingTemplateId %s for %s not found\", ttID, name)\n\t}\n}\n"
  },
  {
    "path": "server/main/doc.go",
    "content": "// Package classification Focalboard Server\n//\n// Focalboard Server\n//\n//\tSchemes: http, https\n//\tHost: localhost\n//\tBasePath: /api/v2\n//\tVersion: 2.0.0\n//\tLicense: Custom https://github.com/mattermost/focalboard/blob/main/LICENSE.txt\n//\tContact: Focalboard<api@focalboard.com> https://www.focalboard.com\n//\n//\tConsumes:\n//\t- application/json\n//\n//\tProduces:\n//\t- application/json\n//\n//\tsecurityDefinitions:\n//\t  BearerAuth:\n//\t    type: apiKey\n//\t    name: Authorization\n//\t    in: header\n//\t    description: 'Pass session token using Bearer authentication, e.g. set header \"Authorization: Bearer <session token>\"'\n//\n// swagger:meta\npackage main\n"
  },
  {
    "path": "server/main/main.go",
    "content": "// Server for Focalboard\npackage main\n\nimport (\n\t\"C\"\n\t\"flag\"\n\t\"log\"\n\t\"os\"\n\t\"os/signal\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/server\"\n\t\"github.com/mattermost/focalboard/server/services/config\"\n\t\"github.com/mattermost/focalboard/server/services/permissions/localpermissions\"\n)\nimport (\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\n// Active server used with shared code (dll)\nvar pServer *server.Server\n\nconst (\n\ttimeBetweenPidMonitoringChecks = 2 * time.Second\n)\n\nfunc isProcessRunning(pid int) bool {\n\tprocess, err := os.FindProcess(pid)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\terr = process.Signal(syscall.Signal(0))\n\n\treturn err == nil\n}\n\n// monitorPid is used to keep the server lifetime in sync with another (client app) process\nfunc monitorPid(pid int, logger *mlog.Logger) {\n\tlogger.Info(\"Monitoring PID\", mlog.Int(\"pid\", pid))\n\n\tgo func() {\n\t\tfor {\n\t\t\tif !isProcessRunning(pid) {\n\t\t\t\tlogger.Info(\"Monitored process not found, exiting.\")\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\n\t\t\ttime.Sleep(timeBetweenPidMonitoringChecks)\n\t\t}\n\t}()\n}\n\nfunc main() {\n\t// Command line args\n\tpMonitorPid := flag.Int(\"monitorpid\", -1, \"a process ID\")\n\tpPort := flag.Int(\"port\", 0, \"the port number\")\n\tpSingleUser := flag.Bool(\"single-user\", false, \"single user mode\")\n\tpDBType := flag.String(\"dbtype\", \"\", \"Database type\")\n\tpDBConfig := flag.String(\"dbconfig\", \"\", \"Database config\")\n\tpConfigFilePath := flag.String(\n\t\t\"config\",\n\t\t\"\",\n\t\t\"Location of the JSON config file\",\n\t)\n\tflag.Parse()\n\n\tconfig, err := config.ReadConfigFile(*pConfigFilePath)\n\tif err != nil {\n\t\tlog.Fatal(\"Unable to read the config file: \", err)\n\t\treturn\n\t}\n\n\tlogger, _ := mlog.NewLogger()\n\tcfgJSON := config.LoggingCfgJSON\n\tif config.LoggingCfgFile == \"\" && cfgJSON == \"\" {\n\t\t// if no logging defined, use default config (console output)\n\t\tcfgJSON = defaultLoggingConfig()\n\t}\n\terr = logger.Configure(config.LoggingCfgFile, cfgJSON, nil)\n\tif err != nil {\n\t\tlog.Fatal(\"Error in config file for logger: \", err)\n\t\treturn\n\t}\n\tdefer func() { _ = logger.Shutdown() }()\n\n\tif logger.HasTargets() {\n\t\trestore := logger.RedirectStdLog(mlog.LvlInfo, mlog.String(\"src\", \"stdlog\"))\n\t\tdefer restore()\n\t}\n\n\tmodel.LogServerInfo(logger)\n\n\tsingleUser := false\n\tif pSingleUser != nil {\n\t\tsingleUser = *pSingleUser\n\t}\n\n\tsingleUserToken := \"\"\n\tif singleUser {\n\t\tsingleUserToken = os.Getenv(\"FOCALBOARD_SINGLE_USER_TOKEN\")\n\t\tif len(singleUserToken) < 1 {\n\t\t\tlogger.Fatal(\"The FOCALBOARD_SINGLE_USER_TOKEN environment variable must be set for single user mode \")\n\t\t\treturn\n\t\t}\n\t\tlogger.Info(\"Single user mode\")\n\t}\n\n\tif pMonitorPid != nil && *pMonitorPid > 0 {\n\t\tmonitorPid(*pMonitorPid, logger)\n\t}\n\n\t// Override config from commandline\n\n\tif pDBType != nil && len(*pDBType) > 0 {\n\t\tconfig.DBType = *pDBType\n\t\tlogger.Info(\"DBType from commandline\", mlog.String(\"DBType\", *pDBType))\n\t}\n\n\tif pDBConfig != nil && len(*pDBConfig) > 0 {\n\t\tconfig.DBConfigString = *pDBConfig\n\t\t// Don't echo, as the confix string may contain passwords\n\t\tlogger.Info(\"DBConfigString overridden from commandline\")\n\t}\n\n\tif pPort != nil && *pPort > 0 && *pPort != config.Port {\n\t\t// Override port\n\t\tlogger.Info(\"Port from commandline\", mlog.Int(\"port\", *pPort))\n\t\tconfig.Port = *pPort\n\t}\n\n\tdb, err := server.NewStore(config, singleUser, logger)\n\tif err != nil {\n\t\tlogger.Fatal(\"server.NewStore ERROR\", mlog.Err(err))\n\t}\n\n\tpermissionsService := localpermissions.New(db, logger)\n\n\tparams := server.Params{\n\t\tCfg:                config,\n\t\tSingleUserToken:    singleUserToken,\n\t\tDBStore:            db,\n\t\tLogger:             logger,\n\t\tPermissionsService: permissionsService,\n\t}\n\n\tserver, err := server.New(params)\n\tif err != nil {\n\t\tlogger.Fatal(\"server.New ERROR\", mlog.Err(err))\n\t}\n\n\tif err := server.Start(); err != nil {\n\t\tlogger.Fatal(\"server.Start ERROR\", mlog.Err(err))\n\t}\n\n\t// Setting up signal capturing\n\tstop := make(chan os.Signal, 1)\n\tsignal.Notify(stop, os.Interrupt)\n\n\t// Waiting for SIGINT (pkill -2)\n\t<-stop\n\n\t_ = server.Shutdown()\n}\n\n// StartServer starts the server\n//\n//export StartServer\nfunc StartServer(webPath *C.char, filesPath *C.char, port int, singleUserToken, dbConfigString, configFilePath *C.char) {\n\tstartServer(\n\t\tC.GoString(webPath),\n\t\tC.GoString(filesPath),\n\t\tport,\n\t\tC.GoString(singleUserToken),\n\t\tC.GoString(dbConfigString),\n\t\tC.GoString(configFilePath),\n\t)\n}\n\n// StopServer stops the server\n//\n//export StopServer\nfunc StopServer() {\n\tstopServer()\n}\n\nfunc startServer(webPath string, filesPath string, port int, singleUserToken, dbConfigString, configFilePath string) {\n\tif pServer != nil {\n\t\tstopServer()\n\t\tpServer = nil\n\t}\n\n\t// config.json file\n\tconfig, err := config.ReadConfigFile(configFilePath)\n\tif err != nil {\n\t\tlog.Fatal(\"Unable to read the config file: \", err)\n\t\treturn\n\t}\n\n\tlogger, _ := mlog.NewLogger()\n\terr = logger.Configure(config.LoggingCfgFile, config.LoggingCfgJSON, nil)\n\tif err != nil {\n\t\tlog.Fatal(\"Error in config file for logger: \", err)\n\t\treturn\n\t}\n\n\tmodel.LogServerInfo(logger)\n\n\tif len(filesPath) > 0 {\n\t\tconfig.FilesPath = filesPath\n\t}\n\n\tif len(webPath) > 0 {\n\t\tconfig.WebPath = webPath\n\t}\n\n\tif port > 0 {\n\t\tconfig.Port = port\n\t}\n\n\tif len(dbConfigString) > 0 {\n\t\tconfig.DBConfigString = dbConfigString\n\t}\n\n\tsingleUser := len(singleUserToken) > 0\n\tdb, err := server.NewStore(config, singleUser, logger)\n\tif err != nil {\n\t\tlogger.Fatal(\"server.NewStore ERROR\", mlog.Err(err))\n\t}\n\n\tpermissionsService := localpermissions.New(db, logger)\n\n\tparams := server.Params{\n\t\tCfg:                config,\n\t\tSingleUserToken:    singleUserToken,\n\t\tDBStore:            db,\n\t\tLogger:             logger,\n\t\tPermissionsService: permissionsService,\n\t}\n\n\tpServer, err = server.New(params)\n\tif err != nil {\n\t\tlogger.Fatal(\"server.New ERROR\", mlog.Err(err))\n\t}\n\n\tif err := pServer.Start(); err != nil {\n\t\tlogger.Fatal(\"server.Start ERROR\", mlog.Err(err))\n\t}\n}\n\nfunc stopServer() {\n\tif pServer == nil {\n\t\treturn\n\t}\n\n\tlogger := pServer.Logger()\n\n\terr := pServer.Shutdown()\n\tif err != nil {\n\t\tlogger.Error(\"server.Shutdown ERROR\", mlog.Err(err))\n\t}\n\n\tif l, ok := logger.(*mlog.Logger); ok {\n\t\t_ = l.Shutdown()\n\t}\n\tpServer = nil\n}\n\nfunc defaultLoggingConfig() string {\n\treturn `\n\t{\n\t\t\"def\": {\n\t\t\t\"type\": \"console\",\n\t\t\t\"options\": {\n\t\t\t\t\"out\": \"stdout\"\n\t\t\t},\n\t\t\t\"format\": \"plain\",\n\t\t\t\"format_options\": {\n\t\t\t\t\"delim\": \" \",\n\t\t\t\t\"min_level_len\": 5,\n\t\t\t\t\"min_msg_len\": 40,\n\t\t\t\t\"enable_color\": true,\n\t\t\t\t\"enable_caller\": true\n\t\t\t},\n\t\t\t\"levels\": [\n\t\t\t\t{\"id\": 5, \"name\": \"debug\"},\n\t\t\t\t{\"id\": 4, \"name\": \"info\", \"color\": 36},\n\t\t\t\t{\"id\": 3, \"name\": \"warn\"},\n\t\t\t\t{\"id\": 2, \"name\": \"error\", \"color\": 31},\n\t\t\t\t{\"id\": 1, \"name\": \"fatal\", \"stacktrace\": true},\n\t\t\t\t{\"id\": 0, \"name\": \"panic\", \"stacktrace\": true}\n\t\t\t]\n\t\t}\n\t}`\n}\n"
  },
  {
    "path": "server/model/auth.go",
    "content": "package model\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/mattermost/focalboard/server/services/auth\"\n)\n\nconst (\n\tMinimumPasswordLength = 8\n)\n\nfunc NewErrAuthParam(msg string) *ErrAuthParam {\n\treturn &ErrAuthParam{\n\t\tmsg: msg,\n\t}\n}\n\ntype ErrAuthParam struct {\n\tmsg string\n}\n\nfunc (pe *ErrAuthParam) Error() string {\n\treturn pe.msg\n}\n\n// LoginRequest is a login request\n// swagger:model\ntype LoginRequest struct {\n\t// Type of login, currently must be set to \"normal\"\n\t// required: true\n\tType string `json:\"type\"`\n\n\t// If specified, login using username\n\t// required: false\n\tUsername string `json:\"username\"`\n\n\t// If specified, login using email\n\t// required: false\n\tEmail string `json:\"email\"`\n\n\t// Password\n\t// required: true\n\tPassword string `json:\"password\"`\n\n\t// MFA token\n\t// required: false\n\t// swagger:ignore\n\tMfaToken string `json:\"mfa_token\"`\n}\n\n// LoginResponse is a login response\n// swagger:model\ntype LoginResponse struct {\n\t// Session token\n\t// required: true\n\tToken string `json:\"token\"`\n}\n\nfunc LoginResponseFromJSON(data io.Reader) (*LoginResponse, error) {\n\tvar resp LoginResponse\n\tif err := json.NewDecoder(data).Decode(&resp); err != nil {\n\t\treturn nil, err\n\t}\n\treturn &resp, nil\n}\n\n// RegisterRequest is a user registration request\n// swagger:model\ntype RegisterRequest struct {\n\t// User name\n\t// required: true\n\tUsername string `json:\"username\"`\n\n\t// User's email\n\t// required: true\n\tEmail string `json:\"email\"`\n\n\t// Password\n\t// required: true\n\tPassword string `json:\"password\"`\n\n\t// Registration authorization token\n\t// required: true\n\tToken string `json:\"token\"`\n}\n\nfunc (rd *RegisterRequest) IsValid() error {\n\tif strings.TrimSpace(rd.Username) == \"\" {\n\t\treturn NewErrAuthParam(\"username is required\")\n\t}\n\tif strings.TrimSpace(rd.Email) == \"\" {\n\t\treturn NewErrAuthParam(\"email is required\")\n\t}\n\tif !auth.IsEmailValid(rd.Email) {\n\t\treturn NewErrAuthParam(\"invalid email format\")\n\t}\n\tif rd.Password == \"\" {\n\t\treturn NewErrAuthParam(\"password is required\")\n\t}\n\treturn isValidPassword(rd.Password)\n}\n\n// ChangePasswordRequest is a user password change request\n// swagger:model\ntype ChangePasswordRequest struct {\n\t// Old password\n\t// required: true\n\tOldPassword string `json:\"oldPassword\"`\n\n\t// New password\n\t// required: true\n\tNewPassword string `json:\"newPassword\"`\n}\n\n// IsValid validates a password change request.\nfunc (rd *ChangePasswordRequest) IsValid() error {\n\tif rd.OldPassword == \"\" {\n\t\treturn NewErrAuthParam(\"old password is required\")\n\t}\n\tif rd.NewPassword == \"\" {\n\t\treturn NewErrAuthParam(\"new password is required\")\n\t}\n\treturn isValidPassword(rd.NewPassword)\n}\n\nfunc isValidPassword(password string) error {\n\tif len(password) < MinimumPasswordLength {\n\t\treturn NewErrAuthParam(fmt.Sprintf(\"password must be at least %d characters\", MinimumPasswordLength))\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "server/model/block.go",
    "content": "package model\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"io\"\n\t\"strconv\"\n\t\"unicode/utf8\"\n\n\t\"github.com/mattermost/focalboard/server/services/audit\"\n)\n\nconst (\n\tBlockTitleMaxBytes  = 65535                  // Maximum size of a TEXT column in MySQL\n\tBlockTitleMaxRunes  = BlockTitleMaxBytes / 4 // Assume a worst-case representation\n\tBlockFieldsMaxRunes = 800000\n)\n\nvar (\n\tErrBlockEmptyBoardID            = errors.New(\"boardID is empty\")\n\tErrBlockTitleSizeLimitExceeded  = errors.New(\"block title size limit exceeded\")\n\tErrBlockFieldsSizeLimitExceeded = errors.New(\"block fields size limit exceeded\")\n)\n\n// Block is the basic data unit\n// swagger:model\ntype Block struct {\n\t// The id for this block\n\t// required: true\n\tID string `json:\"id\"`\n\n\t// The id for this block's parent block. Empty for root blocks\n\t// required: false\n\tParentID string `json:\"parentId\"`\n\n\t// The id for user who created this block\n\t// required: true\n\tCreatedBy string `json:\"createdBy\"`\n\n\t// The id for user who last modified this block\n\t// required: true\n\tModifiedBy string `json:\"modifiedBy\"`\n\n\t// The schema version of this block\n\t// required: true\n\tSchema int64 `json:\"schema\"`\n\n\t// The block type\n\t// required: true\n\tType BlockType `json:\"type\"`\n\n\t// The display title\n\t// required: false\n\tTitle string `json:\"title\"`\n\n\t// The block fields\n\t// required: false\n\tFields map[string]interface{} `json:\"fields\"`\n\n\t// The creation time in miliseconds since the current epoch\n\t// required: true\n\tCreateAt int64 `json:\"createAt\"`\n\n\t// The last modified time in miliseconds since the current epoch\n\t// required: true\n\tUpdateAt int64 `json:\"updateAt\"`\n\n\t// The deleted time in miliseconds since the current epoch. Set to indicate this block is deleted\n\t// required: false\n\tDeleteAt int64 `json:\"deleteAt\"`\n\n\t// Deprecated. The workspace id that the block belongs to\n\t// required: false\n\tWorkspaceID string `json:\"-\"`\n\n\t// The board id that the block belongs to\n\t// required: true\n\tBoardID string `json:\"boardId\"`\n\n\t// Indicates if the card is limited\n\t// required: false\n\tLimited bool `json:\"limited,omitempty\"`\n}\n\n// BlockPatch is a patch for modify blocks\n// swagger:model\ntype BlockPatch struct {\n\t// The id for this block's parent block. Empty for root blocks\n\t// required: false\n\tParentID *string `json:\"parentId\"`\n\n\t// The schema version of this block\n\t// required: false\n\tSchema *int64 `json:\"schema\"`\n\n\t// The block type\n\t// required: false\n\tType *BlockType `json:\"type\"`\n\n\t// The display title\n\t// required: false\n\tTitle *string `json:\"title\"`\n\n\t// The block updated fields\n\t// required: false\n\tUpdatedFields map[string]interface{} `json:\"updatedFields\"`\n\n\t// The block removed fields\n\t// required: false\n\tDeletedFields []string `json:\"deletedFields\"`\n}\n\n// BlockPatchBatch is a batch of IDs and patches for modify blocks\n// swagger:model\ntype BlockPatchBatch struct {\n\t// The id's for of the blocks to patch\n\tBlockIDs []string `json:\"block_ids\"`\n\n\t// The BlockPatches to be applied\n\tBlockPatches []BlockPatch `json:\"block_patches\"`\n}\n\n// BoardModifier is a callback that can modify each board during an import.\n// A cache of arbitrary data will be passed for each call and any changes\n// to the cache will be preserved for the next call.\n// Return true to import the block or false to skip import.\ntype BoardModifier func(board *Board, cache map[string]interface{}) bool\n\n// BlockModifier is a callback that can modify each block during an import.\n// A cache of arbitrary data will be passed for each call and any changes\n// to the cache will be preserved for the next call.\n// Return true to import the block or false to skip import.\ntype BlockModifier func(block *Block, cache map[string]interface{}) bool\n\nfunc BlocksFromJSON(data io.Reader) []*Block {\n\tvar blocks []*Block\n\t_ = json.NewDecoder(data).Decode(&blocks)\n\treturn blocks\n}\n\n// IsValid checks the block for errors before inserting, and makes\n// sure it complies with the requirements of a valid block.\nfunc (b *Block) IsValid() error {\n\tif b.BoardID == \"\" {\n\t\treturn ErrBlockEmptyBoardID\n\t}\n\n\tif utf8.RuneCountInString(b.Title) > BlockTitleMaxRunes {\n\t\treturn ErrBlockTitleSizeLimitExceeded\n\t}\n\n\tfieldsJSON, err := json.Marshal(b.Fields)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif utf8.RuneCountInString(string(fieldsJSON)) > BlockFieldsMaxRunes {\n\t\treturn ErrBlockFieldsSizeLimitExceeded\n\t}\n\n\treturn nil\n}\n\n// LogClone implements the `mlog.LogCloner` interface to provide a subset of Block fields for logging.\nfunc (b *Block) LogClone() interface{} {\n\treturn struct {\n\t\tID       string\n\t\tParentID string\n\t\tBoardID  string\n\t\tType     BlockType\n\t}{\n\t\tID:       b.ID,\n\t\tParentID: b.ParentID,\n\t\tBoardID:  b.BoardID,\n\t\tType:     b.Type,\n\t}\n}\n\n// Patch returns an update version of the block.\nfunc (p *BlockPatch) Patch(block *Block) *Block {\n\tif p.ParentID != nil {\n\t\tblock.ParentID = *p.ParentID\n\t}\n\n\tif p.Schema != nil {\n\t\tblock.Schema = *p.Schema\n\t}\n\n\tif p.Type != nil {\n\t\tblock.Type = *p.Type\n\t}\n\n\tif p.Title != nil {\n\t\tblock.Title = *p.Title\n\t}\n\n\tfor key, field := range p.UpdatedFields {\n\t\tblock.Fields[key] = field\n\t}\n\n\tfor _, key := range p.DeletedFields {\n\t\tdelete(block.Fields, key)\n\t}\n\n\treturn block\n}\n\ntype QueryBlocksOptions struct {\n\tBoardID   string    // if not empty then filter for blocks belonging to specified board\n\tParentID  string    // if not empty then filter for blocks belonging to specified parent\n\tBlockType BlockType // if not empty and not `TypeUnknown` then filter for records of specified block type\n\tPage      int       // page number to select when paginating\n\tPerPage   int       // number of blocks per page (default=-1, meaning unlimited)\n}\n\n// QuerySubtreeOptions are query options that can be passed to GetSubTree methods.\ntype QuerySubtreeOptions struct {\n\tBeforeUpdateAt int64  // if non-zero then filter for records with update_at less than BeforeUpdateAt\n\tAfterUpdateAt  int64  // if non-zero then filter for records with update_at greater than AfterUpdateAt\n\tLimit          uint64 // if non-zero then limit the number of returned records\n}\n\n// QueryBlockHistoryOptions are query options that can be passed to GetBlockHistory.\ntype QueryBlockHistoryOptions struct {\n\tBeforeUpdateAt int64  // if non-zero then filter for records with update_at less than BeforeUpdateAt\n\tAfterUpdateAt  int64  // if non-zero then filter for records with update_at greater than AfterUpdateAt\n\tLimit          uint64 // if non-zero then limit the number of returned records\n\tDescending     bool   // if true then the records are sorted by insert_at in descending order\n}\n\n// QueryBoardHistoryOptions are query options that can be passed to GetBoardHistory.\ntype QueryBoardHistoryOptions struct {\n\tBeforeUpdateAt int64  // if non-zero then filter for records with update_at less than BeforeUpdateAt\n\tAfterUpdateAt  int64  // if non-zero then filter for records with update_at greater than AfterUpdateAt\n\tLimit          uint64 // if non-zero then limit the number of returned records\n\tDescending     bool   // if true then the records are sorted by insert_at in descending order\n}\n\n// QueryBlockHistoryOptions are query options that can be passed to GetBlockHistory.\ntype QueryBlockHistoryChildOptions struct {\n\tBeforeUpdateAt int64 // if non-zero then filter for records with update_at less than BeforeUpdateAt\n\tAfterUpdateAt  int64 // if non-zero then filter for records with update_at greater than AfterUpdateAt\n\tPage           int   // page number to select when paginating\n\tPerPage        int   // number of blocks per page (default=-1, meaning unlimited)\n}\n\nfunc StampModificationMetadata(userID string, blocks []*Block, auditRec *audit.Record) {\n\tif userID == SingleUser {\n\t\tuserID = \"\"\n\t}\n\n\tnow := GetMillis()\n\tfor i := range blocks {\n\t\tblocks[i].ModifiedBy = userID\n\t\tblocks[i].UpdateAt = now\n\n\t\tif auditRec != nil {\n\t\t\tauditRec.AddMeta(\"block_\"+strconv.FormatInt(int64(i), 10), blocks[i])\n\t\t}\n\t}\n}\n\nfunc (b *Block) ShouldBeLimited(cardLimitTimestamp int64) bool {\n\treturn b.Type == TypeCard &&\n\t\tb.UpdateAt < cardLimitTimestamp\n}\n\n// Returns a limited version of the block that doesn't contain the\n// contents of the block, only its IDs and type.\nfunc (b *Block) GetLimited() *Block {\n\tnewBlock := &Block{\n\t\tTitle:       b.Title,\n\t\tID:          b.ID,\n\t\tParentID:    b.ParentID,\n\t\tBoardID:     b.BoardID,\n\t\tSchema:      b.Schema,\n\t\tType:        b.Type,\n\t\tCreateAt:    b.CreateAt,\n\t\tUpdateAt:    b.UpdateAt,\n\t\tDeleteAt:    b.DeleteAt,\n\t\tWorkspaceID: b.WorkspaceID,\n\t\tLimited:     true,\n\t}\n\n\tif iconField, ok := b.Fields[\"icon\"]; ok {\n\t\tnewBlock.Fields = map[string]interface{}{\n\t\t\t\"icon\": iconField,\n\t\t}\n\t}\n\n\treturn newBlock\n}\n"
  },
  {
    "path": "server/model/block_test.go",
    "content": "package model\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n\n\t\"github.com/mattermost/focalboard/server/utils\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestGenerateBlockIDs(t *testing.T) {\n\tt.Run(\"Should generate a new ID for a single block with no references\", func(t *testing.T) {\n\t\tblockID := utils.NewID(utils.IDTypeBlock)\n\t\tblocks := []*Block{{ID: blockID}}\n\n\t\tblocks = GenerateBlockIDs(blocks, &mlog.Logger{})\n\n\t\trequire.NotEqual(t, blockID, blocks[0].ID)\n\t\trequire.Zero(t, blocks[0].BoardID)\n\t\trequire.Zero(t, blocks[0].ParentID)\n\t})\n\n\tt.Run(\"Should generate a new ID for a single block with references\", func(t *testing.T) {\n\t\tblockID := utils.NewID(utils.IDTypeBlock)\n\t\tboardID := utils.NewID(utils.IDTypeBlock)\n\t\tparentID := utils.NewID(utils.IDTypeBlock)\n\t\tblocks := []*Block{{ID: blockID, BoardID: boardID, ParentID: parentID}}\n\n\t\tblocks = GenerateBlockIDs(blocks, &mlog.Logger{})\n\n\t\trequire.NotEqual(t, blockID, blocks[0].ID)\n\t\trequire.Equal(t, boardID, blocks[0].BoardID)\n\t\trequire.Equal(t, parentID, blocks[0].ParentID)\n\t})\n\n\tt.Run(\"Should generate IDs and link multiple blocks with existing references\", func(t *testing.T) {\n\t\tblockID1 := utils.NewID(utils.IDTypeBlock)\n\t\tboardID1 := utils.NewID(utils.IDTypeBlock)\n\t\tparentID1 := utils.NewID(utils.IDTypeBlock)\n\t\tblock1 := &Block{ID: blockID1, BoardID: boardID1, ParentID: parentID1}\n\n\t\tblockID2 := utils.NewID(utils.IDTypeBlock)\n\t\tboardID2 := blockID1\n\t\tparentID2 := utils.NewID(utils.IDTypeBlock)\n\t\tblock2 := &Block{ID: blockID2, BoardID: boardID2, ParentID: parentID2}\n\n\t\tblocks := []*Block{block1, block2}\n\n\t\tblocks = GenerateBlockIDs(blocks, &mlog.Logger{})\n\n\t\trequire.NotEqual(t, blockID1, blocks[0].ID)\n\t\trequire.Equal(t, boardID1, blocks[0].BoardID)\n\t\trequire.Equal(t, parentID1, blocks[0].ParentID)\n\n\t\trequire.NotEqual(t, blockID2, blocks[1].ID)\n\t\trequire.NotEqual(t, boardID2, blocks[1].BoardID)\n\t\trequire.Equal(t, parentID2, blocks[1].ParentID)\n\n\t\t// blockID1 was referenced, so it should still be after the ID\n\t\t// changes\n\t\trequire.Equal(t, blocks[0].ID, blocks[1].BoardID)\n\t})\n\n\tt.Run(\"Should generate new IDs but not modify nonexisting references\", func(t *testing.T) {\n\t\tblockID1 := utils.NewID(utils.IDTypeBlock)\n\t\tboardID1 := \"\"\n\t\tparentID1 := utils.NewID(utils.IDTypeBlock)\n\t\tblock1 := &Block{ID: blockID1, BoardID: boardID1, ParentID: parentID1}\n\n\t\tblockID2 := utils.NewID(utils.IDTypeBlock)\n\t\tboardID2 := utils.NewID(utils.IDTypeBlock)\n\t\tparentID2 := \"\"\n\t\tblock2 := &Block{ID: blockID2, BoardID: boardID2, ParentID: parentID2}\n\n\t\tblocks := []*Block{block1, block2}\n\n\t\tblocks = GenerateBlockIDs(blocks, &mlog.Logger{})\n\n\t\t// only the IDs should have changed\n\t\trequire.NotEqual(t, blockID1, blocks[0].ID)\n\t\trequire.Zero(t, blocks[0].BoardID)\n\t\trequire.Equal(t, parentID1, blocks[0].ParentID)\n\n\t\trequire.NotEqual(t, blockID2, blocks[1].ID)\n\t\trequire.Equal(t, boardID2, blocks[1].BoardID)\n\t\trequire.Zero(t, blocks[1].ParentID)\n\t})\n\n\tt.Run(\"Should modify correctly multiple blocks with existing and nonexisting references\", func(t *testing.T) {\n\t\tblockID1 := utils.NewID(utils.IDTypeBlock)\n\t\tboardID1 := utils.NewID(utils.IDTypeBlock)\n\t\tparentID1 := utils.NewID(utils.IDTypeBlock)\n\t\tblock1 := &Block{ID: blockID1, BoardID: boardID1, ParentID: parentID1}\n\n\t\t// linked to 1\n\t\tblockID2 := utils.NewID(utils.IDTypeBlock)\n\t\tboardID2 := blockID1\n\t\tparentID2 := utils.NewID(utils.IDTypeBlock)\n\t\tblock2 := &Block{ID: blockID2, BoardID: boardID2, ParentID: parentID2}\n\n\t\t// linked to 2\n\t\tblockID3 := utils.NewID(utils.IDTypeBlock)\n\t\tboardID3 := blockID2\n\t\tparentID3 := utils.NewID(utils.IDTypeBlock)\n\t\tblock3 := &Block{ID: blockID3, BoardID: boardID3, ParentID: parentID3}\n\n\t\t// linked to 1\n\t\tblockID4 := utils.NewID(utils.IDTypeBlock)\n\t\tboardID4 := blockID1\n\t\tparentID4 := utils.NewID(utils.IDTypeBlock)\n\t\tblock4 := &Block{ID: blockID4, BoardID: boardID4, ParentID: parentID4}\n\n\t\t// blocks are shuffled\n\t\tblocks := []*Block{block4, block2, block1, block3}\n\n\t\tblocks = GenerateBlockIDs(blocks, &mlog.Logger{})\n\n\t\t// block 1\n\t\trequire.NotEqual(t, blockID1, blocks[2].ID)\n\t\trequire.Equal(t, boardID1, blocks[2].BoardID)\n\t\trequire.Equal(t, parentID1, blocks[2].ParentID)\n\n\t\t// block 2\n\t\trequire.NotEqual(t, blockID2, blocks[1].ID)\n\t\trequire.NotEqual(t, boardID2, blocks[1].BoardID)\n\t\trequire.Equal(t, blocks[2].ID, blocks[1].BoardID) // link to 1\n\t\trequire.Equal(t, parentID2, blocks[1].ParentID)\n\n\t\t// block 3\n\t\trequire.NotEqual(t, blockID3, blocks[3].ID)\n\t\trequire.NotEqual(t, boardID3, blocks[3].BoardID)\n\t\trequire.Equal(t, blocks[1].ID, blocks[3].BoardID) // link to 2\n\t\trequire.Equal(t, parentID3, blocks[3].ParentID)\n\n\t\t// block 4\n\t\trequire.NotEqual(t, blockID4, blocks[0].ID)\n\t\trequire.NotEqual(t, boardID4, blocks[0].BoardID)\n\t\trequire.Equal(t, blocks[2].ID, blocks[0].BoardID) // link to 1\n\t\trequire.Equal(t, parentID4, blocks[0].ParentID)\n\t})\n\n\tt.Run(\"Should update content order\", func(t *testing.T) {\n\t\tblockID1 := utils.NewID(utils.IDTypeBlock)\n\t\tboardID1 := utils.NewID(utils.IDTypeBlock)\n\t\tparentID1 := utils.NewID(utils.IDTypeBlock)\n\t\tblock1 := &Block{\n\t\t\tID:       blockID1,\n\t\t\tBoardID:  boardID1,\n\t\t\tParentID: parentID1,\n\t\t}\n\n\t\tblockID2 := utils.NewID(utils.IDTypeBlock)\n\t\tboardID2 := utils.NewID(utils.IDTypeBlock)\n\t\tparentID2 := utils.NewID(utils.IDTypeBlock)\n\t\tblock2 := &Block{\n\t\t\tID:       blockID2,\n\t\t\tBoardID:  boardID2,\n\t\t\tParentID: parentID2,\n\t\t\tFields: map[string]interface{}{\n\t\t\t\t\"contentOrder\": []interface{}{\n\t\t\t\t\tblockID1,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tblocks := []*Block{block1, block2}\n\n\t\tblocks = GenerateBlockIDs(blocks, &mlog.Logger{})\n\n\t\trequire.NotEqual(t, blockID1, blocks[0].ID)\n\t\trequire.Equal(t, boardID1, blocks[0].BoardID)\n\t\trequire.Equal(t, parentID1, blocks[0].ParentID)\n\n\t\trequire.NotEqual(t, blockID2, blocks[1].ID)\n\t\trequire.Equal(t, boardID2, blocks[1].BoardID)\n\t\trequire.Equal(t, parentID2, blocks[1].ParentID)\n\n\t\t// since block 1 was referenced in block 2,\n\t\t// the ID should have been changed in content order\n\t\tblock2ContentOrder, ok := block2.Fields[\"contentOrder\"].([]interface{})\n\t\trequire.True(t, ok)\n\t\trequire.NotEqual(t, blockID1, block2ContentOrder[0].(string))\n\t\trequire.Equal(t, blocks[0].ID, block2ContentOrder[0].(string))\n\t})\n\n\tt.Run(\"Should update content order when it contain slices\", func(t *testing.T) {\n\t\tblockID1 := utils.NewID(utils.IDTypeBlock)\n\t\tboardID1 := utils.NewID(utils.IDTypeBlock)\n\t\tparentID1 := utils.NewID(utils.IDTypeBlock)\n\t\tblock1 := &Block{\n\t\t\tID:       blockID1,\n\t\t\tBoardID:  boardID1,\n\t\t\tParentID: parentID1,\n\t\t}\n\n\t\tblockID2 := utils.NewID(utils.IDTypeBlock)\n\t\tblock2 := &Block{\n\t\t\tID:       blockID2,\n\t\t\tBoardID:  boardID1,\n\t\t\tParentID: parentID1,\n\t\t}\n\n\t\tblockID3 := utils.NewID(utils.IDTypeBlock)\n\t\tblock3 := &Block{\n\t\t\tID:       blockID3,\n\t\t\tBoardID:  boardID1,\n\t\t\tParentID: parentID1,\n\t\t}\n\n\t\tblockID4 := utils.NewID(utils.IDTypeBlock)\n\t\tboardID2 := utils.NewID(utils.IDTypeBlock)\n\t\tparentID2 := utils.NewID(utils.IDTypeBlock)\n\n\t\tblock4 := &Block{\n\t\t\tID:       blockID4,\n\t\t\tBoardID:  boardID2,\n\t\t\tParentID: parentID2,\n\t\t\tFields: map[string]interface{}{\n\t\t\t\t\"contentOrder\": []interface{}{\n\t\t\t\t\tblockID1,\n\t\t\t\t\t[]interface{}{\n\t\t\t\t\t\tblockID2,\n\t\t\t\t\t\tblockID3,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tblocks := []*Block{block1, block2, block3, block4}\n\n\t\tblocks = GenerateBlockIDs(blocks, &mlog.Logger{})\n\n\t\trequire.NotEqual(t, blockID1, blocks[0].ID)\n\t\trequire.Equal(t, boardID1, blocks[0].BoardID)\n\t\trequire.Equal(t, parentID1, blocks[0].ParentID)\n\n\t\trequire.NotEqual(t, blockID4, blocks[3].ID)\n\t\trequire.Equal(t, boardID2, blocks[3].BoardID)\n\t\trequire.Equal(t, parentID2, blocks[3].ParentID)\n\n\t\t// since block 1 was referenced in block 2,\n\t\t// the ID should have been changed in content order\n\t\tblock4ContentOrder, ok := block4.Fields[\"contentOrder\"].([]interface{})\n\t\trequire.True(t, ok)\n\t\trequire.NotEqual(t, blockID1, block4ContentOrder[0].(string))\n\t\trequire.NotEqual(t, blockID2, block4ContentOrder[1].([]interface{})[0])\n\t\trequire.NotEqual(t, blockID3, block4ContentOrder[1].([]interface{})[1])\n\t\trequire.Equal(t, blocks[0].ID, block4ContentOrder[0].(string))\n\t\trequire.Equal(t, blocks[1].ID, block4ContentOrder[1].([]interface{})[0])\n\t\trequire.Equal(t, blocks[2].ID, block4ContentOrder[1].([]interface{})[1])\n\t})\n\n\tt.Run(\"Should update Id of default template view\", func(t *testing.T) {\n\t\tblockID1 := utils.NewID(utils.IDTypeBlock)\n\t\tboardID1 := utils.NewID(utils.IDTypeBlock)\n\t\tparentID1 := utils.NewID(utils.IDTypeBlock)\n\t\tblock1 := &Block{\n\t\t\tID:       blockID1,\n\t\t\tBoardID:  boardID1,\n\t\t\tParentID: parentID1,\n\t\t}\n\n\t\tblockID2 := utils.NewID(utils.IDTypeBlock)\n\t\tboardID2 := utils.NewID(utils.IDTypeBlock)\n\t\tparentID2 := utils.NewID(utils.IDTypeBlock)\n\t\tblock2 := &Block{\n\t\t\tID:       blockID2,\n\t\t\tBoardID:  boardID2,\n\t\t\tParentID: parentID2,\n\t\t\tFields: map[string]interface{}{\n\t\t\t\t\"defaultTemplateId\": blockID1,\n\t\t\t},\n\t\t}\n\n\t\tblocks := []*Block{block1, block2}\n\n\t\tblocks = GenerateBlockIDs(blocks, &mlog.Logger{})\n\n\t\trequire.NotEqual(t, blockID1, blocks[0].ID)\n\t\trequire.Equal(t, boardID1, blocks[0].BoardID)\n\t\trequire.Equal(t, parentID1, blocks[0].ParentID)\n\n\t\trequire.NotEqual(t, blockID2, blocks[1].ID)\n\t\trequire.Equal(t, boardID2, blocks[1].BoardID)\n\t\trequire.Equal(t, parentID2, blocks[1].ParentID)\n\n\t\tblock2DefaultTemplateID, ok := block2.Fields[\"defaultTemplateId\"].(string)\n\t\trequire.True(t, ok)\n\t\trequire.NotEqual(t, blockID1, block2DefaultTemplateID)\n\t\trequire.Equal(t, blocks[0].ID, block2DefaultTemplateID)\n\t})\n}\n\nfunc TestStampModificationMetadata(t *testing.T) {\n\tt.Run(\"base case\", func(t *testing.T) {\n\t\tblock := &Block{}\n\t\tblocks := []*Block{block}\n\t\tassert.Empty(t, block.ModifiedBy)\n\t\tassert.Empty(t, block.UpdateAt)\n\n\t\tStampModificationMetadata(\"user_id_1\", blocks, nil)\n\t\tassert.Equal(t, \"user_id_1\", blocks[0].ModifiedBy)\n\t\tassert.NotEmpty(t, blocks[0].UpdateAt)\n\t})\n}\n"
  },
  {
    "path": "server/model/blockid.go",
    "content": "package model\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/mattermost/focalboard/server/utils\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\n// GenerateBlockIDs generates new IDs for all the blocks of the list,\n// keeping consistent any references that other blocks would made to\n// the original IDs, so a tree of blocks can get new IDs and maintain\n// its shape.\nfunc GenerateBlockIDs(blocks []*Block, logger mlog.LoggerIFace) []*Block {\n\tblockIDs := map[string]BlockType{}\n\treferenceIDs := map[string]bool{}\n\tfor _, block := range blocks {\n\t\tif _, ok := blockIDs[block.ID]; !ok {\n\t\t\tblockIDs[block.ID] = block.Type\n\t\t}\n\n\t\tif _, ok := referenceIDs[block.BoardID]; !ok {\n\t\t\treferenceIDs[block.BoardID] = true\n\t\t}\n\t\tif _, ok := referenceIDs[block.ParentID]; !ok {\n\t\t\treferenceIDs[block.ParentID] = true\n\t\t}\n\n\t\tif _, ok := block.Fields[\"contentOrder\"]; ok {\n\t\t\tcontentOrder, typeOk := block.Fields[\"contentOrder\"].([]interface{})\n\t\t\tif !typeOk {\n\t\t\t\tlogger.Warn(\n\t\t\t\t\t\"type assertion failed for content order when saving reference block IDs\",\n\t\t\t\t\tmlog.String(\"blockID\", block.ID),\n\t\t\t\t\tmlog.String(\"actionType\", fmt.Sprintf(\"%T\", block.Fields[\"contentOrder\"])),\n\t\t\t\t\tmlog.String(\"expectedType\", \"[]interface{}\"),\n\t\t\t\t\tmlog.String(\"contentOrder\", fmt.Sprintf(\"%v\", block.Fields[\"contentOrder\"])),\n\t\t\t\t)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfor _, blockID := range contentOrder {\n\t\t\t\tswitch v := blockID.(type) {\n\t\t\t\tcase []interface{}:\n\t\t\t\t\tfor _, columnBlockID := range v {\n\t\t\t\t\t\treferenceIDs[columnBlockID.(string)] = true\n\t\t\t\t\t}\n\t\t\t\tcase string:\n\t\t\t\t\treferenceIDs[v] = true\n\t\t\t\tdefault:\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif _, ok := block.Fields[\"defaultTemplateId\"]; ok {\n\t\t\tdefaultTemplateID, typeOk := block.Fields[\"defaultTemplateId\"].(string)\n\t\t\tif !typeOk {\n\t\t\t\tlogger.Warn(\n\t\t\t\t\t\"type assertion failed for default template ID when saving reference block IDs\",\n\t\t\t\t\tmlog.String(\"blockID\", block.ID),\n\t\t\t\t\tmlog.String(\"actionType\", fmt.Sprintf(\"%T\", block.Fields[\"defaultTemplateId\"])),\n\t\t\t\t\tmlog.String(\"expectedType\", \"string\"),\n\t\t\t\t\tmlog.String(\"defaultTemplateId\", fmt.Sprintf(\"%v\", block.Fields[\"defaultTemplateId\"])),\n\t\t\t\t)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treferenceIDs[defaultTemplateID] = true\n\t\t}\n\t}\n\n\tnewIDs := map[string]string{}\n\tfor id, blockType := range blockIDs {\n\t\tfor referenceID := range referenceIDs {\n\t\t\tif id == referenceID {\n\t\t\t\tnewIDs[id] = utils.NewID(BlockType2IDType(blockType))\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t}\n\n\tgetExistingOrOldID := func(id string) string {\n\t\tif existingID, ok := newIDs[id]; ok {\n\t\t\treturn existingID\n\t\t}\n\t\treturn id\n\t}\n\n\tgetExistingOrNewID := func(id string) string {\n\t\tif existingID, ok := newIDs[id]; ok {\n\t\t\treturn existingID\n\t\t}\n\t\treturn utils.NewID(BlockType2IDType(blockIDs[id]))\n\t}\n\n\tnewBlocks := make([]*Block, len(blocks))\n\tfor i, block := range blocks {\n\t\tblock.ID = getExistingOrNewID(block.ID)\n\t\tblock.BoardID = getExistingOrOldID(block.BoardID)\n\t\tblock.ParentID = getExistingOrOldID(block.ParentID)\n\n\t\tblockMod := block\n\t\tif _, ok := blockMod.Fields[\"contentOrder\"]; ok {\n\t\t\tfixFieldIDs(blockMod, \"contentOrder\", getExistingOrOldID, logger)\n\t\t}\n\n\t\tif _, ok := blockMod.Fields[\"cardOrder\"]; ok {\n\t\t\tfixFieldIDs(blockMod, \"cardOrder\", getExistingOrOldID, logger)\n\t\t}\n\n\t\tif _, ok := blockMod.Fields[\"defaultTemplateId\"]; ok {\n\t\t\tdefaultTemplateID, typeOk := blockMod.Fields[\"defaultTemplateId\"].(string)\n\t\t\tif !typeOk {\n\t\t\t\tlogger.Warn(\n\t\t\t\t\t\"type assertion failed for default template ID when saving reference block IDs\",\n\t\t\t\t\tmlog.String(\"blockID\", blockMod.ID),\n\t\t\t\t\tmlog.String(\"actionType\", fmt.Sprintf(\"%T\", blockMod.Fields[\"defaultTemplateId\"])),\n\t\t\t\t\tmlog.String(\"expectedType\", \"string\"),\n\t\t\t\t\tmlog.String(\"defaultTemplateId\", fmt.Sprintf(\"%v\", blockMod.Fields[\"defaultTemplateId\"])),\n\t\t\t\t)\n\t\t\t} else {\n\t\t\t\tblockMod.Fields[\"defaultTemplateId\"] = getExistingOrOldID(defaultTemplateID)\n\t\t\t}\n\t\t}\n\n\t\tnewBlocks[i] = blockMod\n\t}\n\n\treturn newBlocks\n}\n\nfunc fixFieldIDs(block *Block, fieldName string, getExistingOrOldID func(string) string, logger mlog.LoggerIFace) {\n\tfield, typeOk := block.Fields[fieldName].([]interface{})\n\tif !typeOk {\n\t\tlogger.Warn(\n\t\t\t\"type assertion failed for JSON field when setting new block IDs\",\n\t\t\tmlog.String(\"blockID\", block.ID),\n\t\t\tmlog.String(\"fieldName\", fieldName),\n\t\t\tmlog.String(\"actionType\", fmt.Sprintf(\"%T\", block.Fields[fieldName])),\n\t\t\tmlog.String(\"expectedType\", \"[]interface{}\"),\n\t\t\tmlog.String(\"value\", fmt.Sprintf(\"%v\", block.Fields[fieldName])),\n\t\t)\n\t} else {\n\t\tfor j := range field {\n\t\t\tswitch v := field[j].(type) {\n\t\t\tcase string:\n\t\t\t\tfield[j] = getExistingOrOldID(v)\n\t\t\tcase []interface{}:\n\t\t\t\tsubOrder := field[j].([]interface{})\n\t\t\t\tfor k := range v {\n\t\t\t\t\tsubOrder[k] = getExistingOrOldID(v[k].(string))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "server/model/blocktype.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage model\n\nimport (\n\t\"errors\"\n\t\"strings\"\n\n\t\"github.com/mattermost/focalboard/server/utils\"\n)\n\n// BlockType represents a block type.\ntype BlockType string\n\nconst (\n\tTypeUnknown    = \"unknown\"\n\tTypeBoard      = \"board\"\n\tTypeCard       = \"card\"\n\tTypeView       = \"view\"\n\tTypeText       = \"text\"\n\tTypeCheckbox   = \"checkbox\"\n\tTypeComment    = \"comment\"\n\tTypeImage      = \"image\"\n\tTypeAttachment = \"attachment\"\n\tTypeDivider    = \"divider\"\n)\n\nfunc (bt BlockType) String() string {\n\treturn string(bt)\n}\n\n// BlockTypeFromString returns an appropriate BlockType for the specified string.\nfunc BlockTypeFromString(s string) (BlockType, error) {\n\tswitch strings.ToLower(s) {\n\tcase \"board\":\n\t\treturn TypeBoard, nil\n\tcase \"card\":\n\t\treturn TypeCard, nil\n\tcase \"view\":\n\t\treturn TypeView, nil\n\tcase \"text\":\n\t\treturn TypeText, nil\n\tcase \"checkbox\":\n\t\treturn TypeCheckbox, nil\n\tcase \"comment\":\n\t\treturn TypeComment, nil\n\tcase \"image\":\n\t\treturn TypeImage, nil\n\tcase \"attachment\":\n\t\treturn TypeAttachment, nil\n\tcase \"divider\":\n\t\treturn TypeDivider, nil\n\t}\n\treturn TypeUnknown, ErrInvalidBlockType{s}\n}\n\n// BlockType2IDType returns an appropriate IDType for the specified BlockType.\nfunc BlockType2IDType(blockType BlockType) utils.IDType {\n\tswitch blockType {\n\tcase TypeBoard:\n\t\treturn utils.IDTypeBoard\n\tcase TypeCard:\n\t\treturn utils.IDTypeCard\n\tcase TypeView:\n\t\treturn utils.IDTypeView\n\tcase TypeText, TypeCheckbox, TypeComment, TypeDivider:\n\t\treturn utils.IDTypeBlock\n\tcase TypeImage, TypeAttachment:\n\t\treturn utils.IDTypeAttachment\n\t}\n\treturn utils.IDTypeNone\n}\n\n// ErrInvalidBlockType is returned wherever an invalid block type was provided.\ntype ErrInvalidBlockType struct {\n\tType string\n}\n\nfunc (e ErrInvalidBlockType) Error() string {\n\treturn e.Type + \" is an invalid block type.\"\n}\n\n// IsErrInvalidBlockType returns true if `err` is a IsErrInvalidBlockType or wraps one.\nfunc IsErrInvalidBlockType(err error) bool {\n\tvar eibt *ErrInvalidBlockType\n\treturn errors.As(err, &eibt)\n}\n"
  },
  {
    "path": "server/model/board.go",
    "content": "package model\n\nimport (\n\t\"encoding/json\"\n\t\"io\"\n\t\"time\"\n)\n\ntype BoardType string\ntype BoardRole string\ntype BoardSearchField string\n\nconst (\n\tBoardTypeOpen    BoardType = \"O\"\n\tBoardTypePrivate BoardType = \"P\"\n)\n\nconst (\n\tBoardRoleNone      BoardRole = \"\"\n\tBoardRoleViewer    BoardRole = \"viewer\"\n\tBoardRoleCommenter BoardRole = \"commenter\"\n\tBoardRoleEditor    BoardRole = \"editor\"\n\tBoardRoleAdmin     BoardRole = \"admin\"\n)\n\nconst (\n\tBoardSearchFieldNone         BoardSearchField = \"\"\n\tBoardSearchFieldTitle        BoardSearchField = \"title\"\n\tBoardSearchFieldPropertyName BoardSearchField = \"property_name\"\n)\n\n// Board groups a set of blocks and its layout\n// swagger:model\ntype Board struct {\n\t// The ID for the board\n\t// required: true\n\tID string `json:\"id\"`\n\n\t// The ID of the team that the board belongs to\n\t// required: true\n\tTeamID string `json:\"teamId\"`\n\n\t// The ID of the channel that the board was created from\n\t// required: false\n\tChannelID string `json:\"channelId\"`\n\n\t// The ID of the user that created the board\n\t// required: true\n\tCreatedBy string `json:\"createdBy\"`\n\n\t// The ID of the last user that updated the board\n\t// required: true\n\tModifiedBy string `json:\"modifiedBy\"`\n\n\t// The type of the board\n\t// required: true\n\tType BoardType `json:\"type\"`\n\n\t// The minimum role applied when somebody joins the board\n\t// required: true\n\tMinimumRole BoardRole `json:\"minimumRole\"`\n\n\t// The title of the board\n\t// required: false\n\tTitle string `json:\"title\"`\n\n\t// The description of the board\n\t// required: false\n\tDescription string `json:\"description\"`\n\n\t// The icon of the board\n\t// required: false\n\tIcon string `json:\"icon\"`\n\n\t// Indicates if the board shows the description on the interface\n\t// required: false\n\tShowDescription bool `json:\"showDescription\"`\n\n\t// Marks the template boards\n\t// required: false\n\tIsTemplate bool `json:\"isTemplate\"`\n\n\t// Marks the template boards\n\t// required: false\n\tTemplateVersion int `json:\"templateVersion\"`\n\n\t// The properties of the board\n\t// required: false\n\tProperties map[string]interface{} `json:\"properties\"`\n\n\t// The properties of the board cards\n\t// required: false\n\tCardProperties []map[string]interface{} `json:\"cardProperties\"`\n\n\t// The creation time in miliseconds since the current epoch\n\t// required: true\n\tCreateAt int64 `json:\"createAt\"`\n\n\t// The last modified time in miliseconds since the current epoch\n\t// required: true\n\tUpdateAt int64 `json:\"updateAt\"`\n\n\t// The deleted time in miliseconds since the current epoch. Set to indicate this block is deleted\n\t// required: false\n\tDeleteAt int64 `json:\"deleteAt\"`\n}\n\n// GetPropertyString returns the value of the specified property as a string,\n// or error if the property does not exist or is not of type string.\nfunc (b *Board) GetPropertyString(propName string) (string, error) {\n\tval, ok := b.Properties[propName]\n\tif !ok {\n\t\treturn \"\", NewErrNotFound(propName)\n\t}\n\n\ts, ok := val.(string)\n\tif !ok {\n\t\treturn \"\", ErrInvalidPropertyValueType\n\t}\n\treturn s, nil\n}\n\n// BoardPatch is a patch for modify boards\n// swagger:model\ntype BoardPatch struct {\n\t// The type of the board\n\t// required: false\n\tType *BoardType `json:\"type\"`\n\n\t// The minimum role applied when somebody joins the board\n\t// required: false\n\tMinimumRole *BoardRole `json:\"minimumRole\"`\n\n\t// The title of the board\n\t// required: false\n\tTitle *string `json:\"title\"`\n\n\t// The description of the board\n\t// required: false\n\tDescription *string `json:\"description\"`\n\n\t// The icon of the board\n\t// required: false\n\tIcon *string `json:\"icon\"`\n\n\t// Indicates if the board shows the description on the interface\n\t// required: false\n\tShowDescription *bool `json:\"showDescription\"`\n\n\t// Indicates if the board shows the description on the interface\n\t// required: false\n\tChannelID *string `json:\"channelId\"`\n\n\t// The board updated properties\n\t// required: false\n\tUpdatedProperties map[string]interface{} `json:\"updatedProperties\"`\n\n\t// The board removed properties\n\t// required: false\n\tDeletedProperties []string `json:\"deletedProperties\"`\n\n\t// The board updated card properties\n\t// required: false\n\tUpdatedCardProperties []map[string]interface{} `json:\"updatedCardProperties\"`\n\n\t// The board removed card properties\n\t// required: false\n\tDeletedCardProperties []string `json:\"deletedCardProperties\"`\n}\n\n// BoardMember stores the information of the membership of a user on a board\n// swagger:model\ntype BoardMember struct {\n\t// The ID of the board\n\t// required: true\n\tBoardID string `json:\"boardId\"`\n\n\t// The ID of the user\n\t// required: true\n\tUserID string `json:\"userId\"`\n\n\t// The independent roles of the user on the board\n\t// required: false\n\tRoles string `json:\"roles\"`\n\n\t// Minimum role because the board configuration\n\t// required: false\n\tMinimumRole string `json:\"minimumRole\"`\n\n\t// Marks the user as an admin of the board\n\t// required: true\n\tSchemeAdmin bool `json:\"schemeAdmin\"`\n\n\t// Marks the user as an editor of the board\n\t// required: true\n\tSchemeEditor bool `json:\"schemeEditor\"`\n\n\t// Marks the user as an commenter of the board\n\t// required: true\n\tSchemeCommenter bool `json:\"schemeCommenter\"`\n\n\t// Marks the user as an viewer of the board\n\t// required: true\n\tSchemeViewer bool `json:\"schemeViewer\"`\n\n\t// Marks the membership as generated by an access group\n\t// required: true\n\tSynthetic bool `json:\"synthetic\"`\n}\n\n// BoardMetadata contains metadata for a Board\n// swagger:model\ntype BoardMetadata struct {\n\t// The ID for the board\n\t// required: true\n\tBoardID string `json:\"boardId\"`\n\n\t// The most recent time a descendant of this board was added, modified, or deleted\n\t// required: true\n\tDescendantLastUpdateAt int64 `json:\"descendantLastUpdateAt\"`\n\n\t// The earliest time a descendant of this board was added, modified, or deleted\n\t// required: true\n\tDescendantFirstUpdateAt int64 `json:\"descendantFirstUpdateAt\"`\n\n\t// The ID of the user that created the board\n\t// required: true\n\tCreatedBy string `json:\"createdBy\"`\n\n\t// The ID of the user that last modified the most recently modified descendant\n\t// required: true\n\tLastModifiedBy string `json:\"lastModifiedBy\"`\n}\n\nfunc BoardFromJSON(data io.Reader) *Board {\n\tvar board *Board\n\t_ = json.NewDecoder(data).Decode(&board)\n\treturn board\n}\n\nfunc BoardsFromJSON(data io.Reader) []*Board {\n\tvar boards []*Board\n\t_ = json.NewDecoder(data).Decode(&boards)\n\treturn boards\n}\n\nfunc BoardMemberFromJSON(data io.Reader) *BoardMember {\n\tvar boardMember *BoardMember\n\t_ = json.NewDecoder(data).Decode(&boardMember)\n\treturn boardMember\n}\n\nfunc BoardMembersFromJSON(data io.Reader) []*BoardMember {\n\tvar boardMembers []*BoardMember\n\t_ = json.NewDecoder(data).Decode(&boardMembers)\n\treturn boardMembers\n}\n\nfunc BoardMetadataFromJSON(data io.Reader) *BoardMetadata {\n\tvar boardMetadata *BoardMetadata\n\t_ = json.NewDecoder(data).Decode(&boardMetadata)\n\treturn boardMetadata\n}\n\n// Patch returns an updated version of the board.\nfunc (p *BoardPatch) Patch(board *Board) *Board {\n\tif p.Type != nil {\n\t\tboard.Type = *p.Type\n\t}\n\n\tif p.Title != nil {\n\t\tboard.Title = *p.Title\n\t}\n\n\tif p.MinimumRole != nil {\n\t\tboard.MinimumRole = *p.MinimumRole\n\t}\n\n\tif p.Description != nil {\n\t\tboard.Description = *p.Description\n\t}\n\n\tif p.Icon != nil {\n\t\tboard.Icon = *p.Icon\n\t}\n\n\tif p.ShowDescription != nil {\n\t\tboard.ShowDescription = *p.ShowDescription\n\t}\n\n\tif p.ChannelID != nil {\n\t\tboard.ChannelID = *p.ChannelID\n\t}\n\n\tfor key, property := range p.UpdatedProperties {\n\t\tboard.Properties[key] = property\n\t}\n\n\tfor _, key := range p.DeletedProperties {\n\t\tdelete(board.Properties, key)\n\t}\n\n\tif len(p.UpdatedCardProperties) != 0 || len(p.DeletedCardProperties) != 0 {\n\t\t// first we accumulate all properties indexed by, and maintain their order\n\t\tkeyOrder := []string{}\n\t\tcardPropertyMap := map[string]map[string]interface{}{}\n\t\tfor _, prop := range board.CardProperties {\n\t\t\tid, ok := prop[\"id\"].(string)\n\t\t\tif !ok {\n\t\t\t\t// bad property, skipping\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tcardPropertyMap[id] = prop\n\t\t\tkeyOrder = append(keyOrder, id)\n\t\t}\n\n\t\t// if there are properties marked for removal, we delete them\n\t\tfor _, propertyID := range p.DeletedCardProperties {\n\t\t\tdelete(cardPropertyMap, propertyID)\n\t\t}\n\n\t\t// if there are properties marked for update, we replace the\n\t\t// existing ones or add them\n\t\tfor _, newprop := range p.UpdatedCardProperties {\n\t\t\tid, ok := newprop[\"id\"].(string)\n\t\t\tif !ok {\n\t\t\t\t// bad new property, skipping\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t_, exists := cardPropertyMap[id]\n\t\t\tif !exists {\n\t\t\t\tkeyOrder = append(keyOrder, id)\n\t\t\t}\n\t\t\tcardPropertyMap[id] = newprop\n\t\t}\n\n\t\t// and finally we flatten and save the updated properties\n\t\tnewCardProperties := []map[string]interface{}{}\n\t\tfor _, key := range keyOrder {\n\t\t\tp, exists := cardPropertyMap[key]\n\t\t\tif exists {\n\t\t\t\tnewCardProperties = append(newCardProperties, p)\n\t\t\t}\n\t\t}\n\n\t\tboard.CardProperties = newCardProperties\n\t}\n\n\treturn board\n}\n\nfunc IsBoardTypeValid(t BoardType) bool {\n\treturn t == BoardTypeOpen || t == BoardTypePrivate\n}\n\nfunc IsBoardMinimumRoleValid(r BoardRole) bool {\n\treturn r == BoardRoleNone || r == BoardRoleAdmin || r == BoardRoleEditor || r == BoardRoleCommenter || r == BoardRoleViewer\n}\n\nfunc (p *BoardPatch) IsValid() error {\n\tif p.Type != nil && !IsBoardTypeValid(*p.Type) {\n\t\treturn InvalidBoardErr{\"invalid-board-type\"}\n\t}\n\n\tif p.MinimumRole != nil && !IsBoardMinimumRoleValid(*p.MinimumRole) {\n\t\treturn InvalidBoardErr{\"invalid-board-minimum-role\"}\n\t}\n\n\treturn nil\n}\n\ntype InvalidBoardErr struct {\n\tmsg string\n}\n\nfunc (ibe InvalidBoardErr) Error() string {\n\treturn ibe.msg\n}\n\nfunc (b *Board) IsValid() error {\n\tif b.TeamID == \"\" {\n\t\treturn InvalidBoardErr{\"empty-team-id\"}\n\t}\n\n\tif !IsBoardTypeValid(b.Type) {\n\t\treturn InvalidBoardErr{\"invalid-board-type\"}\n\t}\n\n\tif !IsBoardMinimumRoleValid(b.MinimumRole) {\n\t\treturn InvalidBoardErr{\"invalid-board-minimum-role\"}\n\t}\n\n\treturn nil\n}\n\n// BoardMemberHistoryEntry stores the information of the membership of a user on a board\n// swagger:model\ntype BoardMemberHistoryEntry struct {\n\t// The ID of the board\n\t// required: true\n\tBoardID string `json:\"boardId\"`\n\n\t// The ID of the user\n\t// required: true\n\tUserID string `json:\"userId\"`\n\n\t// The action that added this history entry (created or deleted)\n\t// required: false\n\tAction string `json:\"action\"`\n\n\t// The insertion time\n\t// required: true\n\tInsertAt time.Time `json:\"insertAt\"`\n}\n\nfunc BoardSearchFieldFromString(field string) (BoardSearchField, error) {\n\tswitch field {\n\tcase string(BoardSearchFieldTitle):\n\t\treturn BoardSearchFieldTitle, nil\n\tcase string(BoardSearchFieldPropertyName):\n\t\treturn BoardSearchFieldPropertyName, nil\n\t}\n\treturn BoardSearchFieldNone, ErrInvalidBoardSearchField\n}\n"
  },
  {
    "path": "server/model/board_statistics.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\npackage model\n\n// BoardsStatistics is the representation of the statistics for the Boards server\n// swagger:model\ntype BoardsStatistics struct {\n\t// The maximum number of cards on the server\n\t// required: true\n\tBoards int `json:\"board_count\"`\n\n\t// The maximum number of cards on the server\n\t// required: true\n\tCards int `json:\"card_count\"`\n}\n"
  },
  {
    "path": "server/model/boards_and_blocks.go",
    "content": "package model\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/mattermost/focalboard/server/utils\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nvar ErrNoBoardsInBoardsAndBlocks = errors.New(\"at least one board is required\")\nvar ErrNoBlocksInBoardsAndBlocks = errors.New(\"at least one block is required\")\nvar ErrNoTeamInBoardsAndBlocks = errors.New(\"team ID cannot be empty\")\nvar ErrBoardIDsAndPatchesMissmatchInBoardsAndBlocks = errors.New(\"board ids and patches need to match\")\nvar ErrBlockIDsAndPatchesMissmatchInBoardsAndBlocks = errors.New(\"block ids and patches need to match\")\n\ntype BlockDoesntBelongToAnyBoardErr struct {\n\tblockID string\n}\n\nfunc (e BlockDoesntBelongToAnyBoardErr) Error() string {\n\treturn fmt.Sprintf(\"block %s doesn't belong to any board\", e.blockID)\n}\n\n// BoardsAndBlocks is used to operate over boards and blocks at the\n// same time\n// swagger:model\ntype BoardsAndBlocks struct {\n\t// The boards\n\t// required: false\n\tBoards []*Board `json:\"boards\"`\n\n\t// The blocks\n\t// required: false\n\tBlocks []*Block `json:\"blocks\"`\n}\n\nfunc (bab *BoardsAndBlocks) IsValid() error {\n\tif len(bab.Boards) == 0 {\n\t\treturn ErrNoBoardsInBoardsAndBlocks\n\t}\n\n\tif len(bab.Blocks) == 0 {\n\t\treturn ErrNoBlocksInBoardsAndBlocks\n\t}\n\n\tboardsMap := map[string]bool{}\n\tfor _, board := range bab.Boards {\n\t\tboardsMap[board.ID] = true\n\t}\n\n\tfor _, block := range bab.Blocks {\n\t\tif _, ok := boardsMap[block.BoardID]; !ok {\n\t\t\treturn BlockDoesntBelongToAnyBoardErr{block.ID}\n\t\t}\n\t}\n\treturn nil\n}\n\n// DeleteBoardsAndBlocks is used to list the boards and blocks to\n// delete on a request\n// swagger:model\ntype DeleteBoardsAndBlocks struct {\n\t// The boards\n\t// required: true\n\tBoards []string `json:\"boards\"`\n\n\t// The blocks\n\t// required: true\n\tBlocks []string `json:\"blocks\"`\n}\n\nfunc NewDeleteBoardsAndBlocksFromBabs(babs *BoardsAndBlocks) *DeleteBoardsAndBlocks {\n\tboardIDs := make([]string, 0, len(babs.Boards))\n\tblockIDs := make([]string, 0, len(babs.Boards))\n\n\tfor _, board := range babs.Boards {\n\t\tboardIDs = append(boardIDs, board.ID)\n\t}\n\tfor _, block := range babs.Blocks {\n\t\tblockIDs = append(blockIDs, block.ID)\n\t}\n\treturn &DeleteBoardsAndBlocks{\n\t\tBoards: boardIDs,\n\t\tBlocks: blockIDs,\n\t}\n}\n\nfunc (dbab *DeleteBoardsAndBlocks) IsValid() error {\n\tif len(dbab.Boards) == 0 {\n\t\treturn ErrNoBoardsInBoardsAndBlocks\n\t}\n\n\treturn nil\n}\n\n// PatchBoardsAndBlocks is used to patch multiple boards and blocks on\n// a single request\n// swagger:model\ntype PatchBoardsAndBlocks struct {\n\t// The board IDs to patch\n\t// required: true\n\tBoardIDs []string `json:\"boardIDs\"`\n\n\t// The board patches\n\t// required: true\n\tBoardPatches []*BoardPatch `json:\"boardPatches\"`\n\n\t// The block IDs to patch\n\t// required: true\n\tBlockIDs []string `json:\"blockIDs\"`\n\n\t// The block patches\n\t// required: true\n\tBlockPatches []*BlockPatch `json:\"blockPatches\"`\n}\n\nfunc (dbab *PatchBoardsAndBlocks) IsValid() error {\n\tif len(dbab.BoardIDs) == 0 {\n\t\treturn ErrNoBoardsInBoardsAndBlocks\n\t}\n\n\tif len(dbab.BoardIDs) != len(dbab.BoardPatches) {\n\t\treturn ErrBoardIDsAndPatchesMissmatchInBoardsAndBlocks\n\t}\n\n\tif len(dbab.BlockIDs) != len(dbab.BlockPatches) {\n\t\treturn ErrBlockIDsAndPatchesMissmatchInBoardsAndBlocks\n\t}\n\n\treturn nil\n}\n\nfunc GenerateBoardsAndBlocksIDs(bab *BoardsAndBlocks, logger mlog.LoggerIFace) (*BoardsAndBlocks, error) {\n\tif err := bab.IsValid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tblocksByBoard := map[string][]*Block{}\n\tfor _, block := range bab.Blocks {\n\t\tblocksByBoard[block.BoardID] = append(blocksByBoard[block.BoardID], block)\n\t}\n\n\tboards := []*Board{}\n\tblocks := []*Block{}\n\tfor _, board := range bab.Boards {\n\t\tnewID := utils.NewID(utils.IDTypeBoard)\n\t\tfor _, block := range blocksByBoard[board.ID] {\n\t\t\tblock.BoardID = newID\n\t\t\tblocks = append(blocks, block)\n\t\t}\n\n\t\tboard.ID = newID\n\t\tboards = append(boards, board)\n\t}\n\n\tnewBab := &BoardsAndBlocks{\n\t\tBoards: boards,\n\t\tBlocks: GenerateBlockIDs(blocks, logger),\n\t}\n\n\treturn newBab, nil\n}\n\nfunc BoardsAndBlocksFromJSON(data io.Reader) *BoardsAndBlocks {\n\tvar bab *BoardsAndBlocks\n\t_ = json.NewDecoder(data).Decode(&bab)\n\treturn bab\n}\n"
  },
  {
    "path": "server/model/boards_and_blocks_test.go",
    "content": "package model\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nfunc TestIsValidBoardsAndBlocks(t *testing.T) {\n\tt.Run(\"no boards\", func(t *testing.T) {\n\t\tbab := &BoardsAndBlocks{\n\t\t\tBlocks: []*Block{\n\t\t\t\t{ID: \"block-id-1\", BoardID: \"board-id-1\", Type: TypeCard},\n\t\t\t\t{ID: \"block-id-2\", BoardID: \"board-id-2\", Type: TypeCard},\n\t\t\t},\n\t\t}\n\n\t\trequire.ErrorIs(t, bab.IsValid(), ErrNoBoardsInBoardsAndBlocks)\n\t})\n\n\tt.Run(\"no blocks\", func(t *testing.T) {\n\t\tbab := &BoardsAndBlocks{\n\t\t\tBoards: []*Board{\n\t\t\t\t{ID: \"board-id-1\", Type: BoardTypeOpen},\n\t\t\t\t{ID: \"board-id-2\", Type: BoardTypePrivate},\n\t\t\t},\n\t\t}\n\n\t\trequire.ErrorIs(t, bab.IsValid(), ErrNoBlocksInBoardsAndBlocks)\n\t})\n\n\tt.Run(\"block that doesn't belong to the boards\", func(t *testing.T) {\n\t\tbab := &BoardsAndBlocks{\n\t\t\tBoards: []*Board{\n\t\t\t\t{ID: \"board-id-1\", Type: BoardTypeOpen},\n\t\t\t\t{ID: \"board-id-2\", Type: BoardTypePrivate},\n\t\t\t},\n\t\t\tBlocks: []*Block{\n\t\t\t\t{ID: \"block-id-1\", BoardID: \"board-id-1\", Type: TypeCard},\n\t\t\t\t{ID: \"block-id-3\", BoardID: \"board-id-3\", Type: TypeCard},\n\t\t\t\t{ID: \"block-id-2\", BoardID: \"board-id-2\", Type: TypeCard},\n\t\t\t},\n\t\t}\n\n\t\trequire.ErrorIs(t, bab.IsValid(), BlockDoesntBelongToAnyBoardErr{\"block-id-3\"})\n\t})\n\n\tt.Run(\"valid boards and blocks\", func(t *testing.T) {\n\t\tbab := &BoardsAndBlocks{\n\t\t\tBoards: []*Board{\n\t\t\t\t{ID: \"board-id-1\", Type: BoardTypeOpen},\n\t\t\t\t{ID: \"board-id-2\", Type: BoardTypePrivate},\n\t\t\t},\n\t\t\tBlocks: []*Block{\n\t\t\t\t{ID: \"block-id-1\", BoardID: \"board-id-1\", Type: TypeCard},\n\t\t\t\t{ID: \"block-id-3\", BoardID: \"board-id-2\", Type: TypeCard},\n\t\t\t\t{ID: \"block-id-2\", BoardID: \"board-id-2\", Type: TypeCard},\n\t\t\t},\n\t\t}\n\n\t\trequire.NoError(t, bab.IsValid())\n\t})\n}\n\nfunc TestGenerateBoardsAndBlocksIDs(t *testing.T) {\n\tlogger, err := mlog.NewLogger()\n\trequire.NoError(t, err)\n\n\tgetBlockByType := func(blocks []*Block, blockType BlockType) *Block {\n\t\tfor _, b := range blocks {\n\t\t\tif b.Type == blockType {\n\t\t\t\treturn b\n\t\t\t}\n\t\t}\n\t\treturn &Block{}\n\t}\n\n\tgetBoardByTitle := func(boards []*Board, title string) *Board {\n\t\tfor _, b := range boards {\n\t\t\tif b.Title == title {\n\t\t\t\treturn b\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\n\tt.Run(\"invalid boards and blocks\", func(t *testing.T) {\n\t\tbab := &BoardsAndBlocks{\n\t\t\tBlocks: []*Block{\n\t\t\t\t{ID: \"block-id-1\", BoardID: \"board-id-1\", Type: TypeCard},\n\t\t\t\t{ID: \"block-id-2\", BoardID: \"board-id-2\", Type: TypeCard},\n\t\t\t},\n\t\t}\n\n\t\trBab, err := GenerateBoardsAndBlocksIDs(bab, logger)\n\t\trequire.Error(t, err)\n\t\trequire.Nil(t, rBab)\n\t})\n\n\tt.Run(\"correctly generates IDs for all the boards and links the blocks to them, with new IDs too\", func(t *testing.T) {\n\t\tbab := &BoardsAndBlocks{\n\t\t\tBoards: []*Board{\n\t\t\t\t{ID: \"board-id-1\", Type: BoardTypeOpen, Title: \"board1\"},\n\t\t\t\t{ID: \"board-id-2\", Type: BoardTypePrivate, Title: \"board2\"},\n\t\t\t\t{ID: \"board-id-3\", Type: BoardTypeOpen, Title: \"board3\"},\n\t\t\t},\n\t\t\tBlocks: []*Block{\n\t\t\t\t{ID: \"block-id-1\", BoardID: \"board-id-1\", Type: TypeCard},\n\t\t\t\t{ID: \"block-id-2\", BoardID: \"board-id-2\", Type: TypeView},\n\t\t\t\t{ID: \"block-id-3\", BoardID: \"board-id-2\", Type: TypeText},\n\t\t\t},\n\t\t}\n\n\t\trBab, err := GenerateBoardsAndBlocksIDs(bab, logger)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, rBab)\n\n\t\t// all boards and blocks should have refreshed their IDs, and\n\t\t// blocks should be correctly linked to the new board IDs\n\t\tboard1 := getBoardByTitle(rBab.Boards, \"board1\")\n\t\trequire.NotNil(t, board1)\n\t\trequire.NotEmpty(t, board1.ID)\n\t\trequire.NotEqual(t, \"board-id-1\", board1.ID)\n\t\tboard2 := getBoardByTitle(rBab.Boards, \"board2\")\n\t\trequire.NotNil(t, board2)\n\t\trequire.NotEmpty(t, board2.ID)\n\t\trequire.NotEqual(t, \"board-id-2\", board2.ID)\n\t\tboard3 := getBoardByTitle(rBab.Boards, \"board3\")\n\t\trequire.NotNil(t, board3)\n\t\trequire.NotEmpty(t, board3.ID)\n\t\trequire.NotEqual(t, \"board-id-3\", board3.ID)\n\n\t\tblock1 := getBlockByType(rBab.Blocks, TypeCard)\n\t\trequire.NotNil(t, block1)\n\t\trequire.NotEmpty(t, block1.ID)\n\t\trequire.NotEqual(t, \"block-id-1\", block1.ID)\n\t\trequire.Equal(t, board1.ID, block1.BoardID)\n\t\tblock2 := getBlockByType(rBab.Blocks, TypeView)\n\t\trequire.NotNil(t, block2)\n\t\trequire.NotEmpty(t, block2.ID)\n\t\trequire.NotEqual(t, \"block-id-2\", block2.ID)\n\t\trequire.Equal(t, board2.ID, block2.BoardID)\n\t\tblock3 := getBlockByType(rBab.Blocks, TypeText)\n\t\trequire.NotNil(t, block3)\n\t\trequire.NotEmpty(t, block3.ID)\n\t\trequire.NotEqual(t, \"block-id-3\", block3.ID)\n\t\trequire.Equal(t, board2.ID, block3.BoardID)\n\t})\n}\n\nfunc TestIsValidPatchBoardsAndBlocks(t *testing.T) {\n\tnewTitle := \"new title\"\n\tnewDescription := \"new description\"\n\tvar schema int64 = 1\n\n\tt.Run(\"no board ids\", func(t *testing.T) {\n\t\tpbab := &PatchBoardsAndBlocks{\n\t\t\tBoardIDs: []string{},\n\t\t\tBlockIDs: []string{\"block-id-1\"},\n\t\t\tBlockPatches: []*BlockPatch{\n\t\t\t\t{Title: &newTitle},\n\t\t\t\t{Schema: &schema},\n\t\t\t},\n\t\t}\n\n\t\trequire.ErrorIs(t, pbab.IsValid(), ErrNoBoardsInBoardsAndBlocks)\n\t})\n\n\tt.Run(\"missmatch board IDs and patches\", func(t *testing.T) {\n\t\tpbab := &PatchBoardsAndBlocks{\n\t\t\tBoardIDs: []string{\"board-id-1\", \"board-id-2\"},\n\t\t\tBoardPatches: []*BoardPatch{\n\t\t\t\t{Title: &newTitle},\n\t\t\t},\n\t\t\tBlockIDs: []string{\"block-id-1\"},\n\t\t\tBlockPatches: []*BlockPatch{\n\t\t\t\t{Title: &newTitle},\n\t\t\t},\n\t\t}\n\n\t\trequire.ErrorIs(t, pbab.IsValid(), ErrBoardIDsAndPatchesMissmatchInBoardsAndBlocks)\n\t})\n\n\tt.Run(\"missmatch block IDs and patches\", func(t *testing.T) {\n\t\tpbab := &PatchBoardsAndBlocks{\n\t\t\tBoardIDs: []string{\"board-id-1\", \"board-id-2\"},\n\t\t\tBoardPatches: []*BoardPatch{\n\t\t\t\t{Title: &newTitle},\n\t\t\t\t{Description: &newDescription},\n\t\t\t},\n\t\t\tBlockIDs: []string{\"block-id-1\"},\n\t\t\tBlockPatches: []*BlockPatch{\n\t\t\t\t{Title: &newTitle},\n\t\t\t\t{Schema: &schema},\n\t\t\t},\n\t\t}\n\n\t\trequire.ErrorIs(t, pbab.IsValid(), ErrBlockIDsAndPatchesMissmatchInBoardsAndBlocks)\n\t})\n\n\tt.Run(\"valid\", func(t *testing.T) {\n\t\tpbab := &PatchBoardsAndBlocks{\n\t\t\tBoardIDs: []string{\"board-id-1\", \"board-id-2\"},\n\t\t\tBoardPatches: []*BoardPatch{\n\t\t\t\t{Title: &newTitle},\n\t\t\t\t{Description: &newDescription},\n\t\t\t},\n\t\t\tBlockIDs: []string{\"block-id-1\"},\n\t\t\tBlockPatches: []*BlockPatch{\n\t\t\t\t{Title: &newTitle},\n\t\t\t},\n\t\t}\n\n\t\trequire.NoError(t, pbab.IsValid())\n\t})\n}\n\nfunc TestIsValidDeleteBoardsAndBlocks(t *testing.T) {\n\t/*\n\t\tTODO fix this\n\t\tt.Run(\"no board ids\", func(t *testing.T) {\n\t\t\tdbab := &DeleteBoardsAndBlocks{\n\t\t\t\tTeamID: \"team-id\",\n\t\t\t\tBlocks: []string{\"block-id-1\"},\n\t\t\t}\n\n\t\t\trequire.ErrorIs(t, dbab.IsValid(), NoBoardsInBoardsAndBlocksErr)\n\t\t})\n\n\t\tt.Run(\"no block ids\", func(t *testing.T) {\n\t\t\tdbab := &DeleteBoardsAndBlocks{\n\t\t\t\tTeamID: \"team-id\",\n\t\t\t\tBoards: []string{\"board-id-1\", \"board-id-2\"},\n\t\t\t}\n\n\t\t\trequire.ErrorIs(t, dbab.IsValid(), NoBlocksInBoardsAndBlocksErr)\n\t\t})\n\n\t\tt.Run(\"valid\", func(t *testing.T) {\n\t\t\tdbab := &DeleteBoardsAndBlocks{\n\t\t\t\tTeamID: \"team-id\",\n\t\t\t\tBoards: []string{\"board-id-1\", \"board-id-2\"},\n\t\t\t\tBlocks: []string{\"block-id-1\"},\n\t\t\t}\n\n\t\t\trequire.NoError(t, dbab.IsValid())\n\t\t})\n\t*/\n}\n"
  },
  {
    "path": "server/model/card.go",
    "content": "package model\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/mattermost/focalboard/server/utils\"\n\t\"github.com/rivo/uniseg\"\n)\n\nvar ErrBoardIDMismatch = errors.New(\"Board IDs do not match\")\n\ntype ErrInvalidCard struct {\n\tmsg string\n}\n\nfunc NewErrInvalidCard(msg string) ErrInvalidCard {\n\treturn ErrInvalidCard{\n\t\tmsg: msg,\n\t}\n}\n\nfunc (e ErrInvalidCard) Error() string {\n\treturn fmt.Sprintf(\"invalid card, %s\", e.msg)\n}\n\nvar ErrNotCardBlock = errors.New(\"not a card block\")\n\ntype ErrInvalidFieldType struct {\n\tfield string\n}\n\nfunc (e ErrInvalidFieldType) Error() string {\n\treturn fmt.Sprintf(\"invalid type for field '%s'\", e.field)\n}\n\n// Card represents a group of content blocks and properties.\n// swagger:model\ntype Card struct {\n\t// The id for this card\n\t// required: false\n\tID string `json:\"id\"`\n\n\t// The id for board this card belongs to.\n\t// required: false\n\tBoardID string `json:\"boardId\"`\n\n\t// The id for user who created this card\n\t// required: false\n\tCreatedBy string `json:\"createdBy\"`\n\n\t// The id for user who last modified this card\n\t// required: false\n\tModifiedBy string `json:\"modifiedBy\"`\n\n\t// The display title\n\t// required: false\n\tTitle string `json:\"title\"`\n\n\t// An array of content block ids specifying the ordering of content for this card.\n\t// required: false\n\tContentOrder []string `json:\"contentOrder\"`\n\n\t// The icon of the card\n\t// required: false\n\tIcon string `json:\"icon\"`\n\n\t// True if this card belongs to a template\n\t// required: false\n\tIsTemplate bool `json:\"isTemplate\"`\n\n\t// A map of property ids to property values (option ids, strings, array of option ids)\n\t// required: false\n\tProperties map[string]any `json:\"properties\"`\n\n\t// The creation time in milliseconds since the current epoch\n\t// required: false\n\tCreateAt int64 `json:\"createAt\"`\n\n\t// The last modified time in milliseconds since the current epoch\n\t// required: false\n\tUpdateAt int64 `json:\"updateAt\"`\n\n\t// The deleted time in milliseconds since the current epoch. Set to indicate this card is deleted\n\t// required: false\n\tDeleteAt int64 `json:\"deleteAt\"`\n}\n\n// Populate populates a Card with default values.\nfunc (c *Card) Populate() {\n\tif c.ID == \"\" {\n\t\tc.ID = utils.NewID(utils.IDTypeCard)\n\t}\n\tif c.ContentOrder == nil {\n\t\tc.ContentOrder = make([]string, 0)\n\t}\n\tif c.Properties == nil {\n\t\tc.Properties = make(map[string]any)\n\t}\n\tnow := utils.GetMillis()\n\tif c.CreateAt == 0 {\n\t\tc.CreateAt = now\n\t}\n\tif c.UpdateAt == 0 {\n\t\tc.UpdateAt = now\n\t}\n}\n\nfunc (c *Card) PopulateWithBoardID(boardID string) {\n\tc.BoardID = boardID\n\tc.Populate()\n}\n\n// CheckValid returns an error if the Card has invalid field values.\nfunc (c *Card) CheckValid() error {\n\tif c.ID == \"\" {\n\t\treturn ErrInvalidCard{\"ID is missing\"}\n\t}\n\tif c.BoardID == \"\" {\n\t\treturn ErrInvalidCard{\"BoardID is missing\"}\n\t}\n\tif c.ContentOrder == nil {\n\t\treturn ErrInvalidCard{\"ContentOrder is missing\"}\n\t}\n\tif uniseg.GraphemeClusterCount(c.Icon) > 1 {\n\t\treturn ErrInvalidCard{\"Icon can have only one grapheme\"}\n\t}\n\tif c.Properties == nil {\n\t\treturn ErrInvalidCard{\"Properties\"}\n\t}\n\tif c.CreateAt == 0 {\n\t\treturn ErrInvalidCard{\"CreateAt\"}\n\t}\n\tif c.UpdateAt == 0 {\n\t\treturn ErrInvalidCard{\"UpdateAt\"}\n\t}\n\treturn nil\n}\n\n// CardPatch is a patch for modifying cards\n// swagger:model\ntype CardPatch struct {\n\t// The display title\n\t// required: false\n\tTitle *string `json:\"title\"`\n\n\t// An array of content block ids specifying the ordering of content for this card.\n\t// required: false\n\tContentOrder *[]string `json:\"contentOrder\"`\n\n\t// The icon of the card\n\t// required: false\n\tIcon *string `json:\"icon\"`\n\n\t// A map of property ids to property option ids to be updated\n\t// required: false\n\tUpdatedProperties map[string]any `json:\"updatedProperties\"`\n}\n\n// Patch returns an updated version of the card.\nfunc (p *CardPatch) Patch(card *Card) *Card {\n\tif p.Title != nil {\n\t\tcard.Title = *p.Title\n\t}\n\n\tif p.ContentOrder != nil {\n\t\tcard.ContentOrder = *p.ContentOrder\n\t}\n\n\tif p.Icon != nil {\n\t\tcard.Icon = *p.Icon\n\t}\n\n\tif card.Properties == nil {\n\t\tcard.Properties = make(map[string]any)\n\t}\n\n\t// if there are properties marked for update, we replace the\n\t// existing ones or add them\n\tfor propID, propVal := range p.UpdatedProperties {\n\t\tcard.Properties[propID] = propVal\n\t}\n\n\treturn card\n}\n\n// CheckValid returns an error if the CardPatch has invalid field values.\nfunc (p *CardPatch) CheckValid() error {\n\tif p.Icon != nil && uniseg.GraphemeClusterCount(*p.Icon) > 1 {\n\t\treturn ErrInvalidCard{\"Icon can have only one grapheme\"}\n\t}\n\treturn nil\n}\n\n// Card2Block converts a card to block using a shallow copy. Not needed once cards are first class entities.\nfunc Card2Block(card *Card) *Block {\n\tfields := make(map[string]interface{})\n\n\tfields[\"contentOrder\"] = card.ContentOrder\n\tfields[\"icon\"] = card.Icon\n\tfields[\"isTemplate\"] = card.IsTemplate\n\tfields[\"properties\"] = card.Properties\n\n\treturn &Block{\n\t\tID:         card.ID,\n\t\tParentID:   card.BoardID,\n\t\tCreatedBy:  card.CreatedBy,\n\t\tModifiedBy: card.ModifiedBy,\n\t\tSchema:     1,\n\t\tType:       TypeCard,\n\t\tTitle:      card.Title,\n\t\tFields:     fields,\n\t\tCreateAt:   card.CreateAt,\n\t\tUpdateAt:   card.UpdateAt,\n\t\tDeleteAt:   card.DeleteAt,\n\t\tBoardID:    card.BoardID,\n\t}\n}\n\n// Block2Card converts a block to a card. Not needed once cards are first class entities.\nfunc Block2Card(block *Block) (*Card, error) {\n\tif block.Type != TypeCard {\n\t\treturn nil, fmt.Errorf(\"cannot convert block to card: %w\", ErrNotCardBlock)\n\t}\n\n\tcontentOrder := make([]string, 0)\n\ticon := \"\"\n\tisTemplate := false\n\tproperties := make(map[string]any)\n\n\tif co, ok := block.Fields[\"contentOrder\"]; ok {\n\t\tswitch arr := co.(type) {\n\t\tcase []any:\n\t\t\tfor _, str := range arr {\n\t\t\t\tif id, ok := str.(string); ok {\n\t\t\t\t\tcontentOrder = append(contentOrder, id)\n\t\t\t\t} else {\n\t\t\t\t\treturn nil, ErrInvalidFieldType{\"contentOrder item\"}\n\t\t\t\t}\n\t\t\t}\n\t\tcase []string:\n\t\t\tcontentOrder = append(contentOrder, arr...)\n\t\tdefault:\n\t\t\treturn nil, ErrInvalidFieldType{\"contentOrder\"}\n\t\t}\n\t}\n\n\tif iconAny, ok := block.Fields[\"icon\"]; ok {\n\t\tif id, ok := iconAny.(string); ok {\n\t\t\ticon = id\n\t\t} else {\n\t\t\treturn nil, ErrInvalidFieldType{\"icon\"}\n\t\t}\n\t}\n\n\tif isTemplateAny, ok := block.Fields[\"isTemplate\"]; ok {\n\t\tif b, ok := isTemplateAny.(bool); ok {\n\t\t\tisTemplate = b\n\t\t} else {\n\t\t\treturn nil, ErrInvalidFieldType{\"isTemplate\"}\n\t\t}\n\t}\n\n\tif props, ok := block.Fields[\"properties\"]; ok {\n\t\tif propMap, ok := props.(map[string]any); ok {\n\t\t\tfor k, v := range propMap {\n\t\t\t\tproperties[k] = v\n\t\t\t}\n\t\t} else {\n\t\t\treturn nil, ErrInvalidFieldType{\"properties\"}\n\t\t}\n\t}\n\n\tcard := &Card{\n\t\tID:           block.ID,\n\t\tBoardID:      block.BoardID,\n\t\tCreatedBy:    block.CreatedBy,\n\t\tModifiedBy:   block.ModifiedBy,\n\t\tTitle:        block.Title,\n\t\tContentOrder: contentOrder,\n\t\tIcon:         icon,\n\t\tIsTemplate:   isTemplate,\n\t\tProperties:   properties,\n\t\tCreateAt:     block.CreateAt,\n\t\tUpdateAt:     block.UpdateAt,\n\t\tDeleteAt:     block.DeleteAt,\n\t}\n\tcard.Populate()\n\treturn card, nil\n}\n\n// CardPatch2BlockPatch converts a CardPatch to a BlockPatch. Not needed once cards are first class entities.\nfunc CardPatch2BlockPatch(cardPatch *CardPatch) (*BlockPatch, error) {\n\tif err := cardPatch.CheckValid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tblockPatch := &BlockPatch{\n\t\tTitle: cardPatch.Title,\n\t}\n\n\tupdatedFields := make(map[string]any, 0)\n\n\tif cardPatch.ContentOrder != nil {\n\t\tupdatedFields[\"contentOrder\"] = cardPatch.ContentOrder\n\t}\n\tif cardPatch.Icon != nil {\n\t\tupdatedFields[\"icon\"] = cardPatch.Icon\n\t}\n\n\tproperties := make(map[string]any)\n\tfor k, v := range cardPatch.UpdatedProperties {\n\t\tproperties[k] = v\n\t}\n\n\tif len(properties) != 0 {\n\t\tupdatedFields[\"properties\"] = cardPatch.UpdatedProperties\n\t}\n\n\tblockPatch.UpdatedFields = updatedFields\n\n\treturn blockPatch, nil\n}\n"
  },
  {
    "path": "server/model/card_test.go",
    "content": "package model\n\nimport (\n\t\"encoding/json\"\n\t\"testing\"\n\n\t\"github.com/mattermost/focalboard/server/utils\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestBlock2Card(t *testing.T) {\n\tblockID := utils.NewID(utils.IDTypeCard)\n\tboardID := utils.NewID(utils.IDTypeBoard)\n\tuserID := utils.NewID(utils.IDTypeUser)\n\tnow := utils.GetMillis()\n\n\tvar fields map[string]any\n\terr := json.Unmarshal([]byte(sampleBlockFieldsJSON), &fields)\n\trequire.NoError(t, err)\n\n\tblock := &Block{\n\t\tID:         blockID,\n\t\tParentID:   boardID,\n\t\tCreatedBy:  userID,\n\t\tModifiedBy: userID,\n\t\tSchema:     1,\n\t\tType:       TypeCard,\n\t\tTitle:      \"My card title\",\n\t\tFields:     fields,\n\t\tCreateAt:   now,\n\t\tUpdateAt:   now,\n\t\tDeleteAt:   0,\n\t\tBoardID:    boardID,\n\t}\n\n\tt.Run(\"Good block\", func(t *testing.T) {\n\t\tcard, err := Block2Card(block)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, block.ID, card.ID)\n\t\tassert.Equal(t, []string{\"acdxa8r8aht85pyoeuj1ed7tu8w\", \"73urm1huoupd4idzkdq5yaeuyay\", \"ay6sogs9owtd9xbyn49qt3395ko\"}, card.ContentOrder)\n\t\tassert.EqualValues(t, fields[\"icon\"], card.Icon)\n\t\tassert.EqualValues(t, fields[\"isTemplate\"], card.IsTemplate)\n\t\tassert.EqualValues(t, fields[\"properties\"], card.Properties)\n\t})\n\n\tt.Run(\"Not a card\", func(t *testing.T) {\n\t\tblockNotCard := &Block{}\n\n\t\tcard, err := Block2Card(blockNotCard)\n\t\trequire.Error(t, err)\n\t\trequire.Nil(t, card)\n\t})\n}\n\nconst sampleBlockFieldsJSON = `\n{\n\t\"contentOrder\":[\n\t   \"acdxa8r8aht85pyoeuj1ed7tu8w\",\n\t   \"73urm1huoupd4idzkdq5yaeuyay\",\n\t   \"ay6sogs9owtd9xbyn49qt3395ko\"\n\t],\n\t\"icon\":\"🎨\",\n\t\"isTemplate\":false,\n\t\"properties\":{\n\t   \"aa7swu9zz3ofdkcna3h867cum4y\":\"212-444-1234\",\n\t   \"af6fcbb8-ca56-4b73-83eb-37437b9a667d\":\"77c539af-309c-4db1-8329-d20ef7e9eacd\",\n\t   \"aiwt9ibi8jjrf9hzi1xzk8no8mo\":\"foo\",\n\t   \"aj65h4s6ghr6wgh3bnhqbzzmiaa\":\"77\",\n\t   \"ajy6xbebzopojaenbnmfpgtdwso\":\"{\\\"from\\\":1660046400000}\",\n\t   \"amc8wnk1xqj54rymkoqffhtw7ie\":\"zhqsoeqs1pg9i8gk81k9ryy83h\",\n\t   \"aooz77t119y7xtfmoyeiy4up75c\":\"someone@example.com\",\n\t   \"auskzaoaccsn55icuwarf4o3tfe\":\"https://www.google.com\",\n\t   \"aydsk41h6cs1z7nmghaw16jqcia\":[\n\t\t  \"aw565znut6zphbxqhbwyawiuggy\",\n\t\t  \"aefd3pxciomrkur4rc6smg1usoc\",\n\t\t  \"a6c96kwrqaskbtochq9wunmzweh\",\n\t\t  \"atyexeuq993fwwb84bxoqixxqqr\"\n\t   ],\n\t   \"d6b1249b-bc18-45fc-889e-bec48fce80ef\":\"9a090e33-b110-4268-8909-132c5002c90e\",\n\t   \"d9725d14-d5a8-48e5-8de1-6f8c004a9680\":\"3245a32d-f688-463b-87f4-8e7142c1b397\"\n\t}\n}`\n"
  },
  {
    "path": "server/model/category.go",
    "content": "package model\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/mattermost/focalboard/server/utils\"\n)\n\nconst (\n\tCategoryTypeSystem = \"system\"\n\tCategoryTypeCustom = \"custom\"\n)\n\n// Category is a board category\n// swagger:model\ntype Category struct {\n\t// The id for this category\n\t// required: true\n\tID string `json:\"id\"`\n\n\t// The name for this category\n\t// required: true\n\tName string `json:\"name\"`\n\n\t// The user's id for this category\n\t// required: true\n\tUserID string `json:\"userID\"`\n\n\t// The team id for this category\n\t// required: true\n\tTeamID string `json:\"teamID\"`\n\n\t// The creation time in miliseconds since the current epoch\n\t// required: true\n\tCreateAt int64 `json:\"createAt\"`\n\n\t// The last modified time in miliseconds since the current epoch\n\t// required: true\n\tUpdateAt int64 `json:\"updateAt\"`\n\n\t// The deleted time in miliseconds since the current epoch. Set to indicate this category is deleted\n\t// required: false\n\tDeleteAt int64 `json:\"deleteAt\"`\n\n\t// Category's state in client side\n\t// required: true\n\tCollapsed bool `json:\"collapsed\"`\n\n\t// Inter-category sort order per user\n\t// required: true\n\tSortOrder int `json:\"sortOrder\"`\n\n\t// The sorting method applied on this category\n\t// required: true\n\tSorting string `json:\"sorting\"`\n\n\t// Category's type\n\t// required: true\n\tType string `json:\"type\"`\n}\n\nfunc (c *Category) Hydrate() {\n\tif c.ID == \"\" {\n\t\tc.ID = utils.NewID(utils.IDTypeNone)\n\t}\n\n\tif c.CreateAt == 0 {\n\t\tc.CreateAt = utils.GetMillis()\n\t}\n\n\tif c.UpdateAt == 0 {\n\t\tc.UpdateAt = c.CreateAt\n\t}\n\n\tif c.SortOrder < 0 {\n\t\tc.SortOrder = 0\n\t}\n\n\tif strings.TrimSpace(c.Type) == \"\" {\n\t\tc.Type = CategoryTypeCustom\n\t}\n}\n\nfunc (c *Category) IsValid() error {\n\tif strings.TrimSpace(c.ID) == \"\" {\n\t\treturn NewErrInvalidCategory(\"category ID cannot be empty\")\n\t}\n\n\tif strings.TrimSpace(c.Name) == \"\" {\n\t\treturn NewErrInvalidCategory(\"category name cannot be empty\")\n\t}\n\n\tif strings.TrimSpace(c.UserID) == \"\" {\n\t\treturn NewErrInvalidCategory(\"category user ID cannot be empty\")\n\t}\n\n\tif strings.TrimSpace(c.TeamID) == \"\" {\n\t\treturn NewErrInvalidCategory(\"category team id ID cannot be empty\")\n\t}\n\n\tif c.Type != CategoryTypeCustom && c.Type != CategoryTypeSystem {\n\t\treturn NewErrInvalidCategory(fmt.Sprintf(\"category type is invalid. Allowed types: %s and %s\", CategoryTypeSystem, CategoryTypeCustom))\n\t}\n\n\treturn nil\n}\n\nfunc CategoryFromJSON(data io.Reader) *Category {\n\tvar category *Category\n\t_ = json.NewDecoder(data).Decode(&category)\n\treturn category\n}\n"
  },
  {
    "path": "server/model/category_boards.go",
    "content": "package model\n\nconst CategoryBoardsSortOrderGap = 10\n\n// CategoryBoards is a board category and associated boards\n// swagger:model\ntype CategoryBoards struct {\n\tCategory\n\n\t// The IDs of boards in this category\n\t// required: true\n\tBoardMetadata []CategoryBoardMetadata `json:\"boardMetadata\"`\n\n\t// The relative sort order of this board in its category\n\t// required: true\n\tSortOrder int `json:\"sortOrder\"`\n}\n\ntype BoardCategoryWebsocketData struct {\n\tBoardID    string `json:\"boardID\"`\n\tCategoryID string `json:\"categoryID\"`\n\tHidden     bool   `json:\"hidden\"`\n}\n\ntype CategoryBoardMetadata struct {\n\tBoardID string `json:\"boardID\"`\n\tHidden  bool   `json:\"hidden\"`\n}\n"
  },
  {
    "path": "server/model/clientConfig.go",
    "content": "package model\n\n// ClientConfig is the client configuration\n// swagger:model\ntype ClientConfig struct {\n\t// Is telemetry enabled\n\t// required: true\n\tTelemetry bool `json:\"telemetry\"`\n\n\t// The telemetry ID\n\t// required: true\n\tTelemetryID string `json:\"telemetryid\"`\n\n\t// Is public shared boards enabled\n\t// required: true\n\tEnablePublicSharedBoards bool `json:\"enablePublicSharedBoards\"`\n\n\t// Is public shared boards enabled\n\t// required: true\n\tTeammateNameDisplay string `json:\"teammateNameDisplay\"`\n\n\t// The server feature flags\n\t// required: true\n\tFeatureFlags map[string]string `json:\"featureFlags\"`\n\n\t// Required for file upload to check the size of the file\n\t// required: true\n\tMaxFileSize int64 `json:\"maxFileSize\"`\n}\n"
  },
  {
    "path": "server/model/cloud.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage model\n\nconst LimitUnlimited = 0\n\n// BoardsCloudLimits is the representation of the limits for the\n// Boards server\n// swagger:model\ntype BoardsCloudLimits struct {\n\t// The maximum number of cards on the server\n\t// required: true\n\tCards int `json:\"cards\"`\n\n\t// The current number of cards on the server\n\t// required: true\n\tUsedCards int `json:\"used_cards\"`\n\n\t// The updated_at timestamp of the limit card\n\t// required: true\n\tCardLimitTimestamp int64 `json:\"card_limit_timestamp\"`\n\n\t// The maximum number of views for each board\n\t// required: true\n\tViews int `json:\"views\"`\n}\n"
  },
  {
    "path": "server/model/compliance.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\npackage model\n\n// BaordsComplianceResponse is the response body to a request for boards.\n// swagger:model\ntype BoardsComplianceResponse struct {\n\t// True if there is a next page for pagination\n\t// required: true\n\tHasNext bool `json:\"hasNext\"`\n\n\t// The array of board records.\n\t// required: true\n\tResults []*Board `json:\"results\"`\n}\n\n// BoardsComplianceHistoryResponse is the response body to a request for boards history.\n// swagger:model\ntype BoardsComplianceHistoryResponse struct {\n\t// True if there is a next page for pagination\n\t// required: true\n\tHasNext bool `json:\"hasNext\"`\n\n\t// The array of BoardHistory records.\n\t// required: true\n\tResults []*BoardHistory `json:\"results\"`\n}\n\n// BlocksComplianceHistoryResponse is the response body to a request for blocks history.\n// swagger:model\ntype BlocksComplianceHistoryResponse struct {\n\t// True if there is a next page for pagination\n\t// required: true\n\tHasNext bool `json:\"hasNext\"`\n\n\t// The array of BlockHistory records.\n\t// required: true\n\tResults []*BlockHistory `json:\"results\"`\n}\n\n// BoardHistory provides information about the history of a board.\n// swagger:model\ntype BoardHistory struct {\n\tID                      string `json:\"id\"`\n\tTeamID                  string `json:\"teamId\"`\n\tIsDeleted               bool   `json:\"isDeleted\"`\n\tDescendantLastUpdateAt  int64  `json:\"descendantLastUpdateAt\"`\n\tDescendantFirstUpdateAt int64  `json:\"descendantFirstUpdateAt\"`\n\tCreatedBy               string `json:\"createdBy\"`\n\tLastModifiedBy          string `json:\"lastModifiedBy\"`\n}\n\n// BlockHistory provides information about the history of a block.\n// swagger:model\ntype BlockHistory struct {\n\tID             string `json:\"id\"`\n\tTeamID         string `json:\"teamId\"`\n\tBoardID        string `json:\"boardId\"`\n\tType           string `json:\"type\"`\n\tIsDeleted      bool   `json:\"isDeleted\"`\n\tLastUpdateAt   int64  `json:\"lastUpdateAt\"`\n\tFirstUpdateAt  int64  `json:\"firstUpdateAt\"`\n\tCreatedBy      string `json:\"createdBy\"`\n\tLastModifiedBy string `json:\"lastModifiedBy\"`\n}\n\ntype QueryBoardsForComplianceOptions struct {\n\tTeamID  string // if not empty then filter for specific team, otherwise all teams are included\n\tPage    int    // page number to select when paginating\n\tPerPage int    // number of blocks per page (default=60)\n}\n\ntype QueryBoardsComplianceHistoryOptions struct {\n\tModifiedSince  int64  // if non-zero then filter for records with update_at greater than ModifiedSince\n\tIncludeDeleted bool   // if true then deleted blocks are included\n\tTeamID         string // if not empty then filter for specific team, otherwise all teams are included\n\tPage           int    // page number to select when paginating\n\tPerPage        int    // number of blocks per page (default=60)\n}\n\ntype QueryBlocksComplianceHistoryOptions struct {\n\tModifiedSince  int64  // if non-zero then filter for records with update_at greater than ModifiedSince\n\tIncludeDeleted bool   // if true then deleted blocks are included\n\tTeamID         string // if not empty then filter for specific team, otherwise all teams are included\n\tBoardID        string // if not empty then filter for specific board, otherwise all boards are included\n\tPage           int    // page number to select when paginating\n\tPerPage        int    // number of blocks per page (default=60)\n}\n"
  },
  {
    "path": "server/model/database.go",
    "content": "package model\n\nconst (\n\tSqliteDBType   = \"sqlite3\"\n\tPostgresDBType = \"postgres\"\n\tMysqlDBType    = \"mysql\"\n)\n"
  },
  {
    "path": "server/model/error.go",
    "content": "package model\n\nimport (\n\t\"database/sql\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tmmModel \"github.com/mattermost/mattermost/server/public/model\"\n\n\tpluginapi \"github.com/mattermost/mattermost/server/public/pluginapi\"\n)\n\nvar (\n\tErrViewsLimitReached        = errors.New(\"views limit reached for board\")\n\tErrPatchUpdatesLimitedCards = errors.New(\"patch updates cards that are limited\")\n\n\tErrInsufficientLicense = errors.New(\"appropriate license required\")\n\n\tErrCategoryPermissionDenied = errors.New(\"category doesn't belong to user\")\n\tErrCategoryDeleted          = errors.New(\"category is deleted\")\n\n\tErrBoardMemberIsLastAdmin = errors.New(\"cannot leave a board with no admins\")\n\n\tErrRequestEntityTooLarge = errors.New(\"request entity too large\")\n\n\tErrInvalidBoardSearchField = errors.New(\"invalid board search field\")\n)\n\n// ErrNotFound is an error type that can be returned by store APIs\n// when a query unexpectedly fetches no records.\ntype ErrNotFound struct {\n\tentity string\n}\n\n// NewErrNotFound creates a new ErrNotFound instance.\nfunc NewErrNotFound(entity string) *ErrNotFound {\n\treturn &ErrNotFound{\n\t\tentity: entity,\n\t}\n}\n\nfunc (nf *ErrNotFound) Error() string {\n\treturn fmt.Sprintf(\"{%s} not found\", nf.entity)\n}\n\n// ErrNotAllFound is an error type that can be returned by store APIs\n// when a query that should fetch a certain amount of records\n// unexpectedly fetches less.\ntype ErrNotAllFound struct {\n\tentity    string\n\tresources []string\n}\n\nfunc NewErrNotAllFound(entity string, resources []string) *ErrNotAllFound {\n\treturn &ErrNotAllFound{\n\t\tentity:    entity,\n\t\tresources: resources,\n\t}\n}\n\nfunc (naf *ErrNotAllFound) Error() string {\n\treturn fmt.Sprintf(\"not all instances of {%s} in {%s} found\", naf.entity, strings.Join(naf.resources, \", \"))\n}\n\n// ErrBadRequest can be returned when the API handler receives a\n// malformed request.\ntype ErrBadRequest struct {\n\treason string\n}\n\n// NewErrNotFound creates a new ErrNotFound instance.\nfunc NewErrBadRequest(reason string) *ErrBadRequest {\n\treturn &ErrBadRequest{\n\t\treason: reason,\n\t}\n}\n\nfunc (br *ErrBadRequest) Error() string {\n\treturn br.reason\n}\n\n// ErrUnauthorized can be returned when requester has provided an\n// invalid authorization for a given resource or has not provided any.\ntype ErrUnauthorized struct {\n\treason string\n}\n\n// NewErrUnauthorized creates a new ErrUnauthorized instance.\nfunc NewErrUnauthorized(reason string) *ErrUnauthorized {\n\treturn &ErrUnauthorized{\n\t\treason: reason,\n\t}\n}\n\nfunc (br *ErrUnauthorized) Error() string {\n\treturn br.reason\n}\n\n// ErrPermission can be returned when requester lacks a permission for\n// a given resource.\ntype ErrPermission struct {\n\treason string\n}\n\n// NewErrPermission creates a new ErrPermission instance.\nfunc NewErrPermission(reason string) *ErrPermission {\n\treturn &ErrPermission{\n\t\treason: reason,\n\t}\n}\n\nfunc (br *ErrPermission) Error() string {\n\treturn br.reason\n}\n\n// ErrForbidden can be returned when requester doesn't have access to\n// a given resource.\ntype ErrForbidden struct {\n\treason string\n}\n\n// NewErrForbidden creates a new ErrForbidden instance.\nfunc NewErrForbidden(reason string) *ErrForbidden {\n\treturn &ErrForbidden{\n\t\treason: reason,\n\t}\n}\n\nfunc (br *ErrForbidden) Error() string {\n\treturn br.reason\n}\n\ntype ErrInvalidCategory struct {\n\tmsg string\n}\n\nfunc NewErrInvalidCategory(msg string) *ErrInvalidCategory {\n\treturn &ErrInvalidCategory{\n\t\tmsg: msg,\n\t}\n}\n\nfunc (e *ErrInvalidCategory) Error() string {\n\treturn e.msg\n}\n\ntype ErrNotImplemented struct {\n\tmsg string\n}\n\nfunc NewErrNotImplemented(msg string) *ErrNotImplemented {\n\treturn &ErrNotImplemented{\n\t\tmsg: msg,\n\t}\n}\n\nfunc (ni *ErrNotImplemented) Error() string {\n\treturn ni.msg\n}\n\n// IsErrBadRequest returns true if `err` is or wraps one of:\n// - model.ErrBadRequest\n// - model.ErrViewsLimitReached\n// - model.ErrAuthParam\n// - model.ErrInvalidCategory\n// - model.ErrBoardMemberIsLastAdmin\n// - model.ErrBoardIDMismatch\n// - model.ErrBlockTitleSizeLimitExceeded\n// - model.ErrBlockFieldsSizeLimitExceeded.\nfunc IsErrBadRequest(err error) bool {\n\tif err == nil {\n\t\treturn false\n\t}\n\n\t// check if this is a model.ErrBadRequest\n\tvar br *ErrBadRequest\n\tif errors.As(err, &br) {\n\t\treturn true\n\t}\n\n\t// check if this is a model.ErrViewsLimitReached\n\tif errors.Is(err, ErrViewsLimitReached) {\n\t\treturn true\n\t}\n\n\t// check if this is a model.ErrAuthParam\n\tvar ap *ErrAuthParam\n\tif errors.As(err, &ap) {\n\t\treturn true\n\t}\n\n\t// check if this is a model.ErrInvalidCategory\n\tvar ic *ErrInvalidCategory\n\tif errors.As(err, &ic) {\n\t\treturn true\n\t}\n\n\t// check if this is a model.ErrBoardMemberIsLastAdmin\n\tif errors.Is(err, ErrBoardMemberIsLastAdmin) {\n\t\treturn true\n\t}\n\n\t// check if this is a model.ErrBoardIDMismatch\n\tif errors.Is(err, ErrBoardIDMismatch) {\n\t\treturn true\n\t}\n\n\t// check if this is a model.ErrBlockTitleSizeLimitExceeded\n\tif errors.Is(err, ErrBlockTitleSizeLimitExceeded) {\n\t\treturn true\n\t}\n\n\t// check if this is a model.ErrBlockTitleSizeLimitExceeded\n\treturn errors.Is(err, ErrBlockFieldsSizeLimitExceeded)\n}\n\n// IsErrUnauthorized returns true if `err` is or wraps one of:\n// - model.ErrUnauthorized.\nfunc IsErrUnauthorized(err error) bool {\n\tif err == nil {\n\t\treturn false\n\t}\n\n\t// check if this is a model.ErrUnauthorized\n\tvar u *ErrUnauthorized\n\treturn errors.As(err, &u)\n}\n\n// IsErrForbidden returns true if `err` is or wraps one of:\n// - model.ErrForbidden\n// - model.ErrPermission\n// - model.ErrPatchUpdatesLimitedCards\n// - model.ErrorCategoryPermissionDenied.\nfunc IsErrForbidden(err error) bool {\n\tif err == nil {\n\t\treturn false\n\t}\n\n\t// check if this is a model.ErrForbidden\n\tvar f *ErrForbidden\n\tif errors.As(err, &f) {\n\t\treturn true\n\t}\n\n\t// check if this is a model.ErrPermission\n\tvar p *ErrPermission\n\tif errors.As(err, &p) {\n\t\treturn true\n\t}\n\n\t// check if this is a model.ErrPatchUpdatesLimitedCards\n\tif errors.Is(err, ErrPatchUpdatesLimitedCards) {\n\t\treturn true\n\t}\n\n\t// check if this is a model.ErrCategoryPermissionDenied\n\treturn errors.Is(err, ErrCategoryPermissionDenied)\n}\n\n// IsErrNotFound returns true if `err` is or wraps one of:\n// - model.ErrNotFound\n// - model.ErrNotAllFound\n// - sql.ErrNoRows\n// - mattermost-plugin-api/ErrNotFound.\n// - model.ErrCategoryDeleted.\nfunc IsErrNotFound(err error) bool {\n\tif err == nil {\n\t\treturn false\n\t}\n\n\t// check if this is a model.ErrNotFound\n\tvar nf *ErrNotFound\n\tif errors.As(err, &nf) {\n\t\treturn true\n\t}\n\n\t// check if this is a model.ErrNotAllFound\n\tvar naf *ErrNotAllFound\n\tif errors.As(err, &naf) {\n\t\treturn true\n\t}\n\n\t// check if this is a sql.ErrNotFound\n\tif errors.Is(err, sql.ErrNoRows) {\n\t\treturn true\n\t}\n\n\t// check if this is a plugin API error\n\tif errors.Is(err, pluginapi.ErrNotFound) {\n\t\treturn true\n\t}\n\n\t// check if this is a Mattermost AppError with a Not Found status\n\tvar appErr *mmModel.AppError\n\tif errors.As(err, &appErr) {\n\t\tif appErr.StatusCode == http.StatusNotFound {\n\t\t\treturn true\n\t\t}\n\t}\n\n\t// check if this is a model.ErrCategoryDeleted\n\treturn errors.Is(err, ErrCategoryDeleted)\n}\n\n// IsErrRequestEntityTooLarge returns true if `err` is or wraps one of:\n// - model.ErrRequestEntityTooLarge.\nfunc IsErrRequestEntityTooLarge(err error) bool {\n\t// check if this is a model.ErrRequestEntityTooLarge\n\treturn errors.Is(err, ErrRequestEntityTooLarge)\n}\n\n// IsErrNotImplemented returns true if `err` is or wraps one of:\n// - model.ErrNotImplemented\n// - model.ErrInsufficientLicense.\nfunc IsErrNotImplemented(err error) bool {\n\tif err == nil {\n\t\treturn false\n\t}\n\n\t// check if this is a model.ErrNotImplemented\n\tvar eni *ErrNotImplemented\n\tif errors.As(err, &eni) {\n\t\treturn true\n\t}\n\n\t// check if this is a model.ErrInsufficientLicense\n\treturn errors.Is(err, ErrInsufficientLicense)\n}\n"
  },
  {
    "path": "server/model/errorResponse.go",
    "content": "package model\n\n// ErrorResponse is an error response\n// swagger:model\ntype ErrorResponse struct {\n\t// The error message\n\t// required: false\n\tError string `json:\"error\"`\n\n\t// The error code\n\t// required: false\n\tErrorCode int `json:\"errorCode\"`\n}\n"
  },
  {
    "path": "server/model/file.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\npackage model\n\nimport (\n\t\"mime\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/mattermost/focalboard/server/utils\"\n\tmm_model \"github.com/mattermost/mattermost/server/public/model\"\n)\n\nfunc NewFileInfo(name string) *mm_model.FileInfo {\n\textension := strings.ToLower(filepath.Ext(name))\n\tnow := utils.GetMillis()\n\treturn &mm_model.FileInfo{\n\t\tCreatorId: \"boards\",\n\t\tCreateAt:  now,\n\t\tUpdateAt:  now,\n\t\tName:      name,\n\t\tExtension: extension,\n\t\tMimeType:  mime.TypeByExtension(extension),\n\t}\n}\n"
  },
  {
    "path": "server/model/import_export.go",
    "content": "package model\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n)\n\nvar (\n\tErrInvalidImageBlock = errors.New(\"invalid image block\")\n)\n\n// Archive is an import / export archive.\n// TODO: remove once default templates are converted to new archive format.\ntype Archive struct {\n\tVersion int64   `json:\"version\"`\n\tDate    int64   `json:\"date\"`\n\tBlocks  []Block `json:\"blocks\"`\n}\n\n// ArchiveHeader is the content of the first file (`version.json`) within an archive.\ntype ArchiveHeader struct {\n\tVersion int   `json:\"version\"`\n\tDate    int64 `json:\"date\"`\n}\n\n// ArchiveLine is any line in an archive.\ntype ArchiveLine struct {\n\tType string          `json:\"type\"`\n\tData json.RawMessage `json:\"data\"`\n}\n\n// ExportArchiveOptions provides options when exporting one or more boards\n// to an archive.\ntype ExportArchiveOptions struct {\n\tTeamID string\n\n\t// BoardIDs is the list of boards to include in the archive.\n\t// Empty slice means export all boards from workspace/team.\n\tBoardIDs []string\n}\n\n// ImportArchiveOptions provides options when importing an archive.\ntype ImportArchiveOptions struct {\n\tTeamID        string\n\tModifiedBy    string\n\tBoardModifier BoardModifier\n\tBlockModifier BlockModifier\n}\n\n// ErrUnsupportedArchiveVersion is an error returned when trying to import an\n// archive with a version that this server does not support.\ntype ErrUnsupportedArchiveVersion struct {\n\tgot  int\n\twant int\n}\n\n// NewErrUnsupportedArchiveVersion creates a ErrUnsupportedArchiveVersion error.\nfunc NewErrUnsupportedArchiveVersion(got int, want int) ErrUnsupportedArchiveVersion {\n\treturn ErrUnsupportedArchiveVersion{\n\t\tgot:  got,\n\t\twant: want,\n\t}\n}\n\nfunc (e ErrUnsupportedArchiveVersion) Error() string {\n\treturn fmt.Sprintf(\"unsupported archive version; got %d, want %d\", e.got, e.want)\n}\n\n// ErrUnsupportedArchiveLineType is an error returned when trying to import an\n// archive containing an unsupported line type.\ntype ErrUnsupportedArchiveLineType struct {\n\tline int\n\tgot  string\n}\n\n// NewErrUnsupportedArchiveLineType creates a ErrUnsupportedArchiveLineType error.\nfunc NewErrUnsupportedArchiveLineType(line int, got string) ErrUnsupportedArchiveLineType {\n\treturn ErrUnsupportedArchiveLineType{\n\t\tline: line,\n\t\tgot:  got,\n\t}\n}\n\nfunc (e ErrUnsupportedArchiveLineType) Error() string {\n\treturn fmt.Sprintf(\"unsupported archive line type; got %s, line %d\", e.got, e.line)\n}\n"
  },
  {
    "path": "server/model/log_level.go",
    "content": "package model\n\nimport \"github.com/mattermost/logr/v2\"\n\nvar LvlFBTelemetry = logr.Level{\n\tID:   9000,\n\tName: \"telemetry\",\n}\n"
  },
  {
    "path": "server/model/mocks/mockservicesapi.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: github.com/mattermost/focalboard/server/model (interfaces: ServicesAPI)\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tsql \"database/sql\"\n\treflect \"reflect\"\n\n\tgomock \"github.com/golang/mock/gomock\"\n\tmux \"github.com/gorilla/mux\"\n\tmodel \"github.com/mattermost/mattermost/server/public/model\"\n\tmlog \"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\n// MockServicesAPI is a mock of ServicesAPI interface.\ntype MockServicesAPI struct {\n\tctrl     *gomock.Controller\n\trecorder *MockServicesAPIMockRecorder\n}\n\n// MockServicesAPIMockRecorder is the mock recorder for MockServicesAPI.\ntype MockServicesAPIMockRecorder struct {\n\tmock *MockServicesAPI\n}\n\n// NewMockServicesAPI creates a new mock instance.\nfunc NewMockServicesAPI(ctrl *gomock.Controller) *MockServicesAPI {\n\tmock := &MockServicesAPI{ctrl: ctrl}\n\tmock.recorder = &MockServicesAPIMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockServicesAPI) EXPECT() *MockServicesAPIMockRecorder {\n\treturn m.recorder\n}\n\n// CreateMember mocks base method.\nfunc (m *MockServicesAPI) CreateMember(arg0, arg1 string) (*model.TeamMember, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"CreateMember\", arg0, arg1)\n\tret0, _ := ret[0].(*model.TeamMember)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// CreateMember indicates an expected call of CreateMember.\nfunc (mr *MockServicesAPIMockRecorder) CreateMember(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"CreateMember\", reflect.TypeOf((*MockServicesAPI)(nil).CreateMember), arg0, arg1)\n}\n\n// CreatePost mocks base method.\nfunc (m *MockServicesAPI) CreatePost(arg0 *model.Post) (*model.Post, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"CreatePost\", arg0)\n\tret0, _ := ret[0].(*model.Post)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// CreatePost indicates an expected call of CreatePost.\nfunc (mr *MockServicesAPIMockRecorder) CreatePost(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"CreatePost\", reflect.TypeOf((*MockServicesAPI)(nil).CreatePost), arg0)\n}\n\n// DeletePreferencesForUser mocks base method.\nfunc (m *MockServicesAPI) DeletePreferencesForUser(arg0 string, arg1 model.Preferences) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"DeletePreferencesForUser\", arg0, arg1)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// DeletePreferencesForUser indicates an expected call of DeletePreferencesForUser.\nfunc (mr *MockServicesAPIMockRecorder) DeletePreferencesForUser(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"DeletePreferencesForUser\", reflect.TypeOf((*MockServicesAPI)(nil).DeletePreferencesForUser), arg0, arg1)\n}\n\n// EnsureBot mocks base method.\nfunc (m *MockServicesAPI) EnsureBot(arg0 *model.Bot) (string, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"EnsureBot\", arg0)\n\tret0, _ := ret[0].(string)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// EnsureBot indicates an expected call of EnsureBot.\nfunc (mr *MockServicesAPIMockRecorder) EnsureBot(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"EnsureBot\", reflect.TypeOf((*MockServicesAPI)(nil).EnsureBot), arg0)\n}\n\n// GetChannelByID mocks base method.\nfunc (m *MockServicesAPI) GetChannelByID(arg0 string) (*model.Channel, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetChannelByID\", arg0)\n\tret0, _ := ret[0].(*model.Channel)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetChannelByID indicates an expected call of GetChannelByID.\nfunc (mr *MockServicesAPIMockRecorder) GetChannelByID(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetChannelByID\", reflect.TypeOf((*MockServicesAPI)(nil).GetChannelByID), arg0)\n}\n\n// GetChannelMember mocks base method.\nfunc (m *MockServicesAPI) GetChannelMember(arg0, arg1 string) (*model.ChannelMember, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetChannelMember\", arg0, arg1)\n\tret0, _ := ret[0].(*model.ChannelMember)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetChannelMember indicates an expected call of GetChannelMember.\nfunc (mr *MockServicesAPIMockRecorder) GetChannelMember(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetChannelMember\", reflect.TypeOf((*MockServicesAPI)(nil).GetChannelMember), arg0, arg1)\n}\n\n// GetChannelsForTeamForUser mocks base method.\nfunc (m *MockServicesAPI) GetChannelsForTeamForUser(arg0, arg1 string, arg2 bool) (model.ChannelList, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetChannelsForTeamForUser\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(model.ChannelList)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetChannelsForTeamForUser indicates an expected call of GetChannelsForTeamForUser.\nfunc (mr *MockServicesAPIMockRecorder) GetChannelsForTeamForUser(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetChannelsForTeamForUser\", reflect.TypeOf((*MockServicesAPI)(nil).GetChannelsForTeamForUser), arg0, arg1, arg2)\n}\n\n// GetConfig mocks base method.\nfunc (m *MockServicesAPI) GetConfig() *model.Config {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetConfig\")\n\tret0, _ := ret[0].(*model.Config)\n\treturn ret0\n}\n\n// GetConfig indicates an expected call of GetConfig.\nfunc (mr *MockServicesAPIMockRecorder) GetConfig() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetConfig\", reflect.TypeOf((*MockServicesAPI)(nil).GetConfig))\n}\n\n// GetDiagnosticID mocks base method.\nfunc (m *MockServicesAPI) GetDiagnosticID() string {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetDiagnosticID\")\n\tret0, _ := ret[0].(string)\n\treturn ret0\n}\n\n// GetDiagnosticID indicates an expected call of GetDiagnosticID.\nfunc (mr *MockServicesAPIMockRecorder) GetDiagnosticID() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetDiagnosticID\", reflect.TypeOf((*MockServicesAPI)(nil).GetDiagnosticID))\n}\n\n// GetDirectChannel mocks base method.\nfunc (m *MockServicesAPI) GetDirectChannel(arg0, arg1 string) (*model.Channel, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetDirectChannel\", arg0, arg1)\n\tret0, _ := ret[0].(*model.Channel)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetDirectChannel indicates an expected call of GetDirectChannel.\nfunc (mr *MockServicesAPIMockRecorder) GetDirectChannel(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetDirectChannel\", reflect.TypeOf((*MockServicesAPI)(nil).GetDirectChannel), arg0, arg1)\n}\n\n// GetDirectChannelOrCreate mocks base method.\nfunc (m *MockServicesAPI) GetDirectChannelOrCreate(arg0, arg1 string) (*model.Channel, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetDirectChannelOrCreate\", arg0, arg1)\n\tret0, _ := ret[0].(*model.Channel)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetDirectChannelOrCreate indicates an expected call of GetDirectChannelOrCreate.\nfunc (mr *MockServicesAPIMockRecorder) GetDirectChannelOrCreate(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetDirectChannelOrCreate\", reflect.TypeOf((*MockServicesAPI)(nil).GetDirectChannelOrCreate), arg0, arg1)\n}\n\n// GetFileInfo mocks base method.\nfunc (m *MockServicesAPI) GetFileInfo(arg0 string) (*model.FileInfo, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetFileInfo\", arg0)\n\tret0, _ := ret[0].(*model.FileInfo)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetFileInfo indicates an expected call of GetFileInfo.\nfunc (mr *MockServicesAPIMockRecorder) GetFileInfo(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetFileInfo\", reflect.TypeOf((*MockServicesAPI)(nil).GetFileInfo), arg0)\n}\n\n// GetLicense mocks base method.\nfunc (m *MockServicesAPI) GetLicense() *model.License {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetLicense\")\n\tret0, _ := ret[0].(*model.License)\n\treturn ret0\n}\n\n// GetLicense indicates an expected call of GetLicense.\nfunc (mr *MockServicesAPIMockRecorder) GetLicense() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetLicense\", reflect.TypeOf((*MockServicesAPI)(nil).GetLicense))\n}\n\n// GetLogger mocks base method.\nfunc (m *MockServicesAPI) GetLogger() mlog.LoggerIFace {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetLogger\")\n\tret0, _ := ret[0].(mlog.LoggerIFace)\n\treturn ret0\n}\n\n// GetLogger indicates an expected call of GetLogger.\nfunc (mr *MockServicesAPIMockRecorder) GetLogger() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetLogger\", reflect.TypeOf((*MockServicesAPI)(nil).GetLogger))\n}\n\n// GetMasterDB mocks base method.\nfunc (m *MockServicesAPI) GetMasterDB() (*sql.DB, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetMasterDB\")\n\tret0, _ := ret[0].(*sql.DB)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetMasterDB indicates an expected call of GetMasterDB.\nfunc (mr *MockServicesAPIMockRecorder) GetMasterDB() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetMasterDB\", reflect.TypeOf((*MockServicesAPI)(nil).GetMasterDB))\n}\n\n// GetPreferencesForUser mocks base method.\nfunc (m *MockServicesAPI) GetPreferencesForUser(arg0 string) (model.Preferences, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetPreferencesForUser\", arg0)\n\tret0, _ := ret[0].(model.Preferences)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetPreferencesForUser indicates an expected call of GetPreferencesForUser.\nfunc (mr *MockServicesAPIMockRecorder) GetPreferencesForUser(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetPreferencesForUser\", reflect.TypeOf((*MockServicesAPI)(nil).GetPreferencesForUser), arg0)\n}\n\n// GetTeamMember mocks base method.\nfunc (m *MockServicesAPI) GetTeamMember(arg0, arg1 string) (*model.TeamMember, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetTeamMember\", arg0, arg1)\n\tret0, _ := ret[0].(*model.TeamMember)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetTeamMember indicates an expected call of GetTeamMember.\nfunc (mr *MockServicesAPIMockRecorder) GetTeamMember(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetTeamMember\", reflect.TypeOf((*MockServicesAPI)(nil).GetTeamMember), arg0, arg1)\n}\n\n// GetUserByEmail mocks base method.\nfunc (m *MockServicesAPI) GetUserByEmail(arg0 string) (*model.User, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetUserByEmail\", arg0)\n\tret0, _ := ret[0].(*model.User)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetUserByEmail indicates an expected call of GetUserByEmail.\nfunc (mr *MockServicesAPIMockRecorder) GetUserByEmail(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetUserByEmail\", reflect.TypeOf((*MockServicesAPI)(nil).GetUserByEmail), arg0)\n}\n\n// GetUserByID mocks base method.\nfunc (m *MockServicesAPI) GetUserByID(arg0 string) (*model.User, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetUserByID\", arg0)\n\tret0, _ := ret[0].(*model.User)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetUserByID indicates an expected call of GetUserByID.\nfunc (mr *MockServicesAPIMockRecorder) GetUserByID(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetUserByID\", reflect.TypeOf((*MockServicesAPI)(nil).GetUserByID), arg0)\n}\n\n// GetUserByUsername mocks base method.\nfunc (m *MockServicesAPI) GetUserByUsername(arg0 string) (*model.User, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetUserByUsername\", arg0)\n\tret0, _ := ret[0].(*model.User)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetUserByUsername indicates an expected call of GetUserByUsername.\nfunc (mr *MockServicesAPIMockRecorder) GetUserByUsername(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetUserByUsername\", reflect.TypeOf((*MockServicesAPI)(nil).GetUserByUsername), arg0)\n}\n\n// GetUsersFromProfiles mocks base method.\nfunc (m *MockServicesAPI) GetUsersFromProfiles(arg0 *model.UserGetOptions) ([]*model.User, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetUsersFromProfiles\", arg0)\n\tret0, _ := ret[0].([]*model.User)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetUsersFromProfiles indicates an expected call of GetUsersFromProfiles.\nfunc (mr *MockServicesAPIMockRecorder) GetUsersFromProfiles(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetUsersFromProfiles\", reflect.TypeOf((*MockServicesAPI)(nil).GetUsersFromProfiles), arg0)\n}\n\n// HasPermissionTo mocks base method.\nfunc (m *MockServicesAPI) HasPermissionTo(arg0 string, arg1 *model.Permission) bool {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"HasPermissionTo\", arg0, arg1)\n\tret0, _ := ret[0].(bool)\n\treturn ret0\n}\n\n// HasPermissionTo indicates an expected call of HasPermissionTo.\nfunc (mr *MockServicesAPIMockRecorder) HasPermissionTo(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"HasPermissionTo\", reflect.TypeOf((*MockServicesAPI)(nil).HasPermissionTo), arg0, arg1)\n}\n\n// HasPermissionToChannel mocks base method.\nfunc (m *MockServicesAPI) HasPermissionToChannel(arg0, arg1 string, arg2 *model.Permission) bool {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"HasPermissionToChannel\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(bool)\n\treturn ret0\n}\n\n// HasPermissionToChannel indicates an expected call of HasPermissionToChannel.\nfunc (mr *MockServicesAPIMockRecorder) HasPermissionToChannel(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"HasPermissionToChannel\", reflect.TypeOf((*MockServicesAPI)(nil).HasPermissionToChannel), arg0, arg1, arg2)\n}\n\n// HasPermissionToTeam mocks base method.\nfunc (m *MockServicesAPI) HasPermissionToTeam(arg0, arg1 string, arg2 *model.Permission) bool {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"HasPermissionToTeam\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(bool)\n\treturn ret0\n}\n\n// HasPermissionToTeam indicates an expected call of HasPermissionToTeam.\nfunc (mr *MockServicesAPIMockRecorder) HasPermissionToTeam(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"HasPermissionToTeam\", reflect.TypeOf((*MockServicesAPI)(nil).HasPermissionToTeam), arg0, arg1, arg2)\n}\n\n// KVSetWithOptions mocks base method.\nfunc (m *MockServicesAPI) KVSetWithOptions(arg0 string, arg1 []byte, arg2 model.PluginKVSetOptions) (bool, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"KVSetWithOptions\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(bool)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// KVSetWithOptions indicates an expected call of KVSetWithOptions.\nfunc (mr *MockServicesAPIMockRecorder) KVSetWithOptions(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"KVSetWithOptions\", reflect.TypeOf((*MockServicesAPI)(nil).KVSetWithOptions), arg0, arg1, arg2)\n}\n\n// PublishPluginClusterEvent mocks base method.\nfunc (m *MockServicesAPI) PublishPluginClusterEvent(arg0 model.PluginClusterEvent, arg1 model.PluginClusterEventSendOptions) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"PublishPluginClusterEvent\", arg0, arg1)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// PublishPluginClusterEvent indicates an expected call of PublishPluginClusterEvent.\nfunc (mr *MockServicesAPIMockRecorder) PublishPluginClusterEvent(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"PublishPluginClusterEvent\", reflect.TypeOf((*MockServicesAPI)(nil).PublishPluginClusterEvent), arg0, arg1)\n}\n\n// PublishWebSocketEvent mocks base method.\nfunc (m *MockServicesAPI) PublishWebSocketEvent(arg0 string, arg1 map[string]interface{}, arg2 *model.WebsocketBroadcast) {\n\tm.ctrl.T.Helper()\n\tm.ctrl.Call(m, \"PublishWebSocketEvent\", arg0, arg1, arg2)\n}\n\n// PublishWebSocketEvent indicates an expected call of PublishWebSocketEvent.\nfunc (mr *MockServicesAPIMockRecorder) PublishWebSocketEvent(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"PublishWebSocketEvent\", reflect.TypeOf((*MockServicesAPI)(nil).PublishWebSocketEvent), arg0, arg1, arg2)\n}\n\n// RegisterRouter mocks base method.\nfunc (m *MockServicesAPI) RegisterRouter(arg0 *mux.Router) {\n\tm.ctrl.T.Helper()\n\tm.ctrl.Call(m, \"RegisterRouter\", arg0)\n}\n\n// RegisterRouter indicates an expected call of RegisterRouter.\nfunc (mr *MockServicesAPIMockRecorder) RegisterRouter(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"RegisterRouter\", reflect.TypeOf((*MockServicesAPI)(nil).RegisterRouter), arg0)\n}\n\n// UpdatePreferencesForUser mocks base method.\nfunc (m *MockServicesAPI) UpdatePreferencesForUser(arg0 string, arg1 model.Preferences) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdatePreferencesForUser\", arg0, arg1)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// UpdatePreferencesForUser indicates an expected call of UpdatePreferencesForUser.\nfunc (mr *MockServicesAPIMockRecorder) UpdatePreferencesForUser(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdatePreferencesForUser\", reflect.TypeOf((*MockServicesAPI)(nil).UpdatePreferencesForUser), arg0, arg1)\n}\n\n// UpdateUser mocks base method.\nfunc (m *MockServicesAPI) UpdateUser(arg0 *model.User) (*model.User, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdateUser\", arg0)\n\tret0, _ := ret[0].(*model.User)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// UpdateUser indicates an expected call of UpdateUser.\nfunc (mr *MockServicesAPIMockRecorder) UpdateUser(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdateUser\", reflect.TypeOf((*MockServicesAPI)(nil).UpdateUser), arg0)\n}\n"
  },
  {
    "path": "server/model/mocks/propValueResolverMock.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: github.com/mattermost/focalboard/server/model (interfaces: PropValueResolver)\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\treflect \"reflect\"\n\n\tgomock \"github.com/golang/mock/gomock\"\n\tmodel \"github.com/mattermost/focalboard/server/model\"\n)\n\n// MockPropValueResolver is a mock of PropValueResolver interface.\ntype MockPropValueResolver struct {\n\tctrl     *gomock.Controller\n\trecorder *MockPropValueResolverMockRecorder\n}\n\n// MockPropValueResolverMockRecorder is the mock recorder for MockPropValueResolver.\ntype MockPropValueResolverMockRecorder struct {\n\tmock *MockPropValueResolver\n}\n\n// NewMockPropValueResolver creates a new mock instance.\nfunc NewMockPropValueResolver(ctrl *gomock.Controller) *MockPropValueResolver {\n\tmock := &MockPropValueResolver{ctrl: ctrl}\n\tmock.recorder = &MockPropValueResolverMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockPropValueResolver) EXPECT() *MockPropValueResolverMockRecorder {\n\treturn m.recorder\n}\n\n// GetUserByID mocks base method.\nfunc (m *MockPropValueResolver) GetUserByID(arg0 string) (*model.User, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetUserByID\", arg0)\n\tret0, _ := ret[0].(*model.User)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetUserByID indicates an expected call of GetUserByID.\nfunc (mr *MockPropValueResolverMockRecorder) GetUserByID(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetUserByID\", reflect.TypeOf((*MockPropValueResolver)(nil).GetUserByID), arg0)\n}\n"
  },
  {
    "path": "server/model/notification.go",
    "content": "package model\n\nimport (\n\t\"time\"\n\n\t\"github.com/mattermost/mattermost/server/v8/channels/utils\"\n)\n\n// NotificationHint provides a hint that a block has been modified and has subscribers that\n// should be notified.\n// swagger:model\ntype NotificationHint struct {\n\t// BlockType is the block type of the entity (e.g. board, card) that was updated\n\t// required: true\n\tBlockType BlockType `json:\"block_type\"`\n\n\t// BlockID is id of the entity that was updated\n\t// required: true\n\tBlockID string `json:\"block_id\"`\n\n\t// ModifiedByID is the id of the user who made the block change\n\tModifiedByID string `json:\"modified_by_id\"`\n\n\t// CreatedAt is the timestamp this notification hint was created in miliseconds since the current epoch\n\t// required: true\n\tCreateAt int64 `json:\"create_at\"`\n\n\t// NotifyAt is the timestamp this notification should be scheduled in miliseconds since the current epoch\n\t// required: true\n\tNotifyAt int64 `json:\"notify_at\"`\n}\n\nfunc (s *NotificationHint) IsValid() error {\n\tif s == nil {\n\t\treturn ErrInvalidNotificationHint{\"cannot be nil\"}\n\t}\n\tif s.BlockID == \"\" {\n\t\treturn ErrInvalidNotificationHint{\"missing block id\"}\n\t}\n\tif s.BlockType == \"\" {\n\t\treturn ErrInvalidNotificationHint{\"missing block type\"}\n\t}\n\tif s.ModifiedByID == \"\" {\n\t\treturn ErrInvalidNotificationHint{\"missing modified_by id\"}\n\t}\n\treturn nil\n}\n\nfunc (s *NotificationHint) Copy() *NotificationHint {\n\treturn &NotificationHint{\n\t\tBlockType:    s.BlockType,\n\t\tBlockID:      s.BlockID,\n\t\tModifiedByID: s.ModifiedByID,\n\t\tCreateAt:     s.CreateAt,\n\t\tNotifyAt:     s.NotifyAt,\n\t}\n}\n\nfunc (s *NotificationHint) LogClone() interface{} {\n\treturn struct {\n\t\tBlockType    BlockType `json:\"block_type\"`\n\t\tBlockID      string    `json:\"block_id\"`\n\t\tModifiedByID string    `json:\"modified_by_id\"`\n\t\tCreateAt     string    `json:\"create_at\"`\n\t\tNotifyAt     string    `json:\"notify_at\"`\n\t}{\n\t\tBlockType:    s.BlockType,\n\t\tBlockID:      s.BlockID,\n\t\tModifiedByID: s.ModifiedByID,\n\t\tCreateAt:     utils.TimeFromMillis(s.CreateAt).Format(time.StampMilli),\n\t\tNotifyAt:     utils.TimeFromMillis(s.NotifyAt).Format(time.StampMilli),\n\t}\n}\n\ntype ErrInvalidNotificationHint struct {\n\tmsg string\n}\n\nfunc (e ErrInvalidNotificationHint) Error() string {\n\treturn e.msg\n}\n"
  },
  {
    "path": "server/model/permission.go",
    "content": "package model\n\nimport (\n\tmmModel \"github.com/mattermost/mattermost/server/public/model\"\n)\n\nvar (\n\tPermissionViewTeam              = mmModel.PermissionViewTeam\n\tPermissionManageTeam            = mmModel.PermissionManageTeam\n\tPermissionManageSystem          = mmModel.PermissionManageSystem\n\tPermissionReadChannel           = mmModel.PermissionReadChannel\n\tPermissionCreatePost            = mmModel.PermissionCreatePost\n\tPermissionViewMembers           = mmModel.PermissionViewMembers\n\tPermissionCreatePublicChannel   = mmModel.PermissionCreatePublicChannel\n\tPermissionCreatePrivateChannel  = mmModel.PermissionCreatePrivateChannel\n\tPermissionManageBoardType       = &mmModel.Permission{Id: \"manage_board_type\", Name: \"\", Description: \"\", Scope: \"\"}\n\tPermissionDeleteBoard           = &mmModel.Permission{Id: \"delete_board\", Name: \"\", Description: \"\", Scope: \"\"}\n\tPermissionViewBoard             = &mmModel.Permission{Id: \"view_board\", Name: \"\", Description: \"\", Scope: \"\"}\n\tPermissionManageBoardRoles      = &mmModel.Permission{Id: \"manage_board_roles\", Name: \"\", Description: \"\", Scope: \"\"}\n\tPermissionShareBoard            = &mmModel.Permission{Id: \"share_board\", Name: \"\", Description: \"\", Scope: \"\"}\n\tPermissionManageBoardCards      = &mmModel.Permission{Id: \"manage_board_cards\", Name: \"\", Description: \"\", Scope: \"\"}\n\tPermissionManageBoardProperties = &mmModel.Permission{Id: \"manage_board_properties\", Name: \"\", Description: \"\", Scope: \"\"}\n\tPermissionCommentBoardCards     = &mmModel.Permission{Id: \"comment_board_cards\", Name: \"\", Description: \"\", Scope: \"\"}\n\tPermissionDeleteOthersComments  = &mmModel.Permission{Id: \"delete_others_comments\", Name: \"\", Description: \"\", Scope: \"\"}\n)\n"
  },
  {
    "path": "server/model/properties.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\n//go:generate mockgen -destination=mocks/propValueResolverMock.go -package mocks . PropValueResolver\n\npackage model\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/mattermost/focalboard/server/utils\"\n)\n\nvar ErrInvalidBoardBlock = errors.New(\"invalid board block\")\nvar ErrInvalidPropSchema = errors.New(\"invalid property schema\")\nvar ErrInvalidProperty = errors.New(\"invalid property\")\nvar ErrInvalidPropertyValue = errors.New(\"invalid property value\")\nvar ErrInvalidPropertyValueType = errors.New(\"invalid property value type\")\nvar ErrInvalidDate = errors.New(\"invalid date property\")\n\n// PropValueResolver allows PropDef.GetValue to further decode property values, such as\n// looking up usernames from ids.\ntype PropValueResolver interface {\n\tGetUserByID(userID string) (*User, error)\n}\n\n// BlockProperties is a map of Prop's keyed by property id.\ntype BlockProperties map[string]BlockProp\n\n// BlockProp represent a property attached to a block (typically a card).\ntype BlockProp struct {\n\tID    string `json:\"id\"`\n\tIndex int    `json:\"index\"`\n\tName  string `json:\"name\"`\n\tValue string `json:\"value\"`\n}\n\n// PropSchema is a map of PropDef's keyed by property id.\ntype PropSchema map[string]PropDef\n\n// PropDefOption represents an option within a property definition.\ntype PropDefOption struct {\n\tID    string `json:\"id\"`\n\tIndex int    `json:\"index\"`\n\tColor string `json:\"color\"`\n\tValue string `json:\"value\"`\n}\n\n// PropDef represents a property definition as defined in a board's Fields member.\ntype PropDef struct {\n\tID      string                   `json:\"id\"`\n\tIndex   int                      `json:\"index\"`\n\tName    string                   `json:\"name\"`\n\tType    string                   `json:\"type\"`\n\tOptions map[string]PropDefOption `json:\"options\"`\n}\n\n// GetValue resolves the value of a property if the passed value is an ID for an option,\n// otherwise returns the original value.\nfunc (pd PropDef) GetValue(v interface{}, resolver PropValueResolver) (string, error) {\n\tswitch pd.Type {\n\tcase \"select\":\n\t\t// v is the id of an option\n\t\tid, ok := v.(string)\n\t\tif !ok {\n\t\t\treturn \"\", ErrInvalidPropertyValueType\n\t\t}\n\t\topt, ok := pd.Options[id]\n\t\tif !ok {\n\t\t\treturn \"\", ErrInvalidPropertyValue\n\t\t}\n\t\treturn strings.ToUpper(opt.Value), nil\n\n\tcase \"date\":\n\t\t// v is a JSON string\n\t\tdate, ok := v.(string)\n\t\tif !ok {\n\t\t\treturn \"\", ErrInvalidPropertyValueType\n\t\t}\n\t\treturn pd.ParseDate(date)\n\n\tcase \"person\":\n\t\t// v is a userid\n\t\tuserID, ok := v.(string)\n\t\tif !ok {\n\t\t\treturn \"\", ErrInvalidPropertyValueType\n\t\t}\n\t\tif resolver != nil {\n\t\t\tuser, err := resolver.GetUserByID(userID)\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", err\n\t\t\t}\n\t\t\tif user == nil {\n\t\t\t\treturn userID, nil\n\t\t\t}\n\t\t\treturn user.Username, nil\n\t\t}\n\t\treturn userID, nil\n\n\tcase \"multiPerson\":\n\t\t// v is a slice of user IDs\n\t\tuserIDs, ok := v.([]interface{})\n\t\tif !ok {\n\t\t\treturn \"\", fmt.Errorf(\"multiPerson property type: %w\", ErrInvalidPropertyValueType)\n\t\t}\n\t\tif resolver != nil {\n\t\t\tusernames := make([]string, len(userIDs))\n\n\t\t\tfor i, userIDInterface := range userIDs {\n\t\t\t\tuserID := userIDInterface.(string)\n\n\t\t\t\tuser, err := resolver.GetUserByID(userID)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn \"\", err\n\t\t\t\t}\n\t\t\t\tif user == nil {\n\t\t\t\t\tusernames[i] = userID\n\t\t\t\t} else {\n\t\t\t\t\tusernames[i] = user.Username\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn strings.Join(usernames, \", \"), nil\n\t\t}\n\n\tcase \"multiSelect\":\n\t\t// v is a slice of strings containing option ids\n\t\tms, ok := v.([]interface{})\n\t\tif !ok {\n\t\t\treturn \"\", ErrInvalidPropertyValueType\n\t\t}\n\t\tvar sb strings.Builder\n\t\tprefix := \"\"\n\t\tfor _, optid := range ms {\n\t\t\tid, ok := optid.(string)\n\t\t\tif !ok {\n\t\t\t\treturn \"\", ErrInvalidPropertyValueType\n\t\t\t}\n\t\t\topt, ok := pd.Options[id]\n\t\t\tif !ok {\n\t\t\t\treturn \"\", ErrInvalidPropertyValue\n\t\t\t}\n\t\t\tsb.WriteString(prefix)\n\t\t\tprefix = \", \"\n\t\t\tsb.WriteString(strings.ToUpper(opt.Value))\n\t\t}\n\t\treturn sb.String(), nil\n\t}\n\treturn fmt.Sprintf(\"%v\", v), nil\n}\n\nfunc (pd PropDef) ParseDate(s string) (string, error) {\n\t// s is a JSON snippet of the form: {\"from\":1642161600000, \"to\":1642161600000} in milliseconds UTC\n\t// The UI does not yet support date ranges.\n\tvar m map[string]int64\n\tif err := json.Unmarshal([]byte(s), &m); err != nil {\n\t\treturn s, err\n\t}\n\ttsFrom, ok := m[\"from\"]\n\tif !ok {\n\t\treturn s, ErrInvalidDate\n\t}\n\tdate := utils.GetTimeForMillis(tsFrom).Format(\"January 02, 2006\")\n\ttsTo, ok := m[\"to\"]\n\tif ok {\n\t\tdate += \" -> \" + utils.GetTimeForMillis(tsTo).Format(\"January 02, 2006\")\n\t}\n\treturn date, nil\n}\n\n// ParsePropertySchema parses a board block's `Fields` to extract the properties\n// schema for all cards within the board.\n// The result is provided as a map for quick lookup, and the original order is\n// preserved via the `Index` field.\nfunc ParsePropertySchema(board *Board) (PropSchema, error) {\n\tschema := make(map[string]PropDef)\n\n\tfor i, prop := range board.CardProperties {\n\t\tpd := PropDef{\n\t\t\tID:      getMapString(\"id\", prop),\n\t\t\tIndex:   i,\n\t\t\tName:    getMapString(\"name\", prop),\n\t\t\tType:    getMapString(\"type\", prop),\n\t\t\tOptions: make(map[string]PropDefOption),\n\t\t}\n\t\toptsIface, ok := prop[\"options\"]\n\t\tif ok {\n\t\t\topts, ok := optsIface.([]interface{})\n\t\t\tif !ok {\n\t\t\t\treturn nil, ErrInvalidPropSchema\n\t\t\t}\n\t\t\tfor j, propOptIface := range opts {\n\t\t\t\tpropOpt, ok := propOptIface.(map[string]interface{})\n\t\t\t\tif !ok {\n\t\t\t\t\treturn nil, ErrInvalidPropSchema\n\t\t\t\t}\n\t\t\t\tpo := PropDefOption{\n\t\t\t\t\tID:    getMapString(\"id\", propOpt),\n\t\t\t\t\tIndex: j,\n\t\t\t\t\tValue: getMapString(\"value\", propOpt),\n\t\t\t\t\tColor: getMapString(\"color\", propOpt),\n\t\t\t\t}\n\t\t\t\tpd.Options[po.ID] = po\n\t\t\t}\n\t\t}\n\t\tschema[pd.ID] = pd\n\t}\n\treturn schema, nil\n}\n\nfunc getMapString(key string, m map[string]interface{}) string {\n\tiface, ok := m[key]\n\tif !ok {\n\t\treturn \"\"\n\t}\n\n\ts, ok := iface.(string)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn s\n}\n\n// ParseProperties parses a block's `Fields` to extract the properties. Properties typically exist on\n// card blocks.  A resolver can optionally be provided to fetch usernames for `person` prop type.\nfunc ParseProperties(block *Block, schema PropSchema, resolver PropValueResolver) (BlockProperties, error) {\n\tprops := make(map[string]BlockProp)\n\n\tif block == nil {\n\t\treturn props, nil\n\t}\n\n\t// `properties` contains a map (untyped at this point).\n\tpropsIface, ok := block.Fields[\"properties\"]\n\tif !ok {\n\t\treturn props, nil // this is expected for blocks that don't have any properties.\n\t}\n\n\tblockProps, ok := propsIface.(map[string]interface{})\n\tif !ok {\n\t\treturn props, fmt.Errorf(\"`properties` field wrong type: %w\", ErrInvalidProperty)\n\t}\n\n\tif len(blockProps) == 0 {\n\t\treturn props, nil\n\t}\n\n\tfor k, v := range blockProps {\n\t\ts := fmt.Sprintf(\"%v\", v)\n\n\t\tprop := BlockProp{\n\t\t\tID:    k,\n\t\t\tName:  k,\n\t\t\tValue: s,\n\t\t}\n\n\t\tdef, ok := schema[k]\n\t\tif ok {\n\t\t\tval, err := def.GetValue(v, resolver)\n\t\t\tif err != nil {\n\t\t\t\treturn props, fmt.Errorf(\"could not parse property value (%s): %w\", fmt.Sprintf(\"%v\", v), err)\n\t\t\t}\n\t\t\tprop.Name = def.Name\n\t\t\tprop.Value = val\n\t\t\tprop.Index = def.Index\n\t\t}\n\t\tprops[k] = prop\n\t}\n\treturn props, nil\n}\n"
  },
  {
    "path": "server/model/properties_test.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage model\n\nimport (\n\t\"encoding/json\"\n\t\"testing\"\n\n\t\"github.com/mattermost/focalboard/server/utils\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype MockResolver struct{}\n\nfunc (r MockResolver) GetUserByID(userID string) (*User, error) {\n\tif userID == \"user_id_1\" {\n\t\treturn &User{\n\t\t\tID:       \"user_id_1\",\n\t\t\tUsername: \"username_1\",\n\t\t}, nil\n\t} else if userID == \"user_id_2\" {\n\t\treturn &User{\n\t\t\tID:       \"user_id_2\",\n\t\t\tUsername: \"username_2\",\n\t\t}, nil\n\t}\n\n\treturn nil, nil\n}\n\nfunc Test_parsePropertySchema(t *testing.T) {\n\tboard := &Board{\n\t\tID:     utils.NewID(utils.IDTypeBoard),\n\t\tTitle:  \"Test Board\",\n\t\tTeamID: utils.NewID(utils.IDTypeTeam),\n\t}\n\n\terr := json.Unmarshal([]byte(cardPropertiesExample), &board.CardProperties)\n\trequire.NoError(t, err)\n\n\tt.Run(\"parse schema\", func(t *testing.T) {\n\t\tschema, err := ParsePropertySchema(board)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Len(t, schema, 6)\n\n\t\tprop, ok := schema[\"7c212e78-9345-4c60-81b5-0b0e37ce463f\"]\n\t\trequire.True(t, ok)\n\n\t\tassert.Equal(t, \"select\", prop.Type)\n\t\tassert.Equal(t, \"Type\", prop.Name)\n\t\tassert.Len(t, prop.Options, 3)\n\n\t\tprop, ok = schema[\"a8spou7if43eo1rqzb9qeq488so\"]\n\t\trequire.True(t, ok)\n\n\t\tassert.Equal(t, \"date\", prop.Type)\n\t\tassert.Equal(t, \"MyDate\", prop.Name)\n\t\tassert.Empty(t, prop.Options)\n\t})\n}\n\nfunc Test_GetValue(t *testing.T) {\n\tresolver := MockResolver{}\n\n\tpropDef := PropDef{\n\t\tType: \"multiPerson\",\n\t}\n\n\tvalue, err := propDef.GetValue([]interface{}{\"user_id_1\", \"user_id_2\"}, resolver)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"username_1, username_2\", value)\n\n\t// trying with only user\n\tvalue, err = propDef.GetValue([]interface{}{\"user_id_1\"}, resolver)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"username_1\", value)\n\n\t// trying with unknown user\n\tvalue, err = propDef.GetValue([]interface{}{\"user_id_1\", \"user_id_unknown\"}, resolver)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"username_1, user_id_unknown\", value)\n\n\t// trying with multiple unknown users\n\tvalue, err = propDef.GetValue([]interface{}{\"michael_scott\", \"jim_halpert\"}, resolver)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"michael_scott, jim_halpert\", value)\n}\n\nconst (\n\tcardPropertiesExample = `[\n\t   {\n\t\t  \"id\":\"7c212e78-9345-4c60-81b5-0b0e37ce463f\",\n\t\t  \"name\":\"Type\",\n\t\t  \"options\":[\n\t\t\t {\n\t\t\t\t\"color\":\"propColorYellow\",\n\t\t\t\t\"id\":\"31da50ca-f1a9-4d21-8636-17dc387c1a23\",\n\t\t\t\t\"value\":\"Ad Hoc\"\n\t\t\t },\n\t\t\t {\n\t\t\t\t\"color\":\"propColorBlue\",\n\t\t\t\t\"id\":\"def6317c-ec11-410d-8a6b-ea461320f392\",\n\t\t\t\t\"value\":\"Standup\"\n\t\t\t },\n\t\t\t {\n\t\t\t\t\"color\":\"propColorPurple\",\n\t\t\t\t\"id\":\"700f83f8-6a41-46cd-87e2-53e0d0b12cc7\",\n\t\t\t\t\"value\":\"Weekly Sync\"\n\t\t\t }\n\t\t  ],\n\t\t  \"type\":\"select\"\n\t   },\n\t   {\n\t\t  \"id\":\"13d2394a-eb5e-4f22-8c22-6515ec41c4a4\",\n\t\t  \"name\":\"Summary\",\n\t\t  \"options\":[],\n\t\t  \"type\":\"text\"\n\t   },\n\t   {\n\t\t  \"id\":\"566cd860-bbae-4bcd-86a8-7df4db2ba15c\",\n\t\t  \"name\":\"Color\",\n\t\t  \"options\":[\n\t\t\t {\n\t\t\t\t\"color\":\"propColorDefault\",\n\t\t\t\t\"id\":\"efb0c783-f9ea-4938-8b86-9cf425296cd1\",\n\t\t\t\t\"value\":\"RED\"\n\t\t\t },\n\t\t\t {\n\t\t\t\t\"color\":\"propColorDefault\",\n\t\t\t\t\"id\":\"2f100e13-e7c4-4ab6-81c9-a17baf98b311\",\n\t\t\t\t\"value\":\"GREEN\"\n\t\t\t },\n\t\t\t {\n\t\t\t\t\"color\":\"propColorDefault\",\n\t\t\t\t\"id\":\"a05bdc80-bd90-45b0-8805-a7e77a4884be\",\n\t\t\t\t\"value\":\"BLUE\"\n\t\t\t }\n\t\t  ],\n\t\t  \"type\":\"select\"\n\t   },\n\t   {\n\t\t  \"id\":\"aawg1s8rxq8o1bbksxmsmpsdd3r\",\n\t\t  \"name\":\"MyTextProp\",\n\t\t  \"options\":[],\n\t\t  \"type\":\"text\"\n\t   },\n\t   {\n\t\t  \"id\":\"awdwfigo4kse63bdfp56mzhip6w\",\n\t\t  \"name\":\"MyCheckBox\",\n\t\t  \"options\":[],\n\t\t  \"type\":\"checkbox\"\n\t   },\n\t   {\n\t\t  \"id\":\"a8spou7if43eo1rqzb9qeq488so\",\n\t\t  \"name\":\"MyDate\",\n\t\t  \"options\":[],\n\t\t  \"type\":\"date\"\n\t   }\n\t]`\n)\n"
  },
  {
    "path": "server/model/services_api.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\n//go:generate mockgen --build_flags= -destination=mocks/mockservicesapi.go -package mocks . ServicesAPI\n\npackage model\n\nimport (\n\t\"database/sql\"\n\n\t\"github.com/gorilla/mux\"\n\n\tmm_model \"github.com/mattermost/mattermost/server/public/model\"\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nconst (\n\tbotUsername    = \"boards\"\n\tbotDisplayname = \"Boards\"\n\tbotDescription = \"Created by Boards plugin.\"\n)\n\nvar FocalboardBot = &mm_model.Bot{\n\tUsername:    botUsername,\n\tDisplayName: botDisplayname,\n\tDescription: botDescription,\n\tOwnerId:     SystemUserID,\n}\n\ntype ServicesAPI interface {\n\t// Channels service\n\tGetDirectChannel(userID1, userID2 string) (*mm_model.Channel, error)\n\tGetDirectChannelOrCreate(userID1, userID2 string) (*mm_model.Channel, error)\n\tGetChannelByID(channelID string) (*mm_model.Channel, error)\n\tGetChannelMember(channelID string, userID string) (*mm_model.ChannelMember, error)\n\tGetChannelsForTeamForUser(teamID string, userID string, includeDeleted bool) (mm_model.ChannelList, error)\n\n\t// Post service\n\tCreatePost(post *mm_model.Post) (*mm_model.Post, error)\n\n\t// User service\n\tGetUserByID(userID string) (*mm_model.User, error)\n\tGetUserByUsername(name string) (*mm_model.User, error)\n\tGetUserByEmail(email string) (*mm_model.User, error)\n\tUpdateUser(user *mm_model.User) (*mm_model.User, error)\n\tGetUsersFromProfiles(options *mm_model.UserGetOptions) ([]*mm_model.User, error)\n\n\t// Team service\n\tGetTeamMember(teamID string, userID string) (*mm_model.TeamMember, error)\n\tCreateMember(teamID string, userID string) (*mm_model.TeamMember, error)\n\n\t// Permissions service\n\tHasPermissionTo(userID string, permission *mm_model.Permission) bool\n\tHasPermissionToTeam(userID, teamID string, permission *mm_model.Permission) bool\n\tHasPermissionToChannel(askingUserID string, channelID string, permission *mm_model.Permission) bool\n\n\t// Bot service\n\tEnsureBot(bot *mm_model.Bot) (string, error)\n\n\t// License service\n\tGetLicense() *mm_model.License\n\n\t// FileInfoStore service\n\tGetFileInfo(fileID string) (*mm_model.FileInfo, error)\n\n\t// Cluster service\n\tPublishWebSocketEvent(event string, payload map[string]interface{}, broadcast *mm_model.WebsocketBroadcast)\n\tPublishPluginClusterEvent(ev mm_model.PluginClusterEvent, opts mm_model.PluginClusterEventSendOptions) error\n\n\t// Config service\n\tGetConfig() *mm_model.Config\n\n\t// Logger service\n\tGetLogger() mlog.LoggerIFace\n\n\t// KVStore service\n\tKVSetWithOptions(key string, value []byte, options mm_model.PluginKVSetOptions) (bool, error)\n\n\t// Store service\n\tGetMasterDB() (*sql.DB, error)\n\n\t// System service\n\tGetDiagnosticID() string\n\n\t// Router service\n\tRegisterRouter(sub *mux.Router)\n\n\t// Preferences services\n\tGetPreferencesForUser(userID string) (mm_model.Preferences, error)\n\tUpdatePreferencesForUser(userID string, preferences mm_model.Preferences) error\n\tDeletePreferencesForUser(userID string, preferences mm_model.Preferences) error\n}\n"
  },
  {
    "path": "server/model/sharing.go",
    "content": "package model\n\nimport (\n\t\"encoding/json\"\n\t\"io\"\n)\n\n// Sharing is sharing information for a root block\n// swagger:model\ntype Sharing struct {\n\t// ID of the root block\n\t// required: true\n\tID string `json:\"id\"`\n\n\t// Is sharing enabled\n\t// required: true\n\tEnabled bool `json:\"enabled\"`\n\n\t// Access token\n\t// required: true\n\tToken string `json:\"token\"`\n\n\t// ID of the user who last modified this\n\t// required: true\n\tModifiedBy string `json:\"modifiedBy\"`\n\n\t// Updated time in miliseconds since the current epoch\n\t// required: true\n\tUpdateAt int64 `json:\"update_at,omitempty\"`\n}\n\nfunc SharingFromJSON(data io.Reader) Sharing {\n\tvar sharing Sharing\n\t_ = json.NewDecoder(data).Decode(&sharing)\n\treturn sharing\n}\n"
  },
  {
    "path": "server/model/subscription.go",
    "content": "package model\n\nimport (\n\t\"encoding/json\"\n\t\"io\"\n)\n\nconst (\n\tSubTypeUser    = \"user\"\n\tSubTypeChannel = \"channel\"\n)\n\ntype SubscriberType string\n\nfunc (st SubscriberType) IsValid() bool {\n\tswitch st {\n\tcase SubTypeUser, SubTypeChannel:\n\t\treturn true\n\t}\n\treturn false\n}\n\n// Subscription is a subscription to a board, card, etc, for a user or channel.\n// swagger:model\ntype Subscription struct {\n\t// BlockType is the block type of the entity (e.g. board, card) subscribed to\n\t// required: true\n\tBlockType BlockType `json:\"blockType\"`\n\n\t// BlockID is id of the entity being subscribed to\n\t// required: true\n\tBlockID string `json:\"blockId\"`\n\n\t// SubscriberType is the type of the entity (e.g. user, channel) that is subscribing\n\t// required: true\n\tSubscriberType SubscriberType `json:\"subscriberType\"`\n\n\t// SubscriberID is the id of the entity that is subscribing\n\t// required: true\n\tSubscriberID string `json:\"subscriberId\"`\n\n\t// NotifiedAt is the timestamp of the last notification sent for this subscription\n\t// required: true\n\tNotifiedAt int64 `json:\"notifiedAt,omitempty\"`\n\n\t// CreatedAt is the timestamp this subscription was created in miliseconds since the current epoch\n\t// required: true\n\tCreateAt int64 `json:\"createAt\"`\n\n\t// DeleteAt is the timestamp this subscription was deleted in miliseconds since the current epoch, or zero if not deleted\n\t// required: true\n\tDeleteAt int64 `json:\"deleteAt\"`\n}\n\nfunc (s *Subscription) IsValid() error {\n\tif s == nil {\n\t\treturn ErrInvalidSubscription{\"cannot be nil\"}\n\t}\n\tif s.BlockID == \"\" {\n\t\treturn ErrInvalidSubscription{\"missing block id\"}\n\t}\n\tif s.BlockType == \"\" {\n\t\treturn ErrInvalidSubscription{\"missing block type\"}\n\t}\n\tif s.SubscriberID == \"\" {\n\t\treturn ErrInvalidSubscription{\"missing subscriber id\"}\n\t}\n\tif !s.SubscriberType.IsValid() {\n\t\treturn ErrInvalidSubscription{\"invalid subscriber type\"}\n\t}\n\treturn nil\n}\n\nfunc SubscriptionFromJSON(data io.Reader) (*Subscription, error) {\n\tvar subscription Subscription\n\tif err := json.NewDecoder(data).Decode(&subscription); err != nil {\n\t\treturn nil, err\n\t}\n\treturn &subscription, nil\n}\n\ntype ErrInvalidSubscription struct {\n\tmsg string\n}\n\nfunc (e ErrInvalidSubscription) Error() string {\n\treturn e.msg\n}\n\n// Subscriber is an entity (e.g. user, channel) that can subscribe to events from boards, cards, etc\n// swagger:model\ntype Subscriber struct {\n\t// SubscriberType is the type of the entity (e.g. user, channel) that is subscribing\n\t// required: true\n\tSubscriberType SubscriberType `json:\"subscriber_type\"`\n\n\t// SubscriberID is the id of the entity that is subscribing\n\t// required: true\n\tSubscriberID string `json:\"subscriber_id\"`\n\n\t// NotifiedAt is the timestamp this subscriber was last notified\n\tNotifiedAt int64 `json:\"notified_at\"`\n}\n"
  },
  {
    "path": "server/model/team.go",
    "content": "package model\n\nimport (\n\t\"encoding/json\"\n\t\"io\"\n)\n\n// Team is information global to a team\n// swagger:model\ntype Team struct {\n\t// ID of the team\n\t// required: true\n\tID string `json:\"id\"`\n\n\t// Title of the team\n\t// required: false\n\tTitle string `json:\"title\"`\n\n\t// Token required to register new users\n\t// required: true\n\tSignupToken string `json:\"signupToken\"`\n\n\t// Team settings\n\t// required: false\n\tSettings map[string]interface{} `json:\"settings\"`\n\n\t// ID of user who last modified this\n\t// required: true\n\tModifiedBy string `json:\"modifiedBy\"`\n\n\t// Updated time in miliseconds since the current epoch\n\t// required: true\n\tUpdateAt int64 `json:\"updateAt\"`\n}\n\nfunc TeamFromJSON(data io.Reader) *Team {\n\tvar team *Team\n\t_ = json.NewDecoder(data).Decode(&team)\n\treturn team\n}\n\nfunc TeamsFromJSON(data io.Reader) []*Team {\n\tvar teams []*Team\n\t_ = json.NewDecoder(data).Decode(&teams)\n\treturn teams\n}\n"
  },
  {
    "path": "server/model/user.go",
    "content": "package model\n\nimport (\n\t\"encoding/json\"\n\t\"io\"\n)\n\nconst (\n\tSingleUser                    = \"single-user\"\n\tGlobalTeamID                  = \"0\"\n\tSystemUserID                  = \"system\"\n\tPreferencesCategoryFocalboard = \"focalboard\"\n)\n\n// User is a user\n// swagger:model\ntype User struct {\n\t// The user ID\n\t// required: true\n\tID string `json:\"id\"`\n\n\t// The user name\n\t// required: true\n\tUsername string `json:\"username\"`\n\n\t// The user's email\n\t// required: true\n\tEmail string `json:\"-\"`\n\n\t// The user's nickname\n\tNickname string `json:\"nickname\"`\n\t// The user's first name\n\tFirstName string `json:\"firstname\"`\n\t// The user's last name\n\tLastName string `json:\"lastname\"`\n\n\t// swagger:ignore\n\tPassword string `json:\"-\"`\n\n\t// swagger:ignore\n\tMfaSecret string `json:\"-\"`\n\n\t// swagger:ignore\n\tAuthService string `json:\"-\"`\n\n\t// swagger:ignore\n\tAuthData string `json:\"-\"`\n\n\t// Created time in miliseconds since the current epoch\n\t// required: true\n\tCreateAt int64 `json:\"create_at,omitempty\"`\n\n\t// Updated time in miliseconds since the current epoch\n\t// required: true\n\tUpdateAt int64 `json:\"update_at,omitempty\"`\n\n\t// Deleted time in miliseconds since the current epoch, set to indicate user is deleted\n\t// required: true\n\tDeleteAt int64 `json:\"delete_at\"`\n\n\t// If the user is a bot or not\n\t// required: true\n\tIsBot bool `json:\"is_bot\"`\n\n\t// If the user is a guest or not\n\t// required: true\n\tIsGuest bool `json:\"is_guest\"`\n\n\t// Special Permissions the user may have\n\tPermissions []string `json:\"permissions,omitempty\"`\n\n\tRoles string `json:\"roles\"`\n}\n\n// UserPreferencesPatch is a user property patch\n// swagger:model\ntype UserPreferencesPatch struct {\n\t// The user preference updated fields\n\t// required: false\n\tUpdatedFields map[string]string `json:\"updatedFields\"`\n\n\t// The user preference removed fields\n\t// required: false\n\tDeletedFields []string `json:\"deletedFields\"`\n}\n\ntype Session struct {\n\tID          string                 `json:\"id\"`\n\tToken       string                 `json:\"token\"`\n\tUserID      string                 `json:\"user_id\"`\n\tAuthService string                 `json:\"authService\"`\n\tProps       map[string]interface{} `json:\"props\"`\n\tCreateAt    int64                  `json:\"create_at,omitempty\"`\n\tUpdateAt    int64                  `json:\"update_at,omitempty\"`\n}\n\nfunc UserFromJSON(data io.Reader) (*User, error) {\n\tvar user User\n\tif err := json.NewDecoder(data).Decode(&user); err != nil {\n\t\treturn nil, err\n\t}\n\treturn &user, nil\n}\n\nfunc (u *User) Sanitize(options map[string]bool) {\n\tu.Password = \"\"\n\tu.MfaSecret = \"\"\n\n\tif len(options) != 0 && !options[\"email\"] {\n\t\tu.Email = \"\"\n\t}\n\tif len(options) != 0 && !options[\"fullname\"] {\n\t\tu.FirstName = \"\"\n\t\tu.LastName = \"\"\n\t}\n}\n"
  },
  {
    "path": "server/model/util.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage model\n\nimport (\n\t\"time\"\n\n\tmm_model \"github.com/mattermost/mattermost/server/public/model\"\n)\n\n// GetMillis is a convenience method to get milliseconds since epoch.\nfunc GetMillis() int64 {\n\treturn mm_model.GetMillis()\n}\n\n// GetMillisForTime is a convenience method to get milliseconds since epoch for provided Time.\nfunc GetMillisForTime(thisTime time.Time) int64 {\n\treturn mm_model.GetMillisForTime(thisTime)\n}\n\n// GetTimeForMillis is a convenience method to get time.Time for milliseconds since epoch.\nfunc GetTimeForMillis(millis int64) time.Time {\n\treturn mm_model.GetTimeForMillis(millis)\n}\n"
  },
  {
    "path": "server/model/version.go",
    "content": "package model\n\nimport (\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\n// This is a list of all the current versions including any patches.\n// It should be maintained in chronological order with most current\n// release at the front of the list.\nvar versions = []string{\n\t\"8.0.0\",\n\t\"7.12.0\",\n\t\"7.11.1\",\n\t\"7.11.0\",\n\t\"7.10.0\",\n\t\"7.9.0\",\n\t\"7.8.0\",\n\t\"7.7.0\",\n\t\"7.6.0\",\n\t\"7.5.0\",\n\t\"7.4.0\",\n\t\"7.3.0\",\n\t\"7.2.0\",\n\t\"7.0.0\",\n\t\"0.16.0\",\n\t\"0.15.0\",\n\t\"0.14.0\",\n\t\"0.12.0\",\n\t\"0.11.0\",\n\t\"0.10.0\",\n\t\"0.9.4\",\n\t\"0.9.3\",\n\t\"0.9.2\",\n\t\"0.9.1\",\n\t\"0.9.0\",\n\t\"0.8.2\",\n\t\"0.8.1\",\n\t\"0.8.0\",\n\t\"0.7.3\",\n\t\"0.7.2\",\n\t\"0.7.1\",\n\t\"0.7.0\",\n\t\"0.6.7\",\n\t\"0.6.6\",\n\t\"0.6.5\",\n\t\"0.6.2\",\n\t\"0.6.1\",\n\t\"0.6.0\",\n\t\"0.5.0\",\n}\n\nvar (\n\tCurrentVersion = versions[0]\n\tBuildNumber    string\n\tBuildDate      string\n\tBuildHash      string\n\tEdition        string\n)\n\n// LogServerInfo logs information about the server instance.\nfunc LogServerInfo(logger mlog.LoggerIFace) {\n\tlogger.Info(\"Focalboard server\",\n\t\tmlog.String(\"version\", CurrentVersion),\n\t\tmlog.String(\"edition\", Edition),\n\t\tmlog.String(\"build_number\", BuildNumber),\n\t\tmlog.String(\"build_date\", BuildDate),\n\t\tmlog.String(\"build_hash\", BuildHash),\n\t)\n}\n"
  },
  {
    "path": "server/server/initHandlers.go",
    "content": "package server\n\nfunc (s *Server) initHandlers() {\n\tcfg := s.config\n\ts.api.MattermostAuth = cfg.AuthMode == MattermostAuthMod\n}\n"
  },
  {
    "path": "server/server/params.go",
    "content": "package server\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/config\"\n\t\"github.com/mattermost/focalboard/server/services/notify\"\n\t\"github.com/mattermost/focalboard/server/services/permissions\"\n\t\"github.com/mattermost/focalboard/server/services/store\"\n\t\"github.com/mattermost/focalboard/server/ws\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\ntype Params struct {\n\tCfg                *config.Configuration\n\tSingleUserToken    string\n\tDBStore            store.Store\n\tLogger             mlog.LoggerIFace\n\tServerID           string\n\tWSAdapter          ws.Adapter\n\tNotifyBackends     []notify.Backend\n\tPermissionsService permissions.PermissionsService\n\tServicesAPI        model.ServicesAPI\n}\n\nfunc (p Params) CheckValid() error {\n\tif p.Cfg == nil {\n\t\treturn ErrServerParam{name: \"Cfg\", issue: \"cannot be nil\"}\n\t}\n\n\tif p.DBStore == nil {\n\t\treturn ErrServerParam{name: \"DbStore\", issue: \"cannot be nil\"}\n\t}\n\n\tif p.Logger == nil {\n\t\treturn ErrServerParam{name: \"Logger\", issue: \"cannot be nil\"}\n\t}\n\n\tif p.PermissionsService == nil {\n\t\treturn ErrServerParam{name: \"Permissions\", issue: \"cannot be nil\"}\n\t}\n\treturn nil\n}\n\ntype ErrServerParam struct {\n\tname  string\n\tissue string\n}\n\nfunc (e ErrServerParam) Error() string {\n\treturn fmt.Sprintf(\"invalid server params: %s %s\", e.name, e.issue)\n}\n"
  },
  {
    "path": "server/server/server.go",
    "content": "package server\n\nimport (\n\t\"database/sql\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"runtime\"\n\t\"sync\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/gorilla/mux\"\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/mattermost/focalboard/server/api\"\n\t\"github.com/mattermost/focalboard/server/app\"\n\t\"github.com/mattermost/focalboard/server/auth\"\n\tappModel \"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/audit\"\n\t\"github.com/mattermost/focalboard/server/services/config\"\n\t\"github.com/mattermost/focalboard/server/services/metrics\"\n\t\"github.com/mattermost/focalboard/server/services/notify\"\n\t\"github.com/mattermost/focalboard/server/services/notify/notifylogger\"\n\t\"github.com/mattermost/focalboard/server/services/scheduler\"\n\t\"github.com/mattermost/focalboard/server/services/store\"\n\t\"github.com/mattermost/focalboard/server/services/store/sqlstore\"\n\t\"github.com/mattermost/focalboard/server/services/telemetry\"\n\t\"github.com/mattermost/focalboard/server/services/webhook\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n\t\"github.com/mattermost/focalboard/server/web\"\n\t\"github.com/mattermost/focalboard/server/ws\"\n\t\"github.com/oklog/run\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n\t\"github.com/mattermost/mattermost/server/v8/platform/shared/filestore\"\n)\n\nconst (\n\tcleanupSessionTaskFrequency = 10 * time.Minute\n\tupdateMetricsTaskFrequency  = 15 * time.Minute\n\n\tminSessionExpiryTime = int64(60 * 60 * 24 * 31) // 31 days\n\n\tMattermostAuthMod = \"mattermost\"\n)\n\ntype Server struct {\n\tconfig                 *config.Configuration\n\twsAdapter              ws.Adapter\n\twebServer              *web.Server\n\tstore                  store.Store\n\tfilesBackend           filestore.FileBackend\n\ttelemetry              *telemetry.Service\n\tlogger                 mlog.LoggerIFace\n\tcleanUpSessionsTask    *scheduler.ScheduledTask\n\tmetricsServer          *metrics.Service\n\tmetricsService         *metrics.Metrics\n\tmetricsUpdaterTask     *scheduler.ScheduledTask\n\tauditService           *audit.Audit\n\tnotificationService    *notify.Service\n\tservicesStartStopMutex sync.Mutex\n\n\tlocalRouter     *mux.Router\n\tlocalModeServer *http.Server\n\tapi             *api.API\n\tapp             *app.App\n}\n\nfunc New(params Params) (*Server, error) {\n\tif err := params.CheckValid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tauthenticator := auth.New(params.Cfg, params.DBStore, params.PermissionsService)\n\n\t// if no ws adapter is provided, we spin up a websocket server\n\twsAdapter := params.WSAdapter\n\tif wsAdapter == nil {\n\t\twsAdapter = ws.NewServer(authenticator, params.SingleUserToken, params.Cfg.AuthMode == MattermostAuthMod, params.Logger, params.DBStore)\n\t}\n\n\tfilesBackendSettings := filestore.FileBackendSettings{}\n\tfilesBackendSettings.DriverName = params.Cfg.FilesDriver\n\tfilesBackendSettings.Directory = params.Cfg.FilesPath\n\tfilesBackendSettings.AmazonS3AccessKeyId = params.Cfg.FilesS3Config.AccessKeyID\n\tfilesBackendSettings.AmazonS3SecretAccessKey = params.Cfg.FilesS3Config.SecretAccessKey\n\tfilesBackendSettings.AmazonS3Bucket = params.Cfg.FilesS3Config.Bucket\n\tfilesBackendSettings.AmazonS3PathPrefix = params.Cfg.FilesS3Config.PathPrefix\n\tfilesBackendSettings.AmazonS3Region = params.Cfg.FilesS3Config.Region\n\tfilesBackendSettings.AmazonS3Endpoint = params.Cfg.FilesS3Config.Endpoint\n\tfilesBackendSettings.AmazonS3SSL = params.Cfg.FilesS3Config.SSL\n\tfilesBackendSettings.AmazonS3SignV2 = params.Cfg.FilesS3Config.SignV2\n\tfilesBackendSettings.AmazonS3SSE = params.Cfg.FilesS3Config.SSE\n\tfilesBackendSettings.AmazonS3Trace = params.Cfg.FilesS3Config.Trace\n\tfilesBackendSettings.AmazonS3RequestTimeoutMilliseconds = params.Cfg.FilesS3Config.Timeout\n\n\tfilesBackend, appErr := filestore.NewFileBackend(filesBackendSettings)\n\tif appErr != nil {\n\t\tparams.Logger.Error(\"Unable to initialize the files storage\", mlog.Err(appErr))\n\n\t\treturn nil, errors.New(\"unable to initialize the files storage\")\n\t}\n\n\twebhookClient := webhook.NewClient(params.Cfg, params.Logger)\n\n\t// Init metrics\n\tinstanceInfo := metrics.InstanceInfo{\n\t\tVersion:        appModel.CurrentVersion,\n\t\tBuildNum:       appModel.BuildNumber,\n\t\tEdition:        appModel.Edition,\n\t\tInstallationID: os.Getenv(\"MM_CLOUD_INSTALLATION_ID\"),\n\t}\n\tmetricsService := metrics.NewMetrics(instanceInfo)\n\n\t// Init audit\n\tauditService, errAudit := audit.NewAudit()\n\tif errAudit != nil {\n\t\treturn nil, fmt.Errorf(\"unable to create the audit service: %w\", errAudit)\n\t}\n\tif err := auditService.Configure(params.Cfg.AuditCfgFile, params.Cfg.AuditCfgJSON); err != nil {\n\t\treturn nil, fmt.Errorf(\"unable to initialize the audit service: %w\", err)\n\t}\n\n\t// Init notification services\n\tnotificationService, errNotify := initNotificationService(params.NotifyBackends, params.Logger)\n\tif errNotify != nil {\n\t\treturn nil, fmt.Errorf(\"cannot initialize notification service(s): %w\", errNotify)\n\t}\n\n\tappServices := app.Services{\n\t\tAuth:             authenticator,\n\t\tStore:            params.DBStore,\n\t\tFilesBackend:     filesBackend,\n\t\tWebhook:          webhookClient,\n\t\tMetrics:          metricsService,\n\t\tNotifications:    notificationService,\n\t\tLogger:           params.Logger,\n\t\tPermissions:      params.PermissionsService,\n\t\tServicesAPI:      params.ServicesAPI,\n\t\tSkipTemplateInit: utils.IsRunningUnitTests(),\n\t}\n\tapp := app.New(params.Cfg, wsAdapter, appServices)\n\n\tfocalboardAPI := api.NewAPI(app, params.SingleUserToken, params.Cfg.AuthMode, params.PermissionsService, params.Logger, auditService)\n\n\t// Local router for admin APIs\n\tlocalRouter := mux.NewRouter()\n\tfocalboardAPI.RegisterAdminRoutes(localRouter)\n\n\t// Init team\n\tif _, err := app.GetRootTeam(); err != nil {\n\t\tparams.Logger.Error(\"Unable to get root team\", mlog.Err(err))\n\t\treturn nil, err\n\t}\n\n\twebServer := web.NewServer(params.Cfg.WebPath, params.Cfg.ServerRoot, params.Cfg.Port,\n\t\tparams.Cfg.UseSSL, params.Cfg.LocalOnly, params.Logger)\n\t// if the adapter is a routed service, register it before the API\n\tif routedService, ok := wsAdapter.(web.RoutedService); ok {\n\t\twebServer.AddRoutes(routedService)\n\t}\n\twebServer.AddRoutes(focalboardAPI)\n\n\tsettings, err := params.DBStore.GetSystemSettings()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Init telemetry\n\ttelemetryID := settings[\"TelemetryID\"]\n\tif len(telemetryID) == 0 {\n\t\ttelemetryID = utils.NewID(utils.IDTypeNone)\n\t\tif err = params.DBStore.SetSystemSetting(\"TelemetryID\", telemetryID); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\ttelemetryOpts := telemetryOptions{\n\t\tapp:         app,\n\t\tcfg:         params.Cfg,\n\t\ttelemetryID: telemetryID,\n\t\tserverID:    params.ServerID,\n\t\tlogger:      params.Logger,\n\t\tsingleUser:  len(params.SingleUserToken) > 0,\n\t}\n\ttelemetryService := initTelemetry(telemetryOpts)\n\n\tserver := Server{\n\t\tconfig:              params.Cfg,\n\t\twsAdapter:           wsAdapter,\n\t\twebServer:           webServer,\n\t\tstore:               params.DBStore,\n\t\tfilesBackend:        filesBackend,\n\t\ttelemetry:           telemetryService,\n\t\tmetricsServer:       metrics.NewMetricsServer(params.Cfg.PrometheusAddress, metricsService, params.Logger),\n\t\tmetricsService:      metricsService,\n\t\tauditService:        auditService,\n\t\tnotificationService: notificationService,\n\t\tlogger:              params.Logger,\n\t\tlocalRouter:         localRouter,\n\t\tapi:                 focalboardAPI,\n\t\tapp:                 app,\n\t}\n\n\tserver.initHandlers()\n\n\treturn &server, nil\n}\n\nfunc NewStore(config *config.Configuration, isSingleUser bool, logger mlog.LoggerIFace) (store.Store, error) {\n\tsqlDB, err := sql.Open(config.DBType, config.DBConfigString)\n\tif err != nil {\n\t\tlogger.Error(\"connectDatabase failed\", mlog.Err(err))\n\t\treturn nil, err\n\t}\n\n\terr = sqlDB.Ping()\n\tif err != nil {\n\t\tlogger.Error(`Database Ping failed`, mlog.Err(err))\n\t\treturn nil, err\n\t}\n\n\tstoreParams := sqlstore.Params{\n\t\tDBType:           config.DBType,\n\t\tDBPingAttempts:   config.DBPingAttempts,\n\t\tConnectionString: config.DBConfigString,\n\t\tTablePrefix:      config.DBTablePrefix,\n\t\tLogger:           logger,\n\t\tDB:               sqlDB,\n\t\tIsSingleUser:     isSingleUser,\n\t}\n\n\tvar db store.Store\n\tdb, err = sqlstore.New(storeParams)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn db, nil\n}\n\nfunc (s *Server) Start() error {\n\ts.logger.Info(\"Server.Start\")\n\n\ts.webServer.Start()\n\n\ts.servicesStartStopMutex.Lock()\n\tdefer s.servicesStartStopMutex.Unlock()\n\n\tif s.config.EnableLocalMode {\n\t\tif err := s.startLocalModeServer(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif s.config.AuthMode != MattermostAuthMod {\n\t\ts.cleanUpSessionsTask = scheduler.CreateRecurringTask(\"cleanUpSessions\", func() {\n\t\t\tsecondsAgo := minSessionExpiryTime\n\t\t\tif secondsAgo < s.config.SessionExpireTime {\n\t\t\t\tsecondsAgo = s.config.SessionExpireTime\n\t\t\t}\n\n\t\t\tif err := s.store.CleanUpSessions(secondsAgo); err != nil {\n\t\t\t\ts.logger.Error(\"Unable to clean up the sessions\", mlog.Err(err))\n\t\t\t}\n\t\t}, cleanupSessionTaskFrequency)\n\t}\n\n\tmetricsUpdater := func() {\n\t\tblockCounts, err := s.store.GetBlockCountsByType()\n\t\tif err != nil {\n\t\t\ts.logger.Error(\"Error updating metrics\", mlog.String(\"group\", \"blocks\"), mlog.Err(err))\n\t\t\treturn\n\t\t}\n\t\ts.logger.Debug(\"Block metrics collected\", mlog.Map(\"block_counts\", blockCounts))\n\t\tfor blockType, count := range blockCounts {\n\t\t\ts.metricsService.ObserveBlockCount(blockType, count)\n\t\t}\n\t\tboardCount, err := s.store.GetBoardCount()\n\t\tif err != nil {\n\t\t\ts.logger.Error(\"Error updating metrics\", mlog.String(\"group\", \"boards\"), mlog.Err(err))\n\t\t\treturn\n\t\t}\n\t\ts.logger.Debug(\"Board metrics collected\", mlog.Int(\"board_count\", boardCount))\n\t\ts.metricsService.ObserveBoardCount(boardCount)\n\t\tteamCount, err := s.store.GetTeamCount()\n\t\tif err != nil {\n\t\t\ts.logger.Error(\"Error updating metrics\", mlog.String(\"group\", \"teams\"), mlog.Err(err))\n\t\t\treturn\n\t\t}\n\t\ts.logger.Debug(\"Team metrics collected\", mlog.Int(\"team_count\", teamCount))\n\t\ts.metricsService.ObserveTeamCount(teamCount)\n\t}\n\t// metricsUpdater()   Calling this immediately causes integration unit tests to fail.\n\ts.metricsUpdaterTask = scheduler.CreateRecurringTask(\"updateMetrics\", metricsUpdater, updateMetricsTaskFrequency)\n\n\tif s.config.Telemetry {\n\t\tfirstRun := utils.GetMillis()\n\t\ts.telemetry.RunTelemetryJob(firstRun)\n\t}\n\n\tvar group run.Group\n\tif s.config.PrometheusAddress != \"\" {\n\t\tgroup.Add(func() error {\n\t\t\tif err := s.metricsServer.Run(); err != nil {\n\t\t\t\treturn errors.Wrap(err, \"PromServer Run\")\n\t\t\t}\n\t\t\treturn nil\n\t\t}, func(error) {\n\t\t\t_ = s.metricsServer.Shutdown()\n\t\t})\n\n\t\tif err := group.Run(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (s *Server) Shutdown() error {\n\tif err := s.webServer.Shutdown(); err != nil {\n\t\treturn err\n\t}\n\n\ts.stopLocalModeServer()\n\n\ts.servicesStartStopMutex.Lock()\n\tdefer s.servicesStartStopMutex.Unlock()\n\n\tif s.cleanUpSessionsTask != nil {\n\t\ts.cleanUpSessionsTask.Cancel()\n\t}\n\n\tif s.metricsUpdaterTask != nil {\n\t\ts.metricsUpdaterTask.Cancel()\n\t}\n\n\tif err := s.telemetry.Shutdown(); err != nil {\n\t\ts.logger.Warn(\"Error occurred when shutting down telemetry\", mlog.Err(err))\n\t}\n\n\tif err := s.auditService.Shutdown(); err != nil {\n\t\ts.logger.Warn(\"Error occurred when shutting down audit service\", mlog.Err(err))\n\t}\n\n\tif err := s.notificationService.Shutdown(); err != nil {\n\t\ts.logger.Warn(\"Error occurred when shutting down notification service\", mlog.Err(err))\n\t}\n\n\ts.app.Shutdown()\n\n\tdefer s.logger.Info(\"Server.Shutdown\")\n\n\treturn s.store.Shutdown()\n}\n\nfunc (s *Server) Config() *config.Configuration {\n\treturn s.config\n}\n\nfunc (s *Server) Logger() mlog.LoggerIFace {\n\treturn s.logger\n}\n\nfunc (s *Server) App() *app.App {\n\treturn s.app\n}\n\nfunc (s *Server) Store() store.Store {\n\treturn s.store\n}\n\nfunc (s *Server) UpdateAppConfig() {\n\ts.app.SetConfig(s.config)\n}\n\n// Local server\n\nfunc (s *Server) startLocalModeServer() error {\n\ts.localModeServer = &http.Server{ //nolint:gosec\n\t\tHandler:     s.localRouter,\n\t\tConnContext: api.SetContextConn,\n\t}\n\n\t// TODO: Close and delete socket file on shutdown\n\t// Delete existing socket if it exists\n\tif _, err := os.Stat(s.config.LocalModeSocketLocation); err == nil {\n\t\tif err := syscall.Unlink(s.config.LocalModeSocketLocation); err != nil {\n\t\t\ts.logger.Error(\"Unable to unlink socket.\", mlog.Err(err))\n\t\t}\n\t}\n\n\tsocket := s.config.LocalModeSocketLocation\n\tunixListener, err := net.Listen(\"unix\", socket)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif err = os.Chmod(socket, 0600); err != nil {\n\t\treturn err\n\t}\n\n\tgo func() {\n\t\ts.logger.Info(\"Starting unix socket server\")\n\t\terr = s.localModeServer.Serve(unixListener)\n\t\tif err != nil && !errors.Is(err, http.ErrServerClosed) {\n\t\t\ts.logger.Error(\"Error starting unix socket server\", mlog.Err(err))\n\t\t}\n\t}()\n\n\treturn nil\n}\n\nfunc (s *Server) stopLocalModeServer() {\n\tif s.localModeServer != nil {\n\t\t_ = s.localModeServer.Close()\n\t\ts.localModeServer = nil\n\t}\n}\n\nfunc (s *Server) GetRootRouter() *mux.Router {\n\treturn s.webServer.Router()\n}\n\ntype telemetryOptions struct {\n\tapp         *app.App\n\tcfg         *config.Configuration\n\ttelemetryID string\n\tserverID    string\n\tlogger      mlog.LoggerIFace\n\tsingleUser  bool\n}\n\nfunc initTelemetry(opts telemetryOptions) *telemetry.Service {\n\ttelemetryService := telemetry.New(opts.telemetryID, opts.logger)\n\n\ttelemetryService.RegisterTracker(\"server\", func() (telemetry.Tracker, error) {\n\t\treturn map[string]interface{}{\n\t\t\t\"version\":          appModel.CurrentVersion,\n\t\t\t\"build_number\":     appModel.BuildNumber,\n\t\t\t\"build_hash\":       appModel.BuildHash,\n\t\t\t\"edition\":          appModel.Edition,\n\t\t\t\"operating_system\": runtime.GOOS,\n\t\t\t\"server_id\":        opts.serverID,\n\t\t}, nil\n\t})\n\ttelemetryService.RegisterTracker(\"config\", func() (telemetry.Tracker, error) {\n\t\treturn map[string]interface{}{\n\t\t\t\"serverRoot\":                 opts.cfg.ServerRoot == config.DefaultServerRoot,\n\t\t\t\"port\":                       opts.cfg.Port == config.DefaultPort,\n\t\t\t\"useSSL\":                     opts.cfg.UseSSL,\n\t\t\t\"dbType\":                     opts.cfg.DBType,\n\t\t\t\"single_user\":                opts.singleUser,\n\t\t\t\"allow_public_shared_boards\": opts.cfg.EnablePublicSharedBoards,\n\t\t}, nil\n\t})\n\ttelemetryService.RegisterTracker(\"activity\", func() (telemetry.Tracker, error) {\n\t\tm := make(map[string]interface{})\n\t\tvar count int\n\t\tvar err error\n\t\tif count, err = opts.app.GetRegisteredUserCount(); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tm[\"registered_users\"] = count\n\n\t\tif count, err = opts.app.GetDailyActiveUsers(); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tm[\"daily_active_users\"] = count\n\n\t\tif count, err = opts.app.GetWeeklyActiveUsers(); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tm[\"weekly_active_users\"] = count\n\n\t\tif count, err = opts.app.GetMonthlyActiveUsers(); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tm[\"monthly_active_users\"] = count\n\t\treturn m, nil\n\t})\n\ttelemetryService.RegisterTracker(\"blocks\", func() (telemetry.Tracker, error) {\n\t\tblockCounts, err := opts.app.GetBlockCountsByType()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tm := make(map[string]interface{})\n\t\tfor k, v := range blockCounts {\n\t\t\tm[k] = v\n\t\t}\n\t\treturn m, nil\n\t})\n\ttelemetryService.RegisterTracker(\"boards\", func() (telemetry.Tracker, error) {\n\t\tboardCount, err := opts.app.GetBoardCount()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tm := map[string]interface{}{\n\t\t\t\"boards\": boardCount,\n\t\t}\n\t\treturn m, nil\n\t})\n\ttelemetryService.RegisterTracker(\"teams\", func() (telemetry.Tracker, error) {\n\t\tcount, err := opts.app.GetTeamCount()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tm := map[string]interface{}{\n\t\t\t\"teams\": count,\n\t\t}\n\t\treturn m, nil\n\t})\n\treturn telemetryService\n}\n\nfunc initNotificationService(backends []notify.Backend, logger mlog.LoggerIFace) (*notify.Service, error) {\n\tloggerBackend := notifylogger.New(logger, mlog.LvlDebug)\n\n\tbackends = append(backends, loggerBackend)\n\n\tservice, err := notify.New(logger, backends...)\n\treturn service, err\n}\n"
  },
  {
    "path": "server/services/audit/audit.go",
    "content": "package audit\n\nimport (\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nconst (\n\tDefMaxQueueSize = 1000\n\n\tKeyAPIPath   = \"api_path\"\n\tKeyEvent     = \"event\"\n\tKeyStatus    = \"status\"\n\tKeyUserID    = \"user_id\"\n\tKeySessionID = \"session_id\"\n\tKeyClient    = \"client\"\n\tKeyIPAddress = \"ip_address\"\n\tKeyClusterID = \"cluster_id\"\n\tKeyTeamID    = \"team_id\"\n\n\tSuccess = \"success\"\n\tAttempt = \"attempt\"\n\tFail    = \"fail\"\n)\n\nvar (\n\tLevelAuth   = mlog.Level{ID: 1000, Name: \"auth\"}\n\tLevelModify = mlog.Level{ID: 1001, Name: \"mod\"}\n\tLevelRead   = mlog.Level{ID: 1002, Name: \"read\"}\n)\n\n// Audit provides auditing service.\ntype Audit struct {\n\tauditLogger *mlog.Logger\n}\n\n// NewAudit creates a new Audit instance which can be configured via `(*Audit).Configure`.\nfunc NewAudit(options ...mlog.Option) (*Audit, error) {\n\tlogger, err := mlog.NewLogger(options...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &Audit{\n\t\tauditLogger: logger,\n\t}, nil\n}\n\n// Configure provides a new configuration for this audit service.\n// Zero or more sources of config can be provided:\n//\n//\tcfgFile    - path to file containing JSON\n//\tcfgEscaped - JSON string probably from ENV var\n//\n// For each case JSON containing log targets is provided. Target name collisions are resolved\n// using the following precedence:\n//\n//\tcfgFile > cfgEscaped\nfunc (a *Audit) Configure(cfgFile string, cfgEscaped string) error {\n\treturn a.auditLogger.Configure(cfgFile, cfgEscaped, nil)\n}\n\n// Shutdown shuts down the audit service after making best efforts to flush any\n// remaining records.\nfunc (a *Audit) Shutdown() error {\n\treturn a.auditLogger.Shutdown()\n}\n\n// LogRecord emits an audit record with complete info.\nfunc (a *Audit) LogRecord(level mlog.Level, rec *Record) {\n\tfields := make([]mlog.Field, 0, 7+len(rec.Meta))\n\n\tfields = append(fields, mlog.String(KeyAPIPath, rec.APIPath))\n\tfields = append(fields, mlog.String(KeyEvent, rec.Event))\n\tfields = append(fields, mlog.String(KeyStatus, rec.Status))\n\tfields = append(fields, mlog.String(KeyUserID, rec.UserID))\n\tfields = append(fields, mlog.String(KeySessionID, rec.SessionID))\n\tfields = append(fields, mlog.String(KeyClient, rec.Client))\n\tfields = append(fields, mlog.String(KeyIPAddress, rec.IPAddress))\n\n\tfor _, meta := range rec.Meta {\n\t\tfields = append(fields, mlog.Any(meta.K, meta.V))\n\t}\n\n\ta.auditLogger.Log(level, \"audit \"+rec.Event, fields...)\n}\n"
  },
  {
    "path": "server/services/audit/record.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage audit\n\nimport \"github.com/mattermost/mattermost/server/public/shared/mlog\"\n\n// Meta represents metadata that can be added to a audit record as name/value pairs.\ntype Meta struct {\n\tK string\n\tV interface{}\n}\n\n// FuncMetaTypeConv defines a function that can convert meta data types into something\n// that serializes well for audit records.\ntype FuncMetaTypeConv func(val interface{}) (newVal interface{}, converted bool)\n\n// Record provides a consistent set of fields used for all audit logging.\ntype Record struct {\n\tAPIPath   string\n\tEvent     string\n\tStatus    string\n\tUserID    string\n\tSessionID string\n\tClient    string\n\tIPAddress string\n\tMeta      []Meta\n\tmetaConv  []FuncMetaTypeConv\n}\n\n// Success marks the audit record status as successful.\nfunc (rec *Record) Success() {\n\trec.Status = Success\n}\n\n// Success marks the audit record status as failed.\nfunc (rec *Record) Fail() {\n\trec.Status = Fail\n}\n\n// AddMeta adds a single name/value pair to this audit record's metadata.\nfunc (rec *Record) AddMeta(name string, val interface{}) {\n\tif rec.Meta == nil {\n\t\trec.Meta = []Meta{}\n\t}\n\n\t// possibly convert val to something better suited for serializing\n\t// via zero or more conversion functions.\n\tfor _, conv := range rec.metaConv {\n\t\tconverted, wasConverted := conv(val)\n\t\tif wasConverted {\n\t\t\tval = converted\n\t\t\tbreak\n\t\t}\n\t}\n\n\tlc, ok := val.(mlog.LogCloner)\n\tif ok {\n\t\tval = lc.LogClone()\n\t}\n\n\trec.Meta = append(rec.Meta, Meta{K: name, V: val})\n}\n\n// AddMetaTypeConverter adds a function capable of converting meta field types\n// into something more suitable for serialization.\nfunc (rec *Record) AddMetaTypeConverter(f FuncMetaTypeConv) {\n\trec.metaConv = append(rec.metaConv, f)\n}\n"
  },
  {
    "path": "server/services/audit/record_test.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage audit\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype bloated struct {\n\tfld1 string\n\tfld2 string\n\tfld3 string\n\tfld4 string\n}\n\ntype wilted struct {\n\twilt1 string\n}\n\nfunc conv(val interface{}) (interface{}, bool) {\n\tif b, ok := val.(*bloated); ok {\n\t\treturn &wilted{wilt1: b.fld1}, true\n\t}\n\treturn val, false\n}\n\nfunc TestRecord_AddMeta(t *testing.T) {\n\ttype fields struct {\n\t\tmetaConv []FuncMetaTypeConv\n\t}\n\ttype args struct {\n\t\tname string\n\t\tval  interface{}\n\t}\n\ttests := []struct {\n\t\tname     string\n\t\tfields   fields\n\t\targs     args\n\t\twantWilt bool\n\t\twantVal  string\n\t}{\n\t\t{name: \"no converter\", wantWilt: false, wantVal: \"ok\", fields: fields{}, args: args{name: \"prop\", val: \"ok\"}},\n\t\t{name: \"don't convert\", wantWilt: false, wantVal: \"ok\", fields: fields{metaConv: []FuncMetaTypeConv{conv}}, args: args{name: \"prop\", val: \"ok\"}},\n\t\t{name: \"convert\", wantWilt: true, wantVal: \"1\", fields: fields{metaConv: []FuncMetaTypeConv{conv}}, args: args{name: \"prop\", val: &bloated{\n\t\t\tfld1: \"1\", fld2: \"2\", fld3: \"3\", fld4: \"4\"}},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\trec := &Record{\n\t\t\t\tmetaConv: tt.fields.metaConv,\n\t\t\t}\n\t\t\trec.AddMeta(tt.args.name, tt.args.val)\n\n\t\t\t// fetch the prop store in auditRecord meta data\n\t\t\tvar ok bool\n\t\t\tvar got interface{}\n\t\t\tfor _, meta := range rec.Meta {\n\t\t\t\tif meta.K == \"prop\" {\n\t\t\t\t\tok = true\n\t\t\t\t\tgot = meta.V\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\trequire.True(t, ok)\n\n\t\t\t// check if conversion was expected\n\t\t\tval, ok := got.(*wilted)\n\t\t\trequire.Equal(t, tt.wantWilt, ok)\n\n\t\t\tif ok {\n\t\t\t\t// if converted to wilt then make sure field was copied\n\t\t\t\trequire.Equal(t, tt.wantVal, val.wilt1)\n\t\t\t} else {\n\t\t\t\t// if not converted, make sure val is unchanged\n\t\t\t\trequire.Equal(t, tt.wantVal, got)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "server/services/auth/email.go",
    "content": "package auth\n\nimport \"regexp\"\n\nvar emailRegex = regexp.MustCompile(\"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$\")\n\n// IsEmailValid checks if the email provided passes the required structure and length.\nfunc IsEmailValid(e string) bool {\n\tif len(e) < 3 || len(e) > 254 {\n\t\treturn false\n\t}\n\treturn emailRegex.MatchString(e)\n}\n"
  },
  {
    "path": "server/services/auth/password.go",
    "content": "package auth\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"golang.org/x/crypto/bcrypt\"\n)\n\nconst (\n\tPasswordMaximumLength    = 64\n\tPasswordSpecialChars     = \"!\\\"\\\\#$%&'()*+,-./:;<=>?@[]^_`|~\" //nolint:gosec\n\tPasswordNumbers          = \"0123456789\"\n\tPasswordUpperCaseLetters = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\"\n\tPasswordLowerCaseLetters = \"abcdefghijklmnopqrstuvwxyz\"\n\tPasswordAllChars         = PasswordSpecialChars + PasswordNumbers + PasswordUpperCaseLetters + PasswordLowerCaseLetters\n\n\tInvalidLowercasePassword = \"lowercase\"\n\tInvalidMinLengthPassword = \"min-length\"\n\tInvalidMaxLengthPassword = \"max-length\"\n\tInvalidNumberPassword    = \"number\"\n\tInvalidUppercasePassword = \"uppercase\"\n\tInvalidSymbolPassword    = \"symbol\"\n)\n\nvar PasswordHashStrength = 10\n\n// HashPassword generates a hash using the bcrypt.GenerateFromPassword.\nfunc HashPassword(password string) string {\n\thash, err := bcrypt.GenerateFromPassword([]byte(password), PasswordHashStrength)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn string(hash)\n}\n\n// ComparePassword compares the hash.\nfunc ComparePassword(hash, password string) bool {\n\tif len(password) == 0 || len(hash) == 0 {\n\t\treturn false\n\t}\n\n\terr := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))\n\treturn err == nil\n}\n\ntype InvalidPasswordError struct {\n\tFailingCriterias []string\n}\n\nfunc (ipe *InvalidPasswordError) Error() string {\n\treturn fmt.Sprintf(\"invalid password, failing criteria: %s\", strings.Join(ipe.FailingCriterias, \", \"))\n}\n\ntype PasswordSettings struct {\n\tMinimumLength int\n\tLowercase     bool\n\tNumber        bool\n\tUppercase     bool\n\tSymbol        bool\n}\n\nfunc IsPasswordValid(password string, settings PasswordSettings) error {\n\terr := &InvalidPasswordError{\n\t\tFailingCriterias: []string{},\n\t}\n\n\tif len(password) < settings.MinimumLength {\n\t\terr.FailingCriterias = append(err.FailingCriterias, InvalidMinLengthPassword)\n\t}\n\n\tif len(password) > PasswordMaximumLength {\n\t\terr.FailingCriterias = append(err.FailingCriterias, InvalidMaxLengthPassword)\n\t}\n\n\tif settings.Lowercase {\n\t\tif !strings.ContainsAny(password, PasswordLowerCaseLetters) {\n\t\t\terr.FailingCriterias = append(err.FailingCriterias, InvalidLowercasePassword)\n\t\t}\n\t}\n\n\tif settings.Uppercase {\n\t\tif !strings.ContainsAny(password, PasswordUpperCaseLetters) {\n\t\t\terr.FailingCriterias = append(err.FailingCriterias, InvalidUppercasePassword)\n\t\t}\n\t}\n\n\tif settings.Number {\n\t\tif !strings.ContainsAny(password, PasswordNumbers) {\n\t\t\terr.FailingCriterias = append(err.FailingCriterias, InvalidNumberPassword)\n\t\t}\n\t}\n\n\tif settings.Symbol {\n\t\tif !strings.ContainsAny(password, PasswordSpecialChars) {\n\t\t\terr.FailingCriterias = append(err.FailingCriterias, InvalidSymbolPassword)\n\t\t}\n\t}\n\n\tif len(err.FailingCriterias) > 0 {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "server/services/auth/password_test.go",
    "content": "package auth\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestPasswordHash(t *testing.T) {\n\thash := HashPassword(\"Test\")\n\n\tassert.True(t, ComparePassword(hash, \"Test\"), \"Passwords don't match\")\n\tassert.False(t, ComparePassword(hash, \"Test2\"), \"Passwords should not have matched\")\n}\n\nfunc TestIsPasswordValidWithSettings(t *testing.T) {\n\tfor name, tc := range map[string]struct {\n\t\tPassword                 string\n\t\tSettings                 PasswordSettings\n\t\tExpectedFailingCriterias []string\n\t}{\n\t\t\"Short\": {\n\t\t\tPassword: strings.Repeat(\"x\", 3),\n\t\t\tSettings: PasswordSettings{\n\t\t\t\tMinimumLength: 3,\n\t\t\t\tLowercase:     false,\n\t\t\t\tUppercase:     false,\n\t\t\t\tNumber:        false,\n\t\t\t\tSymbol:        false,\n\t\t\t},\n\t\t},\n\t\t\"Long\": {\n\t\t\tPassword: strings.Repeat(\"x\", PasswordMaximumLength),\n\t\t\tSettings: PasswordSettings{\n\t\t\t\tMinimumLength: 3,\n\t\t\t\tLowercase:     false,\n\t\t\t\tUppercase:     false,\n\t\t\t\tNumber:        false,\n\t\t\t\tSymbol:        false,\n\t\t\t},\n\t\t},\n\t\t\"TooShort\": {\n\t\t\tPassword: strings.Repeat(\"x\", 2),\n\t\t\tSettings: PasswordSettings{\n\t\t\t\tMinimumLength: 3,\n\t\t\t\tLowercase:     false,\n\t\t\t\tUppercase:     false,\n\t\t\t\tNumber:        false,\n\t\t\t\tSymbol:        false,\n\t\t\t},\n\t\t\tExpectedFailingCriterias: []string{\"min-length\"},\n\t\t},\n\t\t\"TooLong\": {\n\t\t\tPassword: strings.Repeat(\"x\", PasswordMaximumLength+1),\n\t\t\tSettings: PasswordSettings{\n\t\t\t\tMinimumLength: 3,\n\t\t\t\tLowercase:     false,\n\t\t\t\tUppercase:     false,\n\t\t\t\tNumber:        false,\n\t\t\t\tSymbol:        false,\n\t\t\t},\n\t\t\tExpectedFailingCriterias: []string{\"max-length\"},\n\t\t},\n\t\t\"MissingLower\": {\n\t\t\tPassword: \"AAAAAAAAAAASD123!@#\",\n\t\t\tSettings: PasswordSettings{\n\t\t\t\tMinimumLength: 3,\n\t\t\t\tLowercase:     true,\n\t\t\t\tUppercase:     false,\n\t\t\t\tNumber:        false,\n\t\t\t\tSymbol:        false,\n\t\t\t},\n\t\t\tExpectedFailingCriterias: []string{\"lowercase\"},\n\t\t},\n\t\t\"MissingUpper\": {\n\t\t\tPassword: \"aaaaaaaaaaaaasd123!@#\",\n\t\t\tSettings: PasswordSettings{\n\t\t\t\tMinimumLength: 3,\n\t\t\t\tUppercase:     true,\n\t\t\t\tLowercase:     false,\n\t\t\t\tNumber:        false,\n\t\t\t\tSymbol:        false,\n\t\t\t},\n\t\t\tExpectedFailingCriterias: []string{\"uppercase\"},\n\t\t},\n\t\t\"MissingNumber\": {\n\t\t\tPassword: \"asasdasdsadASD!@#\",\n\t\t\tSettings: PasswordSettings{\n\t\t\t\tMinimumLength: 3,\n\t\t\t\tNumber:        true,\n\t\t\t\tLowercase:     false,\n\t\t\t\tUppercase:     false,\n\t\t\t\tSymbol:        false,\n\t\t\t},\n\t\t\tExpectedFailingCriterias: []string{\"number\"},\n\t\t},\n\t\t\"MissingSymbol\": {\n\t\t\tPassword: \"asdasdasdasdasdASD123\",\n\t\t\tSettings: PasswordSettings{\n\t\t\t\tMinimumLength: 3,\n\t\t\t\tSymbol:        true,\n\t\t\t\tLowercase:     false,\n\t\t\t\tUppercase:     false,\n\t\t\t\tNumber:        false,\n\t\t\t},\n\t\t\tExpectedFailingCriterias: []string{\"symbol\"},\n\t\t},\n\t\t\"MissingMultiple\": {\n\t\t\tPassword: \"asdasdasdasdasdasd\",\n\t\t\tSettings: PasswordSettings{\n\t\t\t\tMinimumLength: 3,\n\t\t\t\tLowercase:     true,\n\t\t\t\tUppercase:     true,\n\t\t\t\tNumber:        true,\n\t\t\t\tSymbol:        true,\n\t\t\t},\n\t\t\tExpectedFailingCriterias: []string{\"uppercase\", \"number\", \"symbol\"},\n\t\t},\n\t\t\"Everything\": {\n\t\t\tPassword: \"asdASD!@#123\",\n\t\t\tSettings: PasswordSettings{\n\t\t\t\tMinimumLength: 3,\n\t\t\t\tLowercase:     true,\n\t\t\t\tUppercase:     true,\n\t\t\t\tNumber:        true,\n\t\t\t\tSymbol:        true,\n\t\t\t},\n\t\t},\n\t} {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\terr := IsPasswordValid(tc.Password, tc.Settings)\n\t\t\tif len(tc.ExpectedFailingCriterias) == 0 {\n\t\t\t\tassert.NoError(t, err)\n\t\t\t} else {\n\t\t\t\trequire.Error(t, err)\n\t\t\t\tvar errFC *InvalidPasswordError\n\t\t\t\tif assert.ErrorAs(t, err, &errFC) {\n\t\t\t\t\tassert.Equal(t, tc.ExpectedFailingCriterias, errFC.FailingCriterias)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "server/services/auth/request_parser.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage auth\n\nimport (\n\t\"net/http\"\n\t\"strings\"\n)\n\nconst (\n\tHeaderToken        = \"token\"\n\tHeaderAuth         = \"Authorization\"\n\tHeaderBearer       = \"BEARER\"\n\tSessionCookieToken = \"FOCALBOARDAUTHTOKEN\"\n)\n\ntype TokenLocation int\n\nconst (\n\tTokenLocationNotFound TokenLocation = iota\n\tTokenLocationHeader\n\tTokenLocationCookie\n\tTokenLocationQueryString\n)\n\nfunc (tl TokenLocation) String() string {\n\tswitch tl {\n\tcase TokenLocationNotFound:\n\t\treturn \"Not Found\"\n\tcase TokenLocationHeader:\n\t\treturn \"Header\"\n\tcase TokenLocationCookie:\n\t\treturn \"Cookie\"\n\tcase TokenLocationQueryString:\n\t\treturn \"QueryString\"\n\tdefault:\n\t\treturn \"Unknown\"\n\t}\n}\n\nfunc ParseAuthTokenFromRequest(r *http.Request) (string, TokenLocation) {\n\tauthHeader := r.Header.Get(HeaderAuth)\n\n\t// Attempt to parse the token from the cookie\n\tif cookie, err := r.Cookie(SessionCookieToken); err == nil {\n\t\treturn cookie.Value, TokenLocationCookie\n\t}\n\n\t// Parse the token from the header\n\tif len(authHeader) > 6 && strings.ToUpper(authHeader[0:6]) == HeaderBearer {\n\t\t// Default session token\n\t\treturn authHeader[7:], TokenLocationHeader\n\t}\n\n\tif len(authHeader) > 5 && strings.ToLower(authHeader[0:5]) == HeaderToken {\n\t\t// OAuth token\n\t\treturn authHeader[6:], TokenLocationHeader\n\t}\n\n\t// Attempt to parse token out of the query string\n\tif token := r.URL.Query().Get(\"access_token\"); token != \"\" {\n\t\treturn token, TokenLocationQueryString\n\t}\n\n\treturn \"\", TokenLocationNotFound\n}\n"
  },
  {
    "path": "server/services/auth/request_parser_test.go",
    "content": "package auth\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"strconv\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestParseAuthTokenFromRequest(t *testing.T) {\n\tcases := []struct {\n\t\theader           string\n\t\tcookie           string\n\t\tquery            string\n\t\texpectedToken    string\n\t\texpectedLocation TokenLocation\n\t}{\n\t\t{\"\", \"\", \"\", \"\", TokenLocationNotFound},\n\t\t{\"token mytoken\", \"\", \"\", \"mytoken\", TokenLocationHeader},\n\t\t{\"BEARER mytoken\", \"\", \"\", \"mytoken\", TokenLocationHeader},\n\t\t{\"\", \"mytoken\", \"\", \"mytoken\", TokenLocationCookie},\n\t\t{\"\", \"\", \"mytoken\", \"mytoken\", TokenLocationQueryString},\n\t}\n\n\tfor testnum, tc := range cases {\n\t\tpathname := \"/test/here\"\n\t\tif tc.query != \"\" {\n\t\t\tpathname += \"?access_token=\" + tc.query\n\t\t}\n\t\treq := httptest.NewRequest(\"GET\", pathname, nil)\n\t\tif tc.header != \"\" {\n\t\t\treq.Header.Add(HeaderAuth, tc.header)\n\t\t}\n\t\tif tc.cookie != \"\" {\n\t\t\treq.AddCookie(&http.Cookie{\n\t\t\t\tName:  \"FOCALBOARDAUTHTOKEN\",\n\t\t\t\tValue: tc.cookie,\n\t\t\t})\n\t\t}\n\n\t\ttoken, location := ParseAuthTokenFromRequest(req)\n\n\t\trequire.Equal(t, tc.expectedToken, token, \"Wrong token on test \"+strconv.Itoa(testnum))\n\t\trequire.Equal(t, tc.expectedLocation, location, \"Wrong location on test \"+strconv.Itoa(testnum))\n\t}\n}\n"
  },
  {
    "path": "server/services/config/config.go",
    "content": "package config\n\nimport (\n\t\"log\"\n\n\t\"github.com/spf13/viper\"\n)\n\nconst (\n\tDefaultServerRoot = \"http://localhost:8000\"\n\tDefaultPort       = 8000\n\tDBPingAttempts    = 5\n)\n\ntype AmazonS3Config struct {\n\tAccessKeyID     string\n\tSecretAccessKey string\n\tBucket          string\n\tPathPrefix      string\n\tRegion          string\n\tEndpoint        string\n\tSSL             bool\n\tSignV2          bool\n\tSSE             bool\n\tTrace           bool\n\tTimeout         int64\n}\n\n// Configuration is the app configuration stored in a json file.\ntype Configuration struct {\n\tServerRoot               string            `json:\"serverRoot\" mapstructure:\"serverRoot\"`\n\tPort                     int               `json:\"port\" mapstructure:\"port\"`\n\tDBType                   string            `json:\"dbtype\" mapstructure:\"dbtype\"`\n\tDBConfigString           string            `json:\"dbconfig\" mapstructure:\"dbconfig\"`\n\tDBPingAttempts           int               `json:\"dbpingattempts\" mapstructure:\"dbpingattempts\"`\n\tDBTablePrefix            string            `json:\"dbtableprefix\" mapstructure:\"dbtableprefix\"`\n\tUseSSL                   bool              `json:\"useSSL\" mapstructure:\"useSSL\"`\n\tSecureCookie             bool              `json:\"secureCookie\" mapstructure:\"secureCookie\"`\n\tWebPath                  string            `json:\"webpath\" mapstructure:\"webpath\"`\n\tFilesDriver              string            `json:\"filesdriver\" mapstructure:\"filesdriver\"`\n\tFilesS3Config            AmazonS3Config    `json:\"filess3config\" mapstructure:\"filess3config\"`\n\tFilesPath                string            `json:\"filespath\" mapstructure:\"filespath\"`\n\tMaxFileSize              int64             `json:\"maxfilesize\" mapstructure:\"maxfilesize\"`\n\tTelemetry                bool              `json:\"telemetry\" mapstructure:\"telemetry\"`\n\tTelemetryID              string            `json:\"telemetryid\" mapstructure:\"telemetryid\"`\n\tPrometheusAddress        string            `json:\"prometheusaddress\" mapstructure:\"prometheusaddress\"`\n\tWebhookUpdate            []string          `json:\"webhook_update\" mapstructure:\"webhook_update\"`\n\tSecret                   string            `json:\"secret\" mapstructure:\"secret\"`\n\tSessionExpireTime        int64             `json:\"session_expire_time\" mapstructure:\"session_expire_time\"`\n\tSessionRefreshTime       int64             `json:\"session_refresh_time\" mapstructure:\"session_refresh_time\"`\n\tLocalOnly                bool              `json:\"localonly\" mapstructure:\"localonly\"`\n\tEnableLocalMode          bool              `json:\"enableLocalMode\" mapstructure:\"enableLocalMode\"`\n\tLocalModeSocketLocation  string            `json:\"localModeSocketLocation\" mapstructure:\"localModeSocketLocation\"`\n\tEnablePublicSharedBoards bool              `json:\"enablePublicSharedBoards\" mapstructure:\"enablePublicSharedBoards\"`\n\tFeatureFlags             map[string]string `json:\"featureFlags\" mapstructure:\"featureFlags\"`\n\tEnableDataRetention      bool              `json:\"enable_data_retention\" mapstructure:\"enable_data_retention\"`\n\tDataRetentionDays        int               `json:\"data_retention_days\" mapstructure:\"data_retention_days\"`\n\tTeammateNameDisplay      string            `json:\"teammate_name_display\" mapstructure:\"teammateNameDisplay\"`\n\tShowEmailAddress         bool              `json:\"show_email_address\" mapstructure:\"showEmailAddress\"`\n\tShowFullName             bool              `json:\"show_full_name\" mapstructure:\"showFullName\"`\n\n\tAuthMode string `json:\"authMode\" mapstructure:\"authMode\"`\n\n\tLoggingCfgFile string `json:\"logging_cfg_file\" mapstructure:\"logging_cfg_file\"`\n\tLoggingCfgJSON string `json:\"logging_cfg_json\" mapstructure:\"logging_cfg_json\"`\n\n\tAuditCfgFile string `json:\"audit_cfg_file\" mapstructure:\"audit_cfg_file\"`\n\tAuditCfgJSON string `json:\"audit_cfg_json\" mapstructure:\"audit_cfg_json\"`\n\n\tNotifyFreqCardSeconds  int `json:\"notify_freq_card_seconds\" mapstructure:\"notify_freq_card_seconds\"`\n\tNotifyFreqBoardSeconds int `json:\"notify_freq_board_seconds\" mapstructure:\"notify_freq_board_seconds\"`\n}\n\n// ReadConfigFile read the configuration from the filesystem.\nfunc ReadConfigFile(configFilePath string) (*Configuration, error) {\n\tif configFilePath == \"\" {\n\t\tviper.SetConfigFile(\"./config.json\")\n\t} else {\n\t\tviper.SetConfigFile(configFilePath)\n\t}\n\n\tviper.SetEnvPrefix(\"focalboard\")\n\tviper.AutomaticEnv() // read config values from env like FOCALBOARD_SERVERROOT=...\n\tviper.SetDefault(\"ServerRoot\", DefaultServerRoot)\n\tviper.SetDefault(\"DBPingAttempts\", DBPingAttempts)\n\tviper.SetDefault(\"Port\", DefaultPort)\n\tviper.SetDefault(\"DBType\", \"sqlite3\")\n\tviper.SetDefault(\"DBConfigString\", \"./focalboard.db\")\n\tviper.SetDefault(\"DBTablePrefix\", \"\")\n\tviper.SetDefault(\"SecureCookie\", false)\n\tviper.SetDefault(\"WebPath\", \"./pack\")\n\tviper.SetDefault(\"FilesPath\", \"./files\")\n\tviper.SetDefault(\"FilesDriver\", \"local\")\n\tviper.SetDefault(\"Telemetry\", true)\n\tviper.SetDefault(\"TelemetryID\", \"\")\n\tviper.SetDefault(\"WebhookUpdate\", nil)\n\tviper.SetDefault(\"SessionExpireTime\", 60*60*24*30) // 30 days session lifetime\n\tviper.SetDefault(\"SessionRefreshTime\", 60*60*5)    // 5 minutes session refresh\n\tviper.SetDefault(\"LocalOnly\", false)\n\tviper.SetDefault(\"EnableLocalMode\", false)\n\tviper.SetDefault(\"LocalModeSocketLocation\", \"/var/tmp/focalboard_local.socket\")\n\tviper.SetDefault(\"EnablePublicSharedBoards\", false)\n\tviper.SetDefault(\"AuthMode\", \"native\")\n\tviper.SetDefault(\"NotifyFreqCardSeconds\", 120)    // 2 minutes after last card edit\n\tviper.SetDefault(\"NotifyFreqBoardSeconds\", 86400) // 1 day after last card edit\n\tviper.SetDefault(\"EnableDataRetention\", false)\n\tviper.SetDefault(\"FeatureFlags\", map[string]string{})\n\tviper.SetDefault(\"DataRetentionDays\", 365) // 1 year is default\n\tviper.SetDefault(\"PrometheusAddress\", \"\")\n\tviper.SetDefault(\"TeammateNameDisplay\", \"username\")\n\tviper.SetDefault(\"ShowEmailAddress\", false)\n\tviper.SetDefault(\"ShowFullName\", false)\n\n\terr := viper.ReadInConfig() // Find and read the config file\n\tif err != nil {             // Handle errors reading the config file\n\t\treturn nil, err\n\t}\n\n\tconfiguration := Configuration{}\n\n\terr = viper.Unmarshal(&configuration)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tlog.Println(\"readConfigFile\")\n\tlog.Printf(\"%+v\", removeSecurityData(configuration))\n\n\treturn &configuration, nil\n}\n\nfunc removeSecurityData(config Configuration) Configuration {\n\tclean := config\n\treturn clean\n}\n"
  },
  {
    "path": "server/services/metrics/metrics.go",
    "content": "package metrics\n\nimport (\n\t\"os\"\n\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus/collectors\"\n)\n\nconst (\n\tMetricsNamespace       = \"focalboard\"\n\tMetricsSubsystemBlocks = \"blocks\"\n\tMetricsSubsystemBoards = \"boards\"\n\tMetricsSubsystemTeams  = \"teams\"\n\tMetricsSubsystemSystem = \"system\"\n\n\tMetricsCloudInstallationLabel = \"installationId\"\n)\n\ntype InstanceInfo struct {\n\tVersion        string\n\tBuildNum       string\n\tEdition        string\n\tInstallationID string\n}\n\n// Metrics used to instrumentate metrics in prometheus.\ntype Metrics struct {\n\tregistry *prometheus.Registry\n\n\tinstance  *prometheus.GaugeVec\n\tstartTime prometheus.Gauge\n\n\tloginCount     prometheus.Counter\n\tlogoutCount    prometheus.Counter\n\tloginFailCount prometheus.Counter\n\n\tblocksInsertedCount prometheus.Counter\n\tblocksPatchedCount  prometheus.Counter\n\tblocksDeletedCount  prometheus.Counter\n\n\tblockCount *prometheus.GaugeVec\n\tboardCount prometheus.Gauge\n\tteamCount  prometheus.Gauge\n\n\tblockLastActivity prometheus.Gauge\n}\n\n// NewMetrics Factory method to create a new metrics collector.\nfunc NewMetrics(info InstanceInfo) *Metrics {\n\tm := &Metrics{}\n\n\tm.registry = prometheus.NewRegistry()\n\toptions := collectors.ProcessCollectorOpts{\n\t\tNamespace: MetricsNamespace,\n\t}\n\tm.registry.MustRegister(collectors.NewProcessCollector(options))\n\tm.registry.MustRegister(collectors.NewGoCollector())\n\n\tadditionalLabels := map[string]string{}\n\tif info.InstallationID != \"\" {\n\t\tadditionalLabels[MetricsCloudInstallationLabel] = os.Getenv(\"MM_CLOUD_INSTALLATION_ID\")\n\t}\n\n\tm.loginCount = prometheus.NewCounter(prometheus.CounterOpts{\n\t\tNamespace:   MetricsNamespace,\n\t\tSubsystem:   MetricsSubsystemSystem,\n\t\tName:        \"login_total\",\n\t\tHelp:        \"Total number of logins.\",\n\t\tConstLabels: additionalLabels,\n\t})\n\tm.registry.MustRegister(m.loginCount)\n\n\tm.logoutCount = prometheus.NewCounter(prometheus.CounterOpts{\n\t\tNamespace:   MetricsNamespace,\n\t\tSubsystem:   MetricsSubsystemSystem,\n\t\tName:        \"logout_total\",\n\t\tHelp:        \"Total number of logouts.\",\n\t\tConstLabels: additionalLabels,\n\t})\n\tm.registry.MustRegister(m.logoutCount)\n\n\tm.loginFailCount = prometheus.NewCounter(prometheus.CounterOpts{\n\t\tNamespace:   MetricsNamespace,\n\t\tSubsystem:   MetricsSubsystemSystem,\n\t\tName:        \"login_fail_total\",\n\t\tHelp:        \"Total number of failed logins.\",\n\t\tConstLabels: additionalLabels,\n\t})\n\tm.registry.MustRegister(m.loginFailCount)\n\n\tm.instance = prometheus.NewGaugeVec(prometheus.GaugeOpts{\n\t\tNamespace:   MetricsNamespace,\n\t\tSubsystem:   MetricsSubsystemSystem,\n\t\tName:        \"focalboard_instance_info\",\n\t\tHelp:        \"Instance information for Focalboard.\",\n\t\tConstLabels: additionalLabels,\n\t}, []string{\"Version\", \"BuildNum\", \"Edition\"})\n\tm.registry.MustRegister(m.instance)\n\tm.instance.WithLabelValues(info.Version, info.BuildNum, info.Edition).Set(1)\n\n\tm.startTime = prometheus.NewGauge(prometheus.GaugeOpts{\n\t\tNamespace:   MetricsNamespace,\n\t\tSubsystem:   MetricsSubsystemSystem,\n\t\tName:        \"server_start_time\",\n\t\tHelp:        \"The time the server started.\",\n\t\tConstLabels: additionalLabels,\n\t})\n\tm.startTime.SetToCurrentTime()\n\tm.registry.MustRegister(m.startTime)\n\n\tm.blocksInsertedCount = prometheus.NewCounter(prometheus.CounterOpts{\n\t\tNamespace:   MetricsNamespace,\n\t\tSubsystem:   MetricsSubsystemBlocks,\n\t\tName:        \"blocks_inserted_total\",\n\t\tHelp:        \"Total number of blocks inserted.\",\n\t\tConstLabels: additionalLabels,\n\t})\n\tm.registry.MustRegister(m.blocksInsertedCount)\n\n\tm.blocksPatchedCount = prometheus.NewCounter(prometheus.CounterOpts{\n\t\tNamespace:   MetricsNamespace,\n\t\tSubsystem:   MetricsSubsystemBlocks,\n\t\tName:        \"blocks_patched_total\",\n\t\tHelp:        \"Total number of blocks patched.\",\n\t\tConstLabels: additionalLabels,\n\t})\n\tm.registry.MustRegister(m.blocksPatchedCount)\n\n\tm.blocksDeletedCount = prometheus.NewCounter(prometheus.CounterOpts{\n\t\tNamespace:   MetricsNamespace,\n\t\tSubsystem:   MetricsSubsystemBlocks,\n\t\tName:        \"blocks_deleted_total\",\n\t\tHelp:        \"Total number of blocks deleted.\",\n\t\tConstLabels: additionalLabels,\n\t})\n\tm.registry.MustRegister(m.blocksDeletedCount)\n\n\tm.blockCount = prometheus.NewGaugeVec(prometheus.GaugeOpts{\n\t\tNamespace:   MetricsNamespace,\n\t\tSubsystem:   MetricsSubsystemBlocks,\n\t\tName:        \"blocks_total\",\n\t\tHelp:        \"Total number of blocks.\",\n\t\tConstLabels: additionalLabels,\n\t}, []string{\"BlockType\"})\n\tm.registry.MustRegister(m.blockCount)\n\n\tm.boardCount = prometheus.NewGauge(prometheus.GaugeOpts{\n\t\tNamespace:   MetricsNamespace,\n\t\tSubsystem:   MetricsSubsystemBoards,\n\t\tName:        \"boards_total\",\n\t\tHelp:        \"Total number of boards.\",\n\t\tConstLabels: additionalLabels,\n\t})\n\tm.registry.MustRegister(m.boardCount)\n\n\tm.teamCount = prometheus.NewGauge(prometheus.GaugeOpts{\n\t\tNamespace:   MetricsNamespace,\n\t\tSubsystem:   MetricsSubsystemTeams,\n\t\tName:        \"teams_total\",\n\t\tHelp:        \"Total number of teams.\",\n\t\tConstLabels: additionalLabels,\n\t})\n\tm.registry.MustRegister(m.teamCount)\n\n\tm.blockLastActivity = prometheus.NewGauge(prometheus.GaugeOpts{\n\t\tNamespace:   MetricsNamespace,\n\t\tSubsystem:   MetricsSubsystemBlocks,\n\t\tName:        \"blocks_last_activity\",\n\t\tHelp:        \"Time of last block insert, update, delete.\",\n\t\tConstLabels: additionalLabels,\n\t})\n\tm.registry.MustRegister(m.blockLastActivity)\n\n\treturn m\n}\n\nfunc (m *Metrics) IncrementLoginCount(num int) {\n\tif m != nil {\n\t\tm.loginCount.Add(float64(num))\n\t}\n}\n\nfunc (m *Metrics) IncrementLogoutCount(num int) {\n\tif m != nil {\n\t\tm.logoutCount.Add(float64(num))\n\t}\n}\n\nfunc (m *Metrics) IncrementLoginFailCount(num int) {\n\tif m != nil {\n\t\tm.loginFailCount.Add(float64(num))\n\t}\n}\n\nfunc (m *Metrics) IncrementBlocksInserted(num int) {\n\tif m != nil {\n\t\tm.blocksInsertedCount.Add(float64(num))\n\t\tm.blockLastActivity.SetToCurrentTime()\n\t}\n}\n\nfunc (m *Metrics) IncrementBlocksPatched(num int) {\n\tif m != nil {\n\t\tm.blocksPatchedCount.Add(float64(num))\n\t\tm.blockLastActivity.SetToCurrentTime()\n\t}\n}\n\nfunc (m *Metrics) IncrementBlocksDeleted(num int) {\n\tif m != nil {\n\t\tm.blocksDeletedCount.Add(float64(num))\n\t\tm.blockLastActivity.SetToCurrentTime()\n\t}\n}\n\nfunc (m *Metrics) ObserveBlockCount(blockType string, count int64) {\n\tif m != nil {\n\t\tm.blockCount.WithLabelValues(blockType).Set(float64(count))\n\t}\n}\n\nfunc (m *Metrics) ObserveBoardCount(count int64) {\n\tif m != nil {\n\t\tm.boardCount.Set(float64(count))\n\t}\n}\n\nfunc (m *Metrics) ObserveTeamCount(count int64) {\n\tif m != nil {\n\t\tm.teamCount.Set(float64(count))\n\t}\n}\n"
  },
  {
    "path": "server/services/metrics/service.go",
    "content": "package metrics\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/pkg/errors\"\n\t\"github.com/prometheus/client_golang/prometheus/promhttp\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\n// Service prometheus to run the server.\ntype Service struct {\n\t*http.Server\n}\n\n// NewMetricsServer factory method to create a new prometheus server.\nfunc NewMetricsServer(address string, metricsService *Metrics, logger mlog.LoggerIFace) *Service {\n\treturn &Service{\n\t\t&http.Server{ //nolint:gosec\n\t\t\tAddr: address,\n\t\t\tHandler: promhttp.HandlerFor(metricsService.registry, promhttp.HandlerOpts{\n\t\t\t\tErrorLog: logger.StdLogger(mlog.LvlError),\n\t\t\t}),\n\t\t},\n\t}\n}\n\n// Run will start the prometheus server.\nfunc (h *Service) Run() error {\n\treturn errors.Wrap(h.Server.ListenAndServe(), \"prometheus ListenAndServe\")\n}\n\n// Shutdown will shutdown the prometheus server.\nfunc (h *Service) Shutdown() error {\n\treturn errors.Wrap(h.Server.Close(), \"prometheus Close\")\n}\n"
  },
  {
    "path": "server/services/notify/notifylogger/logger_backend.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage notifylogger\n\nimport (\n\t\"github.com/mattermost/focalboard/server/services/notify\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nconst (\n\tbackendName = \"notifyLogger\"\n)\n\ntype Backend struct {\n\tlogger mlog.LoggerIFace\n\tlevel  mlog.Level\n}\n\nfunc New(logger mlog.LoggerIFace, level mlog.Level) *Backend {\n\treturn &Backend{\n\t\tlogger: logger,\n\t\tlevel:  level,\n\t}\n}\n\nfunc (b *Backend) Start() error {\n\treturn nil\n}\n\nfunc (b *Backend) ShutDown() error {\n\t_ = b.logger.Flush()\n\treturn nil\n}\n\nfunc (b *Backend) BlockChanged(evt notify.BlockChangeEvent) error {\n\tvar board string\n\tvar card string\n\n\tif evt.Board != nil {\n\t\tboard = evt.Board.Title\n\t}\n\tif evt.Card != nil {\n\t\tcard = evt.Card.Title\n\t}\n\n\tb.logger.Log(b.level, \"Block change event\",\n\t\tmlog.String(\"action\", string(evt.Action)),\n\t\tmlog.String(\"board\", board),\n\t\tmlog.String(\"card\", card),\n\t\tmlog.String(\"block_id\", evt.BlockChanged.ID),\n\t)\n\treturn nil\n}\n\nfunc (b *Backend) Name() string {\n\treturn backendName\n}\n"
  },
  {
    "path": "server/services/notify/notifymentions/app_api.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\npackage notifymentions\n\nimport \"github.com/mattermost/focalboard/server/model\"\n\ntype AppAPI interface {\n\tGetMemberForBoard(boardID, userID string) (*model.BoardMember, error)\n\tAddMemberToBoard(member *model.BoardMember) (*model.BoardMember, error)\n}\n"
  },
  {
    "path": "server/services/notify/notifymentions/delivery.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage notifymentions\n\nimport (\n\t\"github.com/mattermost/focalboard/server/services/notify\"\n\n\tmm_model \"github.com/mattermost/mattermost/server/public/model\"\n)\n\n// MentionDelivery provides an interface for delivering @mention notifications to other systems, such as\n// channels server via plugin API.\n// On success the user id of the user mentioned is returned.\ntype MentionDelivery interface {\n\tMentionDeliver(mentionedUser *mm_model.User, extract string, evt notify.BlockChangeEvent) (string, error)\n\tUserByUsername(mentionUsername string) (*mm_model.User, error)\n}\n"
  },
  {
    "path": "server/services/notify/notifymentions/extract.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage notifymentions\n\nimport \"strings\"\n\nconst (\n\tdefPrefixLines    = 2\n\tdefPrefixMaxChars = 100\n\tdefSuffixLines    = 2\n\tdefSuffixMaxChars = 100\n)\n\ntype limits struct {\n\tprefixLines    int\n\tprefixMaxChars int\n\tsuffixLines    int\n\tsuffixMaxChars int\n}\n\nfunc newLimits() limits {\n\treturn limits{\n\t\tprefixLines:    defPrefixLines,\n\t\tprefixMaxChars: defPrefixMaxChars,\n\t\tsuffixLines:    defSuffixLines,\n\t\tsuffixMaxChars: defSuffixMaxChars,\n\t}\n}\n\n// extractText returns all or a subset of the input string, such that\n// no more than `prefixLines` lines preceding the mention and `suffixLines`\n// lines after the mention are returned, and no more than approx\n// prefixMaxChars+suffixMaxChars are returned.\nfunc extractText(s string, mention string, limits limits) string {\n\tif !strings.HasPrefix(mention, \"@\") {\n\t\tmention = \"@\" + mention\n\t}\n\tlines := strings.Split(s, \"\\n\")\n\n\t// find first line with mention\n\tfound := -1\n\tfor i, l := range lines {\n\t\tif strings.Contains(l, mention) {\n\t\t\tfound = i\n\t\t\tbreak\n\t\t}\n\t}\n\tif found == -1 {\n\t\treturn \"\"\n\t}\n\n\tprefix := safeConcat(lines, found-limits.prefixLines, found)\n\tsuffix := safeConcat(lines, found+1, found+limits.suffixLines+1)\n\tcombined := strings.TrimSpace(strings.Join([]string{prefix, lines[found], suffix}, \"\\n\"))\n\n\t// find mention position within\n\tpos := strings.Index(combined, mention)\n\tpos = max(pos, 0)\n\n\treturn safeSubstr(combined, pos-limits.prefixMaxChars, pos+limits.suffixMaxChars)\n}\n\nfunc safeConcat(lines []string, start int, end int) string {\n\tcount := len(lines)\n\tstart = min(max(start, 0), count)\n\tend = min(max(end, start), count)\n\n\tvar sb strings.Builder\n\tfor i := start; i < end; i++ {\n\t\tif lines[i] != \"\" {\n\t\t\tsb.WriteString(lines[i])\n\t\t\tsb.WriteByte('\\n')\n\t\t}\n\t}\n\treturn strings.TrimSpace(sb.String())\n}\n\nfunc safeSubstr(s string, start int, end int) string {\n\tcount := len(s)\n\tstart = min(max(start, 0), count)\n\tend = min(max(end, start), count)\n\treturn s[start:end]\n}\n\nfunc min(a int, b int) int {\n\tif a < b {\n\t\treturn a\n\t}\n\treturn b\n}\n\nfunc max(a int, b int) int {\n\tif a > b {\n\t\treturn a\n\t}\n\treturn b\n}\n"
  },
  {
    "path": "server/services/notify/notifymentions/extract_test.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage notifymentions\n\nimport (\n\t\"strings\"\n\t\"testing\"\n)\n\nconst (\n\ts0 = \"Zero is in the mind @billy.\"\n\ts1 = \"This is line 1.\"\n\ts2 = \"Line two is right here.\"\n\ts3 = \"Three is the line I am.\"\n\ts4 = \"'Four score and seven years...', said @lincoln.\"\n\ts5 = \"Fast Five was arguably the best F&F film.\"\n\ts6 = \"Big Hero 6 may have an inflated sense of self.\"\n\ts7 = \"The seventh sign, @sarah, will be a failed unit test.\"\n)\n\nvar (\n\tall       = []string{s0, s1, s2, s3, s4, s5, s6, s7}\n\tallConcat = strings.Join(all, \"\\n\")\n\n\textractLimits = limits{\n\t\tprefixLines:    2,\n\t\tprefixMaxChars: 100,\n\t\tsuffixLines:    2,\n\t\tsuffixMaxChars: 100,\n\t}\n)\n\nfunc join(s ...string) string {\n\treturn strings.Join(s, \"\\n\")\n}\n\nfunc Test_extractText(t *testing.T) {\n\ttype args struct {\n\t\ts       string\n\t\tmention string\n\t\tlimits  limits\n\t}\n\ttests := []struct {\n\t\tname string\n\t\targs args\n\t\twant string\n\t}{\n\t\t{name: \"good\", want: join(s2, s3, s4, s5, s6), args: args{mention: \"@lincoln\", limits: extractLimits, s: allConcat}},\n\t\t{name: \"not found\", want: \"\", args: args{mention: \"@bogus\", limits: extractLimits, s: allConcat}},\n\t\t{name: \"one line\", want: join(s4), args: args{mention: \"@lincoln\", limits: extractLimits, s: s4}},\n\t\t{name: \"two lines\", want: join(s4, s5), args: args{mention: \"@lincoln\", limits: extractLimits, s: join(s4, s5)}},\n\t\t{name: \"zero lines\", want: \"\", args: args{mention: \"@lincoln\", limits: extractLimits, s: \"\"}},\n\t\t{name: \"first line mention\", want: join(s0, s1, s2), args: args{mention: \"@billy\", limits: extractLimits, s: allConcat}},\n\t\t{name: \"last line mention\", want: join(s5[7:], s6, s7), args: args{mention: \"@sarah\", limits: extractLimits, s: allConcat}},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := extractText(tt.args.s, tt.args.mention, tt.args.limits); got != tt.want {\n\t\t\t\tt.Errorf(\"extractText()\\ngot:\\n%v\\nwant:\\n%v\\n\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc Test_safeConcat(t *testing.T) {\n\ttype args struct {\n\t\tlines []string\n\t\tstart int\n\t\tend   int\n\t}\n\ttests := []struct {\n\t\tname string\n\t\targs args\n\t\twant string\n\t}{\n\t\t{name: \"out of range\", want: join(s0, s1, s2, s3, s4, s5, s6, s7), args: args{start: -22, end: 99, lines: all}},\n\t\t{name: \"2,3\", want: join(s2, s3), args: args{start: 2, end: 4, lines: all}},\n\t\t{name: \"mismatch\", want: \"\", args: args{start: 4, end: 2, lines: all}},\n\t\t{name: \"empty\", want: \"\", args: args{start: 2, end: 4, lines: []string{}}},\n\t\t{name: \"nil\", want: \"\", args: args{start: 2, end: 4, lines: nil}},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := safeConcat(tt.args.lines, tt.args.start, tt.args.end); got != tt.want {\n\t\t\t\tt.Errorf(\"safeConcat() = [%v], want [%v]\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc Test_safeSubstr(t *testing.T) {\n\ttype args struct {\n\t\ts     string\n\t\tstart int\n\t\tend   int\n\t}\n\ttests := []struct {\n\t\tname string\n\t\targs args\n\t\twant string\n\t}{\n\t\t{name: \"good\", want: \"is line\", args: args{start: 33, end: 40, s: join(s0, s1, s2)}},\n\t\t{name: \"out of range\", want: allConcat, args: args{start: -10, end: 1000, s: allConcat}},\n\t\t{name: \"mismatch\", want: \"\", args: args{start: 33, end: 26, s: allConcat}},\n\t\t{name: \"empty\", want: \"\", args: args{start: 2, end: 4, s: \"\"}},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := safeSubstr(tt.args.s, tt.args.start, tt.args.end); got != tt.want {\n\t\t\t\tt.Errorf(\"safeSubstr()\\ngot:\\n[%v]\\nwant:\\n[%v]\\n\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "server/services/notify/notifymentions/mentions.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage notifymentions\n\nimport (\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\n\tmm_model \"github.com/mattermost/mattermost/server/public/model\"\n)\n\nvar atMentionRegexp = regexp.MustCompile(`\\B@[[:alnum:]][[:alnum:]\\.\\-_:]*`)\n\n// extractMentions extracts any mentions in the specified block and returns\n// a slice of usernames.\nfunc extractMentions(block *model.Block) map[string]struct{} {\n\tmentions := make(map[string]struct{})\n\tif block == nil || !strings.Contains(block.Title, \"@\") {\n\t\treturn mentions\n\t}\n\n\tstr := block.Title\n\n\tfor _, match := range atMentionRegexp.FindAllString(str, -1) {\n\t\tname := mm_model.NormalizeUsername(match[1:])\n\t\tif mm_model.IsValidUsernameAllowRemote(name) {\n\t\t\tmentions[name] = struct{}{}\n\t\t}\n\t}\n\treturn mentions\n}\n"
  },
  {
    "path": "server/services/notify/notifymentions/mentions_backend.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage notifymentions\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"sync\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/notify\"\n\t\"github.com/mattermost/focalboard/server/services/permissions\"\n\t\"github.com/wiggin77/merror\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nconst (\n\tbackendName = \"notifyMentions\"\n)\n\nvar (\n\tErrMentionPermission = errors.New(\"mention not permitted\")\n)\n\ntype MentionListener interface {\n\tOnMention(userID string, evt notify.BlockChangeEvent)\n}\n\ntype BackendParams struct {\n\tAppAPI      AppAPI\n\tPermissions permissions.PermissionsService\n\tDelivery    MentionDelivery\n\tLogger      mlog.LoggerIFace\n}\n\n// Backend provides the notification backend for @mentions.\ntype Backend struct {\n\tappAPI      AppAPI\n\tpermissions permissions.PermissionsService\n\tdelivery    MentionDelivery\n\tlogger      mlog.LoggerIFace\n\n\tmux       sync.RWMutex\n\tlisteners []MentionListener\n}\n\nfunc New(params BackendParams) *Backend {\n\treturn &Backend{\n\t\tappAPI:      params.AppAPI,\n\t\tpermissions: params.Permissions,\n\t\tdelivery:    params.Delivery,\n\t\tlogger:      params.Logger,\n\t}\n}\n\nfunc (b *Backend) Start() error {\n\treturn nil\n}\n\nfunc (b *Backend) ShutDown() error {\n\t_ = b.logger.Flush()\n\treturn nil\n}\n\nfunc (b *Backend) Name() string {\n\treturn backendName\n}\n\nfunc (b *Backend) AddListener(l MentionListener) {\n\tb.mux.Lock()\n\tdefer b.mux.Unlock()\n\tb.listeners = append(b.listeners, l)\n\tb.logger.Debug(\"Mention listener added.\", mlog.Int(\"listener_count\", len(b.listeners)))\n}\n\nfunc (b *Backend) RemoveListener(l MentionListener) {\n\tb.mux.Lock()\n\tdefer b.mux.Unlock()\n\tlist := make([]MentionListener, 0, len(b.listeners))\n\tfor _, listener := range b.listeners {\n\t\tif listener != l {\n\t\t\tlist = append(list, listener)\n\t\t}\n\t}\n\tb.listeners = list\n\tb.logger.Debug(\"Mention listener removed.\", mlog.Int(\"listener_count\", len(b.listeners)))\n}\n\nfunc (b *Backend) BlockChanged(evt notify.BlockChangeEvent) error {\n\tif evt.Board == nil || evt.Card == nil {\n\t\treturn nil\n\t}\n\n\tif evt.Action == notify.Delete {\n\t\treturn nil\n\t}\n\n\tswitch evt.BlockChanged.Type {\n\tcase model.TypeText, model.TypeComment, model.TypeImage:\n\tdefault:\n\t\treturn nil\n\t}\n\n\tmentions := extractMentions(evt.BlockChanged)\n\tif len(mentions) == 0 {\n\t\treturn nil\n\t}\n\n\toldMentions := extractMentions(evt.BlockOld)\n\tmerr := merror.New()\n\n\tb.mux.RLock()\n\tlisteners := make([]MentionListener, len(b.listeners))\n\tcopy(listeners, b.listeners)\n\tb.mux.RUnlock()\n\n\tfor username := range mentions {\n\t\tif _, exists := oldMentions[username]; exists {\n\t\t\t// the mention already existed; no need to notify again\n\t\t\tcontinue\n\t\t}\n\n\t\textract := extractText(evt.BlockChanged.Title, username, newLimits())\n\n\t\tuserID, err := b.deliverMentionNotification(username, extract, evt)\n\t\tif err != nil {\n\t\t\tif errors.Is(err, ErrMentionPermission) {\n\t\t\t\tb.logger.Debug(\"Cannot deliver notification\", mlog.String(\"user\", username), mlog.Err(err))\n\t\t\t} else {\n\t\t\t\tmerr.Append(fmt.Errorf(\"cannot deliver notification for @%s: %w\", username, err))\n\t\t\t}\n\t\t}\n\n\t\tif userID == \"\" {\n\t\t\t// was a `@` followed by something other than a username.\n\t\t\tcontinue\n\t\t}\n\n\t\tb.logger.Debug(\"Mention notification delivered\",\n\t\t\tmlog.String(\"user\", username),\n\t\t\tmlog.Int(\"listener_count\", len(listeners)),\n\t\t)\n\n\t\tfor _, listener := range listeners {\n\t\t\tsafeCallListener(listener, userID, evt, b.logger)\n\t\t}\n\t}\n\treturn merr.ErrorOrNil()\n}\n\nfunc safeCallListener(listener MentionListener, userID string, evt notify.BlockChangeEvent, logger mlog.LoggerIFace) {\n\t// don't let panicky listeners stop notifications\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tlogger.Error(\"panic calling @mention notification listener\", mlog.Any(\"err\", r))\n\t\t}\n\t}()\n\tlistener.OnMention(userID, evt)\n}\n\nfunc (b *Backend) deliverMentionNotification(username string, extract string, evt notify.BlockChangeEvent) (string, error) {\n\tmentionedUser, err := b.delivery.UserByUsername(username)\n\tif err != nil {\n\t\tif model.IsErrNotFound(err) {\n\t\t\t// not really an error; could just be someone typed \"@sometext\"\n\t\t\treturn \"\", nil\n\t\t} else {\n\t\t\treturn \"\", fmt.Errorf(\"cannot lookup mentioned user: %w\", err)\n\t\t}\n\t}\n\n\tif evt.ModifiedBy == nil {\n\t\treturn \"\", fmt.Errorf(\"invalid user cannot mention: %w\", ErrMentionPermission)\n\t}\n\n\tif evt.Board.Type == model.BoardTypeOpen {\n\t\t// public board rules:\n\t\t//    - admin, editor, commenter: can mention anyone on team (mentioned users are automatically added to board)\n\t\t//    - guest: can mention board members\n\t\tswitch {\n\t\tcase evt.ModifiedBy.SchemeAdmin, evt.ModifiedBy.SchemeEditor, evt.ModifiedBy.SchemeCommenter:\n\t\t\tif !b.permissions.HasPermissionToTeam(mentionedUser.Id, evt.TeamID, model.PermissionViewTeam) {\n\t\t\t\treturn \"\", fmt.Errorf(\"%s cannot mention non-team member %s : %w\", evt.ModifiedBy.UserID, mentionedUser.Id, ErrMentionPermission)\n\t\t\t}\n\t\t\t// add mentioned user to board (if not already a member)\n\t\t\tmember, err := b.appAPI.GetMemberForBoard(evt.Board.ID, mentionedUser.Id)\n\t\t\tif member == nil || model.IsErrNotFound(err) {\n\t\t\t\t// create memberships based on minimum board role\n\t\t\t\tnewBoardMember := &model.BoardMember{\n\t\t\t\t\tUserID:  mentionedUser.Id,\n\t\t\t\t\tBoardID: evt.Board.ID,\n\t\t\t\t\tSchemeViewer: evt.Board.MinimumRole == model.BoardRoleViewer ||\n\t\t\t\t\t\tevt.Board.MinimumRole == model.BoardRoleCommenter ||\n\t\t\t\t\t\tevt.Board.MinimumRole == model.BoardRoleEditor,\n\t\t\t\t\tSchemeCommenter: evt.Board.MinimumRole == model.BoardRoleCommenter ||\n\t\t\t\t\t\tevt.Board.MinimumRole == model.BoardRoleEditor,\n\t\t\t\t\tSchemeEditor: evt.Board.MinimumRole == model.BoardRoleEditor,\n\t\t\t\t}\n\t\t\t\tif _, err = b.appAPI.AddMemberToBoard(newBoardMember); err != nil {\n\t\t\t\t\treturn \"\", fmt.Errorf(\"cannot add mentioned user %s to board %s: %w\", mentionedUser.Id, evt.Board.ID, err)\n\t\t\t\t}\n\t\t\t\tb.logger.Debug(\"auto-added mentioned user to board\",\n\t\t\t\t\tmlog.String(\"user_id\", mentionedUser.Id),\n\t\t\t\t\tmlog.String(\"board_id\", evt.Board.ID),\n\t\t\t\t\tmlog.String(\"board_type\", string(evt.Board.Type)),\n\t\t\t\t)\n\t\t\t} else {\n\t\t\t\tb.logger.Debug(\"skipping auto-add mentioned user to board; already a member\",\n\t\t\t\t\tmlog.String(\"user_id\", mentionedUser.Id),\n\t\t\t\t\tmlog.String(\"board_id\", evt.Board.ID),\n\t\t\t\t\tmlog.String(\"board_type\", string(evt.Board.Type)),\n\t\t\t\t)\n\t\t\t}\n\t\tcase evt.ModifiedBy.SchemeViewer:\n\t\t\t// viewer should not have gotten this far since they cannot add text to a card\n\t\t\treturn \"\", fmt.Errorf(\"%s (viewer) cannot mention user %s: %w\", evt.ModifiedBy.UserID, mentionedUser.Id, ErrMentionPermission)\n\t\tdefault:\n\t\t\t// this is a guest\n\t\t\tif !b.permissions.HasPermissionToBoard(mentionedUser.Id, evt.Board.ID, model.PermissionViewBoard) {\n\t\t\t\treturn \"\", fmt.Errorf(\"%s cannot mention non-board member %s : %w\", evt.ModifiedBy.UserID, mentionedUser.Id, ErrMentionPermission)\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// private board rules:\n\t\t//    - admin, editor, commenter, guest: can mention board members\n\t\tswitch {\n\t\tcase evt.ModifiedBy.SchemeViewer:\n\t\t\t// viewer should not have gotten this far since they cannot add text to a card\n\t\t\treturn \"\", fmt.Errorf(\"%s (viewer) cannot mention user %s: %w\", evt.ModifiedBy.UserID, mentionedUser.Id, ErrMentionPermission)\n\t\tdefault:\n\t\t\t// everyone else can mention board members\n\t\t\tif !b.permissions.HasPermissionToBoard(mentionedUser.Id, evt.Board.ID, model.PermissionViewBoard) {\n\t\t\t\treturn \"\", fmt.Errorf(\"%s cannot mention non-board member %s : %w\", evt.ModifiedBy.UserID, mentionedUser.Id, ErrMentionPermission)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn b.delivery.MentionDeliver(mentionedUser, extract, evt)\n}\n"
  },
  {
    "path": "server/services/notify/notifymentions/mentions_test.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage notifymentions\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\n\tmm_model \"github.com/mattermost/mattermost/server/public/model\"\n)\n\nfunc Test_extractMentions(t *testing.T) {\n\ttests := []struct {\n\t\tname  string\n\t\tblock *model.Block\n\t\twant  map[string]struct{}\n\t}{\n\t\t{name: \"empty\", block: makeBlock(\"\"), want: makeMap()},\n\t\t{name: \"zero mentions\", block: makeBlock(\"This is some text.\"), want: makeMap()},\n\t\t{name: \"one mention\", block: makeBlock(\"Hello @user1\"), want: makeMap(\"user1\")},\n\t\t{name: \"multiple mentions\", block: makeBlock(\"Hello @user1, @user2 and @user3\"), want: makeMap(\"user1\", \"user2\", \"user3\")},\n\t\t{name: \"include period\", block: makeBlock(\"Hello @user1.\"), want: makeMap(\"user1.\")},\n\t\t{name: \"include underscore\", block: makeBlock(\"Hello @user1_\"), want: makeMap(\"user1_\")},\n\t\t{name: \"don't include comma\", block: makeBlock(\"Hello @user1,\"), want: makeMap(\"user1\")},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := extractMentions(tt.block); !reflect.DeepEqual(got, tt.want) {\n\t\t\t\tt.Errorf(\"extractMentions() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc makeBlock(text string) *model.Block {\n\treturn &model.Block{\n\t\tID:    mm_model.NewId(),\n\t\tType:  model.TypeComment,\n\t\tTitle: text,\n\t}\n}\n\nfunc makeMap(mentions ...string) map[string]struct{} {\n\tm := make(map[string]struct{})\n\tfor _, mention := range mentions {\n\t\tm[mention] = struct{}{}\n\t}\n\treturn m\n}\n"
  },
  {
    "path": "server/services/notify/notifysubscriptions/app_api.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage notifysubscriptions\n\nimport (\n\t\"time\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n)\n\ntype AppAPI interface {\n\tGetBlockHistory(blockID string, opts model.QueryBlockHistoryOptions) ([]*model.Block, error)\n\tGetBlockHistoryNewestChildren(parentID string, opts model.QueryBlockHistoryChildOptions) ([]*model.Block, bool, error)\n\tGetBoardAndCardByID(blockID string) (board *model.Board, card *model.Block, err error)\n\n\tGetUserByID(userID string) (*model.User, error)\n\n\tCreateSubscription(sub *model.Subscription) (*model.Subscription, error)\n\tGetSubscribersForBlock(blockID string) ([]*model.Subscriber, error)\n\tUpdateSubscribersNotifiedAt(blockID string, notifyAt int64) error\n\n\tUpsertNotificationHint(hint *model.NotificationHint, notificationFreq time.Duration) (*model.NotificationHint, error)\n\tGetNextNotificationHint(remove bool) (*model.NotificationHint, error)\n}\n"
  },
  {
    "path": "server/services/notify/notifysubscriptions/delivery.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage notifysubscriptions\n\nimport (\n\t\"github.com/mattermost/focalboard/server/model\"\n\n\tmm_model \"github.com/mattermost/mattermost/server/public/model\"\n)\n\n// SubscriptionDelivery provides an interface for delivering subscription notifications to other systems, such as\n// channels server via plugin API.\ntype SubscriptionDelivery interface {\n\tSubscriptionDeliverSlackAttachments(teamID string, subscriberID string, subscriberType model.SubscriberType,\n\t\tattachments []*mm_model.SlackAttachment) error\n}\n"
  },
  {
    "path": "server/services/notify/notifysubscriptions/diff.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage notifysubscriptions\n\nimport (\n\t\"fmt\"\n\t\"sort\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\n// Diff represents a difference between two versions of a block.\ntype Diff struct {\n\tBoard   *model.Board\n\tCard    *model.Block\n\tAuthors StringMap\n\n\tBlockType model.BlockType\n\tOldBlock  *model.Block\n\tNewBlock  *model.Block\n\n\tUpdateAt int64 // the UpdateAt of the latest version of the block\n\n\tschemaDiffs []SchemaDiff\n\tPropDiffs   []PropDiff\n\n\tDiffs []*Diff // Diffs for child blocks\n}\n\ntype PropDiff struct {\n\tID       string // property id\n\tIndex    int\n\tName     string\n\tOldValue string\n\tNewValue string\n}\n\ntype SchemaDiff struct {\n\tBoard *model.Board\n\n\tOldPropDef *model.PropDef\n\tNewPropDef *model.PropDef\n}\n\ntype diffGenerator struct {\n\tboard *model.Board\n\tcard  *model.Block\n\n\tstore        AppAPI\n\thint         *model.NotificationHint\n\tlastNotifyAt int64\n\tlogger       mlog.LoggerIFace\n}\n\nfunc (dg *diffGenerator) generateDiffs() ([]*Diff, error) {\n\t// use block_history to fetch blocks in case they were deleted and no longer exist in blocks table.\n\topts := model.QueryBlockHistoryOptions{\n\t\tLimit:      1,\n\t\tDescending: true,\n\t}\n\tblocks, err := dg.store.GetBlockHistory(dg.hint.BlockID, opts)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not get block for notification: %w\", err)\n\t}\n\tif len(blocks) == 0 {\n\t\treturn nil, fmt.Errorf(\"block not found for notification: %w\", err)\n\t}\n\tblock := blocks[0]\n\n\tif dg.board == nil || dg.card == nil {\n\t\treturn nil, fmt.Errorf(\"cannot generate diff for block %s; must have a valid board and card: %w\", dg.hint.BlockID, err)\n\t}\n\n\t// parse board's property schema here so it only happens once.\n\tschema, err := model.ParsePropertySchema(dg.board)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not parse property schema for board %s: %w\", dg.board.ID, err)\n\t}\n\n\tswitch block.Type {\n\tcase model.TypeBoard:\n\t\tdg.logger.Warn(\"generateDiffs for board skipped\", mlog.String(\"block_id\", block.ID))\n\t\t// TODO: Fix this\n\t\t// return dg.generateDiffsForBoard(block, schema)\n\t\treturn nil, nil\n\tcase model.TypeCard:\n\t\tdiff, err := dg.generateDiffsForCard(block, schema)\n\t\tif err != nil || diff == nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn []*Diff{diff}, nil\n\tdefault:\n\t\tdiff, err := dg.generateDiffForBlock(block, schema)\n\t\tif err != nil || diff == nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn []*Diff{diff}, nil\n\t}\n}\n\n// TODO: fix this\n/*\nfunc (dg *diffGenerator) generateDiffsForBoard(board *model.Board, schema model.PropSchema) ([]*Diff, error) {\n\topts := model.QuerySubtreeOptions{\n\t\tAfterUpdateAt: dg.lastNotifyAt,\n\t}\n\n\tfind all child blocks of the board that updated since last notify.\n\tblocks, err := dg.store.GetSubTree2(board.ID, board.ID, opts)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not get subtree for board %s: %w\", board.ID, err)\n\t}\n\n\tvar diffs []*Diff\n\n\tgenerate diff for board title change or description\n\tboardDiff, err := dg.generateDiffForBlock(board, schema)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not generate diff for board %s: %w\", board.ID, err)\n\t}\n\n\tif boardDiff != nil {\n\t\tTODO: phase 2 feature (generate schema diffs and add to board diff) goes here.\n\t\tdiffs = append(diffs, boardDiff)\n\t}\n\n\tfor _, b := range blocks {\n\t\tblock := b\n\t\tif block.Type == model.TypeCard {\n\t\t\tcardDiffs, err := dg.generateDiffsForCard(&block, schema)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tdiffs = append(diffs, cardDiffs)\n\t\t}\n\t}\n\treturn diffs, nil\n}\n*/\n\nfunc (dg *diffGenerator) generateDiffsForCard(card *model.Block, schema model.PropSchema) (*Diff, error) {\n\t// generate diff for card title change and properties.\n\tcardDiff, err := dg.generateDiffForBlock(card, schema)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not generate diff for card %s: %w\", card.ID, err)\n\t}\n\n\t// fetch all card content blocks that were updated after last notify\n\topts := model.QueryBlockHistoryChildOptions{\n\t\tAfterUpdateAt: dg.lastNotifyAt,\n\t}\n\tblocks, _, err := dg.store.GetBlockHistoryNewestChildren(card.ID, opts)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not get subtree for card %s: %w\", card.ID, err)\n\t}\n\n\tauthors := make(StringMap)\n\n\t// walk child blocks\n\tvar childDiffs []*Diff\n\tfor i := range blocks {\n\t\tif blocks[i].ID == card.ID {\n\t\t\tcontinue\n\t\t}\n\n\t\tblockDiff, err := dg.generateDiffForBlock(blocks[i], schema)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"could not generate diff for block %s: %w\", blocks[i].ID, err)\n\t\t}\n\t\tif blockDiff != nil {\n\t\t\tchildDiffs = append(childDiffs, blockDiff)\n\t\t\tauthors.Append(blockDiff.Authors)\n\t\t}\n\t}\n\n\tdg.logger.Debug(\"generateDiffsForCard\",\n\t\tmlog.Bool(\"has_top_changes\", cardDiff != nil),\n\t\tmlog.Int(\"subtree\", len(blocks)),\n\t\tmlog.Array(\"author_names\", authors.Values()),\n\t\tmlog.Int(\"child_diffs\", len(childDiffs)),\n\t)\n\n\tif len(childDiffs) != 0 {\n\t\tif cardDiff == nil { // will be nil if the card has no other changes besides child diffs\n\t\t\tcardDiff = &Diff{\n\t\t\t\tBoard:       dg.board,\n\t\t\t\tCard:        card,\n\t\t\t\tAuthors:     make(StringMap),\n\t\t\t\tBlockType:   card.Type,\n\t\t\t\tOldBlock:    card,\n\t\t\t\tNewBlock:    card,\n\t\t\t\tUpdateAt:    card.UpdateAt,\n\t\t\t\tPropDiffs:   nil,\n\t\t\t\tschemaDiffs: nil,\n\t\t\t}\n\t\t}\n\t\tcardDiff.Diffs = childDiffs\n\t}\n\tcardDiff.Authors.Append(authors)\n\n\treturn cardDiff, nil\n}\n\nfunc (dg *diffGenerator) generateDiffForBlock(newBlock *model.Block, schema model.PropSchema) (*Diff, error) {\n\tdg.logger.Debug(\"generateDiffForBlock - new block\",\n\t\tmlog.String(\"block_id\", newBlock.ID),\n\t\tmlog.String(\"block_type\", string(newBlock.Type)),\n\t\tmlog.String(\"modified_by\", newBlock.ModifiedBy),\n\t\tmlog.Int(\"update_at\", newBlock.UpdateAt),\n\t)\n\n\t// find the version of the block as it was at the time of last notify.\n\topts := model.QueryBlockHistoryOptions{\n\t\tBeforeUpdateAt: dg.lastNotifyAt + 1,\n\t\tLimit:          1,\n\t\tDescending:     true,\n\t}\n\thistory, err := dg.store.GetBlockHistory(newBlock.ID, opts)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not get block history for block %s: %w\", newBlock.ID, err)\n\t}\n\n\tvar oldBlock *model.Block\n\tif len(history) != 0 {\n\t\toldBlock = history[0]\n\n\t\tdg.logger.Debug(\"generateDiffForBlock - old block\",\n\t\t\tmlog.String(\"block_id\", oldBlock.ID),\n\t\t\tmlog.String(\"block_type\", string(oldBlock.Type)),\n\t\t\tmlog.Int(\"before_update_at\", dg.lastNotifyAt),\n\t\t\tmlog.String(\"modified_by\", oldBlock.ModifiedBy),\n\t\t\tmlog.Int(\"update_at\", oldBlock.UpdateAt),\n\t\t)\n\t}\n\n\t// find all the versions of the blocks that changed so we can gather all the author usernames.\n\topts = model.QueryBlockHistoryOptions{\n\t\tAfterUpdateAt: dg.lastNotifyAt,\n\t\tDescending:    true,\n\t}\n\tchgBlocks, err := dg.store.GetBlockHistory(newBlock.ID, opts)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error getting block history for block %s: %w\", newBlock.ID, err)\n\t}\n\tauthors := make(StringMap)\n\n\tdg.logger.Debug(\"generateDiffForBlock - authors\",\n\t\tmlog.Int(\"after_update_at\", dg.lastNotifyAt),\n\t\tmlog.Int(\"history_count\", len(chgBlocks)),\n\t)\n\n\t// have to loop through history slice because GetBlockHistory does not return pointers.\n\tfor _, b := range chgBlocks {\n\t\tuser, err := dg.store.GetUserByID(b.ModifiedBy)\n\t\tif err != nil || user == nil {\n\t\t\tdg.logger.Error(\"could not fetch username for block\",\n\t\t\t\tmlog.String(\"modified_by\", b.ModifiedBy),\n\t\t\t\tmlog.Err(err),\n\t\t\t)\n\t\t\tauthors.Add(b.ModifiedBy, \"unknown_user\") // todo: localize this when server has i18n\n\t\t} else {\n\t\t\tauthors.Add(user.ID, user.Username)\n\t\t}\n\t}\n\n\tpropDiffs := dg.generatePropDiffs(oldBlock, newBlock, schema)\n\n\tdg.logger.Debug(\"generateDiffForBlock - results\",\n\t\tmlog.String(\"block_id\", newBlock.ID),\n\t\tmlog.String(\"block_type\", string(newBlock.Type)),\n\t\tmlog.Array(\"author_names\", authors.Values()),\n\t\tmlog.Int(\"history_count\", len(history)),\n\t\tmlog.Int(\"prop_diff_count\", len(propDiffs)),\n\t)\n\n\tdiff := &Diff{\n\t\tBoard:       dg.board,\n\t\tCard:        dg.card,\n\t\tAuthors:     authors,\n\t\tBlockType:   newBlock.Type,\n\t\tOldBlock:    oldBlock,\n\t\tNewBlock:    newBlock,\n\t\tUpdateAt:    newBlock.UpdateAt,\n\t\tPropDiffs:   propDiffs,\n\t\tschemaDiffs: nil,\n\t}\n\treturn diff, nil\n}\n\nfunc (dg *diffGenerator) generatePropDiffs(oldBlock, newBlock *model.Block, schema model.PropSchema) []PropDiff {\n\tvar propDiffs []PropDiff\n\n\toldProps, err := model.ParseProperties(oldBlock, schema, dg.store)\n\tif err != nil {\n\t\tdg.logger.Error(\"Cannot parse properties for old block\",\n\t\t\tmlog.String(\"block_id\", oldBlock.ID),\n\t\t\tmlog.Err(err),\n\t\t)\n\t}\n\n\tnewProps, err := model.ParseProperties(newBlock, schema, dg.store)\n\tif err != nil {\n\t\tdg.logger.Error(\"Cannot parse properties for new block\",\n\t\t\tmlog.String(\"block_id\", oldBlock.ID),\n\t\t\tmlog.Err(err),\n\t\t)\n\t}\n\n\t// look for new or changed properties.\n\tfor k, prop := range newProps {\n\t\toldP, ok := oldProps[k]\n\t\tif ok {\n\t\t\t// prop changed\n\t\t\tif prop.Value != oldP.Value {\n\t\t\t\tpropDiffs = append(propDiffs, PropDiff{\n\t\t\t\t\tID:       prop.ID,\n\t\t\t\t\tIndex:    prop.Index,\n\t\t\t\t\tName:     prop.Name,\n\t\t\t\t\tNewValue: prop.Value,\n\t\t\t\t\tOldValue: oldP.Value,\n\t\t\t\t})\n\t\t\t}\n\t\t} else {\n\t\t\t// prop added\n\t\t\tpropDiffs = append(propDiffs, PropDiff{\n\t\t\t\tID:       prop.ID,\n\t\t\t\tIndex:    prop.Index,\n\t\t\t\tName:     prop.Name,\n\t\t\t\tNewValue: prop.Value,\n\t\t\t\tOldValue: \"\",\n\t\t\t})\n\t\t}\n\t}\n\n\t// look for deleted properties\n\tfor k, prop := range oldProps {\n\t\t_, ok := newProps[k]\n\t\tif !ok {\n\t\t\t// prop deleted\n\t\t\tpropDiffs = append(propDiffs, PropDiff{\n\t\t\t\tID:       prop.ID,\n\t\t\t\tIndex:    prop.Index,\n\t\t\t\tName:     prop.Name,\n\t\t\t\tNewValue: \"\",\n\t\t\t\tOldValue: prop.Value,\n\t\t\t})\n\t\t}\n\t}\n\treturn sortPropDiffs(propDiffs)\n}\n\nfunc sortPropDiffs(propDiffs []PropDiff) []PropDiff {\n\tif len(propDiffs) == 0 {\n\t\treturn propDiffs\n\t}\n\n\tsort.Slice(propDiffs, func(i, j int) bool {\n\t\treturn propDiffs[i].Index < propDiffs[j].Index\n\t})\n\treturn propDiffs\n}\n"
  },
  {
    "path": "server/services/notify/notifysubscriptions/diff2markdown.go",
    "content": "package notifysubscriptions\n\nimport (\n\t\"strings\"\n\n\t\"github.com/sergi/go-diff/diffmatchpatch\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nfunc generateMarkdownDiff(oldText string, newText string, logger mlog.LoggerIFace) string {\n\toldTxtNorm := normalizeText(oldText)\n\tnewTxtNorm := normalizeText(newText)\n\n\tdmp := diffmatchpatch.New()\n\n\tdiffs := dmp.DiffMain(oldTxtNorm, newTxtNorm, false)\n\n\tdiffs = dmp.DiffCleanupSemantic(diffs)\n\tdiffs = dmp.DiffCleanupEfficiency(diffs)\n\n\t// check there is at least one insert or delete\n\tvar editFound bool\n\tfor _, d := range diffs {\n\t\tif (d.Type == diffmatchpatch.DiffInsert || d.Type == diffmatchpatch.DiffDelete) && strings.TrimSpace(d.Text) != \"\" {\n\t\t\teditFound = true\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif !editFound {\n\t\tlogger.Debug(\"skipping notification for superficial diff\")\n\t\treturn \"\"\n\t}\n\n\tcfg := markDownCfg{\n\t\tinsertOpen:  \"`\",\n\t\tinsertClose: \"`\",\n\t\tdeleteOpen:  \"~~`\",\n\t\tdeleteClose: \"`~~\",\n\t}\n\tmarkdown := generateMarkdown(diffs, cfg)\n\tmarkdown = strings.ReplaceAll(markdown, \"¶\", \"\\n\")\n\n\treturn markdown\n}\n\nconst (\n\ttruncLenEquals  = 60\n\ttruncLenInserts = 120\n\ttruncLenDeletes = 80\n)\n\ntype markDownCfg struct {\n\tinsertOpen  string\n\tinsertClose string\n\tdeleteOpen  string\n\tdeleteClose string\n}\n\nfunc generateMarkdown(diffs []diffmatchpatch.Diff, cfg markDownCfg) string {\n\tsb := &strings.Builder{}\n\n\tvar first, last bool\n\n\tfor i, diff := range diffs {\n\t\tfirst = i == 0\n\t\tlast = i == len(diffs)-1\n\n\t\tswitch diff.Type {\n\t\tcase diffmatchpatch.DiffInsert:\n\t\t\tsb.WriteString(cfg.insertOpen)\n\t\t\tsb.WriteString(truncate(diff.Text, truncLenInserts, first, last))\n\t\t\tsb.WriteString(cfg.insertClose)\n\n\t\tcase diffmatchpatch.DiffDelete:\n\t\t\tsb.WriteString(cfg.deleteOpen)\n\t\t\tsb.WriteString(truncate(diff.Text, truncLenDeletes, first, last))\n\t\t\tsb.WriteString(cfg.deleteClose)\n\n\t\tcase diffmatchpatch.DiffEqual:\n\t\t\tsb.WriteString(truncate(diff.Text, truncLenEquals, first, last))\n\t\t}\n\t}\n\treturn sb.String()\n}\n\nfunc truncate(s string, maxLen int, first bool, last bool) string {\n\tif len(s) < maxLen {\n\t\treturn s\n\t}\n\n\tvar result string\n\n\tswitch {\n\tcase first:\n\t\t// truncate left\n\t\tresult = \" ... \" + rightWords(s, maxLen)\n\tcase last:\n\t\t// truncate right\n\t\tresult = leftWords(s, maxLen) + \" ... \"\n\tdefault:\n\t\t// truncate in the middle\n\t\thalf := len(s) / 2\n\n\t\tleft := leftWords(s[:half], maxLen/2)\n\t\tright := rightWords(s[half:], maxLen/2)\n\n\t\tresult = left + \" ... \" + right\n\t}\n\n\treturn strings.ReplaceAll(result, \"¶\", \"↩\")\n}\n\nfunc normalizeText(s string) string {\n\ts = strings.ReplaceAll(s, \"\\t\", \" \")\n\ts = strings.ReplaceAll(s, \"  \", \" \")\n\ts = strings.ReplaceAll(s, \"\\n\\n\", \"\\n\")\n\ts = strings.ReplaceAll(s, \"\\n\", \"¶\")\n\treturn s\n}\n\n// leftWords returns approximately maxLen characters from the left part of the source string by truncating on the right,\n// with best effort to include whole words.\nfunc leftWords(s string, maxLen int) string {\n\tif len(s) < maxLen {\n\t\treturn s\n\t}\n\tfields := strings.Fields(s)\n\tfields = words(fields, maxLen)\n\n\treturn strings.Join(fields, \" \")\n}\n\n// rightWords returns approximately maxLen from the right part of the source string by truncating from the left,\n// with best effort to include whole words.\nfunc rightWords(s string, maxLen int) string {\n\tif len(s) < maxLen {\n\t\treturn s\n\t}\n\tfields := strings.Fields(s)\n\n\t// reverse the fields so that the right-most words end up at the beginning.\n\treverse(fields)\n\n\tfields = words(fields, maxLen)\n\n\t// reverse the fields again so that the original order is restored.\n\treverse(fields)\n\n\treturn strings.Join(fields, \" \")\n}\n\nfunc reverse(ss []string) {\n\tssLen := len(ss)\n\tfor i := 0; i < ssLen/2; i++ {\n\t\tss[i], ss[ssLen-i-1] = ss[ssLen-i-1], ss[i]\n\t}\n}\n\n// words returns a subslice containing approximately maxChars of characters. The last item may be truncated.\nfunc words(words []string, maxChars int) []string {\n\tvar count int\n\tresult := make([]string, 0, len(words))\n\n\tfor i, w := range words {\n\t\twordLen := len(w)\n\t\tif wordLen+count > maxChars {\n\t\t\tswitch {\n\t\t\tcase i == 0:\n\t\t\t\tresult = append(result, w[:maxChars])\n\t\t\tcase wordLen < 8:\n\t\t\t\tresult = append(result, w)\n\t\t\t}\n\t\t\treturn result\n\t\t}\n\t\tcount += wordLen\n\t\tresult = append(result, w)\n\t}\n\treturn result\n}\n"
  },
  {
    "path": "server/services/notify/notifysubscriptions/diff2markdown_test.go",
    "content": "package notifysubscriptions\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc Test_reverse(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tss   []string\n\t\twant []string\n\t}{\n\t\t{name: \"even\", ss: []string{\"one\", \"two\", \"three\", \"four\"}, want: []string{\"four\", \"three\", \"two\", \"one\"}},\n\t\t{name: \"odd\", ss: []string{\"one\", \"two\", \"three\"}, want: []string{\"three\", \"two\", \"one\"}},\n\t\t{name: \"one\", ss: []string{\"one\"}, want: []string{\"one\"}},\n\t\t{name: \"empty\", ss: []string{}, want: []string{}},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\treverse(tt.ss)\n\t\t\tassert.Equal(t, tt.want, tt.ss)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "server/services/notify/notifysubscriptions/diff2slackattachments.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage notifysubscriptions\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\t\"sync\"\n\t\"text/template\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/wiggin77/merror\"\n\n\tmm_model \"github.com/mattermost/mattermost/server/public/model\"\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nconst (\n\t// card change notifications.\n\tdefAddCardNotify    = \"{{.Authors | printAuthors \\\"unknown_user\\\" }} has added the card {{. | makeLink}}\\n\"\n\tdefModifyCardNotify = \"###### {{.Authors | printAuthors \\\"unknown_user\\\" }} has modified the card {{. | makeLink}} on the board {{. | makeBoardLink}}\\n\"\n\tdefDeleteCardNotify = \"{{.Authors | printAuthors \\\"unknown_user\\\" }} has deleted the card {{. | makeLink}}\\n\"\n)\n\nvar (\n\t// templateCache is a map of text templateCache keyed by languange code.\n\ttemplateCache    = make(map[string]*template.Template)\n\ttemplateCacheMux sync.Mutex\n)\n\n// DiffConvOpts provides options when converting diffs to slack attachments.\ntype DiffConvOpts struct {\n\tLanguage      string\n\tMakeCardLink  func(block *model.Block, board *model.Board, card *model.Block) string\n\tMakeBoardLink func(board *model.Board) string\n\tLogger        mlog.LoggerIFace\n}\n\n// getTemplate returns a new or cached named template based on the language specified.\nfunc getTemplate(name string, opts DiffConvOpts, def string) (*template.Template, error) {\n\ttemplateCacheMux.Lock()\n\tdefer templateCacheMux.Unlock()\n\n\tkey := name + \"&\" + opts.Language\n\tt, ok := templateCache[key]\n\tif !ok {\n\t\tt = template.New(key)\n\n\t\tif opts.MakeCardLink == nil {\n\t\t\topts.MakeCardLink = func(block *model.Block, _ *model.Board, _ *model.Block) string {\n\t\t\t\treturn fmt.Sprintf(\"`%s`\", block.Title)\n\t\t\t}\n\t\t}\n\n\t\tif opts.MakeBoardLink == nil {\n\t\t\topts.MakeBoardLink = func(board *model.Board) string {\n\t\t\t\treturn fmt.Sprintf(\"`%s`\", board.Title)\n\t\t\t}\n\t\t}\n\t\tmyFuncs := template.FuncMap{\n\t\t\t\"getBoardDescription\": getBoardDescription,\n\t\t\t\"makeLink\": func(diff *Diff) string {\n\t\t\t\treturn opts.MakeCardLink(diff.NewBlock, diff.Board, diff.Card)\n\t\t\t},\n\t\t\t\"makeBoardLink\": func(diff *Diff) string {\n\t\t\t\treturn opts.MakeBoardLink(diff.Board)\n\t\t\t},\n\t\t\t\"stripNewlines\": func(s string) string {\n\t\t\t\treturn strings.TrimSpace(strings.ReplaceAll(s, \"\\n\", \"¶ \"))\n\t\t\t},\n\t\t\t\"printAuthors\": func(empty string, authors StringMap) string {\n\t\t\t\treturn makeAuthorsList(authors, empty)\n\t\t\t},\n\t\t}\n\t\tt.Funcs(myFuncs)\n\n\t\ts := def // TODO: lookup i18n string when supported on server\n\t\tt2, err := t.Parse(s)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"cannot parse markdown template '%s' for notifications: %w\", key, err)\n\t\t}\n\t\ttemplateCache[key] = t2\n\t}\n\treturn t, nil\n}\n\nfunc makeAuthorsList(authors StringMap, empty string) string {\n\tif len(authors) == 0 {\n\t\treturn empty\n\t}\n\tprefix := \"\"\n\tsb := &strings.Builder{}\n\tfor _, name := range authors.Values() {\n\t\tsb.WriteString(prefix)\n\t\tsb.WriteString(\"@\")\n\t\tsb.WriteString(strings.TrimSpace(name))\n\t\tprefix = \", \"\n\t}\n\treturn sb.String()\n}\n\n// execTemplate executes the named template corresponding to the template name and language specified.\nfunc execTemplate(w io.Writer, name string, opts DiffConvOpts, def string, data interface{}) error {\n\tt, err := getTemplate(name, opts, def)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn t.Execute(w, data)\n}\n\n// Diffs2SlackAttachments converts a slice of `Diff` to slack attachments to be used in a post.\nfunc Diffs2SlackAttachments(diffs []*Diff, opts DiffConvOpts) ([]*mm_model.SlackAttachment, error) {\n\tvar attachments []*mm_model.SlackAttachment\n\tmerr := merror.New()\n\n\tfor _, d := range diffs {\n\t\t// only handle cards for now.\n\t\tif d.BlockType == model.TypeCard {\n\t\t\ta, err := cardDiff2SlackAttachment(d, opts)\n\t\t\tif err != nil {\n\t\t\t\tmerr.Append(err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif a == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tattachments = append(attachments, a)\n\t\t}\n\t}\n\treturn attachments, merr.ErrorOrNil()\n}\n\nfunc cardDiff2SlackAttachment(cardDiff *Diff, opts DiffConvOpts) (*mm_model.SlackAttachment, error) {\n\t// sanity check\n\tif cardDiff.NewBlock == nil && cardDiff.OldBlock == nil {\n\t\treturn nil, nil\n\t}\n\n\tattachment := &mm_model.SlackAttachment{}\n\tbuf := &bytes.Buffer{}\n\n\t// card added\n\tif cardDiff.NewBlock != nil && cardDiff.OldBlock == nil {\n\t\tif err := execTemplate(buf, \"AddCardNotify\", opts, defAddCardNotify, cardDiff); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tattachment.Pretext = buf.String()\n\t\tattachment.Fallback = attachment.Pretext\n\t\treturn attachment, nil\n\t}\n\n\t// card deleted\n\tif (cardDiff.NewBlock == nil || cardDiff.NewBlock.DeleteAt != 0) && cardDiff.OldBlock != nil {\n\t\tbuf.Reset()\n\t\tif err := execTemplate(buf, \"DeleteCardNotify\", opts, defDeleteCardNotify, cardDiff); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tattachment.Pretext = buf.String()\n\t\tattachment.Fallback = attachment.Pretext\n\t\treturn attachment, nil\n\t}\n\n\t// at this point new and old block are non-nil\n\n\topts.Logger.Debug(\"cardDiff2SlackAttachment\",\n\t\tmlog.String(\"board_id\", cardDiff.Board.ID),\n\t\tmlog.String(\"card_id\", cardDiff.Card.ID),\n\t\tmlog.String(\"new_block_id\", cardDiff.NewBlock.ID),\n\t\tmlog.String(\"old_block_id\", cardDiff.OldBlock.ID),\n\t\tmlog.Int(\"childDiffs\", len(cardDiff.Diffs)),\n\t)\n\n\tbuf.Reset()\n\tif err := execTemplate(buf, \"ModifyCardNotify\", opts, defModifyCardNotify, cardDiff); err != nil {\n\t\treturn nil, fmt.Errorf(\"cannot write notification for card %s: %w\", cardDiff.NewBlock.ID, err)\n\t}\n\tattachment.Pretext = buf.String()\n\tattachment.Fallback = attachment.Pretext\n\n\t// title changes\n\tattachment.Fields = appendTitleChanges(attachment.Fields, cardDiff)\n\n\t// property changes\n\tattachment.Fields = appendPropertyChanges(attachment.Fields, cardDiff)\n\n\t// comment add/delete\n\tattachment.Fields = appendCommentChanges(attachment.Fields, cardDiff)\n\n\t// File Attachment add/delete\n\tattachment.Fields = appendAttachmentChanges(attachment.Fields, cardDiff)\n\n\t// content/description changes\n\tattachment.Fields = appendContentChanges(attachment.Fields, cardDiff, opts.Logger)\n\n\tif len(attachment.Fields) == 0 {\n\t\treturn nil, nil\n\t}\n\treturn attachment, nil\n}\n\nfunc appendTitleChanges(fields []*mm_model.SlackAttachmentField, cardDiff *Diff) []*mm_model.SlackAttachmentField {\n\tif cardDiff.NewBlock.Title != cardDiff.OldBlock.Title {\n\t\tfields = append(fields, &mm_model.SlackAttachmentField{\n\t\t\tShort: false,\n\t\t\tTitle: \"Title\",\n\t\t\tValue: fmt.Sprintf(\"%s  ~~`%s`~~\", stripNewlines(cardDiff.NewBlock.Title), stripNewlines(cardDiff.OldBlock.Title)),\n\t\t})\n\t}\n\treturn fields\n}\n\nfunc appendPropertyChanges(fields []*mm_model.SlackAttachmentField, cardDiff *Diff) []*mm_model.SlackAttachmentField {\n\tif len(cardDiff.PropDiffs) == 0 {\n\t\treturn fields\n\t}\n\n\tfor _, propDiff := range cardDiff.PropDiffs {\n\t\tif propDiff.NewValue == propDiff.OldValue {\n\t\t\tcontinue\n\t\t}\n\n\t\tvar val string\n\t\tif propDiff.OldValue != \"\" {\n\t\t\tval = fmt.Sprintf(\"%s  ~~`%s`~~\", stripNewlines(propDiff.NewValue), stripNewlines(propDiff.OldValue))\n\t\t} else {\n\t\t\tval = propDiff.NewValue\n\t\t}\n\n\t\tfields = append(fields, &mm_model.SlackAttachmentField{\n\t\t\tShort: false,\n\t\t\tTitle: propDiff.Name,\n\t\t\tValue: val,\n\t\t})\n\t}\n\treturn fields\n}\n\nfunc appendCommentChanges(fields []*mm_model.SlackAttachmentField, cardDiff *Diff) []*mm_model.SlackAttachmentField {\n\tfor _, child := range cardDiff.Diffs {\n\t\tif child.BlockType == model.TypeComment {\n\t\t\tvar format string\n\t\t\tvar msg string\n\t\t\tif child.NewBlock != nil && child.OldBlock == nil {\n\t\t\t\t// added comment\n\t\t\t\tformat = \"%s\"\n\t\t\t\tmsg = child.NewBlock.Title\n\t\t\t}\n\n\t\t\tif (child.NewBlock == nil || child.NewBlock.DeleteAt != 0) && child.OldBlock != nil {\n\t\t\t\t// deleted comment\n\t\t\t\tformat = \"~~`%s`~~\"\n\t\t\t\tmsg = stripNewlines(child.OldBlock.Title)\n\t\t\t}\n\n\t\t\tif format != \"\" {\n\t\t\t\tfields = append(fields, &mm_model.SlackAttachmentField{\n\t\t\t\t\tShort: false,\n\t\t\t\t\tTitle: \"Comment by \" + makeAuthorsList(child.Authors, \"unknown_user\"), // todo:  localize this when server has i18n\n\t\t\t\t\tValue: fmt.Sprintf(format, msg),\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\treturn fields\n}\n\nfunc appendAttachmentChanges(fields []*mm_model.SlackAttachmentField, cardDiff *Diff) []*mm_model.SlackAttachmentField {\n\tfor _, child := range cardDiff.Diffs {\n\t\tif child.BlockType == model.TypeAttachment {\n\t\t\tvar format string\n\t\t\tvar msg string\n\t\t\tif child.NewBlock != nil && child.OldBlock == nil {\n\t\t\t\tformat = \"Added an attachment: **`%s`**\"\n\t\t\t\tmsg = child.NewBlock.Title\n\t\t\t} else {\n\t\t\t\tformat = \"Removed ~~`%s`~~ attachment\"\n\t\t\t\tmsg = stripNewlines(child.OldBlock.Title)\n\t\t\t}\n\n\t\t\tif format != \"\" {\n\t\t\t\tfields = append(fields, &mm_model.SlackAttachmentField{\n\t\t\t\t\tShort: false,\n\t\t\t\t\tTitle: \"Changed by \" + makeAuthorsList(child.Authors, \"unknown_user\"), // TODO:  localize this when server has i18n\n\t\t\t\t\tValue: fmt.Sprintf(format, msg),\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\treturn fields\n}\n\nfunc appendContentChanges(fields []*mm_model.SlackAttachmentField, cardDiff *Diff, logger mlog.LoggerIFace) []*mm_model.SlackAttachmentField {\n\tfor _, child := range cardDiff.Diffs {\n\t\tvar opAdd, opDelete bool\n\t\tvar opString string\n\n\t\tswitch {\n\t\tcase child.OldBlock == nil && child.NewBlock != nil:\n\t\t\topAdd = true\n\t\t\topString = \"added\" // TODO: localize when i18n added to server\n\t\tcase child.NewBlock == nil || child.NewBlock.DeleteAt != 0:\n\t\t\topDelete = true\n\t\t\topString = \"deleted\"\n\t\tdefault:\n\t\t\topString = \"modified\"\n\t\t}\n\n\t\tvar newTitle, oldTitle string\n\t\tif child.OldBlock != nil {\n\t\t\toldTitle = child.OldBlock.Title\n\t\t}\n\t\tif child.NewBlock != nil {\n\t\t\tnewTitle = child.NewBlock.Title\n\t\t}\n\n\t\tswitch child.BlockType {\n\t\tcase model.TypeDivider, model.TypeComment:\n\t\t\t// do nothing\n\t\t\tcontinue\n\t\tcase model.TypeImage:\n\t\t\tif newTitle == \"\" {\n\t\t\t\tnewTitle = \"An image was \" + opString + \".\" // TODO: localize when i18n added to server\n\t\t\t}\n\t\t\toldTitle = \"\"\n\t\tcase model.TypeAttachment:\n\t\t\tif newTitle == \"\" {\n\t\t\t\tnewTitle = \"A file attachment was \" + opString + \".\" // TODO: localize when i18n added to server\n\t\t\t}\n\t\t\toldTitle = \"\"\n\t\tdefault:\n\t\t\tif !opAdd {\n\t\t\t\tif opDelete {\n\t\t\t\t\tnewTitle = \"\"\n\t\t\t\t}\n\t\t\t\t// only strip newlines when modifying or deleting\n\t\t\t\toldTitle = stripNewlines(oldTitle)\n\t\t\t\tnewTitle = stripNewlines(newTitle)\n\t\t\t}\n\t\t\tif newTitle == oldTitle {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tlogger.Trace(\"appendContentChanges\",\n\t\t\tmlog.String(\"type\", string(child.BlockType)),\n\t\t\tmlog.String(\"opString\", opString),\n\t\t\tmlog.String(\"oldTitle\", oldTitle),\n\t\t\tmlog.String(\"newTitle\", newTitle),\n\t\t)\n\n\t\tmarkdown := generateMarkdownDiff(oldTitle, newTitle, logger)\n\t\tif markdown == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tfields = append(fields, &mm_model.SlackAttachmentField{\n\t\t\tShort: false,\n\t\t\tTitle: \"Description\",\n\t\t\tValue: markdown,\n\t\t})\n\t}\n\treturn fields\n}\n"
  },
  {
    "path": "server/services/notify/notifysubscriptions/notifier.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage notifysubscriptions\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/permissions\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n\t\"github.com/wiggin77/merror\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nconst (\n\tdefBlockNotificationFreq = time.Minute * 2\n\tenqueueNotifyHintTimeout = time.Second * 10\n\thintQueueSize            = 20\n)\n\nvar (\n\terrEnqueueNotifyHintTimeout = errors.New(\"enqueue notify hint timed out\")\n)\n\n// notifier provides block change notifications for subscribers. Block change events are batched\n// via notifications hints written to the database so that fewer notifications are sent for active\n// blocks.\ntype notifier struct {\n\tserverRoot  string\n\tstore       AppAPI\n\tpermissions permissions.PermissionsService\n\tdelivery    SubscriptionDelivery\n\tlogger      mlog.LoggerIFace\n\n\thints chan *model.NotificationHint\n\n\tmux  sync.Mutex\n\tdone chan struct{}\n}\n\nfunc newNotifier(params BackendParams) *notifier {\n\treturn &notifier{\n\t\tserverRoot:  params.ServerRoot,\n\t\tstore:       params.AppAPI,\n\t\tpermissions: params.Permissions,\n\t\tdelivery:    params.Delivery,\n\t\tlogger:      params.Logger,\n\t\tdone:        nil,\n\t\thints:       make(chan *model.NotificationHint, hintQueueSize),\n\t}\n}\n\nfunc (n *notifier) start() {\n\tn.mux.Lock()\n\tdefer n.mux.Unlock()\n\n\tif n.done == nil {\n\t\tn.done = make(chan struct{})\n\t\tgo n.loop()\n\t}\n}\n\nfunc (n *notifier) stop() {\n\tn.mux.Lock()\n\tdefer n.mux.Unlock()\n\n\tif n.done != nil {\n\t\tclose(n.done)\n\t\tn.done = nil\n\t}\n}\n\nfunc (n *notifier) loop() {\n\tdone := n.done\n\tvar nextNotify time.Time\n\n\tfor {\n\t\thint, err := n.store.GetNextNotificationHint(false)\n\t\tswitch {\n\t\tcase model.IsErrNotFound(err):\n\t\t\t// no hints in table; wait up to an hour or when `onNotifyHint` is called again\n\t\t\tnextNotify = time.Now().Add(time.Hour * 1)\n\t\t\tn.logger.Debug(\"notify loop - no hints in queue\", mlog.Time(\"next_check\", nextNotify))\n\t\tcase err != nil:\n\t\t\t// try again in a minute\n\t\t\tnextNotify = time.Now().Add(time.Minute * 1)\n\t\t\tn.logger.Error(\"notify loop - error fetching next notification\", mlog.Err(err))\n\t\tcase hint.NotifyAt > utils.GetMillis():\n\t\t\t// next hint is not ready yet; sleep until hint.NotifyAt\n\t\t\tnextNotify = utils.GetTimeForMillis(hint.NotifyAt)\n\t\tdefault:\n\t\t\t// it's time to notify\n\t\t\tn.notify()\n\t\t\tcontinue\n\t\t}\n\n\t\tn.logger.Debug(\"subscription notifier loop\",\n\t\t\tmlog.Time(\"next_notify\", nextNotify),\n\t\t)\n\n\t\tselect {\n\t\tcase <-n.hints:\n\t\t\t// A new hint was added. Wake up and check if next hint is ready to go.\n\t\tcase <-time.After(time.Until(nextNotify)):\n\t\t\t// Next scheduled hint should be ready now.\n\t\tcase <-done:\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (n *notifier) onNotifyHint(hint *model.NotificationHint) error {\n\tn.logger.Debug(\"onNotifyHint - enqueing hint\", mlog.Any(\"hint\", hint))\n\n\tselect {\n\tcase n.hints <- hint:\n\tcase <-time.After(enqueueNotifyHintTimeout):\n\t\treturn errEnqueueNotifyHintTimeout\n\t}\n\treturn nil\n}\n\nfunc (n *notifier) notify() {\n\tvar hint *model.NotificationHint\n\tvar err error\n\n\thint, err = n.store.GetNextNotificationHint(true)\n\tif err != nil {\n\t\tif model.IsErrNotFound(err) {\n\t\t\t// Expected when multiple nodes in a cluster try to process the same hint at the same time.\n\t\t\t// This simply means the other node won. Returning here will simply try fetching another hint.\n\t\t\treturn\n\t\t}\n\t\tn.logger.Error(\"notify - error fetching next notification\", mlog.Err(err))\n\t\treturn\n\t}\n\n\tif err = n.notifySubscribers(hint); err != nil {\n\t\tn.logger.Error(\"Error notifying subscribers\", mlog.Err(err))\n\t}\n}\n\nfunc (n *notifier) notifySubscribers(hint *model.NotificationHint) error {\n\t// \tget the subscriber list\n\tsubs, err := n.store.GetSubscribersForBlock(hint.BlockID)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif len(subs) == 0 {\n\t\tn.logger.Debug(\"notifySubscribers - no subscribers\", mlog.Any(\"hint\", hint))\n\t\treturn nil\n\t}\n\n\t// subs slice is sorted by `NotifiedAt`, therefore subs[0] contains the oldest NotifiedAt needed\n\toldestNotifiedAt := subs[0].NotifiedAt\n\n\t// need the block's board and card.\n\tboard, card, err := n.store.GetBoardAndCardByID(hint.BlockID)\n\tif err != nil || board == nil || card == nil {\n\t\treturn fmt.Errorf(\"could not get board & card for block %s: %w\", hint.BlockID, err)\n\t}\n\n\tn.logger.Debug(\"notifySubscribers - subscribers\",\n\t\tmlog.Any(\"hint\", hint),\n\t\tmlog.String(\"board_id\", board.ID),\n\t\tmlog.String(\"card_id\", card.ID),\n\t\tmlog.Int(\"sub_count\", len(subs)),\n\t)\n\n\tdg := &diffGenerator{\n\t\tboard:        board,\n\t\tcard:         card,\n\t\tstore:        n.store,\n\t\thint:         hint,\n\t\tlastNotifyAt: oldestNotifiedAt,\n\t\tlogger:       n.logger,\n\t}\n\tdiffs, err := dg.generateDiffs()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tn.logger.Debug(\"notifySubscribers - diffs\",\n\t\tmlog.Any(\"hint\", hint),\n\t\tmlog.Int(\"diff_count\", len(diffs)),\n\t)\n\n\tif len(diffs) == 0 {\n\t\treturn nil\n\t}\n\n\tdiffAuthors := make(StringMap)\n\tfor _, d := range diffs {\n\t\tdiffAuthors.Append(d.Authors)\n\t}\n\n\topts := DiffConvOpts{\n\t\tLanguage: \"en\", // TODO: use correct language when i18n is available on server.\n\t\tMakeCardLink: func(block *model.Block, board *model.Board, card *model.Block) string {\n\t\t\treturn fmt.Sprintf(\"[%s](%s)\", block.Title, utils.MakeCardLink(n.serverRoot, board.TeamID, board.ID, card.ID))\n\t\t},\n\t\tMakeBoardLink: func(board *model.Board) string {\n\t\t\treturn fmt.Sprintf(\"[%s](%s)\", board.Title, utils.MakeBoardLink(n.serverRoot, board.TeamID, board.ID))\n\t\t},\n\t\tLogger: n.logger,\n\t}\n\n\tattachments, err := Diffs2SlackAttachments(diffs, opts)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tmerr := merror.New()\n\tif len(attachments) > 0 {\n\t\tfor _, sub := range subs {\n\t\t\t// don't notify the author of their own changes.\n\t\t\tauthorName, isAuthor := diffAuthors[sub.SubscriberID]\n\t\t\tif isAuthor && len(diffAuthors) == 1 {\n\t\t\t\tn.logger.Debug(\"notifySubscribers - skipping author\",\n\t\t\t\t\tmlog.Any(\"hint\", hint),\n\t\t\t\t\tmlog.String(\"author_id\", sub.SubscriberID),\n\t\t\t\t\tmlog.String(\"author_username\", authorName),\n\t\t\t\t)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// make sure the subscriber still has permissions for the board.\n\t\t\tif !n.permissions.HasPermissionToBoard(sub.SubscriberID, board.ID, model.PermissionViewBoard) {\n\t\t\t\tn.logger.Debug(\"notifySubscribers - skipping non-board member\",\n\t\t\t\t\tmlog.Any(\"hint\", hint),\n\t\t\t\t\tmlog.String(\"subscriber_id\", sub.SubscriberID),\n\t\t\t\t\tmlog.String(\"board_id\", board.ID),\n\t\t\t\t)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tn.logger.Debug(\"notifySubscribers - deliver\",\n\t\t\t\tmlog.Any(\"hint\", hint),\n\t\t\t\tmlog.String(\"modified_by_id\", hint.ModifiedByID),\n\t\t\t\tmlog.String(\"subscriber_id\", sub.SubscriberID),\n\t\t\t\tmlog.String(\"subscriber_type\", string(sub.SubscriberType)),\n\t\t\t)\n\n\t\t\tif err = n.delivery.SubscriptionDeliverSlackAttachments(board.TeamID, sub.SubscriberID, sub.SubscriberType, attachments); err != nil {\n\t\t\t\tmerr.Append(fmt.Errorf(\"cannot deliver notification to subscriber %s [%s]: %w\",\n\t\t\t\t\tsub.SubscriberID, sub.SubscriberType, err))\n\t\t\t}\n\t\t}\n\t} else {\n\t\tn.logger.Debug(\"notifySubscribers - skip delivery; no chg\",\n\t\t\tmlog.Any(\"hint\", hint),\n\t\t\tmlog.String(\"modified_by_id\", hint.ModifiedByID),\n\t\t)\n\t}\n\n\t// find the new NotifiedAt based on the newest diff.\n\tvar notifiedAt int64\n\tfor _, d := range diffs {\n\t\tif d.UpdateAt > notifiedAt {\n\t\t\tnotifiedAt = d.UpdateAt\n\t\t}\n\t\tfor _, c := range d.Diffs {\n\t\t\tif c.UpdateAt > notifiedAt {\n\t\t\t\tnotifiedAt = c.UpdateAt\n\t\t\t}\n\t\t}\n\t}\n\n\t// update the last notified_at for all subscribers since we at least attempted to notify all of them.\n\terr = dg.store.UpdateSubscribersNotifiedAt(dg.hint.BlockID, notifiedAt)\n\tif err != nil {\n\t\tmerr.Append(fmt.Errorf(\"could not update subscribers notified_at for block %s: %w\", dg.hint.BlockID, err))\n\t}\n\n\treturn merr.ErrorOrNil()\n}\n"
  },
  {
    "path": "server/services/notify/notifysubscriptions/subscriptions_backend.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage notifysubscriptions\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/notify\"\n\t\"github.com/mattermost/focalboard/server/services/permissions\"\n\t\"github.com/wiggin77/merror\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nconst (\n\tbackendName = \"notifySubscriptions\"\n)\n\ntype BackendParams struct {\n\tServerRoot             string\n\tAppAPI                 AppAPI\n\tPermissions            permissions.PermissionsService\n\tDelivery               SubscriptionDelivery\n\tLogger                 mlog.LoggerIFace\n\tNotifyFreqCardSeconds  int\n\tNotifyFreqBoardSeconds int\n}\n\n// Backend provides the notification backend for subscriptions.\ntype Backend struct {\n\tappAPI                 AppAPI\n\tpermissions            permissions.PermissionsService\n\tdelivery               SubscriptionDelivery\n\tnotifier               *notifier\n\tlogger                 mlog.LoggerIFace\n\tnotifyFreqCardSeconds  int\n\tnotifyFreqBoardSeconds int\n}\n\nfunc New(params BackendParams) *Backend {\n\treturn &Backend{\n\t\tappAPI:                 params.AppAPI,\n\t\tdelivery:               params.Delivery,\n\t\tpermissions:            params.Permissions,\n\t\tnotifier:               newNotifier(params),\n\t\tlogger:                 params.Logger,\n\t\tnotifyFreqCardSeconds:  params.NotifyFreqCardSeconds,\n\t\tnotifyFreqBoardSeconds: params.NotifyFreqBoardSeconds,\n\t}\n}\n\nfunc (b *Backend) Start() error {\n\tb.logger.Debug(\"Starting subscriptions backend\",\n\t\tmlog.Int(\"freq_card\", b.notifyFreqCardSeconds),\n\t\tmlog.Int(\"freq_board\", b.notifyFreqBoardSeconds),\n\t)\n\tb.notifier.start()\n\treturn nil\n}\n\nfunc (b *Backend) ShutDown() error {\n\tb.logger.Debug(\"Stopping subscriptions backend\")\n\tb.notifier.stop()\n\t_ = b.logger.Flush()\n\treturn nil\n}\n\nfunc (b *Backend) Name() string {\n\treturn backendName\n}\n\nfunc (b *Backend) getBlockUpdateFreq(blockType model.BlockType) time.Duration {\n\t// check for env variable override\n\tsFreq := os.Getenv(\"MM_BOARDS_NOTIFY_FREQ_SECONDS\")\n\tif sFreq != \"\" && sFreq != \"0\" {\n\t\tif freq, err := strconv.ParseInt(sFreq, 10, 64); err != nil {\n\t\t\tb.logger.Error(\"Environment variable MM_BOARDS_NOTIFY_FREQ_SECONDS invalid (ignoring)\", mlog.Err(err))\n\t\t} else {\n\t\t\treturn time.Second * time.Duration(freq)\n\t\t}\n\t}\n\n\tswitch blockType {\n\tcase model.TypeCard:\n\t\treturn time.Second * time.Duration(b.notifyFreqCardSeconds)\n\tdefault:\n\t\treturn defBlockNotificationFreq\n\t}\n}\n\nfunc (b *Backend) BlockChanged(evt notify.BlockChangeEvent) error {\n\tif evt.Board == nil {\n\t\tb.logger.Warn(\"No board found for block, skipping notify\",\n\t\t\tmlog.String(\"block_id\", evt.BlockChanged.ID),\n\t\t)\n\t\treturn nil\n\t}\n\n\tmerr := merror.New()\n\tvar err error\n\n\t// if new card added, automatically subscribe the author.\n\tif evt.Action == notify.Add && evt.BlockChanged.Type == model.TypeCard {\n\t\tsub := &model.Subscription{\n\t\t\tBlockType:      model.TypeCard,\n\t\t\tBlockID:        evt.BlockChanged.ID,\n\t\t\tSubscriberType: model.SubTypeUser,\n\t\t\tSubscriberID:   evt.ModifiedBy.UserID,\n\t\t}\n\n\t\tif _, err = b.appAPI.CreateSubscription(sub); err != nil {\n\t\t\tb.logger.Warn(\"Cannot subscribe card author to card\",\n\t\t\t\tmlog.String(\"card_id\", evt.BlockChanged.ID),\n\t\t\t\tmlog.Err(err),\n\t\t\t)\n\t\t}\n\t}\n\n\t// notify board subscribers\n\tsubs, err := b.appAPI.GetSubscribersForBlock(evt.Board.ID)\n\tif err != nil {\n\t\tmerr.Append(fmt.Errorf(\"cannot fetch subscribers for board %s: %w\", evt.Board.ID, err))\n\t}\n\tif err = b.notifySubscribers(subs, evt.Board.ID, model.TypeBoard, evt.ModifiedBy.UserID); err != nil {\n\t\tmerr.Append(fmt.Errorf(\"cannot notify board subscribers for board %s: %w\", evt.Board.ID, err))\n\t}\n\n\tif evt.Card == nil {\n\t\treturn merr.ErrorOrNil()\n\t}\n\n\t// notify card subscribers\n\tsubs, err = b.appAPI.GetSubscribersForBlock(evt.Card.ID)\n\tif err != nil {\n\t\tmerr.Append(fmt.Errorf(\"cannot fetch subscribers for card %s: %w\", evt.Card.ID, err))\n\t}\n\tif err = b.notifySubscribers(subs, evt.Card.ID, model.TypeCard, evt.ModifiedBy.UserID); err != nil {\n\t\tmerr.Append(fmt.Errorf(\"cannot notify card subscribers for card %s: %w\", evt.Card.ID, err))\n\t}\n\n\t// notify block subscribers (if/when other types can be subscribed to)\n\tif evt.Board.ID != evt.BlockChanged.ID && evt.Card.ID != evt.BlockChanged.ID {\n\t\tsubs, err := b.appAPI.GetSubscribersForBlock(evt.BlockChanged.ID)\n\t\tif err != nil {\n\t\t\tmerr.Append(fmt.Errorf(\"cannot fetch subscribers for block %s: %w\", evt.BlockChanged.ID, err))\n\t\t}\n\t\tif err := b.notifySubscribers(subs, evt.BlockChanged.ID, evt.BlockChanged.Type, evt.ModifiedBy.UserID); err != nil {\n\t\t\tmerr.Append(fmt.Errorf(\"cannot notify block subscribers for block %s: %w\", evt.BlockChanged.ID, err))\n\t\t}\n\t}\n\treturn merr.ErrorOrNil()\n}\n\n// notifySubscribers triggers a change notification for subscribers by writing a notification hint to the database.\nfunc (b *Backend) notifySubscribers(subs []*model.Subscriber, blockID string, idType model.BlockType, modifiedByID string) error {\n\tif len(subs) == 0 {\n\t\treturn nil\n\t}\n\n\thint := &model.NotificationHint{\n\t\tBlockType:    idType,\n\t\tBlockID:      blockID,\n\t\tModifiedByID: modifiedByID,\n\t}\n\n\thint, err := b.appAPI.UpsertNotificationHint(hint, b.getBlockUpdateFreq(idType))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"cannot upsert notification hint: %w\", err)\n\t}\n\tif err := b.notifier.onNotifyHint(hint); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// OnMention satisfies the `MentionListener` interface and is called whenever a @mention notification\n// is sent. Here we create a subscription for the mentioned user to the card.\nfunc (b *Backend) OnMention(userID string, evt notify.BlockChangeEvent) {\n\tif evt.Card == nil {\n\t\tb.logger.Debug(\"Cannot subscribe mentioned user to nil card\",\n\t\t\tmlog.String(\"user_id\", userID),\n\t\t\tmlog.String(\"block_id\", evt.BlockChanged.ID),\n\t\t)\n\t\treturn\n\t}\n\n\t// user mentioned must be a board member to subscribe to card.\n\tif !b.permissions.HasPermissionToBoard(userID, evt.Board.ID, model.PermissionViewBoard) {\n\t\tb.logger.Debug(\"Not subscribing mentioned non-board member to card\",\n\t\t\tmlog.String(\"user_id\", userID),\n\t\t\tmlog.String(\"block_id\", evt.BlockChanged.ID),\n\t\t)\n\t\treturn\n\t}\n\n\tsub := &model.Subscription{\n\t\tBlockType:      model.TypeCard,\n\t\tBlockID:        evt.Card.ID,\n\t\tSubscriberType: model.SubTypeUser,\n\t\tSubscriberID:   userID,\n\t}\n\n\tvar err error\n\tif _, err = b.appAPI.CreateSubscription(sub); err != nil {\n\t\tb.logger.Warn(\"Cannot subscribe mentioned user to card\",\n\t\t\tmlog.String(\"user_id\", userID),\n\t\t\tmlog.String(\"card_id\", evt.Card.ID),\n\t\t\tmlog.Err(err),\n\t\t)\n\t\treturn\n\t}\n\n\tb.logger.Debug(\"Subscribed mentioned user to card\",\n\t\tmlog.String(\"user_id\", userID),\n\t\tmlog.String(\"card_id\", evt.Card.ID),\n\t)\n}\n"
  },
  {
    "path": "server/services/notify/notifysubscriptions/util.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage notifysubscriptions\n\nimport (\n\t\"strings\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n)\n\nfunc getBoardDescription(board *model.Block) string {\n\tif board == nil {\n\t\treturn \"\"\n\t}\n\n\tdescr, ok := board.Fields[\"description\"]\n\tif !ok {\n\t\treturn \"\"\n\t}\n\n\tdescription, ok := descr.(string)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\n\treturn description\n}\n\nfunc stripNewlines(s string) string {\n\treturn strings.TrimSpace(strings.ReplaceAll(s, \"\\n\", \"¶ \"))\n}\n\ntype StringMap map[string]string\n\nfunc (sm StringMap) Add(k string, v string) {\n\tsm[k] = v\n}\n\nfunc (sm StringMap) Append(m StringMap) {\n\tfor k, v := range m {\n\t\tsm[k] = v\n\t}\n}\n\nfunc (sm StringMap) Keys() []string {\n\tkeys := make([]string, 0, len(sm))\n\tfor k := range sm {\n\t\tkeys = append(keys, k)\n\t}\n\treturn keys\n}\n\nfunc (sm StringMap) Values() []string {\n\tvalues := make([]string, 0, len(sm))\n\tfor _, v := range sm {\n\t\tvalues = append(values, v)\n\t}\n\treturn values\n}\n"
  },
  {
    "path": "server/services/notify/plugindelivery/mention_deliver.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage plugindelivery\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/mattermost/focalboard/server/services/notify\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n\n\tmm_model \"github.com/mattermost/mattermost/server/public/model\"\n)\n\n// MentionDeliver notifies a user they have been mentioned in a blockv ia the plugin API.\nfunc (pd *PluginDelivery) MentionDeliver(mentionedUser *mm_model.User, extract string, evt notify.BlockChangeEvent) (string, error) {\n\tauthor, err := pd.api.GetUserByID(evt.ModifiedBy.UserID)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"cannot find user: %w\", err)\n\t}\n\n\tchannel, err := pd.getDirectChannel(evt.TeamID, mentionedUser.Id, pd.botID)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"cannot get direct channel: %w\", err)\n\t}\n\tlink := utils.MakeCardLink(pd.serverRoot, evt.Board.TeamID, evt.Board.ID, evt.Card.ID)\n\tboardLink := utils.MakeBoardLink(pd.serverRoot, evt.Board.TeamID, evt.Board.ID)\n\n\tpost := &mm_model.Post{\n\t\tUserId:    pd.botID,\n\t\tChannelId: channel.Id,\n\t\tMessage:   formatMessage(author.Username, extract, evt.Card.Title, link, evt.BlockChanged, boardLink, evt.Board.Title),\n\t}\n\n\tif _, err := pd.api.CreatePost(post); err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn mentionedUser.Id, nil\n}\n"
  },
  {
    "path": "server/services/notify/plugindelivery/message.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage plugindelivery\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n)\n\nconst (\n\t// TODO: localize these when i18n is available.\n\tdefCommentTemplate     = \"@%s mentioned you in a comment on the card [%s](%s) in board [%s](%s)\\n> %s\"\n\tdefDescriptionTemplate = \"@%s mentioned you in the card [%s](%s) in board [%s](%s)\\n> %s\"\n)\n\nfunc formatMessage(author string, extract string, card string, link string, block *model.Block, boardLink string, board string) string {\n\ttemplate := defDescriptionTemplate\n\tif block.Type == model.TypeComment {\n\t\ttemplate = defCommentTemplate\n\t}\n\treturn fmt.Sprintf(template, author, card, link, board, boardLink, extract)\n}\n"
  },
  {
    "path": "server/services/notify/plugindelivery/plugin_delivery.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage plugindelivery\n\nimport (\n\tmm_model \"github.com/mattermost/mattermost/server/public/model\"\n)\n\ntype servicesAPI interface {\n\t// GetDirectChannelOrCreate gets a direct message channel,\n\t// or creates one if it does not already exist\n\tGetDirectChannelOrCreate(userID1, userID2 string) (*mm_model.Channel, error)\n\n\t// CreatePost creates a post.\n\tCreatePost(post *mm_model.Post) (*mm_model.Post, error)\n\n\t// GetUserByID gets a user by their ID.\n\tGetUserByID(userID string) (*mm_model.User, error)\n\n\t// GetUserByUsername gets a user by their username.\n\tGetUserByUsername(name string) (*mm_model.User, error)\n\n\t// GetTeamMember gets a team member by their user id.\n\tGetTeamMember(teamID string, userID string) (*mm_model.TeamMember, error)\n\n\t// GetChannelByID gets a Channel by its ID.\n\tGetChannelByID(channelID string) (*mm_model.Channel, error)\n\n\t// GetChannelMember gets a channel member by userID.\n\tGetChannelMember(channelID string, userID string) (*mm_model.ChannelMember, error)\n\n\t// CreateMember adds a user to the specified team. Safe to call if the user is\n\t// already a member of the team.\n\tCreateMember(teamID string, userID string) (*mm_model.TeamMember, error)\n}\n\n// PluginDelivery provides ability to send notifications to direct message channels via Mattermost plugin API.\ntype PluginDelivery struct {\n\tbotID      string\n\tserverRoot string\n\tapi        servicesAPI\n}\n\n// New creates a PluginDelivery instance.\nfunc New(botID string, serverRoot string, api servicesAPI) *PluginDelivery {\n\treturn &PluginDelivery{\n\t\tbotID:      botID,\n\t\tserverRoot: serverRoot,\n\t\tapi:        api,\n\t}\n}\n"
  },
  {
    "path": "server/services/notify/plugindelivery/subscription_deliver.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage plugindelivery\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\n\tmm_model \"github.com/mattermost/mattermost/server/public/model\"\n)\n\nvar (\n\tErrUnsupportedSubscriberType = errors.New(\"invalid subscriber type\")\n)\n\n// SubscriptionDeliverSlashAttachments notifies a user that changes were made to a block they are subscribed to.\nfunc (pd *PluginDelivery) SubscriptionDeliverSlackAttachments(teamID string, subscriberID string, subscriptionType model.SubscriberType,\n\tattachments []*mm_model.SlackAttachment) error {\n\t// check subscriber is member of channel\n\t_, err := pd.api.GetUserByID(subscriberID)\n\tif err != nil {\n\t\tif model.IsErrNotFound(err) {\n\t\t\t// subscriber is not a member of the channel; fail silently.\n\t\t\treturn nil\n\t\t}\n\t\treturn fmt.Errorf(\"cannot fetch channel member for user %s: %w\", subscriberID, err)\n\t}\n\n\tchannelID, err := pd.getDirectChannelID(teamID, subscriberID, subscriptionType, pd.botID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tpost := &mm_model.Post{\n\t\tUserId:    pd.botID,\n\t\tChannelId: channelID,\n\t}\n\n\tmm_model.ParseSlackAttachment(post, attachments)\n\n\t_, err = pd.api.CreatePost(post)\n\treturn err\n}\n\nfunc (pd *PluginDelivery) getDirectChannelID(teamID string, subscriberID string, subscriberType model.SubscriberType, botID string) (string, error) {\n\tswitch subscriberType {\n\tcase model.SubTypeUser:\n\t\tuser, err := pd.api.GetUserByID(subscriberID)\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"cannot find user: %w\", err)\n\t\t}\n\t\tchannel, err := pd.getDirectChannel(teamID, user.Id, botID)\n\t\tif err != nil || channel == nil {\n\t\t\treturn \"\", fmt.Errorf(\"cannot get direct channel: %w\", err)\n\t\t}\n\t\treturn channel.Id, nil\n\tcase model.SubTypeChannel:\n\t\treturn subscriberID, nil\n\tdefault:\n\t\treturn \"\", ErrUnsupportedSubscriberType\n\t}\n}\n\nfunc (pd *PluginDelivery) getDirectChannel(teamID string, userID string, botID string) (*mm_model.Channel, error) {\n\t// first ensure the bot is a member of the team.\n\t_, err := pd.api.CreateMember(teamID, botID)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"cannot add bot to team %s: %w\", teamID, err)\n\t}\n\treturn pd.api.GetDirectChannelOrCreate(userID, botID)\n}\n"
  },
  {
    "path": "server/services/notify/plugindelivery/user.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage plugindelivery\n\nimport (\n\t\"strings\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\n\tmm_model \"github.com/mattermost/mattermost/server/public/model\"\n)\n\nconst (\n\tusernameSpecialChars = \".-_ \"\n)\n\nfunc (pd *PluginDelivery) UserByUsername(username string) (*mm_model.User, error) {\n\t// check for usernames that might have trailing punctuation\n\tvar user *mm_model.User\n\tvar err error\n\tok := true\n\ttrimmed := username\n\tfor ok {\n\t\tuser, err = pd.api.GetUserByUsername(trimmed)\n\t\tif err != nil && !model.IsErrNotFound(err) {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif err == nil {\n\t\t\tbreak\n\t\t}\n\n\t\ttrimmed, ok = trimUsernameSpecialChar(trimmed)\n\t}\n\n\tif user == nil {\n\t\treturn nil, err\n\t}\n\n\treturn user, nil\n}\n\n// trimUsernameSpecialChar tries to remove the last character from word if it\n// is a special character for usernames (dot, dash or underscore). If not, it\n// returns the same string.\nfunc trimUsernameSpecialChar(word string) (string, bool) {\n\tlen := len(word)\n\n\tif len > 0 && strings.LastIndexAny(word, usernameSpecialChars) == (len-1) {\n\t\treturn word[:len-1], true\n\t}\n\n\treturn word, false\n}\n"
  },
  {
    "path": "server/services/notify/plugindelivery/user_test.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage plugindelivery\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\n\tmm_model \"github.com/mattermost/mattermost/server/public/model\"\n)\n\nvar (\n\tdefTeamID = mm_model.NewId()\n\n\tuser1 = &mm_model.User{\n\t\tId:       mm_model.NewId(),\n\t\tUsername: \"dlauder\",\n\t}\n\tuser2 = &mm_model.User{\n\t\tId:       mm_model.NewId(),\n\t\tUsername: \"steve.mqueen\",\n\t}\n\tuser3 = &mm_model.User{\n\t\tId:       mm_model.NewId(),\n\t\tUsername: \"bart_\",\n\t}\n\tuser4 = &mm_model.User{\n\t\tId:       mm_model.NewId(),\n\t\tUsername: \"missing_\",\n\t}\n\tuser5 = &mm_model.User{\n\t\tId:       mm_model.NewId(),\n\t\tUsername: \"wrong_team\",\n\t}\n\n\tmockUsers = map[string]*mm_model.User{\n\t\t\"dlauder\":      user1,\n\t\t\"steve.mqueen\": user2,\n\t\t\"bart_\":        user3,\n\t\t\"wrong_team\":   user5,\n\t}\n)\n\nfunc Test_userByUsername(t *testing.T) {\n\tservicesAPI := newServicesAPIMock(mockUsers)\n\tdelivery := New(\"bot_id\", \"server_root\", servicesAPI)\n\n\ttests := []struct {\n\t\tname    string\n\t\tuname   string\n\t\tteamID  string\n\t\twant    *mm_model.User\n\t\twantErr bool\n\t}{\n\t\t{name: \"user1\", uname: user1.Username, want: user1, wantErr: false},\n\t\t{name: \"user1 with period\", uname: user1.Username + \".\", want: user1, wantErr: false},\n\t\t{name: \"user1 with period plus more\", uname: user1.Username + \". \", want: user1, wantErr: false},\n\t\t{name: \"user2 with periods\", uname: user2.Username + \"...\", want: user2, wantErr: false},\n\t\t{name: \"user2 with underscore\", uname: user2.Username + \"_\", want: user2, wantErr: false},\n\t\t{name: \"user2 with hyphen plus more\", uname: user2.Username + \"- \", want: user2, wantErr: false},\n\t\t{name: \"user2 with hyphen plus all\", uname: user2.Username + \".-_ \", want: user2, wantErr: false},\n\t\t{name: \"user3 with underscore\", uname: user3.Username + \"_\", want: user3, wantErr: false},\n\t\t{name: \"user4 missing\", uname: user4.Username, want: nil, wantErr: true},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := delivery.UserByUsername(tt.uname)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"userByUsername() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif !reflect.DeepEqual(got, tt.want) {\n\t\t\t\tt.Errorf(\"userByUsername()\\ngot:\\n%v\\nwant:\\n%v\\n\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype servicesAPIMock struct {\n\tusers map[string]*mm_model.User\n}\n\nfunc newServicesAPIMock(users map[string]*mm_model.User) servicesAPIMock {\n\treturn servicesAPIMock{\n\t\tusers: users,\n\t}\n}\n\nfunc (m servicesAPIMock) GetUserByUsername(name string) (*mm_model.User, error) {\n\tuser, ok := m.users[name]\n\tif !ok {\n\t\treturn nil, model.NewErrNotFound(name)\n\t}\n\treturn user, nil\n}\n\nfunc (m servicesAPIMock) GetDirectChannel(userID1, userID2 string) (*mm_model.Channel, error) {\n\treturn nil, nil\n}\n\nfunc (m servicesAPIMock) GetDirectChannelOrCreate(userID1, userID2 string) (*mm_model.Channel, error) {\n\treturn nil, nil\n}\n\nfunc (m servicesAPIMock) CreatePost(post *mm_model.Post) (*mm_model.Post, error) {\n\treturn post, nil\n}\n\nfunc (m servicesAPIMock) GetUserByID(userID string) (*mm_model.User, error) {\n\tfor _, user := range m.users {\n\t\tif user.Id == userID {\n\t\t\treturn user, nil\n\t\t}\n\t}\n\treturn nil, model.NewErrNotFound(userID)\n}\n\nfunc (m servicesAPIMock) GetTeamMember(teamID string, userID string) (*mm_model.TeamMember, error) {\n\tuser, err := m.GetUserByID(userID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif teamID != defTeamID {\n\t\treturn nil, model.NewErrNotFound(teamID)\n\t}\n\n\tmember := &mm_model.TeamMember{\n\t\tUserId: user.Id,\n\t\tTeamId: teamID,\n\t}\n\treturn member, nil\n}\n\nfunc (m servicesAPIMock) GetChannelByID(channelID string) (*mm_model.Channel, error) {\n\treturn nil, model.NewErrNotFound(channelID)\n}\n\nfunc (m servicesAPIMock) GetChannelMember(channelID string, userID string) (*mm_model.ChannelMember, error) {\n\treturn nil, model.NewErrNotFound(userID)\n}\n\nfunc (m servicesAPIMock) CreateMember(teamID string, userID string) (*mm_model.TeamMember, error) {\n\tmember := &mm_model.TeamMember{\n\t\tUserId: userID,\n\t\tTeamId: teamID,\n\t}\n\treturn member, nil\n}\n"
  },
  {
    "path": "server/services/notify/service.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage notify\n\nimport (\n\t\"sync\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/wiggin77/merror\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\ntype Action string\n\nconst (\n\tAdd    Action = \"add\"\n\tUpdate Action = \"update\"\n\tDelete Action = \"delete\"\n)\n\ntype BlockChangeEvent struct {\n\tAction       Action\n\tTeamID       string\n\tBoard        *model.Board\n\tCard         *model.Block\n\tBlockChanged *model.Block\n\tBlockOld     *model.Block\n\tModifiedBy   *model.BoardMember\n}\n\n// Backend provides an interface for sending notifications.\ntype Backend interface {\n\tStart() error\n\tShutDown() error\n\tBlockChanged(evt BlockChangeEvent) error\n\tName() string\n}\n\n// Service is a service that sends notifications based on block activity using one or more backends.\ntype Service struct {\n\tmux      sync.RWMutex\n\tbackends []Backend\n\tlogger   mlog.LoggerIFace\n}\n\n// New creates a notification service with one or more Backends capable of sending notifications.\nfunc New(logger mlog.LoggerIFace, backends ...Backend) (*Service, error) {\n\tnotify := &Service{\n\t\tbackends: make([]Backend, 0, len(backends)),\n\t\tlogger:   logger,\n\t}\n\n\tmerr := merror.New()\n\tfor _, backend := range backends {\n\t\tif err := notify.AddBackend(backend); err != nil {\n\t\t\tmerr.Append(err)\n\t\t} else {\n\t\t\tlogger.Info(\"Initialized notification backend\", mlog.String(\"name\", backend.Name()))\n\t\t}\n\t}\n\treturn notify, merr.ErrorOrNil()\n}\n\n// AddBackend adds a backend to the list that will be informed of any block changes.\nfunc (s *Service) AddBackend(backend Backend) error {\n\tif err := backend.Start(); err != nil {\n\t\treturn err\n\t}\n\ts.mux.Lock()\n\tdefer s.mux.Unlock()\n\ts.backends = append(s.backends, backend)\n\treturn nil\n}\n\n// Shutdown calls shutdown for all backends.\nfunc (s *Service) Shutdown() error {\n\ts.mux.Lock()\n\tdefer s.mux.Unlock()\n\n\tmerr := merror.New()\n\tfor _, backend := range s.backends {\n\t\tif err := backend.ShutDown(); err != nil {\n\t\t\tmerr.Append(err)\n\t\t}\n\t}\n\ts.backends = nil\n\treturn merr.ErrorOrNil()\n}\n\n// BlockChanged should be called whenever a block is added/updated/deleted.\n// All backends are informed of the event.\nfunc (s *Service) BlockChanged(evt BlockChangeEvent) {\n\ts.mux.RLock()\n\tdefer s.mux.RUnlock()\n\n\tfor _, backend := range s.backends {\n\t\tif err := backend.BlockChanged(evt); err != nil {\n\t\t\ts.logger.Error(\"Error delivering notification\",\n\t\t\t\tmlog.String(\"backend\", backend.Name()),\n\t\t\t\tmlog.String(\"action\", string(evt.Action)),\n\t\t\t\tmlog.String(\"block_id\", evt.BlockChanged.ID),\n\t\t\t\tmlog.Err(err),\n\t\t\t)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "server/services/permissions/localpermissions/helpers_test.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage localpermissions\n\nimport (\n\t\"testing\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\tpermissionsMocks \"github.com/mattermost/focalboard/server/services/permissions/mocks\"\n\n\tmmModel \"github.com/mattermost/mattermost/server/public/model\"\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n\n\t\"github.com/golang/mock/gomock\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\ntype TestHelper struct {\n\tt           *testing.T\n\tctrl        *gomock.Controller\n\tstore       *permissionsMocks.MockStore\n\tpermissions *Service\n}\n\nfunc SetupTestHelper(t *testing.T) *TestHelper {\n\tctrl := gomock.NewController(t)\n\tmockStore := permissionsMocks.NewMockStore(ctrl)\n\treturn &TestHelper{\n\t\tt:           t,\n\t\tctrl:        ctrl,\n\t\tstore:       mockStore,\n\t\tpermissions: New(mockStore, mlog.CreateConsoleTestLogger(t)),\n\t}\n}\n\nfunc (th *TestHelper) checkBoardPermissions(roleName string, member *model.BoardMember, hasPermissionTo, hasNotPermissionTo []*mmModel.Permission) {\n\tfor _, p := range hasPermissionTo {\n\t\tth.t.Run(roleName+\" \"+p.Id, func(t *testing.T) {\n\t\t\tth.store.EXPECT().\n\t\t\t\tGetMemberForBoard(member.BoardID, member.UserID).\n\t\t\t\tReturn(member, nil).\n\t\t\t\tTimes(1)\n\n\t\t\thasPermission := th.permissions.HasPermissionToBoard(member.UserID, member.BoardID, p)\n\t\t\tassert.True(t, hasPermission)\n\t\t})\n\t}\n\n\tfor _, p := range hasNotPermissionTo {\n\t\tth.t.Run(roleName+\" \"+p.Id, func(t *testing.T) {\n\t\t\tth.store.EXPECT().\n\t\t\t\tGetMemberForBoard(member.BoardID, member.UserID).\n\t\t\t\tReturn(member, nil).\n\t\t\t\tTimes(1)\n\n\t\t\thasPermission := th.permissions.HasPermissionToBoard(member.UserID, member.BoardID, p)\n\t\t\tassert.False(t, hasPermission)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "server/services/permissions/localpermissions/localpermissions.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage localpermissions\n\nimport (\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/permissions\"\n\n\tmmModel \"github.com/mattermost/mattermost/server/public/model\"\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\ntype Service struct {\n\tstore  permissions.Store\n\tlogger mlog.LoggerIFace\n}\n\nfunc New(store permissions.Store, logger mlog.LoggerIFace) *Service {\n\treturn &Service{\n\t\tstore:  store,\n\t\tlogger: logger,\n\t}\n}\n\nfunc (s *Service) HasPermissionTo(userID string, permission *mmModel.Permission) bool {\n\treturn false\n}\n\nfunc (s *Service) HasPermissionToTeam(userID, teamID string, permission *mmModel.Permission) bool {\n\tif userID == \"\" || teamID == \"\" || permission == nil {\n\t\treturn false\n\t}\n\tif permission.Id == model.PermissionManageTeam.Id {\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc (s *Service) HasPermissionToChannel(userID, channelID string, permission *mmModel.Permission) bool {\n\tif userID == \"\" || channelID == \"\" || permission == nil {\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc (s *Service) HasPermissionToBoard(userID, boardID string, permission *mmModel.Permission) bool {\n\tif userID == \"\" || boardID == \"\" || permission == nil {\n\t\treturn false\n\t}\n\n\tmember, err := s.store.GetMemberForBoard(boardID, userID)\n\tif model.IsErrNotFound(err) {\n\t\treturn false\n\t}\n\tif err != nil {\n\t\ts.logger.Error(\"error getting member for board\",\n\t\t\tmlog.String(\"boardID\", boardID),\n\t\t\tmlog.String(\"userID\", userID),\n\t\t\tmlog.Err(err),\n\t\t)\n\t\treturn false\n\t}\n\n\tswitch member.MinimumRole {\n\tcase \"admin\":\n\t\tmember.SchemeAdmin = true\n\tcase \"editor\":\n\t\tmember.SchemeEditor = true\n\tcase \"commenter\":\n\t\tmember.SchemeCommenter = true\n\tcase \"viewer\":\n\t\tmember.SchemeViewer = true\n\t}\n\n\tswitch permission {\n\tcase model.PermissionManageBoardType, model.PermissionDeleteBoard, model.PermissionManageBoardRoles, model.PermissionShareBoard, model.PermissionDeleteOthersComments:\n\t\treturn member.SchemeAdmin\n\tcase model.PermissionManageBoardCards, model.PermissionManageBoardProperties:\n\t\treturn member.SchemeAdmin || member.SchemeEditor\n\tcase model.PermissionCommentBoardCards:\n\t\treturn member.SchemeAdmin || member.SchemeEditor || member.SchemeCommenter\n\tcase model.PermissionViewBoard:\n\t\treturn member.SchemeAdmin || member.SchemeEditor || member.SchemeCommenter || member.SchemeViewer\n\tdefault:\n\t\treturn false\n\t}\n}\n"
  },
  {
    "path": "server/services/permissions/localpermissions/localpermissions_test.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage localpermissions\n\nimport (\n\t\"database/sql\"\n\t\"testing\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\n\tmmModel \"github.com/mattermost/mattermost/server/public/model\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestHasPermissionToTeam(t *testing.T) {\n\tth := SetupTestHelper(t)\n\n\tt.Run(\"empty input should always unauthorize\", func(t *testing.T) {\n\t\tassert.False(t, th.permissions.HasPermissionToTeam(\"\", \"team-id\", model.PermissionManageBoardCards))\n\t\tassert.False(t, th.permissions.HasPermissionToTeam(\"user-id\", \"\", model.PermissionManageBoardCards))\n\t\tassert.False(t, th.permissions.HasPermissionToTeam(\"user-id\", \"team-id\", nil))\n\t})\n\n\tt.Run(\"all users have all permissions on teams\", func(t *testing.T) {\n\t\thasPermission := th.permissions.HasPermissionToTeam(\"user-id\", \"team-id\", model.PermissionManageBoardCards)\n\t\tassert.True(t, hasPermission)\n\t})\n\n\tt.Run(\"no users have PermissionManageTeam on teams\", func(t *testing.T) {\n\t\thasPermission := th.permissions.HasPermissionToTeam(\"user-id\", \"team-id\", model.PermissionManageTeam)\n\t\tassert.False(t, hasPermission)\n\t})\n}\n\nfunc TestHasPermissionToBoard(t *testing.T) {\n\tth := SetupTestHelper(t)\n\n\tt.Run(\"empty input should always unauthorize\", func(t *testing.T) {\n\t\tassert.False(t, th.permissions.HasPermissionToBoard(\"\", \"board-id\", model.PermissionManageBoardCards))\n\t\tassert.False(t, th.permissions.HasPermissionToBoard(\"user-id\", \"\", model.PermissionManageBoardCards))\n\t\tassert.False(t, th.permissions.HasPermissionToBoard(\"user-id\", \"board-id\", nil))\n\t})\n\n\tt.Run(\"nonexistent user\", func(t *testing.T) {\n\t\tuserID := \"user-id\"\n\t\tboardID := \"board-id\"\n\n\t\tth.store.EXPECT().\n\t\t\tGetMemberForBoard(boardID, userID).\n\t\t\tReturn(nil, sql.ErrNoRows).\n\t\t\tTimes(1)\n\n\t\thasPermission := th.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardCards)\n\t\tassert.False(t, hasPermission)\n\t})\n\n\tt.Run(\"board admin\", func(t *testing.T) {\n\t\tmember := &model.BoardMember{\n\t\t\tUserID:      \"user-id\",\n\t\t\tBoardID:     \"board-id\",\n\t\t\tSchemeAdmin: true,\n\t\t}\n\n\t\thasPermissionTo := []*mmModel.Permission{\n\t\t\tmodel.PermissionManageBoardType,\n\t\t\tmodel.PermissionDeleteBoard,\n\t\t\tmodel.PermissionManageBoardRoles,\n\t\t\tmodel.PermissionShareBoard,\n\t\t\tmodel.PermissionManageBoardCards,\n\t\t\tmodel.PermissionViewBoard,\n\t\t\tmodel.PermissionManageBoardProperties,\n\t\t}\n\n\t\thasNotPermissionTo := []*mmModel.Permission{}\n\n\t\tth.checkBoardPermissions(\"admin\", member, hasPermissionTo, hasNotPermissionTo)\n\t})\n\n\tt.Run(\"board editor\", func(t *testing.T) {\n\t\tmember := &model.BoardMember{\n\t\t\tUserID:       \"user-id\",\n\t\t\tBoardID:      \"board-id\",\n\t\t\tSchemeEditor: true,\n\t\t}\n\n\t\thasPermissionTo := []*mmModel.Permission{\n\t\t\tmodel.PermissionManageBoardCards,\n\t\t\tmodel.PermissionViewBoard,\n\t\t\tmodel.PermissionManageBoardProperties,\n\t\t}\n\n\t\thasNotPermissionTo := []*mmModel.Permission{\n\t\t\tmodel.PermissionManageBoardType,\n\t\t\tmodel.PermissionDeleteBoard,\n\t\t\tmodel.PermissionManageBoardRoles,\n\t\t\tmodel.PermissionShareBoard,\n\t\t}\n\n\t\tth.checkBoardPermissions(\"editor\", member, hasPermissionTo, hasNotPermissionTo)\n\t})\n\n\tt.Run(\"board commenter\", func(t *testing.T) {\n\t\tmember := &model.BoardMember{\n\t\t\tUserID:          \"user-id\",\n\t\t\tBoardID:         \"board-id\",\n\t\t\tSchemeCommenter: true,\n\t\t}\n\n\t\thasPermissionTo := []*mmModel.Permission{\n\t\t\tmodel.PermissionViewBoard,\n\t\t}\n\n\t\thasNotPermissionTo := []*mmModel.Permission{\n\t\t\tmodel.PermissionManageBoardType,\n\t\t\tmodel.PermissionDeleteBoard,\n\t\t\tmodel.PermissionManageBoardRoles,\n\t\t\tmodel.PermissionShareBoard,\n\t\t\tmodel.PermissionManageBoardCards,\n\t\t\tmodel.PermissionManageBoardProperties,\n\t\t}\n\n\t\tth.checkBoardPermissions(\"commenter\", member, hasPermissionTo, hasNotPermissionTo)\n\t})\n\n\tt.Run(\"board viewer\", func(t *testing.T) {\n\t\tmember := &model.BoardMember{\n\t\t\tUserID:       \"user-id\",\n\t\t\tBoardID:      \"board-id\",\n\t\t\tSchemeViewer: true,\n\t\t}\n\n\t\thasPermissionTo := []*mmModel.Permission{\n\t\t\tmodel.PermissionViewBoard,\n\t\t}\n\n\t\thasNotPermissionTo := []*mmModel.Permission{\n\t\t\tmodel.PermissionManageBoardType,\n\t\t\tmodel.PermissionDeleteBoard,\n\t\t\tmodel.PermissionManageBoardRoles,\n\t\t\tmodel.PermissionShareBoard,\n\t\t\tmodel.PermissionManageBoardCards,\n\t\t\tmodel.PermissionManageBoardProperties,\n\t\t}\n\n\t\tth.checkBoardPermissions(\"viewer\", member, hasPermissionTo, hasNotPermissionTo)\n\t})\n\n\tt.Run(\"Manage Team Permission \", func(t *testing.T) {\n\t\tmember := &model.BoardMember{\n\t\t\tUserID:       \"user-id\",\n\t\t\tBoardID:      \"board-id\",\n\t\t\tSchemeViewer: true,\n\t\t}\n\n\t\thasPermissionTo := []*mmModel.Permission{\n\t\t\tmodel.PermissionViewBoard,\n\t\t}\n\n\t\thasNotPermissionTo := []*mmModel.Permission{\n\t\t\tmodel.PermissionManageBoardType,\n\t\t\tmodel.PermissionDeleteBoard,\n\t\t\tmodel.PermissionManageBoardRoles,\n\t\t\tmodel.PermissionShareBoard,\n\t\t\tmodel.PermissionManageBoardCards,\n\t\t\tmodel.PermissionManageBoardProperties,\n\t\t}\n\n\t\tth.checkBoardPermissions(\"viewer\", member, hasPermissionTo, hasNotPermissionTo)\n\t})\n}\n"
  },
  {
    "path": "server/services/permissions/mmpermissions/helpers_test.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage mmpermissions\n\nimport (\n\t\"testing\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\tmmpermissionsMocks \"github.com/mattermost/focalboard/server/services/permissions/mmpermissions/mocks\"\n\tpermissionsMocks \"github.com/mattermost/focalboard/server/services/permissions/mocks\"\n\n\tmmModel \"github.com/mattermost/mattermost/server/public/model\"\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n\n\t\"github.com/golang/mock/gomock\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\ntype TestHelper struct {\n\tt           *testing.T\n\tctrl        *gomock.Controller\n\tstore       *permissionsMocks.MockStore\n\tapi         *mmpermissionsMocks.MockAPI\n\tpermissions *Service\n}\n\nfunc SetupTestHelper(t *testing.T) *TestHelper {\n\tctrl := gomock.NewController(t)\n\tmockStore := permissionsMocks.NewMockStore(ctrl)\n\tmockAPI := mmpermissionsMocks.NewMockAPI(ctrl)\n\n\treturn &TestHelper{\n\t\tt:           t,\n\t\tctrl:        ctrl,\n\t\tstore:       mockStore,\n\t\tapi:         mockAPI,\n\t\tpermissions: New(mockStore, mockAPI, mlog.CreateConsoleTestLogger(t)),\n\t}\n}\n\nfunc (th *TestHelper) checkBoardPermissions(roleName string, member *model.BoardMember, teamID string,\n\thasPermissionTo, hasNotPermissionTo []*mmModel.Permission) {\n\tfor _, p := range hasPermissionTo {\n\t\tth.t.Run(roleName+\" \"+p.Id, func(t *testing.T) {\n\t\t\tth.store.EXPECT().\n\t\t\t\tGetBoard(member.BoardID).\n\t\t\t\tReturn(&model.Board{ID: member.BoardID, TeamID: teamID}, nil).\n\t\t\t\tTimes(1)\n\n\t\t\tth.api.EXPECT().\n\t\t\t\tHasPermissionToTeam(member.UserID, teamID, model.PermissionViewTeam).\n\t\t\t\tReturn(true).\n\t\t\t\tTimes(1)\n\n\t\t\tth.store.EXPECT().\n\t\t\t\tGetMemberForBoard(member.BoardID, member.UserID).\n\t\t\t\tReturn(member, nil).\n\t\t\t\tTimes(1)\n\n\t\t\tif !member.SchemeAdmin {\n\t\t\t\tth.api.EXPECT().\n\t\t\t\t\tHasPermissionToTeam(member.UserID, teamID, model.PermissionManageTeam).\n\t\t\t\t\tReturn(roleName == \"elevated-admin\").\n\t\t\t\t\tTimes(1)\n\t\t\t}\n\n\t\t\thasPermission := th.permissions.HasPermissionToBoard(member.UserID, member.BoardID, p)\n\t\t\tassert.True(t, hasPermission)\n\t\t})\n\t}\n\n\tfor _, p := range hasNotPermissionTo {\n\t\tth.t.Run(roleName+\" \"+p.Id, func(t *testing.T) {\n\t\t\tth.store.EXPECT().\n\t\t\t\tGetBoard(member.BoardID).\n\t\t\t\tReturn(&model.Board{ID: member.BoardID, TeamID: teamID}, nil).\n\t\t\t\tTimes(1)\n\n\t\t\tth.api.EXPECT().\n\t\t\t\tHasPermissionToTeam(member.UserID, teamID, model.PermissionViewTeam).\n\t\t\t\tReturn(true).\n\t\t\t\tTimes(1)\n\n\t\t\tth.store.EXPECT().\n\t\t\t\tGetMemberForBoard(member.BoardID, member.UserID).\n\t\t\t\tReturn(member, nil).\n\t\t\t\tTimes(1)\n\n\t\t\tif !member.SchemeAdmin {\n\t\t\t\tth.api.EXPECT().\n\t\t\t\t\tHasPermissionToTeam(member.UserID, teamID, model.PermissionManageTeam).\n\t\t\t\t\tReturn(roleName == \"elevated-admin\").\n\t\t\t\t\tTimes(1)\n\t\t\t}\n\n\t\t\thasPermission := th.permissions.HasPermissionToBoard(member.UserID, member.BoardID, p)\n\t\t\tassert.False(t, hasPermission)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "server/services/permissions/mmpermissions/mmpermissions.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage mmpermissions\n\nimport (\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/permissions\"\n\n\tmmModel \"github.com/mattermost/mattermost/server/public/model\"\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\ntype APIInterface interface {\n\tHasPermissionTo(userID string, permission *mmModel.Permission) bool\n\tHasPermissionToTeam(userID string, teamID string, permission *mmModel.Permission) bool\n\tHasPermissionToChannel(userID string, channelID string, permission *mmModel.Permission) bool\n}\n\ntype Service struct {\n\tstore  permissions.Store\n\tapi    APIInterface\n\tlogger mlog.LoggerIFace\n}\n\nfunc New(store permissions.Store, api APIInterface, logger mlog.LoggerIFace) *Service {\n\treturn &Service{\n\t\tstore:  store,\n\t\tapi:    api,\n\t\tlogger: logger,\n\t}\n}\n\nfunc (s *Service) HasPermissionTo(userID string, permission *mmModel.Permission) bool {\n\tif userID == \"\" || permission == nil {\n\t\treturn false\n\t}\n\treturn s.api.HasPermissionTo(userID, permission)\n}\n\nfunc (s *Service) HasPermissionToTeam(userID, teamID string, permission *mmModel.Permission) bool {\n\tif userID == \"\" || teamID == \"\" || permission == nil {\n\t\treturn false\n\t}\n\treturn s.api.HasPermissionToTeam(userID, teamID, permission)\n}\n\nfunc (s *Service) HasPermissionToChannel(userID, channelID string, permission *mmModel.Permission) bool {\n\tif userID == \"\" || channelID == \"\" || permission == nil {\n\t\treturn false\n\t}\n\treturn s.api.HasPermissionToChannel(userID, channelID, permission)\n}\n\nfunc (s *Service) HasPermissionToBoard(userID, boardID string, permission *mmModel.Permission) bool {\n\tif userID == \"\" || boardID == \"\" || permission == nil {\n\t\treturn false\n\t}\n\n\tboard, err := s.store.GetBoard(boardID)\n\tif model.IsErrNotFound(err) {\n\t\tvar boards []*model.Board\n\t\tboards, err = s.store.GetBoardHistory(boardID, model.QueryBoardHistoryOptions{Limit: 1, Descending: true})\n\t\tif err != nil {\n\t\t\treturn false\n\t\t}\n\t\tif len(boards) == 0 {\n\t\t\treturn false\n\t\t}\n\t\tboard = boards[0]\n\t} else if err != nil {\n\t\ts.logger.Error(\"error getting board\",\n\t\t\tmlog.String(\"boardID\", boardID),\n\t\t\tmlog.String(\"userID\", userID),\n\t\t\tmlog.Err(err),\n\t\t)\n\t\treturn false\n\t}\n\n\t// we need to check that the user has permission to see the team\n\t// regardless of its local permissions to the board\n\tif !s.HasPermissionToTeam(userID, board.TeamID, model.PermissionViewTeam) {\n\t\treturn false\n\t}\n\tmember, err := s.store.GetMemberForBoard(boardID, userID)\n\tif model.IsErrNotFound(err) {\n\t\treturn false\n\t}\n\tif err != nil {\n\t\ts.logger.Error(\"error getting member for board\",\n\t\t\tmlog.String(\"boardID\", boardID),\n\t\t\tmlog.String(\"userID\", userID),\n\t\t\tmlog.Err(err),\n\t\t)\n\t\treturn false\n\t}\n\n\tswitch member.MinimumRole {\n\tcase \"admin\":\n\t\tmember.SchemeAdmin = true\n\tcase \"editor\":\n\t\tmember.SchemeEditor = true\n\tcase \"commenter\":\n\t\tmember.SchemeCommenter = true\n\tcase \"viewer\":\n\t\tmember.SchemeViewer = true\n\t}\n\n\t// Admins become member of boards, but get minimal role\n\t// if they are a System/Team Admin (model.PermissionManageTeam)\n\t// elevate their permissions\n\tif !member.SchemeAdmin && s.HasPermissionToTeam(userID, board.TeamID, model.PermissionManageTeam) {\n\t\treturn true\n\t}\n\n\tswitch permission {\n\tcase model.PermissionManageBoardType, model.PermissionDeleteBoard, model.PermissionManageBoardRoles, model.PermissionShareBoard, model.PermissionDeleteOthersComments:\n\t\treturn member.SchemeAdmin\n\tcase model.PermissionManageBoardCards, model.PermissionManageBoardProperties:\n\t\treturn member.SchemeAdmin || member.SchemeEditor\n\tcase model.PermissionCommentBoardCards:\n\t\treturn member.SchemeAdmin || member.SchemeEditor || member.SchemeCommenter\n\tcase model.PermissionViewBoard:\n\t\treturn member.SchemeAdmin || member.SchemeEditor || member.SchemeCommenter || member.SchemeViewer\n\tdefault:\n\t\treturn false\n\t}\n}\n"
  },
  {
    "path": "server/services/permissions/mmpermissions/mmpermissions_test.go",
    "content": "//go:generate mockgen -destination=mocks/mockpluginapi.go -package mocks github.com/mattermost/mattermost-server/v6/plugin API\npackage mmpermissions\n\nimport (\n\t\"database/sql\"\n\t\"testing\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\n\tmmModel \"github.com/mattermost/mattermost/server/public/model\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nconst (\n\ttestTeamID  = \"team-id\"\n\ttestBoardID = \"board-id\"\n\ttestUserID  = \"user-id\"\n)\n\nfunc TestHasPermissionsToTeam(t *testing.T) {\n\tth := SetupTestHelper(t)\n\n\tt.Run(\"empty input should always unauthorize\", func(t *testing.T) {\n\t\tassert.False(t, th.permissions.HasPermissionToTeam(\"\", testTeamID, model.PermissionManageBoardCards))\n\t\tassert.False(t, th.permissions.HasPermissionToTeam(testUserID, \"\", model.PermissionManageBoardCards))\n\t\tassert.False(t, th.permissions.HasPermissionToTeam(testUserID, testTeamID, nil))\n\t})\n\n\tt.Run(\"should authorize if the plugin API does\", func(t *testing.T) {\n\t\tuserID := testUserID\n\t\tteamID := testTeamID\n\n\t\tth.api.EXPECT().\n\t\t\tHasPermissionToTeam(userID, teamID, model.PermissionViewTeam).\n\t\t\tReturn(true).\n\t\t\tTimes(1)\n\n\t\thasPermission := th.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam)\n\t\tassert.True(t, hasPermission)\n\t})\n\n\tt.Run(\"should not authorize if the plugin API doesn't\", func(t *testing.T) {\n\t\tuserID := testUserID\n\t\tteamID := testTeamID\n\n\t\tth.api.EXPECT().\n\t\t\tHasPermissionToTeam(userID, teamID, model.PermissionViewTeam).\n\t\t\tReturn(false).\n\t\t\tTimes(1)\n\n\t\thasPermission := th.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam)\n\t\tassert.False(t, hasPermission)\n\t})\n}\n\n// test case for user removed.\nfunc TestHasPermissionToBoard(t *testing.T) {\n\tth := SetupTestHelper(t)\n\n\tt.Run(\"empty input should always unauthorize\", func(t *testing.T) {\n\t\tassert.False(t, th.permissions.HasPermissionToBoard(\"\", testBoardID, model.PermissionManageBoardCards))\n\t\tassert.False(t, th.permissions.HasPermissionToBoard(testUserID, \"\", model.PermissionManageBoardCards))\n\t\tassert.False(t, th.permissions.HasPermissionToBoard(testUserID, testBoardID, nil))\n\t})\n\n\tuserID := testUserID\n\tboardID := testBoardID\n\tteamID := testTeamID\n\n\tt.Run(\"nonexistent member\", func(t *testing.T) {\n\t\tth.store.EXPECT().\n\t\t\tGetBoard(boardID).\n\t\t\tReturn(&model.Board{ID: boardID, TeamID: teamID}, nil).\n\t\t\tTimes(1)\n\n\t\tth.api.EXPECT().\n\t\t\tHasPermissionToTeam(userID, teamID, model.PermissionViewTeam).\n\t\t\tReturn(true).\n\t\t\tTimes(1)\n\n\t\tth.store.EXPECT().\n\t\t\tGetMemberForBoard(boardID, userID).\n\t\t\tReturn(nil, sql.ErrNoRows).\n\t\t\tTimes(1)\n\n\t\thasPermission := th.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardCards)\n\t\tassert.False(t, hasPermission)\n\t})\n\n\tt.Run(\"nonexistent board\", func(t *testing.T) {\n\t\tth.store.EXPECT().\n\t\t\tGetBoard(boardID).\n\t\t\tReturn(nil, sql.ErrNoRows).\n\t\t\tTimes(1)\n\n\t\tth.store.EXPECT().\n\t\t\tGetBoardHistory(boardID, model.QueryBoardHistoryOptions{Limit: 1, Descending: true}).\n\t\t\tReturn(nil, sql.ErrNoRows).\n\t\t\tTimes(1)\n\n\t\thasPermission := th.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardCards)\n\t\tassert.False(t, hasPermission)\n\t})\n\n\tt.Run(\"user that has been removed from the team\", func(t *testing.T) {\n\t\tmember := &model.BoardMember{\n\t\t\tUserID:      userID,\n\t\t\tBoardID:     boardID,\n\t\t\tSchemeAdmin: true,\n\t\t}\n\n\t\tth.store.EXPECT().\n\t\t\tGetBoard(boardID).\n\t\t\tReturn(&model.Board{ID: boardID, TeamID: teamID}, nil).\n\t\t\tTimes(1)\n\n\t\tth.api.EXPECT().\n\t\t\tHasPermissionToTeam(userID, teamID, model.PermissionViewTeam).\n\t\t\tReturn(true).\n\t\t\tTimes(1)\n\n\t\tth.store.EXPECT().\n\t\t\tGetMemberForBoard(member.BoardID, member.UserID).\n\t\t\tReturn(member, nil).\n\t\t\tTimes(1)\n\n\t\thasPermission := th.permissions.HasPermissionToBoard(member.UserID, member.BoardID, model.PermissionViewBoard)\n\t\tassert.True(t, hasPermission)\n\t})\n\n\tt.Run(\"board admin\", func(t *testing.T) {\n\t\tmember := &model.BoardMember{\n\t\t\tUserID:      userID,\n\t\t\tBoardID:     boardID,\n\t\t\tSchemeAdmin: true,\n\t\t}\n\n\t\thasPermissionTo := []*mmModel.Permission{\n\t\t\tmodel.PermissionManageBoardType,\n\t\t\tmodel.PermissionDeleteBoard,\n\t\t\tmodel.PermissionManageBoardRoles,\n\t\t\tmodel.PermissionShareBoard,\n\t\t\tmodel.PermissionManageBoardCards,\n\t\t\tmodel.PermissionViewBoard,\n\t\t\tmodel.PermissionManageBoardProperties,\n\t\t}\n\n\t\thasNotPermissionTo := []*mmModel.Permission{}\n\n\t\tth.checkBoardPermissions(\"admin\", member, teamID, hasPermissionTo, hasNotPermissionTo)\n\t})\n\n\tt.Run(\"board editor\", func(t *testing.T) {\n\t\tmember := &model.BoardMember{\n\t\t\tUserID:       userID,\n\t\t\tBoardID:      boardID,\n\t\t\tSchemeEditor: true,\n\t\t}\n\n\t\thasPermissionTo := []*mmModel.Permission{\n\t\t\tmodel.PermissionManageBoardCards,\n\t\t\tmodel.PermissionViewBoard,\n\t\t\tmodel.PermissionManageBoardProperties,\n\t\t}\n\n\t\thasNotPermissionTo := []*mmModel.Permission{\n\t\t\tmodel.PermissionManageBoardType,\n\t\t\tmodel.PermissionDeleteBoard,\n\t\t\tmodel.PermissionManageBoardRoles,\n\t\t\tmodel.PermissionShareBoard,\n\t\t}\n\n\t\tth.checkBoardPermissions(\"editor\", member, teamID, hasPermissionTo, hasNotPermissionTo)\n\t})\n\n\tt.Run(\"board commenter\", func(t *testing.T) {\n\t\tmember := &model.BoardMember{\n\t\t\tUserID:          userID,\n\t\t\tBoardID:         boardID,\n\t\t\tSchemeCommenter: true,\n\t\t}\n\n\t\thasPermissionTo := []*mmModel.Permission{\n\t\t\tmodel.PermissionViewBoard,\n\t\t}\n\n\t\thasNotPermissionTo := []*mmModel.Permission{\n\t\t\tmodel.PermissionManageBoardType,\n\t\t\tmodel.PermissionDeleteBoard,\n\t\t\tmodel.PermissionManageBoardRoles,\n\t\t\tmodel.PermissionShareBoard,\n\t\t\tmodel.PermissionManageBoardCards,\n\t\t\tmodel.PermissionManageBoardProperties,\n\t\t}\n\n\t\tth.checkBoardPermissions(\"commenter\", member, teamID, hasPermissionTo, hasNotPermissionTo)\n\t})\n\n\tt.Run(\"board viewer\", func(t *testing.T) {\n\t\tmember := &model.BoardMember{\n\t\t\tUserID:       userID,\n\t\t\tBoardID:      boardID,\n\t\t\tSchemeViewer: true,\n\t\t}\n\n\t\thasPermissionTo := []*mmModel.Permission{\n\t\t\tmodel.PermissionViewBoard,\n\t\t}\n\n\t\thasNotPermissionTo := []*mmModel.Permission{\n\t\t\tmodel.PermissionManageBoardType,\n\t\t\tmodel.PermissionDeleteBoard,\n\t\t\tmodel.PermissionManageBoardRoles,\n\t\t\tmodel.PermissionShareBoard,\n\t\t\tmodel.PermissionManageBoardCards,\n\t\t\tmodel.PermissionManageBoardProperties,\n\t\t}\n\n\t\tth.checkBoardPermissions(\"viewer\", member, teamID, hasPermissionTo, hasNotPermissionTo)\n\t})\n\n\tt.Run(\"elevate board viewer permissions\", func(t *testing.T) {\n\t\tmember := &model.BoardMember{\n\t\t\tUserID:       userID,\n\t\t\tBoardID:      boardID,\n\t\t\tSchemeViewer: true,\n\t\t}\n\n\t\thasPermissionTo := []*mmModel.Permission{\n\t\t\tmodel.PermissionManageBoardType,\n\t\t\tmodel.PermissionDeleteBoard,\n\t\t\tmodel.PermissionManageBoardRoles,\n\t\t\tmodel.PermissionShareBoard,\n\t\t\tmodel.PermissionManageBoardCards,\n\t\t\tmodel.PermissionViewBoard,\n\t\t\tmodel.PermissionManageBoardProperties,\n\t\t}\n\n\t\thasNotPermissionTo := []*mmModel.Permission{}\n\t\tth.checkBoardPermissions(\"elevated-admin\", member, teamID, hasPermissionTo, hasNotPermissionTo)\n\t})\n}\n"
  },
  {
    "path": "server/services/permissions/mmpermissions/mocks/mockpluginapi.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: github.com/mattermost/mattermost-server/v6/plugin (interfaces: API)\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tio \"io\"\n\thttp \"net/http\"\n\treflect \"reflect\"\n\n\tgomock \"github.com/golang/mock/gomock\"\n\tmodel \"github.com/mattermost/mattermost/server/public/model\"\n)\n\n// MockAPI is a mock of API interface.\ntype MockAPI struct {\n\tctrl     *gomock.Controller\n\trecorder *MockAPIMockRecorder\n}\n\n// MockAPIMockRecorder is the mock recorder for MockAPI.\ntype MockAPIMockRecorder struct {\n\tmock *MockAPI\n}\n\n// NewMockAPI creates a new mock instance.\nfunc NewMockAPI(ctrl *gomock.Controller) *MockAPI {\n\tmock := &MockAPI{ctrl: ctrl}\n\tmock.recorder = &MockAPIMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockAPI) EXPECT() *MockAPIMockRecorder {\n\treturn m.recorder\n}\n\n// AddChannelMember mocks base method.\nfunc (m *MockAPI) AddChannelMember(arg0, arg1 string) (*model.ChannelMember, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"AddChannelMember\", arg0, arg1)\n\tret0, _ := ret[0].(*model.ChannelMember)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// AddChannelMember indicates an expected call of AddChannelMember.\nfunc (mr *MockAPIMockRecorder) AddChannelMember(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"AddChannelMember\", reflect.TypeOf((*MockAPI)(nil).AddChannelMember), arg0, arg1)\n}\n\n// AddReaction mocks base method.\nfunc (m *MockAPI) AddReaction(arg0 *model.Reaction) (*model.Reaction, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"AddReaction\", arg0)\n\tret0, _ := ret[0].(*model.Reaction)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// AddReaction indicates an expected call of AddReaction.\nfunc (mr *MockAPIMockRecorder) AddReaction(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"AddReaction\", reflect.TypeOf((*MockAPI)(nil).AddReaction), arg0)\n}\n\n// AddUserToChannel mocks base method.\nfunc (m *MockAPI) AddUserToChannel(arg0, arg1, arg2 string) (*model.ChannelMember, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"AddUserToChannel\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(*model.ChannelMember)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// AddUserToChannel indicates an expected call of AddUserToChannel.\nfunc (mr *MockAPIMockRecorder) AddUserToChannel(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"AddUserToChannel\", reflect.TypeOf((*MockAPI)(nil).AddUserToChannel), arg0, arg1, arg2)\n}\n\n// CopyFileInfos mocks base method.\nfunc (m *MockAPI) CopyFileInfos(arg0 string, arg1 []string) ([]string, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"CopyFileInfos\", arg0, arg1)\n\tret0, _ := ret[0].([]string)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// CopyFileInfos indicates an expected call of CopyFileInfos.\nfunc (mr *MockAPIMockRecorder) CopyFileInfos(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"CopyFileInfos\", reflect.TypeOf((*MockAPI)(nil).CopyFileInfos), arg0, arg1)\n}\n\n// CreateBot mocks base method.\nfunc (m *MockAPI) CreateBot(arg0 *model.Bot) (*model.Bot, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"CreateBot\", arg0)\n\tret0, _ := ret[0].(*model.Bot)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// CreateBot indicates an expected call of CreateBot.\nfunc (mr *MockAPIMockRecorder) CreateBot(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"CreateBot\", reflect.TypeOf((*MockAPI)(nil).CreateBot), arg0)\n}\n\n// CreateChannel mocks base method.\nfunc (m *MockAPI) CreateChannel(arg0 *model.Channel) (*model.Channel, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"CreateChannel\", arg0)\n\tret0, _ := ret[0].(*model.Channel)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// CreateChannel indicates an expected call of CreateChannel.\nfunc (mr *MockAPIMockRecorder) CreateChannel(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"CreateChannel\", reflect.TypeOf((*MockAPI)(nil).CreateChannel), arg0)\n}\n\n// CreateChannelSidebarCategory mocks base method.\nfunc (m *MockAPI) CreateChannelSidebarCategory(arg0, arg1 string, arg2 *model.SidebarCategoryWithChannels) (*model.SidebarCategoryWithChannels, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"CreateChannelSidebarCategory\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(*model.SidebarCategoryWithChannels)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// CreateChannelSidebarCategory indicates an expected call of CreateChannelSidebarCategory.\nfunc (mr *MockAPIMockRecorder) CreateChannelSidebarCategory(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"CreateChannelSidebarCategory\", reflect.TypeOf((*MockAPI)(nil).CreateChannelSidebarCategory), arg0, arg1, arg2)\n}\n\n// CreateCommand mocks base method.\nfunc (m *MockAPI) CreateCommand(arg0 *model.Command) (*model.Command, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"CreateCommand\", arg0)\n\tret0, _ := ret[0].(*model.Command)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// CreateCommand indicates an expected call of CreateCommand.\nfunc (mr *MockAPIMockRecorder) CreateCommand(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"CreateCommand\", reflect.TypeOf((*MockAPI)(nil).CreateCommand), arg0)\n}\n\n// CreateOAuthApp mocks base method.\nfunc (m *MockAPI) CreateOAuthApp(arg0 *model.OAuthApp) (*model.OAuthApp, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"CreateOAuthApp\", arg0)\n\tret0, _ := ret[0].(*model.OAuthApp)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// CreateOAuthApp indicates an expected call of CreateOAuthApp.\nfunc (mr *MockAPIMockRecorder) CreateOAuthApp(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"CreateOAuthApp\", reflect.TypeOf((*MockAPI)(nil).CreateOAuthApp), arg0)\n}\n\n// CreatePost mocks base method.\nfunc (m *MockAPI) CreatePost(arg0 *model.Post) (*model.Post, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"CreatePost\", arg0)\n\tret0, _ := ret[0].(*model.Post)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// CreatePost indicates an expected call of CreatePost.\nfunc (mr *MockAPIMockRecorder) CreatePost(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"CreatePost\", reflect.TypeOf((*MockAPI)(nil).CreatePost), arg0)\n}\n\n// CreateSession mocks base method.\nfunc (m *MockAPI) CreateSession(arg0 *model.Session) (*model.Session, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"CreateSession\", arg0)\n\tret0, _ := ret[0].(*model.Session)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// CreateSession indicates an expected call of CreateSession.\nfunc (mr *MockAPIMockRecorder) CreateSession(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"CreateSession\", reflect.TypeOf((*MockAPI)(nil).CreateSession), arg0)\n}\n\n// CreateTeam mocks base method.\nfunc (m *MockAPI) CreateTeam(arg0 *model.Team) (*model.Team, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"CreateTeam\", arg0)\n\tret0, _ := ret[0].(*model.Team)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// CreateTeam indicates an expected call of CreateTeam.\nfunc (mr *MockAPIMockRecorder) CreateTeam(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"CreateTeam\", reflect.TypeOf((*MockAPI)(nil).CreateTeam), arg0)\n}\n\n// CreateTeamMember mocks base method.\nfunc (m *MockAPI) CreateTeamMember(arg0, arg1 string) (*model.TeamMember, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"CreateTeamMember\", arg0, arg1)\n\tret0, _ := ret[0].(*model.TeamMember)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// CreateTeamMember indicates an expected call of CreateTeamMember.\nfunc (mr *MockAPIMockRecorder) CreateTeamMember(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"CreateTeamMember\", reflect.TypeOf((*MockAPI)(nil).CreateTeamMember), arg0, arg1)\n}\n\n// CreateTeamMembers mocks base method.\nfunc (m *MockAPI) CreateTeamMembers(arg0 string, arg1 []string, arg2 string) ([]*model.TeamMember, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"CreateTeamMembers\", arg0, arg1, arg2)\n\tret0, _ := ret[0].([]*model.TeamMember)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// CreateTeamMembers indicates an expected call of CreateTeamMembers.\nfunc (mr *MockAPIMockRecorder) CreateTeamMembers(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"CreateTeamMembers\", reflect.TypeOf((*MockAPI)(nil).CreateTeamMembers), arg0, arg1, arg2)\n}\n\n// CreateTeamMembersGracefully mocks base method.\nfunc (m *MockAPI) CreateTeamMembersGracefully(arg0 string, arg1 []string, arg2 string) ([]*model.TeamMemberWithError, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"CreateTeamMembersGracefully\", arg0, arg1, arg2)\n\tret0, _ := ret[0].([]*model.TeamMemberWithError)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// CreateTeamMembersGracefully indicates an expected call of CreateTeamMembersGracefully.\nfunc (mr *MockAPIMockRecorder) CreateTeamMembersGracefully(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"CreateTeamMembersGracefully\", reflect.TypeOf((*MockAPI)(nil).CreateTeamMembersGracefully), arg0, arg1, arg2)\n}\n\n// CreateUploadSession mocks base method.\nfunc (m *MockAPI) CreateUploadSession(arg0 *model.UploadSession) (*model.UploadSession, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"CreateUploadSession\", arg0)\n\tret0, _ := ret[0].(*model.UploadSession)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// CreateUploadSession indicates an expected call of CreateUploadSession.\nfunc (mr *MockAPIMockRecorder) CreateUploadSession(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"CreateUploadSession\", reflect.TypeOf((*MockAPI)(nil).CreateUploadSession), arg0)\n}\n\n// CreateUser mocks base method.\nfunc (m *MockAPI) CreateUser(arg0 *model.User) (*model.User, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"CreateUser\", arg0)\n\tret0, _ := ret[0].(*model.User)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// CreateUser indicates an expected call of CreateUser.\nfunc (mr *MockAPIMockRecorder) CreateUser(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"CreateUser\", reflect.TypeOf((*MockAPI)(nil).CreateUser), arg0)\n}\n\n// CreateUserAccessToken mocks base method.\nfunc (m *MockAPI) CreateUserAccessToken(arg0 *model.UserAccessToken) (*model.UserAccessToken, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"CreateUserAccessToken\", arg0)\n\tret0, _ := ret[0].(*model.UserAccessToken)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// CreateUserAccessToken indicates an expected call of CreateUserAccessToken.\nfunc (mr *MockAPIMockRecorder) CreateUserAccessToken(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"CreateUserAccessToken\", reflect.TypeOf((*MockAPI)(nil).CreateUserAccessToken), arg0)\n}\n\n// DeleteChannel mocks base method.\nfunc (m *MockAPI) DeleteChannel(arg0 string) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"DeleteChannel\", arg0)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// DeleteChannel indicates an expected call of DeleteChannel.\nfunc (mr *MockAPIMockRecorder) DeleteChannel(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"DeleteChannel\", reflect.TypeOf((*MockAPI)(nil).DeleteChannel), arg0)\n}\n\n// DeleteChannelMember mocks base method.\nfunc (m *MockAPI) DeleteChannelMember(arg0, arg1 string) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"DeleteChannelMember\", arg0, arg1)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// DeleteChannelMember indicates an expected call of DeleteChannelMember.\nfunc (mr *MockAPIMockRecorder) DeleteChannelMember(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"DeleteChannelMember\", reflect.TypeOf((*MockAPI)(nil).DeleteChannelMember), arg0, arg1)\n}\n\n// DeleteCommand mocks base method.\nfunc (m *MockAPI) DeleteCommand(arg0 string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"DeleteCommand\", arg0)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// DeleteCommand indicates an expected call of DeleteCommand.\nfunc (mr *MockAPIMockRecorder) DeleteCommand(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"DeleteCommand\", reflect.TypeOf((*MockAPI)(nil).DeleteCommand), arg0)\n}\n\n// DeleteEphemeralPost mocks base method.\nfunc (m *MockAPI) DeleteEphemeralPost(arg0, arg1 string) {\n\tm.ctrl.T.Helper()\n\tm.ctrl.Call(m, \"DeleteEphemeralPost\", arg0, arg1)\n}\n\n// DeleteEphemeralPost indicates an expected call of DeleteEphemeralPost.\nfunc (mr *MockAPIMockRecorder) DeleteEphemeralPost(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"DeleteEphemeralPost\", reflect.TypeOf((*MockAPI)(nil).DeleteEphemeralPost), arg0, arg1)\n}\n\n// DeleteOAuthApp mocks base method.\nfunc (m *MockAPI) DeleteOAuthApp(arg0 string) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"DeleteOAuthApp\", arg0)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// DeleteOAuthApp indicates an expected call of DeleteOAuthApp.\nfunc (mr *MockAPIMockRecorder) DeleteOAuthApp(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"DeleteOAuthApp\", reflect.TypeOf((*MockAPI)(nil).DeleteOAuthApp), arg0)\n}\n\n// DeletePost mocks base method.\nfunc (m *MockAPI) DeletePost(arg0 string) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"DeletePost\", arg0)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// DeletePost indicates an expected call of DeletePost.\nfunc (mr *MockAPIMockRecorder) DeletePost(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"DeletePost\", reflect.TypeOf((*MockAPI)(nil).DeletePost), arg0)\n}\n\n// DeletePreferencesForUser mocks base method.\nfunc (m *MockAPI) DeletePreferencesForUser(arg0 string, arg1 []model.Preference) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"DeletePreferencesForUser\", arg0, arg1)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// DeletePreferencesForUser indicates an expected call of DeletePreferencesForUser.\nfunc (mr *MockAPIMockRecorder) DeletePreferencesForUser(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"DeletePreferencesForUser\", reflect.TypeOf((*MockAPI)(nil).DeletePreferencesForUser), arg0, arg1)\n}\n\n// DeleteTeam mocks base method.\nfunc (m *MockAPI) DeleteTeam(arg0 string) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"DeleteTeam\", arg0)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// DeleteTeam indicates an expected call of DeleteTeam.\nfunc (mr *MockAPIMockRecorder) DeleteTeam(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"DeleteTeam\", reflect.TypeOf((*MockAPI)(nil).DeleteTeam), arg0)\n}\n\n// DeleteTeamMember mocks base method.\nfunc (m *MockAPI) DeleteTeamMember(arg0, arg1, arg2 string) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"DeleteTeamMember\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// DeleteTeamMember indicates an expected call of DeleteTeamMember.\nfunc (mr *MockAPIMockRecorder) DeleteTeamMember(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"DeleteTeamMember\", reflect.TypeOf((*MockAPI)(nil).DeleteTeamMember), arg0, arg1, arg2)\n}\n\n// DeleteUser mocks base method.\nfunc (m *MockAPI) DeleteUser(arg0 string) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"DeleteUser\", arg0)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// DeleteUser indicates an expected call of DeleteUser.\nfunc (mr *MockAPIMockRecorder) DeleteUser(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"DeleteUser\", reflect.TypeOf((*MockAPI)(nil).DeleteUser), arg0)\n}\n\n// DisablePlugin mocks base method.\nfunc (m *MockAPI) DisablePlugin(arg0 string) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"DisablePlugin\", arg0)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// DisablePlugin indicates an expected call of DisablePlugin.\nfunc (mr *MockAPIMockRecorder) DisablePlugin(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"DisablePlugin\", reflect.TypeOf((*MockAPI)(nil).DisablePlugin), arg0)\n}\n\n// EnablePlugin mocks base method.\nfunc (m *MockAPI) EnablePlugin(arg0 string) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"EnablePlugin\", arg0)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// EnablePlugin indicates an expected call of EnablePlugin.\nfunc (mr *MockAPIMockRecorder) EnablePlugin(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"EnablePlugin\", reflect.TypeOf((*MockAPI)(nil).EnablePlugin), arg0)\n}\n\n// EnsureBotUser mocks base method.\nfunc (m *MockAPI) EnsureBotUser(arg0 *model.Bot) (string, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"EnsureBotUser\", arg0)\n\tret0, _ := ret[0].(string)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// EnsureBotUser indicates an expected call of EnsureBotUser.\nfunc (mr *MockAPIMockRecorder) EnsureBotUser(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"EnsureBotUser\", reflect.TypeOf((*MockAPI)(nil).EnsureBotUser), arg0)\n}\n\n// ExecuteSlashCommand mocks base method.\nfunc (m *MockAPI) ExecuteSlashCommand(arg0 *model.CommandArgs) (*model.CommandResponse, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ExecuteSlashCommand\", arg0)\n\tret0, _ := ret[0].(*model.CommandResponse)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ExecuteSlashCommand indicates an expected call of ExecuteSlashCommand.\nfunc (mr *MockAPIMockRecorder) ExecuteSlashCommand(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ExecuteSlashCommand\", reflect.TypeOf((*MockAPI)(nil).ExecuteSlashCommand), arg0)\n}\n\n// ExtendSessionExpiry mocks base method.\nfunc (m *MockAPI) ExtendSessionExpiry(arg0 string, arg1 int64) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ExtendSessionExpiry\", arg0, arg1)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// ExtendSessionExpiry indicates an expected call of ExtendSessionExpiry.\nfunc (mr *MockAPIMockRecorder) ExtendSessionExpiry(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ExtendSessionExpiry\", reflect.TypeOf((*MockAPI)(nil).ExtendSessionExpiry), arg0, arg1)\n}\n\n// GetBot mocks base method.\nfunc (m *MockAPI) GetBot(arg0 string, arg1 bool) (*model.Bot, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetBot\", arg0, arg1)\n\tret0, _ := ret[0].(*model.Bot)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetBot indicates an expected call of GetBot.\nfunc (mr *MockAPIMockRecorder) GetBot(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetBot\", reflect.TypeOf((*MockAPI)(nil).GetBot), arg0, arg1)\n}\n\n// GetBots mocks base method.\nfunc (m *MockAPI) GetBots(arg0 *model.BotGetOptions) ([]*model.Bot, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetBots\", arg0)\n\tret0, _ := ret[0].([]*model.Bot)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetBots indicates an expected call of GetBots.\nfunc (mr *MockAPIMockRecorder) GetBots(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetBots\", reflect.TypeOf((*MockAPI)(nil).GetBots), arg0)\n}\n\n// GetBundlePath mocks base method.\nfunc (m *MockAPI) GetBundlePath() (string, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetBundlePath\")\n\tret0, _ := ret[0].(string)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetBundlePath indicates an expected call of GetBundlePath.\nfunc (mr *MockAPIMockRecorder) GetBundlePath() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetBundlePath\", reflect.TypeOf((*MockAPI)(nil).GetBundlePath))\n}\n\n// GetChannel mocks base method.\nfunc (m *MockAPI) GetChannel(arg0 string) (*model.Channel, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetChannel\", arg0)\n\tret0, _ := ret[0].(*model.Channel)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetChannel indicates an expected call of GetChannel.\nfunc (mr *MockAPIMockRecorder) GetChannel(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetChannel\", reflect.TypeOf((*MockAPI)(nil).GetChannel), arg0)\n}\n\n// GetChannelByName mocks base method.\nfunc (m *MockAPI) GetChannelByName(arg0, arg1 string, arg2 bool) (*model.Channel, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetChannelByName\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(*model.Channel)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetChannelByName indicates an expected call of GetChannelByName.\nfunc (mr *MockAPIMockRecorder) GetChannelByName(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetChannelByName\", reflect.TypeOf((*MockAPI)(nil).GetChannelByName), arg0, arg1, arg2)\n}\n\n// GetChannelByNameForTeamName mocks base method.\nfunc (m *MockAPI) GetChannelByNameForTeamName(arg0, arg1 string, arg2 bool) (*model.Channel, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetChannelByNameForTeamName\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(*model.Channel)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetChannelByNameForTeamName indicates an expected call of GetChannelByNameForTeamName.\nfunc (mr *MockAPIMockRecorder) GetChannelByNameForTeamName(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetChannelByNameForTeamName\", reflect.TypeOf((*MockAPI)(nil).GetChannelByNameForTeamName), arg0, arg1, arg2)\n}\n\n// GetChannelMember mocks base method.\nfunc (m *MockAPI) GetChannelMember(arg0, arg1 string) (*model.ChannelMember, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetChannelMember\", arg0, arg1)\n\tret0, _ := ret[0].(*model.ChannelMember)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetChannelMember indicates an expected call of GetChannelMember.\nfunc (mr *MockAPIMockRecorder) GetChannelMember(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetChannelMember\", reflect.TypeOf((*MockAPI)(nil).GetChannelMember), arg0, arg1)\n}\n\n// GetChannelMembers mocks base method.\nfunc (m *MockAPI) GetChannelMembers(arg0 string, arg1, arg2 int) (model.ChannelMembers, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetChannelMembers\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(model.ChannelMembers)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetChannelMembers indicates an expected call of GetChannelMembers.\nfunc (mr *MockAPIMockRecorder) GetChannelMembers(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetChannelMembers\", reflect.TypeOf((*MockAPI)(nil).GetChannelMembers), arg0, arg1, arg2)\n}\n\n// GetChannelMembersByIds mocks base method.\nfunc (m *MockAPI) GetChannelMembersByIds(arg0 string, arg1 []string) (model.ChannelMembers, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetChannelMembersByIds\", arg0, arg1)\n\tret0, _ := ret[0].(model.ChannelMembers)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetChannelMembersByIds indicates an expected call of GetChannelMembersByIds.\nfunc (mr *MockAPIMockRecorder) GetChannelMembersByIds(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetChannelMembersByIds\", reflect.TypeOf((*MockAPI)(nil).GetChannelMembersByIds), arg0, arg1)\n}\n\n// GetChannelMembersForUser mocks base method.\nfunc (m *MockAPI) GetChannelMembersForUser(arg0, arg1 string, arg2, arg3 int) ([]*model.ChannelMember, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetChannelMembersForUser\", arg0, arg1, arg2, arg3)\n\tret0, _ := ret[0].([]*model.ChannelMember)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetChannelMembersForUser indicates an expected call of GetChannelMembersForUser.\nfunc (mr *MockAPIMockRecorder) GetChannelMembersForUser(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetChannelMembersForUser\", reflect.TypeOf((*MockAPI)(nil).GetChannelMembersForUser), arg0, arg1, arg2, arg3)\n}\n\n// GetChannelSidebarCategories mocks base method.\nfunc (m *MockAPI) GetChannelSidebarCategories(arg0, arg1 string) (*model.OrderedSidebarCategories, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetChannelSidebarCategories\", arg0, arg1)\n\tret0, _ := ret[0].(*model.OrderedSidebarCategories)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetChannelSidebarCategories indicates an expected call of GetChannelSidebarCategories.\nfunc (mr *MockAPIMockRecorder) GetChannelSidebarCategories(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetChannelSidebarCategories\", reflect.TypeOf((*MockAPI)(nil).GetChannelSidebarCategories), arg0, arg1)\n}\n\n// GetChannelStats mocks base method.\nfunc (m *MockAPI) GetChannelStats(arg0 string) (*model.ChannelStats, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetChannelStats\", arg0)\n\tret0, _ := ret[0].(*model.ChannelStats)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetChannelStats indicates an expected call of GetChannelStats.\nfunc (mr *MockAPIMockRecorder) GetChannelStats(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetChannelStats\", reflect.TypeOf((*MockAPI)(nil).GetChannelStats), arg0)\n}\n\n// GetChannelsForTeamForUser mocks base method.\nfunc (m *MockAPI) GetChannelsForTeamForUser(arg0, arg1 string, arg2 bool) ([]*model.Channel, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetChannelsForTeamForUser\", arg0, arg1, arg2)\n\tret0, _ := ret[0].([]*model.Channel)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetChannelsForTeamForUser indicates an expected call of GetChannelsForTeamForUser.\nfunc (mr *MockAPIMockRecorder) GetChannelsForTeamForUser(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetChannelsForTeamForUser\", reflect.TypeOf((*MockAPI)(nil).GetChannelsForTeamForUser), arg0, arg1, arg2)\n}\n\n// GetCommand mocks base method.\nfunc (m *MockAPI) GetCommand(arg0 string) (*model.Command, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetCommand\", arg0)\n\tret0, _ := ret[0].(*model.Command)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetCommand indicates an expected call of GetCommand.\nfunc (mr *MockAPIMockRecorder) GetCommand(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetCommand\", reflect.TypeOf((*MockAPI)(nil).GetCommand), arg0)\n}\n\n// GetConfig mocks base method.\nfunc (m *MockAPI) GetConfig() *model.Config {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetConfig\")\n\tret0, _ := ret[0].(*model.Config)\n\treturn ret0\n}\n\n// GetConfig indicates an expected call of GetConfig.\nfunc (mr *MockAPIMockRecorder) GetConfig() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetConfig\", reflect.TypeOf((*MockAPI)(nil).GetConfig))\n}\n\n// GetDiagnosticId mocks base method.\nfunc (m *MockAPI) GetDiagnosticId() string {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetDiagnosticId\")\n\tret0, _ := ret[0].(string)\n\treturn ret0\n}\n\n// GetDiagnosticId indicates an expected call of GetDiagnosticId.\nfunc (mr *MockAPIMockRecorder) GetDiagnosticId() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetDiagnosticId\", reflect.TypeOf((*MockAPI)(nil).GetDiagnosticId))\n}\n\n// GetDirectChannel mocks base method.\nfunc (m *MockAPI) GetDirectChannel(arg0, arg1 string) (*model.Channel, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetDirectChannel\", arg0, arg1)\n\tret0, _ := ret[0].(*model.Channel)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetDirectChannel indicates an expected call of GetDirectChannel.\nfunc (mr *MockAPIMockRecorder) GetDirectChannel(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetDirectChannel\", reflect.TypeOf((*MockAPI)(nil).GetDirectChannel), arg0, arg1)\n}\n\n// GetEmoji mocks base method.\nfunc (m *MockAPI) GetEmoji(arg0 string) (*model.Emoji, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetEmoji\", arg0)\n\tret0, _ := ret[0].(*model.Emoji)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetEmoji indicates an expected call of GetEmoji.\nfunc (mr *MockAPIMockRecorder) GetEmoji(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetEmoji\", reflect.TypeOf((*MockAPI)(nil).GetEmoji), arg0)\n}\n\n// GetEmojiByName mocks base method.\nfunc (m *MockAPI) GetEmojiByName(arg0 string) (*model.Emoji, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetEmojiByName\", arg0)\n\tret0, _ := ret[0].(*model.Emoji)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetEmojiByName indicates an expected call of GetEmojiByName.\nfunc (mr *MockAPIMockRecorder) GetEmojiByName(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetEmojiByName\", reflect.TypeOf((*MockAPI)(nil).GetEmojiByName), arg0)\n}\n\n// GetEmojiImage mocks base method.\nfunc (m *MockAPI) GetEmojiImage(arg0 string) ([]byte, string, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetEmojiImage\", arg0)\n\tret0, _ := ret[0].([]byte)\n\tret1, _ := ret[1].(string)\n\tret2, _ := ret[2].(*model.AppError)\n\treturn ret0, ret1, ret2\n}\n\n// GetEmojiImage indicates an expected call of GetEmojiImage.\nfunc (mr *MockAPIMockRecorder) GetEmojiImage(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetEmojiImage\", reflect.TypeOf((*MockAPI)(nil).GetEmojiImage), arg0)\n}\n\n// GetEmojiList mocks base method.\nfunc (m *MockAPI) GetEmojiList(arg0 string, arg1, arg2 int) ([]*model.Emoji, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetEmojiList\", arg0, arg1, arg2)\n\tret0, _ := ret[0].([]*model.Emoji)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetEmojiList indicates an expected call of GetEmojiList.\nfunc (mr *MockAPIMockRecorder) GetEmojiList(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetEmojiList\", reflect.TypeOf((*MockAPI)(nil).GetEmojiList), arg0, arg1, arg2)\n}\n\n// GetFile mocks base method.\nfunc (m *MockAPI) GetFile(arg0 string) ([]byte, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetFile\", arg0)\n\tret0, _ := ret[0].([]byte)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetFile indicates an expected call of GetFile.\nfunc (mr *MockAPIMockRecorder) GetFile(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetFile\", reflect.TypeOf((*MockAPI)(nil).GetFile), arg0)\n}\n\n// GetFileInfo mocks base method.\nfunc (m *MockAPI) GetFileInfo(arg0 string) (*model.FileInfo, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetFileInfo\", arg0)\n\tret0, _ := ret[0].(*model.FileInfo)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetFileInfo indicates an expected call of GetFileInfo.\nfunc (mr *MockAPIMockRecorder) GetFileInfo(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetFileInfo\", reflect.TypeOf((*MockAPI)(nil).GetFileInfo), arg0)\n}\n\n// GetFileInfos mocks base method.\nfunc (m *MockAPI) GetFileInfos(arg0, arg1 int, arg2 *model.GetFileInfosOptions) ([]*model.FileInfo, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetFileInfos\", arg0, arg1, arg2)\n\tret0, _ := ret[0].([]*model.FileInfo)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetFileInfos indicates an expected call of GetFileInfos.\nfunc (mr *MockAPIMockRecorder) GetFileInfos(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetFileInfos\", reflect.TypeOf((*MockAPI)(nil).GetFileInfos), arg0, arg1, arg2)\n}\n\n// GetFileLink mocks base method.\nfunc (m *MockAPI) GetFileLink(arg0 string) (string, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetFileLink\", arg0)\n\tret0, _ := ret[0].(string)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetFileLink indicates an expected call of GetFileLink.\nfunc (mr *MockAPIMockRecorder) GetFileLink(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetFileLink\", reflect.TypeOf((*MockAPI)(nil).GetFileLink), arg0)\n}\n\n// GetGroup mocks base method.\nfunc (m *MockAPI) GetGroup(arg0 string) (*model.Group, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetGroup\", arg0)\n\tret0, _ := ret[0].(*model.Group)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetGroup indicates an expected call of GetGroup.\nfunc (mr *MockAPIMockRecorder) GetGroup(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetGroup\", reflect.TypeOf((*MockAPI)(nil).GetGroup), arg0)\n}\n\n// GetGroupByName mocks base method.\nfunc (m *MockAPI) GetGroupByName(arg0 string) (*model.Group, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetGroupByName\", arg0)\n\tret0, _ := ret[0].(*model.Group)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetGroupByName indicates an expected call of GetGroupByName.\nfunc (mr *MockAPIMockRecorder) GetGroupByName(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetGroupByName\", reflect.TypeOf((*MockAPI)(nil).GetGroupByName), arg0)\n}\n\n// GetGroupChannel mocks base method.\nfunc (m *MockAPI) GetGroupChannel(arg0 []string) (*model.Channel, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetGroupChannel\", arg0)\n\tret0, _ := ret[0].(*model.Channel)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetGroupChannel indicates an expected call of GetGroupChannel.\nfunc (mr *MockAPIMockRecorder) GetGroupChannel(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetGroupChannel\", reflect.TypeOf((*MockAPI)(nil).GetGroupChannel), arg0)\n}\n\n// GetGroupMemberUsers mocks base method.\nfunc (m *MockAPI) GetGroupMemberUsers(arg0 string, arg1, arg2 int) ([]*model.User, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetGroupMemberUsers\", arg0, arg1, arg2)\n\tret0, _ := ret[0].([]*model.User)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetGroupMemberUsers indicates an expected call of GetGroupMemberUsers.\nfunc (mr *MockAPIMockRecorder) GetGroupMemberUsers(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetGroupMemberUsers\", reflect.TypeOf((*MockAPI)(nil).GetGroupMemberUsers), arg0, arg1, arg2)\n}\n\n// GetGroupsBySource mocks base method.\nfunc (m *MockAPI) GetGroupsBySource(arg0 model.GroupSource) ([]*model.Group, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetGroupsBySource\", arg0)\n\tret0, _ := ret[0].([]*model.Group)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetGroupsBySource indicates an expected call of GetGroupsBySource.\nfunc (mr *MockAPIMockRecorder) GetGroupsBySource(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetGroupsBySource\", reflect.TypeOf((*MockAPI)(nil).GetGroupsBySource), arg0)\n}\n\n// GetGroupsForUser mocks base method.\nfunc (m *MockAPI) GetGroupsForUser(arg0 string) ([]*model.Group, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetGroupsForUser\", arg0)\n\tret0, _ := ret[0].([]*model.Group)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetGroupsForUser indicates an expected call of GetGroupsForUser.\nfunc (mr *MockAPIMockRecorder) GetGroupsForUser(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetGroupsForUser\", reflect.TypeOf((*MockAPI)(nil).GetGroupsForUser), arg0)\n}\n\n// GetLDAPUserAttributes mocks base method.\nfunc (m *MockAPI) GetLDAPUserAttributes(arg0 string, arg1 []string) (map[string]string, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetLDAPUserAttributes\", arg0, arg1)\n\tret0, _ := ret[0].(map[string]string)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetLDAPUserAttributes indicates an expected call of GetLDAPUserAttributes.\nfunc (mr *MockAPIMockRecorder) GetLDAPUserAttributes(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetLDAPUserAttributes\", reflect.TypeOf((*MockAPI)(nil).GetLDAPUserAttributes), arg0, arg1)\n}\n\n// GetLicense mocks base method.\nfunc (m *MockAPI) GetLicense() *model.License {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetLicense\")\n\tret0, _ := ret[0].(*model.License)\n\treturn ret0\n}\n\n// GetLicense indicates an expected call of GetLicense.\nfunc (mr *MockAPIMockRecorder) GetLicense() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetLicense\", reflect.TypeOf((*MockAPI)(nil).GetLicense))\n}\n\n// GetOAuthApp mocks base method.\nfunc (m *MockAPI) GetOAuthApp(arg0 string) (*model.OAuthApp, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetOAuthApp\", arg0)\n\tret0, _ := ret[0].(*model.OAuthApp)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetOAuthApp indicates an expected call of GetOAuthApp.\nfunc (mr *MockAPIMockRecorder) GetOAuthApp(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetOAuthApp\", reflect.TypeOf((*MockAPI)(nil).GetOAuthApp), arg0)\n}\n\n// GetPluginConfig mocks base method.\nfunc (m *MockAPI) GetPluginConfig() map[string]interface{} {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetPluginConfig\")\n\tret0, _ := ret[0].(map[string]interface{})\n\treturn ret0\n}\n\n// GetPluginConfig indicates an expected call of GetPluginConfig.\nfunc (mr *MockAPIMockRecorder) GetPluginConfig() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetPluginConfig\", reflect.TypeOf((*MockAPI)(nil).GetPluginConfig))\n}\n\n// GetPluginStatus mocks base method.\nfunc (m *MockAPI) GetPluginStatus(arg0 string) (*model.PluginStatus, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetPluginStatus\", arg0)\n\tret0, _ := ret[0].(*model.PluginStatus)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetPluginStatus indicates an expected call of GetPluginStatus.\nfunc (mr *MockAPIMockRecorder) GetPluginStatus(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetPluginStatus\", reflect.TypeOf((*MockAPI)(nil).GetPluginStatus), arg0)\n}\n\n// GetPlugins mocks base method.\nfunc (m *MockAPI) GetPlugins() ([]*model.Manifest, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetPlugins\")\n\tret0, _ := ret[0].([]*model.Manifest)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetPlugins indicates an expected call of GetPlugins.\nfunc (mr *MockAPIMockRecorder) GetPlugins() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetPlugins\", reflect.TypeOf((*MockAPI)(nil).GetPlugins))\n}\n\n// GetPost mocks base method.\nfunc (m *MockAPI) GetPost(arg0 string) (*model.Post, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetPost\", arg0)\n\tret0, _ := ret[0].(*model.Post)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetPost indicates an expected call of GetPost.\nfunc (mr *MockAPIMockRecorder) GetPost(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetPost\", reflect.TypeOf((*MockAPI)(nil).GetPost), arg0)\n}\n\n// GetPostThread mocks base method.\nfunc (m *MockAPI) GetPostThread(arg0 string) (*model.PostList, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetPostThread\", arg0)\n\tret0, _ := ret[0].(*model.PostList)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetPostThread indicates an expected call of GetPostThread.\nfunc (mr *MockAPIMockRecorder) GetPostThread(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetPostThread\", reflect.TypeOf((*MockAPI)(nil).GetPostThread), arg0)\n}\n\n// GetPostsAfter mocks base method.\nfunc (m *MockAPI) GetPostsAfter(arg0, arg1 string, arg2, arg3 int) (*model.PostList, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetPostsAfter\", arg0, arg1, arg2, arg3)\n\tret0, _ := ret[0].(*model.PostList)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetPostsAfter indicates an expected call of GetPostsAfter.\nfunc (mr *MockAPIMockRecorder) GetPostsAfter(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetPostsAfter\", reflect.TypeOf((*MockAPI)(nil).GetPostsAfter), arg0, arg1, arg2, arg3)\n}\n\n// GetPostsBefore mocks base method.\nfunc (m *MockAPI) GetPostsBefore(arg0, arg1 string, arg2, arg3 int) (*model.PostList, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetPostsBefore\", arg0, arg1, arg2, arg3)\n\tret0, _ := ret[0].(*model.PostList)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetPostsBefore indicates an expected call of GetPostsBefore.\nfunc (mr *MockAPIMockRecorder) GetPostsBefore(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetPostsBefore\", reflect.TypeOf((*MockAPI)(nil).GetPostsBefore), arg0, arg1, arg2, arg3)\n}\n\n// GetPostsForChannel mocks base method.\nfunc (m *MockAPI) GetPostsForChannel(arg0 string, arg1, arg2 int) (*model.PostList, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetPostsForChannel\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(*model.PostList)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetPostsForChannel indicates an expected call of GetPostsForChannel.\nfunc (mr *MockAPIMockRecorder) GetPostsForChannel(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetPostsForChannel\", reflect.TypeOf((*MockAPI)(nil).GetPostsForChannel), arg0, arg1, arg2)\n}\n\n// GetPostsSince mocks base method.\nfunc (m *MockAPI) GetPostsSince(arg0 string, arg1 int64) (*model.PostList, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetPostsSince\", arg0, arg1)\n\tret0, _ := ret[0].(*model.PostList)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetPostsSince indicates an expected call of GetPostsSince.\nfunc (mr *MockAPIMockRecorder) GetPostsSince(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetPostsSince\", reflect.TypeOf((*MockAPI)(nil).GetPostsSince), arg0, arg1)\n}\n\n// GetPreferencesForUser mocks base method.\nfunc (m *MockAPI) GetPreferencesForUser(arg0 string) ([]model.Preference, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetPreferencesForUser\", arg0)\n\tret0, _ := ret[0].([]model.Preference)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetPreferencesForUser indicates an expected call of GetPreferencesForUser.\nfunc (mr *MockAPIMockRecorder) GetPreferencesForUser(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetPreferencesForUser\", reflect.TypeOf((*MockAPI)(nil).GetPreferencesForUser), arg0)\n}\n\n// GetProfileImage mocks base method.\nfunc (m *MockAPI) GetProfileImage(arg0 string) ([]byte, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetProfileImage\", arg0)\n\tret0, _ := ret[0].([]byte)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetProfileImage indicates an expected call of GetProfileImage.\nfunc (mr *MockAPIMockRecorder) GetProfileImage(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetProfileImage\", reflect.TypeOf((*MockAPI)(nil).GetProfileImage), arg0)\n}\n\n// GetPublicChannelsForTeam mocks base method.\nfunc (m *MockAPI) GetPublicChannelsForTeam(arg0 string, arg1, arg2 int) ([]*model.Channel, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetPublicChannelsForTeam\", arg0, arg1, arg2)\n\tret0, _ := ret[0].([]*model.Channel)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetPublicChannelsForTeam indicates an expected call of GetPublicChannelsForTeam.\nfunc (mr *MockAPIMockRecorder) GetPublicChannelsForTeam(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetPublicChannelsForTeam\", reflect.TypeOf((*MockAPI)(nil).GetPublicChannelsForTeam), arg0, arg1, arg2)\n}\n\n// GetReactions mocks base method.\nfunc (m *MockAPI) GetReactions(arg0 string) ([]*model.Reaction, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetReactions\", arg0)\n\tret0, _ := ret[0].([]*model.Reaction)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetReactions indicates an expected call of GetReactions.\nfunc (mr *MockAPIMockRecorder) GetReactions(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetReactions\", reflect.TypeOf((*MockAPI)(nil).GetReactions), arg0)\n}\n\n// GetServerVersion mocks base method.\nfunc (m *MockAPI) GetServerVersion() string {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetServerVersion\")\n\tret0, _ := ret[0].(string)\n\treturn ret0\n}\n\n// GetServerVersion indicates an expected call of GetServerVersion.\nfunc (mr *MockAPIMockRecorder) GetServerVersion() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetServerVersion\", reflect.TypeOf((*MockAPI)(nil).GetServerVersion))\n}\n\n// GetSession mocks base method.\nfunc (m *MockAPI) GetSession(arg0 string) (*model.Session, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetSession\", arg0)\n\tret0, _ := ret[0].(*model.Session)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetSession indicates an expected call of GetSession.\nfunc (mr *MockAPIMockRecorder) GetSession(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetSession\", reflect.TypeOf((*MockAPI)(nil).GetSession), arg0)\n}\n\n// GetSystemInstallDate mocks base method.\nfunc (m *MockAPI) GetSystemInstallDate() (int64, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetSystemInstallDate\")\n\tret0, _ := ret[0].(int64)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetSystemInstallDate indicates an expected call of GetSystemInstallDate.\nfunc (mr *MockAPIMockRecorder) GetSystemInstallDate() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetSystemInstallDate\", reflect.TypeOf((*MockAPI)(nil).GetSystemInstallDate))\n}\n\n// GetTeam mocks base method.\nfunc (m *MockAPI) GetTeam(arg0 string) (*model.Team, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetTeam\", arg0)\n\tret0, _ := ret[0].(*model.Team)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetTeam indicates an expected call of GetTeam.\nfunc (mr *MockAPIMockRecorder) GetTeam(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetTeam\", reflect.TypeOf((*MockAPI)(nil).GetTeam), arg0)\n}\n\n// GetTeamByName mocks base method.\nfunc (m *MockAPI) GetTeamByName(arg0 string) (*model.Team, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetTeamByName\", arg0)\n\tret0, _ := ret[0].(*model.Team)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetTeamByName indicates an expected call of GetTeamByName.\nfunc (mr *MockAPIMockRecorder) GetTeamByName(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetTeamByName\", reflect.TypeOf((*MockAPI)(nil).GetTeamByName), arg0)\n}\n\n// GetTeamIcon mocks base method.\nfunc (m *MockAPI) GetTeamIcon(arg0 string) ([]byte, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetTeamIcon\", arg0)\n\tret0, _ := ret[0].([]byte)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetTeamIcon indicates an expected call of GetTeamIcon.\nfunc (mr *MockAPIMockRecorder) GetTeamIcon(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetTeamIcon\", reflect.TypeOf((*MockAPI)(nil).GetTeamIcon), arg0)\n}\n\n// GetTeamMember mocks base method.\nfunc (m *MockAPI) GetTeamMember(arg0, arg1 string) (*model.TeamMember, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetTeamMember\", arg0, arg1)\n\tret0, _ := ret[0].(*model.TeamMember)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetTeamMember indicates an expected call of GetTeamMember.\nfunc (mr *MockAPIMockRecorder) GetTeamMember(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetTeamMember\", reflect.TypeOf((*MockAPI)(nil).GetTeamMember), arg0, arg1)\n}\n\n// GetTeamMembers mocks base method.\nfunc (m *MockAPI) GetTeamMembers(arg0 string, arg1, arg2 int) ([]*model.TeamMember, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetTeamMembers\", arg0, arg1, arg2)\n\tret0, _ := ret[0].([]*model.TeamMember)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetTeamMembers indicates an expected call of GetTeamMembers.\nfunc (mr *MockAPIMockRecorder) GetTeamMembers(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetTeamMembers\", reflect.TypeOf((*MockAPI)(nil).GetTeamMembers), arg0, arg1, arg2)\n}\n\n// GetTeamMembersForUser mocks base method.\nfunc (m *MockAPI) GetTeamMembersForUser(arg0 string, arg1, arg2 int) ([]*model.TeamMember, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetTeamMembersForUser\", arg0, arg1, arg2)\n\tret0, _ := ret[0].([]*model.TeamMember)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetTeamMembersForUser indicates an expected call of GetTeamMembersForUser.\nfunc (mr *MockAPIMockRecorder) GetTeamMembersForUser(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetTeamMembersForUser\", reflect.TypeOf((*MockAPI)(nil).GetTeamMembersForUser), arg0, arg1, arg2)\n}\n\n// GetTeamStats mocks base method.\nfunc (m *MockAPI) GetTeamStats(arg0 string) (*model.TeamStats, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetTeamStats\", arg0)\n\tret0, _ := ret[0].(*model.TeamStats)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetTeamStats indicates an expected call of GetTeamStats.\nfunc (mr *MockAPIMockRecorder) GetTeamStats(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetTeamStats\", reflect.TypeOf((*MockAPI)(nil).GetTeamStats), arg0)\n}\n\n// GetTeams mocks base method.\nfunc (m *MockAPI) GetTeams() ([]*model.Team, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetTeams\")\n\tret0, _ := ret[0].([]*model.Team)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetTeams indicates an expected call of GetTeams.\nfunc (mr *MockAPIMockRecorder) GetTeams() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetTeams\", reflect.TypeOf((*MockAPI)(nil).GetTeams))\n}\n\n// GetTeamsForUser mocks base method.\nfunc (m *MockAPI) GetTeamsForUser(arg0 string) ([]*model.Team, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetTeamsForUser\", arg0)\n\tret0, _ := ret[0].([]*model.Team)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetTeamsForUser indicates an expected call of GetTeamsForUser.\nfunc (mr *MockAPIMockRecorder) GetTeamsForUser(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetTeamsForUser\", reflect.TypeOf((*MockAPI)(nil).GetTeamsForUser), arg0)\n}\n\n// GetTeamsUnreadForUser mocks base method.\nfunc (m *MockAPI) GetTeamsUnreadForUser(arg0 string) ([]*model.TeamUnread, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetTeamsUnreadForUser\", arg0)\n\tret0, _ := ret[0].([]*model.TeamUnread)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetTeamsUnreadForUser indicates an expected call of GetTeamsUnreadForUser.\nfunc (mr *MockAPIMockRecorder) GetTeamsUnreadForUser(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetTeamsUnreadForUser\", reflect.TypeOf((*MockAPI)(nil).GetTeamsUnreadForUser), arg0)\n}\n\n// GetTelemetryId mocks base method.\nfunc (m *MockAPI) GetTelemetryId() string {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetTelemetryId\")\n\tret0, _ := ret[0].(string)\n\treturn ret0\n}\n\n// GetTelemetryId indicates an expected call of GetTelemetryId.\nfunc (mr *MockAPIMockRecorder) GetTelemetryId() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetTelemetryId\", reflect.TypeOf((*MockAPI)(nil).GetTelemetryId))\n}\n\n// GetUnsanitizedConfig mocks base method.\nfunc (m *MockAPI) GetUnsanitizedConfig() *model.Config {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetUnsanitizedConfig\")\n\tret0, _ := ret[0].(*model.Config)\n\treturn ret0\n}\n\n// GetUnsanitizedConfig indicates an expected call of GetUnsanitizedConfig.\nfunc (mr *MockAPIMockRecorder) GetUnsanitizedConfig() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetUnsanitizedConfig\", reflect.TypeOf((*MockAPI)(nil).GetUnsanitizedConfig))\n}\n\n// GetUploadSession mocks base method.\nfunc (m *MockAPI) GetUploadSession(arg0 string) (*model.UploadSession, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetUploadSession\", arg0)\n\tret0, _ := ret[0].(*model.UploadSession)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetUploadSession indicates an expected call of GetUploadSession.\nfunc (mr *MockAPIMockRecorder) GetUploadSession(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetUploadSession\", reflect.TypeOf((*MockAPI)(nil).GetUploadSession), arg0)\n}\n\n// GetUser mocks base method.\nfunc (m *MockAPI) GetUser(arg0 string) (*model.User, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetUser\", arg0)\n\tret0, _ := ret[0].(*model.User)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetUser indicates an expected call of GetUser.\nfunc (mr *MockAPIMockRecorder) GetUser(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetUser\", reflect.TypeOf((*MockAPI)(nil).GetUser), arg0)\n}\n\n// GetUserByEmail mocks base method.\nfunc (m *MockAPI) GetUserByEmail(arg0 string) (*model.User, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetUserByEmail\", arg0)\n\tret0, _ := ret[0].(*model.User)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetUserByEmail indicates an expected call of GetUserByEmail.\nfunc (mr *MockAPIMockRecorder) GetUserByEmail(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetUserByEmail\", reflect.TypeOf((*MockAPI)(nil).GetUserByEmail), arg0)\n}\n\n// GetUserByUsername mocks base method.\nfunc (m *MockAPI) GetUserByUsername(arg0 string) (*model.User, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetUserByUsername\", arg0)\n\tret0, _ := ret[0].(*model.User)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetUserByUsername indicates an expected call of GetUserByUsername.\nfunc (mr *MockAPIMockRecorder) GetUserByUsername(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetUserByUsername\", reflect.TypeOf((*MockAPI)(nil).GetUserByUsername), arg0)\n}\n\n// GetUserStatus mocks base method.\nfunc (m *MockAPI) GetUserStatus(arg0 string) (*model.Status, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetUserStatus\", arg0)\n\tret0, _ := ret[0].(*model.Status)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetUserStatus indicates an expected call of GetUserStatus.\nfunc (mr *MockAPIMockRecorder) GetUserStatus(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetUserStatus\", reflect.TypeOf((*MockAPI)(nil).GetUserStatus), arg0)\n}\n\n// GetUserStatusesByIds mocks base method.\nfunc (m *MockAPI) GetUserStatusesByIds(arg0 []string) ([]*model.Status, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetUserStatusesByIds\", arg0)\n\tret0, _ := ret[0].([]*model.Status)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetUserStatusesByIds indicates an expected call of GetUserStatusesByIds.\nfunc (mr *MockAPIMockRecorder) GetUserStatusesByIds(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetUserStatusesByIds\", reflect.TypeOf((*MockAPI)(nil).GetUserStatusesByIds), arg0)\n}\n\n// GetUsers mocks base method.\nfunc (m *MockAPI) GetUsers(arg0 *model.UserGetOptions) ([]*model.User, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetUsers\", arg0)\n\tret0, _ := ret[0].([]*model.User)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetUsers indicates an expected call of GetUsers.\nfunc (mr *MockAPIMockRecorder) GetUsers(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetUsers\", reflect.TypeOf((*MockAPI)(nil).GetUsers), arg0)\n}\n\n// GetUsersByUsernames mocks base method.\nfunc (m *MockAPI) GetUsersByUsernames(arg0 []string) ([]*model.User, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetUsersByUsernames\", arg0)\n\tret0, _ := ret[0].([]*model.User)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetUsersByUsernames indicates an expected call of GetUsersByUsernames.\nfunc (mr *MockAPIMockRecorder) GetUsersByUsernames(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetUsersByUsernames\", reflect.TypeOf((*MockAPI)(nil).GetUsersByUsernames), arg0)\n}\n\n// GetUsersInChannel mocks base method.\nfunc (m *MockAPI) GetUsersInChannel(arg0, arg1 string, arg2, arg3 int) ([]*model.User, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetUsersInChannel\", arg0, arg1, arg2, arg3)\n\tret0, _ := ret[0].([]*model.User)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetUsersInChannel indicates an expected call of GetUsersInChannel.\nfunc (mr *MockAPIMockRecorder) GetUsersInChannel(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetUsersInChannel\", reflect.TypeOf((*MockAPI)(nil).GetUsersInChannel), arg0, arg1, arg2, arg3)\n}\n\n// GetUsersInTeam mocks base method.\nfunc (m *MockAPI) GetUsersInTeam(arg0 string, arg1, arg2 int) ([]*model.User, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetUsersInTeam\", arg0, arg1, arg2)\n\tret0, _ := ret[0].([]*model.User)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetUsersInTeam indicates an expected call of GetUsersInTeam.\nfunc (mr *MockAPIMockRecorder) GetUsersInTeam(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetUsersInTeam\", reflect.TypeOf((*MockAPI)(nil).GetUsersInTeam), arg0, arg1, arg2)\n}\n\n// HasPermissionTo mocks base method.\nfunc (m *MockAPI) HasPermissionTo(arg0 string, arg1 *model.Permission) bool {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"HasPermissionTo\", arg0, arg1)\n\tret0, _ := ret[0].(bool)\n\treturn ret0\n}\n\n// HasPermissionTo indicates an expected call of HasPermissionTo.\nfunc (mr *MockAPIMockRecorder) HasPermissionTo(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"HasPermissionTo\", reflect.TypeOf((*MockAPI)(nil).HasPermissionTo), arg0, arg1)\n}\n\n// HasPermissionToChannel mocks base method.\nfunc (m *MockAPI) HasPermissionToChannel(arg0, arg1 string, arg2 *model.Permission) bool {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"HasPermissionToChannel\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(bool)\n\treturn ret0\n}\n\n// HasPermissionToChannel indicates an expected call of HasPermissionToChannel.\nfunc (mr *MockAPIMockRecorder) HasPermissionToChannel(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"HasPermissionToChannel\", reflect.TypeOf((*MockAPI)(nil).HasPermissionToChannel), arg0, arg1, arg2)\n}\n\n// HasPermissionToTeam mocks base method.\nfunc (m *MockAPI) HasPermissionToTeam(arg0, arg1 string, arg2 *model.Permission) bool {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"HasPermissionToTeam\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(bool)\n\treturn ret0\n}\n\n// HasPermissionToTeam indicates an expected call of HasPermissionToTeam.\nfunc (mr *MockAPIMockRecorder) HasPermissionToTeam(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"HasPermissionToTeam\", reflect.TypeOf((*MockAPI)(nil).HasPermissionToTeam), arg0, arg1, arg2)\n}\n\n// InstallPlugin mocks base method.\nfunc (m *MockAPI) InstallPlugin(arg0 io.Reader, arg1 bool) (*model.Manifest, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"InstallPlugin\", arg0, arg1)\n\tret0, _ := ret[0].(*model.Manifest)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// InstallPlugin indicates an expected call of InstallPlugin.\nfunc (mr *MockAPIMockRecorder) InstallPlugin(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"InstallPlugin\", reflect.TypeOf((*MockAPI)(nil).InstallPlugin), arg0, arg1)\n}\n\n// IsEnterpriseReady mocks base method.\nfunc (m *MockAPI) IsEnterpriseReady() bool {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"IsEnterpriseReady\")\n\tret0, _ := ret[0].(bool)\n\treturn ret0\n}\n\n// IsEnterpriseReady indicates an expected call of IsEnterpriseReady.\nfunc (mr *MockAPIMockRecorder) IsEnterpriseReady() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"IsEnterpriseReady\", reflect.TypeOf((*MockAPI)(nil).IsEnterpriseReady))\n}\n\n// KVCompareAndDelete mocks base method.\nfunc (m *MockAPI) KVCompareAndDelete(arg0 string, arg1 []byte) (bool, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"KVCompareAndDelete\", arg0, arg1)\n\tret0, _ := ret[0].(bool)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// KVCompareAndDelete indicates an expected call of KVCompareAndDelete.\nfunc (mr *MockAPIMockRecorder) KVCompareAndDelete(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"KVCompareAndDelete\", reflect.TypeOf((*MockAPI)(nil).KVCompareAndDelete), arg0, arg1)\n}\n\n// KVCompareAndSet mocks base method.\nfunc (m *MockAPI) KVCompareAndSet(arg0 string, arg1, arg2 []byte) (bool, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"KVCompareAndSet\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(bool)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// KVCompareAndSet indicates an expected call of KVCompareAndSet.\nfunc (mr *MockAPIMockRecorder) KVCompareAndSet(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"KVCompareAndSet\", reflect.TypeOf((*MockAPI)(nil).KVCompareAndSet), arg0, arg1, arg2)\n}\n\n// KVDelete mocks base method.\nfunc (m *MockAPI) KVDelete(arg0 string) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"KVDelete\", arg0)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// KVDelete indicates an expected call of KVDelete.\nfunc (mr *MockAPIMockRecorder) KVDelete(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"KVDelete\", reflect.TypeOf((*MockAPI)(nil).KVDelete), arg0)\n}\n\n// KVDeleteAll mocks base method.\nfunc (m *MockAPI) KVDeleteAll() *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"KVDeleteAll\")\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// KVDeleteAll indicates an expected call of KVDeleteAll.\nfunc (mr *MockAPIMockRecorder) KVDeleteAll() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"KVDeleteAll\", reflect.TypeOf((*MockAPI)(nil).KVDeleteAll))\n}\n\n// KVGet mocks base method.\nfunc (m *MockAPI) KVGet(arg0 string) ([]byte, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"KVGet\", arg0)\n\tret0, _ := ret[0].([]byte)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// KVGet indicates an expected call of KVGet.\nfunc (mr *MockAPIMockRecorder) KVGet(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"KVGet\", reflect.TypeOf((*MockAPI)(nil).KVGet), arg0)\n}\n\n// KVList mocks base method.\nfunc (m *MockAPI) KVList(arg0, arg1 int) ([]string, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"KVList\", arg0, arg1)\n\tret0, _ := ret[0].([]string)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// KVList indicates an expected call of KVList.\nfunc (mr *MockAPIMockRecorder) KVList(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"KVList\", reflect.TypeOf((*MockAPI)(nil).KVList), arg0, arg1)\n}\n\n// KVSet mocks base method.\nfunc (m *MockAPI) KVSet(arg0 string, arg1 []byte) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"KVSet\", arg0, arg1)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// KVSet indicates an expected call of KVSet.\nfunc (mr *MockAPIMockRecorder) KVSet(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"KVSet\", reflect.TypeOf((*MockAPI)(nil).KVSet), arg0, arg1)\n}\n\n// KVSetWithExpiry mocks base method.\nfunc (m *MockAPI) KVSetWithExpiry(arg0 string, arg1 []byte, arg2 int64) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"KVSetWithExpiry\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// KVSetWithExpiry indicates an expected call of KVSetWithExpiry.\nfunc (mr *MockAPIMockRecorder) KVSetWithExpiry(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"KVSetWithExpiry\", reflect.TypeOf((*MockAPI)(nil).KVSetWithExpiry), arg0, arg1, arg2)\n}\n\n// KVSetWithOptions mocks base method.\nfunc (m *MockAPI) KVSetWithOptions(arg0 string, arg1 []byte, arg2 model.PluginKVSetOptions) (bool, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"KVSetWithOptions\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(bool)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// KVSetWithOptions indicates an expected call of KVSetWithOptions.\nfunc (mr *MockAPIMockRecorder) KVSetWithOptions(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"KVSetWithOptions\", reflect.TypeOf((*MockAPI)(nil).KVSetWithOptions), arg0, arg1, arg2)\n}\n\n// ListBuiltInCommands mocks base method.\nfunc (m *MockAPI) ListBuiltInCommands() ([]*model.Command, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ListBuiltInCommands\")\n\tret0, _ := ret[0].([]*model.Command)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ListBuiltInCommands indicates an expected call of ListBuiltInCommands.\nfunc (mr *MockAPIMockRecorder) ListBuiltInCommands() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ListBuiltInCommands\", reflect.TypeOf((*MockAPI)(nil).ListBuiltInCommands))\n}\n\n// ListCommands mocks base method.\nfunc (m *MockAPI) ListCommands(arg0 string) ([]*model.Command, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ListCommands\", arg0)\n\tret0, _ := ret[0].([]*model.Command)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ListCommands indicates an expected call of ListCommands.\nfunc (mr *MockAPIMockRecorder) ListCommands(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ListCommands\", reflect.TypeOf((*MockAPI)(nil).ListCommands), arg0)\n}\n\n// ListCustomCommands mocks base method.\nfunc (m *MockAPI) ListCustomCommands(arg0 string) ([]*model.Command, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ListCustomCommands\", arg0)\n\tret0, _ := ret[0].([]*model.Command)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ListCustomCommands indicates an expected call of ListCustomCommands.\nfunc (mr *MockAPIMockRecorder) ListCustomCommands(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ListCustomCommands\", reflect.TypeOf((*MockAPI)(nil).ListCustomCommands), arg0)\n}\n\n// ListPluginCommands mocks base method.\nfunc (m *MockAPI) ListPluginCommands(arg0 string) ([]*model.Command, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ListPluginCommands\", arg0)\n\tret0, _ := ret[0].([]*model.Command)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ListPluginCommands indicates an expected call of ListPluginCommands.\nfunc (mr *MockAPIMockRecorder) ListPluginCommands(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ListPluginCommands\", reflect.TypeOf((*MockAPI)(nil).ListPluginCommands), arg0)\n}\n\n// LoadPluginConfiguration mocks base method.\nfunc (m *MockAPI) LoadPluginConfiguration(arg0 interface{}) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"LoadPluginConfiguration\", arg0)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// LoadPluginConfiguration indicates an expected call of LoadPluginConfiguration.\nfunc (mr *MockAPIMockRecorder) LoadPluginConfiguration(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"LoadPluginConfiguration\", reflect.TypeOf((*MockAPI)(nil).LoadPluginConfiguration), arg0)\n}\n\n// LogDebug mocks base method.\nfunc (m *MockAPI) LogDebug(arg0 string, arg1 ...interface{}) {\n\tm.ctrl.T.Helper()\n\tvarargs := []interface{}{arg0}\n\tfor _, a := range arg1 {\n\t\tvarargs = append(varargs, a)\n\t}\n\tm.ctrl.Call(m, \"LogDebug\", varargs...)\n}\n\n// LogDebug indicates an expected call of LogDebug.\nfunc (mr *MockAPIMockRecorder) LogDebug(arg0 interface{}, arg1 ...interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\tvarargs := append([]interface{}{arg0}, arg1...)\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"LogDebug\", reflect.TypeOf((*MockAPI)(nil).LogDebug), varargs...)\n}\n\n// LogError mocks base method.\nfunc (m *MockAPI) LogError(arg0 string, arg1 ...interface{}) {\n\tm.ctrl.T.Helper()\n\tvarargs := []interface{}{arg0}\n\tfor _, a := range arg1 {\n\t\tvarargs = append(varargs, a)\n\t}\n\tm.ctrl.Call(m, \"LogError\", varargs...)\n}\n\n// LogError indicates an expected call of LogError.\nfunc (mr *MockAPIMockRecorder) LogError(arg0 interface{}, arg1 ...interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\tvarargs := append([]interface{}{arg0}, arg1...)\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"LogError\", reflect.TypeOf((*MockAPI)(nil).LogError), varargs...)\n}\n\n// LogInfo mocks base method.\nfunc (m *MockAPI) LogInfo(arg0 string, arg1 ...interface{}) {\n\tm.ctrl.T.Helper()\n\tvarargs := []interface{}{arg0}\n\tfor _, a := range arg1 {\n\t\tvarargs = append(varargs, a)\n\t}\n\tm.ctrl.Call(m, \"LogInfo\", varargs...)\n}\n\n// LogInfo indicates an expected call of LogInfo.\nfunc (mr *MockAPIMockRecorder) LogInfo(arg0 interface{}, arg1 ...interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\tvarargs := append([]interface{}{arg0}, arg1...)\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"LogInfo\", reflect.TypeOf((*MockAPI)(nil).LogInfo), varargs...)\n}\n\n// LogWarn mocks base method.\nfunc (m *MockAPI) LogWarn(arg0 string, arg1 ...interface{}) {\n\tm.ctrl.T.Helper()\n\tvarargs := []interface{}{arg0}\n\tfor _, a := range arg1 {\n\t\tvarargs = append(varargs, a)\n\t}\n\tm.ctrl.Call(m, \"LogWarn\", varargs...)\n}\n\n// LogWarn indicates an expected call of LogWarn.\nfunc (mr *MockAPIMockRecorder) LogWarn(arg0 interface{}, arg1 ...interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\tvarargs := append([]interface{}{arg0}, arg1...)\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"LogWarn\", reflect.TypeOf((*MockAPI)(nil).LogWarn), varargs...)\n}\n\n// OpenInteractiveDialog mocks base method.\nfunc (m *MockAPI) OpenInteractiveDialog(arg0 model.OpenDialogRequest) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"OpenInteractiveDialog\", arg0)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// OpenInteractiveDialog indicates an expected call of OpenInteractiveDialog.\nfunc (mr *MockAPIMockRecorder) OpenInteractiveDialog(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"OpenInteractiveDialog\", reflect.TypeOf((*MockAPI)(nil).OpenInteractiveDialog), arg0)\n}\n\n// PatchBot mocks base method.\nfunc (m *MockAPI) PatchBot(arg0 string, arg1 *model.BotPatch) (*model.Bot, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"PatchBot\", arg0, arg1)\n\tret0, _ := ret[0].(*model.Bot)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// PatchBot indicates an expected call of PatchBot.\nfunc (mr *MockAPIMockRecorder) PatchBot(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"PatchBot\", reflect.TypeOf((*MockAPI)(nil).PatchBot), arg0, arg1)\n}\n\n// PermanentDeleteBot mocks base method.\nfunc (m *MockAPI) PermanentDeleteBot(arg0 string) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"PermanentDeleteBot\", arg0)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// PermanentDeleteBot indicates an expected call of PermanentDeleteBot.\nfunc (mr *MockAPIMockRecorder) PermanentDeleteBot(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"PermanentDeleteBot\", reflect.TypeOf((*MockAPI)(nil).PermanentDeleteBot), arg0)\n}\n\n// PluginHTTP mocks base method.\nfunc (m *MockAPI) PluginHTTP(arg0 *http.Request) *http.Response {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"PluginHTTP\", arg0)\n\tret0, _ := ret[0].(*http.Response)\n\treturn ret0\n}\n\n// PluginHTTP indicates an expected call of PluginHTTP.\nfunc (mr *MockAPIMockRecorder) PluginHTTP(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"PluginHTTP\", reflect.TypeOf((*MockAPI)(nil).PluginHTTP), arg0)\n}\n\n// PublishPluginClusterEvent mocks base method.\nfunc (m *MockAPI) PublishPluginClusterEvent(arg0 model.PluginClusterEvent, arg1 model.PluginClusterEventSendOptions) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"PublishPluginClusterEvent\", arg0, arg1)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// PublishPluginClusterEvent indicates an expected call of PublishPluginClusterEvent.\nfunc (mr *MockAPIMockRecorder) PublishPluginClusterEvent(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"PublishPluginClusterEvent\", reflect.TypeOf((*MockAPI)(nil).PublishPluginClusterEvent), arg0, arg1)\n}\n\n// PublishUserTyping mocks base method.\nfunc (m *MockAPI) PublishUserTyping(arg0, arg1, arg2 string) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"PublishUserTyping\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// PublishUserTyping indicates an expected call of PublishUserTyping.\nfunc (mr *MockAPIMockRecorder) PublishUserTyping(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"PublishUserTyping\", reflect.TypeOf((*MockAPI)(nil).PublishUserTyping), arg0, arg1, arg2)\n}\n\n// PublishWebSocketEvent mocks base method.\nfunc (m *MockAPI) PublishWebSocketEvent(arg0 string, arg1 map[string]interface{}, arg2 *model.WebsocketBroadcast) {\n\tm.ctrl.T.Helper()\n\tm.ctrl.Call(m, \"PublishWebSocketEvent\", arg0, arg1, arg2)\n}\n\n// PublishWebSocketEvent indicates an expected call of PublishWebSocketEvent.\nfunc (mr *MockAPIMockRecorder) PublishWebSocketEvent(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"PublishWebSocketEvent\", reflect.TypeOf((*MockAPI)(nil).PublishWebSocketEvent), arg0, arg1, arg2)\n}\n\n// ReadFile mocks base method.\nfunc (m *MockAPI) ReadFile(arg0 string) ([]byte, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ReadFile\", arg0)\n\tret0, _ := ret[0].([]byte)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// ReadFile indicates an expected call of ReadFile.\nfunc (mr *MockAPIMockRecorder) ReadFile(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ReadFile\", reflect.TypeOf((*MockAPI)(nil).ReadFile), arg0)\n}\n\n// RegisterCollectionAndTopic mocks base method.\nfunc (m *MockAPI) RegisterCollectionAndTopic(arg0, arg1 string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"RegisterCollectionAndTopic\", arg0, arg1)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// RegisterCollectionAndTopic indicates an expected call of RegisterCollectionAndTopic.\nfunc (mr *MockAPIMockRecorder) RegisterCollectionAndTopic(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"RegisterCollectionAndTopic\", reflect.TypeOf((*MockAPI)(nil).RegisterCollectionAndTopic), arg0, arg1)\n}\n\n// RegisterCommand mocks base method.\nfunc (m *MockAPI) RegisterCommand(arg0 *model.Command) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"RegisterCommand\", arg0)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// RegisterCommand indicates an expected call of RegisterCommand.\nfunc (mr *MockAPIMockRecorder) RegisterCommand(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"RegisterCommand\", reflect.TypeOf((*MockAPI)(nil).RegisterCommand), arg0)\n}\n\n// RemovePlugin mocks base method.\nfunc (m *MockAPI) RemovePlugin(arg0 string) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"RemovePlugin\", arg0)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// RemovePlugin indicates an expected call of RemovePlugin.\nfunc (mr *MockAPIMockRecorder) RemovePlugin(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"RemovePlugin\", reflect.TypeOf((*MockAPI)(nil).RemovePlugin), arg0)\n}\n\n// RemoveReaction mocks base method.\nfunc (m *MockAPI) RemoveReaction(arg0 *model.Reaction) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"RemoveReaction\", arg0)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// RemoveReaction indicates an expected call of RemoveReaction.\nfunc (mr *MockAPIMockRecorder) RemoveReaction(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"RemoveReaction\", reflect.TypeOf((*MockAPI)(nil).RemoveReaction), arg0)\n}\n\n// RemoveTeamIcon mocks base method.\nfunc (m *MockAPI) RemoveTeamIcon(arg0 string) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"RemoveTeamIcon\", arg0)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// RemoveTeamIcon indicates an expected call of RemoveTeamIcon.\nfunc (mr *MockAPIMockRecorder) RemoveTeamIcon(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"RemoveTeamIcon\", reflect.TypeOf((*MockAPI)(nil).RemoveTeamIcon), arg0)\n}\n\n// RemoveUserCustomStatus mocks base method.\nfunc (m *MockAPI) RemoveUserCustomStatus(arg0 string) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"RemoveUserCustomStatus\", arg0)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// RemoveUserCustomStatus indicates an expected call of RemoveUserCustomStatus.\nfunc (mr *MockAPIMockRecorder) RemoveUserCustomStatus(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"RemoveUserCustomStatus\", reflect.TypeOf((*MockAPI)(nil).RemoveUserCustomStatus), arg0)\n}\n\n// RequestTrialLicense mocks base method.\nfunc (m *MockAPI) RequestTrialLicense(arg0 string, arg1 int, arg2, arg3 bool) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"RequestTrialLicense\", arg0, arg1, arg2, arg3)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// RequestTrialLicense indicates an expected call of RequestTrialLicense.\nfunc (mr *MockAPIMockRecorder) RequestTrialLicense(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"RequestTrialLicense\", reflect.TypeOf((*MockAPI)(nil).RequestTrialLicense), arg0, arg1, arg2, arg3)\n}\n\n// RevokeSession mocks base method.\nfunc (m *MockAPI) RevokeSession(arg0 string) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"RevokeSession\", arg0)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// RevokeSession indicates an expected call of RevokeSession.\nfunc (mr *MockAPIMockRecorder) RevokeSession(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"RevokeSession\", reflect.TypeOf((*MockAPI)(nil).RevokeSession), arg0)\n}\n\n// RevokeUserAccessToken mocks base method.\nfunc (m *MockAPI) RevokeUserAccessToken(arg0 string) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"RevokeUserAccessToken\", arg0)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// RevokeUserAccessToken indicates an expected call of RevokeUserAccessToken.\nfunc (mr *MockAPIMockRecorder) RevokeUserAccessToken(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"RevokeUserAccessToken\", reflect.TypeOf((*MockAPI)(nil).RevokeUserAccessToken), arg0)\n}\n\n// RolesGrantPermission mocks base method.\nfunc (m *MockAPI) RolesGrantPermission(arg0 []string, arg1 string) bool {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"RolesGrantPermission\", arg0, arg1)\n\tret0, _ := ret[0].(bool)\n\treturn ret0\n}\n\n// RolesGrantPermission indicates an expected call of RolesGrantPermission.\nfunc (mr *MockAPIMockRecorder) RolesGrantPermission(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"RolesGrantPermission\", reflect.TypeOf((*MockAPI)(nil).RolesGrantPermission), arg0, arg1)\n}\n\n// SaveConfig mocks base method.\nfunc (m *MockAPI) SaveConfig(arg0 *model.Config) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"SaveConfig\", arg0)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// SaveConfig indicates an expected call of SaveConfig.\nfunc (mr *MockAPIMockRecorder) SaveConfig(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SaveConfig\", reflect.TypeOf((*MockAPI)(nil).SaveConfig), arg0)\n}\n\n// SavePluginConfig mocks base method.\nfunc (m *MockAPI) SavePluginConfig(arg0 map[string]interface{}) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"SavePluginConfig\", arg0)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// SavePluginConfig indicates an expected call of SavePluginConfig.\nfunc (mr *MockAPIMockRecorder) SavePluginConfig(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SavePluginConfig\", reflect.TypeOf((*MockAPI)(nil).SavePluginConfig), arg0)\n}\n\n// SearchChannels mocks base method.\nfunc (m *MockAPI) SearchChannels(arg0, arg1 string) ([]*model.Channel, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"SearchChannels\", arg0, arg1)\n\tret0, _ := ret[0].([]*model.Channel)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// SearchChannels indicates an expected call of SearchChannels.\nfunc (mr *MockAPIMockRecorder) SearchChannels(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SearchChannels\", reflect.TypeOf((*MockAPI)(nil).SearchChannels), arg0, arg1)\n}\n\n// SearchPostsInTeam mocks base method.\nfunc (m *MockAPI) SearchPostsInTeam(arg0 string, arg1 []*model.SearchParams) ([]*model.Post, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"SearchPostsInTeam\", arg0, arg1)\n\tret0, _ := ret[0].([]*model.Post)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// SearchPostsInTeam indicates an expected call of SearchPostsInTeam.\nfunc (mr *MockAPIMockRecorder) SearchPostsInTeam(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SearchPostsInTeam\", reflect.TypeOf((*MockAPI)(nil).SearchPostsInTeam), arg0, arg1)\n}\n\n// SearchPostsInTeamForUser mocks base method.\nfunc (m *MockAPI) SearchPostsInTeamForUser(arg0, arg1 string, arg2 model.SearchParameter) (*model.PostSearchResults, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"SearchPostsInTeamForUser\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(*model.PostSearchResults)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// SearchPostsInTeamForUser indicates an expected call of SearchPostsInTeamForUser.\nfunc (mr *MockAPIMockRecorder) SearchPostsInTeamForUser(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SearchPostsInTeamForUser\", reflect.TypeOf((*MockAPI)(nil).SearchPostsInTeamForUser), arg0, arg1, arg2)\n}\n\n// SearchTeams mocks base method.\nfunc (m *MockAPI) SearchTeams(arg0 string) ([]*model.Team, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"SearchTeams\", arg0)\n\tret0, _ := ret[0].([]*model.Team)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// SearchTeams indicates an expected call of SearchTeams.\nfunc (mr *MockAPIMockRecorder) SearchTeams(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SearchTeams\", reflect.TypeOf((*MockAPI)(nil).SearchTeams), arg0)\n}\n\n// SearchUsers mocks base method.\nfunc (m *MockAPI) SearchUsers(arg0 *model.UserSearch) ([]*model.User, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"SearchUsers\", arg0)\n\tret0, _ := ret[0].([]*model.User)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// SearchUsers indicates an expected call of SearchUsers.\nfunc (mr *MockAPIMockRecorder) SearchUsers(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SearchUsers\", reflect.TypeOf((*MockAPI)(nil).SearchUsers), arg0)\n}\n\n// SendEphemeralPost mocks base method.\nfunc (m *MockAPI) SendEphemeralPost(arg0 string, arg1 *model.Post) *model.Post {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"SendEphemeralPost\", arg0, arg1)\n\tret0, _ := ret[0].(*model.Post)\n\treturn ret0\n}\n\n// SendEphemeralPost indicates an expected call of SendEphemeralPost.\nfunc (mr *MockAPIMockRecorder) SendEphemeralPost(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SendEphemeralPost\", reflect.TypeOf((*MockAPI)(nil).SendEphemeralPost), arg0, arg1)\n}\n\n// SendMail mocks base method.\nfunc (m *MockAPI) SendMail(arg0, arg1, arg2 string) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"SendMail\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// SendMail indicates an expected call of SendMail.\nfunc (mr *MockAPIMockRecorder) SendMail(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SendMail\", reflect.TypeOf((*MockAPI)(nil).SendMail), arg0, arg1, arg2)\n}\n\n// SetProfileImage mocks base method.\nfunc (m *MockAPI) SetProfileImage(arg0 string, arg1 []byte) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"SetProfileImage\", arg0, arg1)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// SetProfileImage indicates an expected call of SetProfileImage.\nfunc (mr *MockAPIMockRecorder) SetProfileImage(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SetProfileImage\", reflect.TypeOf((*MockAPI)(nil).SetProfileImage), arg0, arg1)\n}\n\n// SetTeamIcon mocks base method.\nfunc (m *MockAPI) SetTeamIcon(arg0 string, arg1 []byte) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"SetTeamIcon\", arg0, arg1)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// SetTeamIcon indicates an expected call of SetTeamIcon.\nfunc (mr *MockAPIMockRecorder) SetTeamIcon(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SetTeamIcon\", reflect.TypeOf((*MockAPI)(nil).SetTeamIcon), arg0, arg1)\n}\n\n// SetUserStatusTimedDND mocks base method.\nfunc (m *MockAPI) SetUserStatusTimedDND(arg0 string, arg1 int64) (*model.Status, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"SetUserStatusTimedDND\", arg0, arg1)\n\tret0, _ := ret[0].(*model.Status)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// SetUserStatusTimedDND indicates an expected call of SetUserStatusTimedDND.\nfunc (mr *MockAPIMockRecorder) SetUserStatusTimedDND(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SetUserStatusTimedDND\", reflect.TypeOf((*MockAPI)(nil).SetUserStatusTimedDND), arg0, arg1)\n}\n\n// UnregisterCommand mocks base method.\nfunc (m *MockAPI) UnregisterCommand(arg0, arg1 string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UnregisterCommand\", arg0, arg1)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// UnregisterCommand indicates an expected call of UnregisterCommand.\nfunc (mr *MockAPIMockRecorder) UnregisterCommand(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UnregisterCommand\", reflect.TypeOf((*MockAPI)(nil).UnregisterCommand), arg0, arg1)\n}\n\n// UpdateBotActive mocks base method.\nfunc (m *MockAPI) UpdateBotActive(arg0 string, arg1 bool) (*model.Bot, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdateBotActive\", arg0, arg1)\n\tret0, _ := ret[0].(*model.Bot)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// UpdateBotActive indicates an expected call of UpdateBotActive.\nfunc (mr *MockAPIMockRecorder) UpdateBotActive(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdateBotActive\", reflect.TypeOf((*MockAPI)(nil).UpdateBotActive), arg0, arg1)\n}\n\n// UpdateChannel mocks base method.\nfunc (m *MockAPI) UpdateChannel(arg0 *model.Channel) (*model.Channel, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdateChannel\", arg0)\n\tret0, _ := ret[0].(*model.Channel)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// UpdateChannel indicates an expected call of UpdateChannel.\nfunc (mr *MockAPIMockRecorder) UpdateChannel(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdateChannel\", reflect.TypeOf((*MockAPI)(nil).UpdateChannel), arg0)\n}\n\n// UpdateChannelMemberNotifications mocks base method.\nfunc (m *MockAPI) UpdateChannelMemberNotifications(arg0, arg1 string, arg2 map[string]string) (*model.ChannelMember, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdateChannelMemberNotifications\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(*model.ChannelMember)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// UpdateChannelMemberNotifications indicates an expected call of UpdateChannelMemberNotifications.\nfunc (mr *MockAPIMockRecorder) UpdateChannelMemberNotifications(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdateChannelMemberNotifications\", reflect.TypeOf((*MockAPI)(nil).UpdateChannelMemberNotifications), arg0, arg1, arg2)\n}\n\n// UpdateChannelMemberRoles mocks base method.\nfunc (m *MockAPI) UpdateChannelMemberRoles(arg0, arg1, arg2 string) (*model.ChannelMember, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdateChannelMemberRoles\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(*model.ChannelMember)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// UpdateChannelMemberRoles indicates an expected call of UpdateChannelMemberRoles.\nfunc (mr *MockAPIMockRecorder) UpdateChannelMemberRoles(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdateChannelMemberRoles\", reflect.TypeOf((*MockAPI)(nil).UpdateChannelMemberRoles), arg0, arg1, arg2)\n}\n\n// UpdateChannelSidebarCategories mocks base method.\nfunc (m *MockAPI) UpdateChannelSidebarCategories(arg0, arg1 string, arg2 []*model.SidebarCategoryWithChannels) ([]*model.SidebarCategoryWithChannels, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdateChannelSidebarCategories\", arg0, arg1, arg2)\n\tret0, _ := ret[0].([]*model.SidebarCategoryWithChannels)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// UpdateChannelSidebarCategories indicates an expected call of UpdateChannelSidebarCategories.\nfunc (mr *MockAPIMockRecorder) UpdateChannelSidebarCategories(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdateChannelSidebarCategories\", reflect.TypeOf((*MockAPI)(nil).UpdateChannelSidebarCategories), arg0, arg1, arg2)\n}\n\n// UpdateCommand mocks base method.\nfunc (m *MockAPI) UpdateCommand(arg0 string, arg1 *model.Command) (*model.Command, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdateCommand\", arg0, arg1)\n\tret0, _ := ret[0].(*model.Command)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// UpdateCommand indicates an expected call of UpdateCommand.\nfunc (mr *MockAPIMockRecorder) UpdateCommand(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdateCommand\", reflect.TypeOf((*MockAPI)(nil).UpdateCommand), arg0, arg1)\n}\n\n// UpdateEphemeralPost mocks base method.\nfunc (m *MockAPI) UpdateEphemeralPost(arg0 string, arg1 *model.Post) *model.Post {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdateEphemeralPost\", arg0, arg1)\n\tret0, _ := ret[0].(*model.Post)\n\treturn ret0\n}\n\n// UpdateEphemeralPost indicates an expected call of UpdateEphemeralPost.\nfunc (mr *MockAPIMockRecorder) UpdateEphemeralPost(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdateEphemeralPost\", reflect.TypeOf((*MockAPI)(nil).UpdateEphemeralPost), arg0, arg1)\n}\n\n// UpdateOAuthApp mocks base method.\nfunc (m *MockAPI) UpdateOAuthApp(arg0 *model.OAuthApp) (*model.OAuthApp, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdateOAuthApp\", arg0)\n\tret0, _ := ret[0].(*model.OAuthApp)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// UpdateOAuthApp indicates an expected call of UpdateOAuthApp.\nfunc (mr *MockAPIMockRecorder) UpdateOAuthApp(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdateOAuthApp\", reflect.TypeOf((*MockAPI)(nil).UpdateOAuthApp), arg0)\n}\n\n// UpdatePost mocks base method.\nfunc (m *MockAPI) UpdatePost(arg0 *model.Post) (*model.Post, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdatePost\", arg0)\n\tret0, _ := ret[0].(*model.Post)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// UpdatePost indicates an expected call of UpdatePost.\nfunc (mr *MockAPIMockRecorder) UpdatePost(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdatePost\", reflect.TypeOf((*MockAPI)(nil).UpdatePost), arg0)\n}\n\n// UpdatePreferencesForUser mocks base method.\nfunc (m *MockAPI) UpdatePreferencesForUser(arg0 string, arg1 []model.Preference) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdatePreferencesForUser\", arg0, arg1)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// UpdatePreferencesForUser indicates an expected call of UpdatePreferencesForUser.\nfunc (mr *MockAPIMockRecorder) UpdatePreferencesForUser(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdatePreferencesForUser\", reflect.TypeOf((*MockAPI)(nil).UpdatePreferencesForUser), arg0, arg1)\n}\n\n// UpdateTeam mocks base method.\nfunc (m *MockAPI) UpdateTeam(arg0 *model.Team) (*model.Team, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdateTeam\", arg0)\n\tret0, _ := ret[0].(*model.Team)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// UpdateTeam indicates an expected call of UpdateTeam.\nfunc (mr *MockAPIMockRecorder) UpdateTeam(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdateTeam\", reflect.TypeOf((*MockAPI)(nil).UpdateTeam), arg0)\n}\n\n// UpdateTeamMemberRoles mocks base method.\nfunc (m *MockAPI) UpdateTeamMemberRoles(arg0, arg1, arg2 string) (*model.TeamMember, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdateTeamMemberRoles\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(*model.TeamMember)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// UpdateTeamMemberRoles indicates an expected call of UpdateTeamMemberRoles.\nfunc (mr *MockAPIMockRecorder) UpdateTeamMemberRoles(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdateTeamMemberRoles\", reflect.TypeOf((*MockAPI)(nil).UpdateTeamMemberRoles), arg0, arg1, arg2)\n}\n\n// UpdateUser mocks base method.\nfunc (m *MockAPI) UpdateUser(arg0 *model.User) (*model.User, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdateUser\", arg0)\n\tret0, _ := ret[0].(*model.User)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// UpdateUser indicates an expected call of UpdateUser.\nfunc (mr *MockAPIMockRecorder) UpdateUser(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdateUser\", reflect.TypeOf((*MockAPI)(nil).UpdateUser), arg0)\n}\n\n// UpdateUserActive mocks base method.\nfunc (m *MockAPI) UpdateUserActive(arg0 string, arg1 bool) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdateUserActive\", arg0, arg1)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// UpdateUserActive indicates an expected call of UpdateUserActive.\nfunc (mr *MockAPIMockRecorder) UpdateUserActive(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdateUserActive\", reflect.TypeOf((*MockAPI)(nil).UpdateUserActive), arg0, arg1)\n}\n\n// UpdateUserCustomStatus mocks base method.\nfunc (m *MockAPI) UpdateUserCustomStatus(arg0 string, arg1 *model.CustomStatus) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdateUserCustomStatus\", arg0, arg1)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// UpdateUserCustomStatus indicates an expected call of UpdateUserCustomStatus.\nfunc (mr *MockAPIMockRecorder) UpdateUserCustomStatus(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdateUserCustomStatus\", reflect.TypeOf((*MockAPI)(nil).UpdateUserCustomStatus), arg0, arg1)\n}\n\n// UpdateUserStatus mocks base method.\nfunc (m *MockAPI) UpdateUserStatus(arg0, arg1 string) (*model.Status, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdateUserStatus\", arg0, arg1)\n\tret0, _ := ret[0].(*model.Status)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// UpdateUserStatus indicates an expected call of UpdateUserStatus.\nfunc (mr *MockAPIMockRecorder) UpdateUserStatus(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdateUserStatus\", reflect.TypeOf((*MockAPI)(nil).UpdateUserStatus), arg0, arg1)\n}\n\n// UploadData mocks base method.\nfunc (m *MockAPI) UploadData(arg0 *model.UploadSession, arg1 io.Reader) (*model.FileInfo, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UploadData\", arg0, arg1)\n\tret0, _ := ret[0].(*model.FileInfo)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// UploadData indicates an expected call of UploadData.\nfunc (mr *MockAPIMockRecorder) UploadData(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UploadData\", reflect.TypeOf((*MockAPI)(nil).UploadData), arg0, arg1)\n}\n\n// UploadFile mocks base method.\nfunc (m *MockAPI) UploadFile(arg0 []byte, arg1, arg2 string) (*model.FileInfo, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UploadFile\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(*model.FileInfo)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// UploadFile indicates an expected call of UploadFile.\nfunc (mr *MockAPIMockRecorder) UploadFile(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UploadFile\", reflect.TypeOf((*MockAPI)(nil).UploadFile), arg0, arg1, arg2)\n}\n"
  },
  {
    "path": "server/services/permissions/mocks/mockstore.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: github.com/mattermost/focalboard/server/services/permissions (interfaces: Store)\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\treflect \"reflect\"\n\n\tgomock \"github.com/golang/mock/gomock\"\n\tmodel \"github.com/mattermost/focalboard/server/model\"\n)\n\n// MockStore is a mock of Store interface.\ntype MockStore struct {\n\tctrl     *gomock.Controller\n\trecorder *MockStoreMockRecorder\n}\n\n// MockStoreMockRecorder is the mock recorder for MockStore.\ntype MockStoreMockRecorder struct {\n\tmock *MockStore\n}\n\n// NewMockStore creates a new mock instance.\nfunc NewMockStore(ctrl *gomock.Controller) *MockStore {\n\tmock := &MockStore{ctrl: ctrl}\n\tmock.recorder = &MockStoreMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockStore) EXPECT() *MockStoreMockRecorder {\n\treturn m.recorder\n}\n\n// GetBoard mocks base method.\nfunc (m *MockStore) GetBoard(arg0 string) (*model.Board, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetBoard\", arg0)\n\tret0, _ := ret[0].(*model.Board)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetBoard indicates an expected call of GetBoard.\nfunc (mr *MockStoreMockRecorder) GetBoard(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetBoard\", reflect.TypeOf((*MockStore)(nil).GetBoard), arg0)\n}\n\n// GetBoardHistory mocks base method.\nfunc (m *MockStore) GetBoardHistory(arg0 string, arg1 model.QueryBoardHistoryOptions) ([]*model.Board, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetBoardHistory\", arg0, arg1)\n\tret0, _ := ret[0].([]*model.Board)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetBoardHistory indicates an expected call of GetBoardHistory.\nfunc (mr *MockStoreMockRecorder) GetBoardHistory(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetBoardHistory\", reflect.TypeOf((*MockStore)(nil).GetBoardHistory), arg0, arg1)\n}\n\n// GetMemberForBoard mocks base method.\nfunc (m *MockStore) GetMemberForBoard(arg0, arg1 string) (*model.BoardMember, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetMemberForBoard\", arg0, arg1)\n\tret0, _ := ret[0].(*model.BoardMember)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetMemberForBoard indicates an expected call of GetMemberForBoard.\nfunc (mr *MockStoreMockRecorder) GetMemberForBoard(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetMemberForBoard\", reflect.TypeOf((*MockStore)(nil).GetMemberForBoard), arg0, arg1)\n}\n"
  },
  {
    "path": "server/services/permissions/permissions.go",
    "content": "//go:generate mockgen -destination=mocks/mockstore.go -package mocks . Store\n// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage permissions\n\nimport (\n\t\"github.com/mattermost/focalboard/server/model\"\n\n\tmmModel \"github.com/mattermost/mattermost/server/public/model\"\n)\n\ntype PermissionsService interface {\n\tHasPermissionTo(userID string, permission *mmModel.Permission) bool\n\tHasPermissionToTeam(userID, teamID string, permission *mmModel.Permission) bool\n\tHasPermissionToChannel(userID, channelID string, permission *mmModel.Permission) bool\n\tHasPermissionToBoard(userID, boardID string, permission *mmModel.Permission) bool\n}\n\ntype Store interface {\n\tGetBoard(boardID string) (*model.Board, error)\n\tGetMemberForBoard(boardID, userID string) (*model.BoardMember, error)\n\tGetBoardHistory(boardID string, opts model.QueryBoardHistoryOptions) ([]*model.Board, error)\n}\n"
  },
  {
    "path": "server/services/scheduler/scheduler.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage scheduler\n\nimport (\n\t\"fmt\"\n\t\"time\"\n)\n\ntype TaskFunc func()\n\ntype ScheduledTask struct {\n\tName      string        `json:\"name\"`\n\tInterval  time.Duration `json:\"interval\"`\n\tRecurring bool          `json:\"recurring\"`\n\tfunction  func()\n\tcancel    chan struct{}\n\tcancelled chan struct{}\n}\n\nfunc CreateTask(name string, function TaskFunc, timeToExecution time.Duration) *ScheduledTask {\n\treturn createTask(name, function, timeToExecution, false)\n}\n\nfunc CreateRecurringTask(name string, function TaskFunc, interval time.Duration) *ScheduledTask {\n\treturn createTask(name, function, interval, true)\n}\n\nfunc createTask(name string, function TaskFunc, interval time.Duration, recurring bool) *ScheduledTask {\n\ttask := &ScheduledTask{\n\t\tName:      name,\n\t\tInterval:  interval,\n\t\tRecurring: recurring,\n\t\tfunction:  function,\n\t\tcancel:    make(chan struct{}),\n\t\tcancelled: make(chan struct{}),\n\t}\n\n\tgo func() {\n\t\tdefer close(task.cancelled)\n\n\t\tticker := time.NewTicker(interval)\n\t\tdefer ticker.Stop()\n\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-ticker.C:\n\t\t\t\tfunction()\n\t\t\tcase <-task.cancel:\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif !task.Recurring {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}()\n\n\treturn task\n}\n\nfunc (task *ScheduledTask) Cancel() {\n\tclose(task.cancel)\n\t<-task.cancelled\n}\n\nfunc (task *ScheduledTask) String() string {\n\treturn fmt.Sprintf(\n\t\t\"%s\\nInterval: %s\\nRecurring: %t\\n\",\n\t\ttask.Name,\n\t\ttask.Interval.String(),\n\t\ttask.Recurring,\n\t)\n}\n"
  },
  {
    "path": "server/services/scheduler/scheduler_test.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage scheduler\n\nimport (\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestCreateTask(t *testing.T) {\n\ttaskName := \"Test Task\"\n\ttaskTime := time.Millisecond * 200\n\ttaskWait := time.Millisecond * 100\n\n\texecutionCount := new(int32)\n\ttestFunc := func() {\n\t\tatomic.AddInt32(executionCount, 1)\n\t}\n\n\ttask := CreateTask(taskName, testFunc, taskTime)\n\n\tassert.EqualValues(t, 0, atomic.LoadInt32(executionCount))\n\n\ttime.Sleep(taskTime + taskWait)\n\n\tassert.EqualValues(t, 1, atomic.LoadInt32(executionCount))\n\tassert.Equal(t, taskName, task.Name)\n\tassert.Equal(t, taskTime, task.Interval)\n\tassert.False(t, task.Recurring)\n}\n\nfunc TestCreateRecurringTask(t *testing.T) {\n\ttaskName := \"Test Recurring Task\"\n\ttaskTime := time.Millisecond * 500\n\ttaskWait := time.Millisecond * 200\n\n\texecutionCount := new(int32)\n\ttestFunc := func() {\n\t\tatomic.AddInt32(executionCount, 1)\n\t}\n\n\ttask := CreateRecurringTask(taskName, testFunc, taskTime)\n\n\tassert.EqualValues(t, 0, atomic.LoadInt32(executionCount))\n\n\ttime.Sleep(taskTime + taskWait)\n\n\tassert.EqualValues(t, 1, atomic.LoadInt32(executionCount))\n\n\ttime.Sleep(taskTime)\n\n\tassert.EqualValues(t, 2, atomic.LoadInt32(executionCount))\n\tassert.Equal(t, taskName, task.Name)\n\tassert.Equal(t, taskTime, task.Interval)\n\tassert.True(t, task.Recurring)\n\n\ttask.Cancel()\n}\n\nfunc TestCancelTask(t *testing.T) {\n\ttaskName := \"Test Task\"\n\ttaskTime := time.Millisecond * 100\n\ttaskWait := time.Millisecond * 100\n\n\texecutionCount := new(int32)\n\ttestFunc := func() {\n\t\tatomic.AddInt32(executionCount, 1)\n\t}\n\n\ttask := CreateTask(taskName, testFunc, taskTime)\n\n\tassert.EqualValues(t, 0, atomic.LoadInt32(executionCount))\n\ttask.Cancel()\n\n\ttime.Sleep(taskTime + taskWait)\n\tassert.EqualValues(t, 0, atomic.LoadInt32(executionCount))\n}\n"
  },
  {
    "path": "server/services/store/generators/main.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage main\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"go/ast\"\n\t\"go/format\"\n\t\"go/parser\"\n\t\"go/token\"\n\t\"io\"\n\t\"log\"\n\t\"os\"\n\t\"path\"\n\t\"strings\"\n\t\"text/template\"\n)\n\nconst (\n\tWithTransactionComment = \"@withTransaction\"\n\tErrorType              = \"error\"\n\tStringType             = \"string\"\n\tIntType                = \"int\"\n\tInt32Type              = \"int32\"\n\tInt64Type              = \"int64\"\n\tBoolType               = \"bool\"\n)\n\nfunc isError(typeName string) bool {\n\treturn strings.Contains(typeName, ErrorType)\n}\n\nfunc isString(typeName string) bool {\n\treturn typeName == StringType\n}\n\nfunc isInt(typeName string) bool {\n\treturn typeName == IntType || typeName == Int32Type || typeName == Int64Type\n}\n\nfunc isBool(typeName string) bool {\n\treturn typeName == BoolType\n}\n\nfunc main() {\n\tif err := buildTransactionalStore(); err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n\nfunc buildTransactionalStore() error {\n\tcode, err := generateLayer(\"TransactionalStore\", \"transactional_store.go.tmpl\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tformatedCode, err := format.Source(code)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn os.WriteFile(path.Join(\"sqlstore/public_methods.go\"), formatedCode, 0644) //nolint:gosec\n}\n\ntype methodParam struct {\n\tName string\n\tType string\n}\n\ntype methodData struct {\n\tParams          []methodParam\n\tResults         []string\n\tWithTransaction bool\n}\n\ntype storeMetadata struct {\n\tName    string\n\tMethods map[string]methodData\n}\n\nvar blacklistedStoreMethodNames = map[string]bool{\n\t\"Shutdown\":  true,\n\t\"DBType\":    true,\n\t\"DBVersion\": true,\n}\n\nfunc extractMethodMetadata(method *ast.Field, src []byte) methodData {\n\tparams := []methodParam{}\n\tresults := []string{}\n\twithTransaction := false\n\tast.Inspect(method.Type, func(expr ast.Node) bool {\n\t\t//nolint:gocritic\n\t\tswitch e := expr.(type) {\n\t\tcase *ast.FuncType:\n\t\t\tif method.Doc != nil {\n\t\t\t\tfor _, comment := range method.Doc.List {\n\t\t\t\t\tif strings.Contains(comment.Text, WithTransactionComment) {\n\t\t\t\t\t\twithTransaction = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif e.Params != nil {\n\t\t\t\tfor _, param := range e.Params.List {\n\t\t\t\t\tfor _, paramName := range param.Names {\n\t\t\t\t\t\tparams = append(params, methodParam{Name: paramName.Name, Type: string(src[param.Type.Pos()-1 : param.Type.End()-1])})\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif e.Results != nil {\n\t\t\t\tfor _, result := range e.Results.List {\n\t\t\t\t\tresults = append(results, string(src[result.Type.Pos()-1:result.Type.End()-1]))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true\n\t})\n\treturn methodData{Params: params, Results: results, WithTransaction: withTransaction}\n}\n\nfunc extractStoreMetadata() (*storeMetadata, error) {\n\t// Create the AST by parsing src.\n\tfset := token.NewFileSet() // positions are relative to fset\n\n\tfile, err := os.Open(\"store.go\")\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"unable to open store/store.go file: %w\", err)\n\t}\n\tsrc, err := io.ReadAll(file)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfile.Close()\n\tf, err := parser.ParseFile(fset, \"\", src, parser.AllErrors|parser.ParseComments)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tmetadata := storeMetadata{Methods: map[string]methodData{}}\n\n\tast.Inspect(f, func(n ast.Node) bool {\n\t\t//nolint:gocritic\n\t\tswitch x := n.(type) {\n\t\tcase *ast.TypeSpec:\n\t\t\tif x.Name.Name == \"Store\" {\n\t\t\t\tfor _, method := range x.Type.(*ast.InterfaceType).Methods.List {\n\t\t\t\t\tmethodName := method.Names[0].Name\n\t\t\t\t\tif _, ok := blacklistedStoreMethodNames[methodName]; ok {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tmetadata.Methods[methodName] = extractMethodMetadata(method, src)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true\n\t})\n\n\treturn &metadata, nil\n}\n\nfunc generateLayer(name, templateFile string) ([]byte, error) {\n\tout := bytes.NewBufferString(\"\")\n\tmetadata, err := extractStoreMetadata()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tmetadata.Name = name\n\n\tmyFuncs := template.FuncMap{\n\t\t\"joinResultsForSignature\": func(results []string) string {\n\t\t\tif len(results) == 0 {\n\t\t\t\treturn \"\"\n\t\t\t}\n\t\t\tif len(results) == 1 {\n\t\t\t\treturn strings.Join(results, \", \")\n\t\t\t}\n\t\t\treturn fmt.Sprintf(\"(%s)\", strings.Join(results, \", \"))\n\t\t},\n\t\t\"genResultsVars\": func(results []string, withNilError bool) string {\n\t\t\tvars := []string{}\n\t\t\tfor i, typeName := range results {\n\t\t\t\tswitch {\n\t\t\t\tcase isError(typeName):\n\t\t\t\t\tif withNilError {\n\t\t\t\t\t\tvars = append(vars, \"nil\")\n\t\t\t\t\t} else {\n\t\t\t\t\t\tvars = append(vars, \"err\")\n\t\t\t\t\t}\n\t\t\t\tcase i == 0:\n\t\t\t\t\tvars = append(vars, \"result\")\n\t\t\t\tdefault:\n\t\t\t\t\tvars = append(vars, fmt.Sprintf(\"resultVar%d\", i))\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn strings.Join(vars, \", \")\n\t\t},\n\t\t\"errorPresent\": func(results []string) bool {\n\t\t\tfor _, typeName := range results {\n\t\t\t\tif isError(typeName) {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn false\n\t\t},\n\t\t\"errorVar\": func(results []string) string {\n\t\t\tfor _, typeName := range results {\n\t\t\t\tif isError(typeName) {\n\t\t\t\t\treturn \"err\"\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn \"\"\n\t\t},\n\t\t\"joinParams\": func(params []methodParam) string {\n\t\t\tparamsNames := make([]string, 0, len(params))\n\t\t\tfor _, param := range params {\n\t\t\t\ttParams := \"\"\n\t\t\t\tif strings.HasPrefix(param.Type, \"...\") {\n\t\t\t\t\ttParams = \"...\"\n\t\t\t\t}\n\t\t\t\tparamsNames = append(paramsNames, param.Name+tParams)\n\t\t\t}\n\t\t\treturn strings.Join(paramsNames, \", \")\n\t\t},\n\t\t\"joinParamsWithType\": func(params []methodParam) string {\n\t\t\tparamsWithType := []string{}\n\t\t\tfor _, param := range params {\n\t\t\t\tswitch param.Type {\n\t\t\t\tcase \"Container\":\n\t\t\t\t\tparamsWithType = append(paramsWithType, fmt.Sprintf(\"%s store.%s\", param.Name, param.Type))\n\t\t\t\tdefault:\n\t\t\t\t\tparamsWithType = append(paramsWithType, fmt.Sprintf(\"%s %s\", param.Name, param.Type))\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn strings.Join(paramsWithType, \", \")\n\t\t},\n\t\t\"renameStoreMethod\": func(methodName string) string {\n\t\t\treturn strings.ToLower(methodName[0:1]) + methodName[1:]\n\t\t},\n\t\t\"genErrorResultsVars\": func(results []string, errName string) string {\n\t\t\tvars := []string{}\n\t\t\tfor _, typeName := range results {\n\t\t\t\tswitch {\n\t\t\t\tcase isError(typeName):\n\t\t\t\t\tvars = append(vars, errName)\n\t\t\t\tcase isString(typeName):\n\t\t\t\t\tvars = append(vars, \"\\\"\\\"\")\n\t\t\t\tcase isInt(typeName):\n\t\t\t\t\tvars = append(vars, \"0\")\n\t\t\t\tcase isBool(typeName):\n\t\t\t\t\tvars = append(vars, \"false\")\n\t\t\t\tdefault:\n\t\t\t\t\tvars = append(vars, \"nil\")\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn strings.Join(vars, \", \")\n\t\t},\n\t}\n\n\tt := template.Must(template.New(templateFile).Funcs(myFuncs).ParseFiles(\"generators/\" + templateFile))\n\tif err = t.Execute(out, metadata); err != nil {\n\t\treturn nil, err\n\t}\n\treturn out.Bytes(), nil\n}\n"
  },
  {
    "path": "server/services/store/generators/transactional_store.go.tmpl",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\n// Code generated by \"make generate\" from the Store interface\n// DO NOT EDIT\n\n// To add a public method, create an entry in the Store interface,\n// prefix it with a @withTransaction comment if you need it to be\n// transactional and then add a private method in the store itself\n// with db sq.BaseRunner as the first parameter before running `make\n// generate`\n\npackage sqlstore\n\nimport (\n\t\"context\"\n\t\"time\"\n\n    \"github.com/mattermost/focalboard/server/model\"\n\n    \"github.com/mattermost/mattermost/server/public/shared/mlog\"\n\tmmModel \"github.com/mattermost/mattermost/server/public/model\"\n)\n\n{{range $index, $element := .Methods}}\nfunc (s *SQLStore) {{$index}}({{$element.Params | joinParamsWithType}}) {{$element.Results | joinResultsForSignature}} {\n    {{- if $element.WithTransaction}}\n    \tif s.dbType == model.SqliteDBType {\n    \t    return s.{{$index | renameStoreMethod}}(s.db, {{$element.Params | joinParams}})\n    \t}\n    \ttx, txErr := s.db.BeginTx(context.Background(), nil)\n        if txErr != nil {\n            return {{ genErrorResultsVars $element.Results \"txErr\"}}\n    \t}\n\n        {{- if $element.Results | len | eq 0}}\n    \ts.{{$index | renameStoreMethod}}(tx, {{$element.Params | joinParams}})\n\n        if err := tx.Commit(); err != nil {\n           return {{ genErrorResultsVars $element.Results \"err\"}}\n        }\n    \t{{else}}\n    \t\t{{genResultsVars $element.Results false }} := s.{{$index | renameStoreMethod}}(tx, {{$element.Params | joinParams}})\n    \t\t{{- if $element.Results | errorPresent }}\n    \t\t\tif {{$element.Results | errorVar}} != nil {\n                    if rollbackErr := tx.Rollback(); rollbackErr != nil {\n                       s.logger.Error(\"transaction rollback error\", mlog.Err(rollbackErr), mlog.String(\"methodName\", \"{{$index}}\"))\n                    }\n                    return {{ genErrorResultsVars $element.Results \"err\"}}\n    \t\t\t}\n    \t\t{{end}}\n            if err := tx.Commit(); err != nil {\n               return {{ genErrorResultsVars $element.Results \"err\"}}\n            }\n\n\t    \treturn {{ genResultsVars $element.Results true -}}\n\t    {{end}}\n    {{else}}\n    return s.{{$index | renameStoreMethod}}(s.db, {{$element.Params | joinParams}})\n    {{end}}\n}\n{{end}}\n"
  },
  {
    "path": "server/services/store/mattermostauthlayer/mattermostauthlayer.go",
    "content": "package mattermostauthlayer\n\nimport (\n\t\"database/sql\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tmmModel \"github.com/mattermost/mattermost/server/public/model\"\n\n\tsq \"github.com/Masterminds/squirrel\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/store\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nvar boardsBotID string\n\n// servicesAPI is the interface required my the MattermostAuthLayer to interact with\n// the mattermost-server. You can use plugin-api or product-api adapter implementations.\ntype servicesAPI interface {\n\tGetDirectChannel(userID1, userID2 string) (*mmModel.Channel, error)\n\tGetChannelByID(channelID string) (*mmModel.Channel, error)\n\tGetChannelMember(channelID string, userID string) (*mmModel.ChannelMember, error)\n\tGetChannelsForTeamForUser(teamID string, userID string, includeDeleted bool) (mmModel.ChannelList, error)\n\tGetUserByID(userID string) (*mmModel.User, error)\n\tUpdateUser(user *mmModel.User) (*mmModel.User, error)\n\tGetUserByEmail(email string) (*mmModel.User, error)\n\tGetUserByUsername(username string) (*mmModel.User, error)\n\tGetLicense() *mmModel.License\n\tGetFileInfo(fileID string) (*mmModel.FileInfo, error)\n\tEnsureBot(bot *mmModel.Bot) (string, error)\n\tCreatePost(post *mmModel.Post) (*mmModel.Post, error)\n\tGetTeamMember(teamID string, userID string) (*mmModel.TeamMember, error)\n\tGetPreferencesForUser(userID string) (mmModel.Preferences, error)\n\tDeletePreferencesForUser(userID string, preferences mmModel.Preferences) error\n\tUpdatePreferencesForUser(userID string, preferences mmModel.Preferences) error\n}\n\n// Store represents the abstraction of the data storage.\ntype MattermostAuthLayer struct {\n\tstore.Store\n\tdbType      string\n\tmmDB        *sql.DB\n\tlogger      mlog.LoggerIFace\n\tservicesAPI servicesAPI\n\ttablePrefix string\n}\n\n// New creates a new SQL implementation of the store.\nfunc New(dbType string, db *sql.DB, store store.Store, logger mlog.LoggerIFace, api servicesAPI, tablePrefix string) (*MattermostAuthLayer, error) {\n\tlayer := &MattermostAuthLayer{\n\t\tStore:       store,\n\t\tdbType:      dbType,\n\t\tmmDB:        db,\n\t\tlogger:      logger,\n\t\tservicesAPI: api,\n\t\ttablePrefix: tablePrefix,\n\t}\n\n\treturn layer, nil\n}\n\n// Shutdown close the connection with the store.\nfunc (s *MattermostAuthLayer) Shutdown() error {\n\treturn s.Store.Shutdown()\n}\n\nfunc (s *MattermostAuthLayer) GetRegisteredUserCount() (int, error) {\n\tquery := s.getQueryBuilder().\n\t\tSelect(\"count(*)\").\n\t\tFrom(\"Users\").\n\t\tWhere(sq.Eq{\"deleteAt\": 0})\n\trow := query.QueryRow()\n\n\tvar count int\n\terr := row.Scan(&count)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\treturn count, nil\n}\n\nfunc (s *MattermostAuthLayer) GetUserByID(userID string) (*model.User, error) {\n\tmmuser, err := s.servicesAPI.GetUserByID(userID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tuser := mmUserToFbUser(mmuser)\n\treturn &user, nil\n}\n\nfunc (s *MattermostAuthLayer) GetUserByEmail(email string) (*model.User, error) {\n\tmmuser, err := s.servicesAPI.GetUserByEmail(email)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tuser := mmUserToFbUser(mmuser)\n\treturn &user, nil\n}\n\nfunc (s *MattermostAuthLayer) GetUserByUsername(username string) (*model.User, error) {\n\tmmuser, err := s.servicesAPI.GetUserByUsername(username)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tuser := mmUserToFbUser(mmuser)\n\treturn &user, nil\n}\n\nfunc (s *MattermostAuthLayer) CreateUser(user *model.User) (*model.User, error) {\n\treturn nil, store.NewNotSupportedError(\"no user creation allowed from focalboard, create it using mattermost\")\n}\n\nfunc (s *MattermostAuthLayer) UpdateUser(user *model.User) (*model.User, error) {\n\treturn nil, store.NewNotSupportedError(\"no update allowed from focalboard, update it using mattermost\")\n}\n\nfunc (s *MattermostAuthLayer) UpdateUserPassword(username, password string) error {\n\treturn store.NewNotSupportedError(\"no update allowed from focalboard, update it using mattermost\")\n}\n\nfunc (s *MattermostAuthLayer) UpdateUserPasswordByID(userID, password string) error {\n\treturn store.NewNotSupportedError(\"no update allowed from focalboard, update it using mattermost\")\n}\n\nfunc (s *MattermostAuthLayer) PatchUserPreferences(userID string, patch model.UserPreferencesPatch) (mmModel.Preferences, error) {\n\tpreferences, err := s.GetUserPreferences(userID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(patch.UpdatedFields) > 0 {\n\t\tupdatedPreferences := mmModel.Preferences{}\n\t\tfor key, value := range patch.UpdatedFields {\n\t\t\tpreference := mmModel.Preference{\n\t\t\t\tUserId:   userID,\n\t\t\t\tCategory: model.PreferencesCategoryFocalboard,\n\t\t\t\tName:     key,\n\t\t\t\tValue:    value,\n\t\t\t}\n\n\t\t\tupdatedPreferences = append(updatedPreferences, preference)\n\t\t}\n\n\t\tif err := s.servicesAPI.UpdatePreferencesForUser(userID, updatedPreferences); err != nil {\n\t\t\ts.logger.Error(\"failed to update user preferences\", mlog.String(\"user_id\", userID), mlog.Err(err))\n\t\t\treturn nil, err\n\t\t}\n\n\t\t// we update the preferences list replacing or adding those\n\t\t// that were updated\n\t\tnewPreferences := mmModel.Preferences{}\n\t\tfor _, existingPreference := range preferences {\n\t\t\thasBeenUpdated := false\n\t\t\tfor _, updatedPreference := range updatedPreferences {\n\t\t\t\tif updatedPreference.Name == existingPreference.Name {\n\t\t\t\t\thasBeenUpdated = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif !hasBeenUpdated {\n\t\t\t\tnewPreferences = append(newPreferences, existingPreference)\n\t\t\t}\n\t\t}\n\t\tnewPreferences = append(newPreferences, updatedPreferences...)\n\t\tpreferences = newPreferences\n\t}\n\n\tif len(patch.DeletedFields) > 0 {\n\t\tdeletedPreferences := mmModel.Preferences{}\n\t\tfor _, key := range patch.DeletedFields {\n\t\t\tpreference := mmModel.Preference{\n\t\t\t\tUserId:   userID,\n\t\t\t\tCategory: model.PreferencesCategoryFocalboard,\n\t\t\t\tName:     key,\n\t\t\t}\n\n\t\t\tdeletedPreferences = append(deletedPreferences, preference)\n\t\t}\n\n\t\tif err := s.servicesAPI.DeletePreferencesForUser(userID, deletedPreferences); err != nil {\n\t\t\ts.logger.Error(\"failed to delete user preferences\", mlog.String(\"user_id\", userID), mlog.Err(err))\n\t\t\treturn nil, err\n\t\t}\n\n\t\t// we update the preferences removing those that have been\n\t\t// deleted\n\t\tnewPreferences := mmModel.Preferences{}\n\t\tfor _, existingPreference := range preferences {\n\t\t\thasBeenDeleted := false\n\t\t\tfor _, deletedPreference := range deletedPreferences {\n\t\t\t\tif deletedPreference.Name == existingPreference.Name {\n\t\t\t\t\thasBeenDeleted = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif !hasBeenDeleted {\n\t\t\t\tnewPreferences = append(newPreferences, existingPreference)\n\t\t\t}\n\t\t}\n\t\tpreferences = newPreferences\n\t}\n\n\treturn preferences, nil\n}\n\nfunc (s *MattermostAuthLayer) GetUserPreferences(userID string) (mmModel.Preferences, error) {\n\treturn s.servicesAPI.GetPreferencesForUser(userID)\n}\n\n// GetActiveUserCount returns the number of users with active sessions within N seconds ago.\nfunc (s *MattermostAuthLayer) GetActiveUserCount(updatedSecondsAgo int64) (int, error) {\n\tquery := s.getQueryBuilder().\n\t\tSelect(\"count(distinct userId)\").\n\t\tFrom(\"Sessions\").\n\t\tWhere(sq.Gt{\"LastActivityAt\": utils.GetMillis() - utils.SecondsToMillis(updatedSecondsAgo)})\n\n\trow := query.QueryRow()\n\n\tvar count int\n\terr := row.Scan(&count)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\treturn count, nil\n}\n\nfunc (s *MattermostAuthLayer) GetSession(token string, expireTime int64) (*model.Session, error) {\n\treturn nil, store.NewNotSupportedError(\"sessions not used when using mattermost\")\n}\n\nfunc (s *MattermostAuthLayer) CreateSession(session *model.Session) error {\n\treturn store.NewNotSupportedError(\"no update allowed from focalboard, update it using mattermost\")\n}\n\nfunc (s *MattermostAuthLayer) RefreshSession(session *model.Session) error {\n\treturn store.NewNotSupportedError(\"no update allowed from focalboard, update it using mattermost\")\n}\n\nfunc (s *MattermostAuthLayer) UpdateSession(session *model.Session) error {\n\treturn store.NewNotSupportedError(\"no update allowed from focalboard, update it using mattermost\")\n}\n\nfunc (s *MattermostAuthLayer) DeleteSession(sessionID string) error {\n\treturn store.NewNotSupportedError(\"no update allowed from focalboard, update it using mattermost\")\n}\n\nfunc (s *MattermostAuthLayer) CleanUpSessions(expireTime int64) error {\n\treturn store.NewNotSupportedError(\"no update allowed from focalboard, update it using mattermost\")\n}\n\nfunc (s *MattermostAuthLayer) GetTeam(id string) (*model.Team, error) {\n\tif id == \"0\" {\n\t\tteam := model.Team{\n\t\t\tID:    id,\n\t\t\tTitle: \"\",\n\t\t}\n\n\t\treturn &team, nil\n\t}\n\n\tquery := s.getQueryBuilder().\n\t\tSelect(\"DisplayName\").\n\t\tFrom(\"Teams\").\n\t\tWhere(sq.Eq{\"ID\": id})\n\n\trow := query.QueryRow()\n\tvar displayName string\n\terr := row.Scan(&displayName)\n\tif err != nil && !model.IsErrNotFound(err) {\n\t\ts.logger.Error(\"GetTeam scan error\",\n\t\t\tmlog.String(\"team_id\", id),\n\t\t\tmlog.Err(err),\n\t\t)\n\t\treturn nil, err\n\t}\n\n\treturn &model.Team{ID: id, Title: displayName}, nil\n}\n\n// GetTeamsForUser retrieves all the teams that the user is a member of.\nfunc (s *MattermostAuthLayer) GetTeamsForUser(userID string) ([]*model.Team, error) {\n\tquery := s.getQueryBuilder().\n\t\tSelect(\"t.Id\", \"t.DisplayName\").\n\t\tFrom(\"Teams as t\").\n\t\tJoin(\"TeamMembers as tm on t.Id=tm.TeamId\").\n\t\tWhere(sq.Eq{\"tm.UserId\": userID}).\n\t\tWhere(sq.Eq{\"tm.DeleteAt\": 0})\n\n\trows, err := query.Query()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer s.CloseRows(rows)\n\n\tteams := []*model.Team{}\n\tfor rows.Next() {\n\t\tvar team model.Team\n\n\t\terr := rows.Scan(\n\t\t\t&team.ID,\n\t\t\t&team.Title,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tteams = append(teams, &team)\n\t}\n\n\treturn teams, nil\n}\n\nfunc (s *MattermostAuthLayer) getQueryBuilder() sq.StatementBuilderType {\n\tbuilder := sq.StatementBuilder\n\tif s.dbType == model.PostgresDBType || s.dbType == model.SqliteDBType {\n\t\tbuilder = builder.PlaceholderFormat(sq.Dollar)\n\t}\n\n\treturn builder.RunWith(s.mmDB)\n}\n\nfunc (s *MattermostAuthLayer) GetUsersByTeam(teamID string, asGuestID string, showEmail, showName bool) ([]*model.User, error) {\n\tquery := s.baseUserQuery(showEmail, showName).\n\t\tWhere(sq.Eq{\"u.deleteAt\": 0})\n\n\tif asGuestID == \"\" {\n\t\tquery = query.\n\t\t\tJoin(\"TeamMembers as tm ON tm.UserID = u.id\").\n\t\t\tWhere(sq.Eq{\"tm.TeamId\": teamID})\n\t} else {\n\t\tboards, err := s.GetBoardsForUserAndTeam(asGuestID, teamID, false)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tboardsIDs := []string{}\n\t\tfor _, board := range boards {\n\t\t\tboardsIDs = append(boardsIDs, board.ID)\n\t\t}\n\t\tquery = query.\n\t\t\tJoin(s.tablePrefix + \"board_members as bm ON bm.UserID = u.ID\").\n\t\t\tWhere(sq.Eq{\"bm.BoardId\": boardsIDs})\n\t}\n\n\trows, err := query.Query()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer s.CloseRows(rows)\n\n\tusers, err := s.usersFromRows(rows)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn users, nil\n}\n\nfunc (s *MattermostAuthLayer) GetUsersList(userIDs []string, showEmail, showName bool) ([]*model.User, error) {\n\tquery := s.baseUserQuery(showEmail, showName).\n\t\tWhere(sq.Eq{\"u.id\": userIDs})\n\n\trows, err := query.Query()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer s.CloseRows(rows)\n\n\tusers, err := s.usersFromRows(rows)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(users) != len(userIDs) {\n\t\treturn users, model.NewErrNotAllFound(\"user\", userIDs)\n\t}\n\n\treturn users, nil\n}\n\nfunc (s *MattermostAuthLayer) SearchUsersByTeam(teamID string, searchQuery string, asGuestID string, excludeBots, showEmail, showName bool) ([]*model.User, error) {\n\tquery := s.baseUserQuery(showEmail, showName).\n\t\tWhere(sq.Eq{\"u.deleteAt\": 0}).\n\t\tWhere(sq.Or{\n\t\t\tsq.Like{\"u.username\": \"%\" + searchQuery + \"%\"},\n\t\t\tsq.Like{\"u.nickname\": \"%\" + searchQuery + \"%\"},\n\t\t\tsq.Like{\"u.firstname\": \"%\" + searchQuery + \"%\"},\n\t\t\tsq.Like{\"u.lastname\": \"%\" + searchQuery + \"%\"},\n\t\t}).\n\t\tOrderBy(\"u.username\").\n\t\tLimit(10)\n\n\tif excludeBots {\n\t\tquery = query.\n\t\t\tWhere(sq.Eq{\"b.UserId IS NOT NULL\": false})\n\t}\n\n\tif asGuestID == \"\" {\n\t\tquery = query.\n\t\t\tJoin(\"TeamMembers as tm ON tm.UserID = u.id\").\n\t\t\tWhere(sq.Eq{\"tm.TeamId\": teamID})\n\t} else {\n\t\tboards, err := s.GetBoardsForUserAndTeam(asGuestID, teamID, false)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tboardsIDs := []string{}\n\t\tfor _, board := range boards {\n\t\t\tboardsIDs = append(boardsIDs, board.ID)\n\t\t}\n\t\tquery = query.\n\t\t\tJoin(s.tablePrefix + \"board_members as bm ON bm.user_id = u.ID\").\n\t\t\tWhere(sq.Eq{\"bm.board_id\": boardsIDs})\n\t}\n\n\trows, err := query.Query()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer s.CloseRows(rows)\n\n\tusers, err := s.usersFromRows(rows)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn users, nil\n}\n\nfunc (s *MattermostAuthLayer) usersFromRows(rows *sql.Rows) ([]*model.User, error) {\n\tusers := []*model.User{}\n\n\tfor rows.Next() {\n\t\tvar user model.User\n\n\t\terr := rows.Scan(\n\t\t\t&user.ID,\n\t\t\t&user.Username,\n\t\t\t&user.Email,\n\t\t\t&user.Nickname,\n\t\t\t&user.FirstName,\n\t\t\t&user.LastName,\n\t\t\t&user.CreateAt,\n\t\t\t&user.UpdateAt,\n\t\t\t&user.DeleteAt,\n\t\t\t&user.IsBot,\n\t\t\t&user.IsGuest,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tusers = append(users, &user)\n\t}\n\n\treturn users, nil\n}\n\nfunc (s *MattermostAuthLayer) CloseRows(rows *sql.Rows) {\n\tif err := rows.Close(); err != nil {\n\t\ts.logger.Error(\"error closing MattermostAuthLayer row set\", mlog.Err(err))\n\t}\n}\n\nfunc (s *MattermostAuthLayer) CreatePrivateWorkspace(userID string) (string, error) {\n\t// we emulate a private workspace by creating\n\t// a DM channel from the user to themselves.\n\tchannel, err := s.servicesAPI.GetDirectChannel(userID, userID)\n\tif err != nil {\n\t\ts.logger.Error(\"error fetching private workspace\", mlog.String(\"userID\", userID), mlog.Err(err))\n\t\treturn \"\", err\n\t}\n\n\treturn channel.Id, nil\n}\n\nfunc mmUserToFbUser(mmUser *mmModel.User) model.User {\n\tauthData := \"\"\n\tif mmUser.AuthData != nil {\n\t\tauthData = *mmUser.AuthData\n\t}\n\treturn model.User{\n\t\tID:          mmUser.Id,\n\t\tUsername:    mmUser.Username,\n\t\tEmail:       mmUser.Email,\n\t\tPassword:    mmUser.Password,\n\t\tNickname:    mmUser.Nickname,\n\t\tFirstName:   mmUser.FirstName,\n\t\tLastName:    mmUser.LastName,\n\t\tMfaSecret:   mmUser.MfaSecret,\n\t\tAuthService: mmUser.AuthService,\n\t\tAuthData:    authData,\n\t\tCreateAt:    mmUser.CreateAt,\n\t\tUpdateAt:    mmUser.UpdateAt,\n\t\tDeleteAt:    mmUser.DeleteAt,\n\t\tIsBot:       mmUser.IsBot,\n\t\tIsGuest:     mmUser.IsGuest(),\n\t\tRoles:       mmUser.Roles,\n\t}\n}\n\nfunc (s *MattermostAuthLayer) GetFileInfo(id string) (*mmModel.FileInfo, error) {\n\tfileInfo, err := s.servicesAPI.GetFileInfo(id)\n\tif err != nil {\n\t\t// Not finding fileinfo is fine because we don't have data for\n\t\t// any existing files already uploaded in Boards before this code\n\t\t// was deployed.\n\t\tvar appErr *mmModel.AppError\n\t\tif errors.As(err, &appErr) {\n\t\t\tif appErr.StatusCode == http.StatusNotFound {\n\t\t\t\treturn nil, model.NewErrNotFound(\"file info ID=\" + id)\n\t\t\t}\n\t\t}\n\n\t\ts.logger.Error(\"error fetching fileinfo\",\n\t\t\tmlog.String(\"id\", id),\n\t\t\tmlog.Err(err),\n\t\t)\n\t\treturn nil, err\n\t}\n\n\treturn fileInfo, nil\n}\n\nfunc (s *MattermostAuthLayer) SaveFileInfo(fileInfo *mmModel.FileInfo) error {\n\tquery := s.getQueryBuilder().\n\t\tInsert(\"FileInfo\").\n\t\tColumns(\n\t\t\t\"Id\",\n\t\t\t\"CreatorId\",\n\t\t\t\"PostId\",\n\t\t\t\"CreateAt\",\n\t\t\t\"UpdateAt\",\n\t\t\t\"DeleteAt\",\n\t\t\t\"Path\",\n\t\t\t\"ThumbnailPath\",\n\t\t\t\"PreviewPath\",\n\t\t\t\"Name\",\n\t\t\t\"Extension\",\n\t\t\t\"Size\",\n\t\t\t\"MimeType\",\n\t\t\t\"Width\",\n\t\t\t\"Height\",\n\t\t\t\"HasPreviewImage\",\n\t\t\t\"MiniPreview\",\n\t\t\t\"Content\",\n\t\t\t\"RemoteId\",\n\t\t\t\"Archived\",\n\t\t).\n\t\tValues(\n\t\t\tfileInfo.Id,\n\t\t\tfileInfo.CreatorId,\n\t\t\tfileInfo.PostId,\n\t\t\tfileInfo.CreateAt,\n\t\t\tfileInfo.UpdateAt,\n\t\t\tfileInfo.DeleteAt,\n\t\t\tfileInfo.Path,\n\t\t\tfileInfo.ThumbnailPath,\n\t\t\tfileInfo.PreviewPath,\n\t\t\tfileInfo.Name,\n\t\t\tfileInfo.Extension,\n\t\t\tfileInfo.Size,\n\t\t\tfileInfo.MimeType,\n\t\t\tfileInfo.Width,\n\t\t\tfileInfo.Height,\n\t\t\tfileInfo.HasPreviewImage,\n\t\t\tfileInfo.MiniPreview,\n\t\t\tfileInfo.Content,\n\t\t\tfileInfo.RemoteId,\n\t\t\tfalse,\n\t\t)\n\n\tif _, err := query.Exec(); err != nil {\n\t\ts.logger.Error(\n\t\t\t\"failed to save fileinfo\",\n\t\t\tmlog.String(\"file_name\", fileInfo.Name),\n\t\t\tmlog.Int(\"size\", fileInfo.Size),\n\t\t\tmlog.Err(err),\n\t\t)\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (s *MattermostAuthLayer) GetLicense() *mmModel.License {\n\treturn s.servicesAPI.GetLicense()\n}\n\nfunc boardFields(prefix string) []string { //nolint:unparam\n\tfields := []string{\n\t\t\"id\",\n\t\t\"team_id\",\n\t\t\"COALESCE(channel_id, '')\",\n\t\t\"COALESCE(created_by, '')\",\n\t\t\"modified_by\",\n\t\t\"type\",\n\t\t\"minimum_role\",\n\t\t\"title\",\n\t\t\"description\",\n\t\t\"icon\",\n\t\t\"show_description\",\n\t\t\"is_template\",\n\t\t\"template_version\",\n\t\t\"COALESCE(properties, '{}')\",\n\t\t\"COALESCE(card_properties, '[]')\",\n\t\t\"create_at\",\n\t\t\"update_at\",\n\t\t\"delete_at\",\n\t}\n\n\tif prefix == \"\" {\n\t\treturn fields\n\t}\n\n\tprefixedFields := make([]string, len(fields))\n\tfor i, field := range fields {\n\t\tif strings.HasPrefix(field, \"COALESCE(\") {\n\t\t\tprefixedFields[i] = strings.Replace(field, \"COALESCE(\", \"COALESCE(\"+prefix, 1)\n\t\t} else {\n\t\t\tprefixedFields[i] = prefix + field\n\t\t}\n\t}\n\treturn prefixedFields\n}\n\nfunc (s *MattermostAuthLayer) baseUserQuery(showEmail, showName bool) sq.SelectBuilder {\n\temailField := \"''\"\n\tif showEmail {\n\t\temailField = \"u.email\"\n\t}\n\tfirstNameField := \"''\"\n\tlastNameField := \"''\"\n\tif showName {\n\t\tfirstNameField = \"u.firstname\"\n\t\tlastNameField = \"u.lastname\"\n\t}\n\n\treturn s.getQueryBuilder().\n\t\tSelect(\n\t\t\t\"u.id\",\n\t\t\t\"u.username\",\n\t\t\temailField,\n\t\t\t\"u.nickname\",\n\t\t\tfirstNameField,\n\t\t\tlastNameField,\n\t\t\t\"u.CreateAt as create_at\",\n\t\t\t\"u.UpdateAt as update_at\",\n\t\t\t\"u.DeleteAt as delete_at\",\n\t\t\t\"b.UserId IS NOT NULL AS is_bot\",\n\t\t\t\"u.roles = 'system_guest' as is_guest\",\n\t\t).\n\t\tFrom(\"Users as u\").\n\t\tLeftJoin(\"Bots b ON ( b.UserID = u.id )\")\n}\n\n// SearchBoardsForUser returns all boards that match with the\n// term that are either private and which the user is a member of, or\n// they're open, regardless of the user membership.\n// Search is case-insensitive.\nfunc (s *MattermostAuthLayer) SearchBoardsForUser(term string, searchField model.BoardSearchField, userID string, includePublicBoards bool) ([]*model.Board, error) {\n\t// as we're joining three queries, we need to avoid numbered\n\t// placeholders until the join is done, so we use the default\n\t// question mark placeholder here\n\tbuilder := s.getQueryBuilder().PlaceholderFormat(sq.Question)\n\n\tboardMembersQ := builder.\n\t\tSelect(boardFields(\"b.\")...).\n\t\tFrom(s.tablePrefix + \"boards as b\").\n\t\tJoin(s.tablePrefix + \"board_members as bm on b.id=bm.board_id\").\n\t\tWhere(sq.Eq{\n\t\t\t\"b.is_template\": false,\n\t\t\t\"bm.user_id\":    userID,\n\t\t})\n\n\tteamMembersQ := builder.\n\t\tSelect(boardFields(\"b.\")...).\n\t\tFrom(s.tablePrefix + \"boards as b\").\n\t\tJoin(\"TeamMembers as tm on tm.teamid=b.team_id\").\n\t\tWhere(sq.Eq{\n\t\t\t\"b.is_template\": false,\n\t\t\t\"tm.userID\":     userID,\n\t\t\t\"tm.deleteAt\":   0,\n\t\t\t\"b.type\":        model.BoardTypeOpen,\n\t\t})\n\n\tchannelMembersQ := builder.\n\t\tSelect(boardFields(\"b.\")...).\n\t\tFrom(s.tablePrefix + \"boards as b\").\n\t\tJoin(\"ChannelMembers as cm on cm.channelId=b.channel_id\").\n\t\tWhere(sq.Eq{\n\t\t\t\"b.is_template\": false,\n\t\t\t\"cm.userId\":     userID,\n\t\t})\n\n\tif term != \"\" {\n\t\tif searchField == model.BoardSearchFieldPropertyName {\n\t\t\tvar where, whereTerm string\n\t\t\tswitch s.dbType {\n\t\t\tcase model.PostgresDBType:\n\t\t\t\twhere = \"b.properties->? is not null\"\n\t\t\t\twhereTerm = term\n\t\t\tcase model.MysqlDBType, model.SqliteDBType:\n\t\t\t\twhere = \"JSON_EXTRACT(b.properties, ?) IS NOT NULL\"\n\t\t\t\twhereTerm = \"$.\" + term\n\t\t\tdefault:\n\t\t\t\twhere = \"b.properties LIKE ?\"\n\t\t\t\twhereTerm = \"%\\\"\" + term + \"\\\"%\"\n\t\t\t}\n\t\t\tboardMembersQ = boardMembersQ.Where(where, whereTerm)\n\t\t\tteamMembersQ = teamMembersQ.Where(where, whereTerm)\n\t\t\tchannelMembersQ = channelMembersQ.Where(where, whereTerm)\n\t\t} else { // model.BoardSearchFieldTitle\n\t\t\t// break search query into space separated words\n\t\t\t// and search for all words.\n\t\t\t// This should later be upgraded to industrial-strength\n\t\t\t// word tokenizer, that uses much more than space\n\t\t\t// to break words.\n\t\t\tconditions := sq.And{}\n\t\t\tfor _, word := range strings.Split(strings.TrimSpace(term), \" \") {\n\t\t\t\tconditions = append(conditions, sq.Like{\"lower(b.title)\": \"%\" + strings.ToLower(word) + \"%\"})\n\t\t\t}\n\n\t\t\tboardMembersQ = boardMembersQ.Where(conditions)\n\t\t\tteamMembersQ = teamMembersQ.Where(conditions)\n\t\t\tchannelMembersQ = channelMembersQ.Where(conditions)\n\t\t}\n\t}\n\n\tteamMembersSQL, teamMembersArgs, err := teamMembersQ.ToSql()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"SearchBoardsForUser error getting teamMembersSQL: %w\", err)\n\t}\n\n\tchannelMembersSQL, channelMembersArgs, err := channelMembersQ.ToSql()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"SearchBoardsForUser error getting channelMembersSQL: %w\", err)\n\t}\n\n\tunionQ := boardMembersQ\n\tuser, err := s.GetUserByID(userID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t// NOTE: theoretically, could do e.g. `isGuest := !includePublicBoards`\n\t// but that introduces some tight coupling + fragility\n\tif !user.IsGuest {\n\t\tunionQ = unionQ.\n\t\t\tPrefix(\"(\").\n\t\t\tSuffix(\") UNION (\"+channelMembersSQL+\")\", channelMembersArgs...)\n\t\tif includePublicBoards {\n\t\t\tunionQ = unionQ.Suffix(\" UNION (\"+teamMembersSQL+\")\", teamMembersArgs...)\n\t\t}\n\t} else if includePublicBoards {\n\t\tunionQ = unionQ.\n\t\t\tPrefix(\"(\").\n\t\t\tSuffix(\") UNION (\"+teamMembersSQL+\")\", teamMembersArgs...)\n\t}\n\n\tunionSQL, unionArgs, err := unionQ.ToSql()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"SearchBoardsForUser error getting unionSQL: %w\", err)\n\t}\n\n\t// if we're using postgres or sqlite, we need to replace the\n\t// question mark placeholder with the numbered dollar one, now\n\t// that the full query is built\n\tif s.dbType == model.PostgresDBType || s.dbType == model.SqliteDBType {\n\t\tvar rErr error\n\t\tunionSQL, rErr = sq.Dollar.ReplacePlaceholders(unionSQL)\n\t\tif rErr != nil {\n\t\t\treturn nil, fmt.Errorf(\"SearchBoardsForUser unable to replace unionSQL placeholders: %w\", rErr)\n\t\t}\n\t}\n\n\trows, err := s.mmDB.Query(unionSQL, unionArgs...)\n\tif err != nil {\n\t\ts.logger.Error(`searchBoardsForUser ERROR`, mlog.Err(err))\n\t\treturn nil, err\n\t}\n\tdefer s.CloseRows(rows)\n\n\treturn s.boardsFromRows(rows, false)\n}\n\n// searchBoardsForUserInTeam returns all boards that match with the\n// term that are either private and which the user is a member of, or\n// they're open, regardless of the user membership.\n// Search is case-insensitive.\nfunc (s *MattermostAuthLayer) SearchBoardsForUserInTeam(teamID, term, userID string) ([]*model.Board, error) {\n\t// as we're joining three queries, we need to avoid numbered\n\t// placeholders until the join is done, so we use the default\n\t// question mark placeholder here\n\tbuilder := s.getQueryBuilder().PlaceholderFormat(sq.Question)\n\n\topenBoardsQ := builder.\n\t\tSelect(boardFields(\"b.\")...).\n\t\tFrom(s.tablePrefix + \"boards as b\").\n\t\tWhere(sq.Eq{\n\t\t\t\"b.is_template\": false,\n\t\t\t\"b.team_id\":     teamID,\n\t\t\t\"b.type\":        model.BoardTypeOpen,\n\t\t})\n\n\tmemberBoardsQ := builder.\n\t\tSelect(boardFields(\"b.\")...).\n\t\tFrom(s.tablePrefix + \"boards AS b\").\n\t\tJoin(s.tablePrefix + \"board_members AS bm on b.id = bm.board_id\").\n\t\tWhere(sq.Eq{\n\t\t\t\"b.is_template\": false,\n\t\t\t\"b.team_id\":     teamID,\n\t\t\t\"bm.user_id\":    userID,\n\t\t})\n\n\tchannelMemberBoardsQ := builder.\n\t\tSelect(boardFields(\"b.\")...).\n\t\tFrom(s.tablePrefix + \"boards AS b\").\n\t\tJoin(\"ChannelMembers AS cm on cm.channelId = b.channel_id\").\n\t\tWhere(sq.Eq{\n\t\t\t\"b.is_template\": false,\n\t\t\t\"b.team_id\":     teamID,\n\t\t\t\"cm.userId\":     userID,\n\t\t})\n\n\tif term != \"\" {\n\t\t// break search query into space separated words\n\t\t// and search for all words.\n\t\t// This should later be upgraded to industrial-strength\n\t\t// word tokenizer, that uses much more than space\n\t\t// to break words.\n\n\t\tconditions := sq.And{}\n\n\t\tfor _, word := range strings.Split(strings.TrimSpace(term), \" \") {\n\t\t\tconditions = append(conditions, sq.Like{\"lower(b.title)\": \"%\" + strings.ToLower(word) + \"%\"})\n\t\t}\n\n\t\topenBoardsQ = openBoardsQ.Where(conditions)\n\t\tmemberBoardsQ = memberBoardsQ.Where(conditions)\n\t\tchannelMemberBoardsQ = channelMemberBoardsQ.Where(conditions)\n\t}\n\n\tmemberBoardsSQL, memberBoardsArgs, err := memberBoardsQ.ToSql()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"SearchBoardsForUserInTeam error getting memberBoardsSQL: %w\", err)\n\t}\n\n\tchannelMemberBoardsSQL, channelMemberBoardsArgs, err := channelMemberBoardsQ.ToSql()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"SearchBoardsForUserInTeam error getting channelMemberBoardsSQL: %w\", err)\n\t}\n\n\tunionQ := openBoardsQ.\n\t\tPrefix(\"(\").\n\t\tSuffix(\") UNION (\"+memberBoardsSQL, memberBoardsArgs...).\n\t\tSuffix(\") UNION (\"+channelMemberBoardsSQL+\")\", channelMemberBoardsArgs...)\n\n\tunionSQL, unionArgs, err := unionQ.ToSql()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"SearchBoardsForUserInTeam error getting unionSQL: %w\", err)\n\t}\n\n\t// if we're using postgres or sqlite, we need to replace the\n\t// question mark placeholder with the numbered dollar one, now\n\t// that the full query is built\n\tif s.dbType == model.PostgresDBType || s.dbType == model.SqliteDBType {\n\t\tvar rErr error\n\t\tunionSQL, rErr = sq.Dollar.ReplacePlaceholders(unionSQL)\n\t\tif rErr != nil {\n\t\t\treturn nil, fmt.Errorf(\"SearchBoardsForUserInTeam unable to replace unionSQL placeholders: %w\", rErr)\n\t\t}\n\t}\n\n\trows, err := s.mmDB.Query(unionSQL, unionArgs...)\n\tif err != nil {\n\t\ts.logger.Error(`searchBoardsForUserInTeam ERROR`, mlog.Err(err))\n\t\treturn nil, err\n\t}\n\tdefer s.CloseRows(rows)\n\n\treturn s.boardsFromRows(rows, false)\n}\n\nfunc (s *MattermostAuthLayer) boardsFromRows(rows *sql.Rows, removeDuplicates bool) ([]*model.Board, error) {\n\tboards := []*model.Board{}\n\tidMap := make(map[string]struct{})\n\n\tfor rows.Next() {\n\t\tvar board model.Board\n\t\tvar propertiesBytes []byte\n\t\tvar cardPropertiesBytes []byte\n\n\t\terr := rows.Scan(\n\t\t\t&board.ID,\n\t\t\t&board.TeamID,\n\t\t\t&board.ChannelID,\n\t\t\t&board.CreatedBy,\n\t\t\t&board.ModifiedBy,\n\t\t\t&board.Type,\n\t\t\t&board.MinimumRole,\n\t\t\t&board.Title,\n\t\t\t&board.Description,\n\t\t\t&board.Icon,\n\t\t\t&board.ShowDescription,\n\t\t\t&board.IsTemplate,\n\t\t\t&board.TemplateVersion,\n\t\t\t&propertiesBytes,\n\t\t\t&cardPropertiesBytes,\n\t\t\t&board.CreateAt,\n\t\t\t&board.UpdateAt,\n\t\t\t&board.DeleteAt,\n\t\t)\n\t\tif err != nil {\n\t\t\ts.logger.Error(\"boardsFromRows scan error\", mlog.Err(err))\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif removeDuplicates {\n\t\t\tif _, ok := idMap[board.ID]; ok {\n\t\t\t\tcontinue\n\t\t\t} else {\n\t\t\t\tidMap[board.ID] = struct{}{}\n\t\t\t}\n\t\t}\n\n\t\terr = json.Unmarshal(propertiesBytes, &board.Properties)\n\t\tif err != nil {\n\t\t\ts.logger.Error(\"board properties unmarshal error\", mlog.Err(err))\n\t\t\treturn nil, err\n\t\t}\n\t\terr = json.Unmarshal(cardPropertiesBytes, &board.CardProperties)\n\t\tif err != nil {\n\t\t\ts.logger.Error(\"board card properties unmarshal error\", mlog.Err(err))\n\t\t\treturn nil, err\n\t\t}\n\n\t\tboards = append(boards, &board)\n\t}\n\n\treturn boards, nil\n}\n\nfunc (s *MattermostAuthLayer) implicitBoardMembershipsFromRows(rows *sql.Rows) ([]*model.BoardMember, error) {\n\tboardMembers := []*model.BoardMember{}\n\n\tfor rows.Next() {\n\t\tvar boardMember model.BoardMember\n\n\t\terr := rows.Scan(\n\t\t\t&boardMember.UserID,\n\t\t\t&boardMember.BoardID,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tboardMember.Roles = \"editor\"\n\t\tboardMember.SchemeEditor = true\n\t\tboardMember.Synthetic = true\n\n\t\tboardMembers = append(boardMembers, &boardMember)\n\t}\n\n\treturn boardMembers, nil\n}\n\nfunc (s *MattermostAuthLayer) GetMemberForBoard(boardID, userID string) (*model.BoardMember, error) {\n\tbm, originalErr := s.Store.GetMemberForBoard(boardID, userID)\n\t// Explicit membership not found\n\tif model.IsErrNotFound(originalErr) {\n\t\tif userID == model.SystemUserID {\n\t\t\treturn nil, model.NewErrNotFound(userID)\n\t\t}\n\t\tvar user *model.User\n\t\t// No synthetic memberships for guests\n\t\tuser, err := s.GetUserByID(userID)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif user.IsGuest {\n\t\t\treturn nil, model.NewErrNotFound(\"user is a guest\")\n\t\t}\n\n\t\tb, boardErr := s.Store.GetBoard(boardID)\n\t\tif boardErr != nil {\n\t\t\treturn nil, boardErr\n\t\t}\n\t\tif b.ChannelID != \"\" {\n\t\t\t_, memberErr := s.servicesAPI.GetChannelMember(b.ChannelID, userID)\n\t\t\tif memberErr != nil {\n\t\t\t\tvar appErr *mmModel.AppError\n\t\t\t\tif errors.As(memberErr, &appErr) && appErr.StatusCode == http.StatusNotFound {\n\t\t\t\t\t// Plugin API returns error if channel member doesn't exist.\n\t\t\t\t\t// We're fine if it doesn't exist, so its not an error for us.\n\t\t\t\t\tmessage := fmt.Sprintf(\"member BoardID=%s UserID=%s\", boardID, userID)\n\t\t\t\t\treturn nil, model.NewErrNotFound(message)\n\t\t\t\t}\n\n\t\t\t\treturn nil, memberErr\n\t\t\t}\n\n\t\t\treturn &model.BoardMember{\n\t\t\t\tBoardID:         boardID,\n\t\t\t\tUserID:          userID,\n\t\t\t\tRoles:           \"editor\",\n\t\t\t\tSchemeAdmin:     false,\n\t\t\t\tSchemeEditor:    true,\n\t\t\t\tSchemeCommenter: false,\n\t\t\t\tSchemeViewer:    false,\n\t\t\t\tSynthetic:       true,\n\t\t\t}, nil\n\t\t}\n\t\tif b.Type == model.BoardTypeOpen && b.IsTemplate {\n\t\t\t_, memberErr := s.servicesAPI.GetTeamMember(b.TeamID, userID)\n\t\t\tif memberErr != nil {\n\t\t\t\tvar appErr *mmModel.AppError\n\t\t\t\tif errors.As(memberErr, &appErr) && appErr.StatusCode == http.StatusNotFound {\n\t\t\t\t\treturn nil, model.NewErrNotFound(userID)\n\t\t\t\t}\n\t\t\t\treturn nil, memberErr\n\t\t\t}\n\n\t\t\treturn &model.BoardMember{\n\t\t\t\tBoardID:         boardID,\n\t\t\t\tUserID:          userID,\n\t\t\t\tRoles:           \"viewer\",\n\t\t\t\tSchemeAdmin:     false,\n\t\t\t\tSchemeEditor:    false,\n\t\t\t\tSchemeCommenter: false,\n\t\t\t\tSchemeViewer:    true,\n\t\t\t\tSynthetic:       true,\n\t\t\t}, nil\n\t\t}\n\t}\n\tif originalErr != nil {\n\t\treturn nil, originalErr\n\t}\n\treturn bm, nil\n}\n\nfunc (s *MattermostAuthLayer) GetMembersForUser(userID string) ([]*model.BoardMember, error) {\n\texplicitMembers, err := s.Store.GetMembersForUser(userID)\n\tif err != nil {\n\t\ts.logger.Error(`getMembersForUser ERROR`, mlog.Err(err))\n\t\treturn nil, err\n\t}\n\n\tquery := s.getQueryBuilder().\n\t\tSelect(\"CM.userID, B.Id\").\n\t\tFrom(s.tablePrefix + \"boards AS B\").\n\t\tJoin(\"ChannelMembers AS CM ON B.channel_id=CM.channelId\").\n\t\tWhere(sq.Eq{\"CM.userID\": userID})\n\n\trows, err := query.Query()\n\tif err != nil {\n\t\ts.logger.Error(`getMembersForUser ERROR`, mlog.Err(err))\n\t\treturn nil, err\n\t}\n\tdefer s.CloseRows(rows)\n\n\tmembers := []*model.BoardMember{}\n\texistingMembers := map[string]bool{}\n\tfor _, m := range explicitMembers {\n\t\tmembers = append(members, m)\n\t\texistingMembers[m.BoardID] = true\n\t}\n\n\t// No synthetic memberships for guests\n\tuser, err := s.GetUserByID(userID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif user.IsGuest {\n\t\treturn members, nil\n\t}\n\n\timplicitMembers, err := s.implicitBoardMembershipsFromRows(rows)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor _, m := range implicitMembers {\n\t\tif !existingMembers[m.BoardID] {\n\t\t\tmembers = append(members, m)\n\t\t}\n\t}\n\n\treturn members, nil\n}\n\nfunc (s *MattermostAuthLayer) GetMembersForBoard(boardID string) ([]*model.BoardMember, error) {\n\texplicitMembers, err := s.Store.GetMembersForBoard(boardID)\n\tif err != nil {\n\t\ts.logger.Error(`getMembersForBoard ERROR`, mlog.Err(err))\n\t\treturn nil, err\n\t}\n\n\tquery := s.getQueryBuilder().\n\t\tSelect(\"CM.userID, B.Id\").\n\t\tFrom(s.tablePrefix + \"boards AS B\").\n\t\tJoin(\"ChannelMembers AS CM ON B.channel_id=CM.channelId\").\n\t\tJoin(\"Users as U on CM.userID = U.id\").\n\t\tLeftJoin(\"Bots as bo on U.id = bo.UserID\").\n\t\tWhere(sq.Eq{\"B.id\": boardID}).\n\t\tWhere(sq.NotEq{\"B.channel_id\": \"\"}).\n\t\t// Filter out guests as they don't have synthetic membership\n\t\tWhere(sq.NotEq{\"U.roles\": \"system_guest\"}).\n\t\tWhere(sq.Eq{\"bo.UserId IS NOT NULL\": false})\n\n\trows, err := query.Query()\n\tif err != nil {\n\t\ts.logger.Error(`getMembersForBoard ERROR`, mlog.Err(err))\n\t\treturn nil, err\n\t}\n\tdefer s.CloseRows(rows)\n\n\timplicitMembers, err := s.implicitBoardMembershipsFromRows(rows)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tmembers := []*model.BoardMember{}\n\texistingMembers := map[string]bool{}\n\tfor _, m := range explicitMembers {\n\t\tmembers = append(members, m)\n\t\texistingMembers[m.UserID] = true\n\t}\n\tfor _, m := range implicitMembers {\n\t\tif !existingMembers[m.UserID] {\n\t\t\tmembers = append(members, m)\n\t\t}\n\t}\n\n\treturn members, nil\n}\n\nfunc (s *MattermostAuthLayer) GetBoardsForUserAndTeam(userID, teamID string, includePublicBoards bool) ([]*model.Board, error) {\n\tif includePublicBoards {\n\t\tboards, err := s.SearchBoardsForUserInTeam(teamID, \"\", userID)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn boards, nil\n\t}\n\n\t// retrieve only direct memberships for user\n\t// this is usually done for guests.\n\tmembers, err := s.GetMembersForUser(userID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tboardIDs := []string{}\n\tfor _, m := range members {\n\t\tboardIDs = append(boardIDs, m.BoardID)\n\t}\n\n\tboards, err := s.Store.GetBoardsInTeamByIds(boardIDs, teamID)\n\tif model.IsErrNotFound(err) {\n\t\tif boards == nil {\n\t\t\tboards = []*model.Board{}\n\t\t}\n\t\treturn boards, nil\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn boards, nil\n}\n\nfunc (s *MattermostAuthLayer) SearchUserChannels(teamID, userID, query string) ([]*mmModel.Channel, error) {\n\tchannels, err := s.servicesAPI.GetChannelsForTeamForUser(teamID, userID, false)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tlowerQuery := strings.ToLower(query)\n\n\tresult := []*mmModel.Channel{}\n\tcount := 0\n\tfor _, channel := range channels {\n\t\tif channel.Type != mmModel.ChannelTypeDirect &&\n\t\t\tchannel.Type != mmModel.ChannelTypeGroup &&\n\t\t\t(strings.Contains(strings.ToLower(channel.Name), lowerQuery) || strings.Contains(strings.ToLower(channel.DisplayName), lowerQuery)) {\n\t\t\tresult = append(result, channel)\n\t\t\tcount++\n\t\t\tif count >= 10 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\treturn result, nil\n}\n\nfunc (s *MattermostAuthLayer) GetChannel(teamID, channelID string) (*mmModel.Channel, error) {\n\tchannel, err := s.servicesAPI.GetChannelByID(channelID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn channel, nil\n}\n\nfunc (s *MattermostAuthLayer) getBoardsBotID() (string, error) {\n\tif boardsBotID == \"\" {\n\t\tvar err error\n\t\tboardsBotID, err = s.servicesAPI.EnsureBot(model.FocalboardBot)\n\t\tif err != nil {\n\t\t\ts.logger.Error(\"failed to ensure boards bot\", mlog.Err(err))\n\t\t\treturn \"\", err\n\t\t}\n\t}\n\treturn boardsBotID, nil\n}\n\nfunc (s *MattermostAuthLayer) SendMessage(message, postType string, receipts []string) error {\n\tbotID, err := s.getBoardsBotID()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, receipt := range receipts {\n\t\tchannel, err := s.servicesAPI.GetDirectChannel(botID, receipt)\n\t\tif err != nil {\n\t\t\ts.logger.Error(\n\t\t\t\t\"failed to get DM channel between system bot and user for receipt\",\n\t\t\t\tmlog.String(\"receipt\", receipt),\n\t\t\t\tmlog.String(\"user_id\", receipt),\n\t\t\t\tmlog.Err(err),\n\t\t\t)\n\t\t\tcontinue\n\t\t}\n\n\t\tif err := s.PostMessage(message, postType, channel.Id); err != nil {\n\t\t\ts.logger.Error(\n\t\t\t\t\"failed to send message to receipt from SendMessage\",\n\t\t\t\tmlog.String(\"receipt\", receipt),\n\t\t\t\tmlog.Err(err),\n\t\t\t)\n\t\t\tcontinue\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (s *MattermostAuthLayer) PostMessage(message, postType, channelID string) error {\n\tbotID, err := s.getBoardsBotID()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tpost := &mmModel.Post{\n\t\tMessage:   message,\n\t\tUserId:    botID,\n\t\tChannelId: channelID,\n\t\tType:      postType,\n\t}\n\n\tif _, err := s.servicesAPI.CreatePost(post); err != nil {\n\t\ts.logger.Error(\n\t\t\t\"failed to send message to receipt from PostMessage\",\n\t\t\tmlog.Err(err),\n\t\t)\n\t}\n\treturn nil\n}\n\nfunc (s *MattermostAuthLayer) GetUserTimezone(userID string) (string, error) {\n\tuser, err := s.servicesAPI.GetUserByID(userID)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\ttimezone := user.Timezone\n\treturn mmModel.GetPreferredTimezone(timezone), nil\n}\n\nfunc (s *MattermostAuthLayer) CanSeeUser(seerID string, seenID string) (bool, error) {\n\tmmuser, appErr := s.servicesAPI.GetUserByID(seerID)\n\tif appErr != nil {\n\t\treturn false, appErr\n\t}\n\tif !mmuser.IsGuest() {\n\t\treturn true, nil\n\t}\n\n\tquery := s.getQueryBuilder().\n\t\tSelect(\"1\").\n\t\tFrom(s.tablePrefix + \"board_members AS bm1\").\n\t\tJoin(s.tablePrefix + \"board_members AS bm2 ON bm1.board_id=bm2.board_id\").\n\t\tWhere(sq.Or{\n\t\t\tsq.And{\n\t\t\t\tsq.Eq{\"bm1.user_id\": seerID},\n\t\t\t\tsq.Eq{\"bm2.user_id\": seenID},\n\t\t\t},\n\t\t\tsq.And{\n\t\t\t\tsq.Eq{\"bm1.user_id\": seenID},\n\t\t\t\tsq.Eq{\"bm2.user_id\": seerID},\n\t\t\t},\n\t\t}).Limit(1)\n\n\trows, err := query.Query()\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer s.CloseRows(rows)\n\n\tfor rows.Next() {\n\t\treturn true, err\n\t}\n\n\tquery = s.getQueryBuilder().\n\t\tSelect(\"1\").\n\t\tFrom(\"channelmembers AS cm1\").\n\t\tJoin(\"channelmembers AS cm2 ON cm1.channelid=cm2.channelid\").\n\t\tWhere(sq.Or{\n\t\t\tsq.And{\n\t\t\t\tsq.Eq{\"cm1.userid\": seerID},\n\t\t\t\tsq.Eq{\"cm2.userid\": seenID},\n\t\t\t},\n\t\t\tsq.And{\n\t\t\t\tsq.Eq{\"cm1.userid\": seenID},\n\t\t\t\tsq.Eq{\"cm2.userid\": seerID},\n\t\t\t},\n\t\t}).Limit(1)\n\n\trows, err = query.Query()\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer s.CloseRows(rows)\n\n\tfor rows.Next() {\n\t\treturn true, err\n\t}\n\n\treturn false, nil\n}\n"
  },
  {
    "path": "server/services/store/mattermostauthlayer/mattermostauthlayer_test.go",
    "content": "package mattermostauthlayer\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/golang/mock/gomock\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\tmockservicesapi \"github.com/mattermost/focalboard/server/model/mocks\"\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nvar errTest = errors.New(\"failed to patch bot\")\n\nfunc TestGetBoardsBotID(t *testing.T) {\n\tctrl := gomock.NewController(t)\n\tservicesAPI := mockservicesapi.NewMockServicesAPI(ctrl)\n\tmmAuthLayer, _ := New(\"test\", nil, nil, mlog.CreateConsoleTestLogger(t), servicesAPI, \"\")\n\n\tservicesAPI.EXPECT().EnsureBot(model.FocalboardBot).Return(\"\", errTest)\n\t_, err := mmAuthLayer.getBoardsBotID()\n\trequire.NotEmpty(t, err)\n\n\tservicesAPI.EXPECT().EnsureBot(model.FocalboardBot).Return(\"TestBotID\", nil).Times(1)\n\tbotID, err := mmAuthLayer.getBoardsBotID()\n\trequire.Empty(t, err)\n\trequire.NotEmpty(t, botID)\n\trequire.Equal(t, \"TestBotID\", botID)\n\n\t// Call again, should not call \"EnsureBot\"\n\tbotID, err = mmAuthLayer.getBoardsBotID()\n\trequire.Empty(t, err)\n\trequire.NotEmpty(t, botID)\n\trequire.Equal(t, \"TestBotID\", botID)\n}\n"
  },
  {
    "path": "server/services/store/mockstore/mockstore.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: github.com/mattermost/focalboard/server/services/store (interfaces: Store)\n\n// Package mockstore is a generated GoMock package.\npackage mockstore\n\nimport (\n\treflect \"reflect\"\n\ttime \"time\"\n\n\tgomock \"github.com/golang/mock/gomock\"\n\tmodel \"github.com/mattermost/focalboard/server/model\"\n\tmodel0 \"github.com/mattermost/mattermost/server/public/model\"\n)\n\n// MockStore is a mock of Store interface.\ntype MockStore struct {\n\tctrl     *gomock.Controller\n\trecorder *MockStoreMockRecorder\n}\n\n// MockStoreMockRecorder is the mock recorder for MockStore.\ntype MockStoreMockRecorder struct {\n\tmock *MockStore\n}\n\n// NewMockStore creates a new mock instance.\nfunc NewMockStore(ctrl *gomock.Controller) *MockStore {\n\tmock := &MockStore{ctrl: ctrl}\n\tmock.recorder = &MockStoreMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockStore) EXPECT() *MockStoreMockRecorder {\n\treturn m.recorder\n}\n\n// AddUpdateCategoryBoard mocks base method.\nfunc (m *MockStore) AddUpdateCategoryBoard(arg0, arg1 string, arg2 []string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"AddUpdateCategoryBoard\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// AddUpdateCategoryBoard indicates an expected call of AddUpdateCategoryBoard.\nfunc (mr *MockStoreMockRecorder) AddUpdateCategoryBoard(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"AddUpdateCategoryBoard\", reflect.TypeOf((*MockStore)(nil).AddUpdateCategoryBoard), arg0, arg1, arg2)\n}\n\n// CanSeeUser mocks base method.\nfunc (m *MockStore) CanSeeUser(arg0, arg1 string) (bool, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"CanSeeUser\", arg0, arg1)\n\tret0, _ := ret[0].(bool)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// CanSeeUser indicates an expected call of CanSeeUser.\nfunc (mr *MockStoreMockRecorder) CanSeeUser(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"CanSeeUser\", reflect.TypeOf((*MockStore)(nil).CanSeeUser), arg0, arg1)\n}\n\n// CleanUpSessions mocks base method.\nfunc (m *MockStore) CleanUpSessions(arg0 int64) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"CleanUpSessions\", arg0)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// CleanUpSessions indicates an expected call of CleanUpSessions.\nfunc (mr *MockStoreMockRecorder) CleanUpSessions(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"CleanUpSessions\", reflect.TypeOf((*MockStore)(nil).CleanUpSessions), arg0)\n}\n\n// CreateBoardsAndBlocks mocks base method.\nfunc (m *MockStore) CreateBoardsAndBlocks(arg0 *model.BoardsAndBlocks, arg1 string) (*model.BoardsAndBlocks, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"CreateBoardsAndBlocks\", arg0, arg1)\n\tret0, _ := ret[0].(*model.BoardsAndBlocks)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// CreateBoardsAndBlocks indicates an expected call of CreateBoardsAndBlocks.\nfunc (mr *MockStoreMockRecorder) CreateBoardsAndBlocks(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"CreateBoardsAndBlocks\", reflect.TypeOf((*MockStore)(nil).CreateBoardsAndBlocks), arg0, arg1)\n}\n\n// CreateBoardsAndBlocksWithAdmin mocks base method.\nfunc (m *MockStore) CreateBoardsAndBlocksWithAdmin(arg0 *model.BoardsAndBlocks, arg1 string) (*model.BoardsAndBlocks, []*model.BoardMember, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"CreateBoardsAndBlocksWithAdmin\", arg0, arg1)\n\tret0, _ := ret[0].(*model.BoardsAndBlocks)\n\tret1, _ := ret[1].([]*model.BoardMember)\n\tret2, _ := ret[2].(error)\n\treturn ret0, ret1, ret2\n}\n\n// CreateBoardsAndBlocksWithAdmin indicates an expected call of CreateBoardsAndBlocksWithAdmin.\nfunc (mr *MockStoreMockRecorder) CreateBoardsAndBlocksWithAdmin(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"CreateBoardsAndBlocksWithAdmin\", reflect.TypeOf((*MockStore)(nil).CreateBoardsAndBlocksWithAdmin), arg0, arg1)\n}\n\n// CreateCategory mocks base method.\nfunc (m *MockStore) CreateCategory(arg0 model.Category) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"CreateCategory\", arg0)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// CreateCategory indicates an expected call of CreateCategory.\nfunc (mr *MockStoreMockRecorder) CreateCategory(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"CreateCategory\", reflect.TypeOf((*MockStore)(nil).CreateCategory), arg0)\n}\n\n// CreateSession mocks base method.\nfunc (m *MockStore) CreateSession(arg0 *model.Session) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"CreateSession\", arg0)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// CreateSession indicates an expected call of CreateSession.\nfunc (mr *MockStoreMockRecorder) CreateSession(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"CreateSession\", reflect.TypeOf((*MockStore)(nil).CreateSession), arg0)\n}\n\n// CreateSubscription mocks base method.\nfunc (m *MockStore) CreateSubscription(arg0 *model.Subscription) (*model.Subscription, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"CreateSubscription\", arg0)\n\tret0, _ := ret[0].(*model.Subscription)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// CreateSubscription indicates an expected call of CreateSubscription.\nfunc (mr *MockStoreMockRecorder) CreateSubscription(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"CreateSubscription\", reflect.TypeOf((*MockStore)(nil).CreateSubscription), arg0)\n}\n\n// CreateUser mocks base method.\nfunc (m *MockStore) CreateUser(arg0 *model.User) (*model.User, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"CreateUser\", arg0)\n\tret0, _ := ret[0].(*model.User)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// CreateUser indicates an expected call of CreateUser.\nfunc (mr *MockStoreMockRecorder) CreateUser(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"CreateUser\", reflect.TypeOf((*MockStore)(nil).CreateUser), arg0)\n}\n\n// DBType mocks base method.\nfunc (m *MockStore) DBType() string {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"DBType\")\n\tret0, _ := ret[0].(string)\n\treturn ret0\n}\n\n// DBType indicates an expected call of DBType.\nfunc (mr *MockStoreMockRecorder) DBType() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"DBType\", reflect.TypeOf((*MockStore)(nil).DBType))\n}\n\n// DBVersion mocks base method.\nfunc (m *MockStore) DBVersion() string {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"DBVersion\")\n\tret0, _ := ret[0].(string)\n\treturn ret0\n}\n\n// DBVersion indicates an expected call of DBVersion.\nfunc (mr *MockStoreMockRecorder) DBVersion() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"DBVersion\", reflect.TypeOf((*MockStore)(nil).DBVersion))\n}\n\n// DeleteBlock mocks base method.\nfunc (m *MockStore) DeleteBlock(arg0, arg1 string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"DeleteBlock\", arg0, arg1)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// DeleteBlock indicates an expected call of DeleteBlock.\nfunc (mr *MockStoreMockRecorder) DeleteBlock(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"DeleteBlock\", reflect.TypeOf((*MockStore)(nil).DeleteBlock), arg0, arg1)\n}\n\n// DeleteBlockRecord mocks base method.\nfunc (m *MockStore) DeleteBlockRecord(arg0, arg1 string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"DeleteBlockRecord\", arg0, arg1)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// DeleteBlockRecord indicates an expected call of DeleteBlockRecord.\nfunc (mr *MockStoreMockRecorder) DeleteBlockRecord(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"DeleteBlockRecord\", reflect.TypeOf((*MockStore)(nil).DeleteBlockRecord), arg0, arg1)\n}\n\n// DeleteBoard mocks base method.\nfunc (m *MockStore) DeleteBoard(arg0, arg1 string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"DeleteBoard\", arg0, arg1)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// DeleteBoard indicates an expected call of DeleteBoard.\nfunc (mr *MockStoreMockRecorder) DeleteBoard(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"DeleteBoard\", reflect.TypeOf((*MockStore)(nil).DeleteBoard), arg0, arg1)\n}\n\n// DeleteBoardRecord mocks base method.\nfunc (m *MockStore) DeleteBoardRecord(arg0, arg1 string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"DeleteBoardRecord\", arg0, arg1)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// DeleteBoardRecord indicates an expected call of DeleteBoardRecord.\nfunc (mr *MockStoreMockRecorder) DeleteBoardRecord(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"DeleteBoardRecord\", reflect.TypeOf((*MockStore)(nil).DeleteBoardRecord), arg0, arg1)\n}\n\n// DeleteBoardsAndBlocks mocks base method.\nfunc (m *MockStore) DeleteBoardsAndBlocks(arg0 *model.DeleteBoardsAndBlocks, arg1 string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"DeleteBoardsAndBlocks\", arg0, arg1)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// DeleteBoardsAndBlocks indicates an expected call of DeleteBoardsAndBlocks.\nfunc (mr *MockStoreMockRecorder) DeleteBoardsAndBlocks(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"DeleteBoardsAndBlocks\", reflect.TypeOf((*MockStore)(nil).DeleteBoardsAndBlocks), arg0, arg1)\n}\n\n// DeleteCategory mocks base method.\nfunc (m *MockStore) DeleteCategory(arg0, arg1, arg2 string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"DeleteCategory\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// DeleteCategory indicates an expected call of DeleteCategory.\nfunc (mr *MockStoreMockRecorder) DeleteCategory(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"DeleteCategory\", reflect.TypeOf((*MockStore)(nil).DeleteCategory), arg0, arg1, arg2)\n}\n\n// DeleteMember mocks base method.\nfunc (m *MockStore) DeleteMember(arg0, arg1 string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"DeleteMember\", arg0, arg1)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// DeleteMember indicates an expected call of DeleteMember.\nfunc (mr *MockStoreMockRecorder) DeleteMember(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"DeleteMember\", reflect.TypeOf((*MockStore)(nil).DeleteMember), arg0, arg1)\n}\n\n// DeleteNotificationHint mocks base method.\nfunc (m *MockStore) DeleteNotificationHint(arg0 string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"DeleteNotificationHint\", arg0)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// DeleteNotificationHint indicates an expected call of DeleteNotificationHint.\nfunc (mr *MockStoreMockRecorder) DeleteNotificationHint(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"DeleteNotificationHint\", reflect.TypeOf((*MockStore)(nil).DeleteNotificationHint), arg0)\n}\n\n// DeleteSession mocks base method.\nfunc (m *MockStore) DeleteSession(arg0 string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"DeleteSession\", arg0)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// DeleteSession indicates an expected call of DeleteSession.\nfunc (mr *MockStoreMockRecorder) DeleteSession(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"DeleteSession\", reflect.TypeOf((*MockStore)(nil).DeleteSession), arg0)\n}\n\n// DeleteSubscription mocks base method.\nfunc (m *MockStore) DeleteSubscription(arg0, arg1 string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"DeleteSubscription\", arg0, arg1)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// DeleteSubscription indicates an expected call of DeleteSubscription.\nfunc (mr *MockStoreMockRecorder) DeleteSubscription(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"DeleteSubscription\", reflect.TypeOf((*MockStore)(nil).DeleteSubscription), arg0, arg1)\n}\n\n// DuplicateBlock mocks base method.\nfunc (m *MockStore) DuplicateBlock(arg0, arg1, arg2 string, arg3 bool) ([]*model.Block, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"DuplicateBlock\", arg0, arg1, arg2, arg3)\n\tret0, _ := ret[0].([]*model.Block)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// DuplicateBlock indicates an expected call of DuplicateBlock.\nfunc (mr *MockStoreMockRecorder) DuplicateBlock(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"DuplicateBlock\", reflect.TypeOf((*MockStore)(nil).DuplicateBlock), arg0, arg1, arg2, arg3)\n}\n\n// DuplicateBoard mocks base method.\nfunc (m *MockStore) DuplicateBoard(arg0, arg1, arg2 string, arg3 bool) (*model.BoardsAndBlocks, []*model.BoardMember, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"DuplicateBoard\", arg0, arg1, arg2, arg3)\n\tret0, _ := ret[0].(*model.BoardsAndBlocks)\n\tret1, _ := ret[1].([]*model.BoardMember)\n\tret2, _ := ret[2].(error)\n\treturn ret0, ret1, ret2\n}\n\n// DuplicateBoard indicates an expected call of DuplicateBoard.\nfunc (mr *MockStoreMockRecorder) DuplicateBoard(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"DuplicateBoard\", reflect.TypeOf((*MockStore)(nil).DuplicateBoard), arg0, arg1, arg2, arg3)\n}\n\n// GetActiveUserCount mocks base method.\nfunc (m *MockStore) GetActiveUserCount(arg0 int64) (int, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetActiveUserCount\", arg0)\n\tret0, _ := ret[0].(int)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetActiveUserCount indicates an expected call of GetActiveUserCount.\nfunc (mr *MockStoreMockRecorder) GetActiveUserCount(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetActiveUserCount\", reflect.TypeOf((*MockStore)(nil).GetActiveUserCount), arg0)\n}\n\n// GetAllTeams mocks base method.\nfunc (m *MockStore) GetAllTeams() ([]*model.Team, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetAllTeams\")\n\tret0, _ := ret[0].([]*model.Team)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetAllTeams indicates an expected call of GetAllTeams.\nfunc (mr *MockStoreMockRecorder) GetAllTeams() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetAllTeams\", reflect.TypeOf((*MockStore)(nil).GetAllTeams))\n}\n\n// GetBlock mocks base method.\nfunc (m *MockStore) GetBlock(arg0 string) (*model.Block, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetBlock\", arg0)\n\tret0, _ := ret[0].(*model.Block)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetBlock indicates an expected call of GetBlock.\nfunc (mr *MockStoreMockRecorder) GetBlock(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetBlock\", reflect.TypeOf((*MockStore)(nil).GetBlock), arg0)\n}\n\n// GetBlockCountsByType mocks base method.\nfunc (m *MockStore) GetBlockCountsByType() (map[string]int64, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetBlockCountsByType\")\n\tret0, _ := ret[0].(map[string]int64)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetBlockCountsByType indicates an expected call of GetBlockCountsByType.\nfunc (mr *MockStoreMockRecorder) GetBlockCountsByType() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetBlockCountsByType\", reflect.TypeOf((*MockStore)(nil).GetBlockCountsByType))\n}\n\n// GetBlockHistory mocks base method.\nfunc (m *MockStore) GetBlockHistory(arg0 string, arg1 model.QueryBlockHistoryOptions) ([]*model.Block, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetBlockHistory\", arg0, arg1)\n\tret0, _ := ret[0].([]*model.Block)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetBlockHistory indicates an expected call of GetBlockHistory.\nfunc (mr *MockStoreMockRecorder) GetBlockHistory(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetBlockHistory\", reflect.TypeOf((*MockStore)(nil).GetBlockHistory), arg0, arg1)\n}\n\n// GetBlockHistoryDescendants mocks base method.\nfunc (m *MockStore) GetBlockHistoryDescendants(arg0 string, arg1 model.QueryBlockHistoryOptions) ([]*model.Block, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetBlockHistoryDescendants\", arg0, arg1)\n\tret0, _ := ret[0].([]*model.Block)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetBlockHistoryDescendants indicates an expected call of GetBlockHistoryDescendants.\nfunc (mr *MockStoreMockRecorder) GetBlockHistoryDescendants(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetBlockHistoryDescendants\", reflect.TypeOf((*MockStore)(nil).GetBlockHistoryDescendants), arg0, arg1)\n}\n\n// GetBlockHistoryNewestChildren mocks base method.\nfunc (m *MockStore) GetBlockHistoryNewestChildren(arg0 string, arg1 model.QueryBlockHistoryChildOptions) ([]*model.Block, bool, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetBlockHistoryNewestChildren\", arg0, arg1)\n\tret0, _ := ret[0].([]*model.Block)\n\tret1, _ := ret[1].(bool)\n\tret2, _ := ret[2].(error)\n\treturn ret0, ret1, ret2\n}\n\n// GetBlockHistoryNewestChildren indicates an expected call of GetBlockHistoryNewestChildren.\nfunc (mr *MockStoreMockRecorder) GetBlockHistoryNewestChildren(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetBlockHistoryNewestChildren\", reflect.TypeOf((*MockStore)(nil).GetBlockHistoryNewestChildren), arg0, arg1)\n}\n\n// GetBlocks mocks base method.\nfunc (m *MockStore) GetBlocks(arg0 model.QueryBlocksOptions) ([]*model.Block, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetBlocks\", arg0)\n\tret0, _ := ret[0].([]*model.Block)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetBlocks indicates an expected call of GetBlocks.\nfunc (mr *MockStoreMockRecorder) GetBlocks(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetBlocks\", reflect.TypeOf((*MockStore)(nil).GetBlocks), arg0)\n}\n\n// GetBlocksByIDs mocks base method.\nfunc (m *MockStore) GetBlocksByIDs(arg0 []string) ([]*model.Block, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetBlocksByIDs\", arg0)\n\tret0, _ := ret[0].([]*model.Block)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetBlocksByIDs indicates an expected call of GetBlocksByIDs.\nfunc (mr *MockStoreMockRecorder) GetBlocksByIDs(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetBlocksByIDs\", reflect.TypeOf((*MockStore)(nil).GetBlocksByIDs), arg0)\n}\n\n// GetBlocksComplianceHistory mocks base method.\nfunc (m *MockStore) GetBlocksComplianceHistory(arg0 model.QueryBlocksComplianceHistoryOptions) ([]*model.BlockHistory, bool, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetBlocksComplianceHistory\", arg0)\n\tret0, _ := ret[0].([]*model.BlockHistory)\n\tret1, _ := ret[1].(bool)\n\tret2, _ := ret[2].(error)\n\treturn ret0, ret1, ret2\n}\n\n// GetBlocksComplianceHistory indicates an expected call of GetBlocksComplianceHistory.\nfunc (mr *MockStoreMockRecorder) GetBlocksComplianceHistory(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetBlocksComplianceHistory\", reflect.TypeOf((*MockStore)(nil).GetBlocksComplianceHistory), arg0)\n}\n\n// GetBlocksForBoard mocks base method.\nfunc (m *MockStore) GetBlocksForBoard(arg0 string) ([]*model.Block, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetBlocksForBoard\", arg0)\n\tret0, _ := ret[0].([]*model.Block)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetBlocksForBoard indicates an expected call of GetBlocksForBoard.\nfunc (mr *MockStoreMockRecorder) GetBlocksForBoard(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetBlocksForBoard\", reflect.TypeOf((*MockStore)(nil).GetBlocksForBoard), arg0)\n}\n\n// GetBlocksWithParent mocks base method.\nfunc (m *MockStore) GetBlocksWithParent(arg0, arg1 string) ([]*model.Block, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetBlocksWithParent\", arg0, arg1)\n\tret0, _ := ret[0].([]*model.Block)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetBlocksWithParent indicates an expected call of GetBlocksWithParent.\nfunc (mr *MockStoreMockRecorder) GetBlocksWithParent(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetBlocksWithParent\", reflect.TypeOf((*MockStore)(nil).GetBlocksWithParent), arg0, arg1)\n}\n\n// GetBlocksWithParentAndType mocks base method.\nfunc (m *MockStore) GetBlocksWithParentAndType(arg0, arg1, arg2 string) ([]*model.Block, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetBlocksWithParentAndType\", arg0, arg1, arg2)\n\tret0, _ := ret[0].([]*model.Block)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetBlocksWithParentAndType indicates an expected call of GetBlocksWithParentAndType.\nfunc (mr *MockStoreMockRecorder) GetBlocksWithParentAndType(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetBlocksWithParentAndType\", reflect.TypeOf((*MockStore)(nil).GetBlocksWithParentAndType), arg0, arg1, arg2)\n}\n\n// GetBlocksWithType mocks base method.\nfunc (m *MockStore) GetBlocksWithType(arg0, arg1 string) ([]*model.Block, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetBlocksWithType\", arg0, arg1)\n\tret0, _ := ret[0].([]*model.Block)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetBlocksWithType indicates an expected call of GetBlocksWithType.\nfunc (mr *MockStoreMockRecorder) GetBlocksWithType(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetBlocksWithType\", reflect.TypeOf((*MockStore)(nil).GetBlocksWithType), arg0, arg1)\n}\n\n// GetBoard mocks base method.\nfunc (m *MockStore) GetBoard(arg0 string) (*model.Board, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetBoard\", arg0)\n\tret0, _ := ret[0].(*model.Board)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetBoard indicates an expected call of GetBoard.\nfunc (mr *MockStoreMockRecorder) GetBoard(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetBoard\", reflect.TypeOf((*MockStore)(nil).GetBoard), arg0)\n}\n\n// GetBoardAndCard mocks base method.\nfunc (m *MockStore) GetBoardAndCard(arg0 *model.Block) (*model.Board, *model.Block, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetBoardAndCard\", arg0)\n\tret0, _ := ret[0].(*model.Board)\n\tret1, _ := ret[1].(*model.Block)\n\tret2, _ := ret[2].(error)\n\treturn ret0, ret1, ret2\n}\n\n// GetBoardAndCard indicates an expected call of GetBoardAndCard.\nfunc (mr *MockStoreMockRecorder) GetBoardAndCard(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetBoardAndCard\", reflect.TypeOf((*MockStore)(nil).GetBoardAndCard), arg0)\n}\n\n// GetBoardAndCardByID mocks base method.\nfunc (m *MockStore) GetBoardAndCardByID(arg0 string) (*model.Board, *model.Block, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetBoardAndCardByID\", arg0)\n\tret0, _ := ret[0].(*model.Board)\n\tret1, _ := ret[1].(*model.Block)\n\tret2, _ := ret[2].(error)\n\treturn ret0, ret1, ret2\n}\n\n// GetBoardAndCardByID indicates an expected call of GetBoardAndCardByID.\nfunc (mr *MockStoreMockRecorder) GetBoardAndCardByID(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetBoardAndCardByID\", reflect.TypeOf((*MockStore)(nil).GetBoardAndCardByID), arg0)\n}\n\n// GetBoardCount mocks base method.\nfunc (m *MockStore) GetBoardCount() (int64, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetBoardCount\")\n\tret0, _ := ret[0].(int64)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetBoardCount indicates an expected call of GetBoardCount.\nfunc (mr *MockStoreMockRecorder) GetBoardCount() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetBoardCount\", reflect.TypeOf((*MockStore)(nil).GetBoardCount))\n}\n\n// GetBoardHistory mocks base method.\nfunc (m *MockStore) GetBoardHistory(arg0 string, arg1 model.QueryBoardHistoryOptions) ([]*model.Board, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetBoardHistory\", arg0, arg1)\n\tret0, _ := ret[0].([]*model.Board)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetBoardHistory indicates an expected call of GetBoardHistory.\nfunc (mr *MockStoreMockRecorder) GetBoardHistory(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetBoardHistory\", reflect.TypeOf((*MockStore)(nil).GetBoardHistory), arg0, arg1)\n}\n\n// GetBoardMemberHistory mocks base method.\nfunc (m *MockStore) GetBoardMemberHistory(arg0, arg1 string, arg2 uint64) ([]*model.BoardMemberHistoryEntry, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetBoardMemberHistory\", arg0, arg1, arg2)\n\tret0, _ := ret[0].([]*model.BoardMemberHistoryEntry)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetBoardMemberHistory indicates an expected call of GetBoardMemberHistory.\nfunc (mr *MockStoreMockRecorder) GetBoardMemberHistory(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetBoardMemberHistory\", reflect.TypeOf((*MockStore)(nil).GetBoardMemberHistory), arg0, arg1, arg2)\n}\n\n// GetBoardsComplianceHistory mocks base method.\nfunc (m *MockStore) GetBoardsComplianceHistory(arg0 model.QueryBoardsComplianceHistoryOptions) ([]*model.BoardHistory, bool, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetBoardsComplianceHistory\", arg0)\n\tret0, _ := ret[0].([]*model.BoardHistory)\n\tret1, _ := ret[1].(bool)\n\tret2, _ := ret[2].(error)\n\treturn ret0, ret1, ret2\n}\n\n// GetBoardsComplianceHistory indicates an expected call of GetBoardsComplianceHistory.\nfunc (mr *MockStoreMockRecorder) GetBoardsComplianceHistory(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetBoardsComplianceHistory\", reflect.TypeOf((*MockStore)(nil).GetBoardsComplianceHistory), arg0)\n}\n\n// GetBoardsForCompliance mocks base method.\nfunc (m *MockStore) GetBoardsForCompliance(arg0 model.QueryBoardsForComplianceOptions) ([]*model.Board, bool, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetBoardsForCompliance\", arg0)\n\tret0, _ := ret[0].([]*model.Board)\n\tret1, _ := ret[1].(bool)\n\tret2, _ := ret[2].(error)\n\treturn ret0, ret1, ret2\n}\n\n// GetBoardsForCompliance indicates an expected call of GetBoardsForCompliance.\nfunc (mr *MockStoreMockRecorder) GetBoardsForCompliance(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetBoardsForCompliance\", reflect.TypeOf((*MockStore)(nil).GetBoardsForCompliance), arg0)\n}\n\n// GetBoardsForUserAndTeam mocks base method.\nfunc (m *MockStore) GetBoardsForUserAndTeam(arg0, arg1 string, arg2 bool) ([]*model.Board, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetBoardsForUserAndTeam\", arg0, arg1, arg2)\n\tret0, _ := ret[0].([]*model.Board)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetBoardsForUserAndTeam indicates an expected call of GetBoardsForUserAndTeam.\nfunc (mr *MockStoreMockRecorder) GetBoardsForUserAndTeam(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetBoardsForUserAndTeam\", reflect.TypeOf((*MockStore)(nil).GetBoardsForUserAndTeam), arg0, arg1, arg2)\n}\n\n// GetBoardsInTeamByIds mocks base method.\nfunc (m *MockStore) GetBoardsInTeamByIds(arg0 []string, arg1 string) ([]*model.Board, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetBoardsInTeamByIds\", arg0, arg1)\n\tret0, _ := ret[0].([]*model.Board)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetBoardsInTeamByIds indicates an expected call of GetBoardsInTeamByIds.\nfunc (mr *MockStoreMockRecorder) GetBoardsInTeamByIds(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetBoardsInTeamByIds\", reflect.TypeOf((*MockStore)(nil).GetBoardsInTeamByIds), arg0, arg1)\n}\n\n// GetCardLimitTimestamp mocks base method.\nfunc (m *MockStore) GetCardLimitTimestamp() (int64, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetCardLimitTimestamp\")\n\tret0, _ := ret[0].(int64)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetCardLimitTimestamp indicates an expected call of GetCardLimitTimestamp.\nfunc (mr *MockStoreMockRecorder) GetCardLimitTimestamp() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetCardLimitTimestamp\", reflect.TypeOf((*MockStore)(nil).GetCardLimitTimestamp))\n}\n\n// GetCategory mocks base method.\nfunc (m *MockStore) GetCategory(arg0 string) (*model.Category, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetCategory\", arg0)\n\tret0, _ := ret[0].(*model.Category)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetCategory indicates an expected call of GetCategory.\nfunc (mr *MockStoreMockRecorder) GetCategory(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetCategory\", reflect.TypeOf((*MockStore)(nil).GetCategory), arg0)\n}\n\n// GetChannel mocks base method.\nfunc (m *MockStore) GetChannel(arg0, arg1 string) (*model0.Channel, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetChannel\", arg0, arg1)\n\tret0, _ := ret[0].(*model0.Channel)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetChannel indicates an expected call of GetChannel.\nfunc (mr *MockStoreMockRecorder) GetChannel(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetChannel\", reflect.TypeOf((*MockStore)(nil).GetChannel), arg0, arg1)\n}\n\n// GetFileInfo mocks base method.\nfunc (m *MockStore) GetFileInfo(arg0 string) (*model0.FileInfo, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetFileInfo\", arg0)\n\tret0, _ := ret[0].(*model0.FileInfo)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetFileInfo indicates an expected call of GetFileInfo.\nfunc (mr *MockStoreMockRecorder) GetFileInfo(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetFileInfo\", reflect.TypeOf((*MockStore)(nil).GetFileInfo), arg0)\n}\n\n// GetLicense mocks base method.\nfunc (m *MockStore) GetLicense() *model0.License {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetLicense\")\n\tret0, _ := ret[0].(*model0.License)\n\treturn ret0\n}\n\n// GetLicense indicates an expected call of GetLicense.\nfunc (mr *MockStoreMockRecorder) GetLicense() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetLicense\", reflect.TypeOf((*MockStore)(nil).GetLicense))\n}\n\n// GetMemberForBoard mocks base method.\nfunc (m *MockStore) GetMemberForBoard(arg0, arg1 string) (*model.BoardMember, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetMemberForBoard\", arg0, arg1)\n\tret0, _ := ret[0].(*model.BoardMember)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetMemberForBoard indicates an expected call of GetMemberForBoard.\nfunc (mr *MockStoreMockRecorder) GetMemberForBoard(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetMemberForBoard\", reflect.TypeOf((*MockStore)(nil).GetMemberForBoard), arg0, arg1)\n}\n\n// GetMembersForBoard mocks base method.\nfunc (m *MockStore) GetMembersForBoard(arg0 string) ([]*model.BoardMember, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetMembersForBoard\", arg0)\n\tret0, _ := ret[0].([]*model.BoardMember)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetMembersForBoard indicates an expected call of GetMembersForBoard.\nfunc (mr *MockStoreMockRecorder) GetMembersForBoard(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetMembersForBoard\", reflect.TypeOf((*MockStore)(nil).GetMembersForBoard), arg0)\n}\n\n// GetMembersForUser mocks base method.\nfunc (m *MockStore) GetMembersForUser(arg0 string) ([]*model.BoardMember, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetMembersForUser\", arg0)\n\tret0, _ := ret[0].([]*model.BoardMember)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetMembersForUser indicates an expected call of GetMembersForUser.\nfunc (mr *MockStoreMockRecorder) GetMembersForUser(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetMembersForUser\", reflect.TypeOf((*MockStore)(nil).GetMembersForUser), arg0)\n}\n\n// GetNextNotificationHint mocks base method.\nfunc (m *MockStore) GetNextNotificationHint(arg0 bool) (*model.NotificationHint, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetNextNotificationHint\", arg0)\n\tret0, _ := ret[0].(*model.NotificationHint)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetNextNotificationHint indicates an expected call of GetNextNotificationHint.\nfunc (mr *MockStoreMockRecorder) GetNextNotificationHint(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetNextNotificationHint\", reflect.TypeOf((*MockStore)(nil).GetNextNotificationHint), arg0)\n}\n\n// GetNotificationHint mocks base method.\nfunc (m *MockStore) GetNotificationHint(arg0 string) (*model.NotificationHint, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetNotificationHint\", arg0)\n\tret0, _ := ret[0].(*model.NotificationHint)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetNotificationHint indicates an expected call of GetNotificationHint.\nfunc (mr *MockStoreMockRecorder) GetNotificationHint(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetNotificationHint\", reflect.TypeOf((*MockStore)(nil).GetNotificationHint), arg0)\n}\n\n// GetRegisteredUserCount mocks base method.\nfunc (m *MockStore) GetRegisteredUserCount() (int, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetRegisteredUserCount\")\n\tret0, _ := ret[0].(int)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetRegisteredUserCount indicates an expected call of GetRegisteredUserCount.\nfunc (mr *MockStoreMockRecorder) GetRegisteredUserCount() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetRegisteredUserCount\", reflect.TypeOf((*MockStore)(nil).GetRegisteredUserCount))\n}\n\n// GetSession mocks base method.\nfunc (m *MockStore) GetSession(arg0 string, arg1 int64) (*model.Session, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetSession\", arg0, arg1)\n\tret0, _ := ret[0].(*model.Session)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetSession indicates an expected call of GetSession.\nfunc (mr *MockStoreMockRecorder) GetSession(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetSession\", reflect.TypeOf((*MockStore)(nil).GetSession), arg0, arg1)\n}\n\n// GetSharing mocks base method.\nfunc (m *MockStore) GetSharing(arg0 string) (*model.Sharing, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetSharing\", arg0)\n\tret0, _ := ret[0].(*model.Sharing)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetSharing indicates an expected call of GetSharing.\nfunc (mr *MockStoreMockRecorder) GetSharing(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetSharing\", reflect.TypeOf((*MockStore)(nil).GetSharing), arg0)\n}\n\n// GetSubTree2 mocks base method.\nfunc (m *MockStore) GetSubTree2(arg0, arg1 string, arg2 model.QuerySubtreeOptions) ([]*model.Block, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetSubTree2\", arg0, arg1, arg2)\n\tret0, _ := ret[0].([]*model.Block)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetSubTree2 indicates an expected call of GetSubTree2.\nfunc (mr *MockStoreMockRecorder) GetSubTree2(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetSubTree2\", reflect.TypeOf((*MockStore)(nil).GetSubTree2), arg0, arg1, arg2)\n}\n\n// GetSubscribersCountForBlock mocks base method.\nfunc (m *MockStore) GetSubscribersCountForBlock(arg0 string) (int, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetSubscribersCountForBlock\", arg0)\n\tret0, _ := ret[0].(int)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetSubscribersCountForBlock indicates an expected call of GetSubscribersCountForBlock.\nfunc (mr *MockStoreMockRecorder) GetSubscribersCountForBlock(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetSubscribersCountForBlock\", reflect.TypeOf((*MockStore)(nil).GetSubscribersCountForBlock), arg0)\n}\n\n// GetSubscribersForBlock mocks base method.\nfunc (m *MockStore) GetSubscribersForBlock(arg0 string) ([]*model.Subscriber, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetSubscribersForBlock\", arg0)\n\tret0, _ := ret[0].([]*model.Subscriber)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetSubscribersForBlock indicates an expected call of GetSubscribersForBlock.\nfunc (mr *MockStoreMockRecorder) GetSubscribersForBlock(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetSubscribersForBlock\", reflect.TypeOf((*MockStore)(nil).GetSubscribersForBlock), arg0)\n}\n\n// GetSubscription mocks base method.\nfunc (m *MockStore) GetSubscription(arg0, arg1 string) (*model.Subscription, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetSubscription\", arg0, arg1)\n\tret0, _ := ret[0].(*model.Subscription)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetSubscription indicates an expected call of GetSubscription.\nfunc (mr *MockStoreMockRecorder) GetSubscription(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetSubscription\", reflect.TypeOf((*MockStore)(nil).GetSubscription), arg0, arg1)\n}\n\n// GetSubscriptions mocks base method.\nfunc (m *MockStore) GetSubscriptions(arg0 string) ([]*model.Subscription, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetSubscriptions\", arg0)\n\tret0, _ := ret[0].([]*model.Subscription)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetSubscriptions indicates an expected call of GetSubscriptions.\nfunc (mr *MockStoreMockRecorder) GetSubscriptions(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetSubscriptions\", reflect.TypeOf((*MockStore)(nil).GetSubscriptions), arg0)\n}\n\n// GetSystemSetting mocks base method.\nfunc (m *MockStore) GetSystemSetting(arg0 string) (string, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetSystemSetting\", arg0)\n\tret0, _ := ret[0].(string)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetSystemSetting indicates an expected call of GetSystemSetting.\nfunc (mr *MockStoreMockRecorder) GetSystemSetting(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetSystemSetting\", reflect.TypeOf((*MockStore)(nil).GetSystemSetting), arg0)\n}\n\n// GetSystemSettings mocks base method.\nfunc (m *MockStore) GetSystemSettings() (map[string]string, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetSystemSettings\")\n\tret0, _ := ret[0].(map[string]string)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetSystemSettings indicates an expected call of GetSystemSettings.\nfunc (mr *MockStoreMockRecorder) GetSystemSettings() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetSystemSettings\", reflect.TypeOf((*MockStore)(nil).GetSystemSettings))\n}\n\n// GetTeam mocks base method.\nfunc (m *MockStore) GetTeam(arg0 string) (*model.Team, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetTeam\", arg0)\n\tret0, _ := ret[0].(*model.Team)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetTeam indicates an expected call of GetTeam.\nfunc (mr *MockStoreMockRecorder) GetTeam(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetTeam\", reflect.TypeOf((*MockStore)(nil).GetTeam), arg0)\n}\n\n// GetTeamCount mocks base method.\nfunc (m *MockStore) GetTeamCount() (int64, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetTeamCount\")\n\tret0, _ := ret[0].(int64)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetTeamCount indicates an expected call of GetTeamCount.\nfunc (mr *MockStoreMockRecorder) GetTeamCount() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetTeamCount\", reflect.TypeOf((*MockStore)(nil).GetTeamCount))\n}\n\n// GetTeamsForUser mocks base method.\nfunc (m *MockStore) GetTeamsForUser(arg0 string) ([]*model.Team, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetTeamsForUser\", arg0)\n\tret0, _ := ret[0].([]*model.Team)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetTeamsForUser indicates an expected call of GetTeamsForUser.\nfunc (mr *MockStoreMockRecorder) GetTeamsForUser(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetTeamsForUser\", reflect.TypeOf((*MockStore)(nil).GetTeamsForUser), arg0)\n}\n\n// GetTemplateBoards mocks base method.\nfunc (m *MockStore) GetTemplateBoards(arg0, arg1 string) ([]*model.Board, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetTemplateBoards\", arg0, arg1)\n\tret0, _ := ret[0].([]*model.Board)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetTemplateBoards indicates an expected call of GetTemplateBoards.\nfunc (mr *MockStoreMockRecorder) GetTemplateBoards(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetTemplateBoards\", reflect.TypeOf((*MockStore)(nil).GetTemplateBoards), arg0, arg1)\n}\n\n// GetUsedCardsCount mocks base method.\nfunc (m *MockStore) GetUsedCardsCount() (int, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetUsedCardsCount\")\n\tret0, _ := ret[0].(int)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetUsedCardsCount indicates an expected call of GetUsedCardsCount.\nfunc (mr *MockStoreMockRecorder) GetUsedCardsCount() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetUsedCardsCount\", reflect.TypeOf((*MockStore)(nil).GetUsedCardsCount))\n}\n\n// GetUserByEmail mocks base method.\nfunc (m *MockStore) GetUserByEmail(arg0 string) (*model.User, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetUserByEmail\", arg0)\n\tret0, _ := ret[0].(*model.User)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetUserByEmail indicates an expected call of GetUserByEmail.\nfunc (mr *MockStoreMockRecorder) GetUserByEmail(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetUserByEmail\", reflect.TypeOf((*MockStore)(nil).GetUserByEmail), arg0)\n}\n\n// GetUserByID mocks base method.\nfunc (m *MockStore) GetUserByID(arg0 string) (*model.User, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetUserByID\", arg0)\n\tret0, _ := ret[0].(*model.User)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetUserByID indicates an expected call of GetUserByID.\nfunc (mr *MockStoreMockRecorder) GetUserByID(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetUserByID\", reflect.TypeOf((*MockStore)(nil).GetUserByID), arg0)\n}\n\n// GetUserByUsername mocks base method.\nfunc (m *MockStore) GetUserByUsername(arg0 string) (*model.User, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetUserByUsername\", arg0)\n\tret0, _ := ret[0].(*model.User)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetUserByUsername indicates an expected call of GetUserByUsername.\nfunc (mr *MockStoreMockRecorder) GetUserByUsername(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetUserByUsername\", reflect.TypeOf((*MockStore)(nil).GetUserByUsername), arg0)\n}\n\n// GetUserCategories mocks base method.\nfunc (m *MockStore) GetUserCategories(arg0, arg1 string) ([]model.Category, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetUserCategories\", arg0, arg1)\n\tret0, _ := ret[0].([]model.Category)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetUserCategories indicates an expected call of GetUserCategories.\nfunc (mr *MockStoreMockRecorder) GetUserCategories(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetUserCategories\", reflect.TypeOf((*MockStore)(nil).GetUserCategories), arg0, arg1)\n}\n\n// GetUserCategoryBoards mocks base method.\nfunc (m *MockStore) GetUserCategoryBoards(arg0, arg1 string) ([]model.CategoryBoards, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetUserCategoryBoards\", arg0, arg1)\n\tret0, _ := ret[0].([]model.CategoryBoards)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetUserCategoryBoards indicates an expected call of GetUserCategoryBoards.\nfunc (mr *MockStoreMockRecorder) GetUserCategoryBoards(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetUserCategoryBoards\", reflect.TypeOf((*MockStore)(nil).GetUserCategoryBoards), arg0, arg1)\n}\n\n// GetUserPreferences mocks base method.\nfunc (m *MockStore) GetUserPreferences(arg0 string) (model0.Preferences, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetUserPreferences\", arg0)\n\tret0, _ := ret[0].(model0.Preferences)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetUserPreferences indicates an expected call of GetUserPreferences.\nfunc (mr *MockStoreMockRecorder) GetUserPreferences(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetUserPreferences\", reflect.TypeOf((*MockStore)(nil).GetUserPreferences), arg0)\n}\n\n// GetUserTimezone mocks base method.\nfunc (m *MockStore) GetUserTimezone(arg0 string) (string, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetUserTimezone\", arg0)\n\tret0, _ := ret[0].(string)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetUserTimezone indicates an expected call of GetUserTimezone.\nfunc (mr *MockStoreMockRecorder) GetUserTimezone(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetUserTimezone\", reflect.TypeOf((*MockStore)(nil).GetUserTimezone), arg0)\n}\n\n// GetUsersByTeam mocks base method.\nfunc (m *MockStore) GetUsersByTeam(arg0, arg1 string, arg2, arg3 bool) ([]*model.User, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetUsersByTeam\", arg0, arg1, arg2, arg3)\n\tret0, _ := ret[0].([]*model.User)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetUsersByTeam indicates an expected call of GetUsersByTeam.\nfunc (mr *MockStoreMockRecorder) GetUsersByTeam(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetUsersByTeam\", reflect.TypeOf((*MockStore)(nil).GetUsersByTeam), arg0, arg1, arg2, arg3)\n}\n\n// GetUsersList mocks base method.\nfunc (m *MockStore) GetUsersList(arg0 []string, arg1, arg2 bool) ([]*model.User, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetUsersList\", arg0, arg1, arg2)\n\tret0, _ := ret[0].([]*model.User)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetUsersList indicates an expected call of GetUsersList.\nfunc (mr *MockStoreMockRecorder) GetUsersList(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetUsersList\", reflect.TypeOf((*MockStore)(nil).GetUsersList), arg0, arg1, arg2)\n}\n\n// InsertBlock mocks base method.\nfunc (m *MockStore) InsertBlock(arg0 *model.Block, arg1 string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"InsertBlock\", arg0, arg1)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// InsertBlock indicates an expected call of InsertBlock.\nfunc (mr *MockStoreMockRecorder) InsertBlock(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"InsertBlock\", reflect.TypeOf((*MockStore)(nil).InsertBlock), arg0, arg1)\n}\n\n// InsertBlocks mocks base method.\nfunc (m *MockStore) InsertBlocks(arg0 []*model.Block, arg1 string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"InsertBlocks\", arg0, arg1)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// InsertBlocks indicates an expected call of InsertBlocks.\nfunc (mr *MockStoreMockRecorder) InsertBlocks(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"InsertBlocks\", reflect.TypeOf((*MockStore)(nil).InsertBlocks), arg0, arg1)\n}\n\n// InsertBoard mocks base method.\nfunc (m *MockStore) InsertBoard(arg0 *model.Board, arg1 string) (*model.Board, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"InsertBoard\", arg0, arg1)\n\tret0, _ := ret[0].(*model.Board)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// InsertBoard indicates an expected call of InsertBoard.\nfunc (mr *MockStoreMockRecorder) InsertBoard(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"InsertBoard\", reflect.TypeOf((*MockStore)(nil).InsertBoard), arg0, arg1)\n}\n\n// InsertBoardWithAdmin mocks base method.\nfunc (m *MockStore) InsertBoardWithAdmin(arg0 *model.Board, arg1 string) (*model.Board, *model.BoardMember, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"InsertBoardWithAdmin\", arg0, arg1)\n\tret0, _ := ret[0].(*model.Board)\n\tret1, _ := ret[1].(*model.BoardMember)\n\tret2, _ := ret[2].(error)\n\treturn ret0, ret1, ret2\n}\n\n// InsertBoardWithAdmin indicates an expected call of InsertBoardWithAdmin.\nfunc (mr *MockStoreMockRecorder) InsertBoardWithAdmin(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"InsertBoardWithAdmin\", reflect.TypeOf((*MockStore)(nil).InsertBoardWithAdmin), arg0, arg1)\n}\n\n// PatchBlock mocks base method.\nfunc (m *MockStore) PatchBlock(arg0 string, arg1 *model.BlockPatch, arg2 string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"PatchBlock\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// PatchBlock indicates an expected call of PatchBlock.\nfunc (mr *MockStoreMockRecorder) PatchBlock(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"PatchBlock\", reflect.TypeOf((*MockStore)(nil).PatchBlock), arg0, arg1, arg2)\n}\n\n// PatchBlocks mocks base method.\nfunc (m *MockStore) PatchBlocks(arg0 *model.BlockPatchBatch, arg1 string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"PatchBlocks\", arg0, arg1)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// PatchBlocks indicates an expected call of PatchBlocks.\nfunc (mr *MockStoreMockRecorder) PatchBlocks(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"PatchBlocks\", reflect.TypeOf((*MockStore)(nil).PatchBlocks), arg0, arg1)\n}\n\n// PatchBoard mocks base method.\nfunc (m *MockStore) PatchBoard(arg0 string, arg1 *model.BoardPatch, arg2 string) (*model.Board, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"PatchBoard\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(*model.Board)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// PatchBoard indicates an expected call of PatchBoard.\nfunc (mr *MockStoreMockRecorder) PatchBoard(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"PatchBoard\", reflect.TypeOf((*MockStore)(nil).PatchBoard), arg0, arg1, arg2)\n}\n\n// PatchBoardsAndBlocks mocks base method.\nfunc (m *MockStore) PatchBoardsAndBlocks(arg0 *model.PatchBoardsAndBlocks, arg1 string) (*model.BoardsAndBlocks, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"PatchBoardsAndBlocks\", arg0, arg1)\n\tret0, _ := ret[0].(*model.BoardsAndBlocks)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// PatchBoardsAndBlocks indicates an expected call of PatchBoardsAndBlocks.\nfunc (mr *MockStoreMockRecorder) PatchBoardsAndBlocks(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"PatchBoardsAndBlocks\", reflect.TypeOf((*MockStore)(nil).PatchBoardsAndBlocks), arg0, arg1)\n}\n\n// PatchUserPreferences mocks base method.\nfunc (m *MockStore) PatchUserPreferences(arg0 string, arg1 model.UserPreferencesPatch) (model0.Preferences, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"PatchUserPreferences\", arg0, arg1)\n\tret0, _ := ret[0].(model0.Preferences)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// PatchUserPreferences indicates an expected call of PatchUserPreferences.\nfunc (mr *MockStoreMockRecorder) PatchUserPreferences(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"PatchUserPreferences\", reflect.TypeOf((*MockStore)(nil).PatchUserPreferences), arg0, arg1)\n}\n\n// PostMessage mocks base method.\nfunc (m *MockStore) PostMessage(arg0, arg1, arg2 string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"PostMessage\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// PostMessage indicates an expected call of PostMessage.\nfunc (mr *MockStoreMockRecorder) PostMessage(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"PostMessage\", reflect.TypeOf((*MockStore)(nil).PostMessage), arg0, arg1, arg2)\n}\n\n// RefreshSession mocks base method.\nfunc (m *MockStore) RefreshSession(arg0 *model.Session) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"RefreshSession\", arg0)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// RefreshSession indicates an expected call of RefreshSession.\nfunc (mr *MockStoreMockRecorder) RefreshSession(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"RefreshSession\", reflect.TypeOf((*MockStore)(nil).RefreshSession), arg0)\n}\n\n// RemoveDefaultTemplates mocks base method.\nfunc (m *MockStore) RemoveDefaultTemplates(arg0 []*model.Board) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"RemoveDefaultTemplates\", arg0)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// RemoveDefaultTemplates indicates an expected call of RemoveDefaultTemplates.\nfunc (mr *MockStoreMockRecorder) RemoveDefaultTemplates(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"RemoveDefaultTemplates\", reflect.TypeOf((*MockStore)(nil).RemoveDefaultTemplates), arg0)\n}\n\n// ReorderCategories mocks base method.\nfunc (m *MockStore) ReorderCategories(arg0, arg1 string, arg2 []string) ([]string, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ReorderCategories\", arg0, arg1, arg2)\n\tret0, _ := ret[0].([]string)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ReorderCategories indicates an expected call of ReorderCategories.\nfunc (mr *MockStoreMockRecorder) ReorderCategories(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ReorderCategories\", reflect.TypeOf((*MockStore)(nil).ReorderCategories), arg0, arg1, arg2)\n}\n\n// ReorderCategoryBoards mocks base method.\nfunc (m *MockStore) ReorderCategoryBoards(arg0 string, arg1 []string) ([]string, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ReorderCategoryBoards\", arg0, arg1)\n\tret0, _ := ret[0].([]string)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ReorderCategoryBoards indicates an expected call of ReorderCategoryBoards.\nfunc (mr *MockStoreMockRecorder) ReorderCategoryBoards(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ReorderCategoryBoards\", reflect.TypeOf((*MockStore)(nil).ReorderCategoryBoards), arg0, arg1)\n}\n\n// RunDataRetention mocks base method.\nfunc (m *MockStore) RunDataRetention(arg0, arg1 int64) (int64, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"RunDataRetention\", arg0, arg1)\n\tret0, _ := ret[0].(int64)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// RunDataRetention indicates an expected call of RunDataRetention.\nfunc (mr *MockStoreMockRecorder) RunDataRetention(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"RunDataRetention\", reflect.TypeOf((*MockStore)(nil).RunDataRetention), arg0, arg1)\n}\n\n// SaveFileInfo mocks base method.\nfunc (m *MockStore) SaveFileInfo(arg0 *model0.FileInfo) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"SaveFileInfo\", arg0)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// SaveFileInfo indicates an expected call of SaveFileInfo.\nfunc (mr *MockStoreMockRecorder) SaveFileInfo(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SaveFileInfo\", reflect.TypeOf((*MockStore)(nil).SaveFileInfo), arg0)\n}\n\n// SaveMember mocks base method.\nfunc (m *MockStore) SaveMember(arg0 *model.BoardMember) (*model.BoardMember, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"SaveMember\", arg0)\n\tret0, _ := ret[0].(*model.BoardMember)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// SaveMember indicates an expected call of SaveMember.\nfunc (mr *MockStoreMockRecorder) SaveMember(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SaveMember\", reflect.TypeOf((*MockStore)(nil).SaveMember), arg0)\n}\n\n// SearchBoardsForUser mocks base method.\nfunc (m *MockStore) SearchBoardsForUser(arg0 string, arg1 model.BoardSearchField, arg2 string, arg3 bool) ([]*model.Board, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"SearchBoardsForUser\", arg0, arg1, arg2, arg3)\n\tret0, _ := ret[0].([]*model.Board)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// SearchBoardsForUser indicates an expected call of SearchBoardsForUser.\nfunc (mr *MockStoreMockRecorder) SearchBoardsForUser(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SearchBoardsForUser\", reflect.TypeOf((*MockStore)(nil).SearchBoardsForUser), arg0, arg1, arg2, arg3)\n}\n\n// SearchBoardsForUserInTeam mocks base method.\nfunc (m *MockStore) SearchBoardsForUserInTeam(arg0, arg1, arg2 string) ([]*model.Board, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"SearchBoardsForUserInTeam\", arg0, arg1, arg2)\n\tret0, _ := ret[0].([]*model.Board)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// SearchBoardsForUserInTeam indicates an expected call of SearchBoardsForUserInTeam.\nfunc (mr *MockStoreMockRecorder) SearchBoardsForUserInTeam(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SearchBoardsForUserInTeam\", reflect.TypeOf((*MockStore)(nil).SearchBoardsForUserInTeam), arg0, arg1, arg2)\n}\n\n// SearchUserChannels mocks base method.\nfunc (m *MockStore) SearchUserChannels(arg0, arg1, arg2 string) ([]*model0.Channel, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"SearchUserChannels\", arg0, arg1, arg2)\n\tret0, _ := ret[0].([]*model0.Channel)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// SearchUserChannels indicates an expected call of SearchUserChannels.\nfunc (mr *MockStoreMockRecorder) SearchUserChannels(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SearchUserChannels\", reflect.TypeOf((*MockStore)(nil).SearchUserChannels), arg0, arg1, arg2)\n}\n\n// SearchUsersByTeam mocks base method.\nfunc (m *MockStore) SearchUsersByTeam(arg0, arg1, arg2 string, arg3, arg4, arg5 bool) ([]*model.User, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"SearchUsersByTeam\", arg0, arg1, arg2, arg3, arg4, arg5)\n\tret0, _ := ret[0].([]*model.User)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// SearchUsersByTeam indicates an expected call of SearchUsersByTeam.\nfunc (mr *MockStoreMockRecorder) SearchUsersByTeam(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SearchUsersByTeam\", reflect.TypeOf((*MockStore)(nil).SearchUsersByTeam), arg0, arg1, arg2, arg3, arg4, arg5)\n}\n\n// SendMessage mocks base method.\nfunc (m *MockStore) SendMessage(arg0, arg1 string, arg2 []string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"SendMessage\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// SendMessage indicates an expected call of SendMessage.\nfunc (mr *MockStoreMockRecorder) SendMessage(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SendMessage\", reflect.TypeOf((*MockStore)(nil).SendMessage), arg0, arg1, arg2)\n}\n\n// SetBoardVisibility mocks base method.\nfunc (m *MockStore) SetBoardVisibility(arg0, arg1, arg2 string, arg3 bool) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"SetBoardVisibility\", arg0, arg1, arg2, arg3)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// SetBoardVisibility indicates an expected call of SetBoardVisibility.\nfunc (mr *MockStoreMockRecorder) SetBoardVisibility(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SetBoardVisibility\", reflect.TypeOf((*MockStore)(nil).SetBoardVisibility), arg0, arg1, arg2, arg3)\n}\n\n// SetSystemSetting mocks base method.\nfunc (m *MockStore) SetSystemSetting(arg0, arg1 string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"SetSystemSetting\", arg0, arg1)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// SetSystemSetting indicates an expected call of SetSystemSetting.\nfunc (mr *MockStoreMockRecorder) SetSystemSetting(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SetSystemSetting\", reflect.TypeOf((*MockStore)(nil).SetSystemSetting), arg0, arg1)\n}\n\n// Shutdown mocks base method.\nfunc (m *MockStore) Shutdown() error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Shutdown\")\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Shutdown indicates an expected call of Shutdown.\nfunc (mr *MockStoreMockRecorder) Shutdown() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Shutdown\", reflect.TypeOf((*MockStore)(nil).Shutdown))\n}\n\n// UndeleteBlock mocks base method.\nfunc (m *MockStore) UndeleteBlock(arg0, arg1 string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UndeleteBlock\", arg0, arg1)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// UndeleteBlock indicates an expected call of UndeleteBlock.\nfunc (mr *MockStoreMockRecorder) UndeleteBlock(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UndeleteBlock\", reflect.TypeOf((*MockStore)(nil).UndeleteBlock), arg0, arg1)\n}\n\n// UndeleteBoard mocks base method.\nfunc (m *MockStore) UndeleteBoard(arg0, arg1 string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UndeleteBoard\", arg0, arg1)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// UndeleteBoard indicates an expected call of UndeleteBoard.\nfunc (mr *MockStoreMockRecorder) UndeleteBoard(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UndeleteBoard\", reflect.TypeOf((*MockStore)(nil).UndeleteBoard), arg0, arg1)\n}\n\n// UpdateCardLimitTimestamp mocks base method.\nfunc (m *MockStore) UpdateCardLimitTimestamp(arg0 int) (int64, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdateCardLimitTimestamp\", arg0)\n\tret0, _ := ret[0].(int64)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// UpdateCardLimitTimestamp indicates an expected call of UpdateCardLimitTimestamp.\nfunc (mr *MockStoreMockRecorder) UpdateCardLimitTimestamp(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdateCardLimitTimestamp\", reflect.TypeOf((*MockStore)(nil).UpdateCardLimitTimestamp), arg0)\n}\n\n// UpdateCategory mocks base method.\nfunc (m *MockStore) UpdateCategory(arg0 model.Category) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdateCategory\", arg0)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// UpdateCategory indicates an expected call of UpdateCategory.\nfunc (mr *MockStoreMockRecorder) UpdateCategory(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdateCategory\", reflect.TypeOf((*MockStore)(nil).UpdateCategory), arg0)\n}\n\n// UpdateSession mocks base method.\nfunc (m *MockStore) UpdateSession(arg0 *model.Session) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdateSession\", arg0)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// UpdateSession indicates an expected call of UpdateSession.\nfunc (mr *MockStoreMockRecorder) UpdateSession(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdateSession\", reflect.TypeOf((*MockStore)(nil).UpdateSession), arg0)\n}\n\n// UpdateSubscribersNotifiedAt mocks base method.\nfunc (m *MockStore) UpdateSubscribersNotifiedAt(arg0 string, arg1 int64) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdateSubscribersNotifiedAt\", arg0, arg1)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// UpdateSubscribersNotifiedAt indicates an expected call of UpdateSubscribersNotifiedAt.\nfunc (mr *MockStoreMockRecorder) UpdateSubscribersNotifiedAt(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdateSubscribersNotifiedAt\", reflect.TypeOf((*MockStore)(nil).UpdateSubscribersNotifiedAt), arg0, arg1)\n}\n\n// UpdateUser mocks base method.\nfunc (m *MockStore) UpdateUser(arg0 *model.User) (*model.User, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdateUser\", arg0)\n\tret0, _ := ret[0].(*model.User)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// UpdateUser indicates an expected call of UpdateUser.\nfunc (mr *MockStoreMockRecorder) UpdateUser(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdateUser\", reflect.TypeOf((*MockStore)(nil).UpdateUser), arg0)\n}\n\n// UpdateUserPassword mocks base method.\nfunc (m *MockStore) UpdateUserPassword(arg0, arg1 string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdateUserPassword\", arg0, arg1)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// UpdateUserPassword indicates an expected call of UpdateUserPassword.\nfunc (mr *MockStoreMockRecorder) UpdateUserPassword(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdateUserPassword\", reflect.TypeOf((*MockStore)(nil).UpdateUserPassword), arg0, arg1)\n}\n\n// UpdateUserPasswordByID mocks base method.\nfunc (m *MockStore) UpdateUserPasswordByID(arg0, arg1 string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdateUserPasswordByID\", arg0, arg1)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// UpdateUserPasswordByID indicates an expected call of UpdateUserPasswordByID.\nfunc (mr *MockStoreMockRecorder) UpdateUserPasswordByID(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdateUserPasswordByID\", reflect.TypeOf((*MockStore)(nil).UpdateUserPasswordByID), arg0, arg1)\n}\n\n// UpsertNotificationHint mocks base method.\nfunc (m *MockStore) UpsertNotificationHint(arg0 *model.NotificationHint, arg1 time.Duration) (*model.NotificationHint, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpsertNotificationHint\", arg0, arg1)\n\tret0, _ := ret[0].(*model.NotificationHint)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// UpsertNotificationHint indicates an expected call of UpsertNotificationHint.\nfunc (mr *MockStoreMockRecorder) UpsertNotificationHint(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpsertNotificationHint\", reflect.TypeOf((*MockStore)(nil).UpsertNotificationHint), arg0, arg1)\n}\n\n// UpsertSharing mocks base method.\nfunc (m *MockStore) UpsertSharing(arg0 model.Sharing) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpsertSharing\", arg0)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// UpsertSharing indicates an expected call of UpsertSharing.\nfunc (mr *MockStoreMockRecorder) UpsertSharing(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpsertSharing\", reflect.TypeOf((*MockStore)(nil).UpsertSharing), arg0)\n}\n\n// UpsertTeamSettings mocks base method.\nfunc (m *MockStore) UpsertTeamSettings(arg0 model.Team) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpsertTeamSettings\", arg0)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// UpsertTeamSettings indicates an expected call of UpsertTeamSettings.\nfunc (mr *MockStoreMockRecorder) UpsertTeamSettings(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpsertTeamSettings\", reflect.TypeOf((*MockStore)(nil).UpsertTeamSettings), arg0)\n}\n\n// UpsertTeamSignupToken mocks base method.\nfunc (m *MockStore) UpsertTeamSignupToken(arg0 model.Team) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpsertTeamSignupToken\", arg0)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// UpsertTeamSignupToken indicates an expected call of UpsertTeamSignupToken.\nfunc (mr *MockStoreMockRecorder) UpsertTeamSignupToken(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpsertTeamSignupToken\", reflect.TypeOf((*MockStore)(nil).UpsertTeamSignupToken), arg0)\n}\n"
  },
  {
    "path": "server/services/store/sqlstore/blocks.go",
    "content": "package sqlstore\n\nimport (\n\t\"database/sql\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/mattermost/focalboard/server/utils\"\n\n\tsq \"github.com/Masterminds/squirrel\"\n\t_ \"github.com/lib/pq\" // postgres driver\n\t\"github.com/mattermost/focalboard/server/model\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nconst (\n\tmaxSearchDepth = 50\n\tdescClause     = \" DESC \"\n)\n\nfunc (s *SQLStore) timestampToCharField(name string, as string) string {\n\tswitch s.dbType {\n\tcase model.MysqlDBType:\n\t\treturn fmt.Sprintf(\"date_format(%s, '%%Y-%%m-%%d %%H:%%i:%%S') AS %s\", name, as)\n\tcase model.PostgresDBType:\n\t\treturn fmt.Sprintf(\"to_char(%s, 'YYYY-MM-DD HH:MI:SS.MS') AS %s\", name, as)\n\tdefault:\n\t\treturn fmt.Sprintf(\"%s AS %s\", name, as)\n\t}\n}\n\nfunc (s *SQLStore) blockFields(tableAlias string) []string {\n\tif tableAlias != \"\" && !strings.HasSuffix(tableAlias, \".\") {\n\t\ttableAlias += \".\"\n\t}\n\n\treturn []string{\n\t\ttableAlias + \"id\",\n\t\ttableAlias + \"parent_id\",\n\t\ttableAlias + \"created_by\",\n\t\ttableAlias + \"modified_by\",\n\t\ttableAlias + s.escapeField(\"schema\"),\n\t\ttableAlias + \"type\",\n\t\ttableAlias + \"title\",\n\t\t\"COALESCE(\" + tableAlias + \"fields, '{}')\",\n\t\ts.timestampToCharField(tableAlias+\"insert_at\", \"insertAt\"),\n\t\ttableAlias + \"create_at\",\n\t\ttableAlias + \"update_at\",\n\t\ttableAlias + \"delete_at\",\n\t\t\"COALESCE(\" + tableAlias + \"board_id, '0')\",\n\t}\n}\n\nfunc (s *SQLStore) getBlocks(db sq.BaseRunner, opts model.QueryBlocksOptions) ([]*model.Block, error) {\n\tquery := s.getQueryBuilder(db).\n\t\tSelect(s.blockFields(\"\")...).\n\t\tFrom(s.tablePrefix + \"blocks\")\n\n\tif opts.BoardID != \"\" {\n\t\tquery = query.Where(sq.Eq{\"board_id\": opts.BoardID})\n\t}\n\n\tif opts.ParentID != \"\" {\n\t\tquery = query.Where(sq.Eq{\"parent_id\": opts.ParentID})\n\t}\n\n\tif opts.BlockType != \"\" && opts.BlockType != model.TypeUnknown {\n\t\tquery = query.Where(sq.Eq{\"type\": opts.BlockType})\n\t}\n\n\tif opts.Page != 0 {\n\t\tquery = query.Offset(uint64(opts.Page * opts.PerPage))\n\t}\n\n\tif opts.PerPage > 0 {\n\t\tquery = query.Limit(uint64(opts.PerPage))\n\t}\n\n\trows, err := query.Query()\n\tif err != nil {\n\t\ts.logger.Error(`getBlocks ERROR`, mlog.Err(err))\n\n\t\treturn nil, err\n\t}\n\tdefer s.CloseRows(rows)\n\n\treturn s.blocksFromRows(rows)\n}\n\nfunc (s *SQLStore) getBlocksWithParentAndType(db sq.BaseRunner, boardID, parentID string, blockType string) ([]*model.Block, error) {\n\topts := model.QueryBlocksOptions{\n\t\tBoardID:   boardID,\n\t\tParentID:  parentID,\n\t\tBlockType: model.BlockType(blockType),\n\t}\n\treturn s.getBlocks(db, opts)\n}\n\nfunc (s *SQLStore) getBlocksWithParent(db sq.BaseRunner, boardID, parentID string) ([]*model.Block, error) {\n\topts := model.QueryBlocksOptions{\n\t\tBoardID:  boardID,\n\t\tParentID: parentID,\n\t}\n\treturn s.getBlocks(db, opts)\n}\n\nfunc (s *SQLStore) getBlocksByIDs(db sq.BaseRunner, ids []string) ([]*model.Block, error) {\n\tquery := s.getQueryBuilder(db).\n\t\tSelect(s.blockFields(\"\")...).\n\t\tFrom(s.tablePrefix + \"blocks\").\n\t\tWhere(sq.Eq{\"id\": ids})\n\n\trows, err := query.Query()\n\tif err != nil {\n\t\ts.logger.Error(`GetBlocksByIDs ERROR`, mlog.Err(err))\n\n\t\treturn nil, err\n\t}\n\tdefer s.CloseRows(rows)\n\n\tblocks, err := s.blocksFromRows(rows)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(blocks) != len(ids) {\n\t\treturn blocks, model.NewErrNotAllFound(\"block\", ids)\n\t}\n\n\treturn blocks, nil\n}\n\nfunc (s *SQLStore) getBlocksWithType(db sq.BaseRunner, boardID, blockType string) ([]*model.Block, error) {\n\topts := model.QueryBlocksOptions{\n\t\tBoardID:   boardID,\n\t\tBlockType: model.BlockType(blockType),\n\t}\n\treturn s.getBlocks(db, opts)\n}\n\n// getSubTree2 returns blocks within 2 levels of the given blockID.\nfunc (s *SQLStore) getSubTree2(db sq.BaseRunner, boardID string, blockID string, opts model.QuerySubtreeOptions) ([]*model.Block, error) {\n\tquery := s.getQueryBuilder(db).\n\t\tSelect(s.blockFields(\"\")...).\n\t\tFrom(s.tablePrefix + \"blocks\").\n\t\tWhere(sq.Or{sq.Eq{\"id\": blockID}, sq.Eq{\"parent_id\": blockID}}).\n\t\tWhere(sq.Eq{\"board_id\": boardID}).\n\t\tOrderBy(\"insert_at, update_at\")\n\n\tif opts.BeforeUpdateAt != 0 {\n\t\tquery = query.Where(sq.LtOrEq{\"update_at\": opts.BeforeUpdateAt})\n\t}\n\n\tif opts.AfterUpdateAt != 0 {\n\t\tquery = query.Where(sq.GtOrEq{\"update_at\": opts.AfterUpdateAt})\n\t}\n\n\tif opts.Limit != 0 {\n\t\tquery = query.Limit(opts.Limit)\n\t}\n\n\trows, err := query.Query()\n\tif err != nil {\n\t\ts.logger.Error(`getSubTree ERROR`, mlog.Err(err))\n\n\t\treturn nil, err\n\t}\n\tdefer s.CloseRows(rows)\n\n\treturn s.blocksFromRows(rows)\n}\n\nfunc (s *SQLStore) getBlocksForBoard(db sq.BaseRunner, boardID string) ([]*model.Block, error) {\n\topts := model.QueryBlocksOptions{\n\t\tBoardID: boardID,\n\t}\n\treturn s.getBlocks(db, opts)\n}\n\nfunc (s *SQLStore) blocksFromRows(rows *sql.Rows) ([]*model.Block, error) {\n\tresults := []*model.Block{}\n\n\tfor rows.Next() {\n\t\tvar block model.Block\n\t\tvar fieldsJSON string\n\t\tvar modifiedBy sql.NullString\n\t\tvar insertAt sql.NullString\n\n\t\terr := rows.Scan(\n\t\t\t&block.ID,\n\t\t\t&block.ParentID,\n\t\t\t&block.CreatedBy,\n\t\t\t&modifiedBy,\n\t\t\t&block.Schema,\n\t\t\t&block.Type,\n\t\t\t&block.Title,\n\t\t\t&fieldsJSON,\n\t\t\t&insertAt,\n\t\t\t&block.CreateAt,\n\t\t\t&block.UpdateAt,\n\t\t\t&block.DeleteAt,\n\t\t\t&block.BoardID)\n\t\tif err != nil {\n\t\t\t// handle this error\n\t\t\ts.logger.Error(`ERROR blocksFromRows`, mlog.Err(err))\n\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif modifiedBy.Valid {\n\t\t\tblock.ModifiedBy = modifiedBy.String\n\t\t}\n\n\t\terr = json.Unmarshal([]byte(fieldsJSON), &block.Fields)\n\t\tif err != nil {\n\t\t\t// handle this error\n\t\t\ts.logger.Error(`ERROR blocksFromRows fields`, mlog.Err(err))\n\n\t\t\treturn nil, err\n\t\t}\n\n\t\tresults = append(results, &block)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s *SQLStore) insertBlock(db sq.BaseRunner, block *model.Block, userID string) error {\n\tif err := block.IsValid(); err != nil {\n\t\treturn fmt.Errorf(\"error validating block %s: %w\", block.ID, err)\n\t}\n\n\tfieldsJSON, err := json.Marshal(block.Fields)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\texistingBlock, err := s.getBlock(db, block.ID)\n\tif err != nil && !model.IsErrNotFound(err) {\n\t\treturn err\n\t}\n\n\tblock.UpdateAt = utils.GetMillis()\n\tblock.ModifiedBy = userID\n\n\tinsertQuery := s.getQueryBuilder(db).Insert(\"\").\n\t\tColumns(\n\t\t\t\"channel_id\",\n\t\t\t\"id\",\n\t\t\t\"parent_id\",\n\t\t\t\"created_by\",\n\t\t\t\"modified_by\",\n\t\t\ts.escapeField(\"schema\"),\n\t\t\t\"type\",\n\t\t\t\"title\",\n\t\t\t\"fields\",\n\t\t\t\"create_at\",\n\t\t\t\"update_at\",\n\t\t\t\"delete_at\",\n\t\t\t\"board_id\",\n\t\t)\n\n\tinsertQueryValues := map[string]interface{}{\n\t\t\"channel_id\":            \"\",\n\t\t\"id\":                    block.ID,\n\t\t\"parent_id\":             block.ParentID,\n\t\ts.escapeField(\"schema\"): block.Schema,\n\t\t\"type\":                  block.Type,\n\t\t\"title\":                 block.Title,\n\t\t\"fields\":                fieldsJSON,\n\t\t\"delete_at\":             block.DeleteAt,\n\t\t\"created_by\":            userID,\n\t\t\"modified_by\":           block.ModifiedBy,\n\t\t\"create_at\":             utils.GetMillis(),\n\t\t\"update_at\":             block.UpdateAt,\n\t\t\"board_id\":              block.BoardID,\n\t}\n\n\tif existingBlock != nil {\n\t\t// block with ID exists, so this is an update operation\n\t\tquery := s.getQueryBuilder(db).Update(s.tablePrefix+\"blocks\").\n\t\t\tWhere(sq.Eq{\"id\": block.ID}).\n\t\t\tWhere(sq.Eq{\"board_id\": block.BoardID}).\n\t\t\tSet(\"parent_id\", block.ParentID).\n\t\t\tSet(\"modified_by\", block.ModifiedBy).\n\t\t\tSet(s.escapeField(\"schema\"), block.Schema).\n\t\t\tSet(\"type\", block.Type).\n\t\t\tSet(\"title\", block.Title).\n\t\t\tSet(\"fields\", fieldsJSON).\n\t\t\tSet(\"update_at\", block.UpdateAt).\n\t\t\tSet(\"delete_at\", block.DeleteAt)\n\n\t\tif _, err := query.Exec(); err != nil {\n\t\t\ts.logger.Error(`InsertBlock error occurred while updating existing block`, mlog.String(\"blockID\", block.ID), mlog.Err(err))\n\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\tblock.CreatedBy = userID\n\t\tquery := insertQuery.SetMap(insertQueryValues).Into(s.tablePrefix + \"blocks\")\n\t\tif _, err := query.Exec(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// writing block history\n\tquery := insertQuery.SetMap(insertQueryValues).Into(s.tablePrefix + \"blocks_history\")\n\tif _, err := query.Exec(); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (s *SQLStore) patchBlock(db sq.BaseRunner, blockID string, blockPatch *model.BlockPatch, userID string) error {\n\texistingBlock, err := s.getBlock(db, blockID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tblock := blockPatch.Patch(existingBlock)\n\treturn s.insertBlock(db, block, userID)\n}\n\nfunc (s *SQLStore) patchBlocks(db sq.BaseRunner, blockPatches *model.BlockPatchBatch, userID string) error {\n\tfor i, blockID := range blockPatches.BlockIDs {\n\t\terr := s.patchBlock(db, blockID, &blockPatches.BlockPatches[i], userID)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (s *SQLStore) insertBlocks(db sq.BaseRunner, blocks []*model.Block, userID string) error {\n\tfor _, block := range blocks {\n\t\tif err := block.IsValid(); err != nil {\n\t\t\treturn fmt.Errorf(\"error validating block %s: %w\", block.ID, err)\n\t\t}\n\t}\n\tfor i := range blocks {\n\t\terr := s.insertBlock(db, blocks[i], userID)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (s *SQLStore) deleteBlock(db sq.BaseRunner, blockID string, modifiedBy string) error {\n\treturn s.deleteBlockAndChildren(db, blockID, modifiedBy, false)\n}\n\nfunc retrieveFileIDFromBlockFieldStorage(id string) string {\n\tparts := strings.Split(id, \".\")\n\tif len(parts) < 1 {\n\t\treturn \"\"\n\t}\n\treturn parts[0][1:]\n}\n\nfunc (s *SQLStore) deleteBlockAndChildren(db sq.BaseRunner, blockID string, modifiedBy string, keepChildren bool) error {\n\tblock, err := s.getBlock(db, blockID)\n\tif model.IsErrNotFound(err) {\n\t\ts.logger.Warn(\"deleteBlock block not found\", mlog.String(\"block_id\", blockID))\n\t\treturn nil // deleting non-exiting block is not considered an error (for now)\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfieldsJSON, err := json.Marshal(block.Fields)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tnow := utils.GetMillis()\n\tinsertQuery := s.getQueryBuilder(db).Insert(s.tablePrefix+\"blocks_history\").\n\t\tColumns(\n\t\t\t\"board_id\",\n\t\t\t\"id\",\n\t\t\t\"parent_id\",\n\t\t\ts.escapeField(\"schema\"),\n\t\t\t\"type\",\n\t\t\t\"title\",\n\t\t\t\"fields\",\n\t\t\t\"modified_by\",\n\t\t\t\"create_at\",\n\t\t\t\"update_at\",\n\t\t\t\"delete_at\",\n\t\t\t\"created_by\",\n\t\t).\n\t\tValues(\n\t\t\tblock.BoardID,\n\t\t\tblock.ID,\n\t\t\tblock.ParentID,\n\t\t\tblock.Schema,\n\t\t\tblock.Type,\n\t\t\tblock.Title,\n\t\t\tfieldsJSON,\n\t\t\tmodifiedBy,\n\t\t\tblock.CreateAt,\n\t\t\tnow,\n\t\t\tnow,\n\t\t\tblock.CreatedBy,\n\t\t)\n\n\tif _, err := insertQuery.Exec(); err != nil {\n\t\treturn err\n\t}\n\n\t// fileId and attachmentId shoudn't exist at the same time\n\tfileID := \"\"\n\tfileIDWithExtention, fileIDExists := block.Fields[\"fileId\"]\n\tif fileIDExists {\n\t\tfileID = retrieveFileIDFromBlockFieldStorage(fileIDWithExtention.(string))\n\t}\n\n\tif fileID == \"\" {\n\t\tattachmentIDWithExtention, attachmentIDExists := block.Fields[\"attachmentId\"]\n\t\tif attachmentIDExists {\n\t\t\tfileID = retrieveFileIDFromBlockFieldStorage(attachmentIDWithExtention.(string))\n\t\t}\n\t}\n\n\tif fileID != \"\" {\n\t\tdeleteFileInfoQuery := s.getQueryBuilder(db).\n\t\t\tUpdate(\"FileInfo\").\n\t\t\tSet(\"DeleteAt\", model.GetMillis()).\n\t\t\tWhere(sq.Eq{\"id\": fileID})\n\t\tif _, err := deleteFileInfoQuery.Exec(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tdeleteQuery := s.getQueryBuilder(db).\n\t\tDelete(s.tablePrefix + \"blocks\").\n\t\tWhere(sq.Eq{\"id\": blockID})\n\n\tif _, err := deleteQuery.Exec(); err != nil {\n\t\treturn err\n\t}\n\n\tif keepChildren {\n\t\treturn nil\n\t}\n\n\treturn s.deleteBlockChildren(db, block.BoardID, block.ID, modifiedBy)\n}\n\nfunc (s *SQLStore) undeleteBlock(db sq.BaseRunner, blockID string, modifiedBy string) error {\n\tblocks, err := s.getBlockHistory(db, blockID, model.QueryBlockHistoryOptions{Limit: 1, Descending: true})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif len(blocks) == 0 {\n\t\ts.logger.Warn(\"undeleteBlock block not found\", mlog.String(\"block_id\", blockID))\n\t\treturn nil // undeleting non-exiting block is not considered an error (for now)\n\t}\n\tblock := blocks[0]\n\n\tif block.DeleteAt == 0 {\n\t\ts.logger.Warn(\"undeleteBlock block not deleted\", mlog.String(\"block_id\", block.ID))\n\t\treturn nil // undeleting not deleted block is not considered an error (for now)\n\t}\n\n\tfieldsJSON, err := json.Marshal(block.Fields)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tnow := utils.GetMillis()\n\tcolumns := []string{\n\t\t\"board_id\",\n\t\t\"channel_id\",\n\t\t\"id\",\n\t\t\"parent_id\",\n\t\ts.escapeField(\"schema\"),\n\t\t\"type\",\n\t\t\"title\",\n\t\t\"fields\",\n\t\t\"modified_by\",\n\t\t\"create_at\",\n\t\t\"update_at\",\n\t\t\"delete_at\",\n\t\t\"created_by\",\n\t}\n\n\tvalues := []interface{}{\n\t\tblock.BoardID,\n\t\t\"\",\n\t\tblock.ID,\n\t\tblock.ParentID,\n\t\tblock.Schema,\n\t\tblock.Type,\n\t\tblock.Title,\n\t\tfieldsJSON,\n\t\tmodifiedBy,\n\t\tblock.CreateAt,\n\t\tnow,\n\t\t0,\n\t\tblock.CreatedBy,\n\t}\n\tinsertHistoryQuery := s.getQueryBuilder(db).Insert(s.tablePrefix + \"blocks_history\").\n\t\tColumns(columns...).\n\t\tValues(values...)\n\tinsertQuery := s.getQueryBuilder(db).Insert(s.tablePrefix + \"blocks\").\n\t\tColumns(columns...).\n\t\tValues(values...)\n\n\tif _, err := insertHistoryQuery.Exec(); err != nil {\n\t\treturn err\n\t}\n\n\tif _, err := insertQuery.Exec(); err != nil {\n\t\treturn err\n\t}\n\n\treturn s.undeleteBlockChildren(db, block.BoardID, block.ID, modifiedBy)\n}\n\nfunc (s *SQLStore) getBlockCountsByType(db sq.BaseRunner) (map[string]int64, error) {\n\tquery := s.getQueryBuilder(db).\n\t\tSelect(\n\t\t\t\"type\",\n\t\t\t\"COUNT(*) AS count\",\n\t\t).\n\t\tFrom(s.tablePrefix + \"blocks\").\n\t\tGroupBy(\"type\")\n\n\trows, err := query.Query()\n\tif err != nil {\n\t\ts.logger.Error(`GetBlockCountsByType ERROR`, mlog.Err(err))\n\n\t\treturn nil, err\n\t}\n\tdefer s.CloseRows(rows)\n\n\tm := make(map[string]int64)\n\n\tfor rows.Next() {\n\t\tvar blockType string\n\t\tvar count int64\n\n\t\terr := rows.Scan(&blockType, &count)\n\t\tif err != nil {\n\t\t\ts.logger.Error(\"Failed to fetch block count\", mlog.Err(err))\n\t\t\treturn nil, err\n\t\t}\n\t\tm[blockType] = count\n\t}\n\treturn m, nil\n}\n\nfunc (s *SQLStore) getBoardCount(db sq.BaseRunner) (int64, error) {\n\tquery := s.getQueryBuilder(db).\n\t\tSelect(\"COUNT(*) AS count\").\n\t\tFrom(s.tablePrefix + \"boards\").\n\t\tWhere(sq.Eq{\"delete_at\": 0}).\n\t\tWhere(sq.Eq{\"is_template\": false})\n\n\trow := query.QueryRow()\n\n\tvar count int64\n\terr := row.Scan(&count)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\treturn count, nil\n}\n\nfunc (s *SQLStore) getBlock(db sq.BaseRunner, blockID string) (*model.Block, error) {\n\tquery := s.getQueryBuilder(db).\n\t\tSelect(s.blockFields(\"\")...).\n\t\tFrom(s.tablePrefix + \"blocks\").\n\t\tWhere(sq.Eq{\"id\": blockID})\n\n\trows, err := query.Query()\n\tif err != nil {\n\t\ts.logger.Error(`GetBlock ERROR`, mlog.Err(err))\n\t\treturn nil, err\n\t}\n\tdefer s.CloseRows(rows)\n\n\tblocks, err := s.blocksFromRows(rows)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(blocks) == 0 {\n\t\treturn nil, model.NewErrNotFound(\"block ID=\" + blockID)\n\t}\n\n\treturn blocks[0], nil\n}\n\nfunc (s *SQLStore) getBlockHistory(db sq.BaseRunner, blockID string, opts model.QueryBlockHistoryOptions) ([]*model.Block, error) {\n\tvar order string\n\tif opts.Descending {\n\t\torder = descClause\n\t}\n\n\tquery := s.getQueryBuilder(db).\n\t\tSelect(s.blockFields(\"\")...).\n\t\tFrom(s.tablePrefix + \"blocks_history\").\n\t\tWhere(sq.Eq{\"id\": blockID}).\n\t\tOrderBy(\"insert_at \" + order + \", update_at\" + order)\n\n\tif opts.BeforeUpdateAt != 0 {\n\t\tquery = query.Where(sq.Lt{\"update_at\": opts.BeforeUpdateAt})\n\t}\n\n\tif opts.AfterUpdateAt != 0 {\n\t\tquery = query.Where(sq.Gt{\"update_at\": opts.AfterUpdateAt})\n\t}\n\n\tif opts.Limit != 0 {\n\t\tquery = query.Limit(opts.Limit)\n\t}\n\n\trows, err := query.Query()\n\tif err != nil {\n\t\ts.logger.Error(`GetBlockHistory ERROR`, mlog.Err(err))\n\t\treturn nil, err\n\t}\n\tdefer s.CloseRows(rows)\n\n\treturn s.blocksFromRows(rows)\n}\n\nfunc (s *SQLStore) getBlockHistoryDescendants(db sq.BaseRunner, boardID string, opts model.QueryBlockHistoryOptions) ([]*model.Block, error) {\n\tvar order string\n\tif opts.Descending {\n\t\torder = descClause\n\t}\n\n\tquery := s.getQueryBuilder(db).\n\t\tSelect(s.blockFields(\"\")...).\n\t\tFrom(s.tablePrefix + \"blocks_history\").\n\t\tWhere(sq.Eq{\"board_id\": boardID}).\n\t\tOrderBy(\"insert_at \" + order + \", update_at\" + order)\n\n\tif opts.BeforeUpdateAt != 0 {\n\t\tquery = query.Where(sq.Lt{\"update_at\": opts.BeforeUpdateAt})\n\t}\n\n\tif opts.AfterUpdateAt != 0 {\n\t\tquery = query.Where(sq.Gt{\"update_at\": opts.AfterUpdateAt})\n\t}\n\n\tif opts.Limit != 0 {\n\t\tquery = query.Limit(opts.Limit)\n\t}\n\n\trows, err := query.Query()\n\tif err != nil {\n\t\ts.logger.Error(`GetBlockHistoryDescendants ERROR`, mlog.Err(err))\n\t\treturn nil, err\n\t}\n\tdefer s.CloseRows(rows)\n\n\treturn s.blocksFromRows(rows)\n}\n\n// getBlockHistoryNewestChildren returns the newest (latest) version child blocks for the\n// specified parent from the blocks_history table. This includes any deleted children.\nfunc (s *SQLStore) getBlockHistoryNewestChildren(db sq.BaseRunner, parentID string, opts model.QueryBlockHistoryChildOptions) ([]*model.Block, bool, error) {\n\t// as we're joining 2 queries, we need to avoid numbered\n\t// placeholders until the join is done, so we use the default\n\t// question mark placeholder here\n\tbuilder := s.getQueryBuilder(db).PlaceholderFormat(sq.Question)\n\n\tsub := builder.\n\t\tSelect(\"bh2.id\", \"MAX(bh2.insert_at) AS max_insert_at\").\n\t\tFrom(s.tablePrefix + \"blocks_history AS bh2\").\n\t\tWhere(sq.Eq{\"bh2.parent_id\": parentID}).\n\t\tGroupBy(\"bh2.id\")\n\n\tif opts.AfterUpdateAt != 0 {\n\t\tsub = sub.Where(sq.Gt{\"bh2.update_at\": opts.AfterUpdateAt})\n\t}\n\n\tif opts.BeforeUpdateAt != 0 {\n\t\tsub = sub.Where(sq.Lt{\"bh2.update_at\": opts.BeforeUpdateAt})\n\t}\n\n\tsubQuery, subArgs, err := sub.ToSql()\n\tif err != nil {\n\t\treturn nil, false, fmt.Errorf(\"getBlockHistoryNewestChildren unable to generate subquery: %w\", err)\n\t}\n\n\tquery := s.getQueryBuilder(db).\n\t\tSelect(s.blockFields(\"bh\")...).\n\t\tFrom(s.tablePrefix+\"blocks_history AS bh\").\n\t\tInnerJoin(\"(\"+subQuery+\") AS sub ON bh.id=sub.id AND bh.insert_at=sub.max_insert_at\", subArgs...)\n\n\tif opts.Page != 0 {\n\t\tquery = query.Offset(uint64(opts.Page * opts.PerPage))\n\t}\n\n\tif opts.PerPage > 0 {\n\t\t// limit+1 to detect if more records available\n\t\tquery = query.Limit(uint64(opts.PerPage + 1))\n\t}\n\n\tsql, args, err := query.ToSql()\n\tif err != nil {\n\t\treturn nil, false, fmt.Errorf(\"getBlockHistoryNewestChildren unable to generate sql: %w\", err)\n\t}\n\n\t// if we're using postgres or sqlite, we need to replace the\n\t// question mark placeholder with the numbered dollar one, now\n\t// that the full query is built\n\tif s.dbType == model.PostgresDBType || s.dbType == model.SqliteDBType {\n\t\tvar rErr error\n\t\tsql, rErr = sq.Dollar.ReplacePlaceholders(sql)\n\t\tif rErr != nil {\n\t\t\treturn nil, false, fmt.Errorf(\"getBlockHistoryNewestChildren unable to replace sql placeholders: %w\", rErr)\n\t\t}\n\t}\n\n\trows, err := db.Query(sql, args...)\n\tif err != nil {\n\t\ts.logger.Error(`getBlockHistoryNewestChildren ERROR`, mlog.Err(err))\n\t\treturn nil, false, err\n\t}\n\tdefer s.CloseRows(rows)\n\n\tblocks, err := s.blocksFromRows(rows)\n\tif err != nil {\n\t\treturn nil, false, err\n\t}\n\n\thasMore := false\n\tif opts.PerPage > 0 && len(blocks) > opts.PerPage {\n\t\tblocks = blocks[:opts.PerPage]\n\t\thasMore = true\n\t}\n\treturn blocks, hasMore, nil\n}\n\n// getBoardAndCardByID returns the first parent of type `card` and first parent of type `board` for the block specified by ID.\n// `board` and/or `card` may return nil without error if the block does not belong to a board or card.\nfunc (s *SQLStore) getBoardAndCardByID(db sq.BaseRunner, blockID string) (board *model.Board, card *model.Block, err error) {\n\t// use block_history to fetch block in case it was deleted and no longer exists in blocks table.\n\topts := model.QueryBlockHistoryOptions{\n\t\tLimit:      1,\n\t\tDescending: true,\n\t}\n\n\tblocks, err := s.getBlockHistory(db, blockID, opts)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tif len(blocks) == 0 {\n\t\treturn nil, nil, model.NewErrNotFound(\"block history BlockID=\" + blockID)\n\t}\n\n\treturn s.getBoardAndCard(db, blocks[0])\n}\n\n// getBoardAndCard returns the first parent of type `card` and and the `board` for the specified block.\n// `board` and/or `card` may return nil without error if the block does not belong to a board or card.\nfunc (s *SQLStore) getBoardAndCard(db sq.BaseRunner, block *model.Block) (board *model.Board, card *model.Block, err error) {\n\tvar count int // don't let invalid blocks hierarchy cause infinite loop.\n\titer := block\n\n\t// use block_history to fetch blocks in case they were deleted and no longer exist in blocks table.\n\topts := model.QueryBlockHistoryOptions{\n\t\tLimit:      1,\n\t\tDescending: true,\n\t}\n\n\tfor {\n\t\tcount++\n\t\tif card == nil && iter.Type == model.TypeCard {\n\t\t\tcard = iter\n\t\t}\n\n\t\tif iter.ParentID == \"\" || card != nil || count > maxSearchDepth {\n\t\t\tbreak\n\t\t}\n\n\t\tblocks, err2 := s.getBlockHistory(db, iter.ParentID, opts)\n\t\tif err2 != nil {\n\t\t\treturn nil, nil, err2\n\t\t}\n\t\tif len(blocks) == 0 {\n\t\t\treturn board, card, nil\n\t\t}\n\t\titer = blocks[0]\n\t}\n\tboard, err = s.getBoard(db, block.BoardID)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\treturn board, card, nil\n}\n\nfunc (s *SQLStore) replaceBlockID(db sq.BaseRunner, currentID, newID, workspaceID string) error {\n\trunUpdateForBlocksAndHistory := func(query sq.UpdateBuilder) error {\n\t\tif _, err := query.Table(s.tablePrefix + \"blocks\").Exec(); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif _, err := query.Table(s.tablePrefix + \"blocks_history\").Exec(); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tbaseQuery := s.getQueryBuilder(db).\n\t\tWhere(sq.Eq{\"workspace_id\": workspaceID})\n\n\t// update ID\n\tupdateIDQ := baseQuery.Update(\"\").\n\t\tSet(\"id\", newID).\n\t\tWhere(sq.Eq{\"id\": currentID})\n\n\tif errID := runUpdateForBlocksAndHistory(updateIDQ); errID != nil {\n\t\ts.logger.Error(`replaceBlockID ERROR`, mlog.Err(errID))\n\t\treturn errID\n\t}\n\n\t// update BoardID\n\tupdateBoardIDQ := baseQuery.Update(\"\").\n\t\tSet(\"board_id\", newID).\n\t\tWhere(sq.Eq{\"board_id\": currentID})\n\n\tif errBoardID := runUpdateForBlocksAndHistory(updateBoardIDQ); errBoardID != nil {\n\t\ts.logger.Error(`replaceBlockID ERROR`, mlog.Err(errBoardID))\n\t\treturn errBoardID\n\t}\n\n\t// update ParentID\n\tupdateParentIDQ := baseQuery.Update(\"\").\n\t\tSet(\"parent_id\", newID).\n\t\tWhere(sq.Eq{\"parent_id\": currentID})\n\n\tif errParentID := runUpdateForBlocksAndHistory(updateParentIDQ); errParentID != nil {\n\t\ts.logger.Error(`replaceBlockID ERROR`, mlog.Err(errParentID))\n\t\treturn errParentID\n\t}\n\n\t// update parent contentOrder\n\tupdateContentOrder := baseQuery.Update(\"\")\n\tif s.dbType == model.PostgresDBType {\n\t\tupdateContentOrder = updateContentOrder.\n\t\t\tSet(\"fields\", sq.Expr(\"REPLACE(fields::text, ?, ?)::json\", currentID, newID)).\n\t\t\tWhere(sq.Like{\"fields->>'contentOrder'\": \"%\" + currentID + \"%\"}).\n\t\t\tWhere(sq.Eq{\"type\": model.TypeCard})\n\t} else {\n\t\tupdateContentOrder = updateContentOrder.\n\t\t\tSet(\"fields\", sq.Expr(\"REPLACE(fields, ?, ?)\", currentID, newID)).\n\t\t\tWhere(sq.Like{\"fields\": \"%\" + currentID + \"%\"}).\n\t\t\tWhere(sq.Eq{\"type\": model.TypeCard})\n\t}\n\n\tif errParentID := runUpdateForBlocksAndHistory(updateContentOrder); errParentID != nil {\n\t\ts.logger.Error(`replaceBlockID ERROR`, mlog.Err(errParentID))\n\t\treturn errParentID\n\t}\n\n\treturn nil\n}\n\nfunc (s *SQLStore) duplicateBlock(db sq.BaseRunner, boardID string, blockID string, userID string, asTemplate bool) ([]*model.Block, error) {\n\tblocks, err := s.getSubTree2(db, boardID, blockID, model.QuerySubtreeOptions{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(blocks) == 0 {\n\t\tmessage := fmt.Sprintf(\"block subtree BoardID=%s BlockID=%s\", boardID, blockID)\n\t\treturn nil, model.NewErrNotFound(message)\n\t}\n\n\tvar rootBlock *model.Block\n\tallBlocks := []*model.Block{}\n\tfor _, block := range blocks {\n\t\tif block.Type == model.TypeComment {\n\t\t\tcontinue\n\t\t}\n\t\tif block.ID == blockID {\n\t\t\tif block.Fields == nil {\n\t\t\t\tblock.Fields = make(map[string]interface{})\n\t\t\t}\n\t\t\tblock.Fields[\"isTemplate\"] = asTemplate\n\t\t\trootBlock = block\n\t\t} else {\n\t\t\tallBlocks = append(allBlocks, block)\n\t\t}\n\t}\n\tallBlocks = append([]*model.Block{rootBlock}, allBlocks...)\n\n\tallBlocks = model.GenerateBlockIDs(allBlocks, nil)\n\tif err := s.insertBlocks(db, allBlocks, userID); err != nil {\n\t\treturn nil, err\n\t}\n\treturn allBlocks, nil\n}\n\nfunc (s *SQLStore) deleteBlockChildren(db sq.BaseRunner, boardID string, parentID string, modifiedBy string) error {\n\tnow := utils.GetMillis()\n\n\tselectQuery := s.getQueryBuilder(db).\n\t\tSelect(\n\t\t\t\"board_id\",\n\t\t\t\"id\",\n\t\t\t\"parent_id\",\n\t\t\ts.escapeField(\"schema\"),\n\t\t\t\"type\",\n\t\t\t\"title\",\n\t\t\t\"fields\",\n\t\t\t\"'\"+modifiedBy+\"'\",\n\t\t\t\"create_at\",\n\t\t\ts.castInt(now, \"update_at\"),\n\t\t\ts.castInt(now, \"delete_at\"),\n\t\t\t\"created_by\",\n\t\t).\n\t\tFrom(s.tablePrefix + \"blocks\").\n\t\tWhere(sq.Eq{\"board_id\": boardID})\n\n\tif parentID != \"\" {\n\t\tselectQuery = selectQuery.Where(sq.Eq{\"parent_id\": parentID})\n\t}\n\n\tinsertQuery := s.getQueryBuilder(db).\n\t\tInsert(s.tablePrefix+\"blocks_history\").\n\t\tColumns(\n\t\t\t\"board_id\",\n\t\t\t\"id\",\n\t\t\t\"parent_id\",\n\t\t\ts.escapeField(\"schema\"),\n\t\t\t\"type\",\n\t\t\t\"title\",\n\t\t\t\"fields\",\n\t\t\t\"modified_by\",\n\t\t\t\"create_at\",\n\t\t\t\"update_at\",\n\t\t\t\"delete_at\",\n\t\t\t\"created_by\",\n\t\t).Select(selectQuery)\n\n\tif _, err := insertQuery.Exec(); err != nil {\n\t\treturn err\n\t}\n\n\tfileDeleteQuery := s.getQueryBuilder(db).\n\t\tSelect(s.blockFields(\"\")...).\n\t\tFrom(s.tablePrefix + \"blocks\").\n\t\tWhere(sq.Eq{\"board_id\": boardID})\n\n\tif parentID != \"\" {\n\t\tfileDeleteQuery = fileDeleteQuery.Where(sq.Eq{\"parent_id\": parentID})\n\t}\n\n\trows, err := fileDeleteQuery.Query()\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer s.CloseRows(rows)\n\tblocks, err := s.blocksFromRows(rows)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfileIDs := make([]string, 0, len(blocks))\n\tfor _, block := range blocks {\n\t\tfileIDWithExtention, fileIDExists := block.Fields[\"fileId\"]\n\t\tif fileIDExists {\n\t\t\tfileIDs = append(fileIDs, retrieveFileIDFromBlockFieldStorage(fileIDWithExtention.(string)))\n\t\t}\n\t\tattachmentIDWithExtention, attachmentIDExists := block.Fields[\"attachmentId\"]\n\t\tif attachmentIDExists {\n\t\t\tfileIDs = append(fileIDs, retrieveFileIDFromBlockFieldStorage(attachmentIDWithExtention.(string)))\n\t\t}\n\t}\n\n\tif len(fileIDs) > 0 {\n\t\tdeleteFileInfoQuery := s.getQueryBuilder(db).\n\t\t\tUpdate(\"FileInfo\").\n\t\t\tSet(\"DeleteAt\", model.GetMillis()).\n\t\t\tWhere(sq.Eq{\"id\": fileIDs})\n\n\t\tif _, err := deleteFileInfoQuery.Exec(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tdeleteQuery := s.getQueryBuilder(db).\n\t\tDelete(s.tablePrefix + \"blocks\").\n\t\tWhere(sq.Eq{\"board_id\": boardID})\n\n\tif parentID != \"\" {\n\t\tdeleteQuery = deleteQuery.Where(sq.Eq{\"parent_id\": parentID})\n\t}\n\n\tif _, err := deleteQuery.Exec(); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (s *SQLStore) undeleteBlockChildren(db sq.BaseRunner, boardID string, parentID string, modifiedBy string) error {\n\tif boardID == \"\" {\n\t\treturn model.ErrBlockEmptyBoardID\n\t}\n\n\twhere := fmt.Sprintf(\"board_id='%s'\", boardID)\n\tif parentID != \"\" {\n\t\twhere += fmt.Sprintf(\" AND parent_id='%s'\", parentID)\n\t}\n\n\tselectQuery := s.getQueryBuilder(db).\n\t\tSelect(\n\t\t\t\"bh.board_id\",\n\t\t\t\"'' AS channel_id\",\n\t\t\t\"bh.id\",\n\t\t\t\"bh.parent_id\",\n\t\t\t\"bh.schema\",\n\t\t\t\"bh.type\",\n\t\t\t\"bh.title\",\n\t\t\t\"bh.fields\",\n\t\t\t\"'\"+modifiedBy+\"' AS modified_by\",\n\t\t\t\"bh.create_at\",\n\t\t\ts.castInt(utils.GetMillis(), \"update_at\"),\n\t\t\ts.castInt(0, \"delete_at\"),\n\t\t\t\"bh.created_by\",\n\t\t).\n\t\tFrom(fmt.Sprintf(`\n\t\t\t\t%sblocks_history AS bh,\n\t\t\t\t(SELECT id, max(insert_at) AS max_insert_at FROM %sblocks_history WHERE %s GROUP BY id) AS sub`,\n\t\t\ts.tablePrefix, s.tablePrefix, where)).\n\t\tWhere(\"bh.id=sub.id\").\n\t\tWhere(\"bh.insert_at=sub.max_insert_at\").\n\t\tWhere(sq.NotEq{\"bh.delete_at\": 0})\n\n\tcolumns := []string{\n\t\t\"board_id\",\n\t\t\"channel_id\",\n\t\t\"id\",\n\t\t\"parent_id\",\n\t\ts.escapeField(\"schema\"),\n\t\t\"type\",\n\t\t\"title\",\n\t\t\"fields\",\n\t\t\"modified_by\",\n\t\t\"create_at\",\n\t\t\"update_at\",\n\t\t\"delete_at\",\n\t\t\"created_by\",\n\t}\n\n\tinsertQuery := s.getQueryBuilder(db).Insert(s.tablePrefix + \"blocks\").\n\t\tColumns(columns...).\n\t\tSelect(selectQuery)\n\n\tinsertHistoryQuery := s.getQueryBuilder(db).Insert(s.tablePrefix + \"blocks_history\").\n\t\tColumns(columns...).\n\t\tSelect(selectQuery)\n\n\tsql, args, err := insertQuery.ToSql()\n\ts.logger.Trace(\"undeleteBlockChildren - insertQuery\",\n\t\tmlog.String(\"sql\", sql),\n\t\tmlog.Array(\"args\", args),\n\t\tmlog.Err(err),\n\t)\n\n\tsql, args, err = insertHistoryQuery.ToSql()\n\ts.logger.Trace(\"undeleteBlockChildren - insertHistoryQuery\",\n\t\tmlog.String(\"sql\", sql),\n\t\tmlog.Array(\"args\", args),\n\t\tmlog.Err(err),\n\t)\n\n\t// insert into blocks table must happen before history table, otherwise the history\n\t// table will be changed and the second query will fail to find the same records.\n\tresult, err := insertQuery.Exec()\n\tif err != nil {\n\t\treturn err\n\t}\n\trowsAffected, _ := result.RowsAffected()\n\ts.logger.Debug(\"undeleteBlockChildren - insertQuery\", mlog.Int(\"rows_affected\", rowsAffected))\n\n\tresult, err = insertHistoryQuery.Exec()\n\tif err != nil {\n\t\treturn err\n\t}\n\trowsAffected, _ = result.RowsAffected()\n\ts.logger.Debug(\"undeleteBlockChildren - insertHistoryQuery\", mlog.Int(\"rows_affected\", rowsAffected))\n\n\treturn nil\n}\n"
  },
  {
    "path": "server/services/store/sqlstore/board.go",
    "content": "package sqlstore\n\nimport (\n\t//nolint:gosec\n\t\"crypto/md5\"\n\t\"database/sql\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/mattermost/focalboard/server/utils\"\n\n\tsq \"github.com/Masterminds/squirrel\"\n\t\"github.com/mattermost/focalboard/server/model\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nfunc boardFields(tableAlias string) []string {\n\tif tableAlias != \"\" && !strings.HasSuffix(tableAlias, \".\") {\n\t\ttableAlias += \".\"\n\t}\n\n\treturn []string{\n\t\ttableAlias + \"id\",\n\t\ttableAlias + \"team_id\",\n\t\t\"COALESCE(\" + tableAlias + \"channel_id, '')\",\n\t\t\"COALESCE(\" + tableAlias + \"created_by, '')\",\n\t\ttableAlias + \"modified_by\",\n\t\ttableAlias + \"type\",\n\t\ttableAlias + \"minimum_role\",\n\t\ttableAlias + \"title\",\n\t\ttableAlias + \"description\",\n\t\ttableAlias + \"icon\",\n\t\ttableAlias + \"show_description\",\n\t\ttableAlias + \"is_template\",\n\t\ttableAlias + \"template_version\",\n\t\t\"COALESCE(\" + tableAlias + \"properties, '{}')\",\n\t\t\"COALESCE(\" + tableAlias + \"card_properties, '[]')\",\n\t\ttableAlias + \"create_at\",\n\t\ttableAlias + \"update_at\",\n\t\ttableAlias + \"delete_at\",\n\t}\n}\n\nfunc boardHistoryFields() []string {\n\tfields := []string{\n\t\t\"id\",\n\t\t\"team_id\",\n\t\t\"COALESCE(channel_id, '')\",\n\t\t\"COALESCE(created_by, '')\",\n\t\t\"COALESCE(modified_by, '')\",\n\t\t\"type\",\n\t\t\"minimum_role\",\n\t\t\"COALESCE(title, '')\",\n\t\t\"COALESCE(description, '')\",\n\t\t\"COALESCE(icon, '')\",\n\t\t\"COALESCE(show_description, false)\",\n\t\t\"COALESCE(is_template, false)\",\n\t\t\"template_version\",\n\t\t\"COALESCE(properties, '{}')\",\n\t\t\"COALESCE(card_properties, '[]')\",\n\t\t\"COALESCE(create_at, 0)\",\n\t\t\"COALESCE(update_at, 0)\",\n\t\t\"COALESCE(delete_at, 0)\",\n\t}\n\n\treturn fields\n}\n\nvar boardMemberFields = []string{\n\t\"COALESCE(B.minimum_role, '')\",\n\t\"BM.board_id\",\n\t\"BM.user_id\",\n\t\"BM.roles\",\n\t\"BM.scheme_admin\",\n\t\"BM.scheme_editor\",\n\t\"BM.scheme_commenter\",\n\t\"BM.scheme_viewer\",\n}\n\nfunc (s *SQLStore) boardsFromRows(rows *sql.Rows) ([]*model.Board, error) {\n\tboards := []*model.Board{}\n\n\tfor rows.Next() {\n\t\tvar board model.Board\n\t\tvar propertiesBytes []byte\n\t\tvar cardPropertiesBytes []byte\n\n\t\terr := rows.Scan(\n\t\t\t&board.ID,\n\t\t\t&board.TeamID,\n\t\t\t&board.ChannelID,\n\t\t\t&board.CreatedBy,\n\t\t\t&board.ModifiedBy,\n\t\t\t&board.Type,\n\t\t\t&board.MinimumRole,\n\t\t\t&board.Title,\n\t\t\t&board.Description,\n\t\t\t&board.Icon,\n\t\t\t&board.ShowDescription,\n\t\t\t&board.IsTemplate,\n\t\t\t&board.TemplateVersion,\n\t\t\t&propertiesBytes,\n\t\t\t&cardPropertiesBytes,\n\t\t\t&board.CreateAt,\n\t\t\t&board.UpdateAt,\n\t\t\t&board.DeleteAt,\n\t\t)\n\t\tif err != nil {\n\t\t\ts.logger.Error(\"boardsFromRows scan error\", mlog.Err(err))\n\t\t\treturn nil, err\n\t\t}\n\n\t\terr = json.Unmarshal(propertiesBytes, &board.Properties)\n\t\tif err != nil {\n\t\t\ts.logger.Error(\"board properties unmarshal error\", mlog.Err(err))\n\t\t\treturn nil, err\n\t\t}\n\t\terr = json.Unmarshal(cardPropertiesBytes, &board.CardProperties)\n\t\tif err != nil {\n\t\t\ts.logger.Error(\"board card properties unmarshal error\", mlog.Err(err))\n\t\t\treturn nil, err\n\t\t}\n\n\t\tboards = append(boards, &board)\n\t}\n\n\treturn boards, nil\n}\n\nfunc (s *SQLStore) boardMembersFromRows(rows *sql.Rows) ([]*model.BoardMember, error) {\n\tboardMembers := []*model.BoardMember{}\n\n\tfor rows.Next() {\n\t\tvar boardMember model.BoardMember\n\n\t\terr := rows.Scan(\n\t\t\t&boardMember.MinimumRole,\n\t\t\t&boardMember.BoardID,\n\t\t\t&boardMember.UserID,\n\t\t\t&boardMember.Roles,\n\t\t\t&boardMember.SchemeAdmin,\n\t\t\t&boardMember.SchemeEditor,\n\t\t\t&boardMember.SchemeCommenter,\n\t\t\t&boardMember.SchemeViewer,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tboardMembers = append(boardMembers, &boardMember)\n\t}\n\n\treturn boardMembers, nil\n}\n\nfunc (s *SQLStore) boardMemberHistoryEntriesFromRows(rows *sql.Rows) ([]*model.BoardMemberHistoryEntry, error) {\n\tboardMemberHistoryEntries := []*model.BoardMemberHistoryEntry{}\n\n\tfor rows.Next() {\n\t\tvar boardMemberHistoryEntry model.BoardMemberHistoryEntry\n\t\tvar insertAt sql.NullString\n\n\t\terr := rows.Scan(\n\t\t\t&boardMemberHistoryEntry.BoardID,\n\t\t\t&boardMemberHistoryEntry.UserID,\n\t\t\t&boardMemberHistoryEntry.Action,\n\t\t\t&insertAt,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\t// parse the insert_at timestamp which is different based on database type.\n\t\tdateTemplate := \"2006-01-02T15:04:05Z0700\"\n\t\tif s.dbType == model.MysqlDBType {\n\t\t\tdateTemplate = \"2006-01-02 15:04:05.000000\"\n\t\t}\n\t\tts, err := time.Parse(dateTemplate, insertAt.String)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"cannot parse datetime '%s' for board_members_history scan: %w\", insertAt.String, err)\n\t\t}\n\t\tboardMemberHistoryEntry.InsertAt = ts\n\n\t\tboardMemberHistoryEntries = append(boardMemberHistoryEntries, &boardMemberHistoryEntry)\n\t}\n\n\treturn boardMemberHistoryEntries, nil\n}\n\nfunc (s *SQLStore) getBoardByCondition(db sq.BaseRunner, conditions ...interface{}) (*model.Board, error) {\n\tboards, err := s.getBoardsByCondition(db, conditions...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn boards[0], nil\n}\n\nfunc (s *SQLStore) getBoardsByCondition(db sq.BaseRunner, conditions ...interface{}) ([]*model.Board, error) {\n\treturn s.getBoardsFieldsByCondition(db, boardFields(\"\"), conditions...)\n}\n\nfunc (s *SQLStore) getBoardsFieldsByCondition(db sq.BaseRunner, fields []string, conditions ...interface{}) ([]*model.Board, error) {\n\tquery := s.getQueryBuilder(db).\n\t\tSelect(fields...).\n\t\tFrom(s.tablePrefix + \"boards\")\n\tfor _, c := range conditions {\n\t\tquery = query.Where(c)\n\t}\n\n\trows, err := query.Query()\n\tif err != nil {\n\t\ts.logger.Error(`getBoardsFieldsByCondition ERROR`, mlog.Err(err))\n\t\treturn nil, err\n\t}\n\tdefer s.CloseRows(rows)\n\n\tboards, err := s.boardsFromRows(rows)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(boards) == 0 {\n\t\treturn nil, model.NewErrNotFound(\"boards\")\n\t}\n\n\treturn boards, nil\n}\n\nfunc (s *SQLStore) getBoard(db sq.BaseRunner, boardID string) (*model.Board, error) {\n\treturn s.getBoardByCondition(db, sq.Eq{\"id\": boardID})\n}\n\nfunc (s *SQLStore) getBoardsForUserAndTeam(db sq.BaseRunner, userID, teamID string, includePublicBoards bool) ([]*model.Board, error) {\n\tquery := s.getQueryBuilder(db).\n\t\tSelect(boardFields(\"b.\")...).\n\t\tDistinct().\n\t\tFrom(s.tablePrefix + \"boards as b\").\n\t\tLeftJoin(s.tablePrefix + \"board_members as bm on b.id=bm.board_id\").\n\t\tWhere(sq.Eq{\"b.team_id\": teamID}).\n\t\tWhere(sq.Eq{\"b.is_template\": false})\n\n\tif includePublicBoards {\n\t\tquery = query.Where(sq.Or{\n\t\t\tsq.Eq{\"b.type\": model.BoardTypeOpen},\n\t\t\tsq.Eq{\"bm.user_id\": userID},\n\t\t})\n\t} else {\n\t\tquery = query.Where(sq.Or{\n\t\t\tsq.Eq{\"bm.user_id\": userID},\n\t\t})\n\t}\n\n\trows, err := query.Query()\n\tif err != nil {\n\t\ts.logger.Error(`getBoardsForUserAndTeam ERROR`, mlog.Err(err))\n\t\treturn nil, err\n\t}\n\tdefer s.CloseRows(rows)\n\n\treturn s.boardsFromRows(rows)\n}\n\nfunc (s *SQLStore) getBoardsInTeamByIds(db sq.BaseRunner, boardIDs []string, teamID string) ([]*model.Board, error) {\n\tquery := s.getQueryBuilder(db).\n\t\tSelect(boardFields(\"b.\")...).\n\t\tFrom(s.tablePrefix + \"boards as b\").\n\t\tWhere(sq.Eq{\"b.team_id\": teamID}).\n\t\tWhere(sq.Eq{\"b.id\": boardIDs})\n\n\trows, err := query.Query()\n\tif err != nil {\n\t\ts.logger.Error(`getBoardsInTeamByIds ERROR`, mlog.Err(err))\n\t\treturn nil, err\n\t}\n\tdefer s.CloseRows(rows)\n\n\tboards, err := s.boardsFromRows(rows)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(boards) != len(boardIDs) {\n\t\ts.logger.Warn(\"getBoardsInTeamByIds mismatched number of boards found\",\n\t\t\tmlog.Int(\"len(boards)\", len(boards)),\n\t\t\tmlog.Int(\"len(boardIDs)\", len(boardIDs)),\n\t\t)\n\t\treturn boards, model.NewErrNotAllFound(\"board\", boardIDs)\n\t}\n\n\treturn boards, nil\n}\n\nfunc (s *SQLStore) insertBoard(db sq.BaseRunner, board *model.Board, userID string) (*model.Board, error) {\n\t// Generate tracking IDs for in-built templates\n\tif board.IsTemplate && board.TeamID == model.GlobalTeamID {\n\t\t//nolint:gosec\n\t\t// we don't need cryptographically secure hash, so MD5 is fine\n\t\tboard.Properties[\"trackingTemplateId\"] = fmt.Sprintf(\"%x\", md5.Sum([]byte(board.Title)))\n\t}\n\n\tpropertiesBytes, err := s.MarshalJSONB(board.Properties)\n\tif err != nil {\n\t\ts.logger.Error(\n\t\t\t\"failed to marshal board.Properties\",\n\t\t\tmlog.String(\"board_id\", board.ID),\n\t\t\tmlog.String(\"board.Properties\", fmt.Sprintf(\"%v\", board.Properties)),\n\t\t\tmlog.Err(err),\n\t\t)\n\t\treturn nil, err\n\t}\n\n\tcardPropertiesBytes, err := s.MarshalJSONB(board.CardProperties)\n\tif err != nil {\n\t\ts.logger.Error(\n\t\t\t\"failed to marshal board.CardProperties\",\n\t\t\tmlog.String(\"board_id\", board.ID),\n\t\t\tmlog.String(\"board.CardProperties\", fmt.Sprintf(\"%v\", board.CardProperties)),\n\t\t\tmlog.Err(err),\n\t\t)\n\t\treturn nil, err\n\t}\n\n\texistingBoard, err := s.getBoard(db, board.ID)\n\tif err != nil && !model.IsErrNotFound(err) {\n\t\treturn nil, fmt.Errorf(\"insertBoard error occurred while fetching existing board %s: %w\", board.ID, err)\n\t}\n\n\tinsertQuery := s.getQueryBuilder(db).Insert(\"\").\n\t\tColumns(boardFields(\"\")...)\n\n\tnow := utils.GetMillis()\n\tboard.ModifiedBy = userID\n\tboard.UpdateAt = now\n\n\tinsertQueryValues := map[string]interface{}{\n\t\t\"id\":               board.ID,\n\t\t\"team_id\":          board.TeamID,\n\t\t\"channel_id\":       board.ChannelID,\n\t\t\"created_by\":       board.CreatedBy,\n\t\t\"modified_by\":      board.ModifiedBy,\n\t\t\"type\":             board.Type,\n\t\t\"title\":            board.Title,\n\t\t\"minimum_role\":     board.MinimumRole,\n\t\t\"description\":      board.Description,\n\t\t\"icon\":             board.Icon,\n\t\t\"show_description\": board.ShowDescription,\n\t\t\"is_template\":      board.IsTemplate,\n\t\t\"template_version\": board.TemplateVersion,\n\t\t\"properties\":       propertiesBytes,\n\t\t\"card_properties\":  cardPropertiesBytes,\n\t\t\"create_at\":        board.CreateAt,\n\t\t\"update_at\":        board.UpdateAt,\n\t\t\"delete_at\":        board.DeleteAt,\n\t}\n\n\tif existingBoard != nil {\n\t\tquery := s.getQueryBuilder(db).Update(s.tablePrefix+\"boards\").\n\t\t\tWhere(sq.Eq{\"id\": board.ID}).\n\t\t\tSet(\"modified_by\", board.ModifiedBy).\n\t\t\tSet(\"type\", board.Type).\n\t\t\tSet(\"channel_id\", board.ChannelID).\n\t\t\tSet(\"minimum_role\", board.MinimumRole).\n\t\t\tSet(\"title\", board.Title).\n\t\t\tSet(\"description\", board.Description).\n\t\t\tSet(\"icon\", board.Icon).\n\t\t\tSet(\"show_description\", board.ShowDescription).\n\t\t\tSet(\"is_template\", board.IsTemplate).\n\t\t\tSet(\"template_version\", board.TemplateVersion).\n\t\t\tSet(\"properties\", propertiesBytes).\n\t\t\tSet(\"card_properties\", cardPropertiesBytes).\n\t\t\tSet(\"update_at\", board.UpdateAt).\n\t\t\tSet(\"delete_at\", board.DeleteAt)\n\n\t\tif _, err := query.Exec(); err != nil {\n\t\t\ts.logger.Error(`InsertBoard error occurred while updating existing board`, mlog.String(\"boardID\", board.ID), mlog.Err(err))\n\t\t\treturn nil, fmt.Errorf(\"insertBoard error occurred while updating existing board %s: %w\", board.ID, err)\n\t\t}\n\t} else {\n\t\tboard.CreatedBy = userID\n\t\tboard.CreateAt = now\n\t\tinsertQueryValues[\"created_by\"] = board.CreatedBy\n\t\tinsertQueryValues[\"create_at\"] = board.CreateAt\n\n\t\tquery := insertQuery.SetMap(insertQueryValues).Into(s.tablePrefix + \"boards\")\n\t\tif _, err := query.Exec(); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"insertBoard error occurred while inserting board %s: %w\", board.ID, err)\n\t\t}\n\t}\n\n\t// writing board history\n\tquery := insertQuery.SetMap(insertQueryValues).Into(s.tablePrefix + \"boards_history\")\n\tif _, err := query.Exec(); err != nil {\n\t\ts.logger.Error(\"failed to insert board history\", mlog.String(\"board_id\", board.ID), mlog.Err(err))\n\t\treturn nil, fmt.Errorf(\"failed to insert board %s history: %w\", board.ID, err)\n\t}\n\n\treturn board, nil\n}\n\nfunc (s *SQLStore) patchBoard(db sq.BaseRunner, boardID string, boardPatch *model.BoardPatch, userID string) (*model.Board, error) {\n\texistingBoard, err := s.getBoard(db, boardID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tboard := boardPatch.Patch(existingBoard)\n\treturn s.insertBoard(db, board, userID)\n}\n\nfunc (s *SQLStore) deleteBoard(db sq.BaseRunner, boardID, userID string) error {\n\treturn s.deleteBoardAndChildren(db, boardID, userID, false)\n}\n\nfunc (s *SQLStore) deleteBoardAndChildren(db sq.BaseRunner, boardID, userID string, keepChildren bool) error {\n\tnow := utils.GetMillis()\n\n\tboard, err := s.getBoard(db, boardID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tpropertiesBytes, err := s.MarshalJSONB(board.Properties)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcardPropertiesBytes, err := s.MarshalJSONB(board.CardProperties)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tinsertQueryValues := map[string]interface{}{\n\t\t\"id\":               board.ID,\n\t\t\"team_id\":          board.TeamID,\n\t\t\"channel_id\":       board.ChannelID,\n\t\t\"created_by\":       board.CreatedBy,\n\t\t\"modified_by\":      userID,\n\t\t\"type\":             board.Type,\n\t\t\"minimum_role\":     board.MinimumRole,\n\t\t\"title\":            board.Title,\n\t\t\"description\":      board.Description,\n\t\t\"icon\":             board.Icon,\n\t\t\"show_description\": board.ShowDescription,\n\t\t\"is_template\":      board.IsTemplate,\n\t\t\"template_version\": board.TemplateVersion,\n\t\t\"properties\":       propertiesBytes,\n\t\t\"card_properties\":  cardPropertiesBytes,\n\t\t\"create_at\":        board.CreateAt,\n\t\t\"update_at\":        now,\n\t\t\"delete_at\":        now,\n\t}\n\n\t// writing board history\n\tinsertQuery := s.getQueryBuilder(db).Insert(\"\").\n\t\tColumns(boardHistoryFields()...)\n\n\tquery := insertQuery.SetMap(insertQueryValues).Into(s.tablePrefix + \"boards_history\")\n\tif _, err := query.Exec(); err != nil {\n\t\treturn err\n\t}\n\n\tdeleteQuery := s.getQueryBuilder(db).\n\t\tDelete(s.tablePrefix + \"boards\").\n\t\tWhere(sq.Eq{\"id\": boardID}).\n\t\tWhere(sq.Eq{\"COALESCE(team_id, '0')\": board.TeamID})\n\n\tif _, err := deleteQuery.Exec(); err != nil {\n\t\treturn err\n\t}\n\n\tif keepChildren {\n\t\treturn nil\n\t}\n\n\treturn s.deleteBlockChildren(db, boardID, \"\", userID)\n}\n\nfunc (s *SQLStore) insertBoardWithAdmin(db sq.BaseRunner, board *model.Board, userID string) (*model.Board, *model.BoardMember, error) {\n\tnewBoard, err := s.insertBoard(db, board, userID)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tbm := &model.BoardMember{\n\t\tBoardID:      newBoard.ID,\n\t\tUserID:       newBoard.CreatedBy,\n\t\tSchemeAdmin:  true,\n\t\tSchemeEditor: true,\n\t}\n\n\tnbm, err := s.saveMember(db, bm)\n\tif err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"cannot save member %s while inserting board %s: %w\", bm.UserID, bm.BoardID, err)\n\t}\n\n\treturn newBoard, nbm, nil\n}\n\nfunc (s *SQLStore) saveMember(db sq.BaseRunner, bm *model.BoardMember) (*model.BoardMember, error) {\n\tqueryValues := map[string]interface{}{\n\t\t\"board_id\":         bm.BoardID,\n\t\t\"user_id\":          bm.UserID,\n\t\t\"roles\":            \"\",\n\t\t\"scheme_admin\":     bm.SchemeAdmin,\n\t\t\"scheme_editor\":    bm.SchemeEditor,\n\t\t\"scheme_commenter\": bm.SchemeCommenter,\n\t\t\"scheme_viewer\":    bm.SchemeViewer,\n\t}\n\n\toldMember, err := s.getMemberForBoard(db, bm.BoardID, bm.UserID)\n\tif err != nil && !model.IsErrNotFound(err) {\n\t\treturn nil, err\n\t}\n\n\tquery := s.getQueryBuilder(db).\n\t\tInsert(s.tablePrefix + \"board_members\").\n\t\tSetMap(queryValues)\n\n\tif s.dbType == model.MysqlDBType {\n\t\tquery = query.Suffix(\n\t\t\t\"ON DUPLICATE KEY UPDATE scheme_admin = ?, scheme_editor = ?, scheme_commenter = ?, scheme_viewer = ?\",\n\t\t\tbm.SchemeAdmin, bm.SchemeEditor, bm.SchemeCommenter, bm.SchemeViewer)\n\t} else {\n\t\tquery = query.Suffix(\n\t\t\t`ON CONFLICT (board_id, user_id)\n             DO UPDATE SET scheme_admin = EXCLUDED.scheme_admin, scheme_editor = EXCLUDED.scheme_editor,\n\t\t\t   scheme_commenter = EXCLUDED.scheme_commenter, scheme_viewer = EXCLUDED.scheme_viewer`,\n\t\t)\n\t}\n\n\tif _, err := query.Exec(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif oldMember == nil {\n\t\taddToMembersHistory := s.getQueryBuilder(db).\n\t\t\tInsert(s.tablePrefix+\"board_members_history\").\n\t\t\tColumns(\"board_id\", \"user_id\", \"action\").\n\t\t\tValues(bm.BoardID, bm.UserID, \"created\")\n\n\t\tif _, err := addToMembersHistory.Exec(); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn bm, nil\n}\n\nfunc (s *SQLStore) deleteMember(db sq.BaseRunner, boardID, userID string) error {\n\tdeleteQuery := s.getQueryBuilder(db).\n\t\tDelete(s.tablePrefix + \"board_members\").\n\t\tWhere(sq.Eq{\"board_id\": boardID}).\n\t\tWhere(sq.Eq{\"user_id\": userID})\n\n\tresult, err := deleteQuery.Exec()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\trowsAffected, err := result.RowsAffected()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif rowsAffected > 0 {\n\t\taddToMembersHistory := s.getQueryBuilder(db).\n\t\t\tInsert(s.tablePrefix+\"board_members_history\").\n\t\t\tColumns(\"board_id\", \"user_id\", \"action\").\n\t\t\tValues(boardID, userID, \"deleted\")\n\n\t\tif _, err := addToMembersHistory.Exec(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (s *SQLStore) getMemberForBoard(db sq.BaseRunner, boardID, userID string) (*model.BoardMember, error) {\n\tquery := s.getQueryBuilder(db).\n\t\tSelect(boardMemberFields...).\n\t\tFrom(s.tablePrefix + \"board_members AS BM\").\n\t\tLeftJoin(s.tablePrefix + \"boards AS B ON B.id=BM.board_id\").\n\t\tWhere(sq.Eq{\"BM.board_id\": boardID}).\n\t\tWhere(sq.Eq{\"BM.user_id\": userID})\n\n\trows, err := query.Query()\n\tif err != nil {\n\t\ts.logger.Error(`getMemberForBoard ERROR`, mlog.Err(err))\n\t\treturn nil, err\n\t}\n\tdefer s.CloseRows(rows)\n\n\tmembers, err := s.boardMembersFromRows(rows)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(members) == 0 {\n\t\tmessage := fmt.Sprintf(\"board member BoardID=%s UserID=%s\", boardID, userID)\n\t\treturn nil, model.NewErrNotFound(message)\n\t}\n\n\treturn members[0], nil\n}\n\nfunc (s *SQLStore) getMembersForUser(db sq.BaseRunner, userID string) ([]*model.BoardMember, error) {\n\tquery := s.getQueryBuilder(db).\n\t\tSelect(boardMemberFields...).\n\t\tFrom(s.tablePrefix + \"board_members AS BM\").\n\t\tLeftJoin(s.tablePrefix + \"boards AS B ON B.id=BM.board_id\").\n\t\tWhere(sq.Eq{\"BM.user_id\": userID})\n\n\trows, err := query.Query()\n\tif err != nil {\n\t\ts.logger.Error(`getMembersForUser ERROR`, mlog.Err(err))\n\t\treturn nil, err\n\t}\n\tdefer s.CloseRows(rows)\n\n\tmembers, err := s.boardMembersFromRows(rows)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn members, nil\n}\n\nfunc (s *SQLStore) getMembersForBoard(db sq.BaseRunner, boardID string) ([]*model.BoardMember, error) {\n\tquery := s.getQueryBuilder(db).\n\t\tSelect(boardMemberFields...).\n\t\tFrom(s.tablePrefix + \"board_members AS BM\").\n\t\tLeftJoin(s.tablePrefix + \"boards AS B ON B.id=BM.board_id\").\n\t\tWhere(sq.Eq{\"BM.board_id\": boardID})\n\n\trows, err := query.Query()\n\tif err != nil {\n\t\ts.logger.Error(`getMembersForBoard ERROR`, mlog.Err(err))\n\t\treturn nil, err\n\t}\n\tdefer s.CloseRows(rows)\n\n\treturn s.boardMembersFromRows(rows)\n}\n\n// searchBoardsForUser returns all boards that match with the\n// term that are either private and which the user is a member of, or\n// they're open, regardless of the user membership.\n// Search is case-insensitive.\nfunc (s *SQLStore) searchBoardsForUser(db sq.BaseRunner, term string, searchField model.BoardSearchField, userID string, includePublicBoards bool) ([]*model.Board, error) {\n\tquery := s.getQueryBuilder(db).\n\t\tSelect(boardFields(\"b.\")...).\n\t\tDistinct().\n\t\tFrom(s.tablePrefix + \"boards as b\").\n\t\tLeftJoin(s.tablePrefix + \"board_members as bm on b.id=bm.board_id\").\n\t\tWhere(sq.Eq{\"b.is_template\": false})\n\n\tif includePublicBoards {\n\t\tquery = query.Where(sq.Or{\n\t\t\tsq.Eq{\"b.type\": model.BoardTypeOpen},\n\t\t\tsq.Eq{\"bm.user_id\": userID},\n\t\t})\n\t} else {\n\t\tquery = query.Where(sq.Or{\n\t\t\tsq.Eq{\"bm.user_id\": userID},\n\t\t})\n\t}\n\n\tif term != \"\" {\n\t\tif searchField == model.BoardSearchFieldPropertyName {\n\t\t\tswitch s.dbType {\n\t\t\tcase model.PostgresDBType:\n\t\t\t\twhere := \"b.properties->? is not null\"\n\t\t\t\tquery = query.Where(where, term)\n\t\t\tcase model.MysqlDBType, model.SqliteDBType:\n\t\t\t\twhere := \"JSON_EXTRACT(b.properties, ?) IS NOT NULL\"\n\t\t\t\tquery = query.Where(where, \"$.\"+term)\n\t\t\tdefault:\n\t\t\t\twhere := \"b.properties LIKE ?\"\n\t\t\t\tquery = query.Where(where, \"%\\\"\"+term+\"\\\"%\")\n\t\t\t}\n\t\t} else { // model.BoardSearchFieldTitle\n\t\t\t// break search query into space separated words\n\t\t\t// and search for all words.\n\t\t\t// This should later be upgraded to industrial-strength\n\t\t\t// word tokenizer, that uses much more than space\n\t\t\t// to break words.\n\t\t\tconditions := sq.And{}\n\t\t\tfor _, word := range strings.Split(strings.TrimSpace(term), \" \") {\n\t\t\t\tconditions = append(conditions, sq.Like{\"lower(b.title)\": \"%\" + strings.ToLower(word) + \"%\"})\n\t\t\t}\n\t\t\tquery = query.Where(conditions)\n\t\t}\n\t}\n\n\trows, err := query.Query()\n\tif err != nil {\n\t\ts.logger.Error(`searchBoardsForUser ERROR`, mlog.Err(err))\n\t\treturn nil, err\n\t}\n\tdefer s.CloseRows(rows)\n\n\treturn s.boardsFromRows(rows)\n}\n\n// searchBoardsForUserInTeam returns all boards that match with the\n// term that are either private and which the user is a member of, or\n// they're open, regardless of the user membership.\n// Search is case-insensitive.\nfunc (s *SQLStore) searchBoardsForUserInTeam(db sq.BaseRunner, teamID, term, userID string) ([]*model.Board, error) {\n\tquery := s.getQueryBuilder(db).\n\t\tSelect(boardFields(\"b.\")...).\n\t\tDistinct().\n\t\tFrom(s.tablePrefix + \"boards as b\").\n\t\tLeftJoin(s.tablePrefix + \"board_members as bm on b.id=bm.board_id\").\n\t\tWhere(sq.Eq{\"b.is_template\": false}).\n\t\tWhere(sq.Eq{\"b.team_id\": teamID}).\n\t\tWhere(sq.Or{\n\t\t\tsq.Eq{\"b.type\": model.BoardTypeOpen},\n\t\t\tsq.And{\n\t\t\t\tsq.Eq{\"b.type\": model.BoardTypePrivate},\n\t\t\t\tsq.Eq{\"bm.user_id\": userID},\n\t\t\t},\n\t\t})\n\n\tif term != \"\" {\n\t\t// break search query into space separated words\n\t\t// and search for all words.\n\t\t// This should later be upgraded to industrial-strength\n\t\t// word tokenizer, that uses much more than space\n\t\t// to break words.\n\n\t\tconditions := sq.And{}\n\n\t\tfor _, word := range strings.Split(strings.TrimSpace(term), \" \") {\n\t\t\tconditions = append(conditions, sq.Like{\"lower(b.title)\": \"%\" + strings.ToLower(word) + \"%\"})\n\t\t}\n\n\t\tquery = query.Where(conditions)\n\t}\n\n\trows, err := query.Query()\n\tif err != nil {\n\t\ts.logger.Error(`searchBoardsForUser ERROR`, mlog.Err(err))\n\t\treturn nil, err\n\t}\n\tdefer s.CloseRows(rows)\n\n\treturn s.boardsFromRows(rows)\n}\n\nfunc (s *SQLStore) getBoardHistory(db sq.BaseRunner, boardID string, opts model.QueryBoardHistoryOptions) ([]*model.Board, error) {\n\tvar order string\n\tif opts.Descending {\n\t\torder = \" DESC \"\n\t}\n\n\tquery := s.getQueryBuilder(db).\n\t\tSelect(boardHistoryFields()...).\n\t\tFrom(s.tablePrefix + \"boards_history\").\n\t\tWhere(sq.Eq{\"id\": boardID}).\n\t\tOrderBy(\"insert_at \" + order + \", update_at\" + order)\n\n\tif opts.BeforeUpdateAt != 0 {\n\t\tquery = query.Where(sq.Lt{\"update_at\": opts.BeforeUpdateAt})\n\t}\n\n\tif opts.AfterUpdateAt != 0 {\n\t\tquery = query.Where(sq.Gt{\"update_at\": opts.AfterUpdateAt})\n\t}\n\n\tif opts.Limit != 0 {\n\t\tquery = query.Limit(opts.Limit)\n\t}\n\n\trows, err := query.Query()\n\tif err != nil {\n\t\ts.logger.Error(`getBoardHistory ERROR`, mlog.Err(err))\n\t\treturn nil, err\n\t}\n\tdefer s.CloseRows(rows)\n\n\treturn s.boardsFromRows(rows)\n}\n\nfunc (s *SQLStore) undeleteBoard(db sq.BaseRunner, boardID string, modifiedBy string) error {\n\tboards, err := s.getBoardHistory(db, boardID, model.QueryBoardHistoryOptions{Limit: 1, Descending: true})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif len(boards) == 0 {\n\t\ts.logger.Warn(\"undeleteBlock board not found\", mlog.String(\"board_id\", boardID))\n\t\treturn nil // undeleting non-existing board is not considered an error (for now)\n\t}\n\tboard := boards[0]\n\n\tif board.DeleteAt == 0 {\n\t\ts.logger.Warn(\"undeleteBlock board not deleted\", mlog.String(\"board_id\", board.ID))\n\t\treturn nil // undeleting not deleted board is not considered an error (for now)\n\t}\n\n\tpropertiesJSON, err := s.MarshalJSONB(board.Properties)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcardPropertiesJSON, err := s.MarshalJSONB(board.CardProperties)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tnow := utils.GetMillis()\n\tcolumns := []string{\n\t\t\"id\",\n\t\t\"team_id\",\n\t\t\"channel_id\",\n\t\t\"created_by\",\n\t\t\"modified_by\",\n\t\t\"type\",\n\t\t\"title\",\n\t\t\"minimum_role\",\n\t\t\"description\",\n\t\t\"icon\",\n\t\t\"show_description\",\n\t\t\"is_template\",\n\t\t\"template_version\",\n\t\t\"properties\",\n\t\t\"card_properties\",\n\t\t\"create_at\",\n\t\t\"update_at\",\n\t\t\"delete_at\",\n\t}\n\n\tvalues := []interface{}{\n\t\tboard.ID,\n\t\tboard.TeamID,\n\t\t\"\",\n\t\tboard.CreatedBy,\n\t\tmodifiedBy,\n\t\tboard.Type,\n\t\tboard.Title,\n\t\tboard.MinimumRole,\n\t\tboard.Description,\n\t\tboard.Icon,\n\t\tboard.ShowDescription,\n\t\tboard.IsTemplate,\n\t\tboard.TemplateVersion,\n\t\tpropertiesJSON,\n\t\tcardPropertiesJSON,\n\t\tboard.CreateAt,\n\t\tnow,\n\t\t0,\n\t}\n\tinsertHistoryQuery := s.getQueryBuilder(db).Insert(s.tablePrefix + \"boards_history\").\n\t\tColumns(columns...).\n\t\tValues(values...)\n\tinsertQuery := s.getQueryBuilder(db).Insert(s.tablePrefix + \"boards\").\n\t\tColumns(columns...).\n\t\tValues(values...)\n\n\tif _, err := insertHistoryQuery.Exec(); err != nil {\n\t\treturn err\n\t}\n\n\tif _, err := insertQuery.Exec(); err != nil {\n\t\treturn err\n\t}\n\n\treturn s.undeleteBlockChildren(db, board.ID, \"\", modifiedBy)\n}\n\nfunc (s *SQLStore) getBoardMemberHistory(db sq.BaseRunner, boardID, userID string, limit uint64) ([]*model.BoardMemberHistoryEntry, error) {\n\tquery := s.getQueryBuilder(db).\n\t\tSelect(\"board_id\", \"user_id\", \"action\", \"insert_at\").\n\t\tFrom(s.tablePrefix + \"board_members_history\").\n\t\tWhere(sq.Eq{\"board_id\": boardID}).\n\t\tWhere(sq.Eq{\"user_id\": userID}).\n\t\tOrderBy(\"insert_at DESC\")\n\n\tif limit > 0 {\n\t\tquery = query.Limit(limit)\n\t}\n\n\trows, err := query.Query()\n\tif err != nil {\n\t\ts.logger.Error(`getBoardMemberHistory ERROR`, mlog.Err(err))\n\t\treturn nil, err\n\t}\n\tdefer s.CloseRows(rows)\n\n\tmemberHistory, err := s.boardMemberHistoryEntriesFromRows(rows)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn memberHistory, nil\n}\n"
  },
  {
    "path": "server/services/store/sqlstore/boards_and_blocks.go",
    "content": "package sqlstore\n\nimport (\n\t\"fmt\"\n\n\tsq \"github.com/Masterminds/squirrel\"\n\t\"github.com/mattermost/focalboard/server/model\"\n)\n\ntype BlockDoesntBelongToBoardsErr struct {\n\tblockID string\n}\n\nfunc (e BlockDoesntBelongToBoardsErr) Error() string {\n\treturn fmt.Sprintf(\"block %s doesn't belong to any of the boards in the delete request\", e.blockID)\n}\n\nfunc (s *SQLStore) createBoardsAndBlocksWithAdmin(db sq.BaseRunner, bab *model.BoardsAndBlocks, userID string) (*model.BoardsAndBlocks, []*model.BoardMember, error) {\n\tnewBab, err := s.createBoardsAndBlocks(db, bab, userID)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tmembers := []*model.BoardMember{}\n\tfor _, board := range newBab.Boards {\n\t\tbm := &model.BoardMember{\n\t\t\tBoardID:      board.ID,\n\t\t\tUserID:       board.CreatedBy,\n\t\t\tSchemeAdmin:  true,\n\t\t\tSchemeEditor: true,\n\t\t}\n\n\t\tnbm, err := s.saveMember(db, bm)\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\n\t\tmembers = append(members, nbm)\n\t}\n\n\treturn newBab, members, nil\n}\n\nfunc (s *SQLStore) createBoardsAndBlocks(db sq.BaseRunner, bab *model.BoardsAndBlocks, userID string) (*model.BoardsAndBlocks, error) {\n\tboards := []*model.Board{}\n\tblocks := []*model.Block{}\n\n\tfor _, board := range bab.Boards {\n\t\tnewBoard, err := s.insertBoard(db, board, userID)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tboards = append(boards, newBoard)\n\t}\n\n\tfor _, block := range bab.Blocks {\n\t\tb := block\n\t\terr := s.insertBlock(db, b, userID)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tblocks = append(blocks, block)\n\t}\n\n\tnewBab := &model.BoardsAndBlocks{\n\t\tBoards: boards,\n\t\tBlocks: blocks,\n\t}\n\n\treturn newBab, nil\n}\n\nfunc (s *SQLStore) patchBoardsAndBlocks(db sq.BaseRunner, pbab *model.PatchBoardsAndBlocks, userID string) (*model.BoardsAndBlocks, error) {\n\tbab := &model.BoardsAndBlocks{}\n\tfor i, boardID := range pbab.BoardIDs {\n\t\tboard, err := s.patchBoard(db, boardID, pbab.BoardPatches[i], userID)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tbab.Boards = append(bab.Boards, board)\n\t}\n\n\tfor i, blockID := range pbab.BlockIDs {\n\t\tif err := s.patchBlock(db, blockID, pbab.BlockPatches[i], userID); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tblock, err := s.getBlock(db, blockID)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tbab.Blocks = append(bab.Blocks, block)\n\t}\n\n\treturn bab, nil\n}\n\n// deleteBoardsAndBlocks deletes all the boards and blocks entities of\n// the DeleteBoardsAndBlocks struct, making sure that all the blocks\n// belong to the boards in the struct.\nfunc (s *SQLStore) deleteBoardsAndBlocks(db sq.BaseRunner, dbab *model.DeleteBoardsAndBlocks, userID string) error {\n\tboardIDMap := map[string]bool{}\n\tfor _, boardID := range dbab.Boards {\n\t\tboardIDMap[boardID] = true\n\t}\n\n\t// delete the blocks first, since deleting the board will clean up any children and we'll get\n\t// not found errors when deleting the blocks after.\n\tfor _, blockID := range dbab.Blocks {\n\t\tblock, err := s.getBlock(db, blockID)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif _, ok := boardIDMap[block.BoardID]; !ok {\n\t\t\treturn BlockDoesntBelongToBoardsErr{blockID}\n\t\t}\n\n\t\tif err := s.deleteBlock(db, blockID, userID); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tfor _, boardID := range dbab.Boards {\n\t\tif err := s.deleteBoard(db, boardID, userID); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (s *SQLStore) duplicateBoard(db sq.BaseRunner, boardID string, userID string, toTeam string, asTemplate bool) (*model.BoardsAndBlocks, []*model.BoardMember, error) {\n\tbab := &model.BoardsAndBlocks{\n\t\tBoards: []*model.Board{},\n\t\tBlocks: []*model.Block{},\n\t}\n\n\tboard, err := s.getBoard(db, boardID)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\t// todo: server localization\n\tif asTemplate == board.IsTemplate {\n\t\t// board -> board or template -> template\n\t\tboard.Title += \" copy\"\n\t} else if asTemplate {\n\t\t// template from board\n\t\tboard.Title = \"New board template\"\n\t}\n\n\t// make new board private\n\tboard.Type = \"P\"\n\tboard.IsTemplate = asTemplate\n\tboard.CreatedBy = userID\n\tboard.ChannelID = \"\"\n\n\tif toTeam != \"\" {\n\t\tboard.TeamID = toTeam\n\t}\n\n\tbab.Boards = []*model.Board{board}\n\tblocks, err := s.getBlocksForBoard(db, boardID)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tnewBlocks := []*model.Block{}\n\tfor _, b := range blocks {\n\t\tif b.Type != model.TypeComment {\n\t\t\tnewBlocks = append(newBlocks, b)\n\t\t}\n\t}\n\tbab.Blocks = newBlocks\n\n\tbab, err = model.GenerateBoardsAndBlocksIDs(bab, nil)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\treturn s.createBoardsAndBlocksWithAdmin(db, bab, userID)\n}\n"
  },
  {
    "path": "server/services/store/sqlstore/category.go",
    "content": "package sqlstore\n\nimport (\n\t\"database/sql\"\n\t\"fmt\"\n\n\tsq \"github.com/Masterminds/squirrel\"\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nconst categorySortOrderGap = 10\n\nfunc (s *SQLStore) categoryFields() []string {\n\treturn []string{\n\t\t\"id\",\n\t\t\"name\",\n\t\t\"user_id\",\n\t\t\"team_id\",\n\t\t\"create_at\",\n\t\t\"update_at\",\n\t\t\"delete_at\",\n\t\t\"collapsed\",\n\t\t\"COALESCE(sort_order, 0)\",\n\t\t\"type\",\n\t}\n}\n\nfunc (s *SQLStore) getCategory(db sq.BaseRunner, id string) (*model.Category, error) {\n\tquery := s.getQueryBuilder(db).\n\t\tSelect(s.categoryFields()...).\n\t\tFrom(s.tablePrefix + \"categories\").\n\t\tWhere(sq.Eq{\"id\": id})\n\n\trows, err := query.Query()\n\tif err != nil {\n\t\ts.logger.Error(\"getCategory error\", mlog.Err(err))\n\t\treturn nil, err\n\t}\n\n\tcategories, err := s.categoriesFromRows(rows)\n\tif err != nil {\n\t\ts.logger.Error(\"getCategory row scan error\", mlog.Err(err))\n\t\treturn nil, err\n\t}\n\n\tif len(categories) == 0 {\n\t\treturn nil, model.NewErrNotFound(\"category ID=\" + id)\n\t}\n\n\treturn &categories[0], nil\n}\n\nfunc (s *SQLStore) createCategory(db sq.BaseRunner, category model.Category) error {\n\t// A new category should always end up at the top.\n\t// So we first insert the provided category, then bump up\n\t// existing user-team categories' order\n\n\t// creating provided category\n\tquery := s.getQueryBuilder(db).\n\t\tInsert(s.tablePrefix+\"categories\").\n\t\tColumns(\n\t\t\t\"id\",\n\t\t\t\"name\",\n\t\t\t\"user_id\",\n\t\t\t\"team_id\",\n\t\t\t\"create_at\",\n\t\t\t\"update_at\",\n\t\t\t\"delete_at\",\n\t\t\t\"collapsed\",\n\t\t\t\"sort_order\",\n\t\t\t\"type\",\n\t\t).\n\t\tValues(\n\t\t\tcategory.ID,\n\t\t\tcategory.Name,\n\t\t\tcategory.UserID,\n\t\t\tcategory.TeamID,\n\t\t\tcategory.CreateAt,\n\t\t\tcategory.UpdateAt,\n\t\t\tcategory.DeleteAt,\n\t\t\tcategory.Collapsed,\n\t\t\tcategory.SortOrder,\n\t\t\tcategory.Type,\n\t\t)\n\n\t_, err := query.Exec()\n\tif err != nil {\n\t\ts.logger.Error(\"Error creating category\", mlog.String(\"category name\", category.Name), mlog.Err(err))\n\t\treturn err\n\t}\n\n\t// bumping up order of existing categories\n\tupdateQuery := s.getQueryBuilder(db).\n\t\tUpdate(s.tablePrefix+\"categories\").\n\t\tSet(\"sort_order\", sq.Expr(fmt.Sprintf(\"sort_order + %d\", categorySortOrderGap))).\n\t\tWhere(\n\t\t\tsq.Eq{\n\t\t\t\t\"user_id\":   category.UserID,\n\t\t\t\t\"team_id\":   category.TeamID,\n\t\t\t\t\"delete_at\": 0,\n\t\t\t},\n\t\t)\n\n\tif _, err := updateQuery.Exec(); err != nil {\n\t\ts.logger.Error(\n\t\t\t\"createCategory failed to update sort order of existing user-team categories\",\n\t\t\tmlog.String(\"user_id\", category.UserID),\n\t\t\tmlog.String(\"team_id\", category.TeamID),\n\t\t\tmlog.Err(err),\n\t\t)\n\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (s *SQLStore) updateCategory(db sq.BaseRunner, category model.Category) error {\n\tquery := s.getQueryBuilder(db).\n\t\tUpdate(s.tablePrefix+\"categories\").\n\t\tSet(\"name\", category.Name).\n\t\tSet(\"update_at\", category.UpdateAt).\n\t\tSet(\"collapsed\", category.Collapsed).\n\t\tWhere(sq.Eq{\n\t\t\t\"id\":        category.ID,\n\t\t\t\"delete_at\": 0,\n\t\t})\n\n\t_, err := query.Exec()\n\tif err != nil {\n\t\ts.logger.Error(\"Error updating category\", mlog.String(\"category_id\", category.ID), mlog.String(\"category_name\", category.Name), mlog.Err(err))\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (s *SQLStore) deleteCategory(db sq.BaseRunner, categoryID, userID, teamID string) error {\n\tquery := s.getQueryBuilder(db).\n\t\tUpdate(s.tablePrefix+\"categories\").\n\t\tSet(\"delete_at\", utils.GetMillis()).\n\t\tWhere(sq.Eq{\n\t\t\t\"id\":        categoryID,\n\t\t\t\"user_id\":   userID,\n\t\t\t\"team_id\":   teamID,\n\t\t\t\"delete_at\": 0,\n\t\t})\n\n\t_, err := query.Exec()\n\tif err != nil {\n\t\ts.logger.Error(\n\t\t\t\"Error updating category\",\n\t\t\tmlog.String(\"category_id\", categoryID),\n\t\t\tmlog.String(\"user_id\", userID),\n\t\t\tmlog.String(\"team_id\", teamID),\n\t\t\tmlog.Err(err),\n\t\t)\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (s *SQLStore) getUserCategories(db sq.BaseRunner, userID, teamID string) ([]model.Category, error) {\n\tquery := s.getQueryBuilder(db).\n\t\tSelect(s.categoryFields()...).\n\t\tFrom(s.tablePrefix+\"categories\").\n\t\tWhere(sq.Eq{\n\t\t\t\"user_id\":   userID,\n\t\t\t\"team_id\":   teamID,\n\t\t\t\"delete_at\": 0,\n\t\t}).\n\t\tOrderBy(\"sort_order\", \"name\")\n\n\trows, err := query.Query()\n\tif err != nil {\n\t\ts.logger.Error(\"getUserCategories error\", mlog.Err(err))\n\t\treturn nil, err\n\t}\n\n\treturn s.categoriesFromRows(rows)\n}\n\nfunc (s *SQLStore) categoriesFromRows(rows *sql.Rows) ([]model.Category, error) {\n\tvar categories []model.Category\n\n\tfor rows.Next() {\n\t\tcategory := model.Category{}\n\t\terr := rows.Scan(\n\t\t\t&category.ID,\n\t\t\t&category.Name,\n\t\t\t&category.UserID,\n\t\t\t&category.TeamID,\n\t\t\t&category.CreateAt,\n\t\t\t&category.UpdateAt,\n\t\t\t&category.DeleteAt,\n\t\t\t&category.Collapsed,\n\t\t\t&category.SortOrder,\n\t\t\t&category.Type,\n\t\t)\n\n\t\tif err != nil {\n\t\t\ts.logger.Error(\"categoriesFromRows row parsing error\", mlog.Err(err))\n\t\t\treturn nil, err\n\t\t}\n\n\t\tcategories = append(categories, category)\n\t}\n\n\treturn categories, nil\n}\n\nfunc (s *SQLStore) reorderCategories(db sq.BaseRunner, userID, teamID string, newCategoryOrder []string) ([]string, error) {\n\tif len(newCategoryOrder) == 0 {\n\t\treturn nil, nil\n\t}\n\n\tupdateCase := sq.Case(\"id\")\n\tfor i, categoryID := range newCategoryOrder {\n\t\tupdateCase = updateCase.When(\"'\"+categoryID+\"'\", sq.Expr(fmt.Sprintf(\"%d\", i*categorySortOrderGap)))\n\t}\n\tupdateCase = updateCase.Else(\"sort_order\")\n\n\tquery := s.getQueryBuilder(db).\n\t\tUpdate(s.tablePrefix+\"categories\").\n\t\tSet(\"sort_order\", updateCase).\n\t\tWhere(sq.Eq{\n\t\t\t\"user_id\": userID,\n\t\t\t\"team_id\": teamID,\n\t\t})\n\n\tif _, err := query.Exec(); err != nil {\n\t\ts.logger.Error(\n\t\t\t\"reorderCategories failed to update category order\",\n\t\t\tmlog.String(\"user_id\", userID),\n\t\t\tmlog.String(\"team_id\", teamID),\n\t\t\tmlog.Err(err),\n\t\t)\n\n\t\treturn nil, err\n\t}\n\n\treturn newCategoryOrder, nil\n}\n"
  },
  {
    "path": "server/services/store/sqlstore/category_boards.go",
    "content": "package sqlstore\n\nimport (\n\t\"database/sql\"\n\t\"fmt\"\n\n\tsq \"github.com/Masterminds/squirrel\"\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nfunc (s *SQLStore) getUserCategoryBoards(db sq.BaseRunner, userID, teamID string) ([]model.CategoryBoards, error) {\n\tcategories, err := s.getUserCategories(db, userID, teamID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tuserCategoryBoards := []model.CategoryBoards{}\n\tfor _, category := range categories {\n\t\tboardMetadata, err := s.getCategoryBoardAttributes(db, category.ID)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tuserCategoryBoard := model.CategoryBoards{\n\t\t\tCategory:      category,\n\t\t\tBoardMetadata: boardMetadata,\n\t\t}\n\n\t\tuserCategoryBoards = append(userCategoryBoards, userCategoryBoard)\n\t}\n\n\treturn userCategoryBoards, nil\n}\n\nfunc (s *SQLStore) getCategoryBoardAttributes(db sq.BaseRunner, categoryID string) ([]model.CategoryBoardMetadata, error) {\n\tquery := s.getQueryBuilder(db).\n\t\tSelect(\"board_id, COALESCE(hidden, false)\").\n\t\tFrom(s.tablePrefix + \"category_boards\").\n\t\tWhere(sq.Eq{\n\t\t\t\"category_id\": categoryID,\n\t\t}).\n\t\tOrderBy(\"sort_order\")\n\n\trows, err := query.Query()\n\tif err != nil {\n\t\ts.logger.Error(\"getCategoryBoards error fetching categoryblocks\", mlog.String(\"categoryID\", categoryID), mlog.Err(err))\n\t\treturn nil, err\n\t}\n\n\treturn s.categoryBoardsFromRows(rows)\n}\n\nfunc (s *SQLStore) addUpdateCategoryBoard(db sq.BaseRunner, userID, categoryID string, boardIDsParam []string) error {\n\t// we need to de-duplicate this array as Postgres failes to\n\t// handle upsert if there are multiple incoming rows\n\t// that conflict the same existing row.\n\t// For example, having the entry \"1\" in DB and trying to upsert \"1\" and \"1\" will fail\n\t// as there are multiple duplicates of the same \"1\".\n\t//\n\t// Source: https://stackoverflow.com/questions/42994373/postgresql-on-conflict-cannot-affect-row-a-second-time\n\tboardIDs := utils.DedupeStringArr(boardIDsParam)\n\n\tif len(boardIDs) == 0 {\n\t\treturn nil\n\t}\n\n\tquery := s.getQueryBuilder(db).\n\t\tInsert(s.tablePrefix+\"category_boards\").\n\t\tColumns(\n\t\t\t\"id\",\n\t\t\t\"user_id\",\n\t\t\t\"category_id\",\n\t\t\t\"board_id\",\n\t\t\t\"create_at\",\n\t\t\t\"update_at\",\n\t\t\t\"sort_order\",\n\t\t\t\"hidden\",\n\t\t)\n\n\tnow := utils.GetMillis()\n\tfor _, boardID := range boardIDs {\n\t\tquery = query.Values(\n\t\t\tutils.NewID(utils.IDTypeNone),\n\t\t\tuserID,\n\t\t\tcategoryID,\n\t\t\tboardID,\n\t\t\tnow,\n\t\t\tnow,\n\t\t\t0,\n\t\t\tfalse,\n\t\t)\n\t}\n\n\tif s.dbType == model.MysqlDBType {\n\t\tquery = query.Suffix(\n\t\t\t\"ON DUPLICATE KEY UPDATE category_id = ?\",\n\t\t\tcategoryID,\n\t\t)\n\t} else {\n\t\tquery = query.Suffix(\n\t\t\t`ON CONFLICT (user_id, board_id)\n\t\t\t DO UPDATE SET category_id = EXCLUDED.category_id, update_at = EXCLUDED.update_at`,\n\t\t)\n\t}\n\n\tif _, err := query.Exec(); err != nil {\n\t\treturn fmt.Errorf(\n\t\t\t\"store addUpdateCategoryBoard: failed to upsert user-board-category userID: %s, categoryID: %s, board_count: %d, error: %w\",\n\t\t\tuserID, categoryID, len(boardIDs), err,\n\t\t)\n\t}\n\n\treturn nil\n}\n\nfunc (s *SQLStore) categoryBoardsFromRows(rows *sql.Rows) ([]model.CategoryBoardMetadata, error) {\n\tmetadata := []model.CategoryBoardMetadata{}\n\n\tfor rows.Next() {\n\t\tdatum := model.CategoryBoardMetadata{}\n\t\terr := rows.Scan(&datum.BoardID, &datum.Hidden)\n\n\t\tif err != nil {\n\t\t\ts.logger.Error(\"categoryBoardsFromRows row scan error\", mlog.Err(err))\n\t\t\treturn nil, err\n\t\t}\n\n\t\tmetadata = append(metadata, datum)\n\t}\n\n\treturn metadata, nil\n}\n\nfunc (s *SQLStore) reorderCategoryBoards(db sq.BaseRunner, categoryID string, newBoardsOrder []string) ([]string, error) {\n\tif len(newBoardsOrder) == 0 {\n\t\treturn nil, nil\n\t}\n\n\tupdateCase := sq.Case(\"board_id\")\n\tfor i, boardID := range newBoardsOrder {\n\t\tupdateCase = updateCase.When(\"'\"+boardID+\"'\", sq.Expr(fmt.Sprintf(\"%d\", i+model.CategoryBoardsSortOrderGap)))\n\t}\n\tupdateCase.Else(\"sort_order\")\n\n\tquery := s.getQueryBuilder(db).\n\t\tUpdate(s.tablePrefix+\"category_boards\").\n\t\tSet(\"sort_order\", updateCase).\n\t\tWhere(sq.Eq{\n\t\t\t\"category_id\": categoryID,\n\t\t})\n\n\tif _, err := query.Exec(); err != nil {\n\t\ts.logger.Error(\n\t\t\t\"reorderCategoryBoards failed to update category board order\",\n\t\t\tmlog.String(\"category_id\", categoryID),\n\t\t\tmlog.Err(err),\n\t\t)\n\n\t\treturn nil, err\n\t}\n\n\treturn newBoardsOrder, nil\n}\n\nfunc (s *SQLStore) setBoardVisibility(db sq.BaseRunner, userID, categoryID, boardID string, visible bool) error {\n\tquery := s.getQueryBuilder(db).\n\t\tUpdate(s.tablePrefix+\"category_boards\").\n\t\tSet(\"hidden\", !visible).\n\t\tWhere(sq.Eq{\n\t\t\t\"user_id\":     userID,\n\t\t\t\"category_id\": categoryID,\n\t\t\t\"board_id\":    boardID,\n\t\t})\n\n\tif _, err := query.Exec(); err != nil {\n\t\ts.logger.Error(\n\t\t\t\"SQLStore setBoardVisibility: failed to update board visibility\",\n\t\t\tmlog.String(\"user_id\", userID),\n\t\t\tmlog.String(\"board_id\", boardID),\n\t\t\tmlog.Bool(\"visible\", visible),\n\t\t\tmlog.Err(err),\n\t\t)\n\n\t\treturn err\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "server/services/store/sqlstore/cloud.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage sqlstore\n\nimport (\n\t\"database/sql\"\n\t\"errors\"\n\t\"strconv\"\n\n\tsq \"github.com/Masterminds/squirrel\"\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/store\"\n)\n\nvar ErrInvalidCardLimitValue = errors.New(\"card limit value is invalid\")\n\n// activeCardsQuery applies the necessary filters to the query for it\n// to fetch an active cards window if the cardLimit is set, or all the\n// active cards if it's 0.\nfunc (s *SQLStore) activeCardsQuery(builder sq.StatementBuilderType, selectStr string, cardLimit int) sq.SelectBuilder {\n\tquery := builder.\n\t\tSelect(selectStr).\n\t\tFrom(s.tablePrefix + \"blocks b\").\n\t\tJoin(s.tablePrefix + \"boards bd on b.board_id=bd.id\").\n\t\tWhere(sq.Eq{\n\t\t\t\"b.delete_at\":    0,\n\t\t\t\"b.type\":         model.TypeCard,\n\t\t\t\"bd.is_template\": false,\n\t\t})\n\n\tif cardLimit != 0 {\n\t\tquery = query.\n\t\t\tLimit(1).\n\t\t\tOffset(uint64(cardLimit - 1))\n\t}\n\n\treturn query\n}\n\n// getUsedCardsCount returns the amount of active cards in the server.\nfunc (s *SQLStore) getUsedCardsCount(db sq.BaseRunner) (int, error) {\n\trow := s.activeCardsQuery(s.getQueryBuilder(db), \"count(b.id)\", 0).\n\t\tQueryRow()\n\n\tvar usedCards int\n\terr := row.Scan(&usedCards)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\treturn usedCards, nil\n}\n\n// getCardLimitTimestamp returns the timestamp value from the\n// system_settings table or zero if it doesn't exist.\nfunc (s *SQLStore) getCardLimitTimestamp(db sq.BaseRunner) (int64, error) {\n\tscanner := s.getQueryBuilder(db).\n\t\tSelect(\"value\").\n\t\tFrom(s.tablePrefix + \"system_settings\").\n\t\tWhere(sq.Eq{\"id\": store.CardLimitTimestampSystemKey}).\n\t\tQueryRow()\n\n\tvar result string\n\terr := scanner.Scan(&result)\n\tif errors.Is(sql.ErrNoRows, err) {\n\t\treturn 0, nil\n\t}\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tcardLimitTimestamp, err := strconv.Atoi(result)\n\tif err != nil {\n\t\treturn 0, ErrInvalidCardLimitValue\n\t}\n\n\treturn int64(cardLimitTimestamp), nil\n}\n\n// updateCardLimitTimestamp updates the card limit value in the\n// system_settings table with the timestamp of the nth last updated\n// card, being nth the value of the cardLimit parameter. If cardLimit\n// is zero, the timestamp will be set to zero.\nfunc (s *SQLStore) updateCardLimitTimestamp(db sq.BaseRunner, cardLimit int) (int64, error) {\n\tquery := s.getQueryBuilder(db).\n\t\tInsert(s.tablePrefix+\"system_settings\").\n\t\tColumns(\"id\", \"value\")\n\n\tvar value interface{} = 0\n\tif cardLimit != 0 {\n\t\tvalue = s.activeCardsQuery(sq.StatementBuilder, \"b.update_at\", cardLimit).\n\t\t\tOrderBy(\"b.update_at DESC\").\n\t\t\tPrefix(\"COALESCE((\").Suffix(\"), 0)\")\n\t}\n\tquery = query.Values(store.CardLimitTimestampSystemKey, value)\n\n\tif s.dbType == model.MysqlDBType {\n\t\tquery = query.Suffix(\"ON DUPLICATE KEY UPDATE value = ?\", value)\n\t} else {\n\t\tquery = query.Suffix(\n\t\t\t`ON CONFLICT (id)\n\t\t\t DO UPDATE SET value = EXCLUDED.value`,\n\t\t)\n\t}\n\n\tresult, err := query.Exec()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tif _, err := result.RowsAffected(); err != nil {\n\t\treturn 0, err\n\t}\n\n\treturn s.getCardLimitTimestamp(db)\n}\n"
  },
  {
    "path": "server/services/store/sqlstore/compliance.go",
    "content": "package sqlstore\n\nimport (\n\t\"database/sql\"\n\n\tsq \"github.com/Masterminds/squirrel\"\n\t\"github.com/mattermost/focalboard/server/model\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nfunc (s *SQLStore) getBoardsForCompliance(db sq.BaseRunner, opts model.QueryBoardsForComplianceOptions) ([]*model.Board, bool, error) {\n\tquery := s.getQueryBuilder(db).\n\t\tSelect(boardFields(\"b.\")...).\n\t\tFrom(s.tablePrefix + \"boards as b\")\n\n\tif opts.TeamID != \"\" {\n\t\tquery = query.Where(sq.Eq{\"b.team_id\": opts.TeamID})\n\t}\n\n\tif opts.Page != 0 {\n\t\tquery = query.Offset(uint64(opts.Page * opts.PerPage))\n\t}\n\n\tif opts.PerPage > 0 {\n\t\t// N+1 to check if there's a next page for pagination\n\t\tquery = query.Limit(uint64(opts.PerPage) + 1)\n\t}\n\n\trows, err := query.Query()\n\tif err != nil {\n\t\ts.logger.Error(`GetBoardsForCompliance ERROR`, mlog.Err(err))\n\t\treturn nil, false, err\n\t}\n\tdefer s.CloseRows(rows)\n\n\tboards, err := s.boardsFromRows(rows)\n\tif err != nil {\n\t\treturn nil, false, err\n\t}\n\n\tvar hasMore bool\n\tif opts.PerPage > 0 && len(boards) > opts.PerPage {\n\t\tboards = boards[0:opts.PerPage]\n\t\thasMore = true\n\t}\n\treturn boards, hasMore, nil\n}\n\nfunc (s *SQLStore) getBoardsComplianceHistory(db sq.BaseRunner, opts model.QueryBoardsComplianceHistoryOptions) ([]*model.BoardHistory, bool, error) {\n\tqueryDescendentLastUpdate := s.getQueryBuilder(db).\n\t\tSelect(\"MAX(blk1.update_at)\").\n\t\tFrom(s.tablePrefix + \"blocks_history as blk1\").\n\t\tWhere(\"blk1.board_id=bh.id\")\n\n\tif !opts.IncludeDeleted {\n\t\tqueryDescendentLastUpdate.Where(sq.Eq{\"blk1.delete_at\": 0})\n\t}\n\n\tsqlDescendentLastUpdate, _, _ := queryDescendentLastUpdate.ToSql()\n\n\tqueryDescendentFirstUpdate := s.getQueryBuilder(db).\n\t\tSelect(\"MIN(blk2.update_at)\").\n\t\tFrom(s.tablePrefix + \"blocks_history as blk2\").\n\t\tWhere(\"blk2.board_id=bh.id\")\n\n\tif !opts.IncludeDeleted {\n\t\tqueryDescendentFirstUpdate.Where(sq.Eq{\"blk2.delete_at\": 0})\n\t}\n\n\tsqlDescendentFirstUpdate, _, _ := queryDescendentFirstUpdate.ToSql()\n\n\tquery := s.getQueryBuilder(db).\n\t\tSelect(\n\t\t\t\"bh.id\",\n\t\t\t\"bh.team_id\",\n\t\t\t\"CASE WHEN bh.delete_at=0 THEN false ELSE true END AS isDeleted\",\n\t\t\t\"COALESCE((\"+sqlDescendentLastUpdate+\"),0) as decendentLastUpdateAt\",\n\t\t\t\"COALESCE((\"+sqlDescendentFirstUpdate+\"),0) as decendentFirstUpdateAt\",\n\t\t\t\"bh.created_by\",\n\t\t\t\"bh.modified_by\",\n\t\t).\n\t\tFrom(s.tablePrefix + \"boards_history as bh\")\n\n\tif !opts.IncludeDeleted {\n\t\t// filtering out deleted boards; join with boards table to ensure no history\n\t\t// for deleted boards are returned. Deleted boards won't exist in boards table.\n\t\tquery = query.Join(s.tablePrefix + \"boards as b ON b.id=bh.id\")\n\t}\n\n\tquery = query.Where(sq.Gt{\"bh.update_at\": opts.ModifiedSince}).\n\t\tGroupBy(\"bh.id\", \"bh.team_id\", \"bh.delete_at\", \"bh.created_by\", \"bh.modified_by\").\n\t\tOrderBy(\"decendentLastUpdateAt desc\", \"bh.id\")\n\n\tif opts.TeamID != \"\" {\n\t\tquery = query.Where(sq.Eq{\"bh.team_id\": opts.TeamID})\n\t}\n\n\tif opts.Page != 0 {\n\t\tquery = query.Offset(uint64(opts.Page * opts.PerPage))\n\t}\n\n\tif opts.PerPage > 0 {\n\t\t// N+1 to check if there's a next page for pagination\n\t\tquery = query.Limit(uint64(opts.PerPage) + 1)\n\t}\n\n\trows, err := query.Query()\n\tif err != nil {\n\t\ts.logger.Error(`GetBoardsComplianceHistory ERROR`, mlog.Err(err))\n\t\treturn nil, false, err\n\t}\n\tdefer s.CloseRows(rows)\n\n\thistory, err := s.boardsHistoryFromRows(rows)\n\tif err != nil {\n\t\treturn nil, false, err\n\t}\n\n\tvar hasMore bool\n\tif opts.PerPage > 0 && len(history) > opts.PerPage {\n\t\thistory = history[0:opts.PerPage]\n\t\thasMore = true\n\t}\n\treturn history, hasMore, nil\n}\n\nfunc (s *SQLStore) getBlocksComplianceHistory(db sq.BaseRunner, opts model.QueryBlocksComplianceHistoryOptions) ([]*model.BlockHistory, bool, error) {\n\tquery := s.getQueryBuilder(db).\n\t\tSelect(\n\t\t\t\"bh.id\",\n\t\t\t\"brd.team_id\",\n\t\t\t\"bh.board_id\",\n\t\t\t\"bh.type\",\n\t\t\t\"CASE WHEN bh.delete_at=0 THEN false ELSE true END AS isDeleted\",\n\t\t\t\"max(bh.update_at) as lastUpdateAt\",\n\t\t\t\"min(bh.update_at) as firstUpdateAt\",\n\t\t\t\"bh.created_by\",\n\t\t\t\"bh.modified_by\",\n\t\t).\n\t\tFrom(s.tablePrefix + \"blocks_history as bh\").\n\t\tJoin(s.tablePrefix + \"boards_history as brd on brd.id=bh.board_id\")\n\n\tif !opts.IncludeDeleted {\n\t\t// filtering out deleted blocks; join with blocks table to ensure no history\n\t\t// for deleted blocks are returned. Deleted blocks won't exist in blocks table.\n\t\tquery = query.Join(s.tablePrefix + \"blocks as b ON b.id=bh.id\")\n\t}\n\n\tquery = query.Where(sq.Gt{\"bh.update_at\": opts.ModifiedSince}).\n\t\tGroupBy(\"bh.id\", \"brd.team_id\", \"bh.board_id\", \"bh.type\", \"bh.delete_at\", \"bh.created_by\", \"bh.modified_by\").\n\t\tOrderBy(\"lastUpdateAt desc\", \"bh.id\")\n\n\tif opts.TeamID != \"\" {\n\t\tquery = query.Where(sq.Eq{\"brd.team_id\": opts.TeamID})\n\t}\n\n\tif opts.BoardID != \"\" {\n\t\tquery = query.Where(sq.Eq{\"bh.board_id\": opts.BoardID})\n\t}\n\n\tif opts.Page != 0 {\n\t\tquery = query.Offset(uint64(opts.Page * opts.PerPage))\n\t}\n\n\tif opts.PerPage > 0 {\n\t\t// N+1 to check if there's a next page for pagination\n\t\tquery = query.Limit(uint64(opts.PerPage) + 1)\n\t}\n\n\trows, err := query.Query()\n\tif err != nil {\n\t\ts.logger.Error(`GetBlocksComplianceHistory ERROR`, mlog.Err(err))\n\t\treturn nil, false, err\n\t}\n\tdefer s.CloseRows(rows)\n\n\thistory, err := s.blocksHistoryFromRows(rows)\n\tif err != nil {\n\t\treturn nil, false, err\n\t}\n\n\tvar hasMore bool\n\tif opts.PerPage > 0 && len(history) > opts.PerPage {\n\t\thistory = history[0:opts.PerPage]\n\t\thasMore = true\n\t}\n\treturn history, hasMore, nil\n}\n\nfunc (s *SQLStore) boardsHistoryFromRows(rows *sql.Rows) ([]*model.BoardHistory, error) {\n\thistory := []*model.BoardHistory{}\n\n\tfor rows.Next() {\n\t\tboardHistory := &model.BoardHistory{}\n\n\t\terr := rows.Scan(\n\t\t\t&boardHistory.ID,\n\t\t\t&boardHistory.TeamID,\n\t\t\t&boardHistory.IsDeleted,\n\t\t\t&boardHistory.DescendantLastUpdateAt,\n\t\t\t&boardHistory.DescendantFirstUpdateAt,\n\t\t\t&boardHistory.CreatedBy,\n\t\t\t&boardHistory.LastModifiedBy,\n\t\t)\n\t\tif err != nil {\n\t\t\ts.logger.Error(\"boardsHistoryFromRows scan error\", mlog.Err(err))\n\t\t\treturn nil, err\n\t\t}\n\n\t\thistory = append(history, boardHistory)\n\t}\n\treturn history, nil\n}\n\nfunc (s *SQLStore) blocksHistoryFromRows(rows *sql.Rows) ([]*model.BlockHistory, error) {\n\thistory := []*model.BlockHistory{}\n\n\tfor rows.Next() {\n\t\tblockHistory := &model.BlockHistory{}\n\n\t\terr := rows.Scan(\n\t\t\t&blockHistory.ID,\n\t\t\t&blockHistory.TeamID,\n\t\t\t&blockHistory.BoardID,\n\t\t\t&blockHistory.Type,\n\t\t\t&blockHistory.IsDeleted,\n\t\t\t&blockHistory.LastUpdateAt,\n\t\t\t&blockHistory.FirstUpdateAt,\n\t\t\t&blockHistory.CreatedBy,\n\t\t\t&blockHistory.LastModifiedBy,\n\t\t)\n\t\tif err != nil {\n\t\t\ts.logger.Error(\"blocksHistoryFromRows scan error\", mlog.Err(err))\n\t\t\treturn nil, err\n\t\t}\n\n\t\thistory = append(history, blockHistory)\n\t}\n\treturn history, nil\n}\n"
  },
  {
    "path": "server/services/store/sqlstore/data_migrations.go",
    "content": "package sqlstore\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\n\tsq \"github.com/Masterminds/squirrel\"\n\t\"github.com/wiggin77/merror\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nconst (\n\t// we group the inserts on batches of 1000 because PostgreSQL\n\t// supports a limit of around 64K values (not rows) on an insert\n\t// query, so we want to stay safely below.\n\tCategoryInsertBatch = 1000\n\n\tTemplatesToTeamsMigrationKey              = \"TemplatesToTeamsMigrationComplete\"\n\tUniqueIDsMigrationKey                     = \"UniqueIDsMigrationComplete\"\n\tCategoryUUIDIDMigrationKey                = \"CategoryUuidIdMigrationComplete\"\n\tTeamLessBoardsMigrationKey                = \"TeamLessBoardsMigrationComplete\"\n\tDeletedMembershipBoardsMigrationKey       = \"DeletedMembershipBoardsMigrationComplete\"\n\tDeDuplicateCategoryBoardTableMigrationKey = \"DeDuplicateCategoryBoardTableComplete\"\n)\n\nfunc (s *SQLStore) getBlocksWithSameID(db sq.BaseRunner) ([]*model.Block, error) {\n\tsubquery, _, _ := s.getQueryBuilder(db).\n\t\tSelect(\"id\").\n\t\tFrom(s.tablePrefix + \"blocks\").\n\t\tHaving(\"count(id) > 1\").\n\t\tGroupBy(\"id\").\n\t\tToSql()\n\n\tblocksFields := []string{\n\t\t\"id\",\n\t\t\"parent_id\",\n\t\t\"root_id\",\n\t\t\"created_by\",\n\t\t\"modified_by\",\n\t\ts.escapeField(\"schema\"),\n\t\t\"type\",\n\t\t\"title\",\n\t\t\"COALESCE(fields, '{}')\",\n\t\ts.timestampToCharField(\"insert_at\", \"insertAt\"),\n\t\t\"create_at\",\n\t\t\"update_at\",\n\t\t\"delete_at\",\n\t\t\"COALESCE(workspace_id, '0')\",\n\t}\n\n\trows, err := s.getQueryBuilder(db).\n\t\tSelect(blocksFields...).\n\t\tFrom(s.tablePrefix + \"blocks\").\n\t\tWhere(fmt.Sprintf(\"id IN (%s)\", subquery)).\n\t\tQuery()\n\tif err != nil {\n\t\ts.logger.Error(`getBlocksWithSameID ERROR`, mlog.Err(err))\n\t\treturn nil, err\n\t}\n\tdefer s.CloseRows(rows)\n\n\treturn s.blocksFromRows(rows)\n}\n\nfunc (s *SQLStore) RunUniqueIDsMigration() error {\n\tsetting, err := s.GetSystemSetting(UniqueIDsMigrationKey)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"cannot get migration state: %w\", err)\n\t}\n\n\t// If the migration is already completed, do not run it again.\n\tif hasAlreadyRun, _ := strconv.ParseBool(setting); hasAlreadyRun {\n\t\treturn nil\n\t}\n\n\ts.logger.Debug(\"Running Unique IDs migration\")\n\n\ttx, txErr := s.db.BeginTx(context.Background(), nil)\n\tif txErr != nil {\n\t\treturn txErr\n\t}\n\n\tblocks, err := s.getBlocksWithSameID(tx)\n\tif err != nil {\n\t\tif rollbackErr := tx.Rollback(); rollbackErr != nil {\n\t\t\ts.logger.Error(\"Unique IDs transaction rollback error\", mlog.Err(rollbackErr), mlog.String(\"methodName\", \"getBlocksWithSameID\"))\n\t\t}\n\t\treturn fmt.Errorf(\"cannot get blocks with same ID: %w\", err)\n\t}\n\n\tblocksByID := map[string][]*model.Block{}\n\tfor _, block := range blocks {\n\t\tblocksByID[block.ID] = append(blocksByID[block.ID], block)\n\t}\n\n\tfor _, blocks := range blocksByID {\n\t\tfor i, block := range blocks {\n\t\t\tif i == 0 {\n\t\t\t\t// do nothing for the first ID, only updating the others\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tnewID := utils.NewID(model.BlockType2IDType(block.Type))\n\t\t\tif err := s.replaceBlockID(tx, block.ID, newID, block.WorkspaceID); err != nil {\n\t\t\t\tif rollbackErr := tx.Rollback(); rollbackErr != nil {\n\t\t\t\t\ts.logger.Error(\"Unique IDs transaction rollback error\", mlog.Err(rollbackErr), mlog.String(\"methodName\", \"replaceBlockID\"))\n\t\t\t\t}\n\t\t\t\treturn fmt.Errorf(\"cannot replace blockID %s: %w\", block.ID, err)\n\t\t\t}\n\t\t}\n\t}\n\n\tif err := s.setSystemSetting(tx, UniqueIDsMigrationKey, strconv.FormatBool(true)); err != nil {\n\t\tif rollbackErr := tx.Rollback(); rollbackErr != nil {\n\t\t\ts.logger.Error(\"Unique IDs transaction rollback error\", mlog.Err(rollbackErr), mlog.String(\"methodName\", \"setSystemSetting\"))\n\t\t}\n\t\treturn fmt.Errorf(\"cannot mark migration as completed: %w\", err)\n\t}\n\n\tif err := tx.Commit(); err != nil {\n\t\treturn fmt.Errorf(\"cannot commit unique IDs transaction: %w\", err)\n\t}\n\n\ts.logger.Debug(\"Unique IDs migration finished successfully\")\n\treturn nil\n}\n\n// RunCategoryUUIDIDMigration takes care of deriving the categories\n// from the boards and its memberships. The name references UUID\n// because of the preexisting purpose of this migration, and has been\n// preserved for compatibility with already migrated instances.\nfunc (s *SQLStore) RunCategoryUUIDIDMigration() error {\n\tsetting, err := s.GetSystemSetting(CategoryUUIDIDMigrationKey)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"cannot get migration state: %w\", err)\n\t}\n\n\t// If the migration is already completed, do not run it again.\n\tif hasAlreadyRun, _ := strconv.ParseBool(setting); hasAlreadyRun {\n\t\treturn nil\n\t}\n\n\ts.logger.Debug(\"Running category UUID ID migration\")\n\n\ttx, txErr := s.db.BeginTx(context.Background(), nil)\n\tif txErr != nil {\n\t\treturn txErr\n\t}\n\n\tif err := s.setSystemSetting(tx, CategoryUUIDIDMigrationKey, strconv.FormatBool(true)); err != nil {\n\t\tif rollbackErr := tx.Rollback(); rollbackErr != nil {\n\t\t\ts.logger.Error(\"category UUIDs transaction rollback error\", mlog.Err(rollbackErr), mlog.String(\"methodName\", \"setSystemSetting\"))\n\t\t}\n\t\treturn fmt.Errorf(\"cannot mark migration as completed: %w\", err)\n\t}\n\n\tif err := tx.Commit(); err != nil {\n\t\treturn fmt.Errorf(\"cannot commit category UUIDs transaction: %w\", err)\n\t}\n\n\ts.logger.Debug(\"category UUIDs migration finished successfully\")\n\treturn nil\n}\n\nfunc (s *SQLStore) RunFixCollationsAndCharsetsMigration() error {\n\t// This is for MySQL only\n\tif s.dbType != model.MysqlDBType {\n\t\treturn nil\n\t}\n\n\t// get collation and charSet setting that Channels is using.\n\t// when personal server or unit testing, no channels tables exist so just set to a default.\n\tvar collation string\n\tvar charSet string\n\tvar err error\n\tif os.Getenv(\"FOCALBOARD_UNIT_TESTING\") == \"1\" {\n\t\tcollation = \"utf8mb4_general_ci\"\n\t\tcharSet = \"utf8mb4\"\n\t} else {\n\t\tcollation, charSet, err = s.getCollationAndCharset(\"Channels\")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// get all FocalBoard tables\n\ttableNames, err := s.getFocalBoardTableNames()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tmerr := merror.New()\n\n\t// alter each table if there is a collation or charset mismatch\n\tfor _, name := range tableNames {\n\t\ttableCollation, tableCharSet, err := s.getCollationAndCharset(name)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif collation == tableCollation && charSet == tableCharSet {\n\t\t\t// nothing to do\n\t\t\tcontinue\n\t\t}\n\n\t\ts.logger.Warn(\n\t\t\t\"found collation/charset mismatch, fixing table\",\n\t\t\tmlog.String(\"tableName\", name),\n\t\t\tmlog.String(\"tableCollation\", tableCollation),\n\t\t\tmlog.String(\"tableCharSet\", tableCharSet),\n\t\t\tmlog.String(\"collation\", collation),\n\t\t\tmlog.String(\"charSet\", charSet),\n\t\t)\n\n\t\tsql := fmt.Sprintf(\"ALTER TABLE %s CONVERT TO CHARACTER SET '%s' COLLATE '%s'\", name, charSet, collation)\n\t\tresult, err := s.db.Exec(sql)\n\t\tif err != nil {\n\t\t\tmerr.Append(err)\n\t\t\tcontinue\n\t\t}\n\t\tnum, err := result.RowsAffected()\n\t\tif err != nil {\n\t\t\tmerr.Append(err)\n\t\t}\n\t\tif num > 0 {\n\t\t\ts.logger.Debug(\"table collation and/or charSet fixed\",\n\t\t\t\tmlog.String(\"table_name\", name),\n\t\t\t)\n\t\t}\n\t}\n\treturn merr.ErrorOrNil()\n}\n\nfunc (s *SQLStore) getFocalBoardTableNames() ([]string, error) {\n\tif s.dbType != model.MysqlDBType {\n\t\treturn nil, newErrInvalidDBType(\"getFocalBoardTableNames requires MySQL\")\n\t}\n\n\tquery := s.getQueryBuilder(s.db).\n\t\tSelect(\"table_name\").\n\t\tFrom(\"information_schema.tables\").\n\t\tWhere(sq.Like{\"table_name\": s.tablePrefix + \"%\"}).\n\t\tWhere(\"table_schema=(SELECT DATABASE())\")\n\n\trows, err := query.Query()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error fetching FocalBoard table names: %w\", err)\n\t}\n\tdefer rows.Close()\n\n\tnames := make([]string, 0)\n\n\tfor rows.Next() {\n\t\tvar tableName string\n\n\t\terr := rows.Scan(&tableName)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"cannot scan result while fetching table names: %w\", err)\n\t\t}\n\n\t\tnames = append(names, tableName)\n\t}\n\n\treturn names, nil\n}\n\nfunc (s *SQLStore) getCollationAndCharset(tableName string) (string, string, error) {\n\tif s.dbType != model.MysqlDBType {\n\t\treturn \"\", \"\", newErrInvalidDBType(\"getCollationAndCharset requires MySQL\")\n\t}\n\n\tquery := s.getQueryBuilder(s.db).\n\t\tSelect(\"table_collation\").\n\t\tFrom(\"information_schema.tables\").\n\t\tWhere(sq.Eq{\"table_name\": tableName}).\n\t\tWhere(\"table_schema=(SELECT DATABASE())\")\n\n\trow := query.QueryRow()\n\n\tvar collation string\n\terr := row.Scan(&collation)\n\tif err != nil {\n\t\treturn \"\", \"\", fmt.Errorf(\"error fetching collation for table %s: %w\", tableName, err)\n\t}\n\n\t// obtains the charset from the first column that has it set\n\tquery = s.getQueryBuilder(s.db).\n\t\tSelect(\"CHARACTER_SET_NAME\").\n\t\tFrom(\"information_schema.columns\").\n\t\tWhere(sq.Eq{\n\t\t\t\"table_name\": tableName,\n\t\t}).\n\t\tWhere(\"table_schema=(SELECT DATABASE())\").\n\t\tWhere(sq.NotEq{\"CHARACTER_SET_NAME\": \"NULL\"}).\n\t\tLimit(1)\n\n\trow = query.QueryRow()\n\n\tvar charSet string\n\terr = row.Scan(&charSet)\n\tif err != nil {\n\t\treturn \"\", \"\", fmt.Errorf(\"error fetching charSet: %w\", err)\n\t}\n\n\treturn collation, charSet, nil\n}\n\nfunc (s *SQLStore) RunDeDuplicateCategoryBoardsMigration(currentMigration int) error {\n\t// not supported for SQLite\n\tif s.dbType == model.SqliteDBType {\n\t\tif mErr := s.setSystemSetting(s.db, DeDuplicateCategoryBoardTableMigrationKey, strconv.FormatBool(true)); mErr != nil {\n\t\t\treturn fmt.Errorf(\"cannot mark migration %s as completed: %w\", \"RunDeDuplicateCategoryBoardsMigration\", mErr)\n\t\t}\n\t\treturn nil\n\t}\n\n\tsetting, err := s.GetSystemSetting(DeDuplicateCategoryBoardTableMigrationKey)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"cannot get DeDuplicateCategoryBoardTableMigration state: %w\", err)\n\t}\n\n\t// If the migration is already completed, do not run it again.\n\tif hasAlreadyRun, _ := strconv.ParseBool(setting); hasAlreadyRun {\n\t\treturn nil\n\t}\n\n\tif currentMigration >= (deDuplicateCategoryBoards + 1) {\n\t\t// if the migration for which we're fixing the data is already applied,\n\t\t// no need to check fix anything\n\n\t\tif mErr := s.setSystemSetting(s.db, DeDuplicateCategoryBoardTableMigrationKey, strconv.FormatBool(true)); mErr != nil {\n\t\t\treturn fmt.Errorf(\"cannot mark migration %s as completed: %w\", \"RunDeDuplicateCategoryBoardsMigration\", mErr)\n\t\t}\n\t\treturn nil\n\t}\n\n\tneeded, err := s.doesDuplicateCategoryBoardsExist()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !needed {\n\t\tif mErr := s.setSystemSetting(s.db, DeDuplicateCategoryBoardTableMigrationKey, strconv.FormatBool(true)); mErr != nil {\n\t\t\treturn fmt.Errorf(\"cannot mark migration %s as completed: %w\", \"RunDeDuplicateCategoryBoardsMigration\", mErr)\n\t\t}\n\t}\n\n\tif s.dbType == model.MysqlDBType {\n\t\treturn s.runMySQLDeDuplicateCategoryBoardsMigration()\n\t} else if s.dbType == model.PostgresDBType {\n\t\treturn s.runPostgresDeDuplicateCategoryBoardsMigration()\n\t}\n\n\tif mErr := s.setSystemSetting(s.db, DeDuplicateCategoryBoardTableMigrationKey, strconv.FormatBool(true)); mErr != nil {\n\t\treturn fmt.Errorf(\"cannot mark migration %s as completed: %w\", \"RunDeDuplicateCategoryBoardsMigration\", mErr)\n\t}\n\n\treturn nil\n}\n\nfunc (s *SQLStore) doesDuplicateCategoryBoardsExist() (bool, error) {\n\tsubQuery := s.getQueryBuilder(s.db).\n\t\tSelect(\"user_id\", \"board_id\", \"count(*) AS count\").\n\t\tFrom(s.tablePrefix+\"category_boards\").\n\t\tGroupBy(\"user_id\", \"board_id\").\n\t\tHaving(\"count(*) > 1\")\n\n\tquery := s.getQueryBuilder(s.db).\n\t\tSelect(\"COUNT(user_id)\").\n\t\tFromSelect(subQuery, \"duplicate_dataset\")\n\n\trow := query.QueryRow()\n\n\tcount := 0\n\tif err := row.Scan(&count); err != nil {\n\t\ts.logger.Error(\"Error occurred reading number of duplicate records in category_boards table\", mlog.Err(err))\n\t\treturn false, err\n\t}\n\n\treturn count > 0, nil\n}\n\nfunc (s *SQLStore) runMySQLDeDuplicateCategoryBoardsMigration() error {\n\ttablePrefix := s.tablePrefix\n\n\tvar queryBuilder strings.Builder\n\tqueryBuilder.WriteString(\"DELETE FROM \")\n\tqueryBuilder.WriteString(tablePrefix)\n\tqueryBuilder.WriteString(\"category_boards WHERE id NOT IN \")\n\tqueryBuilder.WriteString(\"(SELECT * FROM ( SELECT min(id) FROM \")\n\tqueryBuilder.WriteString(tablePrefix)\n\tqueryBuilder.WriteString(\"category_boards GROUP BY user_id, board_id ) as data)\")\n\n\tquery := queryBuilder.String()\n\tif _, err := s.db.Exec(query); err != nil {\n\t\ts.logger.Error(\"Failed to de-duplicate data in category_boards table\", mlog.Err(err))\n\t}\n\n\treturn nil\n}\n\nfunc (s *SQLStore) runPostgresDeDuplicateCategoryBoardsMigration() error {\n\ttablePrefix := s.tablePrefix\n\n\tvar queryBuilder strings.Builder\n\tqueryBuilder.WriteString(\"WITH duplicates AS (SELECT id, ROW_NUMBER() OVER(PARTITION BY user_id, board_id) AS rownum \")\n\tqueryBuilder.WriteString(\"FROM \")\n\tqueryBuilder.WriteString(tablePrefix)\n\tqueryBuilder.WriteString(\"category_boards) \")\n\tqueryBuilder.WriteString(\"DELETE FROM \")\n\tqueryBuilder.WriteString(tablePrefix)\n\tqueryBuilder.WriteString(\"category_boards USING duplicates \")\n\tqueryBuilder.WriteString(\"WHERE \")\n\tqueryBuilder.WriteString(tablePrefix)\n\tqueryBuilder.WriteString(\"category_boards.id = duplicates.id AND duplicates.rownum > 1;\")\n\n\tquery := queryBuilder.String()\n\n\tif _, err := s.db.Exec(query); err != nil {\n\t\ts.logger.Error(\"Failed to de-duplicate data in category_boards table\", mlog.Err(err))\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "server/services/store/sqlstore/data_migrations_test.go",
    "content": "package sqlstore\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestGetBlocksWithSameID(t *testing.T) {\n\tt.Skip(\"we need to setup a test with the database migrated up to version 14 and then run these tests\")\n\n\tstore, tearDown := SetupTests(t)\n\tsqlStore := store.(*SQLStore)\n\tdefer tearDown()\n\n\tcontainer1 := \"1\"\n\tcontainer2 := \"2\"\n\tcontainer3 := \"3\"\n\n\tblock1 := &model.Block{ID: \"block-id-1\", BoardID: \"board-id-1\"}\n\tblock2 := &model.Block{ID: \"block-id-2\", BoardID: \"board-id-2\"}\n\tblock3 := &model.Block{ID: \"block-id-3\", BoardID: \"board-id-3\"}\n\n\tblock4 := &model.Block{ID: \"block-id-1\", BoardID: \"board-id-1\"}\n\tblock5 := &model.Block{ID: \"block-id-2\", BoardID: \"board-id-2\"}\n\n\tblock6 := &model.Block{ID: \"block-id-1\", BoardID: \"board-id-1\"}\n\tblock7 := &model.Block{ID: \"block-id-7\", BoardID: \"board-id-7\"}\n\tblock8 := &model.Block{ID: \"block-id-8\", BoardID: \"board-id-8\"}\n\n\tfor _, block := range []*model.Block{block1, block2, block3} {\n\t\terr := sqlStore.insertLegacyBlock(sqlStore.db, container1, block, \"user-id\")\n\t\trequire.NoError(t, err)\n\t\ttime.Sleep(100 * time.Millisecond)\n\t}\n\n\tfor _, block := range []*model.Block{block4, block5} {\n\t\terr := sqlStore.insertLegacyBlock(sqlStore.db, container2, block, \"user-id\")\n\t\trequire.NoError(t, err)\n\t\ttime.Sleep(100 * time.Millisecond)\n\t}\n\n\tfor _, block := range []*model.Block{block6, block7, block8} {\n\t\terr := sqlStore.insertLegacyBlock(sqlStore.db, container3, block, \"user-id\")\n\t\trequire.NoError(t, err)\n\t\ttime.Sleep(100 * time.Millisecond)\n\t}\n\n\tblocksWithDuplicatedID := []*model.Block{block1, block2, block4, block5, block6}\n\n\tblocks, err := sqlStore.getBlocksWithSameID(sqlStore.db)\n\trequire.NoError(t, err)\n\n\t// we process the found blocks to remove extra information and be\n\t// able to compare both expected and found sets\n\tfoundBlocks := []*model.Block{}\n\tfor _, foundBlock := range blocks {\n\t\tfoundBlocks = append(foundBlocks, &model.Block{ID: foundBlock.ID, BoardID: foundBlock.BoardID})\n\t}\n\n\trequire.ElementsMatch(t, blocksWithDuplicatedID, foundBlocks)\n}\n\nfunc TestReplaceBlockID(t *testing.T) {\n\tt.Skip(\"we need to setup a test with the database migrated up to version 14 and then run these tests\")\n\n\tstore, tearDown := SetupTests(t)\n\tsqlStore := store.(*SQLStore)\n\tdefer tearDown()\n\n\tcontainer1 := \"1\"\n\tcontainer2 := \"2\"\n\n\t// blocks from team1\n\tblock1 := &model.Block{ID: \"block-id-1\", BoardID: \"board-id-1\"}\n\tblock2 := &model.Block{ID: \"block-id-2\", BoardID: \"board-id-2\", ParentID: \"block-id-1\"}\n\tblock3 := &model.Block{ID: \"block-id-3\", BoardID: \"block-id-1\"}\n\tblock4 := &model.Block{ID: \"block-id-4\", BoardID: \"block-id-2\"}\n\tblock5 := &model.Block{ID: \"block-id-5\", BoardID: \"block-id-1\", ParentID: \"block-id-1\"}\n\tblock8 := &model.Block{\n\t\tID: \"block-id-8\", BoardID: \"board-id-2\", Type: model.TypeCard,\n\t\tFields: map[string]interface{}{\"contentOrder\": []string{\"block-id-1\", \"block-id-2\"}},\n\t}\n\n\t// blocks from team2. They're identical to blocks 1 and 2,\n\t// but they shouldn't change\n\tblock6 := &model.Block{ID: \"block-id-1\", BoardID: \"board-id-1\"}\n\tblock7 := &model.Block{ID: \"block-id-2\", BoardID: \"board-id-2\", ParentID: \"block-id-1\"}\n\tblock9 := &model.Block{\n\t\tID: \"block-id-8\", BoardID: \"board-id-2\", Type: model.TypeCard,\n\t\tFields: map[string]interface{}{\"contentOrder\": []string{\"block-id-1\", \"block-id-2\"}},\n\t}\n\n\tfor _, block := range []*model.Block{block1, block2, block3, block4, block5, block8} {\n\t\terr := sqlStore.insertLegacyBlock(sqlStore.db, container1, block, \"user-id\")\n\t\trequire.NoError(t, err)\n\t\ttime.Sleep(100 * time.Millisecond)\n\t}\n\n\tfor _, block := range []*model.Block{block6, block7, block9} {\n\t\terr := sqlStore.insertLegacyBlock(sqlStore.db, container2, block, \"user-id\")\n\t\trequire.NoError(t, err)\n\t\ttime.Sleep(100 * time.Millisecond)\n\t}\n\n\tcurrentID := \"block-id-1\"\n\tnewID := \"new-id-1\"\n\terr := sqlStore.replaceBlockID(sqlStore.db, currentID, newID, \"1\")\n\trequire.NoError(t, err)\n\n\tnewBlock1, err := sqlStore.getLegacyBlock(sqlStore.db, container1, newID)\n\trequire.NoError(t, err)\n\tnewBlock2, err := sqlStore.getLegacyBlock(sqlStore.db, container1, block2.ID)\n\trequire.NoError(t, err)\n\tnewBlock3, err := sqlStore.getLegacyBlock(sqlStore.db, container1, block3.ID)\n\trequire.NoError(t, err)\n\tnewBlock5, err := sqlStore.getLegacyBlock(sqlStore.db, container1, block5.ID)\n\trequire.NoError(t, err)\n\tnewBlock6, err := sqlStore.getLegacyBlock(sqlStore.db, container2, block6.ID)\n\trequire.NoError(t, err)\n\tnewBlock7, err := sqlStore.getLegacyBlock(sqlStore.db, container2, block7.ID)\n\trequire.NoError(t, err)\n\tnewBlock8, err := sqlStore.GetBlock(block8.ID)\n\trequire.NoError(t, err)\n\tnewBlock9, err := sqlStore.GetBlock(block9.ID)\n\trequire.NoError(t, err)\n\n\trequire.Equal(t, newID, newBlock1.ID)\n\trequire.Equal(t, newID, newBlock2.ParentID)\n\trequire.Equal(t, newID, newBlock3.BoardID)\n\trequire.Equal(t, newID, newBlock5.BoardID)\n\trequire.Equal(t, newID, newBlock5.ParentID)\n\trequire.Equal(t, newBlock8.Fields[\"contentOrder\"].([]interface{})[0], newID)\n\trequire.Equal(t, newBlock8.Fields[\"contentOrder\"].([]interface{})[1], \"block-id-2\")\n\n\trequire.Equal(t, currentID, newBlock6.ID)\n\trequire.Equal(t, currentID, newBlock7.ParentID)\n\trequire.Equal(t, newBlock9.Fields[\"contentOrder\"].([]interface{})[0], \"block-id-1\")\n\trequire.Equal(t, newBlock9.Fields[\"contentOrder\"].([]interface{})[1], \"block-id-2\")\n}\n\nfunc TestRunUniqueIDsMigration(t *testing.T) {\n\tt.Skip(\"we need to setup a test with the database migrated up to version 14 and then run these tests\")\n\n\tstore, tearDown := SetupTests(t)\n\tsqlStore := store.(*SQLStore)\n\tdefer tearDown()\n\n\t// we need to mark the migration as not done so we can run it\n\t// again with the test data\n\tkeyErr := sqlStore.SetSystemSetting(UniqueIDsMigrationKey, \"false\")\n\trequire.NoError(t, keyErr)\n\n\tcontainer1 := \"1\"\n\tcontainer2 := \"2\"\n\tcontainer3 := \"3\"\n\n\t// blocks from workspace1. They shouldn't change, as the first\n\t// duplicated ID is preserved\n\tblock1 := &model.Block{ID: \"block-id-1\", BoardID: \"board-id-1\"}\n\tblock2 := &model.Block{ID: \"block-id-2\", BoardID: \"board-id-2\", ParentID: \"block-id-1\"}\n\tblock3 := &model.Block{ID: \"block-id-3\", BoardID: \"block-id-1\"}\n\n\t// blocks from workspace2. They're identical to blocks 1, 2 and 3,\n\t// and they should change\n\tblock4 := &model.Block{ID: \"block-id-1\", BoardID: \"board-id-1\"}\n\tblock5 := &model.Block{ID: \"block-id-2\", BoardID: \"board-id-2\", ParentID: \"block-id-1\"}\n\tblock6 := &model.Block{ID: \"block-id-6\", BoardID: \"block-id-1\", ParentID: \"block-id-2\"}\n\n\t// block from workspace3. It should change as well\n\tblock7 := &model.Block{ID: \"block-id-2\", BoardID: \"board-id-2\"}\n\n\tfor _, block := range []*model.Block{block1, block2, block3} {\n\t\terr := sqlStore.insertLegacyBlock(sqlStore.db, container1, block, \"user-id-2\")\n\t\trequire.NoError(t, err)\n\t\ttime.Sleep(100 * time.Millisecond)\n\t}\n\n\tfor _, block := range []*model.Block{block4, block5, block6} {\n\t\terr := sqlStore.insertLegacyBlock(sqlStore.db, container2, block, \"user-id-2\")\n\t\trequire.NoError(t, err)\n\t\ttime.Sleep(100 * time.Millisecond)\n\t}\n\n\tfor _, block := range []*model.Block{block7} {\n\t\terr := sqlStore.insertLegacyBlock(sqlStore.db, container3, block, \"user-id-2\")\n\t\trequire.NoError(t, err)\n\t\ttime.Sleep(100 * time.Millisecond)\n\t}\n\n\terr := sqlStore.RunUniqueIDsMigration()\n\trequire.NoError(t, err)\n\n\t// blocks from workspace 1 haven't changed, so we can simply fetch them\n\tnewBlock1, err := sqlStore.getLegacyBlock(sqlStore.db, container1, block1.ID)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, newBlock1)\n\tnewBlock2, err := sqlStore.getLegacyBlock(sqlStore.db, container1, block2.ID)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, newBlock2)\n\tnewBlock3, err := sqlStore.getLegacyBlock(sqlStore.db, container1, block3.ID)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, newBlock3)\n\n\t// first two blocks from workspace 2 have changed, so we fetch\n\t// them through the third one, which points to the new IDs\n\tnewBlock6, err := sqlStore.getLegacyBlock(sqlStore.db, container2, block6.ID)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, newBlock6)\n\tnewBlock4, err := sqlStore.getLegacyBlock(sqlStore.db, container2, newBlock6.BoardID)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, newBlock4)\n\tnewBlock5, err := sqlStore.getLegacyBlock(sqlStore.db, container2, newBlock6.ParentID)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, newBlock5)\n\n\t// block from workspace 3 changed as well, so we shouldn't be able\n\t// to fetch it\n\tnewBlock7, err := sqlStore.getLegacyBlock(sqlStore.db, container3, block7.ID)\n\trequire.NoError(t, err)\n\trequire.Nil(t, newBlock7)\n\n\t// workspace 1 block links are maintained\n\trequire.Equal(t, newBlock1.ID, newBlock2.ParentID)\n\trequire.Equal(t, newBlock1.ID, newBlock3.BoardID)\n\n\t// workspace 2 first two block IDs have changed\n\trequire.NotEqual(t, block4.ID, newBlock4.BoardID)\n\trequire.NotEqual(t, block5.ID, newBlock5.ParentID)\n}\n\nfunc TestCheckForMismatchedCollation(t *testing.T) {\n\tstore, tearDown := SetupTests(t)\n\tsqlStore := store.(*SQLStore)\n\tdefer tearDown()\n\n\tif sqlStore.dbType != model.MysqlDBType {\n\t\treturn\n\t}\n\n\t// make sure all collations are consistent.\n\ttableNames, err := sqlStore.getFocalBoardTableNames()\n\trequire.NoError(t, err)\n\n\tsqlCollation := \"SELECT table_collation FROM information_schema.tables WHERE table_name=? and table_schema=(SELECT DATABASE())\"\n\tstmtCollation, err := sqlStore.db.Prepare(sqlCollation)\n\trequire.NoError(t, err)\n\tdefer stmtCollation.Close()\n\n\tvar collation string\n\n\t// make sure the correct charset is applied to each table.\n\tfor i, name := range tableNames {\n\t\trow := stmtCollation.QueryRow(name)\n\n\t\tvar actualCollation string\n\t\terr = row.Scan(&actualCollation)\n\t\trequire.NoError(t, err)\n\n\t\tif collation == \"\" {\n\t\t\tcollation = actualCollation\n\t\t}\n\n\t\tassert.Equalf(t, collation, actualCollation, \"for table_name='%s', index=%d\", name, i)\n\t}\n}\n"
  },
  {
    "path": "server/services/store/sqlstore/data_retention.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\npackage sqlstore\n\nimport (\n\t\"database/sql\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/pkg/errors\"\n\n\tsq \"github.com/Masterminds/squirrel\"\n\t_ \"github.com/lib/pq\" // postgres driver\n\t\"github.com/mattermost/focalboard/server/model\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\ntype RetentionTableDeletionInfo struct {\n\tTable         string\n\tPrimaryKeys   []string\n\tBoardIDColumn string\n}\n\nfunc (s *SQLStore) runDataRetention(db sq.BaseRunner, globalRetentionDate int64, batchSize int64) (int64, error) {\n\ts.logger.Info(\"Start Boards Data Retention\",\n\t\tmlog.String(\"Global Retention Date\", time.Unix(globalRetentionDate/1000, 0).String()),\n\t\tmlog.Int(\"Raw Date\", globalRetentionDate))\n\tdeleteTables := []RetentionTableDeletionInfo{\n\t\t{\n\t\t\tTable:         \"blocks\",\n\t\t\tPrimaryKeys:   []string{\"id\"},\n\t\t\tBoardIDColumn: \"board_id\",\n\t\t},\n\t\t{\n\t\t\tTable:         \"blocks_history\",\n\t\t\tPrimaryKeys:   []string{\"id\"},\n\t\t\tBoardIDColumn: \"board_id\",\n\t\t},\n\t\t{\n\t\t\tTable:         \"boards\",\n\t\t\tPrimaryKeys:   []string{\"id\"},\n\t\t\tBoardIDColumn: \"id\",\n\t\t},\n\t\t{\n\t\t\tTable:         \"boards_history\",\n\t\t\tPrimaryKeys:   []string{\"id\"},\n\t\t\tBoardIDColumn: \"id\",\n\t\t},\n\t\t{\n\t\t\tTable:         \"board_members\",\n\t\t\tPrimaryKeys:   []string{\"board_id\"},\n\t\t\tBoardIDColumn: \"board_id\",\n\t\t},\n\t\t{\n\t\t\tTable:         \"board_members_history\",\n\t\t\tPrimaryKeys:   []string{\"board_id\"},\n\t\t\tBoardIDColumn: \"board_id\",\n\t\t},\n\t\t{\n\t\t\tTable:         \"sharing\",\n\t\t\tPrimaryKeys:   []string{\"id\"},\n\t\t\tBoardIDColumn: \"id\",\n\t\t},\n\t\t{\n\t\t\tTable:         \"category_boards\",\n\t\t\tPrimaryKeys:   []string{\"id\"},\n\t\t\tBoardIDColumn: \"board_id\",\n\t\t},\n\t}\n\n\tsubBuilder := s.getQueryBuilder(db).\n\t\tSelect(\"board_id, MAX(update_at) AS maxDate\").\n\t\tFrom(s.tablePrefix + \"blocks\").\n\t\tGroupBy(\"board_id\")\n\n\tsubQuery, _, _ := subBuilder.ToSql()\n\n\tbuilder := s.getQueryBuilder(db).\n\t\tSelect(\"id\").\n\t\tFrom(s.tablePrefix + \"boards\").\n\t\tLeftJoin(\"( \" + subQuery + \" ) As subquery ON (subquery.board_id = id)\").\n\t\tWhere(sq.Lt{\"maxDate\": globalRetentionDate}).\n\t\tWhere(sq.NotEq{\"team_id\": \"0\"}).\n\t\tWhere(sq.Eq{\"is_template\": false})\n\n\trows, err := builder.Query()\n\tif err != nil {\n\t\ts.logger.Error(`dataRetention subquery ERROR`, mlog.Err(err))\n\t\treturn 0, err\n\t}\n\tdefer s.CloseRows(rows)\n\tdeleteIds, err := idsFromRows(rows)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\ttotalAffected := 0\n\tif len(deleteIds) > 0 {\n\t\tfor _, table := range deleteTables {\n\t\t\taffected, err := s.genericRetentionPoliciesDeletion(db, table, deleteIds, batchSize)\n\t\t\tif err != nil {\n\t\t\t\treturn int64(totalAffected), err\n\t\t\t}\n\t\t\ttotalAffected += int(affected)\n\t\t}\n\t}\n\ts.logger.Info(\"Complete Boards Data Retention\",\n\t\tmlog.Int(\"Total deletion ids\", len(deleteIds)),\n\t\tmlog.Int(\"TotalAffected\", totalAffected))\n\treturn int64(totalAffected), nil\n}\n\nfunc idsFromRows(rows *sql.Rows) ([]string, error) {\n\tdeleteIds := []string{}\n\tfor rows.Next() {\n\t\tvar boardID string\n\t\terr := rows.Scan(\n\t\t\t&boardID,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdeleteIds = append(deleteIds, boardID)\n\t}\n\treturn deleteIds, nil\n}\n\n// genericRetentionPoliciesDeletion actually executes the DELETE query\n// using a sq.SelectBuilder which selects the rows to delete.\nfunc (s *SQLStore) genericRetentionPoliciesDeletion(\n\tdb sq.BaseRunner,\n\tinfo RetentionTableDeletionInfo,\n\tdeleteIds []string,\n\tbatchSize int64,\n) (int64, error) {\n\twhereClause := info.BoardIDColumn + \" IN ('\" + strings.Join(deleteIds, \"','\") + \"')\"\n\tdeleteQuery := s.getQueryBuilder(db).\n\t\tDelete(s.tablePrefix + info.Table).\n\t\tWhere(whereClause)\n\n\tif batchSize > 0 {\n\t\tdeleteQuery.Limit(uint64(batchSize))\n\t\tprimaryKeysStr := \"(\" + strings.Join(info.PrimaryKeys, \",\") + \")\"\n\t\tif s.dbType != model.MysqlDBType {\n\t\t\tselectQuery := s.getQueryBuilder(db).\n\t\t\t\tSelect(primaryKeysStr).\n\t\t\t\tFrom(s.tablePrefix + info.Table).\n\t\t\t\tWhere(whereClause).\n\t\t\t\tLimit(uint64(batchSize))\n\n\t\t\tselectString, _, _ := selectQuery.ToSql()\n\n\t\t\tdeleteQuery = s.getQueryBuilder(db).\n\t\t\t\tDelete(s.tablePrefix + info.Table).\n\t\t\t\tWhere(primaryKeysStr + \" IN (\" + selectString + \")\")\n\t\t}\n\t}\n\n\tvar totalRowsAffected int64\n\tvar batchRowsAffected int64\n\tfor {\n\t\tresult, err := deleteQuery.Exec()\n\t\tif err != nil {\n\t\t\treturn 0, errors.Wrap(err, \"failed to delete \"+info.Table)\n\t\t}\n\n\t\tbatchRowsAffected, err = result.RowsAffected()\n\t\tif err != nil {\n\t\t\treturn 0, errors.Wrap(err, \"failed to get rows affected for \"+info.Table)\n\t\t}\n\t\ttotalRowsAffected += batchRowsAffected\n\t\tif batchRowsAffected != batchSize {\n\t\t\tbreak\n\t\t}\n\t}\n\treturn totalRowsAffected, nil\n}\n"
  },
  {
    "path": "server/services/store/sqlstore/file.go",
    "content": "package sqlstore\n\nimport (\n\t\"database/sql\"\n\t\"errors\"\n\n\tsq \"github.com/Masterminds/squirrel\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\n\tmmModel \"github.com/mattermost/mattermost/server/public/model\"\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nfunc (s *SQLStore) saveFileInfo(db sq.BaseRunner, fileInfo *mmModel.FileInfo) error {\n\tquery := s.getQueryBuilder(db).\n\t\tInsert(s.tablePrefix+\"file_info\").\n\t\tColumns(\n\t\t\t\"id\",\n\t\t\t\"create_at\",\n\t\t\t\"name\",\n\t\t\t\"extension\",\n\t\t\t\"size\",\n\t\t\t\"delete_at\",\n\t\t\t\"path\",\n\t\t\t\"archived\",\n\t\t).\n\t\tValues(\n\t\t\tfileInfo.Id,\n\t\t\tfileInfo.CreateAt,\n\t\t\tfileInfo.Name,\n\t\t\tfileInfo.Extension,\n\t\t\tfileInfo.Size,\n\t\t\tfileInfo.DeleteAt,\n\t\t\tfileInfo.Path,\n\t\t\tfalse,\n\t\t)\n\n\tif _, err := query.Exec(); err != nil {\n\t\ts.logger.Error(\n\t\t\t\"failed to save fileinfo\",\n\t\t\tmlog.String(\"file_name\", fileInfo.Name),\n\t\t\tmlog.Int(\"size\", fileInfo.Size),\n\t\t\tmlog.Err(err),\n\t\t)\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (s *SQLStore) getFileInfo(db sq.BaseRunner, id string) (*mmModel.FileInfo, error) {\n\tquery := s.getQueryBuilder(db).\n\t\tSelect(\n\t\t\t\"id\",\n\t\t\t\"create_at\",\n\t\t\t\"delete_at\",\n\t\t\t\"name\",\n\t\t\t\"extension\",\n\t\t\t\"size\",\n\t\t\t\"archived\",\n\t\t\t\"path\",\n\t\t).\n\t\tFrom(s.tablePrefix + \"file_info\").\n\t\tWhere(sq.Eq{\"Id\": id})\n\n\trow := query.QueryRow()\n\n\tfileInfo := mmModel.FileInfo{}\n\n\terr := row.Scan(\n\t\t&fileInfo.Id,\n\t\t&fileInfo.CreateAt,\n\t\t&fileInfo.DeleteAt,\n\t\t&fileInfo.Name,\n\t\t&fileInfo.Extension,\n\t\t&fileInfo.Size,\n\t\t&fileInfo.Archived,\n\t\t&fileInfo.Path,\n\t)\n\n\tif err != nil {\n\t\tif errors.Is(err, sql.ErrNoRows) {\n\t\t\treturn nil, model.NewErrNotFound(\"file info ID=\" + id)\n\t\t}\n\n\t\ts.logger.Error(\"error scanning fileinfo row\", mlog.String(\"id\", id), mlog.Err(err))\n\t\treturn nil, err\n\t}\n\n\treturn &fileInfo, nil\n}\n"
  },
  {
    "path": "server/services/store/sqlstore/helpers_test.go",
    "content": "package sqlstore\n\nimport (\n\t\"database/sql\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/mattermost/focalboard/server/services/store\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nfunc SetupTests(t *testing.T) (store.Store, func()) {\n\torigUnitTesting := os.Getenv(\"FOCALBOARD_UNIT_TESTING\")\n\tos.Setenv(\"FOCALBOARD_UNIT_TESTING\", \"1\")\n\n\tdbType, connectionString, err := PrepareNewTestDatabase()\n\trequire.NoError(t, err)\n\n\tlogger, _ := mlog.NewLogger()\n\n\tsqlDB, err := sql.Open(dbType, connectionString)\n\trequire.NoError(t, err)\n\terr = sqlDB.Ping()\n\trequire.NoError(t, err)\n\n\tstoreParams := Params{\n\t\tDBType:           dbType,\n\t\tConnectionString: connectionString,\n\t\tDBPingAttempts:   5,\n\t\tTablePrefix:      \"test_\",\n\t\tLogger:           logger,\n\t\tDB:               sqlDB,\n\t}\n\tstore, err := New(storeParams)\n\trequire.NoError(t, err)\n\n\ttearDown := func() {\n\t\tdefer func() { _ = logger.Shutdown() }()\n\t\terr = store.Shutdown()\n\t\trequire.Nil(t, err)\n\t\tif err = os.Remove(connectionString); err == nil {\n\t\t\tlogger.Debug(\"Removed test database\", mlog.String(\"file\", connectionString))\n\t\t}\n\t\tos.Setenv(\"FOCALBOARD_UNIT_TESTING\", origUnitTesting)\n\t}\n\n\treturn store, tearDown\n}\n"
  },
  {
    "path": "server/services/store/sqlstore/legacy_blocks.go",
    "content": "package sqlstore\n\nimport (\n\t\"database/sql\"\n\t\"encoding/json\"\n\n\t\"github.com/mattermost/focalboard/server/utils\"\n\n\tsq \"github.com/Masterminds/squirrel\"\n\t\"github.com/mattermost/focalboard/server/model\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\n// legacyBlocksFromRows is the old getBlock version that still uses\n// the old block model. This method is kept to enable the unique IDs\n// data migration.\nfunc (s *SQLStore) legacyBlocksFromRows(rows *sql.Rows) ([]*model.Block, error) {\n\tresults := []*model.Block{}\n\n\tfor rows.Next() {\n\t\tvar block model.Block\n\t\tvar fieldsJSON string\n\t\tvar modifiedBy sql.NullString\n\t\tvar insertAt string\n\n\t\terr := rows.Scan(\n\t\t\t&block.ID,\n\t\t\t&block.ParentID,\n\t\t\t&block.BoardID,\n\t\t\t&block.CreatedBy,\n\t\t\t&modifiedBy,\n\t\t\t&block.Schema,\n\t\t\t&block.Type,\n\t\t\t&block.Title,\n\t\t\t&fieldsJSON,\n\t\t\t&insertAt,\n\t\t\t&block.CreateAt,\n\t\t\t&block.UpdateAt,\n\t\t\t&block.DeleteAt,\n\t\t\t&block.WorkspaceID)\n\t\tif err != nil {\n\t\t\t// handle this error\n\t\t\ts.logger.Error(`ERROR blocksFromRows`, mlog.Err(err))\n\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif modifiedBy.Valid {\n\t\t\tblock.ModifiedBy = modifiedBy.String\n\t\t}\n\n\t\terr = json.Unmarshal([]byte(fieldsJSON), &block.Fields)\n\t\tif err != nil {\n\t\t\t// handle this error\n\t\t\ts.logger.Error(`ERROR blocksFromRows fields`, mlog.Err(err))\n\n\t\t\treturn nil, err\n\t\t}\n\n\t\tresults = append(results, &block)\n\t}\n\n\treturn results, nil\n}\n\n// getLegacyBlock is the old getBlock version that still uses the old\n// block model. This method is kept to enable the unique IDs data\n// migration.\nfunc (s *SQLStore) getLegacyBlock(db sq.BaseRunner, workspaceID string, blockID string) (*model.Block, error) {\n\tquery := s.getQueryBuilder(db).\n\t\tSelect(\n\t\t\t\"id\",\n\t\t\t\"parent_id\",\n\t\t\t\"root_id\",\n\t\t\t\"created_by\",\n\t\t\t\"modified_by\",\n\t\t\ts.escapeField(\"schema\"),\n\t\t\t\"type\",\n\t\t\t\"title\",\n\t\t\t\"COALESCE(fields, '{}')\",\n\t\t\t\"insert_at\",\n\t\t\t\"create_at\",\n\t\t\t\"update_at\",\n\t\t\t\"delete_at\",\n\t\t\t\"COALESCE(workspace_id, '0')\",\n\t\t).\n\t\tFrom(s.tablePrefix + \"blocks\").\n\t\tWhere(sq.Eq{\"id\": blockID}).\n\t\tWhere(sq.Eq{\"coalesce(workspace_id, '0')\": workspaceID})\n\n\trows, err := query.Query()\n\tif err != nil {\n\t\ts.logger.Error(`GetBlock ERROR`, mlog.Err(err))\n\t\treturn nil, err\n\t}\n\n\tblocks, err := s.legacyBlocksFromRows(rows)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(blocks) == 0 {\n\t\treturn nil, nil\n\t}\n\n\treturn blocks[0], nil\n}\n\n// insertLegacyBlock is the old insertBlock version that still uses\n// the old block model. This method is kept to enable the unique IDs\n// data migration.\nfunc (s *SQLStore) insertLegacyBlock(db sq.BaseRunner, workspaceID string, block *model.Block, userID string) error {\n\tif block.BoardID == \"\" {\n\t\treturn model.ErrBlockEmptyBoardID\n\t}\n\n\tfieldsJSON, err := json.Marshal(block.Fields)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\texistingBlock, err := s.getLegacyBlock(db, workspaceID, block.ID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tblock.UpdateAt = utils.GetMillis()\n\tblock.ModifiedBy = userID\n\n\tinsertQuery := s.getQueryBuilder(db).Insert(\"\").\n\t\tColumns(\n\t\t\t\"workspace_id\",\n\t\t\t\"id\",\n\t\t\t\"parent_id\",\n\t\t\t\"root_id\",\n\t\t\t\"created_by\",\n\t\t\t\"modified_by\",\n\t\t\ts.escapeField(\"schema\"),\n\t\t\t\"type\",\n\t\t\t\"title\",\n\t\t\t\"fields\",\n\t\t\t\"create_at\",\n\t\t\t\"update_at\",\n\t\t\t\"delete_at\",\n\t\t)\n\n\tinsertQueryValues := map[string]interface{}{\n\t\t\"workspace_id\":          workspaceID,\n\t\t\"id\":                    block.ID,\n\t\t\"parent_id\":             block.ParentID,\n\t\t\"root_id\":               block.BoardID,\n\t\ts.escapeField(\"schema\"): block.Schema,\n\t\t\"type\":                  block.Type,\n\t\t\"title\":                 block.Title,\n\t\t\"fields\":                fieldsJSON,\n\t\t\"delete_at\":             block.DeleteAt,\n\t\t\"created_by\":            block.CreatedBy,\n\t\t\"modified_by\":           block.ModifiedBy,\n\t\t\"create_at\":             block.CreateAt,\n\t\t\"update_at\":             block.UpdateAt,\n\t}\n\n\tif existingBlock != nil {\n\t\t// block with ID exists, so this is an update operation\n\t\tquery := s.getQueryBuilder(db).Update(s.tablePrefix+\"blocks\").\n\t\t\tWhere(sq.Eq{\"id\": block.ID}).\n\t\t\tWhere(sq.Eq{\"COALESCE(workspace_id, '0')\": workspaceID}).\n\t\t\tSet(\"parent_id\", block.ParentID).\n\t\t\tSet(\"root_id\", block.BoardID).\n\t\t\tSet(\"modified_by\", block.ModifiedBy).\n\t\t\tSet(s.escapeField(\"schema\"), block.Schema).\n\t\t\tSet(\"type\", block.Type).\n\t\t\tSet(\"title\", block.Title).\n\t\t\tSet(\"fields\", fieldsJSON).\n\t\t\tSet(\"update_at\", block.UpdateAt).\n\t\t\tSet(\"delete_at\", block.DeleteAt)\n\n\t\tif _, err := query.Exec(); err != nil {\n\t\t\ts.logger.Error(`InsertBlock error occurred while updating existing block`, mlog.String(\"blockID\", block.ID), mlog.Err(err))\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\tblock.CreatedBy = userID\n\t\tblock.CreateAt = utils.GetMillis()\n\n\t\tinsertQueryValues[\"created_by\"] = block.CreatedBy\n\t\tinsertQueryValues[\"create_at\"] = block.CreateAt\n\t\tinsertQueryValues[\"update_at\"] = block.UpdateAt\n\t\tinsertQueryValues[\"modified_by\"] = block.ModifiedBy\n\n\t\tquery := insertQuery.SetMap(insertQueryValues).Into(s.tablePrefix + \"blocks\")\n\t\tif _, err := query.Exec(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// writing block history\n\tquery := insertQuery.SetMap(insertQueryValues).Into(s.tablePrefix + \"blocks_history\")\n\tif _, err := query.Exec(); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "server/services/store/sqlstore/migrate.go",
    "content": "package sqlstore\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"database/sql\"\n\t\"embed\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"text/template\"\n\n\tsq \"github.com/Masterminds/squirrel\"\n\n\tmmModel \"github.com/mattermost/mattermost/server/public/model\"\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n\tsqlUtils \"github.com/mattermost/mattermost/server/public/utils/sql\"\n\n\t\"github.com/mattermost/morph\"\n\tdrivers \"github.com/mattermost/morph/drivers\"\n\tmysql \"github.com/mattermost/morph/drivers/mysql\"\n\tpostgres \"github.com/mattermost/morph/drivers/postgres\"\n\tsqlite \"github.com/mattermost/morph/drivers/sqlite\"\n\tembedded \"github.com/mattermost/morph/sources/embedded\"\n\n\t_ \"github.com/lib/pq\" // postgres driver\n\n\t\"github.com/mattermost/focalboard/server/model\"\n)\n\n//go:embed migrations/*.sql\nvar Assets embed.FS\n\nconst (\n\tuniqueIDsMigrationRequiredVersion        = 14\n\tteamLessBoardsMigrationRequiredVersion   = 18\n\tcategoriesUUIDIDMigrationRequiredVersion = 20\n\tdeDuplicateCategoryBoards                = 35\n\n\ttempSchemaMigrationTableName = \"temp_schema_migration\"\n)\n\n// migrations in MySQL need to run with the multiStatements flag\n// enabled, so this method creates a new connection ensuring that it's\n// enabled.\nfunc (s *SQLStore) getMigrationConnection() (*sql.DB, error) {\n\tconnectionString := s.connectionString\n\tif s.dbType == model.MysqlDBType {\n\t\tvar err error\n\t\tconnectionString, err = sqlUtils.ResetReadTimeout(connectionString)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tconnectionString, err = sqlUtils.AppendMultipleStatementsFlag(connectionString)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tvar settings mmModel.SqlSettings\n\tsettings.SetDefaults(false)\n\tif s.configFn != nil {\n\t\tsettings = s.configFn().SqlSettings\n\t}\n\t*settings.DriverName = s.dbType\n\n\tdb, _ := sqlUtils.SetupConnection(s.logger, \"master\", connectionString, &settings, s.dbPingAttempts)\n\n\treturn db, nil\n}\n\nfunc (s *SQLStore) Migrate() error {\n\tif err := s.EnsureSchemaMigrationFormat(); err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\t// the old schema migration table deletion happens after the\n\t\t// migrations have run, to be able to recover its information\n\t\t// in case there would be errors during the process.\n\t\tif err := s.deleteOldSchemaMigrationTable(); err != nil {\n\t\t\ts.logger.Error(\"cannot delete the old schema migration table\", mlog.Err(err))\n\t\t}\n\t}()\n\n\tvar driver drivers.Driver\n\tvar err error\n\n\tif s.dbType == model.SqliteDBType {\n\t\tdriver, err = sqlite.WithInstance(s.db)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tvar db *sql.DB\n\tif s.dbType != model.SqliteDBType {\n\t\ts.logger.Debug(\"Getting migrations connection\")\n\t\tdb, err = s.getMigrationConnection()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tdefer func() {\n\t\t\ts.logger.Debug(\"Closing migrations connection\")\n\t\t\tdb.Close()\n\t\t}()\n\t}\n\n\tif s.dbType == model.PostgresDBType {\n\t\tdriver, err = postgres.WithInstance(db)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif s.dbType == model.MysqlDBType {\n\t\tdriver, err = mysql.WithInstance(db)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tassetsList, err := Assets.ReadDir(\"migrations\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tassetNamesForDriver := make([]string, len(assetsList))\n\tfor i, dirEntry := range assetsList {\n\t\tassetNamesForDriver[i] = dirEntry.Name()\n\t}\n\n\tparams := map[string]interface{}{\n\t\t\"prefix\":     s.tablePrefix,\n\t\t\"postgres\":   s.dbType == model.PostgresDBType,\n\t\t\"sqlite\":     s.dbType == model.SqliteDBType,\n\t\t\"mysql\":      s.dbType == model.MysqlDBType,\n\t\t\"singleUser\": s.isSingleUser,\n\t}\n\n\tmigrationAssets := &embedded.AssetSource{\n\t\tNames: assetNamesForDriver,\n\t\tAssetFunc: func(name string) ([]byte, error) {\n\t\t\tasset, mErr := Assets.ReadFile(\"migrations/\" + name)\n\t\t\tif mErr != nil {\n\t\t\t\treturn nil, mErr\n\t\t\t}\n\n\t\t\ttmpl, pErr := template.New(\"sql\").Funcs(s.GetTemplateHelperFuncs()).Parse(string(asset))\n\t\t\tif pErr != nil {\n\t\t\t\treturn nil, pErr\n\t\t\t}\n\n\t\t\tbuffer := bytes.NewBufferString(\"\")\n\n\t\t\terr = tmpl.Execute(buffer, params)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\ts.logger.Trace(\"migration template\",\n\t\t\t\tmlog.String(\"name\", name),\n\t\t\t\tmlog.String(\"sql\", buffer.String()),\n\t\t\t)\n\n\t\t\treturn buffer.Bytes(), nil\n\t\t},\n\t}\n\n\tsrc, err := embedded.WithInstance(migrationAssets)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\topts := []morph.EngineOption{\n\t\tmorph.WithLock(\"boards-lock-key\"),\n\t\tmorph.SetMigrationTableName(fmt.Sprintf(\"%sschema_migrations\", s.tablePrefix)),\n\t\tmorph.SetStatementTimeoutInSeconds(1000000),\n\t}\n\n\tif s.dbType == model.SqliteDBType {\n\t\topts = opts[:0] // sqlite driver does not support locking, it doesn't need to anyway.\n\t}\n\n\ts.logger.Debug(\"Creating migration engine\")\n\tengine, err := morph.New(context.Background(), driver, src, opts...)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\ts.logger.Debug(\"Closing migration engine\")\n\t\tengine.Close()\n\t}()\n\n\treturn s.runMigrationSequence(engine, driver)\n}\n\n// runMigrationSequence executes all the migrations in order, both\n// plain SQL and data migrations.\nfunc (s *SQLStore) runMigrationSequence(engine *morph.Morph, driver drivers.Driver) error {\n\tif mErr := s.ensureMigrationsAppliedUpToVersion(engine, driver, uniqueIDsMigrationRequiredVersion); mErr != nil {\n\t\treturn mErr\n\t}\n\n\tif mErr := s.RunUniqueIDsMigration(); mErr != nil {\n\t\treturn fmt.Errorf(\"error running unique IDs migration: %w\", mErr)\n\t}\n\n\tif mErr := s.ensureMigrationsAppliedUpToVersion(engine, driver, teamLessBoardsMigrationRequiredVersion); mErr != nil {\n\t\treturn mErr\n\t}\n\n\tif mErr := s.ensureMigrationsAppliedUpToVersion(engine, driver, categoriesUUIDIDMigrationRequiredVersion); mErr != nil {\n\t\treturn mErr\n\t}\n\n\tif mErr := s.RunCategoryUUIDIDMigration(); mErr != nil {\n\t\treturn fmt.Errorf(\"error running categoryID migration: %w\", mErr)\n\t}\n\n\tappliedMigrations, err := driver.AppliedMigrations()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif mErr := s.ensureMigrationsAppliedUpToVersion(engine, driver, deDuplicateCategoryBoards); mErr != nil {\n\t\treturn mErr\n\t}\n\n\tcurrentMigrationVersion := len(appliedMigrations)\n\tif mErr := s.RunDeDuplicateCategoryBoardsMigration(currentMigrationVersion); mErr != nil {\n\t\treturn mErr\n\t}\n\n\ts.logger.Debug(\"== Applying all remaining migrations ====================\",\n\t\tmlog.Int(\"current_version\", len(appliedMigrations)),\n\t)\n\n\tif err := engine.ApplyAll(); err != nil {\n\t\treturn err\n\t}\n\n\t// always run the collations & charset fix-ups\n\tif mErr := s.RunFixCollationsAndCharsetsMigration(); mErr != nil {\n\t\treturn fmt.Errorf(\"error running fix collations and charsets migration: %w\", mErr)\n\t}\n\treturn nil\n}\n\nfunc (s *SQLStore) ensureMigrationsAppliedUpToVersion(engine *morph.Morph, driver drivers.Driver, version int) error {\n\tapplied, err := driver.AppliedMigrations()\n\tif err != nil {\n\t\treturn err\n\t}\n\tcurrentVersion := len(applied)\n\n\ts.logger.Debug(\"== Ensuring migrations applied up to version ====================\",\n\t\tmlog.Int(\"version\", version),\n\t\tmlog.Int(\"current_version\", currentVersion))\n\n\t// if the target version is below or equal to the current one, do\n\t// not migrate either because is not needed (both are equal) or\n\t// because it would downgrade the database (is below)\n\tif version <= currentVersion {\n\t\ts.logger.Debug(\"-- There is no need of applying any migration --------------------\")\n\t\treturn nil\n\t}\n\n\tfor _, migration := range applied {\n\t\ts.logger.Debug(\"-- Found applied migration --------------------\", mlog.Uint(\"version\", migration.Version), mlog.String(\"name\", migration.Name))\n\t}\n\n\tif _, err = engine.Apply(version - currentVersion); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (s *SQLStore) GetTemplateHelperFuncs() template.FuncMap {\n\tfuncs := template.FuncMap{\n\t\t\"addColumnIfNeeded\":     s.genAddColumnIfNeeded,\n\t\t\"dropColumnIfNeeded\":    s.genDropColumnIfNeeded,\n\t\t\"createIndexIfNeeded\":   s.genCreateIndexIfNeeded,\n\t\t\"renameTableIfNeeded\":   s.genRenameTableIfNeeded,\n\t\t\"renameColumnIfNeeded\":  s.genRenameColumnIfNeeded,\n\t\t\"doesTableExist\":        s.doesTableExist,\n\t\t\"doesColumnExist\":       s.doesColumnExist,\n\t\t\"addConstraintIfNeeded\": s.genAddConstraintIfNeeded,\n\t}\n\treturn funcs\n}\n\nfunc (s *SQLStore) genAddColumnIfNeeded(tableName, columnName, datatype, constraint string) (string, error) {\n\ttableName = addPrefixIfNeeded(tableName, s.tablePrefix)\n\tnormTableName := s.normalizeTablename(tableName)\n\n\tswitch s.dbType {\n\tcase model.SqliteDBType:\n\t\t// Sqlite does not support any conditionals that can contain DDL commands. No idempotent migrations for Sqlite :-(\n\t\treturn fmt.Sprintf(\"\\nALTER TABLE %s ADD COLUMN %s %s %s;\\n\", normTableName, columnName, datatype, constraint), nil\n\tcase model.MysqlDBType:\n\t\tvars := map[string]string{\n\t\t\t\"schema\":          s.schemaName,\n\t\t\t\"table_name\":      tableName,\n\t\t\t\"norm_table_name\": normTableName,\n\t\t\t\"column_name\":     columnName,\n\t\t\t\"data_type\":       datatype,\n\t\t\t\"constraint\":      constraint,\n\t\t}\n\t\treturn replaceVars(`\n\t\t\tSET @stmt = (SELECT IF(\n\t\t\t\t(\n\t\t\t\t  SELECT COUNT(column_name) FROM INFORMATION_SCHEMA.COLUMNS\n\t\t\t\t  WHERE table_name = '[[table_name]]'\n\t\t\t\t  AND table_schema = '[[schema]]'\n\t\t\t\t  AND column_name = '[[column_name]]'\n\t\t\t\t) > 0,\n\t\t\t\t'SELECT 1;',\n\t\t\t\t'ALTER TABLE [[norm_table_name]] ADD COLUMN [[column_name]] [[data_type]] [[constraint]];'\n\t\t\t));\n\t\t\tPREPARE addColumnIfNeeded FROM @stmt;\n\t\t\tEXECUTE addColumnIfNeeded;\n\t\t\tDEALLOCATE PREPARE addColumnIfNeeded;\n\t\t`, vars), nil\n\tcase model.PostgresDBType:\n\t\treturn fmt.Sprintf(\"\\nALTER TABLE %s ADD COLUMN IF NOT EXISTS %s %s %s;\\n\", normTableName, columnName, datatype, constraint), nil\n\tdefault:\n\t\treturn \"\", ErrUnsupportedDatabaseType\n\t}\n}\n\nfunc (s *SQLStore) genDropColumnIfNeeded(tableName, columnName string) (string, error) {\n\ttableName = addPrefixIfNeeded(tableName, s.tablePrefix)\n\tnormTableName := s.normalizeTablename(tableName)\n\n\tswitch s.dbType {\n\tcase model.SqliteDBType:\n\t\treturn fmt.Sprintf(\"\\n-- Sqlite3 cannot drop columns for versions less than 3.35.0; drop column '%s' in table '%s' skipped\\n\", columnName, tableName), nil\n\tcase model.MysqlDBType:\n\t\tvars := map[string]string{\n\t\t\t\"schema\":          s.schemaName,\n\t\t\t\"table_name\":      tableName,\n\t\t\t\"norm_table_name\": normTableName,\n\t\t\t\"column_name\":     columnName,\n\t\t}\n\t\treturn replaceVars(`\n\t\t\tSET @stmt = (SELECT IF(\n\t\t\t\t(\n\t\t\t\t  SELECT COUNT(column_name) FROM INFORMATION_SCHEMA.COLUMNS\n\t\t\t\t  WHERE table_name = '[[table_name]]'\n\t\t\t\t  AND table_schema = '[[schema]]'\n\t\t\t\t  AND column_name = '[[column_name]]'\n\t\t\t\t) > 0,\n\t\t\t\t'ALTER TABLE [[norm_table_name]] DROP COLUMN [[column_name]];',\n\t\t\t\t'SELECT 1;'\n\t\t\t));\n\t\t\tPREPARE dropColumnIfNeeded FROM @stmt;\n\t\t\tEXECUTE dropColumnIfNeeded;\n\t\t\tDEALLOCATE PREPARE dropColumnIfNeeded;\n\t\t`, vars), nil\n\tcase model.PostgresDBType:\n\t\treturn fmt.Sprintf(\"\\nALTER TABLE %s DROP COLUMN IF EXISTS %s;\\n\", normTableName, columnName), nil\n\tdefault:\n\t\treturn \"\", ErrUnsupportedDatabaseType\n\t}\n}\n\nfunc (s *SQLStore) genCreateIndexIfNeeded(tableName, columns string) (string, error) {\n\tindexName := getIndexName(tableName, columns)\n\ttableName = addPrefixIfNeeded(tableName, s.tablePrefix)\n\tnormTableName := s.normalizeTablename(tableName)\n\n\tswitch s.dbType {\n\tcase model.SqliteDBType:\n\t\t// No support for idempotent index creation in Sqlite.\n\t\treturn fmt.Sprintf(\"\\nCREATE INDEX %s ON %s (%s);\\n\", indexName, normTableName, columns), nil\n\tcase model.MysqlDBType:\n\t\tvars := map[string]string{\n\t\t\t\"schema\":          s.schemaName,\n\t\t\t\"table_name\":      tableName,\n\t\t\t\"norm_table_name\": normTableName,\n\t\t\t\"index_name\":      indexName,\n\t\t\t\"columns\":         columns,\n\t\t}\n\t\treturn replaceVars(`\n\t\t\tSET @stmt = (SELECT IF(\n\t\t\t\t(\n\t\t\t\t  SELECT COUNT(index_name) FROM INFORMATION_SCHEMA.STATISTICS\n\t\t\t\t  WHERE table_name = '[[table_name]]'\n\t\t\t\t  AND table_schema = '[[schema]]'\n\t\t\t\t  AND index_name = '[[index_name]]'\n\t\t\t\t) > 0,\n\t\t\t\t'SELECT 1;',\n\t\t\t\t'CREATE INDEX [[index_name]] ON [[norm_table_name]] ([[columns]]);'\n\t\t\t));\n\t\t\tPREPARE createIndexIfNeeded FROM @stmt;\n\t\t\tEXECUTE createIndexIfNeeded;\n\t\t\tDEALLOCATE PREPARE createIndexIfNeeded;\n\t\t`, vars), nil\n\tcase model.PostgresDBType:\n\t\treturn fmt.Sprintf(\"\\nCREATE INDEX IF NOT EXISTS %s ON %s (%s);\\n\", indexName, normTableName, columns), nil\n\tdefault:\n\t\treturn \"\", ErrUnsupportedDatabaseType\n\t}\n}\n\nfunc (s *SQLStore) genRenameTableIfNeeded(oldTableName, newTableName string) (string, error) {\n\toldTableName = addPrefixIfNeeded(oldTableName, s.tablePrefix)\n\tnewTableName = addPrefixIfNeeded(newTableName, s.tablePrefix)\n\n\tnormOldTableName := s.normalizeTablename(oldTableName)\n\n\tvars := map[string]string{\n\t\t\"schema\":              s.schemaName,\n\t\t\"table_name\":          newTableName,\n\t\t\"norm_old_table_name\": normOldTableName,\n\t\t\"new_table_name\":      newTableName,\n\t}\n\n\tswitch s.dbType {\n\tcase model.SqliteDBType:\n\t\t// No support for idempotent table renaming in Sqlite.\n\t\treturn fmt.Sprintf(\"\\nALTER TABLE %s RENAME TO %s;\\n\", normOldTableName, newTableName), nil\n\tcase model.MysqlDBType:\n\t\treturn replaceVars(`\n\t\t\tSET @stmt = (SELECT IF(\n\t\t\t\t(\n\t\t\t\tSELECT COUNT(table_name) FROM INFORMATION_SCHEMA.TABLES\n\t\t\t\tWHERE table_name = '[[table_name]]'\n\t\t\t\tAND table_schema = '[[schema]]'\n\t\t\t\t) > 0,\n\t\t\t\t'SELECT 1;',\n\t\t\t\t'RENAME TABLE [[norm_old_table_name]] TO [[new_table_name]];'\n\t\t\t));\n\t\t\tPREPARE renameTableIfNeeded FROM @stmt;\n\t\t\tEXECUTE renameTableIfNeeded;\n\t\t\tDEALLOCATE PREPARE renameTableIfNeeded;\n\t\t`, vars), nil\n\tcase model.PostgresDBType:\n\t\treturn replaceVars(`\n\t\t\tdo $$\n\t\t\tbegin\n\t\t\t\tif (SELECT COUNT(table_name) FROM INFORMATION_SCHEMA.TABLES\n\t\t\t\t\t\t\tWHERE table_name = '[[new_table_name]]'\n\t\t\t\t\t\t\tAND table_schema = '[[schema]]'\n\t\t\t\t) = 0 then\n\t\t\t\t\tALTER TABLE [[norm_old_table_name]] RENAME TO [[new_table_name]];\n\t\t\t\tend if;\n\t\t\tend$$;\n\t\t`, vars), nil\n\tdefault:\n\t\treturn \"\", ErrUnsupportedDatabaseType\n\t}\n}\n\nfunc (s *SQLStore) genRenameColumnIfNeeded(tableName, oldColumnName, newColumnName, dataType string) (string, error) {\n\ttableName = addPrefixIfNeeded(tableName, s.tablePrefix)\n\tnormTableName := s.normalizeTablename(tableName)\n\n\tvars := map[string]string{\n\t\t\"schema\":          s.schemaName,\n\t\t\"table_name\":      tableName,\n\t\t\"norm_table_name\": normTableName,\n\t\t\"old_column_name\": oldColumnName,\n\t\t\"new_column_name\": newColumnName,\n\t\t\"data_type\":       dataType,\n\t}\n\n\tswitch s.dbType {\n\tcase model.SqliteDBType:\n\t\t// No support for idempotent column renaming in Sqlite.\n\t\treturn fmt.Sprintf(\"\\nALTER TABLE %s RENAME COLUMN %s TO %s;\\n\", normTableName, oldColumnName, newColumnName), nil\n\tcase model.MysqlDBType:\n\t\treturn replaceVars(`\n\t\t\tSET @stmt = (SELECT IF(\n\t\t\t\t(\n\t\t\t\tSELECT COUNT(column_name) FROM INFORMATION_SCHEMA.COLUMNS\n\t\t\t\tWHERE table_name = '[[table_name]]'\n\t\t\t\tAND table_schema = '[[schema]]'\n\t\t\t\tAND column_name = '[[new_column_name]]'\n\t\t\t\t) > 0,\n\t\t\t\t'SELECT 1;',\n\t\t\t\t'ALTER TABLE [[norm_table_name]] CHANGE [[old_column_name]] [[new_column_name]] [[data_type]];'\n\t\t\t));\n\t\t\tPREPARE renameColumnIfNeeded FROM @stmt;\n\t\t\tEXECUTE renameColumnIfNeeded;\n\t\t\tDEALLOCATE PREPARE renameColumnIfNeeded;\n\t\t`, vars), nil\n\tcase model.PostgresDBType:\n\t\treturn replaceVars(`\n\t\t\tdo $$\n\t\t\tbegin\n\t\t\t\tif (SELECT COUNT(table_name) FROM INFORMATION_SCHEMA.COLUMNS\n\t\t\t\t\t\t\tWHERE table_name = '[[table_name]]'\n\t\t\t\t\t\t\tAND table_schema = '[[schema]]'\n\t\t\t\t\t\t\tAND column_name = '[[new_column_name]]'\n\t\t\t\t) = 0 then\n\t\t\t\t\tALTER TABLE [[norm_table_name]] RENAME COLUMN [[old_column_name]] TO [[new_column_name]];\n\t\t\t\tend if;\n\t\t\tend$$;\n\t\t`, vars), nil\n\tdefault:\n\t\treturn \"\", ErrUnsupportedDatabaseType\n\t}\n}\n\nfunc (s *SQLStore) doesTableExist(tableName string) (bool, error) {\n\ttableName = addPrefixIfNeeded(tableName, s.tablePrefix)\n\tvar query sq.SelectBuilder\n\n\tswitch s.dbType {\n\tcase model.MysqlDBType, model.PostgresDBType:\n\t\tquery = s.getQueryBuilder(s.db).\n\t\t\tSelect(\"table_name\").\n\t\t\tFrom(\"INFORMATION_SCHEMA.TABLES\").\n\t\t\tWhere(sq.Eq{\n\t\t\t\t\"table_name\":   tableName,\n\t\t\t\t\"table_schema\": s.schemaName,\n\t\t\t})\n\tcase model.SqliteDBType:\n\t\tquery = s.getQueryBuilder(s.db).\n\t\t\tSelect(\"name\").\n\t\t\tFrom(\"sqlite_master\").\n\t\t\tWhere(sq.Eq{\n\t\t\t\t\"name\": tableName,\n\t\t\t\t\"type\": \"table\",\n\t\t\t})\n\tdefault:\n\t\treturn false, ErrUnsupportedDatabaseType\n\t}\n\n\trows, err := query.Query()\n\tif err != nil {\n\t\ts.logger.Error(`doesTableExist ERROR`, mlog.Err(err))\n\t\treturn false, err\n\t}\n\tdefer s.CloseRows(rows)\n\n\texists := rows.Next()\n\tsql, _, _ := query.ToSql()\n\n\ts.logger.Trace(\"doesTableExist\",\n\t\tmlog.String(\"table\", tableName),\n\t\tmlog.Bool(\"exists\", exists),\n\t\tmlog.String(\"sql\", sql),\n\t)\n\treturn exists, nil\n}\n\nfunc (s *SQLStore) doesColumnExist(tableName, columnName string) (bool, error) {\n\ttableName = addPrefixIfNeeded(tableName, s.tablePrefix)\n\tvar query sq.SelectBuilder\n\n\tswitch s.dbType {\n\tcase model.MysqlDBType, model.PostgresDBType:\n\t\tquery = s.getQueryBuilder(s.db).\n\t\t\tSelect(\"table_name\").\n\t\t\tFrom(\"INFORMATION_SCHEMA.COLUMNS\").\n\t\t\tWhere(sq.Eq{\n\t\t\t\t\"table_name\":   tableName,\n\t\t\t\t\"table_schema\": s.schemaName,\n\t\t\t\t\"column_name\":  columnName,\n\t\t\t})\n\tcase model.SqliteDBType:\n\t\tquery = s.getQueryBuilder(s.db).\n\t\t\tSelect(\"name\").\n\t\t\tFrom(fmt.Sprintf(\"pragma_table_info('%s')\", tableName)).\n\t\t\tWhere(sq.Eq{\n\t\t\t\t\"name\": columnName,\n\t\t\t})\n\tdefault:\n\t\treturn false, ErrUnsupportedDatabaseType\n\t}\n\n\trows, err := query.Query()\n\tif err != nil {\n\t\ts.logger.Error(`doesColumnExist ERROR`, mlog.Err(err))\n\t\treturn false, err\n\t}\n\tdefer s.CloseRows(rows)\n\n\texists := rows.Next()\n\tsql, _, _ := query.ToSql()\n\n\ts.logger.Trace(\"doesColumnExist\",\n\t\tmlog.String(\"table\", tableName),\n\t\tmlog.String(\"column\", columnName),\n\t\tmlog.Bool(\"exists\", exists),\n\t\tmlog.String(\"sql\", sql),\n\t)\n\treturn exists, nil\n}\n\nfunc (s *SQLStore) genAddConstraintIfNeeded(tableName, constraintName, constraintType, constraintDefinition string) (string, error) {\n\ttableName = addPrefixIfNeeded(tableName, s.tablePrefix)\n\tnormTableName := s.normalizeTablename(tableName)\n\n\tvar query string\n\n\tvars := map[string]string{\n\t\t\"schema\":                s.schemaName,\n\t\t\"constraint_name\":       constraintName,\n\t\t\"constraint_type\":       constraintType,\n\t\t\"table_name\":            tableName,\n\t\t\"constraint_definition\": constraintDefinition,\n\t\t\"norm_table_name\":       normTableName,\n\t}\n\n\tswitch s.dbType {\n\tcase model.SqliteDBType:\n\t\t// SQLite doesn't have a generic way to add constraint. For example, you can only create indexes on existing tables.\n\t\t// For other constraints, you need to re-build the table. So skipping here.\n\t\t// Include SQLite specific migration in original migration file.\n\t\tquery = fmt.Sprintf(\"\\n-- Sqlite3 cannot drop constraints; drop constraint '%s' in table '%s' skipped\\n\", constraintName, tableName)\n\tcase model.MysqlDBType:\n\t\tquery = replaceVars(`\n\t\t\tSET @stmt = (SELECT IF(\n\t\t\t\t(\n\t\t\t\tSELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS\n\t\t\t\tWHERE constraint_schema = '[[schema]]'\n\t\t\t\tAND constraint_name = '[[constraint_name]]'\n\t\t\t\tAND constraint_type = '[[constraint_type]]'\n\t\t\t\tAND table_name = '[[table_name]]'\n\t\t\t\t) > 0,\n\t\t\t\t'SELECT 1;',\n\t\t\t\t'ALTER TABLE [[norm_table_name]] ADD CONSTRAINT [[constraint_name]] [[constraint_definition]];'\n\t\t\t));\n\t\t\tPREPARE addConstraintIfNeeded FROM @stmt;\n\t\t\tEXECUTE addConstraintIfNeeded;\n\t\t\tDEALLOCATE PREPARE addConstraintIfNeeded;\n\t\t`, vars)\n\tcase model.PostgresDBType:\n\t\tquery = replaceVars(`\n\t\tDO\n\t\t$$\n\t\tBEGIN\n\t\tIF NOT EXISTS (\n\t\t\tSELECT * FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS\n\t\t\t\tWHERE constraint_schema = '[[schema]]'\n\t\t\t\tAND constraint_name = '[[constraint_name]]'\n\t\t\t\tAND constraint_type = '[[constraint_type]]'\n\t\t\t\tAND table_name = '[[table_name]]'\n\t\t) THEN\n\t\t\tALTER TABLE [[norm_table_name]] ADD CONSTRAINT [[constraint_name]] [[constraint_definition]];\n\t\tEND IF;\n\t\tEND;\n\t\t$$\n\t\tLANGUAGE plpgsql;\n\t\t`, vars)\n\t}\n\n\treturn query, nil\n}\n\nfunc addPrefixIfNeeded(s, prefix string) string {\n\tif !strings.HasPrefix(s, prefix) {\n\t\treturn prefix + s\n\t}\n\treturn s\n}\n\nfunc (s *SQLStore) normalizeTablename(tableName string) string {\n\tif s.schemaName != \"\" && !strings.HasPrefix(tableName, s.schemaName+\".\") {\n\t\tschemaName := s.schemaName\n\t\tif s.dbType == model.MysqlDBType {\n\t\t\tschemaName = \"`\" + schemaName + \"`\"\n\t\t}\n\t\ttableName = schemaName + \".\" + tableName\n\t}\n\treturn tableName\n}\n\nfunc getIndexName(tableName string, columns string) string {\n\tvar sb strings.Builder\n\n\t_, _ = sb.WriteString(\"idx_\")\n\t_, _ = sb.WriteString(tableName)\n\n\t// allow developers to separate column names with spaces and/or commas\n\tcolumns = strings.ReplaceAll(columns, \",\", \" \")\n\tcols := strings.Split(columns, \" \")\n\n\tfor _, s := range cols {\n\t\tsub := strings.TrimSpace(s)\n\t\tif sub == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\t_, _ = sb.WriteString(\"_\")\n\t\t_, _ = sb.WriteString(s)\n\t}\n\treturn sb.String()\n}\n\n// replaceVars replaces instances of variable placeholders with the\n// values provided via a map.  Variable placeholders are of the form\n// `[[var_name]]`.\nfunc replaceVars(s string, vars map[string]string) string {\n\tfor key, val := range vars {\n\t\tplaceholder := \"[[\" + key + \"]]\"\n\t\tval = strings.ReplaceAll(val, \"'\", \"\\\\'\")\n\t\ts = strings.ReplaceAll(s, placeholder, val)\n\t}\n\treturn s\n}\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000001_init.down.sql",
    "content": "SELECT 1;\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000001_init.up.sql",
    "content": "CREATE TABLE IF NOT EXISTS {{.prefix}}blocks (\n\tid VARCHAR(36),\n\t{{if .postgres}}insert_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),{{end}}\n\t{{if .sqlite}}insert_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')),{{end}}\n\t{{if .mysql}}insert_at DATETIME(6) NOT NULL DEFAULT NOW(6),{{end}}\n\tparent_id VARCHAR(36),\n\t{{if .mysql}}`schema`{{else}}schema{{end}} BIGINT,\n\ttype TEXT,\n\ttitle TEXT,\n\tfields {{if .postgres}}JSON{{else}}TEXT{{end}},\n\tcreate_at BIGINT,\n\tupdate_at BIGINT,\n\tdelete_at BIGINT,\n\tPRIMARY KEY (id, insert_at)\n) {{if .mysql}}DEFAULT CHARACTER SET utf8mb4{{end}};\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000002_system_settings_table.down.sql",
    "content": "SELECT 1;\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000002_system_settings_table.up.sql",
    "content": "CREATE TABLE IF NOT EXISTS {{.prefix}}system_settings (\n\tid VARCHAR(100),\n\tvalue TEXT,\n\tPRIMARY KEY (id)\n) {{if .mysql}}DEFAULT CHARACTER SET utf8mb4{{end}};\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000003_blocks_rootid.down.sql",
    "content": "SELECT 1;\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000003_blocks_rootid.up.sql",
    "content": "{{- /* addColumnIfNeeded tableName  columnName  datatype  constraint */ -}}\n{{ addColumnIfNeeded \"blocks\" \"root_id\" \"varchar(36)\" \"\"}}"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000004_auth_table.down.sql",
    "content": "SELECT 1;\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000004_auth_table.up.sql",
    "content": "CREATE TABLE IF NOT EXISTS {{.prefix}}users (\n\tid VARCHAR(100),\n\tusername VARCHAR(100),\n\temail VARCHAR(255),\n\tpassword VARCHAR(100),\n\tmfa_secret VARCHAR(100),\n\tauth_service VARCHAR(20),\n\tauth_data VARCHAR(255),\n\tprops       {{if .postgres}}JSON{{else}}TEXT{{end}},\n\tcreate_at    BIGINT,\n\tupdate_at    BIGINT,\n\tdelete_at    BIGINT,\n\tPRIMARY KEY (id)\n) {{if .mysql}}DEFAULT CHARACTER SET utf8mb4{{end}};\n\nCREATE TABLE IF NOT EXISTS {{.prefix}}sessions (\n\tid VARCHAR(100),\n\ttoken VARCHAR(100),\n\tuser_id VARCHAR(100),\n\tprops       {{if .postgres}}JSON{{else}}TEXT{{end}},\n\tcreate_at    BIGINT,\n\tupdate_at    BIGINT,\n\tPRIMARY KEY (id)\n) {{if .mysql}}DEFAULT CHARACTER SET utf8mb4{{end}};\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000005_blocks_modifiedby.down.sql",
    "content": "SELECT 1;\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000005_blocks_modifiedby.up.sql",
    "content": "{{- /* addColumnIfNeeded tableName columnName datatype constraint */ -}}\n{{ addColumnIfNeeded \"blocks\" \"modified_by\" \"varchar(36)\" \"\"}}"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000006_sharing_table.down.sql",
    "content": "SELECT 1;\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000006_sharing_table.up.sql",
    "content": "CREATE TABLE IF NOT EXISTS {{.prefix}}sharing (\n\tid VARCHAR(36),\n\tenabled BOOLEAN,\n\ttoken VARCHAR(100),\n\tmodified_by VARCHAR(36),\n\tupdate_at BIGINT,\n\tPRIMARY KEY (id)\n) {{if .mysql}}DEFAULT CHARACTER SET utf8mb4{{end}};\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000007_workspaces_table.down.sql",
    "content": "SELECT 1;\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000007_workspaces_table.up.sql",
    "content": "CREATE TABLE IF NOT EXISTS {{.prefix}}workspaces (\n\tid VARCHAR(36),\n\tsignup_token VARCHAR(100) NOT NULL,\n\tsettings {{if .postgres}}JSON{{else}}TEXT{{end}},\n\tmodified_by VARCHAR(36),\n\tupdate_at BIGINT,\n\tPRIMARY KEY (id)\n) {{if .mysql}}DEFAULT CHARACTER SET utf8mb4{{end}};\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000008_teams.down.sql",
    "content": "SELECT 1;\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000008_teams.up.sql",
    "content": "{{- /* addColumnIfNeeded tableName columnName datatype constraint */ -}}\n{{ addColumnIfNeeded \"blocks\" \"workspace_id\" \"varchar(36)\" \"\"}}\n\n{{ addColumnIfNeeded \"sharing\" \"workspace_id\" \"varchar(36)\" \"\"}}\n\n{{ addColumnIfNeeded \"sessions\" \"auth_service\" \"varchar(20)\" \"\"}}\n\nUPDATE {{.prefix}}blocks SET workspace_id = '0' WHERE workspace_id = '' OR workspace_id IS NULL;\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000009_blocks_history.down.sql",
    "content": "SELECT 1;\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000009_blocks_history.up.sql",
    "content": "{{- /* Only perform this migration if the blocks_history table does not already exist */ -}}\n\n{{- /* doesTableExist tableName */ -}}\n{{if doesTableExist \"blocks_history\" }}\n\n    SELECT 1;\n\n{{else}}\n\n{{- /* renameTableIfNeeded oldTableName newTableName */ -}}\n{{ renameTableIfNeeded \"blocks\" \"blocks_history\" }}\n\nCREATE TABLE IF NOT EXISTS {{.prefix}}blocks (\n    id VARCHAR(36),\n    {{if .postgres}}insert_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),{{end}}\n    {{if .sqlite}}insert_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')),{{end}}\n    {{if .mysql}}insert_at DATETIME(6) NOT NULL DEFAULT NOW(6),{{end}}\n    parent_id VARCHAR(36),\n    {{if .mysql}}`schema`{{else}}schema{{end}} BIGINT,\n    type TEXT,\n    title TEXT,\n    fields {{if .postgres}}JSON{{else}}TEXT{{end}},\n    create_at BIGINT,\n    update_at BIGINT,\n    delete_at BIGINT,\n    root_id VARCHAR(36),\n    modified_by VARCHAR(36),\n    workspace_id VARCHAR(36),\n    PRIMARY KEY (workspace_id,id)\n) {{if .mysql}}DEFAULT CHARACTER SET utf8mb4{{end}};\n\n{{if .mysql}}\nINSERT IGNORE INTO {{.prefix}}blocks (SELECT * FROM {{.prefix}}blocks_history ORDER BY insert_at DESC);\n{{end}}\n{{if .postgres}}\nINSERT INTO {{.prefix}}blocks (SELECT * FROM {{.prefix}}blocks_history ORDER BY insert_at DESC) ON CONFLICT DO NOTHING;\n{{end}}\n{{if .sqlite}}\nINSERT OR IGNORE INTO {{.prefix}}blocks SELECT * FROM {{.prefix}}blocks_history ORDER BY insert_at DESC;\n{{end}}\n\n{{end}}\n\nDELETE FROM {{.prefix}}blocks where delete_at > 0;\n\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000010_blocks_created_by.down.sql",
    "content": "SELECT 1;\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000010_blocks_created_by.up.sql",
    "content": "{{- /* addColumnIfNeeded tableName columnName datatype constraint) */ -}}\n{{ addColumnIfNeeded \"blocks\" \"created_by\" \"varchar(36)\" \"\"}}\n{{ addColumnIfNeeded \"blocks_history\" \"created_by\" \"varchar(36)\" \"\"}}\n\nUPDATE {{.prefix}}blocks SET created_by = \n    COALESCE(NULLIF((select modified_by from {{.prefix}}blocks_history where {{.prefix}}blocks_history.id = {{.prefix}}blocks.id ORDER BY {{.prefix}}blocks_history.insert_at ASC limit 1), ''), 'system') \nWHERE created_by IS NULL;\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000011_match_collation.down.sql",
    "content": "SELECT 1;\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000011_match_collation.up.sql",
    "content": "{{- /* All tables have collation fixed via code at startup so this migration is no longer needed. */ -}}\n{{- /* See https://github.com/mattermost/focalboard/pull/4002                                     */ -}}\n\nSELECT 1;\n\n\n\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000012_match_column_collation.down.sql",
    "content": "SELECT 1;\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000012_match_column_collation.up.sql",
    "content": "{{if and .mysql .plugin}}\n    -- this migration applies collation on column level.\n    -- collation of mattermost's Channels table\n    SET @mattermostCollation = (SELECT table_collation from information_schema.tables WHERE table_name = 'Channels' AND table_schema = (SELECT DATABASE()));\n    -- charset of mattermost's CHannels table's Name column\n    SET @mattermostCharset = (SELECT CHARACTER_SET_NAME from information_schema.columns WHERE table_name = 'Channels' AND table_schema = (SELECT DATABASE()) AND COLUMN_NAME = 'Name');\n\n    -- blocks\n    SET @updateCollationQuery = CONCAT('ALTER TABLE {{.prefix}}blocks CONVERT TO CHARACTER SET ', @mattermostCharset, ' COLLATE ', @mattermostCollation);\n    PREPARE stmt FROM @updateCollationQuery;\n    EXECUTE stmt;\n    DEALLOCATE PREPARE stmt;\n\n    -- blocks history\n    SET @updateCollationQuery = CONCAT('ALTER TABLE {{.prefix}}blocks_history CONVERT TO CHARACTER SET ', @mattermostCharset, ' COLLATE ', @mattermostCollation);\n    PREPARE stmt FROM @updateCollationQuery;\n    EXECUTE stmt;\n    DEALLOCATE PREPARE stmt;\n\n    -- sessions\n    SET @updateCollationQuery = CONCAT('ALTER TABLE {{.prefix}}sessions CONVERT TO CHARACTER SET ', @mattermostCharset, ' COLLATE ', @mattermostCollation);\n    PREPARE stmt FROM @updateCollationQuery;\n    EXECUTE stmt;\n    DEALLOCATE PREPARE stmt;\n\n    -- sharing\n    SET @updateCollationQuery = CONCAT('ALTER TABLE {{.prefix}}sharing CONVERT TO CHARACTER SET ', @mattermostCharset, ' COLLATE ', @mattermostCollation);\n    PREPARE stmt FROM @updateCollationQuery;\n    EXECUTE stmt;\n    DEALLOCATE PREPARE stmt;\n\n    -- system settings\n    SET @updateCollationQuery = CONCAT('ALTER TABLE {{.prefix}}system_settings CONVERT TO CHARACTER SET ', @mattermostCharset, ' COLLATE ', @mattermostCollation);\n    PREPARE stmt FROM @updateCollationQuery;\n    EXECUTE stmt;\n    DEALLOCATE PREPARE stmt;\n\n    -- users\n    SET @updateCollationQuery = CONCAT('ALTER TABLE {{.prefix}}users CONVERT TO CHARACTER SET ', @mattermostCharset, ' COLLATE ', @mattermostCollation);\n    PREPARE stmt FROM @updateCollationQuery;\n    EXECUTE stmt;\n    DEALLOCATE PREPARE stmt;\n\n    -- workspaces\n    SET @updateCollationQuery = CONCAT('ALTER TABLE {{.prefix}}workspaces CONVERT TO CHARACTER SET ', @mattermostCharset, ' COLLATE ', @mattermostCollation);\n    PREPARE stmt FROM @updateCollationQuery;\n    EXECUTE stmt;\n    DEALLOCATE PREPARE stmt;\n{{else}}\n    -- We need a query here otherwise the migration will result\n    -- in an empty query when the if condition is false.\n    -- Empty query causes a \"Query was empty\" error.\n    SELECT 1;\n{{end}}\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000013_millisecond_timestamps.down.sql",
    "content": "SELECT 1;\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000013_millisecond_timestamps.up.sql",
    "content": "\nUPDATE {{.prefix}}users SET create_at = create_at*1000, update_at = update_at*1000, delete_at = delete_at*1000\n\tWHERE create_at < 1000000000000;\n\nUPDATE {{.prefix}}blocks SET create_at = create_at*1000, update_at = update_at*1000, delete_at = delete_at*1000\n\tWHERE create_at < 1000000000000;\n\nUPDATE {{.prefix}}blocks_history SET create_at = create_at*1000, update_at = update_at*1000, delete_at = delete_at*1000\n\tWHERE create_at < 1000000000000;\n\nUPDATE {{.prefix}}workspaces SET update_at = update_at*1000\n\tWHERE update_at < 1000000000000;\n\nUPDATE {{.prefix}}sharing SET update_at = update_at*1000\n\tWHERE update_at < 1000000000000;\n\nUPDATE {{.prefix}}sessions SET create_at = create_at*1000, update_at = update_at*1000\n\tWHERE create_at < 1000000000000;\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000014_add_not_null_constraint.down.sql",
    "content": "SELECT 1;\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000014_add_not_null_constraint.up.sql",
    "content": "UPDATE {{.prefix}}blocks SET created_by = 'system' where created_by IS NULL;\nUPDATE {{.prefix}}blocks SET modified_by = 'system' where modified_by IS NULL;\n\n{{if .mysql}}\nALTER TABLE {{.prefix}}blocks MODIFY created_by varchar(36) NOT NULL;\nALTER TABLE {{.prefix}}blocks MODIFY modified_by varchar(36) NOT NULL;\n{{end}}\n\n{{if .postgres}}\nALTER TABLE {{.prefix}}blocks ALTER COLUMN created_by set NOT NULL;\nALTER TABLE {{.prefix}}blocks ALTER COLUMN modified_by set NOT NULL;\n{{end}}\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000015_blocks_history_no_nulls.down.sql",
    "content": "SELECT 1;\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000015_blocks_history_no_nulls.up.sql",
    "content": "{{if .mysql}}\n\nUPDATE {{.prefix}}blocks_history AS bh SET bh.parent_id='' WHERE bh.parent_id IS NULL;\nUPDATE {{.prefix}}blocks_history AS bh SET bh.schema=1 WHERE bh.schema IS NULL;\nUPDATE {{.prefix}}blocks_history AS bh SET bh.type='' WHERE bh.type IS NULL;\nUPDATE {{.prefix}}blocks_history AS bh SET bh.title='' WHERE bh.title IS NULL;\nUPDATE {{.prefix}}blocks_history AS bh SET bh.fields='' WHERE bh.fields IS NULL;\nUPDATE {{.prefix}}blocks_history AS bh SET bh.create_at=0 WHERE bh.create_at IS NULL;\nUPDATE {{.prefix}}blocks_history AS bh SET bh.root_id='' WHERE bh.root_id IS NULL;\nUPDATE {{.prefix}}blocks_history AS bh SET bh.created_by='system' WHERE bh.created_by IS NULL;\n\n{{else}}\n\n/* parent_id */\nUPDATE {{.prefix}}blocks_history AS bh1\n\tSET parent_id = COALESCE(\n\t\t(SELECT bh2.parent_id \n\t\tFROM {{.prefix}}blocks_history AS bh2\n\t\tWHERE bh1.id = bh2.id AND bh2.parent_id IS NOT NULL \n\t\tORDER BY bh2.insert_at ASC limit 1) \n\t, '')\nWHERE parent_id IS NULL;\n\n/* schema */\nUPDATE {{.prefix}}blocks_history AS bh1\n\tSET schema = COALESCE(\n\t\t(SELECT bh2.schema \n\t\tFROM {{.prefix}}blocks_history AS bh2\n\t\tWHERE bh1.id = bh2.id AND bh2.schema IS NOT NULL \n\t\tORDER BY bh2.insert_at ASC limit 1) \n\t, 1)\nWHERE schema IS NULL;\n\n/* type */\nUPDATE {{.prefix}}blocks_history AS bh1\n\tSET type = COALESCE(\n\t\t(SELECT bh2.type \n\t\tFROM {{.prefix}}blocks_history AS bh2\n\t\tWHERE bh1.id = bh2.id AND bh2.type IS NOT NULL \n\t\tORDER BY bh2.insert_at ASC limit 1) \n\t, '')\nWHERE type IS NULL;\n\n/* title */\nUPDATE {{.prefix}}blocks_history AS bh1\n\tSET title = COALESCE(\n\t\t(SELECT bh2.title \n\t\tFROM {{.prefix}}blocks_history AS bh2\n\t\tWHERE bh1.id = bh2.id AND bh2.title IS NOT NULL \n\t\tORDER BY bh2.insert_at ASC limit 1) \n\t, '')\nWHERE title IS NULL;\n\n/* fields */\n{{if .postgres}}\n\tUPDATE {{.prefix}}blocks_history AS bh1\n\t\tSET fields = COALESCE(\n\t\t\t(SELECT bh2.fields \n\t\t\tFROM {{.prefix}}blocks_history AS bh2\n\t\t\tWHERE bh1.id = bh2.id AND bh2.fields IS NOT NULL \n\t\t\tORDER BY bh2.insert_at ASC limit 1) \n\t\t, '{}'::json)\n\tWHERE fields IS NULL;\n{{else}}\n\tUPDATE {{.prefix}}blocks_history AS bh1\n\t\tSET fields = COALESCE(\n\t\t\t(SELECT bh2.fields \n\t\t\tFROM {{.prefix}}blocks_history AS bh2\n\t\t\tWHERE bh1.id = bh2.id AND bh2.fields IS NOT NULL \n\t\t\tORDER BY bh2.insert_at ASC limit 1) \n\t\t, '')\n\tWHERE fields IS NULL;\n{{end}}\n\n/* create_at */\nUPDATE {{.prefix}}blocks_history AS bh1\n\tSET create_at = COALESCE(\n\t\t(SELECT bh2.create_at \n\t\tFROM {{.prefix}}blocks_history AS bh2\n\t\tWHERE bh1.id = bh2.id AND bh2.create_at IS NOT NULL \n\t\tORDER BY bh2.insert_at ASC limit 1) \n\t, bh1.update_at)\nWHERE create_at IS NULL;\n\n/* root_id */\nUPDATE {{.prefix}}blocks_history AS bh1\n\tSET root_id = COALESCE(\n\t\t(SELECT bh2.root_id \n\t\tFROM {{.prefix}}blocks_history AS bh2\n\t\tWHERE bh1.id = bh2.id AND bh2.root_id IS NOT NULL \n\t\tORDER BY bh2.insert_at ASC limit 1) \n\t, '')\nWHERE root_id IS NULL;\n\n/* created_by */\nUPDATE {{.prefix}}blocks_history AS bh1\n\tSET created_by = COALESCE(\n\t\t(SELECT bh2.created_by \n\t\tFROM {{.prefix}}blocks_history AS bh2\n\t\tWHERE bh1.id = bh2.id AND bh2.created_by IS NOT NULL \n\t\tORDER BY bh2.insert_at ASC limit 1) \n\t, 'system')\nWHERE created_by IS NULL;\n\n{{end}}\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000016_subscriptions_table.down.sql",
    "content": "SELECT 1;\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000016_subscriptions_table.up.sql",
    "content": "CREATE TABLE IF NOT EXISTS {{.prefix}}subscriptions (\n\tblock_type VARCHAR(10),\n\tblock_id VARCHAR(36),\n\tworkspace_id VARCHAR(36),\n\tsubscriber_type VARCHAR(10),\n\tsubscriber_id VARCHAR(36),\n\tnotified_at BIGINT,\n\tcreate_at BIGINT,\n\tdelete_at BIGINT,\n\tPRIMARY KEY (block_id, subscriber_id)\n) {{if .mysql}}DEFAULT CHARACTER SET utf8mb4{{end}};\n\nCREATE TABLE IF NOT EXISTS {{.prefix}}notification_hints (\n\tblock_type VARCHAR(10),\n\tblock_id VARCHAR(36),\n\tworkspace_id VARCHAR(36),\n\tmodified_by_id VARCHAR(36),\n\tcreate_at BIGINT,\n\tnotify_at BIGINT,\n\tPRIMARY KEY (block_id)\n) {{if .mysql}}DEFAULT CHARACTER SET utf8mb4{{end}};\n\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000017_add_file_info.down.sql",
    "content": "SELECT 1;\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000017_add_file_info.up.sql",
    "content": "CREATE TABLE IF NOT EXISTS {{.prefix}}file_info (\n    id varchar(26) NOT NULL,\n    create_at BIGINT NOT NULL,\n    delete_at BIGINT,\n    name TEXT NOT NULL,\n    extension VARCHAR(50) NOT NULL,\n    size BIGINT NOT NULL,\n    archived BOOLEAN\n) {{if .mysql}}DEFAULT CHARACTER SET utf8mb4{{end}};\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000018_add_teams_and_boards.down.sql",
    "content": "SELECT 1;\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000018_add_teams_and_boards.up.sql",
    "content": "{{- /* renameTableIfNeeded oldTableName newTableName string */ -}}\n{{ renameTableIfNeeded \"workspaces\" \"teams\" }}\n\n{{- /* renameColumnIfNeeded tableName oldColumnName newColumnName dataType */ -}}\n{{ renameColumnIfNeeded \"blocks\" \"workspace_id\" \"channel_id\" \"varchar(36)\" }}\n{{ renameColumnIfNeeded \"blocks_history\" \"workspace_id\" \"channel_id\" \"varchar(36)\" }}\n\n{{- /* dropColumnIfNeeded tableName columnName */ -}}\n{{ dropColumnIfNeeded \"blocks\" \"workspace_id\" }}\n{{ dropColumnIfNeeded \"blocks_history\" \"workspace_id\" }}\n\n{{- /* addColumnIfNeeded tableName columnName datatype constraint */ -}}\n{{ addColumnIfNeeded \"blocks\" \"board_id\" \"varchar(36)\" \"\"}}\n{{ addColumnIfNeeded \"blocks_history\" \"board_id\" \"varchar(36)\" \"\"}}\n\n{{- /* cleanup incorrect data format in column calculations */ -}}\n{{- /* then move from 'board' type to 'view' type*/ -}}\n{{if .mysql}}\nUPDATE {{.prefix}}blocks SET fields = JSON_SET(fields, '$.columnCalculations', JSON_OBJECT()) WHERE JSON_EXTRACT(fields, '$.columnCalculations') = JSON_ARRAY();\n\nUPDATE {{.prefix}}blocks b\n  JOIN (\n    SELECT id, JSON_EXTRACT(fields, '$.columnCalculations') as board_calculations from {{.prefix}}blocks\n    WHERE JSON_EXTRACT(fields, '$.columnCalculations') <> JSON_OBJECT()\n  ) AS s on s.id = b.root_id\n  SET fields = JSON_SET(fields, '$.columnCalculations', JSON_ARRAY(s.board_calculations))\n  WHERE JSON_EXTRACT(b.fields, '$.viewType') = 'table'\n  AND b.type = 'view';\n{{end}}\n\n{{if .postgres}}\nUPDATE {{.prefix}}blocks SET fields = fields::jsonb - 'columnCalculations' || '{\"columnCalculations\": {}}' WHERE fields->>'columnCalculations' = '[]';\n\nWITH subquery AS (\n  SELECT id, fields->'columnCalculations' as board_calculations from {{.prefix}}blocks\n  WHERE fields ->> 'columnCalculations' <> '{}')\nUPDATE {{.prefix}}blocks b\n    SET fields = b.fields::jsonb|| json_build_object('columnCalculations', s.board_calculations::jsonb)::jsonb\n    FROM subquery AS s\n    WHERE s.id = b.root_id\n    AND b.fields ->> 'viewType' = 'table'\n    AND b.type = 'view';\n{{end}}\n\n{{if .sqlite}}\nUPDATE {{.prefix}}blocks SET fields = replace(fields, '\"columnCalculations\":[]', '\"columnCalculations\":{}');\n\nUPDATE {{.prefix}}blocks AS b\n    SET fields = (\n        SELECT  json_set(a.fields, '$.columnCalculations',json_extract(c.fields,  '$.columnCalculations')) from {{.prefix}}blocks AS a\n        JOIN {{.prefix}}blocks AS c on c.id = a.root_id\n        WHERE a.id = b.id)\n    WHERE json_extract(b.fields,'$.viewType') = 'table'\n    AND b.type = 'view';\n{{end}}\n\n{{- /* TODO: Migrate the columnCalculations at app level and remove it from the boards and boards_history tables */ -}}\n\n\n{{- /* add boards tables */ -}}\nCREATE TABLE IF NOT EXISTS {{.prefix}}boards (\n    id VARCHAR(36) NOT NULL PRIMARY KEY,\n\n    {{if .postgres}}insert_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),{{end}}\n\t{{if .sqlite}}insert_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')),{{end}}\n\t{{if .mysql}}insert_at DATETIME(6) NOT NULL DEFAULT NOW(6),{{end}}\n\n    team_id VARCHAR(36) NOT NULL,\n    channel_id VARCHAR(36),\n    created_by VARCHAR(36),\n    modified_by VARCHAR(36),\n    type VARCHAR(1) NOT NULL,\n    title TEXT NOT NULL,\n    description TEXT,\n    icon VARCHAR(256),\n    show_description BOOLEAN,\n    is_template BOOLEAN,\n    template_version INT DEFAULT 0,\n    {{if .mysql}}\n    properties JSON,\n    card_properties JSON,\n    {{end}}\n    {{if .postgres}}\n    properties JSONB,\n    card_properties JSONB,\n    {{end}}\n    {{if .sqlite}}\n    properties TEXT,\n    card_properties TEXT,\n    {{end}}\n    create_at BIGINT,\n    update_at BIGINT,\n    delete_at BIGINT\n) {{if .mysql}}DEFAULT CHARACTER SET utf8mb4{{end}};\n\n{{- /* createIndexIfNeeded tableName columns */ -}}\n{{ createIndexIfNeeded \"boards\" \"team_id, is_template\" }}\n{{ createIndexIfNeeded \"boards\" \"channel_id\" }}\n\nCREATE TABLE IF NOT EXISTS {{.prefix}}boards_history (\n    id VARCHAR(36) NOT NULL,\n\n    {{if .postgres}}insert_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),{{end}}\n\t{{if .sqlite}}insert_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')),{{end}}\n\t{{if .mysql}}insert_at DATETIME(6) NOT NULL DEFAULT NOW(6),{{end}}\n\n    team_id VARCHAR(36) NOT NULL,\n    channel_id VARCHAR(36),\n    created_by VARCHAR(36),\n    modified_by VARCHAR(36),\n    type VARCHAR(1) NOT NULL,\n    title TEXT NOT NULL,\n    description TEXT,\n    icon VARCHAR(256),\n    show_description BOOLEAN,\n    is_template BOOLEAN,\n    template_version INT DEFAULT 0,\n    {{if .mysql}}\n    properties JSON,\n    card_properties JSON,\n    {{end}}\n    {{if .postgres}}\n    properties JSONB,\n    card_properties JSONB,\n    {{end}}\n    {{if .sqlite}}\n    properties TEXT,\n    card_properties TEXT,\n    {{end}}\n    create_at BIGINT,\n    update_at BIGINT,\n    delete_at BIGINT,\n\n    PRIMARY KEY (id, insert_at)\n) {{if .mysql}}DEFAULT CHARACTER SET utf8mb4{{end}};\n\n\n{{- /* migrate board blocks to boards table */ -}}\n{{if .plugin}}\n  {{if .postgres}}\n  INSERT INTO {{.prefix}}boards (\n      SELECT B.id, B.insert_at, C.TeamId, B.channel_id, B.created_by, B.modified_by, C.type,\n                 COALESCE(B.title, ''),\n                 COALESCE((B.fields->>'description')::text, ''),\n                 B.fields->>'icon',\n                 COALESCE((fields->'showDescription')::text::boolean, false),\n                 COALESCE((fields->'isTemplate')::text::boolean, false),\n                 COALESCE((B.fields->'templateVer')::text::int, 0),\n                 '{}', B.fields->'cardProperties', B.create_at,\n                 B.update_at, B.delete_at {{if doesColumnExist \"boards\" \"minimum_role\"}} ,'' {{end}}\n          FROM {{.prefix}}blocks AS B\n          INNER JOIN channels AS C ON C.Id=B.channel_id\n          WHERE B.type='board'\n  );\n  INSERT INTO {{.prefix}}boards_history (\n      SELECT B.id, B.insert_at, C.TeamId, B.channel_id, B.created_by, B.modified_by, C.type,\n                 COALESCE(B.title, ''),\n                 COALESCE((B.fields->>'description')::text, ''),\n                 B.fields->>'icon',\n                 COALESCE((fields->'showDescription')::text::boolean, false),\n                 COALESCE((fields->'isTemplate')::text::boolean, false),\n                 COALESCE((B.fields->'templateVer')::text::int, 0),\n                 '{}', B.fields->'cardProperties', B.create_at,\n                 B.update_at, B.delete_at {{if doesColumnExist \"boards_history\" \"minimum_role\"}} ,'' {{end}}\n          FROM {{.prefix}}blocks_history AS B\n          INNER JOIN channels AS C ON C.Id=B.channel_id\n          WHERE B.type='board'\n  );\n  {{end}}\n  {{if .mysql}}\n  INSERT INTO {{.prefix}}boards (\n      SELECT B.id, B.insert_at, C.TeamId, B.channel_id, B.created_by, B.modified_by, C.Type,\n                 COALESCE(B.title, ''),\n                 COALESCE(JSON_UNQUOTE(JSON_EXTRACT(B.fields,'$.description')), ''),\n                 JSON_UNQUOTE(JSON_EXTRACT(B.fields,'$.icon')),\n                 COALESCE(JSON_EXTRACT(B.fields, '$.showDescription'), 'false') = 'true',\n                 COALESCE(JSON_EXTRACT(B.fields, '$.isTemplate'), 'false') = 'true',\n                 COALESCE(JSON_EXTRACT(B.fields, '$.templateVer'), 0),\n                 '{}', JSON_EXTRACT(B.fields, '$.cardProperties'), B.create_at,\n                 B.update_at, B.delete_at {{if doesColumnExist \"boards\" \"minimum_role\"}} ,'' {{end}}\n          FROM {{.prefix}}blocks AS B\n          INNER JOIN Channels AS C ON C.Id=B.channel_id\n          WHERE B.type='board'\n  );\n  INSERT INTO {{.prefix}}boards_history (\n      SELECT B.id, B.insert_at, C.TeamId, B.channel_id, B.created_by, B.modified_by, C.Type,\n                 COALESCE(B.title, ''),\n                 COALESCE(JSON_UNQUOTE(JSON_EXTRACT(B.fields,'$.description')), ''),\n                 JSON_UNQUOTE(JSON_EXTRACT(B.fields,'$.icon')),\n                 COALESCE(JSON_EXTRACT(B.fields, '$.showDescription'), 'false') = 'true',\n                 COALESCE(JSON_EXTRACT(B.fields, '$.isTemplate'), 'false') = 'true',\n                 COALESCE(JSON_EXTRACT(B.fields, '$.templateVer'), 0),\n                 '{}', JSON_EXTRACT(B.fields, '$.cardProperties'), B.create_at,\n                 B.update_at, B.delete_at {{if doesColumnExist \"boards_history\" \"minimum_role\"}} ,'' {{end}}\n          FROM {{.prefix}}blocks_history AS B\n          INNER JOIN Channels AS C ON C.Id=B.channel_id\n          WHERE B.type='board'\n  );\n  {{end}}\n{{else}}\n  {{if .postgres}}\n  INSERT INTO {{.prefix}}boards (\n      SELECT id, insert_at, '0', channel_id, created_by, modified_by, 'O',\n                 COALESCE(B.title, ''),\n                 COALESCE((fields->>'description')::text, ''),\n                 B.fields->>'icon',\n                 COALESCE((fields->'showDescription')::text::boolean, false),\n                 COALESCE((fields->'isTemplate')::text::boolean, false),\n                 COALESCE((B.fields->'templateVer')::text::int, 0),\n                 '{}', fields->'cardProperties', create_at,\n                 update_at, delete_at {{if doesColumnExist \"boards\" \"minimum_role\"}} ,'editor' {{end}}\n          FROM {{.prefix}}blocks AS B\n          WHERE type='board'\n  );\n  INSERT INTO {{.prefix}}boards_history (\n      SELECT id, insert_at, '0', channel_id, created_by, modified_by, 'O',\n                 COALESCE(B.title, ''),\n                 COALESCE((fields->>'description')::text, ''),\n                 B.fields->>'icon',\n                 COALESCE((fields->'showDescription')::text::boolean, false),\n                 COALESCE((fields->'isTemplate')::text::boolean, false),\n                 COALESCE((B.fields->'templateVer')::text::int, 0),\n                 '{}', fields->'cardProperties', create_at,\n                 update_at, delete_at {{if doesColumnExist \"boards_history\" \"minimum_role\"}} ,'editor' {{end}}\n          FROM {{.prefix}}blocks_history AS B\n          WHERE type='board'\n  );\n  {{end}}\n\n  {{if .mysql}}\n  INSERT INTO {{.prefix}}boards (\n      SELECT id, insert_at, '0', channel_id, created_by, modified_by, 'O',\n                 COALESCE(B.title, ''),\n                 COALESCE(JSON_UNQUOTE(JSON_EXTRACT(B.fields,'$.description')), ''),\n                 JSON_UNQUOTE(JSON_EXTRACT(fields,'$.icon')),\n                 COALESCE(JSON_EXTRACT(B.fields, '$.showDescription'), 'false') = 'true',\n                 COALESCE(JSON_EXTRACT(B.fields, '$.isTemplate'), 'false') = 'true',\n                 COALESCE(JSON_EXTRACT(B.fields, '$.templateVer'), 0),\n                 '{}', JSON_EXTRACT(fields, '$.cardProperties'), create_at,\n                 update_at, delete_at {{if doesColumnExist \"boards\" \"minimum_role\"}} ,'editor' {{end}}\n          FROM {{.prefix}}blocks AS B\n          WHERE type='board'\n  );\n  INSERT INTO {{.prefix}}boards_history (\n      SELECT id, insert_at, '0', channel_id, created_by, modified_by, 'O',\n                 COALESCE(B.title, ''),\n                 COALESCE(JSON_UNQUOTE(JSON_EXTRACT(B.fields,'$.description')), ''),\n                 JSON_UNQUOTE(JSON_EXTRACT(fields,'$.icon')),\n                 COALESCE(JSON_EXTRACT(B.fields, '$.showDescription'), 'false') = 'true',\n                 COALESCE(JSON_EXTRACT(B.fields, '$.isTemplate'), 'false') = 'true',\n                 COALESCE(JSON_EXTRACT(B.fields, '$.templateVer'), 0),\n                 '{}', JSON_EXTRACT(fields, '$.cardProperties'), create_at,\n                 update_at, delete_at {{if doesColumnExist \"boards_history\" \"minimum_role\"}} ,'editor' {{end}}\n          FROM {{.prefix}}blocks_history AS B\n          WHERE type='board'\n  );\n  {{end}}\n  \n  {{if .sqlite}}\n  INSERT INTO {{.prefix}}boards\n      SELECT id, insert_at, '0', channel_id, created_by, modified_by, 'O',\n                 COALESCE(title, ''),\n                 COALESCE(json_extract(fields, '$.description'), ''),\n                 json_extract(fields, '$.icon'), json_extract(fields, '$.showDescription'), json_extract(fields, '$.isTemplate'),\n                 COALESCE(json_extract(fields, '$.templateVer'), 0),\n                 '{}', json_extract(fields, '$.cardProperties'), create_at,\n                 update_at, delete_at {{if doesColumnExist \"boards\" \"minimum_role\"}} ,'editor' {{end}}\n          FROM {{.prefix}}blocks\n          WHERE type='board'\n  ;\n  INSERT INTO {{.prefix}}boards_history\n      SELECT id, insert_at, '0', channel_id, created_by, modified_by, 'O',\n                 COALESCE(title, ''),\n                 COALESCE(json_extract(fields, '$.description'), ''),\n                 json_extract(fields, '$.icon'), json_extract(fields, '$.showDescription'), json_extract(fields, '$.isTemplate'),\n                 COALESCE(json_extract(fields, '$.templateVer'), 0),\n                 '{}', json_extract(fields, '$.cardProperties'), create_at,\n                 update_at, delete_at {{if doesColumnExist \"boards_history\" \"minimum_role\"}} ,'editor' {{end}}\n          FROM {{.prefix}}blocks_history\n          WHERE type='board'\n  ;\n  {{end}}\n{{end}}\n\n\n{{- /* Update block references to boards*/ -}}\nUPDATE {{.prefix}}blocks SET board_id=root_id WHERE board_id IS NULL OR board_id='';\nUPDATE {{.prefix}}blocks_history SET board_id=root_id WHERE board_id IS NULL OR board_id='';\n\n{{- /* Remove boards, including templates */ -}}\nDELETE FROM {{.prefix}}blocks WHERE type = 'board';\nDELETE FROM {{.prefix}}blocks_history WHERE type = 'board';\n\n{{- /* add board_members (only if boards_members doesn't already exist) */ -}}\n{{if not (doesTableExist \"board_members\") }}\nCREATE TABLE IF NOT EXISTS {{.prefix}}board_members (\n    board_id VARCHAR(36) NOT NULL,\n    user_id VARCHAR(36) NOT NULL,\n    roles VARCHAR(64),\n    scheme_admin BOOLEAN,\n    scheme_editor BOOLEAN,\n    scheme_commenter BOOLEAN,\n    scheme_viewer BOOLEAN,\n    PRIMARY KEY (board_id, user_id)\n) {{if .mysql}}DEFAULT CHARACTER SET utf8mb4{{end}};\n\n{{- /* if we're in plugin, migrate channel memberships to the board */ -}}\n{{if .plugin}}\nINSERT INTO {{.prefix}}board_members (\n    SELECT B.Id, CM.UserId, CM.Roles, TRUE, TRUE, FALSE, FALSE\n    FROM {{.prefix}}boards AS B\n    INNER JOIN ChannelMembers as CM ON CM.ChannelId=B.channel_id\n    WHERE CM.SchemeAdmin=True OR (CM.UserId=B.created_by)\n);\n{{end}}\n\n{{- /* if we're in personal server or desktop, create memberships for everyone */ -}}\n{{if and (not .plugin) (not .singleUser)}}\n{{- /* for personal server, create a membership per user and board */ -}}\nINSERT INTO {{.prefix}}board_members\n     SELECT B.id, U.id, '', B.created_by=U.id, TRUE, FALSE, FALSE\n       FROM {{.prefix}}boards AS B, {{.prefix}}users AS U;\n{{end}}\n\n{{if and (not .plugin) .singleUser}}\n{{- /* for personal desktop, as we don't have users, create a membership */ -}}\n{{- /* per board with a fixed user id */ -}}\nINSERT INTO {{.prefix}}board_members\n     SELECT B.id, 'single-user', '', TRUE, TRUE, FALSE, FALSE\n       FROM {{.prefix}}boards AS B;\n{{end}}\n{{end}}\n\n{{- /* createIndexIfNeeded tableName columns */ -}}\n{{ createIndexIfNeeded \"board_members\" \"user_id\" }}\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000019_populate_categories.down.sql",
    "content": "SELECT 1;\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000019_populate_categories.up.sql",
    "content": "CREATE TABLE IF NOT EXISTS {{.prefix}}categories (\n    id varchar(36) NOT NULL,\n    name varchar(100) NOT NULL,\n    user_id varchar(36) NOT NULL,\n    team_id varchar(36) NOT NULL,\n    channel_id varchar(36),\n    create_at BIGINT,\n    update_at BIGINT,\n    delete_at BIGINT,\n    PRIMARY KEY (id)\n    ) {{if .mysql}}DEFAULT CHARACTER SET utf8mb4{{end}};\n\n{{- /* createIndexIfNeeded tableName columns */ -}}\n{{ createIndexIfNeeded \"categories\" \"user_id, team_id\" }}\n\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000020_populate_category_blocks.down.sql",
    "content": "SELECT 1;\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000020_populate_category_blocks.up.sql",
    "content": "CREATE TABLE IF NOT EXISTS {{.prefix}}category_boards (\n    id varchar(36) NOT NULL,\n    user_id varchar(36) NOT NULL,\n    category_id varchar(36) NOT NULL,\n    board_id VARCHAR(36) NOT NULL,\n    create_at BIGINT,\n    update_at BIGINT,\n    delete_at BIGINT,\n    PRIMARY KEY (id)\n) {{if .mysql}}DEFAULT CHARACTER SET utf8mb4{{end}};\n\n{{- /* createIndexIfNeeded tableName columns */ -}}\n{{ createIndexIfNeeded \"category_boards\" \"category_id\" }}\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000021_create_boards_members_history.down.sql",
    "content": "SELECT 1;\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000021_create_boards_members_history.up.sql",
    "content": "{{- /* Only perform this migration if the board_members_history table does not already exist */ -}}\n{{if doesTableExist \"board_members_history\" }}\n\nSELECT 1;\n\n{{else}}\n\nCREATE TABLE IF NOT EXISTS {{.prefix}}board_members_history (\n    board_id VARCHAR(36) NOT NULL,\n    user_id VARCHAR(36) NOT NULL,\n    action VARCHAR(10),\n    {{if .postgres}}insert_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),{{end}}\n\t{{if .sqlite}}insert_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')),{{end}}\n\t{{if .mysql}}insert_at DATETIME(6) NOT NULL DEFAULT NOW(6),{{end}}\n    PRIMARY KEY (board_id, user_id, insert_at)\n) {{if .mysql}}DEFAULT CHARACTER SET utf8mb4{{end}};\n\nINSERT INTO {{.prefix}}board_members_history (board_id, user_id, action) SELECT board_id, user_id, 'created' from {{.prefix}}board_members;\n\n{{end}}\n\n{{- /* createIndexIfNeeded tableName columns */ -}}\n{{ createIndexIfNeeded \"board_members_history\" \"user_id\" }}\n{{ createIndexIfNeeded \"board_members_history\" \"board_id, user_id\" }}\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000022_create_default_board_role.down.sql",
    "content": "SELECT 1;\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000022_create_default_board_role.up.sql",
    "content": "{{- /* addColumnIfNeeded tableName columnName datatype constraint */ -}}\n{{ addColumnIfNeeded \"boards\" \"minimum_role\" \"varchar(36)\" \"NOT NULL DEFAULT ''\"}}\n{{ addColumnIfNeeded \"boards_history\" \"minimum_role\" \"varchar(36)\" \"NOT NULL DEFAULT ''\"}}\n\nUPDATE {{.prefix}}boards SET minimum_role = 'editor' WHERE minimum_role IS NULL OR minimum_role='';\nUPDATE {{.prefix}}boards_history SET minimum_role = 'editor' WHERE minimum_role IS NULL OR minimum_role='';\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000023_persist_category_collapsed_state.down.sql",
    "content": "SELECT 1;\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000023_persist_category_collapsed_state.up.sql",
    "content": "{{- /* addColumnIfNeeded tableName columnName datatype constraint */ -}}\n{{ addColumnIfNeeded \"categories\" \"collapsed\" \"boolean\" \"default false\"}}\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000024_mark_existsing_categories_collapsed.down.sql",
    "content": "SELECT 1;\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000024_mark_existsing_categories_collapsed.up.sql",
    "content": "UPDATE {{.prefix}}categories SET collapsed = true;\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000025_indexes_update.down.sql",
    "content": "SELECT 1;\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000025_indexes_update.up.sql",
    "content": "{{- /* delete old blocks PK and add id as the new one */ -}}\n{{if .mysql}}\nALTER TABLE {{.prefix}}blocks DROP PRIMARY KEY;\nALTER TABLE {{.prefix}}blocks ADD PRIMARY KEY (id);\n{{end}}\n\n{{if .postgres}}\nALTER TABLE {{.prefix}}blocks DROP CONSTRAINT {{.prefix}}blocks_pkey1;\nALTER TABLE {{.prefix}}blocks ADD PRIMARY KEY (id);\n{{end}}\n\n{{- /* there is no way for SQLite to update the PK or add a unique constraint */ -}}\n{{if .sqlite}}\nALTER TABLE {{.prefix}}blocks RENAME TO {{.prefix}}blocks_tmp;\n\nCREATE TABLE IF NOT EXISTS {{.prefix}}blocks (\n        id VARCHAR(36),\n        insert_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')),\n        parent_id VARCHAR(36),\n        schema BIGINT,\n        type TEXT,\n        title TEXT,\n        fields TEXT,\n        create_at BIGINT,\n        update_at BIGINT,\n        delete_at BIGINT,\n        root_id VARCHAR(36),\n        modified_by VARCHAR(36),\n        channel_id VARCHAR(36),\n        created_by VARCHAR(36),\n        board_id VARCHAR(36),\n        PRIMARY KEY (id)\n);\n\nINSERT INTO {{.prefix}}blocks SELECT * FROM {{.prefix}}blocks_tmp;\n\nDROP TABLE {{.prefix}}blocks_tmp;\n{{end}}\n\n{{- /* most block searches use board_id or a combination of board and parent ids */ -}}\n{{ createIndexIfNeeded \"blocks\" \"board_id, parent_id\" }}\n\n{{- /* get subscriptions is used once per board page load */ -}}\n{{ createIndexIfNeeded \"subscriptions\" \"subscriber_id\" }}"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000026_create_preferences_table.down.sql",
    "content": "SELECT 1;\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000026_create_preferences_table.up.sql",
    "content": "CREATE TABLE IF NOT EXISTS {{.prefix}}preferences\n(\n    userid   VARCHAR(36) NOT NULL,\n    category VARCHAR(32) NOT NULL,\n    name     VARCHAR(32) NOT NULL,\n    value    TEXT        NULL,\n    PRIMARY KEY (userid, category, name)\n) {{if .mysql}}DEFAULT CHARACTER SET utf8mb4{{end}};\n\n{{- /* createIndexIfNeeded tableName columns */ -}}\n{{ createIndexIfNeeded \"preferences\" \"category\" }}\n{{ createIndexIfNeeded \"preferences\" \"name\" }}\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000027_migrate_user_props_to_preferences.down.sql",
    "content": "SELECT 1;\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000027_migrate_user_props_to_preferences.up.sql",
    "content": "{{if .plugin}}\n    {{- /* For plugin mode, we need to write into Mattermost's `Preferences` table, hence, no use of `prefix`. */ -}}\n\n    {{if .postgres}}\n        INSERT INTO Preferences (UserId, Category, Name, Value) SELECT Id, 'focalboard', 'welcomePageViewed', replace((Props->'focalboard_welcomePageViewed')::varchar, '\"', '') FROM Users WHERE Props->'focalboard_welcomePageViewed' IS NOT NULL ON CONFLICT DO NOTHING;\n        INSERT INTO Preferences (UserId, Category, Name, Value) SELECT Id, 'focalboard', 'hiddenBoardIDs', replace(replace(replace((Props->'hiddenBoardIDs')::varchar, '\"[', '['), ']\"', ']'), '\\\"', '\"') FROM Users WHERE Props->'hiddenBoardIDs' IS NOT NULL ON CONFLICT DO NOTHING;\n        INSERT INTO Preferences (UserId, Category, Name, Value) SELECT Id, 'focalboard', 'tourCategory', replace((Props->'focalboard_tourCategory')::varchar, '\"', '') FROM Users WHERE Props->'focalboard_tourCategory' IS NOT NULL ON CONFLICT DO NOTHING;\n        INSERT INTO Preferences (UserId, Category, Name, Value) SELECT Id, 'focalboard', 'onboardingTourStep', replace((Props->'focalboard_onboardingTourStep')::varchar, '\"', '') FROM Users WHERE Props->'focalboard_onboardingTourStep' IS NOT NULL ON CONFLICT DO NOTHING;\n        INSERT INTO Preferences (UserId, Category, Name, Value) SELECT Id, 'focalboard', 'onboardingTourStarted', replace((Props->'focalboard_onboardingTourStarted')::varchar, '\"', '') FROM Users WHERE Props->'focalboard_onboardingTourStarted' IS NOT NULL ON CONFLICT DO NOTHING;\n        INSERT INTO Preferences (UserId, Category, Name, Value) SELECT Id, 'focalboard', 'version72MessageCanceled', replace((Props->'focalboard_version72MessageCanceled')::varchar, '\"', '') FROM Users WHERE Props->'focalboard_version72MessageCanceled' IS NOT NULL ON CONFLICT DO NOTHING;\n        INSERT INTO Preferences (UserId, Category, Name, Value) SELECT Id, 'focalboard', 'lastWelcomeVersion', replace((Props->'focalboard_lastWelcomeVersion')::varchar, '\"', '') FROM Users WHERE Props->'focalboard_lastWelcomeVersion' IS NOT NULL ON CONFLICT DO NOTHING;\n\n        UPDATE Users SET props = (props - 'focalboard_welcomePageViewed' - 'hiddenBoardIDs' - 'focalboard_tourCategory' - 'focalboard_onboardingTourStep' - 'focalboard_onboardingTourStarted' - 'focalboard_version72MessageCanceled' - 'focalboard_lastWelcomeVersion') WHERE jsonb_typeof(props) = 'object';\n    {{end}}\n\n    {{if .mysql}}\n        INSERT INTO Preferences (UserId, Category, Name, Value) SELECT Id, 'focalboard', 'welcomePageViewed', replace(JSON_EXTRACT(Props, '$.\"focalboard_welcomePageViewed\"'), '\"', '') FROM Users WHERE JSON_EXTRACT(Props, '$.focalboard_welcomePageViewed') IS NOT NULL ON DUPLICATE KEY UPDATE value = value;\n        INSERT INTO Preferences (UserId, Category, Name, Value) SELECT Id, 'focalboard', 'hiddenBoardIDs', replace(replace(replace(JSON_EXTRACT(Props, '$.\"hiddenBoardIDs\"'), '\"[', '['), ']\"', ']'), '\\\\\"', '\"') FROM Users WHERE JSON_EXTRACT(Props, '$.hiddenBoardIDs') IS NOT NULL ON DUPLICATE KEY UPDATE value = value;\n        INSERT INTO Preferences (UserId, Category, Name, Value) SELECT Id, 'focalboard', 'tourCategory', replace(JSON_EXTRACT(Props, '$.\"focalboard_tourCategory\"'), '\"', '') FROM Users WHERE JSON_EXTRACT(Props, '$.focalboard_tourCategory') IS NOT NULL ON DUPLICATE KEY UPDATE value = value;\n        INSERT INTO Preferences (UserId, Category, Name, Value) SELECT Id, 'focalboard', 'onboardingTourStep', replace(JSON_EXTRACT(Props, '$.\"focalboard_onboardingTourStep\"'), '\"', '') FROM Users WHERE JSON_EXTRACT(Props, '$.focalboard_onboardingTourStep') IS NOT NULL ON DUPLICATE KEY UPDATE value = value;\n        INSERT INTO Preferences (UserId, Category, Name, Value) SELECT Id, 'focalboard', 'onboardingTourStarted', replace(JSON_EXTRACT(Props, '$.\"focalboard_onboardingTourStarted\"'), '\"', '') FROM Users WHERE JSON_EXTRACT(Props, '$.focalboard_onboardingTourStarted') IS NOT NULL ON DUPLICATE KEY UPDATE value = value;\n        INSERT INTO Preferences (UserId, Category, Name, Value) SELECT Id, 'focalboard', 'version72MessageCanceled', replace(JSON_EXTRACT(Props, '$.\"focalboard_version72MessageCanceled\"'), '\"', '') FROM Users WHERE JSON_EXTRACT(Props, '$.focalboard_version72MessageCanceled') IS NOT NULL ON DUPLICATE KEY UPDATE value = value;\n        INSERT INTO Preferences (UserId, Category, Name, Value) SELECT Id, 'focalboard', 'lastWelcomeVersion', replace(JSON_EXTRACT(Props, '$.\"focalboard_lastWelcomeVersion\"'), '\"', '') FROM Users WHERE JSON_EXTRACT(Props, '$.focalboard_lastWelcomeVersion') IS NOT NULL ON DUPLICATE KEY UPDATE value = value;\n\n        UPDATE Users SET Props =  JSON_REMOVE(Props, '$.\"focalboard_welcomePageViewed\"', '$.\"hiddenBoardIDs\"', '$.\"focalboard_tourCategory\"', '$.\"focalboard_onboardingTourStep\"', '$.\"focalboard_onboardingTourStarted\"', '$.\"focalboard_version72MessageCanceled\"', '$.\"focalboard_lastWelcomeVersion\"');\n    {{end}}\n\n    {{if .sqlite}}\n        INSERT OR IGNORE INTO Preferences (UserId, Category, Name, Value) SELECT Id, 'focalboard', 'welcomePageViewed', replace(JSON_EXTRACT(Props, '$.\"focalboard_welcomePageViewed\"'), '\"', '') FROM Users WHERE JSON_EXTRACT(Props, '$.focalboard_welcomePageViewed') IS NOT NULL;\n        INSERT OR IGNORE INTO Preferences (UserId, Category, Name, Value) SELECT Id, 'focalboard', 'hiddenBoardIDs', replace(replace(replace(JSON_EXTRACT(Props, '$.\"hiddenBoardIDs'), '\"[', '['), ']\"', ']'), '\\\\\"', '\"') FROM Users WHERE JSON_EXTRACT(Props, '$.hiddenBoardIDs') IS NOT NULL;\n        INSERT OR IGNORE INTO Preferences (UserId, Category, Name, Value) SELECT Id, 'focalboard', 'tourCategory', replace(JSON_EXTRACT(Props, '$.\"focalboard_tourCategory\"'), '\"', '') FROM Users WHERE JSON_EXTRACT(Props, '$.focalboard_tourCategory') IS NOT NULL;\n        INSERT OR IGNORE INTO Preferences (UserId, Category, Name, Value) SELECT Id, 'focalboard', 'onboardingTourStep', replace(JSON_EXTRACT(Props, '$.\"focalboard_onboardingTourStep\"'), '\"', '') FROM Users WHERE JSON_EXTRACT(Props, '$.focalboard_onboardingTourStep') IS NOT NULL;\n        INSERT OR IGNORE INTO Preferences (UserId, Category, Name, Value) SELECT Id, 'focalboard', 'onboardingTourStarted', replace(JSON_EXTRACT(Props, '$.\"focalboard_onboardingTourStarted\"'), '\"', '') FROM Users WHERE JSON_EXTRACT(Props, '$.focalboard_onboardingTourStarted') IS NOT NULL;\n        INSERT OR IGNORE INTO Preferences (UserId, Category, Name, Value) SELECT Id, 'focalboard', 'version72MessageCanceled', replace(JSON_EXTRACT(Props, '$.\"focalboard_version72MessageCanceled\"'), '\"', '') FROM Users WHERE JSON_EXTRACT(Props, '$.focalboard_version72MessageCanceled') IS NOT NULL;\n        INSERT OR IGNORE INTO Preferences (UserId, Category, Name, Value) SELECT Id, 'focalboard', 'lastWelcomeVersion', replace(JSON_EXTRACT(Props, '$.\"focalboard_lastWelcomeVersion\"'), '\"', '') FROM Users WHERE JSON_EXTRACT(Props, '$.focalboard_lastWelcomeVersion') IS NOT NULL;\n\n        UPDATE Users SET Props =  JSON_REMOVE(Props, '$.\"focalboard_welcomePageViewed\"', '$.\"hiddenBoardIDs\"', '$.\"focalboard_tourCategory\"', '$.\"focalboard_onboardingTourStep\"', '$.\"focalboard_onboardingTourStarted\"', '$.\"focalboard_version72MessageCanceled\"', '$.\"focalboard_lastWelcomeVersion\"');\n    {{end}}\n{{else}}\n    {{- /* For personal server, we need to write to Focalboard's preferences table, hence the use of `prefix`. */ -}}\n\n    {{if .postgres}}\n        INSERT INTO {{.prefix}}preferences (UserId, Category, Name, Value) SELECT Id, 'focalboard', 'welcomePageViewed', replace((Props->'focalboard_welcomePageViewed')::varchar, '\"', '') from {{.prefix}}users WHERE Props->'focalboard_welcomePageViewed' IS NOT NULL ON CONFLICT DO NOTHING;\n        INSERT INTO {{.prefix}}preferences (UserId, Category, Name, Value) SELECT Id, 'focalboard', 'hiddenBoardIDs', replace(replace(replace((Props->'hiddenBoardIDs')::varchar, '\"[', '['), ']\"', ']'), '\\\"', '\"') from {{.prefix}}users WHERE Props->'hiddenBoardIDs' IS NOT NULL ON CONFLICT DO NOTHING;\n        INSERT INTO {{.prefix}}preferences (UserId, Category, Name, Value) SELECT Id, 'focalboard', 'tourCategory', replace((Props->'focalboard_tourCategory')::varchar, '\"', '') from {{.prefix}}users WHERE Props->'focalboard_tourCategory' IS NOT NULL ON CONFLICT DO NOTHING;\n        INSERT INTO {{.prefix}}preferences (UserId, Category, Name, Value) SELECT Id, 'focalboard', 'onboardingTourStep', replace((Props->'focalboard_onboardingTourStep')::varchar, '\"', '') from {{.prefix}}users WHERE Props->'focalboard_onboardingTourStep' IS NOT NULL ON CONFLICT DO NOTHING;\n        INSERT INTO {{.prefix}}preferences (UserId, Category, Name, Value) SELECT Id, 'focalboard', 'onboardingTourStarted', replace((Props->'focalboard_onboardingTourStarted')::varchar, '\"', '') from {{.prefix}}users WHERE Props->'focalboard_onboardingTourStarted' IS NOT NULL ON CONFLICT DO NOTHING;\n        INSERT INTO {{.prefix}}preferences (UserId, Category, Name, Value) SELECT Id, 'focalboard', 'version72MessageCanceled', replace((Props->'focalboard_version72MessageCanceled')::varchar, '\"', '') from {{.prefix}}users WHERE Props->'focalboard_version72MessageCanceled' IS NOT NULL ON CONFLICT DO NOTHING;\n        INSERT INTO {{.prefix}}preferences (UserId, Category, Name, Value) SELECT Id, 'focalboard', 'lastWelcomeVersion', replace((Props->'focalboard_lastWelcomeVersion')::varchar, '\"', '') from {{.prefix}}users WHERE Props->'focalboard_lastWelcomeVersion' IS NOT NULL ON CONFLICT DO NOTHING;\n\n        UPDATE {{.prefix}}users SET props = (props::jsonb - 'focalboard_welcomePageViewed' - 'hiddenBoardIDs' - 'focalboard_tourCategory' - 'focalboard_onboardingTourStep' - 'focalboard_onboardingTourStarted' - 'focalboard_version72MessageCanceled' - 'focalboard_lastWelcomeVersion')::json WHERE jsonb_typeof(props::jsonb) = 'object';\n    {{end}}\n\n    {{if .mysql}}\n        INSERT INTO {{.prefix}}preferences (UserId, Category, Name, Value) SELECT Id, 'focalboard', 'welcomePageViewed', replace(JSON_EXTRACT(Props, '$.\"focalboard_welcomePageViewed\"'), '\"', '') from {{.prefix}}users WHERE JSON_EXTRACT(Props, '$.focalboard_welcomePageViewed') IS NOT NULL ON DUPLICATE KEY UPDATE value = value;\n        INSERT INTO {{.prefix}}preferences (UserId, Category, Name, Value) SELECT Id, 'focalboard', 'hiddenBoardIDs', replace(replace(replace(JSON_EXTRACT(Props, '$.\"hiddenBoardIDs\"'), '\"[', '['), ']\"', ']'), '\\\\\"', '\"') from {{.prefix}}users WHERE JSON_EXTRACT(Props, '$.hiddenBoardIDs') IS NOT NULL ON DUPLICATE KEY UPDATE value = value;\n        INSERT INTO {{.prefix}}preferences (UserId, Category, Name, Value) SELECT Id, 'focalboard', 'tourCategory', replace(JSON_EXTRACT(Props, '$.\"focalboard_tourCategory\"'), '\"', '') from {{.prefix}}users WHERE JSON_EXTRACT(Props, '$.focalboard_tourCategory') IS NOT NULL ON DUPLICATE KEY UPDATE value = value;\n        INSERT INTO {{.prefix}}preferences (UserId, Category, Name, Value) SELECT Id, 'focalboard', 'onboardingTourStep', replace(JSON_EXTRACT(Props, '$.\"focalboard_onboardingTourStep\"'), '\"', '') from {{.prefix}}users WHERE JSON_EXTRACT(Props, '$.focalboard_onboardingTourStep') IS NOT NULL ON DUPLICATE KEY UPDATE value = value;\n        INSERT INTO {{.prefix}}preferences (UserId, Category, Name, Value) SELECT Id, 'focalboard', 'onboardingTourStarted', replace(JSON_EXTRACT(Props, '$.\"focalboard_onboardingTourStarted\"'), '\"', '') from {{.prefix}}users WHERE JSON_EXTRACT(Props, '$.focalboard_onboardingTourStarted') IS NOT NULL ON DUPLICATE KEY UPDATE value = value;\n        INSERT INTO {{.prefix}}preferences (UserId, Category, Name, Value) SELECT Id, 'focalboard', 'version72MessageCanceled', replace(JSON_EXTRACT(Props, '$.\"focalboard_version72MessageCanceled\"'), '\"', '') from {{.prefix}}users WHERE JSON_EXTRACT(Props, '$.focalboard_version72MessageCanceled') IS NOT NULL ON DUPLICATE KEY UPDATE value = value;\n        INSERT INTO {{.prefix}}preferences (UserId, Category, Name, Value) SELECT Id, 'focalboard', 'lastWelcomeVersion', replace(JSON_EXTRACT(Props, '$.\"focalboard_lastWelcomeVersion\"'), '\"', '') from {{.prefix}}users WHERE JSON_EXTRACT(Props, '$.focalboard_lastWelcomeVersion') IS NOT NULL ON DUPLICATE KEY UPDATE value = value;\n\n        UPDATE {{.prefix}}users SET Props =  JSON_REMOVE(Props, '$.\"focalboard_welcomePageViewed\"', '$.\"hiddenBoardIDs\"', '$.\"focalboard_tourCategory\"', '$.\"focalboard_onboardingTourStep\"', '$.\"focalboard_onboardingTourStarted\"', '$.\"focalboard_version72MessageCanceled\"', '$.\"focalboard_lastWelcomeVersion\"');\n    {{end}}\n\n    {{if .sqlite}}\n        {{- /* Surprisingly SQLite and MySQL have same JSON functions and syntax! */ -}}\n\n        INSERT OR IGNORE INTO {{.prefix}}preferences (UserId, Category, Name, Value) SELECT Id, 'focalboard', 'welcomePageViewed', replace(JSON_EXTRACT(Props, '$.\"focalboard_welcomePageViewed\"'), '\"', '') from {{.prefix}}users WHERE JSON_EXTRACT(Props, '$.focalboard_welcomePageViewed') IS NOT NULL;\n        INSERT OR IGNORE INTO {{.prefix}}preferences (UserId, Category, Name, Value) SELECT Id, 'focalboard', 'hiddenBoardIDs', replace(replace(replace(JSON_EXTRACT(Props, '$.\"hiddenBoardIDs\"'), '\"[', '['), ']\"', ']'), '\\\\\"', '\"') from {{.prefix}}users WHERE JSON_EXTRACT(Props, '$.hiddenBoardIDs') IS NOT NULL;\n        INSERT OR IGNORE INTO {{.prefix}}preferences (UserId, Category, Name, Value) SELECT Id, 'focalboard', 'tourCategory', replace(JSON_EXTRACT(Props, '$.\"focalboard_tourCategory\"'), '\"', '') from {{.prefix}}users WHERE JSON_EXTRACT(Props, '$.focalboard_tourCategory') IS NOT NULL;\n        INSERT OR IGNORE INTO {{.prefix}}preferences (UserId, Category, Name, Value) SELECT Id, 'focalboard', 'onboardingTourStep', replace(JSON_EXTRACT(Props, '$.\"focalboard_onboardingTourStep\"'), '\"', '') from {{.prefix}}users WHERE JSON_EXTRACT(Props, '$.focalboard_onboardingTourStep') IS NOT NULL;\n        INSERT OR IGNORE INTO {{.prefix}}preferences (UserId, Category, Name, Value) SELECT Id, 'focalboard', 'onboardingTourStarted', replace(JSON_EXTRACT(Props, '$.\"focalboard_onboardingTourStarted\"'), '\"', '') from {{.prefix}}users WHERE JSON_EXTRACT(Props, '$.focalboard_onboardingTourStarted') IS NOT NULL;\n        INSERT OR IGNORE INTO {{.prefix}}preferences (UserId, Category, Name, Value) SELECT Id, 'focalboard', 'version72MessageCanceled', replace(JSON_EXTRACT(Props, '$.\"focalboard_version72MessageCanceled\"'), '\"', '') from {{.prefix}}users WHERE JSON_EXTRACT(Props, '$.focalboard_version72MessageCanceled') IS NOT NULL;\n        INSERT OR IGNORE INTO {{.prefix}}preferences (UserId, Category, Name, Value) SELECT Id, 'focalboard', 'lastWelcomeVersion', replace(JSON_EXTRACT(Props, '$.\"focalboard_lastWelcomeVersion\"'), '\"', '') from {{.prefix}}users WHERE JSON_EXTRACT(Props, '$.focalboard_lastWelcomeVersion') IS NOT NULL;\n\n        UPDATE {{.prefix}}users SET Props =  JSON_REMOVE(Props, '$.\"focalboard_welcomePageViewed\"', '$.\"hiddenBoardIDs\"', '$.\"focalboard_tourCategory\"', '$.\"focalboard_onboardingTourStep\"', '$.\"focalboard_onboardingTourStarted\"', '$.\"focalboard_version72MessageCanceled\"', '$.\"focalboard_lastWelcomeVersion\"');\n    {{end}}\n\n{{end}}\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000028_remove_template_channel_link.down.sql",
    "content": "SELECT 1;\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000028_remove_template_channel_link.up.sql",
    "content": "UPDATE {{.prefix}}boards SET channel_id = '' WHERE is_template;\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000029_add_category_type_field.down.sql",
    "content": "SELECT 1;\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000029_add_category_type_field.up.sql",
    "content": "{{- /* addColumnIfNeeded tableName columnName datatype constraint */ -}}\n{{ addColumnIfNeeded \"categories\" \"type\" \"varchar(64)\" \"\"}}\n\nUPDATE {{.prefix}}categories SET type = 'custom' WHERE type IS NULL;\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000030_add_category_sort_order.down.sql",
    "content": "SELECT 1;"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000030_add_category_sort_order.up.sql",
    "content": "{{- /* addColumnIfNeeded tableName columnName datatype constraint */ -}}\n{{ addColumnIfNeeded \"categories\" \"sort_order\" \"BIGINT\" \"\"}}"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000031_add_category_boards_sort_order.down.sql",
    "content": "SELECT 1;"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000031_add_category_boards_sort_order.up.sql",
    "content": "{{- /* addColumnIfNeeded tableName columnName datatype constraint */ -}}\n{{ addColumnIfNeeded \"category_boards\" \"sort_order\" \"BIGINT\" \"\"}}"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000032_move_boards_category_to_end.down.sql",
    "content": "SELECT 1;"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000032_move_boards_category_to_end.up.sql",
    "content": "{{- /* To move Boards category to to the last value, we just need a relatively large value. */ -}}\n{{- /* Assigning 10x total number of categories works perfectly. The sort_order is anyways updated */ -}}\n{{- /* when the user manually DNDs a category. */ -}}\n\n{{if or .postgres .sqlite}}\nUPDATE {{.prefix}}categories SET sort_order = (10 * (SELECT COUNT(*) FROM {{.prefix}}categories)) WHERE lower(name) = 'boards';\n{{end}}\n\n{{if .mysql}}\n{{- /* MySQL doesn't allow referencing the same table in subquery and update query like Postgres, */ -}}\n{{- /* So we save the subquery result in a variable to use later. */ -}}\nSET @focalboard_numCategories = (SELECT COUNT(*) FROM {{.prefix}}categories);\nUPDATE {{.prefix}}categories SET sort_order = (10 * @focalboard_numCategories) WHERE lower(name) = 'boards';\nSET @focalboard_numCategories = NULL;\n{{end}}"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000033_remove_deleted_category_boards.down.sql",
    "content": "SELECT 1;"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000033_remove_deleted_category_boards.up.sql",
    "content": "DELETE FROM {{.prefix}}category_boards WHERE delete_at > 0;"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000034_category_boards_remove_unused_delete_at_column.down.sql",
    "content": "SELECT 1;"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000034_category_boards_remove_unused_delete_at_column.up.sql",
    "content": "{{ if or .postgres .mysql }}\n    {{ dropColumnIfNeeded \"category_boards\" \"delete_at\" }}\n{{end}}"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000035_add_hidden_board_column.down.sql",
    "content": "SELECT 1;"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000035_add_hidden_board_column.up.sql",
    "content": "{{ addColumnIfNeeded \"category_boards\" \"hidden\" \"boolean\" \"\" }}"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000036_category_board_add_unique_constraint.down.sql",
    "content": "SELECT 1;"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000036_category_board_add_unique_constraint.up.sql",
    "content": "{{if or .mysql .postgres}}\n{{ addConstraintIfNeeded \"category_boards\" \"unique_user_category_board\" \"UNIQUE\" \"UNIQUE(user_id, board_id)\"}}\n{{end}}\n\n{{if .sqlite}}\n    ALTER TABLE {{.prefix}}category_boards RENAME TO {{.prefix}}category_boards_old;\n\n    CREATE TABLE {{.prefix}}category_boards (\n        id varchar(36) NOT NULL,\n        user_id varchar(36) NOT NULL,\n        category_id varchar(36) NOT NULL,\n        board_id VARCHAR(36) NOT NULL,\n        create_at BIGINT,\n        update_at BIGINT,\n        sort_order BIGINT,\n        hidden boolean,\n        PRIMARY KEY (id),\n        CONSTRAINT unique_user_category_board UNIQUE (user_id, board_id)\n    );\n\n    INSERT INTO {{.prefix}}category_boards\n        (id, user_id, category_id, board_id, create_at, update_at, sort_order, hidden)\n        SELECT id, user_id, category_id, board_id, create_at, update_at, sort_order, hidden FROM {{.prefix}}category_boards_old;\n    DROP TABLE {{.prefix}}category_boards_old;\n\n{{end}}\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000037_hidden_boards_from_preferences.down.sql",
    "content": "SELECT 1;"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000037_hidden_boards_from_preferences.up.sql",
    "content": "{{if .plugin}}\n    {{if .mysql}}\n        UPDATE {{.prefix}}category_boards AS fcb \n            JOIN Preferences p \n            ON fcb.user_id = p.userid\n            AND p.category = 'focalboard'\n            AND p.name = 'hiddenBoardIDs'\n            SET hidden = true\n            WHERE p.value LIKE concat('%', fcb.board_id, '%');\n    {{end}}\n\n    {{if .postgres}}\n        UPDATE {{.prefix}}category_boards as fcb \n            SET hidden = true\n            FROM preferences p\n            WHERE p.userid = fcb.user_id \n            AND p.category = 'focalboard'\n            AND p.name = 'hiddenBoardIDs'\n            AND p.value like ('%' || fcb.board_id || '%');\n    {{end}}\n{{else}}\n    {{if .mysql}}\n        UPDATE {{.prefix}}category_boards AS fcb \n            JOIN {{.prefix}}preferences p \n            ON fcb.user_id = p.userid\n            AND p.category = 'focalboard'\n            AND p.name = 'hiddenBoardIDs'\n            SET hidden = true\n            WHERE p.value LIKE concat('%', fcb.board_id, '%');\n    {{end}}\n\n    {{if .postgres}}\n        UPDATE {{.prefix}}category_boards as fcb \n            SET hidden = true\n            FROM {{.prefix}}preferences p\n            WHERE p.userid = fcb.user_id \n            AND p.category = 'focalboard'\n            AND p.name = 'hiddenBoardIDs'\n            AND p.value like ('%' || fcb.board_id || '%');\n    {{end}}\n{{end}}\n\n{{if .sqlite}}\n    UPDATE {{.prefix}}category_boards\n        SET hidden = true\n        WHERE (user_id || '_' || board_id)\n        IN\n        (\n            SELECT (fcb.user_id || '_' || fcb.board_id)\n            FROM {{.prefix}}category_boards AS fcb\n            JOIN {{.prefix}}preferences p \n            ON p.userid = fcb.user_id \n            AND p.category = 'focalboard'\n            AND p.name = 'hiddenBoardIDs' WHERE \n            p.value LIKE ('%' || fcb.board_id || '%')\n        );\n{{end}}"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000038_delete_hiddenBoardIDs_from_preferences.down.sql",
    "content": "SELECT 1;"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000038_delete_hiddenBoardIDs_from_preferences.up.sql",
    "content": "{{if .plugin}}\n    DELETE FROM Preferences WHERE category = 'focalboard' AND name = 'hiddenBoardIDs';\n{{else}}\n    DELETE FROM {{.prefix}}preferences WHERE category = 'focalboard' AND name = 'hiddenBoardIDs';\n{{end}}"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000039_add_path_to_file_info.down.sql",
    "content": "SELECT 1;"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000039_add_path_to_file_info.up.sql",
    "content": "{{ addColumnIfNeeded \"file_info\" \"path\" \"varchar(512)\" \"\" }}"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000040_fix_fileinfo_soft_deletes.down.sql",
    "content": "SELECT 1;\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/000040_fix_fileinfo_soft_deletes.up.sql",
    "content": "{{if .plugin}}\n  UPDATE FileInfo\n     SET DeleteAt = 0\n   WHERE CreatorId = 'boards'\n     AND DeleteAt != 0;\n{{else}}\nSELECT 1;\n{{end}}\n"
  },
  {
    "path": "server/services/store/sqlstore/migrations/README.md",
    "content": "# Migration Scripts\n\nThese scripts are executed against the current database on server start-up. Any scripts previously executed are skipped, however these scripts are designed to be idempotent for Postgres and MySQL. To correct common problems with schema and data migrations the `focalboard_schema_migrations` table can be cleared of all records and the server restarted.\n\nThe following built-in variables are available:\n\n| Name  | Syntax | Description |\n| ----- | -----  | -----       |\n| schemaName | {{ .schemaName }}     | Returns the database/schema name (e.g. `mattermost_`, `mattermost_test`, `public`, ...) |\n| prefix | {{ .prefix }}     | Returns the table name prefix (e.g. `focalbaord_`) |\n| postgres | {{if .postgres }} ... {{end}} | Returns true if the current database is Postgres. |\n| sqlite   | {{if .sqlite }} ... {{end}}   | Returns true if the current database is Sqlite3. |\n| mysql    | {{if .mysql }} ... {{end}}   | Returns true if the current database is MySQL. |\n| plugin   | {{if .plugin }} ... {{end}}   | Returns true if the server is currently running as a plugin (or product). In others words this is true if the server is not running as stand-alone or personal server. |\n| singleUser   | {{if .singleUser }} ... {{end}}   | Returns true if the server is currently running in single user mode. |\n\nTo help with creating scripts that are idempotent some template functions have been added to the migration engine.\n\n| Name  | Syntax | Description |\n| ----- | -----  | -----       |\n| addColumnIfNeeded   | {{ addColumnIfNeeded schemaName tableName columnName datatype constraint }} | Adds column to table only if column doesn't already exist. |\n| dropColumnIfNeeded  | {{ dropColumnIfNeeded schemaName tableName columnName }} | Drops column from table if the column exists. |\n| createIndexIfNeeded | {{ createIndexIfNeeded schemaName tableName columns }} | Creates an index if it does not already exist. The index name follows the existing convention of using `idx_` plus the table name and all columns separated by underscores. |\n| renameTableIfNeeded | {{ renameTableIfNeeded schemaName oldTableName newTableName }} | Renames the table if the new table name does not exist. |\n| renameColumnIfNeeded | {{ renameColumnIfNeeded schemaName tableName oldVolumnName newColumnName datatype }} | Renames a column if the new column name does not exist. |\n| doesTableExist       | {{if doesTableExist schemaName tableName }} ... {{end}}  | Returns true if the table exists. Typically used in a `if` statement to conditionally include a section of script. Currently the existence of the table is determined before any scripts are executed (limitation of Morph). |\n| doesColumnExist       | {{if doesTableExist schemaName tableName columnName }} ... {{end}}  | Returns true if the column exists. Typically used in a `if` statement to conditionally include a section of script. Currently the existence of the column is determined before any scripts are executed (limitation of Morph). |\n\n**Note, table names should not include table prefix or schema name.**\n\n## Examples\n\n```bash\n{{ addColumnIfNeeded .schemaName \"categories\" \"type\" \"varchar(64)\" \"\"}}\n{{ addColumnIfNeeded .schemaName \"boards_history\" \"minimum_role\" \"varchar(36)\" \"NOT NULL DEFAULT ''\"}}\n```\n\n```bash\n{{ dropColumnIfNeeded .schemaName \"blocks_history\" \"workspace_id\" }}\n```\n\n```bash\n{{ createIndexIfNeeded .schemaName \"boards\" \"team_id, is_template\" }}\n```\n\n```bash\n{{ renameTableIfNeeded .schemaName \"blocks\" \"blocks_history\" }}\n```\n\n```bash\n{{ renameColumnIfNeeded .schemaName \"blocks_history\" \"workspace_id\" \"channel_id\" \"varchar(36)\" }}\n```\n\n```bash\n{{if doesTableExist .schemaName \"blocks_history\" }}\n    SELECT 'table exists';\n{{end}}\n\n{{if not (doesTableExist .schemaName \"blocks_history\") }}\n    SELECT 1;\n{{end}}\n```\n\n```bash\n{{if doesColumnExist .schemaName \"boards_history\" \"minimum_role\"}}\n    UPDATE ...\n {{end}}\n```\n"
  },
  {
    "path": "server/services/store/sqlstore/migrationstests/boards_migrator_test.go",
    "content": "package migrationstests\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"database/sql\"\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"text/template\"\n\n\t\"github.com/mattermost/mattermost/server/public/pluginapi/cluster\"\n\t\"github.com/mattermost/morph\"\n\t\"github.com/mattermost/morph/drivers\"\n\t\"github.com/mattermost/morph/drivers/mysql\"\n\t\"github.com/mattermost/morph/drivers/postgres\"\n\t\"github.com/mattermost/morph/drivers/sqlite\"\n\tembedded \"github.com/mattermost/morph/sources/embedded\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n\tmmSqlStore \"github.com/mattermost/mattermost/server/public/utils/sql\"\n\t\"github.com/mattermost/mattermost/server/v8/channels/db\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/store/sqlstore\"\n)\n\nvar tablePrefix = \"focalboard_\"\n\ntype BoardsMigrator struct {\n\twithMattermostMigrations bool\n\tconnString               string\n\tdriverName               string\n\tdb                       *sql.DB\n\tstore                    *sqlstore.SQLStore\n\tmorphEngine              *morph.Morph\n\tmorphDriver              drivers.Driver\n}\n\nfunc NewBoardsMigrator(withMattermostMigrations bool) *BoardsMigrator {\n\treturn &BoardsMigrator{\n\t\twithMattermostMigrations: withMattermostMigrations,\n\t}\n}\n\nfunc (bm *BoardsMigrator) runMattermostMigrations() error {\n\tassets := db.Assets()\n\tassetsList, err := assets.ReadDir(filepath.Join(\"migrations\", bm.driverName))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tassetNames := make([]string, len(assetsList))\n\tfor i, entry := range assetsList {\n\t\tassetNames[i] = entry.Name()\n\t}\n\n\tsrc, err := embedded.WithInstance(&embedded.AssetSource{\n\t\tNames: assetNames,\n\t\tAssetFunc: func(name string) ([]byte, error) {\n\t\t\treturn assets.ReadFile(filepath.Join(\"migrations\", bm.driverName, name))\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdriver, err := bm.getDriver()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\toptions := []morph.EngineOption{\n\t\tmorph.SetStatementTimeoutInSeconds(1000000),\n\t}\n\n\tengine, err := morph.New(context.Background(), driver, src, options...)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer engine.Close()\n\n\treturn engine.ApplyAll()\n}\n\nfunc (bm *BoardsMigrator) getDriver() (drivers.Driver, error) {\n\tvar driver drivers.Driver\n\tvar err error\n\tswitch bm.driverName {\n\tcase model.PostgresDBType:\n\t\tdriver, err = postgres.WithInstance(bm.db)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\tcase model.MysqlDBType:\n\t\tdriver, err = mysql.WithInstance(bm.db)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\tcase model.SqliteDBType:\n\t\tdriver, err = sqlite.WithInstance(bm.db)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn driver, nil\n}\n\nfunc (bm *BoardsMigrator) getMorphConnection() (*morph.Morph, drivers.Driver, error) {\n\tdriver, err := bm.getDriver()\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tassetsList, err := sqlstore.Assets.ReadDir(\"migrations\")\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tassetNamesForDriver := make([]string, len(assetsList))\n\tfor i, dirEntry := range assetsList {\n\t\tassetNamesForDriver[i] = dirEntry.Name()\n\t}\n\n\tparams := map[string]interface{}{\n\t\t\"prefix\":     tablePrefix,\n\t\t\"postgres\":   bm.driverName == model.PostgresDBType,\n\t\t\"sqlite\":     bm.driverName == model.SqliteDBType,\n\t\t\"mysql\":      bm.driverName == model.MysqlDBType,\n\t\t\"plugin\":     bm.withMattermostMigrations,\n\t\t\"singleUser\": false,\n\t}\n\n\tmigrationAssets := &embedded.AssetSource{\n\t\tNames: assetNamesForDriver,\n\t\tAssetFunc: func(name string) ([]byte, error) {\n\t\t\tasset, mErr := sqlstore.Assets.ReadFile(\"migrations/\" + name)\n\t\t\tif mErr != nil {\n\t\t\t\treturn nil, mErr\n\t\t\t}\n\n\t\t\ttmpl, pErr := template.New(\"sql\").Funcs(bm.store.GetTemplateHelperFuncs()).Parse(string(asset))\n\t\t\tif pErr != nil {\n\t\t\t\treturn nil, pErr\n\t\t\t}\n\t\t\tbuffer := bytes.NewBufferString(\"\")\n\n\t\t\terr = tmpl.Execute(buffer, params)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\treturn buffer.Bytes(), nil\n\t\t},\n\t}\n\n\tsrc, err := embedded.WithInstance(migrationAssets)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tengine, err := morph.New(context.Background(), driver, src, morph.SetMigrationTableName(fmt.Sprintf(\"%sschema_migrations\", tablePrefix)))\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\treturn engine, driver, nil\n}\n\nfunc (bm *BoardsMigrator) Setup() error {\n\tvar err error\n\tbm.driverName, bm.connString, err = sqlstore.PrepareNewTestDatabase()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif bm.driverName == model.MysqlDBType {\n\t\tbm.connString, err = mmSqlStore.ResetReadTimeout(bm.connString)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tbm.connString, err = mmSqlStore.AppendMultipleStatementsFlag(bm.connString)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tvar dbErr error\n\tbm.db, dbErr = sql.Open(bm.driverName, bm.connString)\n\tif dbErr != nil {\n\t\treturn dbErr\n\t}\n\n\tif err := bm.db.Ping(); err != nil {\n\t\treturn err\n\t}\n\n\tif bm.withMattermostMigrations {\n\t\tif err := bm.runMattermostMigrations(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tlogger, _ := mlog.NewLogger()\n\n\tstoreParams := sqlstore.Params{\n\t\tDBType:           bm.driverName,\n\t\tDBPingAttempts:   5,\n\t\tConnectionString: bm.connString,\n\t\tTablePrefix:      tablePrefix,\n\t\tLogger:           logger,\n\t\tDB:               bm.db,\n\t\tNewMutexFn: func(name string) (*cluster.Mutex, error) {\n\t\t\treturn nil, fmt.Errorf(\"not implemented\")\n\t\t},\n\t\tSkipMigrations: true,\n\t}\n\tbm.store, err = sqlstore.New(storeParams)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tmorphEngine, morphDriver, err := bm.getMorphConnection()\n\tif err != nil {\n\t\treturn err\n\t}\n\tbm.morphEngine = morphEngine\n\tbm.morphDriver = morphDriver\n\n\treturn nil\n}\n\nfunc (bm *BoardsMigrator) MigrateToStep(step int) error {\n\tapplied, err := bm.morphDriver.AppliedMigrations()\n\tif err != nil {\n\t\treturn err\n\t}\n\tcurrentVersion := len(applied)\n\n\tif _, err := bm.morphEngine.Apply(step - currentVersion); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (bm *BoardsMigrator) Interceptors() map[int]func() error {\n\treturn map[int]func() error{\n\t\t35: func() error {\n\t\t\treturn bm.store.RunDeDuplicateCategoryBoardsMigration(35)\n\t\t},\n\t}\n}\n\nfunc (bm *BoardsMigrator) TearDown() error {\n\tif err := bm.morphEngine.Close(); err != nil {\n\t\treturn err\n\t}\n\n\tif err := bm.db.Close(); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (bm *BoardsMigrator) DriverName() string {\n\treturn bm.driverName\n}\n\nfunc (bm *BoardsMigrator) DB() *sql.DB {\n\treturn bm.db\n}\n"
  },
  {
    "path": "server/services/store/sqlstore/migrationstests/de_duplicate_category_boards_migration_test.go",
    "content": "package migrationstests\n\nimport (\n\t\"github.com/stretchr/testify/assert\"\n\t\"testing\"\n)\n\nfunc TestRunDeDuplicateCategoryBoardsMigration(t *testing.T) {\n\tth, tearDown := SetupTestHelper(t)\n\tdefer tearDown()\n\n\tif th.IsSQLite() {\n\t\tt.Skip(\"SQLite is not supported for this\")\n\t}\n\n\tth.f.MigrateToStepSkippingLastInterceptor(35).\n\t\tExecFile(\"./fixtures/testDeDuplicateCategoryBoardsMigration.sql\")\n\n\tth.f.RunInterceptor(35)\n\n\t// verifying count of rows\n\tvar count int\n\tcountQuery := \"SELECT COUNT(*) FROM focalboard_category_boards\"\n\trow := th.f.DB().QueryRow(countQuery)\n\terr := row.Scan(&count)\n\tassert.NoError(t, err)\n\tassert.Equal(t, 4, count)\n}\n"
  },
  {
    "path": "server/services/store/sqlstore/migrationstests/fixtures/deletedMembershipBoardsMigrationFixtures.sql",
    "content": "INSERT INTO Teams\n(Id, Name, Type, DeleteAt)\nVALUES\n('team-one', 'team-one', 'O', 0),\n('team-two', 'team-two', 'O', 0),\n('team-three', 'team-three', 'O', 0);\n\nINSERT INTO Channels\n(Id, DeleteAt, TeamId, Type, Name, CreatorId)\nVALUES\n('group-channel', 0, 'team-one', 'G', 'group-channel', 'user-one'),\n('direct-channel', 0, 'team-one', 'D', 'direct-channel', 'user-one');\n\nINSERT INTO Users\n(Id, Username, Email)\nVALUES\n('user-one', 'john-doe', 'john-doe@sample.com'),\n('user-two', 'jane-doe', 'jane-doe@sample.com');\n\nINSERT INTO focalboard_boards\n(id, team_id, channel_id, created_by, modified_by, type, title, description, icon, show_description, is_template, create_at, update_at, delete_at)\nVALUES\n('board-group-channel', 'team-one', 'group-channel', 'user-one', 'user-one', 'P', 'Group Channel Board', '', '', false, false, 123, 123, 0),\n('board-direct-channel', 'team-one', 'direct-channel', 'user-one', 'user-one', 'P', 'Direct Channel Board', '', '', false, false, 123, 123, 0);\n\nINSERT INTO focalboard_board_members\n(board_id, user_id, scheme_admin)\nVALUES\n('board-group-channel', 'user-one', true),\n('board-direct-channel', 'user-one', true);\n\nINSERT INTO TeamMembers\n(TeamId, UserId, DeleteAt, SchemeAdmin)\nVALUES\n('team-one', 'user-one', 123, true),\n('team-one', 'user-two', 123, true),\n('team-two', 'user-one', 123, true),\n('team-two', 'user-two', 123, true),\n('team-three', 'user-one', 0, true),\n('team-three', 'user-two', 0, true);\n\nINSERT INTO ChannelMembers\n(ChannelId, UserId, SchemeUser, SchemeAdmin)\nVALUES\n('group-channel', 'user-one', true, true),\n('group-channel', 'two-one', true, false),\n('direct-channel', 'user-one', true, true);\n"
  },
  {
    "path": "server/services/store/sqlstore/migrationstests/fixtures/test18AddTeamsAndBoardsSQLMigrationFixtures.sql",
    "content": "INSERT INTO Channels (Id, CreateAt, UpdateAt, DeleteAt, TeamId, Type, Name, CreatorId) VALUES ('chan-id', 123, 123, 0, 'team-id', 'O', 'channel', 'user-id');\n\nINSERT INTO focalboard_blocks\n(id, workspace_id, root_id, parent_id, created_by, modified_by, type, title, create_at, update_at, delete_at, fields)\nVALUES\n('board-id', 'chan-id', 'board-id', 'board-id', 'user-id', 'user-id', 'board', 'My Board', 123, 123, 0, '{\"columnCalculations\": {\"__title\":\"countUniqueValue\"}}'),\n('card-id', 'chan-id', 'board-id', 'board-id', 'user-id', 'user-id', 'card', 'A card', 123, 123, 0, '{}'),\n('view-id', 'chan-id', 'board-id', 'board-id', 'user-id', 'user-id', 'view', 'A view', 123, 123, 0, '{\"viewType\":\"table\"}'),\n('view-id2', 'chan-id', 'board-id', 'board-id', 'user-id', 'user-id', 'view', 'A view2', 123, 123, 0, '{\"viewType\":\"board\"}'),\n('board-id2', 'chan-id', 'board-id2', 'board-id2', 'user-id', 'user-id', 'board', 'My Board Two', 123, 123, 0, '{\"description\": \"My Description\",\"showDescription\":true,\"isTemplate\":true,\"templateVer\":1,\"columnCalculations\":[]}');\n"
  },
  {
    "path": "server/services/store/sqlstore/migrationstests/fixtures/test27MigrateUserPropsToPreferences.sql",
    "content": "INSERT INTO focalboard_users\n(id, username, props)\nVALUES\n('user-id', 'johndoe', '{\"focalboard_welcomePageViewed\": true, \"hiddenBoardIDs\": [\"board1\", \"board2\"], \"focalboard_tourCategory\": \"onboarding\", \"focalboard_onboardingTourStep\": 1, \"focalboard_onboardingTourStarted\": false, \"focalboard_version72MessageCanceled\": true, \"focalboard_lastWelcomeVersion\": 7}');\n\nINSERT INTO focalboard_preferences\n(UserId, Category, Name, Value)\nVALUES\n('user-id', 'focalboard', 'onboardingTourStarted', true);\n"
  },
  {
    "path": "server/services/store/sqlstore/migrationstests/fixtures/test28RemoveTemplateChannelLink.sql",
    "content": "INSERT INTO focalboard_boards\n(id, title, type, is_template, channel_id, team_id)\nVALUES\n('board-id', 'Board', 'O', false, 'linked-channel', 'team-id'),\n('template-id', 'Template', 'O', true, 'linked-channel', 'team-id');\n"
  },
  {
    "path": "server/services/store/sqlstore/migrationstests/fixtures/test33_with_deleted_data.sql",
    "content": "INSERT INTO focalboard_category_boards\n(id, user_id, category_id, board_id, create_at, update_at, delete_at, sort_order)\nvalues\n('id-1', 'user_id-1', 'category-id-1', 'board-id-1', 1672988834402, 1672988834402, 0, 0),\n('id-2', 'user_id-1', 'category-id-2', 'board-id-1', 1672988834402, 1672988834402, 0, 0),\n('id-3', 'user_id-2', 'category-id-3', 'board-id-2', 1672988834402, 1672988834402, 1672988834402, 0),\n('id-4', 'user_id-2', 'category-id-3', 'board-id-4', 1672988834402, 1672988834402, 0, 0),\n('id-5', 'user_id-3', 'category-id-4', 'board-id-3', 1672988834402, 1672988834402, 1672988834402, 0);\n"
  },
  {
    "path": "server/services/store/sqlstore/migrationstests/fixtures/test33_with_no_deleted_data.sql",
    "content": "INSERT INTO focalboard_category_boards\n(id, user_id, category_id, board_id, create_at, update_at, delete_at, sort_order)\nvalues\n('id-1', 'user_id-1', 'category-id-1', 'board-id-1', 1672988834402, 1672988834402, 0, 0),\n('id-2', 'user_id-1', 'category-id-2', 'board-id-1', 1672988834402, 1672988834402, 0, 0),\n('id-3', 'user_id-2', 'category-id-3', 'board-id-2', 1672988834402, 1672988834402, 0, 0),\n('id-4', 'user_id-2', 'category-id-3', 'board-id-4', 1672988834402, 1672988834402, 0, 0),\n('id-5', 'user_id-3', 'category-id-4', 'board-id-3', 1672988834402, 1672988834402, 0, 0);\n"
  },
  {
    "path": "server/services/store/sqlstore/migrationstests/fixtures/test34_drop_delete_at_column.sql",
    "content": "ALTER TABLE focalboard_category_boards DROP COLUMN delete_at;"
  },
  {
    "path": "server/services/store/sqlstore/migrationstests/fixtures/test35_add_hidden_column.sql",
    "content": "ALTER TABLE focalboard_category_boards ADD COLUMN hidden boolean;"
  },
  {
    "path": "server/services/store/sqlstore/migrationstests/fixtures/test36_add_unique_constraint.sql",
    "content": ""
  },
  {
    "path": "server/services/store/sqlstore/migrationstests/fixtures/test37_valid_data.sql",
    "content": "INSERT INTO focalboard_category_boards\n(id, user_id, category_id, board_id, create_at, update_at, sort_order, hidden)\nVALUES\n('id-1', 'user-id-1', 'category-id-1', 'board-id-1', 1672889246832, 1672889246832, 0, false),\n('id-2', 'user-id-1', 'category-id-2', 'board-id-2', 1672889246832, 1672889246832, 0, false),\n('id-3', 'user-id-2', 'category-id-3', 'board-id-3', 1672889246832, 1672889246832, 0, false),\n('id-4', 'user-id-2', 'category-id-3', 'board-id-4', 1672889246832, 1672889246832, 0, false),\n('id-5', 'user-id-3', 'category-id-4', 'board-id-5', 1672889246832, 1672889246832, 0, false);\n\nINSERT INTO Preferences VALUES\n('user-id-1', 'focalboard', 'hiddenBoardIDs', '[\"board-id-1\"]'),\n('user-id-2', 'focalboard', 'hiddenBoardIDs', '[\"board-id-3\", \"board-id-4\"]');\n"
  },
  {
    "path": "server/services/store/sqlstore/migrationstests/fixtures/test37_valid_data_no_hidden_boards.sql",
    "content": "INSERT INTO focalboard_category_boards\n(id, user_id, category_id, board_id, create_at, update_at, sort_order, hidden)\nVALUES\n('id-1', 'user-id-1', 'category-id-1', 'board-id-1', 1672889246832, 1672889246832, 0, false),\n('id-2', 'user-id-1', 'category-id-2', 'board-id-2', 1672889246832, 1672889246832, 0, false),\n('id-3', 'user-id-2', 'category-id-3', 'board-id-3', 1672889246832, 1672889246832, 0, false),\n('id-4', 'user-id-2', 'category-id-3', 'board-id-4', 1672889246832, 1672889246832, 0, false),\n('id-5', 'user-id-3', 'category-id-4', 'board-id-5', 1672889246832, 1672889246832, 0, false);\n"
  },
  {
    "path": "server/services/store/sqlstore/migrationstests/fixtures/test37_valid_data_preference_but_no_hidden_board.sql",
    "content": "INSERT INTO focalboard_category_boards\n(id, user_id, category_id, board_id, create_at, update_at, sort_order, hidden)\nVALUES\n('id-1', 'user-id-1', 'category-id-1', 'board-id-1', 1672889246832, 1672889246832, 0, false),\n('id-2', 'user-id-1', 'category-id-2', 'board-id-2', 1672889246832, 1672889246832, 0, false),\n('id-3', 'user-id-2', 'category-id-3', 'board-id-3', 1672889246832, 1672889246832, 0, false),\n('id-4', 'user-id-2', 'category-id-3', 'board-id-4', 1672889246832, 1672889246832, 0, false),\n('id-5', 'user-id-3', 'category-id-4', 'board-id-5', 1672889246832, 1672889246832, 0, false);\n\nINSERT INTO Preferences VALUES\n('user-id-1', 'focalboard', 'hiddenBoardIDs', ''),\n('user-id-2', 'focalboard', 'hiddenBoardIDs', '');\n"
  },
  {
    "path": "server/services/store/sqlstore/migrationstests/fixtures/test37_valid_data_sqlite.sql",
    "content": "INSERT INTO focalboard_category_boards\n(id, user_id, category_id, board_id, create_at, update_at, sort_order, hidden)\nVALUES\n('id-1', 'user-id-1', 'category-id-1', 'board-id-1', 1672889246832, 1672889246832, 0, false),\n('id-2', 'user-id-1', 'category-id-2', 'board-id-2', 1672889246832, 1672889246832, 0, false),\n('id-3', 'user-id-2', 'category-id-3', 'board-id-3', 1672889246832, 1672889246832, 0, false),\n('id-4', 'user-id-2', 'category-id-3', 'board-id-4', 1672889246832, 1672889246832, 0, false),\n('id-5', 'user-id-3', 'category-id-4', 'board-id-5', 1672889246832, 1672889246832, 0, false);\n\nINSERT INTO focalboard_preferences VALUES\n('user-id-1', 'focalboard', 'hiddenBoardIDs', '[\"board-id-1\"]'),\n('user-id-2', 'focalboard', 'hiddenBoardIDs', '[\"board-id-3\", \"board-id-4\"]');\n"
  },
  {
    "path": "server/services/store/sqlstore/migrationstests/fixtures/test37_valid_data_sqlite_preference_but_no_hidden_board.sql",
    "content": "INSERT INTO focalboard_category_boards\n(id, user_id, category_id, board_id, create_at, update_at, sort_order, hidden)\nVALUES\n('id-1', 'user-id-1', 'category-id-1', 'board-id-1', 1672889246832, 1672889246832, 0, false),\n('id-2', 'user-id-1', 'category-id-2', 'board-id-2', 1672889246832, 1672889246832, 0, false),\n('id-3', 'user-id-2', 'category-id-3', 'board-id-3', 1672889246832, 1672889246832, 0, false),\n('id-4', 'user-id-2', 'category-id-3', 'board-id-4', 1672889246832, 1672889246832, 0, false),\n('id-5', 'user-id-3', 'category-id-4', 'board-id-5', 1672889246832, 1672889246832, 0, false);\n\nINSERT INTO focalboard_preferences VALUES\n('user-id-1', 'focalboard', 'hiddenBoardIDs', ''),\n('user-id-2', 'focalboard', 'hiddenBoardIDs', '');\n"
  },
  {
    "path": "server/services/store/sqlstore/migrationstests/fixtures/test38_add_plugin_preferences.sql",
    "content": "INSERT INTO Preferences VALUES\n('user-id-1', 'focalboard', 'hiddenBoardIDs', '[\"board-id-1\"]'),\n('user-id-2', 'focalboard', 'hiddenBoardIDs', '[\"board-id-3\", \"board-id-4\"]'),\n('user-id-3', 'lorem', 'lorem', ''),\n('user-id-4', 'ipsum', 'ipsum', '');"
  },
  {
    "path": "server/services/store/sqlstore/migrationstests/fixtures/test38_add_standalone_preferences.sql",
    "content": "INSERT INTO focalboard_preferences VALUES\n('user-id-1', 'focalboard', 'hiddenBoardIDs', '[\"board-id-1\"]'),\n('user-id-2', 'focalboard', 'hiddenBoardIDs', '[\"board-id-3\", \"board-id-4\"]'),\n('user-id-2', 'lorem', 'lorem', ''),\n('user-id-2', 'ipsum', 'ipsum', '');"
  },
  {
    "path": "server/services/store/sqlstore/migrationstests/fixtures/test40FixFileinfoSoftDeletes.sql",
    "content": "INSERT INTO FileInfo\n(Id, CreatorId, CreateAt, UpdateAt, DeleteAt)\nVALUES\n('fileinfo-1', 'user-id', 1, 1, 1000),\n('fileinfo-2', 'user-id', 1, 1, 1000),\n('fileinfo-3', 'user-id', 1, 1, 0),\n('fileinfo-4', 'boards', 1, 1, 2000),\n('fileinfo-5', 'boards', 1, 1, 2000),\n('fileinfo-6', 'boards', 1, 1, 0);\n"
  },
  {
    "path": "server/services/store/sqlstore/migrationstests/fixtures/testDeDuplicateCategoryBoardsMigration.sql",
    "content": "INSERT INTO focalboard_category_boards(id, user_id, category_id, board_id, create_at, update_at, sort_order)\nVALUES\n    ('id_1', 'user_id_1', 'category_id_1', 'board_id_1', 0, 0, 0),\n    ('id_2', 'user_id_1', 'category_id_2', 'board_id_1', 0, 0, 0),\n    ('id_3', 'user_id_1', 'category_id_3', 'board_id_1', 0, 0, 0),\n    ('id_4', 'user_id_2', 'category_id_4', 'board_id_2', 0, 0, 0),\n    ('id_5', 'user_id_2', 'category_id_5', 'board_id_2', 0, 0, 0),\n    ('id_6', 'user_id_3', 'category_id_6', 'board_id_3', 0, 0, 0),\n    ('id_7', 'user_id_4', 'category_id_6', 'board_id_4', 0, 0, 0);\n"
  },
  {
    "path": "server/services/store/sqlstore/migrationstests/helpers_test.go",
    "content": "package migrationstests\n\nimport (\n\t\"testing\"\n\n\t\"github.com/mgdelacroix/foundation\"\n)\n\ntype TestHelper struct {\n\tt *testing.T\n\tf *foundation.Foundation\n}\n\nfunc (th *TestHelper) IsPostgres() bool {\n\treturn th.f.DB().DriverName() == \"postgres\"\n}\n\nfunc (th *TestHelper) IsMySQL() bool {\n\treturn th.f.DB().DriverName() == \"mysql\"\n}\n\nfunc (th *TestHelper) IsSQLite() bool {\n\treturn th.f.DB().DriverName() == \"sqlite3\"\n}\n\nfunc SetupTestHelper(t *testing.T) (*TestHelper, func()) {\n\treturn setupTestHelper(t)\n}\n\nfunc setupTestHelper(t *testing.T) (*TestHelper, func()) {\n\tf := foundation.New(t, NewBoardsMigrator(false))\n\n\tth := &TestHelper{\n\t\tt: t,\n\t\tf: f,\n\t}\n\n\ttearDown := func() {\n\t\tth.f.TearDown()\n\t}\n\n\treturn th, tearDown\n}\n"
  },
  {
    "path": "server/services/store/sqlstore/migrationstests/migrate_34_test.go",
    "content": "package migrationstests\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test34DropDeleteAtColumnMySQLPostgres(t *testing.T) {\n\tt.Run(\"column exists\", func(t *testing.T) {\n\t\tth, tearDown := SetupTestHelper(t)\n\t\tdefer tearDown()\n\n\t\tth.f.MigrateToStep(34)\n\n\t\t// migration 34 only works for MySQL and PostgreSQL\n\t\tif th.IsMySQL() {\n\t\t\tvar count int\n\t\t\tquery := \"SELECT COUNT(column_name) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = 'focalboard_category_boards' AND column_name = 'delete_at'\"\n\t\t\tth.f.DB().Get(&count, query)\n\t\t\trequire.Equal(t, 0, count)\n\t\t} else if th.IsPostgres() {\n\t\t\tvar count int\n\t\t\tquery := \"select count(*) from information_schema.columns where table_name = 'focalboard_category_boards' and column_name = 'delete_at'\"\n\t\t\tth.f.DB().Get(&count, query)\n\t\t\trequire.Equal(t, 0, count)\n\t\t}\n\t})\n\n\tt.Run(\"column already deleted\", func(t *testing.T) {\n\t\tth, tearDown := SetupTestHelper(t)\n\t\tdefer tearDown()\n\n\t\t// For migration 34, we don't drop column\n\t\t// on SQLite, so no need to test for it.\n\t\tif th.IsSQLite() {\n\t\t\treturn\n\t\t}\n\n\t\tth.f.MigrateToStep(33).\n\t\t\tExecFile(\"./fixtures/test34_drop_delete_at_column.sql\")\n\n\t\tth.f.MigrateToStep(34)\n\n\t\tif th.IsMySQL() {\n\t\t\tvar count int\n\t\t\tquery := \"SELECT COUNT(column_name) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = 'focalboard_category_boards' AND column_name = 'delete_at'\"\n\t\t\tth.f.DB().Get(&count, query)\n\t\t\trequire.Equal(t, 0, count)\n\t\t} else if th.IsPostgres() {\n\t\t\tvar count int\n\t\t\tquery := \"select count(*) from information_schema.columns where table_name = 'focalboard_category_boards' and column_name = 'delete_at'\"\n\t\t\tth.f.DB().Get(&count, query)\n\t\t\trequire.Equal(t, 0, count)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "server/services/store/sqlstore/migrationstests/migration35_test.go",
    "content": "package migrationstests\n\nimport \"testing\"\n\nfunc Test35AddHIddenColumnToCategoryBoards(t *testing.T) {\n\tt.Run(\"base case - column doesn't already exist\", func(t *testing.T) {\n\t\tth, tearDown := SetupTestHelper(t)\n\t\tdefer tearDown()\n\t\tth.f.MigrateToStep(35)\n\t})\n\n\tt.Run(\"column already exist\", func(t *testing.T) {\n\t\tth, tearDown := SetupTestHelper(t)\n\t\tdefer tearDown()\n\n\t\t// We don't support adding column in idempotent manner\n\t\t// for SQLite, so no need to check for it.\n\t\tif th.IsSQLite() {\n\t\t\treturn\n\t\t}\n\n\t\tth.f.MigrateToStep(34).\n\t\t\tExecFile(\"./fixtures/test35_add_hidden_column.sql\")\n\n\t\tth.f.MigrateToStep(35)\n\t})\n}\n"
  },
  {
    "path": "server/services/store/sqlstore/migrationstests/migration36_test.go",
    "content": "package migrationstests\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test36AddUniqueConstraintToCategoryBoards(t *testing.T) {\n\tt.Run(\"constraint doesn't alreadt exists\", func(t *testing.T) {\n\t\tth, tearDown := SetupTestHelper(t)\n\t\tdefer tearDown()\n\n\t\tth.f.MigrateToStep(36)\n\n\t\t// verifying if constraint has been added\n\n\t\t//can't verify in sqlite, so skipping it\n\t\tif th.IsSQLite() {\n\t\t\treturn\n\t\t}\n\n\t\tvar count int\n\t\tquery := \"SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS \" +\n\t\t\t\"WHERE constraint_name = 'unique_user_category_board' \" +\n\t\t\t\"AND constraint_type = 'UNIQUE' \" +\n\t\t\t\"AND table_name = 'focalboard_category_boards'\"\n\t\tth.f.DB().Get(&count, query)\n\n\t\trequire.Equal(t, 1, count)\n\t})\n\n\tt.Run(\"constraint already exists\", func(t *testing.T) {\n\t\tth, tearDown := SetupTestHelper(t)\n\t\tdefer tearDown()\n\n\t\t// SQLIte doesn't support adding constraint to existing table\n\t\t// and neither do we, so skipping for sqlite\n\t\tif th.IsSQLite() {\n\t\t\treturn\n\t\t}\n\n\t\tth.f.MigrateToStep(35)\n\n\t\tif th.IsMySQL() {\n\t\t\tth.f.DB().Exec(\"alter table focalboard_category_boards add constraint unique_user_category_board UNIQUE(user_id, board_id);\")\n\t\t} else if th.IsPostgres() {\n\t\t\tth.f.DB().Exec(\"ALTER TABLE focalboard_category_boards ADD CONSTRAINT unique_user_category_board UNIQUE(user_id, board_id);\")\n\t\t}\n\n\t\tth.f.MigrateToStep(36)\n\n\t\tvar schema string\n\t\tif th.IsMySQL() {\n\t\t\tschema = \"DATABASE()\"\n\t\t} else if th.IsPostgres() {\n\t\t\tschema = \"'public'\"\n\t\t}\n\n\t\tvar count int\n\t\tquery := \"SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS \" +\n\t\t\t\"WHERE constraint_schema =  \" + schema + \" \" +\n\t\t\t\"AND constraint_name = 'unique_user_category_board' \" +\n\t\t\t\"AND constraint_type = 'UNIQUE' \" +\n\t\t\t\"AND table_name = 'focalboard_category_boards'\"\n\t\tth.f.DB().Get(&count, query)\n\t\trequire.Equal(t, 1, count)\n\t})\n}\n"
  },
  {
    "path": "server/services/store/sqlstore/migrationstests/migration37_test.go",
    "content": "package migrationstests\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test37MigrateHiddenBoardIDTest(t *testing.T) {\n\tt.Run(\"no existing hidden boards exist\", func(t *testing.T) {\n\t\tth, tearDown := SetupTestHelper(t)\n\t\tdefer tearDown()\n\t\tth.f.MigrateToStep(37)\n\t})\n\n\tt.Run(\"SQLite - existsing category boards with some hidden boards\", func(t *testing.T) {\n\t\tth, tearDown := SetupTestHelper(t)\n\t\tdefer tearDown()\n\n\t\tif th.IsMySQL() || th.IsPostgres() {\n\t\t\treturn\n\t\t}\n\n\t\tth.f.MigrateToStep(36).\n\t\t\tExecFile(\"./fixtures/test37_valid_data_sqlite.sql\")\n\n\t\tth.f.MigrateToStep(37)\n\n\t\ttype categoryBoard struct {\n\t\t\tUser_ID     string\n\t\t\tCategory_ID string\n\t\t\tBoard_ID    string\n\t\t\tHidden      bool\n\t\t}\n\n\t\tvar hiddenCategoryBoards []categoryBoard\n\n\t\tquery := \"SELECT user_id, category_id, board_id, hidden FROM focalboard_category_boards WHERE hidden = true\"\n\t\terr := th.f.DB().Select(&hiddenCategoryBoards, query)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 3, len(hiddenCategoryBoards))\n\t\trequire.Contains(t, hiddenCategoryBoards, categoryBoard{User_ID: \"user-id-1\", Category_ID: \"category-id-1\", Board_ID: \"board-id-1\", Hidden: true})\n\t\trequire.Contains(t, hiddenCategoryBoards, categoryBoard{User_ID: \"user-id-2\", Category_ID: \"category-id-3\", Board_ID: \"board-id-3\", Hidden: true})\n\t\trequire.Contains(t, hiddenCategoryBoards, categoryBoard{User_ID: \"user-id-2\", Category_ID: \"category-id-3\", Board_ID: \"board-id-4\", Hidden: true})\n\t})\n\n\tt.Run(\"SQLite - preference but no hidden board\", func(t *testing.T) {\n\t\tth, tearDown := SetupTestHelper(t)\n\t\tdefer tearDown()\n\n\t\tif th.IsMySQL() || th.IsPostgres() {\n\t\t\treturn\n\t\t}\n\n\t\tth.f.MigrateToStep(36).\n\t\t\tExecFile(\"./fixtures/test37_valid_data_sqlite_preference_but_no_hidden_board.sql\")\n\n\t\tth.f.MigrateToStep(37)\n\n\t\tvar count int\n\t\tquery := \"SELECT count(*) FROM focalboard_category_boards WHERE hidden = true\"\n\t\terr := th.f.DB().Get(&count, query)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 0, count)\n\t})\n\n}\n"
  },
  {
    "path": "server/services/store/sqlstore/migrationstests/migration38_test.go",
    "content": "package migrationstests\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test38RemoveHiddenBoardIDsFromPreferences(t *testing.T) {\n\tt.Run(\"standalone - no data exist\", func(t *testing.T) {\n\t\tth, tearDown := SetupTestHelper(t)\n\t\tdefer tearDown()\n\t\tth.f.MigrateToStep(38)\n\t})\n\n\tt.Run(\"plugin - no data exist\", func(t *testing.T) {\n\t\tth, tearDown := SetupTestHelper(t)\n\t\tdefer tearDown()\n\t\tth.f.MigrateToStep(38)\n\t})\n\n\tt.Run(\"standalone - some data exist\", func(t *testing.T) {\n\t\tth, tearDown := SetupTestHelper(t)\n\t\tdefer tearDown()\n\t\tth.f.MigrateToStep(37).\n\t\t\tExecFile(\"./fixtures/test38_add_standalone_preferences.sql\")\n\n\t\t// verify existing data count\n\t\tvar count int\n\t\tcountQuery := \"SELECT COUNT(*) FROM focalboard_preferences\"\n\t\terr := th.f.DB().Get(&count, countQuery)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 4, count)\n\n\t\tth.f.MigrateToStep(38)\n\n\t\t// now the count should be 0\n\t\terr = th.f.DB().Get(&count, countQuery)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 2, count)\n\t})\n}\n"
  },
  {
    "path": "server/services/store/sqlstore/migrationstests/migration_27_test.go",
    "content": "package migrationstests\n\nimport (\n\t\"encoding/json\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test27MigrateUserPropsToPreferences(t *testing.T) {\n\tt.Run(\"should correctly migrate properties on personal server and desktop\", func(t *testing.T) {\n\t\tth, tearDown := SetupTestHelper(t)\n\t\tdefer tearDown()\n\n\t\tth.f.MigrateToStep(26).\n\t\t\tExecFile(\"./fixtures/test27MigrateUserPropsToPreferences.sql\")\n\n\t\t// first we check that the data was correctly loaded from the\n\t\t// fixtures. We could perfectly skip this step, but as the\n\t\t// failing data is in a JSON field, I preferred to leave it\n\t\t// for clarity\n\t\tuser := struct {\n\t\t\tID       string\n\t\t\tUsername string\n\t\t\tProps    string\n\t\t}{}\n\n\t\terr := th.f.DB().Get(&user, \"SELECT id, username, props FROM focalboard_users WHERE id = 'user-id'\")\n\t\trequire.NoError(t, err)\n\t\tuserProps := map[string]any{}\n\t\trequire.NoError(t, json.Unmarshal([]byte(user.Props), &userProps))\n\n\t\trequire.Equal(t, \"johndoe\", user.Username)\n\t\trequire.Contains(t, userProps, \"focalboard_welcomePageViewed\")\n\t\trequire.True(t, userProps[\"focalboard_welcomePageViewed\"].(bool))\n\t\trequire.Contains(t, userProps, \"hiddenBoardIDs\")\n\t\trequire.ElementsMatch(t, []string{\"board1\", \"board2\"}, userProps[\"hiddenBoardIDs\"])\n\t\trequire.Contains(t, userProps, \"focalboard_tourCategory\")\n\t\trequire.Equal(t, \"onboarding\", userProps[\"focalboard_tourCategory\"])\n\t\trequire.Contains(t, userProps, \"focalboard_onboardingTourStep\")\n\t\trequire.Equal(t, float64(1), userProps[\"focalboard_onboardingTourStep\"])\n\t\trequire.Contains(t, userProps, \"focalboard_onboardingTourStarted\")\n\t\t// initially, onboardingTourStarted will be false on the user,\n\t\t// but already inserted in the preferences table as true. The\n\t\t// migration should not overwrite the already existing value,\n\t\t// so after migration #27, this value should be true\n\t\trequire.False(t, userProps[\"focalboard_onboardingTourStarted\"].(bool))\n\t\trequire.Contains(t, userProps, \"focalboard_version72MessageCanceled\")\n\t\trequire.True(t, userProps[\"focalboard_version72MessageCanceled\"].(bool))\n\t\trequire.Contains(t, userProps, \"focalboard_lastWelcomeVersion\")\n\t\trequire.Equal(t, float64(7), userProps[\"focalboard_lastWelcomeVersion\"])\n\n\t\t// we apply the migration\n\t\tth.f.MigrateToStep(27)\n\n\t\t// then we load the preferences on a new struct\n\t\tuserPreferences := []struct {\n\t\t\tName  string\n\t\t\tValue string\n\t\t}{}\n\n\t\tnErr := th.f.DB().Select(&userPreferences, \"SELECT name, value FROM focalboard_preferences WHERE UserId = 'user-id'\")\n\t\trequire.NoError(t, nErr)\n\n\t\t// helper function to quickly get a preference value from the\n\t\t// userPreferences slice\n\t\tgetValue := func(name string) string {\n\t\t\tfor _, userPreference := range userPreferences {\n\t\t\t\tif userPreference.Name == name {\n\t\t\t\t\treturn userPreference.Value\n\t\t\t\t}\n\t\t\t}\n\t\t\trequire.FailNow(t, \"could not found preference\", \"while searching for name %q\", name)\n\t\t\treturn \"this should never be reached\"\n\t\t}\n\n\t\t// and we check that the values are correct\n\t\twelcomePageViewedValue := getValue(\"welcomePageViewed\")\n\t\t// the checks for true or 1 make the test work for all DBs,\n\t\t// that were representing the boolean values in the JSON\n\t\t// struct in different ways\n\t\trequire.True(t, welcomePageViewedValue == \"true\" || welcomePageViewedValue == \"1\")\n\n\t\thiddenBoardIDsValue := getValue(\"hiddenBoardIDs\")\n\t\trequire.Contains(t, hiddenBoardIDsValue, \"board1\")\n\t\trequire.Contains(t, hiddenBoardIDsValue, \"board2\")\n\n\t\trequire.Equal(t, \"onboarding\", getValue(\"tourCategory\"))\n\n\t\tonboardingTourStepValue := getValue(\"onboardingTourStep\")\n\t\trequire.True(t, onboardingTourStepValue == \"true\" || onboardingTourStepValue == \"1\")\n\n\t\tonboardingTourStartedValue := getValue(\"onboardingTourStarted\")\n\t\trequire.True(t, onboardingTourStartedValue == \"true\" || onboardingTourStartedValue == \"1\")\n\n\t\tversion72MessageCanceledValue := getValue(\"version72MessageCanceled\")\n\t\trequire.True(t, version72MessageCanceledValue == \"true\" || version72MessageCanceledValue == \"1\")\n\n\t\trequire.Equal(t, \"7\", getValue(\"lastWelcomeVersion\"))\n\t})\n}\n"
  },
  {
    "path": "server/services/store/sqlstore/migrationstests/migration_28_test.go",
    "content": "package migrationstests\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test28RemoveTemplateChannelLink(t *testing.T) {\n\tt.Run(\"should correctly remove the channel link from templates\", func(t *testing.T) {\n\t\tth, tearDown := SetupTestHelper(t)\n\t\tdefer tearDown()\n\n\t\tth.f.MigrateToStep(27).\n\t\t\tExecFile(\"./fixtures/test28RemoveTemplateChannelLink.sql\")\n\n\t\t// first we check that the data has the expected shape\n\t\tboard := struct {\n\t\t\tID          string\n\t\t\tIs_template bool\n\t\t\tChannel_id  string\n\t\t}{}\n\n\t\ttemplate := struct {\n\t\t\tID          string\n\t\t\tIs_template bool\n\t\t\tChannel_id  string\n\t\t}{}\n\n\t\tbErr := th.f.DB().Get(&board, \"SELECT id, is_template, channel_id FROM focalboard_boards WHERE id = 'board-id'\")\n\t\trequire.NoError(t, bErr)\n\t\trequire.False(t, board.Is_template)\n\t\trequire.Equal(t, \"linked-channel\", board.Channel_id)\n\n\t\ttErr := th.f.DB().Get(&template, \"SELECT id, is_template, channel_id FROM focalboard_boards WHERE id = 'template-id'\")\n\t\trequire.NoError(t, tErr)\n\t\trequire.True(t, template.Is_template)\n\t\trequire.Equal(t, \"linked-channel\", template.Channel_id)\n\n\t\t// we apply the migration\n\t\tth.f.MigrateToStep(28)\n\n\t\t// then we reuse the structs to load again the data and check\n\t\t// that the changes were correctly applied\n\t\tbErr = th.f.DB().Get(&board, \"SELECT id, is_template, channel_id FROM focalboard_boards WHERE id = 'board-id'\")\n\t\trequire.NoError(t, bErr)\n\t\trequire.False(t, board.Is_template)\n\t\trequire.Equal(t, \"linked-channel\", board.Channel_id)\n\n\t\ttErr = th.f.DB().Get(&template, \"SELECT id, is_template, channel_id FROM focalboard_boards WHERE id = 'template-id'\")\n\t\trequire.NoError(t, tErr)\n\t\trequire.True(t, template.Is_template)\n\t\trequire.Empty(t, template.Channel_id)\n\t})\n}\n"
  },
  {
    "path": "server/services/store/sqlstore/migrationstests/migration_33_test.go",
    "content": "package migrationstests\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test33RemoveDeletedCategoryBoards(t *testing.T) {\n\tt.Run(\"base case - no data in table\", func(t *testing.T) {\n\t\tth, tearDown := SetupTestHelper(t)\n\t\tdefer tearDown()\n\t\tth.f.MigrateToStep(33)\n\t})\n\n\tt.Run(\"existing data - 2 soft deleted records\", func(t *testing.T) {\n\t\tth, tearDown := SetupTestHelper(t)\n\t\tdefer tearDown()\n\n\t\tth.f.MigrateToStep(32).\n\t\t\tExecFile(\"./fixtures/test33_with_deleted_data.sql\")\n\n\t\t// cound total records\n\t\tvar count int\n\t\terr := th.f.DB().Get(&count, \"SELECT COUNT(*) FROM focalboard_category_boards\")\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 5, count)\n\n\t\t// now we run the migration\n\t\tth.f.MigrateToStep(33)\n\n\t\t// and verify record count again.\n\t\t// The soft deleted records should have been removed from the DB now\n\t\terr = th.f.DB().Get(&count, \"SELECT COUNT(*) FROM focalboard_category_boards\")\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 3, count)\n\t})\n\n\tt.Run(\"existing data - no soft deleted records\", func(t *testing.T) {\n\t\tth, tearDown := SetupTestHelper(t)\n\t\tdefer tearDown()\n\n\t\tth.f.MigrateToStep(32).\n\t\t\tExecFile(\"./fixtures/test33_with_no_deleted_data.sql\")\n\n\t\t// cound total records\n\t\tvar count int\n\t\terr := th.f.DB().Get(&count, \"SELECT COUNT(*) FROM focalboard_category_boards\")\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 5, count)\n\n\t\t// now we run the migration\n\t\tth.f.MigrateToStep(33)\n\n\t\t// and verify record count again.\n\t\t// Since there were no soft-deleted records, nothing should have been\n\t\t// deleted from the database.\n\t\terr = th.f.DB().Get(&count, \"SELECT COUNT(*) FROM focalboard_category_boards\")\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 5, count)\n\n\t})\n}\n"
  },
  {
    "path": "server/services/store/sqlstore/notificationhints.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage sqlstore\n\nimport (\n\t\"database/sql\"\n\t\"fmt\"\n\t\"time\"\n\n\tsq \"github.com/Masterminds/squirrel\"\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nvar notificationHintFields = []string{\n\t\"block_type\",\n\t\"block_id\",\n\t\"modified_by_id\",\n\t\"create_at\",\n\t\"notify_at\",\n}\n\nfunc valuesForNotificationHint(hint *model.NotificationHint) []interface{} {\n\treturn []interface{}{\n\t\thint.BlockType,\n\t\thint.BlockID,\n\t\thint.ModifiedByID,\n\t\thint.CreateAt,\n\t\thint.NotifyAt,\n\t}\n}\n\nfunc (s *SQLStore) notificationHintFromRows(rows *sql.Rows) ([]*model.NotificationHint, error) {\n\thints := []*model.NotificationHint{}\n\n\tfor rows.Next() {\n\t\tvar hint model.NotificationHint\n\t\terr := rows.Scan(\n\t\t\t&hint.BlockType,\n\t\t\t&hint.BlockID,\n\t\t\t&hint.ModifiedByID,\n\t\t\t&hint.CreateAt,\n\t\t\t&hint.NotifyAt,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\thints = append(hints, &hint)\n\t}\n\treturn hints, nil\n}\n\n// upsertNotificationHint creates or updates a notification hint. When updating the `notify_at` is set\n// to the current time plus `notifyFreq`.\nfunc (s *SQLStore) upsertNotificationHint(db sq.BaseRunner, hint *model.NotificationHint, notifyFreq time.Duration) (*model.NotificationHint, error) {\n\tif err := hint.IsValid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\thint.CreateAt = utils.GetMillis()\n\n\tnotifyAt := utils.GetMillisForTime(time.Now().Add(notifyFreq))\n\thint.NotifyAt = notifyAt\n\n\tquery := s.getQueryBuilder(db).Insert(s.tablePrefix + \"notification_hints\").\n\t\tColumns(notificationHintFields...).\n\t\tValues(valuesForNotificationHint(hint)...)\n\n\tif s.dbType == model.MysqlDBType {\n\t\tquery = query.Suffix(\"ON DUPLICATE KEY UPDATE notify_at = ?\", notifyAt)\n\t} else {\n\t\tquery = query.Suffix(\"ON CONFLICT (block_id) DO UPDATE SET notify_at = ?\", notifyAt)\n\t}\n\n\tif _, err := query.Exec(); err != nil {\n\t\ts.logger.Error(\"Cannot upsert notification hint\",\n\t\t\tmlog.String(\"block_id\", hint.BlockID),\n\t\t\tmlog.Err(err),\n\t\t)\n\t\treturn nil, err\n\t}\n\treturn hint, nil\n}\n\n// deleteNotificationHint deletes the notification hint for the specified block.\nfunc (s *SQLStore) deleteNotificationHint(db sq.BaseRunner, blockID string) error {\n\tquery := s.getQueryBuilder(db).\n\t\tDelete(s.tablePrefix + \"notification_hints\").\n\t\tWhere(sq.Eq{\"block_id\": blockID})\n\n\tresult, err := query.Exec()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcount, err := result.RowsAffected()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif count == 0 {\n\t\treturn model.NewErrNotFound(\"notification hint BlockID=\" + blockID)\n\t}\n\n\treturn nil\n}\n\n// getNotificationHint fetches the notification hint for the specified block.\nfunc (s *SQLStore) getNotificationHint(db sq.BaseRunner, blockID string) (*model.NotificationHint, error) {\n\tquery := s.getQueryBuilder(db).\n\t\tSelect(notificationHintFields...).\n\t\tFrom(s.tablePrefix + \"notification_hints\").\n\t\tWhere(sq.Eq{\"block_id\": blockID})\n\n\trows, err := query.Query()\n\tif err != nil {\n\t\ts.logger.Error(\"Cannot fetch notification hint\",\n\t\t\tmlog.String(\"block_id\", blockID),\n\t\t\tmlog.Err(err),\n\t\t)\n\t\treturn nil, err\n\t}\n\tdefer s.CloseRows(rows)\n\n\thint, err := s.notificationHintFromRows(rows)\n\tif err != nil {\n\t\ts.logger.Error(\"Cannot get notification hint\",\n\t\t\tmlog.String(\"block_id\", blockID),\n\t\t\tmlog.Err(err),\n\t\t)\n\t\treturn nil, err\n\t}\n\tif len(hint) == 0 {\n\t\treturn nil, model.NewErrNotFound(\"notification hint BlockID=\" + blockID)\n\t}\n\treturn hint[0], nil\n}\n\n// getNextNotificationHint fetches the next scheduled notification hint. If remove is true\n// then the hint is removed from the database as well, as if popping from a stack.\nfunc (s *SQLStore) getNextNotificationHint(db sq.BaseRunner, remove bool) (*model.NotificationHint, error) {\n\tselectQuery := s.getQueryBuilder(db).\n\t\tSelect(notificationHintFields...).\n\t\tFrom(s.tablePrefix + \"notification_hints\").\n\t\tOrderBy(\"notify_at\").\n\t\tLimit(1)\n\n\trows, err := selectQuery.Query()\n\tif err != nil {\n\t\ts.logger.Error(\"Cannot fetch next notification hint\",\n\t\t\tmlog.Err(err),\n\t\t)\n\t\treturn nil, err\n\t}\n\tdefer s.CloseRows(rows)\n\n\thints, err := s.notificationHintFromRows(rows)\n\tif err != nil {\n\t\ts.logger.Error(\"Cannot get next notification hint\",\n\t\t\tmlog.Err(err),\n\t\t)\n\t\treturn nil, err\n\t}\n\tif len(hints) == 0 {\n\t\treturn nil, model.NewErrNotFound(\"next notification hint\")\n\t}\n\n\thint := hints[0]\n\n\tif remove {\n\t\tdeleteQuery := s.getQueryBuilder(db).\n\t\t\tDelete(s.tablePrefix + \"notification_hints\").\n\t\t\tWhere(sq.Eq{\"block_id\": hint.BlockID})\n\n\t\tresult, err := deleteQuery.Exec()\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"cannot delete while getting next notification hint: %w\", err)\n\t\t}\n\t\trows, err := result.RowsAffected()\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"cannot verify delete while getting next notification hint: %w\", err)\n\t\t}\n\t\tif rows == 0 {\n\t\t\t// another node likely has grabbed this hint for processing concurrently; let that node handle it\n\t\t\t// and we'll return an error here so we try again.\n\t\t\treturn nil, model.NewErrNotFound(\"notification hint\")\n\t\t}\n\t}\n\n\treturn hint, nil\n}\n"
  },
  {
    "path": "server/services/store/sqlstore/params.go",
    "content": "package sqlstore\n\nimport (\n\t\"database/sql\"\n\t\"fmt\"\n\n\tmmModel \"github.com/mattermost/mattermost/server/public/model\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\n// servicesAPI is the interface required my the Params to interact with the mattermost-server.\n// You can use plugin-api or product-api adapter implementations.\ntype servicesAPI interface {\n\tGetChannelByID(string) (*mmModel.Channel, error)\n}\n\ntype Params struct {\n\tDBType           string\n\tConnectionString string\n\tDBPingAttempts   int\n\tTablePrefix      string\n\tLogger           mlog.LoggerIFace\n\tDB               *sql.DB\n\tIsSingleUser     bool\n\tNewMutexFn       MutexFactory\n\tServicesAPI      servicesAPI\n\tSkipMigrations   bool\n\tConfigFn         func() *mmModel.Config\n}\n\ntype ErrStoreParam struct {\n\tname  string\n\tissue string\n}\n\nfunc (e ErrStoreParam) Error() string {\n\treturn fmt.Sprintf(\"invalid store params: %s %s\", e.name, e.issue)\n}\n"
  },
  {
    "path": "server/services/store/sqlstore/public_methods.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\n// Code generated by \"make generate\" from the Store interface\n// DO NOT EDIT\n\n// To add a public method, create an entry in the Store interface,\n// prefix it with a @withTransaction comment if you need it to be\n// transactional and then add a private method in the store itself\n// with db sq.BaseRunner as the first parameter before running `make\n// generate`\n\npackage sqlstore\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\n\tmmModel \"github.com/mattermost/mattermost/server/public/model\"\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nfunc (s *SQLStore) AddUpdateCategoryBoard(userID string, categoryID string, boardIDs []string) error {\n\tif s.dbType == model.SqliteDBType {\n\t\treturn s.addUpdateCategoryBoard(s.db, userID, categoryID, boardIDs)\n\t}\n\ttx, txErr := s.db.BeginTx(context.Background(), nil)\n\tif txErr != nil {\n\t\treturn txErr\n\t}\n\terr := s.addUpdateCategoryBoard(tx, userID, categoryID, boardIDs)\n\tif err != nil {\n\t\tif rollbackErr := tx.Rollback(); rollbackErr != nil {\n\t\t\ts.logger.Error(\"transaction rollback error\", mlog.Err(rollbackErr), mlog.String(\"methodName\", \"AddUpdateCategoryBoard\"))\n\t\t}\n\t\treturn err\n\t}\n\n\tif err := tx.Commit(); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n\n}\n\nfunc (s *SQLStore) CanSeeUser(seerID string, seenID string) (bool, error) {\n\treturn s.canSeeUser(s.db, seerID, seenID)\n\n}\n\nfunc (s *SQLStore) CleanUpSessions(expireTime int64) error {\n\treturn s.cleanUpSessions(s.db, expireTime)\n\n}\n\nfunc (s *SQLStore) CreateBoardsAndBlocks(bab *model.BoardsAndBlocks, userID string) (*model.BoardsAndBlocks, error) {\n\tif s.dbType == model.SqliteDBType {\n\t\treturn s.createBoardsAndBlocks(s.db, bab, userID)\n\t}\n\ttx, txErr := s.db.BeginTx(context.Background(), nil)\n\tif txErr != nil {\n\t\treturn nil, txErr\n\t}\n\tresult, err := s.createBoardsAndBlocks(tx, bab, userID)\n\tif err != nil {\n\t\tif rollbackErr := tx.Rollback(); rollbackErr != nil {\n\t\t\ts.logger.Error(\"transaction rollback error\", mlog.Err(rollbackErr), mlog.String(\"methodName\", \"CreateBoardsAndBlocks\"))\n\t\t}\n\t\treturn nil, err\n\t}\n\n\tif err := tx.Commit(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn result, nil\n\n}\n\nfunc (s *SQLStore) CreateBoardsAndBlocksWithAdmin(bab *model.BoardsAndBlocks, userID string) (*model.BoardsAndBlocks, []*model.BoardMember, error) {\n\tif s.dbType == model.SqliteDBType {\n\t\treturn s.createBoardsAndBlocksWithAdmin(s.db, bab, userID)\n\t}\n\ttx, txErr := s.db.BeginTx(context.Background(), nil)\n\tif txErr != nil {\n\t\treturn nil, nil, txErr\n\t}\n\tresult, resultVar1, err := s.createBoardsAndBlocksWithAdmin(tx, bab, userID)\n\tif err != nil {\n\t\tif rollbackErr := tx.Rollback(); rollbackErr != nil {\n\t\t\ts.logger.Error(\"transaction rollback error\", mlog.Err(rollbackErr), mlog.String(\"methodName\", \"CreateBoardsAndBlocksWithAdmin\"))\n\t\t}\n\t\treturn nil, nil, err\n\t}\n\n\tif err := tx.Commit(); err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\treturn result, resultVar1, nil\n\n}\n\nfunc (s *SQLStore) CreateCategory(category model.Category) error {\n\tif s.dbType == model.SqliteDBType {\n\t\treturn s.createCategory(s.db, category)\n\t}\n\ttx, txErr := s.db.BeginTx(context.Background(), nil)\n\tif txErr != nil {\n\t\treturn txErr\n\t}\n\terr := s.createCategory(tx, category)\n\tif err != nil {\n\t\tif rollbackErr := tx.Rollback(); rollbackErr != nil {\n\t\t\ts.logger.Error(\"transaction rollback error\", mlog.Err(rollbackErr), mlog.String(\"methodName\", \"CreateCategory\"))\n\t\t}\n\t\treturn err\n\t}\n\n\tif err := tx.Commit(); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n\n}\n\nfunc (s *SQLStore) CreateSession(session *model.Session) error {\n\treturn s.createSession(s.db, session)\n\n}\n\nfunc (s *SQLStore) CreateSubscription(sub *model.Subscription) (*model.Subscription, error) {\n\treturn s.createSubscription(s.db, sub)\n\n}\n\nfunc (s *SQLStore) CreateUser(user *model.User) (*model.User, error) {\n\treturn s.createUser(s.db, user)\n\n}\n\nfunc (s *SQLStore) DeleteBlock(blockID string, modifiedBy string) error {\n\tif s.dbType == model.SqliteDBType {\n\t\treturn s.deleteBlock(s.db, blockID, modifiedBy)\n\t}\n\ttx, txErr := s.db.BeginTx(context.Background(), nil)\n\tif txErr != nil {\n\t\treturn txErr\n\t}\n\terr := s.deleteBlock(tx, blockID, modifiedBy)\n\tif err != nil {\n\t\tif rollbackErr := tx.Rollback(); rollbackErr != nil {\n\t\t\ts.logger.Error(\"transaction rollback error\", mlog.Err(rollbackErr), mlog.String(\"methodName\", \"DeleteBlock\"))\n\t\t}\n\t\treturn err\n\t}\n\n\tif err := tx.Commit(); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n\n}\n\nfunc (s *SQLStore) DeleteBlockRecord(blockID string, modifiedBy string) error {\n\treturn s.deleteBlockRecord(s.db, blockID, modifiedBy)\n\n}\n\nfunc (s *SQLStore) DeleteBoard(boardID string, userID string) error {\n\tif s.dbType == model.SqliteDBType {\n\t\treturn s.deleteBoard(s.db, boardID, userID)\n\t}\n\ttx, txErr := s.db.BeginTx(context.Background(), nil)\n\tif txErr != nil {\n\t\treturn txErr\n\t}\n\terr := s.deleteBoard(tx, boardID, userID)\n\tif err != nil {\n\t\tif rollbackErr := tx.Rollback(); rollbackErr != nil {\n\t\t\ts.logger.Error(\"transaction rollback error\", mlog.Err(rollbackErr), mlog.String(\"methodName\", \"DeleteBoard\"))\n\t\t}\n\t\treturn err\n\t}\n\n\tif err := tx.Commit(); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n\n}\n\nfunc (s *SQLStore) DeleteBoardRecord(boardID string, modifiedBy string) error {\n\treturn s.deleteBoardRecord(s.db, boardID, modifiedBy)\n\n}\n\nfunc (s *SQLStore) DeleteBoardsAndBlocks(dbab *model.DeleteBoardsAndBlocks, userID string) error {\n\tif s.dbType == model.SqliteDBType {\n\t\treturn s.deleteBoardsAndBlocks(s.db, dbab, userID)\n\t}\n\ttx, txErr := s.db.BeginTx(context.Background(), nil)\n\tif txErr != nil {\n\t\treturn txErr\n\t}\n\terr := s.deleteBoardsAndBlocks(tx, dbab, userID)\n\tif err != nil {\n\t\tif rollbackErr := tx.Rollback(); rollbackErr != nil {\n\t\t\ts.logger.Error(\"transaction rollback error\", mlog.Err(rollbackErr), mlog.String(\"methodName\", \"DeleteBoardsAndBlocks\"))\n\t\t}\n\t\treturn err\n\t}\n\n\tif err := tx.Commit(); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n\n}\n\nfunc (s *SQLStore) DeleteCategory(categoryID string, userID string, teamID string) error {\n\treturn s.deleteCategory(s.db, categoryID, userID, teamID)\n\n}\n\nfunc (s *SQLStore) DeleteMember(boardID string, userID string) error {\n\treturn s.deleteMember(s.db, boardID, userID)\n\n}\n\nfunc (s *SQLStore) DeleteNotificationHint(blockID string) error {\n\treturn s.deleteNotificationHint(s.db, blockID)\n\n}\n\nfunc (s *SQLStore) DeleteSession(sessionID string) error {\n\treturn s.deleteSession(s.db, sessionID)\n\n}\n\nfunc (s *SQLStore) DeleteSubscription(blockID string, subscriberID string) error {\n\treturn s.deleteSubscription(s.db, blockID, subscriberID)\n\n}\n\nfunc (s *SQLStore) DuplicateBlock(boardID string, blockID string, userID string, asTemplate bool) ([]*model.Block, error) {\n\tif s.dbType == model.SqliteDBType {\n\t\treturn s.duplicateBlock(s.db, boardID, blockID, userID, asTemplate)\n\t}\n\ttx, txErr := s.db.BeginTx(context.Background(), nil)\n\tif txErr != nil {\n\t\treturn nil, txErr\n\t}\n\tresult, err := s.duplicateBlock(tx, boardID, blockID, userID, asTemplate)\n\tif err != nil {\n\t\tif rollbackErr := tx.Rollback(); rollbackErr != nil {\n\t\t\ts.logger.Error(\"transaction rollback error\", mlog.Err(rollbackErr), mlog.String(\"methodName\", \"DuplicateBlock\"))\n\t\t}\n\t\treturn nil, err\n\t}\n\n\tif err := tx.Commit(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn result, nil\n\n}\n\nfunc (s *SQLStore) DuplicateBoard(boardID string, userID string, toTeam string, asTemplate bool) (*model.BoardsAndBlocks, []*model.BoardMember, error) {\n\tif s.dbType == model.SqliteDBType {\n\t\treturn s.duplicateBoard(s.db, boardID, userID, toTeam, asTemplate)\n\t}\n\ttx, txErr := s.db.BeginTx(context.Background(), nil)\n\tif txErr != nil {\n\t\treturn nil, nil, txErr\n\t}\n\tresult, resultVar1, err := s.duplicateBoard(tx, boardID, userID, toTeam, asTemplate)\n\tif err != nil {\n\t\tif rollbackErr := tx.Rollback(); rollbackErr != nil {\n\t\t\ts.logger.Error(\"transaction rollback error\", mlog.Err(rollbackErr), mlog.String(\"methodName\", \"DuplicateBoard\"))\n\t\t}\n\t\treturn nil, nil, err\n\t}\n\n\tif err := tx.Commit(); err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\treturn result, resultVar1, nil\n\n}\n\nfunc (s *SQLStore) GetActiveUserCount(updatedSecondsAgo int64) (int, error) {\n\treturn s.getActiveUserCount(s.db, updatedSecondsAgo)\n\n}\n\nfunc (s *SQLStore) GetAllTeams() ([]*model.Team, error) {\n\treturn s.getAllTeams(s.db)\n\n}\n\nfunc (s *SQLStore) GetBlock(blockID string) (*model.Block, error) {\n\treturn s.getBlock(s.db, blockID)\n\n}\n\nfunc (s *SQLStore) GetBlockCountsByType() (map[string]int64, error) {\n\treturn s.getBlockCountsByType(s.db)\n\n}\n\nfunc (s *SQLStore) GetBlockHistory(blockID string, opts model.QueryBlockHistoryOptions) ([]*model.Block, error) {\n\treturn s.getBlockHistory(s.db, blockID, opts)\n\n}\n\nfunc (s *SQLStore) GetBlockHistoryDescendants(boardID string, opts model.QueryBlockHistoryOptions) ([]*model.Block, error) {\n\treturn s.getBlockHistoryDescendants(s.db, boardID, opts)\n\n}\n\nfunc (s *SQLStore) GetBlockHistoryNewestChildren(parentID string, opts model.QueryBlockHistoryChildOptions) ([]*model.Block, bool, error) {\n\treturn s.getBlockHistoryNewestChildren(s.db, parentID, opts)\n\n}\n\nfunc (s *SQLStore) GetBlocks(opts model.QueryBlocksOptions) ([]*model.Block, error) {\n\treturn s.getBlocks(s.db, opts)\n\n}\n\nfunc (s *SQLStore) GetBlocksByIDs(ids []string) ([]*model.Block, error) {\n\treturn s.getBlocksByIDs(s.db, ids)\n\n}\n\nfunc (s *SQLStore) GetBlocksComplianceHistory(opts model.QueryBlocksComplianceHistoryOptions) ([]*model.BlockHistory, bool, error) {\n\treturn s.getBlocksComplianceHistory(s.db, opts)\n\n}\n\nfunc (s *SQLStore) GetBlocksForBoard(boardID string) ([]*model.Block, error) {\n\treturn s.getBlocksForBoard(s.db, boardID)\n\n}\n\nfunc (s *SQLStore) GetBlocksWithParent(boardID string, parentID string) ([]*model.Block, error) {\n\treturn s.getBlocksWithParent(s.db, boardID, parentID)\n\n}\n\nfunc (s *SQLStore) GetBlocksWithParentAndType(boardID string, parentID string, blockType string) ([]*model.Block, error) {\n\treturn s.getBlocksWithParentAndType(s.db, boardID, parentID, blockType)\n\n}\n\nfunc (s *SQLStore) GetBlocksWithType(boardID string, blockType string) ([]*model.Block, error) {\n\treturn s.getBlocksWithType(s.db, boardID, blockType)\n\n}\n\nfunc (s *SQLStore) GetBoard(id string) (*model.Board, error) {\n\treturn s.getBoard(s.db, id)\n\n}\n\nfunc (s *SQLStore) GetBoardAndCard(block *model.Block) (*model.Board, *model.Block, error) {\n\treturn s.getBoardAndCard(s.db, block)\n\n}\n\nfunc (s *SQLStore) GetBoardAndCardByID(blockID string) (*model.Board, *model.Block, error) {\n\treturn s.getBoardAndCardByID(s.db, blockID)\n\n}\n\nfunc (s *SQLStore) GetBoardCount() (int64, error) {\n\treturn s.getBoardCount(s.db)\n\n}\n\nfunc (s *SQLStore) GetBoardHistory(boardID string, opts model.QueryBoardHistoryOptions) ([]*model.Board, error) {\n\treturn s.getBoardHistory(s.db, boardID, opts)\n\n}\n\nfunc (s *SQLStore) GetBoardMemberHistory(boardID string, userID string, limit uint64) ([]*model.BoardMemberHistoryEntry, error) {\n\treturn s.getBoardMemberHistory(s.db, boardID, userID, limit)\n\n}\n\nfunc (s *SQLStore) GetBoardsComplianceHistory(opts model.QueryBoardsComplianceHistoryOptions) ([]*model.BoardHistory, bool, error) {\n\treturn s.getBoardsComplianceHistory(s.db, opts)\n\n}\n\nfunc (s *SQLStore) GetBoardsForCompliance(opts model.QueryBoardsForComplianceOptions) ([]*model.Board, bool, error) {\n\treturn s.getBoardsForCompliance(s.db, opts)\n\n}\n\nfunc (s *SQLStore) GetBoardsForUserAndTeam(userID string, teamID string, includePublicBoards bool) ([]*model.Board, error) {\n\treturn s.getBoardsForUserAndTeam(s.db, userID, teamID, includePublicBoards)\n\n}\n\nfunc (s *SQLStore) GetBoardsInTeamByIds(boardIDs []string, teamID string) ([]*model.Board, error) {\n\treturn s.getBoardsInTeamByIds(s.db, boardIDs, teamID)\n\n}\n\nfunc (s *SQLStore) GetCardLimitTimestamp() (int64, error) {\n\treturn s.getCardLimitTimestamp(s.db)\n\n}\n\nfunc (s *SQLStore) GetCategory(id string) (*model.Category, error) {\n\treturn s.getCategory(s.db, id)\n\n}\n\nfunc (s *SQLStore) GetChannel(teamID string, channelID string) (*mmModel.Channel, error) {\n\treturn s.getChannel(s.db, teamID, channelID)\n\n}\n\nfunc (s *SQLStore) GetFileInfo(id string) (*mmModel.FileInfo, error) {\n\treturn s.getFileInfo(s.db, id)\n\n}\n\nfunc (s *SQLStore) GetLicense() *mmModel.License {\n\treturn s.getLicense(s.db)\n\n}\n\nfunc (s *SQLStore) GetMemberForBoard(boardID string, userID string) (*model.BoardMember, error) {\n\treturn s.getMemberForBoard(s.db, boardID, userID)\n\n}\n\nfunc (s *SQLStore) GetMembersForBoard(boardID string) ([]*model.BoardMember, error) {\n\treturn s.getMembersForBoard(s.db, boardID)\n\n}\n\nfunc (s *SQLStore) GetMembersForUser(userID string) ([]*model.BoardMember, error) {\n\treturn s.getMembersForUser(s.db, userID)\n\n}\n\nfunc (s *SQLStore) GetNextNotificationHint(remove bool) (*model.NotificationHint, error) {\n\treturn s.getNextNotificationHint(s.db, remove)\n\n}\n\nfunc (s *SQLStore) GetNotificationHint(blockID string) (*model.NotificationHint, error) {\n\treturn s.getNotificationHint(s.db, blockID)\n\n}\n\nfunc (s *SQLStore) GetRegisteredUserCount() (int, error) {\n\treturn s.getRegisteredUserCount(s.db)\n\n}\n\nfunc (s *SQLStore) GetSession(token string, expireTime int64) (*model.Session, error) {\n\treturn s.getSession(s.db, token, expireTime)\n\n}\n\nfunc (s *SQLStore) GetSharing(rootID string) (*model.Sharing, error) {\n\treturn s.getSharing(s.db, rootID)\n\n}\n\nfunc (s *SQLStore) GetSubTree2(boardID string, blockID string, opts model.QuerySubtreeOptions) ([]*model.Block, error) {\n\treturn s.getSubTree2(s.db, boardID, blockID, opts)\n\n}\n\nfunc (s *SQLStore) GetSubscribersCountForBlock(blockID string) (int, error) {\n\treturn s.getSubscribersCountForBlock(s.db, blockID)\n\n}\n\nfunc (s *SQLStore) GetSubscribersForBlock(blockID string) ([]*model.Subscriber, error) {\n\treturn s.getSubscribersForBlock(s.db, blockID)\n\n}\n\nfunc (s *SQLStore) GetSubscription(blockID string, subscriberID string) (*model.Subscription, error) {\n\treturn s.getSubscription(s.db, blockID, subscriberID)\n\n}\n\nfunc (s *SQLStore) GetSubscriptions(subscriberID string) ([]*model.Subscription, error) {\n\treturn s.getSubscriptions(s.db, subscriberID)\n\n}\n\nfunc (s *SQLStore) GetSystemSetting(key string) (string, error) {\n\treturn s.getSystemSetting(s.db, key)\n\n}\n\nfunc (s *SQLStore) GetSystemSettings() (map[string]string, error) {\n\treturn s.getSystemSettings(s.db)\n\n}\n\nfunc (s *SQLStore) GetTeam(ID string) (*model.Team, error) {\n\treturn s.getTeam(s.db, ID)\n\n}\n\nfunc (s *SQLStore) GetTeamCount() (int64, error) {\n\treturn s.getTeamCount(s.db)\n\n}\n\nfunc (s *SQLStore) GetTeamsForUser(userID string) ([]*model.Team, error) {\n\treturn s.getTeamsForUser(s.db, userID)\n\n}\n\nfunc (s *SQLStore) GetTemplateBoards(teamID string, userID string) ([]*model.Board, error) {\n\treturn s.getTemplateBoards(s.db, teamID, userID)\n\n}\n\nfunc (s *SQLStore) GetUsedCardsCount() (int, error) {\n\treturn s.getUsedCardsCount(s.db)\n\n}\n\nfunc (s *SQLStore) GetUserByEmail(email string) (*model.User, error) {\n\treturn s.getUserByEmail(s.db, email)\n\n}\n\nfunc (s *SQLStore) GetUserByID(userID string) (*model.User, error) {\n\treturn s.getUserByID(s.db, userID)\n\n}\n\nfunc (s *SQLStore) GetUserByUsername(username string) (*model.User, error) {\n\treturn s.getUserByUsername(s.db, username)\n\n}\n\nfunc (s *SQLStore) GetUserCategories(userID string, teamID string) ([]model.Category, error) {\n\treturn s.getUserCategories(s.db, userID, teamID)\n\n}\n\nfunc (s *SQLStore) GetUserCategoryBoards(userID string, teamID string) ([]model.CategoryBoards, error) {\n\treturn s.getUserCategoryBoards(s.db, userID, teamID)\n\n}\n\nfunc (s *SQLStore) GetUserPreferences(userID string) (mmModel.Preferences, error) {\n\treturn s.getUserPreferences(s.db, userID)\n\n}\n\nfunc (s *SQLStore) GetUserTimezone(userID string) (string, error) {\n\treturn s.getUserTimezone(s.db, userID)\n\n}\n\nfunc (s *SQLStore) GetUsersByTeam(teamID string, asGuestID string, showEmail bool, showName bool) ([]*model.User, error) {\n\treturn s.getUsersByTeam(s.db, teamID, asGuestID, showEmail, showName)\n\n}\n\nfunc (s *SQLStore) GetUsersList(userIDs []string, showEmail bool, showName bool) ([]*model.User, error) {\n\treturn s.getUsersList(s.db, userIDs, showEmail, showName)\n\n}\n\nfunc (s *SQLStore) InsertBlock(block *model.Block, userID string) error {\n\tif s.dbType == model.SqliteDBType {\n\t\treturn s.insertBlock(s.db, block, userID)\n\t}\n\ttx, txErr := s.db.BeginTx(context.Background(), nil)\n\tif txErr != nil {\n\t\treturn txErr\n\t}\n\terr := s.insertBlock(tx, block, userID)\n\tif err != nil {\n\t\tif rollbackErr := tx.Rollback(); rollbackErr != nil {\n\t\t\ts.logger.Error(\"transaction rollback error\", mlog.Err(rollbackErr), mlog.String(\"methodName\", \"InsertBlock\"))\n\t\t}\n\t\treturn err\n\t}\n\n\tif err := tx.Commit(); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n\n}\n\nfunc (s *SQLStore) InsertBlocks(blocks []*model.Block, userID string) error {\n\tif s.dbType == model.SqliteDBType {\n\t\treturn s.insertBlocks(s.db, blocks, userID)\n\t}\n\ttx, txErr := s.db.BeginTx(context.Background(), nil)\n\tif txErr != nil {\n\t\treturn txErr\n\t}\n\terr := s.insertBlocks(tx, blocks, userID)\n\tif err != nil {\n\t\tif rollbackErr := tx.Rollback(); rollbackErr != nil {\n\t\t\ts.logger.Error(\"transaction rollback error\", mlog.Err(rollbackErr), mlog.String(\"methodName\", \"InsertBlocks\"))\n\t\t}\n\t\treturn err\n\t}\n\n\tif err := tx.Commit(); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n\n}\n\nfunc (s *SQLStore) InsertBoard(board *model.Board, userID string) (*model.Board, error) {\n\treturn s.insertBoard(s.db, board, userID)\n\n}\n\nfunc (s *SQLStore) InsertBoardWithAdmin(board *model.Board, userID string) (*model.Board, *model.BoardMember, error) {\n\tif s.dbType == model.SqliteDBType {\n\t\treturn s.insertBoardWithAdmin(s.db, board, userID)\n\t}\n\ttx, txErr := s.db.BeginTx(context.Background(), nil)\n\tif txErr != nil {\n\t\treturn nil, nil, txErr\n\t}\n\tresult, resultVar1, err := s.insertBoardWithAdmin(tx, board, userID)\n\tif err != nil {\n\t\tif rollbackErr := tx.Rollback(); rollbackErr != nil {\n\t\t\ts.logger.Error(\"transaction rollback error\", mlog.Err(rollbackErr), mlog.String(\"methodName\", \"InsertBoardWithAdmin\"))\n\t\t}\n\t\treturn nil, nil, err\n\t}\n\n\tif err := tx.Commit(); err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\treturn result, resultVar1, nil\n\n}\n\nfunc (s *SQLStore) PatchBlock(blockID string, blockPatch *model.BlockPatch, userID string) error {\n\tif s.dbType == model.SqliteDBType {\n\t\treturn s.patchBlock(s.db, blockID, blockPatch, userID)\n\t}\n\ttx, txErr := s.db.BeginTx(context.Background(), nil)\n\tif txErr != nil {\n\t\treturn txErr\n\t}\n\terr := s.patchBlock(tx, blockID, blockPatch, userID)\n\tif err != nil {\n\t\tif rollbackErr := tx.Rollback(); rollbackErr != nil {\n\t\t\ts.logger.Error(\"transaction rollback error\", mlog.Err(rollbackErr), mlog.String(\"methodName\", \"PatchBlock\"))\n\t\t}\n\t\treturn err\n\t}\n\n\tif err := tx.Commit(); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n\n}\n\nfunc (s *SQLStore) PatchBlocks(blockPatches *model.BlockPatchBatch, userID string) error {\n\tif s.dbType == model.SqliteDBType {\n\t\treturn s.patchBlocks(s.db, blockPatches, userID)\n\t}\n\ttx, txErr := s.db.BeginTx(context.Background(), nil)\n\tif txErr != nil {\n\t\treturn txErr\n\t}\n\terr := s.patchBlocks(tx, blockPatches, userID)\n\tif err != nil {\n\t\tif rollbackErr := tx.Rollback(); rollbackErr != nil {\n\t\t\ts.logger.Error(\"transaction rollback error\", mlog.Err(rollbackErr), mlog.String(\"methodName\", \"PatchBlocks\"))\n\t\t}\n\t\treturn err\n\t}\n\n\tif err := tx.Commit(); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n\n}\n\nfunc (s *SQLStore) PatchBoard(boardID string, boardPatch *model.BoardPatch, userID string) (*model.Board, error) {\n\tif s.dbType == model.SqliteDBType {\n\t\treturn s.patchBoard(s.db, boardID, boardPatch, userID)\n\t}\n\ttx, txErr := s.db.BeginTx(context.Background(), nil)\n\tif txErr != nil {\n\t\treturn nil, txErr\n\t}\n\tresult, err := s.patchBoard(tx, boardID, boardPatch, userID)\n\tif err != nil {\n\t\tif rollbackErr := tx.Rollback(); rollbackErr != nil {\n\t\t\ts.logger.Error(\"transaction rollback error\", mlog.Err(rollbackErr), mlog.String(\"methodName\", \"PatchBoard\"))\n\t\t}\n\t\treturn nil, err\n\t}\n\n\tif err := tx.Commit(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn result, nil\n\n}\n\nfunc (s *SQLStore) PatchBoardsAndBlocks(pbab *model.PatchBoardsAndBlocks, userID string) (*model.BoardsAndBlocks, error) {\n\tif s.dbType == model.SqliteDBType {\n\t\treturn s.patchBoardsAndBlocks(s.db, pbab, userID)\n\t}\n\ttx, txErr := s.db.BeginTx(context.Background(), nil)\n\tif txErr != nil {\n\t\treturn nil, txErr\n\t}\n\tresult, err := s.patchBoardsAndBlocks(tx, pbab, userID)\n\tif err != nil {\n\t\tif rollbackErr := tx.Rollback(); rollbackErr != nil {\n\t\t\ts.logger.Error(\"transaction rollback error\", mlog.Err(rollbackErr), mlog.String(\"methodName\", \"PatchBoardsAndBlocks\"))\n\t\t}\n\t\treturn nil, err\n\t}\n\n\tif err := tx.Commit(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn result, nil\n\n}\n\nfunc (s *SQLStore) PatchUserPreferences(userID string, patch model.UserPreferencesPatch) (mmModel.Preferences, error) {\n\treturn s.patchUserPreferences(s.db, userID, patch)\n\n}\n\nfunc (s *SQLStore) PostMessage(message string, postType string, channelID string) error {\n\treturn s.postMessage(s.db, message, postType, channelID)\n\n}\n\nfunc (s *SQLStore) RefreshSession(session *model.Session) error {\n\treturn s.refreshSession(s.db, session)\n\n}\n\nfunc (s *SQLStore) RemoveDefaultTemplates(boards []*model.Board) error {\n\treturn s.removeDefaultTemplates(s.db, boards)\n\n}\n\nfunc (s *SQLStore) ReorderCategories(userID string, teamID string, newCategoryOrder []string) ([]string, error) {\n\treturn s.reorderCategories(s.db, userID, teamID, newCategoryOrder)\n\n}\n\nfunc (s *SQLStore) ReorderCategoryBoards(categoryID string, newBoardsOrder []string) ([]string, error) {\n\treturn s.reorderCategoryBoards(s.db, categoryID, newBoardsOrder)\n\n}\n\nfunc (s *SQLStore) RunDataRetention(globalRetentionDate int64, batchSize int64) (int64, error) {\n\tif s.dbType == model.SqliteDBType {\n\t\treturn s.runDataRetention(s.db, globalRetentionDate, batchSize)\n\t}\n\ttx, txErr := s.db.BeginTx(context.Background(), nil)\n\tif txErr != nil {\n\t\treturn 0, txErr\n\t}\n\tresult, err := s.runDataRetention(tx, globalRetentionDate, batchSize)\n\tif err != nil {\n\t\tif rollbackErr := tx.Rollback(); rollbackErr != nil {\n\t\t\ts.logger.Error(\"transaction rollback error\", mlog.Err(rollbackErr), mlog.String(\"methodName\", \"RunDataRetention\"))\n\t\t}\n\t\treturn 0, err\n\t}\n\n\tif err := tx.Commit(); err != nil {\n\t\treturn 0, err\n\t}\n\n\treturn result, nil\n\n}\n\nfunc (s *SQLStore) SaveFileInfo(fileInfo *mmModel.FileInfo) error {\n\treturn s.saveFileInfo(s.db, fileInfo)\n\n}\n\nfunc (s *SQLStore) SaveMember(bm *model.BoardMember) (*model.BoardMember, error) {\n\treturn s.saveMember(s.db, bm)\n\n}\n\nfunc (s *SQLStore) SearchBoardsForUser(term string, searchField model.BoardSearchField, userID string, includePublicBoards bool) ([]*model.Board, error) {\n\treturn s.searchBoardsForUser(s.db, term, searchField, userID, includePublicBoards)\n\n}\n\nfunc (s *SQLStore) SearchBoardsForUserInTeam(teamID string, term string, userID string) ([]*model.Board, error) {\n\treturn s.searchBoardsForUserInTeam(s.db, teamID, term, userID)\n\n}\n\nfunc (s *SQLStore) SearchUserChannels(teamID string, userID string, query string) ([]*mmModel.Channel, error) {\n\treturn s.searchUserChannels(s.db, teamID, userID, query)\n\n}\n\nfunc (s *SQLStore) SearchUsersByTeam(teamID string, searchQuery string, asGuestID string, excludeBots bool, showEmail bool, showName bool) ([]*model.User, error) {\n\treturn s.searchUsersByTeam(s.db, teamID, searchQuery, asGuestID, excludeBots, showEmail, showName)\n\n}\n\nfunc (s *SQLStore) SendMessage(message string, postType string, receipts []string) error {\n\treturn s.sendMessage(s.db, message, postType, receipts)\n\n}\n\nfunc (s *SQLStore) SetBoardVisibility(userID string, categoryID string, boardID string, visible bool) error {\n\treturn s.setBoardVisibility(s.db, userID, categoryID, boardID, visible)\n\n}\n\nfunc (s *SQLStore) SetSystemSetting(key string, value string) error {\n\treturn s.setSystemSetting(s.db, key, value)\n\n}\n\nfunc (s *SQLStore) UndeleteBlock(blockID string, modifiedBy string) error {\n\tif s.dbType == model.SqliteDBType {\n\t\treturn s.undeleteBlock(s.db, blockID, modifiedBy)\n\t}\n\ttx, txErr := s.db.BeginTx(context.Background(), nil)\n\tif txErr != nil {\n\t\treturn txErr\n\t}\n\terr := s.undeleteBlock(tx, blockID, modifiedBy)\n\tif err != nil {\n\t\tif rollbackErr := tx.Rollback(); rollbackErr != nil {\n\t\t\ts.logger.Error(\"transaction rollback error\", mlog.Err(rollbackErr), mlog.String(\"methodName\", \"UndeleteBlock\"))\n\t\t}\n\t\treturn err\n\t}\n\n\tif err := tx.Commit(); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n\n}\n\nfunc (s *SQLStore) UndeleteBoard(boardID string, modifiedBy string) error {\n\tif s.dbType == model.SqliteDBType {\n\t\treturn s.undeleteBoard(s.db, boardID, modifiedBy)\n\t}\n\ttx, txErr := s.db.BeginTx(context.Background(), nil)\n\tif txErr != nil {\n\t\treturn txErr\n\t}\n\terr := s.undeleteBoard(tx, boardID, modifiedBy)\n\tif err != nil {\n\t\tif rollbackErr := tx.Rollback(); rollbackErr != nil {\n\t\t\ts.logger.Error(\"transaction rollback error\", mlog.Err(rollbackErr), mlog.String(\"methodName\", \"UndeleteBoard\"))\n\t\t}\n\t\treturn err\n\t}\n\n\tif err := tx.Commit(); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n\n}\n\nfunc (s *SQLStore) UpdateCardLimitTimestamp(cardLimit int) (int64, error) {\n\treturn s.updateCardLimitTimestamp(s.db, cardLimit)\n\n}\n\nfunc (s *SQLStore) UpdateCategory(category model.Category) error {\n\treturn s.updateCategory(s.db, category)\n\n}\n\nfunc (s *SQLStore) UpdateSession(session *model.Session) error {\n\treturn s.updateSession(s.db, session)\n\n}\n\nfunc (s *SQLStore) UpdateSubscribersNotifiedAt(blockID string, notifiedAt int64) error {\n\treturn s.updateSubscribersNotifiedAt(s.db, blockID, notifiedAt)\n\n}\n\nfunc (s *SQLStore) UpdateUser(user *model.User) (*model.User, error) {\n\treturn s.updateUser(s.db, user)\n\n}\n\nfunc (s *SQLStore) UpdateUserPassword(username string, password string) error {\n\treturn s.updateUserPassword(s.db, username, password)\n\n}\n\nfunc (s *SQLStore) UpdateUserPasswordByID(userID string, password string) error {\n\treturn s.updateUserPasswordByID(s.db, userID, password)\n\n}\n\nfunc (s *SQLStore) UpsertNotificationHint(hint *model.NotificationHint, notificationFreq time.Duration) (*model.NotificationHint, error) {\n\treturn s.upsertNotificationHint(s.db, hint, notificationFreq)\n\n}\n\nfunc (s *SQLStore) UpsertSharing(sharing model.Sharing) error {\n\treturn s.upsertSharing(s.db, sharing)\n\n}\n\nfunc (s *SQLStore) UpsertTeamSettings(team model.Team) error {\n\treturn s.upsertTeamSettings(s.db, team)\n\n}\n\nfunc (s *SQLStore) UpsertTeamSignupToken(team model.Team) error {\n\treturn s.upsertTeamSignupToken(s.db, team)\n\n}\n"
  },
  {
    "path": "server/services/store/sqlstore/schema_table_migration.go",
    "content": "package sqlstore\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\n\tsq \"github.com/Masterminds/squirrel\"\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/morph/models\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\n// EnsureSchemaMigrationFormat checks the schema migrations table\n// format and, if it's not using the new shape, it migrates the old\n// one's status before initializing the migrations engine.\nfunc (s *SQLStore) EnsureSchemaMigrationFormat() error {\n\tmigrationNeeded, err := s.isSchemaMigrationNeeded()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !migrationNeeded {\n\t\ts.logger.Info(\"Schema migration table is correct format\")\n\t\treturn nil\n\t}\n\n\ts.logger.Info(\"Migrating schema migration to new format\")\n\n\tlegacySchemaVersion, err := s.getLegacySchemaVersion()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tmigrations, err := getEmbeddedMigrations()\n\tif err != nil {\n\t\treturn err\n\t}\n\tfilteredMigrations := filterMigrations(migrations, legacySchemaVersion)\n\n\tif err := s.createTempSchemaTable(); err != nil {\n\t\treturn err\n\t}\n\n\ts.logger.Info(\"Populating the temporal schema table\", mlog.Uint(\"legacySchemaVersion\", legacySchemaVersion), mlog.Int(\"migrations\", len(filteredMigrations)))\n\n\tif err := s.populateTempSchemaTable(filteredMigrations); err != nil {\n\t\treturn err\n\t}\n\n\tif err := s.useNewSchemaTable(); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// getEmbeddedMigrations returns a list of the embedded migrations\n// using the morph migration format. The migrations do not have the\n// contents set, as the goal is to obtain a list of them.\nfunc getEmbeddedMigrations() ([]*models.Migration, error) {\n\tassetsList, err := Assets.ReadDir(\"migrations\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tmigrations := []*models.Migration{}\n\tfor _, f := range assetsList {\n\t\tm, err := models.NewMigration(io.NopCloser(&bytes.Buffer{}), f.Name())\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif m.Direction != models.Up {\n\t\t\tcontinue\n\t\t}\n\n\t\tmigrations = append(migrations, m)\n\t}\n\n\treturn migrations, nil\n}\n\n// filterMigrations takes the whole list of migrations parsed from the\n// embedded directory and returns a filtered list that only contains\n// one migration per version and those migrations that have already\n// run based on the legacySchemaVersion.\nfunc filterMigrations(migrations []*models.Migration, legacySchemaVersion uint32) []*models.Migration {\n\tfilteredMigrations := []*models.Migration{}\n\tfor _, migration := range migrations {\n\t\t// we only take into account up migrations to avoid duplicates\n\t\tif migration.Direction != models.Up {\n\t\t\tcontinue\n\t\t}\n\n\t\t// we're only interested on registering migrations that\n\t\t// already run, so we skip those above the legacy version\n\t\tif migration.Version > legacySchemaVersion {\n\t\t\tcontinue\n\t\t}\n\n\t\tfilteredMigrations = append(filteredMigrations, migration)\n\t}\n\n\treturn filteredMigrations\n}\n\nfunc (s *SQLStore) isSchemaMigrationNeeded() (bool, error) {\n\t// Check if `name` column exists on schema version table.\n\t// This column exists only for the new schema version table.\n\n\t// SQLite needs a bit of a special handling\n\tif s.dbType == model.SqliteDBType {\n\t\treturn s.isSchemaMigrationNeededSQLite()\n\t}\n\n\tquery := s.getQueryBuilder(s.db).\n\t\tSelect(\"COLUMN_NAME\").\n\t\tFrom(\"information_schema.COLUMNS\").\n\t\tWhere(sq.Eq{\n\t\t\t\"TABLE_NAME\": s.tablePrefix + \"schema_migrations\",\n\t\t})\n\n\tswitch s.dbType {\n\tcase model.MysqlDBType:\n\t\tquery = query.Where(sq.Eq{\"TABLE_SCHEMA\": s.schemaName})\n\tcase model.PostgresDBType:\n\t\tquery = query.Where(\"table_schema = current_schema()\")\n\t}\n\n\trows, err := query.Query()\n\tif err != nil {\n\t\ts.logger.Error(\"failed to fetch columns in schema_migrations table\", mlog.Err(err))\n\t\treturn false, err\n\t}\n\n\tdefer s.CloseRows(rows)\n\n\tdata := []string{}\n\tfor rows.Next() {\n\t\tvar columnName string\n\n\t\terr := rows.Scan(&columnName)\n\t\tif err != nil {\n\t\t\ts.logger.Error(\"error scanning rows from schema_migrations table definition\", mlog.Err(err))\n\t\t\treturn false, err\n\t\t}\n\n\t\tdata = append(data, columnName)\n\t}\n\n\tif len(data) == 0 {\n\t\t// if no data then table does not exist and therefore a schema migration is not needed.\n\t\treturn false, nil\n\t}\n\n\tfor _, columnName := range data {\n\t\t// look for a column named 'name', if found then no migration is needed\n\t\tif strings.ToLower(columnName) == \"name\" {\n\t\t\treturn false, nil\n\t\t}\n\t}\n\n\treturn true, nil\n}\n\nfunc (s *SQLStore) isSchemaMigrationNeededSQLite() (bool, error) {\n\t// the way to check presence of a column is different\n\t// for SQLite. Hence, the separate function\n\n\tquery := fmt.Sprintf(\"PRAGMA table_info(\\\"%sschema_migrations\\\");\", s.tablePrefix)\n\trows, err := s.db.Query(query)\n\tif err != nil {\n\t\ts.logger.Error(\"SQLite - failed to check for columns in schema_migrations table\", mlog.Err(err))\n\t\treturn false, err\n\t}\n\n\tdefer s.CloseRows(rows)\n\n\tconst (\n\t\tidxCid = iota\n\t\tidxName\n\t\tidxType\n\t\tidxNotnull\n\t\tidxDfltValue\n\t\tidxPk\n\t)\n\n\tdata := [][]*string{}\n\tfor rows.Next() {\n\t\t// PRAGMA returns 6 columns\n\t\trow := make([]*string, 6)\n\n\t\terr := rows.Scan(\n\t\t\t&row[idxCid],\n\t\t\t&row[idxName],\n\t\t\t&row[idxType],\n\t\t\t&row[idxNotnull],\n\t\t\t&row[idxDfltValue],\n\t\t\t&row[idxPk],\n\t\t)\n\t\tif err != nil {\n\t\t\ts.logger.Error(\"error scanning rows from SQLite schema_migrations table definition\", mlog.Err(err))\n\t\t\treturn false, err\n\t\t}\n\n\t\tdata = append(data, row)\n\t}\n\n\tif len(data) == 0 {\n\t\t// if no data then table does not exist and therefore a schema migration is not needed.\n\t\treturn false, nil\n\t}\n\n\tfor _, row := range data {\n\t\t// look for a column named 'name', if found then no migration is needed\n\t\tif len(row) >= 2 && strings.ToLower(*row[idxName]) == \"name\" {\n\t\t\treturn false, nil\n\t\t}\n\t}\n\n\treturn true, nil\n}\n\nfunc (s *SQLStore) getLegacySchemaVersion() (uint32, error) {\n\tquery := s.getQueryBuilder(s.db).\n\t\tSelect(\"version\").\n\t\tFrom(s.tablePrefix + \"schema_migrations\")\n\n\trow := query.QueryRow()\n\n\tvar version uint32\n\tif err := row.Scan(&version); err != nil {\n\t\ts.logger.Error(\"error fetching legacy schema version\", mlog.Err(err))\n\t\treturn version, err\n\t}\n\n\treturn version, nil\n}\n\nfunc (s *SQLStore) createTempSchemaTable() error {\n\t// squirrel doesn't support DDL query in query builder\n\t// so, we need to use a plain old string\n\tquery := fmt.Sprintf(\"CREATE TABLE IF NOT EXISTS %s (Version bigint NOT NULL, Name varchar(64) NOT NULL, PRIMARY KEY (Version))\", s.tablePrefix+tempSchemaMigrationTableName)\n\tif _, err := s.db.Exec(query); err != nil {\n\t\ts.logger.Error(\"failed to create temporary schema migration table\", mlog.Err(err))\n\t\ts.logger.Error(\"createTempSchemaTable error  \" + err.Error())\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (s *SQLStore) populateTempSchemaTable(migrations []*models.Migration) error {\n\tquery := s.getQueryBuilder(s.db).\n\t\tInsert(s.tablePrefix+tempSchemaMigrationTableName).\n\t\tColumns(\"Version\", \"Name\")\n\n\tfor _, migration := range migrations {\n\t\ts.logger.Info(\"-- Registering migration\", mlog.Uint(\"version\", migration.Version), mlog.String(\"name\", migration.Name))\n\t\tquery = query.Values(migration.Version, migration.Name)\n\t}\n\n\tif _, err := query.Exec(); err != nil {\n\t\ts.logger.Error(\"failed to insert migration records into temporary schema table\", mlog.Err(err))\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (s *SQLStore) useNewSchemaTable() error {\n\t// first delete the old table, then\n\t// rename the new table to old table's name\n\n\t// renaming old schema migration table. Will delete later once the migration is\n\t// complete, just in case.\n\tvar query string\n\tif s.dbType == model.MysqlDBType {\n\t\tquery = fmt.Sprintf(\"RENAME TABLE `%sschema_migrations` TO `%sschema_migrations_old_temp`\", s.tablePrefix, s.tablePrefix)\n\t} else {\n\t\tquery = fmt.Sprintf(\"ALTER TABLE %sschema_migrations RENAME TO %sschema_migrations_old_temp\", s.tablePrefix, s.tablePrefix)\n\t}\n\n\tif _, err := s.db.Exec(query); err != nil {\n\t\ts.logger.Error(\"failed to rename old schema migration table\", mlog.Err(err))\n\t\treturn err\n\t}\n\n\t// renaming new temp table to old table's name\n\tif s.dbType == model.MysqlDBType {\n\t\tquery = fmt.Sprintf(\"RENAME TABLE `%s%s` TO `%sschema_migrations`\", s.tablePrefix, tempSchemaMigrationTableName, s.tablePrefix)\n\t} else {\n\t\tquery = fmt.Sprintf(\"ALTER TABLE %s%s RENAME TO %sschema_migrations\", s.tablePrefix, tempSchemaMigrationTableName, s.tablePrefix)\n\t}\n\n\tif _, err := s.db.Exec(query); err != nil {\n\t\ts.logger.Error(\"failed to rename temp schema table\", mlog.Err(err))\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (s *SQLStore) deleteOldSchemaMigrationTable() error {\n\tquery := \"DROP TABLE IF EXISTS \" + s.tablePrefix + \"schema_migrations_old_temp\"\n\tif _, err := s.db.Exec(query); err != nil {\n\t\ts.logger.Error(\"failed to delete old temp schema migrations table\", mlog.Err(err))\n\t\treturn err\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "server/services/store/sqlstore/schema_table_migration_test.go",
    "content": "package sqlstore\n\nimport (\n\t\"testing\"\n\n\t\"github.com/mattermost/morph/models\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestGetEmbeddedMigrations(t *testing.T) {\n\tt.Run(\"should find migrations on the embedded assets\", func(t *testing.T) {\n\t\tmigrations, err := getEmbeddedMigrations()\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, migrations)\n\t})\n}\n\nfunc TestFilterMigrations(t *testing.T) {\n\tmigrations := []*models.Migration{\n\t\t{Direction: models.Up, Version: 1},\n\t\t{Direction: models.Down, Version: 1},\n\t\t{Direction: models.Up, Version: 2},\n\t\t{Direction: models.Down, Version: 2},\n\t\t{Direction: models.Up, Version: 3},\n\t\t{Direction: models.Down, Version: 3},\n\t\t{Direction: models.Up, Version: 4},\n\t\t{Direction: models.Down, Version: 4},\n\t}\n\n\tt.Run(\"only up migrations should be included\", func(t *testing.T) {\n\t\tfilteredMigrations := filterMigrations(migrations, 4)\n\t\trequire.Len(t, filteredMigrations, 4)\n\t\tfor _, migration := range filteredMigrations {\n\t\t\trequire.Equal(t, models.Up, migration.Direction)\n\t\t}\n\t})\n\n\tt.Run(\"only migrations below or equal to the legacy schema version should be included\", func(t *testing.T) {\n\t\ttestCases := []struct {\n\t\t\tName             string\n\t\t\tLegacyVersion    uint32\n\t\t\tExpectedVersions []uint32\n\t\t}{\n\t\t\t{\"All should be included\", 4, []uint32{1, 2, 3, 4}},\n\t\t\t{\"Only half should be included\", 2, []uint32{1, 2}},\n\t\t\t{\"Three including the third should be included\", 3, []uint32{1, 2, 3}},\n\t\t}\n\n\t\tfor _, tc := range testCases {\n\t\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\t\tfilteredMigrations := filterMigrations(migrations, tc.LegacyVersion)\n\t\t\t\trequire.Len(t, filteredMigrations, int(tc.LegacyVersion))\n\n\t\t\t\tversions := make([]uint32, len(filteredMigrations))\n\t\t\t\tfor i, migration := range filteredMigrations {\n\t\t\t\t\tversions[i] = migration.Version\n\t\t\t\t}\n\n\t\t\t\trequire.ElementsMatch(t, versions, tc.ExpectedVersions)\n\t\t\t})\n\t\t}\n\t})\n\n\tt.Run(\"migrations should be included even if they're not sorted\", func(t *testing.T) {\n\t\tunsortedMigrations := []*models.Migration{\n\t\t\t{Direction: models.Up, Version: 4},\n\t\t\t{Direction: models.Down, Version: 4},\n\t\t\t{Direction: models.Up, Version: 1},\n\t\t\t{Direction: models.Down, Version: 2},\n\t\t\t{Direction: models.Down, Version: 1},\n\t\t\t{Direction: models.Up, Version: 3},\n\t\t\t{Direction: models.Down, Version: 3},\n\t\t\t{Direction: models.Up, Version: 2},\n\t\t}\n\n\t\ttestCases := []struct {\n\t\t\tName             string\n\t\t\tLegacyVersion    uint32\n\t\t\tExpectedVersions []uint32\n\t\t}{\n\t\t\t{\"All should be included\", 4, []uint32{1, 2, 3, 4}},\n\t\t\t{\"Only half should be included\", 2, []uint32{1, 2}},\n\t\t\t{\"Three including the third should be included\", 3, []uint32{1, 2, 3}},\n\t\t}\n\n\t\tfor _, tc := range testCases {\n\t\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\t\tfilteredMigrations := filterMigrations(unsortedMigrations, tc.LegacyVersion)\n\t\t\t\trequire.Len(t, filteredMigrations, int(tc.LegacyVersion))\n\n\t\t\t\tversions := make([]uint32, len(filteredMigrations))\n\t\t\t\tfor i, migration := range filteredMigrations {\n\t\t\t\t\tversions[i] = migration.Version\n\t\t\t\t}\n\n\t\t\t\trequire.ElementsMatch(t, versions, tc.ExpectedVersions)\n\t\t\t})\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "server/services/store/sqlstore/session.go",
    "content": "package sqlstore\n\nimport (\n\t\"encoding/json\"\n\n\tsq \"github.com/Masterminds/squirrel\"\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n)\n\n// GetActiveUserCount returns the number of users with active sessions within N seconds ago.\nfunc (s *SQLStore) getActiveUserCount(db sq.BaseRunner, updatedSecondsAgo int64) (int, error) {\n\tquery := s.getQueryBuilder(db).\n\t\tSelect(\"count(distinct user_id)\").\n\t\tFrom(s.tablePrefix + \"sessions\").\n\t\tWhere(sq.Gt{\"update_at\": utils.GetMillis() - utils.SecondsToMillis(updatedSecondsAgo)})\n\n\trow := query.QueryRow()\n\n\tvar count int\n\terr := row.Scan(&count)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\treturn count, nil\n}\n\nfunc (s *SQLStore) getSession(db sq.BaseRunner, token string, expireTimeSeconds int64) (*model.Session, error) {\n\tquery := s.getQueryBuilder(db).\n\t\tSelect(\"id\", \"token\", \"user_id\", \"auth_service\", \"props\").\n\t\tFrom(s.tablePrefix + \"sessions\").\n\t\tWhere(sq.Eq{\"token\": token}).\n\t\tWhere(sq.Gt{\"update_at\": utils.GetMillis() - utils.SecondsToMillis(expireTimeSeconds)})\n\n\trow := query.QueryRow()\n\tsession := model.Session{}\n\n\tvar propsBytes []byte\n\terr := row.Scan(&session.ID, &session.Token, &session.UserID, &session.AuthService, &propsBytes)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = json.Unmarshal(propsBytes, &session.Props)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &session, nil\n}\n\nfunc (s *SQLStore) createSession(db sq.BaseRunner, session *model.Session) error {\n\tnow := utils.GetMillis()\n\n\tpropsBytes, err := json.Marshal(session.Props)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tquery := s.getQueryBuilder(db).Insert(s.tablePrefix+\"sessions\").\n\t\tColumns(\"id\", \"token\", \"user_id\", \"auth_service\", \"props\", \"create_at\", \"update_at\").\n\t\tValues(session.ID, session.Token, session.UserID, session.AuthService, propsBytes, now, now)\n\n\t_, err = query.Exec()\n\treturn err\n}\n\nfunc (s *SQLStore) refreshSession(db sq.BaseRunner, session *model.Session) error {\n\tnow := utils.GetMillis()\n\n\tquery := s.getQueryBuilder(db).Update(s.tablePrefix+\"sessions\").\n\t\tWhere(sq.Eq{\"token\": session.Token}).\n\t\tSet(\"update_at\", now)\n\n\t_, err := query.Exec()\n\treturn err\n}\n\nfunc (s *SQLStore) updateSession(db sq.BaseRunner, session *model.Session) error {\n\tnow := utils.GetMillis()\n\n\tpropsBytes, err := json.Marshal(session.Props)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tquery := s.getQueryBuilder(db).Update(s.tablePrefix+\"sessions\").\n\t\tWhere(sq.Eq{\"token\": session.Token}).\n\t\tSet(\"update_at\", now).\n\t\tSet(\"props\", propsBytes)\n\n\t_, err = query.Exec()\n\treturn err\n}\n\nfunc (s *SQLStore) deleteSession(db sq.BaseRunner, sessionID string) error {\n\tquery := s.getQueryBuilder(db).Delete(s.tablePrefix + \"sessions\").\n\t\tWhere(sq.Eq{\"id\": sessionID})\n\n\t_, err := query.Exec()\n\treturn err\n}\n\nfunc (s *SQLStore) cleanUpSessions(db sq.BaseRunner, expireTimeSeconds int64) error {\n\tquery := s.getQueryBuilder(db).Delete(s.tablePrefix + \"sessions\").\n\t\tWhere(sq.Lt{\"update_at\": utils.GetMillis() - utils.SecondsToMillis(expireTimeSeconds)})\n\n\t_, err := query.Exec()\n\treturn err\n}\n"
  },
  {
    "path": "server/services/store/sqlstore/sharing.go",
    "content": "package sqlstore\n\nimport (\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n\n\tsq \"github.com/Masterminds/squirrel\"\n)\n\nfunc (s *SQLStore) upsertSharing(db sq.BaseRunner, sharing model.Sharing) error {\n\tnow := utils.GetMillis()\n\n\tquery := s.getQueryBuilder(db).\n\t\tInsert(s.tablePrefix+\"sharing\").\n\t\tColumns(\n\t\t\t\"id\",\n\t\t\t\"enabled\",\n\t\t\t\"token\",\n\t\t\t\"modified_by\",\n\t\t\t\"update_at\",\n\t\t).\n\t\tValues(\n\t\t\tsharing.ID,\n\t\t\tsharing.Enabled,\n\t\t\tsharing.Token,\n\t\t\tsharing.ModifiedBy,\n\t\t\tnow,\n\t\t)\n\tif s.dbType == model.MysqlDBType {\n\t\tquery = query.Suffix(\"ON DUPLICATE KEY UPDATE enabled = ?, token = ?, modified_by = ?, update_at = ?\",\n\t\t\tsharing.Enabled, sharing.Token, sharing.ModifiedBy, now)\n\t} else {\n\t\tquery = query.Suffix(\n\t\t\t`ON CONFLICT (id)\n\t\t\t DO UPDATE SET enabled = EXCLUDED.enabled, token = EXCLUDED.token, modified_by = EXCLUDED.modified_by, update_at = EXCLUDED.update_at`,\n\t\t)\n\t}\n\n\t_, err := query.Exec()\n\treturn err\n}\n\nfunc (s *SQLStore) getSharing(db sq.BaseRunner, boardID string) (*model.Sharing, error) {\n\tquery := s.getQueryBuilder(db).\n\t\tSelect(\n\t\t\t\"id\",\n\t\t\t\"enabled\",\n\t\t\t\"token\",\n\t\t\t\"modified_by\",\n\t\t\t\"update_at\",\n\t\t).\n\t\tFrom(s.tablePrefix + \"sharing\").\n\t\tWhere(sq.Eq{\"id\": boardID})\n\trow := query.QueryRow()\n\tsharing := model.Sharing{}\n\n\terr := row.Scan(\n\t\t&sharing.ID,\n\t\t&sharing.Enabled,\n\t\t&sharing.Token,\n\t\t&sharing.ModifiedBy,\n\t\t&sharing.UpdateAt,\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &sharing, nil\n}\n"
  },
  {
    "path": "server/services/store/sqlstore/sqlite.go",
    "content": "//go:build sqlite3\n\npackage sqlstore\n\nimport _ \"github.com/mattn/go-sqlite3\" // sqlite driver\n"
  },
  {
    "path": "server/services/store/sqlstore/sqlstore.go",
    "content": "package sqlstore\n\nimport (\n\t\"database/sql\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"strings\"\n\n\tsq \"github.com/Masterminds/squirrel\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/store\"\n\t\"github.com/mattermost/mattermost/server/public/pluginapi/cluster\"\n\n\tmmModel \"github.com/mattermost/mattermost/server/public/model\"\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\n// SQLStore is a SQL database.\ntype SQLStore struct {\n\tdb               *sql.DB\n\tdbType           string\n\ttablePrefix      string\n\tconnectionString string\n\tdbPingAttempts   int\n\tisSingleUser     bool\n\tlogger           mlog.LoggerIFace\n\tNewMutexFn       MutexFactory\n\tservicesAPI      servicesAPI\n\tisBinaryParam    bool\n\tschemaName       string\n\tconfigFn         func() *mmModel.Config\n}\n\n// MutexFactory is used by the store in plugin mode to generate\n// a cluster mutex.\ntype MutexFactory func(name string) (*cluster.Mutex, error)\n\n// New creates a new SQL implementation of the store.\nfunc New(params Params) (*SQLStore, error) {\n\tparams.Logger.Info(\"connectDatabase\", mlog.String(\"dbType\", params.DBType))\n\tstore := &SQLStore{\n\t\t// TODO: add replica DB support too.\n\t\tdb:               params.DB,\n\t\tdbType:           params.DBType,\n\t\tdbPingAttempts:   params.DBPingAttempts,\n\t\ttablePrefix:      params.TablePrefix,\n\t\tconnectionString: params.ConnectionString,\n\t\tlogger:           params.Logger,\n\t\tisSingleUser:     params.IsSingleUser,\n\t\tNewMutexFn:       params.NewMutexFn,\n\t\tservicesAPI:      params.ServicesAPI,\n\t\tconfigFn:         params.ConfigFn,\n\t}\n\n\tvar err error\n\tstore.isBinaryParam, err = store.computeBinaryParam()\n\tif err != nil {\n\t\tparams.Logger.Error(`Cannot compute binary parameter`, mlog.Err(err))\n\t\treturn nil, err\n\t}\n\n\tstore.schemaName, err = store.GetSchemaName()\n\tif err != nil {\n\t\tparams.Logger.Error(`Cannot get schema name`, mlog.Err(err))\n\t\treturn nil, err\n\t}\n\n\tif !params.SkipMigrations {\n\t\tif mErr := store.Migrate(); mErr != nil {\n\t\t\tparams.Logger.Error(`Table creation / migration failed`, mlog.Err(mErr))\n\n\t\t\treturn nil, mErr\n\t\t}\n\t}\n\treturn store, nil\n}\n\nfunc (s *SQLStore) IsMariaDB() bool {\n\tif s.dbType != model.MysqlDBType {\n\t\treturn false\n\t}\n\n\trow := s.db.QueryRow(\"SELECT Version()\")\n\n\tvar version string\n\tif err := row.Scan(&version); err != nil {\n\t\ts.logger.Error(\"error checking database version\", mlog.Err(err))\n\t\treturn false\n\t}\n\n\treturn strings.Contains(strings.ToLower(version), \"mariadb\")\n}\n\n// computeBinaryParam returns whether the data source uses binary_parameters\n// when using Postgres.\nfunc (s *SQLStore) computeBinaryParam() (bool, error) {\n\tif s.dbType != model.PostgresDBType {\n\t\treturn false, nil\n\t}\n\n\turl, err := url.Parse(s.connectionString)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treturn url.Query().Get(\"binary_parameters\") == \"yes\", nil\n}\n\n// Shutdown close the connection with the store.\nfunc (s *SQLStore) Shutdown() error {\n\treturn s.db.Close()\n}\n\n// DBHandle returns the raw sql.DB handle.\n// It is used by the mattermostauthlayer to run their own\n// raw SQL queries.\nfunc (s *SQLStore) DBHandle() *sql.DB {\n\treturn s.db\n}\n\n// DBType returns the DB driver used for the store.\nfunc (s *SQLStore) DBType() string {\n\treturn s.dbType\n}\n\nfunc (s *SQLStore) getQueryBuilder(db sq.BaseRunner) sq.StatementBuilderType {\n\tbuilder := sq.StatementBuilder\n\tif s.dbType == model.PostgresDBType || s.dbType == model.SqliteDBType {\n\t\tbuilder = builder.PlaceholderFormat(sq.Dollar)\n\t}\n\n\treturn builder.RunWith(db)\n}\n\nfunc (s *SQLStore) escapeField(fieldName string) string { //nolint:unparam\n\tif s.dbType == model.MysqlDBType {\n\t\treturn \"`\" + fieldName + \"`\"\n\t}\n\tif s.dbType == model.PostgresDBType || s.dbType == model.SqliteDBType {\n\t\treturn \"\\\"\" + fieldName + \"\\\"\"\n\t}\n\treturn fieldName\n}\n\nfunc (s *SQLStore) concatenationSelector(field string, delimiter string) string {\n\tif s.dbType == model.SqliteDBType {\n\t\treturn fmt.Sprintf(\"group_concat(%s)\", field)\n\t}\n\tif s.dbType == model.PostgresDBType {\n\t\treturn fmt.Sprintf(\"string_agg(%s, '%s')\", field, delimiter)\n\t}\n\tif s.dbType == model.MysqlDBType {\n\t\treturn fmt.Sprintf(\"GROUP_CONCAT(%s SEPARATOR '%s')\", field, delimiter)\n\t}\n\treturn \"\"\n}\n\nfunc (s *SQLStore) elementInColumn(column string) string {\n\tif s.dbType == model.SqliteDBType || s.dbType == model.MysqlDBType {\n\t\treturn fmt.Sprintf(\"instr(%s, ?) > 0\", column)\n\t}\n\tif s.dbType == model.PostgresDBType {\n\t\treturn fmt.Sprintf(\"position(? in %s) > 0\", column)\n\t}\n\treturn \"\"\n}\n\nfunc (s *SQLStore) getLicense(db sq.BaseRunner) *mmModel.License {\n\treturn nil\n}\n\nfunc (s *SQLStore) searchUserChannels(db sq.BaseRunner, teamID, userID, query string) ([]*mmModel.Channel, error) {\n\treturn nil, store.NewNotSupportedError(\"search user channels not supported on standalone mode\")\n}\n\nfunc (s *SQLStore) getChannel(db sq.BaseRunner, teamID, channel string) (*mmModel.Channel, error) {\n\treturn nil, store.NewNotSupportedError(\"get channel not supported on standalone mode\")\n}\n\nfunc (s *SQLStore) DBVersion() string {\n\tvar version string\n\tvar row *sql.Row\n\n\tswitch s.dbType {\n\tcase model.MysqlDBType:\n\t\trow = s.db.QueryRow(\"SELECT VERSION()\")\n\tcase model.PostgresDBType:\n\t\trow = s.db.QueryRow(\"SHOW server_version\")\n\tcase model.SqliteDBType:\n\t\trow = s.db.QueryRow(\"SELECT sqlite_version()\")\n\tdefault:\n\t\treturn \"\"\n\t}\n\n\tif err := row.Scan(&version); err != nil {\n\t\ts.logger.Error(\"error checking database version\", mlog.Err(err))\n\t\treturn \"\"\n\t}\n\n\treturn version\n}\n"
  },
  {
    "path": "server/services/store/sqlstore/sqlstore_test.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage sqlstore\n\nimport (\n\t\"testing\"\n\n\t\"github.com/mattermost/focalboard/server/services/store/storetests\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestSQLStore(t *testing.T) {\n\tt.Run(\"BlocksStore\", func(t *testing.T) { storetests.StoreTestBlocksStore(t, SetupTests) })\n\tt.Run(\"SharingStore\", func(t *testing.T) { storetests.StoreTestSharingStore(t, SetupTests) })\n\tt.Run(\"SystemStore\", func(t *testing.T) { storetests.StoreTestSystemStore(t, SetupTests) })\n\tt.Run(\"UserStore\", func(t *testing.T) { storetests.StoreTestUserStore(t, SetupTests) })\n\tt.Run(\"SessionStore\", func(t *testing.T) { storetests.StoreTestSessionStore(t, SetupTests) })\n\tt.Run(\"TeamStore\", func(t *testing.T) { storetests.StoreTestTeamStore(t, SetupTests) })\n\tt.Run(\"BoardStore\", func(t *testing.T) { storetests.StoreTestBoardStore(t, SetupTests) })\n\tt.Run(\"BoardsAndBlocksStore\", func(t *testing.T) { storetests.StoreTestBoardsAndBlocksStore(t, SetupTests) })\n\tt.Run(\"SubscriptionStore\", func(t *testing.T) { storetests.StoreTestSubscriptionsStore(t, SetupTests) })\n\tt.Run(\"NotificationHintStore\", func(t *testing.T) { storetests.StoreTestNotificationHintsStore(t, SetupTests) })\n\tt.Run(\"DataRetention\", func(t *testing.T) { storetests.StoreTestDataRetention(t, SetupTests) })\n\tt.Run(\"CloudStore\", func(t *testing.T) { storetests.StoreTestCloudStore(t, SetupTests) })\n\tt.Run(\"StoreTestFileStore\", func(t *testing.T) { storetests.StoreTestFileStore(t, SetupTests) })\n\tt.Run(\"StoreTestCategoryStore\", func(t *testing.T) { storetests.StoreTestCategoryStore(t, SetupTests) })\n\tt.Run(\"StoreTestCategoryBoardsStore\", func(t *testing.T) { storetests.StoreTestCategoryBoardsStore(t, SetupTests) })\n\tt.Run(\"ComplianceHistoryStore\", func(t *testing.T) { storetests.StoreTestComplianceHistoryStore(t, SetupTests) })\n}\n\n//  tests for  utility functions inside sqlstore.go\n\nfunc TestConcatenationSelector(t *testing.T) {\n\tstore, tearDown := SetupTests(t)\n\tsqlStore := store.(*SQLStore)\n\tdefer tearDown()\n\n\tconcatenationString := sqlStore.concatenationSelector(\"a\", \",\")\n\tswitch sqlStore.dbType {\n\tcase model.SqliteDBType:\n\t\trequire.Equal(t, concatenationString, \"group_concat(a)\")\n\tcase model.MysqlDBType:\n\t\trequire.Equal(t, concatenationString, \"GROUP_CONCAT(a SEPARATOR ',')\")\n\tcase model.PostgresDBType:\n\t\trequire.Equal(t, concatenationString, \"string_agg(a, ',')\")\n\t}\n}\n\nfunc TestElementInColumn(t *testing.T) {\n\tstore, _ := SetupTests(t)\n\tsqlStore := store.(*SQLStore)\n\n\tinLiteral := sqlStore.elementInColumn(\"test_column\")\n\tswitch sqlStore.dbType {\n\tcase model.SqliteDBType:\n\t\trequire.Equal(t, inLiteral, \"instr(test_column, ?) > 0\")\n\tcase model.MysqlDBType:\n\t\trequire.Equal(t, inLiteral, \"instr(test_column, ?) > 0\")\n\tcase model.PostgresDBType:\n\t\trequire.Equal(t, inLiteral, \"position(? in test_column) > 0\")\n\t}\n}\n"
  },
  {
    "path": "server/services/store/sqlstore/subscriptions.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage sqlstore\n\nimport (\n\t\"database/sql\"\n\t\"fmt\"\n\n\tsq \"github.com/Masterminds/squirrel\"\n\t\"github.com/mattermost/focalboard/server/model\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nvar subscriptionFields = []string{\n\t\"block_type\",\n\t\"block_id\",\n\t\"subscriber_type\",\n\t\"subscriber_id\",\n\t\"notified_at\",\n\t\"create_at\",\n\t\"delete_at\",\n}\n\nfunc valuesForSubscription(sub *model.Subscription) []interface{} {\n\treturn []interface{}{\n\t\tsub.BlockType,\n\t\tsub.BlockID,\n\t\tsub.SubscriberType,\n\t\tsub.SubscriberID,\n\t\tsub.NotifiedAt,\n\t\tsub.CreateAt,\n\t\tsub.DeleteAt,\n\t}\n}\n\nfunc (s *SQLStore) subscriptionsFromRows(rows *sql.Rows) ([]*model.Subscription, error) {\n\tsubscriptions := []*model.Subscription{}\n\n\tfor rows.Next() {\n\t\tvar sub model.Subscription\n\t\terr := rows.Scan(\n\t\t\t&sub.BlockType,\n\t\t\t&sub.BlockID,\n\t\t\t&sub.SubscriberType,\n\t\t\t&sub.SubscriberID,\n\t\t\t&sub.NotifiedAt,\n\t\t\t&sub.CreateAt,\n\t\t\t&sub.DeleteAt,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tsubscriptions = append(subscriptions, &sub)\n\t}\n\treturn subscriptions, nil\n}\n\n// createSubscription creates a new subscription, or returns an existing subscription\n// for the block & subscriber.\nfunc (s *SQLStore) createSubscription(db sq.BaseRunner, sub *model.Subscription) (*model.Subscription, error) {\n\tif err := sub.IsValid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tnow := model.GetMillis()\n\n\tsubAdd := *sub\n\tsubAdd.NotifiedAt = now // notified_at set so first notification doesn't pick up all history\n\tsubAdd.CreateAt = now\n\tsubAdd.DeleteAt = 0\n\n\tquery := s.getQueryBuilder(db).\n\t\tInsert(s.tablePrefix + \"subscriptions\").\n\t\tColumns(subscriptionFields...).\n\t\tValues(valuesForSubscription(&subAdd)...)\n\n\tif s.dbType == model.MysqlDBType {\n\t\tquery = query.Suffix(\"ON DUPLICATE KEY UPDATE delete_at = 0, notified_at = ?\", now)\n\t} else {\n\t\tquery = query.Suffix(\"ON CONFLICT (block_id,subscriber_id) DO UPDATE SET delete_at = 0, notified_at = ?\", now)\n\t}\n\n\tif _, err := query.Exec(); err != nil {\n\t\ts.logger.Error(\"Cannot create subscription\",\n\t\t\tmlog.String(\"block_id\", sub.BlockID),\n\t\t\tmlog.String(\"subscriber_id\", sub.SubscriberID),\n\t\t\tmlog.Err(err),\n\t\t)\n\t\treturn nil, err\n\t}\n\treturn &subAdd, nil\n}\n\n// deleteSubscription soft deletes the subscription for a specific block and subscriber.\nfunc (s *SQLStore) deleteSubscription(db sq.BaseRunner, blockID string, subscriberID string) error {\n\tnow := model.GetMillis()\n\n\tquery := s.getQueryBuilder(db).\n\t\tUpdate(s.tablePrefix+\"subscriptions\").\n\t\tSet(\"delete_at\", now).\n\t\tWhere(sq.Eq{\"block_id\": blockID}).\n\t\tWhere(sq.Eq{\"subscriber_id\": subscriberID})\n\n\tresult, err := query.Exec()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcount, err := result.RowsAffected()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif count == 0 {\n\t\tmessage := fmt.Sprintf(\"subscription BlockID=%s SubscriberID=%s\", blockID, subscriberID)\n\t\treturn model.NewErrNotFound(message)\n\t}\n\n\treturn nil\n}\n\n// getSubscription fetches the subscription for a specific block and subscriber.\nfunc (s *SQLStore) getSubscription(db sq.BaseRunner, blockID string, subscriberID string) (*model.Subscription, error) {\n\tquery := s.getQueryBuilder(db).\n\t\tSelect(subscriptionFields...).\n\t\tFrom(s.tablePrefix + \"subscriptions\").\n\t\tWhere(sq.Eq{\"block_id\": blockID}).\n\t\tWhere(sq.Eq{\"subscriber_id\": subscriberID}).\n\t\tWhere(sq.Eq{\"delete_at\": 0})\n\n\trows, err := query.Query()\n\tif err != nil {\n\t\ts.logger.Error(\"Cannot fetch subscription for block & subscriber\",\n\t\t\tmlog.String(\"block_id\", blockID),\n\t\t\tmlog.String(\"subscriber_id\", subscriberID),\n\t\t\tmlog.Err(err),\n\t\t)\n\t\treturn nil, err\n\t}\n\tdefer s.CloseRows(rows)\n\n\tsubscriptions, err := s.subscriptionsFromRows(rows)\n\tif err != nil {\n\t\ts.logger.Error(\"Cannot get subscription for block & subscriber\",\n\t\t\tmlog.String(\"block_id\", blockID),\n\t\t\tmlog.String(\"subscriber_id\", subscriberID),\n\t\t\tmlog.Err(err),\n\t\t)\n\t\treturn nil, err\n\t}\n\tif len(subscriptions) == 0 {\n\t\tmessage := fmt.Sprintf(\"subscription BlockID=%s SubscriberID=%s\", blockID, subscriberID)\n\t\treturn nil, model.NewErrNotFound(message)\n\t}\n\treturn subscriptions[0], nil\n}\n\n// getSubscriptions fetches all subscriptions for a specific subscriber.\nfunc (s *SQLStore) getSubscriptions(db sq.BaseRunner, subscriberID string) ([]*model.Subscription, error) {\n\tquery := s.getQueryBuilder(db).\n\t\tSelect(subscriptionFields...).\n\t\tFrom(s.tablePrefix + \"subscriptions\").\n\t\tWhere(sq.Eq{\"subscriber_id\": subscriberID}).\n\t\tWhere(sq.Eq{\"delete_at\": 0})\n\n\trows, err := query.Query()\n\tif err != nil {\n\t\ts.logger.Error(\"Cannot fetch subscriptions for subscriber\",\n\t\t\tmlog.String(\"subscriber_id\", subscriberID),\n\t\t\tmlog.Err(err),\n\t\t)\n\t\treturn nil, err\n\t}\n\tdefer s.CloseRows(rows)\n\n\treturn s.subscriptionsFromRows(rows)\n}\n\n// getSubscribersForBlock fetches all subscribers for a block.\nfunc (s *SQLStore) getSubscribersForBlock(db sq.BaseRunner, blockID string) ([]*model.Subscriber, error) {\n\tquery := s.getQueryBuilder(db).\n\t\tSelect(\n\t\t\t\"subscriber_type\",\n\t\t\t\"subscriber_id\",\n\t\t\t\"notified_at\",\n\t\t).\n\t\tFrom(s.tablePrefix + \"subscriptions\").\n\t\tWhere(sq.Eq{\"block_id\": blockID}).\n\t\tWhere(sq.Eq{\"delete_at\": 0}).\n\t\tOrderBy(\"notified_at\")\n\n\trows, err := query.Query()\n\tif err != nil {\n\t\ts.logger.Error(\"Cannot fetch subscribers for block\",\n\t\t\tmlog.String(\"block_id\", blockID),\n\t\t\tmlog.Err(err),\n\t\t)\n\t\treturn nil, err\n\t}\n\tdefer s.CloseRows(rows)\n\n\tsubscribers := []*model.Subscriber{}\n\n\tfor rows.Next() {\n\t\tvar sub model.Subscriber\n\t\terr := rows.Scan(\n\t\t\t&sub.SubscriberType,\n\t\t\t&sub.SubscriberID,\n\t\t\t&sub.NotifiedAt,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tsubscribers = append(subscribers, &sub)\n\t}\n\treturn subscribers, nil\n}\n\n// getSubscribersCountForBlock returns a count of all subscribers for a block.\nfunc (s *SQLStore) getSubscribersCountForBlock(db sq.BaseRunner, blockID string) (int, error) {\n\tquery := s.getQueryBuilder(db).\n\t\tSelect(\"count(subscriber_id)\").\n\t\tFrom(s.tablePrefix + \"subscriptions\").\n\t\tWhere(sq.Eq{\"block_id\": blockID}).\n\t\tWhere(sq.Eq{\"delete_at\": 0})\n\n\trow := query.QueryRow()\n\n\tvar count int\n\terr := row.Scan(&count)\n\tif err != nil {\n\t\ts.logger.Error(\"Cannot count subscribers for block\",\n\t\t\tmlog.String(\"block_id\", blockID),\n\t\t\tmlog.Err(err),\n\t\t)\n\t\treturn 0, err\n\t}\n\treturn count, nil\n}\n\n// updateSubscribersNotifiedAt updates the notified_at field of all subscribers for a block.\nfunc (s *SQLStore) updateSubscribersNotifiedAt(db sq.BaseRunner, blockID string, notifiedAt int64) error {\n\tquery := s.getQueryBuilder(db).\n\t\tUpdate(s.tablePrefix+\"subscriptions\").\n\t\tSet(\"notified_at\", notifiedAt).\n\t\tWhere(sq.Eq{\"block_id\": blockID}).\n\t\tWhere(sq.Eq{\"delete_at\": 0})\n\n\tif _, err := query.Exec(); err != nil {\n\t\ts.logger.Error(\"UpdateSubscribersNotifiedAt error occurred while updating subscriber(s)\",\n\t\t\tmlog.String(\"blockID\", blockID),\n\t\t\tmlog.Err(err),\n\t\t)\n\t\treturn err\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "server/services/store/sqlstore/system.go",
    "content": "package sqlstore\n\nimport (\n\tsq \"github.com/Masterminds/squirrel\"\n\t\"github.com/mattermost/focalboard/server/model\"\n)\n\nfunc (s *SQLStore) getSystemSetting(db sq.BaseRunner, key string) (string, error) {\n\tscanner := s.getQueryBuilder(db).\n\t\tSelect(\"value\").\n\t\tFrom(s.tablePrefix + \"system_settings\").\n\t\tWhere(sq.Eq{\"id\": key}).\n\t\tQueryRow()\n\n\tvar result string\n\terr := scanner.Scan(&result)\n\tif err != nil && !model.IsErrNotFound(err) {\n\t\treturn \"\", err\n\t}\n\n\treturn result, nil\n}\n\nfunc (s *SQLStore) getSystemSettings(db sq.BaseRunner) (map[string]string, error) {\n\tquery := s.getQueryBuilder(db).Select(\"*\").From(s.tablePrefix + \"system_settings\")\n\n\trows, err := query.Query()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer s.CloseRows(rows)\n\n\tresults := map[string]string{}\n\n\tfor rows.Next() {\n\t\tvar id string\n\t\tvar value string\n\n\t\terr := rows.Scan(&id, &value)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tresults[id] = value\n\t}\n\n\treturn results, nil\n}\n\nfunc (s *SQLStore) setSystemSetting(db sq.BaseRunner, id, value string) error {\n\tquery := s.getQueryBuilder(db).Insert(s.tablePrefix+\"system_settings\").Columns(\"id\", \"value\").Values(id, value)\n\n\tif s.dbType == model.MysqlDBType {\n\t\tquery = query.Suffix(\"ON DUPLICATE KEY UPDATE value = ?\", value)\n\t} else {\n\t\tquery = query.Suffix(\"ON CONFLICT (id) DO UPDATE SET value = EXCLUDED.value\")\n\t}\n\n\t_, err := query.Exec()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "server/services/store/sqlstore/team.go",
    "content": "package sqlstore\n\nimport (\n\t\"database/sql\"\n\t\"encoding/json\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n\n\tsq \"github.com/Masterminds/squirrel\"\n)\n\nvar (\n\tteamFields = []string{\n\t\t\"id\",\n\t\t\"signup_token\",\n\t\t\"COALESCE(settings, '{}')\",\n\t\t\"modified_by\",\n\t\t\"update_at\",\n\t}\n)\n\nfunc (s *SQLStore) upsertTeamSignupToken(db sq.BaseRunner, team model.Team) error {\n\tnow := utils.GetMillis()\n\n\tquery := s.getQueryBuilder(db).\n\t\tInsert(s.tablePrefix+\"teams\").\n\t\tColumns(\n\t\t\t\"id\",\n\t\t\t\"signup_token\",\n\t\t\t\"modified_by\",\n\t\t\t\"update_at\",\n\t\t).\n\t\tValues(\n\t\t\tteam.ID,\n\t\t\tteam.SignupToken,\n\t\t\tteam.ModifiedBy,\n\t\t\tnow,\n\t\t)\n\tif s.dbType == model.MysqlDBType {\n\t\tquery = query.Suffix(\"ON DUPLICATE KEY UPDATE signup_token = ?, modified_by = ?, update_at = ?\",\n\t\t\tteam.SignupToken, team.ModifiedBy, now)\n\t} else {\n\t\tquery = query.Suffix(\n\t\t\t`ON CONFLICT (id)\n\t\t\t DO UPDATE SET signup_token = EXCLUDED.signup_token, modified_by = EXCLUDED.modified_by, update_at = EXCLUDED.update_at`,\n\t\t)\n\t}\n\n\t_, err := query.Exec()\n\treturn err\n}\n\nfunc (s *SQLStore) upsertTeamSettings(db sq.BaseRunner, team model.Team) error {\n\tnow := utils.GetMillis()\n\tsignupToken := utils.NewID(utils.IDTypeToken)\n\n\tsettingsJSON, err := json.Marshal(team.Settings)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tquery := s.getQueryBuilder(db).\n\t\tInsert(s.tablePrefix+\"teams\").\n\t\tColumns(\n\t\t\t\"id\",\n\t\t\t\"signup_token\",\n\t\t\t\"settings\",\n\t\t\t\"modified_by\",\n\t\t\t\"update_at\",\n\t\t).\n\t\tValues(\n\t\t\tteam.ID,\n\t\t\tsignupToken,\n\t\t\tsettingsJSON,\n\t\t\tteam.ModifiedBy,\n\t\t\tnow,\n\t\t)\n\tif s.dbType == model.MysqlDBType {\n\t\tquery = query.Suffix(\"ON DUPLICATE KEY UPDATE settings = ?, modified_by = ?, update_at = ?\", settingsJSON, team.ModifiedBy, now)\n\t} else {\n\t\tquery = query.Suffix(\n\t\t\t`ON CONFLICT (id)\n\t\t\t DO UPDATE SET settings = EXCLUDED.settings, modified_by = EXCLUDED.modified_by, update_at = EXCLUDED.update_at`,\n\t\t)\n\t}\n\n\t_, err = query.Exec()\n\treturn err\n}\n\nfunc (s *SQLStore) getTeam(db sq.BaseRunner, id string) (*model.Team, error) {\n\tvar settingsJSON string\n\n\tquery := s.getQueryBuilder(db).\n\t\tSelect(\n\t\t\t\"id\",\n\t\t\t\"signup_token\",\n\t\t\t\"COALESCE(settings, '{}')\",\n\t\t\t\"modified_by\",\n\t\t\t\"update_at\",\n\t\t).\n\t\tFrom(s.tablePrefix + \"teams\").\n\t\tWhere(sq.Eq{\"id\": id})\n\trow := query.QueryRow()\n\tteam := model.Team{}\n\n\terr := row.Scan(\n\t\t&team.ID,\n\t\t&team.SignupToken,\n\t\t&settingsJSON,\n\t\t&team.ModifiedBy,\n\t\t&team.UpdateAt,\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = json.Unmarshal([]byte(settingsJSON), &team.Settings)\n\tif err != nil {\n\t\ts.logger.Error(`ERROR GetTeam settings json.Unmarshal`, mlog.Err(err))\n\t\treturn nil, err\n\t}\n\n\treturn &team, nil\n}\n\nfunc (s *SQLStore) getTeamsForUser(db sq.BaseRunner, _ string) ([]*model.Team, error) {\n\treturn s.getAllTeams(db)\n}\n\nfunc (s *SQLStore) getTeamCount(db sq.BaseRunner) (int64, error) {\n\tquery := s.getQueryBuilder(db).\n\t\tSelect(\n\t\t\t\"COUNT(*) AS count\",\n\t\t).\n\t\tFrom(s.tablePrefix + \"teams\")\n\n\trows, err := query.Query()\n\tif err != nil {\n\t\ts.logger.Error(\"ERROR GetTeamCount\", mlog.Err(err))\n\t\treturn 0, err\n\t}\n\tdefer s.CloseRows(rows)\n\n\tvar count int64\n\n\trows.Next()\n\terr = rows.Scan(&count)\n\tif err != nil {\n\t\ts.logger.Error(\"Failed to fetch team count\", mlog.Err(err))\n\t\treturn 0, err\n\t}\n\treturn count, nil\n}\n\nfunc (s *SQLStore) teamsFromRows(rows *sql.Rows) ([]*model.Team, error) {\n\tteams := []*model.Team{}\n\n\tfor rows.Next() {\n\t\tvar team model.Team\n\t\tvar settingsBytes []byte\n\n\t\terr := rows.Scan(\n\t\t\t&team.ID,\n\t\t\t&team.SignupToken,\n\t\t\t&settingsBytes,\n\t\t\t&team.ModifiedBy,\n\t\t\t&team.UpdateAt,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\terr = json.Unmarshal(settingsBytes, &team.Settings)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tteams = append(teams, &team)\n\t}\n\n\treturn teams, nil\n}\n\nfunc (s *SQLStore) getAllTeams(db sq.BaseRunner) ([]*model.Team, error) {\n\tquery := s.getQueryBuilder(db).\n\t\tSelect(teamFields...).\n\t\tFrom(s.tablePrefix + \"teams\")\n\trows, err := query.Query()\n\tif err != nil {\n\t\ts.logger.Error(\"ERROR GetAllTeams\", mlog.Err(err))\n\t\treturn nil, err\n\t}\n\tdefer s.CloseRows(rows)\n\n\tteams, err := s.teamsFromRows(rows)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn teams, nil\n}\n"
  },
  {
    "path": "server/services/store/sqlstore/templates.go",
    "content": "package sqlstore\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\n\tsq \"github.com/Masterminds/squirrel\"\n\t\"github.com/mattermost/focalboard/server/model\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nvar (\n\tErrUnsupportedDatabaseType = errors.New(\"database type is unsupported\")\n)\n\n// removeDefaultTemplates deletes all the default templates and their children.\nfunc (s *SQLStore) removeDefaultTemplates(db sq.BaseRunner, boards []*model.Board) error {\n\tcount := 0\n\tfor _, board := range boards {\n\t\tif board.CreatedBy != model.SystemUserID {\n\t\t\tcontinue\n\t\t}\n\t\t// default template deletion does not need to go to blocks_history\n\t\tdeleteQuery := s.getQueryBuilder(db).\n\t\t\tDelete(s.tablePrefix + \"boards\").\n\t\t\tWhere(sq.Eq{\"id\": board.ID}).\n\t\t\tWhere(sq.Eq{\"is_template\": true})\n\n\t\tif _, err := deleteQuery.Exec(); err != nil {\n\t\t\treturn fmt.Errorf(\"cannot delete default template %s: %w\", board.ID, err)\n\t\t}\n\n\t\tdeleteQuery = s.getQueryBuilder(db).\n\t\t\tDelete(s.tablePrefix + \"blocks\").\n\t\t\tWhere(sq.Or{\n\t\t\t\tsq.Eq{\"parent_id\": board.ID},\n\t\t\t\tsq.Eq{\"root_id\": board.ID},\n\t\t\t\tsq.Eq{\"board_id\": board.ID},\n\t\t\t})\n\n\t\tif _, err := deleteQuery.Exec(); err != nil {\n\t\t\treturn fmt.Errorf(\"cannot delete default template %s: %w\", board.ID, err)\n\t\t}\n\n\t\ts.logger.Trace(\"removed default template block\",\n\t\t\tmlog.String(\"board_id\", board.ID),\n\t\t)\n\t\tcount++\n\t}\n\n\ts.logger.Debug(\"Removed default templates\", mlog.Int(\"count\", count))\n\n\treturn nil\n}\n\n// getTemplateBoards fetches all template boards .\nfunc (s *SQLStore) getTemplateBoards(db sq.BaseRunner, teamID, userID string) ([]*model.Board, error) {\n\tquery := s.getQueryBuilder(db).\n\t\tSelect(boardFields(\"\")...).\n\t\tFrom(s.tablePrefix+\"boards as b\").\n\t\tLeftJoin(s.tablePrefix+\"board_members as bm on b.id = bm.board_id and bm.user_id = ?\", userID).\n\t\tWhere(sq.Eq{\"is_template\": true}).\n\t\tWhere(sq.Eq{\"b.team_id\": teamID}).\n\t\tWhere(sq.Or{\n\t\t\t// this is to include public templates even if there is not board_member entry\n\t\t\tsq.And{\n\t\t\t\tsq.Eq{\"bm.board_id\": nil},\n\t\t\t\tsq.Eq{\"b.type\": model.BoardTypeOpen},\n\t\t\t},\n\t\t\tsq.And{\n\t\t\t\tsq.NotEq{\"bm.board_id\": nil},\n\t\t\t},\n\t\t})\n\n\trows, err := query.Query()\n\tif err != nil {\n\t\ts.logger.Error(`getTemplateBoards ERROR`, mlog.Err(err))\n\t\treturn nil, err\n\t}\n\tdefer s.CloseRows(rows)\n\n\tuserTemplates, err := s.boardsFromRows(rows)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn userTemplates, nil\n}\n"
  },
  {
    "path": "server/services/store/sqlstore/user.go",
    "content": "package sqlstore\n\nimport (\n\t\"database/sql\"\n\t\"errors\"\n\t\"fmt\"\n\n\tmmModel \"github.com/mattermost/mattermost/server/public/model\"\n\t\"github.com/mattermost/mattermost/server/v8/channels/store\"\n\n\tsq \"github.com/Masterminds/squirrel\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nvar (\n\terrUnsupportedOperation = errors.New(\"unsupported operation\")\n)\n\ntype UserNotFoundError struct {\n\tid string\n}\n\nfunc (unf UserNotFoundError) Error() string {\n\treturn fmt.Sprintf(\"user not found (%s)\", unf.id)\n}\n\nfunc (s *SQLStore) getRegisteredUserCount(db sq.BaseRunner) (int, error) {\n\tquery := s.getQueryBuilder(db).\n\t\tSelect(\"count(*)\").\n\t\tFrom(s.tablePrefix + \"users\").\n\t\tWhere(sq.Eq{\"delete_at\": 0})\n\trow := query.QueryRow()\n\n\tvar count int\n\terr := row.Scan(&count)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\treturn count, nil\n}\n\nfunc (s *SQLStore) getUserByCondition(db sq.BaseRunner, condition sq.Eq) (*model.User, error) {\n\tusers, err := s.getUsersByCondition(db, condition, 0)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(users) == 0 {\n\t\treturn nil, model.NewErrNotFound(\"user\")\n\t}\n\n\treturn users[0], nil\n}\n\nfunc (s *SQLStore) getUsersByCondition(db sq.BaseRunner, condition interface{}, limit uint64) ([]*model.User, error) {\n\tquery := s.getQueryBuilder(db).\n\t\tSelect(\n\t\t\t\"id\",\n\t\t\t\"username\",\n\t\t\t\"email\",\n\t\t\t\"password\",\n\t\t\t\"mfa_secret\",\n\t\t\t\"auth_service\",\n\t\t\t\"auth_data\",\n\t\t\t\"create_at\",\n\t\t\t\"update_at\",\n\t\t\t\"delete_at\",\n\t\t).\n\t\tFrom(s.tablePrefix + \"users\").\n\t\tWhere(sq.Eq{\"delete_at\": 0}).\n\t\tWhere(condition)\n\n\tif limit != 0 {\n\t\tquery = query.Limit(limit)\n\t}\n\n\trows, err := query.Query()\n\tif err != nil {\n\t\ts.logger.Error(`getUsersByCondition ERROR`, mlog.Err(err))\n\t\treturn nil, err\n\t}\n\tdefer s.CloseRows(rows)\n\n\tusers, err := s.usersFromRows(rows)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(users) == 0 {\n\t\treturn nil, model.NewErrNotFound(\"user\")\n\t}\n\n\treturn users, nil\n}\n\nfunc (s *SQLStore) getUserByID(db sq.BaseRunner, userID string) (*model.User, error) {\n\treturn s.getUserByCondition(db, sq.Eq{\"id\": userID})\n}\n\nfunc (s *SQLStore) getUsersList(db sq.BaseRunner, userIDs []string, _, _ bool) ([]*model.User, error) {\n\tusers, err := s.getUsersByCondition(db, sq.Eq{\"id\": userIDs}, 0)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(users) != len(userIDs) {\n\t\treturn users, model.NewErrNotAllFound(\"user\", userIDs)\n\t}\n\n\treturn users, nil\n}\n\nfunc (s *SQLStore) getUserByEmail(db sq.BaseRunner, email string) (*model.User, error) {\n\treturn s.getUserByCondition(db, sq.Eq{\"email\": email})\n}\n\nfunc (s *SQLStore) getUserByUsername(db sq.BaseRunner, username string) (*model.User, error) {\n\treturn s.getUserByCondition(db, sq.Eq{\"username\": username})\n}\n\nfunc (s *SQLStore) createUser(db sq.BaseRunner, user *model.User) (*model.User, error) {\n\tnow := utils.GetMillis()\n\tuser.CreateAt = now\n\tuser.UpdateAt = now\n\tuser.DeleteAt = 0\n\n\tquery := s.getQueryBuilder(db).Insert(s.tablePrefix+\"users\").\n\t\tColumns(\"id\", \"username\", \"email\", \"password\", \"mfa_secret\", \"auth_service\", \"auth_data\", \"create_at\", \"update_at\", \"delete_at\").\n\t\tValues(user.ID, user.Username, user.Email, user.Password, user.MfaSecret, user.AuthService, user.AuthData, user.CreateAt, user.UpdateAt, user.DeleteAt)\n\n\t_, err := query.Exec()\n\treturn user, err\n}\n\nfunc (s *SQLStore) updateUser(db sq.BaseRunner, user *model.User) (*model.User, error) {\n\tnow := utils.GetMillis()\n\tuser.UpdateAt = now\n\n\tquery := s.getQueryBuilder(db).Update(s.tablePrefix+\"users\").\n\t\tSet(\"username\", user.Username).\n\t\tSet(\"email\", user.Email).\n\t\tSet(\"update_at\", user.UpdateAt).\n\t\tWhere(sq.Eq{\"id\": user.ID})\n\n\tresult, err := query.Exec()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trowCount, err := result.RowsAffected()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif rowCount < 1 {\n\t\treturn nil, UserNotFoundError{user.ID}\n\t}\n\n\treturn user, nil\n}\n\nfunc (s *SQLStore) updateUserPassword(db sq.BaseRunner, username, password string) error {\n\tnow := utils.GetMillis()\n\n\tquery := s.getQueryBuilder(db).Update(s.tablePrefix+\"users\").\n\t\tSet(\"password\", password).\n\t\tSet(\"update_at\", now).\n\t\tWhere(sq.Eq{\"username\": username})\n\n\tresult, err := query.Exec()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\trowCount, err := result.RowsAffected()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif rowCount < 1 {\n\t\treturn UserNotFoundError{username}\n\t}\n\n\treturn nil\n}\n\nfunc (s *SQLStore) updateUserPasswordByID(db sq.BaseRunner, userID, password string) error {\n\tnow := utils.GetMillis()\n\n\tquery := s.getQueryBuilder(db).Update(s.tablePrefix+\"users\").\n\t\tSet(\"password\", password).\n\t\tSet(\"update_at\", now).\n\t\tWhere(sq.Eq{\"id\": userID})\n\n\tresult, err := query.Exec()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\trowCount, err := result.RowsAffected()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif rowCount < 1 {\n\t\treturn UserNotFoundError{userID}\n\t}\n\n\treturn nil\n}\n\nfunc (s *SQLStore) getUsersByTeam(db sq.BaseRunner, _ string, _ string, _, _ bool) ([]*model.User, error) {\n\tusers, err := s.getUsersByCondition(db, nil, 0)\n\tif model.IsErrNotFound(err) {\n\t\treturn []*model.User{}, nil\n\t}\n\n\treturn users, err\n}\n\nfunc (s *SQLStore) searchUsersByTeam(db sq.BaseRunner, _ string, searchQuery string, _ string, _, _, _ bool) ([]*model.User, error) {\n\tusers, err := s.getUsersByCondition(db, &sq.Like{\"username\": \"%\" + searchQuery + \"%\"}, 10)\n\tif model.IsErrNotFound(err) {\n\t\treturn []*model.User{}, nil\n\t}\n\n\treturn users, err\n}\n\nfunc (s *SQLStore) usersFromRows(rows *sql.Rows) ([]*model.User, error) {\n\tusers := []*model.User{}\n\n\tfor rows.Next() {\n\t\tvar user model.User\n\n\t\terr := rows.Scan(\n\t\t\t&user.ID,\n\t\t\t&user.Username,\n\t\t\t&user.Email,\n\t\t\t&user.Password,\n\t\t\t&user.MfaSecret,\n\t\t\t&user.AuthService,\n\t\t\t&user.AuthData,\n\t\t\t&user.CreateAt,\n\t\t\t&user.UpdateAt,\n\t\t\t&user.DeleteAt,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tusers = append(users, &user)\n\t}\n\n\treturn users, nil\n}\n\nfunc (s *SQLStore) patchUserPreferences(db sq.BaseRunner, userID string, patch model.UserPreferencesPatch) (mmModel.Preferences, error) {\n\tpreferences, err := s.getUserPreferences(db, userID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(patch.UpdatedFields) > 0 {\n\t\tfor key, value := range patch.UpdatedFields {\n\t\t\tpreference := mmModel.Preference{\n\t\t\t\tUserId:   userID,\n\t\t\t\tCategory: model.PreferencesCategoryFocalboard,\n\t\t\t\tName:     key,\n\t\t\t\tValue:    value,\n\t\t\t}\n\n\t\t\tif err := s.updateUserPreference(db, preference); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tnewPreferences := mmModel.Preferences{}\n\t\t\tfor _, existingPreference := range preferences {\n\t\t\t\tif preference.Name != existingPreference.Name {\n\t\t\t\t\tnewPreferences = append(newPreferences, existingPreference)\n\t\t\t\t}\n\t\t\t}\n\t\t\tnewPreferences = append(newPreferences, preference)\n\t\t\tpreferences = newPreferences\n\t\t}\n\t}\n\n\tif len(patch.DeletedFields) > 0 {\n\t\tfor _, key := range patch.DeletedFields {\n\t\t\tpreference := mmModel.Preference{\n\t\t\t\tUserId:   userID,\n\t\t\t\tCategory: model.PreferencesCategoryFocalboard,\n\t\t\t\tName:     key,\n\t\t\t}\n\n\t\t\tif err := s.deleteUserPreference(db, preference); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tnewPreferences := mmModel.Preferences{}\n\t\t\tfor _, existingPreference := range preferences {\n\t\t\t\tif preference.Name != existingPreference.Name {\n\t\t\t\t\tnewPreferences = append(newPreferences, existingPreference)\n\t\t\t\t}\n\t\t\t}\n\t\t\tpreferences = newPreferences\n\t\t}\n\t}\n\n\treturn preferences, nil\n}\n\nfunc (s *SQLStore) updateUserPreference(db sq.BaseRunner, preference mmModel.Preference) error {\n\tquery := s.getQueryBuilder(db).\n\t\tInsert(s.tablePrefix+\"preferences\").\n\t\tColumns(\"UserId\", \"Category\", \"Name\", \"Value\").\n\t\tValues(preference.UserId, preference.Category, preference.Name, preference.Value)\n\n\tswitch s.dbType {\n\tcase model.MysqlDBType:\n\t\tquery = query.SuffixExpr(sq.Expr(\"ON DUPLICATE KEY UPDATE Value = ?\", preference.Value))\n\tcase model.PostgresDBType:\n\t\tquery = query.SuffixExpr(sq.Expr(\"ON CONFLICT (userid, category, name) DO UPDATE SET Value = ?\", preference.Value))\n\tcase model.SqliteDBType:\n\t\tquery = query.SuffixExpr(sq.Expr(\" on conflict(userid, category, name) do update set value = excluded.value\"))\n\tdefault:\n\t\treturn store.NewErrNotImplemented(\"failed to update preference because of missing driver\")\n\t}\n\n\tif _, err := query.Exec(); err != nil {\n\t\treturn fmt.Errorf(\"failed to upsert user preference in database: userID: %s name: %s value: %s error: %w\", preference.UserId, preference.Name, preference.Value, err)\n\t}\n\n\treturn nil\n}\n\nfunc (s *SQLStore) deleteUserPreference(db sq.BaseRunner, preference mmModel.Preference) error {\n\tquery := s.getQueryBuilder(db).\n\t\tDelete(s.tablePrefix + \"preferences\").\n\t\tWhere(sq.Eq{\"UserId\": preference.UserId}).\n\t\tWhere(sq.Eq{\"Category\": preference.Category}).\n\t\tWhere(sq.Eq{\"Name\": preference.Name})\n\n\tif _, err := query.Exec(); err != nil {\n\t\treturn fmt.Errorf(\"failed to delete user preference from database: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc (s *SQLStore) canSeeUser(db sq.BaseRunner, seerID string, seenID string) (bool, error) {\n\treturn true, nil\n}\n\nfunc (s *SQLStore) sendMessage(db sq.BaseRunner, message, postType string, receipts []string) error {\n\treturn errUnsupportedOperation\n}\n\nfunc (s *SQLStore) postMessage(db sq.BaseRunner, message, postType string, channel string) error {\n\treturn errUnsupportedOperation\n}\n\nfunc (s *SQLStore) getUserTimezone(_ sq.BaseRunner, _ string) (string, error) {\n\treturn \"\", errUnsupportedOperation\n}\n\nfunc (s *SQLStore) getUserPreferences(db sq.BaseRunner, userID string) (mmModel.Preferences, error) {\n\tquery := s.getQueryBuilder(db).\n\t\tSelect(\"userid\", \"category\", \"name\", \"value\").\n\t\tFrom(s.tablePrefix + \"preferences\").\n\t\tWhere(sq.Eq{\n\t\t\t\"userid\":   userID,\n\t\t\t\"category\": model.PreferencesCategoryFocalboard,\n\t\t})\n\n\trows, err := query.Query()\n\tif err != nil {\n\t\ts.logger.Error(\"failed to fetch user preferences\", mlog.String(\"user_id\", userID), mlog.Err(err))\n\t\treturn nil, err\n\t}\n\n\tdefer rows.Close()\n\n\tpreferences, err := s.preferencesFromRows(rows)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn preferences, nil\n}\n\nfunc (s *SQLStore) preferencesFromRows(rows *sql.Rows) ([]mmModel.Preference, error) {\n\tpreferences := []mmModel.Preference{}\n\n\tfor rows.Next() {\n\t\tvar preference mmModel.Preference\n\n\t\terr := rows.Scan(\n\t\t\t&preference.UserId,\n\t\t\t&preference.Category,\n\t\t\t&preference.Name,\n\t\t\t&preference.Value,\n\t\t)\n\n\t\tif err != nil {\n\t\t\ts.logger.Error(\"failed to scan row for user preference\", mlog.Err(err))\n\t\t\treturn nil, err\n\t\t}\n\n\t\tpreferences = append(preferences, preference)\n\t}\n\n\treturn preferences, nil\n}\n"
  },
  {
    "path": "server/services/store/sqlstore/util.go",
    "content": "package sqlstore\n\nimport (\n\t\"database/sql\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\tsq \"github.com/Masterminds/squirrel\"\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nfunc (s *SQLStore) CloseRows(rows *sql.Rows) {\n\tif err := rows.Close(); err != nil {\n\t\ts.logger.Error(\"error closing MattermostAuthLayer row set\", mlog.Err(err))\n\t}\n}\n\nfunc (s *SQLStore) IsErrNotFound(err error) bool {\n\treturn model.IsErrNotFound(err)\n}\n\nfunc (s *SQLStore) MarshalJSONB(data interface{}) ([]byte, error) {\n\tb, err := json.Marshal(data)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif s.isBinaryParam {\n\t\tb = append([]byte{0x01}, b...)\n\t}\n\n\treturn b, nil\n}\n\nfunc PrepareNewTestDatabase() (dbType string, connectionString string, err error) {\n\tdbType = strings.TrimSpace(os.Getenv(\"FOCALBOARD_STORE_TEST_DB_TYPE\"))\n\tif dbType == \"\" {\n\t\tdbType = model.SqliteDBType\n\t}\n\tif dbType == \"mariadb\" {\n\t\tdbType = model.MysqlDBType\n\t}\n\n\tvar dbName string\n\tvar rootUser string\n\n\tif dbType == model.SqliteDBType {\n\t\tfile, err := os.CreateTemp(\"\", \"fbtest_*.db\")\n\t\tif err != nil {\n\t\t\treturn \"\", \"\", err\n\t\t}\n\t\tconnectionString = file.Name() + \"?_busy_timeout=5000\"\n\t\t_ = file.Close()\n\t} else if port := strings.TrimSpace(os.Getenv(\"FOCALBOARD_STORE_TEST_DOCKER_PORT\")); port != \"\" {\n\t\t// docker unit tests take priority over any DSN env vars\n\t\tvar template string\n\t\tswitch dbType {\n\t\tcase model.MysqlDBType:\n\t\t\ttemplate = \"%s:mostest@tcp(localhost:%s)/%s?charset=utf8mb4,utf8&writeTimeout=30s\"\n\t\t\trootUser = \"root\"\n\t\tcase model.PostgresDBType:\n\t\t\ttemplate = \"postgres://%s:mostest@localhost:%s/%s?sslmode=disable\\u0026connect_timeout=10\"\n\t\t\trootUser = \"mmuser\"\n\t\tdefault:\n\t\t\treturn \"\", \"\", newErrInvalidDBType(dbType)\n\t\t}\n\n\t\tconnectionString = fmt.Sprintf(template, rootUser, port, \"\")\n\n\t\t// create a new database each run\n\t\tsqlDB, err := sql.Open(dbType, connectionString)\n\t\tif err != nil {\n\t\t\treturn \"\", \"\", fmt.Errorf(\"cannot connect to %s database: %w\", dbType, err)\n\t\t}\n\t\tdefer sqlDB.Close()\n\n\t\terr = sqlDB.Ping()\n\t\tif err != nil {\n\t\t\treturn \"\", \"\", fmt.Errorf(\"cannot ping %s database: %w\", dbType, err)\n\t\t}\n\n\t\tdbName = \"testdb_\" + utils.NewID(utils.IDTypeNone)[:8]\n\t\t_, err = sqlDB.Exec(fmt.Sprintf(\"CREATE DATABASE %s;\", dbName))\n\t\tif err != nil {\n\t\t\treturn \"\", \"\", fmt.Errorf(\"cannot create %s database %s: %w\", dbType, dbName, err)\n\t\t}\n\n\t\tif dbType != model.PostgresDBType {\n\t\t\t_, err = sqlDB.Exec(fmt.Sprintf(\"GRANT ALL PRIVILEGES ON %s.* TO mmuser;\", dbName))\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", \"\", fmt.Errorf(\"cannot grant permissions on %s database %s: %w\", dbType, dbName, err)\n\t\t\t}\n\t\t}\n\n\t\tconnectionString = fmt.Sprintf(template, \"mmuser\", port, dbName)\n\t} else {\n\t\t// mysql or postgres need a DSN (connection string)\n\t\tconnectionString = strings.TrimSpace(os.Getenv(\"FOCALBOARD_STORE_TEST_CONN_STRING\"))\n\t}\n\n\treturn dbType, connectionString, nil\n}\n\ntype ErrInvalidDBType struct {\n\tdbType string\n}\n\nfunc newErrInvalidDBType(dbType string) error {\n\treturn ErrInvalidDBType{\n\t\tdbType: dbType,\n\t}\n}\n\nfunc (e ErrInvalidDBType) Error() string {\n\treturn \"unsupported database type: \" + e.dbType\n}\n\n// deleteBoardRecord deletes a boards record without deleting any child records in the blocks table.\n// FOR UNIT TESTING ONLY.\nfunc (s *SQLStore) deleteBoardRecord(db sq.BaseRunner, boardID string, modifiedBy string) error {\n\treturn s.deleteBoardAndChildren(db, boardID, modifiedBy, true)\n}\n\n// deleteBlockRecord deletes a blocks record without deleting any child records in the blocks table.\n// FOR UNIT TESTING ONLY.\nfunc (s *SQLStore) deleteBlockRecord(db sq.BaseRunner, blockID, modifiedBy string) error {\n\treturn s.deleteBlockAndChildren(db, blockID, modifiedBy, true)\n}\n\nfunc (s *SQLStore) castInt(val int64, as string) string {\n\tif s.dbType == model.MysqlDBType {\n\t\treturn fmt.Sprintf(\"cast(%d as unsigned) AS %s\", val, as)\n\t}\n\treturn fmt.Sprintf(\"cast(%d as bigint) AS %s\", val, as)\n}\n\nfunc (s *SQLStore) GetSchemaName() (string, error) {\n\tvar query sq.SelectBuilder\n\n\tswitch s.dbType {\n\tcase model.MysqlDBType:\n\t\tquery = s.getQueryBuilder(s.db).Select(\"DATABASE()\")\n\tcase model.PostgresDBType:\n\t\tquery = s.getQueryBuilder(s.db).Select(\"current_schema()\")\n\tcase model.SqliteDBType:\n\t\treturn \"\", nil\n\tdefault:\n\t\treturn \"\", ErrUnsupportedDatabaseType\n\t}\n\n\tscanner := query.QueryRow()\n\n\tvar result string\n\terr := scanner.Scan(&result)\n\tif err != nil && !model.IsErrNotFound(err) {\n\t\treturn \"\", err\n\t}\n\n\treturn result, nil\n}\n"
  },
  {
    "path": "server/services/store/store.go",
    "content": "//go:generate mockgen -destination=mockstore/mockstore.go -package mockstore . Store\n//go:generate go run ./generators/main.go\npackage store\n\nimport (\n\t\"time\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\n\tmmModel \"github.com/mattermost/mattermost/server/public/model\"\n)\n\nconst CardLimitTimestampSystemKey = \"card_limit_timestamp\"\n\n// Store represents the abstraction of the data storage.\ntype Store interface {\n\tGetBlocks(opts model.QueryBlocksOptions) ([]*model.Block, error)\n\tGetBlocksWithParentAndType(boardID, parentID string, blockType string) ([]*model.Block, error)\n\tGetBlocksWithParent(boardID, parentID string) ([]*model.Block, error)\n\tGetBlocksByIDs(ids []string) ([]*model.Block, error)\n\tGetBlocksWithType(boardID, blockType string) ([]*model.Block, error)\n\tGetSubTree2(boardID, blockID string, opts model.QuerySubtreeOptions) ([]*model.Block, error)\n\tGetBlocksForBoard(boardID string) ([]*model.Block, error)\n\t// @withTransaction\n\tInsertBlock(block *model.Block, userID string) error\n\t// @withTransaction\n\tDeleteBlock(blockID string, modifiedBy string) error\n\t// @withTransaction\n\tInsertBlocks(blocks []*model.Block, userID string) error\n\t// @withTransaction\n\tUndeleteBlock(blockID string, modifiedBy string) error\n\t// @withTransaction\n\tUndeleteBoard(boardID string, modifiedBy string) error\n\tGetBlockCountsByType() (map[string]int64, error)\n\tGetBoardCount() (int64, error)\n\tGetBlock(blockID string) (*model.Block, error)\n\t// @withTransaction\n\tPatchBlock(blockID string, blockPatch *model.BlockPatch, userID string) error\n\tGetBlockHistory(blockID string, opts model.QueryBlockHistoryOptions) ([]*model.Block, error)\n\tGetBlockHistoryDescendants(boardID string, opts model.QueryBlockHistoryOptions) ([]*model.Block, error)\n\tGetBlockHistoryNewestChildren(parentID string, opts model.QueryBlockHistoryChildOptions) ([]*model.Block, bool, error)\n\tGetBoardHistory(boardID string, opts model.QueryBoardHistoryOptions) ([]*model.Board, error)\n\tGetBoardAndCardByID(blockID string) (board *model.Board, card *model.Block, err error)\n\tGetBoardAndCard(block *model.Block) (board *model.Board, card *model.Block, err error)\n\t// @withTransaction\n\tDuplicateBoard(boardID string, userID string, toTeam string, asTemplate bool) (*model.BoardsAndBlocks, []*model.BoardMember, error)\n\t// @withTransaction\n\tDuplicateBlock(boardID string, blockID string, userID string, asTemplate bool) ([]*model.Block, error)\n\t// @withTransaction\n\tPatchBlocks(blockPatches *model.BlockPatchBatch, userID string) error\n\n\tShutdown() error\n\n\tGetSystemSetting(key string) (string, error)\n\tGetSystemSettings() (map[string]string, error)\n\tSetSystemSetting(key, value string) error\n\n\tGetRegisteredUserCount() (int, error)\n\tGetUserByID(userID string) (*model.User, error)\n\tGetUsersList(userIDs []string, showEmail, showName bool) ([]*model.User, error)\n\tGetUserByEmail(email string) (*model.User, error)\n\tGetUserByUsername(username string) (*model.User, error)\n\tCreateUser(user *model.User) (*model.User, error)\n\tUpdateUser(user *model.User) (*model.User, error)\n\tUpdateUserPassword(username, password string) error\n\tUpdateUserPasswordByID(userID, password string) error\n\tGetUsersByTeam(teamID string, asGuestID string, showEmail, showName bool) ([]*model.User, error)\n\tSearchUsersByTeam(teamID string, searchQuery string, asGuestID string, excludeBots bool, showEmail, showName bool) ([]*model.User, error)\n\tPatchUserPreferences(userID string, patch model.UserPreferencesPatch) (mmModel.Preferences, error)\n\tGetUserPreferences(userID string) (mmModel.Preferences, error)\n\n\tGetActiveUserCount(updatedSecondsAgo int64) (int, error)\n\tGetSession(token string, expireTime int64) (*model.Session, error)\n\tCreateSession(session *model.Session) error\n\tRefreshSession(session *model.Session) error\n\tUpdateSession(session *model.Session) error\n\tDeleteSession(sessionID string) error\n\tCleanUpSessions(expireTime int64) error\n\n\tUpsertSharing(sharing model.Sharing) error\n\tGetSharing(rootID string) (*model.Sharing, error)\n\n\tUpsertTeamSignupToken(team model.Team) error\n\tUpsertTeamSettings(team model.Team) error\n\tGetTeam(ID string) (*model.Team, error)\n\tGetTeamsForUser(userID string) ([]*model.Team, error)\n\tGetAllTeams() ([]*model.Team, error)\n\tGetTeamCount() (int64, error)\n\n\tInsertBoard(board *model.Board, userID string) (*model.Board, error)\n\t// @withTransaction\n\tInsertBoardWithAdmin(board *model.Board, userID string) (*model.Board, *model.BoardMember, error)\n\t// @withTransaction\n\tPatchBoard(boardID string, boardPatch *model.BoardPatch, userID string) (*model.Board, error)\n\tGetBoard(id string) (*model.Board, error)\n\tGetBoardsForUserAndTeam(userID, teamID string, includePublicBoards bool) ([]*model.Board, error)\n\tGetBoardsInTeamByIds(boardIDs []string, teamID string) ([]*model.Board, error)\n\t// @withTransaction\n\tDeleteBoard(boardID, userID string) error\n\n\tSaveMember(bm *model.BoardMember) (*model.BoardMember, error)\n\tDeleteMember(boardID, userID string) error\n\tGetMemberForBoard(boardID, userID string) (*model.BoardMember, error)\n\tGetBoardMemberHistory(boardID, userID string, limit uint64) ([]*model.BoardMemberHistoryEntry, error)\n\tGetMembersForBoard(boardID string) ([]*model.BoardMember, error)\n\tGetMembersForUser(userID string) ([]*model.BoardMember, error)\n\tCanSeeUser(seerID string, seenID string) (bool, error)\n\tSearchBoardsForUser(term string, searchField model.BoardSearchField, userID string, includePublicBoards bool) ([]*model.Board, error)\n\tSearchBoardsForUserInTeam(teamID, term, userID string) ([]*model.Board, error)\n\n\t// @withTransaction\n\tCreateBoardsAndBlocksWithAdmin(bab *model.BoardsAndBlocks, userID string) (*model.BoardsAndBlocks, []*model.BoardMember, error)\n\t// @withTransaction\n\tCreateBoardsAndBlocks(bab *model.BoardsAndBlocks, userID string) (*model.BoardsAndBlocks, error)\n\t// @withTransaction\n\tPatchBoardsAndBlocks(pbab *model.PatchBoardsAndBlocks, userID string) (*model.BoardsAndBlocks, error)\n\t// @withTransaction\n\tDeleteBoardsAndBlocks(dbab *model.DeleteBoardsAndBlocks, userID string) error\n\n\tGetCategory(id string) (*model.Category, error)\n\n\tGetUserCategories(userID, teamID string) ([]model.Category, error)\n\t// @withTransaction\n\tCreateCategory(category model.Category) error\n\tUpdateCategory(category model.Category) error\n\tDeleteCategory(categoryID, userID, teamID string) error\n\tReorderCategories(userID, teamID string, newCategoryOrder []string) ([]string, error)\n\n\tGetUserCategoryBoards(userID, teamID string) ([]model.CategoryBoards, error)\n\n\tGetFileInfo(id string) (*mmModel.FileInfo, error)\n\tSaveFileInfo(fileInfo *mmModel.FileInfo) error\n\n\t// @withTransaction\n\tAddUpdateCategoryBoard(userID, categoryID string, boardIDs []string) error\n\tReorderCategoryBoards(categoryID string, newBoardsOrder []string) ([]string, error)\n\tSetBoardVisibility(userID, categoryID, boardID string, visible bool) error\n\n\tCreateSubscription(sub *model.Subscription) (*model.Subscription, error)\n\tDeleteSubscription(blockID string, subscriberID string) error\n\tGetSubscription(blockID string, subscriberID string) (*model.Subscription, error)\n\tGetSubscriptions(subscriberID string) ([]*model.Subscription, error)\n\tGetSubscribersForBlock(blockID string) ([]*model.Subscriber, error)\n\tGetSubscribersCountForBlock(blockID string) (int, error)\n\tUpdateSubscribersNotifiedAt(blockID string, notifiedAt int64) error\n\n\tUpsertNotificationHint(hint *model.NotificationHint, notificationFreq time.Duration) (*model.NotificationHint, error)\n\tDeleteNotificationHint(blockID string) error\n\tGetNotificationHint(blockID string) (*model.NotificationHint, error)\n\tGetNextNotificationHint(remove bool) (*model.NotificationHint, error)\n\n\tRemoveDefaultTemplates(boards []*model.Board) error\n\tGetTemplateBoards(teamID, userID string) ([]*model.Board, error)\n\n\t// @withTransaction\n\tRunDataRetention(globalRetentionDate int64, batchSize int64) (int64, error)\n\n\tGetUsedCardsCount() (int, error)\n\tGetCardLimitTimestamp() (int64, error)\n\tUpdateCardLimitTimestamp(cardLimit int) (int64, error)\n\n\tDBType() string\n\tDBVersion() string\n\n\tGetLicense() *mmModel.License\n\tSearchUserChannels(teamID, userID, query string) ([]*mmModel.Channel, error)\n\tGetChannel(teamID, channelID string) (*mmModel.Channel, error)\n\tPostMessage(message, postType, channelID string) error\n\tSendMessage(message, postType string, receipts []string) error\n\n\tGetUserTimezone(userID string) (string, error)\n\n\t// Compliance\n\tGetBoardsForCompliance(opts model.QueryBoardsForComplianceOptions) ([]*model.Board, bool, error)\n\tGetBoardsComplianceHistory(opts model.QueryBoardsComplianceHistoryOptions) ([]*model.BoardHistory, bool, error)\n\tGetBlocksComplianceHistory(opts model.QueryBlocksComplianceHistoryOptions) ([]*model.BlockHistory, bool, error)\n\n\t// For unit testing only\n\tDeleteBoardRecord(boardID, modifiedBy string) error\n\tDeleteBlockRecord(blockID, modifiedBy string) error\n}\n\ntype NotSupportedError struct {\n\tmsg string\n}\n\nfunc NewNotSupportedError(msg string) NotSupportedError {\n\treturn NotSupportedError{msg: msg}\n}\n\nfunc (pe NotSupportedError) Error() string {\n\treturn pe.msg\n}\n"
  },
  {
    "path": "server/services/store/storetests/blocks.go",
    "content": "package storetests\n\nimport (\n\t\"math\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/store\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nconst (\n\ttestUserID  = \"user-id\"\n\ttestTeamID  = \"team-id\"\n\ttestBoardID = \"board-id\"\n)\n\nfunc StoreTestBlocksStore(t *testing.T, setup func(t *testing.T) (store.Store, func())) {\n\tt.Run(\"InsertBlock\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestInsertBlock(t, store)\n\t})\n\tt.Run(\"InsertBlocks\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestInsertBlocks(t, store)\n\t})\n\tt.Run(\"PatchBlock\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestPatchBlock(t, store)\n\t})\n\tt.Run(\"PatchBlocks\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestPatchBlocks(t, store)\n\t})\n\tt.Run(\"DeleteBlock\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestDeleteBlock(t, store)\n\t})\n\tt.Run(\"UndeleteBlock\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestUndeleteBlock(t, store)\n\t})\n\tt.Run(\"GetSubTree2\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestGetSubTree2(t, store)\n\t})\n\tt.Run(\"GetBlocks\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestGetBlocks(t, store)\n\t})\n\tt.Run(\"GetBlock\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestGetBlock(t, store)\n\t})\n\tt.Run(\"DuplicateBlock\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestDuplicateBlock(t, store)\n\t})\n\tt.Run(\"GetBlockMetadata\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestGetBlockMetadata(t, store)\n\t})\n\tt.Run(\"UndeleteBlockChildren\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestUndeleteBlockChildren(t, store)\n\t})\n\tt.Run(\"GetBlockHistoryNewestChildren\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestGetBlockHistoryNewestChildren(t, store)\n\t})\n}\n\nfunc testInsertBlock(t *testing.T, store store.Store) {\n\tuserID := testUserID\n\tboardID := testBoardID\n\n\tblocks, errBlocks := store.GetBlocksForBoard(boardID)\n\trequire.NoError(t, errBlocks)\n\tinitialCount := len(blocks)\n\n\tt.Run(\"valid block\", func(t *testing.T) {\n\t\tblock := &model.Block{\n\t\t\tID:         \"id-test\",\n\t\t\tBoardID:    boardID,\n\t\t\tModifiedBy: userID,\n\t\t}\n\n\t\terr := store.InsertBlock(block, \"user-id-1\")\n\t\trequire.NoError(t, err)\n\n\t\tblocks, err := store.GetBlocksForBoard(boardID)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, blocks, initialCount+1)\n\t})\n\n\tt.Run(\"invalid rootid\", func(t *testing.T) {\n\t\tblock := &model.Block{\n\t\t\tID:         \"id-test\",\n\t\t\tBoardID:    \"\",\n\t\t\tModifiedBy: userID,\n\t\t}\n\n\t\terr := store.InsertBlock(block, \"user-id-1\")\n\t\trequire.Error(t, err)\n\n\t\tblocks, err := store.GetBlocksForBoard(boardID)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, blocks, initialCount+1)\n\t})\n\n\tt.Run(\"invalid fields data\", func(t *testing.T) {\n\t\tblock := &model.Block{\n\t\t\tID:         \"id-test\",\n\t\t\tBoardID:    \"id-test\",\n\t\t\tModifiedBy: userID,\n\t\t\tFields:     map[string]interface{}{\"no-serialiable-value\": t.Run},\n\t\t}\n\n\t\terr := store.InsertBlock(block, \"user-id-1\")\n\t\trequire.Error(t, err)\n\n\t\tblocks, err := store.GetBlocksForBoard(boardID)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, blocks, initialCount+1)\n\t})\n\n\tt.Run(\"block with title too large\", func(t *testing.T) {\n\t\tblock := &model.Block{\n\t\t\tID:         \"id-test\",\n\t\t\tBoardID:    boardID,\n\t\t\tModifiedBy: userID,\n\t\t\tTitle:      strings.Repeat(\"A\", model.BlockTitleMaxRunes+1),\n\t\t}\n\n\t\terr := store.InsertBlock(block, \"user-id-1\")\n\t\trequire.ErrorIs(t, err, model.ErrBlockTitleSizeLimitExceeded)\n\t})\n\n\tt.Run(\"block with aggregated fields size too large\", func(t *testing.T) {\n\t\tblock := &model.Block{\n\t\t\tID:         \"id-test\",\n\t\t\tBoardID:    boardID,\n\t\t\tModifiedBy: userID,\n\t\t\tFields: map[string]any{\n\t\t\t\t\"one\":   strings.Repeat(\"1\", model.BlockFieldsMaxRunes/4),\n\t\t\t\t\"two\":   strings.Repeat(\"2\", model.BlockFieldsMaxRunes/4),\n\t\t\t\t\"three\": strings.Repeat(\"3\", model.BlockFieldsMaxRunes/4),\n\t\t\t\t\"four\":  strings.Repeat(\"4\", model.BlockFieldsMaxRunes/4),\n\t\t\t},\n\t\t}\n\n\t\terr := store.InsertBlock(block, \"user-id-2\")\n\t\trequire.ErrorIs(t, err, model.ErrBlockFieldsSizeLimitExceeded)\n\t})\n\n\tt.Run(\"insert new block\", func(t *testing.T) {\n\t\tblock := &model.Block{\n\t\t\tBoardID: testBoardID,\n\t\t}\n\n\t\terr := store.InsertBlock(block, \"user-id-2\")\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"user-id-2\", block.CreatedBy)\n\t})\n\n\tt.Run(\"update existing block\", func(t *testing.T) {\n\t\tblock := &model.Block{\n\t\t\tID:      \"id-2\",\n\t\t\tBoardID: \"board-id-1\",\n\t\t\tTitle:   \"Old Title\",\n\t\t}\n\n\t\t// inserting\n\t\terr := store.InsertBlock(block, \"user-id-2\")\n\t\trequire.NoError(t, err)\n\n\t\t// created by populated from user id for new blocks\n\t\trequire.Equal(t, \"user-id-2\", block.CreatedBy)\n\n\t\t// hack to avoid multiple, quick updates to a card\n\t\t// violating block_history composite primary key constraint\n\t\ttime.Sleep(1 * time.Millisecond)\n\n\t\t// updating\n\t\tnewBlock := &model.Block{\n\t\t\tID:        \"id-2\",\n\t\t\tBoardID:   \"board-id-1\",\n\t\t\tCreatedBy: \"user-id-3\",\n\t\t\tTitle:     \"New Title\",\n\t\t}\n\t\terr = store.InsertBlock(newBlock, \"user-id-4\")\n\t\trequire.NoError(t, err)\n\t\t// created by is not altered for existing blocks\n\t\trequire.Equal(t, \"user-id-3\", newBlock.CreatedBy)\n\t\trequire.Equal(t, \"New Title\", newBlock.Title)\n\t})\n\n\tt.Run(\"update existing block with title too large\", func(t *testing.T) {\n\t\tblock := &model.Block{\n\t\t\tID:        \"id-3\",\n\t\t\tBoardID:   \"board-id-1\",\n\t\t\tCreatedBy: \"user-id-3\",\n\t\t\tTitle:     \"New Title\",\n\t\t}\n\n\t\t// inserting\n\t\terr := store.InsertBlock(block, \"user-id-3\")\n\t\trequire.NoError(t, err)\n\n\t\t// created by populated from user id for new blocks\n\t\trequire.Equal(t, \"user-id-3\", block.CreatedBy)\n\n\t\t// hack to avoid multiple, quick updates to a card\n\t\t// violating block_history composite primary key constraint\n\t\ttime.Sleep(1 * time.Millisecond)\n\n\t\t// updating\n\t\tnewBlock := &model.Block{\n\t\t\tID:        \"id-3\",\n\t\t\tBoardID:   \"board-id-1\",\n\t\t\tCreatedBy: \"user-id-3\",\n\t\t\tTitle:     strings.Repeat(\"A\", model.BlockTitleMaxRunes+1),\n\t\t}\n\t\terr = store.InsertBlock(newBlock, \"user-id-3\")\n\t\trequire.ErrorIs(t, err, model.ErrBlockTitleSizeLimitExceeded)\n\t})\n\n\tt.Run(\"update existing block with aggregated fields size too large\", func(t *testing.T) {\n\t\tblock := &model.Block{\n\t\t\tID:        \"id-3\",\n\t\t\tBoardID:   \"board-id-1\",\n\t\t\tCreatedBy: \"user-id-3\",\n\t\t\tTitle:     \"New Title\",\n\t\t}\n\n\t\t// inserting\n\t\terr := store.InsertBlock(block, \"user-id-3\")\n\t\trequire.NoError(t, err)\n\n\t\t// created by populated from user id for new blocks\n\t\trequire.Equal(t, \"user-id-3\", block.CreatedBy)\n\n\t\t// hack to avoid multiple, quick updates to a card\n\t\t// violating block_history composite primary key constraint\n\t\ttime.Sleep(1 * time.Millisecond)\n\n\t\t// updating\n\t\tnewBlock := &model.Block{\n\t\t\tID:        \"id-3\",\n\t\t\tBoardID:   \"board-id-1\",\n\t\t\tCreatedBy: \"user-id-3\",\n\t\t\tFields: map[string]any{\n\t\t\t\t\"one\":   strings.Repeat(\"1\", model.BlockFieldsMaxRunes/4),\n\t\t\t\t\"two\":   strings.Repeat(\"2\", model.BlockFieldsMaxRunes/4),\n\t\t\t\t\"three\": strings.Repeat(\"3\", model.BlockFieldsMaxRunes/4),\n\t\t\t\t\"four\":  strings.Repeat(\"4\", model.BlockFieldsMaxRunes/4),\n\t\t\t},\n\t\t}\n\t\terr = store.InsertBlock(newBlock, \"user-id-3\")\n\t\trequire.ErrorIs(t, err, model.ErrBlockFieldsSizeLimitExceeded)\n\t})\n\n\tcreatedAt, err := time.Parse(time.RFC822, \"01 Jan 90 01:00 IST\")\n\tassert.NoError(t, err)\n\n\tupdateAt, err := time.Parse(time.RFC822, \"02 Jan 90 01:00 IST\")\n\tassert.NoError(t, err)\n\n\tt.Run(\"data tamper attempt\", func(t *testing.T) {\n\t\tblock := &model.Block{\n\t\t\tID:         \"id-10\",\n\t\t\tBoardID:    \"board-id-1\",\n\t\t\tTitle:      \"Old Title\",\n\t\t\tCreateAt:   utils.GetMillisForTime(createdAt),\n\t\t\tUpdateAt:   utils.GetMillisForTime(updateAt),\n\t\t\tCreatedBy:  \"user-id-5\",\n\t\t\tModifiedBy: \"user-id-6\",\n\t\t}\n\n\t\t// inserting\n\t\terr := store.InsertBlock(block, \"user-id-1\")\n\t\trequire.NoError(t, err)\n\t\texpectedTime := time.Now()\n\n\t\tretrievedBlock, err := store.GetBlock(\"id-10\")\n\t\tassert.NoError(t, err)\n\t\tassert.NotNil(t, retrievedBlock)\n\t\tassert.Equal(t, \"board-id-1\", retrievedBlock.BoardID)\n\t\tassert.Equal(t, \"user-id-1\", retrievedBlock.CreatedBy)\n\t\tassert.Equal(t, \"user-id-1\", retrievedBlock.ModifiedBy)\n\t\tassert.WithinDurationf(t, expectedTime, utils.GetTimeForMillis(retrievedBlock.CreateAt), 1*time.Second, \"create time should be current time\")\n\t\tassert.WithinDurationf(t, expectedTime, utils.GetTimeForMillis(retrievedBlock.UpdateAt), 1*time.Second, \"update time should be current time\")\n\t})\n}\n\nfunc testInsertBlocks(t *testing.T, store store.Store) {\n\tuserID := testUserID\n\n\tblocks, errBlocks := store.GetBlocksForBoard(\"id-test\")\n\trequire.NoError(t, errBlocks)\n\tinitialCount := len(blocks)\n\n\tt.Run(\"invalid block\", func(t *testing.T) {\n\t\tvalidBlock := &model.Block{\n\t\t\tID:         \"id-test\",\n\t\t\tBoardID:    \"id-test\",\n\t\t\tModifiedBy: userID,\n\t\t}\n\n\t\tinvalidBlock := &model.Block{\n\t\t\tID:         \"id-test\",\n\t\t\tBoardID:    \"\",\n\t\t\tModifiedBy: userID,\n\t\t}\n\n\t\tnewBlocks := []*model.Block{validBlock, invalidBlock}\n\n\t\ttime.Sleep(1 * time.Millisecond)\n\t\terr := store.InsertBlocks(newBlocks, \"user-id-1\")\n\t\trequire.Error(t, err)\n\n\t\tblocks, err := store.GetBlocksForBoard(\"id-test\")\n\t\trequire.NoError(t, err)\n\t\t// no blocks should have been inserted\n\t\trequire.Len(t, blocks, initialCount)\n\t})\n}\n\nfunc testPatchBlock(t *testing.T, store store.Store) {\n\tuserID := testUserID\n\tboardID := \"board-id-1\"\n\n\tblock := &model.Block{\n\t\tID:         \"id-test\",\n\t\tBoardID:    boardID,\n\t\tTitle:      \"oldTitle\",\n\t\tModifiedBy: userID,\n\t\tFields:     map[string]interface{}{\"test\": \"test value\", \"test2\": \"test value 2\"},\n\t}\n\n\terr := store.InsertBlock(block, \"user-id-1\")\n\trequire.NoError(t, err)\n\n\tblocks, errBlocks := store.GetBlocksForBoard(boardID)\n\trequire.NoError(t, errBlocks)\n\tinitialCount := len(blocks)\n\n\tt.Run(\"not existing block id\", func(t *testing.T) {\n\t\terr := store.PatchBlock(\"invalid-block-id\", &model.BlockPatch{}, \"user-id-1\")\n\t\tvar nf *model.ErrNotFound\n\t\trequire.ErrorAs(t, err, &nf)\n\t\trequire.True(t, model.IsErrNotFound(err))\n\n\t\tblocks, err := store.GetBlocksForBoard(boardID)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, blocks, initialCount)\n\t})\n\n\tt.Run(\"invalid fields data\", func(t *testing.T) {\n\t\tblockPatch := &model.BlockPatch{\n\t\t\tUpdatedFields: map[string]interface{}{\"no-serialiable-value\": t.Run},\n\t\t}\n\n\t\terr := store.PatchBlock(\"id-test\", blockPatch, \"user-id-1\")\n\t\trequire.Error(t, err)\n\n\t\tblocks, err := store.GetBlocksForBoard(boardID)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, blocks, initialCount)\n\t})\n\n\tt.Run(\"update block fields\", func(t *testing.T) {\n\t\tnewTitle := \"New title\"\n\t\tblockPatch := model.BlockPatch{\n\t\t\tTitle: &newTitle,\n\t\t}\n\n\t\t// Wait for not colliding the ID+insert_at key\n\t\ttime.Sleep(1 * time.Millisecond)\n\n\t\t// inserting\n\t\terr := store.PatchBlock(\"id-test\", &blockPatch, \"user-id-2\")\n\t\trequire.NoError(t, err)\n\n\t\tretrievedBlock, err := store.GetBlock(\"id-test\")\n\t\trequire.NoError(t, err)\n\n\t\t// created by populated from user id for new blocks\n\t\trequire.Equal(t, \"user-id-2\", retrievedBlock.ModifiedBy)\n\t\trequire.Equal(t, \"New title\", retrievedBlock.Title)\n\t})\n\n\tt.Run(\"update block custom fields\", func(t *testing.T) {\n\t\tblockPatch := &model.BlockPatch{\n\t\t\tUpdatedFields: map[string]interface{}{\"test\": \"new test value\", \"test3\": \"new value\"},\n\t\t}\n\n\t\t// Wait for not colliding the ID+insert_at key\n\t\ttime.Sleep(1 * time.Millisecond)\n\n\t\t// inserting\n\t\terr := store.PatchBlock(\"id-test\", blockPatch, \"user-id-2\")\n\t\trequire.NoError(t, err)\n\n\t\tretrievedBlock, err := store.GetBlock(\"id-test\")\n\t\trequire.NoError(t, err)\n\n\t\t// created by populated from user id for new blocks\n\t\trequire.Equal(t, \"user-id-2\", retrievedBlock.ModifiedBy)\n\t\trequire.Equal(t, \"new test value\", retrievedBlock.Fields[\"test\"])\n\t\trequire.Equal(t, \"test value 2\", retrievedBlock.Fields[\"test2\"])\n\t\trequire.Equal(t, \"new value\", retrievedBlock.Fields[\"test3\"])\n\t})\n\n\tt.Run(\"remove block custom fields\", func(t *testing.T) {\n\t\tblockPatch := &model.BlockPatch{\n\t\t\tDeletedFields: []string{\"test\", \"test3\", \"test100\"},\n\t\t}\n\n\t\t// Wait for not colliding the ID+insert_at key\n\t\ttime.Sleep(1 * time.Millisecond)\n\n\t\t// inserting\n\t\terr := store.PatchBlock(\"id-test\", blockPatch, \"user-id-2\")\n\t\trequire.NoError(t, err)\n\n\t\tretrievedBlock, err := store.GetBlock(\"id-test\")\n\t\trequire.NoError(t, err)\n\n\t\t// created by populated from user id for new blocks\n\t\trequire.Equal(t, \"user-id-2\", retrievedBlock.ModifiedBy)\n\t\trequire.Equal(t, nil, retrievedBlock.Fields[\"test\"])\n\t\trequire.Equal(t, \"test value 2\", retrievedBlock.Fields[\"test2\"])\n\t\trequire.Equal(t, nil, retrievedBlock.Fields[\"test3\"])\n\t})\n}\n\nfunc testPatchBlocks(t *testing.T, store store.Store) {\n\tblock := &model.Block{\n\t\tID:      \"id-test\",\n\t\tBoardID: \"id-test\",\n\t\tTitle:   \"oldTitle\",\n\t}\n\n\tblock2 := &model.Block{\n\t\tID:      \"id-test2\",\n\t\tBoardID: \"id-test2\",\n\t\tTitle:   \"oldTitle2\",\n\t}\n\n\tinsertBlocks := []*model.Block{block, block2}\n\terr := store.InsertBlocks(insertBlocks, \"user-id-1\")\n\trequire.NoError(t, err)\n\n\tt.Run(\"successful updated existing blocks\", func(t *testing.T) {\n\t\ttitle := \"updatedTitle\"\n\t\tblockPatch := model.BlockPatch{\n\t\t\tTitle: &title,\n\t\t}\n\n\t\tblockPatch2 := model.BlockPatch{\n\t\t\tTitle: &title,\n\t\t}\n\n\t\tblockIds := []string{\"id-test\", \"id-test2\"}\n\t\tblockPatches := []model.BlockPatch{blockPatch, blockPatch2}\n\n\t\ttime.Sleep(1 * time.Millisecond)\n\t\terr := store.PatchBlocks(&model.BlockPatchBatch{BlockIDs: blockIds, BlockPatches: blockPatches}, \"user-id-1\")\n\t\trequire.NoError(t, err)\n\n\t\tretrievedBlock, err := store.GetBlock(\"id-test\")\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, title, retrievedBlock.Title)\n\n\t\tretrievedBlock2, err := store.GetBlock(\"id-test2\")\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, title, retrievedBlock2.Title)\n\t})\n\n\tt.Run(\"invalid block id, nothing updated existing blocks\", func(t *testing.T) {\n\t\tif store.DBType() == model.SqliteDBType {\n\t\t\tt.Skip(\"No transactions support int sqlite\")\n\t\t}\n\n\t\ttitle := \"Another Title\"\n\t\tblockPatch := model.BlockPatch{\n\t\t\tTitle: &title,\n\t\t}\n\n\t\tblockPatch2 := model.BlockPatch{\n\t\t\tTitle: &title,\n\t\t}\n\n\t\tblockIds := []string{\"id-test\", \"invalid id\"}\n\t\tblockPatches := []model.BlockPatch{blockPatch, blockPatch2}\n\n\t\ttime.Sleep(1 * time.Millisecond)\n\t\terr := store.PatchBlocks(&model.BlockPatchBatch{BlockIDs: blockIds, BlockPatches: blockPatches}, \"user-id-1\")\n\t\tvar nf *model.ErrNotFound\n\t\trequire.ErrorAs(t, err, &nf)\n\n\t\tretrievedBlock, err := store.GetBlock(\"id-test\")\n\t\trequire.NoError(t, err)\n\t\trequire.NotEqual(t, title, retrievedBlock.Title)\n\t})\n}\n\nvar (\n\tsubtreeSampleBlocks = []*model.Block{\n\t\t{\n\t\t\tID:         \"parent\",\n\t\t\tBoardID:    testBoardID,\n\t\t\tModifiedBy: testUserID,\n\t\t},\n\t\t{\n\t\t\tID:         \"child1\",\n\t\t\tBoardID:    testBoardID,\n\t\t\tParentID:   \"parent\",\n\t\t\tModifiedBy: testUserID,\n\t\t},\n\t\t{\n\t\t\tID:         \"child2\",\n\t\t\tBoardID:    testBoardID,\n\t\t\tParentID:   \"parent\",\n\t\t\tModifiedBy: testUserID,\n\t\t},\n\t\t{\n\t\t\tID:         \"grandchild1\",\n\t\t\tBoardID:    testBoardID,\n\t\t\tParentID:   \"child1\",\n\t\t\tModifiedBy: testUserID,\n\t\t},\n\t\t{\n\t\t\tID:         \"grandchild2\",\n\t\t\tBoardID:    testBoardID,\n\t\t\tParentID:   \"child2\",\n\t\t\tModifiedBy: testUserID,\n\t\t},\n\t\t{\n\t\t\tID:         \"greatgrandchild1\",\n\t\t\tBoardID:    testBoardID,\n\t\t\tParentID:   \"grandchild1\",\n\t\t\tModifiedBy: testUserID,\n\t\t},\n\t}\n)\n\nfunc testGetSubTree2(t *testing.T, store store.Store) {\n\tboardID := testBoardID\n\tblocks, err := store.GetBlocksForBoard(boardID)\n\trequire.NoError(t, err)\n\tinitialCount := len(blocks)\n\n\tInsertBlocks(t, store, subtreeSampleBlocks, \"user-id-1\")\n\ttime.Sleep(1 * time.Millisecond)\n\tdefer DeleteBlocks(t, store, subtreeSampleBlocks, \"test\")\n\n\tblocks, err = store.GetBlocksForBoard(boardID)\n\trequire.NoError(t, err)\n\trequire.Len(t, blocks, initialCount+6)\n\n\tt.Run(\"from root id\", func(t *testing.T) {\n\t\tblocks, err = store.GetSubTree2(boardID, \"parent\", model.QuerySubtreeOptions{})\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, blocks, 3)\n\t\trequire.True(t, ContainsBlockWithID(blocks, \"parent\"))\n\t\trequire.True(t, ContainsBlockWithID(blocks, \"child1\"))\n\t\trequire.True(t, ContainsBlockWithID(blocks, \"child2\"))\n\t})\n\n\tt.Run(\"from child id\", func(t *testing.T) {\n\t\tblocks, err = store.GetSubTree2(boardID, \"child1\", model.QuerySubtreeOptions{})\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, blocks, 2)\n\t\trequire.True(t, ContainsBlockWithID(blocks, \"child1\"))\n\t\trequire.True(t, ContainsBlockWithID(blocks, \"grandchild1\"))\n\t})\n\n\tt.Run(\"from not existing id\", func(t *testing.T) {\n\t\tblocks, err = store.GetSubTree2(boardID, \"not-exists\", model.QuerySubtreeOptions{})\n\t\trequire.NoError(t, err)\n\t\trequire.Empty(t, blocks)\n\t})\n}\n\nfunc testDeleteBlock(t *testing.T, store store.Store) {\n\tuserID := testUserID\n\tboardID := testBoardID\n\n\tblocks, err := store.GetBlocksForBoard(boardID)\n\trequire.NoError(t, err)\n\tinitialCount := len(blocks)\n\n\tblocksToInsert := []*model.Block{\n\t\t{\n\t\t\tID:         \"block1\",\n\t\t\tBoardID:    boardID,\n\t\t\tModifiedBy: userID,\n\t\t},\n\t\t{\n\t\t\tID:         \"block2\",\n\t\t\tBoardID:    boardID,\n\t\t\tModifiedBy: userID,\n\t\t},\n\t\t{\n\t\t\tID:         \"block3\",\n\t\t\tBoardID:    boardID,\n\t\t\tModifiedBy: userID,\n\t\t},\n\t}\n\tInsertBlocks(t, store, blocksToInsert, \"user-id-1\")\n\tdefer DeleteBlocks(t, store, blocksToInsert, \"test\")\n\n\tblocks, err = store.GetBlocksForBoard(boardID)\n\trequire.NoError(t, err)\n\trequire.Len(t, blocks, initialCount+3)\n\n\tt.Run(\"existing id\", func(t *testing.T) {\n\t\t// Wait for not colliding the ID+insert_at key\n\t\ttime.Sleep(1 * time.Millisecond)\n\t\terr := store.DeleteBlock(\"block1\", userID)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"existing id multiple times\", func(t *testing.T) {\n\t\t// Wait for not colliding the ID+insert_at key\n\t\ttime.Sleep(1 * time.Millisecond)\n\t\terr := store.DeleteBlock(\"block1\", userID)\n\t\trequire.NoError(t, err)\n\t\t// Wait for not colliding the ID+insert_at key\n\t\ttime.Sleep(1 * time.Millisecond)\n\t\terr = store.DeleteBlock(\"block1\", userID)\n\t\trequire.NoError(t, err)\n\t})\n\n\tt.Run(\"from not existing id\", func(t *testing.T) {\n\t\t// Wait for not colliding the ID+insert_at key\n\t\ttime.Sleep(1 * time.Millisecond)\n\t\terr := store.DeleteBlock(\"not-exists\", userID)\n\t\trequire.NoError(t, err)\n\t})\n}\n\nfunc testUndeleteBlock(t *testing.T, store store.Store) {\n\tboardID := testBoardID\n\tuserID := testUserID\n\n\tblocks, err := store.GetBlocksForBoard(boardID)\n\trequire.NoError(t, err)\n\tinitialCount := len(blocks)\n\n\tblocksToInsert := []*model.Block{\n\t\t{\n\t\t\tID:         \"block1\",\n\t\t\tBoardID:    boardID,\n\t\t\tModifiedBy: userID,\n\t\t},\n\t\t{\n\t\t\tID:         \"block2\",\n\t\t\tBoardID:    boardID,\n\t\t\tModifiedBy: userID,\n\t\t},\n\t\t{\n\t\t\tID:         \"block3\",\n\t\t\tBoardID:    boardID,\n\t\t\tModifiedBy: userID,\n\t\t},\n\t}\n\tInsertBlocks(t, store, blocksToInsert, \"user-id-1\")\n\tdefer DeleteBlocks(t, store, blocksToInsert, \"test\")\n\n\tblocks, err = store.GetBlocksForBoard(boardID)\n\trequire.NoError(t, err)\n\trequire.Len(t, blocks, initialCount+3)\n\n\tt.Run(\"existing id\", func(t *testing.T) {\n\t\t// Wait for not colliding the ID+insert_at key\n\t\ttime.Sleep(1 * time.Millisecond)\n\t\terr := store.DeleteBlock(\"block1\", userID)\n\t\trequire.NoError(t, err)\n\n\t\tblock, err := store.GetBlock(\"block1\")\n\t\tvar nf *model.ErrNotFound\n\t\trequire.ErrorAs(t, err, &nf)\n\t\trequire.Nil(t, block)\n\n\t\ttime.Sleep(1 * time.Millisecond)\n\t\terr = store.UndeleteBlock(\"block1\", userID)\n\t\trequire.NoError(t, err)\n\n\t\tblock, err = store.GetBlock(\"block1\")\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, block)\n\t})\n\n\tt.Run(\"existing id multiple times\", func(t *testing.T) {\n\t\t// Wait for not colliding the ID+insert_at key\n\t\ttime.Sleep(1 * time.Millisecond)\n\t\terr := store.DeleteBlock(\"block1\", userID)\n\t\trequire.NoError(t, err)\n\n\t\tblock, err := store.GetBlock(\"block1\")\n\t\tvar nf *model.ErrNotFound\n\t\trequire.ErrorAs(t, err, &nf)\n\t\trequire.Nil(t, block)\n\n\t\t// Wait for not colliding the ID+insert_at key\n\t\ttime.Sleep(1 * time.Millisecond)\n\t\terr = store.UndeleteBlock(\"block1\", userID)\n\t\trequire.NoError(t, err)\n\n\t\tblock, err = store.GetBlock(\"block1\")\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, block)\n\n\t\t// Wait for not colliding the ID+insert_at key\n\t\ttime.Sleep(1 * time.Millisecond)\n\t\terr = store.UndeleteBlock(\"block1\", userID)\n\t\trequire.NoError(t, err)\n\n\t\tblock, err = store.GetBlock(\"block1\")\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, block)\n\t})\n\n\tt.Run(\"from not existing id\", func(t *testing.T) {\n\t\t// Wait for not colliding the ID+insert_at key\n\t\ttime.Sleep(1 * time.Millisecond)\n\t\terr := store.UndeleteBlock(\"not-exists\", userID)\n\t\trequire.NoError(t, err)\n\n\t\tblock, err := store.GetBlock(\"not-exists\")\n\t\tvar nf *model.ErrNotFound\n\t\trequire.ErrorAs(t, err, &nf)\n\t\trequire.Nil(t, block)\n\t})\n}\n\nfunc testGetBlocks(t *testing.T, store store.Store) {\n\tboardID := testBoardID\n\tblocks, err := store.GetBlocksForBoard(boardID)\n\trequire.NoError(t, err)\n\n\tblocksToInsert := []*model.Block{\n\t\t{\n\t\t\tID:         \"block1\",\n\t\t\tBoardID:    boardID,\n\t\t\tParentID:   \"\",\n\t\t\tModifiedBy: testUserID,\n\t\t\tType:       \"test\",\n\t\t},\n\t\t{\n\t\t\tID:         \"block2\",\n\t\t\tBoardID:    boardID,\n\t\t\tParentID:   \"block1\",\n\t\t\tModifiedBy: testUserID,\n\t\t\tType:       \"test\",\n\t\t},\n\t\t{\n\t\t\tID:         \"block3\",\n\t\t\tBoardID:    boardID,\n\t\t\tParentID:   \"block1\",\n\t\t\tModifiedBy: testUserID,\n\t\t\tType:       \"test\",\n\t\t},\n\t\t{\n\t\t\tID:         \"block4\",\n\t\t\tBoardID:    boardID,\n\t\t\tParentID:   \"block1\",\n\t\t\tModifiedBy: testUserID,\n\t\t\tType:       \"test2\",\n\t\t},\n\t\t{\n\t\t\tID:         \"block5\",\n\t\t\tBoardID:    boardID,\n\t\t\tParentID:   \"block2\",\n\t\t\tModifiedBy: testUserID,\n\t\t\tType:       \"test\",\n\t\t},\n\t}\n\tInsertBlocks(t, store, blocksToInsert, \"user-id-1\")\n\tdefer DeleteBlocks(t, store, blocksToInsert, \"test\")\n\n\tt.Run(\"not existing parent\", func(t *testing.T) {\n\t\ttime.Sleep(1 * time.Millisecond)\n\t\tblocks, err = store.GetBlocksWithParentAndType(boardID, \"not-exists\", \"test\")\n\t\trequire.NoError(t, err)\n\t\trequire.Empty(t, blocks)\n\t})\n\n\tt.Run(\"not existing type\", func(t *testing.T) {\n\t\ttime.Sleep(1 * time.Millisecond)\n\t\tblocks, err = store.GetBlocksWithParentAndType(boardID, \"block1\", \"not-existing\")\n\t\trequire.NoError(t, err)\n\t\trequire.Empty(t, blocks)\n\t})\n\n\tt.Run(\"valid parent and type\", func(t *testing.T) {\n\t\ttime.Sleep(1 * time.Millisecond)\n\t\tblocks, err = store.GetBlocksWithParentAndType(boardID, \"block1\", \"test\")\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, blocks, 2)\n\t})\n\n\tt.Run(\"not existing parent\", func(t *testing.T) {\n\t\ttime.Sleep(1 * time.Millisecond)\n\t\tblocks, err = store.GetBlocksWithParent(boardID, \"not-exists\")\n\t\trequire.NoError(t, err)\n\t\trequire.Empty(t, blocks)\n\t})\n\n\tt.Run(\"valid parent\", func(t *testing.T) {\n\t\ttime.Sleep(1 * time.Millisecond)\n\t\tblocks, err = store.GetBlocksWithParent(boardID, \"block1\")\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, blocks, 3)\n\t})\n\n\tt.Run(\"not existing type\", func(t *testing.T) {\n\t\ttime.Sleep(1 * time.Millisecond)\n\t\tblocks, err = store.GetBlocksWithType(boardID, \"not-exists\")\n\t\trequire.NoError(t, err)\n\t\trequire.Empty(t, blocks)\n\t})\n\n\tt.Run(\"valid type\", func(t *testing.T) {\n\t\ttime.Sleep(1 * time.Millisecond)\n\t\tblocks, err = store.GetBlocksWithType(boardID, \"test\")\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, blocks, 4)\n\t})\n\n\tt.Run(\"not existing board\", func(t *testing.T) {\n\t\ttime.Sleep(1 * time.Millisecond)\n\t\tblocks, err = store.GetBlocksForBoard(\"not-exists\")\n\t\trequire.NoError(t, err)\n\t\trequire.Empty(t, blocks)\n\t})\n\n\tt.Run(\"all blocks of the a board\", func(t *testing.T) {\n\t\ttime.Sleep(1 * time.Millisecond)\n\t\tblocks, err = store.GetBlocksForBoard(boardID)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, blocks, 5)\n\t})\n\n\tt.Run(\"several blocks by ids\", func(t *testing.T) {\n\t\ttime.Sleep(1 * time.Millisecond)\n\t\tblocks, err = store.GetBlocksByIDs([]string{\"block2\", \"block4\"})\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, blocks, 2)\n\t})\n\n\tt.Run(\"blocks by ids where some are not found\", func(t *testing.T) {\n\t\ttime.Sleep(1 * time.Millisecond)\n\t\tblocks, err = store.GetBlocksByIDs([]string{\"block2\", \"blockNonexistent\"})\n\t\tvar naf *model.ErrNotAllFound\n\t\trequire.ErrorAs(t, err, &naf)\n\t\trequire.True(t, model.IsErrNotFound(err))\n\t\trequire.Len(t, blocks, 1)\n\t})\n\n\tt.Run(\"blocks by ids where none are found\", func(t *testing.T) {\n\t\ttime.Sleep(1 * time.Millisecond)\n\t\tblocks, err = store.GetBlocksByIDs([]string{\"blockNonexistent1\", \"blockNonexistent2\"})\n\t\tvar naf *model.ErrNotAllFound\n\t\trequire.ErrorAs(t, err, &naf)\n\t\trequire.True(t, model.IsErrNotFound(err))\n\t\trequire.Empty(t, blocks)\n\t})\n}\n\nfunc testGetBlock(t *testing.T, store store.Store) {\n\tt.Run(\"get a block\", func(t *testing.T) {\n\t\tblock := &model.Block{\n\t\t\tID:         \"block-id-10\",\n\t\t\tBoardID:    \"board-id-1\",\n\t\t\tModifiedBy: \"user-id-1\",\n\t\t}\n\n\t\terr := store.InsertBlock(block, \"user-id-1\")\n\t\trequire.NoError(t, err)\n\n\t\tfetchedBlock, err := store.GetBlock(\"block-id-10\")\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, fetchedBlock)\n\t\trequire.Equal(t, \"block-id-10\", fetchedBlock.ID)\n\t\trequire.Equal(t, \"board-id-1\", fetchedBlock.BoardID)\n\t\trequire.Equal(t, \"user-id-1\", fetchedBlock.CreatedBy)\n\t\trequire.Equal(t, \"user-id-1\", fetchedBlock.ModifiedBy)\n\t\tassert.WithinDurationf(t, time.Now(), utils.GetTimeForMillis(fetchedBlock.CreateAt), 1*time.Second, \"create time should be current time\")\n\t\tassert.WithinDurationf(t, time.Now(), utils.GetTimeForMillis(fetchedBlock.UpdateAt), 1*time.Second, \"update time should be current time\")\n\t})\n\n\tt.Run(\"get a non-existing block\", func(t *testing.T) {\n\t\tfetchedBlock, err := store.GetBlock(\"non-existing-id\")\n\t\tvar nf *model.ErrNotFound\n\t\trequire.ErrorAs(t, err, &nf)\n\t\trequire.Nil(t, fetchedBlock)\n\t})\n}\n\nfunc testDuplicateBlock(t *testing.T, store store.Store) {\n\tblocksToInsert := subtreeSampleBlocks\n\tblocksToInsert = append(blocksToInsert,\n\t\t&model.Block{\n\t\t\tID:         \"grandchild1a\",\n\t\t\tBoardID:    testBoardID,\n\t\t\tParentID:   \"child1\",\n\t\t\tModifiedBy: testUserID,\n\t\t\tType:       model.TypeComment,\n\t\t},\n\t\t&model.Block{\n\t\t\tID:         \"grandchild2a\",\n\t\t\tBoardID:    testBoardID,\n\t\t\tParentID:   \"child2\",\n\t\t\tModifiedBy: testUserID,\n\t\t\tType:       model.TypeComment,\n\t\t},\n\t)\n\n\tInsertBlocks(t, store, blocksToInsert, \"user-id-1\")\n\ttime.Sleep(1 * time.Millisecond)\n\tdefer DeleteBlocks(t, store, subtreeSampleBlocks, \"test\")\n\n\tt.Run(\"duplicate existing block as no template\", func(t *testing.T) {\n\t\tblocks, err := store.DuplicateBlock(testBoardID, \"child1\", testUserID, false)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, blocks, 2)\n\t\trequire.Equal(t, false, blocks[0].Fields[\"isTemplate\"])\n\t})\n\n\tt.Run(\"duplicate existing block as template\", func(t *testing.T) {\n\t\tblocks, err := store.DuplicateBlock(testBoardID, \"child1\", testUserID, true)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, blocks, 2)\n\t\trequire.Equal(t, true, blocks[0].Fields[\"isTemplate\"])\n\t})\n\n\tt.Run(\"duplicate not existing block\", func(t *testing.T) {\n\t\tblocks, err := store.DuplicateBlock(testBoardID, \"not-existing-id\", testUserID, false)\n\t\trequire.Error(t, err)\n\t\trequire.Nil(t, blocks)\n\t})\n\n\tt.Run(\"duplicate not existing board\", func(t *testing.T) {\n\t\tblocks, err := store.DuplicateBlock(\"not-existing-board\", \"not-existing-id\", testUserID, false)\n\t\trequire.Error(t, err)\n\t\trequire.Nil(t, blocks)\n\t})\n\n\tt.Run(\"not matching board/block\", func(t *testing.T) {\n\t\tblocks, err := store.DuplicateBlock(\"other-id\", \"child1\", testUserID, false)\n\t\trequire.Error(t, err)\n\t\trequire.Nil(t, blocks)\n\t})\n}\n\nfunc testGetBlockMetadata(t *testing.T, store store.Store) {\n\tboardID := testBoardID\n\tblocks, err := store.GetBlocksForBoard(boardID)\n\trequire.NoError(t, err)\n\n\tblocksToInsert := []*model.Block{\n\t\t{\n\t\t\tID:         \"block1\",\n\t\t\tBoardID:    boardID,\n\t\t\tParentID:   \"\",\n\t\t\tModifiedBy: testUserID,\n\t\t\tType:       \"test\",\n\t\t},\n\t\t{\n\t\t\tID:         \"block2\",\n\t\t\tBoardID:    boardID,\n\t\t\tParentID:   \"block1\",\n\t\t\tModifiedBy: testUserID,\n\t\t\tType:       \"test\",\n\t\t},\n\t\t{\n\t\t\tID:         \"block3\",\n\t\t\tBoardID:    boardID,\n\t\t\tParentID:   \"block1\",\n\t\t\tModifiedBy: testUserID,\n\t\t\tType:       \"test\",\n\t\t},\n\t\t{\n\t\t\tID:         \"block4\",\n\t\t\tBoardID:    boardID,\n\t\t\tParentID:   \"block1\",\n\t\t\tModifiedBy: testUserID,\n\t\t\tType:       \"test2\",\n\t\t},\n\t\t{\n\t\t\tID:         \"block5\",\n\t\t\tBoardID:    boardID,\n\t\t\tParentID:   \"block2\",\n\t\t\tModifiedBy: testUserID,\n\t\t\tType:       \"test\",\n\t\t},\n\t}\n\n\tfor _, v := range blocksToInsert {\n\t\ttime.Sleep(20 * time.Millisecond)\n\t\tsubBlocks := []*model.Block{v}\n\t\tInsertBlocks(t, store, subBlocks, testUserID)\n\t}\n\tdefer DeleteBlocks(t, store, blocksToInsert, \"test\")\n\n\tt.Run(\"get full block history\", func(t *testing.T) {\n\t\topts := model.QueryBlockHistoryOptions{\n\t\t\tDescending: false,\n\t\t}\n\t\tblocks, err = store.GetBlockHistoryDescendants(boardID, opts)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, blocks, 5)\n\t\texpectedBlock := blocksToInsert[0]\n\t\tblock := blocks[0]\n\n\t\trequire.Equal(t, expectedBlock.ID, block.ID)\n\t})\n\n\tt.Run(\"get full block history descending\", func(t *testing.T) {\n\t\topts := model.QueryBlockHistoryOptions{\n\t\t\tDescending: true,\n\t\t}\n\t\tblocks, err = store.GetBlockHistoryDescendants(boardID, opts)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, blocks, 5)\n\t\texpectedBlock := blocksToInsert[len(blocksToInsert)-1]\n\t\tblock := blocks[0]\n\n\t\trequire.Equal(t, expectedBlock.ID, block.ID)\n\t})\n\n\tt.Run(\"get limited block history\", func(t *testing.T) {\n\t\topts := model.QueryBlockHistoryOptions{\n\t\t\tLimit:      3,\n\t\t\tDescending: false,\n\t\t}\n\t\tblocks, err = store.GetBlockHistoryDescendants(boardID, opts)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, blocks, 3)\n\t})\n\n\tt.Run(\"get first block history\", func(t *testing.T) {\n\t\topts := model.QueryBlockHistoryOptions{\n\t\t\tLimit:      1,\n\t\t\tDescending: false,\n\t\t}\n\t\tblocks, err = store.GetBlockHistoryDescendants(boardID, opts)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, blocks, 1)\n\t\texpectedBlock := blocksToInsert[0]\n\t\tblock := blocks[0]\n\n\t\trequire.Equal(t, expectedBlock.ID, block.ID)\n\t})\n\n\tt.Run(\"get last block history\", func(t *testing.T) {\n\t\topts := model.QueryBlockHistoryOptions{\n\t\t\tLimit:      1,\n\t\t\tDescending: true,\n\t\t}\n\t\tblocks, err = store.GetBlockHistoryDescendants(boardID, opts)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, blocks, 1)\n\t\texpectedBlock := blocksToInsert[len(blocksToInsert)-1]\n\t\tblock := blocks[0]\n\n\t\trequire.Equal(t, expectedBlock.ID, block.ID)\n\t})\n\n\tt.Run(\"get block history after updateAt\", func(t *testing.T) {\n\t\trBlock, err2 := store.GetBlock(\"block3\")\n\t\trequire.NoError(t, err2)\n\t\trequire.NotZero(t, rBlock.UpdateAt)\n\n\t\topts := model.QueryBlockHistoryOptions{\n\t\t\tAfterUpdateAt: rBlock.UpdateAt,\n\t\t\tDescending:    false,\n\t\t}\n\t\tblocks, err = store.GetBlockHistoryDescendants(boardID, opts)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, blocks, 2)\n\t\texpectedBlock := blocksToInsert[3]\n\t\tblock := blocks[0]\n\n\t\trequire.Equal(t, expectedBlock.ID, block.ID)\n\t})\n\n\tt.Run(\"get block history before updateAt\", func(t *testing.T) {\n\t\trBlock, err2 := store.GetBlock(\"block3\")\n\t\trequire.NoError(t, err2)\n\t\trequire.NotZero(t, rBlock.UpdateAt)\n\n\t\topts := model.QueryBlockHistoryOptions{\n\t\t\tBeforeUpdateAt: rBlock.UpdateAt,\n\t\t\tDescending:     true,\n\t\t}\n\t\tblocks, err = store.GetBlockHistoryDescendants(boardID, opts)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, blocks, 2)\n\t\texpectedBlock := blocksToInsert[1]\n\t\tblock := blocks[0]\n\n\t\trequire.Equal(t, expectedBlock.ID, block.ID)\n\t})\n\n\tt.Run(\"get full block history after delete\", func(t *testing.T) {\n\t\ttime.Sleep(20 * time.Millisecond)\n\t\t// this will delete `block1` and any other blocks with `block1` as parent.\n\t\terr = store.DeleteBlock(blocksToInsert[0].ID, testUserID)\n\t\trequire.NoError(t, err)\n\n\t\topts := model.QueryBlockHistoryOptions{\n\t\t\tDescending: true,\n\t\t}\n\t\tblocks, err = store.GetBlockHistoryDescendants(boardID, opts)\n\t\trequire.NoError(t, err)\n\t\t// all 5 blocks get a history record for insert, then `block1` gets a record for delete,\n\t\t// and all 3 `block1` children get a record for delete. Thus total is 9.\n\t\trequire.Len(t, blocks, 9)\n\t})\n\n\tt.Run(\"get full block history after undelete\", func(t *testing.T) {\n\t\ttime.Sleep(20 * time.Millisecond)\n\t\t// this will undelete `block1` and its children\n\t\terr = store.UndeleteBlock(blocksToInsert[0].ID, testUserID)\n\t\trequire.NoError(t, err)\n\n\t\topts := model.QueryBlockHistoryOptions{\n\t\t\tDescending: true,\n\t\t}\n\t\tblocks, err = store.GetBlockHistoryDescendants(boardID, opts)\n\t\trequire.NoError(t, err)\n\t\t// previous test put 9 records in history table. In this test 1 record was added for undeleting\n\t\t// `block1` and another 3 for undeleting the children for a total of 13.\n\t\trequire.Len(t, blocks, 13)\n\t})\n\n\tt.Run(\"get block history of a board with no history\", func(t *testing.T) {\n\t\topts := model.QueryBlockHistoryOptions{}\n\n\t\tblocks, err = store.GetBlockHistoryDescendants(\"nonexistent-board-id\", opts)\n\t\trequire.NoError(t, err)\n\t\trequire.Empty(t, blocks)\n\t})\n}\n\nfunc testUndeleteBlockChildren(t *testing.T, store store.Store) {\n\tboards := createTestBoards(t, store, testTeamID, testUserID, 2)\n\tboardDelete := boards[0]\n\tboardKeep := boards[1]\n\tuserID := testUserID\n\n\t// create some blocks to be deleted\n\tcardsDelete := createTestCards(t, store, userID, boardDelete.ID, 3)\n\tblocksDelete := createTestBlocksForCard(t, store, cardsDelete[0].ID, 5)\n\trequire.Len(t, blocksDelete, 5)\n\n\t// create some blocks to keep\n\tcardsKeep := createTestCards(t, store, userID, boardKeep.ID, 3)\n\tblocksKeep := createTestBlocksForCard(t, store, cardsKeep[0].ID, 4)\n\trequire.Len(t, blocksKeep, 4)\n\n\tt.Run(\"undelete block children for card\", func(t *testing.T) {\n\t\tcardDelete := cardsDelete[0]\n\t\tcardKeep := cardsKeep[0]\n\n\t\t// delete a card\n\t\terr := store.DeleteBlock(cardDelete.ID, testUserID)\n\t\trequire.NoError(t, err)\n\n\t\t// ensure the card was deleted\n\t\tblock, err := store.GetBlock(cardDelete.ID)\n\t\trequire.Error(t, err)\n\t\trequire.Nil(t, block)\n\n\t\t// ensure the card children were deleted\n\t\tblocks, err := store.GetBlocksWithParentAndType(cardDelete.BoardID, cardDelete.ID, model.TypeText)\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, blocks)\n\n\t\t// ensure the other card children remain.\n\t\tblocks, err = store.GetBlocksWithParentAndType(cardKeep.BoardID, cardKeep.ID, model.TypeText)\n\t\trequire.NoError(t, err)\n\t\tassert.Len(t, blocks, len(blocksKeep))\n\n\t\t// undelete the card\n\t\terr = store.UndeleteBlock(cardDelete.ID, testUserID)\n\t\trequire.NoError(t, err)\n\n\t\t// ensure the card was restored\n\t\tblock, err = store.GetBlock(cardDelete.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, block)\n\n\t\t// ensure the card children were restored\n\t\tblocks, err = store.GetBlocksWithParentAndType(cardDelete.BoardID, cardDelete.ID, model.TypeText)\n\t\trequire.NoError(t, err)\n\t\tassert.Len(t, blocks, len(blocksDelete))\n\t})\n\n\tt.Run(\"undelete block children for board\", func(t *testing.T) {\n\t\t// delete the board\n\t\terr := store.DeleteBoard(boardDelete.ID, testUserID)\n\t\trequire.NoError(t, err)\n\n\t\t// ensure the board was deleted\n\t\tboard, err := store.GetBoard(boardDelete.ID)\n\t\trequire.Error(t, err)\n\t\trequire.Nil(t, board)\n\n\t\t// ensure all cards and blocks for the board were deleted\n\t\tblocks, err := store.GetBlocksForBoard(boardDelete.ID)\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, blocks)\n\n\t\t// ensure the other board's cards and blocks remain.\n\t\tblocks, err = store.GetBlocksForBoard(boardKeep.ID)\n\t\trequire.NoError(t, err)\n\t\tassert.Len(t, blocks, len(blocksKeep)+len(cardsKeep))\n\n\t\t// undelete the board\n\t\terr = store.UndeleteBoard(boardDelete.ID, testUserID)\n\t\trequire.NoError(t, err)\n\n\t\t// ensure the board was restored\n\t\tboard, err = store.GetBoard(boardDelete.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, board)\n\n\t\t// ensure the board's cards and blocks were restored.\n\t\tblocks, err = store.GetBlocksForBoard(boardDelete.ID)\n\t\trequire.NoError(t, err)\n\t\tassert.Len(t, blocks, len(blocksDelete)+len(cardsDelete))\n\t})\n}\n\nfunc testGetBlockHistoryNewestChildren(t *testing.T, store store.Store) {\n\tboards := createTestBoards(t, store, testTeamID, testUserID, 2)\n\tboard := boards[0]\n\n\tconst cardCount = 10\n\tconst patchCount = 5\n\n\t// create a card and some content blocks\n\tcards := createTestCards(t, store, testUserID, board.ID, 1)\n\tcard := cards[0]\n\tcontent := createTestBlocksForCard(t, store, card.ID, cardCount)\n\n\t// patch the content blocks to create some history records\n\tfor i := 1; i <= patchCount; i++ {\n\t\tfor _, block := range content {\n\t\t\ttitle := strconv.FormatInt(int64(i), 10)\n\t\t\tpatch := &model.BlockPatch{\n\t\t\t\tTitle: &title,\n\t\t\t}\n\t\t\terr := store.PatchBlock(block.ID, patch, testUserID)\n\t\t\trequire.NoError(t, err, \"error patching content blocks\")\n\t\t}\n\t}\n\n\t// delete some of the content blocks\n\terr := store.DeleteBlock(content[0].ID, testUserID)\n\trequire.NoError(t, err, \"error deleting content block\")\n\terr = store.DeleteBlock(content[3].ID, testUserID)\n\trequire.NoError(t, err, \"error deleting content block\")\n\terr = store.DeleteBlock(content[7].ID, testUserID)\n\trequire.NoError(t, err, \"error deleting content block\")\n\n\tt.Run(\"invalid card\", func(t *testing.T) {\n\t\topts := model.QueryBlockHistoryChildOptions{}\n\t\tblocks, hasMore, err := store.GetBlockHistoryNewestChildren(utils.NewID(utils.IDTypeCard), opts)\n\t\trequire.NoError(t, err)\n\t\trequire.False(t, hasMore)\n\t\trequire.Empty(t, blocks)\n\t})\n\n\tt.Run(\"valid card with no children\", func(t *testing.T) {\n\t\topts := model.QueryBlockHistoryChildOptions{}\n\t\temptyCard := createTestCards(t, store, testUserID, board.ID, 1)[0]\n\t\tblocks, hasMore, err := store.GetBlockHistoryNewestChildren(emptyCard.ID, opts)\n\t\trequire.NoError(t, err)\n\t\trequire.False(t, hasMore)\n\t\trequire.Empty(t, blocks)\n\t})\n\n\tt.Run(\"valid card with children\", func(t *testing.T) {\n\t\topts := model.QueryBlockHistoryChildOptions{}\n\t\tblocks, hasMore, err := store.GetBlockHistoryNewestChildren(card.ID, opts)\n\t\trequire.NoError(t, err)\n\t\trequire.False(t, hasMore)\n\t\trequire.Len(t, blocks, cardCount)\n\t\trequire.ElementsMatch(t, extractIDs(t, blocks), extractIDs(t, content))\n\n\t\texpected := strconv.FormatInt(patchCount, 10)\n\t\tfor _, b := range blocks {\n\t\t\trequire.Equal(t, expected, b.Title)\n\t\t}\n\t})\n\n\tt.Run(\"pagination\", func(t *testing.T) {\n\t\topts := model.QueryBlockHistoryChildOptions{\n\t\t\tPerPage: 3,\n\t\t}\n\n\t\tcollected := make([]*model.Block, 0)\n\t\treps := 0\n\t\tfor {\n\t\t\treps++\n\t\t\tblocks, hasMore, err := store.GetBlockHistoryNewestChildren(card.ID, opts)\n\t\t\trequire.NoError(t, err)\n\t\t\tcollected = append(collected, blocks...)\n\t\t\tif !hasMore {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\topts.Page++\n\t\t}\n\n\t\tassert.Len(t, collected, cardCount)\n\t\tassert.Equal(t, math.Floor(float64(cardCount/opts.PerPage)+1), float64(reps))\n\n\t\texpected := strconv.FormatInt(patchCount, 10)\n\t\tfor _, b := range collected {\n\t\t\trequire.Equal(t, expected, b.Title)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "server/services/store/storetests/boards.go",
    "content": "package storetests\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/store\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc StoreTestBoardStore(t *testing.T, setup func(t *testing.T) (store.Store, func())) {\n\tt.Run(\"GetBoard\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestGetBoard(t, store)\n\t})\n\tt.Run(\"GetBoardsForUserAndTeam\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestGetBoardsForUserAndTeam(t, store)\n\t})\n\tt.Run(\"GetBoardsInTeamByIds\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestGetBoardsInTeamByIds(t, store)\n\t})\n\tt.Run(\"InsertBoard\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestInsertBoard(t, store)\n\t})\n\tt.Run(\"PatchBoard\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestPatchBoard(t, store)\n\t})\n\tt.Run(\"DeleteBoard\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestDeleteBoard(t, store)\n\t})\n\tt.Run(\"UndeleteBoard\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestUndeleteBoard(t, store)\n\t})\n\tt.Run(\"InsertBoardWithAdmin\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestInsertBoardWithAdmin(t, store)\n\t})\n\tt.Run(\"SaveMember\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestSaveMember(t, store)\n\t})\n\tt.Run(\"GetMemberForBoard\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestGetMemberForBoard(t, store)\n\t})\n\tt.Run(\"GetMembersForBoard\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestGetMembersForBoard(t, store)\n\t})\n\tt.Run(\"GetMembersForUser\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestGetMembersForUser(t, store)\n\t})\n\tt.Run(\"DeleteMember\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestDeleteMember(t, store)\n\t})\n\tt.Run(\"SearchBoardsForUser\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestSearchBoardsForUser(t, store)\n\t})\n\tt.Run(\"SearchBoardsForUserInTeam\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestSearchBoardsForUserInTeam(t, store)\n\t})\n\tt.Run(\"GetBoardHistory\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestGetBoardHistory(t, store)\n\t})\n\tt.Run(\"GetBoardCount\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestGetBoardCount(t, store)\n\t})\n}\n\nfunc testGetBoard(t *testing.T, store store.Store) {\n\tuserID := testUserID\n\n\tt.Run(\"existing board\", func(t *testing.T) {\n\t\tboard := &model.Board{\n\t\t\tID:     \"id-1\",\n\t\t\tTeamID: testTeamID,\n\t\t\tType:   model.BoardTypeOpen,\n\t\t}\n\n\t\t_, err := store.InsertBoard(board, userID)\n\t\trequire.NoError(t, err)\n\n\t\trBoard, err := store.GetBoard(board.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, board.ID, rBoard.ID)\n\t\trequire.Equal(t, board.TeamID, rBoard.TeamID)\n\t\trequire.Equal(t, userID, rBoard.CreatedBy)\n\t\trequire.Equal(t, userID, rBoard.ModifiedBy)\n\t\trequire.Equal(t, board.Type, rBoard.Type)\n\t\trequire.NotZero(t, rBoard.CreateAt)\n\t\trequire.NotZero(t, rBoard.UpdateAt)\n\t})\n\n\tt.Run(\"nonexisting board\", func(t *testing.T) {\n\t\trBoard, err := store.GetBoard(\"nonexistent-id\")\n\t\tvar nf *model.ErrNotFound\n\t\trequire.ErrorAs(t, err, &nf)\n\t\trequire.True(t, model.IsErrNotFound(err), \"Should be ErrNotFound compatible error\")\n\t\trequire.Nil(t, rBoard)\n\t})\n}\n\nfunc testGetBoardsForUserAndTeam(t *testing.T, store store.Store) {\n\tuserID := \"user-id-1\"\n\n\tt.Run(\"should return empty list if no results are found\", func(t *testing.T) {\n\t\tboards, err := store.GetBoardsForUserAndTeam(testUserID, testTeamID, true)\n\t\trequire.NoError(t, err)\n\t\trequire.Empty(t, boards)\n\t})\n\n\tt.Run(\"should return only the boards of the team that the user is a member of\", func(t *testing.T) {\n\t\tteamID1 := \"team-id-1\"\n\t\tteamID2 := \"team-id-2\"\n\n\t\t// team 1 boards\n\t\tboard1 := &model.Board{\n\t\t\tID:     \"board-id-1\",\n\t\t\tTeamID: teamID1,\n\t\t\tType:   model.BoardTypeOpen,\n\t\t}\n\t\trBoard1, _, err := store.InsertBoardWithAdmin(board1, userID)\n\t\trequire.NoError(t, err)\n\n\t\tboard2 := &model.Board{\n\t\t\tID:     \"board-id-2\",\n\t\t\tTeamID: teamID1,\n\t\t\tType:   model.BoardTypePrivate,\n\t\t}\n\t\trBoard2, _, err := store.InsertBoardWithAdmin(board2, userID)\n\t\trequire.NoError(t, err)\n\n\t\tboard3 := &model.Board{\n\t\t\tID:     \"board-id-3\",\n\t\t\tTeamID: teamID1,\n\t\t\tType:   model.BoardTypeOpen,\n\t\t}\n\t\trBoard3, err := store.InsertBoard(board3, \"other-user\")\n\t\trequire.NoError(t, err)\n\n\t\tboard4 := &model.Board{\n\t\t\tID:     \"board-id-4\",\n\t\t\tTeamID: teamID1,\n\t\t\tType:   model.BoardTypePrivate,\n\t\t}\n\t\t_, err = store.InsertBoard(board4, \"other-user\")\n\t\trequire.NoError(t, err)\n\n\t\t// team 2 boards\n\t\tboard5 := &model.Board{\n\t\t\tID:     \"board-id-5\",\n\t\t\tTeamID: teamID2,\n\t\t\tType:   model.BoardTypeOpen,\n\t\t}\n\t\t_, _, err = store.InsertBoardWithAdmin(board5, userID)\n\t\trequire.NoError(t, err)\n\n\t\tboard6 := &model.Board{\n\t\t\tID:     \"board-id-6\",\n\t\t\tTeamID: teamID1,\n\t\t\tType:   model.BoardTypePrivate,\n\t\t}\n\t\t_, err = store.InsertBoard(board6, \"other-user\")\n\t\trequire.NoError(t, err)\n\n\t\tt.Run(\"should only find the two boards that the user is a member of for team 1 plus the one open board\", func(t *testing.T) {\n\t\t\tboards, err := store.GetBoardsForUserAndTeam(userID, teamID1, true)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.ElementsMatch(t, []*model.Board{\n\t\t\t\trBoard1,\n\t\t\t\trBoard2,\n\t\t\t\trBoard3,\n\t\t\t}, boards)\n\t\t})\n\n\t\tt.Run(\"should only find the two boards that the user is a member of for team 1\", func(t *testing.T) {\n\t\t\tboards, err := store.GetBoardsForUserAndTeam(userID, teamID1, false)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.ElementsMatch(t, []*model.Board{\n\t\t\t\trBoard1,\n\t\t\t\trBoard2,\n\t\t\t}, boards)\n\t\t})\n\n\t\tt.Run(\"should only find the board that the user is a member of for team 2\", func(t *testing.T) {\n\t\t\tboards, err := store.GetBoardsForUserAndTeam(userID, teamID2, true)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, boards, 1)\n\t\t\trequire.Equal(t, board5.ID, boards[0].ID)\n\t\t})\n\t})\n}\n\nfunc testGetBoardsInTeamByIds(t *testing.T, store store.Store) {\n\tt.Run(\"should return err not all found if one or more of the ids are not found\", func(t *testing.T) {\n\t\tfor _, boardID := range []string{\"board-id-1\", \"board-id-2\"} {\n\t\t\tboard := &model.Board{\n\t\t\t\tID:     boardID,\n\t\t\t\tTeamID: testTeamID,\n\t\t\t\tType:   model.BoardTypeOpen,\n\t\t\t}\n\t\t\trBoard, _, err := store.InsertBoardWithAdmin(board, testUserID)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NotNil(t, rBoard)\n\t\t}\n\n\t\ttestCases := []struct {\n\t\t\tName          string\n\t\t\tBoardIDs      []string\n\t\t\tExpectedError bool\n\t\t\tExpectedLen   int\n\t\t}{\n\t\t\t{\n\t\t\t\tName:          \"if none of the IDs are found\",\n\t\t\t\tBoardIDs:      []string{\"nonexistent-1\", \"nonexistent-2\"},\n\t\t\t\tExpectedError: true,\n\t\t\t\tExpectedLen:   0,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:          \"if not all of the IDs are found\",\n\t\t\t\tBoardIDs:      []string{\"nonexistent-1\", \"board-id-1\"},\n\t\t\t\tExpectedError: true,\n\t\t\t\tExpectedLen:   1,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:          \"if all of the IDs are found\",\n\t\t\t\tBoardIDs:      []string{\"board-id-1\", \"board-id-2\"},\n\t\t\t\tExpectedError: false,\n\t\t\t\tExpectedLen:   2,\n\t\t\t},\n\t\t}\n\n\t\tfor _, tc := range testCases {\n\t\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\t\tboards, err := store.GetBoardsInTeamByIds(tc.BoardIDs, testTeamID)\n\t\t\t\tif tc.ExpectedError {\n\t\t\t\t\tvar naf *model.ErrNotAllFound\n\t\t\t\t\trequire.ErrorAs(t, err, &naf)\n\t\t\t\t\trequire.True(t, model.IsErrNotFound(err), \"Should be ErrNotFound compatible error\")\n\t\t\t\t} else {\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t}\n\t\t\t\trequire.Len(t, boards, tc.ExpectedLen)\n\t\t\t})\n\t\t}\n\t})\n}\n\nfunc testInsertBoard(t *testing.T, store store.Store) {\n\tuserID := testUserID\n\n\tt.Run(\"valid public board\", func(t *testing.T) {\n\t\tboard := &model.Board{\n\t\t\tID:     \"id-test-public\",\n\t\t\tTeamID: testTeamID,\n\t\t\tType:   model.BoardTypeOpen,\n\t\t}\n\n\t\tnewBoard, err := store.InsertBoard(board, userID)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, board.ID, newBoard.ID)\n\t\trequire.Equal(t, newBoard.Type, model.BoardTypeOpen)\n\t\trequire.NotZero(t, newBoard.CreateAt)\n\t\trequire.NotZero(t, newBoard.UpdateAt)\n\t\trequire.Zero(t, newBoard.DeleteAt)\n\t\trequire.Equal(t, userID, newBoard.CreatedBy)\n\t\trequire.Equal(t, newBoard.CreatedBy, newBoard.ModifiedBy)\n\t})\n\n\tt.Run(\"valid private board\", func(t *testing.T) {\n\t\tboard := &model.Board{\n\t\t\tID:     \"id-test-private\",\n\t\t\tTeamID: testTeamID,\n\t\t\tType:   model.BoardTypePrivate,\n\t\t}\n\n\t\tnewBoard, err := store.InsertBoard(board, userID)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, board.ID, newBoard.ID)\n\t\trequire.Equal(t, newBoard.Type, model.BoardTypePrivate)\n\t\trequire.NotZero(t, newBoard.CreateAt)\n\t\trequire.NotZero(t, newBoard.UpdateAt)\n\t\trequire.Zero(t, newBoard.DeleteAt)\n\t\trequire.Equal(t, userID, newBoard.CreatedBy)\n\t\trequire.Equal(t, newBoard.CreatedBy, newBoard.ModifiedBy)\n\t})\n\n\tt.Run(\"invalid properties field board\", func(t *testing.T) {\n\t\tboard := &model.Board{\n\t\t\tID:         \"id-test-props\",\n\t\t\tTeamID:     testTeamID,\n\t\t\tProperties: map[string]interface{}{\"no-serializable-value\": t.Run},\n\t\t}\n\n\t\t_, err := store.InsertBoard(board, userID)\n\t\trequire.Error(t, err)\n\n\t\trBoard, err := store.GetBoard(board.ID)\n\t\trequire.True(t, model.IsErrNotFound(err), \"Should be ErrNotFound compatible error\")\n\t\trequire.Nil(t, rBoard)\n\t})\n\n\tt.Run(\"update board\", func(t *testing.T) {\n\t\tboard := &model.Board{\n\t\t\tID:     \"id-test-public\",\n\t\t\tTeamID: testTeamID,\n\t\t\tTitle:  \"New title\",\n\t\t}\n\n\t\t// wait to avoid hitting pk uniqueness constraint in history\n\t\ttime.Sleep(10 * time.Millisecond)\n\n\t\tnewBoard, err := store.InsertBoard(board, \"user2\")\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"New title\", newBoard.Title)\n\t\trequire.Equal(t, \"user2\", newBoard.ModifiedBy)\n\t})\n\n\tt.Run(\"test update board type\", func(t *testing.T) {\n\t\tboard := &model.Board{\n\t\t\tID:    \"id-test-type-board\",\n\t\t\tTitle: \"Public board\",\n\t\t\tType:  model.BoardTypeOpen,\n\t\t}\n\n\t\tnewBoard, err := store.InsertBoard(board, userID)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, model.BoardTypeOpen, newBoard.Type)\n\n\t\tboardUpdate := &model.Board{\n\t\t\tID:   \"id-test-type-board\",\n\t\t\tType: model.BoardTypePrivate,\n\t\t}\n\n\t\t// wait to avoid hitting pk uniqueness constraint in history\n\t\ttime.Sleep(10 * time.Millisecond)\n\n\t\tmodifiedBoard, err := store.InsertBoard(boardUpdate, userID)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, model.BoardTypePrivate, modifiedBoard.Type)\n\t})\n}\n\nfunc testPatchBoard(t *testing.T, store store.Store) {\n\tuserID := testUserID\n\n\tt.Run(\"should return error if the board doesn't exist\", func(t *testing.T) {\n\t\tnewTitle := \"A new title\"\n\t\tpatch := &model.BoardPatch{Title: &newTitle}\n\n\t\tboard, err := store.PatchBoard(\"nonexistent-board-id\", patch, userID)\n\t\trequire.Error(t, err)\n\t\trequire.Nil(t, board)\n\t})\n\n\tt.Run(\"should correctly apply a simple patch\", func(t *testing.T) {\n\t\tboardID := utils.NewID(utils.IDTypeBoard)\n\t\tuserID2 := \"user-id-2\"\n\n\t\tboard := &model.Board{\n\t\t\tID:          boardID,\n\t\t\tTeamID:      testTeamID,\n\t\t\tType:        model.BoardTypeOpen,\n\t\t\tTitle:       \"A simple title\",\n\t\t\tDescription: \"A simple description\",\n\t\t}\n\n\t\tnewBoard, err := store.InsertBoard(board, userID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, newBoard)\n\t\trequire.Equal(t, userID, newBoard.CreatedBy)\n\n\t\t// wait to avoid hitting pk uniqueness constraint in history\n\t\ttime.Sleep(10 * time.Millisecond)\n\n\t\tnewTitle := \"A new title\"\n\t\tnewDescription := \"A new description\"\n\t\tpatch := &model.BoardPatch{Title: &newTitle, Description: &newDescription}\n\t\tpatchedBoard, err := store.PatchBoard(boardID, patch, userID2)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, newTitle, patchedBoard.Title)\n\t\trequire.Equal(t, newDescription, patchedBoard.Description)\n\t\trequire.Equal(t, userID, patchedBoard.CreatedBy)\n\t\trequire.Equal(t, userID2, patchedBoard.ModifiedBy)\n\t})\n\n\tt.Run(\"should correctly update the board properties\", func(t *testing.T) {\n\t\tboardID := utils.NewID(utils.IDTypeBoard)\n\n\t\tboard := &model.Board{\n\t\t\tID:     boardID,\n\t\t\tTeamID: testTeamID,\n\t\t\tType:   model.BoardTypeOpen,\n\t\t\tProperties: map[string]interface{}{\n\t\t\t\t\"one\": \"1\",\n\t\t\t\t\"two\": \"2\",\n\t\t\t},\n\t\t}\n\n\t\tnewBoard, err := store.InsertBoard(board, userID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, newBoard)\n\t\trequire.Equal(t, \"1\", newBoard.Properties[\"one\"].(string))\n\t\trequire.Equal(t, \"2\", newBoard.Properties[\"two\"].(string))\n\n\t\t// wait to avoid hitting pk uniqueness constraint in history\n\t\ttime.Sleep(10 * time.Millisecond)\n\n\t\tpatch := &model.BoardPatch{\n\t\t\tUpdatedProperties: map[string]interface{}{\"three\": \"3\"},\n\t\t\tDeletedProperties: []string{\"one\"},\n\t\t}\n\t\tpatchedBoard, err := store.PatchBoard(boardID, patch, userID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotContains(t, patchedBoard.Properties, \"one\")\n\t\trequire.Equal(t, \"2\", patchedBoard.Properties[\"two\"].(string))\n\t\trequire.Equal(t, \"3\", patchedBoard.Properties[\"three\"].(string))\n\t})\n\n\tt.Run(\"should correctly modify the board's type\", func(t *testing.T) {\n\t\tboardID := utils.NewID(utils.IDTypeBoard)\n\n\t\tboard := &model.Board{\n\t\t\tID:     boardID,\n\t\t\tTeamID: testTeamID,\n\t\t\tType:   model.BoardTypeOpen,\n\t\t}\n\n\t\tnewBoard, err := store.InsertBoard(board, userID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, newBoard)\n\t\trequire.Equal(t, newBoard.Type, model.BoardTypeOpen)\n\n\t\t// wait to avoid hitting pk uniqueness constraint in history\n\t\ttime.Sleep(10 * time.Millisecond)\n\n\t\tnewType := model.BoardTypePrivate\n\t\tpatch := &model.BoardPatch{Type: &newType}\n\t\tpatchedBoard, err := store.PatchBoard(boardID, patch, userID)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, model.BoardTypePrivate, patchedBoard.Type)\n\t})\n\n\tt.Run(\"a patch that doesn't include any of the properties should not modify them\", func(t *testing.T) {\n\t\tboardID := utils.NewID(utils.IDTypeBoard)\n\t\tproperties := map[string]interface{}{\"prop1\": \"val1\"}\n\t\tcardProperties := []map[string]interface{}{{\"prop2\": \"val2\"}}\n\n\t\tboard := &model.Board{\n\t\t\tID:             boardID,\n\t\t\tTeamID:         testTeamID,\n\t\t\tType:           model.BoardTypeOpen,\n\t\t\tProperties:     properties,\n\t\t\tCardProperties: cardProperties,\n\t\t}\n\n\t\tnewBoard, err := store.InsertBoard(board, userID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, newBoard)\n\t\trequire.Equal(t, newBoard.Type, model.BoardTypeOpen)\n\t\trequire.Equal(t, properties, newBoard.Properties)\n\t\trequire.Equal(t, cardProperties, newBoard.CardProperties)\n\n\t\t// wait to avoid hitting pk uniqueness constraint in history\n\t\ttime.Sleep(10 * time.Millisecond)\n\n\t\tnewType := model.BoardTypePrivate\n\t\tpatch := &model.BoardPatch{Type: &newType}\n\t\tpatchedBoard, err := store.PatchBoard(boardID, patch, userID)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, model.BoardTypePrivate, patchedBoard.Type)\n\t\trequire.Equal(t, properties, patchedBoard.Properties)\n\t\trequire.Equal(t, cardProperties, patchedBoard.CardProperties)\n\t})\n\n\tt.Run(\"a patch that removes a card property and updates another should work correctly\", func(t *testing.T) {\n\t\tboardID := utils.NewID(utils.IDTypeBoard)\n\t\tprop1 := map[string]interface{}{\"id\": \"prop1\", \"value\": \"val1\"}\n\t\tprop2 := map[string]interface{}{\"id\": \"prop2\", \"value\": \"val2\"}\n\t\tprop3 := map[string]interface{}{\"id\": \"prop3\", \"value\": \"val3\"}\n\t\tcardProperties := []map[string]interface{}{prop1, prop2, prop3}\n\n\t\tboard := &model.Board{\n\t\t\tID:             boardID,\n\t\t\tTeamID:         testTeamID,\n\t\t\tType:           model.BoardTypeOpen,\n\t\t\tCardProperties: cardProperties,\n\t\t}\n\n\t\tnewBoard, err := store.InsertBoard(board, userID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, newBoard)\n\t\trequire.Equal(t, newBoard.Type, model.BoardTypeOpen)\n\t\trequire.Equal(t, cardProperties, newBoard.CardProperties)\n\n\t\t// wait to avoid hitting pk uniqueness constraint in history\n\t\ttime.Sleep(10 * time.Millisecond)\n\n\t\tnewProp1 := map[string]interface{}{\"id\": \"prop1\", \"value\": \"newval1\"}\n\t\texpectedCardProperties := []map[string]interface{}{newProp1, prop3}\n\t\tpatch := &model.BoardPatch{\n\t\t\tUpdatedCardProperties: []map[string]interface{}{newProp1},\n\t\t\tDeletedCardProperties: []string{\"prop2\"},\n\t\t}\n\t\tpatchedBoard, err := store.PatchBoard(boardID, patch, userID)\n\t\trequire.NoError(t, err)\n\t\trequire.ElementsMatch(t, expectedCardProperties, patchedBoard.CardProperties)\n\t})\n}\n\nfunc testDeleteBoard(t *testing.T, store store.Store) {\n\tuserID := testUserID\n\n\tt.Run(\"should return an error if the board doesn't exist\", func(t *testing.T) {\n\t\trequire.Error(t, store.DeleteBoard(\"nonexistent-board-id\", userID))\n\t})\n\n\tt.Run(\"should correctly delete the board\", func(t *testing.T) {\n\t\tboardID := utils.NewID(utils.IDTypeBoard)\n\n\t\tboard := &model.Board{\n\t\t\tID:     boardID,\n\t\t\tTeamID: testTeamID,\n\t\t\tType:   model.BoardTypeOpen,\n\t\t}\n\n\t\tnewBoard, err := store.InsertBoard(board, userID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, newBoard)\n\n\t\trBoard, err := store.GetBoard(boardID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, rBoard)\n\n\t\t// wait to avoid hitting pk uniqueness constraint in history\n\t\ttime.Sleep(10 * time.Millisecond)\n\n\t\trequire.NoError(t, store.DeleteBoard(boardID, userID))\n\n\t\tr2Board, err := store.GetBoard(boardID)\n\t\trequire.True(t, model.IsErrNotFound(err), \"Should be ErrNotFound compatible error\")\n\t\trequire.Nil(t, r2Board)\n\t})\n}\n\nfunc testInsertBoardWithAdmin(t *testing.T, store store.Store) {\n\tuserID := testUserID\n\n\tt.Run(\"should correctly create a board and the admin membership with the creator\", func(t *testing.T) {\n\t\tboardID := utils.NewID(utils.IDTypeBoard)\n\n\t\tboard := &model.Board{\n\t\t\tID:     boardID,\n\t\t\tTeamID: testTeamID,\n\t\t\tType:   model.BoardTypeOpen,\n\t\t}\n\n\t\tnewBoard, newMember, err := store.InsertBoardWithAdmin(board, userID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, newBoard)\n\t\trequire.Equal(t, userID, newBoard.CreatedBy)\n\t\trequire.Equal(t, userID, newBoard.ModifiedBy)\n\t\trequire.NotNil(t, newMember)\n\t\trequire.Equal(t, userID, newMember.UserID)\n\t\trequire.Equal(t, boardID, newMember.BoardID)\n\t\trequire.True(t, newMember.SchemeAdmin)\n\t\trequire.True(t, newMember.SchemeEditor)\n\t})\n}\n\nfunc testSaveMember(t *testing.T, store store.Store) {\n\tuserID := testUserID\n\tboardID := testBoardID\n\n\tt.Run(\"should correctly create a member\", func(t *testing.T) {\n\t\tbm := &model.BoardMember{\n\t\t\tUserID:      userID,\n\t\t\tBoardID:     boardID,\n\t\t\tSchemeAdmin: true,\n\t\t}\n\n\t\tmemberHistory, err := store.GetBoardMemberHistory(boardID, userID, 0)\n\t\trequire.NoError(t, err)\n\t\tinitialMemberHistory := len(memberHistory)\n\n\t\tnbm, err := store.SaveMember(bm)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, userID, nbm.UserID)\n\t\trequire.Equal(t, boardID, nbm.BoardID)\n\n\t\trequire.True(t, nbm.SchemeAdmin)\n\n\t\tmemberHistory, err = store.GetBoardMemberHistory(boardID, userID, 0)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, memberHistory, initialMemberHistory+1)\n\t})\n\n\tt.Run(\"should correctly update a member\", func(t *testing.T) {\n\t\tbm := &model.BoardMember{\n\t\t\tUserID:       userID,\n\t\t\tBoardID:      boardID,\n\t\t\tSchemeEditor: true,\n\t\t\tSchemeViewer: true,\n\t\t}\n\n\t\tmemberHistory, err := store.GetBoardMemberHistory(boardID, userID, 0)\n\t\trequire.NoError(t, err)\n\t\tinitialMemberHistory := len(memberHistory)\n\n\t\tnbm, err := store.SaveMember(bm)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, userID, nbm.UserID)\n\t\trequire.Equal(t, boardID, nbm.BoardID)\n\n\t\trequire.False(t, nbm.SchemeAdmin)\n\t\trequire.True(t, nbm.SchemeEditor)\n\t\trequire.True(t, nbm.SchemeViewer)\n\n\t\tmemberHistory, err = store.GetBoardMemberHistory(boardID, userID, 0)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, memberHistory, initialMemberHistory)\n\t})\n\n\tt.Run(\"should return empty list if no results are found\", func(t *testing.T) {\n\t\tmemberHistory, err := store.GetBoardMemberHistory(boardID, \"nonexistent-user\", 0)\n\t\trequire.NoError(t, err)\n\t\trequire.Empty(t, memberHistory)\n\t})\n}\n\nfunc testGetMemberForBoard(t *testing.T, store store.Store) {\n\tuserID := testUserID\n\tboardID := testBoardID\n\n\tt.Run(\"should return an error not found for nonexisting membership\", func(t *testing.T) {\n\t\tbm, err := store.GetMemberForBoard(boardID, userID)\n\t\tvar nf *model.ErrNotFound\n\t\trequire.ErrorAs(t, err, &nf)\n\t\trequire.True(t, model.IsErrNotFound(err), \"Should be ErrNotFound compatible error\")\n\t\trequire.Nil(t, bm)\n\t})\n\n\tt.Run(\"should return the membership if exists\", func(t *testing.T) {\n\t\tbm := &model.BoardMember{\n\t\t\tUserID:      userID,\n\t\t\tBoardID:     boardID,\n\t\t\tSchemeAdmin: true,\n\t\t}\n\n\t\tnbm, err := store.SaveMember(bm)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, nbm)\n\n\t\trbm, err := store.GetMemberForBoard(boardID, userID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, rbm)\n\t\trequire.Equal(t, userID, rbm.UserID)\n\t\trequire.Equal(t, boardID, rbm.BoardID)\n\t\trequire.True(t, rbm.SchemeAdmin)\n\t})\n}\n\nfunc testGetMembersForBoard(t *testing.T, store store.Store) {\n\tt.Run(\"should return empty list if there are no members on a board\", func(t *testing.T) {\n\t\tmembers, err := store.GetMembersForBoard(testBoardID)\n\t\trequire.NoError(t, err)\n\t\trequire.Empty(t, members)\n\t})\n\n\tt.Run(\"should return the members of the board\", func(t *testing.T) {\n\t\tboardID1 := \"board-id-1\"\n\t\tboardID2 := \"board-id-2\"\n\n\t\tuserID1 := \"user-id-11\"\n\t\tuserID2 := \"user-id-12\"\n\t\tuserID3 := \"user-id-13\"\n\n\t\tbm1 := &model.BoardMember{BoardID: boardID1, UserID: userID1, SchemeAdmin: true}\n\t\t_, err1 := store.SaveMember(bm1)\n\t\trequire.NoError(t, err1)\n\n\t\tbm2 := &model.BoardMember{BoardID: boardID1, UserID: userID2, SchemeEditor: true}\n\t\t_, err2 := store.SaveMember(bm2)\n\t\trequire.NoError(t, err2)\n\n\t\tbm3 := &model.BoardMember{BoardID: boardID2, UserID: userID3, SchemeAdmin: true}\n\t\t_, err3 := store.SaveMember(bm3)\n\t\trequire.NoError(t, err3)\n\n\t\tgetMemberIDs := func(members []*model.BoardMember) []string {\n\t\t\tids := make([]string, len(members))\n\t\t\tfor i, member := range members {\n\t\t\t\tids[i] = member.UserID\n\t\t\t}\n\t\t\treturn ids\n\t\t}\n\n\t\tboard1Members, err := store.GetMembersForBoard(boardID1)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, board1Members, 2)\n\t\trequire.ElementsMatch(t, []string{userID1, userID2}, getMemberIDs(board1Members))\n\n\t\tboard2Members, err := store.GetMembersForBoard(boardID2)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, board2Members, 1)\n\t\trequire.ElementsMatch(t, []string{userID3}, getMemberIDs(board2Members))\n\t})\n}\n\nfunc testGetMembersForUser(t *testing.T, store store.Store) {\n\tt.Run(\"should return empty list if there are no memberships for a user\", func(t *testing.T) {\n\t\tmembers, err := store.GetMembersForUser(testUserID)\n\t\trequire.NoError(t, err)\n\t\trequire.Empty(t, members)\n\t})\n}\n\nfunc testDeleteMember(t *testing.T, store store.Store) {\n\tuserID := testUserID\n\tboardID := testBoardID\n\n\tt.Run(\"should return nil if deleting a nonexistent member\", func(t *testing.T) {\n\t\tmemberHistory, err := store.GetBoardMemberHistory(boardID, userID, 0)\n\t\trequire.NoError(t, err)\n\t\tinitialMemberHistory := len(memberHistory)\n\n\t\trequire.NoError(t, store.DeleteMember(boardID, userID))\n\n\t\tmemberHistory, err = store.GetBoardMemberHistory(boardID, userID, 0)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, memberHistory, initialMemberHistory)\n\t})\n\n\tt.Run(\"should correctly delete a member\", func(t *testing.T) {\n\t\tbm := &model.BoardMember{\n\t\t\tUserID:      userID,\n\t\t\tBoardID:     boardID,\n\t\t\tSchemeAdmin: true,\n\t\t}\n\n\t\tnbm, err := store.SaveMember(bm)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, nbm)\n\n\t\tmemberHistory, err := store.GetBoardMemberHistory(boardID, userID, 0)\n\t\trequire.NoError(t, err)\n\t\tinitialMemberHistory := len(memberHistory)\n\n\t\t// wait to avoid hitting pk uniqueness constraint in history\n\t\ttime.Sleep(1 * time.Millisecond)\n\n\t\trequire.NoError(t, store.DeleteMember(boardID, userID))\n\n\t\trbm, err := store.GetMemberForBoard(boardID, userID)\n\t\trequire.True(t, model.IsErrNotFound(err), \"Should be ErrNotFound compatible error\")\n\t\trequire.Nil(t, rbm)\n\n\t\tmemberHistory, err = store.GetBoardMemberHistory(boardID, userID, 0)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, memberHistory, initialMemberHistory+1)\n\t})\n}\n\nfunc testSearchBoardsForUser(t *testing.T, store store.Store) {\n\tteamID1 := \"team-id-1\"\n\tteamID2 := \"team-id-2\"\n\tuserID := \"user-id-1\"\n\n\tt.Run(\"should return empty if user is not a member of any board and there are no public boards on the team\", func(t *testing.T) {\n\t\tboards, err := store.SearchBoardsForUser(\"\", model.BoardSearchFieldTitle, userID, true)\n\t\trequire.NoError(t, err)\n\t\trequire.Empty(t, boards)\n\t})\n\n\tboard1 := &model.Board{\n\t\tID:         \"board-id-1\",\n\t\tTeamID:     teamID1,\n\t\tType:       model.BoardTypeOpen,\n\t\tTitle:      \"Public Board with admin\",\n\t\tProperties: map[string]any{\"foo\": \"bar1\"},\n\t}\n\t_, _, err := store.InsertBoardWithAdmin(board1, userID)\n\trequire.NoError(t, err)\n\n\tboard2 := &model.Board{\n\t\tID:         \"board-id-2\",\n\t\tTeamID:     teamID1,\n\t\tType:       model.BoardTypeOpen,\n\t\tTitle:      \"Public Board\",\n\t\tProperties: map[string]any{\"foo\": \"bar2\"},\n\t}\n\t_, err = store.InsertBoard(board2, userID)\n\trequire.NoError(t, err)\n\n\tboard3 := &model.Board{\n\t\tID:     \"board-id-3\",\n\t\tTeamID: teamID1,\n\t\tType:   model.BoardTypePrivate,\n\t\tTitle:  \"Private Board with admin\",\n\t}\n\t_, _, err = store.InsertBoardWithAdmin(board3, userID)\n\trequire.NoError(t, err)\n\n\tboard4 := &model.Board{\n\t\tID:     \"board-id-4\",\n\t\tTeamID: teamID1,\n\t\tType:   model.BoardTypePrivate,\n\t\tTitle:  \"Private Board\",\n\t}\n\t_, err = store.InsertBoard(board4, userID)\n\trequire.NoError(t, err)\n\n\tboard5 := &model.Board{\n\t\tID:     \"board-id-5\",\n\t\tTeamID: teamID2,\n\t\tType:   model.BoardTypeOpen,\n\t\tTitle:  \"Public Board with admin in team 2\",\n\t}\n\t_, _, err = store.InsertBoardWithAdmin(board5, userID)\n\trequire.NoError(t, err)\n\n\ttestCases := []struct {\n\t\tName             string\n\t\tTeamID           string\n\t\tUserID           string\n\t\tTerm             string\n\t\tSearchField      model.BoardSearchField\n\t\tIncludePublic    bool\n\t\tExpectedBoardIDs []string\n\t}{\n\t\t{\n\t\t\tName:             \"should find all private boards that the user is a member of and public boards with an empty term\",\n\t\t\tTeamID:           teamID1,\n\t\t\tUserID:           userID,\n\t\t\tTerm:             \"\",\n\t\t\tSearchField:      model.BoardSearchFieldTitle,\n\t\t\tIncludePublic:    true,\n\t\t\tExpectedBoardIDs: []string{board1.ID, board2.ID, board3.ID, board5.ID},\n\t\t},\n\t\t{\n\t\t\tName:             \"should find all with term board\",\n\t\t\tTeamID:           teamID1,\n\t\t\tUserID:           userID,\n\t\t\tTerm:             \"board\",\n\t\t\tSearchField:      model.BoardSearchFieldTitle,\n\t\t\tIncludePublic:    true,\n\t\t\tExpectedBoardIDs: []string{board1.ID, board2.ID, board3.ID, board5.ID},\n\t\t},\n\t\t{\n\t\t\tName:             \"should find all with term board where the user is member of\",\n\t\t\tTeamID:           teamID1,\n\t\t\tUserID:           userID,\n\t\t\tTerm:             \"board\",\n\t\t\tSearchField:      model.BoardSearchFieldTitle,\n\t\t\tIncludePublic:    false,\n\t\t\tExpectedBoardIDs: []string{board1.ID, board3.ID, board5.ID},\n\t\t},\n\t\t{\n\t\t\tName:             \"should find only public as per the term, wether user is a member or not\",\n\t\t\tTeamID:           teamID1,\n\t\t\tUserID:           userID,\n\t\t\tTerm:             \"public\",\n\t\t\tSearchField:      model.BoardSearchFieldTitle,\n\t\t\tIncludePublic:    true,\n\t\t\tExpectedBoardIDs: []string{board1.ID, board2.ID, board5.ID},\n\t\t},\n\t\t{\n\t\t\tName:             \"should find only private as per the term, wether user is a member or not\",\n\t\t\tTeamID:           teamID1,\n\t\t\tUserID:           userID,\n\t\t\tTerm:             \"priv\",\n\t\t\tSearchField:      model.BoardSearchFieldTitle,\n\t\t\tIncludePublic:    true,\n\t\t\tExpectedBoardIDs: []string{board3.ID},\n\t\t},\n\t\t{\n\t\t\tName:             \"should find no board in team 2 with a non matching term\",\n\t\t\tTeamID:           teamID2,\n\t\t\tUserID:           userID,\n\t\t\tTerm:             \"non-matching-term\",\n\t\t\tSearchField:      model.BoardSearchFieldTitle,\n\t\t\tIncludePublic:    true,\n\t\t\tExpectedBoardIDs: []string{},\n\t\t},\n\t\t{\n\t\t\tName:             \"should find all boards with a named property\",\n\t\t\tTeamID:           teamID1,\n\t\t\tUserID:           userID,\n\t\t\tTerm:             \"foo\",\n\t\t\tSearchField:      model.BoardSearchFieldPropertyName,\n\t\t\tIncludePublic:    true,\n\t\t\tExpectedBoardIDs: []string{board1.ID, board2.ID},\n\t\t},\n\t\t{\n\t\t\tName:             \"should find no boards with a non-existing named property\",\n\t\t\tTeamID:           teamID1,\n\t\t\tUserID:           userID,\n\t\t\tTerm:             \"bogus\",\n\t\t\tSearchField:      model.BoardSearchFieldPropertyName,\n\t\t\tIncludePublic:    true,\n\t\t\tExpectedBoardIDs: []string{},\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\tboards, err := store.SearchBoardsForUser(tc.Term, tc.SearchField, tc.UserID, tc.IncludePublic)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tboardIDs := []string{}\n\t\t\tfor _, board := range boards {\n\t\t\t\tboardIDs = append(boardIDs, board.ID)\n\t\t\t}\n\t\t\trequire.ElementsMatch(t, tc.ExpectedBoardIDs, boardIDs)\n\t\t})\n\t}\n}\n\nfunc testSearchBoardsForUserInTeam(t *testing.T, store store.Store) {\n\tt.Run(\"should return empty list if there are no resutls\", func(t *testing.T) {\n\t\tboards, err := store.SearchBoardsForUserInTeam(\"nonexistent-team-id\", \"\", testUserID)\n\t\trequire.NoError(t, err)\n\t\trequire.Empty(t, boards)\n\t})\n}\n\nfunc testUndeleteBoard(t *testing.T, store store.Store) {\n\tuserID := testUserID\n\n\tt.Run(\"existing id\", func(t *testing.T) {\n\t\tboardID := utils.NewID(utils.IDTypeBoard)\n\n\t\tboard := &model.Board{\n\t\t\tID:              boardID,\n\t\t\tTeamID:          testTeamID,\n\t\t\tType:            model.BoardTypeOpen,\n\t\t\tTitle:           \"Dunder Mifflin Scranton\",\n\t\t\tMinimumRole:     model.BoardRoleCommenter,\n\t\t\tDescription:     \"Bears, beets, Battlestar Gallectica\",\n\t\t\tIcon:            \"🐻\",\n\t\t\tShowDescription: true,\n\t\t\tIsTemplate:      false,\n\t\t\tProperties: map[string]interface{}{\n\t\t\t\t\"prop_1\": \"value_1\",\n\t\t\t},\n\t\t\tCardProperties: []map[string]interface{}{\n\t\t\t\t{\n\t\t\t\t\t\"prop_1\": \"value_1\",\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tnewBoard, err := store.InsertBoard(board, userID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, newBoard)\n\n\t\t// Wait for not colliding the ID+insert_at key\n\t\ttime.Sleep(1 * time.Millisecond)\n\t\terr = store.DeleteBoard(boardID, userID)\n\t\trequire.NoError(t, err)\n\n\t\tboard, err = store.GetBoard(boardID)\n\t\trequire.Error(t, err)\n\t\trequire.Nil(t, board)\n\n\t\ttime.Sleep(1 * time.Millisecond)\n\t\terr = store.UndeleteBoard(boardID, userID)\n\t\trequire.NoError(t, err)\n\n\t\tboard, err = store.GetBoard(boardID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, board)\n\n\t\t// verifying the data after un-delete\n\t\trequire.Equal(t, \"Dunder Mifflin Scranton\", board.Title)\n\t\trequire.Equal(t, \"user-id\", board.CreatedBy)\n\t\trequire.Equal(t, \"user-id\", board.ModifiedBy)\n\t\trequire.Equal(t, model.BoardRoleCommenter, board.MinimumRole)\n\t\trequire.Equal(t, \"Bears, beets, Battlestar Gallectica\", board.Description)\n\t\trequire.Equal(t, \"🐻\", board.Icon)\n\t\trequire.True(t, board.ShowDescription)\n\t\trequire.False(t, board.IsTemplate)\n\t\trequire.Equal(t, board.Properties[\"prop_1\"].(string), \"value_1\")\n\t\trequire.Equal(t, 1, len(board.CardProperties))\n\t\trequire.Equal(t, board.CardProperties[0][\"prop_1\"], \"value_1\")\n\t\trequire.Equal(t, board.CardProperties[0][\"prop_1\"], \"value_1\")\n\t})\n\n\tt.Run(\"existing id multiple times\", func(t *testing.T) {\n\t\tboardID := utils.NewID(utils.IDTypeBoard)\n\n\t\tboard := &model.Board{\n\t\t\tID:     boardID,\n\t\t\tTeamID: testTeamID,\n\t\t\tType:   model.BoardTypeOpen,\n\t\t}\n\n\t\tnewBoard, err := store.InsertBoard(board, userID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, newBoard)\n\n\t\t// Wait for not colliding the ID+insert_at key\n\t\ttime.Sleep(1 * time.Millisecond)\n\t\terr = store.DeleteBoard(boardID, userID)\n\t\trequire.NoError(t, err)\n\n\t\tboard, err = store.GetBoard(boardID)\n\t\trequire.Error(t, err)\n\t\trequire.Nil(t, board)\n\n\t\t// Wait for not colliding the ID+insert_at key\n\t\ttime.Sleep(1 * time.Millisecond)\n\t\terr = store.UndeleteBoard(boardID, userID)\n\t\trequire.NoError(t, err)\n\n\t\tboard, err = store.GetBoard(boardID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, board)\n\n\t\t// Wait for not colliding the ID+insert_at key\n\t\ttime.Sleep(1 * time.Millisecond)\n\t\terr = store.UndeleteBoard(boardID, userID)\n\t\trequire.NoError(t, err)\n\n\t\tboard, err = store.GetBoard(boardID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, board)\n\t})\n\n\tt.Run(\"from not existing id\", func(t *testing.T) {\n\t\t// Wait for not colliding the ID+insert_at key\n\t\ttime.Sleep(1 * time.Millisecond)\n\t\terr := store.UndeleteBoard(\"not-exists\", userID)\n\t\trequire.NoError(t, err)\n\n\t\tblock, err := store.GetBoard(\"not-exists\")\n\t\trequire.Error(t, err)\n\t\trequire.Nil(t, block)\n\t})\n}\n\nfunc testGetBoardHistory(t *testing.T, store store.Store) {\n\tuserID := testUserID\n\n\tt.Run(\"testGetBoardHistory: create board\", func(t *testing.T) {\n\t\toriginalTitle := \"Board: original title\"\n\t\tboardID := utils.NewID(utils.IDTypeBoard)\n\t\tboard := &model.Board{\n\t\t\tID:     boardID,\n\t\t\tTitle:  originalTitle,\n\t\t\tTeamID: testTeamID,\n\t\t\tType:   model.BoardTypeOpen,\n\t\t}\n\n\t\trBoard1, err := store.InsertBoard(board, userID)\n\t\trequire.NoError(t, err)\n\n\t\topts := model.QueryBoardHistoryOptions{\n\t\t\tLimit:      0,\n\t\t\tDescending: false,\n\t\t}\n\n\t\tboards, err := store.GetBoardHistory(board.ID, opts)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, boards, 1)\n\n\t\t// wait to avoid hitting pk uniqueness constraint in history\n\t\ttime.Sleep(10 * time.Millisecond)\n\n\t\tuserID2 := \"user-id-2\"\n\t\tnewTitle := \"Board: A new title\"\n\t\tnewDescription := \"A new description\"\n\t\tpatch := &model.BoardPatch{Title: &newTitle, Description: &newDescription}\n\t\tpatchedBoard, err := store.PatchBoard(boardID, patch, userID2)\n\t\trequire.NoError(t, err)\n\n\t\t// Updated history\n\t\tboards, err = store.GetBoardHistory(board.ID, opts)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, boards, 2)\n\t\trequire.Equal(t, boards[0].Title, originalTitle)\n\t\trequire.Equal(t, boards[1].Title, newTitle)\n\t\trequire.Equal(t, boards[1].Description, newDescription)\n\n\t\t// Check history against latest board\n\t\trBoard2, err := store.GetBoard(board.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, rBoard2.Title, newTitle)\n\t\trequire.Equal(t, rBoard2.Title, boards[1].Title)\n\t\trequire.NotZero(t, rBoard2.UpdateAt)\n\t\trequire.Equal(t, rBoard1.UpdateAt, boards[0].UpdateAt)\n\t\trequire.Equal(t, rBoard2.UpdateAt, patchedBoard.UpdateAt)\n\t\trequire.Equal(t, rBoard2.UpdateAt, boards[1].UpdateAt)\n\t\trequire.Equal(t, rBoard1, boards[0])\n\t\trequire.Equal(t, rBoard2, boards[1])\n\n\t\t// wait to avoid hitting pk uniqueness constraint in history\n\t\ttime.Sleep(10 * time.Millisecond)\n\n\t\tnewTitle2 := \"Board: A new title 2\"\n\t\tpatch2 := &model.BoardPatch{Title: &newTitle2}\n\t\tpatchBoard2, err := store.PatchBoard(boardID, patch2, userID2)\n\t\trequire.NoError(t, err)\n\n\t\t// Updated history\n\t\topts = model.QueryBoardHistoryOptions{\n\t\t\tLimit:      1,\n\t\t\tDescending: true,\n\t\t}\n\t\tboards, err = store.GetBoardHistory(board.ID, opts)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, boards, 1)\n\t\trequire.Equal(t, boards[0].Title, newTitle2)\n\t\trequire.Equal(t, boards[0], patchBoard2)\n\n\t\t// Delete board\n\t\ttime.Sleep(10 * time.Millisecond)\n\t\terr = store.DeleteBoard(boardID, userID)\n\t\trequire.NoError(t, err)\n\n\t\t// Updated history after delete\n\t\topts = model.QueryBoardHistoryOptions{\n\t\t\tLimit:      0,\n\t\t\tDescending: true,\n\t\t}\n\t\tboards, err = store.GetBoardHistory(board.ID, opts)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, boards, 4)\n\t\trequire.NotZero(t, boards[0].UpdateAt)\n\t\trequire.Greater(t, boards[0].UpdateAt, patchBoard2.UpdateAt)\n\t\trequire.NotZero(t, boards[0].DeleteAt)\n\t\trequire.Greater(t, boards[0].DeleteAt, patchBoard2.UpdateAt)\n\t})\n\n\tt.Run(\"testGetBoardHistory: nonexisting board\", func(t *testing.T) {\n\t\topts := model.QueryBoardHistoryOptions{\n\t\t\tLimit:      0,\n\t\t\tDescending: false,\n\t\t}\n\t\tboards, err := store.GetBoardHistory(\"nonexistent-id\", opts)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, boards, 0)\n\t})\n}\n\nfunc testGetBoardCount(t *testing.T, store store.Store) {\n\tuserID := testUserID\n\n\tt.Run(\"test GetBoardCount\", func(t *testing.T) {\n\t\toriginalCount, err := store.GetBoardCount()\n\t\trequire.NoError(t, err)\n\n\t\ttitle := \"Board: original title\"\n\t\tboardID := utils.NewID(utils.IDTypeBoard)\n\t\tboard := &model.Board{\n\t\t\tID:     boardID,\n\t\t\tTitle:  title,\n\t\t\tTeamID: testTeamID,\n\t\t\tType:   model.BoardTypeOpen,\n\t\t}\n\n\t\t_, err = store.InsertBoard(board, userID)\n\t\trequire.NoError(t, err)\n\n\t\tnewCount, err := store.GetBoardCount()\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, originalCount+1, newCount)\n\t})\n}\n"
  },
  {
    "path": "server/services/store/storetests/boards_and_blocks.go",
    "content": "package storetests\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/store\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc StoreTestBoardsAndBlocksStore(t *testing.T, setup func(t *testing.T) (store.Store, func())) {\n\tt.Run(\"createBoardsAndBlocks\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestCreateBoardsAndBlocks(t, store)\n\t})\n\tt.Run(\"patchBoardsAndBlocks\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestPatchBoardsAndBlocks(t, store)\n\t})\n\tt.Run(\"deleteBoardsAndBlocks\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestDeleteBoardsAndBlocks(t, store)\n\t})\n\n\tt.Run(\"duplicateBoard\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestDuplicateBoard(t, store)\n\t})\n}\n\nfunc testCreateBoardsAndBlocks(t *testing.T, store store.Store) {\n\tteamID := testTeamID\n\tuserID := testUserID\n\n\tboards, err := store.GetBoardsForUserAndTeam(userID, teamID, true)\n\trequire.Nil(t, err)\n\trequire.Empty(t, boards)\n\n\tt.Run(\"create boards and blocks\", func(t *testing.T) {\n\t\tnewBab := &model.BoardsAndBlocks{\n\t\t\tBoards: []*model.Board{\n\t\t\t\t{ID: \"board-id-1\", TeamID: teamID, Type: model.BoardTypeOpen},\n\t\t\t\t{ID: \"board-id-2\", TeamID: teamID, Type: model.BoardTypePrivate},\n\t\t\t\t{ID: \"board-id-3\", TeamID: teamID, Type: model.BoardTypeOpen},\n\t\t\t},\n\t\t\tBlocks: []*model.Block{\n\t\t\t\t{ID: \"block-id-1\", BoardID: \"board-id-1\", Type: model.TypeCard},\n\t\t\t\t{ID: \"block-id-2\", BoardID: \"board-id-2\", Type: model.TypeCard},\n\t\t\t},\n\t\t}\n\n\t\tbab, err := store.CreateBoardsAndBlocks(newBab, userID)\n\t\trequire.Nil(t, err)\n\t\trequire.NotNil(t, bab)\n\t\trequire.Len(t, bab.Boards, 3)\n\t\trequire.Len(t, bab.Blocks, 2)\n\n\t\tboardIDs := []string{}\n\t\tfor _, board := range bab.Boards {\n\t\t\tboardIDs = append(boardIDs, board.ID)\n\t\t}\n\n\t\tblockIDs := []string{}\n\t\tfor _, block := range bab.Blocks {\n\t\t\tblockIDs = append(blockIDs, block.ID)\n\t\t}\n\n\t\trequire.ElementsMatch(t, []string{\"board-id-1\", \"board-id-2\", \"board-id-3\"}, boardIDs)\n\t\trequire.ElementsMatch(t, []string{\"block-id-1\", \"block-id-2\"}, blockIDs)\n\t})\n\n\tt.Run(\"create boards and blocks with admin\", func(t *testing.T) {\n\t\tnewBab := &model.BoardsAndBlocks{\n\t\t\tBoards: []*model.Board{\n\t\t\t\t{ID: \"board-id-4\", TeamID: teamID, Type: model.BoardTypeOpen},\n\t\t\t\t{ID: \"board-id-5\", TeamID: teamID, Type: model.BoardTypePrivate},\n\t\t\t\t{ID: \"board-id-6\", TeamID: teamID, Type: model.BoardTypeOpen},\n\t\t\t},\n\t\t\tBlocks: []*model.Block{\n\t\t\t\t{ID: \"block-id-3\", BoardID: \"board-id-4\", Type: model.TypeCard},\n\t\t\t\t{ID: \"block-id-4\", BoardID: \"board-id-5\", Type: model.TypeCard},\n\t\t\t},\n\t\t}\n\n\t\tbab, members, err := store.CreateBoardsAndBlocksWithAdmin(newBab, userID)\n\t\trequire.Nil(t, err)\n\t\trequire.NotNil(t, bab)\n\t\trequire.Len(t, bab.Boards, 3)\n\t\trequire.Len(t, bab.Blocks, 2)\n\t\trequire.Len(t, members, 3)\n\n\t\tboardIDs := []string{}\n\t\tfor _, board := range bab.Boards {\n\t\t\tboardIDs = append(boardIDs, board.ID)\n\t\t}\n\n\t\tblockIDs := []string{}\n\t\tfor _, block := range bab.Blocks {\n\t\t\tblockIDs = append(blockIDs, block.ID)\n\t\t}\n\n\t\trequire.ElementsMatch(t, []string{\"board-id-4\", \"board-id-5\", \"board-id-6\"}, boardIDs)\n\t\trequire.ElementsMatch(t, []string{\"block-id-3\", \"block-id-4\"}, blockIDs)\n\n\t\tmemberBoardIDs := []string{}\n\t\tfor _, member := range members {\n\t\t\trequire.Equal(t, userID, member.UserID)\n\t\t\tmemberBoardIDs = append(memberBoardIDs, member.BoardID)\n\t\t}\n\t\trequire.ElementsMatch(t, []string{\"board-id-4\", \"board-id-5\", \"board-id-6\"}, memberBoardIDs)\n\t})\n\n\tt.Run(\"on failure, nothing should be saved\", func(t *testing.T) {\n\t\t// one of the blocks is invalid as it doesn't have BoardID\n\t\tnewBab := &model.BoardsAndBlocks{\n\t\t\tBoards: []*model.Board{\n\t\t\t\t{ID: \"board-id-7\", TeamID: teamID, Type: model.BoardTypeOpen},\n\t\t\t\t{ID: \"board-id-8\", TeamID: teamID, Type: model.BoardTypePrivate},\n\t\t\t\t{ID: \"board-id-9\", TeamID: teamID, Type: model.BoardTypeOpen},\n\t\t\t},\n\t\t\tBlocks: []*model.Block{\n\t\t\t\t{ID: \"block-id-5\", BoardID: \"board-id-7\", Type: model.TypeCard},\n\t\t\t\t{ID: \"block-id-6\", BoardID: \"\", Type: model.TypeCard},\n\t\t\t},\n\t\t}\n\n\t\tbab, err := store.CreateBoardsAndBlocks(newBab, userID)\n\t\trequire.Error(t, err)\n\t\trequire.Nil(t, bab)\n\n\t\tbab, members, err := store.CreateBoardsAndBlocksWithAdmin(newBab, userID)\n\t\trequire.Error(t, err)\n\t\trequire.Empty(t, bab)\n\t\trequire.Empty(t, members)\n\t})\n\n\tt.Run(\"should apply block size limits\", func(t *testing.T) {\n\t\t// one of the blocks is invalid as it has a title too large\n\t\tnewBab := &model.BoardsAndBlocks{\n\t\t\tBoards: []*model.Board{\n\t\t\t\t{ID: \"board-id-7\", TeamID: teamID, Type: model.BoardTypeOpen},\n\t\t\t\t{ID: \"board-id-8\", TeamID: teamID, Type: model.BoardTypePrivate},\n\t\t\t\t{ID: \"board-id-9\", TeamID: teamID, Type: model.BoardTypeOpen},\n\t\t\t},\n\t\t\tBlocks: []*model.Block{\n\t\t\t\t{ID: \"block-id-5\", BoardID: \"board-id-7\", Type: model.TypeCard},\n\t\t\t\t{ID: \"block-id-6\", BoardID: \"board-id-8\", Type: model.TypeCard, Title: strings.Repeat(\"A\", model.BlockTitleMaxRunes+1)},\n\t\t\t},\n\t\t}\n\n\t\tbab, err := store.CreateBoardsAndBlocks(newBab, userID)\n\t\trequire.ErrorIs(t, err, model.ErrBlockTitleSizeLimitExceeded)\n\t\trequire.Nil(t, bab)\n\n\t\tbab, members, err := store.CreateBoardsAndBlocksWithAdmin(newBab, userID)\n\t\trequire.ErrorIs(t, err, model.ErrBlockTitleSizeLimitExceeded)\n\t\trequire.Empty(t, bab)\n\t\trequire.Empty(t, members)\n\t})\n}\n\nfunc testPatchBoardsAndBlocks(t *testing.T, store store.Store) {\n\tteamID := testTeamID\n\tuserID := testUserID\n\n\tt.Run(\"on failure, nothing should be saved\", func(t *testing.T) {\n\t\tif store.DBType() == model.SqliteDBType {\n\t\t\tt.Skip(\"No transactions support int sqlite\")\n\t\t}\n\n\t\tinitialTitle := \"initial title\"\n\t\tnewTitle := \"new title\"\n\n\t\tboard := &model.Board{\n\t\t\tID:     \"board-id-1\",\n\t\t\tTitle:  initialTitle,\n\t\t\tTeamID: teamID,\n\t\t\tType:   model.BoardTypeOpen,\n\t\t}\n\t\t_, err := store.InsertBoard(board, userID)\n\t\trequire.NoError(t, err)\n\n\t\tblock := &model.Block{\n\t\t\tID:      \"block-id-1\",\n\t\t\tBoardID: \"board-id-1\",\n\t\t\tTitle:   initialTitle,\n\t\t}\n\t\trequire.NoError(t, store.InsertBlock(block, userID))\n\n\t\t// apply the patches\n\t\tpbab := &model.PatchBoardsAndBlocks{\n\t\t\tBoardIDs: []string{\"board-id-1\"},\n\t\t\tBoardPatches: []*model.BoardPatch{\n\t\t\t\t{Title: &newTitle},\n\t\t\t},\n\t\t\tBlockIDs: []string{\"block-id-1\", \"block-id-2\"},\n\t\t\tBlockPatches: []*model.BlockPatch{\n\t\t\t\t{Title: &newTitle},\n\t\t\t\t{Title: &newTitle},\n\t\t\t},\n\t\t}\n\n\t\ttime.Sleep(10 * time.Millisecond)\n\n\t\tbab, err := store.PatchBoardsAndBlocks(pbab, userID)\n\t\trequire.Error(t, err)\n\t\trequire.Nil(t, bab)\n\n\t\t// check that things have not changed\n\t\trBoard, err := store.GetBoard(\"board-id-1\")\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, initialTitle, rBoard.Title)\n\n\t\trBlock, err := store.GetBlock(\"block-id-1\")\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, initialTitle, rBlock.Title)\n\t})\n\n\tt.Run(\"should apply block size limits\", func(t *testing.T) {\n\t\tif store.DBType() == model.SqliteDBType {\n\t\t\tt.Skip(\"No transactions support int sqlite\")\n\t\t}\n\n\t\tinitialTitle := \"initial title\"\n\t\tnewTitle := strings.Repeat(\"A\", model.BlockTitleMaxRunes+1)\n\n\t\tboard := &model.Board{\n\t\t\tID:     \"board-id-1\",\n\t\t\tTitle:  initialTitle,\n\t\t\tTeamID: teamID,\n\t\t\tType:   model.BoardTypeOpen,\n\t\t}\n\t\t_, err := store.InsertBoard(board, userID)\n\t\trequire.NoError(t, err)\n\n\t\tblock := &model.Block{\n\t\t\tID:      \"block-id-1\",\n\t\t\tBoardID: \"board-id-1\",\n\t\t\tTitle:   initialTitle,\n\t\t}\n\t\trequire.NoError(t, store.InsertBlock(block, userID))\n\n\t\t// apply the patches\n\t\tpbab := &model.PatchBoardsAndBlocks{\n\t\t\tBlockIDs: []string{\"block-id-1\"},\n\t\t\tBlockPatches: []*model.BlockPatch{\n\t\t\t\t{Title: &newTitle},\n\t\t\t},\n\t\t}\n\n\t\ttime.Sleep(10 * time.Millisecond)\n\n\t\tbab, err := store.PatchBoardsAndBlocks(pbab, userID)\n\t\trequire.ErrorIs(t, err, model.ErrBlockTitleSizeLimitExceeded)\n\t\trequire.Nil(t, bab)\n\n\t\t// check that things have not changed\n\t\trBlock, err := store.GetBlock(\"block-id-1\")\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, initialTitle, rBlock.Title)\n\t})\n\n\tt.Run(\"patch boards and blocks\", func(t *testing.T) {\n\t\tnewBab := &model.BoardsAndBlocks{\n\t\t\tBoards: []*model.Board{\n\t\t\t\t{ID: \"board-id-1\", Description: \"initial description\", TeamID: teamID, Type: model.BoardTypeOpen},\n\t\t\t\t{ID: \"board-id-2\", TeamID: teamID, Type: model.BoardTypePrivate},\n\t\t\t\t{ID: \"board-id-3\", Title: \"initial title\", TeamID: teamID, Type: model.BoardTypeOpen},\n\t\t\t},\n\t\t\tBlocks: []*model.Block{\n\t\t\t\t{ID: \"block-id-1\", Title: \"initial title\", BoardID: \"board-id-1\", Type: model.TypeCard},\n\t\t\t\t{ID: \"block-id-2\", Schema: 1, BoardID: \"board-id-2\", Type: model.TypeCard},\n\t\t\t},\n\t\t}\n\n\t\trBab, err := store.CreateBoardsAndBlocks(newBab, userID)\n\t\trequire.Nil(t, err)\n\t\trequire.NotNil(t, rBab)\n\t\trequire.Len(t, rBab.Boards, 3)\n\t\trequire.Len(t, rBab.Blocks, 2)\n\n\t\t// apply the patches\n\t\tnewTitle := \"new title\"\n\t\tnewDescription := \"new description\"\n\t\tvar newSchema int64 = 2\n\n\t\tpbab := &model.PatchBoardsAndBlocks{\n\t\t\tBoardIDs: []string{\"board-id-3\", \"board-id-1\"},\n\t\t\tBoardPatches: []*model.BoardPatch{\n\t\t\t\t{Title: &newTitle, Description: &newDescription},\n\t\t\t\t{Description: &newDescription},\n\t\t\t},\n\t\t\tBlockIDs: []string{\"block-id-1\", \"block-id-2\"},\n\t\t\tBlockPatches: []*model.BlockPatch{\n\t\t\t\t{Title: &newTitle},\n\t\t\t\t{Schema: &newSchema},\n\t\t\t},\n\t\t}\n\n\t\ttime.Sleep(10 * time.Millisecond)\n\n\t\tbab, err := store.PatchBoardsAndBlocks(pbab, userID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, bab)\n\t\trequire.Len(t, bab.Boards, 2)\n\t\trequire.Len(t, bab.Blocks, 2)\n\n\t\t// check that things have changed\n\t\tboard1, err := store.GetBoard(\"board-id-1\")\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, newDescription, board1.Description)\n\n\t\tboard3, err := store.GetBoard(\"board-id-3\")\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, newTitle, board3.Title)\n\t\trequire.Equal(t, newDescription, board3.Description)\n\n\t\tblock1, err := store.GetBlock(\"block-id-1\")\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, newTitle, block1.Title)\n\n\t\tblock2, err := store.GetBlock(\"block-id-2\")\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, newSchema, block2.Schema)\n\t})\n}\n\nfunc testDeleteBoardsAndBlocks(t *testing.T, store store.Store) {\n\tteamID := testTeamID\n\tuserID := testUserID\n\n\tt.Run(\"should not delete anything if a block doesn't belong to any of the boards\", func(t *testing.T) {\n\t\tif store.DBType() == model.SqliteDBType {\n\t\t\tt.Skip(\"No transactions support int sqlite\")\n\t\t}\n\n\t\tnewBoard1 := &model.Board{\n\t\t\tID:     utils.NewID(utils.IDTypeBoard),\n\t\t\tTeamID: teamID,\n\t\t\tType:   model.BoardTypeOpen,\n\t\t}\n\t\tboard1, err := store.InsertBoard(newBoard1, userID)\n\t\trequire.NoError(t, err)\n\n\t\tblock1 := &model.Block{\n\t\t\tID:      utils.NewID(utils.IDTypeBlock),\n\t\t\tBoardID: board1.ID,\n\t\t}\n\t\trequire.NoError(t, store.InsertBlock(block1, userID))\n\n\t\tblock2 := &model.Block{\n\t\t\tID:      utils.NewID(utils.IDTypeBlock),\n\t\t\tBoardID: board1.ID,\n\t\t}\n\t\trequire.NoError(t, store.InsertBlock(block2, userID))\n\n\t\tnewBoard2 := &model.Board{\n\t\t\tID:     utils.NewID(utils.IDTypeBoard),\n\t\t\tTeamID: teamID,\n\t\t\tType:   model.BoardTypeOpen,\n\t\t}\n\t\tboard2, err := store.InsertBoard(newBoard2, userID)\n\t\trequire.NoError(t, err)\n\n\t\tblock3 := &model.Block{\n\t\t\tID:      utils.NewID(utils.IDTypeBlock),\n\t\t\tBoardID: board2.ID,\n\t\t}\n\t\trequire.NoError(t, store.InsertBlock(block3, userID))\n\n\t\tblock4 := &model.Block{\n\t\t\tID:      utils.NewID(utils.IDTypeBlock),\n\t\t\tBoardID: \"different-board-id\",\n\t\t}\n\t\trequire.NoError(t, store.InsertBlock(block4, userID))\n\n\t\tdbab := &model.DeleteBoardsAndBlocks{\n\t\t\tBoards: []string{board1.ID, board2.ID},\n\t\t\tBlocks: []string{block1.ID, block2.ID, block3.ID, block4.ID},\n\t\t}\n\n\t\ttime.Sleep(10 * time.Millisecond)\n\n\t\texpectedErrorMsg := fmt.Sprintf(\"block %s doesn't belong to any of the boards in the delete request\", block4.ID)\n\t\trequire.EqualError(t, store.DeleteBoardsAndBlocks(dbab, userID), expectedErrorMsg)\n\n\t\t// all the entities should still exist\n\t\trBoard1, err := store.GetBoard(board1.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, rBoard1)\n\t\trBlock1, err := store.GetBlock(block1.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, rBlock1)\n\t\trBlock2, err := store.GetBlock(block2.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, rBlock2)\n\n\t\trBoard2, err := store.GetBoard(board2.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, rBoard2)\n\t\trBlock3, err := store.GetBlock(block3.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, rBlock3)\n\t\trBlock4, err := store.GetBlock(block4.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, rBlock4)\n\t})\n\n\tt.Run(\"should not delete anything if a board doesn't exist\", func(t *testing.T) {\n\t\tif store.DBType() == model.SqliteDBType {\n\t\t\tt.Skip(\"No transactions support int sqlite\")\n\t\t}\n\n\t\tnewBoard1 := &model.Board{\n\t\t\tID:     utils.NewID(utils.IDTypeBoard),\n\t\t\tTeamID: teamID,\n\t\t\tType:   model.BoardTypeOpen,\n\t\t}\n\t\tboard1, err := store.InsertBoard(newBoard1, userID)\n\t\trequire.NoError(t, err)\n\n\t\tblock1 := &model.Block{\n\t\t\tID:      utils.NewID(utils.IDTypeBlock),\n\t\t\tBoardID: board1.ID,\n\t\t}\n\t\trequire.NoError(t, store.InsertBlock(block1, userID))\n\n\t\tblock2 := &model.Block{\n\t\t\tID:      utils.NewID(utils.IDTypeBlock),\n\t\t\tBoardID: board1.ID,\n\t\t}\n\t\trequire.NoError(t, store.InsertBlock(block2, userID))\n\n\t\tnewBoard2 := &model.Board{\n\t\t\tID:     utils.NewID(utils.IDTypeBoard),\n\t\t\tTeamID: teamID,\n\t\t\tType:   model.BoardTypeOpen,\n\t\t}\n\t\tboard2, err := store.InsertBoard(newBoard2, userID)\n\t\trequire.NoError(t, err)\n\n\t\tblock3 := &model.Block{\n\t\t\tID:      utils.NewID(utils.IDTypeBlock),\n\t\t\tBoardID: board2.ID,\n\t\t}\n\t\trequire.NoError(t, store.InsertBlock(block3, userID))\n\n\t\tblock4 := &model.Block{\n\t\t\tID:      utils.NewID(utils.IDTypeBlock),\n\t\t\tBoardID: board2.ID,\n\t\t}\n\t\trequire.NoError(t, store.InsertBlock(block4, userID))\n\n\t\tdbab := &model.DeleteBoardsAndBlocks{\n\t\t\tBoards: []string{board1.ID, board2.ID, \"a nonexistent board ID\"},\n\t\t\tBlocks: []string{block1.ID, block2.ID, block3.ID, block4.ID},\n\t\t}\n\n\t\ttime.Sleep(10 * time.Millisecond)\n\n\t\trequire.True(t, model.IsErrNotFound(store.DeleteBoardsAndBlocks(dbab, userID)))\n\n\t\t// all the entities should still exist\n\t\trBoard1, err := store.GetBoard(board1.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, rBoard1)\n\t\trBlock1, err := store.GetBlock(block1.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, rBlock1)\n\t\trBlock2, err := store.GetBlock(block2.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, rBlock2)\n\n\t\trBoard2, err := store.GetBoard(board2.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, rBoard2)\n\t\trBlock3, err := store.GetBlock(block3.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, rBlock3)\n\t\trBlock4, err := store.GetBlock(block4.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, rBlock4)\n\t})\n\n\tt.Run(\"should not delete anything if a block doesn't exist\", func(t *testing.T) {\n\t\tif store.DBType() == model.SqliteDBType {\n\t\t\tt.Skip(\"No transactions support int sqlite\")\n\t\t}\n\n\t\tnewBoard1 := &model.Board{\n\t\t\tID:     utils.NewID(utils.IDTypeBoard),\n\t\t\tTeamID: teamID,\n\t\t\tType:   model.BoardTypeOpen,\n\t\t}\n\t\tboard1, err := store.InsertBoard(newBoard1, userID)\n\t\trequire.NoError(t, err)\n\n\t\tblock1 := &model.Block{\n\t\t\tID:      utils.NewID(utils.IDTypeBlock),\n\t\t\tBoardID: board1.ID,\n\t\t}\n\t\trequire.NoError(t, store.InsertBlock(block1, userID))\n\n\t\tblock2 := &model.Block{\n\t\t\tID:      utils.NewID(utils.IDTypeBlock),\n\t\t\tBoardID: board1.ID,\n\t\t}\n\t\trequire.NoError(t, store.InsertBlock(block2, userID))\n\n\t\tnewBoard2 := &model.Board{\n\t\t\tID:     utils.NewID(utils.IDTypeBoard),\n\t\t\tTeamID: teamID,\n\t\t\tType:   model.BoardTypeOpen,\n\t\t}\n\t\tboard2, err := store.InsertBoard(newBoard2, userID)\n\t\trequire.NoError(t, err)\n\n\t\tblock3 := &model.Block{\n\t\t\tID:      utils.NewID(utils.IDTypeBlock),\n\t\t\tBoardID: board2.ID,\n\t\t}\n\t\trequire.NoError(t, store.InsertBlock(block3, userID))\n\n\t\tblock4 := &model.Block{\n\t\t\tID:      utils.NewID(utils.IDTypeBlock),\n\t\t\tBoardID: board2.ID,\n\t\t}\n\t\trequire.NoError(t, store.InsertBlock(block4, userID))\n\n\t\tdbab := &model.DeleteBoardsAndBlocks{\n\t\t\tBoards: []string{board1.ID, board2.ID},\n\t\t\tBlocks: []string{block1.ID, block2.ID, block3.ID, block4.ID, \"a nonexistent block ID\"},\n\t\t}\n\n\t\ttime.Sleep(10 * time.Millisecond)\n\n\t\trequire.True(t, model.IsErrNotFound(store.DeleteBoardsAndBlocks(dbab, userID)))\n\n\t\t// all the entities should still exist\n\t\trBoard1, err := store.GetBoard(board1.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, rBoard1)\n\t\trBlock1, err := store.GetBlock(block1.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, rBlock1)\n\t\trBlock2, err := store.GetBlock(block2.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, rBlock2)\n\n\t\trBoard2, err := store.GetBoard(board2.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, rBoard2)\n\t\trBlock3, err := store.GetBlock(block3.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, rBlock3)\n\t\trBlock4, err := store.GetBlock(block4.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, rBlock4)\n\t})\n\n\tt.Run(\"should work properly if all the entities are related\", func(t *testing.T) {\n\t\tnewBoard1 := &model.Board{\n\t\t\tID:     utils.NewID(utils.IDTypeBoard),\n\t\t\tTeamID: teamID,\n\t\t\tType:   model.BoardTypeOpen,\n\t\t}\n\t\tboard1, err := store.InsertBoard(newBoard1, userID)\n\t\trequire.NoError(t, err)\n\n\t\tblock1 := &model.Block{\n\t\t\tID:      utils.NewID(utils.IDTypeBlock),\n\t\t\tBoardID: board1.ID,\n\t\t}\n\t\trequire.NoError(t, store.InsertBlock(block1, userID))\n\n\t\tblock2 := &model.Block{\n\t\t\tID:      utils.NewID(utils.IDTypeBlock),\n\t\t\tBoardID: board1.ID,\n\t\t}\n\t\trequire.NoError(t, store.InsertBlock(block2, userID))\n\n\t\tnewBoard2 := &model.Board{\n\t\t\tID:     utils.NewID(utils.IDTypeBoard),\n\t\t\tTeamID: teamID,\n\t\t\tType:   model.BoardTypeOpen,\n\t\t}\n\t\tboard2, err := store.InsertBoard(newBoard2, userID)\n\t\trequire.NoError(t, err)\n\n\t\tblock3 := &model.Block{\n\t\t\tID:      utils.NewID(utils.IDTypeBlock),\n\t\t\tBoardID: board2.ID,\n\t\t}\n\t\trequire.NoError(t, store.InsertBlock(block3, userID))\n\n\t\tblock4 := &model.Block{\n\t\t\tID:      utils.NewID(utils.IDTypeBlock),\n\t\t\tBoardID: board2.ID,\n\t\t}\n\t\trequire.NoError(t, store.InsertBlock(block4, userID))\n\n\t\tdbab := &model.DeleteBoardsAndBlocks{\n\t\t\tBoards: []string{board1.ID, board2.ID},\n\t\t\tBlocks: []string{block1.ID, block2.ID, block3.ID, block4.ID},\n\t\t}\n\n\t\ttime.Sleep(10 * time.Millisecond)\n\n\t\trequire.NoError(t, store.DeleteBoardsAndBlocks(dbab, userID))\n\n\t\trBoard1, err := store.GetBoard(board1.ID)\n\t\trequire.Error(t, err)\n\t\trequire.True(t, model.IsErrNotFound(err))\n\t\trequire.Nil(t, rBoard1)\n\t\trBlock1, err := store.GetBlock(block1.ID)\n\t\trequire.Error(t, err)\n\t\trequire.True(t, model.IsErrNotFound(err))\n\t\trequire.Nil(t, rBlock1)\n\t\trBlock2, err := store.GetBlock(block2.ID)\n\t\trequire.Error(t, err)\n\t\trequire.True(t, model.IsErrNotFound(err))\n\t\trequire.Nil(t, rBlock2)\n\n\t\trBoard2, err := store.GetBoard(board2.ID)\n\t\trequire.Error(t, err)\n\t\trequire.True(t, model.IsErrNotFound(err))\n\t\trequire.Nil(t, rBoard2)\n\t\trBlock3, err := store.GetBlock(block3.ID)\n\t\trequire.Error(t, err)\n\t\trequire.True(t, model.IsErrNotFound(err))\n\t\trequire.Nil(t, rBlock3)\n\t\trBlock4, err := store.GetBlock(block4.ID)\n\t\trequire.Error(t, err)\n\t\trequire.True(t, model.IsErrNotFound(err))\n\t\trequire.Nil(t, rBlock4)\n\t})\n}\n\nfunc testDuplicateBoard(t *testing.T, store store.Store) {\n\tteamID := testTeamID\n\tuserID := testUserID\n\n\tnewBab := &model.BoardsAndBlocks{\n\t\tBoards: []*model.Board{\n\t\t\t{ID: \"board-id-1\", TeamID: teamID, Type: model.BoardTypeOpen, ChannelID: \"test-channel\"},\n\t\t\t{ID: \"board-id-2\", TeamID: teamID, Type: model.BoardTypePrivate},\n\t\t\t{ID: \"board-id-3\", TeamID: teamID, Type: model.BoardTypeOpen},\n\t\t},\n\t\tBlocks: []*model.Block{\n\t\t\t{ID: \"block-id-1\", BoardID: \"board-id-1\", Type: model.TypeCard},\n\t\t\t{ID: \"block-id-1a\", BoardID: \"board-id-1\", Type: model.TypeComment},\n\t\t\t{ID: \"block-id-2\", BoardID: \"board-id-2\", Type: model.TypeCard},\n\t\t},\n\t}\n\n\tbab, err := store.CreateBoardsAndBlocks(newBab, userID)\n\trequire.Nil(t, err)\n\trequire.NotNil(t, bab)\n\trequire.Len(t, bab.Boards, 3)\n\trequire.Len(t, bab.Blocks, 3)\n\n\tt.Run(\"duplicate existing board as no template\", func(t *testing.T) {\n\t\tbab, members, err := store.DuplicateBoard(\"board-id-1\", userID, teamID, false)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, members, 1)\n\t\trequire.Len(t, bab.Boards, 1)\n\t\trequire.Len(t, bab.Blocks, 1)\n\t\trequire.Equal(t, bab.Boards[0].IsTemplate, false)\n\t\trequire.Equal(t, \"\", bab.Boards[0].ChannelID)\n\t})\n\n\tt.Run(\"duplicate existing board as template\", func(t *testing.T) {\n\t\tbab, members, err := store.DuplicateBoard(\"board-id-1\", userID, teamID, true)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, members, 1)\n\t\trequire.Len(t, bab.Boards, 1)\n\t\trequire.Len(t, bab.Blocks, 1)\n\t\trequire.Equal(t, bab.Boards[0].IsTemplate, true)\n\t\trequire.Equal(t, \"\", bab.Boards[0].ChannelID)\n\t})\n\n\tt.Run(\"duplicate not existing board\", func(t *testing.T) {\n\t\tbab, members, err := store.DuplicateBoard(\"not-existing-id\", userID, teamID, false)\n\t\trequire.Error(t, err)\n\t\trequire.Nil(t, members)\n\t\trequire.Nil(t, bab)\n\t})\n}\n"
  },
  {
    "path": "server/services/store/storetests/category.go",
    "content": "package storetests\n\nimport (\n\t\"testing\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/store\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\ntype testFunc func(t *testing.T, store store.Store)\n\nfunc StoreTestCategoryStore(t *testing.T, setup func(t *testing.T) (store.Store, func())) {\n\ttests := map[string]testFunc{\n\t\t\"CreateCategory\":          testGetCreateCategory,\n\t\t\"UpdateCategory\":          testUpdateCategory,\n\t\t\"DeleteCategory\":          testDeleteCategory,\n\t\t\"GetUserCategories\":       testGetUserCategories,\n\t\t\"ReorderCategories\":       testReorderCategories,\n\t\t\"ReorderCategoriesBoards\": testReorderCategoryBoards,\n\t}\n\n\tfor name, f := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tstore, tearDown := setup(t)\n\t\t\tdefer tearDown()\n\t\t\tf(t, store)\n\t\t})\n\t}\n}\n\nfunc testGetCreateCategory(t *testing.T, store store.Store) {\n\tt.Run(\"save uncollapsed category\", func(t *testing.T) {\n\t\tnow := utils.GetMillis()\n\t\tcategory := model.Category{\n\t\t\tID:        \"category_id_1\",\n\t\t\tName:      \"Category\",\n\t\t\tUserID:    \"user_id_1\",\n\t\t\tTeamID:    \"team_id_1\",\n\t\t\tCreateAt:  now,\n\t\t\tUpdateAt:  now,\n\t\t\tDeleteAt:  0,\n\t\t\tCollapsed: false,\n\t\t}\n\n\t\terr := store.CreateCategory(category)\n\t\tassert.NoError(t, err)\n\n\t\tcreatedCategory, err := store.GetCategory(\"category_id_1\")\n\t\tassert.NoError(t, err)\n\t\tassert.Equal(t, \"Category\", createdCategory.Name)\n\t\tassert.Equal(t, \"user_id_1\", createdCategory.UserID)\n\t\tassert.Equal(t, \"team_id_1\", createdCategory.TeamID)\n\t\tassert.Equal(t, false, createdCategory.Collapsed)\n\t})\n\n\tt.Run(\"save collapsed category\", func(t *testing.T) {\n\t\tnow := utils.GetMillis()\n\t\tcategory := model.Category{\n\t\t\tID:        \"category_id_2\",\n\t\t\tName:      \"Category\",\n\t\t\tUserID:    \"user_id_1\",\n\t\t\tTeamID:    \"team_id_1\",\n\t\t\tCreateAt:  now,\n\t\t\tUpdateAt:  now,\n\t\t\tDeleteAt:  0,\n\t\t\tCollapsed: true,\n\t\t}\n\n\t\terr := store.CreateCategory(category)\n\t\tassert.NoError(t, err)\n\n\t\tcreatedCategory, err := store.GetCategory(\"category_id_2\")\n\t\tassert.NoError(t, err)\n\t\tassert.Equal(t, \"Category\", createdCategory.Name)\n\t\tassert.Equal(t, \"user_id_1\", createdCategory.UserID)\n\t\tassert.Equal(t, \"team_id_1\", createdCategory.TeamID)\n\t\tassert.Equal(t, true, createdCategory.Collapsed)\n\t})\n\n\tt.Run(\"get nonexistent category\", func(t *testing.T) {\n\t\tcategory, err := store.GetCategory(\"nonexistent\")\n\t\tassert.Error(t, err)\n\t\tvar nf *model.ErrNotFound\n\t\tassert.ErrorAs(t, err, &nf)\n\t\tassert.Nil(t, category)\n\t})\n}\n\nfunc testUpdateCategory(t *testing.T, store store.Store) {\n\tnow := utils.GetMillis()\n\tcategory := model.Category{\n\t\tID:        \"category_id_1\",\n\t\tName:      \"Category 1\",\n\t\tUserID:    \"user_id_1\",\n\t\tTeamID:    \"team_id_1\",\n\t\tCreateAt:  now,\n\t\tUpdateAt:  now,\n\t\tDeleteAt:  0,\n\t\tCollapsed: false,\n\t}\n\n\terr := store.CreateCategory(category)\n\tassert.NoError(t, err)\n\n\tupdateNow := utils.GetMillis()\n\tupdatedCategory := model.Category{\n\t\tID:        \"category_id_1\",\n\t\tName:      \"Category 1 New\",\n\t\tUserID:    \"user_id_1\",\n\t\tTeamID:    \"team_id_1\",\n\t\tCreateAt:  now,\n\t\tUpdateAt:  updateNow,\n\t\tDeleteAt:  0,\n\t\tCollapsed: true,\n\t}\n\n\terr = store.UpdateCategory(updatedCategory)\n\tassert.NoError(t, err)\n\n\tfetchedCategory, err := store.GetCategory(\"category_id_1\")\n\tassert.NoError(t, err)\n\tassert.Equal(t, \"category_id_1\", fetchedCategory.ID)\n\tassert.Equal(t, \"Category 1 New\", fetchedCategory.Name)\n\tassert.Equal(t, true, fetchedCategory.Collapsed)\n\n\t// now lets try to un-collapse the same category\n\tupdatedCategory.Collapsed = false\n\terr = store.UpdateCategory(updatedCategory)\n\tassert.NoError(t, err)\n\n\tfetchedCategory, err = store.GetCategory(\"category_id_1\")\n\tassert.NoError(t, err)\n\tassert.Equal(t, \"category_id_1\", fetchedCategory.ID)\n\tassert.Equal(t, \"Category 1 New\", fetchedCategory.Name)\n\tassert.Equal(t, false, fetchedCategory.Collapsed)\n}\n\nfunc testDeleteCategory(t *testing.T, store store.Store) {\n\tnow := utils.GetMillis()\n\tcategory := model.Category{\n\t\tID:        \"category_id_1\",\n\t\tName:      \"Category 1\",\n\t\tUserID:    \"user_id_1\",\n\t\tTeamID:    \"team_id_1\",\n\t\tCreateAt:  now,\n\t\tUpdateAt:  now,\n\t\tDeleteAt:  0,\n\t\tCollapsed: false,\n\t}\n\n\terr := store.CreateCategory(category)\n\tassert.NoError(t, err)\n\n\terr = store.DeleteCategory(\"category_id_1\", \"user_id_1\", \"team_id_1\")\n\tassert.NoError(t, err)\n\n\tdeletedCategory, err := store.GetCategory(\"category_id_1\")\n\tassert.NoError(t, err)\n\tassert.Equal(t, \"category_id_1\", deletedCategory.ID)\n\tassert.Equal(t, \"Category 1\", deletedCategory.Name)\n\tassert.Equal(t, false, deletedCategory.Collapsed)\n\tassert.Greater(t, deletedCategory.DeleteAt, int64(0))\n}\n\nfunc testGetUserCategories(t *testing.T, store store.Store) {\n\tnow := utils.GetMillis()\n\tcategory1 := model.Category{\n\t\tID:        \"category_id_1\",\n\t\tName:      \"Category 1\",\n\t\tUserID:    \"user_id_1\",\n\t\tTeamID:    \"team_id_1\",\n\t\tCreateAt:  now,\n\t\tUpdateAt:  now,\n\t\tDeleteAt:  0,\n\t\tCollapsed: false,\n\t}\n\terr := store.CreateCategory(category1)\n\tassert.NoError(t, err)\n\n\tcategory2 := model.Category{\n\t\tID:        \"category_id_2\",\n\t\tName:      \"Category 2\",\n\t\tUserID:    \"user_id_1\",\n\t\tTeamID:    \"team_id_1\",\n\t\tCreateAt:  now,\n\t\tUpdateAt:  now,\n\t\tDeleteAt:  0,\n\t\tCollapsed: false,\n\t}\n\terr = store.CreateCategory(category2)\n\tassert.NoError(t, err)\n\n\tcategory3 := model.Category{\n\t\tID:        \"category_id_3\",\n\t\tName:      \"Category 2\",\n\t\tUserID:    \"user_id_1\",\n\t\tTeamID:    \"team_id_1\",\n\t\tCreateAt:  now,\n\t\tUpdateAt:  now,\n\t\tDeleteAt:  0,\n\t\tCollapsed: false,\n\t}\n\terr = store.CreateCategory(category3)\n\tassert.NoError(t, err)\n\n\tuserCategories, err := store.GetUserCategoryBoards(\"user_id_1\", \"team_id_1\")\n\tassert.NoError(t, err)\n\tassert.Equal(t, 3, len(userCategories))\n}\n\nfunc testReorderCategories(t *testing.T, store store.Store) {\n\t// setup\n\terr := store.CreateCategory(model.Category{\n\t\tID:     \"category_id_1\",\n\t\tName:   \"Category 1\",\n\t\tType:   \"custom\",\n\t\tUserID: \"user_id\",\n\t\tTeamID: \"team_id\",\n\t})\n\tassert.NoError(t, err)\n\n\terr = store.CreateCategory(model.Category{\n\t\tID:     \"category_id_2\",\n\t\tName:   \"Category 2\",\n\t\tType:   \"custom\",\n\t\tUserID: \"user_id\",\n\t\tTeamID: \"team_id\",\n\t})\n\tassert.NoError(t, err)\n\n\terr = store.CreateCategory(model.Category{\n\t\tID:     \"category_id_3\",\n\t\tName:   \"Category 3\",\n\t\tType:   \"custom\",\n\t\tUserID: \"user_id\",\n\t\tTeamID: \"team_id\",\n\t})\n\tassert.NoError(t, err)\n\n\t// verify the current order\n\tcategories, err := store.GetUserCategories(\"user_id\", \"team_id\")\n\tassert.NoError(t, err)\n\tassert.Equal(t, 3, len(categories))\n\n\t// the categories should show up in reverse insertion order (latest one first)\n\tassert.Equal(t, \"category_id_3\", categories[0].ID)\n\tassert.Equal(t, \"category_id_2\", categories[1].ID)\n\tassert.Equal(t, \"category_id_1\", categories[2].ID)\n\n\t// re-ordering categories normally\n\t_, err = store.ReorderCategories(\"user_id\", \"team_id\", []string{\n\t\t\"category_id_2\",\n\t\t\"category_id_3\",\n\t\t\"category_id_1\",\n\t})\n\tassert.NoError(t, err)\n\n\t// verify the board order\n\tcategories, err = store.GetUserCategories(\"user_id\", \"team_id\")\n\tassert.NoError(t, err)\n\tassert.Equal(t, 3, len(categories))\n\tassert.Equal(t, \"category_id_2\", categories[0].ID)\n\tassert.Equal(t, \"category_id_3\", categories[1].ID)\n\tassert.Equal(t, \"category_id_1\", categories[2].ID)\n\n\t// lets try specifying a non existing category ID.\n\t// It shouldn't cause any problem\n\t_, err = store.ReorderCategories(\"user_id\", \"team_id\", []string{\n\t\t\"category_id_1\",\n\t\t\"category_id_2\",\n\t\t\"category_id_3\",\n\t\t\"non-existing-category-id\",\n\t})\n\tassert.NoError(t, err)\n\n\tcategories, err = store.GetUserCategories(\"user_id\", \"team_id\")\n\tassert.NoError(t, err)\n\tassert.Equal(t, 3, len(categories))\n\tassert.Equal(t, \"category_id_1\", categories[0].ID)\n\tassert.Equal(t, \"category_id_2\", categories[1].ID)\n\tassert.Equal(t, \"category_id_3\", categories[2].ID)\n}\n\nfunc testReorderCategoryBoards(t *testing.T, store store.Store) {\n\t// setup\n\terr := store.CreateCategory(model.Category{\n\t\tID:     \"category_id_1\",\n\t\tName:   \"Category 1\",\n\t\tType:   \"custom\",\n\t\tUserID: \"user_id\",\n\t\tTeamID: \"team_id\",\n\t})\n\tassert.NoError(t, err)\n\n\terr = store.AddUpdateCategoryBoard(\"user_id\", \"category_id_1\", []string{\n\t\t\"board_id_1\",\n\t\t\"board_id_2\",\n\t\t\"board_id_3\",\n\t\t\"board_id_4\",\n\t})\n\tassert.NoError(t, err)\n\n\t// verify current order\n\tcategoryBoards, err := store.GetUserCategoryBoards(\"user_id\", \"team_id\")\n\tassert.NoError(t, err)\n\tassert.Equal(t, 1, len(categoryBoards))\n\tassert.Equal(t, 4, len(categoryBoards[0].BoardMetadata))\n\tassert.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: \"board_id_1\", Hidden: false})\n\tassert.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: \"board_id_2\", Hidden: false})\n\tassert.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: \"board_id_3\", Hidden: false})\n\tassert.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: \"board_id_4\", Hidden: false})\n\n\t// reordering\n\tnewOrder, err := store.ReorderCategoryBoards(\"category_id_1\", []string{\n\t\t\"board_id_3\",\n\t\t\"board_id_1\",\n\t\t\"board_id_2\",\n\t\t\"board_id_4\",\n\t})\n\tassert.NoError(t, err)\n\tassert.Equal(t, \"board_id_3\", newOrder[0])\n\tassert.Equal(t, \"board_id_1\", newOrder[1])\n\tassert.Equal(t, \"board_id_2\", newOrder[2])\n\tassert.Equal(t, \"board_id_4\", newOrder[3])\n\n\t// verify new order\n\tcategoryBoards, err = store.GetUserCategoryBoards(\"user_id\", \"team_id\")\n\tassert.NoError(t, err)\n\tassert.Equal(t, 1, len(categoryBoards))\n\tassert.Equal(t, 4, len(categoryBoards[0].BoardMetadata))\n\tassert.Equal(t, \"board_id_3\", categoryBoards[0].BoardMetadata[0].BoardID)\n\tassert.Equal(t, \"board_id_1\", categoryBoards[0].BoardMetadata[1].BoardID)\n\tassert.Equal(t, \"board_id_2\", categoryBoards[0].BoardMetadata[2].BoardID)\n\tassert.Equal(t, \"board_id_4\", categoryBoards[0].BoardMetadata[3].BoardID)\n}\n"
  },
  {
    "path": "server/services/store/storetests/categoryBoards.go",
    "content": "package storetests\n\nimport (\n\t\"testing\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/store\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc StoreTestCategoryBoardsStore(t *testing.T, setup func(t *testing.T) (store.Store, func())) {\n\tt.Run(\"GetUserCategoryBoards\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestGetUserCategoryBoards(t, store)\n\t})\n\n\tt.Run(\"AddUpdateCategoryBoard\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestAddUpdateCategoryBoard(t, store)\n\t})\n\n\tt.Run(\"SetBoardVisibility\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestSetBoardVisibility(t, store)\n\t})\n}\n\nfunc testGetUserCategoryBoards(t *testing.T, store store.Store) {\n\tnow := utils.GetMillis()\n\tcategory1 := model.Category{\n\t\tID:        \"category_id_1\",\n\t\tName:      \"Category 1\",\n\t\tUserID:    \"user_id_1\",\n\t\tTeamID:    \"team_id_1\",\n\t\tCreateAt:  now,\n\t\tUpdateAt:  now,\n\t\tDeleteAt:  0,\n\t\tCollapsed: false,\n\t}\n\terr := store.CreateCategory(category1)\n\tassert.NoError(t, err)\n\n\tcategory2 := model.Category{\n\t\tID:        \"category_id_2\",\n\t\tName:      \"Category 2\",\n\t\tUserID:    \"user_id_1\",\n\t\tTeamID:    \"team_id_1\",\n\t\tCreateAt:  now,\n\t\tUpdateAt:  now,\n\t\tDeleteAt:  0,\n\t\tCollapsed: false,\n\t}\n\terr = store.CreateCategory(category2)\n\tassert.NoError(t, err)\n\n\tcategory3 := model.Category{\n\t\tID:        \"category_id_3\",\n\t\tName:      \"Category 3\",\n\t\tUserID:    \"user_id_1\",\n\t\tTeamID:    \"team_id_1\",\n\t\tCreateAt:  now,\n\t\tUpdateAt:  now,\n\t\tDeleteAt:  0,\n\t\tCollapsed: false,\n\t}\n\terr = store.CreateCategory(category3)\n\tassert.NoError(t, err)\n\n\t// Adding Board 1 and Board 2 to Category 1\n\t// The boards don't need to exists in DB for this test\n\terr = store.AddUpdateCategoryBoard(\"user_id_1\", \"category_id_1\", []string{\"board_1\"})\n\tassert.NoError(t, err)\n\n\terr = store.AddUpdateCategoryBoard(\"user_id_1\", \"category_id_1\", []string{\"board_2\"})\n\tassert.NoError(t, err)\n\n\t// Adding Board 3 to Category 2\n\terr = store.AddUpdateCategoryBoard(\"user_id_1\", \"category_id_2\", []string{\"board_3\"})\n\tassert.NoError(t, err)\n\n\t// we'll leave category 3 empty\n\n\tuserCategoryBoards, err := store.GetUserCategoryBoards(\"user_id_1\", \"team_id_1\")\n\tassert.NoError(t, err)\n\n\t// we created 3 categories for the user\n\tassert.Equal(t, 3, len(userCategoryBoards))\n\n\tvar category1BoardCategory model.CategoryBoards\n\tvar category2BoardCategory model.CategoryBoards\n\tvar category3BoardCategory model.CategoryBoards\n\n\tfor i := range userCategoryBoards {\n\t\tswitch userCategoryBoards[i].ID {\n\t\tcase \"category_id_1\":\n\t\t\tcategory1BoardCategory = userCategoryBoards[i]\n\t\tcase \"category_id_2\":\n\t\t\tcategory2BoardCategory = userCategoryBoards[i]\n\t\tcase \"category_id_3\":\n\t\t\tcategory3BoardCategory = userCategoryBoards[i]\n\t\t}\n\t}\n\n\tassert.NotEmpty(t, category1BoardCategory)\n\tassert.Equal(t, 2, len(category1BoardCategory.BoardMetadata))\n\n\tassert.NotEmpty(t, category1BoardCategory)\n\tassert.Equal(t, 1, len(category2BoardCategory.BoardMetadata))\n\n\tassert.NotEmpty(t, category1BoardCategory)\n\tassert.Equal(t, 0, len(category3BoardCategory.BoardMetadata))\n\n\tt.Run(\"get empty category boards\", func(t *testing.T) {\n\t\tuserCategoryBoards, err := store.GetUserCategoryBoards(\"nonexistent-user-id\", \"nonexistent-team-id\")\n\t\tassert.NoError(t, err)\n\t\tassert.Empty(t, userCategoryBoards)\n\t})\n}\n\nfunc testAddUpdateCategoryBoard(t *testing.T, store store.Store) {\n\t// creating few boards and categories to later associoate with the category\n\t_, _, err := store.CreateBoardsAndBlocksWithAdmin(&model.BoardsAndBlocks{\n\t\tBoards: []*model.Board{\n\t\t\t{\n\t\t\t\tID:     \"board_id_1\",\n\t\t\t\tTeamID: \"team_id\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tID:     \"board_id_2\",\n\t\t\t\tTeamID: \"team_id\",\n\t\t\t},\n\t\t},\n\t}, \"user_id\")\n\tassert.NoError(t, err)\n\n\terr = store.CreateCategory(model.Category{\n\t\tID:     \"category_id\",\n\t\tName:   \"Category\",\n\t\tUserID: \"user_id\",\n\t\tTeamID: \"team_id\",\n\t})\n\tassert.NoError(t, err)\n\n\t// adding a few boards to the category\n\terr = store.AddUpdateCategoryBoard(\"user_id\", \"category_id\", []string{\"board_id_1\", \"board_id_2\"})\n\tassert.NoError(t, err)\n\n\t// verify inserted data\n\tcategoryBoards, err := store.GetUserCategoryBoards(\"user_id\", \"team_id\")\n\tassert.NoError(t, err)\n\tassert.Equal(t, 1, len(categoryBoards))\n\tassert.Equal(t, \"category_id\", categoryBoards[0].ID)\n\tassert.Equal(t, 2, len(categoryBoards[0].BoardMetadata))\n\tassert.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: \"board_id_1\", Hidden: false})\n\tassert.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: \"board_id_2\", Hidden: false})\n\n\t// adding new boards to the same category\n\terr = store.AddUpdateCategoryBoard(\"user_id\", \"category_id\", []string{\"board_id_3\"})\n\tassert.NoError(t, err)\n\n\t// verify inserted data\n\tcategoryBoards, err = store.GetUserCategoryBoards(\"user_id\", \"team_id\")\n\tassert.NoError(t, err)\n\tassert.Equal(t, 1, len(categoryBoards))\n\tassert.Equal(t, \"category_id\", categoryBoards[0].ID)\n\tassert.Equal(t, 3, len(categoryBoards[0].BoardMetadata))\n\tassert.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: \"board_id_3\", Hidden: false})\n\n\t// passing empty array\n\terr = store.AddUpdateCategoryBoard(\"user_id\", \"category_id\", []string{})\n\tassert.NoError(t, err)\n\n\t// verify inserted data\n\tcategoryBoards, err = store.GetUserCategoryBoards(\"user_id\", \"team_id\")\n\tassert.NoError(t, err)\n\tassert.Equal(t, 1, len(categoryBoards))\n\tassert.Equal(t, \"category_id\", categoryBoards[0].ID)\n\tassert.Equal(t, 3, len(categoryBoards[0].BoardMetadata))\n\n\t// passing duplicate data in input\n\terr = store.AddUpdateCategoryBoard(\"user_id\", \"category_id\", []string{\"board_id_4\", \"board_id_4\"})\n\tassert.NoError(t, err)\n\n\t// verify inserted data\n\tcategoryBoards, err = store.GetUserCategoryBoards(\"user_id\", \"team_id\")\n\tassert.NoError(t, err)\n\tassert.Equal(t, 1, len(categoryBoards))\n\tassert.Equal(t, \"category_id\", categoryBoards[0].ID)\n\tassert.Equal(t, 4, len(categoryBoards[0].BoardMetadata))\n\tassert.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: \"board_id_4\", Hidden: false})\n\n\t// adding already added board\n\terr = store.AddUpdateCategoryBoard(\"user_id\", \"category_id\", []string{\"board_id_1\", \"board_id_2\"})\n\tassert.NoError(t, err)\n\n\t// verify inserted data\n\tcategoryBoards, err = store.GetUserCategoryBoards(\"user_id\", \"team_id\")\n\tassert.NoError(t, err)\n\tassert.Equal(t, 1, len(categoryBoards))\n\tassert.Equal(t, \"category_id\", categoryBoards[0].ID)\n\tassert.Equal(t, 4, len(categoryBoards[0].BoardMetadata))\n\n\t// passing already added board along with a new board\n\terr = store.AddUpdateCategoryBoard(\"user_id\", \"category_id\", []string{\"board_id_1\", \"board_id_5\"})\n\tassert.NoError(t, err)\n\n\t// verify inserted data\n\tcategoryBoards, err = store.GetUserCategoryBoards(\"user_id\", \"team_id\")\n\tassert.NoError(t, err)\n\tassert.Equal(t, 1, len(categoryBoards))\n\tassert.Equal(t, \"category_id\", categoryBoards[0].ID)\n\tassert.Equal(t, 5, len(categoryBoards[0].BoardMetadata))\n\tassert.Contains(t, categoryBoards[0].BoardMetadata, model.CategoryBoardMetadata{BoardID: \"board_id_5\", Hidden: false})\n}\n\nfunc testSetBoardVisibility(t *testing.T, store store.Store) {\n\t_, _, err := store.CreateBoardsAndBlocksWithAdmin(&model.BoardsAndBlocks{\n\t\tBoards: []*model.Board{\n\t\t\t{\n\t\t\t\tID:     \"board_id_1\",\n\t\t\t\tTeamID: \"team_id\",\n\t\t\t},\n\t\t},\n\t}, \"user_id\")\n\tassert.NoError(t, err)\n\n\terr = store.CreateCategory(model.Category{\n\t\tID:     \"category_id\",\n\t\tName:   \"Category\",\n\t\tUserID: \"user_id\",\n\t\tTeamID: \"team_id\",\n\t})\n\tassert.NoError(t, err)\n\n\t// adding a few boards to the category\n\terr = store.AddUpdateCategoryBoard(\"user_id\", \"category_id\", []string{\"board_id_1\"})\n\tassert.NoError(t, err)\n\n\terr = store.SetBoardVisibility(\"user_id\", \"category_id\", \"board_id_1\", true)\n\tassert.NoError(t, err)\n\n\t// verify set visibility\n\tcategoryBoards, err := store.GetUserCategoryBoards(\"user_id\", \"team_id\")\n\tassert.NoError(t, err)\n\tassert.Equal(t, 1, len(categoryBoards))\n\tassert.Equal(t, \"category_id\", categoryBoards[0].ID)\n\tassert.Equal(t, 1, len(categoryBoards[0].BoardMetadata))\n\tassert.False(t, categoryBoards[0].BoardMetadata[0].Hidden)\n\n\terr = store.SetBoardVisibility(\"user_id\", \"category_id\", \"board_id_1\", false)\n\tassert.NoError(t, err)\n\n\t// verify set visibility\n\tcategoryBoards, err = store.GetUserCategoryBoards(\"user_id\", \"team_id\")\n\tassert.NoError(t, err)\n\tassert.True(t, categoryBoards[0].BoardMetadata[0].Hidden)\n}\n"
  },
  {
    "path": "server/services/store/storetests/cloud.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage storetests\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\tstoreservice \"github.com/mattermost/focalboard/server/services/store\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n)\n\nfunc StoreTestCloudStore(t *testing.T, setup func(t *testing.T) (storeservice.Store, func())) {\n\tt.Run(\"GetUsedCardsCount\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestGetUsedCardsCount(t, store)\n\t})\n\tt.Run(\"TestGetCardLimitTimestamp\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestGetCardLimitTimestamp(t, store)\n\t})\n\tt.Run(\"TestUpdateCardLimitTimestamp\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestUpdateCardLimitTimestamp(t, store)\n\t})\n}\n\nfunc testGetUsedCardsCount(t *testing.T, store storeservice.Store) {\n\tuserID := \"user-id\"\n\n\tt.Run(\"should return zero when no cards have been created\", func(t *testing.T) {\n\t\tcount, err := store.GetUsedCardsCount()\n\t\trequire.NoError(t, err)\n\t\trequire.Zero(t, count)\n\t})\n\n\tt.Run(\"should correctly return the cards of all boards\", func(t *testing.T) {\n\t\t// two boards\n\t\tfor _, boardID := range []string{\"board1\", \"board2\"} {\n\t\t\tboardType := model.BoardTypeOpen\n\t\t\tif boardID == \"board2\" {\n\t\t\t\tboardType = model.BoardTypePrivate\n\t\t\t}\n\n\t\t\tboard := &model.Board{\n\t\t\t\tID:     boardID,\n\t\t\t\tTeamID: testTeamID,\n\t\t\t\tType:   boardType,\n\t\t\t}\n\n\t\t\t_, err := store.InsertBoard(board, userID)\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\t// board 1 has three cards\n\t\tfor _, cardID := range []string{\"card1\", \"card2\", \"card3\"} {\n\t\t\tcard := &model.Block{\n\t\t\t\tID:       cardID,\n\t\t\t\tParentID: \"board1\",\n\t\t\t\tBoardID:  \"board1\",\n\t\t\t\tType:     model.TypeCard,\n\t\t\t}\n\t\t\trequire.NoError(t, store.InsertBlock(card, userID))\n\t\t}\n\n\t\t// board 2 has two cards\n\t\tfor _, cardID := range []string{\"card4\", \"card5\"} {\n\t\t\tcard := &model.Block{\n\t\t\t\tID:       cardID,\n\t\t\t\tParentID: \"board2\",\n\t\t\t\tBoardID:  \"board2\",\n\t\t\t\tType:     model.TypeCard,\n\t\t\t}\n\t\t\trequire.NoError(t, store.InsertBlock(card, userID))\n\t\t}\n\n\t\tcount, err := store.GetUsedCardsCount()\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 5, count)\n\t})\n\n\tt.Run(\"should not take into account content blocks\", func(t *testing.T) {\n\t\t// we add a couple of content blocks\n\t\ttext := &model.Block{\n\t\t\tID:       \"text-id\",\n\t\t\tParentID: \"card1\",\n\t\t\tBoardID:  \"board1\",\n\t\t\tType:     model.TypeText,\n\t\t}\n\t\trequire.NoError(t, store.InsertBlock(text, userID))\n\n\t\tview := &model.Block{\n\t\t\tID:       \"view-id\",\n\t\t\tParentID: \"board1\",\n\t\t\tBoardID:  \"board1\",\n\t\t\tType:     model.TypeView,\n\t\t}\n\t\trequire.NoError(t, store.InsertBlock(view, userID))\n\n\t\t// and count should not change\n\t\tcount, err := store.GetUsedCardsCount()\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 5, count)\n\t})\n\n\tt.Run(\"should not take into account cards belonging to templates\", func(t *testing.T) {\n\t\t// we add a template with cards\n\t\ttemplateID := \"template-id\"\n\t\tboardTemplate := &model.Block{\n\t\t\tID:      templateID,\n\t\t\tBoardID: templateID,\n\t\t\tType:    model.TypeBoard,\n\t\t\tFields: map[string]interface{}{\n\t\t\t\t\"isTemplate\": true,\n\t\t\t},\n\t\t}\n\t\trequire.NoError(t, store.InsertBlock(boardTemplate, userID))\n\n\t\tfor _, cardID := range []string{\"card6\", \"card7\", \"card8\"} {\n\t\t\tcard := &model.Block{\n\t\t\t\tID:       cardID,\n\t\t\t\tParentID: templateID,\n\t\t\t\tBoardID:  templateID,\n\t\t\t\tType:     model.TypeCard,\n\t\t\t}\n\t\t\trequire.NoError(t, store.InsertBlock(card, userID))\n\t\t}\n\n\t\t// and count should still be the same\n\t\tcount, err := store.GetUsedCardsCount()\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 5, count)\n\t})\n\n\tt.Run(\"should not take into account deleted cards\", func(t *testing.T) {\n\t\t// we create a ninth card on the first board\n\t\tcard9 := &model.Block{\n\t\t\tID:       \"card9\",\n\t\t\tParentID: \"board1\",\n\t\t\tBoardID:  \"board1\",\n\t\t\tType:     model.TypeCard,\n\t\t\tDeleteAt: utils.GetMillis(),\n\t\t}\n\t\trequire.NoError(t, store.InsertBlock(card9, userID))\n\n\t\t// and count should still be the same\n\t\tcount, err := store.GetUsedCardsCount()\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 5, count)\n\t})\n\n\tt.Run(\"should not take into account cards from deleted boards\", func(t *testing.T) {\n\t\trequire.NoError(t, store.DeleteBoard(\"board2\", \"user-id\"))\n\n\t\tcount, err := store.GetUsedCardsCount()\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 3, count)\n\t})\n}\n\nfunc testGetCardLimitTimestamp(t *testing.T, store storeservice.Store) {\n\tt.Run(\"should return 0 if there is no entry in the database\", func(t *testing.T) {\n\t\trawValue, err := store.GetSystemSetting(storeservice.CardLimitTimestampSystemKey)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"\", rawValue)\n\n\t\tcardLimitTimestamp, err := store.GetCardLimitTimestamp()\n\t\trequire.NoError(t, err)\n\t\trequire.Zero(t, cardLimitTimestamp)\n\t})\n\n\tt.Run(\"should return an int64 representation of the value\", func(t *testing.T) {\n\t\trequire.NoError(t, store.SetSystemSetting(storeservice.CardLimitTimestampSystemKey, \"1234\"))\n\n\t\tcardLimitTimestamp, err := store.GetCardLimitTimestamp()\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, int64(1234), cardLimitTimestamp)\n\t})\n\n\tt.Run(\"should return an invalid value error if the value is not a number\", func(t *testing.T) {\n\t\trequire.NoError(t, store.SetSystemSetting(storeservice.CardLimitTimestampSystemKey, \"abc\"))\n\n\t\tcardLimitTimestamp, err := store.GetCardLimitTimestamp()\n\t\trequire.ErrorContains(t, err, \"card limit value is invalid\")\n\t\trequire.Zero(t, cardLimitTimestamp)\n\t})\n}\n\nfunc testUpdateCardLimitTimestamp(t *testing.T, store storeservice.Store) {\n\tuserID := \"user-id\"\n\n\t// two boards\n\tfor _, boardID := range []string{\"board1\", \"board2\"} {\n\t\tboardType := model.BoardTypeOpen\n\t\tif boardID == \"board2\" {\n\t\t\tboardType = model.BoardTypePrivate\n\t\t}\n\n\t\tboard := &model.Board{\n\t\t\tID:     boardID,\n\t\t\tTeamID: testTeamID,\n\t\t\tType:   boardType,\n\t\t}\n\n\t\t_, err := store.InsertBoard(board, userID)\n\t\trequire.NoError(t, err)\n\t}\n\n\t// board 1 has five cards\n\tfor _, cardID := range []string{\"card1\", \"card2\", \"card3\", \"card4\", \"card5\"} {\n\t\tcard := &model.Block{\n\t\t\tID:       cardID,\n\t\t\tParentID: \"board1\",\n\t\t\tBoardID:  \"board1\",\n\t\t\tType:     model.TypeCard,\n\t\t}\n\t\trequire.NoError(t, store.InsertBlock(card, userID))\n\t\ttime.Sleep(10 * time.Millisecond)\n\t}\n\n\t// board 2 has five cards\n\tfor _, cardID := range []string{\"card6\", \"card7\", \"card8\", \"card9\", \"card10\"} {\n\t\tcard := &model.Block{\n\t\t\tID:       cardID,\n\t\t\tParentID: \"board2\",\n\t\t\tBoardID:  \"board2\",\n\t\t\tType:     model.TypeCard,\n\t\t}\n\t\trequire.NoError(t, store.InsertBlock(card, userID))\n\t\ttime.Sleep(10 * time.Millisecond)\n\t}\n\n\tt.Run(\"should set the timestamp to zero if the card limit is zero\", func(t *testing.T) {\n\t\tcardLimitTimestamp, err := store.UpdateCardLimitTimestamp(0)\n\t\trequire.NoError(t, err)\n\t\trequire.Zero(t, cardLimitTimestamp)\n\n\t\tcardLimitTimestampStr, err := store.GetSystemSetting(storeservice.CardLimitTimestampSystemKey)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"0\", cardLimitTimestampStr)\n\t})\n\n\tt.Run(\"should correctly modify the limit several times in a row\", func(t *testing.T) {\n\t\tcardLimitTimestamp, err := store.UpdateCardLimitTimestamp(0)\n\t\trequire.NoError(t, err)\n\t\trequire.Zero(t, cardLimitTimestamp)\n\n\t\tcardLimitTimestamp, err = store.UpdateCardLimitTimestamp(10)\n\t\trequire.NoError(t, err)\n\t\trequire.NotZero(t, cardLimitTimestamp)\n\n\t\tcardLimitTimestampStr, err := store.GetSystemSetting(storeservice.CardLimitTimestampSystemKey)\n\t\trequire.NoError(t, err)\n\t\trequire.NotEqual(t, \"0\", cardLimitTimestampStr)\n\n\t\tcardLimitTimestamp, err = store.UpdateCardLimitTimestamp(0)\n\t\trequire.NoError(t, err)\n\t\trequire.Zero(t, cardLimitTimestamp)\n\n\t\tcardLimitTimestampStr, err = store.GetSystemSetting(storeservice.CardLimitTimestampSystemKey)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"0\", cardLimitTimestampStr)\n\t})\n\n\tt.Run(\"should set the correct timestamp\", func(t *testing.T) {\n\t\tt.Run(\"limit 10\", func(t *testing.T) {\n\t\t\t// we fetch the first block\n\t\t\tcard1, err := store.GetBlock(\"card1\")\n\t\t\trequire.NoError(t, err)\n\n\t\t\t// and assert that if the limit is 10, the stored\n\t\t\t// timestamp corresponds to the card's update_at\n\t\t\tcardLimitTimestamp, err := store.UpdateCardLimitTimestamp(10)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, card1.UpdateAt, cardLimitTimestamp)\n\t\t})\n\n\t\tt.Run(\"limit 5\", func(t *testing.T) {\n\t\t\t// if the limit is 5, the timestamp should be the one from\n\t\t\t// the sixth card (the first five are older and out of the\n\t\t\tcard6, err := store.GetBlock(\"card6\")\n\t\t\trequire.NoError(t, err)\n\n\t\t\tcardLimitTimestamp, err := store.UpdateCardLimitTimestamp(5)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, card6.UpdateAt, cardLimitTimestamp)\n\t\t})\n\n\t\tt.Run(\"limit should be zero if we have less cards than the limit\", func(t *testing.T) {\n\t\t\tcardLimitTimestamp, err := store.UpdateCardLimitTimestamp(100)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Zero(t, cardLimitTimestamp)\n\t\t})\n\n\t\tt.Run(\"we update the first inserted card and assert that with limit 1 that's the limit that is set\", func(t *testing.T) {\n\t\t\ttime.Sleep(10 * time.Millisecond)\n\t\t\tcard1, err := store.GetBlock(\"card1\")\n\t\t\trequire.NoError(t, err)\n\n\t\t\tcard1.Title = \"New title\"\n\t\t\trequire.NoError(t, store.InsertBlock(card1, userID))\n\n\t\t\tnewCard1, err := store.GetBlock(\"card1\")\n\t\t\trequire.NoError(t, err)\n\n\t\t\tcardLimitTimestamp, err := store.UpdateCardLimitTimestamp(1)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, newCard1.UpdateAt, cardLimitTimestamp)\n\t\t})\n\n\t\tt.Run(\"limit should stop applying if we remove the last card\", func(t *testing.T) {\n\t\t\tinitialCardLimitTimestamp, err := store.GetCardLimitTimestamp()\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NotZero(t, initialCardLimitTimestamp)\n\n\t\t\ttime.Sleep(10 * time.Millisecond)\n\t\t\trequire.NoError(t, store.DeleteBlock(\"card1\", userID))\n\n\t\t\tcardLimitTimestamp, err := store.UpdateCardLimitTimestamp(10)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Zero(t, cardLimitTimestamp)\n\t\t})\n\t})\n}\n"
  },
  {
    "path": "server/services/store/storetests/compliance.go",
    "content": "package storetests\n\nimport (\n\t\"math\"\n\t\"testing\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/store\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc StoreTestComplianceHistoryStore(t *testing.T, setup func(t *testing.T) (store.Store, func())) {\n\tt.Run(\"GetBoardsForCompliance\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestGetBoardsForCompliance(t, store)\n\t})\n\tt.Run(\"GetBoardsComplianceHistory\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestGetBoardsComplianceHistory(t, store)\n\t})\n\tt.Run(\"GetBlocksComplianceHistory\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestGetBlocksComplianceHistory(t, store)\n\t})\n}\n\nfunc testGetBoardsForCompliance(t *testing.T, store store.Store) {\n\tteam1 := testTeamID\n\tteam2 := utils.NewID(utils.IDTypeTeam)\n\n\tboardsAdded1 := createTestBoards(t, store, team1, testUserID, 10)\n\tboardsAdded2 := createTestBoards(t, store, team2, testUserID, 7)\n\n\tdeleteTestBoard(t, store, boardsAdded1[0].ID, testUserID)\n\tdeleteTestBoard(t, store, boardsAdded1[1].ID, testUserID)\n\tboardsAdded1 = boardsAdded1[2:]\n\n\tt.Run(\"Invalid teamID\", func(t *testing.T) {\n\t\topts := model.QueryBoardsForComplianceOptions{\n\t\t\tTeamID: utils.NewID(utils.IDTypeTeam),\n\t\t}\n\n\t\tboards, hasMore, err := store.GetBoardsForCompliance(opts)\n\n\t\tassert.Empty(t, boards)\n\t\tassert.False(t, hasMore)\n\t\tassert.NoError(t, err)\n\t})\n\n\tt.Run(\"All teams\", func(t *testing.T) {\n\t\topts := model.QueryBoardsForComplianceOptions{}\n\n\t\tboards, hasMore, err := store.GetBoardsForCompliance(opts)\n\n\t\tassert.ElementsMatch(t, extractIDs(t, boards), extractIDs(t, boardsAdded1, boardsAdded2))\n\t\tassert.False(t, hasMore)\n\t\tassert.NoError(t, err)\n\t})\n\n\tt.Run(\"Specific team\", func(t *testing.T) {\n\t\topts := model.QueryBoardsForComplianceOptions{\n\t\t\tTeamID: team1,\n\t\t}\n\n\t\tboards, hasMore, err := store.GetBoardsForCompliance(opts)\n\n\t\tassert.ElementsMatch(t, extractIDs(t, boards), extractIDs(t, boardsAdded1))\n\t\tassert.False(t, hasMore)\n\t\tassert.NoError(t, err)\n\t})\n\n\tt.Run(\"Pagination\", func(t *testing.T) {\n\t\topts := model.QueryBoardsForComplianceOptions{\n\t\t\tPage:    0,\n\t\t\tPerPage: 3,\n\t\t}\n\n\t\treps := 0\n\t\tallBoards := make([]*model.Board, 0, 20)\n\n\t\tfor {\n\t\t\tboards, hasMore, err := store.GetBoardsForCompliance(opts)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NotEmpty(t, boards)\n\t\t\tallBoards = append(allBoards, boards...)\n\n\t\t\tif !hasMore {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\topts.Page++\n\t\t\treps++\n\t\t}\n\n\t\tassert.ElementsMatch(t, extractIDs(t, allBoards), extractIDs(t, boardsAdded1, boardsAdded2))\n\t})\n}\n\nfunc testGetBoardsComplianceHistory(t *testing.T, store store.Store) {\n\tteam1 := testTeamID\n\tteam2 := utils.NewID(utils.IDTypeTeam)\n\n\tboardsTeam1 := createTestBoards(t, store, team1, testUserID, 11)\n\tboardsTeam2 := createTestBoards(t, store, team2, testUserID, 7)\n\tboardsAdded := make([]*model.Board, 0)\n\tboardsAdded = append(boardsAdded, boardsTeam1...)\n\tboardsAdded = append(boardsAdded, boardsTeam2...)\n\n\tdeleteTestBoard(t, store, boardsTeam1[0].ID, testUserID)\n\tdeleteTestBoard(t, store, boardsTeam1[1].ID, testUserID)\n\tboardsDeleted := boardsTeam1[0:2]\n\tboardsTeam1 = boardsTeam1[2:]\n\n\tt.Log(\"boardsTeam1: \", extractIDs(t, boardsTeam1))\n\tt.Log(\"boardsTeam2: \", extractIDs(t, boardsTeam2))\n\tt.Log(\"boardsAdded: \", extractIDs(t, boardsAdded))\n\tt.Log(\"boardsDeleted: \", extractIDs(t, boardsDeleted))\n\n\tt.Run(\"Invalid teamID\", func(t *testing.T) {\n\t\topts := model.QueryBoardsComplianceHistoryOptions{\n\t\t\tTeamID: utils.NewID(utils.IDTypeTeam),\n\t\t}\n\n\t\tboardHistories, hasMore, err := store.GetBoardsComplianceHistory(opts)\n\n\t\tassert.Empty(t, boardHistories)\n\t\tassert.False(t, hasMore)\n\t\tassert.NoError(t, err)\n\t})\n\n\tt.Run(\"All teams, include deleted\", func(t *testing.T) {\n\t\topts := model.QueryBoardsComplianceHistoryOptions{\n\t\t\tIncludeDeleted: true,\n\t\t}\n\n\t\tboardHistories, hasMore, err := store.GetBoardsComplianceHistory(opts)\n\n\t\t// boardHistories should contain a record for each board added, plus a record for the 2 deleted.\n\t\tassert.ElementsMatch(t, extractIDs(t, boardHistories), extractIDs(t, boardsAdded, boardsDeleted))\n\t\tassert.False(t, hasMore)\n\t\tassert.NoError(t, err)\n\t})\n\n\tt.Run(\"All teams, exclude deleted\", func(t *testing.T) {\n\t\topts := model.QueryBoardsComplianceHistoryOptions{\n\t\t\tIncludeDeleted: false,\n\t\t}\n\n\t\tboardHistories, hasMore, err := store.GetBoardsComplianceHistory(opts)\n\n\t\t// boardHistories should contain a record for each board added, minus the two deleted.\n\t\tassert.ElementsMatch(t, extractIDs(t, boardHistories), extractIDs(t, boardsTeam1, boardsTeam2))\n\t\tassert.False(t, hasMore)\n\t\tassert.NoError(t, err)\n\t})\n\n\tt.Run(\"Specific team\", func(t *testing.T) {\n\t\topts := model.QueryBoardsComplianceHistoryOptions{\n\t\t\tTeamID: team1,\n\t\t}\n\n\t\tboardHistories, hasMore, err := store.GetBoardsComplianceHistory(opts)\n\n\t\tassert.ElementsMatch(t, extractIDs(t, boardHistories), extractIDs(t, boardsTeam1))\n\t\tassert.False(t, hasMore)\n\t\tassert.NoError(t, err)\n\t})\n\n\tt.Run(\"Pagination\", func(t *testing.T) {\n\t\topts := model.QueryBoardsComplianceHistoryOptions{\n\t\t\tPage:    0,\n\t\t\tPerPage: 3,\n\t\t}\n\n\t\treps := 0\n\t\tallHistories := make([]*model.BoardHistory, 0)\n\n\t\tfor {\n\t\t\treps++\n\t\t\tboardHistories, hasMore, err := store.GetBoardsComplianceHistory(opts)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NotEmpty(t, boardHistories)\n\t\t\tallHistories = append(allHistories, boardHistories...)\n\n\t\t\tif !hasMore {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\topts.Page++\n\t\t}\n\n\t\tassert.ElementsMatch(t, extractIDs(t, allHistories), extractIDs(t, boardsTeam1, boardsTeam2))\n\t\texpectedCount := len(boardsTeam1) + len(boardsTeam2)\n\t\tassert.Equal(t, math.Floor(float64(expectedCount/opts.PerPage)+1), float64(reps))\n\t})\n}\n\nfunc testGetBlocksComplianceHistory(t *testing.T, store store.Store) {\n\tteam1 := testTeamID\n\tteam2 := utils.NewID(utils.IDTypeTeam)\n\n\tboardsTeam1 := createTestBoards(t, store, team1, testUserID, 3)\n\tboardsTeam2 := createTestBoards(t, store, team2, testUserID, 1)\n\n\t// add cards (13 in total)\n\tcards1Team1 := createTestCards(t, store, testUserID, boardsTeam1[0].ID, 3)\n\tcards2Team1 := createTestCards(t, store, testUserID, boardsTeam1[1].ID, 5)\n\tcards3Team1 := createTestCards(t, store, testUserID, boardsTeam1[2].ID, 2)\n\tcards1Team2 := createTestCards(t, store, testUserID, boardsTeam2[0].ID, 3)\n\n\tdeleteTestBoard(t, store, boardsTeam1[0].ID, testUserID)\n\tcardsDeleted := cards1Team1\n\n\tt.Run(\"Invalid teamID\", func(t *testing.T) {\n\t\topts := model.QueryBlocksComplianceHistoryOptions{\n\t\t\tTeamID: utils.NewID(utils.IDTypeTeam),\n\t\t}\n\n\t\tboards, hasMore, err := store.GetBlocksComplianceHistory(opts)\n\n\t\tassert.Empty(t, boards)\n\t\tassert.False(t, hasMore)\n\t\tassert.NoError(t, err)\n\t})\n\n\tt.Run(\"All teams, include deleted\", func(t *testing.T) {\n\t\topts := model.QueryBlocksComplianceHistoryOptions{\n\t\t\tIncludeDeleted: true,\n\t\t}\n\n\t\tblockHistories, hasMore, err := store.GetBlocksComplianceHistory(opts)\n\n\t\t// blockHistories should have records for all cards added, plus all cards deleted\n\t\tassert.ElementsMatch(t, extractIDs(t, blockHistories, nil),\n\t\t\textractIDs(t, cards1Team1, cards2Team1, cards3Team1, cards1Team2, cardsDeleted))\n\t\tassert.False(t, hasMore)\n\t\tassert.NoError(t, err)\n\t})\n\n\tt.Run(\"All teams, exclude deleted\", func(t *testing.T) {\n\t\topts := model.QueryBlocksComplianceHistoryOptions{}\n\n\t\tblockHistories, hasMore, err := store.GetBlocksComplianceHistory(opts)\n\n\t\t// blockHistories should have records for all cards added that have not been deleted\n\t\tassert.ElementsMatch(t, extractIDs(t, blockHistories, nil),\n\t\t\textractIDs(t, cards2Team1, cards3Team1, cards1Team2))\n\t\tassert.False(t, hasMore)\n\t\tassert.NoError(t, err)\n\t})\n\n\tt.Run(\"Specific team\", func(t *testing.T) {\n\t\topts := model.QueryBlocksComplianceHistoryOptions{\n\t\t\tTeamID: team1,\n\t\t}\n\n\t\tblockHistories, hasMore, err := store.GetBlocksComplianceHistory(opts)\n\n\t\tassert.ElementsMatch(t, extractIDs(t, blockHistories), extractIDs(t, cards2Team1, cards3Team1))\n\t\tassert.False(t, hasMore)\n\t\tassert.NoError(t, err)\n\t})\n\n\tt.Run(\"Pagination\", func(t *testing.T) {\n\t\topts := model.QueryBlocksComplianceHistoryOptions{\n\t\t\tPage:    0,\n\t\t\tPerPage: 3,\n\t\t}\n\n\t\treps := 0\n\t\tallHistories := make([]*model.BlockHistory, 0)\n\n\t\tfor {\n\t\t\treps++\n\t\t\tblockHistories, hasMore, err := store.GetBlocksComplianceHistory(opts)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NotEmpty(t, blockHistories)\n\t\t\tallHistories = append(allHistories, blockHistories...)\n\n\t\t\tif !hasMore {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\topts.Page++\n\t\t}\n\n\t\tassert.ElementsMatch(t, extractIDs(t, allHistories), extractIDs(t, cards2Team1, cards3Team1, cards1Team2))\n\n\t\texpectedCount := len(cards2Team1) + len(cards3Team1) + len(cards1Team2)\n\t\tassert.Equal(t, math.Floor(float64(expectedCount/opts.PerPage)+1), float64(reps))\n\t})\n}\n"
  },
  {
    "path": "server/services/store/storetests/data_retention.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\npackage storetests\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/store\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nconst (\n\tboardID    = \"board-id-test\"\n\tcategoryID = \"category-id-test\"\n)\n\nfunc StoreTestDataRetention(t *testing.T, setup func(t *testing.T) (store.Store, func())) {\n\tt.Run(\"RunDataRetention\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\n\t\tcategory := model.Category{\n\t\t\tID:     categoryID,\n\t\t\tName:   \"TestCategory\",\n\t\t\tUserID: testUserID,\n\t\t\tTeamID: testTeamID,\n\t\t}\n\t\terr := store.CreateCategory(category)\n\t\trequire.NoError(t, err)\n\n\t\ttestRunDataRetention(t, store, 0)\n\t\ttestRunDataRetention(t, store, 2)\n\t\ttestRunDataRetention(t, store, 10)\n\t})\n}\n\nfunc LoadData(t *testing.T, store store.Store) {\n\tvalidBoard := model.Board{\n\t\tID:         boardID,\n\t\tIsTemplate: false,\n\t\tModifiedBy: testUserID,\n\t\tTeamID:     testTeamID,\n\t}\n\tboard, err := store.InsertBoard(&validBoard, testUserID)\n\trequire.NoError(t, err)\n\n\tvalidBlock := &model.Block{\n\t\tID:         \"id-test\",\n\t\tBoardID:    board.ID,\n\t\tModifiedBy: testUserID,\n\t}\n\n\tvalidBlock2 := &model.Block{\n\t\tID:         \"id-test2\",\n\t\tBoardID:    board.ID,\n\t\tModifiedBy: testUserID,\n\t}\n\tvalidBlock3 := &model.Block{\n\t\tID:         \"id-test3\",\n\t\tBoardID:    board.ID,\n\t\tModifiedBy: testUserID,\n\t}\n\n\tvalidBlock4 := &model.Block{\n\t\tID:         \"id-test4\",\n\t\tBoardID:    board.ID,\n\t\tModifiedBy: testUserID,\n\t}\n\n\tnewBlocks := []*model.Block{validBlock, validBlock2, validBlock3, validBlock4}\n\n\terr = store.InsertBlocks(newBlocks, testUserID)\n\trequire.NoError(t, err)\n\n\tmember := &model.BoardMember{\n\t\tUserID:      testUserID,\n\t\tBoardID:     boardID,\n\t\tSchemeAdmin: true,\n\t}\n\t_, err = store.SaveMember(member)\n\trequire.NoError(t, err)\n\n\tsharing := model.Sharing{\n\t\tID:      boardID,\n\t\tEnabled: true,\n\t\tToken:   \"testToken\",\n\t}\n\terr = store.UpsertSharing(sharing)\n\trequire.NoError(t, err)\n\n\terr = store.AddUpdateCategoryBoard(testUserID, categoryID, []string{boardID})\n\trequire.NoError(t, err)\n}\n\nfunc testRunDataRetention(t *testing.T, store store.Store, batchSize int) {\n\tLoadData(t, store)\n\n\tblocks, err := store.GetBlocksForBoard(boardID)\n\trequire.NoError(t, err)\n\trequire.Len(t, blocks, 4)\n\tinitialCount := len(blocks)\n\n\tt.Run(\"test no deletions\", func(t *testing.T) {\n\t\tdeletions, err := store.RunDataRetention(utils.GetMillisForTime(time.Now().Add(-time.Hour*1)), int64(batchSize))\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, int64(0), deletions)\n\t})\n\n\tt.Run(\"test all deletions\", func(t *testing.T) {\n\t\tdeletions, err := store.RunDataRetention(utils.GetMillisForTime(time.Now().Add(time.Hour*1)), int64(batchSize))\n\t\trequire.NoError(t, err)\n\t\trequire.True(t, deletions > int64(initialCount))\n\n\t\t// expect all blocks to be deleted.\n\t\tblocks, errBlocks := store.GetBlocksForBoard(boardID)\n\t\trequire.NoError(t, errBlocks)\n\t\trequire.Equal(t, 0, len(blocks))\n\n\t\t// GetMemberForBoard throws error on now rows found\n\t\tmember, err := store.GetMemberForBoard(boardID, testUserID)\n\t\trequire.Error(t, err)\n\t\trequire.True(t, model.IsErrNotFound(err), err)\n\t\trequire.Nil(t, member)\n\n\t\t// GetSharing throws error on now rows found\n\t\tsharing, err := store.GetSharing(boardID)\n\t\trequire.Error(t, err)\n\t\trequire.True(t, model.IsErrNotFound(err), err)\n\t\trequire.Nil(t, sharing)\n\n\t\tcategory, err := store.GetUserCategoryBoards(boardID, testTeamID)\n\t\trequire.NoError(t, err)\n\t\trequire.Empty(t, category)\n\t})\n}\n"
  },
  {
    "path": "server/services/store/storetests/files.go",
    "content": "package storetests\n\nimport (\n\t\"testing\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/store\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n\tmmModel \"github.com/mattermost/mattermost/server/public/model\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc StoreTestFileStore(t *testing.T, setup func(t *testing.T) (store.Store, func())) {\n\tsqlStore, tearDown := setup(t)\n\tdefer tearDown()\n\n\tt.Run(\"should save and retrieve fileinfo\", func(t *testing.T) {\n\t\tfileInfo := &mmModel.FileInfo{\n\t\t\tId:        \"file_info_1\",\n\t\t\tCreateAt:  utils.GetMillis(),\n\t\t\tName:      \"Dunder Mifflin Sales Report 2022\",\n\t\t\tExtension: \".sales\",\n\t\t\tSize:      112233,\n\t\t\tDeleteAt:  0,\n\t\t}\n\n\t\terr := sqlStore.SaveFileInfo(fileInfo)\n\t\trequire.NoError(t, err)\n\n\t\tretrievedFileInfo, err := sqlStore.GetFileInfo(\"file_info_1\")\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"file_info_1\", retrievedFileInfo.Id)\n\t\trequire.Equal(t, \"Dunder Mifflin Sales Report 2022\", retrievedFileInfo.Name)\n\t\trequire.Equal(t, \".sales\", retrievedFileInfo.Extension)\n\t\trequire.Equal(t, int64(112233), retrievedFileInfo.Size)\n\t\trequire.Equal(t, int64(0), retrievedFileInfo.DeleteAt)\n\t\trequire.False(t, retrievedFileInfo.Archived)\n\t})\n\n\tt.Run(\"should return an error on not found\", func(t *testing.T) {\n\t\tfileInfo, err := sqlStore.GetFileInfo(\"nonexistent\")\n\t\trequire.Error(t, err)\n\t\tvar nf *model.ErrNotFound\n\t\trequire.ErrorAs(t, err, &nf)\n\t\trequire.Nil(t, fileInfo)\n\t})\n}\n"
  },
  {
    "path": "server/services/store/storetests/helpers.go",
    "content": "package storetests\n\nimport (\n\t\"testing\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/store\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc InsertBlocks(t *testing.T, s store.Store, blocks []*model.Block, userID string) {\n\tfor i := range blocks {\n\t\terr := s.InsertBlock(blocks[i], userID)\n\t\trequire.NoError(t, err)\n\t}\n}\n\nfunc DeleteBlocks(t *testing.T, s store.Store, blocks []*model.Block, modifiedBy string) {\n\tfor _, block := range blocks {\n\t\terr := s.DeleteBlock(block.ID, modifiedBy)\n\t\trequire.NoError(t, err)\n\t}\n}\n\nfunc ContainsBlockWithID(blocks []*model.Block, blockID string) bool {\n\tfor _, block := range blocks {\n\t\tif block.ID == blockID {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n"
  },
  {
    "path": "server/services/store/storetests/notificationhints.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage storetests\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/store\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n)\n\nfunc StoreTestNotificationHintsStore(t *testing.T, setup func(t *testing.T) (store.Store, func())) {\n\tt.Run(\"UpsertNotificationHint\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestUpsertNotificationHint(t, store)\n\t})\n\n\tt.Run(\"DeleteNotificationHint\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestDeleteNotificationHint(t, store)\n\t})\n\n\tt.Run(\"GetNotificationHint\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestGetNotificationHint(t, store)\n\t})\n\n\tt.Run(\"GetNextNotificationHint\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestGetNextNotificationHint(t, store)\n\t})\n}\n\nfunc testUpsertNotificationHint(t *testing.T, store store.Store) {\n\tt.Run(\"create notification hint\", func(t *testing.T) {\n\t\thint := &model.NotificationHint{\n\t\t\tBlockType:    model.TypeCard,\n\t\t\tBlockID:      utils.NewID(utils.IDTypeBlock),\n\t\t\tModifiedByID: utils.NewID(utils.IDTypeUser),\n\t\t}\n\n\t\thintNew, err := store.UpsertNotificationHint(hint, time.Second*15)\n\t\trequire.NoError(t, err, \"upsert notification hint should not error\")\n\t\tassert.Equal(t, hint.BlockID, hintNew.BlockID)\n\t\tassert.NoError(t, hintNew.IsValid())\n\t})\n\n\tt.Run(\"duplicate notification hint\", func(t *testing.T) {\n\t\thint := &model.NotificationHint{\n\t\t\tBlockType:    model.TypeCard,\n\t\t\tBlockID:      utils.NewID(utils.IDTypeBlock),\n\t\t\tModifiedByID: utils.NewID(utils.IDTypeUser),\n\t\t}\n\t\thintNew, err := store.UpsertNotificationHint(hint, time.Second*15)\n\t\trequire.NoError(t, err, \"upsert notification hint should not error\")\n\n\t\t// sleep a short time so the notify_at timestamps won't collide\n\t\ttime.Sleep(time.Millisecond * 20)\n\n\t\thint = &model.NotificationHint{\n\t\t\tBlockType:    model.TypeCard,\n\t\t\tBlockID:      hintNew.BlockID,\n\t\t\tModifiedByID: hintNew.ModifiedByID,\n\t\t}\n\t\thintDup, err := store.UpsertNotificationHint(hint, time.Second*15)\n\n\t\trequire.NoError(t, err, \"upsert notification hint should not error\")\n\t\t// notify_at should be updated\n\t\tassert.Greater(t, hintDup.NotifyAt, hintNew.NotifyAt)\n\t})\n\n\tt.Run(\"invalid notification hint\", func(t *testing.T) {\n\t\thint := &model.NotificationHint{}\n\n\t\t_, err := store.UpsertNotificationHint(hint, time.Second*15)\n\t\tassert.ErrorAs(t, err, &model.ErrInvalidNotificationHint{}, \"invalid notification hint should error\")\n\n\t\thint.BlockType = \"board\"\n\t\t_, err = store.UpsertNotificationHint(hint, time.Second*15)\n\t\tassert.ErrorAs(t, err, &model.ErrInvalidNotificationHint{}, \"invalid notification hint should error\")\n\n\t\t_, err = store.UpsertNotificationHint(hint, time.Second*15)\n\t\tassert.ErrorAs(t, err, &model.ErrInvalidNotificationHint{}, \"invalid notification hint should error\")\n\n\t\thint.ModifiedByID = utils.NewID(utils.IDTypeUser)\n\t\t_, err = store.UpsertNotificationHint(hint, time.Second*15)\n\t\tassert.ErrorAs(t, err, &model.ErrInvalidNotificationHint{}, \"invalid notification hint should error\")\n\n\t\thint.BlockID = utils.NewID(utils.IDTypeBlock)\n\t\thintNew, err := store.UpsertNotificationHint(hint, time.Second*15)\n\t\tassert.NoError(t, err, \"valid notification hint should not error\")\n\t\tassert.NoError(t, hintNew.IsValid(), \"created notification hint should be valid\")\n\t})\n}\n\nfunc testDeleteNotificationHint(t *testing.T, store store.Store) {\n\tt.Run(\"delete notification hint\", func(t *testing.T) {\n\t\thint := &model.NotificationHint{\n\t\t\tBlockType:    model.TypeCard,\n\t\t\tBlockID:      utils.NewID(utils.IDTypeBlock),\n\t\t\tModifiedByID: utils.NewID(utils.IDTypeUser),\n\t\t}\n\t\thintNew, err := store.UpsertNotificationHint(hint, time.Second*15)\n\t\trequire.NoError(t, err, \"create notification hint should not error\")\n\n\t\t// check the notification hint exists\n\t\thint, err = store.GetNotificationHint(hintNew.BlockID)\n\t\trequire.NoError(t, err, \"get notification hint should not error\")\n\t\tassert.Equal(t, hintNew.BlockID, hint.BlockID)\n\t\tassert.Equal(t, hintNew.CreateAt, hint.CreateAt)\n\n\t\terr = store.DeleteNotificationHint(hintNew.BlockID)\n\t\trequire.NoError(t, err, \"delete notification hint should not error\")\n\n\t\t// check the notification hint was deleted\n\t\thint, err = store.GetNotificationHint(hintNew.BlockID)\n\t\trequire.True(t, model.IsErrNotFound(err), \"error should be of type store.ErrNotFound\")\n\t\tassert.Nil(t, hint)\n\t})\n\n\tt.Run(\"delete non-existent notification hint\", func(t *testing.T) {\n\t\terr := store.DeleteNotificationHint(\"bogus\")\n\t\trequire.True(t, model.IsErrNotFound(err), \"error should be of type store.ErrNotFound\")\n\t})\n}\n\nfunc testGetNotificationHint(t *testing.T, store store.Store) {\n\tt.Run(\"get notification hint\", func(t *testing.T) {\n\t\thint := &model.NotificationHint{\n\t\t\tBlockType:    model.TypeCard,\n\t\t\tBlockID:      utils.NewID(utils.IDTypeBlock),\n\t\t\tModifiedByID: utils.NewID(utils.IDTypeUser),\n\t\t}\n\t\thintNew, err := store.UpsertNotificationHint(hint, time.Second*15)\n\t\trequire.NoError(t, err, \"create notification hint should not error\")\n\n\t\t// make sure notification hint can be fetched\n\t\thint, err = store.GetNotificationHint(hintNew.BlockID)\n\t\trequire.NoError(t, err, \"get notification hint should not error\")\n\t\tassert.Equal(t, hintNew, hint)\n\t})\n\n\tt.Run(\"get non-existent notification hint\", func(t *testing.T) {\n\t\thint, err := store.GetNotificationHint(\"bogus\")\n\t\trequire.True(t, model.IsErrNotFound(err), \"error should be of type store.ErrNotFound\")\n\t\tassert.Nil(t, hint, \"hint should be nil\")\n\t})\n}\n\nfunc testGetNextNotificationHint(t *testing.T, store store.Store) {\n\tt.Run(\"get next notification hint\", func(t *testing.T) {\n\t\tconst loops = 5\n\t\tids := [5]string{}\n\t\tmodifiedBy := utils.NewID(utils.IDTypeUser)\n\n\t\t// create some hints with unique notifyAt\n\t\tfor i := 0; i < loops; i++ {\n\t\t\thint := &model.NotificationHint{\n\t\t\t\tBlockType:    model.TypeCard,\n\t\t\t\tBlockID:      utils.NewID(utils.IDTypeBlock),\n\t\t\t\tModifiedByID: modifiedBy,\n\t\t\t}\n\t\t\thintNew, err := store.UpsertNotificationHint(hint, time.Second*15)\n\t\t\trequire.NoError(t, err, \"create notification hint should not error\")\n\n\t\t\tids[i] = hintNew.BlockID\n\t\t\ttime.Sleep(time.Millisecond * 20) // ensure next timestamp is unique\n\t\t}\n\n\t\t// check the hints come back in the right order\n\t\tnotifyAt := utils.GetMillisForTime(time.Now().Add(time.Millisecond * 50))\n\t\tfor i := 0; i < loops; i++ {\n\t\t\thint, err := store.GetNextNotificationHint(false)\n\t\t\trequire.NoError(t, err, \"get next notification hint should not error\")\n\t\t\trequire.NotNil(t, hint, \"get next notification hint should not return nil\")\n\t\t\tassert.Equal(t, ids[i], hint.BlockID)\n\t\t\tassert.Less(t, notifyAt, hint.NotifyAt)\n\t\t\tnotifyAt = hint.NotifyAt\n\n\t\t\terr = store.DeleteNotificationHint(hint.BlockID)\n\t\t\trequire.NoError(t, err, \"delete notification hint should not error\")\n\t\t}\n\t})\n\n\tt.Run(\"get next notification hint from empty table\", func(t *testing.T) {\n\t\t// empty the table\n\t\terr := emptyNotificationHintTable(store)\n\t\trequire.NoError(t, err, \"emptying notification hint table should not error\")\n\n\t\tfor {\n\t\t\thint, err2 := store.GetNextNotificationHint(false)\n\t\t\tif model.IsErrNotFound(err2) {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\trequire.NoError(t, err2, \"get next notification hint should not error\")\n\n\t\t\terr2 = store.DeleteNotificationHint(hint.BlockID)\n\t\t\trequire.NoError(t, err2, \"delete notification hint should not error\")\n\t\t}\n\n\t\t_, err = store.GetNextNotificationHint(false)\n\t\trequire.True(t, model.IsErrNotFound(err), \"error should be of type store.ErrNotFound\")\n\t})\n\n\tt.Run(\"get next notification hint and remove\", func(t *testing.T) {\n\t\t// empty the table\n\t\terr := emptyNotificationHintTable(store)\n\t\trequire.NoError(t, err, \"emptying notification hint table should not error\")\n\n\t\thint := &model.NotificationHint{\n\t\t\tBlockType:    model.TypeCard,\n\t\t\tBlockID:      utils.NewID(utils.IDTypeBlock),\n\t\t\tModifiedByID: utils.NewID(utils.IDTypeUser),\n\t\t}\n\t\thintNew, err := store.UpsertNotificationHint(hint, time.Second*1)\n\t\trequire.NoError(t, err, \"create notification hint should not error\")\n\n\t\thintDeleted, err := store.GetNextNotificationHint(true)\n\t\trequire.NoError(t, err, \"get next notification hint should not error\")\n\t\trequire.NotNil(t, hintDeleted, \"get next notification hint should not return nil\")\n\t\tassert.Equal(t, hintNew.BlockID, hintDeleted.BlockID)\n\n\t\t// should be no hint left\n\t\t_, err = store.GetNextNotificationHint(false)\n\t\trequire.True(t, model.IsErrNotFound(err), \"error should be of type store.ErrNotFound\")\n\t})\n}\n\nfunc emptyNotificationHintTable(store store.Store) error {\n\tfor {\n\t\thint, err := store.GetNextNotificationHint(false)\n\t\tif model.IsErrNotFound(err) {\n\t\t\tbreak\n\t\t}\n\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\terr = store.DeleteNotificationHint(hint.BlockID)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "server/services/store/storetests/session.go",
    "content": "package storetests\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/store\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc StoreTestSessionStore(t *testing.T, setup func(t *testing.T) (store.Store, func())) {\n\tt.Run(\"CreateAndGetAndDeleteSession\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestCreateAndGetAndDeleteSession(t, store)\n\t})\n\n\tt.Run(\"GetActiveUserCount\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestGetActiveUserCount(t, store)\n\t})\n\n\tt.Run(\"UpdateSession\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestUpdateSession(t, store)\n\t})\n}\n\nfunc testCreateAndGetAndDeleteSession(t *testing.T, store store.Store) {\n\tsession := &model.Session{\n\t\tID:    \"session-id\",\n\t\tToken: \"token\",\n\t}\n\n\tt.Run(\"CreateAndGetSession\", func(t *testing.T) {\n\t\terr := store.CreateSession(session)\n\t\trequire.NoError(t, err)\n\n\t\tgot, err := store.GetSession(session.Token, 60*60)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, session, got)\n\t})\n\n\tt.Run(\"Get nonexistent session\", func(t *testing.T) {\n\t\tgot, err := store.GetSession(\"nonexistent-token\", 60*60)\n\t\trequire.True(t, model.IsErrNotFound(err))\n\t\trequire.Nil(t, got)\n\t})\n\n\tt.Run(\"DeleteAndGetSession\", func(t *testing.T) {\n\t\terr := store.DeleteSession(session.ID)\n\t\trequire.NoError(t, err)\n\n\t\t_, err = store.GetSession(session.Token, 60*60)\n\t\trequire.Error(t, err)\n\t})\n}\n\nfunc testGetActiveUserCount(t *testing.T, store store.Store) {\n\tt.Run(\"no active user\", func(t *testing.T) {\n\t\tcount, err := store.GetActiveUserCount(60)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 0, count)\n\t})\n\n\tt.Run(\"active user\", func(t *testing.T) {\n\t\t// gen random count active user session\n\t\tcount := int(time.Now().Unix() % 10)\n\t\tfor i := 0; i < count; i++ {\n\t\t\tsession := &model.Session{\n\t\t\t\tID:     fmt.Sprintf(\"id-%d\", i),\n\t\t\t\tUserID: fmt.Sprintf(\"user-id-%d\", i),\n\t\t\t\tToken:  fmt.Sprintf(\"token-%d\", i),\n\t\t\t}\n\t\t\terr := store.CreateSession(session)\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\tgot, err := store.GetActiveUserCount(60)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, count, got)\n\t})\n}\n\nfunc testUpdateSession(t *testing.T, store store.Store) {\n\tsession := &model.Session{\n\t\tID:    \"session-id\",\n\t\tToken: \"token\",\n\t\tProps: map[string]interface{}{\"field1\": \"A\"},\n\t}\n\n\terr := store.CreateSession(session)\n\trequire.NoError(t, err)\n\n\t// update session\n\tsession.Props[\"field1\"] = \"B\"\n\terr = store.UpdateSession(session)\n\trequire.NoError(t, err)\n\n\tgot, err := store.GetSession(session.Token, 60)\n\trequire.NoError(t, err)\n\trequire.Equal(t, session, got)\n}\n"
  },
  {
    "path": "server/services/store/storetests/sharing.go",
    "content": "package storetests\n\nimport (\n\t\"testing\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/store\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc StoreTestSharingStore(t *testing.T, setup func(t *testing.T) (store.Store, func())) {\n\tt.Run(\"UpsertSharingAndGetSharing\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestUpsertSharingAndGetSharing(t, store)\n\t})\n}\n\nfunc testUpsertSharingAndGetSharing(t *testing.T, store store.Store) {\n\tt.Run(\"Insert first sharing and get it\", func(t *testing.T) {\n\t\tsharing := model.Sharing{\n\t\t\tID:         \"sharing-id\",\n\t\t\tEnabled:    true,\n\t\t\tToken:      \"token\",\n\t\t\tModifiedBy: testUserID,\n\t\t}\n\n\t\terr := store.UpsertSharing(sharing)\n\t\trequire.NoError(t, err)\n\t\tnewSharing, err := store.GetSharing(\"sharing-id\")\n\t\trequire.NoError(t, err)\n\t\tnewSharing.UpdateAt = 0\n\t\trequire.Equal(t, sharing, *newSharing)\n\t})\n\tt.Run(\"Upsert the inserted sharing and get it\", func(t *testing.T) {\n\t\tsharing := model.Sharing{\n\t\t\tID:         \"sharing-id\",\n\t\t\tEnabled:    true,\n\t\t\tToken:      \"token2\",\n\t\t\tModifiedBy: \"user-id2\",\n\t\t}\n\n\t\tnewSharing, err := store.GetSharing(\"sharing-id\")\n\t\trequire.NoError(t, err)\n\t\tnewSharing.UpdateAt = 0\n\t\trequire.NotEqual(t, sharing, *newSharing)\n\n\t\terr = store.UpsertSharing(sharing)\n\t\trequire.NoError(t, err)\n\t\tnewSharing, err = store.GetSharing(\"sharing-id\")\n\t\trequire.NoError(t, err)\n\t\tnewSharing.UpdateAt = 0\n\t\trequire.Equal(t, sharing, *newSharing)\n\t})\n\tt.Run(\"Get not existing sharing\", func(t *testing.T) {\n\t\t_, err := store.GetSharing(\"not-existing\")\n\t\trequire.Error(t, err)\n\t\trequire.True(t, model.IsErrNotFound(err))\n\t})\n}\n"
  },
  {
    "path": "server/services/store/storetests/subscriptions.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage storetests\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/store\"\n)\n\n//nolint:dupl\nfunc StoreTestSubscriptionsStore(t *testing.T, setup func(t *testing.T) (store.Store, func())) {\n\tt.Run(\"CreateSubscription\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestCreateSubscription(t, store)\n\t})\n\n\tt.Run(\"DeleteSubscription\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestDeleteSubscription(t, store)\n\t})\n\n\tt.Run(\"UndeleteSubscription\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestUndeleteSubscription(t, store)\n\t})\n\n\tt.Run(\"GetSubscription\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestGetSubscription(t, store)\n\t})\n\n\tt.Run(\"GetSubscriptions\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestGetSubscriptions(t, store)\n\t})\n\n\tt.Run(\"GetSubscribersForBlock\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestGetSubscribersForBlock(t, store)\n\t})\n}\n\nfunc testCreateSubscription(t *testing.T, store store.Store) {\n\tt.Run(\"create subscriptions\", func(t *testing.T) {\n\t\tusers := createTestUsers(t, store, 10)\n\t\tblocks := createTestBlocks(t, store, users[0].ID, 50)\n\n\t\tfor i, user := range users {\n\t\t\tfor j := 0; j < i; j++ {\n\t\t\t\tsub := &model.Subscription{\n\t\t\t\t\tBlockType:      blocks[j].Type,\n\t\t\t\t\tBlockID:        blocks[j].ID,\n\t\t\t\t\tSubscriberType: \"user\",\n\t\t\t\t\tSubscriberID:   user.ID,\n\t\t\t\t}\n\t\t\t\tsubNew, err := store.CreateSubscription(sub)\n\t\t\t\trequire.NoError(t, err, \"create subscription should not error\")\n\n\t\t\t\tassert.NotZero(t, subNew.NotifiedAt)\n\t\t\t\tassert.NotZero(t, subNew.CreateAt)\n\t\t\t\tassert.Zero(t, subNew.DeleteAt)\n\t\t\t}\n\t\t}\n\n\t\t// ensure each user has the right number of subscriptions\n\t\tfor i, user := range users {\n\t\t\tsubs, err := store.GetSubscriptions(user.ID)\n\t\t\trequire.NoError(t, err, \"get subscriptions should not error\")\n\t\t\tassert.Len(t, subs, i)\n\t\t}\n\t})\n\n\tt.Run(\"duplicate subscription\", func(t *testing.T) {\n\t\tadmin := createTestUsers(t, store, 1)[0]\n\t\tuser := createTestUsers(t, store, 1)[0]\n\t\tblock := createTestBlocks(t, store, admin.ID, 1)[0]\n\n\t\tsub := &model.Subscription{\n\t\t\tBlockType:      block.Type,\n\t\t\tBlockID:        block.ID,\n\t\t\tSubscriberType: \"user\",\n\t\t\tSubscriberID:   user.ID,\n\t\t}\n\t\tsubNew, err := store.CreateSubscription(sub)\n\t\trequire.NoError(t, err, \"create subscription should not error\")\n\n\t\tsub = &model.Subscription{\n\t\t\tBlockType:      block.Type,\n\t\t\tBlockID:        block.ID,\n\t\t\tSubscriberType: \"user\",\n\t\t\tSubscriberID:   user.ID,\n\t\t}\n\n\t\tsubDup, err := store.CreateSubscription(sub)\n\t\trequire.NoError(t, err, \"create duplicate subscription should not error\")\n\n\t\tassert.Equal(t, subNew.BlockID, subDup.BlockID)\n\t\tassert.Equal(t, subNew.SubscriberID, subDup.SubscriberID)\n\t})\n\n\tt.Run(\"invalid subscription\", func(t *testing.T) {\n\t\tadmin := createTestUsers(t, store, 1)[0]\n\t\tuser := createTestUsers(t, store, 1)[0]\n\t\tblock := createTestBlocks(t, store, admin.ID, 1)[0]\n\n\t\tsub := &model.Subscription{}\n\n\t\t_, err := store.CreateSubscription(sub)\n\t\tassert.ErrorAs(t, err, &model.ErrInvalidSubscription{}, \"invalid subscription should error\")\n\n\t\tsub.BlockType = block.Type\n\t\t_, err = store.CreateSubscription(sub)\n\t\tassert.ErrorAs(t, err, &model.ErrInvalidSubscription{}, \"invalid subscription should error\")\n\n\t\tsub.BlockID = block.ID\n\t\t_, err = store.CreateSubscription(sub)\n\t\tassert.ErrorAs(t, err, &model.ErrInvalidSubscription{}, \"invalid subscription should error\")\n\n\t\tsub.SubscriberType = \"user\"\n\t\t_, err = store.CreateSubscription(sub)\n\t\tassert.ErrorAs(t, err, &model.ErrInvalidSubscription{}, \"invalid subscription should error\")\n\n\t\tsub.SubscriberID = user.ID\n\t\tsubNew, err := store.CreateSubscription(sub)\n\t\tassert.NoError(t, err, \"valid subscription should not error\")\n\n\t\tassert.NoError(t, subNew.IsValid(), \"created subscription should be valid\")\n\t})\n}\n\nfunc testDeleteSubscription(t *testing.T, s store.Store) {\n\tt.Run(\"delete subscription\", func(t *testing.T) {\n\t\tuser := createTestUsers(t, s, 1)[0]\n\t\tblock := createTestBlocks(t, s, user.ID, 1)[0]\n\n\t\tsub := &model.Subscription{\n\t\t\tBlockType:      block.Type,\n\t\t\tBlockID:        block.ID,\n\t\t\tSubscriberType: \"user\",\n\t\t\tSubscriberID:   user.ID,\n\t\t}\n\t\tsubNew, err := s.CreateSubscription(sub)\n\t\trequire.NoError(t, err, \"create subscription should not error\")\n\n\t\t// check the subscription exists\n\t\tsubs, err := s.GetSubscriptions(user.ID)\n\t\trequire.NoError(t, err, \"get subscriptions should not error\")\n\t\tassert.Len(t, subs, 1)\n\t\tassert.Equal(t, subNew.BlockID, subs[0].BlockID)\n\t\tassert.Equal(t, subNew.SubscriberID, subs[0].SubscriberID)\n\n\t\terr = s.DeleteSubscription(block.ID, user.ID)\n\t\trequire.NoError(t, err, \"delete subscription should not error\")\n\n\t\t// check the subscription was deleted\n\t\tsubs, err = s.GetSubscriptions(user.ID)\n\t\trequire.NoError(t, err, \"get subscriptions should not error\")\n\t\tassert.Empty(t, subs)\n\t})\n\n\tt.Run(\"delete non-existent subscription\", func(t *testing.T) {\n\t\terr := s.DeleteSubscription(\"bogus\", \"bogus\")\n\t\trequire.Error(t, err, \"delete non-existent subscription should error\")\n\t\trequire.True(t, model.IsErrNotFound(err), \"Should be ErrNotFound compatible error\")\n\t})\n}\n\nfunc testUndeleteSubscription(t *testing.T, s store.Store) {\n\tt.Run(\"undelete subscription\", func(t *testing.T) {\n\t\tuser := createTestUsers(t, s, 1)[0]\n\t\tblock := createTestBlocks(t, s, user.ID, 1)[0]\n\n\t\tsub := &model.Subscription{\n\t\t\tBlockType:      block.Type,\n\t\t\tBlockID:        block.ID,\n\t\t\tSubscriberType: \"user\",\n\t\t\tSubscriberID:   user.ID,\n\t\t}\n\t\tsubNew, err := s.CreateSubscription(sub)\n\t\trequire.NoError(t, err, \"create subscription should not error\")\n\n\t\t// check the subscription exists\n\t\tsubs, err := s.GetSubscriptions(user.ID)\n\t\trequire.NoError(t, err, \"get subscriptions should not error\")\n\t\tassert.Len(t, subs, 1)\n\t\tassert.Equal(t, subNew.BlockID, subs[0].BlockID)\n\t\tassert.Equal(t, subNew.SubscriberID, subs[0].SubscriberID)\n\n\t\terr = s.DeleteSubscription(block.ID, user.ID)\n\t\trequire.NoError(t, err, \"delete subscription should not error\")\n\n\t\t// check the subscription was deleted\n\t\tsubs, err = s.GetSubscriptions(user.ID)\n\t\trequire.NoError(t, err, \"get subscriptions should not error\")\n\t\tassert.Empty(t, subs)\n\n\t\t// re-create the subscription\n\t\tsubUndeleted, err := s.CreateSubscription(sub)\n\t\trequire.NoError(t, err, \"create subscription should not error\")\n\n\t\t// check the undeleted subscription exists\n\t\tsubs, err = s.GetSubscriptions(user.ID)\n\t\trequire.NoError(t, err, \"get subscriptions should not error\")\n\t\tassert.Len(t, subs, 1)\n\t\tassert.Equal(t, subUndeleted.BlockID, subs[0].BlockID)\n\t\tassert.Equal(t, subUndeleted.SubscriberID, subs[0].SubscriberID)\n\t})\n}\n\nfunc testGetSubscription(t *testing.T, s store.Store) {\n\tt.Run(\"get subscription\", func(t *testing.T) {\n\t\tuser := createTestUsers(t, s, 1)[0]\n\t\tblock := createTestBlocks(t, s, user.ID, 1)[0]\n\n\t\tsub := &model.Subscription{\n\t\t\tBlockType:      block.Type,\n\t\t\tBlockID:        block.ID,\n\t\t\tSubscriberType: \"user\",\n\t\t\tSubscriberID:   user.ID,\n\t\t}\n\t\tsubNew, err := s.CreateSubscription(sub)\n\t\trequire.NoError(t, err, \"create subscription should not error\")\n\n\t\t// make sure subscription can be fetched\n\t\tsub, err = s.GetSubscription(block.ID, user.ID)\n\t\trequire.NoError(t, err, \"get subscription should not error\")\n\t\tassert.Equal(t, subNew, sub)\n\t})\n\n\tt.Run(\"get non-existent subscription\", func(t *testing.T) {\n\t\tsub, err := s.GetSubscription(\"bogus\", \"bogus\")\n\t\trequire.Error(t, err, \"get non-existent subscription should error\")\n\t\trequire.True(t, model.IsErrNotFound(err), \"Should be ErrNotFound compatible error\")\n\t\trequire.Nil(t, sub, \"get subscription should return nil\")\n\t})\n}\n\nfunc testGetSubscriptions(t *testing.T, store store.Store) {\n\tt.Run(\"get subscriptions\", func(t *testing.T) {\n\t\tauthor := createTestUsers(t, store, 1)[0]\n\t\tuser := createTestUsers(t, store, 1)[0]\n\t\tblocks := createTestBlocks(t, store, author.ID, 50)\n\n\t\tfor _, block := range blocks {\n\t\t\tsub := &model.Subscription{\n\t\t\t\tBlockType:      block.Type,\n\t\t\t\tBlockID:        block.ID,\n\t\t\t\tSubscriberType: \"user\",\n\t\t\t\tSubscriberID:   user.ID,\n\t\t\t}\n\t\t\t_, err := store.CreateSubscription(sub)\n\t\t\trequire.NoError(t, err, \"create subscription should not error\")\n\t\t}\n\n\t\t// ensure user has the right number of subscriptions\n\t\tsubs, err := store.GetSubscriptions(user.ID)\n\t\trequire.NoError(t, err, \"get subscriptions should not error\")\n\t\tassert.Len(t, subs, len(blocks))\n\n\t\t// ensure author has no subscriptions\n\t\tsubs, err = store.GetSubscriptions(author.ID)\n\t\trequire.NoError(t, err, \"get subscriptions should not error\")\n\t\tassert.Empty(t, subs)\n\t})\n\n\tt.Run(\"get subscriptions for invalid user\", func(t *testing.T) {\n\t\tsubs, err := store.GetSubscriptions(\"bogus\")\n\t\trequire.NoError(t, err, \"get subscriptions should not error\")\n\t\tassert.Empty(t, subs)\n\t})\n}\n\nfunc testGetSubscribersForBlock(t *testing.T, store store.Store) {\n\tt.Run(\"get subscribers for block\", func(t *testing.T) {\n\t\tusers := createTestUsers(t, store, 50)\n\t\tblocks := createTestBlocks(t, store, users[0].ID, 2)\n\n\t\tfor _, user := range users {\n\t\t\tsub := &model.Subscription{\n\t\t\t\tBlockType:      blocks[1].Type,\n\t\t\t\tBlockID:        blocks[1].ID,\n\t\t\t\tSubscriberType: \"user\",\n\t\t\t\tSubscriberID:   user.ID,\n\t\t\t}\n\t\t\t_, err := store.CreateSubscription(sub)\n\t\t\trequire.NoError(t, err, \"create subscription should not error\")\n\t\t}\n\n\t\t// make sure block[1] has the right number of users subscribed\n\t\tsubs, err := store.GetSubscribersForBlock(blocks[1].ID)\n\t\trequire.NoError(t, err, \"get subscribers for block should not error\")\n\t\tassert.Len(t, subs, 50)\n\n\t\tcount, err := store.GetSubscribersCountForBlock(blocks[1].ID)\n\t\trequire.NoError(t, err, \"get subscribers for block should not error\")\n\t\tassert.Equal(t, 50, count)\n\n\t\t// make sure block[0] has zero users subscribed\n\t\tsubs, err = store.GetSubscribersForBlock(blocks[0].ID)\n\t\trequire.NoError(t, err, \"get subscribers for block should not error\")\n\t\tassert.Empty(t, subs)\n\n\t\tcount, err = store.GetSubscribersCountForBlock(blocks[0].ID)\n\t\trequire.NoError(t, err, \"get subscribers for block should not error\")\n\t\tassert.Zero(t, count)\n\t})\n\n\tt.Run(\"get subscribers for invalid block\", func(t *testing.T) {\n\t\tsubs, err := store.GetSubscribersForBlock(\"bogus\")\n\t\trequire.NoError(t, err, \"get subscribers for block should not error\")\n\t\tassert.Empty(t, subs)\n\t})\n}\n"
  },
  {
    "path": "server/services/store/storetests/system.go",
    "content": "package storetests\n\nimport (\n\t\"testing\"\n\n\t\"github.com/mattermost/focalboard/server/services/store\"\n\t\"github.com/stretchr/testify/require\"\n)\n\n// these system settings are created when running the data migrations,\n// so they will be present after the tests setup.\nvar dataMigrationSystemSettings = map[string]string{\n\t\"UniqueIDsMigrationComplete\":            \"true\",\n\t\"CategoryUuidIdMigrationComplete\":       \"true\",\n\t\"DeDuplicateCategoryBoardTableComplete\": \"true\",\n}\n\nfunc addBaseSettings(m map[string]string) map[string]string {\n\tr := map[string]string{}\n\tfor k, v := range dataMigrationSystemSettings {\n\t\tr[k] = v\n\t}\n\tfor k, v := range m {\n\t\tr[k] = v\n\t}\n\treturn r\n}\n\nfunc StoreTestSystemStore(t *testing.T, setup func(t *testing.T) (store.Store, func())) {\n\tt.Run(\"SetGetSystemSettings\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestSetGetSystemSettings(t, store)\n\t})\n}\n\nfunc testSetGetSystemSettings(t *testing.T, store store.Store) {\n\tt.Run(\"Get empty settings\", func(t *testing.T) {\n\t\tsettings, err := store.GetSystemSettings()\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, dataMigrationSystemSettings, settings)\n\t})\n\n\tt.Run(\"Set, update and get multiple settings\", func(t *testing.T) {\n\t\terr := store.SetSystemSetting(\"test-1\", \"test-value-1\")\n\t\trequire.NoError(t, err)\n\t\terr = store.SetSystemSetting(\"test-2\", \"test-value-2\")\n\t\trequire.NoError(t, err)\n\t\tsettings, err := store.GetSystemSettings()\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, addBaseSettings(map[string]string{\"test-1\": \"test-value-1\", \"test-2\": \"test-value-2\"}), settings)\n\n\t\terr = store.SetSystemSetting(\"test-2\", \"test-value-updated-2\")\n\t\trequire.NoError(t, err)\n\t\tsettings, err = store.GetSystemSettings()\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, addBaseSettings(map[string]string{\"test-1\": \"test-value-1\", \"test-2\": \"test-value-updated-2\"}), settings)\n\t})\n\n\tt.Run(\"Get a single setting\", func(t *testing.T) {\n\t\tvalue, err := store.GetSystemSetting(\"test-1\")\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"test-value-1\", value)\n\t})\n}\n"
  },
  {
    "path": "server/services/store/storetests/teams.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage storetests\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/mattermost/focalboard/server/services/store\"\n)\n\nfunc StoreTestTeamStore(t *testing.T, setup func(t *testing.T) (store.Store, func())) {\n\tt.Run(\"GetTeam\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestGetTeam(t, store)\n\t})\n\n\tt.Run(\"UpsertTeamSignupToken\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestUpsertTeamSignupToken(t, store)\n\t})\n\n\tt.Run(\"UpsertTeamSettings\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestUpsertTeamSettings(t, store)\n\t})\n\n\tt.Run(\"GetAllTeams\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestGetAllTeams(t, store)\n\t})\n}\n\nfunc testGetTeam(t *testing.T, store store.Store) {\n\tt.Run(\"Nonexistent team\", func(t *testing.T) {\n\t\tgot, err := store.GetTeam(\"nonexistent-id\")\n\t\trequire.Error(t, err)\n\t\trequire.True(t, model.IsErrNotFound(err))\n\t\trequire.Nil(t, got)\n\t})\n\n\tt.Run(\"Valid team\", func(t *testing.T) {\n\t\tteamID := \"0\"\n\t\tteam := &model.Team{\n\t\t\tID:          teamID,\n\t\t\tSignupToken: utils.NewID(utils.IDTypeToken),\n\t\t}\n\n\t\terr := store.UpsertTeamSignupToken(*team)\n\t\trequire.NoError(t, err)\n\n\t\tgot, err := store.GetTeam(teamID)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, teamID, got.ID)\n\t})\n}\n\nfunc testUpsertTeamSignupToken(t *testing.T, store store.Store) {\n\tt.Run(\"Insert and update team with signup token\", func(t *testing.T) {\n\t\tteamID := \"0\"\n\t\tteam := &model.Team{\n\t\t\tID:          teamID,\n\t\t\tSignupToken: utils.NewID(utils.IDTypeToken),\n\t\t}\n\n\t\t// insert\n\t\terr := store.UpsertTeamSignupToken(*team)\n\t\trequire.NoError(t, err)\n\n\t\tgot, err := store.GetTeam(teamID)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, team.ID, got.ID)\n\t\trequire.Equal(t, team.SignupToken, got.SignupToken)\n\n\t\t// update signup token\n\t\tteam.SignupToken = utils.NewID(utils.IDTypeToken)\n\t\terr = store.UpsertTeamSignupToken(*team)\n\t\trequire.NoError(t, err)\n\n\t\tgot, err = store.GetTeam(teamID)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, team.ID, got.ID)\n\t\trequire.Equal(t, team.SignupToken, got.SignupToken)\n\t})\n}\n\nfunc testUpsertTeamSettings(t *testing.T, store store.Store) {\n\tt.Run(\"Insert and update team with settings\", func(t *testing.T) {\n\t\tteamID := \"0\"\n\t\tteam := &model.Team{\n\t\t\tID: teamID,\n\t\t\tSettings: map[string]interface{}{\n\t\t\t\t\"field1\": \"A\",\n\t\t\t},\n\t\t}\n\n\t\t// insert\n\t\terr := store.UpsertTeamSettings(*team)\n\t\trequire.NoError(t, err)\n\n\t\tgot, err := store.GetTeam(teamID)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, team.ID, got.ID)\n\t\trequire.Equal(t, team.Settings, got.Settings)\n\n\t\t// update settings\n\t\tteam.Settings = map[string]interface{}{\n\t\t\t\"field1\": \"B\",\n\t\t}\n\t\terr = store.UpsertTeamSettings(*team)\n\t\trequire.NoError(t, err)\n\n\t\tgot2, err := store.GetTeam(teamID)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, team.ID, got2.ID)\n\t\trequire.Equal(t, team.Settings, got2.Settings)\n\t\trequire.Equal(t, got.SignupToken, got2.SignupToken)\n\t})\n}\n\nfunc testGetAllTeams(t *testing.T, store store.Store) {\n\tt.Run(\"No teams response\", func(t *testing.T) {\n\t\tgot, err := store.GetAllTeams()\n\t\trequire.NoError(t, err)\n\t\trequire.Empty(t, got)\n\t})\n\n\tt.Run(\"Insert multiple team and get all teams\", func(t *testing.T) {\n\t\t// insert\n\t\tteamCount := 10\n\t\tfor i := 0; i < teamCount; i++ {\n\t\t\tteamID := fmt.Sprintf(\"%d\", i)\n\t\t\tteam := &model.Team{\n\t\t\t\tID:          teamID,\n\t\t\t\tSignupToken: utils.NewID(utils.IDTypeToken),\n\t\t\t}\n\n\t\t\terr := store.UpsertTeamSignupToken(*team)\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\tgot, err := store.GetAllTeams()\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, got, teamCount)\n\t})\n}\n"
  },
  {
    "path": "server/services/store/storetests/users.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage storetests\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/store\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n)\n\n//nolint:dupl\nfunc StoreTestUserStore(t *testing.T, setup func(t *testing.T) (store.Store, func())) {\n\tt.Run(\"GetUsersByTeam\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestGetUsersByTeam(t, store)\n\t})\n\n\tt.Run(\"CreateAndGetUser\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestCreateAndGetUser(t, store)\n\t})\n\n\tt.Run(\"GetUsersList\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestGetUsersList(t, store)\n\t})\n\n\tt.Run(\"CreateAndUpdateUser\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestCreateAndUpdateUser(t, store)\n\t})\n\n\tt.Run(\"CreateAndGetRegisteredUserCount\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestCreateAndGetRegisteredUserCount(t, store)\n\t})\n\n\tt.Run(\"TestPatchUserProps\", func(t *testing.T) {\n\t\tstore, tearDown := setup(t)\n\t\tdefer tearDown()\n\t\ttestPatchUserProps(t, store)\n\t})\n}\n\nfunc testGetUsersByTeam(t *testing.T, store store.Store) {\n\tt.Run(\"GetTeamUsers\", func(t *testing.T) {\n\t\tusers, err := store.GetUsersByTeam(\"team_1\", \"\", false, false)\n\t\trequire.Equal(t, 0, len(users))\n\t\trequire.NoError(t, err)\n\n\t\tuserID := utils.NewID(utils.IDTypeUser)\n\n\t\tuser, err := store.CreateUser(&model.User{\n\t\t\tID:       userID,\n\t\t\tUsername: \"darth.vader\",\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, user)\n\t\trequire.Equal(t, userID, user.ID)\n\t\trequire.Equal(t, \"darth.vader\", user.Username)\n\n\t\tdefer func() {\n\t\t\t_, _ = store.UpdateUser(&model.User{\n\t\t\t\tID:       userID,\n\t\t\t\tDeleteAt: utils.GetMillis(),\n\t\t\t})\n\t\t}()\n\n\t\tusers, err = store.GetUsersByTeam(\"team_1\", \"\", false, false)\n\t\trequire.Equal(t, 1, len(users))\n\t\trequire.Equal(t, \"darth.vader\", users[0].Username)\n\t\trequire.NoError(t, err)\n\t})\n}\n\nfunc testCreateAndGetUser(t *testing.T, store store.Store) {\n\tuser := &model.User{\n\t\tID:       utils.NewID(utils.IDTypeUser),\n\t\tUsername: \"damao\",\n\t\tEmail:    \"mock@email.com\",\n\t}\n\n\tt.Run(\"CreateUser\", func(t *testing.T) {\n\t\tnewUser, err := store.CreateUser(user)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, newUser)\n\t})\n\n\tt.Run(\"GetUserByID\", func(t *testing.T) {\n\t\tgot, err := store.GetUserByID(user.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, user.ID, got.ID)\n\t\trequire.Equal(t, user.Username, got.Username)\n\t\trequire.Equal(t, user.Email, got.Email)\n\t})\n\n\tt.Run(\"GetUserByID nonexistent\", func(t *testing.T) {\n\t\tgot, err := store.GetUserByID(\"nonexistent-id\")\n\t\tvar nf *model.ErrNotFound\n\t\trequire.ErrorAs(t, err, &nf)\n\t\trequire.Nil(t, got)\n\t})\n\n\tt.Run(\"GetUserByUsername\", func(t *testing.T) {\n\t\tgot, err := store.GetUserByUsername(user.Username)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, user.ID, got.ID)\n\t\trequire.Equal(t, user.Username, got.Username)\n\t\trequire.Equal(t, user.Email, got.Email)\n\t})\n\n\tt.Run(\"GetUserByUsername nonexistent\", func(t *testing.T) {\n\t\tgot, err := store.GetUserByID(\"nonexistent-username\")\n\t\tvar nf *model.ErrNotFound\n\t\trequire.ErrorAs(t, err, &nf)\n\t\trequire.Nil(t, got)\n\t})\n\n\tt.Run(\"GetUserByEmail\", func(t *testing.T) {\n\t\tgot, err := store.GetUserByEmail(user.Email)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, user.ID, got.ID)\n\t\trequire.Equal(t, user.Username, got.Username)\n\t\trequire.Equal(t, user.Email, got.Email)\n\t})\n\n\tt.Run(\"GetUserByEmail nonexistent\", func(t *testing.T) {\n\t\tgot, err := store.GetUserByID(\"nonexistent-email\")\n\t\tvar nf *model.ErrNotFound\n\t\trequire.ErrorAs(t, err, &nf)\n\t\trequire.Nil(t, got)\n\t})\n}\n\nfunc testGetUsersList(t *testing.T, store store.Store) {\n\tfor _, id := range []string{\"user1\", \"user2\"} {\n\t\tuser := &model.User{\n\t\t\tID:       id,\n\t\t\tUsername: fmt.Sprintf(\"%s-username\", id),\n\t\t\tEmail:    fmt.Sprintf(\"%s@sample.com\", id),\n\t\t}\n\t\tnewUser, err := store.CreateUser(user)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, newUser)\n\t}\n\n\ttestCases := []struct {\n\t\tName          string\n\t\tUserIDs       []string\n\t\tExpectedError bool\n\t\tExpectedIDs   []string\n\t}{\n\t\t{\n\t\t\tName:          \"all of the IDs are found\",\n\t\t\tUserIDs:       []string{\"user1\", \"user2\"},\n\t\t\tExpectedError: false,\n\t\t\tExpectedIDs:   []string{\"user1\", \"user2\"},\n\t\t},\n\t\t{\n\t\t\tName:          \"some of the IDs are found\",\n\t\t\tUserIDs:       []string{\"user2\", \"non-existent\"},\n\t\t\tExpectedError: true,\n\t\t\tExpectedIDs:   []string{\"user2\"},\n\t\t},\n\t\t{\n\t\t\tName:          \"none of the IDs are found\",\n\t\t\tUserIDs:       []string{\"non-existent-1\", \"non-existent-2\"},\n\t\t\tExpectedError: true,\n\t\t\tExpectedIDs:   []string{},\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\tusers, err := store.GetUsersList(tc.UserIDs, false, false)\n\t\t\tif tc.ExpectedError {\n\t\t\t\trequire.Error(t, err)\n\t\t\t\trequire.True(t, model.IsErrNotFound(err))\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\n\t\t\tuserIDs := []string{}\n\t\t\tfor _, user := range users {\n\t\t\t\tuserIDs = append(userIDs, user.ID)\n\t\t\t}\n\t\t\trequire.ElementsMatch(t, tc.ExpectedIDs, userIDs)\n\t\t})\n\t}\n}\n\nfunc testCreateAndUpdateUser(t *testing.T, store store.Store) {\n\tuser := &model.User{\n\t\tID: utils.NewID(utils.IDTypeUser),\n\t}\n\tnewUser, err := store.CreateUser(user)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, newUser)\n\n\tt.Run(\"UpdateUser\", func(t *testing.T) {\n\t\tuser.Username = \"damao\"\n\t\tuser.Email = \"mock@email.com\"\n\t\tuUser, err := store.UpdateUser(user)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, uUser)\n\t\trequire.Equal(t, user.Username, uUser.Username)\n\t\trequire.Equal(t, user.Email, uUser.Email)\n\n\t\tgot, err := store.GetUserByID(user.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, user.ID, got.ID)\n\t\trequire.Equal(t, user.Username, got.Username)\n\t\trequire.Equal(t, user.Email, got.Email)\n\t})\n\n\tt.Run(\"UpdateUserPassword\", func(t *testing.T) {\n\t\tnewPassword := utils.NewID(utils.IDTypeNone)\n\t\terr := store.UpdateUserPassword(user.Username, newPassword)\n\t\trequire.NoError(t, err)\n\n\t\tgot, err := store.GetUserByUsername(user.Username)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, user.Username, got.Username)\n\t\trequire.Equal(t, newPassword, got.Password)\n\t})\n\n\tt.Run(\"UpdateUserPasswordByID\", func(t *testing.T) {\n\t\tnewPassword := utils.NewID(utils.IDTypeNone)\n\t\terr := store.UpdateUserPasswordByID(user.ID, newPassword)\n\t\trequire.NoError(t, err)\n\n\t\tgot, err := store.GetUserByID(user.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, user.ID, got.ID)\n\t\trequire.Equal(t, newPassword, got.Password)\n\t})\n}\n\nfunc testCreateAndGetRegisteredUserCount(t *testing.T, store store.Store) {\n\trandomN := int(time.Now().Unix() % 10)\n\tfor i := 0; i < randomN; i++ {\n\t\tuser, err := store.CreateUser(&model.User{\n\t\t\tID: utils.NewID(utils.IDTypeUser),\n\t\t})\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, user)\n\t}\n\n\tgot, err := store.GetRegisteredUserCount()\n\trequire.NoError(t, err)\n\trequire.Equal(t, randomN, got)\n}\n\nfunc testPatchUserProps(t *testing.T, store store.Store) {\n\tuser := &model.User{\n\t\tID: utils.NewID(utils.IDTypeUser),\n\t}\n\tnewUser, err := store.CreateUser(user)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, newUser)\n\n\tkey1 := \"new_key_1\"\n\tkey2 := \"new_key_2\"\n\tkey3 := \"new_key_3\"\n\n\t// Only update props\n\tpatch := model.UserPreferencesPatch{\n\t\tUpdatedFields: map[string]string{\n\t\t\tkey1: \"new_value_1\",\n\t\t\tkey2: \"new_value_2\",\n\t\t\tkey3: \"new_value_3\",\n\t\t},\n\t}\n\n\tuserPreferences, err := store.PatchUserPreferences(user.ID, patch)\n\trequire.NoError(t, err)\n\trequire.Equal(t, 3, len(userPreferences))\n\n\tfor _, preference := range userPreferences {\n\t\tswitch preference.Name {\n\t\tcase key1:\n\t\t\trequire.Equal(t, \"new_value_1\", preference.Value)\n\t\tcase key2:\n\t\t\trequire.Equal(t, \"new_value_2\", preference.Value)\n\t\tcase key3:\n\t\t\trequire.Equal(t, \"new_value_3\", preference.Value)\n\t\t}\n\t}\n\n\t// Delete a prop\n\tpatch = model.UserPreferencesPatch{\n\t\tDeletedFields: []string{\n\t\t\tkey1,\n\t\t},\n\t}\n\n\tuserPreferences, err = store.PatchUserPreferences(user.ID, patch)\n\trequire.NoError(t, err)\n\n\tfor _, preference := range userPreferences {\n\t\tswitch preference.Name {\n\t\tcase key1:\n\t\t\tt.Errorf(\"new_key_1 shouldn't exist in user preference as we just deleted it\")\n\t\tcase key2:\n\t\t\trequire.Equal(t, \"new_value_2\", preference.Value)\n\t\tcase key3:\n\t\t\trequire.Equal(t, \"new_value_3\", preference.Value)\n\t\t}\n\t}\n\n\t// update and delete together\n\tpatch = model.UserPreferencesPatch{\n\t\tUpdatedFields: map[string]string{\n\t\t\tkey3: \"new_value_3_new_again\",\n\t\t},\n\t\tDeletedFields: []string{\n\t\t\tkey2,\n\t\t},\n\t}\n\tuserPreferences, err = store.PatchUserPreferences(user.ID, patch)\n\trequire.NoError(t, err)\n\n\tfor _, preference := range userPreferences {\n\t\tswitch preference.Name {\n\t\tcase key1:\n\t\t\tt.Errorf(\"new_key_1 shouldn't exist in user preference as we just deleted it\")\n\t\tcase key2:\n\t\t\tt.Errorf(\"new_key_2 shouldn't exist in user preference as we just deleted it\")\n\t\tcase key3:\n\t\t\trequire.Equal(t, \"new_value_3_new_again\", preference.Value)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "server/services/store/storetests/util.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage storetests\n\nimport (\n\t\"fmt\"\n\t\"sort\"\n\t\"testing\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/store\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc createTestUsers(t *testing.T, store store.Store, num int) []*model.User {\n\tvar users []*model.User\n\tfor i := 0; i < num; i++ {\n\t\tuser := &model.User{\n\t\t\tID:       utils.NewID(utils.IDTypeUser),\n\t\t\tUsername: fmt.Sprintf(\"mooncake.%d\", i),\n\t\t\tEmail:    fmt.Sprintf(\"mooncake.%d@example.com\", i),\n\t\t}\n\t\tnewUser, err := store.CreateUser(user)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, newUser)\n\n\t\tusers = append(users, user)\n\t}\n\treturn users\n}\n\nfunc createTestBlocks(t *testing.T, store store.Store, userID string, num int) []*model.Block {\n\tvar blocks []*model.Block\n\tfor i := 0; i < num; i++ {\n\t\tblock := &model.Block{\n\t\t\tID:        utils.NewID(utils.IDTypeBlock),\n\t\t\tBoardID:   utils.NewID(utils.IDTypeBoard),\n\t\t\tType:      model.TypeCard,\n\t\t\tCreatedBy: userID,\n\t\t}\n\t\terr := store.InsertBlock(block, userID)\n\t\trequire.NoError(t, err)\n\n\t\tblocks = append(blocks, block)\n\t}\n\treturn blocks\n}\n\nfunc createTestBlocksForCard(t *testing.T, store store.Store, cardID string, num int) []*model.Block {\n\tcard, err := store.GetBlock(cardID)\n\trequire.NoError(t, err)\n\tassert.EqualValues(t, model.TypeCard, card.Type)\n\n\tvar blocks []*model.Block\n\tfor i := 0; i < num; i++ {\n\t\tblock := &model.Block{\n\t\t\tID:        utils.NewID(utils.IDTypeBlock),\n\t\t\tBoardID:   card.BoardID,\n\t\t\tType:      model.TypeText,\n\t\t\tCreatedBy: card.CreatedBy,\n\t\t\tParentID:  card.ID,\n\t\t\tTitle:     fmt.Sprintf(\"text %d\", i),\n\t\t}\n\t\terr := store.InsertBlock(block, card.CreatedBy)\n\t\trequire.NoError(t, err)\n\n\t\tblocks = append(blocks, block)\n\t}\n\treturn blocks\n}\n\n//nolint:unparam\nfunc createTestCards(t *testing.T, store store.Store, userID string, boardID string, num int) []*model.Block {\n\tvar blocks []*model.Block\n\tfor i := 0; i < num; i++ {\n\t\tblock := &model.Block{\n\t\t\tID:        utils.NewID(utils.IDTypeCard),\n\t\t\tBoardID:   boardID,\n\t\t\tParentID:  boardID,\n\t\t\tType:      model.TypeCard,\n\t\t\tCreatedBy: userID,\n\t\t\tTitle:     fmt.Sprintf(\"card %d\", i),\n\t\t}\n\t\terr := store.InsertBlock(block, userID)\n\t\trequire.NoError(t, err)\n\n\t\tblocks = append(blocks, block)\n\t}\n\treturn blocks\n}\n\n//nolint:unparam\nfunc createTestBoards(t *testing.T, store store.Store, teamID string, userID string, num int) []*model.Board {\n\tvar boards []*model.Board\n\tfor i := 0; i < num; i++ {\n\t\tboard := &model.Board{\n\t\t\tID:        utils.NewID(utils.IDTypeBoard),\n\t\t\tTeamID:    teamID,\n\t\t\tType:      \"O\",\n\t\t\tCreatedBy: userID,\n\t\t\tTitle:     fmt.Sprintf(\"board %d\", i),\n\t\t}\n\t\tboardNew, err := store.InsertBoard(board, userID)\n\t\trequire.NoError(t, err)\n\n\t\tboards = append(boards, boardNew)\n\t}\n\treturn boards\n}\n\n//nolint:unparam\nfunc deleteTestBoard(t *testing.T, store store.Store, boardID string, userID string) {\n\terr := store.DeleteBoard(boardID, userID)\n\trequire.NoError(t, err)\n}\n\n// extractIDs is a test helper that extracts a sorted slice of IDs from slices of various struct types.\n// Might have used generics here except that would require implementing a `GetID` method on each type.\nfunc extractIDs(t *testing.T, arr ...any) []string {\n\tids := make([]string, 0)\n\n\tfor _, item := range arr {\n\t\tif item == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tswitch tarr := item.(type) {\n\t\tcase []*model.Board:\n\t\t\tfor _, b := range tarr {\n\t\t\t\tif b != nil {\n\t\t\t\t\tids = append(ids, b.ID)\n\t\t\t\t}\n\t\t\t}\n\t\tcase []*model.BoardHistory:\n\t\t\tfor _, bh := range tarr {\n\t\t\t\tids = append(ids, bh.ID)\n\t\t\t}\n\t\tcase []*model.Block:\n\t\t\tfor _, b := range tarr {\n\t\t\t\tif b != nil {\n\t\t\t\t\tids = append(ids, b.ID)\n\t\t\t\t}\n\t\t\t}\n\t\tcase []*model.BlockHistory:\n\t\t\tfor _, bh := range tarr {\n\t\t\t\tids = append(ids, bh.ID)\n\t\t\t}\n\t\tdefault:\n\t\t\tt.Errorf(\"unsupported type %T extracting board ID\", item)\n\t\t}\n\t}\n\n\t// sort the ids to make it easier to compare lists of ids visually.\n\tsort.Strings(ids)\n\treturn ids\n}\n"
  },
  {
    "path": "server/services/telemetry/mocks/ServerIface.go",
    "content": "// Code generated by mockery v1.0.0. DO NOT EDIT.\n\n// Regenerate this file using `make telemetry-mocks`.\n\npackage mocks\n\nimport (\n\thttpservice \"github.com/mattermost/mattermost/server/v8/platform/services/httpservice\"\n\tmock \"github.com/stretchr/testify/mock\"\n\n\tmodel \"github.com/mattermost/mattermost/server/public/model\"\n\n\tplugin \"github.com/mattermost/mattermost/server/public/plugin\"\n)\n\n// ServerIface is an autogenerated mock type for the ServerIface type\ntype ServerIface struct {\n\tmock.Mock\n}\n\n// Config provides a mock function with given fields:\nfunc (_m *ServerIface) Config() *model.Config {\n\tret := _m.Called()\n\n\tvar r0 *model.Config\n\tif rf, ok := ret.Get(0).(func() *model.Config); ok {\n\t\tr0 = rf()\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(*model.Config)\n\t\t}\n\t}\n\n\treturn r0\n}\n\n// GetPluginsEnvironment provides a mock function with given fields:\nfunc (_m *ServerIface) GetPluginsEnvironment() *plugin.Environment {\n\tret := _m.Called()\n\n\tvar r0 *plugin.Environment\n\tif rf, ok := ret.Get(0).(func() *plugin.Environment); ok {\n\t\tr0 = rf()\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(*plugin.Environment)\n\t\t}\n\t}\n\n\treturn r0\n}\n\n// GetRoleByName provides a mock function with given fields: _a0\nfunc (_m *ServerIface) GetRoleByName(_a0 string) (*model.Role, *model.AppError) {\n\tret := _m.Called(_a0)\n\n\tvar r0 *model.Role\n\tif rf, ok := ret.Get(0).(func(string) *model.Role); ok {\n\t\tr0 = rf(_a0)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(*model.Role)\n\t\t}\n\t}\n\n\tvar r1 *model.AppError\n\tif rf, ok := ret.Get(1).(func(string) *model.AppError); ok {\n\t\tr1 = rf(_a0)\n\t} else {\n\t\tif ret.Get(1) != nil {\n\t\t\tr1 = ret.Get(1).(*model.AppError)\n\t\t}\n\t}\n\n\treturn r0, r1\n}\n\n// GetSchemes provides a mock function with given fields: _a0, _a1, _a2\nfunc (_m *ServerIface) GetSchemes(_a0 string, _a1 int, _a2 int) ([]*model.Scheme, *model.AppError) {\n\tret := _m.Called(_a0, _a1, _a2)\n\n\tvar r0 []*model.Scheme\n\tif rf, ok := ret.Get(0).(func(string, int, int) []*model.Scheme); ok {\n\t\tr0 = rf(_a0, _a1, _a2)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).([]*model.Scheme)\n\t\t}\n\t}\n\n\tvar r1 *model.AppError\n\tif rf, ok := ret.Get(1).(func(string, int, int) *model.AppError); ok {\n\t\tr1 = rf(_a0, _a1, _a2)\n\t} else {\n\t\tif ret.Get(1) != nil {\n\t\t\tr1 = ret.Get(1).(*model.AppError)\n\t\t}\n\t}\n\n\treturn r0, r1\n}\n\n// HttpService provides a mock function with given fields:\nfunc (_m *ServerIface) HttpService() httpservice.HTTPService {\n\tret := _m.Called()\n\n\tvar r0 httpservice.HTTPService\n\tif rf, ok := ret.Get(0).(func() httpservice.HTTPService); ok {\n\t\tr0 = rf()\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(httpservice.HTTPService)\n\t\t}\n\t}\n\n\treturn r0\n}\n\n// IsLeader provides a mock function with given fields:\nfunc (_m *ServerIface) IsLeader() bool {\n\tret := _m.Called()\n\n\tvar r0 bool\n\tif rf, ok := ret.Get(0).(func() bool); ok {\n\t\tr0 = rf()\n\t} else {\n\t\tr0 = ret.Get(0).(bool)\n\t}\n\n\treturn r0\n}\n\n// License provides a mock function with given fields:\nfunc (_m *ServerIface) License() *model.License {\n\tret := _m.Called()\n\n\tvar r0 *model.License\n\tif rf, ok := ret.Get(0).(func() *model.License); ok {\n\t\tr0 = rf()\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(*model.License)\n\t\t}\n\t}\n\n\treturn r0\n}\n"
  },
  {
    "path": "server/services/telemetry/telemetry.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage telemetry\n\nimport (\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/scheduler\"\n\trudder \"github.com/rudderlabs/analytics-go\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n\t\"github.com/mattermost/mattermost/server/v8/channels/utils\"\n)\n\nconst (\n\trudderKey                  = \"placeholder_rudder_key\"\n\trudderDataplaneURL         = \"placeholder_rudder_dataplane_url\"\n\ttimeBetweenTelemetryChecks = 10 * time.Minute\n)\n\ntype TrackerFunc func() (Tracker, error)\n\ntype Tracker map[string]interface{}\n\ntype Service struct {\n\ttrackers                   map[string]TrackerFunc\n\tlogger                     mlog.LoggerIFace\n\trudderClient               rudder.Client\n\ttelemetryID                string\n\ttimestampLastTelemetrySent time.Time\n}\n\ntype RudderConfig struct {\n\tRudderKey    string\n\tDataplaneURL string\n}\n\nfunc New(telemetryID string, logger mlog.LoggerIFace) *Service {\n\tservice := &Service{\n\t\tlogger:      logger,\n\t\ttelemetryID: telemetryID,\n\t\ttrackers:    map[string]TrackerFunc{},\n\t}\n\n\treturn service\n}\n\nfunc (ts *Service) RegisterTracker(name string, f TrackerFunc) {\n\tts.trackers[name] = f\n}\n\nfunc (ts *Service) getRudderConfig() RudderConfig {\n\tif !strings.Contains(rudderKey, \"placeholder\") && !strings.Contains(rudderDataplaneURL, \"placeholder\") {\n\t\treturn RudderConfig{rudderKey, rudderDataplaneURL}\n\t}\n\tif os.Getenv(\"RUDDER_KEY\") != \"\" && os.Getenv(\"RUDDER_DATAPLANE_URL\") != \"\" {\n\t\treturn RudderConfig{os.Getenv(\"RUDDER_KEY\"), os.Getenv(\"RUDDER_DATAPLANE_URL\")}\n\t}\n\treturn RudderConfig{}\n}\n\nfunc (ts *Service) sendDailyTelemetry(override bool) {\n\tconfig := ts.getRudderConfig()\n\tif (config.DataplaneURL != \"\" && config.RudderKey != \"\") || override {\n\t\tts.initRudder(config.DataplaneURL, config.RudderKey)\n\n\t\tfor name, tracker := range ts.trackers {\n\t\t\tm, err := tracker()\n\t\t\tif err != nil {\n\t\t\t\tts.logger.Error(\"Error fetching telemetry data\", mlog.String(\"name\", name), mlog.Err(err))\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tts.sendTelemetry(name, m)\n\t\t}\n\t}\n}\n\nfunc (ts *Service) sendTelemetry(event string, properties map[string]interface{}) {\n\tif ts.rudderClient != nil {\n\t\tvar context *rudder.Context\n\t\t_ = ts.rudderClient.Enqueue(rudder.Track{\n\t\t\tEvent:      event,\n\t\t\tUserId:     ts.telemetryID,\n\t\t\tProperties: properties,\n\t\t\tContext:    context,\n\t\t})\n\t}\n}\n\nfunc (ts *Service) initRudder(endpoint, rudderKey string) {\n\tif ts.rudderClient == nil {\n\t\tconfig := rudder.Config{}\n\t\tconfig.Logger = rudder.StdLogger(ts.logger.StdLogger(model.LvlFBTelemetry))\n\t\tconfig.Endpoint = endpoint\n\t\t// For testing\n\t\tif endpoint != rudderDataplaneURL {\n\t\t\tconfig.Verbose = true\n\t\t\tconfig.BatchSize = 1\n\t\t}\n\t\tclient, err := rudder.NewWithConfig(rudderKey, endpoint, config)\n\t\tif err != nil {\n\t\t\tts.logger.Fatal(\"Failed to create Rudder instance\")\n\t\t\treturn\n\t\t}\n\t\t_ = client.Enqueue(rudder.Identify{\n\t\t\tUserId: ts.telemetryID,\n\t\t})\n\n\t\tts.rudderClient = client\n\t}\n}\n\nfunc (ts *Service) doTelemetryIfNeeded(firstRun time.Time) {\n\thoursSinceFirstServerRun := time.Since(firstRun).Hours()\n\n\t// Send once every 10 minutes for the first hour\n\tif hoursSinceFirstServerRun < 1 {\n\t\tts.doTelemetry()\n\t\treturn\n\t}\n\n\t// Send once every hour thereafter for the first 12 hours\n\tif hoursSinceFirstServerRun <= 12 && time.Since(ts.timestampLastTelemetrySent) >= time.Hour {\n\t\tts.doTelemetry()\n\t\treturn\n\t}\n\n\t// Send at the 24 hour mark and every 24 hours after\n\tif hoursSinceFirstServerRun > 12 && time.Since(ts.timestampLastTelemetrySent) >= 24*time.Hour {\n\t\tts.doTelemetry()\n\t\treturn\n\t}\n}\n\nfunc (ts *Service) RunTelemetryJob(firstRunMillis int64) {\n\t// Send on boot\n\tts.doTelemetry()\n\tscheduler.CreateRecurringTask(\"Telemetry\", func() {\n\t\tts.doTelemetryIfNeeded(utils.TimeFromMillis(firstRunMillis))\n\t}, timeBetweenTelemetryChecks)\n}\n\nfunc (ts *Service) doTelemetry() {\n\tts.timestampLastTelemetrySent = time.Now()\n\tts.sendDailyTelemetry(false)\n}\n\n// Shutdown closes the telemetry client.\nfunc (ts *Service) Shutdown() error {\n\tif ts.rudderClient != nil {\n\t\treturn ts.rudderClient.Close()\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "server/services/telemetry/telemetry_test.go",
    "content": "package telemetry\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc mockServer() (chan []byte, *httptest.Server) {\n\tdone := make(chan []byte, 1)\n\n\tserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tbuf := bytes.NewBuffer(nil)\n\t\tif _, err := io.Copy(buf, r.Body); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\tvar v interface{}\n\t\terr := json.Unmarshal(buf.Bytes(), &v)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\tb, err := json.MarshalIndent(v, \"\", \"  \")\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\t// filter the identify message\n\t\tif strings.Contains(string(b), `\"type\": \"identify\"`) {\n\t\t\treturn\n\t\t}\n\n\t\tdone <- b\n\t}))\n\n\treturn done, server\n}\n\nfunc TestTelemetry(t *testing.T) {\n\treceiveChan, server := mockServer()\n\n\tos.Setenv(\"RUDDER_KEY\", \"mock-test-rudder-key\")\n\tos.Setenv(\"RUDDER_DATAPLANE_URL\", server.URL)\n\n\tcheckMockRudderServer := func(t *testing.T) {\n\t\t// check mock rudder server got\n\t\tgot := string(<-receiveChan)\n\t\trequire.Contains(t, got, \"mockTrackerKey\")\n\t\trequire.Contains(t, got, \"mockTrackerValue\")\n\t}\n\n\tt.Run(\"Register tracker and run telemetry job\", func(t *testing.T) {\n\t\tservice := New(\"mockTelemetryID\", mlog.CreateConsoleTestLogger(t))\n\t\tservice.RegisterTracker(\"mockTracker\", func() (Tracker, error) {\n\t\t\treturn map[string]interface{}{\n\t\t\t\t\"mockTrackerKey\": \"mockTrackerValue\",\n\t\t\t}, nil\n\t\t})\n\n\t\tservice.RunTelemetryJob(time.Now().UnixNano() / int64(time.Millisecond))\n\t\tcheckMockRudderServer(t)\n\t})\n\n\tt.Run(\"do telemetry if needed\", func(t *testing.T) {\n\t\tservice := New(\"mockTelemetryID\", mlog.CreateConsoleTestLogger(t))\n\t\tservice.RegisterTracker(\"mockTracker\", func() (Tracker, error) {\n\t\t\treturn map[string]interface{}{\n\t\t\t\t\"mockTrackerKey\": \"mockTrackerValue\",\n\t\t\t}, nil\n\t\t})\n\n\t\tfirstRun := time.Now()\n\t\tt.Run(\"Send once every 10 minutes for the first hour\", func(t *testing.T) {\n\t\t\tservice.doTelemetryIfNeeded(firstRun.Add(-30 * time.Minute))\n\t\t\tcheckMockRudderServer(t)\n\t\t})\n\n\t\tt.Run(\"Send once every hour thereafter for the first 12 hours\", func(t *testing.T) {\n\t\t\t// firstRun is 2 hours ago and timestampLastTelemetrySent is hour ago\n\t\t\t// need to do telemetry\n\t\t\tservice.timestampLastTelemetrySent = time.Now().Add(-time.Hour)\n\t\t\tservice.doTelemetryIfNeeded(firstRun.Add(-2 * time.Hour))\n\t\t\tcheckMockRudderServer(t)\n\n\t\t\t// firstRun is 2 hours ago and timestampLastTelemetrySent is just now\n\t\t\t// no need to do telemetry\n\t\t\tservice.doTelemetryIfNeeded(firstRun.Add(-2 * time.Hour))\n\t\t\trequire.Equal(t, 0, len(receiveChan))\n\t\t})\n\t\tt.Run(\"Send at the 24 hour mark and every 24 hours after\", func(t *testing.T) {\n\t\t\t// firstRun is 24 hours ago and timestampLastTelemetrySent is 24 hours ago\n\t\t\t// need to do telemetry\n\t\t\tservice.timestampLastTelemetrySent = time.Now().Add(-24 * time.Hour)\n\t\t\tservice.doTelemetryIfNeeded(firstRun.Add(-24 * time.Hour))\n\t\t\tcheckMockRudderServer(t)\n\n\t\t\t// firstRun is 24 hours ago and timestampLastTelemetrySent is just now\n\t\t\t// no need to do telemetry\n\t\t\tservice.doTelemetryIfNeeded(firstRun.Add(-24 * time.Hour))\n\t\t\trequire.Equal(t, 0, len(receiveChan))\n\t\t})\n\t})\n}\n"
  },
  {
    "path": "server/services/webhook/webhook.go",
    "content": "package webhook\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/config\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\n// NotifyUpdate calls webhooks.\nfunc (wh *Client) NotifyUpdate(block *model.Block) {\n\tif len(wh.config.WebhookUpdate) < 1 {\n\t\treturn\n\t}\n\n\tjson, err := json.Marshal(block)\n\tif err != nil {\n\t\twh.logger.Fatal(\"NotifyUpdate: json.Marshal\", mlog.Err(err))\n\t}\n\tfor _, url := range wh.config.WebhookUpdate {\n\t\tresp, _ := http.Post(url, \"application/json\", bytes.NewBuffer(json)) //nolint:gosec\n\t\t_, _ = io.ReadAll(resp.Body)\n\t\tresp.Body.Close()\n\n\t\twh.logger.Debug(\"webhook.NotifyUpdate\", mlog.String(\"url\", url))\n\t}\n}\n\n// Client is a webhook client.\ntype Client struct {\n\tconfig *config.Configuration\n\tlogger mlog.LoggerIFace\n}\n\n// NewClient creates a new Client.\nfunc NewClient(config *config.Configuration, logger mlog.LoggerIFace) *Client {\n\treturn &Client{\n\t\tconfig: config,\n\t\tlogger: logger,\n\t}\n}\n"
  },
  {
    "path": "server/services/webhook/webhook_test.go",
    "content": "package webhook\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/services/config\"\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nfunc TestClientUpdateNotify(t *testing.T) {\n\tvar isNotified bool\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tisNotified = true\n\t}))\n\tdefer ts.Close()\n\n\tcfg := &config.Configuration{\n\t\tWebhookUpdate: []string{ts.URL},\n\t}\n\n\tlogger, _ := mlog.NewLogger()\n\tdefer func() {\n\t\terr := logger.Shutdown()\n\t\tassert.NoError(t, err)\n\t}()\n\n\tclient := NewClient(cfg, logger)\n\n\tclient.NotifyUpdate(&model.Block{})\n\n\tif !isNotified {\n\t\tt.Error(\"webhook url not be notified\")\n\t}\n}\n"
  },
  {
    "path": "server/swagger/README.md",
    "content": "# Swagger / OpenAPI 2.0 auto-generated files\n\n⚠️ **Warning:** The API is currently considered Beta and major changes are planned. Please [see this note](https://github.com/mattermost/focalboard/discussions/2139) for more details.\n\nThis folder is generated by the `make swagger` command from comments in the server code.\n\nPrerequisites:\n1. [go-swagger](https://goswagger.io/install.html)\n2. [openapi-generator](https://github.com/OpenAPITools/openapi-generator)\n\nThese can be installed via Homebrew:\n```\nbrew tap go-swagger/go-swagger\nbrew install go-swagger\nbrew install openapi-generator\n```\n\n# Server API documentation\n\nSee the generated [server API documentation here](https://htmlpreview.github.io/?https://github.com/mattermost/focalboard/blob/main/server/swagger/docs/html/index.html).\n\n# How to authenticate\n\nTo auth against Personal Server, first call login with your credentials to get a token, e.g.\n```\ncurl -X POST \\\n -H \"Accept: application/json\" \\\n -H \"X-Requested-With: XMLHttpRequest\" \\\n -H \"Content-Type: application/json\" \\\n \"http://localhost:8000/api/v2/login\" \\\n -d '{\n  \"type\" : \"normal\",\n  \"username\" : \"testuser\",\n  \"password\" : \"testpass\"\n}'\n```\n\nThis should return a token in the form:\n```\n{\"token\":\"abcdefghijklmnopqrstuvwxyz1\"}\n```\n\nPass this as the bearer auth to subsequent calls, e.g.\n```\ncurl -X GET \\\n -H \"Accept: application/json\" \\\n -H \"Authorization: Bearer abcdefghijklmnopqrstuvwxyz1\" \\\n -H \"X-Requested-With: XMLHttpRequest\" \\\n -H \"Content-Type: application/json\" \\\n \"http://localhost:8000/api/v2/teams/0/boards\"\n```\n\n# Differences for Mattermost Boards\n\nThe auto-generated Swagger API documentation is for Focalboard Personal Server. If you are calling the API on Mattermost Boards, the additional changes are:\n\n### API URLs endpoint\n\nThe API endpoint is at `https://SERVERNAME/plugins/focalboard/api/`, e.g. `https://community.mattermost.com/plugins/focalboard/api/`.\n\n### Use the Mattermost auth token\n\nRefer to the [Mattermost API documentation here](https://api.mattermost.com/#tag/authentication) on how to obtain the auth token.\n\nPass this token as a bearer token to the Boards APIs, e.g.\n\n```\ncurl -i -H \"X-Requested-With: XMLHttpRequest\" -H 'Authorization: Bearer abcdefghijklmnopqrstuvwxyz' https://community.mattermost.com/plugins/focalboard/api/v2/workspaces\n```\n\nNote that the `X-Requested-With: XMLHttpRequest` header is required to pass the CSRF check.\n\n# We want to hear from you!\n\nIf you are planning on using the Boards API, we would love to hear about what you'd like to do, and how we can improve the APIs in the future. [See here](https://github.com/mattermost/focalboard/discussions/2139) for more details on how to connect.\n"
  },
  {
    "path": "server/swagger/docs/html/.openapi-generator/VERSION",
    "content": "6.2.1"
  },
  {
    "path": "server/swagger/docs/html/.openapi-generator-ignore",
    "content": "# OpenAPI Generator Ignore\n# Generated by openapi-generator https://github.com/openapitools/openapi-generator\n\n# Use this file to prevent files from being overwritten by the generator.\n# The patterns follow closely to .gitignore or .dockerignore.\n\n# As an example, the C# client generator defines ApiClient.cs.\n# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:\n#ApiClient.cs\n\n# You can match any string of characters against a directory, file or extension with a single asterisk (*):\n#foo/*/qux\n# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux\n\n# You can recursively match patterns against a directory, file or extension with a double asterisk (**):\n#foo/**/qux\n# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux\n\n# You can also negate patterns with an exclamation (!).\n# For example, you can ignore all files in a docs folder with the file extension .md:\n#docs/*.md\n# Then explicitly reverse the ignore rule for a single file:\n#!docs/README.md\n"
  },
  {
    "path": "server/swagger/docs/html/index.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n  <title>Focalboard Server</title>\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n  <meta charset=\"UTF-8\" />\n  <script>\n/*! jQuery v3.1.0 | (c) jQuery Foundation | jquery.org/license */\n!function(a,b){\"use strict\";\"object\"==typeof module&&\"object\"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error(\"jQuery requires a window with a document\");return b(a)}:b(a)}(\"undefined\"!=typeof window?window:this,function(a,b){\"use strict\";var c=[],d=a.document,e=Object.getPrototypeOf,f=c.slice,g=c.concat,h=c.push,i=c.indexOf,j={},k=j.toString,l=j.hasOwnProperty,m=l.toString,n=m.call(Object),o={};function p(a,b){b=b||d;var c=b.createElement(\"script\");c.text=a,b.head.appendChild(c).parentNode.removeChild(c)}var q=\"3.1.0\",r=function(a,b){return new r.fn.init(a,b)},s=/^[\\s\\uFEFF\\xA0]+|[\\s\\uFEFF\\xA0]+$/g,t=/^-ms-/,u=/-([a-z])/g,v=function(a,b){return b.toUpperCase()};r.fn=r.prototype={jquery:q,constructor:r,length:0,toArray:function(){return f.call(this)},get:function(a){return null!=a?a<0?this[a+this.length]:this[a]:f.call(this)},pushStack:function(a){var b=r.merge(this.constructor(),a);return b.prevObject=this,b},each:function(a){return r.each(this,a)},map:function(a){return this.pushStack(r.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(f.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(a<0?b:0);return this.pushStack(c>=0&&c<b?[this[c]]:[])},end:function(){return this.prevObject||this.constructor()},push:h,sort:c.sort,splice:c.splice},r.extend=r.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for(\"boolean\"==typeof g&&(j=g,g=arguments[h]||{},h++),\"object\"==typeof g||r.isFunction(g)||(g={}),h===i&&(g=this,h--);h<i;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(r.isPlainObject(d)||(e=r.isArray(d)))?(e?(e=!1,f=c&&r.isArray(c)?c:[]):f=c&&r.isPlainObject(c)?c:{},g[b]=r.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},r.extend({expando:\"jQuery\"+(q+Math.random()).replace(/\\D/g,\"\"),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return\"function\"===r.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){var b=r.type(a);return(\"number\"===b||\"string\"===b)&&!isNaN(a-parseFloat(a))},isPlainObject:function(a){var b,c;return!(!a||\"[object Object]\"!==k.call(a))&&(!(b=e(a))||(c=l.call(b,\"constructor\")&&b.constructor,\"function\"==typeof c&&m.call(c)===n))},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+\"\":\"object\"==typeof a||\"function\"==typeof a?j[k.call(a)]||\"object\":typeof a},globalEval:function(a){p(a)},camelCase:function(a){return a.replace(t,\"ms-\").replace(u,v)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b){var c,d=0;if(w(a)){for(c=a.length;d<c;d++)if(b.call(a[d],d,a[d])===!1)break}else for(d in a)if(b.call(a[d],d,a[d])===!1)break;return a},trim:function(a){return null==a?\"\":(a+\"\").replace(s,\"\")},makeArray:function(a,b){var c=b||[];return null!=a&&(w(Object(a))?r.merge(c,\"string\"==typeof a?[a]:a):h.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:i.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;d<c;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;f<g;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,e,f=0,h=[];if(w(a))for(d=a.length;f<d;f++)e=b(a[f],f,c),null!=e&&h.push(e);else for(f in a)e=b(a[f],f,c),null!=e&&h.push(e);return g.apply([],h)},guid:1,proxy:function(a,b){var c,d,e;if(\"string\"==typeof b&&(c=a[b],b=a,a=c),r.isFunction(a))return d=f.call(arguments,2),e=function(){return a.apply(b||this,d.concat(f.call(arguments)))},e.guid=a.guid=a.guid||r.guid++,e},now:Date.now,support:o}),\"function\"==typeof Symbol&&(r.fn[Symbol.iterator]=c[Symbol.iterator]),r.each(\"Boolean Number String Function Array Date RegExp Object Error Symbol\".split(\" \"),function(a,b){j[\"[object \"+b+\"]\"]=b.toLowerCase()});function w(a){var b=!!a&&\"length\"in a&&a.length,c=r.type(a);return\"function\"!==c&&!r.isWindow(a)&&(\"array\"===c||0===b||\"number\"==typeof b&&b>0&&b-1 in a)}var x=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u=\"sizzle\"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=function(a,b){for(var c=0,d=a.length;c<d;c++)if(a[c]===b)return c;return-1},J=\"checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped\",K=\"[\\\\x20\\\\t\\\\r\\\\n\\\\f]\",L=\"(?:\\\\\\\\.|[\\\\w-]|[^\\0-\\\\xa0])+\",M=\"\\\\[\"+K+\"*(\"+L+\")(?:\"+K+\"*([*^$|!~]?=)\"+K+\"*(?:'((?:\\\\\\\\.|[^\\\\\\\\'])*)'|\\\"((?:\\\\\\\\.|[^\\\\\\\\\\\"])*)\\\"|(\"+L+\"))|)\"+K+\"*\\\\]\",N=\":(\"+L+\")(?:\\\\((('((?:\\\\\\\\.|[^\\\\\\\\'])*)'|\\\"((?:\\\\\\\\.|[^\\\\\\\\\\\"])*)\\\")|((?:\\\\\\\\.|[^\\\\\\\\()[\\\\]]|\"+M+\")*)|.*)\\\\)|)\",O=new RegExp(K+\"+\",\"g\"),P=new RegExp(\"^\"+K+\"+|((?:^|[^\\\\\\\\])(?:\\\\\\\\.)*)\"+K+\"+$\",\"g\"),Q=new RegExp(\"^\"+K+\"*,\"+K+\"*\"),R=new RegExp(\"^\"+K+\"*([>+~]|\"+K+\")\"+K+\"*\"),S=new RegExp(\"=\"+K+\"*([^\\\\]'\\\"]*?)\"+K+\"*\\\\]\",\"g\"),T=new RegExp(N),U=new RegExp(\"^\"+L+\"$\"),V={ID:new RegExp(\"^#(\"+L+\")\"),CLASS:new RegExp(\"^\\\\.(\"+L+\")\"),TAG:new RegExp(\"^(\"+L+\"|[*])\"),ATTR:new RegExp(\"^\"+M),PSEUDO:new RegExp(\"^\"+N),CHILD:new RegExp(\"^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\\\(\"+K+\"*(even|odd|(([+-]|)(\\\\d*)n|)\"+K+\"*(?:([+-]|)\"+K+\"*(\\\\d+)|))\"+K+\"*\\\\)|)\",\"i\"),bool:new RegExp(\"^(?:\"+J+\")$\",\"i\"),needsContext:new RegExp(\"^\"+K+\"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\\\(\"+K+\"*((?:-\\\\d)?\\\\d*)\"+K+\"*\\\\)|)(?=[^-]|$)\",\"i\")},W=/^(?:input|select|textarea|button)$/i,X=/^h\\d$/i,Y=/^[^{]+\\{\\s*\\[native \\w/,Z=/^(?:#([\\w-]+)|(\\w+)|\\.([\\w-]+))$/,$=/[+~]/,_=new RegExp(\"\\\\\\\\([\\\\da-f]{1,6}\"+K+\"?|(\"+K+\")|.)\",\"ig\"),aa=function(a,b,c){var d=\"0x\"+b-65536;return d!==d||c?b:d<0?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ba=/([\\0-\\x1f\\x7f]|^-?\\d)|^-$|[^\\x80-\\uFFFF\\w-]/g,ca=function(a,b){return b?\"\\0\"===a?\"\\ufffd\":a.slice(0,-1)+\"\\\\\"+a.charCodeAt(a.length-1).toString(16)+\" \":\"\\\\\"+a},da=function(){m()},ea=ta(function(a){return a.disabled===!0},{dir:\"parentNode\",next:\"legend\"});try{G.apply(D=H.call(v.childNodes),v.childNodes),D[v.childNodes.length].nodeType}catch(fa){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s=b&&b.ownerDocument,w=b?b.nodeType:9;if(d=d||[],\"string\"!=typeof a||!a||1!==w&&9!==w&&11!==w)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==w&&(l=Z.exec(a)))if(f=l[1]){if(9===w){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(s&&(j=s.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(l[2])return G.apply(d,b.getElementsByTagName(a)),d;if((f=l[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+\" \"]&&(!q||!q.test(a))){if(1!==w)s=b,r=a;else if(\"object\"!==b.nodeName.toLowerCase()){(k=b.getAttribute(\"id\"))?k=k.replace(ba,ca):b.setAttribute(\"id\",k=u),o=g(a),h=o.length;while(h--)o[h]=\"#\"+k+\" \"+sa(o[h]);r=o.join(\",\"),s=$.test(a)&&qa(b.parentNode)||b}if(r)try{return G.apply(d,s.querySelectorAll(r)),d}catch(x){}finally{k===u&&b.removeAttribute(\"id\")}}}return i(a.replace(P,\"$1\"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+\" \")>d.cacheLength&&delete b[a.shift()],b[c+\" \"]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement(\"fieldset\");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split(\"|\"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&a.sourceIndex-b.sourceIndex;if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return\"input\"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return(\"input\"===c||\"button\"===c)&&b.type===a}}function oa(a){return function(b){return\"label\"in b&&b.disabled===a||\"form\"in b&&b.disabled===a||\"form\"in b&&b.disabled===!1&&(b.isDisabled===a||b.isDisabled!==!a&&(\"label\"in b||!ea(b))!==a)}}function pa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function qa(a){return a&&\"undefined\"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return!!b&&\"HTML\"!==b.nodeName},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),v!==n&&(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener(\"unload\",da,!1):e.attachEvent&&e.attachEvent(\"onunload\",da)),c.attributes=ja(function(a){return a.className=\"i\",!a.getAttribute(\"className\")}),c.getElementsByTagName=ja(function(a){return a.appendChild(n.createComment(\"\")),!a.getElementsByTagName(\"*\").length}),c.getElementsByClassName=Y.test(n.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if(\"undefined\"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}},d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){return a.getAttribute(\"id\")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){var c=\"undefined\"!=typeof a.getAttributeNode&&a.getAttributeNode(\"id\");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return\"undefined\"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if(\"*\"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){if(\"undefined\"!=typeof b.getElementsByClassName&&p)return b.getElementsByClassName(a)},r=[],q=[],(c.qsa=Y.test(n.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML=\"<a id='\"+u+\"'></a><select id='\"+u+\"-\\r\\\\' msallowcapture=''><option selected=''></option></select>\",a.querySelectorAll(\"[msallowcapture^='']\").length&&q.push(\"[*^$]=\"+K+\"*(?:''|\\\"\\\")\"),a.querySelectorAll(\"[selected]\").length||q.push(\"\\\\[\"+K+\"*(?:value|\"+J+\")\"),a.querySelectorAll(\"[id~=\"+u+\"-]\").length||q.push(\"~=\"),a.querySelectorAll(\":checked\").length||q.push(\":checked\"),a.querySelectorAll(\"a#\"+u+\"+*\").length||q.push(\".#.+[+~]\")}),ja(function(a){a.innerHTML=\"<a href='' disabled='disabled'></a><select disabled='disabled'><option/></select>\";var b=n.createElement(\"input\");b.setAttribute(\"type\",\"hidden\"),a.appendChild(b).setAttribute(\"name\",\"D\"),a.querySelectorAll(\"[name=d]\").length&&q.push(\"name\"+K+\"*[*^$|!~]?=\"),2!==a.querySelectorAll(\":enabled\").length&&q.push(\":enabled\",\":disabled\"),o.appendChild(a).disabled=!0,2!==a.querySelectorAll(\":disabled\").length&&q.push(\":enabled\",\":disabled\"),a.querySelectorAll(\"*,:x\"),q.push(\",.*:\")})),(c.matchesSelector=Y.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,\"*\"),s.call(a,\"[s!='']:x\"),r.push(\"!=\",N)}),q=q.length&&new RegExp(q.join(\"|\")),r=r.length&&new RegExp(r.join(\"|\")),b=Y.test(o.compareDocumentPosition),t=b||Y.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?I(k,a)-I(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?I(k,a)-I(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?la(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(S,\"='$1']\"),c.matchesSelector&&p&&!A[b+\" \"]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.escape=function(a){return(a+\"\").replace(ba,ca)},ga.error=function(a){throw new Error(\"Syntax error, unrecognized expression: \"+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c=\"\",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if(\"string\"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:V,attrHandle:{},find:{},relative:{\">\":{dir:\"parentNode\",first:!0},\" \":{dir:\"parentNode\"},\"+\":{dir:\"previousSibling\",first:!0},\"~\":{dir:\"previousSibling\"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(_,aa),a[3]=(a[3]||a[4]||a[5]||\"\").replace(_,aa),\"~=\"===a[2]&&(a[3]=\" \"+a[3]+\" \"),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),\"nth\"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*(\"even\"===a[3]||\"odd\"===a[3])),a[5]=+(a[7]+a[8]||\"odd\"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return V.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||\"\":c&&T.test(c)&&(b=g(c,!0))&&(b=c.indexOf(\")\",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(_,aa).toLowerCase();return\"*\"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+\" \"];return b||(b=new RegExp(\"(^|\"+K+\")\"+a+\"(\"+K+\"|$)\"))&&y(a,function(a){return b.test(\"string\"==typeof a.className&&a.className||\"undefined\"!=typeof a.getAttribute&&a.getAttribute(\"class\")||\"\")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?\"!=\"===b:!b||(e+=\"\",\"=\"===b?e===c:\"!=\"===b?e!==c:\"^=\"===b?c&&0===e.indexOf(c):\"*=\"===b?c&&e.indexOf(c)>-1:\"$=\"===b?c&&e.slice(-c.length)===c:\"~=\"===b?(\" \"+e.replace(O,\" \")+\" \").indexOf(c)>-1:\"|=\"===b&&(e===c||e.slice(0,c.length+1)===c+\"-\"))}},CHILD:function(a,b,c,d,e){var f=\"nth\"!==a.slice(0,3),g=\"last\"!==a.slice(-4),h=\"of-type\"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?\"nextSibling\":\"previousSibling\",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p=\"only\"===a&&!o&&\"nextSibling\"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error(\"unsupported pseudo: \"+a);return e[u]?e(b):e.length>1?(c=[a,a,\"\",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=I(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(P,\"$1\"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(_,aa),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return U.test(a||\"\")||ga.error(\"unsupported lang: \"+a),a=a.replace(_,aa).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute(\"xml:lang\")||b.getAttribute(\"lang\"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+\"-\");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:oa(!1),disabled:oa(!0),checked:function(a){var b=a.nodeName.toLowerCase();return\"input\"===b&&!!a.checked||\"option\"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return X.test(a.nodeName)},input:function(a){return W.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return\"input\"===b&&\"button\"===a.type||\"button\"===b},text:function(a){var b;return\"input\"===a.nodeName.toLowerCase()&&\"text\"===a.type&&(null==(b=a.getAttribute(\"type\"))||\"text\"===b.toLowerCase())},first:pa(function(){return[0]}),last:pa(function(a,b){return[b-1]}),eq:pa(function(a,b,c){return[c<0?c+b:c]}),even:pa(function(a,b){for(var c=0;c<b;c+=2)a.push(c);return a}),odd:pa(function(a,b){for(var c=1;c<b;c+=2)a.push(c);return a}),lt:pa(function(a,b,c){for(var d=c<0?c+b:c;--d>=0;)a.push(d);return a}),gt:pa(function(a,b,c){for(var d=c<0?c+b:c;++d<b;)a.push(d);return a})}},d.pseudos.nth=d.pseudos.eq;for(b in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})d.pseudos[b]=ma(b);for(b in{submit:!0,reset:!0})d.pseudos[b]=na(b);function ra(){}ra.prototype=d.filters=d.pseudos,d.setFilters=new ra,g=ga.tokenize=function(a,b){var c,e,f,g,h,i,j,k=z[a+\" \"];if(k)return b?0:k.slice(0);h=a,i=[],j=d.preFilter;while(h){c&&!(e=Q.exec(h))||(e&&(h=h.slice(e[0].length)||h),i.push(f=[])),c=!1,(e=R.exec(h))&&(c=e.shift(),f.push({value:c,type:e[0].replace(P,\" \")}),h=h.slice(c.length));for(g in d.filter)!(e=V[g].exec(h))||j[g]&&!(e=j[g](e))||(c=e.shift(),f.push({value:c,type:g,matches:e}),h=h.slice(c.length));if(!c)break}return b?h.length:h?ga.error(a):z(a,i).slice(0)};function sa(a){for(var b=0,c=a.length,d=\"\";b<c;b++)d+=a[b].value;return d}function ta(a,b,c){var d=b.dir,e=b.next,f=e||d,g=c&&\"parentNode\"===f,h=x++;return b.first?function(b,c,e){while(b=b[d])if(1===b.nodeType||g)return a(b,c,e)}:function(b,c,i){var j,k,l,m=[w,h];if(i){while(b=b[d])if((1===b.nodeType||g)&&a(b,c,i))return!0}else while(b=b[d])if(1===b.nodeType||g)if(l=b[u]||(b[u]={}),k=l[b.uniqueID]||(l[b.uniqueID]={}),e&&e===b.nodeName.toLowerCase())b=b[d]||b;else{if((j=k[f])&&j[0]===w&&j[1]===h)return m[2]=j[2];if(k[f]=m,m[2]=a(b,c,i))return!0}}}function ua(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function va(a,b,c){for(var d=0,e=b.length;d<e;d++)ga(a,b[d],c);return c}function wa(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;h<i;h++)(f=a[h])&&(c&&!c(f,d,e)||(g.push(f),j&&b.push(h)));return g}function xa(a,b,c,d,e,f){return d&&!d[u]&&(d=xa(d)),e&&!e[u]&&(e=xa(e,f)),ia(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||va(b||\"*\",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:wa(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=wa(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?I(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=wa(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ya(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[\" \"],i=g?1:0,k=ta(function(a){return a===b},h,!0),l=ta(function(a){return I(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];i<f;i++)if(c=d.relative[a[i].type])m=[ta(ua(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;e<f;e++)if(d.relative[a[e].type])break;return xa(i>1&&ua(m),i>1&&sa(a.slice(0,i-1).concat({value:\" \"===a[i-2].type?\"*\":\"\"})).replace(P,\"$1\"),c,i<e&&ya(a.slice(i,e)),e<f&&ya(a=a.slice(e)),e<f&&sa(a))}m.push(c)}return ua(m)}function za(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s=\"0\",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG(\"*\",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=E.call(i));u=wa(u)}G.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&ga.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+\" \"];if(!f){b||(b=g(a)),c=b.length;while(c--)f=ya(b[c]),f[u]?d.push(f):e.push(f);f=A(a,za(e,d)),f.selector=a}return f},i=ga.select=function(a,b,e,f){var i,j,k,l,m,n=\"function\"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&\"ID\"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(_,aa),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=V.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(_,aa),$.test(j[0].type)&&qa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&sa(j),!a)return G.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,!b||$.test(a)&&qa(b.parentNode)||b),e},c.sortStable=u.split(\"\").sort(B).join(\"\")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement(\"fieldset\"))}),ja(function(a){return a.innerHTML=\"<a href='#'></a>\",\"#\"===a.firstChild.getAttribute(\"href\")})||ka(\"type|href|height|width\",function(a,b,c){if(!c)return a.getAttribute(b,\"type\"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML=\"<input/>\",a.firstChild.setAttribute(\"value\",\"\"),\"\"===a.firstChild.getAttribute(\"value\")})||ka(\"value\",function(a,b,c){if(!c&&\"input\"===a.nodeName.toLowerCase())return a.defaultValue}),ja(function(a){return null==a.getAttribute(\"disabled\")})||ka(J,function(a,b,c){var d;if(!c)return a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);r.find=x,r.expr=x.selectors,r.expr[\":\"]=r.expr.pseudos,r.uniqueSort=r.unique=x.uniqueSort,r.text=x.getText,r.isXMLDoc=x.isXML,r.contains=x.contains,r.escapeSelector=x.escape;var y=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&r(a).is(c))break;d.push(a)}return d},z=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},A=r.expr.match.needsContext,B=/^<([a-z][^\\/\\0>:\\x20\\t\\r\\n\\f]*)[\\x20\\t\\r\\n\\f]*\\/?>(?:<\\/\\1>|)$/i,C=/^.[^:#\\[\\.,]*$/;function D(a,b,c){if(r.isFunction(b))return r.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return r.grep(a,function(a){return a===b!==c});if(\"string\"==typeof b){if(C.test(b))return r.filter(b,a,c);b=r.filter(b,a)}return r.grep(a,function(a){return i.call(b,a)>-1!==c&&1===a.nodeType})}r.filter=function(a,b,c){var d=b[0];return c&&(a=\":not(\"+a+\")\"),1===b.length&&1===d.nodeType?r.find.matchesSelector(d,a)?[d]:[]:r.find.matches(a,r.grep(b,function(a){return 1===a.nodeType}))},r.fn.extend({find:function(a){var b,c,d=this.length,e=this;if(\"string\"!=typeof a)return this.pushStack(r(a).filter(function(){for(b=0;b<d;b++)if(r.contains(e[b],this))return!0}));for(c=this.pushStack([]),b=0;b<d;b++)r.find(a,e[b],c);return d>1?r.uniqueSort(c):c},filter:function(a){return this.pushStack(D(this,a||[],!1))},not:function(a){return this.pushStack(D(this,a||[],!0))},is:function(a){return!!D(this,\"string\"==typeof a&&A.test(a)?r(a):a||[],!1).length}});var E,F=/^(?:\\s*(<[\\w\\W]+>)[^>]*|#([\\w-]+))$/,G=r.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||E,\"string\"==typeof a){if(e=\"<\"===a[0]&&\">\"===a[a.length-1]&&a.length>=3?[null,a,null]:F.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof r?b[0]:b,r.merge(this,r.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),B.test(e[1])&&r.isPlainObject(b))for(e in b)r.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&(this[0]=f,this.length=1),this}return a.nodeType?(this[0]=a,this.length=1,this):r.isFunction(a)?void 0!==c.ready?c.ready(a):a(r):r.makeArray(a,this)};G.prototype=r.fn,E=r(d);var H=/^(?:parents|prev(?:Until|All))/,I={children:!0,contents:!0,next:!0,prev:!0};r.fn.extend({has:function(a){var b=r(a,this),c=b.length;return this.filter(function(){for(var a=0;a<c;a++)if(r.contains(this,b[a]))return!0})},closest:function(a,b){var c,d=0,e=this.length,f=[],g=\"string\"!=typeof a&&r(a);if(!A.test(a))for(;d<e;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&r.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?r.uniqueSort(f):f)},index:function(a){return a?\"string\"==typeof a?i.call(r(a),this[0]):i.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(r.uniqueSort(r.merge(this.get(),r(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function J(a,b){while((a=a[b])&&1!==a.nodeType);return a}r.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return y(a,\"parentNode\")},parentsUntil:function(a,b,c){return y(a,\"parentNode\",c)},next:function(a){return J(a,\"nextSibling\")},prev:function(a){return J(a,\"previousSibling\")},nextAll:function(a){return y(a,\"nextSibling\")},prevAll:function(a){return y(a,\"previousSibling\")},nextUntil:function(a,b,c){return y(a,\"nextSibling\",c)},prevUntil:function(a,b,c){return y(a,\"previousSibling\",c)},siblings:function(a){return z((a.parentNode||{}).firstChild,a)},children:function(a){return z(a.firstChild)},contents:function(a){return a.contentDocument||r.merge([],a.childNodes)}},function(a,b){r.fn[a]=function(c,d){var e=r.map(this,b,c);return\"Until\"!==a.slice(-5)&&(d=c),d&&\"string\"==typeof d&&(e=r.filter(d,e)),this.length>1&&(I[a]||r.uniqueSort(e),H.test(a)&&e.reverse()),this.pushStack(e)}});var K=/\\S+/g;function L(a){var b={};return r.each(a.match(K)||[],function(a,c){b[c]=!0}),b}r.Callbacks=function(a){a=\"string\"==typeof a?L(a):r.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h<f.length)f[h].apply(c[0],c[1])===!1&&a.stopOnFalse&&(h=f.length,c=!1)}a.memory||(c=!1),b=!1,e&&(f=c?[]:\"\")},j={add:function(){return f&&(c&&!b&&(h=f.length-1,g.push(c)),function d(b){r.each(b,function(b,c){r.isFunction(c)?a.unique&&j.has(c)||f.push(c):c&&c.length&&\"string\"!==r.type(c)&&d(c)})}(arguments),c&&!b&&i()),this},remove:function(){return r.each(arguments,function(a,b){var c;while((c=r.inArray(b,f,c))>-1)f.splice(c,1),c<=h&&h--}),this},has:function(a){return a?r.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c=\"\",this},disabled:function(){return!f},lock:function(){return e=g=[],c||b||(f=c=\"\"),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j};function M(a){return a}function N(a){throw a}function O(a,b,c){var d;try{a&&r.isFunction(d=a.promise)?d.call(a).done(b).fail(c):a&&r.isFunction(d=a.then)?d.call(a,b,c):b.call(void 0,a)}catch(a){c.call(void 0,a)}}r.extend({Deferred:function(b){var c=[[\"notify\",\"progress\",r.Callbacks(\"memory\"),r.Callbacks(\"memory\"),2],[\"resolve\",\"done\",r.Callbacks(\"once memory\"),r.Callbacks(\"once memory\"),0,\"resolved\"],[\"reject\",\"fail\",r.Callbacks(\"once memory\"),r.Callbacks(\"once memory\"),1,\"rejected\"]],d=\"pending\",e={state:function(){return d},always:function(){return f.done(arguments).fail(arguments),this},\"catch\":function(a){return e.then(null,a)},pipe:function(){var a=arguments;return r.Deferred(function(b){r.each(c,function(c,d){var e=r.isFunction(a[d[4]])&&a[d[4]];f[d[1]](function(){var a=e&&e.apply(this,arguments);a&&r.isFunction(a.promise)?a.promise().progress(b.notify).done(b.resolve).fail(b.reject):b[d[0]+\"With\"](this,e?[a]:arguments)})}),a=null}).promise()},then:function(b,d,e){var f=0;function g(b,c,d,e){return function(){var h=this,i=arguments,j=function(){var a,j;if(!(b<f)){if(a=d.apply(h,i),a===c.promise())throw new TypeError(\"Thenable self-resolution\");j=a&&(\"object\"==typeof a||\"function\"==typeof a)&&a.then,r.isFunction(j)?e?j.call(a,g(f,c,M,e),g(f,c,N,e)):(f++,j.call(a,g(f,c,M,e),g(f,c,N,e),g(f,c,M,c.notifyWith))):(d!==M&&(h=void 0,i=[a]),(e||c.resolveWith)(h,i))}},k=e?j:function(){try{j()}catch(a){r.Deferred.exceptionHook&&r.Deferred.exceptionHook(a,k.stackTrace),b+1>=f&&(d!==N&&(h=void 0,i=[a]),c.rejectWith(h,i))}};b?k():(r.Deferred.getStackHook&&(k.stackTrace=r.Deferred.getStackHook()),a.setTimeout(k))}}return r.Deferred(function(a){c[0][3].add(g(0,a,r.isFunction(e)?e:M,a.notifyWith)),c[1][3].add(g(0,a,r.isFunction(b)?b:M)),c[2][3].add(g(0,a,r.isFunction(d)?d:N))}).promise()},promise:function(a){return null!=a?r.extend(a,e):e}},f={};return r.each(c,function(a,b){var g=b[2],h=b[5];e[b[1]]=g.add,h&&g.add(function(){d=h},c[3-a][2].disable,c[0][2].lock),g.add(b[3].fire),f[b[0]]=function(){return f[b[0]+\"With\"](this===f?void 0:this,arguments),this},f[b[0]+\"With\"]=g.fireWith}),e.promise(f),b&&b.call(f,f),f},when:function(a){var b=arguments.length,c=b,d=Array(c),e=f.call(arguments),g=r.Deferred(),h=function(a){return function(c){d[a]=this,e[a]=arguments.length>1?f.call(arguments):c,--b||g.resolveWith(d,e)}};if(b<=1&&(O(a,g.done(h(c)).resolve,g.reject),\"pending\"===g.state()||r.isFunction(e[c]&&e[c].then)))return g.then();while(c--)O(e[c],h(c),g.reject);return g.promise()}});var P=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;r.Deferred.exceptionHook=function(b,c){a.console&&a.console.warn&&b&&P.test(b.name)&&a.console.warn(\"jQuery.Deferred exception: \"+b.message,b.stack,c)},r.readyException=function(b){a.setTimeout(function(){throw b})};var Q=r.Deferred();r.fn.ready=function(a){return Q.then(a)[\"catch\"](function(a){r.readyException(a)}),this},r.extend({isReady:!1,readyWait:1,holdReady:function(a){a?r.readyWait++:r.ready(!0)},ready:function(a){(a===!0?--r.readyWait:r.isReady)||(r.isReady=!0,a!==!0&&--r.readyWait>0||Q.resolveWith(d,[r]))}}),r.ready.then=Q.then;function R(){d.removeEventListener(\"DOMContentLoaded\",R),a.removeEventListener(\"load\",R),r.ready()}\"complete\"===d.readyState||\"loading\"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(r.ready):(d.addEventListener(\"DOMContentLoaded\",R),a.addEventListener(\"load\",R));var S=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if(\"object\"===r.type(c)){e=!0;for(h in c)S(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,\nr.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(r(a),c)})),b))for(;h<i;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},T=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function U(){this.expando=r.expando+U.uid++}U.uid=1,U.prototype={cache:function(a){var b=a[this.expando];return b||(b={},T(a)&&(a.nodeType?a[this.expando]=b:Object.defineProperty(a,this.expando,{value:b,configurable:!0}))),b},set:function(a,b,c){var d,e=this.cache(a);if(\"string\"==typeof b)e[r.camelCase(b)]=c;else for(d in b)e[r.camelCase(d)]=b[d];return e},get:function(a,b){return void 0===b?this.cache(a):a[this.expando]&&a[this.expando][r.camelCase(b)]},access:function(a,b,c){return void 0===b||b&&\"string\"==typeof b&&void 0===c?this.get(a,b):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d=a[this.expando];if(void 0!==d){if(void 0!==b){r.isArray(b)?b=b.map(r.camelCase):(b=r.camelCase(b),b=b in d?[b]:b.match(K)||[]),c=b.length;while(c--)delete d[b[c]]}(void 0===b||r.isEmptyObject(d))&&(a.nodeType?a[this.expando]=void 0:delete a[this.expando])}},hasData:function(a){var b=a[this.expando];return void 0!==b&&!r.isEmptyObject(b)}};var V=new U,W=new U,X=/^(?:\\{[\\w\\W]*\\}|\\[[\\w\\W]*\\])$/,Y=/[A-Z]/g;function Z(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d=\"data-\"+b.replace(Y,\"-$&\").toLowerCase(),c=a.getAttribute(d),\"string\"==typeof c){try{c=\"true\"===c||\"false\"!==c&&(\"null\"===c?null:+c+\"\"===c?+c:X.test(c)?JSON.parse(c):c)}catch(e){}W.set(a,b,c)}else c=void 0;return c}r.extend({hasData:function(a){return W.hasData(a)||V.hasData(a)},data:function(a,b,c){return W.access(a,b,c)},removeData:function(a,b){W.remove(a,b)},_data:function(a,b,c){return V.access(a,b,c)},_removeData:function(a,b){V.remove(a,b)}}),r.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=W.get(f),1===f.nodeType&&!V.get(f,\"hasDataAttrs\"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf(\"data-\")&&(d=r.camelCase(d.slice(5)),Z(f,d,e[d])));V.set(f,\"hasDataAttrs\",!0)}return e}return\"object\"==typeof a?this.each(function(){W.set(this,a)}):S(this,function(b){var c;if(f&&void 0===b){if(c=W.get(f,a),void 0!==c)return c;if(c=Z(f,a),void 0!==c)return c}else this.each(function(){W.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){W.remove(this,a)})}}),r.extend({queue:function(a,b,c){var d;if(a)return b=(b||\"fx\")+\"queue\",d=V.get(a,b),c&&(!d||r.isArray(c)?d=V.access(a,b,r.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||\"fx\";var c=r.queue(a,b),d=c.length,e=c.shift(),f=r._queueHooks(a,b),g=function(){r.dequeue(a,b)};\"inprogress\"===e&&(e=c.shift(),d--),e&&(\"fx\"===b&&c.unshift(\"inprogress\"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+\"queueHooks\";return V.get(a,c)||V.access(a,c,{empty:r.Callbacks(\"once memory\").add(function(){V.remove(a,[b+\"queue\",c])})})}}),r.fn.extend({queue:function(a,b){var c=2;return\"string\"!=typeof a&&(b=a,a=\"fx\",c--),arguments.length<c?r.queue(this[0],a):void 0===b?this:this.each(function(){var c=r.queue(this,a,b);r._queueHooks(this,a),\"fx\"===a&&\"inprogress\"!==c[0]&&r.dequeue(this,a)})},dequeue:function(a){return this.each(function(){r.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||\"fx\",[])},promise:function(a,b){var c,d=1,e=r.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};\"string\"!=typeof a&&(b=a,a=void 0),a=a||\"fx\";while(g--)c=V.get(f[g],a+\"queueHooks\"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}});var $=/[+-]?(?:\\d*\\.|)\\d+(?:[eE][+-]?\\d+|)/.source,_=new RegExp(\"^(?:([+-])=|)(\"+$+\")([a-z%]*)$\",\"i\"),aa=[\"Top\",\"Right\",\"Bottom\",\"Left\"],ba=function(a,b){return a=b||a,\"none\"===a.style.display||\"\"===a.style.display&&r.contains(a.ownerDocument,a)&&\"none\"===r.css(a,\"display\")},ca=function(a,b,c,d){var e,f,g={};for(f in b)g[f]=a.style[f],a.style[f]=b[f];e=c.apply(a,d||[]);for(f in b)a.style[f]=g[f];return e};function da(a,b,c,d){var e,f=1,g=20,h=d?function(){return d.cur()}:function(){return r.css(a,b,\"\")},i=h(),j=c&&c[3]||(r.cssNumber[b]?\"\":\"px\"),k=(r.cssNumber[b]||\"px\"!==j&&+i)&&_.exec(r.css(a,b));if(k&&k[3]!==j){j=j||k[3],c=c||[],k=+i||1;do f=f||\".5\",k/=f,r.style(a,b,k+j);while(f!==(f=h()/i)&&1!==f&&--g)}return c&&(k=+k||+i||0,e=c[1]?k+(c[1]+1)*c[2]:+c[2],d&&(d.unit=j,d.start=k,d.end=e)),e}var ea={};function fa(a){var b,c=a.ownerDocument,d=a.nodeName,e=ea[d];return e?e:(b=c.body.appendChild(c.createElement(d)),e=r.css(b,\"display\"),b.parentNode.removeChild(b),\"none\"===e&&(e=\"block\"),ea[d]=e,e)}function ga(a,b){for(var c,d,e=[],f=0,g=a.length;f<g;f++)d=a[f],d.style&&(c=d.style.display,b?(\"none\"===c&&(e[f]=V.get(d,\"display\")||null,e[f]||(d.style.display=\"\")),\"\"===d.style.display&&ba(d)&&(e[f]=fa(d))):\"none\"!==c&&(e[f]=\"none\",V.set(d,\"display\",c)));for(f=0;f<g;f++)null!=e[f]&&(a[f].style.display=e[f]);return a}r.fn.extend({show:function(){return ga(this,!0)},hide:function(){return ga(this)},toggle:function(a){return\"boolean\"==typeof a?a?this.show():this.hide():this.each(function(){ba(this)?r(this).show():r(this).hide()})}});var ha=/^(?:checkbox|radio)$/i,ia=/<([a-z][^\\/\\0>\\x20\\t\\r\\n\\f]+)/i,ja=/^$|\\/(?:java|ecma)script/i,ka={option:[1,\"<select multiple='multiple'>\",\"</select>\"],thead:[1,\"<table>\",\"</table>\"],col:[2,\"<table><colgroup>\",\"</colgroup></table>\"],tr:[2,\"<table><tbody>\",\"</tbody></table>\"],td:[3,\"<table><tbody><tr>\",\"</tr></tbody></table>\"],_default:[0,\"\",\"\"]};ka.optgroup=ka.option,ka.tbody=ka.tfoot=ka.colgroup=ka.caption=ka.thead,ka.th=ka.td;function la(a,b){var c=\"undefined\"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||\"*\"):\"undefined\"!=typeof a.querySelectorAll?a.querySelectorAll(b||\"*\"):[];return void 0===b||b&&r.nodeName(a,b)?r.merge([a],c):c}function ma(a,b){for(var c=0,d=a.length;c<d;c++)V.set(a[c],\"globalEval\",!b||V.get(b[c],\"globalEval\"))}var na=/<|&#?\\w+;/;function oa(a,b,c,d,e){for(var f,g,h,i,j,k,l=b.createDocumentFragment(),m=[],n=0,o=a.length;n<o;n++)if(f=a[n],f||0===f)if(\"object\"===r.type(f))r.merge(m,f.nodeType?[f]:f);else if(na.test(f)){g=g||l.appendChild(b.createElement(\"div\")),h=(ia.exec(f)||[\"\",\"\"])[1].toLowerCase(),i=ka[h]||ka._default,g.innerHTML=i[1]+r.htmlPrefilter(f)+i[2],k=i[0];while(k--)g=g.lastChild;r.merge(m,g.childNodes),g=l.firstChild,g.textContent=\"\"}else m.push(b.createTextNode(f));l.textContent=\"\",n=0;while(f=m[n++])if(d&&r.inArray(f,d)>-1)e&&e.push(f);else if(j=r.contains(f.ownerDocument,f),g=la(l.appendChild(f),\"script\"),j&&ma(g),c){k=0;while(f=g[k++])ja.test(f.type||\"\")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement(\"div\")),c=d.createElement(\"input\");c.setAttribute(\"type\",\"radio\"),c.setAttribute(\"checked\",\"checked\"),c.setAttribute(\"name\",\"t\"),b.appendChild(c),o.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML=\"<textarea>x</textarea>\",o.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var pa=d.documentElement,qa=/^key/,ra=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,sa=/^([^.]*)(?:\\.(.+)|)/;function ta(){return!0}function ua(){return!1}function va(){try{return d.activeElement}catch(a){}}function wa(a,b,c,d,e,f){var g,h;if(\"object\"==typeof b){\"string\"!=typeof c&&(d=d||c,c=void 0);for(h in b)wa(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&(\"string\"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=ua;else if(!e)return a;return 1===f&&(g=e,e=function(a){return r().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=r.guid++)),a.each(function(){r.event.add(this,b,e,d,c)})}r.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=V.get(a);if(q){c.handler&&(f=c,c=f.handler,e=f.selector),e&&r.find.matchesSelector(pa,e),c.guid||(c.guid=r.guid++),(i=q.events)||(i=q.events={}),(g=q.handle)||(g=q.handle=function(b){return\"undefined\"!=typeof r&&r.event.triggered!==b.type?r.event.dispatch.apply(a,arguments):void 0}),b=(b||\"\").match(K)||[\"\"],j=b.length;while(j--)h=sa.exec(b[j])||[],n=p=h[1],o=(h[2]||\"\").split(\".\").sort(),n&&(l=r.event.special[n]||{},n=(e?l.delegateType:l.bindType)||n,l=r.event.special[n]||{},k=r.extend({type:n,origType:p,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&r.expr.match.needsContext.test(e),namespace:o.join(\".\")},f),(m=i[n])||(m=i[n]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,o,g)!==!1||a.addEventListener&&a.addEventListener(n,g)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),r.event.global[n]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=V.hasData(a)&&V.get(a);if(q&&(i=q.events)){b=(b||\"\").match(K)||[\"\"],j=b.length;while(j--)if(h=sa.exec(b[j])||[],n=p=h[1],o=(h[2]||\"\").split(\".\").sort(),n){l=r.event.special[n]||{},n=(d?l.delegateType:l.bindType)||n,m=i[n]||[],h=h[2]&&new RegExp(\"(^|\\\\.)\"+o.join(\"\\\\.(?:.*\\\\.|)\")+\"(\\\\.|$)\"),g=f=m.length;while(f--)k=m[f],!e&&p!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&(\"**\"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,o,q.handle)!==!1||r.removeEvent(a,n,q.handle),delete i[n])}else for(n in i)r.event.remove(a,n+b[j],c,d,!0);r.isEmptyObject(i)&&V.remove(a,\"handle events\")}},dispatch:function(a){var b=r.event.fix(a),c,d,e,f,g,h,i=new Array(arguments.length),j=(V.get(this,\"events\")||{})[b.type]||[],k=r.event.special[b.type]||{};for(i[0]=b,c=1;c<arguments.length;c++)i[c]=arguments[c];if(b.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,b)!==!1){h=r.event.handlers.call(this,b,j),c=0;while((f=h[c++])&&!b.isPropagationStopped()){b.currentTarget=f.elem,d=0;while((g=f.handlers[d++])&&!b.isImmediatePropagationStopped())b.rnamespace&&!b.rnamespace.test(g.namespace)||(b.handleObj=g,b.data=g.data,e=((r.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(b.result=e)===!1&&(b.preventDefault(),b.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,b),b.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(\"click\"!==a.type||isNaN(a.button)||a.button<1))for(;i!==this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||\"click\"!==a.type)){for(d=[],c=0;c<h;c++)f=b[c],e=f.selector+\" \",void 0===d[e]&&(d[e]=f.needsContext?r(e,this).index(i)>-1:r.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h<b.length&&g.push({elem:this,handlers:b.slice(h)}),g},addProp:function(a,b){Object.defineProperty(r.Event.prototype,a,{enumerable:!0,configurable:!0,get:r.isFunction(b)?function(){if(this.originalEvent)return b(this.originalEvent)}:function(){if(this.originalEvent)return this.originalEvent[a]},set:function(b){Object.defineProperty(this,a,{enumerable:!0,configurable:!0,writable:!0,value:b})}})},fix:function(a){return a[r.expando]?a:new r.Event(a)},special:{load:{noBubble:!0},focus:{trigger:function(){if(this!==va()&&this.focus)return this.focus(),!1},delegateType:\"focusin\"},blur:{trigger:function(){if(this===va()&&this.blur)return this.blur(),!1},delegateType:\"focusout\"},click:{trigger:function(){if(\"checkbox\"===this.type&&this.click&&r.nodeName(this,\"input\"))return this.click(),!1},_default:function(a){return r.nodeName(a.target,\"a\")}},beforeunload:{postDispatch:function(a){void 0!==a.result&&a.originalEvent&&(a.originalEvent.returnValue=a.result)}}}},r.removeEvent=function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c)},r.Event=function(a,b){return this instanceof r.Event?(a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||void 0===a.defaultPrevented&&a.returnValue===!1?ta:ua,this.target=a.target&&3===a.target.nodeType?a.target.parentNode:a.target,this.currentTarget=a.currentTarget,this.relatedTarget=a.relatedTarget):this.type=a,b&&r.extend(this,b),this.timeStamp=a&&a.timeStamp||r.now(),void(this[r.expando]=!0)):new r.Event(a,b)},r.Event.prototype={constructor:r.Event,isDefaultPrevented:ua,isPropagationStopped:ua,isImmediatePropagationStopped:ua,isSimulated:!1,preventDefault:function(){var a=this.originalEvent;this.isDefaultPrevented=ta,a&&!this.isSimulated&&a.preventDefault()},stopPropagation:function(){var a=this.originalEvent;this.isPropagationStopped=ta,a&&!this.isSimulated&&a.stopPropagation()},stopImmediatePropagation:function(){var a=this.originalEvent;this.isImmediatePropagationStopped=ta,a&&!this.isSimulated&&a.stopImmediatePropagation(),this.stopPropagation()}},r.each({altKey:!0,bubbles:!0,cancelable:!0,changedTouches:!0,ctrlKey:!0,detail:!0,eventPhase:!0,metaKey:!0,pageX:!0,pageY:!0,shiftKey:!0,view:!0,\"char\":!0,charCode:!0,key:!0,keyCode:!0,button:!0,buttons:!0,clientX:!0,clientY:!0,offsetX:!0,offsetY:!0,pointerId:!0,pointerType:!0,screenX:!0,screenY:!0,targetTouches:!0,toElement:!0,touches:!0,which:function(a){var b=a.button;return null==a.which&&qa.test(a.type)?null!=a.charCode?a.charCode:a.keyCode:!a.which&&void 0!==b&&ra.test(a.type)?1&b?1:2&b?3:4&b?2:0:a.which}},r.event.addProp),r.each({mouseenter:\"mouseover\",mouseleave:\"mouseout\",pointerenter:\"pointerover\",pointerleave:\"pointerout\"},function(a,b){r.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj;return e&&(e===d||r.contains(d,e))||(a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b),c}}}),r.fn.extend({on:function(a,b,c,d){return wa(this,a,b,c,d)},one:function(a,b,c,d){return wa(this,a,b,c,d,1)},off:function(a,b,c){var d,e;if(a&&a.preventDefault&&a.handleObj)return d=a.handleObj,r(a.delegateTarget).off(d.namespace?d.origType+\".\"+d.namespace:d.origType,d.selector,d.handler),this;if(\"object\"==typeof a){for(e in a)this.off(e,b,a[e]);return this}return b!==!1&&\"function\"!=typeof b||(c=b,b=void 0),c===!1&&(c=ua),this.each(function(){r.event.remove(this,a,c,b)})}});var xa=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\\/\\0>\\x20\\t\\r\\n\\f]*)[^>]*)\\/>/gi,ya=/<script|<style|<link/i,za=/checked\\s*(?:[^=]|=\\s*.checked.)/i,Aa=/^true\\/(.*)/,Ba=/^\\s*<!(?:\\[CDATA\\[|--)|(?:\\]\\]|--)>\\s*$/g;function Ca(a,b){return r.nodeName(a,\"table\")&&r.nodeName(11!==b.nodeType?b:b.firstChild,\"tr\")?a.getElementsByTagName(\"tbody\")[0]||a:a}function Da(a){return a.type=(null!==a.getAttribute(\"type\"))+\"/\"+a.type,a}function Ea(a){var b=Aa.exec(a.type);return b?a.type=b[1]:a.removeAttribute(\"type\"),a}function Fa(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(V.hasData(a)&&(f=V.access(a),g=V.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;c<d;c++)r.event.add(b,e,j[e][c])}W.hasData(a)&&(h=W.access(a),i=r.extend({},h),W.set(b,i))}}function Ga(a,b){var c=b.nodeName.toLowerCase();\"input\"===c&&ha.test(a.type)?b.checked=a.checked:\"input\"!==c&&\"textarea\"!==c||(b.defaultValue=a.defaultValue)}function Ha(a,b,c,d){b=g.apply([],b);var e,f,h,i,j,k,l=0,m=a.length,n=m-1,q=b[0],s=r.isFunction(q);if(s||m>1&&\"string\"==typeof q&&!o.checkClone&&za.test(q))return a.each(function(e){var f=a.eq(e);s&&(b[0]=q.call(this,e,f.html())),Ha(f,b,c,d)});if(m&&(e=oa(b,a[0].ownerDocument,!1,a,d),f=e.firstChild,1===e.childNodes.length&&(e=f),f||d)){for(h=r.map(la(e,\"script\"),Da),i=h.length;l<m;l++)j=e,l!==n&&(j=r.clone(j,!0,!0),i&&r.merge(h,la(j,\"script\"))),c.call(a[l],j,l);if(i)for(k=h[h.length-1].ownerDocument,r.map(h,Ea),l=0;l<i;l++)j=h[l],ja.test(j.type||\"\")&&!V.access(j,\"globalEval\")&&r.contains(k,j)&&(j.src?r._evalUrl&&r._evalUrl(j.src):p(j.textContent.replace(Ba,\"\"),k))}return a}function Ia(a,b,c){for(var d,e=b?r.filter(b,a):a,f=0;null!=(d=e[f]);f++)c||1!==d.nodeType||r.cleanData(la(d)),d.parentNode&&(c&&r.contains(d.ownerDocument,d)&&ma(la(d,\"script\")),d.parentNode.removeChild(d));return a}r.extend({htmlPrefilter:function(a){return a.replace(xa,\"<$1></$2>\")},clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=r.contains(a.ownerDocument,a);if(!(o.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||r.isXMLDoc(a)))for(g=la(h),f=la(a),d=0,e=f.length;d<e;d++)Ga(f[d],g[d]);if(b)if(c)for(f=f||la(a),g=g||la(h),d=0,e=f.length;d<e;d++)Fa(f[d],g[d]);else Fa(a,h);return g=la(h,\"script\"),g.length>0&&ma(g,!i&&la(a,\"script\")),h},cleanData:function(a){for(var b,c,d,e=r.event.special,f=0;void 0!==(c=a[f]);f++)if(T(c)){if(b=c[V.expando]){if(b.events)for(d in b.events)e[d]?r.event.remove(c,d):r.removeEvent(c,d,b.handle);c[V.expando]=void 0}c[W.expando]&&(c[W.expando]=void 0)}}}),r.fn.extend({detach:function(a){return Ia(this,a,!0)},remove:function(a){return Ia(this,a)},text:function(a){return S(this,function(a){return void 0===a?r.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=a)})},null,a,arguments.length)},append:function(){return Ha(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ca(this,a);b.appendChild(a)}})},prepend:function(){return Ha(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ca(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return Ha(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return Ha(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(r.cleanData(la(a,!1)),a.textContent=\"\");return this},clone:function(a,b){return a=null!=a&&a,b=null==b?a:b,this.map(function(){return r.clone(this,a,b)})},html:function(a){return S(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if(\"string\"==typeof a&&!ya.test(a)&&!ka[(ia.exec(a)||[\"\",\"\"])[1].toLowerCase()]){a=r.htmlPrefilter(a);try{for(;c<d;c++)b=this[c]||{},1===b.nodeType&&(r.cleanData(la(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=[];return Ha(this,arguments,function(b){var c=this.parentNode;r.inArray(this,a)<0&&(r.cleanData(la(this)),c&&c.replaceChild(b,this))},a)}}),r.each({appendTo:\"append\",prependTo:\"prepend\",insertBefore:\"before\",insertAfter:\"after\",replaceAll:\"replaceWith\"},function(a,b){r.fn[a]=function(a){for(var c,d=[],e=r(a),f=e.length-1,g=0;g<=f;g++)c=g===f?this:this.clone(!0),r(e[g])[b](c),h.apply(d,c.get());return this.pushStack(d)}});var Ja=/^margin/,Ka=new RegExp(\"^(\"+$+\")(?!px)[a-z%]+$\",\"i\"),La=function(b){var c=b.ownerDocument.defaultView;return c&&c.opener||(c=a),c.getComputedStyle(b)};!function(){function b(){if(i){i.style.cssText=\"box-sizing:border-box;position:relative;display:block;margin:auto;border:1px;padding:1px;top:1%;width:50%\",i.innerHTML=\"\",pa.appendChild(h);var b=a.getComputedStyle(i);c=\"1%\"!==b.top,g=\"2px\"===b.marginLeft,e=\"4px\"===b.width,i.style.marginRight=\"50%\",f=\"4px\"===b.marginRight,pa.removeChild(h),i=null}}var c,e,f,g,h=d.createElement(\"div\"),i=d.createElement(\"div\");i.style&&(i.style.backgroundClip=\"content-box\",i.cloneNode(!0).style.backgroundClip=\"\",o.clearCloneStyle=\"content-box\"===i.style.backgroundClip,h.style.cssText=\"border:0;width:8px;height:0;top:0;left:-9999px;padding:0;margin-top:1px;position:absolute\",h.appendChild(i),r.extend(o,{pixelPosition:function(){return b(),c},boxSizingReliable:function(){return b(),e},pixelMarginRight:function(){return b(),f},reliableMarginLeft:function(){return b(),g}}))}();function Ma(a,b,c){var d,e,f,g,h=a.style;return c=c||La(a),c&&(g=c.getPropertyValue(b)||c[b],\"\"!==g||r.contains(a.ownerDocument,a)||(g=r.style(a,b)),!o.pixelMarginRight()&&Ka.test(g)&&Ja.test(b)&&(d=h.width,e=h.minWidth,f=h.maxWidth,h.minWidth=h.maxWidth=h.width=g,g=c.width,h.width=d,h.minWidth=e,h.maxWidth=f)),void 0!==g?g+\"\":g}function Na(a,b){return{get:function(){return a()?void delete this.get:(this.get=b).apply(this,arguments)}}}var Oa=/^(none|table(?!-c[ea]).+)/,Pa={position:\"absolute\",visibility:\"hidden\",display:\"block\"},Qa={letterSpacing:\"0\",fontWeight:\"400\"},Ra=[\"Webkit\",\"Moz\",\"ms\"],Sa=d.createElement(\"div\").style;function Ta(a){if(a in Sa)return a;var b=a[0].toUpperCase()+a.slice(1),c=Ra.length;while(c--)if(a=Ra[c]+b,a in Sa)return a}function Ua(a,b,c){var d=_.exec(b);return d?Math.max(0,d[2]-(c||0))+(d[3]||\"px\"):b}function Va(a,b,c,d,e){for(var f=c===(d?\"border\":\"content\")?4:\"width\"===b?1:0,g=0;f<4;f+=2)\"margin\"===c&&(g+=r.css(a,c+aa[f],!0,e)),d?(\"content\"===c&&(g-=r.css(a,\"padding\"+aa[f],!0,e)),\"margin\"!==c&&(g-=r.css(a,\"border\"+aa[f]+\"Width\",!0,e))):(g+=r.css(a,\"padding\"+aa[f],!0,e),\"padding\"!==c&&(g+=r.css(a,\"border\"+aa[f]+\"Width\",!0,e)));return g}function Wa(a,b,c){var d,e=!0,f=La(a),g=\"border-box\"===r.css(a,\"boxSizing\",!1,f);if(a.getClientRects().length&&(d=a.getBoundingClientRect()[b]),d<=0||null==d){if(d=Ma(a,b,f),(d<0||null==d)&&(d=a.style[b]),Ka.test(d))return d;e=g&&(o.boxSizingReliable()||d===a.style[b]),d=parseFloat(d)||0}return d+Va(a,b,c||(g?\"border\":\"content\"),e,f)+\"px\"}r.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=Ma(a,\"opacity\");return\"\"===c?\"1\":c}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{\"float\":\"cssFloat\"},style:function(a,b,c,d){if(a&&3!==a.nodeType&&8!==a.nodeType&&a.style){var e,f,g,h=r.camelCase(b),i=a.style;return b=r.cssProps[h]||(r.cssProps[h]=Ta(h)||h),g=r.cssHooks[b]||r.cssHooks[h],void 0===c?g&&\"get\"in g&&void 0!==(e=g.get(a,!1,d))?e:i[b]:(f=typeof c,\"string\"===f&&(e=_.exec(c))&&e[1]&&(c=da(a,b,e),f=\"number\"),null!=c&&c===c&&(\"number\"===f&&(c+=e&&e[3]||(r.cssNumber[h]?\"\":\"px\")),o.clearCloneStyle||\"\"!==c||0!==b.indexOf(\"background\")||(i[b]=\"inherit\"),g&&\"set\"in g&&void 0===(c=g.set(a,c,d))||(i[b]=c)),void 0)}},css:function(a,b,c,d){var e,f,g,h=r.camelCase(b);return b=r.cssProps[h]||(r.cssProps[h]=Ta(h)||h),g=r.cssHooks[b]||r.cssHooks[h],g&&\"get\"in g&&(e=g.get(a,!0,c)),void 0===e&&(e=Ma(a,b,d)),\"normal\"===e&&b in Qa&&(e=Qa[b]),\"\"===c||c?(f=parseFloat(e),c===!0||isFinite(f)?f||0:e):e}}),r.each([\"height\",\"width\"],function(a,b){r.cssHooks[b]={get:function(a,c,d){if(c)return!Oa.test(r.css(a,\"display\"))||a.getClientRects().length&&a.getBoundingClientRect().width?Wa(a,b,d):ca(a,Pa,function(){return Wa(a,b,d)})},set:function(a,c,d){var e,f=d&&La(a),g=d&&Va(a,b,d,\"border-box\"===r.css(a,\"boxSizing\",!1,f),f);return g&&(e=_.exec(c))&&\"px\"!==(e[3]||\"px\")&&(a.style[b]=c,c=r.css(a,b)),Ua(a,c,g)}}}),r.cssHooks.marginLeft=Na(o.reliableMarginLeft,function(a,b){if(b)return(parseFloat(Ma(a,\"marginLeft\"))||a.getBoundingClientRect().left-ca(a,{marginLeft:0},function(){return a.getBoundingClientRect().left}))+\"px\"}),r.each({margin:\"\",padding:\"\",border:\"Width\"},function(a,b){r.cssHooks[a+b]={expand:function(c){for(var d=0,e={},f=\"string\"==typeof c?c.split(\" \"):[c];d<4;d++)e[a+aa[d]+b]=f[d]||f[d-2]||f[0];return e}},Ja.test(a)||(r.cssHooks[a+b].set=Ua)}),r.fn.extend({css:function(a,b){return S(this,function(a,b,c){var d,e,f={},g=0;if(r.isArray(b)){for(d=La(a),e=b.length;g<e;g++)f[b[g]]=r.css(a,b[g],!1,d);return f}return void 0!==c?r.style(a,b,c):r.css(a,b)},a,b,arguments.length>1)}});function Xa(a,b,c,d,e){return new Xa.prototype.init(a,b,c,d,e)}r.Tween=Xa,Xa.prototype={constructor:Xa,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||r.easing._default,this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(r.cssNumber[c]?\"\":\"px\")},cur:function(){var a=Xa.propHooks[this.prop];return a&&a.get?a.get(this):Xa.propHooks._default.get(this)},run:function(a){var b,c=Xa.propHooks[this.prop];return this.options.duration?this.pos=b=r.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Xa.propHooks._default.set(this),this}},Xa.prototype.init.prototype=Xa.prototype,Xa.propHooks={_default:{get:function(a){var b;return 1!==a.elem.nodeType||null!=a.elem[a.prop]&&null==a.elem.style[a.prop]?a.elem[a.prop]:(b=r.css(a.elem,a.prop,\"\"),b&&\"auto\"!==b?b:0)},set:function(a){r.fx.step[a.prop]?r.fx.step[a.prop](a):1!==a.elem.nodeType||null==a.elem.style[r.cssProps[a.prop]]&&!r.cssHooks[a.prop]?a.elem[a.prop]=a.now:r.style(a.elem,a.prop,a.now+a.unit)}}},Xa.propHooks.scrollTop=Xa.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},r.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2},_default:\"swing\"},r.fx=Xa.prototype.init,r.fx.step={};var Ya,Za,$a=/^(?:toggle|show|hide)$/,_a=/queueHooks$/;function ab(){Za&&(a.requestAnimationFrame(ab),r.fx.tick())}function bb(){return a.setTimeout(function(){Ya=void 0}),Ya=r.now()}function cb(a,b){var c,d=0,e={height:a};for(b=b?1:0;d<4;d+=2-b)c=aa[d],e[\"margin\"+c]=e[\"padding\"+c]=a;return b&&(e.opacity=e.width=a),e}function db(a,b,c){for(var d,e=(gb.tweeners[b]||[]).concat(gb.tweeners[\"*\"]),f=0,g=e.length;f<g;f++)if(d=e[f].call(c,b,a))return d}function eb(a,b,c){var d,e,f,g,h,i,j,k,l=\"width\"in b||\"height\"in b,m=this,n={},o=a.style,p=a.nodeType&&ba(a),q=V.get(a,\"fxshow\");c.queue||(g=r._queueHooks(a,\"fx\"),null==g.unqueued&&(g.unqueued=0,h=g.empty.fire,g.empty.fire=function(){g.unqueued||h()}),g.unqueued++,m.always(function(){m.always(function(){g.unqueued--,r.queue(a,\"fx\").length||g.empty.fire()})}));for(d in b)if(e=b[d],$a.test(e)){if(delete b[d],f=f||\"toggle\"===e,e===(p?\"hide\":\"show\")){if(\"show\"!==e||!q||void 0===q[d])continue;p=!0}n[d]=q&&q[d]||r.style(a,d)}if(i=!r.isEmptyObject(b),i||!r.isEmptyObject(n)){l&&1===a.nodeType&&(c.overflow=[o.overflow,o.overflowX,o.overflowY],j=q&&q.display,null==j&&(j=V.get(a,\"display\")),k=r.css(a,\"display\"),\"none\"===k&&(j?k=j:(ga([a],!0),j=a.style.display||j,k=r.css(a,\"display\"),ga([a]))),(\"inline\"===k||\"inline-block\"===k&&null!=j)&&\"none\"===r.css(a,\"float\")&&(i||(m.done(function(){o.display=j}),null==j&&(k=o.display,j=\"none\"===k?\"\":k)),o.display=\"inline-block\")),c.overflow&&(o.overflow=\"hidden\",m.always(function(){o.overflow=c.overflow[0],o.overflowX=c.overflow[1],o.overflowY=c.overflow[2]})),i=!1;for(d in n)i||(q?\"hidden\"in q&&(p=q.hidden):q=V.access(a,\"fxshow\",{display:j}),f&&(q.hidden=!p),p&&ga([a],!0),m.done(function(){p||ga([a]),V.remove(a,\"fxshow\");for(d in n)r.style(a,d,n[d])})),i=db(p?q[d]:0,d,m),d in q||(q[d]=i.start,p&&(i.end=i.start,i.start=0))}}function fb(a,b){var c,d,e,f,g;for(c in a)if(d=r.camelCase(c),e=b[d],f=a[c],r.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=r.cssHooks[d],g&&\"expand\"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}function gb(a,b,c){var d,e,f=0,g=gb.prefilters.length,h=r.Deferred().always(function(){delete i.elem}),i=function(){if(e)return!1;for(var b=Ya||bb(),c=Math.max(0,j.startTime+j.duration-b),d=c/j.duration||0,f=1-d,g=0,i=j.tweens.length;g<i;g++)j.tweens[g].run(f);return h.notifyWith(a,[j,f,c]),f<1&&i?c:(h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:r.extend({},b),opts:r.extend(!0,{specialEasing:{},easing:r.easing._default},c),originalProperties:b,originalOptions:c,startTime:Ya||bb(),duration:c.duration,tweens:[],createTween:function(b,c){var d=r.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(d),d},stop:function(b){var c=0,d=b?j.tweens.length:0;if(e)return this;for(e=!0;c<d;c++)j.tweens[c].run(1);return b?(h.notifyWith(a,[j,1,0]),h.resolveWith(a,[j,b])):h.rejectWith(a,[j,b]),this}}),k=j.props;for(fb(k,j.opts.specialEasing);f<g;f++)if(d=gb.prefilters[f].call(j,a,k,j.opts))return r.isFunction(d.stop)&&(r._queueHooks(j.elem,j.opts.queue).stop=r.proxy(d.stop,d)),d;return r.map(k,db,j),r.isFunction(j.opts.start)&&j.opts.start.call(a,j),r.fx.timer(r.extend(i,{elem:a,anim:j,queue:j.opts.queue})),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always)}r.Animation=r.extend(gb,{tweeners:{\"*\":[function(a,b){var c=this.createTween(a,b);return da(c.elem,a,_.exec(b),c),c}]},tweener:function(a,b){r.isFunction(a)?(b=a,a=[\"*\"]):a=a.match(K);for(var c,d=0,e=a.length;d<e;d++)c=a[d],gb.tweeners[c]=gb.tweeners[c]||[],gb.tweeners[c].unshift(b)},prefilters:[eb],prefilter:function(a,b){b?gb.prefilters.unshift(a):gb.prefilters.push(a)}}),r.speed=function(a,b,c){var e=a&&\"object\"==typeof a?r.extend({},a):{complete:c||!c&&b||r.isFunction(a)&&a,duration:a,easing:c&&b||b&&!r.isFunction(b)&&b};return r.fx.off||d.hidden?e.duration=0:e.duration=\"number\"==typeof e.duration?e.duration:e.duration in r.fx.speeds?r.fx.speeds[e.duration]:r.fx.speeds._default,null!=e.queue&&e.queue!==!0||(e.queue=\"fx\"),e.old=e.complete,e.complete=function(){r.isFunction(e.old)&&e.old.call(this),e.queue&&r.dequeue(this,e.queue)},e},r.fn.extend({fadeTo:function(a,b,c,d){return this.filter(ba).css(\"opacity\",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=r.isEmptyObject(a),f=r.speed(b,c,d),g=function(){var b=gb(this,r.extend({},a),f);(e||V.get(this,\"finish\"))&&b.stop(!0)};return g.finish=g,e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,b,c){var d=function(a){var b=a.stop;delete a.stop,b(c)};return\"string\"!=typeof a&&(c=b,b=a,a=void 0),b&&a!==!1&&this.queue(a||\"fx\",[]),this.each(function(){var b=!0,e=null!=a&&a+\"queueHooks\",f=r.timers,g=V.get(this);if(e)g[e]&&g[e].stop&&d(g[e]);else for(e in g)g[e]&&g[e].stop&&_a.test(e)&&d(g[e]);for(e=f.length;e--;)f[e].elem!==this||null!=a&&f[e].queue!==a||(f[e].anim.stop(c),b=!1,f.splice(e,1));!b&&c||r.dequeue(this,a)})},finish:function(a){return a!==!1&&(a=a||\"fx\"),this.each(function(){var b,c=V.get(this),d=c[a+\"queue\"],e=c[a+\"queueHooks\"],f=r.timers,g=d?d.length:0;for(c.finish=!0,r.queue(this,a,[]),e&&e.stop&&e.stop.call(this,!0),b=f.length;b--;)f[b].elem===this&&f[b].queue===a&&(f[b].anim.stop(!0),f.splice(b,1));for(b=0;b<g;b++)d[b]&&d[b].finish&&d[b].finish.call(this);delete c.finish})}}),r.each([\"toggle\",\"show\",\"hide\"],function(a,b){var c=r.fn[b];r.fn[b]=function(a,d,e){return null==a||\"boolean\"==typeof a?c.apply(this,arguments):this.animate(cb(b,!0),a,d,e)}}),r.each({slideDown:cb(\"show\"),slideUp:cb(\"hide\"),slideToggle:cb(\"toggle\"),fadeIn:{opacity:\"show\"},fadeOut:{opacity:\"hide\"},fadeToggle:{opacity:\"toggle\"}},function(a,b){r.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),r.timers=[],r.fx.tick=function(){var a,b=0,c=r.timers;for(Ya=r.now();b<c.length;b++)a=c[b],a()||c[b]!==a||c.splice(b--,1);c.length||r.fx.stop(),Ya=void 0},r.fx.timer=function(a){r.timers.push(a),a()?r.fx.start():r.timers.pop()},r.fx.interval=13,r.fx.start=function(){Za||(Za=a.requestAnimationFrame?a.requestAnimationFrame(ab):a.setInterval(r.fx.tick,r.fx.interval))},r.fx.stop=function(){a.cancelAnimationFrame?a.cancelAnimationFrame(Za):a.clearInterval(Za),Za=null},r.fx.speeds={slow:600,fast:200,_default:400},r.fn.delay=function(b,c){return b=r.fx?r.fx.speeds[b]||b:b,c=c||\"fx\",this.queue(c,function(c,d){var e=a.setTimeout(c,b);d.stop=function(){a.clearTimeout(e)}})},function(){var a=d.createElement(\"input\"),b=d.createElement(\"select\"),c=b.appendChild(d.createElement(\"option\"));a.type=\"checkbox\",o.checkOn=\"\"!==a.value,o.optSelected=c.selected,a=d.createElement(\"input\"),a.value=\"t\",a.type=\"radio\",o.radioValue=\"t\"===a.value}();var hb,ib=r.expr.attrHandle;r.fn.extend({attr:function(a,b){return S(this,r.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){r.removeAttr(this,a)})}}),r.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return\"undefined\"==typeof a.getAttribute?r.prop(a,b,c):(1===f&&r.isXMLDoc(a)||(e=r.attrHooks[b.toLowerCase()]||(r.expr.match.bool.test(b)?hb:void 0)),void 0!==c?null===c?void r.removeAttr(a,b):e&&\"set\"in e&&void 0!==(d=e.set(a,c,b))?d:(a.setAttribute(b,c+\"\"),c):e&&\"get\"in e&&null!==(d=e.get(a,b))?d:(d=r.find.attr(a,b),null==d?void 0:d))},attrHooks:{type:{set:function(a,b){if(!o.radioValue&&\"radio\"===b&&r.nodeName(a,\"input\")){var c=a.value;return a.setAttribute(\"type\",b),c&&(a.value=c),b}}}},removeAttr:function(a,b){var c,d=0,e=b&&b.match(K);\nif(e&&1===a.nodeType)while(c=e[d++])a.removeAttribute(c)}}),hb={set:function(a,b,c){return b===!1?r.removeAttr(a,c):a.setAttribute(c,c),c}},r.each(r.expr.match.bool.source.match(/\\w+/g),function(a,b){var c=ib[b]||r.find.attr;ib[b]=function(a,b,d){var e,f,g=b.toLowerCase();return d||(f=ib[g],ib[g]=e,e=null!=c(a,b,d)?g:null,ib[g]=f),e}});var jb=/^(?:input|select|textarea|button)$/i,kb=/^(?:a|area)$/i;r.fn.extend({prop:function(a,b){return S(this,r.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[r.propFix[a]||a]})}}),r.extend({prop:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return 1===f&&r.isXMLDoc(a)||(b=r.propFix[b]||b,e=r.propHooks[b]),void 0!==c?e&&\"set\"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&\"get\"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=r.find.attr(a,\"tabindex\");return b?parseInt(b,10):jb.test(a.nodeName)||kb.test(a.nodeName)&&a.href?0:-1}}},propFix:{\"for\":\"htmlFor\",\"class\":\"className\"}}),o.optSelected||(r.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null},set:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}}),r.each([\"tabIndex\",\"readOnly\",\"maxLength\",\"cellSpacing\",\"cellPadding\",\"rowSpan\",\"colSpan\",\"useMap\",\"frameBorder\",\"contentEditable\"],function(){r.propFix[this.toLowerCase()]=this});var lb=/[\\t\\r\\n\\f]/g;function mb(a){return a.getAttribute&&a.getAttribute(\"class\")||\"\"}r.fn.extend({addClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).addClass(a.call(this,b,mb(this)))});if(\"string\"==typeof a&&a){b=a.match(K)||[];while(c=this[i++])if(e=mb(c),d=1===c.nodeType&&(\" \"+e+\" \").replace(lb,\" \")){g=0;while(f=b[g++])d.indexOf(\" \"+f+\" \")<0&&(d+=f+\" \");h=r.trim(d),e!==h&&c.setAttribute(\"class\",h)}}return this},removeClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).removeClass(a.call(this,b,mb(this)))});if(!arguments.length)return this.attr(\"class\",\"\");if(\"string\"==typeof a&&a){b=a.match(K)||[];while(c=this[i++])if(e=mb(c),d=1===c.nodeType&&(\" \"+e+\" \").replace(lb,\" \")){g=0;while(f=b[g++])while(d.indexOf(\" \"+f+\" \")>-1)d=d.replace(\" \"+f+\" \",\" \");h=r.trim(d),e!==h&&c.setAttribute(\"class\",h)}}return this},toggleClass:function(a,b){var c=typeof a;return\"boolean\"==typeof b&&\"string\"===c?b?this.addClass(a):this.removeClass(a):r.isFunction(a)?this.each(function(c){r(this).toggleClass(a.call(this,c,mb(this),b),b)}):this.each(function(){var b,d,e,f;if(\"string\"===c){d=0,e=r(this),f=a.match(K)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else void 0!==a&&\"boolean\"!==c||(b=mb(this),b&&V.set(this,\"__className__\",b),this.setAttribute&&this.setAttribute(\"class\",b||a===!1?\"\":V.get(this,\"__className__\")||\"\"))})},hasClass:function(a){var b,c,d=0;b=\" \"+a+\" \";while(c=this[d++])if(1===c.nodeType&&(\" \"+mb(c)+\" \").replace(lb,\" \").indexOf(b)>-1)return!0;return!1}});var nb=/\\r/g,ob=/[\\x20\\t\\r\\n\\f]+/g;r.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=r.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,r(this).val()):a,null==e?e=\"\":\"number\"==typeof e?e+=\"\":r.isArray(e)&&(e=r.map(e,function(a){return null==a?\"\":a+\"\"})),b=r.valHooks[this.type]||r.valHooks[this.nodeName.toLowerCase()],b&&\"set\"in b&&void 0!==b.set(this,e,\"value\")||(this.value=e))});if(e)return b=r.valHooks[e.type]||r.valHooks[e.nodeName.toLowerCase()],b&&\"get\"in b&&void 0!==(c=b.get(e,\"value\"))?c:(c=e.value,\"string\"==typeof c?c.replace(nb,\"\"):null==c?\"\":c)}}}),r.extend({valHooks:{option:{get:function(a){var b=r.find.attr(a,\"value\");return null!=b?b:r.trim(r.text(a)).replace(ob,\" \")}},select:{get:function(a){for(var b,c,d=a.options,e=a.selectedIndex,f=\"select-one\"===a.type,g=f?null:[],h=f?e+1:d.length,i=e<0?h:f?e:0;i<h;i++)if(c=d[i],(c.selected||i===e)&&!c.disabled&&(!c.parentNode.disabled||!r.nodeName(c.parentNode,\"optgroup\"))){if(b=r(c).val(),f)return b;g.push(b)}return g},set:function(a,b){var c,d,e=a.options,f=r.makeArray(b),g=e.length;while(g--)d=e[g],(d.selected=r.inArray(r.valHooks.option.get(d),f)>-1)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),r.each([\"radio\",\"checkbox\"],function(){r.valHooks[this]={set:function(a,b){if(r.isArray(b))return a.checked=r.inArray(r(a).val(),b)>-1}},o.checkOn||(r.valHooks[this].get=function(a){return null===a.getAttribute(\"value\")?\"on\":a.value})});var pb=/^(?:focusinfocus|focusoutblur)$/;r.extend(r.event,{trigger:function(b,c,e,f){var g,h,i,j,k,m,n,o=[e||d],p=l.call(b,\"type\")?b.type:b,q=l.call(b,\"namespace\")?b.namespace.split(\".\"):[];if(h=i=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!pb.test(p+r.event.triggered)&&(p.indexOf(\".\")>-1&&(q=p.split(\".\"),p=q.shift(),q.sort()),k=p.indexOf(\":\")<0&&\"on\"+p,b=b[r.expando]?b:new r.Event(p,\"object\"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=q.join(\".\"),b.rnamespace=b.namespace?new RegExp(\"(^|\\\\.)\"+q.join(\"\\\\.(?:.*\\\\.|)\")+\"(\\\\.|$)\"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:r.makeArray(c,[b]),n=r.event.special[p]||{},f||!n.trigger||n.trigger.apply(e,c)!==!1)){if(!f&&!n.noBubble&&!r.isWindow(e)){for(j=n.delegateType||p,pb.test(j+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),i=h;i===(e.ownerDocument||d)&&o.push(i.defaultView||i.parentWindow||a)}g=0;while((h=o[g++])&&!b.isPropagationStopped())b.type=g>1?j:n.bindType||p,m=(V.get(h,\"events\")||{})[b.type]&&V.get(h,\"handle\"),m&&m.apply(h,c),m=k&&h[k],m&&m.apply&&T(h)&&(b.result=m.apply(h,c),b.result===!1&&b.preventDefault());return b.type=p,f||b.isDefaultPrevented()||n._default&&n._default.apply(o.pop(),c)!==!1||!T(e)||k&&r.isFunction(e[p])&&!r.isWindow(e)&&(i=e[k],i&&(e[k]=null),r.event.triggered=p,e[p](),r.event.triggered=void 0,i&&(e[k]=i)),b.result}},simulate:function(a,b,c){var d=r.extend(new r.Event,c,{type:a,isSimulated:!0});r.event.trigger(d,null,b)}}),r.fn.extend({trigger:function(a,b){return this.each(function(){r.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];if(c)return r.event.trigger(a,b,c,!0)}}),r.each(\"blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu\".split(\" \"),function(a,b){r.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),r.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),o.focusin=\"onfocusin\"in a,o.focusin||r.each({focus:\"focusin\",blur:\"focusout\"},function(a,b){var c=function(a){r.event.simulate(b,a.target,r.event.fix(a))};r.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=V.access(d,b);e||d.addEventListener(a,c,!0),V.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=V.access(d,b)-1;e?V.access(d,b,e):(d.removeEventListener(a,c,!0),V.remove(d,b))}}});var qb=a.location,rb=r.now(),sb=/\\?/;r.parseXML=function(b){var c;if(!b||\"string\"!=typeof b)return null;try{c=(new a.DOMParser).parseFromString(b,\"text/xml\")}catch(d){c=void 0}return c&&!c.getElementsByTagName(\"parsererror\").length||r.error(\"Invalid XML: \"+b),c};var tb=/\\[\\]$/,ub=/\\r?\\n/g,vb=/^(?:submit|button|image|reset|file)$/i,wb=/^(?:input|select|textarea|keygen)/i;function xb(a,b,c,d){var e;if(r.isArray(b))r.each(b,function(b,e){c||tb.test(a)?d(a,e):xb(a+\"[\"+(\"object\"==typeof e&&null!=e?b:\"\")+\"]\",e,c,d)});else if(c||\"object\"!==r.type(b))d(a,b);else for(e in b)xb(a+\"[\"+e+\"]\",b[e],c,d)}r.param=function(a,b){var c,d=[],e=function(a,b){var c=r.isFunction(b)?b():b;d[d.length]=encodeURIComponent(a)+\"=\"+encodeURIComponent(null==c?\"\":c)};if(r.isArray(a)||a.jquery&&!r.isPlainObject(a))r.each(a,function(){e(this.name,this.value)});else for(c in a)xb(c,a[c],b,e);return d.join(\"&\")},r.fn.extend({serialize:function(){return r.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=r.prop(this,\"elements\");return a?r.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!r(this).is(\":disabled\")&&wb.test(this.nodeName)&&!vb.test(a)&&(this.checked||!ha.test(a))}).map(function(a,b){var c=r(this).val();return null==c?null:r.isArray(c)?r.map(c,function(a){return{name:b.name,value:a.replace(ub,\"\\r\\n\")}}):{name:b.name,value:c.replace(ub,\"\\r\\n\")}}).get()}});var yb=/%20/g,zb=/#.*$/,Ab=/([?&])_=[^&]*/,Bb=/^(.*?):[ \\t]*([^\\r\\n]*)$/gm,Cb=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Db=/^(?:GET|HEAD)$/,Eb=/^\\/\\//,Fb={},Gb={},Hb=\"*/\".concat(\"*\"),Ib=d.createElement(\"a\");Ib.href=qb.href;function Jb(a){return function(b,c){\"string\"!=typeof b&&(c=b,b=\"*\");var d,e=0,f=b.toLowerCase().match(K)||[];if(r.isFunction(c))while(d=f[e++])\"+\"===d[0]?(d=d.slice(1)||\"*\",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function Kb(a,b,c,d){var e={},f=a===Gb;function g(h){var i;return e[h]=!0,r.each(a[h]||[],function(a,h){var j=h(b,c,d);return\"string\"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e[\"*\"]&&g(\"*\")}function Lb(a,b){var c,d,e=r.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&r.extend(!0,a,d),a}function Mb(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while(\"*\"===i[0])i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader(\"Content-Type\"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+\" \"+i[0]]){f=e;break}g||(g=e)}f=f||g}if(f)return f!==i[0]&&i.unshift(f),c[f]}function Nb(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if(\"*\"===f)f=i;else if(\"*\"!==i&&i!==f){if(g=j[i+\" \"+f]||j[\"* \"+f],!g)for(e in j)if(h=e.split(\" \"),h[1]===f&&(g=j[i+\" \"+h[0]]||j[\"* \"+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a[\"throws\"])b=g(b);else try{b=g(b)}catch(l){return{state:\"parsererror\",error:g?l:\"No conversion from \"+i+\" to \"+f}}}return{state:\"success\",data:b}}r.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:qb.href,type:\"GET\",isLocal:Cb.test(qb.protocol),global:!0,processData:!0,async:!0,contentType:\"application/x-www-form-urlencoded; charset=UTF-8\",accepts:{\"*\":Hb,text:\"text/plain\",html:\"text/html\",xml:\"application/xml, text/xml\",json:\"application/json, text/javascript\"},contents:{xml:/\\bxml\\b/,html:/\\bhtml/,json:/\\bjson\\b/},responseFields:{xml:\"responseXML\",text:\"responseText\",json:\"responseJSON\"},converters:{\"* text\":String,\"text html\":!0,\"text json\":JSON.parse,\"text xml\":r.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?Lb(Lb(a,r.ajaxSettings),b):Lb(r.ajaxSettings,a)},ajaxPrefilter:Jb(Fb),ajaxTransport:Jb(Gb),ajax:function(b,c){\"object\"==typeof b&&(c=b,b=void 0),c=c||{};var e,f,g,h,i,j,k,l,m,n,o=r.ajaxSetup({},c),p=o.context||o,q=o.context&&(p.nodeType||p.jquery)?r(p):r.event,s=r.Deferred(),t=r.Callbacks(\"once memory\"),u=o.statusCode||{},v={},w={},x=\"canceled\",y={readyState:0,getResponseHeader:function(a){var b;if(k){if(!h){h={};while(b=Bb.exec(g))h[b[1].toLowerCase()]=b[2]}b=h[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return k?g:null},setRequestHeader:function(a,b){return null==k&&(a=w[a.toLowerCase()]=w[a.toLowerCase()]||a,v[a]=b),this},overrideMimeType:function(a){return null==k&&(o.mimeType=a),this},statusCode:function(a){var b;if(a)if(k)y.always(a[y.status]);else for(b in a)u[b]=[u[b],a[b]];return this},abort:function(a){var b=a||x;return e&&e.abort(b),A(0,b),this}};if(s.promise(y),o.url=((b||o.url||qb.href)+\"\").replace(Eb,qb.protocol+\"//\"),o.type=c.method||c.type||o.method||o.type,o.dataTypes=(o.dataType||\"*\").toLowerCase().match(K)||[\"\"],null==o.crossDomain){j=d.createElement(\"a\");try{j.href=o.url,j.href=j.href,o.crossDomain=Ib.protocol+\"//\"+Ib.host!=j.protocol+\"//\"+j.host}catch(z){o.crossDomain=!0}}if(o.data&&o.processData&&\"string\"!=typeof o.data&&(o.data=r.param(o.data,o.traditional)),Kb(Fb,o,c,y),k)return y;l=r.event&&o.global,l&&0===r.active++&&r.event.trigger(\"ajaxStart\"),o.type=o.type.toUpperCase(),o.hasContent=!Db.test(o.type),f=o.url.replace(zb,\"\"),o.hasContent?o.data&&o.processData&&0===(o.contentType||\"\").indexOf(\"application/x-www-form-urlencoded\")&&(o.data=o.data.replace(yb,\"+\")):(n=o.url.slice(f.length),o.data&&(f+=(sb.test(f)?\"&\":\"?\")+o.data,delete o.data),o.cache===!1&&(f=f.replace(Ab,\"\"),n=(sb.test(f)?\"&\":\"?\")+\"_=\"+rb++ +n),o.url=f+n),o.ifModified&&(r.lastModified[f]&&y.setRequestHeader(\"If-Modified-Since\",r.lastModified[f]),r.etag[f]&&y.setRequestHeader(\"If-None-Match\",r.etag[f])),(o.data&&o.hasContent&&o.contentType!==!1||c.contentType)&&y.setRequestHeader(\"Content-Type\",o.contentType),y.setRequestHeader(\"Accept\",o.dataTypes[0]&&o.accepts[o.dataTypes[0]]?o.accepts[o.dataTypes[0]]+(\"*\"!==o.dataTypes[0]?\", \"+Hb+\"; q=0.01\":\"\"):o.accepts[\"*\"]);for(m in o.headers)y.setRequestHeader(m,o.headers[m]);if(o.beforeSend&&(o.beforeSend.call(p,y,o)===!1||k))return y.abort();if(x=\"abort\",t.add(o.complete),y.done(o.success),y.fail(o.error),e=Kb(Gb,o,c,y)){if(y.readyState=1,l&&q.trigger(\"ajaxSend\",[y,o]),k)return y;o.async&&o.timeout>0&&(i=a.setTimeout(function(){y.abort(\"timeout\")},o.timeout));try{k=!1,e.send(v,A)}catch(z){if(k)throw z;A(-1,z)}}else A(-1,\"No Transport\");function A(b,c,d,h){var j,m,n,v,w,x=c;k||(k=!0,i&&a.clearTimeout(i),e=void 0,g=h||\"\",y.readyState=b>0?4:0,j=b>=200&&b<300||304===b,d&&(v=Mb(o,y,d)),v=Nb(o,v,y,j),j?(o.ifModified&&(w=y.getResponseHeader(\"Last-Modified\"),w&&(r.lastModified[f]=w),w=y.getResponseHeader(\"etag\"),w&&(r.etag[f]=w)),204===b||\"HEAD\"===o.type?x=\"nocontent\":304===b?x=\"notmodified\":(x=v.state,m=v.data,n=v.error,j=!n)):(n=x,!b&&x||(x=\"error\",b<0&&(b=0))),y.status=b,y.statusText=(c||x)+\"\",j?s.resolveWith(p,[m,x,y]):s.rejectWith(p,[y,x,n]),y.statusCode(u),u=void 0,l&&q.trigger(j?\"ajaxSuccess\":\"ajaxError\",[y,o,j?m:n]),t.fireWith(p,[y,x]),l&&(q.trigger(\"ajaxComplete\",[y,o]),--r.active||r.event.trigger(\"ajaxStop\")))}return y},getJSON:function(a,b,c){return r.get(a,b,c,\"json\")},getScript:function(a,b){return r.get(a,void 0,b,\"script\")}}),r.each([\"get\",\"post\"],function(a,b){r[b]=function(a,c,d,e){return r.isFunction(c)&&(e=e||d,d=c,c=void 0),r.ajax(r.extend({url:a,type:b,dataType:e,data:c,success:d},r.isPlainObject(a)&&a))}}),r._evalUrl=function(a){return r.ajax({url:a,type:\"GET\",dataType:\"script\",cache:!0,async:!1,global:!1,\"throws\":!0})},r.fn.extend({wrapAll:function(a){var b;return this[0]&&(r.isFunction(a)&&(a=a.call(this[0])),b=r(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this},wrapInner:function(a){return r.isFunction(a)?this.each(function(b){r(this).wrapInner(a.call(this,b))}):this.each(function(){var b=r(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=r.isFunction(a);return this.each(function(c){r(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(a){return this.parent(a).not(\"body\").each(function(){r(this).replaceWith(this.childNodes)}),this}}),r.expr.pseudos.hidden=function(a){return!r.expr.pseudos.visible(a)},r.expr.pseudos.visible=function(a){return!!(a.offsetWidth||a.offsetHeight||a.getClientRects().length)},r.ajaxSettings.xhr=function(){try{return new a.XMLHttpRequest}catch(b){}};var Ob={0:200,1223:204},Pb=r.ajaxSettings.xhr();o.cors=!!Pb&&\"withCredentials\"in Pb,o.ajax=Pb=!!Pb,r.ajaxTransport(function(b){var c,d;if(o.cors||Pb&&!b.crossDomain)return{send:function(e,f){var g,h=b.xhr();if(h.open(b.type,b.url,b.async,b.username,b.password),b.xhrFields)for(g in b.xhrFields)h[g]=b.xhrFields[g];b.mimeType&&h.overrideMimeType&&h.overrideMimeType(b.mimeType),b.crossDomain||e[\"X-Requested-With\"]||(e[\"X-Requested-With\"]=\"XMLHttpRequest\");for(g in e)h.setRequestHeader(g,e[g]);c=function(a){return function(){c&&(c=d=h.onload=h.onerror=h.onabort=h.onreadystatechange=null,\"abort\"===a?h.abort():\"error\"===a?\"number\"!=typeof h.status?f(0,\"error\"):f(h.status,h.statusText):f(Ob[h.status]||h.status,h.statusText,\"text\"!==(h.responseType||\"text\")||\"string\"!=typeof h.responseText?{binary:h.response}:{text:h.responseText},h.getAllResponseHeaders()))}},h.onload=c(),d=h.onerror=c(\"error\"),void 0!==h.onabort?h.onabort=d:h.onreadystatechange=function(){4===h.readyState&&a.setTimeout(function(){c&&d()})},c=c(\"abort\");try{h.send(b.hasContent&&b.data||null)}catch(i){if(c)throw i}},abort:function(){c&&c()}}}),r.ajaxPrefilter(function(a){a.crossDomain&&(a.contents.script=!1)}),r.ajaxSetup({accepts:{script:\"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript\"},contents:{script:/\\b(?:java|ecma)script\\b/},converters:{\"text script\":function(a){return r.globalEval(a),a}}}),r.ajaxPrefilter(\"script\",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type=\"GET\")}),r.ajaxTransport(\"script\",function(a){if(a.crossDomain){var b,c;return{send:function(e,f){b=r(\"<script>\").prop({charset:a.scriptCharset,src:a.url}).on(\"load error\",c=function(a){b.remove(),c=null,a&&f(\"error\"===a.type?404:200,a.type)}),d.head.appendChild(b[0])},abort:function(){c&&c()}}}});var Qb=[],Rb=/(=)\\?(?=&|$)|\\?\\?/;r.ajaxSetup({jsonp:\"callback\",jsonpCallback:function(){var a=Qb.pop()||r.expando+\"_\"+rb++;return this[a]=!0,a}}),r.ajaxPrefilter(\"json jsonp\",function(b,c,d){var e,f,g,h=b.jsonp!==!1&&(Rb.test(b.url)?\"url\":\"string\"==typeof b.data&&0===(b.contentType||\"\").indexOf(\"application/x-www-form-urlencoded\")&&Rb.test(b.data)&&\"data\");if(h||\"jsonp\"===b.dataTypes[0])return e=b.jsonpCallback=r.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,h?b[h]=b[h].replace(Rb,\"$1\"+e):b.jsonp!==!1&&(b.url+=(sb.test(b.url)?\"&\":\"?\")+b.jsonp+\"=\"+e),b.converters[\"script json\"]=function(){return g||r.error(e+\" was not called\"),g[0]},b.dataTypes[0]=\"json\",f=a[e],a[e]=function(){g=arguments},d.always(function(){void 0===f?r(a).removeProp(e):a[e]=f,b[e]&&(b.jsonpCallback=c.jsonpCallback,Qb.push(e)),g&&r.isFunction(f)&&f(g[0]),g=f=void 0}),\"script\"}),o.createHTMLDocument=function(){var a=d.implementation.createHTMLDocument(\"\").body;return a.innerHTML=\"<form></form><form></form>\",2===a.childNodes.length}(),r.parseHTML=function(a,b,c){if(\"string\"!=typeof a)return[];\"boolean\"==typeof b&&(c=b,b=!1);var e,f,g;return b||(o.createHTMLDocument?(b=d.implementation.createHTMLDocument(\"\"),e=b.createElement(\"base\"),e.href=d.location.href,b.head.appendChild(e)):b=d),f=B.exec(a),g=!c&&[],f?[b.createElement(f[1])]:(f=oa([a],b,g),g&&g.length&&r(g).remove(),r.merge([],f.childNodes))},r.fn.load=function(a,b,c){var d,e,f,g=this,h=a.indexOf(\" \");return h>-1&&(d=r.trim(a.slice(h)),a=a.slice(0,h)),r.isFunction(b)?(c=b,b=void 0):b&&\"object\"==typeof b&&(e=\"POST\"),g.length>0&&r.ajax({url:a,type:e||\"GET\",dataType:\"html\",data:b}).done(function(a){f=arguments,g.html(d?r(\"<div>\").append(r.parseHTML(a)).find(d):a)}).always(c&&function(a,b){g.each(function(){c.apply(this,f||[a.responseText,b,a])})}),this},r.each([\"ajaxStart\",\"ajaxStop\",\"ajaxComplete\",\"ajaxError\",\"ajaxSuccess\",\"ajaxSend\"],function(a,b){r.fn[b]=function(a){return this.on(b,a)}}),r.expr.pseudos.animated=function(a){return r.grep(r.timers,function(b){return a===b.elem}).length};function Sb(a){return r.isWindow(a)?a:9===a.nodeType&&a.defaultView}r.offset={setOffset:function(a,b,c){var d,e,f,g,h,i,j,k=r.css(a,\"position\"),l=r(a),m={};\"static\"===k&&(a.style.position=\"relative\"),h=l.offset(),f=r.css(a,\"top\"),i=r.css(a,\"left\"),j=(\"absolute\"===k||\"fixed\"===k)&&(f+i).indexOf(\"auto\")>-1,j?(d=l.position(),g=d.top,e=d.left):(g=parseFloat(f)||0,e=parseFloat(i)||0),r.isFunction(b)&&(b=b.call(a,c,r.extend({},h))),null!=b.top&&(m.top=b.top-h.top+g),null!=b.left&&(m.left=b.left-h.left+e),\"using\"in b?b.using.call(a,m):l.css(m)}},r.fn.extend({offset:function(a){if(arguments.length)return void 0===a?this:this.each(function(b){r.offset.setOffset(this,a,b)});var b,c,d,e,f=this[0];if(f)return f.getClientRects().length?(d=f.getBoundingClientRect(),d.width||d.height?(e=f.ownerDocument,c=Sb(e),b=e.documentElement,{top:d.top+c.pageYOffset-b.clientTop,left:d.left+c.pageXOffset-b.clientLeft}):d):{top:0,left:0}},position:function(){if(this[0]){var a,b,c=this[0],d={top:0,left:0};return\"fixed\"===r.css(c,\"position\")?b=c.getBoundingClientRect():(a=this.offsetParent(),b=this.offset(),r.nodeName(a[0],\"html\")||(d=a.offset()),d={top:d.top+r.css(a[0],\"borderTopWidth\",!0),left:d.left+r.css(a[0],\"borderLeftWidth\",!0)}),{top:b.top-d.top-r.css(c,\"marginTop\",!0),left:b.left-d.left-r.css(c,\"marginLeft\",!0)}}},offsetParent:function(){return this.map(function(){var a=this.offsetParent;while(a&&\"static\"===r.css(a,\"position\"))a=a.offsetParent;return a||pa})}}),r.each({scrollLeft:\"pageXOffset\",scrollTop:\"pageYOffset\"},function(a,b){var c=\"pageYOffset\"===b;r.fn[a]=function(d){return S(this,function(a,d,e){var f=Sb(a);return void 0===e?f?f[b]:a[d]:void(f?f.scrollTo(c?f.pageXOffset:e,c?e:f.pageYOffset):a[d]=e)},a,d,arguments.length)}}),r.each([\"top\",\"left\"],function(a,b){r.cssHooks[b]=Na(o.pixelPosition,function(a,c){if(c)return c=Ma(a,b),Ka.test(c)?r(a).position()[b]+\"px\":c})}),r.each({Height:\"height\",Width:\"width\"},function(a,b){r.each({padding:\"inner\"+a,content:b,\"\":\"outer\"+a},function(c,d){r.fn[d]=function(e,f){var g=arguments.length&&(c||\"boolean\"!=typeof e),h=c||(e===!0||f===!0?\"margin\":\"border\");return S(this,function(b,c,e){var f;return r.isWindow(b)?0===d.indexOf(\"outer\")?b[\"inner\"+a]:b.document.documentElement[\"client\"+a]:9===b.nodeType?(f=b.documentElement,Math.max(b.body[\"scroll\"+a],f[\"scroll\"+a],b.body[\"offset\"+a],f[\"offset\"+a],f[\"client\"+a])):void 0===e?r.css(b,c,h):r.style(b,c,e,h)},b,g?e:void 0,g)}})}),r.fn.extend({bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return 1===arguments.length?this.off(a,\"**\"):this.off(b,a||\"**\",c)}}),r.parseJSON=JSON.parse,\"function\"==typeof define&&define.amd&&define(\"jquery\",[],function(){return r});var Tb=a.jQuery,Ub=a.$;return r.noConflict=function(b){return a.$===r&&(a.$=Ub),b&&a.jQuery===r&&(a.jQuery=Tb),r},b||(a.jQuery=a.$=r),r});\n</script>\n\n  <script>\n!function(){var q=null;window.PR_SHOULD_USE_CONTINUATION=!0;\n(function(){function S(a){function d(e){var b=e.charCodeAt(0);if(b!==92)return b;var a=e.charAt(1);return(b=r[a])?b:\"0\"<=a&&a<=\"7\"?parseInt(e.substring(1),8):a===\"u\"||a===\"x\"?parseInt(e.substring(2),16):e.charCodeAt(1)}function g(e){if(e<32)return(e<16?\"\\\\x0\":\"\\\\x\")+e.toString(16);e=String.fromCharCode(e);return e===\"\\\\\"||e===\"-\"||e===\"]\"||e===\"^\"?\"\\\\\"+e:e}function b(e){var b=e.substring(1,e.length-1).match(/\\\\u[\\dA-Fa-f]{4}|\\\\x[\\dA-Fa-f]{2}|\\\\[0-3][0-7]{0,2}|\\\\[0-7]{1,2}|\\\\[\\S\\s]|[^\\\\]/g),e=[],a=\nb[0]===\"^\",c=[\"[\"];a&&c.push(\"^\");for(var a=a?1:0,f=b.length;a<f;++a){var h=b[a];if(/\\\\[bdsw]/i.test(h))c.push(h);else{var h=d(h),l;a+2<f&&\"-\"===b[a+1]?(l=d(b[a+2]),a+=2):l=h;e.push([h,l]);l<65||h>122||(l<65||h>90||e.push([Math.max(65,h)|32,Math.min(l,90)|32]),l<97||h>122||e.push([Math.max(97,h)&-33,Math.min(l,122)&-33]))}}e.sort(function(e,a){return e[0]-a[0]||a[1]-e[1]});b=[];f=[];for(a=0;a<e.length;++a)h=e[a],h[0]<=f[1]+1?f[1]=Math.max(f[1],h[1]):b.push(f=h);for(a=0;a<b.length;++a)h=b[a],c.push(g(h[0])),\nh[1]>h[0]&&(h[1]+1>h[0]&&c.push(\"-\"),c.push(g(h[1])));c.push(\"]\");return c.join(\"\")}function s(e){for(var a=e.source.match(/\\[(?:[^\\\\\\]]|\\\\[\\S\\s])*]|\\\\u[\\dA-Fa-f]{4}|\\\\x[\\dA-Fa-f]{2}|\\\\\\d+|\\\\[^\\dux]|\\(\\?[!:=]|[()^]|[^()[\\\\^]+/g),c=a.length,d=[],f=0,h=0;f<c;++f){var l=a[f];l===\"(\"?++h:\"\\\\\"===l.charAt(0)&&(l=+l.substring(1))&&(l<=h?d[l]=-1:a[f]=g(l))}for(f=1;f<d.length;++f)-1===d[f]&&(d[f]=++x);for(h=f=0;f<c;++f)l=a[f],l===\"(\"?(++h,d[h]||(a[f]=\"(?:\")):\"\\\\\"===l.charAt(0)&&(l=+l.substring(1))&&l<=h&&\n(a[f]=\"\\\\\"+d[l]);for(f=0;f<c;++f)\"^\"===a[f]&&\"^\"!==a[f+1]&&(a[f]=\"\");if(e.ignoreCase&&m)for(f=0;f<c;++f)l=a[f],e=l.charAt(0),l.length>=2&&e===\"[\"?a[f]=b(l):e!==\"\\\\\"&&(a[f]=l.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return\"[\"+String.fromCharCode(a&-33,a|32)+\"]\"}));return a.join(\"\")}for(var x=0,m=!1,j=!1,k=0,c=a.length;k<c;++k){var i=a[k];if(i.ignoreCase)j=!0;else if(/[a-z]/i.test(i.source.replace(/\\\\u[\\da-f]{4}|\\\\x[\\da-f]{2}|\\\\[^UXux]/gi,\"\"))){m=!0;j=!1;break}}for(var r={b:8,t:9,n:10,v:11,\nf:12,r:13},n=[],k=0,c=a.length;k<c;++k){i=a[k];if(i.global||i.multiline)throw Error(\"\"+i);n.push(\"(?:\"+s(i)+\")\")}return RegExp(n.join(\"|\"),j?\"gi\":\"g\")}function T(a,d){function g(a){var c=a.nodeType;if(c==1){if(!b.test(a.className)){for(c=a.firstChild;c;c=c.nextSibling)g(c);c=a.nodeName.toLowerCase();if(\"br\"===c||\"li\"===c)s[j]=\"\\n\",m[j<<1]=x++,m[j++<<1|1]=a}}else if(c==3||c==4)c=a.nodeValue,c.length&&(c=d?c.replace(/\\r\\n?/g,\"\\n\"):c.replace(/[\\t\\n\\r ]+/g,\" \"),s[j]=c,m[j<<1]=x,x+=c.length,m[j++<<1|1]=\na)}var b=/(?:^|\\s)nocode(?:\\s|$)/,s=[],x=0,m=[],j=0;g(a);return{a:s.join(\"\").replace(/\\n$/,\"\"),d:m}}function H(a,d,g,b){d&&(a={a:d,e:a},g(a),b.push.apply(b,a.g))}function U(a){for(var d=void 0,g=a.firstChild;g;g=g.nextSibling)var b=g.nodeType,d=b===1?d?a:g:b===3?V.test(g.nodeValue)?a:d:d;return d===a?void 0:d}function C(a,d){function g(a){for(var j=a.e,k=[j,\"pln\"],c=0,i=a.a.match(s)||[],r={},n=0,e=i.length;n<e;++n){var z=i[n],w=r[z],t=void 0,f;if(typeof w===\"string\")f=!1;else{var h=b[z.charAt(0)];\nif(h)t=z.match(h[1]),w=h[0];else{for(f=0;f<x;++f)if(h=d[f],t=z.match(h[1])){w=h[0];break}t||(w=\"pln\")}if((f=w.length>=5&&\"lang-\"===w.substring(0,5))&&!(t&&typeof t[1]===\"string\"))f=!1,w=\"src\";f||(r[z]=w)}h=c;c+=z.length;if(f){f=t[1];var l=z.indexOf(f),B=l+f.length;t[2]&&(B=z.length-t[2].length,l=B-f.length);w=w.substring(5);H(j+h,z.substring(0,l),g,k);H(j+h+l,f,I(w,f),k);H(j+h+B,z.substring(B),g,k)}else k.push(j+h,w)}a.g=k}var b={},s;(function(){for(var g=a.concat(d),j=[],k={},c=0,i=g.length;c<i;++c){var r=\ng[c],n=r[3];if(n)for(var e=n.length;--e>=0;)b[n.charAt(e)]=r;r=r[1];n=\"\"+r;k.hasOwnProperty(n)||(j.push(r),k[n]=q)}j.push(/[\\S\\s]/);s=S(j)})();var x=d.length;return g}function v(a){var d=[],g=[];a.tripleQuotedStrings?d.push([\"str\",/^(?:'''(?:[^'\\\\]|\\\\[\\S\\s]|''?(?=[^']))*(?:'''|$)|\"\"\"(?:[^\"\\\\]|\\\\[\\S\\s]|\"\"?(?=[^\"]))*(?:\"\"\"|$)|'(?:[^'\\\\]|\\\\[\\S\\s])*(?:'|$)|\"(?:[^\"\\\\]|\\\\[\\S\\s])*(?:\"|$))/,q,\"'\\\"\"]):a.multiLineStrings?d.push([\"str\",/^(?:'(?:[^'\\\\]|\\\\[\\S\\s])*(?:'|$)|\"(?:[^\"\\\\]|\\\\[\\S\\s])*(?:\"|$)|`(?:[^\\\\`]|\\\\[\\S\\s])*(?:`|$))/,\nq,\"'\\\"`\"]):d.push([\"str\",/^(?:'(?:[^\\n\\r'\\\\]|\\\\.)*(?:'|$)|\"(?:[^\\n\\r\"\\\\]|\\\\.)*(?:\"|$))/,q,\"\\\"'\"]);a.verbatimStrings&&g.push([\"str\",/^@\"(?:[^\"]|\"\")*(?:\"|$)/,q]);var b=a.hashComments;b&&(a.cStyleComments?(b>1?d.push([\"com\",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,\"#\"]):d.push([\"com\",/^#(?:(?:define|e(?:l|nd)if|else|error|ifn?def|include|line|pragma|undef|warning)\\b|[^\\n\\r]*)/,q,\"#\"]),g.push([\"str\",/^<(?:(?:(?:\\.\\.\\/)*|\\/?)(?:[\\w-]+(?:\\/[\\w-]+)+)?[\\w-]+\\.h(?:h|pp|\\+\\+)?|[a-z]\\w*)>/,q])):d.push([\"com\",\n/^#[^\\n\\r]*/,q,\"#\"]));a.cStyleComments&&(g.push([\"com\",/^\\/\\/[^\\n\\r]*/,q]),g.push([\"com\",/^\\/\\*[\\S\\s]*?(?:\\*\\/|$)/,q]));if(b=a.regexLiterals){var s=(b=b>1?\"\":\"\\n\\r\")?\".\":\"[\\\\S\\\\s]\";g.push([\"lang-regex\",RegExp(\"^(?:^^\\\\.?|[+-]|[!=]=?=?|\\\\#|%=?|&&?=?|\\\\(|\\\\*=?|[+\\\\-]=|->|\\\\/=?|::?|<<?=?|>>?>?=?|,|;|\\\\?|@|\\\\[|~|{|\\\\^\\\\^?=?|\\\\|\\\\|?=?|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\\\s*(\"+(\"/(?=[^/*\"+b+\"])(?:[^/\\\\x5B\\\\x5C\"+b+\"]|\\\\x5C\"+s+\"|\\\\x5B(?:[^\\\\x5C\\\\x5D\"+b+\"]|\\\\x5C\"+\ns+\")*(?:\\\\x5D|$))+/\")+\")\")])}(b=a.types)&&g.push([\"typ\",b]);b=(\"\"+a.keywords).replace(/^ | $/g,\"\");b.length&&g.push([\"kwd\",RegExp(\"^(?:\"+b.replace(/[\\s,]+/g,\"|\")+\")\\\\b\"),q]);d.push([\"pln\",/^\\s+/,q,\" \\r\\n\\t\\u00a0\"]);b=\"^.[^\\\\s\\\\w.$@'\\\"`/\\\\\\\\]*\";a.regexLiterals&&(b+=\"(?!s*/)\");g.push([\"lit\",/^@[$_a-z][\\w$@]*/i,q],[\"typ\",/^(?:[@_]?[A-Z]+[a-z][\\w$@]*|\\w+_t\\b)/,q],[\"pln\",/^[$_a-z][\\w$@]*/i,q],[\"lit\",/^(?:0x[\\da-f]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+-]?\\d+)?)[a-z]*/i,q,\"0123456789\"],[\"pln\",/^\\\\[\\S\\s]?/,\nq],[\"pun\",RegExp(b),q]);return C(d,g)}function J(a,d,g){function b(a){var c=a.nodeType;if(c==1&&!x.test(a.className))if(\"br\"===a.nodeName)s(a),a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)b(a);else if((c==3||c==4)&&g){var d=a.nodeValue,i=d.match(m);if(i)c=d.substring(0,i.index),a.nodeValue=c,(d=d.substring(i.index+i[0].length))&&a.parentNode.insertBefore(j.createTextNode(d),a.nextSibling),s(a),c||a.parentNode.removeChild(a)}}function s(a){function b(a,c){var d=\nc?a.cloneNode(!1):a,e=a.parentNode;if(e){var e=b(e,1),g=a.nextSibling;e.appendChild(d);for(var i=g;i;i=g)g=i.nextSibling,e.appendChild(i)}return d}for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),d;(d=a.parentNode)&&d.nodeType===1;)a=d;c.push(a)}for(var x=/(?:^|\\s)nocode(?:\\s|$)/,m=/\\r\\n?|\\n/,j=a.ownerDocument,k=j.createElement(\"li\");a.firstChild;)k.appendChild(a.firstChild);for(var c=[k],i=0;i<c.length;++i)b(c[i]);d===(d|0)&&c[0].setAttribute(\"value\",d);var r=j.createElement(\"ol\");\nr.className=\"linenums\";for(var d=Math.max(0,d-1|0)||0,i=0,n=c.length;i<n;++i)k=c[i],k.className=\"L\"+(i+d)%10,k.firstChild||k.appendChild(j.createTextNode(\"\\u00a0\")),r.appendChild(k);a.appendChild(r)}function p(a,d){for(var g=d.length;--g>=0;){var b=d[g];F.hasOwnProperty(b)?D.console&&console.warn(\"cannot override language handler %s\",b):F[b]=a}}function I(a,d){if(!a||!F.hasOwnProperty(a))a=/^\\s*</.test(d)?\"default-markup\":\"default-code\";return F[a]}function K(a){var d=a.h;try{var g=T(a.c,a.i),b=g.a;\na.a=b;a.d=g.d;a.e=0;I(d,b)(a);var s=/\\bMSIE\\s(\\d+)/.exec(navigator.userAgent),s=s&&+s[1]<=8,d=/\\n/g,x=a.a,m=x.length,g=0,j=a.d,k=j.length,b=0,c=a.g,i=c.length,r=0;c[i]=m;var n,e;for(e=n=0;e<i;)c[e]!==c[e+2]?(c[n++]=c[e++],c[n++]=c[e++]):e+=2;i=n;for(e=n=0;e<i;){for(var p=c[e],w=c[e+1],t=e+2;t+2<=i&&c[t+1]===w;)t+=2;c[n++]=p;c[n++]=w;e=t}c.length=n;var f=a.c,h;if(f)h=f.style.display,f.style.display=\"none\";try{for(;b<k;){var l=j[b+2]||m,B=c[r+2]||m,t=Math.min(l,B),A=j[b+1],G;if(A.nodeType!==1&&(G=x.substring(g,\nt))){s&&(G=G.replace(d,\"\\r\"));A.nodeValue=G;var L=A.ownerDocument,o=L.createElement(\"span\");o.className=c[r+1];var v=A.parentNode;v.replaceChild(o,A);o.appendChild(A);g<l&&(j[b+1]=A=L.createTextNode(x.substring(t,l)),v.insertBefore(A,o.nextSibling))}g=t;g>=l&&(b+=2);g>=B&&(r+=2)}}finally{if(f)f.style.display=h}}catch(u){D.console&&console.log(u&&u.stack||u)}}var D=window,y=[\"break,continue,do,else,for,if,return,while\"],E=[[y,\"auto,case,char,const,default,double,enum,extern,float,goto,inline,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile\"],\n\"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof\"],M=[E,\"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,delegate,dynamic_cast,explicit,export,friend,generic,late_check,mutable,namespace,nullptr,property,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where\"],N=[E,\"abstract,assert,boolean,byte,extends,final,finally,implements,import,instanceof,interface,null,native,package,strictfp,super,synchronized,throws,transient\"],\nO=[N,\"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,internal,into,is,let,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var,virtual,where\"],E=[E,\"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN\"],P=[y,\"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None\"],\nQ=[y,\"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END\"],W=[y,\"as,assert,const,copy,drop,enum,extern,fail,false,fn,impl,let,log,loop,match,mod,move,mut,priv,pub,pure,ref,self,static,struct,true,trait,type,unsafe,use\"],y=[y,\"case,done,elif,esac,eval,fi,function,in,local,set,then,until\"],R=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\\d*)\\b/,\nV=/\\S/,X=v({keywords:[M,O,E,\"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END\",P,Q,y],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),F={};p(X,[\"default-code\"]);p(C([],[[\"pln\",/^[^<?]+/],[\"dec\",/^<!\\w[^>]*(?:>|$)/],[\"com\",/^<\\!--[\\S\\s]*?(?:--\\>|$)/],[\"lang-\",/^<\\?([\\S\\s]+?)(?:\\?>|$)/],[\"lang-\",/^<%([\\S\\s]+?)(?:%>|$)/],[\"pun\",/^(?:<[%?]|[%?]>)/],[\"lang-\",\n/^<xmp\\b[^>]*>([\\S\\s]+?)<\\/xmp\\b[^>]*>/i],[\"lang-js\",/^<script\\b[^>]*>([\\S\\s]*?)(<\\/script\\b[^>]*>)/i],[\"lang-css\",/^<style\\b[^>]*>([\\S\\s]*?)(<\\/style\\b[^>]*>)/i],[\"lang-in.tag\",/^(<\\/?[a-z][^<>]*>)/i]]),[\"default-markup\",\"htm\",\"html\",\"mxml\",\"xhtml\",\"xml\",\"xsl\"]);p(C([[\"pln\",/^\\s+/,q,\" \\t\\r\\n\"],[\"atv\",/^(?:\"[^\"]*\"?|'[^']*'?)/,q,\"\\\"'\"]],[[\"tag\",/^^<\\/?[a-z](?:[\\w-.:]*\\w)?|\\/?>$/i],[\"atn\",/^(?!style[\\s=]|on)[a-z](?:[\\w:-]*\\w)?/i],[\"lang-uq.val\",/^=\\s*([^\\s\"'>]*(?:[^\\s\"'/>]|\\/(?=\\s)))/],[\"pun\",/^[/<->]+/],\n[\"lang-js\",/^on\\w+\\s*=\\s*\"([^\"]+)\"/i],[\"lang-js\",/^on\\w+\\s*=\\s*'([^']+)'/i],[\"lang-js\",/^on\\w+\\s*=\\s*([^\\s\"'>]+)/i],[\"lang-css\",/^style\\s*=\\s*\"([^\"]+)\"/i],[\"lang-css\",/^style\\s*=\\s*'([^']+)'/i],[\"lang-css\",/^style\\s*=\\s*([^\\s\"'>]+)/i]]),[\"in.tag\"]);p(C([],[[\"atv\",/^[\\S\\s]+/]]),[\"uq.val\"]);p(v({keywords:M,hashComments:!0,cStyleComments:!0,types:R}),[\"c\",\"cc\",\"cpp\",\"cxx\",\"cyc\",\"m\"]);p(v({keywords:\"null,true,false\"}),[\"json\"]);p(v({keywords:O,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:R}),\n[\"cs\"]);p(v({keywords:N,cStyleComments:!0}),[\"java\"]);p(v({keywords:y,hashComments:!0,multiLineStrings:!0}),[\"bash\",\"bsh\",\"csh\",\"sh\"]);p(v({keywords:P,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}),[\"cv\",\"py\",\"python\"]);p(v({keywords:\"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END\",hashComments:!0,multiLineStrings:!0,regexLiterals:2}),[\"perl\",\"pl\",\"pm\"]);p(v({keywords:Q,\nhashComments:!0,multiLineStrings:!0,regexLiterals:!0}),[\"rb\",\"ruby\"]);p(v({keywords:E,cStyleComments:!0,regexLiterals:!0}),[\"javascript\",\"js\"]);p(v({keywords:\"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,throw,true,try,unless,until,when,while,yes\",hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),[\"coffee\"]);p(v({keywords:W,cStyleComments:!0,multilineStrings:!0}),[\"rc\",\"rs\",\"rust\"]);\np(C([],[[\"str\",/^[\\S\\s]+/]]),[\"regex\"]);var Y=D.PR={createSimpleLexer:C,registerLangHandler:p,sourceDecorator:v,PR_ATTRIB_NAME:\"atn\",PR_ATTRIB_VALUE:\"atv\",PR_COMMENT:\"com\",PR_DECLARATION:\"dec\",PR_KEYWORD:\"kwd\",PR_LITERAL:\"lit\",PR_NOCODE:\"nocode\",PR_PLAIN:\"pln\",PR_PUNCTUATION:\"pun\",PR_SOURCE:\"src\",PR_STRING:\"str\",PR_TAG:\"tag\",PR_TYPE:\"typ\",prettyPrintOne:D.prettyPrintOne=function(a,d,g){var b=document.createElement(\"div\");b.innerHTML=\"<pre>\"+a+\"</pre>\";b=b.firstChild;g&&J(b,g,!0);K({h:d,j:g,c:b,i:1});\nreturn b.innerHTML},prettyPrint:D.prettyPrint=function(a,d){function g(){for(var b=D.PR_SHOULD_USE_CONTINUATION?c.now()+250:Infinity;i<p.length&&c.now()<b;i++){for(var d=p[i],j=h,k=d;k=k.previousSibling;){var m=k.nodeType,o=(m===7||m===8)&&k.nodeValue;if(o?!/^\\??prettify\\b/.test(o):m!==3||/\\S/.test(k.nodeValue))break;if(o){j={};o.replace(/\\b(\\w+)=([\\w%+\\-.:]+)/g,function(a,b,c){j[b]=c});break}}k=d.className;if((j!==h||e.test(k))&&!v.test(k)){m=!1;for(o=d.parentNode;o;o=o.parentNode)if(f.test(o.tagName)&&\no.className&&e.test(o.className)){m=!0;break}if(!m){d.className+=\" prettyprinted\";m=j.lang;if(!m){var m=k.match(n),y;if(!m&&(y=U(d))&&t.test(y.tagName))m=y.className.match(n);m&&(m=m[1])}if(w.test(d.tagName))o=1;else var o=d.currentStyle,u=s.defaultView,o=(o=o?o.whiteSpace:u&&u.getComputedStyle?u.getComputedStyle(d,q).getPropertyValue(\"white-space\"):0)&&\"pre\"===o.substring(0,3);u=j.linenums;if(!(u=u===\"true\"||+u))u=(u=k.match(/\\blinenums\\b(?::(\\d+))?/))?u[1]&&u[1].length?+u[1]:!0:!1;u&&J(d,u,o);r=\n{h:m,c:d,j:u,i:o};K(r)}}}i<p.length?setTimeout(g,250):\"function\"===typeof a&&a()}for(var b=d||document.body,s=b.ownerDocument||document,b=[b.getElementsByTagName(\"pre\"),b.getElementsByTagName(\"code\"),b.getElementsByTagName(\"xmp\")],p=[],m=0;m<b.length;++m)for(var j=0,k=b[m].length;j<k;++j)p.push(b[m][j]);var b=q,c=Date;c.now||(c={now:function(){return+new Date}});var i=0,r,n=/\\blang(?:uage)?-([\\w.]+)(?!\\S)/,e=/\\bprettyprint\\b/,v=/\\bprettyprinted\\b/,w=/pre|xmp/i,t=/^code$/i,f=/^(?:pre|code|xmp)$/i,\nh={};g()}};typeof define===\"function\"&&define.amd&&define(\"google-code-prettify\",[],function(){return Y})})();}()\n</script>\n\n  <script>\n/*!\n* Bootstrap.js by @fat & @mdo\n* Copyright 2013 Twitter, Inc.\n* https://www.apache.org/licenses/LICENSE-2.0.txt\n*/\n!function(e){\"use strict\";e(function(){e.support.transition=function(){var e=function(){var e=document.createElement(\"bootstrap\"),t={WebkitTransition:\"webkitTransitionEnd\",MozTransition:\"transitionend\",OTransition:\"oTransitionEnd otransitionend\",transition:\"transitionend\"},n;for(n in t)if(e.style[n]!==undefined)return t[n]}();return e&&{end:e}}()})}(window.jQuery),!function(e){\"use strict\";var t='[data-dismiss=\"alert\"]',n=function(n){e(n).on(\"click\",t,this.close)};n.prototype.close=function(t){function s(){i.trigger(\"closed\").remove()}var n=e(this),r=n.attr(\"data-target\"),i;r||(r=n.attr(\"href\"),r=r&&r.replace(/.*(?=#[^\\s]*$)/,\"\")),i=e(r),t&&t.preventDefault(),i.length||(i=n.hasClass(\"alert\")?n:n.parent()),i.trigger(t=e.Event(\"close\"));if(t.isDefaultPrevented())return;i.removeClass(\"in\"),e.support.transition&&i.hasClass(\"fade\")?i.on(e.support.transition.end,s):s()};var r=e.fn.alert;e.fn.alert=function(t){return this.each(function(){var r=e(this),i=r.data(\"alert\");i||r.data(\"alert\",i=new n(this)),typeof t==\"string\"&&i[t].call(r)})},e.fn.alert.Constructor=n,e.fn.alert.noConflict=function(){return e.fn.alert=r,this},e(document).on(\"click.alert.data-api\",t,n.prototype.close)}(window.jQuery),!function(e){\"use strict\";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.button.defaults,n)};t.prototype.setState=function(e){var t=\"disabled\",n=this.$element,r=n.data(),i=n.is(\"input\")?\"val\":\"html\";e+=\"Text\",r.resetText||n.data(\"resetText\",n[i]()),n[i](r[e]||this.options[e]),setTimeout(function(){e==\"loadingText\"?n.addClass(t).attr(t,t):n.removeClass(t).removeAttr(t)},0)},t.prototype.toggle=function(){var e=this.$element.closest('[data-toggle=\"buttons-radio\"]');e&&e.find(\".active\").removeClass(\"active\"),this.$element.toggleClass(\"active\")};var n=e.fn.button;e.fn.button=function(n){return this.each(function(){var r=e(this),i=r.data(\"button\"),s=typeof n==\"object\"&&n;i||r.data(\"button\",i=new t(this,s)),n==\"toggle\"?i.toggle():n&&i.setState(n)})},e.fn.button.defaults={loadingText:\"loading...\"},e.fn.button.Constructor=t,e.fn.button.noConflict=function(){return e.fn.button=n,this},e(document).on(\"click.button.data-api\",\"[data-toggle^=button]\",function(t){var n=e(t.target);n.hasClass(\"btn\")||(n=n.closest(\".btn\")),n.button(\"toggle\")})}(window.jQuery),!function(e){\"use strict\";var t=function(t,n){this.$element=e(t),this.$indicators=this.$element.find(\".carousel-indicators\"),this.options=n,this.options.pause==\"hover\"&&this.$element.on(\"mouseenter\",e.proxy(this.pause,this)).on(\"mouseleave\",e.proxy(this.cycle,this))};t.prototype={cycle:function(t){return t||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(e.proxy(this.next,this),this.options.interval)),this},getActiveIndex:function(){return this.$active=this.$element.find(\".item.active\"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},to:function(t){var n=this.getActiveIndex(),r=this;if(t>this.$items.length-1||t<0)return;return this.sliding?this.$element.one(\"slid\",function(){r.to(t)}):n==t?this.pause().cycle():this.slide(t>n?\"next\":\"prev\",e(this.$items[t]))},pause:function(t){return t||(this.paused=!0),this.$element.find(\".next, .prev\").length&&e.support.transition.end&&(this.$element.trigger(e.support.transition.end),this.cycle(!0)),clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide(\"next\")},prev:function(){if(this.sliding)return;return this.slide(\"prev\")},slide:function(t,n){var r=this.$element.find(\".item.active\"),i=n||r[t](),s=this.interval,o=t==\"next\"?\"left\":\"right\",u=t==\"next\"?\"first\":\"last\",a=this,f;this.sliding=!0,s&&this.pause(),i=i.length?i:this.$element.find(\".item\")[u](),f=e.Event(\"slide\",{relatedTarget:i[0],direction:o});if(i.hasClass(\"active\"))return;this.$indicators.length&&(this.$indicators.find(\".active\").removeClass(\"active\"),this.$element.one(\"slid\",function(){var t=e(a.$indicators.children()[a.getActiveIndex()]);t&&t.addClass(\"active\")}));if(e.support.transition&&this.$element.hasClass(\"slide\")){this.$element.trigger(f);if(f.isDefaultPrevented())return;i.addClass(t),i[0].offsetWidth,r.addClass(o),i.addClass(o),this.$element.one(e.support.transition.end,function(){i.removeClass([t,o].join(\" \")).addClass(\"active\"),r.removeClass([\"active\",o].join(\" \")),a.sliding=!1,setTimeout(function(){a.$element.trigger(\"slid\")},0)})}else{this.$element.trigger(f);if(f.isDefaultPrevented())return;r.removeClass(\"active\"),i.addClass(\"active\"),this.sliding=!1,this.$element.trigger(\"slid\")}return s&&this.cycle(),this}};var n=e.fn.carousel;e.fn.carousel=function(n){return this.each(function(){var r=e(this),i=r.data(\"carousel\"),s=e.extend({},e.fn.carousel.defaults,typeof n==\"object\"&&n),o=typeof n==\"string\"?n:s.slide;i||r.data(\"carousel\",i=new t(this,s)),typeof n==\"number\"?i.to(n):o?i[o]():s.interval&&i.pause().cycle()})},e.fn.carousel.defaults={interval:5e3,pause:\"hover\"},e.fn.carousel.Constructor=t,e.fn.carousel.noConflict=function(){return e.fn.carousel=n,this},e(document).on(\"click.carousel.data-api\",\"[data-slide], [data-slide-to]\",function(t){var n=e(this),r,i=e(n.attr(\"data-target\")||(r=n.attr(\"href\"))&&r.replace(/.*(?=#[^\\s]+$)/,\"\")),s=e.extend({},i.data(),n.data()),o;i.carousel(s),(o=n.attr(\"data-slide-to\"))&&i.data(\"carousel\").pause().to(o).cycle(),t.preventDefault()})}(window.jQuery),!function(e){\"use strict\";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.collapse.defaults,n),this.options.parent&&(this.$parent=e(this.options.parent)),this.options.toggle&&this.toggle()};t.prototype={constructor:t,dimension:function(){var e=this.$element.hasClass(\"width\");return e?\"width\":\"height\"},show:function(){var t,n,r,i;if(this.transitioning||this.$element.hasClass(\"in\"))return;t=this.dimension(),n=e.camelCase([\"scroll\",t].join(\"-\")),r=this.$parent&&this.$parent.find(\"> .accordion-group > .in\");if(r&&r.length){i=r.data(\"collapse\");if(i&&i.transitioning)return;r.collapse(\"hide\"),i||r.data(\"collapse\",null)}this.$element[t](0),this.transition(\"addClass\",e.Event(\"show\"),\"shown\"),e.support.transition&&this.$element[t](this.$element[0][n])},hide:function(){var t;if(this.transitioning||!this.$element.hasClass(\"in\"))return;t=this.dimension(),this.reset(this.$element[t]()),this.transition(\"removeClass\",e.Event(\"hide\"),\"hidden\"),this.$element[t](0)},reset:function(e){var t=this.dimension();return this.$element.removeClass(\"collapse\")[t](e||\"auto\")[0].offsetWidth,this.$element[e!==null?\"addClass\":\"removeClass\"](\"collapse\"),this},transition:function(t,n,r){var i=this,s=function(){n.type==\"show\"&&i.reset(),i.transitioning=0,i.$element.trigger(r)};this.$element.trigger(n);if(n.isDefaultPrevented())return;this.transitioning=1,this.$element[t](\"in\"),e.support.transition&&this.$element.hasClass(\"collapse\")?this.$element.one(e.support.transition.end,s):s()},toggle:function(){this[this.$element.hasClass(\"in\")?\"hide\":\"show\"]()}};var n=e.fn.collapse;e.fn.collapse=function(n){return this.each(function(){var r=e(this),i=r.data(\"collapse\"),s=e.extend({},e.fn.collapse.defaults,r.data(),typeof n==\"object\"&&n);i||r.data(\"collapse\",i=new t(this,s)),typeof n==\"string\"&&i[n]()})},e.fn.collapse.defaults={toggle:!0},e.fn.collapse.Constructor=t,e.fn.collapse.noConflict=function(){return e.fn.collapse=n,this},e(document).on(\"click.collapse.data-api\",\"[data-toggle=collapse]\",function(t){var n=e(this),r,i=n.attr(\"data-target\")||t.preventDefault()||(r=n.attr(\"href\"))&&r.replace(/.*(?=#[^\\s]+$)/,\"\"),s=e(i).data(\"collapse\")?\"toggle\":n.data();n[e(i).hasClass(\"in\")?\"addClass\":\"removeClass\"](\"collapsed\"),e(i).collapse(s)})}(window.jQuery),!function(e){\"use strict\";function r(){e(\".dropdown-backdrop\").remove(),e(t).each(function(){i(e(this)).removeClass(\"open\")})}function i(t){var n=t.attr(\"data-target\"),r;n||(n=t.attr(\"href\"),n=n&&/#/.test(n)&&n.replace(/.*(?=#[^\\s]*$)/,\"\")),r=n&&e(n);if(!r||!r.length)r=t.parent();return r}var t=\"[data-toggle=dropdown]\",n=function(t){var n=e(t).on(\"click.dropdown.data-api\",this.toggle);e(\"html\").on(\"click.dropdown.data-api\",function(){n.parent().removeClass(\"open\")})};n.prototype={constructor:n,toggle:function(t){var n=e(this),s,o;if(n.is(\".disabled, :disabled\"))return;return s=i(n),o=s.hasClass(\"open\"),r(),o||(\"ontouchstart\"in document.documentElement&&e('<div class=\"dropdown-backdrop\"/>').insertBefore(e(this)).on(\"click\",r),s.toggleClass(\"open\")),n.focus(),!1},keydown:function(n){var r,s,o,u,a,f;if(!/(38|40|27)/.test(n.keyCode))return;r=e(this),n.preventDefault(),n.stopPropagation();if(r.is(\".disabled, :disabled\"))return;u=i(r),a=u.hasClass(\"open\");if(!a||a&&n.keyCode==27)return n.which==27&&u.find(t).focus(),r.click();s=e(\"[role=menu] li:not(.divider):visible a\",u);if(!s.length)return;f=s.index(s.filter(\":focus\")),n.keyCode==38&&f>0&&f--,n.keyCode==40&&f<s.length-1&&f++,~f||(f=0),s.eq(f).focus()}};var s=e.fn.dropdown;e.fn.dropdown=function(t){return this.each(function(){var r=e(this),i=r.data(\"dropdown\");i||r.data(\"dropdown\",i=new n(this)),typeof t==\"string\"&&i[t].call(r)})},e.fn.dropdown.Constructor=n,e.fn.dropdown.noConflict=function(){return e.fn.dropdown=s,this},e(document).on(\"click.dropdown.data-api\",r).on(\"click.dropdown.data-api\",\".dropdown form\",function(e){e.stopPropagation()}).on(\"click.dropdown.data-api\",t,n.prototype.toggle).on(\"keydown.dropdown.data-api\",t+\", [role=menu]\",n.prototype.keydown)}(window.jQuery),!function(e){\"use strict\";var t=function(t,n){this.options=n,this.$element=e(t).delegate('[data-dismiss=\"modal\"]',\"click.dismiss.modal\",e.proxy(this.hide,this)),this.options.remote&&this.$element.find(\".modal-body\").load(this.options.remote)};t.prototype={constructor:t,toggle:function(){return this[this.isShown?\"hide\":\"show\"]()},show:function(){var t=this,n=e.Event(\"show\");this.$element.trigger(n);if(this.isShown||n.isDefaultPrevented())return;this.isShown=!0,this.escape(),this.backdrop(function(){var n=e.support.transition&&t.$element.hasClass(\"fade\");t.$element.parent().length||t.$element.appendTo(document.body),t.$element.show(),n&&t.$element[0].offsetWidth,t.$element.addClass(\"in\").attr(\"aria-hidden\",!1),t.enforceFocus(),n?t.$element.one(e.support.transition.end,function(){t.$element.focus().trigger(\"shown\")}):t.$element.focus().trigger(\"shown\")})},hide:function(t){t&&t.preventDefault();var n=this;t=e.Event(\"hide\"),this.$element.trigger(t);if(!this.isShown||t.isDefaultPrevented())return;this.isShown=!1,this.escape(),e(document).off(\"focusin.modal\"),this.$element.removeClass(\"in\").attr(\"aria-hidden\",!0),e.support.transition&&this.$element.hasClass(\"fade\")?this.hideWithTransition():this.hideModal()},enforceFocus:function(){var t=this;e(document).on(\"focusin.modal\",function(e){t.$element[0]!==e.target&&!t.$element.has(e.target).length&&t.$element.focus()})},escape:function(){var e=this;this.isShown&&this.options.keyboard?this.$element.on(\"keyup.dismiss.modal\",function(t){t.which==27&&e.hide()}):this.isShown||this.$element.off(\"keyup.dismiss.modal\")},hideWithTransition:function(){var t=this,n=setTimeout(function(){t.$element.off(e.support.transition.end),t.hideModal()},500);this.$element.one(e.support.transition.end,function(){clearTimeout(n),t.hideModal()})},hideModal:function(){var e=this;this.$element.hide(),this.backdrop(function(){e.removeBackdrop(),e.$element.trigger(\"hidden\")})},removeBackdrop:function(){this.$backdrop&&this.$backdrop.remove(),this.$backdrop=null},backdrop:function(t){var n=this,r=this.$element.hasClass(\"fade\")?\"fade\":\"\";if(this.isShown&&this.options.backdrop){var i=e.support.transition&&r;this.$backdrop=e('<div class=\"modal-backdrop '+r+'\" />').appendTo(document.body),this.$backdrop.click(this.options.backdrop==\"static\"?e.proxy(this.$element[0].focus,this.$element[0]):e.proxy(this.hide,this)),i&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass(\"in\");if(!t)return;i?this.$backdrop.one(e.support.transition.end,t):t()}else!this.isShown&&this.$backdrop?(this.$backdrop.removeClass(\"in\"),e.support.transition&&this.$element.hasClass(\"fade\")?this.$backdrop.one(e.support.transition.end,t):t()):t&&t()}};var n=e.fn.modal;e.fn.modal=function(n){return this.each(function(){var r=e(this),i=r.data(\"modal\"),s=e.extend({},e.fn.modal.defaults,r.data(),typeof n==\"object\"&&n);i||r.data(\"modal\",i=new t(this,s)),typeof n==\"string\"?i[n]():s.show&&i.show()})},e.fn.modal.defaults={backdrop:!0,keyboard:!0,show:!0},e.fn.modal.Constructor=t,e.fn.modal.noConflict=function(){return e.fn.modal=n,this},e(document).on(\"click.modal.data-api\",'[data-toggle=\"modal\"]',function(t){var n=e(this),r=n.attr(\"href\"),i=e(n.attr(\"data-target\")||r&&r.replace(/.*(?=#[^\\s]+$)/,\"\")),s=i.data(\"modal\")?\"toggle\":e.extend({remote:!/#/.test(r)&&r},i.data(),n.data());t.preventDefault(),i.modal(s).one(\"hide\",function(){n.focus()})})}(window.jQuery),!function(e){\"use strict\";var t=function(e,t){this.init(\"tooltip\",e,t)};t.prototype={constructor:t,init:function(t,n,r){var i,s,o,u,a;this.type=t,this.$element=e(n),this.options=this.getOptions(r),this.enabled=!0,o=this.options.trigger.split(\" \");for(a=o.length;a--;)u=o[a],u==\"click\"?this.$element.on(\"click.\"+this.type,this.options.selector,e.proxy(this.toggle,this)):u!=\"manual\"&&(i=u==\"hover\"?\"mouseenter\":\"focus\",s=u==\"hover\"?\"mouseleave\":\"blur\",this.$element.on(i+\".\"+this.type,this.options.selector,e.proxy(this.enter,this)),this.$element.on(s+\".\"+this.type,this.options.selector,e.proxy(this.leave,this)));this.options.selector?this._options=e.extend({},this.options,{trigger:\"manual\",selector:\"\"}):this.fixTitle()},getOptions:function(t){return t=e.extend({},e.fn[this.type].defaults,this.$element.data(),t),t.delay&&typeof t.delay==\"number\"&&(t.delay={show:t.delay,hide:t.delay}),t},enter:function(t){var n=e.fn[this.type].defaults,r={},i;this._options&&e.each(this._options,function(e,t){n[e]!=t&&(r[e]=t)},this),i=e(t.currentTarget)[this.type](r).data(this.type);if(!i.options.delay||!i.options.delay.show)return i.show();clearTimeout(this.timeout),i.hoverState=\"in\",this.timeout=setTimeout(function(){i.hoverState==\"in\"&&i.show()},i.options.delay.show)},leave:function(t){var n=e(t.currentTarget)[this.type](this._options).data(this.type);this.timeout&&clearTimeout(this.timeout);if(!n.options.delay||!n.options.delay.hide)return n.hide();n.hoverState=\"out\",this.timeout=setTimeout(function(){n.hoverState==\"out\"&&n.hide()},n.options.delay.hide)},show:function(){var t,n,r,i,s,o,u=e.Event(\"show\");if(this.hasContent()&&this.enabled){this.$element.trigger(u);if(u.isDefaultPrevented())return;t=this.tip(),this.setContent(),this.options.animation&&t.addClass(\"fade\"),s=typeof this.options.placement==\"function\"?this.options.placement.call(this,t[0],this.$element[0]):this.options.placement,t.detach().css({top:0,left:0,display:\"block\"}),this.options.container?t.appendTo(this.options.container):t.insertAfter(this.$element),n=this.getPosition(),r=t[0].offsetWidth,i=t[0].offsetHeight;switch(s){case\"bottom\":o={top:n.top+n.height,left:n.left+n.width/2-r/2};break;case\"top\":o={top:n.top-i,left:n.left+n.width/2-r/2};break;case\"left\":o={top:n.top+n.height/2-i/2,left:n.left-r};break;case\"right\":o={top:n.top+n.height/2-i/2,left:n.left+n.width}}this.applyPlacement(o,s),this.$element.trigger(\"shown\")}},applyPlacement:function(e,t){var n=this.tip(),r=n[0].offsetWidth,i=n[0].offsetHeight,s,o,u,a;n.offset(e).addClass(t).addClass(\"in\"),s=n[0].offsetWidth,o=n[0].offsetHeight,t==\"top\"&&o!=i&&(e.top=e.top+i-o,a=!0),t==\"bottom\"||t==\"top\"?(u=0,e.left<0&&(u=e.left*-2,e.left=0,n.offset(e),s=n[0].offsetWidth,o=n[0].offsetHeight),this.replaceArrow(u-r+s,s,\"left\")):this.replaceArrow(o-i,o,\"top\"),a&&n.offset(e)},replaceArrow:function(e,t,n){this.arrow().css(n,e?50*(1-e/t)+\"%\":\"\")},setContent:function(){var e=this.tip(),t=this.getTitle();e.find(\".tooltip-inner\")[this.options.html?\"html\":\"text\"](t),e.removeClass(\"fade in top bottom left right\")},hide:function(){function i(){var t=setTimeout(function(){n.off(e.support.transition.end).detach()},500);n.one(e.support.transition.end,function(){clearTimeout(t),n.detach()})}var t=this,n=this.tip(),r=e.Event(\"hide\");this.$element.trigger(r);if(r.isDefaultPrevented())return;return n.removeClass(\"in\"),e.support.transition&&this.$tip.hasClass(\"fade\")?i():n.detach(),this.$element.trigger(\"hidden\"),this},fixTitle:function(){var e=this.$element;(e.attr(\"title\")||typeof e.attr(\"data-original-title\")!=\"string\")&&e.attr(\"data-original-title\",e.attr(\"title\")||\"\").attr(\"title\",\"\")},hasContent:function(){return this.getTitle()},getPosition:function(){var t=this.$element[0];return e.extend({},typeof t.getBoundingClientRect==\"function\"?t.getBoundingClientRect():{width:t.offsetWidth,height:t.offsetHeight},this.$element.offset())},getTitle:function(){var e,t=this.$element,n=this.options;return e=t.attr(\"data-original-title\")||(typeof n.title==\"function\"?n.title.call(t[0]):n.title),e},tip:function(){return this.$tip=this.$tip||e(this.options.template)},arrow:function(){return this.$arrow=this.$arrow||this.tip().find(\".tooltip-arrow\")},validate:function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},enable:function(){this.enabled=!0},disable:function(){this.enabled=!1},toggleEnabled:function(){this.enabled=!this.enabled},toggle:function(t){var n=t?e(t.currentTarget)[this.type](this._options).data(this.type):this;n.tip().hasClass(\"in\")?n.hide():n.show()},destroy:function(){this.hide().$element.off(\".\"+this.type).removeData(this.type)}};var n=e.fn.tooltip;e.fn.tooltip=function(n){return this.each(function(){var r=e(this),i=r.data(\"tooltip\"),s=typeof n==\"object\"&&n;i||r.data(\"tooltip\",i=new t(this,s)),typeof n==\"string\"&&i[n]()})},e.fn.tooltip.Constructor=t,e.fn.tooltip.defaults={animation:!0,placement:\"top\",selector:!1,template:'<div class=\"tooltip\"><div class=\"tooltip-arrow\"></div><div class=\"tooltip-inner\"></div></div>',trigger:\"hover focus\",title:\"\",delay:0,html:!1,container:!1},e.fn.tooltip.noConflict=function(){return e.fn.tooltip=n,this}}(window.jQuery),!function(e){\"use strict\";var t=function(e,t){this.init(\"popover\",e,t)};t.prototype=e.extend({},e.fn.tooltip.Constructor.prototype,{constructor:t,setContent:function(){var e=this.tip(),t=this.getTitle(),n=this.getContent();e.find(\".popover-title\")[this.options.html?\"html\":\"text\"](t),e.find(\".popover-content\")[this.options.html?\"html\":\"text\"](n),e.removeClass(\"fade top bottom left right in\")},hasContent:function(){return this.getTitle()||this.getContent()},getContent:function(){var e,t=this.$element,n=this.options;return e=(typeof n.content==\"function\"?n.content.call(t[0]):n.content)||t.attr(\"data-content\"),e},tip:function(){return this.$tip||(this.$tip=e(this.options.template)),this.$tip},destroy:function(){this.hide().$element.off(\".\"+this.type).removeData(this.type)}});var n=e.fn.popover;e.fn.popover=function(n){return this.each(function(){var r=e(this),i=r.data(\"popover\"),s=typeof n==\"object\"&&n;i||r.data(\"popover\",i=new t(this,s)),typeof n==\"string\"&&i[n]()})},e.fn.popover.Constructor=t,e.fn.popover.defaults=e.extend({},e.fn.tooltip.defaults,{placement:\"right\",trigger:\"click\",content:\"\",template:'<div class=\"popover\"><div class=\"arrow\"></div><h3 class=\"popover-title\"></h3><div class=\"popover-content\"></div></div>'}),e.fn.popover.noConflict=function(){return e.fn.popover=n,this}}(window.jQuery),!function(e){\"use strict\";function t(t,n){var r=e.proxy(this.process,this),i=e(t).is(\"body\")?e(window):e(t),s;this.options=e.extend({},e.fn.scrollspy.defaults,n),this.$scrollElement=i.on(\"scroll.scroll-spy.data-api\",r),this.selector=(this.options.target||(s=e(t).attr(\"href\"))&&s.replace(/.*(?=#[^\\s]+$)/,\"\")||\"\")+\" .nav li > a\",this.$body=e(\"body\"),this.refresh(),this.process()}t.prototype={constructor:t,refresh:function(){var t=this,n;this.offsets=e([]),this.targets=e([]),n=this.$body.find(this.selector).map(function(){var n=e(this),r=n.data(\"target\")||n.attr(\"href\"),i=/^#\\w/.test(r)&&e(r);return i&&i.length&&[[i.position().top+(!e.isWindow(t.$scrollElement.get(0))&&t.$scrollElement.scrollTop()),r]]||null}).sort(function(e,t){return e[0]-t[0]}).each(function(){t.offsets.push(this[0]),t.targets.push(this[1])})},process:function(){var e=this.$scrollElement.scrollTop()+this.options.offset,t=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,n=t-this.$scrollElement.height(),r=this.offsets,i=this.targets,s=this.activeTarget,o;if(e>=n)return s!=(o=i.last()[0])&&this.activate(o);for(o=r.length;o--;)s!=i[o]&&e>=r[o]&&(!r[o+1]||e<=r[o+1])&&this.activate(i[o])},activate:function(t){var n,r;this.activeTarget=t,e(this.selector).parent(\".active\").removeClass(\"active\"),r=this.selector+'[data-target=\"'+t+'\"],'+this.selector+'[href=\"'+t+'\"]',n=e(r).parent(\"li\").addClass(\"active\"),n.parent(\".dropdown-menu\").length&&(n=n.closest(\"li.dropdown\").addClass(\"active\")),n.trigger(\"activate\")}};var n=e.fn.scrollspy;e.fn.scrollspy=function(n){return this.each(function(){var r=e(this),i=r.data(\"scrollspy\"),s=typeof n==\"object\"&&n;i||r.data(\"scrollspy\",i=new t(this,s)),typeof n==\"string\"&&i[n]()})},e.fn.scrollspy.Constructor=t,e.fn.scrollspy.defaults={offset:10},e.fn.scrollspy.noConflict=function(){return e.fn.scrollspy=n,this},e(window).on(\"load\",function(){e('[data-spy=\"scroll\"]').each(function(){var t=e(this);t.scrollspy(t.data())})})}(window.jQuery),!function(e){\"use strict\";var t=function(t){this.element=e(t)};t.prototype={constructor:t,show:function(){var t=this.element,n=t.closest(\"ul:not(.dropdown-menu)\"),r=t.attr(\"data-target\"),i,s,o;r||(r=t.attr(\"href\"),r=r&&r.replace(/.*(?=#[^\\s]*$)/,\"\"));if(t.parent(\"li\").hasClass(\"active\"))return;i=n.find(\".active:last a\")[0],o=e.Event(\"show\",{relatedTarget:i}),t.trigger(o);if(o.isDefaultPrevented())return;s=e(r),this.activate(t.parent(\"li\"),n),this.activate(s,s.parent(),function(){t.trigger({type:\"shown\",relatedTarget:i})})},activate:function(t,n,r){function o(){i.removeClass(\"active\").find(\"> .dropdown-menu > .active\").removeClass(\"active\"),t.addClass(\"active\"),s?(t[0].offsetWidth,t.addClass(\"in\")):t.removeClass(\"fade\"),t.parent(\".dropdown-menu\")&&t.closest(\"li.dropdown\").addClass(\"active\"),r&&r()}var i=n.find(\"> .active\"),s=r&&e.support.transition&&i.hasClass(\"fade\");s?i.one(e.support.transition.end,o):o(),i.removeClass(\"in\")}};var n=e.fn.tab;e.fn.tab=function(n){return this.each(function(){var r=e(this),i=r.data(\"tab\");i||r.data(\"tab\",i=new t(this)),typeof n==\"string\"&&i[n]()})},e.fn.tab.Constructor=t,e.fn.tab.noConflict=function(){return e.fn.tab=n,this},e(document).on(\"click.tab.data-api\",'[data-toggle=\"tab\"], [data-toggle=\"pill\"]',function(t){t.preventDefault(),e(this).tab(\"show\")})}(window.jQuery),!function(e){\"use strict\";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.typeahead.defaults,n),this.matcher=this.options.matcher||this.matcher,this.sorter=this.options.sorter||this.sorter,this.highlighter=this.options.highlighter||this.highlighter,this.updater=this.options.updater||this.updater,this.source=this.options.source,this.$menu=e(this.options.menu),this.shown=!1,this.listen()};t.prototype={constructor:t,select:function(){var e=this.$menu.find(\".active\").attr(\"data-value\");return this.$element.val(this.updater(e)).change(),this.hide()},updater:function(e){return e},show:function(){var t=e.extend({},this.$element.position(),{height:this.$element[0].offsetHeight});return this.$menu.insertAfter(this.$element).css({top:t.top+t.height,left:t.left}).show(),this.shown=!0,this},hide:function(){return this.$menu.hide(),this.shown=!1,this},lookup:function(t){var n;return this.query=this.$element.val(),!this.query||this.query.length<this.options.minLength?this.shown?this.hide():this:(n=e.isFunction(this.source)?this.source(this.query,e.proxy(this.process,this)):this.source,n?this.process(n):this)},process:function(t){var n=this;return t=e.grep(t,function(e){return n.matcher(e)}),t=this.sorter(t),t.length?this.render(t.slice(0,this.options.items)).show():this.shown?this.hide():this},matcher:function(e){return~e.toLowerCase().indexOf(this.query.toLowerCase())},sorter:function(e){var t=[],n=[],r=[],i;while(i=e.shift())i.toLowerCase().indexOf(this.query.toLowerCase())?~i.indexOf(this.query)?n.push(i):r.push(i):t.push(i);return t.concat(n,r)},highlighter:function(e){var t=this.query.replace(/[\\-\\[\\]{}()*+?.,\\\\\\^$|#\\s]/g,\"\\\\$&\");return e.replace(new RegExp(\"(\"+t+\")\",\"ig\"),function(e,t){return\"<strong>\"+t+\"</strong>\"})},render:function(t){var n=this;return t=e(t).map(function(t,r){return t=e(n.options.item).attr(\"data-value\",r),t.find(\"a\").html(n.highlighter(r)),t[0]}),t.first().addClass(\"active\"),this.$menu.html(t),this},next:function(t){var n=this.$menu.find(\".active\").removeClass(\"active\"),r=n.next();r.length||(r=e(this.$menu.find(\"li\")[0])),r.addClass(\"active\")},prev:function(e){var t=this.$menu.find(\".active\").removeClass(\"active\"),n=t.prev();n.length||(n=this.$menu.find(\"li\").last()),n.addClass(\"active\")},listen:function(){this.$element.on(\"focus\",e.proxy(this.focus,this)).on(\"blur\",e.proxy(this.blur,this)).on(\"keypress\",e.proxy(this.keypress,this)).on(\"keyup\",e.proxy(this.keyup,this)),this.eventSupported(\"keydown\")&&this.$element.on(\"keydown\",e.proxy(this.keydown,this)),this.$menu.on(\"click\",e.proxy(this.click,this)).on(\"mouseenter\",\"li\",e.proxy(this.mouseenter,this)).on(\"mouseleave\",\"li\",e.proxy(this.mouseleave,this))},eventSupported:function(e){var t=e in this.$element;return t||(this.$element.setAttribute(e,\"return;\"),t=typeof this.$element[e]==\"function\"),t},move:function(e){if(!this.shown)return;switch(e.keyCode){case 9:case 13:case 27:e.preventDefault();break;case 38:e.preventDefault(),this.prev();break;case 40:e.preventDefault(),this.next()}e.stopPropagation()},keydown:function(t){this.suppressKeyPressRepeat=~e.inArray(t.keyCode,[40,38,9,13,27]),this.move(t)},keypress:function(e){if(this.suppressKeyPressRepeat)return;this.move(e)},keyup:function(e){switch(e.keyCode){case 40:case 38:case 16:case 17:case 18:break;case 9:case 13:if(!this.shown)return;this.select();break;case 27:if(!this.shown)return;this.hide();break;default:this.lookup()}e.stopPropagation(),e.preventDefault()},focus:function(e){this.focused=!0},blur:function(e){this.focused=!1,!this.mousedover&&this.shown&&this.hide()},click:function(e){e.stopPropagation(),e.preventDefault(),this.select(),this.$element.focus()},mouseenter:function(t){this.mousedover=!0,this.$menu.find(\".active\").removeClass(\"active\"),e(t.currentTarget).addClass(\"active\")},mouseleave:function(e){this.mousedover=!1,!this.focused&&this.shown&&this.hide()}};var n=e.fn.typeahead;e.fn.typeahead=function(n){return this.each(function(){var r=e(this),i=r.data(\"typeahead\"),s=typeof n==\"object\"&&n;i||r.data(\"typeahead\",i=new t(this,s)),typeof n==\"string\"&&i[n]()})},e.fn.typeahead.defaults={source:[],items:8,menu:'<ul class=\"typeahead dropdown-menu\"></ul>',item:'<li><a href=\"#\"></a></li>',minLength:1},e.fn.typeahead.Constructor=t,e.fn.typeahead.noConflict=function(){return e.fn.typeahead=n,this},e(document).on(\"focus.typeahead.data-api\",'[data-provide=\"typeahead\"]',function(t){var n=e(this);if(n.data(\"typeahead\"))return;n.typeahead(n.data())})}(window.jQuery),!function(e){\"use strict\";var t=function(t,n){this.options=e.extend({},e.fn.affix.defaults,n),this.$window=e(window).on(\"scroll.affix.data-api\",e.proxy(this.checkPosition,this)).on(\"click.affix.data-api\",e.proxy(function(){setTimeout(e.proxy(this.checkPosition,this),1)},this)),this.$element=e(t),this.checkPosition()};t.prototype.checkPosition=function(){if(!this.$element.is(\":visible\"))return;var t=e(document).height(),n=this.$window.scrollTop(),r=this.$element.offset(),i=this.options.offset,s=i.bottom,o=i.top,u=\"affix affix-top affix-bottom\",a;typeof i!=\"object\"&&(s=o=i),typeof o==\"function\"&&(o=i.top()),typeof s==\"function\"&&(s=i.bottom()),a=this.unpin!=null&&n+this.unpin<=r.top?!1:s!=null&&r.top+this.$element.height()>=t-s?\"bottom\":o!=null&&n<=o?\"top\":!1;if(this.affixed===a)return;this.affixed=a,this.unpin=a==\"bottom\"?r.top-n:null,this.$element.removeClass(u).addClass(\"affix\"+(a?\"-\"+a:\"\"))};var n=e.fn.affix;e.fn.affix=function(n){return this.each(function(){var r=e(this),i=r.data(\"affix\"),s=typeof n==\"object\"&&n;i||r.data(\"affix\",i=new t(this,s)),typeof n==\"string\"&&i[n]()})},e.fn.affix.Constructor=t,e.fn.affix.defaults={offset:0},e.fn.affix.noConflict=function(){return e.fn.affix=n,this},e(window).on(\"load\",function(){e('[data-spy=\"affix\"]').each(function(){var t=e(this),n=t.data();n.offset=n.offset||{},n.offsetBottom&&(n.offset.bottom=n.offsetBottom),n.offsetTop&&(n.offset.top=n.offsetTop),t.affix(n)})})}(window.jQuery);\n</script>\n\n  <script>\n/**\n * marked - a markdown parser\n * Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed)\n * https://github.com/chjj/marked\n */\n(function(){var block={newline:/^\\n+/,code:/^( {4}[^\\n]+\\n*)+/,fences:noop,hr:/^( *[-*_]){3,} *(?:\\n+|$)/,heading:/^ *(#{1,6}) *([^\\n]+?) *#* *(?:\\n+|$)/,nptable:noop,lheading:/^([^\\n]+)\\n *(=|-){2,} *(?:\\n+|$)/,blockquote:/^( *>[^\\n]+(\\n(?!def)[^\\n]+)*\\n*)+/,list:/^( *)(bull) [\\s\\S]+?(?:hr|def|\\n{2,}(?! )(?!\\1bull )\\n*|\\s*$)/,html:/^ *(?:comment *(?:\\n|\\s*$)|closed *(?:\\n{2,}|\\s*$)|closing *(?:\\n{2,}|\\s*$))/,def:/^ *\\[([^\\]]+)\\]: *<?([^\\s>]+)>?(?: +[\"(]([^\\n]+)[\")])? *(?:\\n+|$)/,table:noop,paragraph:/^((?:[^\\n]+\\n?(?!hr|heading|lheading|blockquote|tag|def))+)\\n*/,text:/^[^\\n]+/};block.bullet=/(?:[*+-]|\\d+\\.)/;block.item=/^( *)(bull) [^\\n]*(?:\\n(?!\\1bull )[^\\n]*)*/;block.item=replace(block.item,\"gm\")(/bull/g,block.bullet)();block.list=replace(block.list)(/bull/g,block.bullet)(\"hr\",\"\\\\n+(?=\\\\1?(?:[-*_] *){3,}(?:\\\\n+|$))\")(\"def\",\"\\\\n+(?=\"+block.def.source+\")\")();block.blockquote=replace(block.blockquote)(\"def\",block.def)();block._tag=\"(?!(?:\"+\"a|em|strong|small|s|cite|q|dfn|abbr|data|time|code\"+\"|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo\"+\"|span|br|wbr|ins|del|img)\\\\b)\\\\w+(?!:/|[^\\\\w\\\\s@]*@)\\\\b\";block.html=replace(block.html)(\"comment\",/<!--[\\s\\S]*?-->/)(\"closed\",/<(tag)[\\s\\S]+?<\\/\\1>/)(\"closing\",/<tag(?:\"[^\"]*\"|'[^']*'|[^'\">])*?>/)(/tag/g,block._tag)();block.paragraph=replace(block.paragraph)(\"hr\",block.hr)(\"heading\",block.heading)(\"lheading\",block.lheading)(\"blockquote\",block.blockquote)(\"tag\",\"<\"+block._tag)(\"def\",block.def)();block.normal=merge({},block);block.gfm=merge({},block.normal,{fences:/^ *(`{3,}|~{3,})[ \\.]*(\\S+)? *\\n([\\s\\S]*?)\\s*\\1 *(?:\\n+|$)/,paragraph:/^/,heading:/^ *(#{1,6}) +([^\\n]+?) *#* *(?:\\n+|$)/});block.gfm.paragraph=replace(block.paragraph)(\"(?!\",\"(?!\"+block.gfm.fences.source.replace(\"\\\\1\",\"\\\\2\")+\"|\"+block.list.source.replace(\"\\\\1\",\"\\\\3\")+\"|\")();block.tables=merge({},block.gfm,{nptable:/^ *(\\S.*\\|.*)\\n *([-:]+ *\\|[-| :]*)\\n((?:.*\\|.*(?:\\n|$))*)\\n*/,table:/^ *\\|(.+)\\n *\\|( *[-:]+[-| :]*)\\n((?: *\\|.*(?:\\n|$))*)\\n*/});function Lexer(options){this.tokens=[];this.tokens.links={};this.options=options||marked.defaults;this.rules=block.normal;if(this.options.gfm){if(this.options.tables){this.rules=block.tables}else{this.rules=block.gfm}}}Lexer.rules=block;Lexer.lex=function(src,options){var lexer=new Lexer(options);return lexer.lex(src)};Lexer.prototype.lex=function(src){src=src.replace(/\\r\\n|\\r/g,\"\\n\").replace(/\\t/g,\"    \").replace(/\\u00a0/g,\" \").replace(/\\u2424/g,\"\\n\");return this.token(src,true)};Lexer.prototype.token=function(src,top,bq){var src=src.replace(/^ +$/gm,\"\"),next,loose,cap,bull,b,item,space,i,l;while(src){if(cap=this.rules.newline.exec(src)){src=src.substring(cap[0].length);if(cap[0].length>1){this.tokens.push({type:\"space\"})}}if(cap=this.rules.code.exec(src)){src=src.substring(cap[0].length);cap=cap[0].replace(/^ {4}/gm,\"\");this.tokens.push({type:\"code\",text:!this.options.pedantic?cap.replace(/\\n+$/,\"\"):cap});continue}if(cap=this.rules.fences.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:\"code\",lang:cap[2],text:cap[3]||\"\"});continue}if(cap=this.rules.heading.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:\"heading\",depth:cap[1].length,text:cap[2]});continue}if(top&&(cap=this.rules.nptable.exec(src))){src=src.substring(cap[0].length);item={type:\"table\",header:cap[1].replace(/^ *| *\\| *$/g,\"\").split(/ *\\| */),align:cap[2].replace(/^ *|\\| *$/g,\"\").split(/ *\\| */),cells:cap[3].replace(/\\n$/,\"\").split(\"\\n\")};for(i=0;i<item.align.length;i++){if(/^ *-+: *$/.test(item.align[i])){item.align[i]=\"right\"}else if(/^ *:-+: *$/.test(item.align[i])){item.align[i]=\"center\"}else if(/^ *:-+ *$/.test(item.align[i])){item.align[i]=\"left\"}else{item.align[i]=null}}for(i=0;i<item.cells.length;i++){item.cells[i]=item.cells[i].split(/ *\\| */)}this.tokens.push(item);continue}if(cap=this.rules.lheading.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:\"heading\",depth:cap[2]===\"=\"?1:2,text:cap[1]});continue}if(cap=this.rules.hr.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:\"hr\"});continue}if(cap=this.rules.blockquote.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:\"blockquote_start\"});cap=cap[0].replace(/^ *> ?/gm,\"\");this.token(cap,top,true);this.tokens.push({type:\"blockquote_end\"});continue}if(cap=this.rules.list.exec(src)){src=src.substring(cap[0].length);bull=cap[2];this.tokens.push({type:\"list_start\",ordered:bull.length>1});cap=cap[0].match(this.rules.item);next=false;l=cap.length;i=0;for(;i<l;i++){item=cap[i];space=item.length;item=item.replace(/^ *([*+-]|\\d+\\.) +/,\"\");if(~item.indexOf(\"\\n \")){space-=item.length;item=!this.options.pedantic?item.replace(new RegExp(\"^ {1,\"+space+\"}\",\"gm\"),\"\"):item.replace(/^ {1,4}/gm,\"\")}if(this.options.smartLists&&i!==l-1){b=block.bullet.exec(cap[i+1])[0];if(bull!==b&&!(bull.length>1&&b.length>1)){src=cap.slice(i+1).join(\"\\n\")+src;i=l-1}}loose=next||/\\n\\n(?!\\s*$)/.test(item);if(i!==l-1){next=item.charAt(item.length-1)===\"\\n\";if(!loose)loose=next}this.tokens.push({type:loose?\"loose_item_start\":\"list_item_start\"});this.token(item,false,bq);this.tokens.push({type:\"list_item_end\"})}this.tokens.push({type:\"list_end\"});continue}if(cap=this.rules.html.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:this.options.sanitize?\"paragraph\":\"html\",pre:!this.options.sanitizer&&(cap[1]===\"pre\"||cap[1]===\"script\"||cap[1]===\"style\"),text:cap[0]});continue}if(!bq&&top&&(cap=this.rules.def.exec(src))){src=src.substring(cap[0].length);this.tokens.links[cap[1].toLowerCase()]={href:cap[2],title:cap[3]};continue}if(top&&(cap=this.rules.table.exec(src))){src=src.substring(cap[0].length);item={type:\"table\",header:cap[1].replace(/^ *| *\\| *$/g,\"\").split(/ *\\| */),align:cap[2].replace(/^ *|\\| *$/g,\"\").split(/ *\\| */),cells:cap[3].replace(/(?: *\\| *)?\\n$/,\"\").split(\"\\n\")};for(i=0;i<item.align.length;i++){if(/^ *-+: *$/.test(item.align[i])){item.align[i]=\"right\"}else if(/^ *:-+: *$/.test(item.align[i])){item.align[i]=\"center\"}else if(/^ *:-+ *$/.test(item.align[i])){item.align[i]=\"left\"}else{item.align[i]=null}}for(i=0;i<item.cells.length;i++){item.cells[i]=item.cells[i].replace(/^ *\\| *| *\\| *$/g,\"\").split(/ *\\| */)}this.tokens.push(item);continue}if(top&&(cap=this.rules.paragraph.exec(src))){src=src.substring(cap[0].length);this.tokens.push({type:\"paragraph\",text:cap[1].charAt(cap[1].length-1)===\"\\n\"?cap[1].slice(0,-1):cap[1]});continue}if(cap=this.rules.text.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:\"text\",text:cap[0]});continue}if(src){throw new Error(\"Infinite loop on byte: \"+src.charCodeAt(0))}}return this.tokens};var inline={escape:/^\\\\([\\\\`*{}\\[\\]()#+\\-.!_>])/,autolink:/^<([^ >]+(@|:\\/)[^ >]+)>/,url:noop,tag:/^<!--[\\s\\S]*?-->|^<\\/?\\w+(?:\"[^\"]*\"|'[^']*'|[^'\">])*?>/,link:/^!?\\[(inside)\\]\\(href\\)/,reflink:/^!?\\[(inside)\\]\\s*\\[([^\\]]*)\\]/,nolink:/^!?\\[((?:\\[[^\\]]*\\]|[^\\[\\]])*)\\]/,strong:/^__([\\s\\S]+?)__(?!_)|^\\*\\*([\\s\\S]+?)\\*\\*(?!\\*)/,em:/^\\b_((?:[^_]|__)+?)_\\b|^\\*((?:\\*\\*|[\\s\\S])+?)\\*(?!\\*)/,code:/^(`+)\\s*([\\s\\S]*?[^`])\\s*\\1(?!`)/,br:/^ {2,}\\n(?!\\s*$)/,del:noop,text:/^[\\s\\S]+?(?=[\\\\<!\\[_*`]| {2,}\\n|$)/};inline._inside=/(?:\\[[^\\]]*\\]|[^\\[\\]]|\\](?=[^\\[]*\\]))*/;inline._href=/\\s*<?([\\s\\S]*?)>?(?:\\s+['\"]([\\s\\S]*?)['\"])?\\s*/;inline.link=replace(inline.link)(\"inside\",inline._inside)(\"href\",inline._href)();inline.reflink=replace(inline.reflink)(\"inside\",inline._inside)();inline.normal=merge({},inline);inline.pedantic=merge({},inline.normal,{strong:/^__(?=\\S)([\\s\\S]*?\\S)__(?!_)|^\\*\\*(?=\\S)([\\s\\S]*?\\S)\\*\\*(?!\\*)/,em:/^_(?=\\S)([\\s\\S]*?\\S)_(?!_)|^\\*(?=\\S)([\\s\\S]*?\\S)\\*(?!\\*)/});inline.gfm=merge({},inline.normal,{escape:replace(inline.escape)(\"])\",\"~|])\")(),url:/^(https?:\\/\\/[^\\s<]+[^<.,:;\"')\\]\\s])/,del:/^~~(?=\\S)([\\s\\S]*?\\S)~~/,text:replace(inline.text)(\"]|\",\"~]|\")(\"|\",\"|https?://|\")()});inline.breaks=merge({},inline.gfm,{br:replace(inline.br)(\"{2,}\",\"*\")(),text:replace(inline.gfm.text)(\"{2,}\",\"*\")()});function InlineLexer(links,options){this.options=options||marked.defaults;this.links=links;this.rules=inline.normal;this.renderer=this.options.renderer||new Renderer;this.renderer.options=this.options;if(!this.links){throw new Error(\"Tokens array requires a `links` property.\")}if(this.options.gfm){if(this.options.breaks){this.rules=inline.breaks}else{this.rules=inline.gfm}}else if(this.options.pedantic){this.rules=inline.pedantic}}InlineLexer.rules=inline;InlineLexer.output=function(src,links,options){var inline=new InlineLexer(links,options);return inline.output(src)};InlineLexer.prototype.output=function(src){var out=\"\",link,text,href,cap;while(src){if(cap=this.rules.escape.exec(src)){src=src.substring(cap[0].length);out+=cap[1];continue}if(cap=this.rules.autolink.exec(src)){src=src.substring(cap[0].length);if(cap[2]===\"@\"){text=cap[1].charAt(6)===\":\"?this.mangle(cap[1].substring(7)):this.mangle(cap[1]);href=this.mangle(\"mailto:\")+text}else{text=escape(cap[1]);href=text}out+=this.renderer.link(href,null,text);continue}if(!this.inLink&&(cap=this.rules.url.exec(src))){src=src.substring(cap[0].length);text=escape(cap[1]);href=text;out+=this.renderer.link(href,null,text);continue}if(cap=this.rules.tag.exec(src)){if(!this.inLink&&/^<a /i.test(cap[0])){this.inLink=true}else if(this.inLink&&/^<\\/a>/i.test(cap[0])){this.inLink=false}src=src.substring(cap[0].length);out+=this.options.sanitize?this.options.sanitizer?this.options.sanitizer(cap[0]):escape(cap[0]):cap[0];continue}if(cap=this.rules.link.exec(src)){src=src.substring(cap[0].length);this.inLink=true;out+=this.outputLink(cap,{href:cap[2],title:cap[3]});this.inLink=false;continue}if((cap=this.rules.reflink.exec(src))||(cap=this.rules.nolink.exec(src))){src=src.substring(cap[0].length);link=(cap[2]||cap[1]).replace(/\\s+/g,\" \");link=this.links[link.toLowerCase()];if(!link||!link.href){out+=cap[0].charAt(0);src=cap[0].substring(1)+src;continue}this.inLink=true;out+=this.outputLink(cap,link);this.inLink=false;continue}if(cap=this.rules.strong.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.strong(this.output(cap[2]||cap[1]));continue}if(cap=this.rules.em.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.em(this.output(cap[2]||cap[1]));continue}if(cap=this.rules.code.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.codespan(escape(cap[2],true));continue}if(cap=this.rules.br.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.br();continue}if(cap=this.rules.del.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.del(this.output(cap[1]));continue}if(cap=this.rules.text.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.text(escape(this.smartypants(cap[0])));continue}if(src){throw new Error(\"Infinite loop on byte: \"+src.charCodeAt(0))}}return out};InlineLexer.prototype.outputLink=function(cap,link){var href=escape(link.href),title=link.title?escape(link.title):null;return cap[0].charAt(0)!==\"!\"?this.renderer.link(href,title,this.output(cap[1])):this.renderer.image(href,title,escape(cap[1]))};InlineLexer.prototype.smartypants=function(text){if(!this.options.smartypants)return text;return text.replace(/---/g,\"—\").replace(/--/g,\"–\").replace(/(^|[-\\u2014/(\\[{\"\\s])'/g,\"$1‘\").replace(/'/g,\"’\").replace(/(^|[-\\u2014/(\\[{\\u2018\\s])\"/g,\"$1“\").replace(/\"/g,\"”\").replace(/\\.{3}/g,\"…\")};InlineLexer.prototype.mangle=function(text){if(!this.options.mangle)return text;var out=\"\",l=text.length,i=0,ch;for(;i<l;i++){ch=text.charCodeAt(i);if(Math.random()>.5){ch=\"x\"+ch.toString(16)}out+=\"&#\"+ch+\";\"}return out};function Renderer(options){this.options=options||{}}Renderer.prototype.code=function(code,lang,escaped){if(this.options.highlight){var out=this.options.highlight(code,lang);if(out!=null&&out!==code){escaped=true;code=out}}if(!lang){return\"<pre><code>\"+(escaped?code:escape(code,true))+\"\\n</code></pre>\"}return'<pre><code class=\"'+this.options.langPrefix+escape(lang,true)+'\">'+(escaped?code:escape(code,true))+\"\\n</code></pre>\\n\"};Renderer.prototype.blockquote=function(quote){return\"<blockquote>\\n\"+quote+\"</blockquote>\\n\"};Renderer.prototype.html=function(html){return html};Renderer.prototype.heading=function(text,level,raw){return\"<h\"+level+' id=\"'+this.options.headerPrefix+raw.toLowerCase().replace(/[^\\w]+/g,\"-\")+'\">'+text+\"</h\"+level+\">\\n\"};Renderer.prototype.hr=function(){return this.options.xhtml?\"<hr/>\\n\":\"<hr>\\n\"};Renderer.prototype.list=function(body,ordered){var type=ordered?\"ol\":\"ul\";return\"<\"+type+\">\\n\"+body+\"</\"+type+\">\\n\"};Renderer.prototype.listitem=function(text){return\"<li>\"+text+\"</li>\\n\"};Renderer.prototype.paragraph=function(text){return\"<p>\"+text+\"</p>\\n\"};Renderer.prototype.table=function(header,body){return\"<table>\\n\"+\"<thead>\\n\"+header+\"</thead>\\n\"+\"<tbody>\\n\"+body+\"</tbody>\\n\"+\"</table>\\n\"};Renderer.prototype.tablerow=function(content){return\"<tr>\\n\"+content+\"</tr>\\n\"};Renderer.prototype.tablecell=function(content,flags){var type=flags.header?\"th\":\"td\";var tag=flags.align?\"<\"+type+' style=\"text-align:'+flags.align+'\">':\"<\"+type+\">\";return tag+content+\"</\"+type+\">\\n\"};Renderer.prototype.strong=function(text){return\"<strong>\"+text+\"</strong>\"};Renderer.prototype.em=function(text){return\"<em>\"+text+\"</em>\"};Renderer.prototype.codespan=function(text){return\"<code>\"+text+\"</code>\"};Renderer.prototype.br=function(){return this.options.xhtml?\"<br/>\":\"<br>\"};Renderer.prototype.del=function(text){return\"<del>\"+text+\"</del>\"};Renderer.prototype.link=function(href,title,text){if(this.options.sanitize){try{var prot=decodeURIComponent(unescape(href)).replace(/[^\\w:]/g,\"\").toLowerCase()}catch(e){return\"\"}if(prot.indexOf(\"javascript:\")===0||prot.indexOf(\"vbscript:\")===0){return\"\"}}var out='<a href=\"'+href+'\"';if(title){out+=' title=\"'+title+'\"'}out+=\">\"+text+\"</a>\";return out};Renderer.prototype.image=function(href,title,text){var out='<img src=\"'+href+'\" alt=\"'+text+'\"';if(title){out+=' title=\"'+title+'\"'}out+=this.options.xhtml?\"/>\":\">\";return out};Renderer.prototype.text=function(text){return text};function Parser(options){this.tokens=[];this.token=null;this.options=options||marked.defaults;this.options.renderer=this.options.renderer||new Renderer;this.renderer=this.options.renderer;this.renderer.options=this.options}Parser.parse=function(src,options,renderer){var parser=new Parser(options,renderer);return parser.parse(src)};Parser.prototype.parse=function(src){this.inline=new InlineLexer(src.links,this.options,this.renderer);this.tokens=src.reverse();var out=\"\";while(this.next()){out+=this.tok()}return out};Parser.prototype.next=function(){return this.token=this.tokens.pop()};Parser.prototype.peek=function(){return this.tokens[this.tokens.length-1]||0};Parser.prototype.parseText=function(){var body=this.token.text;while(this.peek().type===\"text\"){body+=\"\\n\"+this.next().text}return this.inline.output(body)};Parser.prototype.tok=function(){switch(this.token.type){case\"space\":{return\"\"}case\"hr\":{return this.renderer.hr()}case\"heading\":{return this.renderer.heading(this.inline.output(this.token.text),this.token.depth,this.token.text)}case\"code\":{return this.renderer.code(this.token.text,this.token.lang,this.token.escaped)}case\"table\":{var header=\"\",body=\"\",i,row,cell,flags,j;cell=\"\";for(i=0;i<this.token.header.length;i++){flags={header:true,align:this.token.align[i]};cell+=this.renderer.tablecell(this.inline.output(this.token.header[i]),{header:true,align:this.token.align[i]})}header+=this.renderer.tablerow(cell);for(i=0;i<this.token.cells.length;i++){row=this.token.cells[i];cell=\"\";for(j=0;j<row.length;j++){cell+=this.renderer.tablecell(this.inline.output(row[j]),{header:false,align:this.token.align[j]})}body+=this.renderer.tablerow(cell)}return this.renderer.table(header,body)}case\"blockquote_start\":{var body=\"\";while(this.next().type!==\"blockquote_end\"){body+=this.tok()}return this.renderer.blockquote(body)}case\"list_start\":{var body=\"\",ordered=this.token.ordered;while(this.next().type!==\"list_end\"){body+=this.tok()}return this.renderer.list(body,ordered)}case\"list_item_start\":{var body=\"\";while(this.next().type!==\"list_item_end\"){body+=this.token.type===\"text\"?this.parseText():this.tok()}return this.renderer.listitem(body)}case\"loose_item_start\":{var body=\"\";while(this.next().type!==\"list_item_end\"){body+=this.tok()}return this.renderer.listitem(body)}case\"html\":{var html=!this.token.pre&&!this.options.pedantic?this.inline.output(this.token.text):this.token.text;return this.renderer.html(html)}case\"paragraph\":{return this.renderer.paragraph(this.inline.output(this.token.text))}case\"text\":{return this.renderer.paragraph(this.parseText())}}};function escape(html,encode){return html.replace(!encode?/&(?!#?\\w+;)/g:/&/g,\"&amp;\").replace(/</g,\"&lt;\").replace(/>/g,\"&gt;\").replace(/\"/g,\"&quot;\").replace(/'/g,\"&#39;\")}function unescape(html){return html.replace(/&([#\\w]+);/g,function(_,n){n=n.toLowerCase();if(n===\"colon\")return\":\";if(n.charAt(0)===\"#\"){return n.charAt(1)===\"x\"?String.fromCharCode(parseInt(n.substring(2),16)):String.fromCharCode(+n.substring(1))}return\"\"})}function replace(regex,opt){regex=regex.source;opt=opt||\"\";return function self(name,val){if(!name)return new RegExp(regex,opt);val=val.source||val;val=val.replace(/(^|[^\\[])\\^/g,\"$1\");regex=regex.replace(name,val);return self}}function noop(){}noop.exec=noop;function merge(obj){var i=1,target,key;for(;i<arguments.length;i++){target=arguments[i];for(key in target){if(Object.prototype.hasOwnProperty.call(target,key)){obj[key]=target[key]}}}return obj}function marked(src,opt,callback){if(callback||typeof opt===\"function\"){if(!callback){callback=opt;opt=null}opt=merge({},marked.defaults,opt||{});var highlight=opt.highlight,tokens,pending,i=0;try{tokens=Lexer.lex(src,opt)}catch(e){return callback(e)}pending=tokens.length;var done=function(err){if(err){opt.highlight=highlight;return callback(err)}var out;try{out=Parser.parse(tokens,opt)}catch(e){err=e}opt.highlight=highlight;return err?callback(err):callback(null,out)};if(!highlight||highlight.length<3){return done()}delete opt.highlight;if(!pending)return done();for(;i<tokens.length;i++){(function(token){if(token.type!==\"code\"){return--pending||done()}return highlight(token.text,token.lang,function(err,code){if(err)return done(err);if(code==null||code===token.text){return--pending||done()}token.text=code;token.escaped=true;--pending||done()})})(tokens[i])}return}try{if(opt)opt=merge({},marked.defaults,opt);return Parser.parse(Lexer.lex(src,opt),opt)}catch(e){e.message+=\"\\nPlease report this to https://github.com/chjj/marked.\";if((opt||marked.defaults).silent){return\"<p>An error occurred:</p><pre>\"+escape(e.message+\"\",true)+\"</pre>\"}throw e}}marked.options=marked.setOptions=function(opt){merge(marked.defaults,opt);return marked};marked.defaults={gfm:true,tables:true,breaks:false,pedantic:false,sanitize:false,sanitizer:null,mangle:true,smartLists:false,silent:false,highlight:null,langPrefix:\"lang-\",smartypants:false,headerPrefix:\"\",renderer:new Renderer,xhtml:false};marked.Parser=Parser;marked.parser=Parser.parse;marked.Renderer=Renderer;marked.Lexer=Lexer;marked.lexer=Lexer.lex;marked.InlineLexer=InlineLexer;marked.inlineLexer=InlineLexer.output;marked.parse=marked;if(typeof module!==\"undefined\"&&typeof exports===\"object\"){module.exports=marked}else if(typeof define===\"function\"&&define.amd){define(function(){return marked})}else{this.marked=marked}}).call(function(){return this||(typeof window!==\"undefined\"?window:global)}());\n</script>\n\n  <script>\n    $( document ).ready(function() {\n      marked.setOptions({\n        renderer: new marked.Renderer(),\n        gfm: true,\n        tables: true,\n        breaks: false,\n        pedantic: false,\n        sanitize: false,\n        smartLists: true,\n        smartypants: false\n      });\n\n      var textFile = null;\n\n      /// Function to be used to download a text json schema\n      function makeTextFile(text) {\n\n        var data = new Blob([text], {type: 'text/plain'});\n\n        // If we are replacing a previously generated file we need to\n        // manually revoke the object URL to avoid memory leaks.\n        if (textFile !== null) {\n          window.URL.revokeObjectURL(textFile);\n        }\n\n        textFile = window.URL.createObjectURL(data);\n\n        var a = document.createElement(\"a\");\n        document.body.appendChild(a);\n        a.style = \"display: none\";\n        a.href = textFile;\n        a.download = 'schema.txt';\n        a.click();\n\n        return textFile;\n      };\n\n      /// TODO: Implement resizing for expanding within iframe\n      function callResize() {\n        window.parent.postMessage('resize', \"*\");\n      }\n\n      function processMarked() {\n        $(\".marked\").each(function() {\n          $(this).html(marked($(this).html()));\n        });\n      }\n\n      // Bootstrap Scrollspy\n      $(this).scrollspy({ target: '#scrollingNav', offset: 18 });\n\n      // Content-Scroll on Navigation click.\n      $('.sidenav').find('a').on('click', function(e) {\n          e.preventDefault();\n          var id = $(this).attr('href');\n          if ($(id).length > 0)\n              $('html,body').animate({ scrollTop: parseInt($(id).offset().top) }, 400);\n          window.location.hash = $(this).attr('href');\n      });\n\n      // Quickjump on Pageload to hash position.\n      if(window.location.hash) {\n          var id = window.location.hash;\n          if ($(id).length > 0)\n              $('html,body').animate({ scrollTop: parseInt($(id).offset().top) }, 0);\n      }\n\n\n      function initDynamic() {\n        // tabs\n        $('.nav-tabs-examples a').click(function (e) {\n            e.preventDefault();\n            $(this).tab('show');\n        });\n\n\n        $('.nav-tabs-examples').find('a:first').tab('show');\n\n        // call scrollspy refresh method\n        $(window).scrollspy('refresh');\n      }\n\n      initDynamic();\n\n      // Pre- / Code-Format\n      prettyPrint();\n\n      //Convert elements with \"marked\" class to markdown\n      processMarked();\n    });\n\n    function findNode(id, currentNode) {\n        var currentChild,\n            result;\n\n        if ( Object.keys(currentNode)[0] == id) {\n            return currentNode;\n        } else {\n            // Use a for loop instead of forEach to avoid nested functions\n            // Otherwise \"return\" will not work properly\n            for(var propt in currentNode){\n                if (currentNode.hasOwnProperty(propt)) {\n                    currentChild = currentNode[propt]\n                    if (id == propt) {\n                        return currentChild;\n                    } else {\n                        // Search in the current child\n                        if (typeof(currentChild) === 'object') {\n                            result = findNode(id, currentChild);\n                            if (result != false) {\n                                return result;\n                            }\n                        }\n                    }\n                }\n            }\n            // The node has not been found and we have no more options\n           return false;\n        }\n    }\n  </script>\n  <style type=\"text/css\">\n    @import url('https://fonts.googleapis.com/css?family=Source+Code+Pro');\n\n    \n/*!\n * Bootstrap v2.3.2\n *\n * Copyright 2013 Twitter, Inc\n * Licensed under the Apache License v2.0\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Designed and built with all the love in the world by @mdo and @fat.\n */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:\"\"}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}a:hover,a:active{outline:0}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{width:auto\\9;height:auto;max-width:100%;vertical-align:middle;border:0;-ms-interpolation-mode:bicubic}#map_canvas img,.google-maps img{max-width:none}button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle}button,input{*overflow:visible;line-height:normal}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}button,html input[type=\"button\"],input[type=\"reset\"],input[type=\"submit\"]{cursor:pointer;-webkit-appearance:button}label,select,button,input[type=\"button\"],input[type=\"reset\"],input[type=\"submit\"],input[type=\"radio\"],input[type=\"checkbox\"]{cursor:pointer}input[type=\"search\"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=\"search\"]::-webkit-search-decoration,input[type=\"search\"]::-webkit-search-cancel-button{-webkit-appearance:none}textarea{overflow:auto;vertical-align:top}@media print{*{color:#000!important;text-shadow:none!important;background:transparent!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:\" (\" attr(href) \")\"}abbr[title]:after{content:\" (\" attr(title) \")\"}.ir a:after,a[href^=\"javascript:\"]:after,a[href^=\"#\"]:after{content:\"\"}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}}body{margin:0;font-family:\"Helvetica Neue\",Helvetica,Arial,sans-serif;font-size:14px;line-height:20px;color:#333;background-color:#fff}a{color:#08c;text-decoration:none}a:hover,a:focus{color:#005580;text-decoration:underline}.img-rounded{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.img-polaroid{padding:4px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.1);box-shadow:0 1px 3px rgba(0,0,0,0.1)}.img-circle{-webkit-border-radius:500px;-moz-border-radius:500px;border-radius:500px}.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:\"\"}.row:after{clear:both}[class*=\"span\"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.span12{width:940px}.span11{width:860px}.span10{width:780px}.span9{width:700px}.span8{width:620px}.span7{width:540px}.span6{width:460px}.span5{width:380px}.span4{width:300px}.span3{width:220px}.span2{width:140px}.span1{width:60px}.offset12{margin-left:980px}.offset11{margin-left:900px}.offset10{margin-left:820px}.offset9{margin-left:740px}.offset8{margin-left:660px}.offset7{margin-left:580px}.offset6{margin-left:500px}.offset5{margin-left:420px}.offset4{margin-left:340px}.offset3{margin-left:260px}.offset2{margin-left:180px}.offset1{margin-left:100px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:\"\"}.row-fluid:after{clear:both}.row-fluid [class*=\"span\"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.127659574468085%;*margin-left:2.074468085106383%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*=\"span\"]:first-child{margin-left:0}.row-fluid .controls-row [class*=\"span\"]+[class*=\"span\"]{margin-left:2.127659574468085%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.48936170212765%;*width:91.43617021276594%}.row-fluid .span10{width:82.97872340425532%;*width:82.92553191489361%}.row-fluid .span9{width:74.46808510638297%;*width:74.41489361702126%}.row-fluid .span8{width:65.95744680851064%;*width:65.90425531914893%}.row-fluid .span7{width:57.44680851063829%;*width:57.39361702127659%}.row-fluid .span6{width:48.93617021276595%;*width:48.88297872340425%}.row-fluid .span5{width:40.42553191489362%;*width:40.37234042553192%}.row-fluid .span4{width:31.914893617021278%;*width:31.861702127659576%}.row-fluid .span3{width:23.404255319148934%;*width:23.351063829787233%}.row-fluid .span2{width:14.893617021276595%;*width:14.840425531914894%}.row-fluid .span1{width:6.382978723404255%;*width:6.329787234042553%}.row-fluid .offset12{margin-left:104.25531914893617%;*margin-left:104.14893617021275%}.row-fluid .offset12:first-child{margin-left:102.12765957446808%;*margin-left:102.02127659574467%}.row-fluid .offset11{margin-left:95.74468085106382%;*margin-left:95.6382978723404%}.row-fluid .offset11:first-child{margin-left:93.61702127659574%;*margin-left:93.51063829787232%}.row-fluid .offset10{margin-left:87.23404255319149%;*margin-left:87.12765957446807%}.row-fluid .offset10:first-child{margin-left:85.1063829787234%;*margin-left:84.99999999999999%}.row-fluid .offset9{margin-left:78.72340425531914%;*margin-left:78.61702127659572%}.row-fluid .offset9:first-child{margin-left:76.59574468085106%;*margin-left:76.48936170212764%}.row-fluid .offset8{margin-left:70.2127659574468%;*margin-left:70.10638297872339%}.row-fluid .offset8:first-child{margin-left:68.08510638297872%;*margin-left:67.9787234042553%}.row-fluid .offset7{margin-left:61.70212765957446%;*margin-left:61.59574468085106%}.row-fluid .offset7:first-child{margin-left:59.574468085106375%;*margin-left:59.46808510638297%}.row-fluid .offset6{margin-left:53.191489361702125%;*margin-left:53.085106382978715%}.row-fluid .offset6:first-child{margin-left:51.063829787234035%;*margin-left:50.95744680851063%}.row-fluid .offset5{margin-left:44.68085106382979%;*margin-left:44.57446808510638%}.row-fluid .offset5:first-child{margin-left:42.5531914893617%;*margin-left:42.4468085106383%}.row-fluid .offset4{margin-left:36.170212765957444%;*margin-left:36.06382978723405%}.row-fluid .offset4:first-child{margin-left:34.04255319148936%;*margin-left:33.93617021276596%}.row-fluid .offset3{margin-left:27.659574468085104%;*margin-left:27.5531914893617%}.row-fluid .offset3:first-child{margin-left:25.53191489361702%;*margin-left:25.425531914893618%}.row-fluid .offset2{margin-left:19.148936170212764%;*margin-left:19.04255319148936%}.row-fluid .offset2:first-child{margin-left:17.02127659574468%;*margin-left:16.914893617021278%}.row-fluid .offset1{margin-left:10.638297872340425%;*margin-left:10.53191489361702%}.row-fluid .offset1:first-child{margin-left:8.51063829787234%;*margin-left:8.404255319148938%}[class*=\"span\"].hide,.row-fluid [class*=\"span\"].hide{display:none}[class*=\"span\"].pull-right,.row-fluid [class*=\"span\"].pull-right{float:right}.container{margin-right:auto;margin-left:auto;*zoom:1}.container:before,.container:after{display:table;line-height:0;content:\"\"}.container:after{clear:both}.container-fluid{padding-right:20px;padding-left:20px;*zoom:1}.container-fluid:before,.container-fluid:after{display:table;line-height:0;content:\"\"}.container-fluid:after{clear:both}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:21px;font-weight:200;line-height:30px}small{font-size:85%}strong{font-weight:bold}em{font-style:italic}cite{font-style:normal}.muted{color:#999}a.muted:hover,a.muted:focus{color:#808080}.text-warning{color:#c09853}a.text-warning:hover,a.text-warning:focus{color:#a47e3c}.text-error{color:#b94a48}a.text-error:hover,a.text-error:focus{color:#953b39}.text-info{color:#3a87ad}a.text-info:hover,a.text-info:focus{color:#2d6987}.text-success{color:#468847}a.text-success:hover,a.text-success:focus{color:#356635}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}h1,h2,h3,h4,h5,h6{margin:10px 0;font-family:inherit;font-weight:bold;line-height:20px;color:inherit;text-rendering:optimizelegibility}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:normal;line-height:1;color:#999}h1,h2,h3{line-height:40px}h1{font-size:38.5px}h2{font-size:31.5px}h3{font-size:24.5px}h4{font-size:17.5px}h5{font-size:14px}h6{font-size:11.9px}h1 small{font-size:24.5px}h2 small{font-size:17.5px}h3 small{font-size:14px}h4 small{font-size:14px}.page-header{padding-bottom:9px;margin:20px 0 30px;border-bottom:1px solid #eee}ul,ol{padding:0;margin:0 0 10px 25px}ul ul,ul ol,ol ol,ol ul{margin-bottom:0}li{line-height:20px}ul.unstyled,ol.unstyled{margin-left:0;list-style:none}ul.inline,ol.inline{margin-left:0;list-style:none}ul.inline>li,ol.inline>li{display:inline-block;*display:inline;padding-right:5px;padding-left:5px;*zoom:1}dl{margin-bottom:20px}dt,dd{line-height:20px}dt{font-weight:bold}dd{margin-left:10px}.dl-horizontal{*zoom:1}.dl-horizontal:before,.dl-horizontal:after{display:table;line-height:0;content:\"\"}.dl-horizontal:after{clear:both}.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}hr{margin:20px 0;border:0;border-top:1px solid #eee;border-bottom:1px solid #fff}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999}abbr.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:0 0 0 15px;margin:0 0 20px;border-left:5px solid #eee}blockquote p{margin-bottom:0;font-size:17.5px;font-weight:300;line-height:1.25}blockquote small{display:block;line-height:20px;color:#999}blockquote small:before{content:'\\2014 \\00A0'}blockquote.pull-right{float:right;padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0}blockquote.pull-right p,blockquote.pull-right small{text-align:right}blockquote.pull-right small:before{content:''}blockquote.pull-right small:after{content:'\\00A0 \\2014'}q:before,q:after,blockquote:before,blockquote:after{content:\"\"}address{display:block;margin-bottom:20px;font-style:normal;line-height:20px}code,pre{padding:0 3px 2px;font-family:Monaco,Menlo,Consolas,\"Courier New\",monospace;font-size:12px;color:#333;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}code{padding:2px 4px;color:#d14;white-space:nowrap;background-color:#f7f7f9;border:1px solid #e1e1e8}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:20px;word-break:break-all;word-wrap:break-word;white-space:pre;white-space:pre-wrap;background-color:#f5f5f5;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.15);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}pre.prettyprint{margin-bottom:20px}pre code{padding:0;color:inherit;white-space:pre;white-space:pre-wrap;background-color:transparent;border:0}.pre-scrollable{max-height:340px;overflow-y:scroll}form{margin:0 0 20px}fieldset{padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:40px;color:#333;border:0;border-bottom:1px solid #e5e5e5}legend small{font-size:15px;color:#999}label,input,button,select,textarea{font-size:14px;font-weight:normal;line-height:20px}input,button,select,textarea{font-family:\"Helvetica Neue\",Helvetica,Arial,sans-serif}label{display:block;margin-bottom:5px}select,textarea,input[type=\"text\"],input[type=\"password\"],input[type=\"datetime\"],input[type=\"datetime-local\"],input[type=\"date\"],input[type=\"month\"],input[type=\"time\"],input[type=\"week\"],input[type=\"number\"],input[type=\"email\"],input[type=\"url\"],input[type=\"search\"],input[type=\"tel\"],input[type=\"color\"],.uneditable-input{display:inline-block;height:20px;padding:4px 6px;margin-bottom:10px;font-size:14px;line-height:20px;color:#555;vertical-align:middle;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}input,textarea,.uneditable-input{width:206px}textarea{height:auto}textarea,input[type=\"text\"],input[type=\"password\"],input[type=\"datetime\"],input[type=\"datetime-local\"],input[type=\"date\"],input[type=\"month\"],input[type=\"time\"],input[type=\"week\"],input[type=\"number\"],input[type=\"email\"],input[type=\"url\"],input[type=\"search\"],input[type=\"tel\"],input[type=\"color\"],.uneditable-input{background-color:#fff;border:1px solid #ccc;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border linear .2s,box-shadow linear .2s;-moz-transition:border linear .2s,box-shadow linear .2s;-o-transition:border linear .2s,box-shadow linear .2s;transition:border linear .2s,box-shadow linear .2s}textarea:focus,input[type=\"text\"]:focus,input[type=\"password\"]:focus,input[type=\"datetime\"]:focus,input[type=\"datetime-local\"]:focus,input[type=\"date\"]:focus,input[type=\"month\"]:focus,input[type=\"time\"]:focus,input[type=\"week\"]:focus,input[type=\"number\"]:focus,input[type=\"email\"]:focus,input[type=\"url\"]:focus,input[type=\"search\"]:focus,input[type=\"tel\"]:focus,input[type=\"color\"]:focus,.uneditable-input:focus{border-color:rgba(82,168,236,0.8);outline:0;outline:thin dotted \\9;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}input[type=\"radio\"],input[type=\"checkbox\"]{margin:4px 0 0;margin-top:1px \\9;*margin-top:0;line-height:normal}input[type=\"file\"],input[type=\"image\"],input[type=\"submit\"],input[type=\"reset\"],input[type=\"button\"],input[type=\"radio\"],input[type=\"checkbox\"]{width:auto}select,input[type=\"file\"]{height:30px;*margin-top:4px;line-height:30px}select{width:220px;background-color:#fff;border:1px solid #ccc}select[multiple],select[size]{height:auto}select:focus,input[type=\"file\"]:focus,input[type=\"radio\"]:focus,input[type=\"checkbox\"]:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.uneditable-input,.uneditable-textarea{color:#999;cursor:not-allowed;background-color:#fcfcfc;border-color:#ccc;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);box-shadow:inset 0 1px 2px rgba(0,0,0,0.025)}.uneditable-input{overflow:hidden;white-space:nowrap}.uneditable-textarea{width:auto;height:auto}input:-moz-placeholder,textarea:-moz-placeholder{color:#999}input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:#999}input::-webkit-input-placeholder,textarea::-webkit-input-placeholder{color:#999}.radio,.checkbox{min-height:20px;padding-left:20px}.radio input[type=\"radio\"],.checkbox input[type=\"checkbox\"]{float:left;margin-left:-20px}.controls>.radio:first-child,.controls>.checkbox:first-child{padding-top:5px}.radio.inline,.checkbox.inline{display:inline-block;padding-top:5px;margin-bottom:0;vertical-align:middle}.radio.inline+.radio.inline,.checkbox.inline+.checkbox.inline{margin-left:10px}.input-mini{width:60px}.input-small{width:90px}.input-medium{width:150px}.input-large{width:210px}.input-xlarge{width:270px}.input-xxlarge{width:530px}input[class*=\"span\"],select[class*=\"span\"],textarea[class*=\"span\"],.uneditable-input[class*=\"span\"],.row-fluid input[class*=\"span\"],.row-fluid select[class*=\"span\"],.row-fluid textarea[class*=\"span\"],.row-fluid .uneditable-input[class*=\"span\"]{float:none;margin-left:0}.input-append input[class*=\"span\"],.input-append .uneditable-input[class*=\"span\"],.input-prepend input[class*=\"span\"],.input-prepend .uneditable-input[class*=\"span\"],.row-fluid input[class*=\"span\"],.row-fluid select[class*=\"span\"],.row-fluid textarea[class*=\"span\"],.row-fluid .uneditable-input[class*=\"span\"],.row-fluid .input-prepend [class*=\"span\"],.row-fluid .input-append [class*=\"span\"]{display:inline-block}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*=\"span\"]+[class*=\"span\"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:926px}input.span11,textarea.span11,.uneditable-input.span11{width:846px}input.span10,textarea.span10,.uneditable-input.span10{width:766px}input.span9,textarea.span9,.uneditable-input.span9{width:686px}input.span8,textarea.span8,.uneditable-input.span8{width:606px}input.span7,textarea.span7,.uneditable-input.span7{width:526px}input.span6,textarea.span6,.uneditable-input.span6{width:446px}input.span5,textarea.span5,.uneditable-input.span5{width:366px}input.span4,textarea.span4,.uneditable-input.span4{width:286px}input.span3,textarea.span3,.uneditable-input.span3{width:206px}input.span2,textarea.span2,.uneditable-input.span2{width:126px}input.span1,textarea.span1,.uneditable-input.span1{width:46px}.controls-row{*zoom:1}.controls-row:before,.controls-row:after{display:table;line-height:0;content:\"\"}.controls-row:after{clear:both}.controls-row [class*=\"span\"],.row-fluid .controls-row [class*=\"span\"]{float:left}.controls-row .checkbox[class*=\"span\"],.controls-row .radio[class*=\"span\"]{padding-top:5px}input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#eee}input[type=\"radio\"][disabled],input[type=\"checkbox\"][disabled],input[type=\"radio\"][readonly],input[type=\"checkbox\"][readonly]{background-color:transparent}.control-group.warning .control-label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#c09853}.control-group.warning .checkbox,.control-group.warning .radio,.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#c09853}.control-group.warning input,.control-group.warning select,.control-group.warning textarea{border-color:#c09853;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.warning input:focus,.control-group.warning select:focus,.control-group.warning textarea:focus{border-color:#a47e3c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e}.control-group.warning .input-prepend .add-on,.control-group.warning .input-append .add-on{color:#c09853;background-color:#fcf8e3;border-color:#c09853}.control-group.error .control-label,.control-group.error .help-block,.control-group.error .help-inline{color:#b94a48}.control-group.error .checkbox,.control-group.error .radio,.control-group.error input,.control-group.error select,.control-group.error textarea{color:#b94a48}.control-group.error input,.control-group.error select,.control-group.error textarea{border-color:#b94a48;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.error input:focus,.control-group.error select:focus,.control-group.error textarea:focus{border-color:#953b39;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392}.control-group.error .input-prepend .add-on,.control-group.error .input-append .add-on{color:#b94a48;background-color:#f2dede;border-color:#b94a48}.control-group.success .control-label,.control-group.success .help-block,.control-group.success .help-inline{color:#468847}.control-group.success .checkbox,.control-group.success .radio,.control-group.success input,.control-group.success select,.control-group.success textarea{color:#468847}.control-group.success input,.control-group.success select,.control-group.success textarea{border-color:#468847;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.success input:focus,.control-group.success select:focus,.control-group.success textarea:focus{border-color:#356635;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b}.control-group.success .input-prepend .add-on,.control-group.success .input-append .add-on{color:#468847;background-color:#dff0d8;border-color:#468847}.control-group.info .control-label,.control-group.info .help-block,.control-group.info .help-inline{color:#3a87ad}.control-group.info .checkbox,.control-group.info .radio,.control-group.info input,.control-group.info select,.control-group.info textarea{color:#3a87ad}.control-group.info input,.control-group.info select,.control-group.info textarea{border-color:#3a87ad;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.info input:focus,.control-group.info select:focus,.control-group.info textarea:focus{border-color:#2d6987;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3}.control-group.info .input-prepend .add-on,.control-group.info .input-append .add-on{color:#3a87ad;background-color:#d9edf7;border-color:#3a87ad}input:focus:invalid,textarea:focus:invalid,select:focus:invalid{color:#b94a48;border-color:#ee5f5b}input:focus:invalid:focus,textarea:focus:invalid:focus,select:focus:invalid:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7}.form-actions{padding:19px 20px 20px;margin-top:20px;margin-bottom:20px;background-color:#f5f5f5;border-top:1px solid #e5e5e5;*zoom:1}.form-actions:before,.form-actions:after{display:table;line-height:0;content:\"\"}.form-actions:after{clear:both}.help-block,.help-inline{color:#595959}.help-block{display:block;margin-bottom:10px}.help-inline{display:inline-block;*display:inline;padding-left:5px;vertical-align:middle;*zoom:1}.input-append,.input-prepend{display:inline-block;margin-bottom:10px;font-size:0;white-space:nowrap;vertical-align:middle}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input,.input-append .dropdown-menu,.input-prepend .dropdown-menu,.input-append .popover,.input-prepend .popover{font-size:14px}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input{position:relative;margin-bottom:0;*margin-left:0;vertical-align:top;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-append input:focus,.input-prepend input:focus,.input-append select:focus,.input-prepend select:focus,.input-append .uneditable-input:focus,.input-prepend .uneditable-input:focus{z-index:2}.input-append .add-on,.input-prepend .add-on{display:inline-block;width:auto;height:20px;min-width:16px;padding:4px 5px;font-size:14px;font-weight:normal;line-height:20px;text-align:center;text-shadow:0 1px 0 #fff;background-color:#eee;border:1px solid #ccc}.input-append .add-on,.input-prepend .add-on,.input-append .btn,.input-prepend .btn,.input-append .btn-group>.dropdown-toggle,.input-prepend .btn-group>.dropdown-toggle{vertical-align:top;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-append .active,.input-prepend .active{background-color:#a9dba9;border-color:#46a546}.input-prepend .add-on,.input-prepend .btn{margin-right:-1px}.input-prepend .add-on:first-child,.input-prepend .btn:first-child{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-append input,.input-append select,.input-append .uneditable-input{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-append input+.btn-group .btn:last-child,.input-append select+.btn-group .btn:last-child,.input-append .uneditable-input+.btn-group .btn:last-child{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-append .add-on,.input-append .btn,.input-append .btn-group{margin-left:-1px}.input-append .add-on:last-child,.input-append .btn:last-child,.input-append .btn-group:last-child>.dropdown-toggle{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append input,.input-prepend.input-append select,.input-prepend.input-append .uneditable-input{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-prepend.input-append input+.btn-group .btn,.input-prepend.input-append select+.btn-group .btn,.input-prepend.input-append .uneditable-input+.btn-group .btn{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append .add-on:first-child,.input-prepend.input-append .btn:first-child{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-prepend.input-append .add-on:last-child,.input-prepend.input-append .btn:last-child{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append .btn-group:first-child{margin-left:0}input.search-query{padding-right:14px;padding-right:4px \\9;padding-left:14px;padding-left:4px \\9;margin-bottom:0;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.form-search .input-append .search-query,.form-search .input-prepend .search-query{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.form-search .input-append .search-query{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search .input-append .btn{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .search-query{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .btn{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search input,.form-inline input,.form-horizontal input,.form-search textarea,.form-inline textarea,.form-horizontal textarea,.form-search select,.form-inline select,.form-horizontal select,.form-search .help-inline,.form-inline .help-inline,.form-horizontal .help-inline,.form-search .uneditable-input,.form-inline .uneditable-input,.form-horizontal .uneditable-input,.form-search .input-prepend,.form-inline .input-prepend,.form-horizontal .input-prepend,.form-search .input-append,.form-inline .input-append,.form-horizontal .input-append{display:inline-block;*display:inline;margin-bottom:0;vertical-align:middle;*zoom:1}.form-search .hide,.form-inline .hide,.form-horizontal .hide{display:none}.form-search label,.form-inline label,.form-search .btn-group,.form-inline .btn-group{display:inline-block}.form-search .input-append,.form-inline .input-append,.form-search .input-prepend,.form-inline .input-prepend{margin-bottom:0}.form-search .radio,.form-search .checkbox,.form-inline .radio,.form-inline .checkbox{padding-left:0;margin-bottom:0;vertical-align:middle}.form-search .radio input[type=\"radio\"],.form-search .checkbox input[type=\"checkbox\"],.form-inline .radio input[type=\"radio\"],.form-inline .checkbox input[type=\"checkbox\"]{float:left;margin-right:3px;margin-left:0}.control-group{margin-bottom:10px}legend+.control-group{margin-top:20px;-webkit-margin-top-collapse:separate}.form-horizontal .control-group{margin-bottom:20px;*zoom:1}.form-horizontal .control-group:before,.form-horizontal .control-group:after{display:table;line-height:0;content:\"\"}.form-horizontal .control-group:after{clear:both}.form-horizontal .control-label{float:left;width:160px;padding-top:5px;text-align:right}.form-horizontal .controls{*display:inline-block;*padding-left:20px;margin-left:180px;*margin-left:0}.form-horizontal .controls:first-child{*padding-left:180px}.form-horizontal .help-block{margin-bottom:0}.form-horizontal input+.help-block,.form-horizontal select+.help-block,.form-horizontal textarea+.help-block,.form-horizontal .uneditable-input+.help-block,.form-horizontal .input-prepend+.help-block,.form-horizontal .input-append+.help-block{margin-top:10px}.form-horizontal .form-actions{padding-left:180px}table{max-width:100%;background-color:transparent;border-collapse:collapse;border-spacing:0}.table{width:100%;margin-bottom:20px}.table th,.table td{padding:8px;line-height:20px;text-align:left;vertical-align:top;border-top:1px solid #ddd}.table th{font-weight:bold}.table thead th{vertical-align:bottom}.table caption+thead tr:first-child th,.table caption+thead tr:first-child td,.table colgroup+thead tr:first-child th,.table colgroup+thead tr:first-child td,.table thead:first-child tr:first-child th,.table thead:first-child tr:first-child td{border-top:0}.table tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed th,.table-condensed td{padding:4px 5px}.table-bordered{border:1px solid #ddd;border-collapse:separate;*border-collapse:collapse;border-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.table-bordered th,.table-bordered td{border-left:1px solid #ddd}.table-bordered caption+thead tr:first-child th,.table-bordered caption+tbody tr:first-child th,.table-bordered caption+tbody tr:first-child td,.table-bordered colgroup+thead tr:first-child th,.table-bordered colgroup+tbody tr:first-child th,.table-bordered colgroup+tbody tr:first-child td,.table-bordered thead:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child td{border-top:0}.table-bordered thead:first-child tr:first-child>th:first-child,.table-bordered tbody:first-child tr:first-child>td:first-child,.table-bordered tbody:first-child tr:first-child>th:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px}.table-bordered thead:first-child tr:first-child>th:last-child,.table-bordered tbody:first-child tr:first-child>td:last-child,.table-bordered tbody:first-child tr:first-child>th:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px}.table-bordered thead:last-child tr:last-child>th:first-child,.table-bordered tbody:last-child tr:last-child>td:first-child,.table-bordered tbody:last-child tr:last-child>th:first-child,.table-bordered tfoot:last-child tr:last-child>td:first-child,.table-bordered tfoot:last-child tr:last-child>th:first-child{-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px}.table-bordered thead:last-child tr:last-child>th:last-child,.table-bordered tbody:last-child tr:last-child>td:last-child,.table-bordered tbody:last-child tr:last-child>th:last-child,.table-bordered tfoot:last-child tr:last-child>td:last-child,.table-bordered tfoot:last-child tr:last-child>th:last-child{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px}.table-bordered tfoot+tbody:last-child tr:last-child td:first-child{-webkit-border-bottom-left-radius:0;border-bottom-left-radius:0;-moz-border-radius-bottomleft:0}.table-bordered tfoot+tbody:last-child tr:last-child td:last-child{-webkit-border-bottom-right-radius:0;border-bottom-right-radius:0;-moz-border-radius-bottomright:0}.table-bordered caption+thead tr:first-child th:first-child,.table-bordered caption+tbody tr:first-child td:first-child,.table-bordered colgroup+thead tr:first-child th:first-child,.table-bordered colgroup+tbody tr:first-child td:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px}.table-bordered caption+thead tr:first-child th:last-child,.table-bordered caption+tbody tr:first-child td:last-child,.table-bordered colgroup+thead tr:first-child th:last-child,.table-bordered colgroup+tbody tr:first-child td:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px}.table-striped tbody>tr:nth-child(odd)>td,.table-striped tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}.table-hover tbody tr:hover>td,.table-hover tbody tr:hover>th{background-color:#f5f5f5}table td[class*=\"span\"],table th[class*=\"span\"],.row-fluid table td[class*=\"span\"],.row-fluid table th[class*=\"span\"]{display:table-cell;float:none;margin-left:0}.table td.span1,.table th.span1{float:none;width:44px;margin-left:0}.table td.span2,.table th.span2{float:none;width:124px;margin-left:0}.table td.span3,.table th.span3{float:none;width:204px;margin-left:0}.table td.span4,.table th.span4{float:none;width:284px;margin-left:0}.table td.span5,.table th.span5{float:none;width:364px;margin-left:0}.table td.span6,.table th.span6{float:none;width:444px;margin-left:0}.table td.span7,.table th.span7{float:none;width:524px;margin-left:0}.table td.span8,.table th.span8{float:none;width:604px;margin-left:0}.table td.span9,.table th.span9{float:none;width:684px;margin-left:0}.table td.span10,.table th.span10{float:none;width:764px;margin-left:0}.table td.span11,.table th.span11{float:none;width:844px;margin-left:0}.table td.span12,.table th.span12{float:none;width:924px;margin-left:0}.table tbody tr.success>td{background-color:#dff0d8}.table tbody tr.error>td{background-color:#f2dede}.table tbody tr.warning>td{background-color:#fcf8e3}.table tbody tr.info>td{background-color:#d9edf7}.table-hover tbody tr.success:hover>td{background-color:#d0e9c6}.table-hover tbody tr.error:hover>td{background-color:#ebcccc}.table-hover tbody tr.warning:hover>td{background-color:#faf2cc}.table-hover tbody tr.info:hover>td{background-color:#c4e3f3}[class^=\"icon-\"],[class*=\" icon-\"]{display:inline-block;width:14px;height:14px;margin-top:1px;*margin-right:.3em;line-height:14px;vertical-align:text-top;background-image:url(\"../img/glyphicons-halflings.png\");background-position:14px 14px;background-repeat:no-repeat}.icon-white,.nav-pills>.active>a>[class^=\"icon-\"],.nav-pills>.active>a>[class*=\" icon-\"],.nav-list>.active>a>[class^=\"icon-\"],.nav-list>.active>a>[class*=\" icon-\"],.navbar-inverse .nav>.active>a>[class^=\"icon-\"],.navbar-inverse .nav>.active>a>[class*=\" icon-\"],.dropdown-menu>li>a:hover>[class^=\"icon-\"],.dropdown-menu>li>a:focus>[class^=\"icon-\"],.dropdown-menu>li>a:hover>[class*=\" icon-\"],.dropdown-menu>li>a:focus>[class*=\" icon-\"],.dropdown-menu>.active>a>[class^=\"icon-\"],.dropdown-menu>.active>a>[class*=\" icon-\"],.dropdown-submenu:hover>a>[class^=\"icon-\"],.dropdown-submenu:focus>a>[class^=\"icon-\"],.dropdown-submenu:hover>a>[class*=\" icon-\"],.dropdown-submenu:focus>a>[class*=\" icon-\"]{background-image:url(\"../img/glyphicons-halflings-white.png\")}.icon-glass{background-position:0 0}.icon-music{background-position:-24px 0}.icon-search{background-position:-48px 0}.icon-envelope{background-position:-72px 0}.icon-heart{background-position:-96px 0}.icon-star{background-position:-120px 0}.icon-star-empty{background-position:-144px 0}.icon-user{background-position:-168px 0}.icon-film{background-position:-192px 0}.icon-th-large{background-position:-216px 0}.icon-th{background-position:-240px 0}.icon-th-list{background-position:-264px 0}.icon-ok{background-position:-288px 0}.icon-remove{background-position:-312px 0}.icon-zoom-in{background-position:-336px 0}.icon-zoom-out{background-position:-360px 0}.icon-off{background-position:-384px 0}.icon-signal{background-position:-408px 0}.icon-cog{background-position:-432px 0}.icon-trash{background-position:-456px 0}.icon-home{background-position:0 -24px}.icon-file{background-position:-24px -24px}.icon-time{background-position:-48px -24px}.icon-road{background-position:-72px -24px}.icon-download-alt{background-position:-96px -24px}.icon-download{background-position:-120px -24px}.icon-upload{background-position:-144px -24px}.icon-inbox{background-position:-168px -24px}.icon-play-circle{background-position:-192px -24px}.icon-repeat{background-position:-216px -24px}.icon-refresh{background-position:-240px -24px}.icon-list-alt{background-position:-264px -24px}.icon-lock{background-position:-287px -24px}.icon-flag{background-position:-312px -24px}.icon-headphones{background-position:-336px -24px}.icon-volume-off{background-position:-360px -24px}.icon-volume-down{background-position:-384px -24px}.icon-volume-up{background-position:-408px -24px}.icon-qrcode{background-position:-432px -24px}.icon-barcode{background-position:-456px -24px}.icon-tag{background-position:0 -48px}.icon-tags{background-position:-25px -48px}.icon-book{background-position:-48px -48px}.icon-bookmark{background-position:-72px -48px}.icon-print{background-position:-96px -48px}.icon-camera{background-position:-120px -48px}.icon-font{background-position:-144px -48px}.icon-bold{background-position:-167px -48px}.icon-italic{background-position:-192px -48px}.icon-text-height{background-position:-216px -48px}.icon-text-width{background-position:-240px -48px}.icon-align-left{background-position:-264px -48px}.icon-align-center{background-position:-288px -48px}.icon-align-right{background-position:-312px -48px}.icon-align-justify{background-position:-336px -48px}.icon-list{background-position:-360px -48px}.icon-indent-left{background-position:-384px -48px}.icon-indent-right{background-position:-408px -48px}.icon-facetime-video{background-position:-432px -48px}.icon-picture{background-position:-456px -48px}.icon-pencil{background-position:0 -72px}.icon-map-marker{background-position:-24px -72px}.icon-adjust{background-position:-48px -72px}.icon-tint{background-position:-72px -72px}.icon-edit{background-position:-96px -72px}.icon-share{background-position:-120px -72px}.icon-check{background-position:-144px -72px}.icon-move{background-position:-168px -72px}.icon-step-backward{background-position:-192px -72px}.icon-fast-backward{background-position:-216px -72px}.icon-backward{background-position:-240px -72px}.icon-play{background-position:-264px -72px}.icon-pause{background-position:-288px -72px}.icon-stop{background-position:-312px -72px}.icon-forward{background-position:-336px -72px}.icon-fast-forward{background-position:-360px -72px}.icon-step-forward{background-position:-384px -72px}.icon-eject{background-position:-408px -72px}.icon-chevron-left{background-position:-432px -72px}.icon-chevron-right{background-position:-456px -72px}.icon-plus-sign{background-position:0 -96px}.icon-minus-sign{background-position:-24px -96px}.icon-remove-sign{background-position:-48px -96px}.icon-ok-sign{background-position:-72px -96px}.icon-question-sign{background-position:-96px -96px}.icon-info-sign{background-position:-120px -96px}.icon-screenshot{background-position:-144px -96px}.icon-remove-circle{background-position:-168px -96px}.icon-ok-circle{background-position:-192px -96px}.icon-ban-circle{background-position:-216px -96px}.icon-arrow-left{background-position:-240px -96px}.icon-arrow-right{background-position:-264px -96px}.icon-arrow-up{background-position:-289px -96px}.icon-arrow-down{background-position:-312px -96px}.icon-share-alt{background-position:-336px -96px}.icon-resize-full{background-position:-360px -96px}.icon-resize-small{background-position:-384px -96px}.icon-plus{background-position:-408px -96px}.icon-minus{background-position:-433px -96px}.icon-asterisk{background-position:-456px -96px}.icon-exclamation-sign{background-position:0 -120px}.icon-gift{background-position:-24px -120px}.icon-leaf{background-position:-48px -120px}.icon-fire{background-position:-72px -120px}.icon-eye-open{background-position:-96px -120px}.icon-eye-close{background-position:-120px -120px}.icon-warning-sign{background-position:-144px -120px}.icon-plane{background-position:-168px -120px}.icon-calendar{background-position:-192px -120px}.icon-random{width:16px;background-position:-216px -120px}.icon-comment{background-position:-240px -120px}.icon-magnet{background-position:-264px -120px}.icon-chevron-up{background-position:-288px -120px}.icon-chevron-down{background-position:-313px -119px}.icon-retweet{background-position:-336px -120px}.icon-shopping-cart{background-position:-360px -120px}.icon-folder-close{width:16px;background-position:-384px -120px}.icon-folder-open{width:16px;background-position:-408px -120px}.icon-resize-vertical{background-position:-432px -119px}.icon-resize-horizontal{background-position:-456px -118px}.icon-hdd{background-position:0 -144px}.icon-bullhorn{background-position:-24px -144px}.icon-bell{background-position:-48px -144px}.icon-certificate{background-position:-72px -144px}.icon-thumbs-up{background-position:-96px -144px}.icon-thumbs-down{background-position:-120px -144px}.icon-hand-right{background-position:-144px -144px}.icon-hand-left{background-position:-168px -144px}.icon-hand-up{background-position:-192px -144px}.icon-hand-down{background-position:-216px -144px}.icon-circle-arrow-right{background-position:-240px -144px}.icon-circle-arrow-left{background-position:-264px -144px}.icon-circle-arrow-up{background-position:-288px -144px}.icon-circle-arrow-down{background-position:-312px -144px}.icon-globe{background-position:-336px -144px}.icon-wrench{background-position:-360px -144px}.icon-tasks{background-position:-384px -144px}.icon-filter{background-position:-408px -144px}.icon-briefcase{background-position:-432px -144px}.icon-fullscreen{background-position:-456px -144px}.dropup,.dropdown{position:relative}.dropdown-toggle{*margin-bottom:-3px}.dropdown-toggle:active,.open .dropdown-toggle{outline:0}.caret{display:inline-block;width:0;height:0;vertical-align:top;border-top:4px solid #000;border-right:4px solid transparent;border-left:4px solid transparent;content:\"\"}.dropdown .caret{margin-top:8px;margin-left:2px}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);*border-right-width:2px;*border-bottom-width:2px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:20px;color:#333;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus,.dropdown-submenu:hover>a,.dropdown-submenu:focus>a{color:#fff;text-decoration:none;background-color:#0081c2;background-image:-moz-linear-gradient(top,#08c,#0077b3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#0077b3));background-image:-webkit-linear-gradient(top,#08c,#0077b3);background-image:-o-linear-gradient(top,#08c,#0077b3);background-image:linear-gradient(to bottom,#08c,#0077b3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0077b3',GradientType=0)}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;background-color:#0081c2;background-image:-moz-linear-gradient(top,#08c,#0077b3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#0077b3));background-image:-webkit-linear-gradient(top,#08c,#0077b3);background-image:-o-linear-gradient(top,#08c,#0077b3);background-image:linear-gradient(to bottom,#08c,#0077b3);background-repeat:repeat-x;outline:0;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0077b3',GradientType=0)}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#999}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;cursor:default;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open{*z-index:1000}.open>.dropdown-menu{display:block}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid #000;content:\"\"}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}.dropdown-submenu{position:relative}.dropdown-submenu>.dropdown-menu{top:0;left:100%;margin-top:-6px;margin-left:-1px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px}.dropdown-submenu:hover>.dropdown-menu{display:block}.dropup .dropdown-submenu>.dropdown-menu{top:auto;bottom:0;margin-top:0;margin-bottom:-2px;-webkit-border-radius:5px 5px 5px 0;-moz-border-radius:5px 5px 5px 0;border-radius:5px 5px 5px 0}.dropdown-submenu>a:after{display:block;float:right;width:0;height:0;margin-top:5px;margin-right:-10px;border-color:transparent;border-left-color:#ccc;border-style:solid;border-width:5px 0 5px 5px;content:\" \"}.dropdown-submenu:hover>a:after{border-left-color:#fff}.dropdown-submenu.pull-left{float:none}.dropdown-submenu.pull-left>.dropdown-menu{left:-100%;margin-left:10px;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.dropdown .dropdown-menu .nav-header{padding-right:20px;padding-left:20px}.typeahead{z-index:1051;margin-top:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-large{padding:24px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.well-small{padding:9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.fade{opacity:0;-webkit-transition:opacity .15s linear;-moz-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;-moz-transition:height .35s ease;-o-transition:height .35s ease;transition:height .35s ease}.collapse.in{height:auto}.close{float:right;font-size:20px;font-weight:bold;line-height:20px;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;opacity:.4;filter:alpha(opacity=40)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.btn{display:inline-block;*display:inline;padding:4px 12px;margin-bottom:0;*margin-left:.3em;font-size:14px;line-height:20px;color:#333;text-align:center;text-shadow:0 1px 1px rgba(255,255,255,0.75);vertical-align:middle;cursor:pointer;background-color:#f5f5f5;*background-color:#e6e6e6;background-image:-moz-linear-gradient(top,#fff,#e6e6e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,#e6e6e6);background-image:-o-linear-gradient(top,#fff,#e6e6e6);background-image:linear-gradient(to bottom,#fff,#e6e6e6);background-repeat:repeat-x;border:1px solid #ccc;*border:0;border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);border-bottom-color:#b3b3b3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffe6e6e6',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);*zoom:1;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.btn:hover,.btn:focus,.btn:active,.btn.active,.btn.disabled,.btn[disabled]{color:#333;background-color:#e6e6e6;*background-color:#d9d9d9}.btn:active,.btn.active{background-color:#ccc \\9}.btn:first-child{*margin-left:0}.btn:hover,.btn:focus{color:#333;text-decoration:none;background-position:0 -15px;-webkit-transition:background-position .1s linear;-moz-transition:background-position .1s linear;-o-transition:background-position .1s linear;transition:background-position .1s linear}.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.btn.disabled,.btn[disabled]{cursor:default;background-image:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-large{padding:11px 19px;font-size:17.5px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.btn-large [class^=\"icon-\"],.btn-large [class*=\" icon-\"]{margin-top:4px}.btn-small{padding:2px 10px;font-size:11.9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.btn-small [class^=\"icon-\"],.btn-small [class*=\" icon-\"]{margin-top:0}.btn-mini [class^=\"icon-\"],.btn-mini [class*=\" icon-\"]{margin-top:-1px}.btn-mini{padding:0 6px;font-size:10.5px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.btn-block{display:block;width:100%;padding-right:0;padding-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.btn-block+.btn-block{margin-top:5px}input[type=\"submit\"].btn-block,input[type=\"reset\"].btn-block,input[type=\"button\"].btn-block{width:100%}.btn-primary.active,.btn-warning.active,.btn-danger.active,.btn-success.active,.btn-info.active,.btn-inverse.active{color:rgba(255,255,255,0.75)}.btn-primary{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#006dcc;*background-color:#04c;background-image:-moz-linear-gradient(top,#08c,#04c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#04c));background-image:-webkit-linear-gradient(top,#08c,#04c);background-image:-o-linear-gradient(top,#08c,#04c);background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;border-color:#04c #04c #002a80;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0044cc',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{color:#fff;background-color:#04c;*background-color:#003bb3}.btn-primary:active,.btn-primary.active{background-color:#039 \\9}.btn-warning{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#faa732;*background-color:#f89406;background-image:-moz-linear-gradient(top,#fbb450,#f89406);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fbb450),to(#f89406));background-image:-webkit-linear-gradient(top,#fbb450,#f89406);background-image:-o-linear-gradient(top,#fbb450,#f89406);background-image:linear-gradient(to bottom,#fbb450,#f89406);background-repeat:repeat-x;border-color:#f89406 #f89406 #ad6704;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450',endColorstr='#fff89406',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{color:#fff;background-color:#f89406;*background-color:#df8505}.btn-warning:active,.btn-warning.active{background-color:#c67605 \\9}.btn-danger{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#da4f49;*background-color:#bd362f;background-image:-moz-linear-gradient(top,#ee5f5b,#bd362f);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#bd362f));background-image:-webkit-linear-gradient(top,#ee5f5b,#bd362f);background-image:-o-linear-gradient(top,#ee5f5b,#bd362f);background-image:linear-gradient(to bottom,#ee5f5b,#bd362f);background-repeat:repeat-x;border-color:#bd362f #bd362f #802420;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b',endColorstr='#ffbd362f',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{color:#fff;background-color:#bd362f;*background-color:#a9302a}.btn-danger:active,.btn-danger.active{background-color:#942a25 \\9}.btn-success{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#5bb75b;*background-color:#51a351;background-image:-moz-linear-gradient(top,#62c462,#51a351);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#51a351));background-image:-webkit-linear-gradient(top,#62c462,#51a351);background-image:-o-linear-gradient(top,#62c462,#51a351);background-image:linear-gradient(to bottom,#62c462,#51a351);background-repeat:repeat-x;border-color:#51a351 #51a351 #387038;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462',endColorstr='#ff51a351',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{color:#fff;background-color:#51a351;*background-color:#499249}.btn-success:active,.btn-success.active{background-color:#408140 \\9}.btn-info{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#49afcd;*background-color:#2f96b4;background-image:-moz-linear-gradient(top,#5bc0de,#2f96b4);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#2f96b4));background-image:-webkit-linear-gradient(top,#5bc0de,#2f96b4);background-image:-o-linear-gradient(top,#5bc0de,#2f96b4);background-image:linear-gradient(to bottom,#5bc0de,#2f96b4);background-repeat:repeat-x;border-color:#2f96b4 #2f96b4 #1f6377;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff2f96b4',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{color:#fff;background-color:#2f96b4;*background-color:#2a85a0}.btn-info:active,.btn-info.active{background-color:#24748c \\9}.btn-inverse{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#363636;*background-color:#222;background-image:-moz-linear-gradient(top,#444,#222);background-image:-webkit-gradient(linear,0 0,0 100%,from(#444),to(#222));background-image:-webkit-linear-gradient(top,#444,#222);background-image:-o-linear-gradient(top,#444,#222);background-image:linear-gradient(to bottom,#444,#222);background-repeat:repeat-x;border-color:#222 #222 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff444444',endColorstr='#ff222222',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-inverse:hover,.btn-inverse:focus,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{color:#fff;background-color:#222;*background-color:#151515}.btn-inverse:active,.btn-inverse.active{background-color:#080808 \\9}button.btn,input[type=\"submit\"].btn{*padding-top:3px;*padding-bottom:3px}button.btn::-moz-focus-inner,input[type=\"submit\"].btn::-moz-focus-inner{padding:0;border:0}button.btn.btn-large,input[type=\"submit\"].btn.btn-large{*padding-top:7px;*padding-bottom:7px}button.btn.btn-small,input[type=\"submit\"].btn.btn-small{*padding-top:3px;*padding-bottom:3px}button.btn.btn-mini,input[type=\"submit\"].btn.btn-mini{*padding-top:1px;*padding-bottom:1px}.btn-link,.btn-link:active,.btn-link[disabled]{background-color:transparent;background-image:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-link{color:#08c;cursor:pointer;border-color:transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-link:hover,.btn-link:focus{color:#005580;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,.btn-link[disabled]:focus{color:#333;text-decoration:none}.btn-group{position:relative;display:inline-block;*display:inline;*margin-left:.3em;font-size:0;white-space:nowrap;vertical-align:middle;*zoom:1}.btn-group:first-child{*margin-left:0}.btn-group+.btn-group{margin-left:5px}.btn-toolbar{margin-top:10px;margin-bottom:10px;font-size:0}.btn-toolbar>.btn+.btn,.btn-toolbar>.btn-group+.btn,.btn-toolbar>.btn+.btn-group{margin-left:5px}.btn-group>.btn{position:relative;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group>.btn+.btn{margin-left:-1px}.btn-group>.btn,.btn-group>.dropdown-menu,.btn-group>.popover{font-size:14px}.btn-group>.btn-mini{font-size:10.5px}.btn-group>.btn-small{font-size:11.9px}.btn-group>.btn-large{font-size:17.5px}.btn-group>.btn:first-child{margin-left:0;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-bottomleft:4px;-moz-border-radius-topleft:4px}.btn-group>.btn:last-child,.btn-group>.dropdown-toggle{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-bottomright:4px}.btn-group>.btn.large:first-child{margin-left:0;-webkit-border-bottom-left-radius:6px;border-bottom-left-radius:6px;-webkit-border-top-left-radius:6px;border-top-left-radius:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topleft:6px}.btn-group>.btn.large:last-child,.btn-group>.large.dropdown-toggle{-webkit-border-top-right-radius:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;border-bottom-right-radius:6px;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px}.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active{z-index:2}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{*padding-top:5px;padding-right:8px;*padding-bottom:5px;padding-left:8px;-webkit-box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.btn-group>.btn-mini+.dropdown-toggle{*padding-top:2px;padding-right:5px;*padding-bottom:2px;padding-left:5px}.btn-group>.btn-small+.dropdown-toggle{*padding-top:5px;*padding-bottom:4px}.btn-group>.btn-large+.dropdown-toggle{*padding-top:7px;padding-right:12px;*padding-bottom:7px;padding-left:12px}.btn-group.open .dropdown-toggle{background-image:none;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.btn-group.open .btn.dropdown-toggle{background-color:#e6e6e6}.btn-group.open .btn-primary.dropdown-toggle{background-color:#04c}.btn-group.open .btn-warning.dropdown-toggle{background-color:#f89406}.btn-group.open .btn-danger.dropdown-toggle{background-color:#bd362f}.btn-group.open .btn-success.dropdown-toggle{background-color:#51a351}.btn-group.open .btn-info.dropdown-toggle{background-color:#2f96b4}.btn-group.open .btn-inverse.dropdown-toggle{background-color:#222}.btn .caret{margin-top:8px;margin-left:0}.btn-large .caret{margin-top:6px}.btn-large .caret{border-top-width:5px;border-right-width:5px;border-left-width:5px}.btn-mini .caret,.btn-small .caret{margin-top:8px}.dropup .btn-large .caret{border-bottom-width:5px}.btn-primary .caret,.btn-warning .caret,.btn-danger .caret,.btn-info .caret,.btn-success .caret,.btn-inverse .caret{border-top-color:#fff;border-bottom-color:#fff}.btn-group-vertical{display:inline-block;*display:inline;*zoom:1}.btn-group-vertical>.btn{display:block;float:none;max-width:100%;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group-vertical>.btn+.btn{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:first-child{-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.btn-group-vertical>.btn:last-child{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.btn-group-vertical>.btn-large:first-child{-webkit-border-radius:6px 6px 0 0;-moz-border-radius:6px 6px 0 0;border-radius:6px 6px 0 0}.btn-group-vertical>.btn-large:last-child{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}.alert{padding:8px 35px 8px 14px;margin-bottom:20px;text-shadow:0 1px 0 rgba(255,255,255,0.5);background-color:#fcf8e3;border:1px solid #fbeed5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.alert,.alert h4{color:#c09853}.alert h4{margin:0}.alert .close{position:relative;top:-2px;right:-21px;line-height:20px}.alert-success{color:#468847;background-color:#dff0d8;border-color:#d6e9c6}.alert-success h4{color:#468847}.alert-danger,.alert-error{color:#b94a48;background-color:#f2dede;border-color:#eed3d7}.alert-danger h4,.alert-error h4{color:#b94a48}.alert-info{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1}.alert-info h4{color:#3a87ad}.alert-block{padding-top:14px;padding-bottom:14px}.alert-block>p,.alert-block>ul{margin-bottom:0}.alert-block p+p{margin-top:5px}.nav{margin-bottom:20px;margin-left:0;list-style:none}.nav>li>a{display:block}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eee}.nav>li>a>img{max-width:none}.nav>.pull-right{float:right}.nav-header{display:block;padding:3px 15px;font-size:11px;font-weight:bold;line-height:20px;color:#999;text-shadow:0 1px 0 rgba(255,255,255,0.5);text-transform:uppercase}.nav li+.nav-header{margin-top:9px}.nav-list{padding-right:15px;padding-left:15px;margin-bottom:0}.nav-list>li>a,.nav-list .nav-header{margin-right:-15px;margin-left:-15px;text-shadow:0 1px 0 rgba(255,255,255,0.5)}.nav-list>li>a{padding:3px 15px}.nav-list>.active>a,.nav-list>.active>a:hover,.nav-list>.active>a:focus{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.2);background-color:#08c}.nav-list [class^=\"icon-\"],.nav-list [class*=\" icon-\"]{margin-right:2px}.nav-list .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.nav-tabs,.nav-pills{*zoom:1}.nav-tabs:before,.nav-pills:before,.nav-tabs:after,.nav-pills:after{display:table;line-height:0;content:\"\"}.nav-tabs:after,.nav-pills:after{clear:both}.nav-tabs>li,.nav-pills>li{float:left}.nav-tabs>li>a,.nav-pills>li>a{padding-right:12px;padding-left:12px;margin-right:2px;line-height:14px}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{margin-bottom:-1px}.nav-tabs>li>a{padding-top:8px;padding-bottom:8px;line-height:20px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover,.nav-tabs>li>a:focus{border-color:#eee #eee #ddd}.nav-tabs>.active>a,.nav-tabs>.active>a:hover,.nav-tabs>.active>a:focus{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-pills>li>a{padding-top:8px;padding-bottom:8px;margin-top:2px;margin-bottom:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.nav-pills>.active>a,.nav-pills>.active>a:hover,.nav-pills>.active>a:focus{color:#fff;background-color:#08c}.nav-stacked>li{float:none}.nav-stacked>li>a{margin-right:0}.nav-tabs.nav-stacked{border-bottom:0}.nav-tabs.nav-stacked>li>a{border:1px solid #ddd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.nav-tabs.nav-stacked>li:first-child>a{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-topleft:4px}.nav-tabs.nav-stacked>li:last-child>a{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomright:4px;-moz-border-radius-bottomleft:4px}.nav-tabs.nav-stacked>li>a:hover,.nav-tabs.nav-stacked>li>a:focus{z-index:2;border-color:#ddd}.nav-pills.nav-stacked>li>a{margin-bottom:3px}.nav-pills.nav-stacked>li:last-child>a{margin-bottom:1px}.nav-tabs .dropdown-menu{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}.nav-pills .dropdown-menu{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.nav .dropdown-toggle .caret{margin-top:6px;border-top-color:#08c;border-bottom-color:#08c}.nav .dropdown-toggle:hover .caret,.nav .dropdown-toggle:focus .caret{border-top-color:#005580;border-bottom-color:#005580}.nav-tabs .dropdown-toggle .caret{margin-top:8px}.nav .active .dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.nav-tabs .active .dropdown-toggle .caret{border-top-color:#555;border-bottom-color:#555}.nav>.dropdown.active>a:hover,.nav>.dropdown.active>a:focus{cursor:pointer}.nav-tabs .open .dropdown-toggle,.nav-pills .open .dropdown-toggle,.nav>li.dropdown.open.active>a:hover,.nav>li.dropdown.open.active>a:focus{color:#fff;background-color:#999;border-color:#999}.nav li.dropdown.open .caret,.nav li.dropdown.open.active .caret,.nav li.dropdown.open a:hover .caret,.nav li.dropdown.open a:focus .caret{border-top-color:#fff;border-bottom-color:#fff;opacity:1;filter:alpha(opacity=100)}.tabs-stacked .open>a:hover,.tabs-stacked .open>a:focus{border-color:#999}.tabbable{*zoom:1}.tabbable:before,.tabbable:after{display:table;line-height:0;content:\"\"}.tabbable:after{clear:both}.tab-content{overflow:auto}.tabs-below>.nav-tabs,.tabs-right>.nav-tabs,.tabs-left>.nav-tabs{border-bottom:0}.tab-content>.tab-pane,.pill-content>.pill-pane{display:none}.tab-content>.active,.pill-content>.active{display:block}.tabs-below>.nav-tabs{border-top:1px solid #ddd}.tabs-below>.nav-tabs>li{margin-top:-1px;margin-bottom:0}.tabs-below>.nav-tabs>li>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.tabs-below>.nav-tabs>li>a:hover,.tabs-below>.nav-tabs>li>a:focus{border-top-color:#ddd;border-bottom-color:transparent}.tabs-below>.nav-tabs>.active>a,.tabs-below>.nav-tabs>.active>a:hover,.tabs-below>.nav-tabs>.active>a:focus{border-color:transparent #ddd #ddd #ddd}.tabs-left>.nav-tabs>li,.tabs-right>.nav-tabs>li{float:none}.tabs-left>.nav-tabs>li>a,.tabs-right>.nav-tabs>li>a{min-width:74px;margin-right:0;margin-bottom:3px}.tabs-left>.nav-tabs{float:left;margin-right:19px;border-right:1px solid #ddd}.tabs-left>.nav-tabs>li>a{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.tabs-left>.nav-tabs>li>a:hover,.tabs-left>.nav-tabs>li>a:focus{border-color:#eee #ddd #eee #eee}.tabs-left>.nav-tabs .active>a,.tabs-left>.nav-tabs .active>a:hover,.tabs-left>.nav-tabs .active>a:focus{border-color:#ddd transparent #ddd #ddd;*border-right-color:#fff}.tabs-right>.nav-tabs{float:right;margin-left:19px;border-left:1px solid #ddd}.tabs-right>.nav-tabs>li>a{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.tabs-right>.nav-tabs>li>a:hover,.tabs-right>.nav-tabs>li>a:focus{border-color:#eee #eee #eee #ddd}.tabs-right>.nav-tabs .active>a,.tabs-right>.nav-tabs .active>a:hover,.tabs-right>.nav-tabs .active>a:focus{border-color:#ddd #ddd #ddd transparent;*border-left-color:#fff}.nav>.disabled>a{color:#999}.nav>.disabled>a:hover,.nav>.disabled>a:focus{text-decoration:none;cursor:default;background-color:transparent}.navbar{*position:relative;*z-index:2;margin-bottom:20px;overflow:visible}.navbar-inner{min-height:40px;padding-right:20px;padding-left:20px;background-color:#fafafa;background-image:-moz-linear-gradient(top,#fff,#f2f2f2);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#f2f2f2));background-image:-webkit-linear-gradient(top,#fff,#f2f2f2);background-image:-o-linear-gradient(top,#fff,#f2f2f2);background-image:linear-gradient(to bottom,#fff,#f2f2f2);background-repeat:repeat-x;border:1px solid #d4d4d4;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#fff2f2f2',GradientType=0);*zoom:1;-webkit-box-shadow:0 1px 4px rgba(0,0,0,0.065);-moz-box-shadow:0 1px 4px rgba(0,0,0,0.065);box-shadow:0 1px 4px rgba(0,0,0,0.065)}.navbar-inner:before,.navbar-inner:after{display:table;line-height:0;content:\"\"}.navbar-inner:after{clear:both}.navbar .container{width:auto}.nav-collapse.collapse{height:auto;overflow:visible}.navbar .brand{display:block;float:left;padding:10px 20px 10px;margin-left:-20px;font-size:20px;font-weight:200;color:#777;text-shadow:0 1px 0 #fff}.navbar .brand:hover,.navbar .brand:focus{text-decoration:none}.navbar-text{margin-bottom:0;line-height:40px;color:#777}.navbar-link{color:#777}.navbar-link:hover,.navbar-link:focus{color:#333}.navbar .divider-vertical{height:40px;margin:0 9px;border-right:1px solid #fff;border-left:1px solid #f2f2f2}.navbar .btn,.navbar .btn-group{margin-top:5px}.navbar .btn-group .btn,.navbar .input-prepend .btn,.navbar .input-append .btn,.navbar .input-prepend .btn-group,.navbar .input-append .btn-group{margin-top:0}.navbar-form{margin-bottom:0;*zoom:1}.navbar-form:before,.navbar-form:after{display:table;line-height:0;content:\"\"}.navbar-form:after{clear:both}.navbar-form input,.navbar-form select,.navbar-form .radio,.navbar-form .checkbox{margin-top:5px}.navbar-form input,.navbar-form select,.navbar-form .btn{display:inline-block;margin-bottom:0}.navbar-form input[type=\"image\"],.navbar-form input[type=\"checkbox\"],.navbar-form input[type=\"radio\"]{margin-top:3px}.navbar-form .input-append,.navbar-form .input-prepend{margin-top:5px;white-space:nowrap}.navbar-form .input-append input,.navbar-form .input-prepend input{margin-top:0}.navbar-search{position:relative;float:left;margin-top:5px;margin-bottom:0}.navbar-search .search-query{padding:4px 14px;margin-bottom:0;font-family:\"Helvetica Neue\",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:1;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.navbar-static-top{position:static;margin-bottom:0}.navbar-static-top .navbar-inner{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;margin-bottom:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{border-width:0 0 1px}.navbar-fixed-bottom .navbar-inner{border-width:1px 0 0}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding-right:0;padding-left:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.navbar-fixed-top{top:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{-webkit-box-shadow:0 1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 10px rgba(0,0,0,0.1);box-shadow:0 1px 10px rgba(0,0,0,0.1)}.navbar-fixed-bottom{bottom:0}.navbar-fixed-bottom .navbar-inner{-webkit-box-shadow:0 -1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 -1px 10px rgba(0,0,0,0.1);box-shadow:0 -1px 10px rgba(0,0,0,0.1)}.navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0}.navbar .nav.pull-right{float:right;margin-right:0}.navbar .nav>li{float:left}.navbar .nav>li>a{float:none;padding:10px 15px 10px;color:#777;text-decoration:none;text-shadow:0 1px 0 #fff}.navbar .nav .dropdown-toggle .caret{margin-top:8px}.navbar .nav>li>a:focus,.navbar .nav>li>a:hover{color:#333;text-decoration:none;background-color:transparent}.navbar .nav>.active>a,.navbar .nav>.active>a:hover,.navbar .nav>.active>a:focus{color:#555;text-decoration:none;background-color:#e5e5e5;-webkit-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);-moz-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);box-shadow:inset 0 3px 8px rgba(0,0,0,0.125)}.navbar .btn-navbar{display:none;float:right;padding:7px 10px;margin-right:5px;margin-left:5px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#ededed;*background-color:#e5e5e5;background-image:-moz-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f2f2f2),to(#e5e5e5));background-image:-webkit-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:-o-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:linear-gradient(to bottom,#f2f2f2,#e5e5e5);background-repeat:repeat-x;border-color:#e5e5e5 #e5e5e5 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2',endColorstr='#ffe5e5e5',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075)}.navbar .btn-navbar:hover,.navbar .btn-navbar:focus,.navbar .btn-navbar:active,.navbar .btn-navbar.active,.navbar .btn-navbar.disabled,.navbar .btn-navbar[disabled]{color:#fff;background-color:#e5e5e5;*background-color:#d9d9d9}.navbar .btn-navbar:active,.navbar .btn-navbar.active{background-color:#ccc \\9}.navbar .btn-navbar .icon-bar{display:block;width:18px;height:2px;background-color:#f5f5f5;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,0.25);-moz-box-shadow:0 1px 0 rgba(0,0,0,0.25);box-shadow:0 1px 0 rgba(0,0,0,0.25)}.btn-navbar .icon-bar+.icon-bar{margin-top:3px}.navbar .nav>li>.dropdown-menu:before{position:absolute;top:-7px;left:9px;display:inline-block;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-left:7px solid transparent;border-bottom-color:rgba(0,0,0,0.2);content:''}.navbar .nav>li>.dropdown-menu:after{position:absolute;top:-6px;left:10px;display:inline-block;border-right:6px solid transparent;border-bottom:6px solid #fff;border-left:6px solid transparent;content:''}.navbar-fixed-bottom .nav>li>.dropdown-menu:before{top:auto;bottom:-7px;border-top:7px solid #ccc;border-bottom:0;border-top-color:rgba(0,0,0,0.2)}.navbar-fixed-bottom .nav>li>.dropdown-menu:after{top:auto;bottom:-6px;border-top:6px solid #fff;border-bottom:0}.navbar .nav li.dropdown>a:hover .caret,.navbar .nav li.dropdown>a:focus .caret{border-top-color:#333;border-bottom-color:#333}.navbar .nav li.dropdown.open>.dropdown-toggle,.navbar .nav li.dropdown.active>.dropdown-toggle,.navbar .nav li.dropdown.open.active>.dropdown-toggle{color:#555;background-color:#e5e5e5}.navbar .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#777;border-bottom-color:#777}.navbar .nav li.dropdown.open>.dropdown-toggle .caret,.navbar .nav li.dropdown.active>.dropdown-toggle .caret,.navbar .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#555;border-bottom-color:#555}.navbar .pull-right>li>.dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right{right:0;left:auto}.navbar .pull-right>li>.dropdown-menu:before,.navbar .nav>li>.dropdown-menu.pull-right:before{right:12px;left:auto}.navbar .pull-right>li>.dropdown-menu:after,.navbar .nav>li>.dropdown-menu.pull-right:after{right:13px;left:auto}.navbar .pull-right>li>.dropdown-menu .dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right .dropdown-menu{right:100%;left:auto;margin-right:-1px;margin-left:0;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.navbar-inverse .navbar-inner{background-color:#1b1b1b;background-image:-moz-linear-gradient(top,#222,#111);background-image:-webkit-gradient(linear,0 0,0 100%,from(#222),to(#111));background-image:-webkit-linear-gradient(top,#222,#111);background-image:-o-linear-gradient(top,#222,#111);background-image:linear-gradient(to bottom,#222,#111);background-repeat:repeat-x;border-color:#252525;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222',endColorstr='#ff111111',GradientType=0)}.navbar-inverse .brand,.navbar-inverse .nav>li>a{color:#999;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.navbar-inverse .brand:hover,.navbar-inverse .nav>li>a:hover,.navbar-inverse .brand:focus,.navbar-inverse .nav>li>a:focus{color:#fff}.navbar-inverse .brand{color:#999}.navbar-inverse .navbar-text{color:#999}.navbar-inverse .nav>li>a:focus,.navbar-inverse .nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .nav .active>a,.navbar-inverse .nav .active>a:hover,.navbar-inverse .nav .active>a:focus{color:#fff;background-color:#111}.navbar-inverse .navbar-link{color:#999}.navbar-inverse .navbar-link:hover,.navbar-inverse .navbar-link:focus{color:#fff}.navbar-inverse .divider-vertical{border-right-color:#222;border-left-color:#111}.navbar-inverse .nav li.dropdown.open>.dropdown-toggle,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle{color:#fff;background-color:#111}.navbar-inverse .nav li.dropdown>a:hover .caret,.navbar-inverse .nav li.dropdown>a:focus .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#999;border-bottom-color:#999}.navbar-inverse .nav li.dropdown.open>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .navbar-search .search-query{color:#fff;background-color:#515151;border-color:#111;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);-webkit-transition:none;-moz-transition:none;-o-transition:none;transition:none}.navbar-inverse .navbar-search .search-query:-moz-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query:-ms-input-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query::-webkit-input-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query:focus,.navbar-inverse .navbar-search .search-query.focused{padding:5px 15px;color:#333;text-shadow:0 1px 0 #fff;background-color:#fff;border:0;outline:0;-webkit-box-shadow:0 0 3px rgba(0,0,0,0.15);-moz-box-shadow:0 0 3px rgba(0,0,0,0.15);box-shadow:0 0 3px rgba(0,0,0,0.15)}.navbar-inverse .btn-navbar{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e0e0e;*background-color:#040404;background-image:-moz-linear-gradient(top,#151515,#040404);background-image:-webkit-gradient(linear,0 0,0 100%,from(#151515),to(#040404));background-image:-webkit-linear-gradient(top,#151515,#040404);background-image:-o-linear-gradient(top,#151515,#040404);background-image:linear-gradient(to bottom,#151515,#040404);background-repeat:repeat-x;border-color:#040404 #040404 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff151515',endColorstr='#ff040404',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.navbar-inverse .btn-navbar:hover,.navbar-inverse .btn-navbar:focus,.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active,.navbar-inverse .btn-navbar.disabled,.navbar-inverse .btn-navbar[disabled]{color:#fff;background-color:#040404;*background-color:#000}.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active{background-color:#000 \\9}.breadcrumb{padding:8px 15px;margin:0 0 20px;list-style:none;background-color:#f5f5f5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.breadcrumb>li{display:inline-block;*display:inline;text-shadow:0 1px 0 #fff;*zoom:1}.breadcrumb>li>.divider{padding:0 5px;color:#ccc}.breadcrumb>.active{color:#999}.pagination{margin:20px 0}.pagination ul{display:inline-block;*display:inline;margin-bottom:0;margin-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;*zoom:1;-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:0 1px 2px rgba(0,0,0,0.05);box-shadow:0 1px 2px rgba(0,0,0,0.05)}.pagination ul>li{display:inline}.pagination ul>li>a,.pagination ul>li>span{float:left;padding:4px 12px;line-height:20px;text-decoration:none;background-color:#fff;border:1px solid #ddd;border-left-width:0}.pagination ul>li>a:hover,.pagination ul>li>a:focus,.pagination ul>.active>a,.pagination ul>.active>span{background-color:#f5f5f5}.pagination ul>.active>a,.pagination ul>.active>span{color:#999;cursor:default}.pagination ul>.disabled>span,.pagination ul>.disabled>a,.pagination ul>.disabled>a:hover,.pagination ul>.disabled>a:focus{color:#999;cursor:default;background-color:transparent}.pagination ul>li:first-child>a,.pagination ul>li:first-child>span{border-left-width:1px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-bottomleft:4px;-moz-border-radius-topleft:4px}.pagination ul>li:last-child>a,.pagination ul>li:last-child>span{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-bottomright:4px}.pagination-centered{text-align:center}.pagination-right{text-align:right}.pagination-large ul>li>a,.pagination-large ul>li>span{padding:11px 19px;font-size:17.5px}.pagination-large ul>li:first-child>a,.pagination-large ul>li:first-child>span{-webkit-border-bottom-left-radius:6px;border-bottom-left-radius:6px;-webkit-border-top-left-radius:6px;border-top-left-radius:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topleft:6px}.pagination-large ul>li:last-child>a,.pagination-large ul>li:last-child>span{-webkit-border-top-right-radius:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;border-bottom-right-radius:6px;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px}.pagination-mini ul>li:first-child>a,.pagination-small ul>li:first-child>a,.pagination-mini ul>li:first-child>span,.pagination-small ul>li:first-child>span{-webkit-border-bottom-left-radius:3px;border-bottom-left-radius:3px;-webkit-border-top-left-radius:3px;border-top-left-radius:3px;-moz-border-radius-bottomleft:3px;-moz-border-radius-topleft:3px}.pagination-mini ul>li:last-child>a,.pagination-small ul>li:last-child>a,.pagination-mini ul>li:last-child>span,.pagination-small ul>li:last-child>span{-webkit-border-top-right-radius:3px;border-top-right-radius:3px;-webkit-border-bottom-right-radius:3px;border-bottom-right-radius:3px;-moz-border-radius-topright:3px;-moz-border-radius-bottomright:3px}.pagination-small ul>li>a,.pagination-small ul>li>span{padding:2px 10px;font-size:11.9px}.pagination-mini ul>li>a,.pagination-mini ul>li>span{padding:0 6px;font-size:10.5px}.pager{margin:20px 0;text-align:center;list-style:none;*zoom:1}.pager:before,.pager:after{display:table;line-height:0;content:\"\"}.pager:after{clear:both}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#f5f5f5}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#999;cursor:default;background-color:#fff}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop,.modal-backdrop.fade.in{opacity:.8;filter:alpha(opacity=80)}.modal{position:fixed;top:10%;left:50%;z-index:1050;width:560px;margin-left:-280px;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;outline:0;-webkit-box-shadow:0 3px 7px rgba(0,0,0,0.3);-moz-box-shadow:0 3px 7px rgba(0,0,0,0.3);box-shadow:0 3px 7px rgba(0,0,0,0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box}.modal.fade{top:-25%;-webkit-transition:opacity .3s linear,top .3s ease-out;-moz-transition:opacity .3s linear,top .3s ease-out;-o-transition:opacity .3s linear,top .3s ease-out;transition:opacity .3s linear,top .3s ease-out}.modal.fade.in{top:10%}.modal-header{padding:9px 15px;border-bottom:1px solid #eee}.modal-header .close{margin-top:2px}.modal-header h3{margin:0;line-height:30px}.modal-body{position:relative;max-height:400px;padding:15px;overflow-y:auto}.modal-form{margin-bottom:0}.modal-footer{padding:14px 15px 15px;margin-bottom:0;text-align:right;background-color:#f5f5f5;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;*zoom:1;-webkit-box-shadow:inset 0 1px 0 #fff;-moz-box-shadow:inset 0 1px 0 #fff;box-shadow:inset 0 1px 0 #fff}.modal-footer:before,.modal-footer:after{display:table;line-height:0;content:\"\"}.modal-footer:after{clear:both}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.tooltip{position:absolute;z-index:1030;display:block;font-size:11px;line-height:1.4;opacity:0;filter:alpha(opacity=0);visibility:visible}.tooltip.in{opacity:.8;filter:alpha(opacity=80)}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-top-color:#000;border-width:5px 5px 0}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-right-color:#000;border-width:5px 5px 5px 0}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-left-color:#000;border-width:5px 0 5px 5px}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-bottom-color:#000;border-width:0 5px 5px}.popover{position:absolute;top:0;left:0;z-index:1010;display:none;max-width:276px;padding:1px;text-align:left;white-space:normal;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;font-weight:normal;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.popover-title:empty{display:none}.popover-content{padding:9px 14px}.popover .arrow,.popover .arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover .arrow{border-width:11px}.popover .arrow:after{border-width:10px;content:\"\"}.popover.top .arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,0.25);border-bottom-width:0}.popover.top .arrow:after{bottom:1px;margin-left:-10px;border-top-color:#fff;border-bottom-width:0}.popover.right .arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,0.25);border-left-width:0}.popover.right .arrow:after{bottom:-10px;left:1px;border-right-color:#fff;border-left-width:0}.popover.bottom .arrow{top:-11px;left:50%;margin-left:-11px;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,0.25);border-top-width:0}.popover.bottom .arrow:after{top:1px;margin-left:-10px;border-bottom-color:#fff;border-top-width:0}.popover.left .arrow{top:50%;right:-11px;margin-top:-11px;border-left-color:#999;border-left-color:rgba(0,0,0,0.25);border-right-width:0}.popover.left .arrow:after{right:1px;bottom:-10px;border-left-color:#fff;border-right-width:0}.thumbnails{margin-left:-20px;list-style:none;*zoom:1}.thumbnails:before,.thumbnails:after{display:table;line-height:0;content:\"\"}.thumbnails:after{clear:both}.row-fluid .thumbnails{margin-left:0}.thumbnails>li{float:left;margin-bottom:20px;margin-left:20px}.thumbnail{display:block;padding:4px;line-height:20px;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.055);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.055);box-shadow:0 1px 3px rgba(0,0,0,0.055);-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}a.thumbnail:hover,a.thumbnail:focus{border-color:#08c;-webkit-box-shadow:0 1px 4px rgba(0,105,214,0.25);-moz-box-shadow:0 1px 4px rgba(0,105,214,0.25);box-shadow:0 1px 4px rgba(0,105,214,0.25)}.thumbnail>img{display:block;max-width:100%;margin-right:auto;margin-left:auto}.thumbnail .caption{padding:9px;color:#555}.media,.media-body{overflow:hidden;*overflow:visible;zoom:1}.media,.media .media{margin-top:15px}.media:first-child{margin-top:0}.media-object{display:block}.media-heading{margin:0 0 5px}.media>.pull-left{margin-right:10px}.media>.pull-right{margin-left:10px}.media-list{margin-left:0;list-style:none}.label,.badge{display:inline-block;padding:2px 4px;font-size:11.844px;font-weight:bold;line-height:14px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);white-space:nowrap;vertical-align:baseline;background-color:#999}.label{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.badge{padding-right:9px;padding-left:9px;-webkit-border-radius:9px;-moz-border-radius:9px;border-radius:9px}.label:empty,.badge:empty{display:none}a.label:hover,a.label:focus,a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}.label-important,.badge-important{background-color:#b94a48}.label-important[href],.badge-important[href]{background-color:#953b39}.label-warning,.badge-warning{background-color:#f89406}.label-warning[href],.badge-warning[href]{background-color:#c67605}.label-success,.badge-success{background-color:#468847}.label-success[href],.badge-success[href]{background-color:#356635}.label-info,.badge-info{background-color:#3a87ad}.label-info[href],.badge-info[href]{background-color:#2d6987}.label-inverse,.badge-inverse{background-color:#333}.label-inverse[href],.badge-inverse[href]{background-color:#1a1a1a}.btn .label,.btn .badge{position:relative;top:-1px}.btn-mini .label,.btn-mini .badge{top:0}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-moz-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-ms-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:0 0}to{background-position:40px 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f7f7f7;background-image:-moz-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f5f5f5),to(#f9f9f9));background-image:-webkit-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-o-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:linear-gradient(to bottom,#f5f5f5,#f9f9f9);background-repeat:repeat-x;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#fff9f9f9',GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress .bar{float:left;width:0;height:100%;font-size:12px;color:#fff;text-align:center;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e90d2;background-image:-moz-linear-gradient(top,#149bdf,#0480be);background-image:-webkit-gradient(linear,0 0,0 100%,from(#149bdf),to(#0480be));background-image:-webkit-linear-gradient(top,#149bdf,#0480be);background-image:-o-linear-gradient(top,#149bdf,#0480be);background-image:linear-gradient(to bottom,#149bdf,#0480be);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf',endColorstr='#ff0480be',GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-transition:width .6s ease;-moz-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress .bar+.bar{-webkit-box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15)}.progress-striped .bar{background-color:#149bdf;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;-moz-background-size:40px 40px;-o-background-size:40px 40px;background-size:40px 40px}.progress.active .bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;-ms-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-danger .bar,.progress .bar-danger{background-color:#dd514c;background-image:-moz-linear-gradient(top,#ee5f5b,#c43c35);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#c43c35));background-image:-webkit-linear-gradient(top,#ee5f5b,#c43c35);background-image:-o-linear-gradient(top,#ee5f5b,#c43c35);background-image:linear-gradient(to bottom,#ee5f5b,#c43c35);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b',endColorstr='#ffc43c35',GradientType=0)}.progress-danger.progress-striped .bar,.progress-striped .bar-danger{background-color:#ee5f5b;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-success .bar,.progress .bar-success{background-color:#5eb95e;background-image:-moz-linear-gradient(top,#62c462,#57a957);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#57a957));background-image:-webkit-linear-gradient(top,#62c462,#57a957);background-image:-o-linear-gradient(top,#62c462,#57a957);background-image:linear-gradient(to bottom,#62c462,#57a957);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462',endColorstr='#ff57a957',GradientType=0)}.progress-success.progress-striped .bar,.progress-striped .bar-success{background-color:#62c462;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-info .bar,.progress .bar-info{background-color:#4bb1cf;background-image:-moz-linear-gradient(top,#5bc0de,#339bb9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#339bb9));background-image:-webkit-linear-gradient(top,#5bc0de,#339bb9);background-image:-o-linear-gradient(top,#5bc0de,#339bb9);background-image:linear-gradient(to bottom,#5bc0de,#339bb9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff339bb9',GradientType=0)}.progress-info.progress-striped .bar,.progress-striped .bar-info{background-color:#5bc0de;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-warning .bar,.progress .bar-warning{background-color:#faa732;background-image:-moz-linear-gradient(top,#fbb450,#f89406);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fbb450),to(#f89406));background-image:-webkit-linear-gradient(top,#fbb450,#f89406);background-image:-o-linear-gradient(top,#fbb450,#f89406);background-image:linear-gradient(to bottom,#fbb450,#f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450',endColorstr='#fff89406',GradientType=0)}.progress-warning.progress-striped .bar,.progress-striped .bar-warning{background-color:#fbb450;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.accordion{margin-bottom:20px}.accordion-group{margin-bottom:2px;border:1px solid #e5e5e5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.accordion-heading{border-bottom:0}.accordion-heading .accordion-toggle{display:block;padding:8px 15px}.accordion-toggle{cursor:pointer}.accordion-inner{padding:9px 15px;border-top:1px solid #e5e5e5}.carousel{position:relative;margin-bottom:20px;line-height:1}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-moz-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;line-height:1}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:40%;left:15px;width:40px;height:40px;margin-top:-20px;font-size:60px;font-weight:100;line-height:30px;color:#fff;text-align:center;background:#222;border:3px solid #fff;-webkit-border-radius:23px;-moz-border-radius:23px;border-radius:23px;opacity:.5;filter:alpha(opacity=50)}.carousel-control.right{right:15px;left:auto}.carousel-control:hover,.carousel-control:focus{color:#fff;text-decoration:none;opacity:.9;filter:alpha(opacity=90)}.carousel-indicators{position:absolute;top:15px;right:15px;z-index:5;margin:0;list-style:none}.carousel-indicators li{display:block;float:left;width:10px;height:10px;margin-left:5px;text-indent:-999px;background-color:#ccc;background-color:rgba(255,255,255,0.25);border-radius:5px}.carousel-indicators .active{background-color:#fff}.carousel-caption{position:absolute;right:0;bottom:0;left:0;padding:15px;background:#333;background:rgba(0,0,0,0.75)}.carousel-caption h4,.carousel-caption p{line-height:20px;color:#fff}.carousel-caption h4{margin:0 0 5px}.carousel-caption p{margin-bottom:0}.hero-unit{padding:60px;margin-bottom:30px;font-size:18px;font-weight:200;line-height:30px;color:inherit;background-color:#eee;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;letter-spacing:-1px;color:inherit}.hero-unit li{line-height:30px}.pull-right{float:right}.pull-left{float:left}.hide{display:none}.show{display:block}.invisible{visibility:hidden}.affix{position:fixed}\n\n    /* Pretty printing styles. Used with prettify.js. */\n/* Vim sunburst theme by David Leibovic */\npre .str {\n  color: #65B042;\n}\n/* string  - green */\npre .kwd {\n  color: #E28964;\n}\n/* keyword - dark pink */\npre .com {\n  color: #AEAEAE;\n  font-style: italic;\n}\n/* comment - gray */\npre .typ {\n  color: #89bdff;\n}\n/* type - light blue */\npre .lit {\n  color: #3387CC;\n}\n/* literal - blue */\npre .pun {\n  color: #fff;\n}\n/* punctuation - white */\npre .pln {\n  color: #fff;\n}\n/* plaintext - white */\npre .tag {\n  color: #89bdff;\n}\n/* html/xml tag    - light blue */\npre .atn {\n  color: #bdb76b;\n}\n/* html/xml attribute name  - khaki */\npre .atv {\n  color: #65B042;\n}\n/* html/xml attribute value - green */\npre .dec {\n  color: #3387CC;\n}\n/* decimal - blue */\n/* Specify class=linenums on a pre to get line numbering */\nol.linenums {\n  margin-top: 0;\n  margin-bottom: 0;\n  color: #AEAEAE;\n}\n/* IE indents via margin-left */\nli.L0,\nli.L1,\nli.L2,\nli.L3,\nli.L5,\nli.L6,\nli.L7,\nli.L8 {\n  list-style-type: none;\n}\n/* Alternate shading for lines */\n@media print {\n  pre .str {\n    color: #060;\n  }\n  pre .kwd {\n    color: #006;\n    font-weight: bold;\n  }\n  pre .com {\n    color: #600;\n    font-style: italic;\n  }\n  pre .typ {\n    color: #404;\n    font-weight: bold;\n  }\n  pre .lit {\n    color: #044;\n  }\n  pre .pun {\n    color: #440;\n  }\n  pre .pln {\n    color: #000;\n  }\n  pre .tag {\n    color: #006;\n    font-weight: bold;\n  }\n  pre .atn {\n    color: #404;\n  }\n  pre .atv {\n    color: #060;\n  }\n}\n\n    /* ------------------------------------------------------------------------------------------\n * Content\n * ------------------------------------------------------------------------------------------ */\n\n\n* {\n  font-family: 'Source Code Pro', sans-serif;\n}\nbody {\n  min-width: 980px;\n}\n\n.app-desc {\n  color: #808080\n}\n\nbody, p, a, div, th, td, li {\n  font-family: \"Source Sans Pro\", sans-serif;\n  font-weight: 400;\n  font-size: 16px;\n  text-shadow: none !important;\n}\n\ntd.code {\n  font-size: 14px;\n  font-family: \"Source Code Pro\", monospace;\n  font-style: normal;\n  font-weight: 400;\n}\n\n#content {\n  padding-top: 16px;\n  z-Index: -1;\n  margin-left: 270px;\n}\n\np {\n  color: #808080;\n}\n\nh1 {\n  font-family: \"Source Sans Pro Semibold\", sans-serif;\n  font-weight: normal;\n  font-size: 44px;\n  line-height: 50px;\n  margin: 0 0 10px 0;\n  padding: 0;\n}\n\nh2 {\n  font-family: \"Source Sans Pro\", sans-serif;\n  font-weight: normal;\n  font-size: 24px;\n  line-height: 40px;\n  margin: 0 0 20px 0;\n  padding: 0;\n}\n\nsection {\n  border-top: 1px solid #ebebeb;\n  padding: 30px 0;\n}\n\nsection h1 {\n  font-family: \"Source Sans Pro\", sans-serif;\n  font-weight: 700;\n  font-size: 32px;\n  line-height: 40px;\n  padding-bottom: 14px;\n  margin: 0 0 20px 0;\n  padding: 0;\n}\n\narticle {\n  padding: 14px 0 30px 0;\n}\n\narticle h1 {\n  font-family: \"Source Sans Pro Bold\", sans-serif;\n  font-weight: 600;\n  font-size: 24px;\n  line-height: 26px;\n}\n\narticle h2 {\n  font-family: \"Source Sans Pro\", sans-serif;\n  font-weight: 600;\n  font-size: 18px;\n  line-height: 24px;\n  margin: 0 0 10px 0;\n}\n\narticle h3 {\n  font-family: \"Source Sans Pro\", sans-serif;\n  font-weight: 600;\n  font-size: 16px;\n  line-height: 18px;\n  margin: 0 0 10px 0;\n}\n\narticle h4 {\n  font-family: \"Source Sans Pro\", sans-serif;\n  font-weight: 600;\n  font-size: 14px;\n  line-height: 16px;\n  margin: 0 0 8px 0;\n}\n\ntable {\n  border-collapse: collapse;\n  width: 100%;\n  margin: 0 0 20px 0;\n}\n\nth {\n  background-color: #f5f5f5;\n  text-align: left;\n  font-family: \"Source Sans Pro\", sans-serif;\n  font-weight: 700;\n  padding: 4px 8px;\n  border: #e0e0e0 1px solid;\n}\n\ntd {\n  vertical-align: top;\n  padding: 2px 8px;\n  border: #e0e0e0 1px solid;\n}\n\n#generator .content {\n  color: #b0b0b0;\n  border-top: 1px solid #ebebeb;\n  padding: 10px 0;\n}\n\n.label-optional {\n  float: right;\n}\n\n.open-left {\n  right: 0;\n  left: auto;\n}\n\n/* ------------------------------------------------------------------------------------------\n * apidoc - intro\n * ------------------------------------------------------------------------------------------ */\n\n#apidoc .apidoc {\n  border-top: 1px solid #ebebeb;\n  padding: 30px 0;\n}\n\n#apidoc h1 {\n  font-family: \"Source Sans Pro\", sans-serif;\n  font-weight: 700;\n  font-size: 32px;\n  line-height: 40px;\n  padding-bottom: 14px;\n  margin: 0 0 20px 0;\n  padding: 0;\n}\n\n#apidoc h2 {\n  font-family: \"Source Sans Pro Bold\", sans-serif;\n  font-weight: 600;\n  font-size: 22px;\n  line-height: 26px;\n  padding-top: 14px;\n}\n\n/* ------------------------------------------------------------------------------------------\n * pre / code\n * ------------------------------------------------------------------------------------------ */\npre {\n  background-color: #292b36;\n  color: #ffffff;\n  padding: 10px;\n  border-radius: 6px;\n  position: relative;\n  margin: 10px 0 20px 0;\n}\n\ncode.language-text {\n  word-wrap: break-word;\n}\n\npre.language-json {\n  overflow: auto;\n}\n\npre.language-html {\n  margin: 40px 0 20px 0;\n}\n\npre.language-html:before {\n  content: attr(data-type);\n  position: absolute;\n  top: -30px;\n  left: 0;\n  font-family: \"Source Sans Pro\", sans-serif;\n  font-weight: 600;\n  font-size: 15px;\n  display: inline-block;\n  padding: 2px 5px;\n  border-radius: 6px;\n  text-transform: uppercase;\n  background-color: #3387CC;\n  color: #ffffff;\n}\n\npre.language-html[data-type=\"get\"]:before {\n  background-color: green;\n}\n\npre.language-html[data-type=\"put\"]:before {\n  background-color: #e5c500;\n}\n\npre.language-html[data-type=\"post\"]:before {\n  background-color: #4070ec;\n}\n\npre.language-html[data-type=\"delete\"]:before {\n  background-color: #ed0039;\n}\n\npre.language-api .str {\n  color: #ffffff;\n}\n\npre.language-api .pln,\npre.language-api .pun {\n  color: #65B042;\n}\n\npre code {\n  display: block;\n  font-size: 14px;\n  font-family: \"Source Code Pro\", monospace;\n  font-style: normal;\n  font-weight: 400;\n}\n\npre code.sample-request-response-json {\n  white-space: pre-wrap;\n  max-height: 500px;\n  overflow: auto;\n}\n\n/* ------------------------------------------------------------------------------------------\n * Sidenav\n * ------------------------------------------------------------------------------------------ */\n.sidenav {\n  width: 228px;\n  margin: 0;\n  padding: 20px;\n  position: fixed;\n  top: 0;\n  left: 0;\n  bottom: 0;\n  overflow-x: hidden;\n  overflow-y: auto;\n  background-color: #f5f5f5;\n  z-index: 10;\n}\n\n.sidenav > li > a {\n  display: block;\n  width: 192px;\n  margin: 0;\n  padding: 2px 11px;\n  border: 0;\n  border-left: transparent 4px solid;\n  border-right: transparent 4px solid;\n  font-family: \"Source Sans Pro\", sans-serif;\n  font-weight: 400;\n  font-size: 14px;\n}\n\n.sidenav > li.nav-header > a {\n  padding: 5px 15px;\n  border: 1px solid #e5e5e5;\n  width: 190px;\n  font-family: \"Source Sans Pro\", sans-serif;\n  font-weight: 700;\n  font-size: 16px;\n  background-color: #4c8eca;\n  color: #fff;\n}\n\n.sidenav > li.nav-header.active > a {\n  background-color: #4c8eca;\n  color: #fff;\n}\n\n\n00427D\n\n.sidenav > .active > a {\n  position: relative;\n  z-index: 2;\n}\n\n.sidenav > li > a:hover {\n  background-color: #ffffff;\n}\n\n.sidenav > li.has-modifications a {\n  border-right: #60d060 4px solid;\n}\n\n.sidenav > li.is-new a {\n  border-left: #e5e5e5 4px solid;\n}\n\n/* ------------------------------------------------------------------------------------------\n * Tabs\n * ------------------------------------------------------------------------------------------ */\nul.nav-tabs {\n  margin: 0;\n}\n\n/* ------------------------------------------------------------------------------------------\n * Print\n * ------------------------------------------------------------------------------------------ */\n\n@media print {\n\n  #sidenav,\n  #version,\n  #versions,\n  section .version,\n  section .versions {\n    display: none;\n  }\n\n  #content {\n    margin-left: 0;\n  }\n\n  a {\n    text-decoration: none;\n    color: inherit;\n  }\n\n  a:after {\n    content: \" [\" attr(href) \"] \";\n  }\n\n  p {\n    color: #000000\n  }\n\n  pre {\n    background-color: #ffffff;\n    color: #000000;\n    padding: 10px;\n    border: #808080 1px solid;\n    border-radius: 6px;\n    position: relative;\n    margin: 10px 0 20px 0;\n  }\n\n} /* /@media print */\n\n.doc-chapter {\n  display: none;\n  background-color: #eee;\n  border-radius: 1px;\n  padding: 10px;\n  margin-bottom: 20px;\n}\n\n/*!\n* json-schema-view-js\n* https://github.com/mohsen1/json-schema-view-js#readme\n* Version: 0.4.1 - 2015-11-12T17:19:27.615Z\n* License: MIT\n*/\n\n.json-schema-view .toggle-handle:after, .json-schema-view.json-schema-view-dark .toggle-handle:after, json-schema-view .toggle-handle:after, json-schema-view[json-schema-view-dark] .toggle-handle:after {\n  content: \"\\25BC\"\n}\n.json-schema-view .title, .json-schema-view.json-schema-view-dark .title, json-schema-view .title, json-schema-view[json-schema-view-dark] .title {\n  font-weight: 700;\n  cursor: pointer\n}\n.json-schema-view, json-schema-view {\n  font-family: monospace;\n  font-size: 0;\n  display: table-cell\n}\n.json-schema-view>*, json-schema-view>* {\n  font-size: 14px\n}\n.json-schema-view .toggle-handle, json-schema-view .toggle-handle {\n  cursor: pointer;\n  margin: auto .3em;\n  font-size: 10px;\n  display: inline-block;\n  transform-origin: 50% 40%;\n  transition: transform 150ms ease-in\n}\n.json-schema-view .toggle-handle, .json-schema-view .toggle-handle:hover, json-schema-view .toggle-handle, json-schema-view .toggle-handle:hover {\n  text-decoration: none;\n  color: #333\n}\n.json-schema-view .description, json-schema-view .description {\n  color: gray;\n  font-style: italic\n}\n.json-schema-view .readOnly, json-schema-view .readOnly {\n  color: gray;\n  font-style: italic\n}\n.json-schema-view .nullable, json-schema-view .nullable {\n  color: gray;\n  font-style: italic\n}\n.pattern, .example {\n  color: blue;\n}\n.default {\n  color: black;\n}\n.required {\n  color: black;\n}\n.json-schema-view .title, .json-schema-view .title:hover, json-schema-view .title, json-schema-view .title:hover {\n  text-decoration: none;\n  color: #333\n}\n.json-schema-view .brace, .json-schema-view .bracket, .json-schema-view .title, json-schema-view .brace, json-schema-view .bracket, json-schema-view .title {\n  color: #333\n}\n.json-schema-view .property, json-schema-view .property {\n  font-size: 0;\n  display: table-row\n}\n.json-schema-view .property>*, json-schema-view .property>* {\n  font-size: 14px;\n  padding: .2em\n}\n.json-schema-view .name, json-schema-view .name {\n  color: #00f;\n  display: table-cell;\n  vertical-align: top\n}\n.json-schema-view .type, json-schema-view .type {\n  color: green\n}\n.json-schema-view .type-any, json-schema-view .type-any {\n  color: #33f\n}\n.json-schema-view .required, json-schema-view .required {\n  color: red\n}\n.json-schema-view .inner, json-schema-view .inner {\n  padding-left: 18px\n}\n.json-schema-view.collapsed .description, .json-schema-view.collapsed .property, json-schema-view.collapsed .description, json-schema-view.collapsed .property {\n  display: none\n}\n.json-schema-view.collapsed .closing.brace, json-schema-view.collapsed .closing.brace {\n  display: inline-block\n}\n.json-schema-view.collapsed .toggle-handle, json-schema-view.collapsed .toggle-handle {\n  transform: rotate(-90deg)\n}\n.json-schema-view.json-schema-view-dark, json-schema-view[json-schema-view-dark] {\n  font-family: monospace;\n  font-size: 0;\n  display: table-cell\n}\n.json-schema-view.json-schema-view-dark>*, json-schema-view[json-schema-view-dark]>* {\n  font-size: 14px\n}\n.json-schema-view.json-schema-view-dark .toggle-handle, json-schema-view[json-schema-view-dark] .toggle-handle {\n  cursor: pointer;\n  margin: auto .3em;\n  font-size: 10px;\n  display: inline-block;\n  transform-origin: 50% 40%;\n  transition: transform 150ms ease-in\n}\n.json-schema-view.json-schema-view-dark .toggle-handle, .json-schema-view.json-schema-view-dark .toggle-handle:hover, json-schema-view[json-schema-view-dark] .toggle-handle, json-schema-view[json-schema-view-dark] .toggle-handle:hover {\n  text-decoration: none;\n  color: #eee\n}\n.json-schema-view.json-schema-view-dark .description, json-schema-view[json-schema-view-dark] .description {\n  color: gray;\n  font-style: italic\n}\n.json-schema-view.json-schema-view-dark .title, .json-schema-view.json-schema-view-dark .title:hover, json-schema-view[json-schema-view-dark] .title, json-schema-view[json-schema-view-dark] .title:hover {\n  text-decoration: none;\n  color: #eee\n}\n.json-schema-view.json-schema-view-dark .brace, .json-schema-view.json-schema-view-dark .bracket, .json-schema-view.json-schema-view-dark .title, json-schema-view[json-schema-view-dark] .brace, json-schema-view[json-schema-view-dark] .bracket, json-schema-view[json-schema-view-dark] .title {\n  color: #eee\n}\n.json-schema-view.json-schema-view-dark .property, json-schema-view[json-schema-view-dark] .property {\n  font-size: 0;\n  display: table-row\n}\n.json-schema-view.json-schema-view-dark .property>*, json-schema-view[json-schema-view-dark] .property>* {\n  font-size: 14px;\n  padding: .2em\n}\n.json-schema-view.json-schema-view-dark .name, json-schema-view[json-schema-view-dark] .name {\n  color: #add8e6;\n  display: table-cell;\n  vertical-align: top\n}\n.json-schema-view.json-schema-view-dark .type, json-schema-view[json-schema-view-dark] .type {\n  color: #90ee90\n}\n.json-schema-view.json-schema-view-dark .type-any, json-schema-view[json-schema-view-dark] .type-any {\n  color: #d4ebf2\n}\n.json-schema-view.json-schema-view-dark .required, json-schema-view[json-schema-view-dark] .required {\n  color: #fe0000\n}\n.json-schema-view.json-schema-view-dark .inner, json-schema-view[json-schema-view-dark] .inner {\n  padding-left: 18px\n}\n.json-schema-view.json-schema-view-dark.collapsed .description, .json-schema-view.json-schema-view-dark.collapsed .property, json-schema-view[json-schema-view-dark].collapsed .description, json-schema-view[json-schema-view-dark].collapsed .property {\n  display: none\n}\n.json-schema-view.json-schema-view-dark.collapsed .closing.brace, json-schema-view[json-schema-view-dark].collapsed .closing.brace {\n  display: inline-block\n}\n.json-schema-view.json-schema-view-dark.collapsed .toggle-handle, json-schema-view[json-schema-view-dark].collapsed .toggle-handle {\n  transform: rotate(-90deg)\n}\n.exampleStyle {\n  padding: 30px; border-left: 1px solid #eee; border-right: 1px solid #eee; border-bottom: 1px solid #eee;\n}\n  </style>\n</head>\n<body>\n  <script>\n    // Script section to load models into a JS Var\n    var defs = {}\n    defs[\"onboard_200_response\"] = {\n  \"type\" : \"object\",\n  \"properties\" : {\n    \"boardID\" : {\n      \"type\" : \"string\",\n      \"description\" : \"Board ID\"\n    },\n    \"teamID\" : {\n      \"type\" : \"string\",\n      \"description\" : \"Team ID\"\n    }\n  }\n};\n\n    var errs = {};\n  </script>\n\n  <div class=\"container-fluid\">\n    <div class=\"row-fluid\">\n      <div id=\"sidenav\" class=\"span2\">\n        <nav id=\"scrollingNav\">\n          <ul class=\"sidenav nav nav-list\">\n            <!-- Logo Area -->\n              <!--<div style=\"width: 80%; background-color: #4c8eca; color: white; padding: 20px; text-align: center; margin-bottom: 20px; \">\n\n              API Docs 2\n\n              </div>\n            -->\n            <li class=\"nav-fixed nav-header active\" data-group=\"_\"><a href=\"#api-_\">API Summary</a></li>\n\n                  <li class=\"nav-header\" data-group=\"Default\"><a href=\"#api-Default\">API Methods - Default</a></li>\n                    <li data-group=\"Default\" data-name=\"addMember\" class=\"\">\n                      <a href=\"#api-Default-addMember\">addMember</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"archiveExportBoard\" class=\"\">\n                      <a href=\"#api-Default-archiveExportBoard\">archiveExportBoard</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"archiveExportTeam\" class=\"\">\n                      <a href=\"#api-Default-archiveExportTeam\">archiveExportTeam</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"archiveImport\" class=\"\">\n                      <a href=\"#api-Default-archiveImport\">archiveImport</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"changePassword\" class=\"\">\n                      <a href=\"#api-Default-changePassword\">changePassword</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"cloudLimits\" class=\"\">\n                      <a href=\"#api-Default-cloudLimits\">cloudLimits</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"createBoard\" class=\"\">\n                      <a href=\"#api-Default-createBoard\">createBoard</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"createCard\" class=\"\">\n                      <a href=\"#api-Default-createCard\">createCard</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"createCategory\" class=\"\">\n                      <a href=\"#api-Default-createCategory\">createCategory</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"createSubscription\" class=\"\">\n                      <a href=\"#api-Default-createSubscription\">createSubscription</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"deleteBlock\" class=\"\">\n                      <a href=\"#api-Default-deleteBlock\">deleteBlock</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"deleteBoard\" class=\"\">\n                      <a href=\"#api-Default-deleteBoard\">deleteBoard</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"deleteBoardsAndBlocks\" class=\"\">\n                      <a href=\"#api-Default-deleteBoardsAndBlocks\">deleteBoardsAndBlocks</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"deleteCategory\" class=\"\">\n                      <a href=\"#api-Default-deleteCategory\">deleteCategory</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"deleteMember\" class=\"\">\n                      <a href=\"#api-Default-deleteMember\">deleteMember</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"deleteSubscription\" class=\"\">\n                      <a href=\"#api-Default-deleteSubscription\">deleteSubscription</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"duplicateBlock\" class=\"\">\n                      <a href=\"#api-Default-duplicateBlock\">duplicateBlock</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"duplicateBoard\" class=\"\">\n                      <a href=\"#api-Default-duplicateBoard\">duplicateBoard</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"getBlocks\" class=\"\">\n                      <a href=\"#api-Default-getBlocks\">getBlocks</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"getBoard\" class=\"\">\n                      <a href=\"#api-Default-getBoard\">getBoard</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"getBoardMetadata\" class=\"\">\n                      <a href=\"#api-Default-getBoardMetadata\">getBoardMetadata</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"getBoards\" class=\"\">\n                      <a href=\"#api-Default-getBoards\">getBoards</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"getCard\" class=\"\">\n                      <a href=\"#api-Default-getCard\">getCard</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"getCards\" class=\"\">\n                      <a href=\"#api-Default-getCards\">getCards</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"getChannel\" class=\"\">\n                      <a href=\"#api-Default-getChannel\">getChannel</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"getClientConfig\" class=\"\">\n                      <a href=\"#api-Default-getClientConfig\">getClientConfig</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"getFile\" class=\"\">\n                      <a href=\"#api-Default-getFile\">getFile</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"getMe\" class=\"\">\n                      <a href=\"#api-Default-getMe\">getMe</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"getMembersForBoard\" class=\"\">\n                      <a href=\"#api-Default-getMembersForBoard\">getMembersForBoard</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"getMyMemberships\" class=\"\">\n                      <a href=\"#api-Default-getMyMemberships\">getMyMemberships</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"getSharing\" class=\"\">\n                      <a href=\"#api-Default-getSharing\">getSharing</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"getSubscriptions\" class=\"\">\n                      <a href=\"#api-Default-getSubscriptions\">getSubscriptions</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"getTeam\" class=\"\">\n                      <a href=\"#api-Default-getTeam\">getTeam</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"getTeamUsers\" class=\"\">\n                      <a href=\"#api-Default-getTeamUsers\">getTeamUsers</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"getTeams\" class=\"\">\n                      <a href=\"#api-Default-getTeams\">getTeams</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"getTemplates\" class=\"\">\n                      <a href=\"#api-Default-getTemplates\">getTemplates</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"getUser\" class=\"\">\n                      <a href=\"#api-Default-getUser\">getUser</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"getUserBoardsInsights\" class=\"\">\n                      <a href=\"#api-Default-getUserBoardsInsights\">getUserBoardsInsights</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"getUserCategoryBoards\" class=\"\">\n                      <a href=\"#api-Default-getUserCategoryBoards\">getUserCategoryBoards</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"getUserConfig\" class=\"\">\n                      <a href=\"#api-Default-getUserConfig\">getUserConfig</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"getUsersList\" class=\"\">\n                      <a href=\"#api-Default-getUsersList\">getUsersList</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"handleNotifyAdminUpgrade\" class=\"\">\n                      <a href=\"#api-Default-handleNotifyAdminUpgrade\">handleNotifyAdminUpgrade</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"handleStatistics\" class=\"\">\n                      <a href=\"#api-Default-handleStatistics\">handleStatistics</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"handleTeamBoardsInsights\" class=\"\">\n                      <a href=\"#api-Default-handleTeamBoardsInsights\">handleTeamBoardsInsights</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"hello\" class=\"\">\n                      <a href=\"#api-Default-hello\">hello</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"insertBoardsAndBlocks\" class=\"\">\n                      <a href=\"#api-Default-insertBoardsAndBlocks\">insertBoardsAndBlocks</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"joinBoard\" class=\"\">\n                      <a href=\"#api-Default-joinBoard\">joinBoard</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"leaveBoard\" class=\"\">\n                      <a href=\"#api-Default-leaveBoard\">leaveBoard</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"login\" class=\"\">\n                      <a href=\"#api-Default-login\">login</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"logout\" class=\"\">\n                      <a href=\"#api-Default-logout\">logout</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"onboard\" class=\"\">\n                      <a href=\"#api-Default-onboard\">onboard</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"patchBlock\" class=\"\">\n                      <a href=\"#api-Default-patchBlock\">patchBlock</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"patchBlocks\" class=\"\">\n                      <a href=\"#api-Default-patchBlocks\">patchBlocks</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"patchBoard\" class=\"\">\n                      <a href=\"#api-Default-patchBoard\">patchBoard</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"patchBoardsAndBlocks\" class=\"\">\n                      <a href=\"#api-Default-patchBoardsAndBlocks\">patchBoardsAndBlocks</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"patchCard\" class=\"\">\n                      <a href=\"#api-Default-patchCard\">patchCard</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"ping\" class=\"\">\n                      <a href=\"#api-Default-ping\">ping</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"postSharing\" class=\"\">\n                      <a href=\"#api-Default-postSharing\">postSharing</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"regenerateSignupToken\" class=\"\">\n                      <a href=\"#api-Default-regenerateSignupToken\">regenerateSignupToken</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"register\" class=\"\">\n                      <a href=\"#api-Default-register\">register</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"searchAllBoards\" class=\"\">\n                      <a href=\"#api-Default-searchAllBoards\">searchAllBoards</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"searchBoards\" class=\"\">\n                      <a href=\"#api-Default-searchBoards\">searchBoards</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"searchLinkableBoards\" class=\"\">\n                      <a href=\"#api-Default-searchLinkableBoards\">searchLinkableBoards</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"searchMyChannels\" class=\"\">\n                      <a href=\"#api-Default-searchMyChannels\">searchMyChannels</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"undeleteBlock\" class=\"\">\n                      <a href=\"#api-Default-undeleteBlock\">undeleteBlock</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"undeleteBoard\" class=\"\">\n                      <a href=\"#api-Default-undeleteBoard\">undeleteBoard</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"updateBlocks\" class=\"\">\n                      <a href=\"#api-Default-updateBlocks\">updateBlocks</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"updateCategory\" class=\"\">\n                      <a href=\"#api-Default-updateCategory\">updateCategory</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"updateCategoryBoard\" class=\"\">\n                      <a href=\"#api-Default-updateCategoryBoard\">updateCategoryBoard</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"updateMember\" class=\"\">\n                      <a href=\"#api-Default-updateMember\">updateMember</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"updateUserConfig\" class=\"\">\n                      <a href=\"#api-Default-updateUserConfig\">updateUserConfig</a>\n                    </li>\n                    <li data-group=\"Default\" data-name=\"uploadFile\" class=\"\">\n                      <a href=\"#api-Default-uploadFile\">uploadFile</a>\n                    </li>\n          </ul>\n        </nav>\n      </div>\n      <div id=\"content\">\n        <div id=\"project\">\n          <div class=\"pull-left\">\n            <h1>Focalboard Server</h1>\n          </div>\n          <div class=\"clearfix\"></div>\n        </div>\n        <div id=\"header\">\n          <div id=\"api-_\">\n            <h2 id=\"welcome-to-apidoc\">API and SDK Documentation</h2>\n              <div class=\"app-desc\">Version: 2.0.0</div>\n            <hr>\n            <div id=\"app-description\" class=\"app-desc\">\n                Focalboard Server\n            </div>\n          </div>\n        </div>\n        <div id=\"sections\">\n                <section id=\"api-Default\">\n                  <h1>Default</h1>\n                    <div id=\"api-Default-addMember\">\n                      <article id=\"api-Default-addMember-0\" data-group=\"User\" data-name=\"addMember\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>addMember</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Adds a new member to a board</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"post\"><code><span class=\"pln\">/boards/{boardID}/members</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-addMember-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-addMember-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-addMember-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-addMember-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-addMember-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-addMember-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-addMember-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-addMember-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-addMember-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-addMember-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-addMember-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-addMember-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-addMember-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X POST \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n -H \"Content-Type: application/json\" \\\n \"http://localhost/api/v2/boards/{boardID}/members\" \\\n -d ''\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-addMember-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String boardID = boardID_example; // String | Board ID\n        Object body = Object; // Object | \n\n        try {\n            Object result = apiInstance.addMember(boardID, body);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#addMember\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-addMember-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String boardID = boardID_example; // String | Board ID\n        Object body = Object; // Object | \n\n        try {\n            Object result = apiInstance.addMember(boardID, body);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#addMember\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-addMember-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-addMember-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *boardID = boardID_example; // Board ID (default to null)\nObject *body = Object; // \n\n[apiInstance addMemberWith:boardID\n    body:body\n              completionHandler: ^(Object output, NSError* error) {\n    if (output) {\n        NSLog(@\"%@\", output);\n    }\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-addMember-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar boardID = boardID_example; // {String} Board ID\nvar body = Object; // {Object} \n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully. Returned data: ' + data);\n  }\n};\napi.addMember(boardID, body, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-addMember-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-addMember-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class addMemberExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var boardID = boardID_example;  // String | Board ID (default to null)\n            var body = Object;  // Object | \n\n            try {\n                Object result = apiInstance.addMember(boardID, body);\n                Debug.WriteLine(result);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.addMember: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-addMember-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$boardID = boardID_example; // String | Board ID\n$body = Object; // Object | \n\ntry {\n    $result = $api_instance->addMember($boardID, $body);\n    print_r($result);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->addMember: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-addMember-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $boardID = boardID_example; # String | Board ID\nmy $body = WWW::OPenAPIClient::Object::Object->new(); # Object | \n\neval {\n    my $result = $api_instance->addMember(boardID => $boardID, body => $body);\n    print Dumper($result);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->addMember: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-addMember-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nboardID = boardID_example # String | Board ID (default to null)\nbody = Object # Object | \n\ntry:\n    api_response = api_instance.add_member(boardID, body)\n    pprint(api_response)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->addMember: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-addMember-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let boardID = boardID_example; // String\n    let body = Object; // Object\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.addMember(boardID, body, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">boardID*</td>\n<td>\n\n\n    <div id=\"d2e199_addMember_boardID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nBoard ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n                            <div class=\"methodsubtabletitle\">Body parameters</div>\n                            <table id=\"methodsubtable\">\n                              <tr>\n                                <th width=\"150px\">Name</th>\n                                <th>Description</th>\n                              </tr>\n                                <tr><td style=\"width:150px;\">body <span style=\"color:red;\">*</span></td>\n<td>\n<p class=\"marked\">membership to replace the current one with</p>\n<script>\n$(document).ready(function() {\n  var schemaWrapper = {\n  \"description\" : \"membership to replace the current one with\",\n  \"content\" : {\n    \"application/json\" : {\n      \"schema\" : {\n        \"$ref\" : \"#/components/schemas/BoardMember\"\n      }\n    }\n  },\n  \"required\" : true\n};\n\n  var schema = findNode('schema',schemaWrapper).schema;\n  if (!schema) {\n    schema = schemaWrapper.schema;\n  }\n  if (schema.$ref != null) {\n    schema = defsParser.$refs.get(schema.$ref);\n  } else {\n    schemaWrapper.definitions = Object.assign({}, defs);\n    $RefParser.dereference(schemaWrapper).catch(function(err) {\n      console.log(err);\n    });\n  }\n\n  var view = new JSONSchemaView(schema,2,{isBodyParam: true});\n  var result = $('#d2e199_addMember_body');\n  result.empty();\n  result.append(view.render());\n});\n</script>\n<div id=\"d2e199_addMember_body\"></div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-addMember-title-200\"></h3>\n                            <p id=\"examples-Default-addMember-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-addMember-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-addMember-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-addMember-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-addMember-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-addMember-200-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-addMember-200-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-addMember-200-schema\">\n                                  <div id=\"responses-Default-addMember-schema-200\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=BoardMember stores the information of the membership of a user on a board, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-addMember-200-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-addMember-schema-200');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-addMember-200-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-addMember-title-404\"></h3>\n                            <p id=\"examples-Default-addMember-description-404\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault404_description = `board not found`;\n                              var responseDefault404_description_break = responseDefault404_description.indexOf('\\n');\n                              if (responseDefault404_description_break == -1) {\n                                $(\"#examples-Default-addMember-title-404\").text(\"Status: 404 - \" + responseDefault404_description);\n                              } else {\n                                $(\"#examples-Default-addMember-title-404\").text(\"Status: 404 - \" + responseDefault404_description.substring(0, responseDefault404_description_break));\n                                $(\"#examples-Default-addMember-description-404\").html(responseDefault404_description.substring(responseDefault404_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-addMember-404\" class=\"nav nav-tabs nav-tabs-examples\" >\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-addMember-404-wrapper\" style='margin-bottom: 10px;'>\n                            </div>\n                            <h3 id=\"examples-Default-addMember-title-default\"></h3>\n                            <p id=\"examples-Default-addMember-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-addMember-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-addMember-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-addMember-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-addMember-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-addMember-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-addMember-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-addMember-default-schema\">\n                                  <div id=\"responses-Default-addMember-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-addMember-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-addMember-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-addMember-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-archiveExportBoard\">\n                      <article id=\"api-Default-archiveExportBoard-0\" data-group=\"User\" data-name=\"archiveExportBoard\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>archiveExportBoard</h1>\n                          <p>Exports an archive of all blocks for one boards.</p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\"></p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"get\"><code><span class=\"pln\">/boards/{boardID}/archive/export</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-archiveExportBoard-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-archiveExportBoard-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-archiveExportBoard-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-archiveExportBoard-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-archiveExportBoard-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-archiveExportBoard-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-archiveExportBoard-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-archiveExportBoard-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-archiveExportBoard-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-archiveExportBoard-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-archiveExportBoard-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-archiveExportBoard-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-archiveExportBoard-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X GET \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n \"http://localhost/api/v2/boards/{boardID}/archive/export\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-archiveExportBoard-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String boardID = boardID_example; // String | Id of board to export\n\n        try {\n            apiInstance.archiveExportBoard(boardID);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#archiveExportBoard\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-archiveExportBoard-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String boardID = boardID_example; // String | Id of board to export\n\n        try {\n            apiInstance.archiveExportBoard(boardID);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#archiveExportBoard\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-archiveExportBoard-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-archiveExportBoard-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *boardID = boardID_example; // Id of board to export (default to null)\n\n// Exports an archive of all blocks for one boards.\n[apiInstance archiveExportBoardWith:boardID\n              completionHandler: ^(NSError* error) {\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-archiveExportBoard-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar boardID = boardID_example; // {String} Id of board to export\n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully.');\n  }\n};\napi.archiveExportBoard(boardID, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-archiveExportBoard-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-archiveExportBoard-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class archiveExportBoardExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var boardID = boardID_example;  // String | Id of board to export (default to null)\n\n            try {\n                // Exports an archive of all blocks for one boards.\n                apiInstance.archiveExportBoard(boardID);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.archiveExportBoard: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-archiveExportBoard-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$boardID = boardID_example; // String | Id of board to export\n\ntry {\n    $api_instance->archiveExportBoard($boardID);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->archiveExportBoard: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-archiveExportBoard-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $boardID = boardID_example; # String | Id of board to export\n\neval {\n    $api_instance->archiveExportBoard(boardID => $boardID);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->archiveExportBoard: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-archiveExportBoard-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nboardID = boardID_example # String | Id of board to export (default to null)\n\ntry:\n    # Exports an archive of all blocks for one boards.\n    api_instance.archive_export_board(boardID)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->archiveExportBoard: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-archiveExportBoard-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let boardID = boardID_example; // String\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.archiveExportBoard(boardID, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">boardID*</td>\n<td>\n\n\n    <div id=\"d2e199_archiveExportBoard_boardID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nId of board to export\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-archiveExportBoard-title-200\"></h3>\n                            <p id=\"examples-Default-archiveExportBoard-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-archiveExportBoard-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-archiveExportBoard-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-archiveExportBoard-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-archiveExportBoard-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-archiveExportBoard-200-wrapper\" style='margin-bottom: 10px;'>\n                            </div>\n                            <h3 id=\"examples-Default-archiveExportBoard-title-default\"></h3>\n                            <p id=\"examples-Default-archiveExportBoard-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-archiveExportBoard-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-archiveExportBoard-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-archiveExportBoard-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-archiveExportBoard-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-archiveExportBoard-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-archiveExportBoard-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-archiveExportBoard-default-schema\">\n                                  <div id=\"responses-Default-archiveExportBoard-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-archiveExportBoard-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-archiveExportBoard-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-archiveExportBoard-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-archiveExportTeam\">\n                      <article id=\"api-Default-archiveExportTeam-0\" data-group=\"User\" data-name=\"archiveExportTeam\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>archiveExportTeam</h1>\n                          <p>Exports an archive of all blocks for all the boards in a team.</p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\"></p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"get\"><code><span class=\"pln\">/teams/{teamID}/archive/export</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-archiveExportTeam-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-archiveExportTeam-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-archiveExportTeam-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-archiveExportTeam-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-archiveExportTeam-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-archiveExportTeam-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-archiveExportTeam-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-archiveExportTeam-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-archiveExportTeam-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-archiveExportTeam-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-archiveExportTeam-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-archiveExportTeam-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-archiveExportTeam-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X GET \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n \"http://localhost/api/v2/teams/{teamID}/archive/export\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-archiveExportTeam-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String teamID = teamID_example; // String | Id of team\n\n        try {\n            apiInstance.archiveExportTeam(teamID);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#archiveExportTeam\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-archiveExportTeam-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String teamID = teamID_example; // String | Id of team\n\n        try {\n            apiInstance.archiveExportTeam(teamID);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#archiveExportTeam\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-archiveExportTeam-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-archiveExportTeam-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *teamID = teamID_example; // Id of team (default to null)\n\n// Exports an archive of all blocks for all the boards in a team.\n[apiInstance archiveExportTeamWith:teamID\n              completionHandler: ^(NSError* error) {\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-archiveExportTeam-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar teamID = teamID_example; // {String} Id of team\n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully.');\n  }\n};\napi.archiveExportTeam(teamID, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-archiveExportTeam-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-archiveExportTeam-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class archiveExportTeamExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var teamID = teamID_example;  // String | Id of team (default to null)\n\n            try {\n                // Exports an archive of all blocks for all the boards in a team.\n                apiInstance.archiveExportTeam(teamID);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.archiveExportTeam: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-archiveExportTeam-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$teamID = teamID_example; // String | Id of team\n\ntry {\n    $api_instance->archiveExportTeam($teamID);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->archiveExportTeam: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-archiveExportTeam-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $teamID = teamID_example; # String | Id of team\n\neval {\n    $api_instance->archiveExportTeam(teamID => $teamID);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->archiveExportTeam: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-archiveExportTeam-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nteamID = teamID_example # String | Id of team (default to null)\n\ntry:\n    # Exports an archive of all blocks for all the boards in a team.\n    api_instance.archive_export_team(teamID)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->archiveExportTeam: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-archiveExportTeam-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let teamID = teamID_example; // String\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.archiveExportTeam(teamID, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">teamID*</td>\n<td>\n\n\n    <div id=\"d2e199_archiveExportTeam_teamID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nId of team\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-archiveExportTeam-title-200\"></h3>\n                            <p id=\"examples-Default-archiveExportTeam-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-archiveExportTeam-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-archiveExportTeam-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-archiveExportTeam-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-archiveExportTeam-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-archiveExportTeam-200-wrapper\" style='margin-bottom: 10px;'>\n                            </div>\n                            <h3 id=\"examples-Default-archiveExportTeam-title-default\"></h3>\n                            <p id=\"examples-Default-archiveExportTeam-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-archiveExportTeam-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-archiveExportTeam-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-archiveExportTeam-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-archiveExportTeam-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-archiveExportTeam-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-archiveExportTeam-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-archiveExportTeam-default-schema\">\n                                  <div id=\"responses-Default-archiveExportTeam-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-archiveExportTeam-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-archiveExportTeam-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-archiveExportTeam-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-archiveImport\">\n                      <article id=\"api-Default-archiveImport-0\" data-group=\"User\" data-name=\"archiveImport\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>archiveImport</h1>\n                          <p>Import an archive of boards.</p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\"></p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"post\"><code><span class=\"pln\">/teams/{teamID}/archive/import</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-archiveImport-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-archiveImport-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-archiveImport-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-archiveImport-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-archiveImport-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-archiveImport-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-archiveImport-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-archiveImport-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-archiveImport-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-archiveImport-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-archiveImport-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-archiveImport-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-archiveImport-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X POST \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n -H \"Content-Type: multipart/form-data\" \\\n \"http://localhost/api/v2/teams/{teamID}/archive/import\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-archiveImport-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String teamID = teamID_example; // String | Team ID\n        File file = BINARY_DATA_HERE; // File | archive file to import\n\n        try {\n            apiInstance.archiveImport(teamID, file);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#archiveImport\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-archiveImport-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String teamID = teamID_example; // String | Team ID\n        File file = BINARY_DATA_HERE; // File | archive file to import\n\n        try {\n            apiInstance.archiveImport(teamID, file);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#archiveImport\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-archiveImport-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-archiveImport-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *teamID = teamID_example; // Team ID (default to null)\nFile *file = BINARY_DATA_HERE; // archive file to import (default to null)\n\n// Import an archive of boards.\n[apiInstance archiveImportWith:teamID\n    file:file\n              completionHandler: ^(NSError* error) {\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-archiveImport-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar teamID = teamID_example; // {String} Team ID\nvar file = BINARY_DATA_HERE; // {File} archive file to import\n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully.');\n  }\n};\napi.archiveImport(teamID, file, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-archiveImport-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-archiveImport-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class archiveImportExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var teamID = teamID_example;  // String | Team ID (default to null)\n            var file = BINARY_DATA_HERE;  // File | archive file to import (default to null)\n\n            try {\n                // Import an archive of boards.\n                apiInstance.archiveImport(teamID, file);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.archiveImport: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-archiveImport-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$teamID = teamID_example; // String | Team ID\n$file = BINARY_DATA_HERE; // File | archive file to import\n\ntry {\n    $api_instance->archiveImport($teamID, $file);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->archiveImport: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-archiveImport-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $teamID = teamID_example; # String | Team ID\nmy $file = BINARY_DATA_HERE; # File | archive file to import\n\neval {\n    $api_instance->archiveImport(teamID => $teamID, file => $file);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->archiveImport: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-archiveImport-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nteamID = teamID_example # String | Team ID (default to null)\nfile = BINARY_DATA_HERE # File | archive file to import (default to null)\n\ntry:\n    # Import an archive of boards.\n    api_instance.archive_import(teamID, file)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->archiveImport: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-archiveImport-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let teamID = teamID_example; // String\n    let file = BINARY_DATA_HERE; // File\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.archiveImport(teamID, file, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">teamID*</td>\n<td>\n\n\n    <div id=\"d2e199_archiveImport_teamID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nTeam ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n                            <div class=\"methodsubtabletitle\">Form parameters</div>\n                            <table id=\"methodsubtable\">\n                              <tr>\n                                <th width=\"150px\">Name</th>\n                                <th>Description</th>\n                              </tr>\n                                  <tr><td style=\"width:150px;\">file*</td>\n<td>\n\n\n    <div id=\"d2e199_archiveImport_file\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    File\n                </span>\n                    <span class=\"format\">\n                        (binary)\n                    </span>\n\n                    <div class=\"inner description marked\">\narchive file to import\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-archiveImport-title-200\"></h3>\n                            <p id=\"examples-Default-archiveImport-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-archiveImport-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-archiveImport-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-archiveImport-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-archiveImport-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-archiveImport-200-wrapper\" style='margin-bottom: 10px;'>\n                            </div>\n                            <h3 id=\"examples-Default-archiveImport-title-default\"></h3>\n                            <p id=\"examples-Default-archiveImport-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-archiveImport-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-archiveImport-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-archiveImport-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-archiveImport-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-archiveImport-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-archiveImport-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-archiveImport-default-schema\">\n                                  <div id=\"responses-Default-archiveImport-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-archiveImport-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-archiveImport-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-archiveImport-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-changePassword\">\n                      <article id=\"api-Default-changePassword-0\" data-group=\"User\" data-name=\"changePassword\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>changePassword</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Change a user&#39;s password</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"post\"><code><span class=\"pln\">/users/{userID}/changepassword</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-changePassword-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-changePassword-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-changePassword-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-changePassword-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-changePassword-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-changePassword-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-changePassword-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-changePassword-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-changePassword-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-changePassword-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-changePassword-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-changePassword-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-changePassword-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X POST \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n -H \"Content-Type: application/json\" \\\n \"http://localhost/api/v2/users/{userID}/changepassword\" \\\n -d ''\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-changePassword-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String userID = userID_example; // String | User ID\n        Object body = Object; // Object | \n\n        try {\n            apiInstance.changePassword(userID, body);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#changePassword\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-changePassword-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String userID = userID_example; // String | User ID\n        Object body = Object; // Object | \n\n        try {\n            apiInstance.changePassword(userID, body);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#changePassword\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-changePassword-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-changePassword-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *userID = userID_example; // User ID (default to null)\nObject *body = Object; // \n\n[apiInstance changePasswordWith:userID\n    body:body\n              completionHandler: ^(NSError* error) {\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-changePassword-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar userID = userID_example; // {String} User ID\nvar body = Object; // {Object} \n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully.');\n  }\n};\napi.changePassword(userID, body, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-changePassword-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-changePassword-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class changePasswordExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var userID = userID_example;  // String | User ID (default to null)\n            var body = Object;  // Object | \n\n            try {\n                apiInstance.changePassword(userID, body);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.changePassword: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-changePassword-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$userID = userID_example; // String | User ID\n$body = Object; // Object | \n\ntry {\n    $api_instance->changePassword($userID, $body);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->changePassword: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-changePassword-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $userID = userID_example; # String | User ID\nmy $body = WWW::OPenAPIClient::Object::Object->new(); # Object | \n\neval {\n    $api_instance->changePassword(userID => $userID, body => $body);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->changePassword: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-changePassword-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nuserID = userID_example # String | User ID (default to null)\nbody = Object # Object | \n\ntry:\n    api_instance.change_password(userID, body)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->changePassword: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-changePassword-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let userID = userID_example; // String\n    let body = Object; // Object\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.changePassword(userID, body, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">userID*</td>\n<td>\n\n\n    <div id=\"d2e199_changePassword_userID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nUser ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n                            <div class=\"methodsubtabletitle\">Body parameters</div>\n                            <table id=\"methodsubtable\">\n                              <tr>\n                                <th width=\"150px\">Name</th>\n                                <th>Description</th>\n                              </tr>\n                                <tr><td style=\"width:150px;\">body <span style=\"color:red;\">*</span></td>\n<td>\n<p class=\"marked\">Change password request</p>\n<script>\n$(document).ready(function() {\n  var schemaWrapper = {\n  \"description\" : \"Change password request\",\n  \"content\" : {\n    \"application/json\" : {\n      \"schema\" : {\n        \"$ref\" : \"#/components/schemas/ChangePasswordRequest\"\n      }\n    }\n  },\n  \"required\" : true\n};\n\n  var schema = findNode('schema',schemaWrapper).schema;\n  if (!schema) {\n    schema = schemaWrapper.schema;\n  }\n  if (schema.$ref != null) {\n    schema = defsParser.$refs.get(schema.$ref);\n  } else {\n    schemaWrapper.definitions = Object.assign({}, defs);\n    $RefParser.dereference(schemaWrapper).catch(function(err) {\n      console.log(err);\n    });\n  }\n\n  var view = new JSONSchemaView(schema,2,{isBodyParam: true});\n  var result = $('#d2e199_changePassword_body');\n  result.empty();\n  result.append(view.render());\n});\n</script>\n<div id=\"d2e199_changePassword_body\"></div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-changePassword-title-200\"></h3>\n                            <p id=\"examples-Default-changePassword-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-changePassword-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-changePassword-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-changePassword-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-changePassword-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-changePassword-200-wrapper\" style='margin-bottom: 10px;'>\n                            </div>\n                            <h3 id=\"examples-Default-changePassword-title-400\"></h3>\n                            <p id=\"examples-Default-changePassword-description-400\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault400_description = `invalid request`;\n                              var responseDefault400_description_break = responseDefault400_description.indexOf('\\n');\n                              if (responseDefault400_description_break == -1) {\n                                $(\"#examples-Default-changePassword-title-400\").text(\"Status: 400 - \" + responseDefault400_description);\n                              } else {\n                                $(\"#examples-Default-changePassword-title-400\").text(\"Status: 400 - \" + responseDefault400_description.substring(0, responseDefault400_description_break));\n                                $(\"#examples-Default-changePassword-description-400\").html(responseDefault400_description.substring(responseDefault400_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-changePassword-400\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-changePassword-400-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-changePassword-400-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-changePassword-400-schema\">\n                                  <div id=\"responses-Default-changePassword-schema-400\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-changePassword-400-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-changePassword-schema-400');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-changePassword-400-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-changePassword-title-500\"></h3>\n                            <p id=\"examples-Default-changePassword-description-500\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault500_description = `internal error`;\n                              var responseDefault500_description_break = responseDefault500_description.indexOf('\\n');\n                              if (responseDefault500_description_break == -1) {\n                                $(\"#examples-Default-changePassword-title-500\").text(\"Status: 500 - \" + responseDefault500_description);\n                              } else {\n                                $(\"#examples-Default-changePassword-title-500\").text(\"Status: 500 - \" + responseDefault500_description.substring(0, responseDefault500_description_break));\n                                $(\"#examples-Default-changePassword-description-500\").html(responseDefault500_description.substring(responseDefault500_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-changePassword-500\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-changePassword-500-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-changePassword-500-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-changePassword-500-schema\">\n                                  <div id=\"responses-Default-changePassword-schema-500\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-changePassword-500-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-changePassword-schema-500');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-changePassword-500-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-cloudLimits\">\n                      <article id=\"api-Default-cloudLimits-0\" data-group=\"User\" data-name=\"cloudLimits\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>cloudLimits</h1>\n                          <p>Fetches the cloud limits of the server.</p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\"></p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"get\"><code><span class=\"pln\">/limits</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-cloudLimits-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-cloudLimits-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-cloudLimits-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-cloudLimits-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-cloudLimits-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-cloudLimits-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-cloudLimits-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-cloudLimits-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-cloudLimits-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-cloudLimits-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-cloudLimits-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-cloudLimits-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-cloudLimits-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X GET \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n \"http://localhost/api/v2/limits\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-cloudLimits-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n\n        try {\n            Object result = apiInstance.cloudLimits();\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#cloudLimits\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-cloudLimits-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n\n        try {\n            Object result = apiInstance.cloudLimits();\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#cloudLimits\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-cloudLimits-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-cloudLimits-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\n\n// Fetches the cloud limits of the server.\n[apiInstance cloudLimitsWithCompletionHandler: \n              ^(Object output, NSError* error) {\n    if (output) {\n        NSLog(@\"%@\", output);\n    }\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-cloudLimits-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully. Returned data: ' + data);\n  }\n};\napi.cloudLimits(callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-cloudLimits-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-cloudLimits-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class cloudLimitsExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n\n            try {\n                // Fetches the cloud limits of the server.\n                Object result = apiInstance.cloudLimits();\n                Debug.WriteLine(result);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.cloudLimits: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-cloudLimits-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n\ntry {\n    $result = $api_instance->cloudLimits();\n    print_r($result);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->cloudLimits: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-cloudLimits-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\n\neval {\n    my $result = $api_instance->cloudLimits();\n    print Dumper($result);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->cloudLimits: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-cloudLimits-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\n\ntry:\n    # Fetches the cloud limits of the server.\n    api_response = api_instance.cloud_limits()\n    pprint(api_response)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->cloudLimits: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-cloudLimits-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.cloudLimits(&context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n\n\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-cloudLimits-title-200\"></h3>\n                            <p id=\"examples-Default-cloudLimits-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-cloudLimits-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-cloudLimits-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-cloudLimits-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-cloudLimits-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-cloudLimits-200-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-cloudLimits-200-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-cloudLimits-200-schema\">\n                                  <div id=\"responses-Default-cloudLimits-schema-200\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=BoardsCloudLimits is the representation of the limits for the\nBoards server, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-cloudLimits-200-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-cloudLimits-schema-200');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-cloudLimits-200-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-cloudLimits-title-default\"></h3>\n                            <p id=\"examples-Default-cloudLimits-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-cloudLimits-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-cloudLimits-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-cloudLimits-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-cloudLimits-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-cloudLimits-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-cloudLimits-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-cloudLimits-default-schema\">\n                                  <div id=\"responses-Default-cloudLimits-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-cloudLimits-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-cloudLimits-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-cloudLimits-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-createBoard\">\n                      <article id=\"api-Default-createBoard-0\" data-group=\"User\" data-name=\"createBoard\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>createBoard</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Creates a new board</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"post\"><code><span class=\"pln\">/boards</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-createBoard-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-createBoard-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-createBoard-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-createBoard-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-createBoard-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-createBoard-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-createBoard-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-createBoard-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-createBoard-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-createBoard-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-createBoard-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-createBoard-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-createBoard-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X POST \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n -H \"Content-Type: application/json\" \\\n \"http://localhost/api/v2/boards\" \\\n -d ''\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-createBoard-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        Object body = Object; // Object | \n\n        try {\n            Object result = apiInstance.createBoard(body);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#createBoard\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-createBoard-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        Object body = Object; // Object | \n\n        try {\n            Object result = apiInstance.createBoard(body);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#createBoard\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-createBoard-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-createBoard-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nObject *body = Object; // \n\n[apiInstance createBoardWith:body\n              completionHandler: ^(Object output, NSError* error) {\n    if (output) {\n        NSLog(@\"%@\", output);\n    }\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-createBoard-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar body = Object; // {Object} \n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully. Returned data: ' + data);\n  }\n};\napi.createBoard(body, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-createBoard-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-createBoard-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class createBoardExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var body = Object;  // Object | \n\n            try {\n                Object result = apiInstance.createBoard(body);\n                Debug.WriteLine(result);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.createBoard: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-createBoard-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$body = Object; // Object | \n\ntry {\n    $result = $api_instance->createBoard($body);\n    print_r($result);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->createBoard: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-createBoard-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $body = WWW::OPenAPIClient::Object::Object->new(); # Object | \n\neval {\n    my $result = $api_instance->createBoard(body => $body);\n    print Dumper($result);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->createBoard: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-createBoard-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nbody = Object # Object | \n\ntry:\n    api_response = api_instance.create_board(body)\n    pprint(api_response)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->createBoard: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-createBoard-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let body = Object; // Object\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.createBoard(body, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n\n\n                            <div class=\"methodsubtabletitle\">Body parameters</div>\n                            <table id=\"methodsubtable\">\n                              <tr>\n                                <th width=\"150px\">Name</th>\n                                <th>Description</th>\n                              </tr>\n                                <tr><td style=\"width:150px;\">body <span style=\"color:red;\">*</span></td>\n<td>\n<p class=\"marked\">the board to create</p>\n<script>\n$(document).ready(function() {\n  var schemaWrapper = {\n  \"description\" : \"the board to create\",\n  \"content\" : {\n    \"application/json\" : {\n      \"schema\" : {\n        \"$ref\" : \"#/components/schemas/Board\"\n      }\n    }\n  },\n  \"required\" : true\n};\n\n  var schema = findNode('schema',schemaWrapper).schema;\n  if (!schema) {\n    schema = schemaWrapper.schema;\n  }\n  if (schema.$ref != null) {\n    schema = defsParser.$refs.get(schema.$ref);\n  } else {\n    schemaWrapper.definitions = Object.assign({}, defs);\n    $RefParser.dereference(schemaWrapper).catch(function(err) {\n      console.log(err);\n    });\n  }\n\n  var view = new JSONSchemaView(schema,2,{isBodyParam: true});\n  var result = $('#d2e199_createBoard_body');\n  result.empty();\n  result.append(view.render());\n});\n</script>\n<div id=\"d2e199_createBoard_body\"></div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-createBoard-title-200\"></h3>\n                            <p id=\"examples-Default-createBoard-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-createBoard-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-createBoard-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-createBoard-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-createBoard-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-createBoard-200-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-createBoard-200-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-createBoard-200-schema\">\n                                  <div id=\"responses-Default-createBoard-schema-200\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=Board groups a set of blocks and its layout, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-createBoard-200-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-createBoard-schema-200');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-createBoard-200-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-createBoard-title-default\"></h3>\n                            <p id=\"examples-Default-createBoard-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-createBoard-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-createBoard-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-createBoard-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-createBoard-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-createBoard-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-createBoard-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-createBoard-default-schema\">\n                                  <div id=\"responses-Default-createBoard-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-createBoard-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-createBoard-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-createBoard-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-createCard\">\n                      <article id=\"api-Default-createCard-0\" data-group=\"User\" data-name=\"createCard\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>createCard</h1>\n                          <p>Creates a new card for the specified board.</p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\"></p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"post\"><code><span class=\"pln\">/boards/{boardID}/cards</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-createCard-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-createCard-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-createCard-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-createCard-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-createCard-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-createCard-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-createCard-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-createCard-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-createCard-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-createCard-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-createCard-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-createCard-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-createCard-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X POST \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n -H \"Content-Type: application/json\" \\\n \"http://localhost/api/v2/boards/{boardID}/cards?disable_notify=\" \\\n -d ''\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-createCard-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String boardID = boardID_example; // String | Board ID\n        Object body = Object; // Object | \n        oas_any_type_not_mapped disableNotify = ; // oas_any_type_not_mapped | Disables notifications (for bulk data inserting)\n\n        try {\n            Object result = apiInstance.createCard(boardID, body, disableNotify);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#createCard\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-createCard-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String boardID = boardID_example; // String | Board ID\n        Object body = Object; // Object | \n        oas_any_type_not_mapped disableNotify = ; // oas_any_type_not_mapped | Disables notifications (for bulk data inserting)\n\n        try {\n            Object result = apiInstance.createCard(boardID, body, disableNotify);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#createCard\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-createCard-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-createCard-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *boardID = boardID_example; // Board ID (default to null)\nObject *body = Object; // \noas_any_type_not_mapped *disableNotify = ; // Disables notifications (for bulk data inserting) (optional) (default to null)\n\n// Creates a new card for the specified board.\n[apiInstance createCardWith:boardID\n    body:body\n    disableNotify:disableNotify\n              completionHandler: ^(Object output, NSError* error) {\n    if (output) {\n        NSLog(@\"%@\", output);\n    }\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-createCard-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar boardID = boardID_example; // {String} Board ID\nvar body = Object; // {Object} \nvar opts = {\n  'disableNotify':  // {oas_any_type_not_mapped} Disables notifications (for bulk data inserting)\n};\n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully. Returned data: ' + data);\n  }\n};\napi.createCard(boardID, body, opts, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-createCard-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-createCard-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class createCardExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var boardID = boardID_example;  // String | Board ID (default to null)\n            var body = Object;  // Object | \n            var disableNotify = new oas_any_type_not_mapped(); // oas_any_type_not_mapped | Disables notifications (for bulk data inserting) (optional)  (default to null)\n\n            try {\n                // Creates a new card for the specified board.\n                Object result = apiInstance.createCard(boardID, body, disableNotify);\n                Debug.WriteLine(result);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.createCard: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-createCard-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$boardID = boardID_example; // String | Board ID\n$body = Object; // Object | \n$disableNotify = ; // oas_any_type_not_mapped | Disables notifications (for bulk data inserting)\n\ntry {\n    $result = $api_instance->createCard($boardID, $body, $disableNotify);\n    print_r($result);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->createCard: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-createCard-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $boardID = boardID_example; # String | Board ID\nmy $body = WWW::OPenAPIClient::Object::Object->new(); # Object | \nmy $disableNotify = ; # oas_any_type_not_mapped | Disables notifications (for bulk data inserting)\n\neval {\n    my $result = $api_instance->createCard(boardID => $boardID, body => $body, disableNotify => $disableNotify);\n    print Dumper($result);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->createCard: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-createCard-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nboardID = boardID_example # String | Board ID (default to null)\nbody = Object # Object | \ndisableNotify =  # oas_any_type_not_mapped | Disables notifications (for bulk data inserting) (optional) (default to null)\n\ntry:\n    # Creates a new card for the specified board.\n    api_response = api_instance.create_card(boardID, body, disableNotify=disableNotify)\n    pprint(api_response)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->createCard: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-createCard-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let boardID = boardID_example; // String\n    let body = Object; // Object\n    let disableNotify = ; // oas_any_type_not_mapped\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.createCard(boardID, body, disableNotify, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">boardID*</td>\n<td>\n\n\n    <div id=\"d2e199_createCard_boardID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nBoard ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n                            <div class=\"methodsubtabletitle\">Body parameters</div>\n                            <table id=\"methodsubtable\">\n                              <tr>\n                                <th width=\"150px\">Name</th>\n                                <th>Description</th>\n                              </tr>\n                                <tr><td style=\"width:150px;\">body <span style=\"color:red;\">*</span></td>\n<td>\n<p class=\"marked\">the card to create</p>\n<script>\n$(document).ready(function() {\n  var schemaWrapper = {\n  \"description\" : \"the card to create\",\n  \"content\" : {\n    \"application/json\" : {\n      \"schema\" : {\n        \"$ref\" : \"#/components/schemas/Card\"\n      }\n    }\n  },\n  \"required\" : true\n};\n\n  var schema = findNode('schema',schemaWrapper).schema;\n  if (!schema) {\n    schema = schemaWrapper.schema;\n  }\n  if (schema.$ref != null) {\n    schema = defsParser.$refs.get(schema.$ref);\n  } else {\n    schemaWrapper.definitions = Object.assign({}, defs);\n    $RefParser.dereference(schemaWrapper).catch(function(err) {\n      console.log(err);\n    });\n  }\n\n  var view = new JSONSchemaView(schema,2,{isBodyParam: true});\n  var result = $('#d2e199_createCard_body');\n  result.empty();\n  result.append(view.render());\n});\n</script>\n<div id=\"d2e199_createCard_body\"></div>\n</td>\n</tr>\n\n                            </table>\n\n\n                            <div class=\"methodsubtabletitle\">Query parameters</div>\n                            <table id=\"methodsubtable\">\n                              <tr>\n                                <th width=\"150px\">Name</th>\n                                <th>Description</th>\n                              </tr>\n                                <tr><td style=\"width:150px;\">disable_notify</td>\n<td>\n\n\n    <div id=\"d2e199_createCard_disableNotify\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    oas_any_type_not_mapped\n                </span>\n\n                    <div class=\"inner description marked\">\nDisables notifications (for bulk data inserting)\n                    </div>\n            </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-createCard-title-200\"></h3>\n                            <p id=\"examples-Default-createCard-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-createCard-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-createCard-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-createCard-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-createCard-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-createCard-200-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-createCard-200-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-createCard-200-schema\">\n                                  <div id=\"responses-Default-createCard-schema-200\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {title=Card represents a group of content blocks and properties., x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-createCard-200-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-createCard-schema-200');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-createCard-200-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-createCard-title-default\"></h3>\n                            <p id=\"examples-Default-createCard-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-createCard-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-createCard-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-createCard-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-createCard-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-createCard-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-createCard-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-createCard-default-schema\">\n                                  <div id=\"responses-Default-createCard-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-createCard-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-createCard-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-createCard-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-createCategory\">\n                      <article id=\"api-Default-createCategory-0\" data-group=\"User\" data-name=\"createCategory\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>createCategory</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Create a category for boards</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"post\"><code><span class=\"pln\">/teams/{teamID}/categories</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-createCategory-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-createCategory-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-createCategory-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-createCategory-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-createCategory-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-createCategory-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-createCategory-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-createCategory-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-createCategory-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-createCategory-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-createCategory-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-createCategory-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-createCategory-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X POST \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n -H \"Content-Type: application/json\" \\\n \"http://localhost/api/v2/teams/{teamID}/categories\" \\\n -d ''\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-createCategory-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String teamID = teamID_example; // String | Team ID\n        Object body = Object; // Object | \n\n        try {\n            Object result = apiInstance.createCategory(teamID, body);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#createCategory\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-createCategory-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String teamID = teamID_example; // String | Team ID\n        Object body = Object; // Object | \n\n        try {\n            Object result = apiInstance.createCategory(teamID, body);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#createCategory\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-createCategory-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-createCategory-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *teamID = teamID_example; // Team ID (default to null)\nObject *body = Object; // \n\n[apiInstance createCategoryWith:teamID\n    body:body\n              completionHandler: ^(Object output, NSError* error) {\n    if (output) {\n        NSLog(@\"%@\", output);\n    }\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-createCategory-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar teamID = teamID_example; // {String} Team ID\nvar body = Object; // {Object} \n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully. Returned data: ' + data);\n  }\n};\napi.createCategory(teamID, body, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-createCategory-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-createCategory-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class createCategoryExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var teamID = teamID_example;  // String | Team ID (default to null)\n            var body = Object;  // Object | \n\n            try {\n                Object result = apiInstance.createCategory(teamID, body);\n                Debug.WriteLine(result);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.createCategory: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-createCategory-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$teamID = teamID_example; // String | Team ID\n$body = Object; // Object | \n\ntry {\n    $result = $api_instance->createCategory($teamID, $body);\n    print_r($result);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->createCategory: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-createCategory-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $teamID = teamID_example; # String | Team ID\nmy $body = WWW::OPenAPIClient::Object::Object->new(); # Object | \n\neval {\n    my $result = $api_instance->createCategory(teamID => $teamID, body => $body);\n    print Dumper($result);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->createCategory: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-createCategory-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nteamID = teamID_example # String | Team ID (default to null)\nbody = Object # Object | \n\ntry:\n    api_response = api_instance.create_category(teamID, body)\n    pprint(api_response)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->createCategory: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-createCategory-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let teamID = teamID_example; // String\n    let body = Object; // Object\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.createCategory(teamID, body, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">teamID*</td>\n<td>\n\n\n    <div id=\"d2e199_createCategory_teamID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nTeam ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n                            <div class=\"methodsubtabletitle\">Body parameters</div>\n                            <table id=\"methodsubtable\">\n                              <tr>\n                                <th width=\"150px\">Name</th>\n                                <th>Description</th>\n                              </tr>\n                                <tr><td style=\"width:150px;\">body <span style=\"color:red;\">*</span></td>\n<td>\n<p class=\"marked\">category to create</p>\n<script>\n$(document).ready(function() {\n  var schemaWrapper = {\n  \"description\" : \"category to create\",\n  \"content\" : {\n    \"application/json\" : {\n      \"schema\" : {\n        \"$ref\" : \"#/components/schemas/Category\"\n      }\n    }\n  },\n  \"required\" : true\n};\n\n  var schema = findNode('schema',schemaWrapper).schema;\n  if (!schema) {\n    schema = schemaWrapper.schema;\n  }\n  if (schema.$ref != null) {\n    schema = defsParser.$refs.get(schema.$ref);\n  } else {\n    schemaWrapper.definitions = Object.assign({}, defs);\n    $RefParser.dereference(schemaWrapper).catch(function(err) {\n      console.log(err);\n    });\n  }\n\n  var view = new JSONSchemaView(schema,2,{isBodyParam: true});\n  var result = $('#d2e199_createCategory_body');\n  result.empty();\n  result.append(view.render());\n});\n</script>\n<div id=\"d2e199_createCategory_body\"></div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-createCategory-title-200\"></h3>\n                            <p id=\"examples-Default-createCategory-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-createCategory-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-createCategory-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-createCategory-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-createCategory-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-createCategory-200-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-createCategory-200-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-createCategory-200-schema\">\n                                  <div id=\"responses-Default-createCategory-schema-200\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=Category is a board category, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-createCategory-200-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-createCategory-schema-200');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-createCategory-200-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-createCategory-title-default\"></h3>\n                            <p id=\"examples-Default-createCategory-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-createCategory-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-createCategory-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-createCategory-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-createCategory-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-createCategory-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-createCategory-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-createCategory-default-schema\">\n                                  <div id=\"responses-Default-createCategory-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-createCategory-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-createCategory-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-createCategory-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-createSubscription\">\n                      <article id=\"api-Default-createSubscription-0\" data-group=\"User\" data-name=\"createSubscription\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>createSubscription</h1>\n                          <p>Creates a subscription to a block for a user. The user will receive change notifications for the block.</p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\"></p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"post\"><code><span class=\"pln\">/subscriptions</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-createSubscription-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-createSubscription-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-createSubscription-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-createSubscription-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-createSubscription-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-createSubscription-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-createSubscription-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-createSubscription-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-createSubscription-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-createSubscription-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-createSubscription-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-createSubscription-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-createSubscription-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X POST \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n -H \"Content-Type: application/json\" \\\n \"http://localhost/api/v2/subscriptions\" \\\n -d ''\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-createSubscription-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        Object body = Object; // Object | \n\n        try {\n            Object result = apiInstance.createSubscription(body);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#createSubscription\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-createSubscription-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        Object body = Object; // Object | \n\n        try {\n            Object result = apiInstance.createSubscription(body);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#createSubscription\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-createSubscription-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-createSubscription-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nObject *body = Object; // \n\n// Creates a subscription to a block for a user. The user will receive change notifications for the block.\n[apiInstance createSubscriptionWith:body\n              completionHandler: ^(Object output, NSError* error) {\n    if (output) {\n        NSLog(@\"%@\", output);\n    }\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-createSubscription-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar body = Object; // {Object} \n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully. Returned data: ' + data);\n  }\n};\napi.createSubscription(body, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-createSubscription-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-createSubscription-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class createSubscriptionExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var body = Object;  // Object | \n\n            try {\n                // Creates a subscription to a block for a user. The user will receive change notifications for the block.\n                Object result = apiInstance.createSubscription(body);\n                Debug.WriteLine(result);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.createSubscription: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-createSubscription-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$body = Object; // Object | \n\ntry {\n    $result = $api_instance->createSubscription($body);\n    print_r($result);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->createSubscription: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-createSubscription-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $body = WWW::OPenAPIClient::Object::Object->new(); # Object | \n\neval {\n    my $result = $api_instance->createSubscription(body => $body);\n    print Dumper($result);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->createSubscription: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-createSubscription-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nbody = Object # Object | \n\ntry:\n    # Creates a subscription to a block for a user. The user will receive change notifications for the block.\n    api_response = api_instance.create_subscription(body)\n    pprint(api_response)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->createSubscription: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-createSubscription-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let body = Object; // Object\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.createSubscription(body, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n\n\n                            <div class=\"methodsubtabletitle\">Body parameters</div>\n                            <table id=\"methodsubtable\">\n                              <tr>\n                                <th width=\"150px\">Name</th>\n                                <th>Description</th>\n                              </tr>\n                                <tr><td style=\"width:150px;\">body <span style=\"color:red;\">*</span></td>\n<td>\n<p class=\"marked\">subscription definition</p>\n<script>\n$(document).ready(function() {\n  var schemaWrapper = {\n  \"description\" : \"subscription definition\",\n  \"content\" : {\n    \"application/json\" : {\n      \"schema\" : {\n        \"$ref\" : \"#/components/schemas/Subscription\"\n      }\n    }\n  },\n  \"required\" : true\n};\n\n  var schema = findNode('schema',schemaWrapper).schema;\n  if (!schema) {\n    schema = schemaWrapper.schema;\n  }\n  if (schema.$ref != null) {\n    schema = defsParser.$refs.get(schema.$ref);\n  } else {\n    schemaWrapper.definitions = Object.assign({}, defs);\n    $RefParser.dereference(schemaWrapper).catch(function(err) {\n      console.log(err);\n    });\n  }\n\n  var view = new JSONSchemaView(schema,2,{isBodyParam: true});\n  var result = $('#d2e199_createSubscription_body');\n  result.empty();\n  result.append(view.render());\n});\n</script>\n<div id=\"d2e199_createSubscription_body\"></div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-createSubscription-title-200\"></h3>\n                            <p id=\"examples-Default-createSubscription-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-createSubscription-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-createSubscription-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-createSubscription-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-createSubscription-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-createSubscription-200-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-createSubscription-200-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-createSubscription-200-schema\">\n                                  <div id=\"responses-Default-createSubscription-schema-200\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=User is a user, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-createSubscription-200-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-createSubscription-schema-200');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-createSubscription-200-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-createSubscription-title-default\"></h3>\n                            <p id=\"examples-Default-createSubscription-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-createSubscription-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-createSubscription-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-createSubscription-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-createSubscription-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-createSubscription-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-createSubscription-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-createSubscription-default-schema\">\n                                  <div id=\"responses-Default-createSubscription-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-createSubscription-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-createSubscription-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-createSubscription-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-deleteBlock\">\n                      <article id=\"api-Default-deleteBlock-0\" data-group=\"User\" data-name=\"deleteBlock\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>deleteBlock</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Deletes a block</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"delete\"><code><span class=\"pln\">/boards/{boardID}/blocks/{blockID}</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-deleteBlock-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-deleteBlock-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-deleteBlock-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-deleteBlock-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-deleteBlock-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-deleteBlock-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-deleteBlock-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-deleteBlock-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-deleteBlock-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-deleteBlock-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-deleteBlock-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-deleteBlock-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-deleteBlock-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X DELETE \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n \"http://localhost/api/v2/boards/{boardID}/blocks/{blockID}?disable_notify=\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-deleteBlock-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String boardID = boardID_example; // String | Board ID\n        String blockID = blockID_example; // String | ID of block to delete\n        oas_any_type_not_mapped disableNotify = ; // oas_any_type_not_mapped | Disables notifications (for bulk deletion)\n\n        try {\n            apiInstance.deleteBlock(boardID, blockID, disableNotify);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#deleteBlock\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-deleteBlock-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String boardID = boardID_example; // String | Board ID\n        String blockID = blockID_example; // String | ID of block to delete\n        oas_any_type_not_mapped disableNotify = ; // oas_any_type_not_mapped | Disables notifications (for bulk deletion)\n\n        try {\n            apiInstance.deleteBlock(boardID, blockID, disableNotify);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#deleteBlock\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-deleteBlock-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-deleteBlock-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *boardID = boardID_example; // Board ID (default to null)\nString *blockID = blockID_example; // ID of block to delete (default to null)\noas_any_type_not_mapped *disableNotify = ; // Disables notifications (for bulk deletion) (optional) (default to null)\n\n[apiInstance deleteBlockWith:boardID\n    blockID:blockID\n    disableNotify:disableNotify\n              completionHandler: ^(NSError* error) {\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-deleteBlock-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar boardID = boardID_example; // {String} Board ID\nvar blockID = blockID_example; // {String} ID of block to delete\nvar opts = {\n  'disableNotify':  // {oas_any_type_not_mapped} Disables notifications (for bulk deletion)\n};\n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully.');\n  }\n};\napi.deleteBlock(boardID, blockID, opts, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-deleteBlock-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-deleteBlock-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class deleteBlockExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var boardID = boardID_example;  // String | Board ID (default to null)\n            var blockID = blockID_example;  // String | ID of block to delete (default to null)\n            var disableNotify = new oas_any_type_not_mapped(); // oas_any_type_not_mapped | Disables notifications (for bulk deletion) (optional)  (default to null)\n\n            try {\n                apiInstance.deleteBlock(boardID, blockID, disableNotify);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.deleteBlock: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-deleteBlock-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$boardID = boardID_example; // String | Board ID\n$blockID = blockID_example; // String | ID of block to delete\n$disableNotify = ; // oas_any_type_not_mapped | Disables notifications (for bulk deletion)\n\ntry {\n    $api_instance->deleteBlock($boardID, $blockID, $disableNotify);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->deleteBlock: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-deleteBlock-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $boardID = boardID_example; # String | Board ID\nmy $blockID = blockID_example; # String | ID of block to delete\nmy $disableNotify = ; # oas_any_type_not_mapped | Disables notifications (for bulk deletion)\n\neval {\n    $api_instance->deleteBlock(boardID => $boardID, blockID => $blockID, disableNotify => $disableNotify);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->deleteBlock: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-deleteBlock-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nboardID = boardID_example # String | Board ID (default to null)\nblockID = blockID_example # String | ID of block to delete (default to null)\ndisableNotify =  # oas_any_type_not_mapped | Disables notifications (for bulk deletion) (optional) (default to null)\n\ntry:\n    api_instance.delete_block(boardID, blockID, disableNotify=disableNotify)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->deleteBlock: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-deleteBlock-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let boardID = boardID_example; // String\n    let blockID = blockID_example; // String\n    let disableNotify = ; // oas_any_type_not_mapped\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.deleteBlock(boardID, blockID, disableNotify, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">boardID*</td>\n<td>\n\n\n    <div id=\"d2e199_deleteBlock_boardID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nBoard ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                                  <tr><td style=\"width:150px;\">blockID*</td>\n<td>\n\n\n    <div id=\"d2e199_deleteBlock_blockID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nID of block to delete\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n\n                            <div class=\"methodsubtabletitle\">Query parameters</div>\n                            <table id=\"methodsubtable\">\n                              <tr>\n                                <th width=\"150px\">Name</th>\n                                <th>Description</th>\n                              </tr>\n                                <tr><td style=\"width:150px;\">disable_notify</td>\n<td>\n\n\n    <div id=\"d2e199_deleteBlock_disableNotify\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    oas_any_type_not_mapped\n                </span>\n\n                    <div class=\"inner description marked\">\nDisables notifications (for bulk deletion)\n                    </div>\n            </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-deleteBlock-title-200\"></h3>\n                            <p id=\"examples-Default-deleteBlock-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-deleteBlock-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-deleteBlock-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-deleteBlock-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-deleteBlock-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-deleteBlock-200-wrapper\" style='margin-bottom: 10px;'>\n                            </div>\n                            <h3 id=\"examples-Default-deleteBlock-title-404\"></h3>\n                            <p id=\"examples-Default-deleteBlock-description-404\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault404_description = `block not found`;\n                              var responseDefault404_description_break = responseDefault404_description.indexOf('\\n');\n                              if (responseDefault404_description_break == -1) {\n                                $(\"#examples-Default-deleteBlock-title-404\").text(\"Status: 404 - \" + responseDefault404_description);\n                              } else {\n                                $(\"#examples-Default-deleteBlock-title-404\").text(\"Status: 404 - \" + responseDefault404_description.substring(0, responseDefault404_description_break));\n                                $(\"#examples-Default-deleteBlock-description-404\").html(responseDefault404_description.substring(responseDefault404_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-deleteBlock-404\" class=\"nav nav-tabs nav-tabs-examples\" >\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-deleteBlock-404-wrapper\" style='margin-bottom: 10px;'>\n                            </div>\n                            <h3 id=\"examples-Default-deleteBlock-title-default\"></h3>\n                            <p id=\"examples-Default-deleteBlock-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-deleteBlock-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-deleteBlock-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-deleteBlock-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-deleteBlock-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-deleteBlock-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-deleteBlock-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-deleteBlock-default-schema\">\n                                  <div id=\"responses-Default-deleteBlock-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-deleteBlock-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-deleteBlock-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-deleteBlock-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-deleteBoard\">\n                      <article id=\"api-Default-deleteBoard-0\" data-group=\"User\" data-name=\"deleteBoard\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>deleteBoard</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Removes a board</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"delete\"><code><span class=\"pln\">/boards/{boardID}</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-deleteBoard-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-deleteBoard-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-deleteBoard-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-deleteBoard-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-deleteBoard-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-deleteBoard-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-deleteBoard-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-deleteBoard-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-deleteBoard-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-deleteBoard-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-deleteBoard-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-deleteBoard-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-deleteBoard-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X DELETE \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n \"http://localhost/api/v2/boards/{boardID}\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-deleteBoard-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String boardID = boardID_example; // String | Board ID\n\n        try {\n            apiInstance.deleteBoard(boardID);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#deleteBoard\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-deleteBoard-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String boardID = boardID_example; // String | Board ID\n\n        try {\n            apiInstance.deleteBoard(boardID);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#deleteBoard\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-deleteBoard-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-deleteBoard-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *boardID = boardID_example; // Board ID (default to null)\n\n[apiInstance deleteBoardWith:boardID\n              completionHandler: ^(NSError* error) {\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-deleteBoard-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar boardID = boardID_example; // {String} Board ID\n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully.');\n  }\n};\napi.deleteBoard(boardID, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-deleteBoard-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-deleteBoard-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class deleteBoardExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var boardID = boardID_example;  // String | Board ID (default to null)\n\n            try {\n                apiInstance.deleteBoard(boardID);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.deleteBoard: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-deleteBoard-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$boardID = boardID_example; // String | Board ID\n\ntry {\n    $api_instance->deleteBoard($boardID);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->deleteBoard: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-deleteBoard-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $boardID = boardID_example; # String | Board ID\n\neval {\n    $api_instance->deleteBoard(boardID => $boardID);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->deleteBoard: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-deleteBoard-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nboardID = boardID_example # String | Board ID (default to null)\n\ntry:\n    api_instance.delete_board(boardID)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->deleteBoard: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-deleteBoard-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let boardID = boardID_example; // String\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.deleteBoard(boardID, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">boardID*</td>\n<td>\n\n\n    <div id=\"d2e199_deleteBoard_boardID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nBoard ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-deleteBoard-title-200\"></h3>\n                            <p id=\"examples-Default-deleteBoard-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-deleteBoard-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-deleteBoard-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-deleteBoard-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-deleteBoard-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-deleteBoard-200-wrapper\" style='margin-bottom: 10px;'>\n                            </div>\n                            <h3 id=\"examples-Default-deleteBoard-title-404\"></h3>\n                            <p id=\"examples-Default-deleteBoard-description-404\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault404_description = `board not found`;\n                              var responseDefault404_description_break = responseDefault404_description.indexOf('\\n');\n                              if (responseDefault404_description_break == -1) {\n                                $(\"#examples-Default-deleteBoard-title-404\").text(\"Status: 404 - \" + responseDefault404_description);\n                              } else {\n                                $(\"#examples-Default-deleteBoard-title-404\").text(\"Status: 404 - \" + responseDefault404_description.substring(0, responseDefault404_description_break));\n                                $(\"#examples-Default-deleteBoard-description-404\").html(responseDefault404_description.substring(responseDefault404_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-deleteBoard-404\" class=\"nav nav-tabs nav-tabs-examples\" >\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-deleteBoard-404-wrapper\" style='margin-bottom: 10px;'>\n                            </div>\n                            <h3 id=\"examples-Default-deleteBoard-title-default\"></h3>\n                            <p id=\"examples-Default-deleteBoard-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-deleteBoard-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-deleteBoard-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-deleteBoard-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-deleteBoard-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-deleteBoard-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-deleteBoard-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-deleteBoard-default-schema\">\n                                  <div id=\"responses-Default-deleteBoard-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-deleteBoard-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-deleteBoard-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-deleteBoard-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-deleteBoardsAndBlocks\">\n                      <article id=\"api-Default-deleteBoardsAndBlocks-0\" data-group=\"User\" data-name=\"deleteBoardsAndBlocks\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>deleteBoardsAndBlocks</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Deletes boards and blocks</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"delete\"><code><span class=\"pln\">/boards-and-blocks</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-deleteBoardsAndBlocks-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-deleteBoardsAndBlocks-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-deleteBoardsAndBlocks-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-deleteBoardsAndBlocks-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-deleteBoardsAndBlocks-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-deleteBoardsAndBlocks-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-deleteBoardsAndBlocks-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-deleteBoardsAndBlocks-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-deleteBoardsAndBlocks-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-deleteBoardsAndBlocks-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-deleteBoardsAndBlocks-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-deleteBoardsAndBlocks-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-deleteBoardsAndBlocks-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X DELETE \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n -H \"Content-Type: application/json\" \\\n \"http://localhost/api/v2/boards-and-blocks\" \\\n -d ''\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-deleteBoardsAndBlocks-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        Object body = Object; // Object | \n\n        try {\n            apiInstance.deleteBoardsAndBlocks(body);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#deleteBoardsAndBlocks\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-deleteBoardsAndBlocks-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        Object body = Object; // Object | \n\n        try {\n            apiInstance.deleteBoardsAndBlocks(body);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#deleteBoardsAndBlocks\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-deleteBoardsAndBlocks-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-deleteBoardsAndBlocks-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nObject *body = Object; // \n\n[apiInstance deleteBoardsAndBlocksWith:body\n              completionHandler: ^(NSError* error) {\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-deleteBoardsAndBlocks-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar body = Object; // {Object} \n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully.');\n  }\n};\napi.deleteBoardsAndBlocks(body, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-deleteBoardsAndBlocks-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-deleteBoardsAndBlocks-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class deleteBoardsAndBlocksExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var body = Object;  // Object | \n\n            try {\n                apiInstance.deleteBoardsAndBlocks(body);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.deleteBoardsAndBlocks: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-deleteBoardsAndBlocks-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$body = Object; // Object | \n\ntry {\n    $api_instance->deleteBoardsAndBlocks($body);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->deleteBoardsAndBlocks: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-deleteBoardsAndBlocks-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $body = WWW::OPenAPIClient::Object::Object->new(); # Object | \n\neval {\n    $api_instance->deleteBoardsAndBlocks(body => $body);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->deleteBoardsAndBlocks: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-deleteBoardsAndBlocks-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nbody = Object # Object | \n\ntry:\n    api_instance.delete_boards_and_blocks(body)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->deleteBoardsAndBlocks: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-deleteBoardsAndBlocks-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let body = Object; // Object\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.deleteBoardsAndBlocks(body, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n\n\n                            <div class=\"methodsubtabletitle\">Body parameters</div>\n                            <table id=\"methodsubtable\">\n                              <tr>\n                                <th width=\"150px\">Name</th>\n                                <th>Description</th>\n                              </tr>\n                                <tr><td style=\"width:150px;\">body <span style=\"color:red;\">*</span></td>\n<td>\n<p class=\"marked\">the boards and blocks to delete</p>\n<script>\n$(document).ready(function() {\n  var schemaWrapper = {\n  \"description\" : \"the boards and blocks to delete\",\n  \"content\" : {\n    \"application/json\" : {\n      \"schema\" : {\n        \"$ref\" : \"#/components/schemas/DeleteBoardsAndBlocks\"\n      }\n    }\n  },\n  \"required\" : true\n};\n\n  var schema = findNode('schema',schemaWrapper).schema;\n  if (!schema) {\n    schema = schemaWrapper.schema;\n  }\n  if (schema.$ref != null) {\n    schema = defsParser.$refs.get(schema.$ref);\n  } else {\n    schemaWrapper.definitions = Object.assign({}, defs);\n    $RefParser.dereference(schemaWrapper).catch(function(err) {\n      console.log(err);\n    });\n  }\n\n  var view = new JSONSchemaView(schema,2,{isBodyParam: true});\n  var result = $('#d2e199_deleteBoardsAndBlocks_body');\n  result.empty();\n  result.append(view.render());\n});\n</script>\n<div id=\"d2e199_deleteBoardsAndBlocks_body\"></div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-deleteBoardsAndBlocks-title-200\"></h3>\n                            <p id=\"examples-Default-deleteBoardsAndBlocks-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-deleteBoardsAndBlocks-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-deleteBoardsAndBlocks-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-deleteBoardsAndBlocks-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-deleteBoardsAndBlocks-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-deleteBoardsAndBlocks-200-wrapper\" style='margin-bottom: 10px;'>\n                            </div>\n                            <h3 id=\"examples-Default-deleteBoardsAndBlocks-title-default\"></h3>\n                            <p id=\"examples-Default-deleteBoardsAndBlocks-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-deleteBoardsAndBlocks-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-deleteBoardsAndBlocks-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-deleteBoardsAndBlocks-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-deleteBoardsAndBlocks-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-deleteBoardsAndBlocks-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-deleteBoardsAndBlocks-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-deleteBoardsAndBlocks-default-schema\">\n                                  <div id=\"responses-Default-deleteBoardsAndBlocks-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-deleteBoardsAndBlocks-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-deleteBoardsAndBlocks-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-deleteBoardsAndBlocks-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-deleteCategory\">\n                      <article id=\"api-Default-deleteCategory-0\" data-group=\"User\" data-name=\"deleteCategory\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>deleteCategory</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Delete a category</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"delete\"><code><span class=\"pln\">/teams/{teamID}/categories/{categoryID}</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-deleteCategory-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-deleteCategory-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-deleteCategory-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-deleteCategory-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-deleteCategory-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-deleteCategory-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-deleteCategory-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-deleteCategory-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-deleteCategory-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-deleteCategory-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-deleteCategory-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-deleteCategory-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-deleteCategory-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X DELETE \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n \"http://localhost/api/v2/teams/{teamID}/categories/{categoryID}\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-deleteCategory-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String teamID = teamID_example; // String | Team ID\n        String categoryID = categoryID_example; // String | Category ID\n\n        try {\n            apiInstance.deleteCategory(teamID, categoryID);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#deleteCategory\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-deleteCategory-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String teamID = teamID_example; // String | Team ID\n        String categoryID = categoryID_example; // String | Category ID\n\n        try {\n            apiInstance.deleteCategory(teamID, categoryID);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#deleteCategory\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-deleteCategory-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-deleteCategory-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *teamID = teamID_example; // Team ID (default to null)\nString *categoryID = categoryID_example; // Category ID (default to null)\n\n[apiInstance deleteCategoryWith:teamID\n    categoryID:categoryID\n              completionHandler: ^(NSError* error) {\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-deleteCategory-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar teamID = teamID_example; // {String} Team ID\nvar categoryID = categoryID_example; // {String} Category ID\n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully.');\n  }\n};\napi.deleteCategory(teamID, categoryID, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-deleteCategory-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-deleteCategory-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class deleteCategoryExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var teamID = teamID_example;  // String | Team ID (default to null)\n            var categoryID = categoryID_example;  // String | Category ID (default to null)\n\n            try {\n                apiInstance.deleteCategory(teamID, categoryID);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.deleteCategory: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-deleteCategory-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$teamID = teamID_example; // String | Team ID\n$categoryID = categoryID_example; // String | Category ID\n\ntry {\n    $api_instance->deleteCategory($teamID, $categoryID);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->deleteCategory: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-deleteCategory-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $teamID = teamID_example; # String | Team ID\nmy $categoryID = categoryID_example; # String | Category ID\n\neval {\n    $api_instance->deleteCategory(teamID => $teamID, categoryID => $categoryID);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->deleteCategory: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-deleteCategory-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nteamID = teamID_example # String | Team ID (default to null)\ncategoryID = categoryID_example # String | Category ID (default to null)\n\ntry:\n    api_instance.delete_category(teamID, categoryID)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->deleteCategory: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-deleteCategory-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let teamID = teamID_example; // String\n    let categoryID = categoryID_example; // String\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.deleteCategory(teamID, categoryID, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">teamID*</td>\n<td>\n\n\n    <div id=\"d2e199_deleteCategory_teamID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nTeam ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                                  <tr><td style=\"width:150px;\">categoryID*</td>\n<td>\n\n\n    <div id=\"d2e199_deleteCategory_categoryID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nCategory ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-deleteCategory-title-200\"></h3>\n                            <p id=\"examples-Default-deleteCategory-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-deleteCategory-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-deleteCategory-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-deleteCategory-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-deleteCategory-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-deleteCategory-200-wrapper\" style='margin-bottom: 10px;'>\n                            </div>\n                            <h3 id=\"examples-Default-deleteCategory-title-default\"></h3>\n                            <p id=\"examples-Default-deleteCategory-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-deleteCategory-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-deleteCategory-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-deleteCategory-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-deleteCategory-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-deleteCategory-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-deleteCategory-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-deleteCategory-default-schema\">\n                                  <div id=\"responses-Default-deleteCategory-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-deleteCategory-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-deleteCategory-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-deleteCategory-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-deleteMember\">\n                      <article id=\"api-Default-deleteMember-0\" data-group=\"User\" data-name=\"deleteMember\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>deleteMember</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Deletes a member from a board</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"delete\"><code><span class=\"pln\">/boards/{boardID}/members/{userID}</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-deleteMember-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-deleteMember-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-deleteMember-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-deleteMember-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-deleteMember-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-deleteMember-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-deleteMember-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-deleteMember-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-deleteMember-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-deleteMember-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-deleteMember-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-deleteMember-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-deleteMember-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X DELETE \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n \"http://localhost/api/v2/boards/{boardID}/members/{userID}\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-deleteMember-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String boardID = boardID_example; // String | Board ID\n        String userID = userID_example; // String | User ID\n\n        try {\n            apiInstance.deleteMember(boardID, userID);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#deleteMember\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-deleteMember-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String boardID = boardID_example; // String | Board ID\n        String userID = userID_example; // String | User ID\n\n        try {\n            apiInstance.deleteMember(boardID, userID);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#deleteMember\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-deleteMember-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-deleteMember-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *boardID = boardID_example; // Board ID (default to null)\nString *userID = userID_example; // User ID (default to null)\n\n[apiInstance deleteMemberWith:boardID\n    userID:userID\n              completionHandler: ^(NSError* error) {\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-deleteMember-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar boardID = boardID_example; // {String} Board ID\nvar userID = userID_example; // {String} User ID\n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully.');\n  }\n};\napi.deleteMember(boardID, userID, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-deleteMember-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-deleteMember-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class deleteMemberExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var boardID = boardID_example;  // String | Board ID (default to null)\n            var userID = userID_example;  // String | User ID (default to null)\n\n            try {\n                apiInstance.deleteMember(boardID, userID);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.deleteMember: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-deleteMember-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$boardID = boardID_example; // String | Board ID\n$userID = userID_example; // String | User ID\n\ntry {\n    $api_instance->deleteMember($boardID, $userID);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->deleteMember: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-deleteMember-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $boardID = boardID_example; # String | Board ID\nmy $userID = userID_example; # String | User ID\n\neval {\n    $api_instance->deleteMember(boardID => $boardID, userID => $userID);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->deleteMember: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-deleteMember-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nboardID = boardID_example # String | Board ID (default to null)\nuserID = userID_example # String | User ID (default to null)\n\ntry:\n    api_instance.delete_member(boardID, userID)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->deleteMember: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-deleteMember-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let boardID = boardID_example; // String\n    let userID = userID_example; // String\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.deleteMember(boardID, userID, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">boardID*</td>\n<td>\n\n\n    <div id=\"d2e199_deleteMember_boardID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nBoard ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                                  <tr><td style=\"width:150px;\">userID*</td>\n<td>\n\n\n    <div id=\"d2e199_deleteMember_userID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nUser ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-deleteMember-title-200\"></h3>\n                            <p id=\"examples-Default-deleteMember-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-deleteMember-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-deleteMember-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-deleteMember-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-deleteMember-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-deleteMember-200-wrapper\" style='margin-bottom: 10px;'>\n                            </div>\n                            <h3 id=\"examples-Default-deleteMember-title-404\"></h3>\n                            <p id=\"examples-Default-deleteMember-description-404\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault404_description = `board not found`;\n                              var responseDefault404_description_break = responseDefault404_description.indexOf('\\n');\n                              if (responseDefault404_description_break == -1) {\n                                $(\"#examples-Default-deleteMember-title-404\").text(\"Status: 404 - \" + responseDefault404_description);\n                              } else {\n                                $(\"#examples-Default-deleteMember-title-404\").text(\"Status: 404 - \" + responseDefault404_description.substring(0, responseDefault404_description_break));\n                                $(\"#examples-Default-deleteMember-description-404\").html(responseDefault404_description.substring(responseDefault404_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-deleteMember-404\" class=\"nav nav-tabs nav-tabs-examples\" >\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-deleteMember-404-wrapper\" style='margin-bottom: 10px;'>\n                            </div>\n                            <h3 id=\"examples-Default-deleteMember-title-default\"></h3>\n                            <p id=\"examples-Default-deleteMember-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-deleteMember-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-deleteMember-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-deleteMember-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-deleteMember-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-deleteMember-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-deleteMember-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-deleteMember-default-schema\">\n                                  <div id=\"responses-Default-deleteMember-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-deleteMember-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-deleteMember-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-deleteMember-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-deleteSubscription\">\n                      <article id=\"api-Default-deleteSubscription-0\" data-group=\"User\" data-name=\"deleteSubscription\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>deleteSubscription</h1>\n                          <p>Deletes a subscription a user has for a a block. The user will no longer receive change notifications for the block.</p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\"></p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"delete\"><code><span class=\"pln\">/subscriptions/{blockID}/{subscriberID}</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-deleteSubscription-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-deleteSubscription-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-deleteSubscription-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-deleteSubscription-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-deleteSubscription-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-deleteSubscription-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-deleteSubscription-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-deleteSubscription-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-deleteSubscription-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-deleteSubscription-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-deleteSubscription-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-deleteSubscription-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-deleteSubscription-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X DELETE \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n \"http://localhost/api/v2/subscriptions/{blockID}/{subscriberID}\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-deleteSubscription-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String blockID = blockID_example; // String | Block ID\n        String subscriberID = subscriberID_example; // String | Subscriber ID\n\n        try {\n            apiInstance.deleteSubscription(blockID, subscriberID);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#deleteSubscription\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-deleteSubscription-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String blockID = blockID_example; // String | Block ID\n        String subscriberID = subscriberID_example; // String | Subscriber ID\n\n        try {\n            apiInstance.deleteSubscription(blockID, subscriberID);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#deleteSubscription\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-deleteSubscription-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-deleteSubscription-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *blockID = blockID_example; // Block ID (default to null)\nString *subscriberID = subscriberID_example; // Subscriber ID (default to null)\n\n// Deletes a subscription a user has for a a block. The user will no longer receive change notifications for the block.\n[apiInstance deleteSubscriptionWith:blockID\n    subscriberID:subscriberID\n              completionHandler: ^(NSError* error) {\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-deleteSubscription-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar blockID = blockID_example; // {String} Block ID\nvar subscriberID = subscriberID_example; // {String} Subscriber ID\n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully.');\n  }\n};\napi.deleteSubscription(blockID, subscriberID, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-deleteSubscription-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-deleteSubscription-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class deleteSubscriptionExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var blockID = blockID_example;  // String | Block ID (default to null)\n            var subscriberID = subscriberID_example;  // String | Subscriber ID (default to null)\n\n            try {\n                // Deletes a subscription a user has for a a block. The user will no longer receive change notifications for the block.\n                apiInstance.deleteSubscription(blockID, subscriberID);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.deleteSubscription: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-deleteSubscription-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$blockID = blockID_example; // String | Block ID\n$subscriberID = subscriberID_example; // String | Subscriber ID\n\ntry {\n    $api_instance->deleteSubscription($blockID, $subscriberID);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->deleteSubscription: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-deleteSubscription-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $blockID = blockID_example; # String | Block ID\nmy $subscriberID = subscriberID_example; # String | Subscriber ID\n\neval {\n    $api_instance->deleteSubscription(blockID => $blockID, subscriberID => $subscriberID);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->deleteSubscription: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-deleteSubscription-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nblockID = blockID_example # String | Block ID (default to null)\nsubscriberID = subscriberID_example # String | Subscriber ID (default to null)\n\ntry:\n    # Deletes a subscription a user has for a a block. The user will no longer receive change notifications for the block.\n    api_instance.delete_subscription(blockID, subscriberID)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->deleteSubscription: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-deleteSubscription-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let blockID = blockID_example; // String\n    let subscriberID = subscriberID_example; // String\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.deleteSubscription(blockID, subscriberID, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">blockID*</td>\n<td>\n\n\n    <div id=\"d2e199_deleteSubscription_blockID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nBlock ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                                  <tr><td style=\"width:150px;\">subscriberID*</td>\n<td>\n\n\n    <div id=\"d2e199_deleteSubscription_subscriberID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nSubscriber ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-deleteSubscription-title-200\"></h3>\n                            <p id=\"examples-Default-deleteSubscription-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-deleteSubscription-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-deleteSubscription-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-deleteSubscription-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-deleteSubscription-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-deleteSubscription-200-wrapper\" style='margin-bottom: 10px;'>\n                            </div>\n                            <h3 id=\"examples-Default-deleteSubscription-title-default\"></h3>\n                            <p id=\"examples-Default-deleteSubscription-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-deleteSubscription-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-deleteSubscription-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-deleteSubscription-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-deleteSubscription-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-deleteSubscription-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-deleteSubscription-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-deleteSubscription-default-schema\">\n                                  <div id=\"responses-Default-deleteSubscription-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-deleteSubscription-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-deleteSubscription-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-deleteSubscription-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-duplicateBlock\">\n                      <article id=\"api-Default-duplicateBlock-0\" data-group=\"User\" data-name=\"duplicateBlock\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>duplicateBlock</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Returns the new created blocks</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"post\"><code><span class=\"pln\">/boards/{boardID}/blocks/{blockID}/duplicate</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-duplicateBlock-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-duplicateBlock-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-duplicateBlock-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-duplicateBlock-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-duplicateBlock-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-duplicateBlock-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-duplicateBlock-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-duplicateBlock-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-duplicateBlock-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-duplicateBlock-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-duplicateBlock-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-duplicateBlock-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-duplicateBlock-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X POST \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n \"http://localhost/api/v2/boards/{boardID}/blocks/{blockID}/duplicate\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-duplicateBlock-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String boardID = boardID_example; // String | Board ID\n        String blockID = blockID_example; // String | Block ID\n\n        try {\n            array[Object] result = apiInstance.duplicateBlock(boardID, blockID);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#duplicateBlock\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-duplicateBlock-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String boardID = boardID_example; // String | Board ID\n        String blockID = blockID_example; // String | Block ID\n\n        try {\n            array[Object] result = apiInstance.duplicateBlock(boardID, blockID);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#duplicateBlock\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-duplicateBlock-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-duplicateBlock-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *boardID = boardID_example; // Board ID (default to null)\nString *blockID = blockID_example; // Block ID (default to null)\n\n[apiInstance duplicateBlockWith:boardID\n    blockID:blockID\n              completionHandler: ^(array[Object] output, NSError* error) {\n    if (output) {\n        NSLog(@\"%@\", output);\n    }\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-duplicateBlock-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar boardID = boardID_example; // {String} Board ID\nvar blockID = blockID_example; // {String} Block ID\n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully. Returned data: ' + data);\n  }\n};\napi.duplicateBlock(boardID, blockID, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-duplicateBlock-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-duplicateBlock-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class duplicateBlockExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var boardID = boardID_example;  // String | Board ID (default to null)\n            var blockID = blockID_example;  // String | Block ID (default to null)\n\n            try {\n                array[Object] result = apiInstance.duplicateBlock(boardID, blockID);\n                Debug.WriteLine(result);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.duplicateBlock: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-duplicateBlock-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$boardID = boardID_example; // String | Board ID\n$blockID = blockID_example; // String | Block ID\n\ntry {\n    $result = $api_instance->duplicateBlock($boardID, $blockID);\n    print_r($result);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->duplicateBlock: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-duplicateBlock-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $boardID = boardID_example; # String | Board ID\nmy $blockID = blockID_example; # String | Block ID\n\neval {\n    my $result = $api_instance->duplicateBlock(boardID => $boardID, blockID => $blockID);\n    print Dumper($result);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->duplicateBlock: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-duplicateBlock-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nboardID = boardID_example # String | Board ID (default to null)\nblockID = blockID_example # String | Block ID (default to null)\n\ntry:\n    api_response = api_instance.duplicate_block(boardID, blockID)\n    pprint(api_response)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->duplicateBlock: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-duplicateBlock-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let boardID = boardID_example; // String\n    let blockID = blockID_example; // String\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.duplicateBlock(boardID, blockID, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">boardID*</td>\n<td>\n\n\n    <div id=\"d2e199_duplicateBlock_boardID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nBoard ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                                  <tr><td style=\"width:150px;\">blockID*</td>\n<td>\n\n\n    <div id=\"d2e199_duplicateBlock_blockID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nBlock ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-duplicateBlock-title-200\"></h3>\n                            <p id=\"examples-Default-duplicateBlock-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-duplicateBlock-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-duplicateBlock-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-duplicateBlock-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-duplicateBlock-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-duplicateBlock-200-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-duplicateBlock-200-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-duplicateBlock-200-schema\">\n                                  <div id=\"responses-Default-duplicateBlock-schema-200\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = ;\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-duplicateBlock-200-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-duplicateBlock-schema-200');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-duplicateBlock-200-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-duplicateBlock-title-404\"></h3>\n                            <p id=\"examples-Default-duplicateBlock-description-404\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault404_description = `board or block not found`;\n                              var responseDefault404_description_break = responseDefault404_description.indexOf('\\n');\n                              if (responseDefault404_description_break == -1) {\n                                $(\"#examples-Default-duplicateBlock-title-404\").text(\"Status: 404 - \" + responseDefault404_description);\n                              } else {\n                                $(\"#examples-Default-duplicateBlock-title-404\").text(\"Status: 404 - \" + responseDefault404_description.substring(0, responseDefault404_description_break));\n                                $(\"#examples-Default-duplicateBlock-description-404\").html(responseDefault404_description.substring(responseDefault404_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-duplicateBlock-404\" class=\"nav nav-tabs nav-tabs-examples\" >\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-duplicateBlock-404-wrapper\" style='margin-bottom: 10px;'>\n                            </div>\n                            <h3 id=\"examples-Default-duplicateBlock-title-default\"></h3>\n                            <p id=\"examples-Default-duplicateBlock-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-duplicateBlock-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-duplicateBlock-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-duplicateBlock-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-duplicateBlock-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-duplicateBlock-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-duplicateBlock-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-duplicateBlock-default-schema\">\n                                  <div id=\"responses-Default-duplicateBlock-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-duplicateBlock-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-duplicateBlock-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-duplicateBlock-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-duplicateBoard\">\n                      <article id=\"api-Default-duplicateBoard-0\" data-group=\"User\" data-name=\"duplicateBoard\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>duplicateBoard</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Returns the new created board and all the blocks</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"post\"><code><span class=\"pln\">/boards/{boardID}/duplicate</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-duplicateBoard-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-duplicateBoard-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-duplicateBoard-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-duplicateBoard-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-duplicateBoard-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-duplicateBoard-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-duplicateBoard-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-duplicateBoard-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-duplicateBoard-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-duplicateBoard-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-duplicateBoard-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-duplicateBoard-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-duplicateBoard-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X POST \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n \"http://localhost/api/v2/boards/{boardID}/duplicate\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-duplicateBoard-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String boardID = boardID_example; // String | Board ID\n\n        try {\n            Object result = apiInstance.duplicateBoard(boardID);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#duplicateBoard\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-duplicateBoard-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String boardID = boardID_example; // String | Board ID\n\n        try {\n            Object result = apiInstance.duplicateBoard(boardID);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#duplicateBoard\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-duplicateBoard-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-duplicateBoard-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *boardID = boardID_example; // Board ID (default to null)\n\n[apiInstance duplicateBoardWith:boardID\n              completionHandler: ^(Object output, NSError* error) {\n    if (output) {\n        NSLog(@\"%@\", output);\n    }\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-duplicateBoard-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar boardID = boardID_example; // {String} Board ID\n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully. Returned data: ' + data);\n  }\n};\napi.duplicateBoard(boardID, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-duplicateBoard-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-duplicateBoard-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class duplicateBoardExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var boardID = boardID_example;  // String | Board ID (default to null)\n\n            try {\n                Object result = apiInstance.duplicateBoard(boardID);\n                Debug.WriteLine(result);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.duplicateBoard: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-duplicateBoard-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$boardID = boardID_example; // String | Board ID\n\ntry {\n    $result = $api_instance->duplicateBoard($boardID);\n    print_r($result);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->duplicateBoard: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-duplicateBoard-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $boardID = boardID_example; # String | Board ID\n\neval {\n    my $result = $api_instance->duplicateBoard(boardID => $boardID);\n    print Dumper($result);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->duplicateBoard: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-duplicateBoard-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nboardID = boardID_example # String | Board ID (default to null)\n\ntry:\n    api_response = api_instance.duplicate_board(boardID)\n    pprint(api_response)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->duplicateBoard: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-duplicateBoard-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let boardID = boardID_example; // String\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.duplicateBoard(boardID, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">boardID*</td>\n<td>\n\n\n    <div id=\"d2e199_duplicateBoard_boardID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nBoard ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-duplicateBoard-title-200\"></h3>\n                            <p id=\"examples-Default-duplicateBoard-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-duplicateBoard-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-duplicateBoard-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-duplicateBoard-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-duplicateBoard-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-duplicateBoard-200-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-duplicateBoard-200-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-duplicateBoard-200-schema\">\n                                  <div id=\"responses-Default-duplicateBoard-schema-200\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=BoardsAndBlocks is used to operate over boards and blocks at the\nsame time, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-duplicateBoard-200-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-duplicateBoard-schema-200');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-duplicateBoard-200-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-duplicateBoard-title-404\"></h3>\n                            <p id=\"examples-Default-duplicateBoard-description-404\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault404_description = `board not found`;\n                              var responseDefault404_description_break = responseDefault404_description.indexOf('\\n');\n                              if (responseDefault404_description_break == -1) {\n                                $(\"#examples-Default-duplicateBoard-title-404\").text(\"Status: 404 - \" + responseDefault404_description);\n                              } else {\n                                $(\"#examples-Default-duplicateBoard-title-404\").text(\"Status: 404 - \" + responseDefault404_description.substring(0, responseDefault404_description_break));\n                                $(\"#examples-Default-duplicateBoard-description-404\").html(responseDefault404_description.substring(responseDefault404_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-duplicateBoard-404\" class=\"nav nav-tabs nav-tabs-examples\" >\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-duplicateBoard-404-wrapper\" style='margin-bottom: 10px;'>\n                            </div>\n                            <h3 id=\"examples-Default-duplicateBoard-title-default\"></h3>\n                            <p id=\"examples-Default-duplicateBoard-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-duplicateBoard-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-duplicateBoard-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-duplicateBoard-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-duplicateBoard-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-duplicateBoard-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-duplicateBoard-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-duplicateBoard-default-schema\">\n                                  <div id=\"responses-Default-duplicateBoard-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-duplicateBoard-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-duplicateBoard-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-duplicateBoard-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-getBlocks\">\n                      <article id=\"api-Default-getBlocks-0\" data-group=\"User\" data-name=\"getBlocks\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>getBlocks</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Returns blocks</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"get\"><code><span class=\"pln\">/boards/{boardID}/blocks</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-getBlocks-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getBlocks-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getBlocks-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-getBlocks-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-getBlocks-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getBlocks-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-getBlocks-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-getBlocks-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getBlocks-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getBlocks-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getBlocks-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getBlocks-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-getBlocks-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X GET \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n \"http://localhost/api/v2/boards/{boardID}/blocks?parent_id=parentId_example&type=type_example\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-getBlocks-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String boardID = boardID_example; // String | Board ID\n        String parentId = parentId_example; // String | ID of parent block, omit to specify all blocks\n        String type = type_example; // String | Type of blocks to return, omit to specify all types\n\n        try {\n            array[Object] result = apiInstance.getBlocks(boardID, parentId, type);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#getBlocks\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-getBlocks-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String boardID = boardID_example; // String | Board ID\n        String parentId = parentId_example; // String | ID of parent block, omit to specify all blocks\n        String type = type_example; // String | Type of blocks to return, omit to specify all types\n\n        try {\n            array[Object] result = apiInstance.getBlocks(boardID, parentId, type);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#getBlocks\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-getBlocks-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-getBlocks-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *boardID = boardID_example; // Board ID (default to null)\nString *parentId = parentId_example; // ID of parent block, omit to specify all blocks (optional) (default to null)\nString *type = type_example; // Type of blocks to return, omit to specify all types (optional) (default to null)\n\n[apiInstance getBlocksWith:boardID\n    parentId:parentId\n    type:type\n              completionHandler: ^(array[Object] output, NSError* error) {\n    if (output) {\n        NSLog(@\"%@\", output);\n    }\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getBlocks-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar boardID = boardID_example; // {String} Board ID\nvar opts = {\n  'parentId': parentId_example, // {String} ID of parent block, omit to specify all blocks\n  'type': type_example // {String} Type of blocks to return, omit to specify all types\n};\n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully. Returned data: ' + data);\n  }\n};\napi.getBlocks(boardID, opts, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-getBlocks-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-getBlocks-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class getBlocksExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var boardID = boardID_example;  // String | Board ID (default to null)\n            var parentId = parentId_example;  // String | ID of parent block, omit to specify all blocks (optional)  (default to null)\n            var type = type_example;  // String | Type of blocks to return, omit to specify all types (optional)  (default to null)\n\n            try {\n                array[Object] result = apiInstance.getBlocks(boardID, parentId, type);\n                Debug.WriteLine(result);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.getBlocks: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getBlocks-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$boardID = boardID_example; // String | Board ID\n$parentId = parentId_example; // String | ID of parent block, omit to specify all blocks\n$type = type_example; // String | Type of blocks to return, omit to specify all types\n\ntry {\n    $result = $api_instance->getBlocks($boardID, $parentId, $type);\n    print_r($result);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->getBlocks: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getBlocks-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $boardID = boardID_example; # String | Board ID\nmy $parentId = parentId_example; # String | ID of parent block, omit to specify all blocks\nmy $type = type_example; # String | Type of blocks to return, omit to specify all types\n\neval {\n    my $result = $api_instance->getBlocks(boardID => $boardID, parentId => $parentId, type => $type);\n    print Dumper($result);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->getBlocks: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getBlocks-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nboardID = boardID_example # String | Board ID (default to null)\nparentId = parentId_example # String | ID of parent block, omit to specify all blocks (optional) (default to null)\ntype = type_example # String | Type of blocks to return, omit to specify all types (optional) (default to null)\n\ntry:\n    api_response = api_instance.get_blocks(boardID, parentId=parentId, type=type)\n    pprint(api_response)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->getBlocks: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getBlocks-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let boardID = boardID_example; // String\n    let parentId = parentId_example; // String\n    let type = type_example; // String\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.getBlocks(boardID, parentId, type, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">boardID*</td>\n<td>\n\n\n    <div id=\"d2e199_getBlocks_boardID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nBoard ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n\n                            <div class=\"methodsubtabletitle\">Query parameters</div>\n                            <table id=\"methodsubtable\">\n                              <tr>\n                                <th width=\"150px\">Name</th>\n                                <th>Description</th>\n                              </tr>\n                                <tr><td style=\"width:150px;\">parent_id</td>\n<td>\n\n\n    <div id=\"d2e199_getBlocks_parentId\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nID of parent block, omit to specify all blocks\n                    </div>\n            </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                                <tr><td style=\"width:150px;\">type</td>\n<td>\n\n\n    <div id=\"d2e199_getBlocks_type\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nType of blocks to return, omit to specify all types\n                    </div>\n            </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-getBlocks-title-200\"></h3>\n                            <p id=\"examples-Default-getBlocks-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-getBlocks-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-getBlocks-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-getBlocks-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getBlocks-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-getBlocks-200-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getBlocks-200-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-getBlocks-200-schema\">\n                                  <div id=\"responses-Default-getBlocks-schema-200\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = ;\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-getBlocks-200-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-getBlocks-schema-200');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-getBlocks-200-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-getBlocks-title-404\"></h3>\n                            <p id=\"examples-Default-getBlocks-description-404\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault404_description = `board not found`;\n                              var responseDefault404_description_break = responseDefault404_description.indexOf('\\n');\n                              if (responseDefault404_description_break == -1) {\n                                $(\"#examples-Default-getBlocks-title-404\").text(\"Status: 404 - \" + responseDefault404_description);\n                              } else {\n                                $(\"#examples-Default-getBlocks-title-404\").text(\"Status: 404 - \" + responseDefault404_description.substring(0, responseDefault404_description_break));\n                                $(\"#examples-Default-getBlocks-description-404\").html(responseDefault404_description.substring(responseDefault404_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getBlocks-404\" class=\"nav nav-tabs nav-tabs-examples\" >\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getBlocks-404-wrapper\" style='margin-bottom: 10px;'>\n                            </div>\n                            <h3 id=\"examples-Default-getBlocks-title-default\"></h3>\n                            <p id=\"examples-Default-getBlocks-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-getBlocks-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-getBlocks-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-getBlocks-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getBlocks-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-getBlocks-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getBlocks-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-getBlocks-default-schema\">\n                                  <div id=\"responses-Default-getBlocks-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-getBlocks-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-getBlocks-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-getBlocks-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-getBoard\">\n                      <article id=\"api-Default-getBoard-0\" data-group=\"User\" data-name=\"getBoard\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>getBoard</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Returns a board</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"get\"><code><span class=\"pln\">/boards/{boardID}</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-getBoard-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getBoard-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getBoard-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-getBoard-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-getBoard-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getBoard-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-getBoard-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-getBoard-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getBoard-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getBoard-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getBoard-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getBoard-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-getBoard-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X GET \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n \"http://localhost/api/v2/boards/{boardID}\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-getBoard-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String boardID = boardID_example; // String | Board ID\n\n        try {\n            Object result = apiInstance.getBoard(boardID);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#getBoard\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-getBoard-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String boardID = boardID_example; // String | Board ID\n\n        try {\n            Object result = apiInstance.getBoard(boardID);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#getBoard\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-getBoard-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-getBoard-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *boardID = boardID_example; // Board ID (default to null)\n\n[apiInstance getBoardWith:boardID\n              completionHandler: ^(Object output, NSError* error) {\n    if (output) {\n        NSLog(@\"%@\", output);\n    }\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getBoard-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar boardID = boardID_example; // {String} Board ID\n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully. Returned data: ' + data);\n  }\n};\napi.getBoard(boardID, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-getBoard-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-getBoard-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class getBoardExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var boardID = boardID_example;  // String | Board ID (default to null)\n\n            try {\n                Object result = apiInstance.getBoard(boardID);\n                Debug.WriteLine(result);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.getBoard: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getBoard-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$boardID = boardID_example; // String | Board ID\n\ntry {\n    $result = $api_instance->getBoard($boardID);\n    print_r($result);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->getBoard: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getBoard-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $boardID = boardID_example; # String | Board ID\n\neval {\n    my $result = $api_instance->getBoard(boardID => $boardID);\n    print Dumper($result);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->getBoard: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getBoard-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nboardID = boardID_example # String | Board ID (default to null)\n\ntry:\n    api_response = api_instance.get_board(boardID)\n    pprint(api_response)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->getBoard: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getBoard-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let boardID = boardID_example; // String\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.getBoard(boardID, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">boardID*</td>\n<td>\n\n\n    <div id=\"d2e199_getBoard_boardID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nBoard ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-getBoard-title-200\"></h3>\n                            <p id=\"examples-Default-getBoard-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-getBoard-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-getBoard-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-getBoard-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getBoard-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-getBoard-200-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getBoard-200-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-getBoard-200-schema\">\n                                  <div id=\"responses-Default-getBoard-schema-200\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=Board groups a set of blocks and its layout, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-getBoard-200-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-getBoard-schema-200');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-getBoard-200-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-getBoard-title-404\"></h3>\n                            <p id=\"examples-Default-getBoard-description-404\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault404_description = `board not found`;\n                              var responseDefault404_description_break = responseDefault404_description.indexOf('\\n');\n                              if (responseDefault404_description_break == -1) {\n                                $(\"#examples-Default-getBoard-title-404\").text(\"Status: 404 - \" + responseDefault404_description);\n                              } else {\n                                $(\"#examples-Default-getBoard-title-404\").text(\"Status: 404 - \" + responseDefault404_description.substring(0, responseDefault404_description_break));\n                                $(\"#examples-Default-getBoard-description-404\").html(responseDefault404_description.substring(responseDefault404_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getBoard-404\" class=\"nav nav-tabs nav-tabs-examples\" >\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getBoard-404-wrapper\" style='margin-bottom: 10px;'>\n                            </div>\n                            <h3 id=\"examples-Default-getBoard-title-default\"></h3>\n                            <p id=\"examples-Default-getBoard-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-getBoard-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-getBoard-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-getBoard-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getBoard-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-getBoard-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getBoard-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-getBoard-default-schema\">\n                                  <div id=\"responses-Default-getBoard-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-getBoard-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-getBoard-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-getBoard-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-getBoardMetadata\">\n                      <article id=\"api-Default-getBoardMetadata-0\" data-group=\"User\" data-name=\"getBoardMetadata\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>getBoardMetadata</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Returns a board&#39;s metadata</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"get\"><code><span class=\"pln\">/boards/{boardID}/metadata</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-getBoardMetadata-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getBoardMetadata-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getBoardMetadata-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-getBoardMetadata-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-getBoardMetadata-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getBoardMetadata-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-getBoardMetadata-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-getBoardMetadata-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getBoardMetadata-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getBoardMetadata-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getBoardMetadata-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getBoardMetadata-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-getBoardMetadata-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X GET \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n \"http://localhost/api/v2/boards/{boardID}/metadata\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-getBoardMetadata-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String boardID = boardID_example; // String | Board ID\n\n        try {\n            Object result = apiInstance.getBoardMetadata(boardID);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#getBoardMetadata\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-getBoardMetadata-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String boardID = boardID_example; // String | Board ID\n\n        try {\n            Object result = apiInstance.getBoardMetadata(boardID);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#getBoardMetadata\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-getBoardMetadata-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-getBoardMetadata-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *boardID = boardID_example; // Board ID (default to null)\n\n[apiInstance getBoardMetadataWith:boardID\n              completionHandler: ^(Object output, NSError* error) {\n    if (output) {\n        NSLog(@\"%@\", output);\n    }\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getBoardMetadata-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar boardID = boardID_example; // {String} Board ID\n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully. Returned data: ' + data);\n  }\n};\napi.getBoardMetadata(boardID, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-getBoardMetadata-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-getBoardMetadata-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class getBoardMetadataExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var boardID = boardID_example;  // String | Board ID (default to null)\n\n            try {\n                Object result = apiInstance.getBoardMetadata(boardID);\n                Debug.WriteLine(result);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.getBoardMetadata: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getBoardMetadata-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$boardID = boardID_example; // String | Board ID\n\ntry {\n    $result = $api_instance->getBoardMetadata($boardID);\n    print_r($result);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->getBoardMetadata: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getBoardMetadata-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $boardID = boardID_example; # String | Board ID\n\neval {\n    my $result = $api_instance->getBoardMetadata(boardID => $boardID);\n    print Dumper($result);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->getBoardMetadata: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getBoardMetadata-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nboardID = boardID_example # String | Board ID (default to null)\n\ntry:\n    api_response = api_instance.get_board_metadata(boardID)\n    pprint(api_response)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->getBoardMetadata: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getBoardMetadata-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let boardID = boardID_example; // String\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.getBoardMetadata(boardID, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">boardID*</td>\n<td>\n\n\n    <div id=\"d2e199_getBoardMetadata_boardID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nBoard ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-getBoardMetadata-title-200\"></h3>\n                            <p id=\"examples-Default-getBoardMetadata-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-getBoardMetadata-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-getBoardMetadata-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-getBoardMetadata-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getBoardMetadata-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-getBoardMetadata-200-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getBoardMetadata-200-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-getBoardMetadata-200-schema\">\n                                  <div id=\"responses-Default-getBoardMetadata-schema-200\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=BoardMetadata contains metadata for a Board, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-getBoardMetadata-200-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-getBoardMetadata-schema-200');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-getBoardMetadata-200-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-getBoardMetadata-title-404\"></h3>\n                            <p id=\"examples-Default-getBoardMetadata-description-404\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault404_description = `board not found`;\n                              var responseDefault404_description_break = responseDefault404_description.indexOf('\\n');\n                              if (responseDefault404_description_break == -1) {\n                                $(\"#examples-Default-getBoardMetadata-title-404\").text(\"Status: 404 - \" + responseDefault404_description);\n                              } else {\n                                $(\"#examples-Default-getBoardMetadata-title-404\").text(\"Status: 404 - \" + responseDefault404_description.substring(0, responseDefault404_description_break));\n                                $(\"#examples-Default-getBoardMetadata-description-404\").html(responseDefault404_description.substring(responseDefault404_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getBoardMetadata-404\" class=\"nav nav-tabs nav-tabs-examples\" >\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getBoardMetadata-404-wrapper\" style='margin-bottom: 10px;'>\n                            </div>\n                            <h3 id=\"examples-Default-getBoardMetadata-title-501\"></h3>\n                            <p id=\"examples-Default-getBoardMetadata-description-501\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault501_description = `required license not found`;\n                              var responseDefault501_description_break = responseDefault501_description.indexOf('\\n');\n                              if (responseDefault501_description_break == -1) {\n                                $(\"#examples-Default-getBoardMetadata-title-501\").text(\"Status: 501 - \" + responseDefault501_description);\n                              } else {\n                                $(\"#examples-Default-getBoardMetadata-title-501\").text(\"Status: 501 - \" + responseDefault501_description.substring(0, responseDefault501_description_break));\n                                $(\"#examples-Default-getBoardMetadata-description-501\").html(responseDefault501_description.substring(responseDefault501_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getBoardMetadata-501\" class=\"nav nav-tabs nav-tabs-examples\" >\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getBoardMetadata-501-wrapper\" style='margin-bottom: 10px;'>\n                            </div>\n                            <h3 id=\"examples-Default-getBoardMetadata-title-default\"></h3>\n                            <p id=\"examples-Default-getBoardMetadata-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-getBoardMetadata-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-getBoardMetadata-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-getBoardMetadata-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getBoardMetadata-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-getBoardMetadata-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getBoardMetadata-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-getBoardMetadata-default-schema\">\n                                  <div id=\"responses-Default-getBoardMetadata-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-getBoardMetadata-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-getBoardMetadata-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-getBoardMetadata-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-getBoards\">\n                      <article id=\"api-Default-getBoards-0\" data-group=\"User\" data-name=\"getBoards\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>getBoards</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Returns team boards</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"get\"><code><span class=\"pln\">/teams/{teamID}/boards</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-getBoards-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getBoards-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getBoards-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-getBoards-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-getBoards-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getBoards-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-getBoards-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-getBoards-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getBoards-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getBoards-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getBoards-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getBoards-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-getBoards-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X GET \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n \"http://localhost/api/v2/teams/{teamID}/boards\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-getBoards-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String teamID = teamID_example; // String | Team ID\n\n        try {\n            array[Object] result = apiInstance.getBoards(teamID);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#getBoards\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-getBoards-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String teamID = teamID_example; // String | Team ID\n\n        try {\n            array[Object] result = apiInstance.getBoards(teamID);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#getBoards\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-getBoards-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-getBoards-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *teamID = teamID_example; // Team ID (default to null)\n\n[apiInstance getBoardsWith:teamID\n              completionHandler: ^(array[Object] output, NSError* error) {\n    if (output) {\n        NSLog(@\"%@\", output);\n    }\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getBoards-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar teamID = teamID_example; // {String} Team ID\n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully. Returned data: ' + data);\n  }\n};\napi.getBoards(teamID, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-getBoards-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-getBoards-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class getBoardsExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var teamID = teamID_example;  // String | Team ID (default to null)\n\n            try {\n                array[Object] result = apiInstance.getBoards(teamID);\n                Debug.WriteLine(result);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.getBoards: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getBoards-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$teamID = teamID_example; // String | Team ID\n\ntry {\n    $result = $api_instance->getBoards($teamID);\n    print_r($result);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->getBoards: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getBoards-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $teamID = teamID_example; # String | Team ID\n\neval {\n    my $result = $api_instance->getBoards(teamID => $teamID);\n    print Dumper($result);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->getBoards: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getBoards-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nteamID = teamID_example # String | Team ID (default to null)\n\ntry:\n    api_response = api_instance.get_boards(teamID)\n    pprint(api_response)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->getBoards: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getBoards-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let teamID = teamID_example; // String\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.getBoards(teamID, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">teamID*</td>\n<td>\n\n\n    <div id=\"d2e199_getBoards_teamID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nTeam ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-getBoards-title-200\"></h3>\n                            <p id=\"examples-Default-getBoards-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-getBoards-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-getBoards-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-getBoards-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getBoards-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-getBoards-200-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getBoards-200-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-getBoards-200-schema\">\n                                  <div id=\"responses-Default-getBoards-schema-200\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = ;\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-getBoards-200-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-getBoards-schema-200');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-getBoards-200-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-getBoards-title-default\"></h3>\n                            <p id=\"examples-Default-getBoards-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-getBoards-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-getBoards-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-getBoards-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getBoards-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-getBoards-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getBoards-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-getBoards-default-schema\">\n                                  <div id=\"responses-Default-getBoards-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-getBoards-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-getBoards-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-getBoards-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-getCard\">\n                      <article id=\"api-Default-getCard-0\" data-group=\"User\" data-name=\"getCard\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>getCard</h1>\n                          <p>Fetches the specified card.</p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\"></p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"get\"><code><span class=\"pln\">/cards/{cardID}</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-getCard-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getCard-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getCard-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-getCard-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-getCard-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getCard-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-getCard-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-getCard-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getCard-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getCard-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getCard-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getCard-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-getCard-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X GET \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n \"http://localhost/api/v2/cards/{cardID}\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-getCard-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String cardID = cardID_example; // String | Card ID\n\n        try {\n            Object result = apiInstance.getCard(cardID);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#getCard\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-getCard-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String cardID = cardID_example; // String | Card ID\n\n        try {\n            Object result = apiInstance.getCard(cardID);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#getCard\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-getCard-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-getCard-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *cardID = cardID_example; // Card ID (default to null)\n\n// Fetches the specified card.\n[apiInstance getCardWith:cardID\n              completionHandler: ^(Object output, NSError* error) {\n    if (output) {\n        NSLog(@\"%@\", output);\n    }\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getCard-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar cardID = cardID_example; // {String} Card ID\n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully. Returned data: ' + data);\n  }\n};\napi.getCard(cardID, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-getCard-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-getCard-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class getCardExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var cardID = cardID_example;  // String | Card ID (default to null)\n\n            try {\n                // Fetches the specified card.\n                Object result = apiInstance.getCard(cardID);\n                Debug.WriteLine(result);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.getCard: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getCard-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$cardID = cardID_example; // String | Card ID\n\ntry {\n    $result = $api_instance->getCard($cardID);\n    print_r($result);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->getCard: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getCard-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $cardID = cardID_example; # String | Card ID\n\neval {\n    my $result = $api_instance->getCard(cardID => $cardID);\n    print Dumper($result);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->getCard: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getCard-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\ncardID = cardID_example # String | Card ID (default to null)\n\ntry:\n    # Fetches the specified card.\n    api_response = api_instance.get_card(cardID)\n    pprint(api_response)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->getCard: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getCard-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let cardID = cardID_example; // String\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.getCard(cardID, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">cardID*</td>\n<td>\n\n\n    <div id=\"d2e199_getCard_cardID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nCard ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-getCard-title-200\"></h3>\n                            <p id=\"examples-Default-getCard-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-getCard-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-getCard-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-getCard-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getCard-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-getCard-200-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getCard-200-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-getCard-200-schema\">\n                                  <div id=\"responses-Default-getCard-schema-200\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {title=Card represents a group of content blocks and properties., x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-getCard-200-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-getCard-schema-200');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-getCard-200-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-getCard-title-default\"></h3>\n                            <p id=\"examples-Default-getCard-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-getCard-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-getCard-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-getCard-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getCard-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-getCard-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getCard-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-getCard-default-schema\">\n                                  <div id=\"responses-Default-getCard-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-getCard-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-getCard-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-getCard-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-getCards\">\n                      <article id=\"api-Default-getCards-0\" data-group=\"User\" data-name=\"getCards\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>getCards</h1>\n                          <p>Fetches cards for the specified board.</p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\"></p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"get\"><code><span class=\"pln\">/boards/{boardID}/cards</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-getCards-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getCards-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getCards-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-getCards-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-getCards-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getCards-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-getCards-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-getCards-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getCards-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getCards-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getCards-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getCards-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-getCards-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X GET \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n \"http://localhost/api/v2/boards/{boardID}/cards?page=56&per_page=56\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-getCards-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String boardID = boardID_example; // String | Board ID\n        Integer page = 56; // Integer | The page to select (default=0)\n        Integer perPage = 56; // Integer | Number of cards to return per page(default=100)\n\n        try {\n            array[Object] result = apiInstance.getCards(boardID, page, perPage);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#getCards\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-getCards-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String boardID = boardID_example; // String | Board ID\n        Integer page = 56; // Integer | The page to select (default=0)\n        Integer perPage = 56; // Integer | Number of cards to return per page(default=100)\n\n        try {\n            array[Object] result = apiInstance.getCards(boardID, page, perPage);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#getCards\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-getCards-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-getCards-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *boardID = boardID_example; // Board ID (default to null)\nInteger *page = 56; // The page to select (default=0) (optional) (default to null)\nInteger *perPage = 56; // Number of cards to return per page(default=100) (optional) (default to null)\n\n// Fetches cards for the specified board.\n[apiInstance getCardsWith:boardID\n    page:page\n    perPage:perPage\n              completionHandler: ^(array[Object] output, NSError* error) {\n    if (output) {\n        NSLog(@\"%@\", output);\n    }\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getCards-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar boardID = boardID_example; // {String} Board ID\nvar opts = {\n  'page': 56, // {Integer} The page to select (default=0)\n  'perPage': 56 // {Integer} Number of cards to return per page(default=100)\n};\n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully. Returned data: ' + data);\n  }\n};\napi.getCards(boardID, opts, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-getCards-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-getCards-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class getCardsExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var boardID = boardID_example;  // String | Board ID (default to null)\n            var page = 56;  // Integer | The page to select (default=0) (optional)  (default to null)\n            var perPage = 56;  // Integer | Number of cards to return per page(default=100) (optional)  (default to null)\n\n            try {\n                // Fetches cards for the specified board.\n                array[Object] result = apiInstance.getCards(boardID, page, perPage);\n                Debug.WriteLine(result);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.getCards: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getCards-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$boardID = boardID_example; // String | Board ID\n$page = 56; // Integer | The page to select (default=0)\n$perPage = 56; // Integer | Number of cards to return per page(default=100)\n\ntry {\n    $result = $api_instance->getCards($boardID, $page, $perPage);\n    print_r($result);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->getCards: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getCards-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $boardID = boardID_example; # String | Board ID\nmy $page = 56; # Integer | The page to select (default=0)\nmy $perPage = 56; # Integer | Number of cards to return per page(default=100)\n\neval {\n    my $result = $api_instance->getCards(boardID => $boardID, page => $page, perPage => $perPage);\n    print Dumper($result);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->getCards: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getCards-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nboardID = boardID_example # String | Board ID (default to null)\npage = 56 # Integer | The page to select (default=0) (optional) (default to null)\nperPage = 56 # Integer | Number of cards to return per page(default=100) (optional) (default to null)\n\ntry:\n    # Fetches cards for the specified board.\n    api_response = api_instance.get_cards(boardID, page=page, perPage=perPage)\n    pprint(api_response)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->getCards: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getCards-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let boardID = boardID_example; // String\n    let page = 56; // Integer\n    let perPage = 56; // Integer\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.getCards(boardID, page, perPage, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">boardID*</td>\n<td>\n\n\n    <div id=\"d2e199_getCards_boardID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nBoard ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n\n                            <div class=\"methodsubtabletitle\">Query parameters</div>\n                            <table id=\"methodsubtable\">\n                              <tr>\n                                <th width=\"150px\">Name</th>\n                                <th>Description</th>\n                              </tr>\n                                <tr><td style=\"width:150px;\">page</td>\n<td>\n\n\n    <div id=\"d2e199_getCards_page\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    Integer\n                </span>\n\n                    <div class=\"inner description marked\">\nThe page to select (default&#x3D;0)\n                    </div>\n            </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                                <tr><td style=\"width:150px;\">per_page</td>\n<td>\n\n\n    <div id=\"d2e199_getCards_perPage\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    Integer\n                </span>\n\n                    <div class=\"inner description marked\">\nNumber of cards to return per page(default&#x3D;100)\n                    </div>\n            </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-getCards-title-200\"></h3>\n                            <p id=\"examples-Default-getCards-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-getCards-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-getCards-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-getCards-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getCards-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-getCards-200-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getCards-200-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-getCards-200-schema\">\n                                  <div id=\"responses-Default-getCards-schema-200\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = ;\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-getCards-200-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-getCards-schema-200');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-getCards-200-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-getCards-title-default\"></h3>\n                            <p id=\"examples-Default-getCards-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-getCards-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-getCards-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-getCards-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getCards-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-getCards-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getCards-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-getCards-default-schema\">\n                                  <div id=\"responses-Default-getCards-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-getCards-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-getCards-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-getCards-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-getChannel\">\n                      <article id=\"api-Default-getChannel-0\" data-group=\"User\" data-name=\"getChannel\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>getChannel</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Returns the requested channel</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"get\"><code><span class=\"pln\">/teams/{teamID}/channels/{channelID}</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-getChannel-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getChannel-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getChannel-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-getChannel-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-getChannel-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getChannel-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-getChannel-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-getChannel-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getChannel-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getChannel-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getChannel-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getChannel-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-getChannel-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X GET \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n \"http://localhost/api/v2/teams/{teamID}/channels/{channelID}\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-getChannel-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String teamID = teamID_example; // String | Team ID\n        String channelID = channelID_example; // String | Channel ID\n\n        try {\n            array[Channel] result = apiInstance.getChannel(teamID, channelID);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#getChannel\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-getChannel-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String teamID = teamID_example; // String | Team ID\n        String channelID = channelID_example; // String | Channel ID\n\n        try {\n            array[Channel] result = apiInstance.getChannel(teamID, channelID);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#getChannel\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-getChannel-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-getChannel-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *teamID = teamID_example; // Team ID (default to null)\nString *channelID = channelID_example; // Channel ID (default to null)\n\n[apiInstance getChannelWith:teamID\n    channelID:channelID\n              completionHandler: ^(array[Channel] output, NSError* error) {\n    if (output) {\n        NSLog(@\"%@\", output);\n    }\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getChannel-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar teamID = teamID_example; // {String} Team ID\nvar channelID = channelID_example; // {String} Channel ID\n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully. Returned data: ' + data);\n  }\n};\napi.getChannel(teamID, channelID, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-getChannel-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-getChannel-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class getChannelExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var teamID = teamID_example;  // String | Team ID (default to null)\n            var channelID = channelID_example;  // String | Channel ID (default to null)\n\n            try {\n                array[Channel] result = apiInstance.getChannel(teamID, channelID);\n                Debug.WriteLine(result);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.getChannel: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getChannel-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$teamID = teamID_example; // String | Team ID\n$channelID = channelID_example; // String | Channel ID\n\ntry {\n    $result = $api_instance->getChannel($teamID, $channelID);\n    print_r($result);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->getChannel: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getChannel-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $teamID = teamID_example; # String | Team ID\nmy $channelID = channelID_example; # String | Channel ID\n\neval {\n    my $result = $api_instance->getChannel(teamID => $teamID, channelID => $channelID);\n    print Dumper($result);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->getChannel: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getChannel-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nteamID = teamID_example # String | Team ID (default to null)\nchannelID = channelID_example # String | Channel ID (default to null)\n\ntry:\n    api_response = api_instance.get_channel(teamID, channelID)\n    pprint(api_response)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->getChannel: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getChannel-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let teamID = teamID_example; // String\n    let channelID = channelID_example; // String\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.getChannel(teamID, channelID, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">teamID*</td>\n<td>\n\n\n    <div id=\"d2e199_getChannel_teamID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nTeam ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                                  <tr><td style=\"width:150px;\">channelID*</td>\n<td>\n\n\n    <div id=\"d2e199_getChannel_channelID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nChannel ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-getChannel-title-200\"></h3>\n                            <p id=\"examples-Default-getChannel-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-getChannel-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-getChannel-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-getChannel-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getChannel-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-getChannel-200-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getChannel-200-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-getChannel-200-schema\">\n                                  <div id=\"responses-Default-getChannel-schema-200\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = ;\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-getChannel-200-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-getChannel-schema-200');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-getChannel-200-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-getChannel-title-default\"></h3>\n                            <p id=\"examples-Default-getChannel-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-getChannel-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-getChannel-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-getChannel-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getChannel-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-getChannel-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getChannel-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-getChannel-default-schema\">\n                                  <div id=\"responses-Default-getChannel-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-getChannel-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-getChannel-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-getChannel-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-getClientConfig\">\n                      <article id=\"api-Default-getClientConfig-0\" data-group=\"User\" data-name=\"getClientConfig\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>getClientConfig</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Returns the client configuration</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"get\"><code><span class=\"pln\">/clientConfig</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-getClientConfig-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getClientConfig-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getClientConfig-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-getClientConfig-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-getClientConfig-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getClientConfig-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-getClientConfig-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-getClientConfig-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getClientConfig-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getClientConfig-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getClientConfig-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getClientConfig-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-getClientConfig-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X GET \\\n -H \"Accept: application/json\" \\\n \"http://localhost/api/v2/clientConfig\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-getClientConfig-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n\n        try {\n            Object result = apiInstance.getClientConfig();\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#getClientConfig\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-getClientConfig-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n\n        try {\n            Object result = apiInstance.getClientConfig();\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#getClientConfig\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-getClientConfig-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-getClientConfig-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\n\n[apiInstance getClientConfigWithCompletionHandler: \n              ^(Object output, NSError* error) {\n    if (output) {\n        NSLog(@\"%@\", output);\n    }\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getClientConfig-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully. Returned data: ' + data);\n  }\n};\napi.getClientConfig(callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-getClientConfig-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-getClientConfig-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class getClientConfigExample\n    {\n        public void main()\n        {\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n\n            try {\n                Object result = apiInstance.getClientConfig();\n                Debug.WriteLine(result);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.getClientConfig: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getClientConfig-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n\ntry {\n    $result = $api_instance->getClientConfig();\n    print_r($result);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->getClientConfig: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getClientConfig-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\n\neval {\n    my $result = $api_instance->getClientConfig();\n    print Dumper($result);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->getClientConfig: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getClientConfig-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\n\ntry:\n    api_response = api_instance.get_client_config()\n    pprint(api_response)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->getClientConfig: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getClientConfig-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.getClientConfig(&context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n\n\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-getClientConfig-title-200\"></h3>\n                            <p id=\"examples-Default-getClientConfig-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-getClientConfig-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-getClientConfig-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-getClientConfig-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getClientConfig-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-getClientConfig-200-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getClientConfig-200-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-getClientConfig-200-schema\">\n                                  <div id=\"responses-Default-getClientConfig-schema-200\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ClientConfig is the client configuration, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-getClientConfig-200-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-getClientConfig-schema-200');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-getClientConfig-200-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-getClientConfig-title-default\"></h3>\n                            <p id=\"examples-Default-getClientConfig-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-getClientConfig-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-getClientConfig-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-getClientConfig-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getClientConfig-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-getClientConfig-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getClientConfig-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-getClientConfig-default-schema\">\n                                  <div id=\"responses-Default-getClientConfig-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-getClientConfig-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-getClientConfig-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-getClientConfig-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-getFile\">\n                      <article id=\"api-Default-getFile-0\" data-group=\"User\" data-name=\"getFile\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>getFile</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Returns the contents of an uploaded file</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"get\"><code><span class=\"pln\">/files/teams/{teamID}/{boardID}/{filename}</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-getFile-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getFile-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getFile-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-getFile-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-getFile-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getFile-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-getFile-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-getFile-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getFile-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getFile-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getFile-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getFile-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-getFile-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X GET \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json,image/jpg,image/png,image/gif\" \\\n \"http://localhost/api/v2/files/teams/{teamID}/{boardID}/{filename}\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-getFile-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String teamID = teamID_example; // String | Team ID\n        String boardID = boardID_example; // String | Board ID\n        String filename = filename_example; // String | name of the file\n\n        try {\n            apiInstance.getFile(teamID, boardID, filename);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#getFile\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-getFile-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String teamID = teamID_example; // String | Team ID\n        String boardID = boardID_example; // String | Board ID\n        String filename = filename_example; // String | name of the file\n\n        try {\n            apiInstance.getFile(teamID, boardID, filename);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#getFile\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-getFile-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-getFile-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *teamID = teamID_example; // Team ID (default to null)\nString *boardID = boardID_example; // Board ID (default to null)\nString *filename = filename_example; // name of the file (default to null)\n\n[apiInstance getFileWith:teamID\n    boardID:boardID\n    filename:filename\n              completionHandler: ^(NSError* error) {\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getFile-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar teamID = teamID_example; // {String} Team ID\nvar boardID = boardID_example; // {String} Board ID\nvar filename = filename_example; // {String} name of the file\n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully.');\n  }\n};\napi.getFile(teamID, boardID, filename, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-getFile-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-getFile-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class getFileExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var teamID = teamID_example;  // String | Team ID (default to null)\n            var boardID = boardID_example;  // String | Board ID (default to null)\n            var filename = filename_example;  // String | name of the file (default to null)\n\n            try {\n                apiInstance.getFile(teamID, boardID, filename);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.getFile: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getFile-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$teamID = teamID_example; // String | Team ID\n$boardID = boardID_example; // String | Board ID\n$filename = filename_example; // String | name of the file\n\ntry {\n    $api_instance->getFile($teamID, $boardID, $filename);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->getFile: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getFile-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $teamID = teamID_example; # String | Team ID\nmy $boardID = boardID_example; # String | Board ID\nmy $filename = filename_example; # String | name of the file\n\neval {\n    $api_instance->getFile(teamID => $teamID, boardID => $boardID, filename => $filename);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->getFile: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getFile-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nteamID = teamID_example # String | Team ID (default to null)\nboardID = boardID_example # String | Board ID (default to null)\nfilename = filename_example # String | name of the file (default to null)\n\ntry:\n    api_instance.get_file(teamID, boardID, filename)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->getFile: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getFile-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let teamID = teamID_example; // String\n    let boardID = boardID_example; // String\n    let filename = filename_example; // String\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.getFile(teamID, boardID, filename, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">teamID*</td>\n<td>\n\n\n    <div id=\"d2e199_getFile_teamID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nTeam ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                                  <tr><td style=\"width:150px;\">boardID*</td>\n<td>\n\n\n    <div id=\"d2e199_getFile_boardID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nBoard ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                                  <tr><td style=\"width:150px;\">filename*</td>\n<td>\n\n\n    <div id=\"d2e199_getFile_filename\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nname of the file\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-getFile-title-200\"></h3>\n                            <p id=\"examples-Default-getFile-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-getFile-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-getFile-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-getFile-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getFile-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getFile-200-wrapper\" style='margin-bottom: 10px;'>\n                            </div>\n                            <h3 id=\"examples-Default-getFile-title-404\"></h3>\n                            <p id=\"examples-Default-getFile-description-404\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault404_description = `file not found`;\n                              var responseDefault404_description_break = responseDefault404_description.indexOf('\\n');\n                              if (responseDefault404_description_break == -1) {\n                                $(\"#examples-Default-getFile-title-404\").text(\"Status: 404 - \" + responseDefault404_description);\n                              } else {\n                                $(\"#examples-Default-getFile-title-404\").text(\"Status: 404 - \" + responseDefault404_description.substring(0, responseDefault404_description_break));\n                                $(\"#examples-Default-getFile-description-404\").html(responseDefault404_description.substring(responseDefault404_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getFile-404\" class=\"nav nav-tabs nav-tabs-examples\" >\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getFile-404-wrapper\" style='margin-bottom: 10px;'>\n                            </div>\n                            <h3 id=\"examples-Default-getFile-title-default\"></h3>\n                            <p id=\"examples-Default-getFile-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-getFile-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-getFile-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-getFile-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getFile-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-getFile-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getFile-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-getFile-default-schema\">\n                                  <div id=\"responses-Default-getFile-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-getFile-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-getFile-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-getFile-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-getMe\">\n                      <article id=\"api-Default-getMe-0\" data-group=\"User\" data-name=\"getMe\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>getMe</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Returns the currently logged-in user</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"get\"><code><span class=\"pln\">/users/me</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-getMe-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getMe-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getMe-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-getMe-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-getMe-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getMe-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-getMe-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-getMe-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getMe-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getMe-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getMe-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getMe-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-getMe-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X GET \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n \"http://localhost/api/v2/users/me\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-getMe-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n\n        try {\n            Object result = apiInstance.getMe();\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#getMe\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-getMe-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n\n        try {\n            Object result = apiInstance.getMe();\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#getMe\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-getMe-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-getMe-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\n\n[apiInstance getMeWithCompletionHandler: \n              ^(Object output, NSError* error) {\n    if (output) {\n        NSLog(@\"%@\", output);\n    }\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getMe-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully. Returned data: ' + data);\n  }\n};\napi.getMe(callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-getMe-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-getMe-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class getMeExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n\n            try {\n                Object result = apiInstance.getMe();\n                Debug.WriteLine(result);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.getMe: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getMe-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n\ntry {\n    $result = $api_instance->getMe();\n    print_r($result);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->getMe: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getMe-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\n\neval {\n    my $result = $api_instance->getMe();\n    print Dumper($result);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->getMe: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getMe-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\n\ntry:\n    api_response = api_instance.get_me()\n    pprint(api_response)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->getMe: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getMe-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.getMe(&context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n\n\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-getMe-title-200\"></h3>\n                            <p id=\"examples-Default-getMe-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-getMe-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-getMe-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-getMe-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getMe-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-getMe-200-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getMe-200-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-getMe-200-schema\">\n                                  <div id=\"responses-Default-getMe-schema-200\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=User is a user, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-getMe-200-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-getMe-schema-200');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-getMe-200-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-getMe-title-default\"></h3>\n                            <p id=\"examples-Default-getMe-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-getMe-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-getMe-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-getMe-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getMe-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-getMe-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getMe-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-getMe-default-schema\">\n                                  <div id=\"responses-Default-getMe-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-getMe-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-getMe-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-getMe-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-getMembersForBoard\">\n                      <article id=\"api-Default-getMembersForBoard-0\" data-group=\"User\" data-name=\"getMembersForBoard\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>getMembersForBoard</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Returns the members of the board</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"get\"><code><span class=\"pln\">/boards/{boardID}/members</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-getMembersForBoard-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getMembersForBoard-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getMembersForBoard-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-getMembersForBoard-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-getMembersForBoard-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getMembersForBoard-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-getMembersForBoard-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-getMembersForBoard-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getMembersForBoard-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getMembersForBoard-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getMembersForBoard-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getMembersForBoard-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-getMembersForBoard-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X GET \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n \"http://localhost/api/v2/boards/{boardID}/members\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-getMembersForBoard-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String boardID = boardID_example; // String | Board ID\n\n        try {\n            array[Object] result = apiInstance.getMembersForBoard(boardID);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#getMembersForBoard\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-getMembersForBoard-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String boardID = boardID_example; // String | Board ID\n\n        try {\n            array[Object] result = apiInstance.getMembersForBoard(boardID);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#getMembersForBoard\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-getMembersForBoard-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-getMembersForBoard-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *boardID = boardID_example; // Board ID (default to null)\n\n[apiInstance getMembersForBoardWith:boardID\n              completionHandler: ^(array[Object] output, NSError* error) {\n    if (output) {\n        NSLog(@\"%@\", output);\n    }\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getMembersForBoard-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar boardID = boardID_example; // {String} Board ID\n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully. Returned data: ' + data);\n  }\n};\napi.getMembersForBoard(boardID, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-getMembersForBoard-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-getMembersForBoard-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class getMembersForBoardExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var boardID = boardID_example;  // String | Board ID (default to null)\n\n            try {\n                array[Object] result = apiInstance.getMembersForBoard(boardID);\n                Debug.WriteLine(result);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.getMembersForBoard: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getMembersForBoard-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$boardID = boardID_example; // String | Board ID\n\ntry {\n    $result = $api_instance->getMembersForBoard($boardID);\n    print_r($result);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->getMembersForBoard: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getMembersForBoard-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $boardID = boardID_example; # String | Board ID\n\neval {\n    my $result = $api_instance->getMembersForBoard(boardID => $boardID);\n    print Dumper($result);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->getMembersForBoard: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getMembersForBoard-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nboardID = boardID_example # String | Board ID (default to null)\n\ntry:\n    api_response = api_instance.get_members_for_board(boardID)\n    pprint(api_response)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->getMembersForBoard: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getMembersForBoard-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let boardID = boardID_example; // String\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.getMembersForBoard(boardID, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">boardID*</td>\n<td>\n\n\n    <div id=\"d2e199_getMembersForBoard_boardID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nBoard ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-getMembersForBoard-title-200\"></h3>\n                            <p id=\"examples-Default-getMembersForBoard-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-getMembersForBoard-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-getMembersForBoard-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-getMembersForBoard-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getMembersForBoard-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-getMembersForBoard-200-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getMembersForBoard-200-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-getMembersForBoard-200-schema\">\n                                  <div id=\"responses-Default-getMembersForBoard-schema-200\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = ;\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-getMembersForBoard-200-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-getMembersForBoard-schema-200');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-getMembersForBoard-200-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-getMembersForBoard-title-default\"></h3>\n                            <p id=\"examples-Default-getMembersForBoard-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-getMembersForBoard-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-getMembersForBoard-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-getMembersForBoard-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getMembersForBoard-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-getMembersForBoard-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getMembersForBoard-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-getMembersForBoard-default-schema\">\n                                  <div id=\"responses-Default-getMembersForBoard-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-getMembersForBoard-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-getMembersForBoard-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-getMembersForBoard-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-getMyMemberships\">\n                      <article id=\"api-Default-getMyMemberships-0\" data-group=\"User\" data-name=\"getMyMemberships\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>getMyMemberships</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Returns the currently users board memberships</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"get\"><code><span class=\"pln\">/users/me/memberships</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-getMyMemberships-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getMyMemberships-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getMyMemberships-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-getMyMemberships-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-getMyMemberships-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getMyMemberships-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-getMyMemberships-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-getMyMemberships-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getMyMemberships-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getMyMemberships-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getMyMemberships-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getMyMemberships-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-getMyMemberships-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X GET \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n \"http://localhost/api/v2/users/me/memberships\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-getMyMemberships-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n\n        try {\n            array[Object] result = apiInstance.getMyMemberships();\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#getMyMemberships\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-getMyMemberships-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n\n        try {\n            array[Object] result = apiInstance.getMyMemberships();\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#getMyMemberships\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-getMyMemberships-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-getMyMemberships-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\n\n[apiInstance getMyMembershipsWithCompletionHandler: \n              ^(array[Object] output, NSError* error) {\n    if (output) {\n        NSLog(@\"%@\", output);\n    }\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getMyMemberships-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully. Returned data: ' + data);\n  }\n};\napi.getMyMemberships(callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-getMyMemberships-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-getMyMemberships-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class getMyMembershipsExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n\n            try {\n                array[Object] result = apiInstance.getMyMemberships();\n                Debug.WriteLine(result);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.getMyMemberships: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getMyMemberships-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n\ntry {\n    $result = $api_instance->getMyMemberships();\n    print_r($result);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->getMyMemberships: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getMyMemberships-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\n\neval {\n    my $result = $api_instance->getMyMemberships();\n    print Dumper($result);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->getMyMemberships: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getMyMemberships-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\n\ntry:\n    api_response = api_instance.get_my_memberships()\n    pprint(api_response)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->getMyMemberships: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getMyMemberships-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.getMyMemberships(&context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n\n\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-getMyMemberships-title-200\"></h3>\n                            <p id=\"examples-Default-getMyMemberships-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-getMyMemberships-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-getMyMemberships-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-getMyMemberships-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getMyMemberships-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-getMyMemberships-200-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getMyMemberships-200-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-getMyMemberships-200-schema\">\n                                  <div id=\"responses-Default-getMyMemberships-schema-200\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = ;\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-getMyMemberships-200-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-getMyMemberships-schema-200');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-getMyMemberships-200-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-getMyMemberships-title-default\"></h3>\n                            <p id=\"examples-Default-getMyMemberships-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-getMyMemberships-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-getMyMemberships-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-getMyMemberships-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getMyMemberships-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-getMyMemberships-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getMyMemberships-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-getMyMemberships-default-schema\">\n                                  <div id=\"responses-Default-getMyMemberships-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-getMyMemberships-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-getMyMemberships-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-getMyMemberships-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-getSharing\">\n                      <article id=\"api-Default-getSharing-0\" data-group=\"User\" data-name=\"getSharing\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>getSharing</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Returns sharing information for a board</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"get\"><code><span class=\"pln\">/boards/{boardID}/sharing</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-getSharing-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getSharing-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getSharing-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-getSharing-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-getSharing-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getSharing-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-getSharing-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-getSharing-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getSharing-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getSharing-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getSharing-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getSharing-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-getSharing-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X GET \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n \"http://localhost/api/v2/boards/{boardID}/sharing\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-getSharing-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String boardID = boardID_example; // String | Board ID\n\n        try {\n            Object result = apiInstance.getSharing(boardID);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#getSharing\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-getSharing-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String boardID = boardID_example; // String | Board ID\n\n        try {\n            Object result = apiInstance.getSharing(boardID);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#getSharing\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-getSharing-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-getSharing-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *boardID = boardID_example; // Board ID (default to null)\n\n[apiInstance getSharingWith:boardID\n              completionHandler: ^(Object output, NSError* error) {\n    if (output) {\n        NSLog(@\"%@\", output);\n    }\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getSharing-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar boardID = boardID_example; // {String} Board ID\n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully. Returned data: ' + data);\n  }\n};\napi.getSharing(boardID, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-getSharing-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-getSharing-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class getSharingExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var boardID = boardID_example;  // String | Board ID (default to null)\n\n            try {\n                Object result = apiInstance.getSharing(boardID);\n                Debug.WriteLine(result);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.getSharing: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getSharing-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$boardID = boardID_example; // String | Board ID\n\ntry {\n    $result = $api_instance->getSharing($boardID);\n    print_r($result);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->getSharing: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getSharing-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $boardID = boardID_example; # String | Board ID\n\neval {\n    my $result = $api_instance->getSharing(boardID => $boardID);\n    print Dumper($result);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->getSharing: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getSharing-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nboardID = boardID_example # String | Board ID (default to null)\n\ntry:\n    api_response = api_instance.get_sharing(boardID)\n    pprint(api_response)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->getSharing: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getSharing-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let boardID = boardID_example; // String\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.getSharing(boardID, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">boardID*</td>\n<td>\n\n\n    <div id=\"d2e199_getSharing_boardID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nBoard ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-getSharing-title-200\"></h3>\n                            <p id=\"examples-Default-getSharing-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-getSharing-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-getSharing-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-getSharing-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getSharing-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-getSharing-200-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getSharing-200-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-getSharing-200-schema\">\n                                  <div id=\"responses-Default-getSharing-schema-200\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=Sharing is sharing information for a root block, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-getSharing-200-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-getSharing-schema-200');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-getSharing-200-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-getSharing-title-404\"></h3>\n                            <p id=\"examples-Default-getSharing-description-404\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault404_description = `board not found`;\n                              var responseDefault404_description_break = responseDefault404_description.indexOf('\\n');\n                              if (responseDefault404_description_break == -1) {\n                                $(\"#examples-Default-getSharing-title-404\").text(\"Status: 404 - \" + responseDefault404_description);\n                              } else {\n                                $(\"#examples-Default-getSharing-title-404\").text(\"Status: 404 - \" + responseDefault404_description.substring(0, responseDefault404_description_break));\n                                $(\"#examples-Default-getSharing-description-404\").html(responseDefault404_description.substring(responseDefault404_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getSharing-404\" class=\"nav nav-tabs nav-tabs-examples\" >\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getSharing-404-wrapper\" style='margin-bottom: 10px;'>\n                            </div>\n                            <h3 id=\"examples-Default-getSharing-title-default\"></h3>\n                            <p id=\"examples-Default-getSharing-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-getSharing-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-getSharing-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-getSharing-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getSharing-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-getSharing-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getSharing-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-getSharing-default-schema\">\n                                  <div id=\"responses-Default-getSharing-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-getSharing-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-getSharing-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-getSharing-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-getSubscriptions\">\n                      <article id=\"api-Default-getSubscriptions-0\" data-group=\"User\" data-name=\"getSubscriptions\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>getSubscriptions</h1>\n                          <p>Gets subscriptions for a user.</p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\"></p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"get\"><code><span class=\"pln\">/subscriptions/{subscriberID}</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-getSubscriptions-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getSubscriptions-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getSubscriptions-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-getSubscriptions-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-getSubscriptions-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getSubscriptions-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-getSubscriptions-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-getSubscriptions-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getSubscriptions-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getSubscriptions-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getSubscriptions-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getSubscriptions-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-getSubscriptions-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X GET \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n \"http://localhost/api/v2/subscriptions/{subscriberID}\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-getSubscriptions-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String subscriberID = subscriberID_example; // String | Subscriber ID\n\n        try {\n            array[Object] result = apiInstance.getSubscriptions(subscriberID);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#getSubscriptions\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-getSubscriptions-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String subscriberID = subscriberID_example; // String | Subscriber ID\n\n        try {\n            array[Object] result = apiInstance.getSubscriptions(subscriberID);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#getSubscriptions\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-getSubscriptions-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-getSubscriptions-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *subscriberID = subscriberID_example; // Subscriber ID (default to null)\n\n// Gets subscriptions for a user.\n[apiInstance getSubscriptionsWith:subscriberID\n              completionHandler: ^(array[Object] output, NSError* error) {\n    if (output) {\n        NSLog(@\"%@\", output);\n    }\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getSubscriptions-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar subscriberID = subscriberID_example; // {String} Subscriber ID\n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully. Returned data: ' + data);\n  }\n};\napi.getSubscriptions(subscriberID, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-getSubscriptions-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-getSubscriptions-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class getSubscriptionsExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var subscriberID = subscriberID_example;  // String | Subscriber ID (default to null)\n\n            try {\n                // Gets subscriptions for a user.\n                array[Object] result = apiInstance.getSubscriptions(subscriberID);\n                Debug.WriteLine(result);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.getSubscriptions: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getSubscriptions-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$subscriberID = subscriberID_example; // String | Subscriber ID\n\ntry {\n    $result = $api_instance->getSubscriptions($subscriberID);\n    print_r($result);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->getSubscriptions: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getSubscriptions-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $subscriberID = subscriberID_example; # String | Subscriber ID\n\neval {\n    my $result = $api_instance->getSubscriptions(subscriberID => $subscriberID);\n    print Dumper($result);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->getSubscriptions: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getSubscriptions-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nsubscriberID = subscriberID_example # String | Subscriber ID (default to null)\n\ntry:\n    # Gets subscriptions for a user.\n    api_response = api_instance.get_subscriptions(subscriberID)\n    pprint(api_response)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->getSubscriptions: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getSubscriptions-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let subscriberID = subscriberID_example; // String\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.getSubscriptions(subscriberID, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">subscriberID*</td>\n<td>\n\n\n    <div id=\"d2e199_getSubscriptions_subscriberID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nSubscriber ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-getSubscriptions-title-200\"></h3>\n                            <p id=\"examples-Default-getSubscriptions-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-getSubscriptions-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-getSubscriptions-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-getSubscriptions-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getSubscriptions-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-getSubscriptions-200-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getSubscriptions-200-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-getSubscriptions-200-schema\">\n                                  <div id=\"responses-Default-getSubscriptions-schema-200\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = ;\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-getSubscriptions-200-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-getSubscriptions-schema-200');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-getSubscriptions-200-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-getSubscriptions-title-default\"></h3>\n                            <p id=\"examples-Default-getSubscriptions-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-getSubscriptions-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-getSubscriptions-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-getSubscriptions-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getSubscriptions-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-getSubscriptions-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getSubscriptions-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-getSubscriptions-default-schema\">\n                                  <div id=\"responses-Default-getSubscriptions-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-getSubscriptions-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-getSubscriptions-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-getSubscriptions-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-getTeam\">\n                      <article id=\"api-Default-getTeam-0\" data-group=\"User\" data-name=\"getTeam\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>getTeam</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Returns information of the root team</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"get\"><code><span class=\"pln\">/teams/{teamID}</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-getTeam-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getTeam-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getTeam-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-getTeam-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-getTeam-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getTeam-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-getTeam-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-getTeam-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getTeam-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getTeam-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getTeam-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getTeam-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-getTeam-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X GET \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n \"http://localhost/api/v2/teams/{teamID}\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-getTeam-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String teamID = teamID_example; // String | Team ID\n\n        try {\n            Object result = apiInstance.getTeam(teamID);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#getTeam\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-getTeam-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String teamID = teamID_example; // String | Team ID\n\n        try {\n            Object result = apiInstance.getTeam(teamID);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#getTeam\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-getTeam-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-getTeam-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *teamID = teamID_example; // Team ID (default to null)\n\n[apiInstance getTeamWith:teamID\n              completionHandler: ^(Object output, NSError* error) {\n    if (output) {\n        NSLog(@\"%@\", output);\n    }\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getTeam-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar teamID = teamID_example; // {String} Team ID\n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully. Returned data: ' + data);\n  }\n};\napi.getTeam(teamID, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-getTeam-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-getTeam-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class getTeamExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var teamID = teamID_example;  // String | Team ID (default to null)\n\n            try {\n                Object result = apiInstance.getTeam(teamID);\n                Debug.WriteLine(result);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.getTeam: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getTeam-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$teamID = teamID_example; // String | Team ID\n\ntry {\n    $result = $api_instance->getTeam($teamID);\n    print_r($result);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->getTeam: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getTeam-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $teamID = teamID_example; # String | Team ID\n\neval {\n    my $result = $api_instance->getTeam(teamID => $teamID);\n    print Dumper($result);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->getTeam: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getTeam-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nteamID = teamID_example # String | Team ID (default to null)\n\ntry:\n    api_response = api_instance.get_team(teamID)\n    pprint(api_response)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->getTeam: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getTeam-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let teamID = teamID_example; // String\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.getTeam(teamID, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">teamID*</td>\n<td>\n\n\n    <div id=\"d2e199_getTeam_teamID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nTeam ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-getTeam-title-200\"></h3>\n                            <p id=\"examples-Default-getTeam-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-getTeam-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-getTeam-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-getTeam-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getTeam-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-getTeam-200-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getTeam-200-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-getTeam-200-schema\">\n                                  <div id=\"responses-Default-getTeam-schema-200\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=Team is information global to a team, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-getTeam-200-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-getTeam-schema-200');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-getTeam-200-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-getTeam-title-default\"></h3>\n                            <p id=\"examples-Default-getTeam-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-getTeam-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-getTeam-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-getTeam-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getTeam-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-getTeam-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getTeam-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-getTeam-default-schema\">\n                                  <div id=\"responses-Default-getTeam-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-getTeam-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-getTeam-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-getTeam-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-getTeamUsers\">\n                      <article id=\"api-Default-getTeamUsers-0\" data-group=\"User\" data-name=\"getTeamUsers\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>getTeamUsers</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Returns team users</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"get\"><code><span class=\"pln\">/teams/{teamID}/users</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-getTeamUsers-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getTeamUsers-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getTeamUsers-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-getTeamUsers-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-getTeamUsers-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getTeamUsers-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-getTeamUsers-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-getTeamUsers-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getTeamUsers-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getTeamUsers-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getTeamUsers-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getTeamUsers-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-getTeamUsers-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X GET \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n \"http://localhost/api/v2/teams/{teamID}/users?search=search_example&exclude_bots=true\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-getTeamUsers-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String teamID = teamID_example; // String | Team ID\n        String search = search_example; // String | string to filter users list\n        Boolean excludeBots = true; // Boolean | exclude bot users\n\n        try {\n            array[Object] result = apiInstance.getTeamUsers(teamID, search, excludeBots);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#getTeamUsers\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-getTeamUsers-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String teamID = teamID_example; // String | Team ID\n        String search = search_example; // String | string to filter users list\n        Boolean excludeBots = true; // Boolean | exclude bot users\n\n        try {\n            array[Object] result = apiInstance.getTeamUsers(teamID, search, excludeBots);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#getTeamUsers\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-getTeamUsers-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-getTeamUsers-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *teamID = teamID_example; // Team ID (default to null)\nString *search = search_example; // string to filter users list (optional) (default to null)\nBoolean *excludeBots = true; // exclude bot users (optional) (default to null)\n\n[apiInstance getTeamUsersWith:teamID\n    search:search\n    excludeBots:excludeBots\n              completionHandler: ^(array[Object] output, NSError* error) {\n    if (output) {\n        NSLog(@\"%@\", output);\n    }\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getTeamUsers-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar teamID = teamID_example; // {String} Team ID\nvar opts = {\n  'search': search_example, // {String} string to filter users list\n  'excludeBots': true // {Boolean} exclude bot users\n};\n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully. Returned data: ' + data);\n  }\n};\napi.getTeamUsers(teamID, opts, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-getTeamUsers-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-getTeamUsers-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class getTeamUsersExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var teamID = teamID_example;  // String | Team ID (default to null)\n            var search = search_example;  // String | string to filter users list (optional)  (default to null)\n            var excludeBots = true;  // Boolean | exclude bot users (optional)  (default to null)\n\n            try {\n                array[Object] result = apiInstance.getTeamUsers(teamID, search, excludeBots);\n                Debug.WriteLine(result);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.getTeamUsers: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getTeamUsers-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$teamID = teamID_example; // String | Team ID\n$search = search_example; // String | string to filter users list\n$excludeBots = true; // Boolean | exclude bot users\n\ntry {\n    $result = $api_instance->getTeamUsers($teamID, $search, $excludeBots);\n    print_r($result);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->getTeamUsers: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getTeamUsers-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $teamID = teamID_example; # String | Team ID\nmy $search = search_example; # String | string to filter users list\nmy $excludeBots = true; # Boolean | exclude bot users\n\neval {\n    my $result = $api_instance->getTeamUsers(teamID => $teamID, search => $search, excludeBots => $excludeBots);\n    print Dumper($result);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->getTeamUsers: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getTeamUsers-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nteamID = teamID_example # String | Team ID (default to null)\nsearch = search_example # String | string to filter users list (optional) (default to null)\nexcludeBots = true # Boolean | exclude bot users (optional) (default to null)\n\ntry:\n    api_response = api_instance.get_team_users(teamID, search=search, excludeBots=excludeBots)\n    pprint(api_response)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->getTeamUsers: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getTeamUsers-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let teamID = teamID_example; // String\n    let search = search_example; // String\n    let excludeBots = true; // Boolean\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.getTeamUsers(teamID, search, excludeBots, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">teamID*</td>\n<td>\n\n\n    <div id=\"d2e199_getTeamUsers_teamID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nTeam ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n\n                            <div class=\"methodsubtabletitle\">Query parameters</div>\n                            <table id=\"methodsubtable\">\n                              <tr>\n                                <th width=\"150px\">Name</th>\n                                <th>Description</th>\n                              </tr>\n                                <tr><td style=\"width:150px;\">search</td>\n<td>\n\n\n    <div id=\"d2e199_getTeamUsers_search\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nstring to filter users list\n                    </div>\n            </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                                <tr><td style=\"width:150px;\">exclude_bots</td>\n<td>\n\n\n    <div id=\"d2e199_getTeamUsers_excludeBots\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    Boolean\n                </span>\n\n                    <div class=\"inner description marked\">\nexclude bot users\n                    </div>\n            </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-getTeamUsers-title-200\"></h3>\n                            <p id=\"examples-Default-getTeamUsers-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-getTeamUsers-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-getTeamUsers-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-getTeamUsers-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getTeamUsers-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-getTeamUsers-200-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getTeamUsers-200-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-getTeamUsers-200-schema\">\n                                  <div id=\"responses-Default-getTeamUsers-schema-200\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = ;\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-getTeamUsers-200-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-getTeamUsers-schema-200');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-getTeamUsers-200-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-getTeamUsers-title-default\"></h3>\n                            <p id=\"examples-Default-getTeamUsers-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-getTeamUsers-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-getTeamUsers-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-getTeamUsers-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getTeamUsers-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-getTeamUsers-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getTeamUsers-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-getTeamUsers-default-schema\">\n                                  <div id=\"responses-Default-getTeamUsers-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-getTeamUsers-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-getTeamUsers-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-getTeamUsers-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-getTeams\">\n                      <article id=\"api-Default-getTeams-0\" data-group=\"User\" data-name=\"getTeams\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>getTeams</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Returns information of all the teams</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"get\"><code><span class=\"pln\">/teams</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-getTeams-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getTeams-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getTeams-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-getTeams-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-getTeams-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getTeams-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-getTeams-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-getTeams-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getTeams-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getTeams-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getTeams-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getTeams-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-getTeams-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X GET \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n \"http://localhost/api/v2/teams\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-getTeams-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n\n        try {\n            array[Object] result = apiInstance.getTeams();\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#getTeams\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-getTeams-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n\n        try {\n            array[Object] result = apiInstance.getTeams();\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#getTeams\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-getTeams-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-getTeams-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\n\n[apiInstance getTeamsWithCompletionHandler: \n              ^(array[Object] output, NSError* error) {\n    if (output) {\n        NSLog(@\"%@\", output);\n    }\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getTeams-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully. Returned data: ' + data);\n  }\n};\napi.getTeams(callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-getTeams-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-getTeams-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class getTeamsExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n\n            try {\n                array[Object] result = apiInstance.getTeams();\n                Debug.WriteLine(result);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.getTeams: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getTeams-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n\ntry {\n    $result = $api_instance->getTeams();\n    print_r($result);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->getTeams: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getTeams-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\n\neval {\n    my $result = $api_instance->getTeams();\n    print Dumper($result);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->getTeams: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getTeams-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\n\ntry:\n    api_response = api_instance.get_teams()\n    pprint(api_response)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->getTeams: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getTeams-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.getTeams(&context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n\n\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-getTeams-title-200\"></h3>\n                            <p id=\"examples-Default-getTeams-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-getTeams-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-getTeams-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-getTeams-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getTeams-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-getTeams-200-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getTeams-200-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-getTeams-200-schema\">\n                                  <div id=\"responses-Default-getTeams-schema-200\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = ;\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-getTeams-200-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-getTeams-schema-200');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-getTeams-200-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-getTeams-title-default\"></h3>\n                            <p id=\"examples-Default-getTeams-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-getTeams-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-getTeams-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-getTeams-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getTeams-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-getTeams-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getTeams-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-getTeams-default-schema\">\n                                  <div id=\"responses-Default-getTeams-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-getTeams-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-getTeams-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-getTeams-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-getTemplates\">\n                      <article id=\"api-Default-getTemplates-0\" data-group=\"User\" data-name=\"getTemplates\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>getTemplates</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Returns team templates</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"get\"><code><span class=\"pln\">/teams/{teamID}/templates</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-getTemplates-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getTemplates-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getTemplates-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-getTemplates-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-getTemplates-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getTemplates-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-getTemplates-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-getTemplates-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getTemplates-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getTemplates-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getTemplates-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getTemplates-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-getTemplates-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X GET \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n \"http://localhost/api/v2/teams/{teamID}/templates\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-getTemplates-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String teamID = teamID_example; // String | Team ID\n\n        try {\n            array[Object] result = apiInstance.getTemplates(teamID);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#getTemplates\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-getTemplates-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String teamID = teamID_example; // String | Team ID\n\n        try {\n            array[Object] result = apiInstance.getTemplates(teamID);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#getTemplates\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-getTemplates-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-getTemplates-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *teamID = teamID_example; // Team ID (default to null)\n\n[apiInstance getTemplatesWith:teamID\n              completionHandler: ^(array[Object] output, NSError* error) {\n    if (output) {\n        NSLog(@\"%@\", output);\n    }\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getTemplates-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar teamID = teamID_example; // {String} Team ID\n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully. Returned data: ' + data);\n  }\n};\napi.getTemplates(teamID, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-getTemplates-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-getTemplates-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class getTemplatesExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var teamID = teamID_example;  // String | Team ID (default to null)\n\n            try {\n                array[Object] result = apiInstance.getTemplates(teamID);\n                Debug.WriteLine(result);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.getTemplates: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getTemplates-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$teamID = teamID_example; // String | Team ID\n\ntry {\n    $result = $api_instance->getTemplates($teamID);\n    print_r($result);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->getTemplates: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getTemplates-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $teamID = teamID_example; # String | Team ID\n\neval {\n    my $result = $api_instance->getTemplates(teamID => $teamID);\n    print Dumper($result);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->getTemplates: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getTemplates-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nteamID = teamID_example # String | Team ID (default to null)\n\ntry:\n    api_response = api_instance.get_templates(teamID)\n    pprint(api_response)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->getTemplates: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getTemplates-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let teamID = teamID_example; // String\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.getTemplates(teamID, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">teamID*</td>\n<td>\n\n\n    <div id=\"d2e199_getTemplates_teamID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nTeam ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-getTemplates-title-200\"></h3>\n                            <p id=\"examples-Default-getTemplates-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-getTemplates-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-getTemplates-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-getTemplates-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getTemplates-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-getTemplates-200-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getTemplates-200-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-getTemplates-200-schema\">\n                                  <div id=\"responses-Default-getTemplates-schema-200\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = ;\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-getTemplates-200-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-getTemplates-schema-200');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-getTemplates-200-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-getTemplates-title-default\"></h3>\n                            <p id=\"examples-Default-getTemplates-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-getTemplates-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-getTemplates-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-getTemplates-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getTemplates-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-getTemplates-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getTemplates-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-getTemplates-default-schema\">\n                                  <div id=\"responses-Default-getTemplates-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-getTemplates-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-getTemplates-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-getTemplates-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-getUser\">\n                      <article id=\"api-Default-getUser-0\" data-group=\"User\" data-name=\"getUser\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>getUser</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Returns a user</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"get\"><code><span class=\"pln\">/users/{userID}</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-getUser-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getUser-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getUser-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-getUser-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-getUser-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getUser-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-getUser-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-getUser-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getUser-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getUser-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getUser-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getUser-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-getUser-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X GET \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n \"http://localhost/api/v2/users/{userID}\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-getUser-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String userID = userID_example; // String | User ID\n\n        try {\n            Object result = apiInstance.getUser(userID);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#getUser\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-getUser-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String userID = userID_example; // String | User ID\n\n        try {\n            Object result = apiInstance.getUser(userID);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#getUser\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-getUser-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-getUser-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *userID = userID_example; // User ID (default to null)\n\n[apiInstance getUserWith:userID\n              completionHandler: ^(Object output, NSError* error) {\n    if (output) {\n        NSLog(@\"%@\", output);\n    }\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getUser-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar userID = userID_example; // {String} User ID\n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully. Returned data: ' + data);\n  }\n};\napi.getUser(userID, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-getUser-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-getUser-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class getUserExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var userID = userID_example;  // String | User ID (default to null)\n\n            try {\n                Object result = apiInstance.getUser(userID);\n                Debug.WriteLine(result);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.getUser: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getUser-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$userID = userID_example; // String | User ID\n\ntry {\n    $result = $api_instance->getUser($userID);\n    print_r($result);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->getUser: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getUser-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $userID = userID_example; # String | User ID\n\neval {\n    my $result = $api_instance->getUser(userID => $userID);\n    print Dumper($result);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->getUser: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getUser-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nuserID = userID_example # String | User ID (default to null)\n\ntry:\n    api_response = api_instance.get_user(userID)\n    pprint(api_response)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->getUser: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getUser-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let userID = userID_example; // String\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.getUser(userID, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">userID*</td>\n<td>\n\n\n    <div id=\"d2e199_getUser_userID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nUser ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-getUser-title-200\"></h3>\n                            <p id=\"examples-Default-getUser-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-getUser-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-getUser-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-getUser-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getUser-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-getUser-200-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getUser-200-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-getUser-200-schema\">\n                                  <div id=\"responses-Default-getUser-schema-200\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=User is a user, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-getUser-200-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-getUser-schema-200');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-getUser-200-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-getUser-title-default\"></h3>\n                            <p id=\"examples-Default-getUser-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-getUser-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-getUser-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-getUser-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getUser-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-getUser-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getUser-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-getUser-default-schema\">\n                                  <div id=\"responses-Default-getUser-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-getUser-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-getUser-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-getUser-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-getUserBoardsInsights\">\n                      <article id=\"api-Default-getUserBoardsInsights-0\" data-group=\"User\" data-name=\"getUserBoardsInsights\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>getUserBoardsInsights</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Returns user boards insights</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"get\"><code><span class=\"pln\">/users/me/boards/insights</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-getUserBoardsInsights-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getUserBoardsInsights-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getUserBoardsInsights-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-getUserBoardsInsights-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-getUserBoardsInsights-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getUserBoardsInsights-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-getUserBoardsInsights-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-getUserBoardsInsights-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getUserBoardsInsights-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getUserBoardsInsights-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getUserBoardsInsights-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getUserBoardsInsights-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-getUserBoardsInsights-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X GET \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n \"http://localhost/api/v2/users/me/boards/insights?time_range=timeRange_example&page=page_example&per_page=perPage_example\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-getUserBoardsInsights-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String teamID = teamID_example; // String | Team ID\n        String timeRange = timeRange_example; // String | duration of data to calculate insights for\n        String page = page_example; // String | page offset for top boards\n        String perPage = perPage_example; // String | limit for boards in a page.\n\n        try {\n            array[Object] result = apiInstance.getUserBoardsInsights(teamID, timeRange, page, perPage);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#getUserBoardsInsights\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-getUserBoardsInsights-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String teamID = teamID_example; // String | Team ID\n        String timeRange = timeRange_example; // String | duration of data to calculate insights for\n        String page = page_example; // String | page offset for top boards\n        String perPage = perPage_example; // String | limit for boards in a page.\n\n        try {\n            array[Object] result = apiInstance.getUserBoardsInsights(teamID, timeRange, page, perPage);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#getUserBoardsInsights\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-getUserBoardsInsights-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-getUserBoardsInsights-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *teamID = teamID_example; // Team ID (default to null)\nString *timeRange = timeRange_example; // duration of data to calculate insights for (default to null)\nString *page = page_example; // page offset for top boards (default to null)\nString *perPage = perPage_example; // limit for boards in a page. (default to null)\n\n[apiInstance getUserBoardsInsightsWith:teamID\n    timeRange:timeRange\n    page:page\n    perPage:perPage\n              completionHandler: ^(array[Object] output, NSError* error) {\n    if (output) {\n        NSLog(@\"%@\", output);\n    }\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getUserBoardsInsights-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar teamID = teamID_example; // {String} Team ID\nvar timeRange = timeRange_example; // {String} duration of data to calculate insights for\nvar page = page_example; // {String} page offset for top boards\nvar perPage = perPage_example; // {String} limit for boards in a page.\n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully. Returned data: ' + data);\n  }\n};\napi.getUserBoardsInsights(teamID, timeRange, page, perPage, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-getUserBoardsInsights-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-getUserBoardsInsights-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class getUserBoardsInsightsExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var teamID = teamID_example;  // String | Team ID (default to null)\n            var timeRange = timeRange_example;  // String | duration of data to calculate insights for (default to null)\n            var page = page_example;  // String | page offset for top boards (default to null)\n            var perPage = perPage_example;  // String | limit for boards in a page. (default to null)\n\n            try {\n                array[Object] result = apiInstance.getUserBoardsInsights(teamID, timeRange, page, perPage);\n                Debug.WriteLine(result);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.getUserBoardsInsights: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getUserBoardsInsights-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$teamID = teamID_example; // String | Team ID\n$timeRange = timeRange_example; // String | duration of data to calculate insights for\n$page = page_example; // String | page offset for top boards\n$perPage = perPage_example; // String | limit for boards in a page.\n\ntry {\n    $result = $api_instance->getUserBoardsInsights($teamID, $timeRange, $page, $perPage);\n    print_r($result);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->getUserBoardsInsights: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getUserBoardsInsights-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $teamID = teamID_example; # String | Team ID\nmy $timeRange = timeRange_example; # String | duration of data to calculate insights for\nmy $page = page_example; # String | page offset for top boards\nmy $perPage = perPage_example; # String | limit for boards in a page.\n\neval {\n    my $result = $api_instance->getUserBoardsInsights(teamID => $teamID, timeRange => $timeRange, page => $page, perPage => $perPage);\n    print Dumper($result);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->getUserBoardsInsights: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getUserBoardsInsights-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nteamID = teamID_example # String | Team ID (default to null)\ntimeRange = timeRange_example # String | duration of data to calculate insights for (default to null)\npage = page_example # String | page offset for top boards (default to null)\nperPage = perPage_example # String | limit for boards in a page. (default to null)\n\ntry:\n    api_response = api_instance.get_user_boards_insights(teamID, timeRange, page, perPage)\n    pprint(api_response)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->getUserBoardsInsights: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getUserBoardsInsights-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let teamID = teamID_example; // String\n    let timeRange = timeRange_example; // String\n    let page = page_example; // String\n    let perPage = perPage_example; // String\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.getUserBoardsInsights(teamID, timeRange, page, perPage, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">teamID*</td>\n<td>\n\n\n    <div id=\"d2e199_getUserBoardsInsights_teamID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nTeam ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n\n                            <div class=\"methodsubtabletitle\">Query parameters</div>\n                            <table id=\"methodsubtable\">\n                              <tr>\n                                <th width=\"150px\">Name</th>\n                                <th>Description</th>\n                              </tr>\n                                <tr><td style=\"width:150px;\">time_range*</td>\n<td>\n\n\n    <div id=\"d2e199_getUserBoardsInsights_timeRange\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nduration of data to calculate insights for\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                                <tr><td style=\"width:150px;\">page*</td>\n<td>\n\n\n    <div id=\"d2e199_getUserBoardsInsights_page\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\npage offset for top boards\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                                <tr><td style=\"width:150px;\">per_page*</td>\n<td>\n\n\n    <div id=\"d2e199_getUserBoardsInsights_perPage\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nlimit for boards in a page.\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-getUserBoardsInsights-title-200\"></h3>\n                            <p id=\"examples-Default-getUserBoardsInsights-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-getUserBoardsInsights-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-getUserBoardsInsights-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-getUserBoardsInsights-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getUserBoardsInsights-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-getUserBoardsInsights-200-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getUserBoardsInsights-200-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-getUserBoardsInsights-200-schema\">\n                                  <div id=\"responses-Default-getUserBoardsInsights-schema-200\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = ;\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-getUserBoardsInsights-200-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-getUserBoardsInsights-schema-200');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-getUserBoardsInsights-200-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-getUserBoardsInsights-title-default\"></h3>\n                            <p id=\"examples-Default-getUserBoardsInsights-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-getUserBoardsInsights-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-getUserBoardsInsights-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-getUserBoardsInsights-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getUserBoardsInsights-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-getUserBoardsInsights-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getUserBoardsInsights-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-getUserBoardsInsights-default-schema\">\n                                  <div id=\"responses-Default-getUserBoardsInsights-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-getUserBoardsInsights-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-getUserBoardsInsights-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-getUserBoardsInsights-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-getUserCategoryBoards\">\n                      <article id=\"api-Default-getUserCategoryBoards-0\" data-group=\"User\" data-name=\"getUserCategoryBoards\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>getUserCategoryBoards</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Gets the user&#39;s board categories</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"get\"><code><span class=\"pln\">/teams/{teamID}/categories</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-getUserCategoryBoards-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getUserCategoryBoards-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getUserCategoryBoards-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-getUserCategoryBoards-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-getUserCategoryBoards-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getUserCategoryBoards-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-getUserCategoryBoards-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-getUserCategoryBoards-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getUserCategoryBoards-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getUserCategoryBoards-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getUserCategoryBoards-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getUserCategoryBoards-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-getUserCategoryBoards-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X GET \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n \"http://localhost/api/v2/teams/{teamID}/categories\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-getUserCategoryBoards-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String teamID = teamID_example; // String | Team ID\n\n        try {\n            array[Object] result = apiInstance.getUserCategoryBoards(teamID);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#getUserCategoryBoards\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-getUserCategoryBoards-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String teamID = teamID_example; // String | Team ID\n\n        try {\n            array[Object] result = apiInstance.getUserCategoryBoards(teamID);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#getUserCategoryBoards\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-getUserCategoryBoards-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-getUserCategoryBoards-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *teamID = teamID_example; // Team ID (default to null)\n\n[apiInstance getUserCategoryBoardsWith:teamID\n              completionHandler: ^(array[Object] output, NSError* error) {\n    if (output) {\n        NSLog(@\"%@\", output);\n    }\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getUserCategoryBoards-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar teamID = teamID_example; // {String} Team ID\n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully. Returned data: ' + data);\n  }\n};\napi.getUserCategoryBoards(teamID, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-getUserCategoryBoards-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-getUserCategoryBoards-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class getUserCategoryBoardsExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var teamID = teamID_example;  // String | Team ID (default to null)\n\n            try {\n                array[Object] result = apiInstance.getUserCategoryBoards(teamID);\n                Debug.WriteLine(result);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.getUserCategoryBoards: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getUserCategoryBoards-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$teamID = teamID_example; // String | Team ID\n\ntry {\n    $result = $api_instance->getUserCategoryBoards($teamID);\n    print_r($result);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->getUserCategoryBoards: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getUserCategoryBoards-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $teamID = teamID_example; # String | Team ID\n\neval {\n    my $result = $api_instance->getUserCategoryBoards(teamID => $teamID);\n    print Dumper($result);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->getUserCategoryBoards: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getUserCategoryBoards-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nteamID = teamID_example # String | Team ID (default to null)\n\ntry:\n    api_response = api_instance.get_user_category_boards(teamID)\n    pprint(api_response)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->getUserCategoryBoards: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getUserCategoryBoards-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let teamID = teamID_example; // String\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.getUserCategoryBoards(teamID, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">teamID*</td>\n<td>\n\n\n    <div id=\"d2e199_getUserCategoryBoards_teamID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nTeam ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-getUserCategoryBoards-title-200\"></h3>\n                            <p id=\"examples-Default-getUserCategoryBoards-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-getUserCategoryBoards-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-getUserCategoryBoards-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-getUserCategoryBoards-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getUserCategoryBoards-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-getUserCategoryBoards-200-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getUserCategoryBoards-200-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-getUserCategoryBoards-200-schema\">\n                                  <div id=\"responses-Default-getUserCategoryBoards-schema-200\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = ;\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-getUserCategoryBoards-200-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-getUserCategoryBoards-schema-200');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-getUserCategoryBoards-200-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-getUserCategoryBoards-title-default\"></h3>\n                            <p id=\"examples-Default-getUserCategoryBoards-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-getUserCategoryBoards-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-getUserCategoryBoards-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-getUserCategoryBoards-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getUserCategoryBoards-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-getUserCategoryBoards-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getUserCategoryBoards-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-getUserCategoryBoards-default-schema\">\n                                  <div id=\"responses-Default-getUserCategoryBoards-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-getUserCategoryBoards-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-getUserCategoryBoards-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-getUserCategoryBoards-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-getUserConfig\">\n                      <article id=\"api-Default-getUserConfig-0\" data-group=\"User\" data-name=\"getUserConfig\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>getUserConfig</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Returns an array of user preferences</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"get\"><code><span class=\"pln\">/users/me/config</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-getUserConfig-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getUserConfig-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getUserConfig-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-getUserConfig-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-getUserConfig-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getUserConfig-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-getUserConfig-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-getUserConfig-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getUserConfig-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getUserConfig-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getUserConfig-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getUserConfig-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-getUserConfig-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X GET \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n \"http://localhost/api/v2/users/me/config\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-getUserConfig-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n\n        try {\n            Preferences result = apiInstance.getUserConfig();\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#getUserConfig\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-getUserConfig-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n\n        try {\n            Preferences result = apiInstance.getUserConfig();\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#getUserConfig\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-getUserConfig-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-getUserConfig-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\n\n[apiInstance getUserConfigWithCompletionHandler: \n              ^(Preferences output, NSError* error) {\n    if (output) {\n        NSLog(@\"%@\", output);\n    }\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getUserConfig-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully. Returned data: ' + data);\n  }\n};\napi.getUserConfig(callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-getUserConfig-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-getUserConfig-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class getUserConfigExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n\n            try {\n                Preferences result = apiInstance.getUserConfig();\n                Debug.WriteLine(result);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.getUserConfig: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getUserConfig-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n\ntry {\n    $result = $api_instance->getUserConfig();\n    print_r($result);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->getUserConfig: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getUserConfig-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\n\neval {\n    my $result = $api_instance->getUserConfig();\n    print Dumper($result);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->getUserConfig: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getUserConfig-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\n\ntry:\n    api_response = api_instance.get_user_config()\n    pprint(api_response)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->getUserConfig: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getUserConfig-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.getUserConfig(&context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n\n\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-getUserConfig-title-200\"></h3>\n                            <p id=\"examples-Default-getUserConfig-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-getUserConfig-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-getUserConfig-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-getUserConfig-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getUserConfig-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-getUserConfig-200-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getUserConfig-200-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-getUserConfig-200-schema\">\n                                  <div id=\"responses-Default-getUserConfig-schema-200\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = ;\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-getUserConfig-200-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-getUserConfig-schema-200');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-getUserConfig-200-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-getUserConfig-title-default\"></h3>\n                            <p id=\"examples-Default-getUserConfig-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-getUserConfig-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-getUserConfig-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-getUserConfig-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getUserConfig-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-getUserConfig-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getUserConfig-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-getUserConfig-default-schema\">\n                                  <div id=\"responses-Default-getUserConfig-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-getUserConfig-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-getUserConfig-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-getUserConfig-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-getUsersList\">\n                      <article id=\"api-Default-getUsersList-0\" data-group=\"User\" data-name=\"getUsersList\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>getUsersList</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Returns a user[]</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"post\"><code><span class=\"pln\">/users</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-getUsersList-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getUsersList-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getUsersList-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-getUsersList-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-getUsersList-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getUsersList-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-getUsersList-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-getUsersList-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getUsersList-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getUsersList-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getUsersList-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-getUsersList-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-getUsersList-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X POST \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n \"http://localhost/api/v2/users\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-getUsersList-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String userID = userID_example; // String | User ID\n\n        try {\n            Object result = apiInstance.getUsersList(userID);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#getUsersList\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-getUsersList-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String userID = userID_example; // String | User ID\n\n        try {\n            Object result = apiInstance.getUsersList(userID);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#getUsersList\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-getUsersList-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-getUsersList-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *userID = userID_example; // User ID (default to null)\n\n[apiInstance getUsersListWith:userID\n              completionHandler: ^(Object output, NSError* error) {\n    if (output) {\n        NSLog(@\"%@\", output);\n    }\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getUsersList-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar userID = userID_example; // {String} User ID\n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully. Returned data: ' + data);\n  }\n};\napi.getUsersList(userID, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-getUsersList-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-getUsersList-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class getUsersListExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var userID = userID_example;  // String | User ID (default to null)\n\n            try {\n                Object result = apiInstance.getUsersList(userID);\n                Debug.WriteLine(result);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.getUsersList: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getUsersList-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$userID = userID_example; // String | User ID\n\ntry {\n    $result = $api_instance->getUsersList($userID);\n    print_r($result);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->getUsersList: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getUsersList-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $userID = userID_example; # String | User ID\n\neval {\n    my $result = $api_instance->getUsersList(userID => $userID);\n    print Dumper($result);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->getUsersList: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getUsersList-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nuserID = userID_example # String | User ID (default to null)\n\ntry:\n    api_response = api_instance.get_users_list(userID)\n    pprint(api_response)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->getUsersList: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-getUsersList-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let userID = userID_example; // String\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.getUsersList(userID, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">userID*</td>\n<td>\n\n\n    <div id=\"d2e199_getUsersList_userID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nUser ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-getUsersList-title-200\"></h3>\n                            <p id=\"examples-Default-getUsersList-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-getUsersList-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-getUsersList-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-getUsersList-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getUsersList-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-getUsersList-200-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getUsersList-200-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-getUsersList-200-schema\">\n                                  <div id=\"responses-Default-getUsersList-schema-200\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=User is a user, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-getUsersList-200-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-getUsersList-schema-200');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-getUsersList-200-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-getUsersList-title-default\"></h3>\n                            <p id=\"examples-Default-getUsersList-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-getUsersList-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-getUsersList-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-getUsersList-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-getUsersList-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-getUsersList-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-getUsersList-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-getUsersList-default-schema\">\n                                  <div id=\"responses-Default-getUsersList-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-getUsersList-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-getUsersList-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-getUsersList-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-handleNotifyAdminUpgrade\">\n                      <article id=\"api-Default-handleNotifyAdminUpgrade-0\" data-group=\"User\" data-name=\"handleNotifyAdminUpgrade\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>handleNotifyAdminUpgrade</h1>\n                          <p>Notifies admins for upgrade request.</p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\"></p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"get\"><code><span class=\"pln\">/api/v2/teams/{teamID}/notifyadminupgrade</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-handleNotifyAdminUpgrade-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-handleNotifyAdminUpgrade-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-handleNotifyAdminUpgrade-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-handleNotifyAdminUpgrade-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-handleNotifyAdminUpgrade-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-handleNotifyAdminUpgrade-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-handleNotifyAdminUpgrade-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-handleNotifyAdminUpgrade-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-handleNotifyAdminUpgrade-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-handleNotifyAdminUpgrade-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-handleNotifyAdminUpgrade-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-handleNotifyAdminUpgrade-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-handleNotifyAdminUpgrade-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X GET \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n \"http://localhost/api/v2/api/v2/teams/{teamID}/notifyadminupgrade\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-handleNotifyAdminUpgrade-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String teamID = teamID_example; // String | Team ID\n\n        try {\n            apiInstance.handleNotifyAdminUpgrade(teamID);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#handleNotifyAdminUpgrade\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-handleNotifyAdminUpgrade-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String teamID = teamID_example; // String | Team ID\n\n        try {\n            apiInstance.handleNotifyAdminUpgrade(teamID);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#handleNotifyAdminUpgrade\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-handleNotifyAdminUpgrade-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-handleNotifyAdminUpgrade-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *teamID = teamID_example; // Team ID (default to null)\n\n// Notifies admins for upgrade request.\n[apiInstance handleNotifyAdminUpgradeWith:teamID\n              completionHandler: ^(NSError* error) {\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-handleNotifyAdminUpgrade-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar teamID = teamID_example; // {String} Team ID\n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully.');\n  }\n};\napi.handleNotifyAdminUpgrade(teamID, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-handleNotifyAdminUpgrade-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-handleNotifyAdminUpgrade-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class handleNotifyAdminUpgradeExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var teamID = teamID_example;  // String | Team ID (default to null)\n\n            try {\n                // Notifies admins for upgrade request.\n                apiInstance.handleNotifyAdminUpgrade(teamID);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.handleNotifyAdminUpgrade: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-handleNotifyAdminUpgrade-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$teamID = teamID_example; // String | Team ID\n\ntry {\n    $api_instance->handleNotifyAdminUpgrade($teamID);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->handleNotifyAdminUpgrade: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-handleNotifyAdminUpgrade-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $teamID = teamID_example; # String | Team ID\n\neval {\n    $api_instance->handleNotifyAdminUpgrade(teamID => $teamID);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->handleNotifyAdminUpgrade: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-handleNotifyAdminUpgrade-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nteamID = teamID_example # String | Team ID (default to null)\n\ntry:\n    # Notifies admins for upgrade request.\n    api_instance.handle_notify_admin_upgrade(teamID)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->handleNotifyAdminUpgrade: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-handleNotifyAdminUpgrade-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let teamID = teamID_example; // String\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.handleNotifyAdminUpgrade(teamID, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">teamID*</td>\n<td>\n\n\n    <div id=\"d2e199_handleNotifyAdminUpgrade_teamID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nTeam ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-handleNotifyAdminUpgrade-title-200\"></h3>\n                            <p id=\"examples-Default-handleNotifyAdminUpgrade-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-handleNotifyAdminUpgrade-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-handleNotifyAdminUpgrade-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-handleNotifyAdminUpgrade-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-handleNotifyAdminUpgrade-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-handleNotifyAdminUpgrade-200-wrapper\" style='margin-bottom: 10px;'>\n                            </div>\n                            <h3 id=\"examples-Default-handleNotifyAdminUpgrade-title-default\"></h3>\n                            <p id=\"examples-Default-handleNotifyAdminUpgrade-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-handleNotifyAdminUpgrade-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-handleNotifyAdminUpgrade-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-handleNotifyAdminUpgrade-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-handleNotifyAdminUpgrade-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-handleNotifyAdminUpgrade-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-handleNotifyAdminUpgrade-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-handleNotifyAdminUpgrade-default-schema\">\n                                  <div id=\"responses-Default-handleNotifyAdminUpgrade-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-handleNotifyAdminUpgrade-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-handleNotifyAdminUpgrade-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-handleNotifyAdminUpgrade-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-handleStatistics\">\n                      <article id=\"api-Default-handleStatistics-0\" data-group=\"User\" data-name=\"handleStatistics\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>handleStatistics</h1>\n                          <p>Fetches the statistic  of the server.</p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\"></p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"get\"><code><span class=\"pln\">/statistics</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-handleStatistics-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-handleStatistics-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-handleStatistics-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-handleStatistics-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-handleStatistics-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-handleStatistics-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-handleStatistics-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-handleStatistics-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-handleStatistics-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-handleStatistics-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-handleStatistics-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-handleStatistics-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-handleStatistics-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X GET \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n \"http://localhost/api/v2/statistics\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-handleStatistics-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n\n        try {\n            BoardStatistics result = apiInstance.handleStatistics();\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#handleStatistics\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-handleStatistics-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n\n        try {\n            BoardStatistics result = apiInstance.handleStatistics();\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#handleStatistics\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-handleStatistics-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-handleStatistics-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\n\n// Fetches the statistic  of the server.\n[apiInstance handleStatisticsWithCompletionHandler: \n              ^(BoardStatistics output, NSError* error) {\n    if (output) {\n        NSLog(@\"%@\", output);\n    }\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-handleStatistics-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully. Returned data: ' + data);\n  }\n};\napi.handleStatistics(callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-handleStatistics-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-handleStatistics-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class handleStatisticsExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n\n            try {\n                // Fetches the statistic  of the server.\n                BoardStatistics result = apiInstance.handleStatistics();\n                Debug.WriteLine(result);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.handleStatistics: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-handleStatistics-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n\ntry {\n    $result = $api_instance->handleStatistics();\n    print_r($result);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->handleStatistics: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-handleStatistics-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\n\neval {\n    my $result = $api_instance->handleStatistics();\n    print Dumper($result);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->handleStatistics: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-handleStatistics-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\n\ntry:\n    # Fetches the statistic  of the server.\n    api_response = api_instance.handle_statistics()\n    pprint(api_response)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->handleStatistics: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-handleStatistics-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.handleStatistics(&context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n\n\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-handleStatistics-title-200\"></h3>\n                            <p id=\"examples-Default-handleStatistics-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-handleStatistics-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-handleStatistics-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-handleStatistics-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-handleStatistics-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-handleStatistics-200-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-handleStatistics-200-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-handleStatistics-200-schema\">\n                                  <div id=\"responses-Default-handleStatistics-schema-200\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = ;\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-handleStatistics-200-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-handleStatistics-schema-200');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-handleStatistics-200-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-handleStatistics-title-default\"></h3>\n                            <p id=\"examples-Default-handleStatistics-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-handleStatistics-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-handleStatistics-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-handleStatistics-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-handleStatistics-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-handleStatistics-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-handleStatistics-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-handleStatistics-default-schema\">\n                                  <div id=\"responses-Default-handleStatistics-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-handleStatistics-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-handleStatistics-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-handleStatistics-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-handleTeamBoardsInsights\">\n                      <article id=\"api-Default-handleTeamBoardsInsights-0\" data-group=\"User\" data-name=\"handleTeamBoardsInsights\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>handleTeamBoardsInsights</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Returns team boards insights</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"get\"><code><span class=\"pln\">/teams/{teamID}/boards/insights</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-handleTeamBoardsInsights-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-handleTeamBoardsInsights-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-handleTeamBoardsInsights-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-handleTeamBoardsInsights-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-handleTeamBoardsInsights-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-handleTeamBoardsInsights-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-handleTeamBoardsInsights-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-handleTeamBoardsInsights-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-handleTeamBoardsInsights-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-handleTeamBoardsInsights-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-handleTeamBoardsInsights-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-handleTeamBoardsInsights-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-handleTeamBoardsInsights-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X GET \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n \"http://localhost/api/v2/teams/{teamID}/boards/insights?time_range=timeRange_example&page=page_example&per_page=perPage_example\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-handleTeamBoardsInsights-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String teamID = teamID_example; // String | Team ID\n        String timeRange = timeRange_example; // String | duration of data to calculate insights for\n        String page = page_example; // String | page offset for top boards\n        String perPage = perPage_example; // String | limit for boards in a page.\n\n        try {\n            array[Object] result = apiInstance.handleTeamBoardsInsights(teamID, timeRange, page, perPage);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#handleTeamBoardsInsights\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-handleTeamBoardsInsights-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String teamID = teamID_example; // String | Team ID\n        String timeRange = timeRange_example; // String | duration of data to calculate insights for\n        String page = page_example; // String | page offset for top boards\n        String perPage = perPage_example; // String | limit for boards in a page.\n\n        try {\n            array[Object] result = apiInstance.handleTeamBoardsInsights(teamID, timeRange, page, perPage);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#handleTeamBoardsInsights\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-handleTeamBoardsInsights-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-handleTeamBoardsInsights-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *teamID = teamID_example; // Team ID (default to null)\nString *timeRange = timeRange_example; // duration of data to calculate insights for (default to null)\nString *page = page_example; // page offset for top boards (default to null)\nString *perPage = perPage_example; // limit for boards in a page. (default to null)\n\n[apiInstance handleTeamBoardsInsightsWith:teamID\n    timeRange:timeRange\n    page:page\n    perPage:perPage\n              completionHandler: ^(array[Object] output, NSError* error) {\n    if (output) {\n        NSLog(@\"%@\", output);\n    }\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-handleTeamBoardsInsights-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar teamID = teamID_example; // {String} Team ID\nvar timeRange = timeRange_example; // {String} duration of data to calculate insights for\nvar page = page_example; // {String} page offset for top boards\nvar perPage = perPage_example; // {String} limit for boards in a page.\n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully. Returned data: ' + data);\n  }\n};\napi.handleTeamBoardsInsights(teamID, timeRange, page, perPage, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-handleTeamBoardsInsights-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-handleTeamBoardsInsights-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class handleTeamBoardsInsightsExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var teamID = teamID_example;  // String | Team ID (default to null)\n            var timeRange = timeRange_example;  // String | duration of data to calculate insights for (default to null)\n            var page = page_example;  // String | page offset for top boards (default to null)\n            var perPage = perPage_example;  // String | limit for boards in a page. (default to null)\n\n            try {\n                array[Object] result = apiInstance.handleTeamBoardsInsights(teamID, timeRange, page, perPage);\n                Debug.WriteLine(result);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.handleTeamBoardsInsights: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-handleTeamBoardsInsights-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$teamID = teamID_example; // String | Team ID\n$timeRange = timeRange_example; // String | duration of data to calculate insights for\n$page = page_example; // String | page offset for top boards\n$perPage = perPage_example; // String | limit for boards in a page.\n\ntry {\n    $result = $api_instance->handleTeamBoardsInsights($teamID, $timeRange, $page, $perPage);\n    print_r($result);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->handleTeamBoardsInsights: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-handleTeamBoardsInsights-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $teamID = teamID_example; # String | Team ID\nmy $timeRange = timeRange_example; # String | duration of data to calculate insights for\nmy $page = page_example; # String | page offset for top boards\nmy $perPage = perPage_example; # String | limit for boards in a page.\n\neval {\n    my $result = $api_instance->handleTeamBoardsInsights(teamID => $teamID, timeRange => $timeRange, page => $page, perPage => $perPage);\n    print Dumper($result);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->handleTeamBoardsInsights: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-handleTeamBoardsInsights-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nteamID = teamID_example # String | Team ID (default to null)\ntimeRange = timeRange_example # String | duration of data to calculate insights for (default to null)\npage = page_example # String | page offset for top boards (default to null)\nperPage = perPage_example # String | limit for boards in a page. (default to null)\n\ntry:\n    api_response = api_instance.handle_team_boards_insights(teamID, timeRange, page, perPage)\n    pprint(api_response)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->handleTeamBoardsInsights: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-handleTeamBoardsInsights-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let teamID = teamID_example; // String\n    let timeRange = timeRange_example; // String\n    let page = page_example; // String\n    let perPage = perPage_example; // String\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.handleTeamBoardsInsights(teamID, timeRange, page, perPage, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">teamID*</td>\n<td>\n\n\n    <div id=\"d2e199_handleTeamBoardsInsights_teamID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nTeam ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n\n                            <div class=\"methodsubtabletitle\">Query parameters</div>\n                            <table id=\"methodsubtable\">\n                              <tr>\n                                <th width=\"150px\">Name</th>\n                                <th>Description</th>\n                              </tr>\n                                <tr><td style=\"width:150px;\">time_range*</td>\n<td>\n\n\n    <div id=\"d2e199_handleTeamBoardsInsights_timeRange\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nduration of data to calculate insights for\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                                <tr><td style=\"width:150px;\">page*</td>\n<td>\n\n\n    <div id=\"d2e199_handleTeamBoardsInsights_page\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\npage offset for top boards\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                                <tr><td style=\"width:150px;\">per_page*</td>\n<td>\n\n\n    <div id=\"d2e199_handleTeamBoardsInsights_perPage\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nlimit for boards in a page.\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-handleTeamBoardsInsights-title-200\"></h3>\n                            <p id=\"examples-Default-handleTeamBoardsInsights-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-handleTeamBoardsInsights-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-handleTeamBoardsInsights-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-handleTeamBoardsInsights-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-handleTeamBoardsInsights-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-handleTeamBoardsInsights-200-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-handleTeamBoardsInsights-200-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-handleTeamBoardsInsights-200-schema\">\n                                  <div id=\"responses-Default-handleTeamBoardsInsights-schema-200\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = ;\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-handleTeamBoardsInsights-200-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-handleTeamBoardsInsights-schema-200');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-handleTeamBoardsInsights-200-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-handleTeamBoardsInsights-title-default\"></h3>\n                            <p id=\"examples-Default-handleTeamBoardsInsights-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-handleTeamBoardsInsights-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-handleTeamBoardsInsights-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-handleTeamBoardsInsights-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-handleTeamBoardsInsights-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-handleTeamBoardsInsights-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-handleTeamBoardsInsights-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-handleTeamBoardsInsights-default-schema\">\n                                  <div id=\"responses-Default-handleTeamBoardsInsights-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-handleTeamBoardsInsights-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-handleTeamBoardsInsights-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-handleTeamBoardsInsights-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-hello\">\n                      <article id=\"api-Default-hello-0\" data-group=\"User\" data-name=\"hello\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>hello</h1>\n                          <p>Responds with &#x60;Hello&#x60; if the web service is running.</p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\"></p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"get\"><code><span class=\"pln\">/hello</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-hello-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-hello-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-hello-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-hello-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-hello-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-hello-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-hello-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-hello-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-hello-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-hello-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-hello-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-hello-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-hello-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X GET \\\n \"http://localhost/api/v2/hello\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-hello-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n\n        try {\n            apiInstance.hello();\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#hello\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-hello-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n\n        try {\n            apiInstance.hello();\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#hello\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-hello-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-hello-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\n\n// Responds with `Hello` if the web service is running.\n[apiInstance helloWithCompletionHandler: \n              ^(NSError* error) {\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-hello-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully.');\n  }\n};\napi.hello(callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-hello-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-hello-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class helloExample\n    {\n        public void main()\n        {\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n\n            try {\n                // Responds with `Hello` if the web service is running.\n                apiInstance.hello();\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.hello: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-hello-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n\ntry {\n    $api_instance->hello();\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->hello: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-hello-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\n\neval {\n    $api_instance->hello();\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->hello: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-hello-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\n\ntry:\n    # Responds with `Hello` if the web service is running.\n    api_instance.hello()\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->hello: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-hello-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.hello(&context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n\n\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-hello-title-200\"></h3>\n                            <p id=\"examples-Default-hello-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-hello-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-hello-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-hello-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-hello-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-hello-200-wrapper\" style='margin-bottom: 10px;'>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-insertBoardsAndBlocks\">\n                      <article id=\"api-Default-insertBoardsAndBlocks-0\" data-group=\"User\" data-name=\"insertBoardsAndBlocks\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>insertBoardsAndBlocks</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Creates new boards and blocks</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"post\"><code><span class=\"pln\">/boards-and-blocks</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-insertBoardsAndBlocks-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-insertBoardsAndBlocks-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-insertBoardsAndBlocks-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-insertBoardsAndBlocks-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-insertBoardsAndBlocks-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-insertBoardsAndBlocks-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-insertBoardsAndBlocks-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-insertBoardsAndBlocks-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-insertBoardsAndBlocks-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-insertBoardsAndBlocks-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-insertBoardsAndBlocks-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-insertBoardsAndBlocks-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-insertBoardsAndBlocks-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X POST \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n -H \"Content-Type: application/json\" \\\n \"http://localhost/api/v2/boards-and-blocks\" \\\n -d ''\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-insertBoardsAndBlocks-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        Object body = Object; // Object | \n\n        try {\n            Object result = apiInstance.insertBoardsAndBlocks(body);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#insertBoardsAndBlocks\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-insertBoardsAndBlocks-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        Object body = Object; // Object | \n\n        try {\n            Object result = apiInstance.insertBoardsAndBlocks(body);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#insertBoardsAndBlocks\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-insertBoardsAndBlocks-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-insertBoardsAndBlocks-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nObject *body = Object; // \n\n[apiInstance insertBoardsAndBlocksWith:body\n              completionHandler: ^(Object output, NSError* error) {\n    if (output) {\n        NSLog(@\"%@\", output);\n    }\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-insertBoardsAndBlocks-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar body = Object; // {Object} \n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully. Returned data: ' + data);\n  }\n};\napi.insertBoardsAndBlocks(body, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-insertBoardsAndBlocks-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-insertBoardsAndBlocks-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class insertBoardsAndBlocksExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var body = Object;  // Object | \n\n            try {\n                Object result = apiInstance.insertBoardsAndBlocks(body);\n                Debug.WriteLine(result);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.insertBoardsAndBlocks: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-insertBoardsAndBlocks-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$body = Object; // Object | \n\ntry {\n    $result = $api_instance->insertBoardsAndBlocks($body);\n    print_r($result);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->insertBoardsAndBlocks: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-insertBoardsAndBlocks-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $body = WWW::OPenAPIClient::Object::Object->new(); # Object | \n\neval {\n    my $result = $api_instance->insertBoardsAndBlocks(body => $body);\n    print Dumper($result);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->insertBoardsAndBlocks: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-insertBoardsAndBlocks-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nbody = Object # Object | \n\ntry:\n    api_response = api_instance.insert_boards_and_blocks(body)\n    pprint(api_response)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->insertBoardsAndBlocks: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-insertBoardsAndBlocks-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let body = Object; // Object\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.insertBoardsAndBlocks(body, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n\n\n                            <div class=\"methodsubtabletitle\">Body parameters</div>\n                            <table id=\"methodsubtable\">\n                              <tr>\n                                <th width=\"150px\">Name</th>\n                                <th>Description</th>\n                              </tr>\n                                <tr><td style=\"width:150px;\">body <span style=\"color:red;\">*</span></td>\n<td>\n<p class=\"marked\">the boards and blocks to create</p>\n<script>\n$(document).ready(function() {\n  var schemaWrapper = {\n  \"description\" : \"the boards and blocks to create\",\n  \"content\" : {\n    \"application/json\" : {\n      \"schema\" : {\n        \"$ref\" : \"#/components/schemas/BoardsAndBlocks\"\n      }\n    }\n  },\n  \"required\" : true\n};\n\n  var schema = findNode('schema',schemaWrapper).schema;\n  if (!schema) {\n    schema = schemaWrapper.schema;\n  }\n  if (schema.$ref != null) {\n    schema = defsParser.$refs.get(schema.$ref);\n  } else {\n    schemaWrapper.definitions = Object.assign({}, defs);\n    $RefParser.dereference(schemaWrapper).catch(function(err) {\n      console.log(err);\n    });\n  }\n\n  var view = new JSONSchemaView(schema,2,{isBodyParam: true});\n  var result = $('#d2e199_insertBoardsAndBlocks_body');\n  result.empty();\n  result.append(view.render());\n});\n</script>\n<div id=\"d2e199_insertBoardsAndBlocks_body\"></div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-insertBoardsAndBlocks-title-200\"></h3>\n                            <p id=\"examples-Default-insertBoardsAndBlocks-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-insertBoardsAndBlocks-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-insertBoardsAndBlocks-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-insertBoardsAndBlocks-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-insertBoardsAndBlocks-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-insertBoardsAndBlocks-200-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-insertBoardsAndBlocks-200-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-insertBoardsAndBlocks-200-schema\">\n                                  <div id=\"responses-Default-insertBoardsAndBlocks-schema-200\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=BoardsAndBlocks is used to operate over boards and blocks at the\nsame time, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-insertBoardsAndBlocks-200-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-insertBoardsAndBlocks-schema-200');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-insertBoardsAndBlocks-200-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-insertBoardsAndBlocks-title-default\"></h3>\n                            <p id=\"examples-Default-insertBoardsAndBlocks-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-insertBoardsAndBlocks-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-insertBoardsAndBlocks-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-insertBoardsAndBlocks-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-insertBoardsAndBlocks-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-insertBoardsAndBlocks-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-insertBoardsAndBlocks-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-insertBoardsAndBlocks-default-schema\">\n                                  <div id=\"responses-Default-insertBoardsAndBlocks-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-insertBoardsAndBlocks-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-insertBoardsAndBlocks-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-insertBoardsAndBlocks-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-joinBoard\">\n                      <article id=\"api-Default-joinBoard-0\" data-group=\"User\" data-name=\"joinBoard\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>joinBoard</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Become a member of a board</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"post\"><code><span class=\"pln\">/boards/{boardID}/join</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-joinBoard-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-joinBoard-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-joinBoard-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-joinBoard-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-joinBoard-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-joinBoard-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-joinBoard-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-joinBoard-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-joinBoard-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-joinBoard-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-joinBoard-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-joinBoard-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-joinBoard-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X POST \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n \"http://localhost/api/v2/boards/{boardID}/join\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-joinBoard-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String boardID = boardID_example; // String | Board ID\n\n        try {\n            Object result = apiInstance.joinBoard(boardID);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#joinBoard\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-joinBoard-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String boardID = boardID_example; // String | Board ID\n\n        try {\n            Object result = apiInstance.joinBoard(boardID);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#joinBoard\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-joinBoard-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-joinBoard-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *boardID = boardID_example; // Board ID (default to null)\n\n[apiInstance joinBoardWith:boardID\n              completionHandler: ^(Object output, NSError* error) {\n    if (output) {\n        NSLog(@\"%@\", output);\n    }\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-joinBoard-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar boardID = boardID_example; // {String} Board ID\n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully. Returned data: ' + data);\n  }\n};\napi.joinBoard(boardID, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-joinBoard-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-joinBoard-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class joinBoardExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var boardID = boardID_example;  // String | Board ID (default to null)\n\n            try {\n                Object result = apiInstance.joinBoard(boardID);\n                Debug.WriteLine(result);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.joinBoard: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-joinBoard-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$boardID = boardID_example; // String | Board ID\n\ntry {\n    $result = $api_instance->joinBoard($boardID);\n    print_r($result);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->joinBoard: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-joinBoard-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $boardID = boardID_example; # String | Board ID\n\neval {\n    my $result = $api_instance->joinBoard(boardID => $boardID);\n    print Dumper($result);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->joinBoard: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-joinBoard-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nboardID = boardID_example # String | Board ID (default to null)\n\ntry:\n    api_response = api_instance.join_board(boardID)\n    pprint(api_response)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->joinBoard: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-joinBoard-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let boardID = boardID_example; // String\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.joinBoard(boardID, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">boardID*</td>\n<td>\n\n\n    <div id=\"d2e199_joinBoard_boardID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nBoard ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-joinBoard-title-200\"></h3>\n                            <p id=\"examples-Default-joinBoard-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-joinBoard-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-joinBoard-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-joinBoard-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-joinBoard-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-joinBoard-200-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-joinBoard-200-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-joinBoard-200-schema\">\n                                  <div id=\"responses-Default-joinBoard-schema-200\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=BoardMember stores the information of the membership of a user on a board, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-joinBoard-200-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-joinBoard-schema-200');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-joinBoard-200-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-joinBoard-title-403\"></h3>\n                            <p id=\"examples-Default-joinBoard-description-403\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault403_description = `access denied`;\n                              var responseDefault403_description_break = responseDefault403_description.indexOf('\\n');\n                              if (responseDefault403_description_break == -1) {\n                                $(\"#examples-Default-joinBoard-title-403\").text(\"Status: 403 - \" + responseDefault403_description);\n                              } else {\n                                $(\"#examples-Default-joinBoard-title-403\").text(\"Status: 403 - \" + responseDefault403_description.substring(0, responseDefault403_description_break));\n                                $(\"#examples-Default-joinBoard-description-403\").html(responseDefault403_description.substring(responseDefault403_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-joinBoard-403\" class=\"nav nav-tabs nav-tabs-examples\" >\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-joinBoard-403-wrapper\" style='margin-bottom: 10px;'>\n                            </div>\n                            <h3 id=\"examples-Default-joinBoard-title-404\"></h3>\n                            <p id=\"examples-Default-joinBoard-description-404\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault404_description = `board not found`;\n                              var responseDefault404_description_break = responseDefault404_description.indexOf('\\n');\n                              if (responseDefault404_description_break == -1) {\n                                $(\"#examples-Default-joinBoard-title-404\").text(\"Status: 404 - \" + responseDefault404_description);\n                              } else {\n                                $(\"#examples-Default-joinBoard-title-404\").text(\"Status: 404 - \" + responseDefault404_description.substring(0, responseDefault404_description_break));\n                                $(\"#examples-Default-joinBoard-description-404\").html(responseDefault404_description.substring(responseDefault404_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-joinBoard-404\" class=\"nav nav-tabs nav-tabs-examples\" >\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-joinBoard-404-wrapper\" style='margin-bottom: 10px;'>\n                            </div>\n                            <h3 id=\"examples-Default-joinBoard-title-default\"></h3>\n                            <p id=\"examples-Default-joinBoard-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-joinBoard-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-joinBoard-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-joinBoard-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-joinBoard-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-joinBoard-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-joinBoard-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-joinBoard-default-schema\">\n                                  <div id=\"responses-Default-joinBoard-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-joinBoard-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-joinBoard-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-joinBoard-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-leaveBoard\">\n                      <article id=\"api-Default-leaveBoard-0\" data-group=\"User\" data-name=\"leaveBoard\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>leaveBoard</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Remove your own membership from a board</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"post\"><code><span class=\"pln\">/boards/{boardID}/leave</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-leaveBoard-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-leaveBoard-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-leaveBoard-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-leaveBoard-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-leaveBoard-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-leaveBoard-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-leaveBoard-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-leaveBoard-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-leaveBoard-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-leaveBoard-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-leaveBoard-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-leaveBoard-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-leaveBoard-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X POST \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n \"http://localhost/api/v2/boards/{boardID}/leave\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-leaveBoard-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String boardID = boardID_example; // String | Board ID\n\n        try {\n            apiInstance.leaveBoard(boardID);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#leaveBoard\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-leaveBoard-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String boardID = boardID_example; // String | Board ID\n\n        try {\n            apiInstance.leaveBoard(boardID);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#leaveBoard\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-leaveBoard-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-leaveBoard-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *boardID = boardID_example; // Board ID (default to null)\n\n[apiInstance leaveBoardWith:boardID\n              completionHandler: ^(NSError* error) {\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-leaveBoard-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar boardID = boardID_example; // {String} Board ID\n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully.');\n  }\n};\napi.leaveBoard(boardID, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-leaveBoard-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-leaveBoard-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class leaveBoardExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var boardID = boardID_example;  // String | Board ID (default to null)\n\n            try {\n                apiInstance.leaveBoard(boardID);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.leaveBoard: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-leaveBoard-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$boardID = boardID_example; // String | Board ID\n\ntry {\n    $api_instance->leaveBoard($boardID);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->leaveBoard: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-leaveBoard-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $boardID = boardID_example; # String | Board ID\n\neval {\n    $api_instance->leaveBoard(boardID => $boardID);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->leaveBoard: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-leaveBoard-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nboardID = boardID_example # String | Board ID (default to null)\n\ntry:\n    api_instance.leave_board(boardID)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->leaveBoard: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-leaveBoard-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let boardID = boardID_example; // String\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.leaveBoard(boardID, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">boardID*</td>\n<td>\n\n\n    <div id=\"d2e199_leaveBoard_boardID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nBoard ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-leaveBoard-title-200\"></h3>\n                            <p id=\"examples-Default-leaveBoard-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-leaveBoard-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-leaveBoard-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-leaveBoard-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-leaveBoard-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-leaveBoard-200-wrapper\" style='margin-bottom: 10px;'>\n                            </div>\n                            <h3 id=\"examples-Default-leaveBoard-title-403\"></h3>\n                            <p id=\"examples-Default-leaveBoard-description-403\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault403_description = `access denied`;\n                              var responseDefault403_description_break = responseDefault403_description.indexOf('\\n');\n                              if (responseDefault403_description_break == -1) {\n                                $(\"#examples-Default-leaveBoard-title-403\").text(\"Status: 403 - \" + responseDefault403_description);\n                              } else {\n                                $(\"#examples-Default-leaveBoard-title-403\").text(\"Status: 403 - \" + responseDefault403_description.substring(0, responseDefault403_description_break));\n                                $(\"#examples-Default-leaveBoard-description-403\").html(responseDefault403_description.substring(responseDefault403_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-leaveBoard-403\" class=\"nav nav-tabs nav-tabs-examples\" >\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-leaveBoard-403-wrapper\" style='margin-bottom: 10px;'>\n                            </div>\n                            <h3 id=\"examples-Default-leaveBoard-title-404\"></h3>\n                            <p id=\"examples-Default-leaveBoard-description-404\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault404_description = `board not found`;\n                              var responseDefault404_description_break = responseDefault404_description.indexOf('\\n');\n                              if (responseDefault404_description_break == -1) {\n                                $(\"#examples-Default-leaveBoard-title-404\").text(\"Status: 404 - \" + responseDefault404_description);\n                              } else {\n                                $(\"#examples-Default-leaveBoard-title-404\").text(\"Status: 404 - \" + responseDefault404_description.substring(0, responseDefault404_description_break));\n                                $(\"#examples-Default-leaveBoard-description-404\").html(responseDefault404_description.substring(responseDefault404_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-leaveBoard-404\" class=\"nav nav-tabs nav-tabs-examples\" >\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-leaveBoard-404-wrapper\" style='margin-bottom: 10px;'>\n                            </div>\n                            <h3 id=\"examples-Default-leaveBoard-title-default\"></h3>\n                            <p id=\"examples-Default-leaveBoard-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-leaveBoard-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-leaveBoard-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-leaveBoard-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-leaveBoard-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-leaveBoard-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-leaveBoard-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-leaveBoard-default-schema\">\n                                  <div id=\"responses-Default-leaveBoard-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-leaveBoard-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-leaveBoard-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-leaveBoard-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-login\">\n                      <article id=\"api-Default-login-0\" data-group=\"User\" data-name=\"login\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>login</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Login user</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"post\"><code><span class=\"pln\">/login</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-login-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-login-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-login-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-login-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-login-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-login-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-login-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-login-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-login-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-login-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-login-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-login-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-login-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X POST \\\n -H \"Accept: application/json\" \\\n -H \"Content-Type: application/json\" \\\n \"http://localhost/api/v2/login\" \\\n -d ''\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-login-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        Object body = Object; // Object | \n\n        try {\n            Object result = apiInstance.login(body);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#login\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-login-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        Object body = Object; // Object | \n\n        try {\n            Object result = apiInstance.login(body);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#login\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-login-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-login-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nObject *body = Object; // \n\n[apiInstance loginWith:body\n              completionHandler: ^(Object output, NSError* error) {\n    if (output) {\n        NSLog(@\"%@\", output);\n    }\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-login-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar body = Object; // {Object} \n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully. Returned data: ' + data);\n  }\n};\napi.login(body, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-login-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-login-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class loginExample\n    {\n        public void main()\n        {\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var body = Object;  // Object | \n\n            try {\n                Object result = apiInstance.login(body);\n                Debug.WriteLine(result);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.login: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-login-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$body = Object; // Object | \n\ntry {\n    $result = $api_instance->login($body);\n    print_r($result);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->login: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-login-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $body = WWW::OPenAPIClient::Object::Object->new(); # Object | \n\neval {\n    my $result = $api_instance->login(body => $body);\n    print Dumper($result);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->login: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-login-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nbody = Object # Object | \n\ntry:\n    api_response = api_instance.login(body)\n    pprint(api_response)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->login: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-login-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let body = Object; // Object\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.login(body, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n\n\n                            <div class=\"methodsubtabletitle\">Body parameters</div>\n                            <table id=\"methodsubtable\">\n                              <tr>\n                                <th width=\"150px\">Name</th>\n                                <th>Description</th>\n                              </tr>\n                                <tr><td style=\"width:150px;\">body <span style=\"color:red;\">*</span></td>\n<td>\n<p class=\"marked\">Login request</p>\n<script>\n$(document).ready(function() {\n  var schemaWrapper = {\n  \"description\" : \"Login request\",\n  \"content\" : {\n    \"application/json\" : {\n      \"schema\" : {\n        \"$ref\" : \"#/components/schemas/LoginRequest\"\n      }\n    }\n  },\n  \"required\" : true\n};\n\n  var schema = findNode('schema',schemaWrapper).schema;\n  if (!schema) {\n    schema = schemaWrapper.schema;\n  }\n  if (schema.$ref != null) {\n    schema = defsParser.$refs.get(schema.$ref);\n  } else {\n    schemaWrapper.definitions = Object.assign({}, defs);\n    $RefParser.dereference(schemaWrapper).catch(function(err) {\n      console.log(err);\n    });\n  }\n\n  var view = new JSONSchemaView(schema,2,{isBodyParam: true});\n  var result = $('#d2e199_login_body');\n  result.empty();\n  result.append(view.render());\n});\n</script>\n<div id=\"d2e199_login_body\"></div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-login-title-200\"></h3>\n                            <p id=\"examples-Default-login-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-login-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-login-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-login-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-login-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-login-200-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-login-200-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-login-200-schema\">\n                                  <div id=\"responses-Default-login-schema-200\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=LoginResponse is a login response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-login-200-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-login-schema-200');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-login-200-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-login-title-401\"></h3>\n                            <p id=\"examples-Default-login-description-401\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault401_description = `invalid login`;\n                              var responseDefault401_description_break = responseDefault401_description.indexOf('\\n');\n                              if (responseDefault401_description_break == -1) {\n                                $(\"#examples-Default-login-title-401\").text(\"Status: 401 - \" + responseDefault401_description);\n                              } else {\n                                $(\"#examples-Default-login-title-401\").text(\"Status: 401 - \" + responseDefault401_description.substring(0, responseDefault401_description_break));\n                                $(\"#examples-Default-login-description-401\").html(responseDefault401_description.substring(responseDefault401_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-login-401\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-login-401-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-login-401-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-login-401-schema\">\n                                  <div id=\"responses-Default-login-schema-401\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-login-401-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-login-schema-401');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-login-401-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-login-title-500\"></h3>\n                            <p id=\"examples-Default-login-description-500\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault500_description = `internal error`;\n                              var responseDefault500_description_break = responseDefault500_description.indexOf('\\n');\n                              if (responseDefault500_description_break == -1) {\n                                $(\"#examples-Default-login-title-500\").text(\"Status: 500 - \" + responseDefault500_description);\n                              } else {\n                                $(\"#examples-Default-login-title-500\").text(\"Status: 500 - \" + responseDefault500_description.substring(0, responseDefault500_description_break));\n                                $(\"#examples-Default-login-description-500\").html(responseDefault500_description.substring(responseDefault500_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-login-500\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-login-500-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-login-500-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-login-500-schema\">\n                                  <div id=\"responses-Default-login-schema-500\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-login-500-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-login-schema-500');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-login-500-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-logout\">\n                      <article id=\"api-Default-logout-0\" data-group=\"User\" data-name=\"logout\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>logout</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Logout user</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"post\"><code><span class=\"pln\">/logout</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-logout-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-logout-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-logout-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-logout-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-logout-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-logout-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-logout-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-logout-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-logout-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-logout-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-logout-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-logout-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-logout-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X POST \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n \"http://localhost/api/v2/logout\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-logout-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n\n        try {\n            apiInstance.logout();\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#logout\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-logout-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n\n        try {\n            apiInstance.logout();\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#logout\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-logout-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-logout-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\n\n[apiInstance logoutWithCompletionHandler: \n              ^(NSError* error) {\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-logout-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully.');\n  }\n};\napi.logout(callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-logout-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-logout-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class logoutExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n\n            try {\n                apiInstance.logout();\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.logout: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-logout-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n\ntry {\n    $api_instance->logout();\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->logout: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-logout-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\n\neval {\n    $api_instance->logout();\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->logout: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-logout-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\n\ntry:\n    api_instance.logout()\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->logout: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-logout-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.logout(&context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n\n\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-logout-title-200\"></h3>\n                            <p id=\"examples-Default-logout-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-logout-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-logout-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-logout-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-logout-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-logout-200-wrapper\" style='margin-bottom: 10px;'>\n                            </div>\n                            <h3 id=\"examples-Default-logout-title-500\"></h3>\n                            <p id=\"examples-Default-logout-description-500\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault500_description = `internal error`;\n                              var responseDefault500_description_break = responseDefault500_description.indexOf('\\n');\n                              if (responseDefault500_description_break == -1) {\n                                $(\"#examples-Default-logout-title-500\").text(\"Status: 500 - \" + responseDefault500_description);\n                              } else {\n                                $(\"#examples-Default-logout-title-500\").text(\"Status: 500 - \" + responseDefault500_description.substring(0, responseDefault500_description_break));\n                                $(\"#examples-Default-logout-description-500\").html(responseDefault500_description.substring(responseDefault500_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-logout-500\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-logout-500-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-logout-500-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-logout-500-schema\">\n                                  <div id=\"responses-Default-logout-schema-500\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-logout-500-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-logout-schema-500');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-logout-500-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-onboard\">\n                      <article id=\"api-Default-onboard-0\" data-group=\"User\" data-name=\"onboard\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>onboard</h1>\n                          <p>Onboards a user on Boards.</p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\"></p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"post\"><code><span class=\"pln\">/team/{teamID}/onboard</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-onboard-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-onboard-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-onboard-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-onboard-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-onboard-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-onboard-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-onboard-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-onboard-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-onboard-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-onboard-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-onboard-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-onboard-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-onboard-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X POST \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n \"http://localhost/api/v2/team/{teamID}/onboard\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-onboard-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String teamID = teamID_example; // String | Team ID\n\n        try {\n            onboard_200_response result = apiInstance.onboard(teamID);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#onboard\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-onboard-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String teamID = teamID_example; // String | Team ID\n\n        try {\n            onboard_200_response result = apiInstance.onboard(teamID);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#onboard\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-onboard-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-onboard-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *teamID = teamID_example; // Team ID (default to null)\n\n// Onboards a user on Boards.\n[apiInstance onboardWith:teamID\n              completionHandler: ^(onboard_200_response output, NSError* error) {\n    if (output) {\n        NSLog(@\"%@\", output);\n    }\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-onboard-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar teamID = teamID_example; // {String} Team ID\n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully. Returned data: ' + data);\n  }\n};\napi.onboard(teamID, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-onboard-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-onboard-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class onboardExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var teamID = teamID_example;  // String | Team ID (default to null)\n\n            try {\n                // Onboards a user on Boards.\n                onboard_200_response result = apiInstance.onboard(teamID);\n                Debug.WriteLine(result);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.onboard: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-onboard-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$teamID = teamID_example; // String | Team ID\n\ntry {\n    $result = $api_instance->onboard($teamID);\n    print_r($result);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->onboard: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-onboard-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $teamID = teamID_example; # String | Team ID\n\neval {\n    my $result = $api_instance->onboard(teamID => $teamID);\n    print Dumper($result);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->onboard: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-onboard-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nteamID = teamID_example # String | Team ID (default to null)\n\ntry:\n    # Onboards a user on Boards.\n    api_response = api_instance.onboard(teamID)\n    pprint(api_response)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->onboard: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-onboard-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let teamID = teamID_example; // String\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.onboard(teamID, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">teamID*</td>\n<td>\n\n\n    <div id=\"d2e199_onboard_teamID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nTeam ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-onboard-title-200\"></h3>\n                            <p id=\"examples-Default-onboard-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-onboard-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-onboard-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-onboard-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-onboard-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-onboard-200-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-onboard-200-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-onboard-200-schema\">\n                                  <div id=\"responses-Default-onboard-schema-200\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = ;\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-onboard-200-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-onboard-schema-200');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-onboard-200-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-onboard-title-default\"></h3>\n                            <p id=\"examples-Default-onboard-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-onboard-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-onboard-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-onboard-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-onboard-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-onboard-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-onboard-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-onboard-default-schema\">\n                                  <div id=\"responses-Default-onboard-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-onboard-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-onboard-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-onboard-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-patchBlock\">\n                      <article id=\"api-Default-patchBlock-0\" data-group=\"User\" data-name=\"patchBlock\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>patchBlock</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Partially updates a block</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"patch\"><code><span class=\"pln\">/boards/{boardID}/blocks/{blockID}</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-patchBlock-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-patchBlock-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-patchBlock-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-patchBlock-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-patchBlock-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-patchBlock-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-patchBlock-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-patchBlock-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-patchBlock-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-patchBlock-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-patchBlock-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-patchBlock-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-patchBlock-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X PATCH \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n -H \"Content-Type: application/json\" \\\n \"http://localhost/api/v2/boards/{boardID}/blocks/{blockID}?disable_notify=\" \\\n -d ''\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-patchBlock-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String boardID = boardID_example; // String | Board ID\n        String blockID = blockID_example; // String | ID of block to patch\n        Object body = Object; // Object | \n        oas_any_type_not_mapped disableNotify = ; // oas_any_type_not_mapped | Disables notifications (for bulk patching)\n\n        try {\n            apiInstance.patchBlock(boardID, blockID, body, disableNotify);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#patchBlock\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-patchBlock-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String boardID = boardID_example; // String | Board ID\n        String blockID = blockID_example; // String | ID of block to patch\n        Object body = Object; // Object | \n        oas_any_type_not_mapped disableNotify = ; // oas_any_type_not_mapped | Disables notifications (for bulk patching)\n\n        try {\n            apiInstance.patchBlock(boardID, blockID, body, disableNotify);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#patchBlock\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-patchBlock-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-patchBlock-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *boardID = boardID_example; // Board ID (default to null)\nString *blockID = blockID_example; // ID of block to patch (default to null)\nObject *body = Object; // \noas_any_type_not_mapped *disableNotify = ; // Disables notifications (for bulk patching) (optional) (default to null)\n\n[apiInstance patchBlockWith:boardID\n    blockID:blockID\n    body:body\n    disableNotify:disableNotify\n              completionHandler: ^(NSError* error) {\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-patchBlock-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar boardID = boardID_example; // {String} Board ID\nvar blockID = blockID_example; // {String} ID of block to patch\nvar body = Object; // {Object} \nvar opts = {\n  'disableNotify':  // {oas_any_type_not_mapped} Disables notifications (for bulk patching)\n};\n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully.');\n  }\n};\napi.patchBlock(boardID, blockID, body, opts, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-patchBlock-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-patchBlock-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class patchBlockExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var boardID = boardID_example;  // String | Board ID (default to null)\n            var blockID = blockID_example;  // String | ID of block to patch (default to null)\n            var body = Object;  // Object | \n            var disableNotify = new oas_any_type_not_mapped(); // oas_any_type_not_mapped | Disables notifications (for bulk patching) (optional)  (default to null)\n\n            try {\n                apiInstance.patchBlock(boardID, blockID, body, disableNotify);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.patchBlock: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-patchBlock-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$boardID = boardID_example; // String | Board ID\n$blockID = blockID_example; // String | ID of block to patch\n$body = Object; // Object | \n$disableNotify = ; // oas_any_type_not_mapped | Disables notifications (for bulk patching)\n\ntry {\n    $api_instance->patchBlock($boardID, $blockID, $body, $disableNotify);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->patchBlock: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-patchBlock-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $boardID = boardID_example; # String | Board ID\nmy $blockID = blockID_example; # String | ID of block to patch\nmy $body = WWW::OPenAPIClient::Object::Object->new(); # Object | \nmy $disableNotify = ; # oas_any_type_not_mapped | Disables notifications (for bulk patching)\n\neval {\n    $api_instance->patchBlock(boardID => $boardID, blockID => $blockID, body => $body, disableNotify => $disableNotify);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->patchBlock: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-patchBlock-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nboardID = boardID_example # String | Board ID (default to null)\nblockID = blockID_example # String | ID of block to patch (default to null)\nbody = Object # Object | \ndisableNotify =  # oas_any_type_not_mapped | Disables notifications (for bulk patching) (optional) (default to null)\n\ntry:\n    api_instance.patch_block(boardID, blockID, body, disableNotify=disableNotify)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->patchBlock: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-patchBlock-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let boardID = boardID_example; // String\n    let blockID = blockID_example; // String\n    let body = Object; // Object\n    let disableNotify = ; // oas_any_type_not_mapped\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.patchBlock(boardID, blockID, body, disableNotify, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">boardID*</td>\n<td>\n\n\n    <div id=\"d2e199_patchBlock_boardID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nBoard ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                                  <tr><td style=\"width:150px;\">blockID*</td>\n<td>\n\n\n    <div id=\"d2e199_patchBlock_blockID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nID of block to patch\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n                            <div class=\"methodsubtabletitle\">Body parameters</div>\n                            <table id=\"methodsubtable\">\n                              <tr>\n                                <th width=\"150px\">Name</th>\n                                <th>Description</th>\n                              </tr>\n                                <tr><td style=\"width:150px;\">body <span style=\"color:red;\">*</span></td>\n<td>\n<p class=\"marked\">block patch to apply</p>\n<script>\n$(document).ready(function() {\n  var schemaWrapper = {\n  \"description\" : \"block patch to apply\",\n  \"content\" : {\n    \"application/json\" : {\n      \"schema\" : {\n        \"$ref\" : \"#/components/schemas/BlockPatch\"\n      }\n    }\n  },\n  \"required\" : true\n};\n\n  var schema = findNode('schema',schemaWrapper).schema;\n  if (!schema) {\n    schema = schemaWrapper.schema;\n  }\n  if (schema.$ref != null) {\n    schema = defsParser.$refs.get(schema.$ref);\n  } else {\n    schemaWrapper.definitions = Object.assign({}, defs);\n    $RefParser.dereference(schemaWrapper).catch(function(err) {\n      console.log(err);\n    });\n  }\n\n  var view = new JSONSchemaView(schema,2,{isBodyParam: true});\n  var result = $('#d2e199_patchBlock_body');\n  result.empty();\n  result.append(view.render());\n});\n</script>\n<div id=\"d2e199_patchBlock_body\"></div>\n</td>\n</tr>\n\n                            </table>\n\n\n                            <div class=\"methodsubtabletitle\">Query parameters</div>\n                            <table id=\"methodsubtable\">\n                              <tr>\n                                <th width=\"150px\">Name</th>\n                                <th>Description</th>\n                              </tr>\n                                <tr><td style=\"width:150px;\">disable_notify</td>\n<td>\n\n\n    <div id=\"d2e199_patchBlock_disableNotify\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    oas_any_type_not_mapped\n                </span>\n\n                    <div class=\"inner description marked\">\nDisables notifications (for bulk patching)\n                    </div>\n            </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-patchBlock-title-200\"></h3>\n                            <p id=\"examples-Default-patchBlock-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-patchBlock-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-patchBlock-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-patchBlock-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-patchBlock-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-patchBlock-200-wrapper\" style='margin-bottom: 10px;'>\n                            </div>\n                            <h3 id=\"examples-Default-patchBlock-title-404\"></h3>\n                            <p id=\"examples-Default-patchBlock-description-404\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault404_description = `block not found`;\n                              var responseDefault404_description_break = responseDefault404_description.indexOf('\\n');\n                              if (responseDefault404_description_break == -1) {\n                                $(\"#examples-Default-patchBlock-title-404\").text(\"Status: 404 - \" + responseDefault404_description);\n                              } else {\n                                $(\"#examples-Default-patchBlock-title-404\").text(\"Status: 404 - \" + responseDefault404_description.substring(0, responseDefault404_description_break));\n                                $(\"#examples-Default-patchBlock-description-404\").html(responseDefault404_description.substring(responseDefault404_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-patchBlock-404\" class=\"nav nav-tabs nav-tabs-examples\" >\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-patchBlock-404-wrapper\" style='margin-bottom: 10px;'>\n                            </div>\n                            <h3 id=\"examples-Default-patchBlock-title-default\"></h3>\n                            <p id=\"examples-Default-patchBlock-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-patchBlock-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-patchBlock-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-patchBlock-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-patchBlock-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-patchBlock-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-patchBlock-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-patchBlock-default-schema\">\n                                  <div id=\"responses-Default-patchBlock-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-patchBlock-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-patchBlock-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-patchBlock-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-patchBlocks\">\n                      <article id=\"api-Default-patchBlocks-0\" data-group=\"User\" data-name=\"patchBlocks\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>patchBlocks</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Partially updates batch of blocks</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"patch\"><code><span class=\"pln\">/boards/{boardID}/blocks/</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-patchBlocks-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-patchBlocks-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-patchBlocks-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-patchBlocks-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-patchBlocks-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-patchBlocks-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-patchBlocks-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-patchBlocks-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-patchBlocks-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-patchBlocks-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-patchBlocks-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-patchBlocks-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-patchBlocks-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X PATCH \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n -H \"Content-Type: application/json\" \\\n \"http://localhost/api/v2/boards/{boardID}/blocks/?disable_notify=\" \\\n -d ''\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-patchBlocks-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String boardID = boardID_example; // String | Workspace ID\n        Object body = Object; // Object | \n        oas_any_type_not_mapped disableNotify = ; // oas_any_type_not_mapped | Disables notifications (for bulk patching)\n\n        try {\n            apiInstance.patchBlocks(boardID, body, disableNotify);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#patchBlocks\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-patchBlocks-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String boardID = boardID_example; // String | Workspace ID\n        Object body = Object; // Object | \n        oas_any_type_not_mapped disableNotify = ; // oas_any_type_not_mapped | Disables notifications (for bulk patching)\n\n        try {\n            apiInstance.patchBlocks(boardID, body, disableNotify);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#patchBlocks\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-patchBlocks-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-patchBlocks-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *boardID = boardID_example; // Workspace ID (default to null)\nObject *body = Object; // \noas_any_type_not_mapped *disableNotify = ; // Disables notifications (for bulk patching) (optional) (default to null)\n\n[apiInstance patchBlocksWith:boardID\n    body:body\n    disableNotify:disableNotify\n              completionHandler: ^(NSError* error) {\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-patchBlocks-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar boardID = boardID_example; // {String} Workspace ID\nvar body = Object; // {Object} \nvar opts = {\n  'disableNotify':  // {oas_any_type_not_mapped} Disables notifications (for bulk patching)\n};\n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully.');\n  }\n};\napi.patchBlocks(boardID, body, opts, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-patchBlocks-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-patchBlocks-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class patchBlocksExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var boardID = boardID_example;  // String | Workspace ID (default to null)\n            var body = Object;  // Object | \n            var disableNotify = new oas_any_type_not_mapped(); // oas_any_type_not_mapped | Disables notifications (for bulk patching) (optional)  (default to null)\n\n            try {\n                apiInstance.patchBlocks(boardID, body, disableNotify);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.patchBlocks: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-patchBlocks-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$boardID = boardID_example; // String | Workspace ID\n$body = Object; // Object | \n$disableNotify = ; // oas_any_type_not_mapped | Disables notifications (for bulk patching)\n\ntry {\n    $api_instance->patchBlocks($boardID, $body, $disableNotify);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->patchBlocks: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-patchBlocks-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $boardID = boardID_example; # String | Workspace ID\nmy $body = WWW::OPenAPIClient::Object::Object->new(); # Object | \nmy $disableNotify = ; # oas_any_type_not_mapped | Disables notifications (for bulk patching)\n\neval {\n    $api_instance->patchBlocks(boardID => $boardID, body => $body, disableNotify => $disableNotify);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->patchBlocks: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-patchBlocks-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nboardID = boardID_example # String | Workspace ID (default to null)\nbody = Object # Object | \ndisableNotify =  # oas_any_type_not_mapped | Disables notifications (for bulk patching) (optional) (default to null)\n\ntry:\n    api_instance.patch_blocks(boardID, body, disableNotify=disableNotify)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->patchBlocks: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-patchBlocks-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let boardID = boardID_example; // String\n    let body = Object; // Object\n    let disableNotify = ; // oas_any_type_not_mapped\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.patchBlocks(boardID, body, disableNotify, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">boardID*</td>\n<td>\n\n\n    <div id=\"d2e199_patchBlocks_boardID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nWorkspace ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n                            <div class=\"methodsubtabletitle\">Body parameters</div>\n                            <table id=\"methodsubtable\">\n                              <tr>\n                                <th width=\"150px\">Name</th>\n                                <th>Description</th>\n                              </tr>\n                                <tr><td style=\"width:150px;\">body <span style=\"color:red;\">*</span></td>\n<td>\n<p class=\"marked\">block Ids and block patches to apply</p>\n<script>\n$(document).ready(function() {\n  var schemaWrapper = {\n  \"description\" : \"block Ids and block patches to apply\",\n  \"content\" : {\n    \"application/json\" : {\n      \"schema\" : {\n        \"$ref\" : \"#/components/schemas/BlockPatchBatch\"\n      }\n    }\n  },\n  \"required\" : true\n};\n\n  var schema = findNode('schema',schemaWrapper).schema;\n  if (!schema) {\n    schema = schemaWrapper.schema;\n  }\n  if (schema.$ref != null) {\n    schema = defsParser.$refs.get(schema.$ref);\n  } else {\n    schemaWrapper.definitions = Object.assign({}, defs);\n    $RefParser.dereference(schemaWrapper).catch(function(err) {\n      console.log(err);\n    });\n  }\n\n  var view = new JSONSchemaView(schema,2,{isBodyParam: true});\n  var result = $('#d2e199_patchBlocks_body');\n  result.empty();\n  result.append(view.render());\n});\n</script>\n<div id=\"d2e199_patchBlocks_body\"></div>\n</td>\n</tr>\n\n                            </table>\n\n\n                            <div class=\"methodsubtabletitle\">Query parameters</div>\n                            <table id=\"methodsubtable\">\n                              <tr>\n                                <th width=\"150px\">Name</th>\n                                <th>Description</th>\n                              </tr>\n                                <tr><td style=\"width:150px;\">disable_notify</td>\n<td>\n\n\n    <div id=\"d2e199_patchBlocks_disableNotify\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    oas_any_type_not_mapped\n                </span>\n\n                    <div class=\"inner description marked\">\nDisables notifications (for bulk patching)\n                    </div>\n            </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-patchBlocks-title-200\"></h3>\n                            <p id=\"examples-Default-patchBlocks-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-patchBlocks-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-patchBlocks-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-patchBlocks-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-patchBlocks-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-patchBlocks-200-wrapper\" style='margin-bottom: 10px;'>\n                            </div>\n                            <h3 id=\"examples-Default-patchBlocks-title-default\"></h3>\n                            <p id=\"examples-Default-patchBlocks-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-patchBlocks-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-patchBlocks-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-patchBlocks-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-patchBlocks-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-patchBlocks-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-patchBlocks-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-patchBlocks-default-schema\">\n                                  <div id=\"responses-Default-patchBlocks-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-patchBlocks-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-patchBlocks-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-patchBlocks-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-patchBoard\">\n                      <article id=\"api-Default-patchBoard-0\" data-group=\"User\" data-name=\"patchBoard\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>patchBoard</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Partially updates a board</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"patch\"><code><span class=\"pln\">/boards/{boardID}</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-patchBoard-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-patchBoard-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-patchBoard-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-patchBoard-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-patchBoard-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-patchBoard-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-patchBoard-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-patchBoard-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-patchBoard-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-patchBoard-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-patchBoard-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-patchBoard-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-patchBoard-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X PATCH \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n -H \"Content-Type: application/json\" \\\n \"http://localhost/api/v2/boards/{boardID}\" \\\n -d ''\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-patchBoard-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String boardID = boardID_example; // String | Board ID\n        Object body = Object; // Object | \n\n        try {\n            Object result = apiInstance.patchBoard(boardID, body);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#patchBoard\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-patchBoard-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String boardID = boardID_example; // String | Board ID\n        Object body = Object; // Object | \n\n        try {\n            Object result = apiInstance.patchBoard(boardID, body);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#patchBoard\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-patchBoard-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-patchBoard-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *boardID = boardID_example; // Board ID (default to null)\nObject *body = Object; // \n\n[apiInstance patchBoardWith:boardID\n    body:body\n              completionHandler: ^(Object output, NSError* error) {\n    if (output) {\n        NSLog(@\"%@\", output);\n    }\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-patchBoard-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar boardID = boardID_example; // {String} Board ID\nvar body = Object; // {Object} \n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully. Returned data: ' + data);\n  }\n};\napi.patchBoard(boardID, body, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-patchBoard-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-patchBoard-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class patchBoardExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var boardID = boardID_example;  // String | Board ID (default to null)\n            var body = Object;  // Object | \n\n            try {\n                Object result = apiInstance.patchBoard(boardID, body);\n                Debug.WriteLine(result);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.patchBoard: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-patchBoard-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$boardID = boardID_example; // String | Board ID\n$body = Object; // Object | \n\ntry {\n    $result = $api_instance->patchBoard($boardID, $body);\n    print_r($result);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->patchBoard: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-patchBoard-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $boardID = boardID_example; # String | Board ID\nmy $body = WWW::OPenAPIClient::Object::Object->new(); # Object | \n\neval {\n    my $result = $api_instance->patchBoard(boardID => $boardID, body => $body);\n    print Dumper($result);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->patchBoard: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-patchBoard-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nboardID = boardID_example # String | Board ID (default to null)\nbody = Object # Object | \n\ntry:\n    api_response = api_instance.patch_board(boardID, body)\n    pprint(api_response)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->patchBoard: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-patchBoard-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let boardID = boardID_example; // String\n    let body = Object; // Object\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.patchBoard(boardID, body, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">boardID*</td>\n<td>\n\n\n    <div id=\"d2e199_patchBoard_boardID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nBoard ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n                            <div class=\"methodsubtabletitle\">Body parameters</div>\n                            <table id=\"methodsubtable\">\n                              <tr>\n                                <th width=\"150px\">Name</th>\n                                <th>Description</th>\n                              </tr>\n                                <tr><td style=\"width:150px;\">body <span style=\"color:red;\">*</span></td>\n<td>\n<p class=\"marked\">board patch to apply</p>\n<script>\n$(document).ready(function() {\n  var schemaWrapper = {\n  \"description\" : \"board patch to apply\",\n  \"content\" : {\n    \"application/json\" : {\n      \"schema\" : {\n        \"$ref\" : \"#/components/schemas/BoardPatch\"\n      }\n    }\n  },\n  \"required\" : true\n};\n\n  var schema = findNode('schema',schemaWrapper).schema;\n  if (!schema) {\n    schema = schemaWrapper.schema;\n  }\n  if (schema.$ref != null) {\n    schema = defsParser.$refs.get(schema.$ref);\n  } else {\n    schemaWrapper.definitions = Object.assign({}, defs);\n    $RefParser.dereference(schemaWrapper).catch(function(err) {\n      console.log(err);\n    });\n  }\n\n  var view = new JSONSchemaView(schema,2,{isBodyParam: true});\n  var result = $('#d2e199_patchBoard_body');\n  result.empty();\n  result.append(view.render());\n});\n</script>\n<div id=\"d2e199_patchBoard_body\"></div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-patchBoard-title-200\"></h3>\n                            <p id=\"examples-Default-patchBoard-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-patchBoard-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-patchBoard-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-patchBoard-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-patchBoard-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-patchBoard-200-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-patchBoard-200-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-patchBoard-200-schema\">\n                                  <div id=\"responses-Default-patchBoard-schema-200\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=Board groups a set of blocks and its layout, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-patchBoard-200-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-patchBoard-schema-200');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-patchBoard-200-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-patchBoard-title-404\"></h3>\n                            <p id=\"examples-Default-patchBoard-description-404\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault404_description = `board not found`;\n                              var responseDefault404_description_break = responseDefault404_description.indexOf('\\n');\n                              if (responseDefault404_description_break == -1) {\n                                $(\"#examples-Default-patchBoard-title-404\").text(\"Status: 404 - \" + responseDefault404_description);\n                              } else {\n                                $(\"#examples-Default-patchBoard-title-404\").text(\"Status: 404 - \" + responseDefault404_description.substring(0, responseDefault404_description_break));\n                                $(\"#examples-Default-patchBoard-description-404\").html(responseDefault404_description.substring(responseDefault404_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-patchBoard-404\" class=\"nav nav-tabs nav-tabs-examples\" >\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-patchBoard-404-wrapper\" style='margin-bottom: 10px;'>\n                            </div>\n                            <h3 id=\"examples-Default-patchBoard-title-default\"></h3>\n                            <p id=\"examples-Default-patchBoard-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-patchBoard-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-patchBoard-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-patchBoard-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-patchBoard-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-patchBoard-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-patchBoard-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-patchBoard-default-schema\">\n                                  <div id=\"responses-Default-patchBoard-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-patchBoard-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-patchBoard-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-patchBoard-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-patchBoardsAndBlocks\">\n                      <article id=\"api-Default-patchBoardsAndBlocks-0\" data-group=\"User\" data-name=\"patchBoardsAndBlocks\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>patchBoardsAndBlocks</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Patches a set of related boards and blocks</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"patch\"><code><span class=\"pln\">/boards-and-blocks</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-patchBoardsAndBlocks-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-patchBoardsAndBlocks-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-patchBoardsAndBlocks-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-patchBoardsAndBlocks-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-patchBoardsAndBlocks-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-patchBoardsAndBlocks-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-patchBoardsAndBlocks-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-patchBoardsAndBlocks-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-patchBoardsAndBlocks-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-patchBoardsAndBlocks-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-patchBoardsAndBlocks-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-patchBoardsAndBlocks-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-patchBoardsAndBlocks-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X PATCH \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n -H \"Content-Type: application/json\" \\\n \"http://localhost/api/v2/boards-and-blocks\" \\\n -d ''\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-patchBoardsAndBlocks-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        Object body = Object; // Object | \n\n        try {\n            Object result = apiInstance.patchBoardsAndBlocks(body);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#patchBoardsAndBlocks\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-patchBoardsAndBlocks-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        Object body = Object; // Object | \n\n        try {\n            Object result = apiInstance.patchBoardsAndBlocks(body);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#patchBoardsAndBlocks\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-patchBoardsAndBlocks-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-patchBoardsAndBlocks-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nObject *body = Object; // \n\n[apiInstance patchBoardsAndBlocksWith:body\n              completionHandler: ^(Object output, NSError* error) {\n    if (output) {\n        NSLog(@\"%@\", output);\n    }\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-patchBoardsAndBlocks-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar body = Object; // {Object} \n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully. Returned data: ' + data);\n  }\n};\napi.patchBoardsAndBlocks(body, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-patchBoardsAndBlocks-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-patchBoardsAndBlocks-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class patchBoardsAndBlocksExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var body = Object;  // Object | \n\n            try {\n                Object result = apiInstance.patchBoardsAndBlocks(body);\n                Debug.WriteLine(result);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.patchBoardsAndBlocks: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-patchBoardsAndBlocks-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$body = Object; // Object | \n\ntry {\n    $result = $api_instance->patchBoardsAndBlocks($body);\n    print_r($result);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->patchBoardsAndBlocks: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-patchBoardsAndBlocks-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $body = WWW::OPenAPIClient::Object::Object->new(); # Object | \n\neval {\n    my $result = $api_instance->patchBoardsAndBlocks(body => $body);\n    print Dumper($result);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->patchBoardsAndBlocks: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-patchBoardsAndBlocks-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nbody = Object # Object | \n\ntry:\n    api_response = api_instance.patch_boards_and_blocks(body)\n    pprint(api_response)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->patchBoardsAndBlocks: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-patchBoardsAndBlocks-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let body = Object; // Object\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.patchBoardsAndBlocks(body, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n\n\n                            <div class=\"methodsubtabletitle\">Body parameters</div>\n                            <table id=\"methodsubtable\">\n                              <tr>\n                                <th width=\"150px\">Name</th>\n                                <th>Description</th>\n                              </tr>\n                                <tr><td style=\"width:150px;\">body <span style=\"color:red;\">*</span></td>\n<td>\n<p class=\"marked\">the patches for the boards and blocks</p>\n<script>\n$(document).ready(function() {\n  var schemaWrapper = {\n  \"description\" : \"the patches for the boards and blocks\",\n  \"content\" : {\n    \"application/json\" : {\n      \"schema\" : {\n        \"$ref\" : \"#/components/schemas/PatchBoardsAndBlocks\"\n      }\n    }\n  },\n  \"required\" : true\n};\n\n  var schema = findNode('schema',schemaWrapper).schema;\n  if (!schema) {\n    schema = schemaWrapper.schema;\n  }\n  if (schema.$ref != null) {\n    schema = defsParser.$refs.get(schema.$ref);\n  } else {\n    schemaWrapper.definitions = Object.assign({}, defs);\n    $RefParser.dereference(schemaWrapper).catch(function(err) {\n      console.log(err);\n    });\n  }\n\n  var view = new JSONSchemaView(schema,2,{isBodyParam: true});\n  var result = $('#d2e199_patchBoardsAndBlocks_body');\n  result.empty();\n  result.append(view.render());\n});\n</script>\n<div id=\"d2e199_patchBoardsAndBlocks_body\"></div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-patchBoardsAndBlocks-title-200\"></h3>\n                            <p id=\"examples-Default-patchBoardsAndBlocks-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-patchBoardsAndBlocks-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-patchBoardsAndBlocks-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-patchBoardsAndBlocks-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-patchBoardsAndBlocks-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-patchBoardsAndBlocks-200-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-patchBoardsAndBlocks-200-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-patchBoardsAndBlocks-200-schema\">\n                                  <div id=\"responses-Default-patchBoardsAndBlocks-schema-200\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=BoardsAndBlocks is used to operate over boards and blocks at the\nsame time, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-patchBoardsAndBlocks-200-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-patchBoardsAndBlocks-schema-200');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-patchBoardsAndBlocks-200-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-patchBoardsAndBlocks-title-default\"></h3>\n                            <p id=\"examples-Default-patchBoardsAndBlocks-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-patchBoardsAndBlocks-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-patchBoardsAndBlocks-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-patchBoardsAndBlocks-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-patchBoardsAndBlocks-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-patchBoardsAndBlocks-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-patchBoardsAndBlocks-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-patchBoardsAndBlocks-default-schema\">\n                                  <div id=\"responses-Default-patchBoardsAndBlocks-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-patchBoardsAndBlocks-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-patchBoardsAndBlocks-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-patchBoardsAndBlocks-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-patchCard\">\n                      <article id=\"api-Default-patchCard-0\" data-group=\"User\" data-name=\"patchCard\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>patchCard</h1>\n                          <p>Patches the specified card.</p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\"></p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"patch\"><code><span class=\"pln\">/cards/{cardID}/cards</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-patchCard-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-patchCard-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-patchCard-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-patchCard-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-patchCard-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-patchCard-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-patchCard-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-patchCard-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-patchCard-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-patchCard-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-patchCard-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-patchCard-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-patchCard-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X PATCH \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n -H \"Content-Type: application/json\" \\\n \"http://localhost/api/v2/cards/{cardID}/cards?disable_notify=\" \\\n -d ''\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-patchCard-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String cardID = cardID_example; // String | Card ID\n        Object body = Object; // Object | \n        oas_any_type_not_mapped disableNotify = ; // oas_any_type_not_mapped | Disables notifications (for bulk data patching)\n\n        try {\n            Object result = apiInstance.patchCard(cardID, body, disableNotify);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#patchCard\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-patchCard-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String cardID = cardID_example; // String | Card ID\n        Object body = Object; // Object | \n        oas_any_type_not_mapped disableNotify = ; // oas_any_type_not_mapped | Disables notifications (for bulk data patching)\n\n        try {\n            Object result = apiInstance.patchCard(cardID, body, disableNotify);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#patchCard\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-patchCard-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-patchCard-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *cardID = cardID_example; // Card ID (default to null)\nObject *body = Object; // \noas_any_type_not_mapped *disableNotify = ; // Disables notifications (for bulk data patching) (optional) (default to null)\n\n// Patches the specified card.\n[apiInstance patchCardWith:cardID\n    body:body\n    disableNotify:disableNotify\n              completionHandler: ^(Object output, NSError* error) {\n    if (output) {\n        NSLog(@\"%@\", output);\n    }\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-patchCard-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar cardID = cardID_example; // {String} Card ID\nvar body = Object; // {Object} \nvar opts = {\n  'disableNotify':  // {oas_any_type_not_mapped} Disables notifications (for bulk data patching)\n};\n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully. Returned data: ' + data);\n  }\n};\napi.patchCard(cardID, body, opts, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-patchCard-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-patchCard-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class patchCardExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var cardID = cardID_example;  // String | Card ID (default to null)\n            var body = Object;  // Object | \n            var disableNotify = new oas_any_type_not_mapped(); // oas_any_type_not_mapped | Disables notifications (for bulk data patching) (optional)  (default to null)\n\n            try {\n                // Patches the specified card.\n                Object result = apiInstance.patchCard(cardID, body, disableNotify);\n                Debug.WriteLine(result);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.patchCard: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-patchCard-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$cardID = cardID_example; // String | Card ID\n$body = Object; // Object | \n$disableNotify = ; // oas_any_type_not_mapped | Disables notifications (for bulk data patching)\n\ntry {\n    $result = $api_instance->patchCard($cardID, $body, $disableNotify);\n    print_r($result);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->patchCard: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-patchCard-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $cardID = cardID_example; # String | Card ID\nmy $body = WWW::OPenAPIClient::Object::Object->new(); # Object | \nmy $disableNotify = ; # oas_any_type_not_mapped | Disables notifications (for bulk data patching)\n\neval {\n    my $result = $api_instance->patchCard(cardID => $cardID, body => $body, disableNotify => $disableNotify);\n    print Dumper($result);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->patchCard: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-patchCard-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\ncardID = cardID_example # String | Card ID (default to null)\nbody = Object # Object | \ndisableNotify =  # oas_any_type_not_mapped | Disables notifications (for bulk data patching) (optional) (default to null)\n\ntry:\n    # Patches the specified card.\n    api_response = api_instance.patch_card(cardID, body, disableNotify=disableNotify)\n    pprint(api_response)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->patchCard: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-patchCard-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let cardID = cardID_example; // String\n    let body = Object; // Object\n    let disableNotify = ; // oas_any_type_not_mapped\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.patchCard(cardID, body, disableNotify, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">cardID*</td>\n<td>\n\n\n    <div id=\"d2e199_patchCard_cardID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nCard ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n                            <div class=\"methodsubtabletitle\">Body parameters</div>\n                            <table id=\"methodsubtable\">\n                              <tr>\n                                <th width=\"150px\">Name</th>\n                                <th>Description</th>\n                              </tr>\n                                <tr><td style=\"width:150px;\">body <span style=\"color:red;\">*</span></td>\n<td>\n<p class=\"marked\">the card patch</p>\n<script>\n$(document).ready(function() {\n  var schemaWrapper = {\n  \"description\" : \"the card patch\",\n  \"content\" : {\n    \"application/json\" : {\n      \"schema\" : {\n        \"$ref\" : \"#/components/schemas/CardPatch\"\n      }\n    }\n  },\n  \"required\" : true\n};\n\n  var schema = findNode('schema',schemaWrapper).schema;\n  if (!schema) {\n    schema = schemaWrapper.schema;\n  }\n  if (schema.$ref != null) {\n    schema = defsParser.$refs.get(schema.$ref);\n  } else {\n    schemaWrapper.definitions = Object.assign({}, defs);\n    $RefParser.dereference(schemaWrapper).catch(function(err) {\n      console.log(err);\n    });\n  }\n\n  var view = new JSONSchemaView(schema,2,{isBodyParam: true});\n  var result = $('#d2e199_patchCard_body');\n  result.empty();\n  result.append(view.render());\n});\n</script>\n<div id=\"d2e199_patchCard_body\"></div>\n</td>\n</tr>\n\n                            </table>\n\n\n                            <div class=\"methodsubtabletitle\">Query parameters</div>\n                            <table id=\"methodsubtable\">\n                              <tr>\n                                <th width=\"150px\">Name</th>\n                                <th>Description</th>\n                              </tr>\n                                <tr><td style=\"width:150px;\">disable_notify</td>\n<td>\n\n\n    <div id=\"d2e199_patchCard_disableNotify\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    oas_any_type_not_mapped\n                </span>\n\n                    <div class=\"inner description marked\">\nDisables notifications (for bulk data patching)\n                    </div>\n            </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-patchCard-title-200\"></h3>\n                            <p id=\"examples-Default-patchCard-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-patchCard-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-patchCard-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-patchCard-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-patchCard-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-patchCard-200-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-patchCard-200-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-patchCard-200-schema\">\n                                  <div id=\"responses-Default-patchCard-schema-200\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {title=Card represents a group of content blocks and properties., x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-patchCard-200-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-patchCard-schema-200');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-patchCard-200-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-patchCard-title-default\"></h3>\n                            <p id=\"examples-Default-patchCard-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-patchCard-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-patchCard-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-patchCard-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-patchCard-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-patchCard-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-patchCard-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-patchCard-default-schema\">\n                                  <div id=\"responses-Default-patchCard-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-patchCard-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-patchCard-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-patchCard-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-ping\">\n                      <article id=\"api-Default-ping-0\" data-group=\"User\" data-name=\"ping\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>ping</h1>\n                          <p>Responds with server metadata if the web service is running.</p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\"></p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"get\"><code><span class=\"pln\">/ping</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-ping-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-ping-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-ping-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-ping-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-ping-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-ping-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-ping-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-ping-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-ping-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-ping-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-ping-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-ping-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-ping-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X GET \\\n \"http://localhost/api/v2/ping\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-ping-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n\n        try {\n            apiInstance.ping();\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#ping\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-ping-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n\n        try {\n            apiInstance.ping();\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#ping\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-ping-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-ping-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\n\n// Responds with server metadata if the web service is running.\n[apiInstance pingWithCompletionHandler: \n              ^(NSError* error) {\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-ping-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully.');\n  }\n};\napi.ping(callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-ping-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-ping-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class pingExample\n    {\n        public void main()\n        {\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n\n            try {\n                // Responds with server metadata if the web service is running.\n                apiInstance.ping();\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.ping: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-ping-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n\ntry {\n    $api_instance->ping();\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->ping: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-ping-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\n\neval {\n    $api_instance->ping();\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->ping: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-ping-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\n\ntry:\n    # Responds with server metadata if the web service is running.\n    api_instance.ping()\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->ping: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-ping-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.ping(&context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n\n\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-ping-title-200\"></h3>\n                            <p id=\"examples-Default-ping-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-ping-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-ping-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-ping-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-ping-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-ping-200-wrapper\" style='margin-bottom: 10px;'>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-postSharing\">\n                      <article id=\"api-Default-postSharing-0\" data-group=\"User\" data-name=\"postSharing\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>postSharing</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Sets sharing information for a board</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"post\"><code><span class=\"pln\">/boards/{boardID}/sharing</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-postSharing-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-postSharing-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-postSharing-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-postSharing-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-postSharing-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-postSharing-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-postSharing-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-postSharing-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-postSharing-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-postSharing-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-postSharing-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-postSharing-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-postSharing-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X POST \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n -H \"Content-Type: application/json\" \\\n \"http://localhost/api/v2/boards/{boardID}/sharing\" \\\n -d ''\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-postSharing-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String boardID = boardID_example; // String | Board ID\n        Object body = Object; // Object | \n\n        try {\n            apiInstance.postSharing(boardID, body);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#postSharing\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-postSharing-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String boardID = boardID_example; // String | Board ID\n        Object body = Object; // Object | \n\n        try {\n            apiInstance.postSharing(boardID, body);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#postSharing\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-postSharing-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-postSharing-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *boardID = boardID_example; // Board ID (default to null)\nObject *body = Object; // \n\n[apiInstance postSharingWith:boardID\n    body:body\n              completionHandler: ^(NSError* error) {\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-postSharing-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar boardID = boardID_example; // {String} Board ID\nvar body = Object; // {Object} \n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully.');\n  }\n};\napi.postSharing(boardID, body, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-postSharing-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-postSharing-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class postSharingExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var boardID = boardID_example;  // String | Board ID (default to null)\n            var body = Object;  // Object | \n\n            try {\n                apiInstance.postSharing(boardID, body);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.postSharing: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-postSharing-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$boardID = boardID_example; // String | Board ID\n$body = Object; // Object | \n\ntry {\n    $api_instance->postSharing($boardID, $body);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->postSharing: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-postSharing-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $boardID = boardID_example; # String | Board ID\nmy $body = WWW::OPenAPIClient::Object::Object->new(); # Object | \n\neval {\n    $api_instance->postSharing(boardID => $boardID, body => $body);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->postSharing: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-postSharing-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nboardID = boardID_example # String | Board ID (default to null)\nbody = Object # Object | \n\ntry:\n    api_instance.post_sharing(boardID, body)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->postSharing: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-postSharing-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let boardID = boardID_example; // String\n    let body = Object; // Object\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.postSharing(boardID, body, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">boardID*</td>\n<td>\n\n\n    <div id=\"d2e199_postSharing_boardID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nBoard ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n                            <div class=\"methodsubtabletitle\">Body parameters</div>\n                            <table id=\"methodsubtable\">\n                              <tr>\n                                <th width=\"150px\">Name</th>\n                                <th>Description</th>\n                              </tr>\n                                <tr><td style=\"width:150px;\">body <span style=\"color:red;\">*</span></td>\n<td>\n<p class=\"marked\">sharing information for a root block</p>\n<script>\n$(document).ready(function() {\n  var schemaWrapper = {\n  \"description\" : \"sharing information for a root block\",\n  \"content\" : {\n    \"application/json\" : {\n      \"schema\" : {\n        \"$ref\" : \"#/components/schemas/Sharing\"\n      }\n    }\n  },\n  \"required\" : true\n};\n\n  var schema = findNode('schema',schemaWrapper).schema;\n  if (!schema) {\n    schema = schemaWrapper.schema;\n  }\n  if (schema.$ref != null) {\n    schema = defsParser.$refs.get(schema.$ref);\n  } else {\n    schemaWrapper.definitions = Object.assign({}, defs);\n    $RefParser.dereference(schemaWrapper).catch(function(err) {\n      console.log(err);\n    });\n  }\n\n  var view = new JSONSchemaView(schema,2,{isBodyParam: true});\n  var result = $('#d2e199_postSharing_body');\n  result.empty();\n  result.append(view.render());\n});\n</script>\n<div id=\"d2e199_postSharing_body\"></div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-postSharing-title-200\"></h3>\n                            <p id=\"examples-Default-postSharing-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-postSharing-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-postSharing-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-postSharing-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-postSharing-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-postSharing-200-wrapper\" style='margin-bottom: 10px;'>\n                            </div>\n                            <h3 id=\"examples-Default-postSharing-title-default\"></h3>\n                            <p id=\"examples-Default-postSharing-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-postSharing-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-postSharing-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-postSharing-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-postSharing-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-postSharing-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-postSharing-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-postSharing-default-schema\">\n                                  <div id=\"responses-Default-postSharing-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-postSharing-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-postSharing-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-postSharing-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-regenerateSignupToken\">\n                      <article id=\"api-Default-regenerateSignupToken-0\" data-group=\"User\" data-name=\"regenerateSignupToken\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>regenerateSignupToken</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Regenerates the signup token for the root team</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"post\"><code><span class=\"pln\">/teams/{teamID}/regenerate_signup_token</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-regenerateSignupToken-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-regenerateSignupToken-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-regenerateSignupToken-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-regenerateSignupToken-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-regenerateSignupToken-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-regenerateSignupToken-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-regenerateSignupToken-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-regenerateSignupToken-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-regenerateSignupToken-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-regenerateSignupToken-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-regenerateSignupToken-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-regenerateSignupToken-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-regenerateSignupToken-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X POST \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n \"http://localhost/api/v2/teams/{teamID}/regenerate_signup_token\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-regenerateSignupToken-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String teamID = teamID_example; // String | Team ID\n\n        try {\n            apiInstance.regenerateSignupToken(teamID);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#regenerateSignupToken\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-regenerateSignupToken-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String teamID = teamID_example; // String | Team ID\n\n        try {\n            apiInstance.regenerateSignupToken(teamID);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#regenerateSignupToken\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-regenerateSignupToken-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-regenerateSignupToken-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *teamID = teamID_example; // Team ID (default to null)\n\n[apiInstance regenerateSignupTokenWith:teamID\n              completionHandler: ^(NSError* error) {\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-regenerateSignupToken-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar teamID = teamID_example; // {String} Team ID\n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully.');\n  }\n};\napi.regenerateSignupToken(teamID, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-regenerateSignupToken-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-regenerateSignupToken-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class regenerateSignupTokenExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var teamID = teamID_example;  // String | Team ID (default to null)\n\n            try {\n                apiInstance.regenerateSignupToken(teamID);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.regenerateSignupToken: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-regenerateSignupToken-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$teamID = teamID_example; // String | Team ID\n\ntry {\n    $api_instance->regenerateSignupToken($teamID);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->regenerateSignupToken: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-regenerateSignupToken-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $teamID = teamID_example; # String | Team ID\n\neval {\n    $api_instance->regenerateSignupToken(teamID => $teamID);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->regenerateSignupToken: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-regenerateSignupToken-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nteamID = teamID_example # String | Team ID (default to null)\n\ntry:\n    api_instance.regenerate_signup_token(teamID)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->regenerateSignupToken: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-regenerateSignupToken-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let teamID = teamID_example; // String\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.regenerateSignupToken(teamID, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">teamID*</td>\n<td>\n\n\n    <div id=\"d2e199_regenerateSignupToken_teamID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nTeam ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-regenerateSignupToken-title-200\"></h3>\n                            <p id=\"examples-Default-regenerateSignupToken-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-regenerateSignupToken-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-regenerateSignupToken-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-regenerateSignupToken-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-regenerateSignupToken-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-regenerateSignupToken-200-wrapper\" style='margin-bottom: 10px;'>\n                            </div>\n                            <h3 id=\"examples-Default-regenerateSignupToken-title-default\"></h3>\n                            <p id=\"examples-Default-regenerateSignupToken-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-regenerateSignupToken-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-regenerateSignupToken-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-regenerateSignupToken-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-regenerateSignupToken-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-regenerateSignupToken-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-regenerateSignupToken-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-regenerateSignupToken-default-schema\">\n                                  <div id=\"responses-Default-regenerateSignupToken-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-regenerateSignupToken-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-regenerateSignupToken-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-regenerateSignupToken-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-register\">\n                      <article id=\"api-Default-register-0\" data-group=\"User\" data-name=\"register\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>register</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Register new user</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"post\"><code><span class=\"pln\">/register</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-register-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-register-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-register-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-register-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-register-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-register-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-register-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-register-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-register-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-register-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-register-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-register-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-register-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X POST \\\n -H \"Accept: application/json\" \\\n -H \"Content-Type: application/json\" \\\n \"http://localhost/api/v2/register\" \\\n -d ''\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-register-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        Object body = Object; // Object | \n\n        try {\n            apiInstance.register(body);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#register\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-register-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        Object body = Object; // Object | \n\n        try {\n            apiInstance.register(body);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#register\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-register-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-register-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nObject *body = Object; // \n\n[apiInstance registerWith:body\n              completionHandler: ^(NSError* error) {\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-register-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar body = Object; // {Object} \n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully.');\n  }\n};\napi.register(body, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-register-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-register-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class registerExample\n    {\n        public void main()\n        {\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var body = Object;  // Object | \n\n            try {\n                apiInstance.register(body);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.register: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-register-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$body = Object; // Object | \n\ntry {\n    $api_instance->register($body);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->register: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-register-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $body = WWW::OPenAPIClient::Object::Object->new(); # Object | \n\neval {\n    $api_instance->register(body => $body);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->register: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-register-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nbody = Object # Object | \n\ntry:\n    api_instance.register(body)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->register: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-register-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let body = Object; // Object\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.register(body, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n\n\n                            <div class=\"methodsubtabletitle\">Body parameters</div>\n                            <table id=\"methodsubtable\">\n                              <tr>\n                                <th width=\"150px\">Name</th>\n                                <th>Description</th>\n                              </tr>\n                                <tr><td style=\"width:150px;\">body <span style=\"color:red;\">*</span></td>\n<td>\n<p class=\"marked\">Register request</p>\n<script>\n$(document).ready(function() {\n  var schemaWrapper = {\n  \"description\" : \"Register request\",\n  \"content\" : {\n    \"application/json\" : {\n      \"schema\" : {\n        \"$ref\" : \"#/components/schemas/RegisterRequest\"\n      }\n    }\n  },\n  \"required\" : true\n};\n\n  var schema = findNode('schema',schemaWrapper).schema;\n  if (!schema) {\n    schema = schemaWrapper.schema;\n  }\n  if (schema.$ref != null) {\n    schema = defsParser.$refs.get(schema.$ref);\n  } else {\n    schemaWrapper.definitions = Object.assign({}, defs);\n    $RefParser.dereference(schemaWrapper).catch(function(err) {\n      console.log(err);\n    });\n  }\n\n  var view = new JSONSchemaView(schema,2,{isBodyParam: true});\n  var result = $('#d2e199_register_body');\n  result.empty();\n  result.append(view.render());\n});\n</script>\n<div id=\"d2e199_register_body\"></div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-register-title-200\"></h3>\n                            <p id=\"examples-Default-register-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-register-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-register-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-register-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-register-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-register-200-wrapper\" style='margin-bottom: 10px;'>\n                            </div>\n                            <h3 id=\"examples-Default-register-title-401\"></h3>\n                            <p id=\"examples-Default-register-description-401\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault401_description = `invalid registration token`;\n                              var responseDefault401_description_break = responseDefault401_description.indexOf('\\n');\n                              if (responseDefault401_description_break == -1) {\n                                $(\"#examples-Default-register-title-401\").text(\"Status: 401 - \" + responseDefault401_description);\n                              } else {\n                                $(\"#examples-Default-register-title-401\").text(\"Status: 401 - \" + responseDefault401_description.substring(0, responseDefault401_description_break));\n                                $(\"#examples-Default-register-description-401\").html(responseDefault401_description.substring(responseDefault401_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-register-401\" class=\"nav nav-tabs nav-tabs-examples\" >\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-register-401-wrapper\" style='margin-bottom: 10px;'>\n                            </div>\n                            <h3 id=\"examples-Default-register-title-500\"></h3>\n                            <p id=\"examples-Default-register-description-500\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault500_description = `internal error`;\n                              var responseDefault500_description_break = responseDefault500_description.indexOf('\\n');\n                              if (responseDefault500_description_break == -1) {\n                                $(\"#examples-Default-register-title-500\").text(\"Status: 500 - \" + responseDefault500_description);\n                              } else {\n                                $(\"#examples-Default-register-title-500\").text(\"Status: 500 - \" + responseDefault500_description.substring(0, responseDefault500_description_break));\n                                $(\"#examples-Default-register-description-500\").html(responseDefault500_description.substring(responseDefault500_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-register-500\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-register-500-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-register-500-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-register-500-schema\">\n                                  <div id=\"responses-Default-register-schema-500\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-register-500-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-register-schema-500');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-register-500-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-searchAllBoards\">\n                      <article id=\"api-Default-searchAllBoards-0\" data-group=\"User\" data-name=\"searchAllBoards\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>searchAllBoards</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Returns the boards that match with a search term</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"get\"><code><span class=\"pln\">/boards/search</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-searchAllBoards-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-searchAllBoards-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-searchAllBoards-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-searchAllBoards-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-searchAllBoards-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-searchAllBoards-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-searchAllBoards-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-searchAllBoards-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-searchAllBoards-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-searchAllBoards-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-searchAllBoards-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-searchAllBoards-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-searchAllBoards-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X GET \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n \"http://localhost/api/v2/boards/search?q=q_example\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-searchAllBoards-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String q = q_example; // String | The search term. Must have at least one character\n\n        try {\n            array[Object] result = apiInstance.searchAllBoards(q);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#searchAllBoards\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-searchAllBoards-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String q = q_example; // String | The search term. Must have at least one character\n\n        try {\n            array[Object] result = apiInstance.searchAllBoards(q);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#searchAllBoards\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-searchAllBoards-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-searchAllBoards-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *q = q_example; // The search term. Must have at least one character (default to null)\n\n[apiInstance searchAllBoardsWith:q\n              completionHandler: ^(array[Object] output, NSError* error) {\n    if (output) {\n        NSLog(@\"%@\", output);\n    }\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-searchAllBoards-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar q = q_example; // {String} The search term. Must have at least one character\n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully. Returned data: ' + data);\n  }\n};\napi.searchAllBoards(q, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-searchAllBoards-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-searchAllBoards-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class searchAllBoardsExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var q = q_example;  // String | The search term. Must have at least one character (default to null)\n\n            try {\n                array[Object] result = apiInstance.searchAllBoards(q);\n                Debug.WriteLine(result);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.searchAllBoards: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-searchAllBoards-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$q = q_example; // String | The search term. Must have at least one character\n\ntry {\n    $result = $api_instance->searchAllBoards($q);\n    print_r($result);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->searchAllBoards: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-searchAllBoards-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $q = q_example; # String | The search term. Must have at least one character\n\neval {\n    my $result = $api_instance->searchAllBoards(q => $q);\n    print Dumper($result);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->searchAllBoards: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-searchAllBoards-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nq = q_example # String | The search term. Must have at least one character (default to null)\n\ntry:\n    api_response = api_instance.search_all_boards(q)\n    pprint(api_response)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->searchAllBoards: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-searchAllBoards-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let q = q_example; // String\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.searchAllBoards(q, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n\n\n\n\n                            <div class=\"methodsubtabletitle\">Query parameters</div>\n                            <table id=\"methodsubtable\">\n                              <tr>\n                                <th width=\"150px\">Name</th>\n                                <th>Description</th>\n                              </tr>\n                                <tr><td style=\"width:150px;\">q*</td>\n<td>\n\n\n    <div id=\"d2e199_searchAllBoards_q\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nThe search term. Must have at least one character\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-searchAllBoards-title-200\"></h3>\n                            <p id=\"examples-Default-searchAllBoards-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-searchAllBoards-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-searchAllBoards-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-searchAllBoards-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-searchAllBoards-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-searchAllBoards-200-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-searchAllBoards-200-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-searchAllBoards-200-schema\">\n                                  <div id=\"responses-Default-searchAllBoards-schema-200\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = ;\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-searchAllBoards-200-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-searchAllBoards-schema-200');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-searchAllBoards-200-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-searchAllBoards-title-default\"></h3>\n                            <p id=\"examples-Default-searchAllBoards-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-searchAllBoards-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-searchAllBoards-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-searchAllBoards-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-searchAllBoards-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-searchAllBoards-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-searchAllBoards-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-searchAllBoards-default-schema\">\n                                  <div id=\"responses-Default-searchAllBoards-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-searchAllBoards-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-searchAllBoards-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-searchAllBoards-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-searchBoards\">\n                      <article id=\"api-Default-searchBoards-0\" data-group=\"User\" data-name=\"searchBoards\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>searchBoards</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Returns the boards that match with a search term in the team</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"get\"><code><span class=\"pln\">/teams/{teamID}/boards/search</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-searchBoards-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-searchBoards-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-searchBoards-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-searchBoards-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-searchBoards-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-searchBoards-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-searchBoards-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-searchBoards-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-searchBoards-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-searchBoards-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-searchBoards-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-searchBoards-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-searchBoards-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X GET \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n \"http://localhost/api/v2/teams/{teamID}/boards/search?q=q_example\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-searchBoards-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String teamID = teamID_example; // String | Team ID\n        String q = q_example; // String | The search term. Must have at least one character\n\n        try {\n            array[Object] result = apiInstance.searchBoards(teamID, q);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#searchBoards\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-searchBoards-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String teamID = teamID_example; // String | Team ID\n        String q = q_example; // String | The search term. Must have at least one character\n\n        try {\n            array[Object] result = apiInstance.searchBoards(teamID, q);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#searchBoards\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-searchBoards-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-searchBoards-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *teamID = teamID_example; // Team ID (default to null)\nString *q = q_example; // The search term. Must have at least one character (default to null)\n\n[apiInstance searchBoardsWith:teamID\n    q:q\n              completionHandler: ^(array[Object] output, NSError* error) {\n    if (output) {\n        NSLog(@\"%@\", output);\n    }\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-searchBoards-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar teamID = teamID_example; // {String} Team ID\nvar q = q_example; // {String} The search term. Must have at least one character\n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully. Returned data: ' + data);\n  }\n};\napi.searchBoards(teamID, q, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-searchBoards-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-searchBoards-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class searchBoardsExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var teamID = teamID_example;  // String | Team ID (default to null)\n            var q = q_example;  // String | The search term. Must have at least one character (default to null)\n\n            try {\n                array[Object] result = apiInstance.searchBoards(teamID, q);\n                Debug.WriteLine(result);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.searchBoards: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-searchBoards-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$teamID = teamID_example; // String | Team ID\n$q = q_example; // String | The search term. Must have at least one character\n\ntry {\n    $result = $api_instance->searchBoards($teamID, $q);\n    print_r($result);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->searchBoards: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-searchBoards-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $teamID = teamID_example; # String | Team ID\nmy $q = q_example; # String | The search term. Must have at least one character\n\neval {\n    my $result = $api_instance->searchBoards(teamID => $teamID, q => $q);\n    print Dumper($result);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->searchBoards: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-searchBoards-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nteamID = teamID_example # String | Team ID (default to null)\nq = q_example # String | The search term. Must have at least one character (default to null)\n\ntry:\n    api_response = api_instance.search_boards(teamID, q)\n    pprint(api_response)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->searchBoards: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-searchBoards-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let teamID = teamID_example; // String\n    let q = q_example; // String\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.searchBoards(teamID, q, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">teamID*</td>\n<td>\n\n\n    <div id=\"d2e199_searchBoards_teamID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nTeam ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n\n                            <div class=\"methodsubtabletitle\">Query parameters</div>\n                            <table id=\"methodsubtable\">\n                              <tr>\n                                <th width=\"150px\">Name</th>\n                                <th>Description</th>\n                              </tr>\n                                <tr><td style=\"width:150px;\">q*</td>\n<td>\n\n\n    <div id=\"d2e199_searchBoards_q\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nThe search term. Must have at least one character\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-searchBoards-title-200\"></h3>\n                            <p id=\"examples-Default-searchBoards-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-searchBoards-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-searchBoards-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-searchBoards-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-searchBoards-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-searchBoards-200-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-searchBoards-200-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-searchBoards-200-schema\">\n                                  <div id=\"responses-Default-searchBoards-schema-200\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = ;\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-searchBoards-200-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-searchBoards-schema-200');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-searchBoards-200-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-searchBoards-title-default\"></h3>\n                            <p id=\"examples-Default-searchBoards-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-searchBoards-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-searchBoards-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-searchBoards-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-searchBoards-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-searchBoards-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-searchBoards-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-searchBoards-default-schema\">\n                                  <div id=\"responses-Default-searchBoards-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-searchBoards-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-searchBoards-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-searchBoards-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-searchLinkableBoards\">\n                      <article id=\"api-Default-searchLinkableBoards-0\" data-group=\"User\" data-name=\"searchLinkableBoards\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>searchLinkableBoards</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Returns the boards that match with a search term in the team and the\nuser has permission to manage members</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"get\"><code><span class=\"pln\">/teams/{teamID}/boards/search/linkable</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-searchLinkableBoards-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-searchLinkableBoards-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-searchLinkableBoards-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-searchLinkableBoards-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-searchLinkableBoards-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-searchLinkableBoards-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-searchLinkableBoards-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-searchLinkableBoards-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-searchLinkableBoards-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-searchLinkableBoards-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-searchLinkableBoards-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-searchLinkableBoards-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-searchLinkableBoards-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X GET \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n \"http://localhost/api/v2/teams/{teamID}/boards/search/linkable?q=q_example\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-searchLinkableBoards-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String teamID = teamID_example; // String | Team ID\n        String q = q_example; // String | The search term. Must have at least one character\n\n        try {\n            array[Object] result = apiInstance.searchLinkableBoards(teamID, q);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#searchLinkableBoards\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-searchLinkableBoards-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String teamID = teamID_example; // String | Team ID\n        String q = q_example; // String | The search term. Must have at least one character\n\n        try {\n            array[Object] result = apiInstance.searchLinkableBoards(teamID, q);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#searchLinkableBoards\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-searchLinkableBoards-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-searchLinkableBoards-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *teamID = teamID_example; // Team ID (default to null)\nString *q = q_example; // The search term. Must have at least one character (default to null)\n\n[apiInstance searchLinkableBoardsWith:teamID\n    q:q\n              completionHandler: ^(array[Object] output, NSError* error) {\n    if (output) {\n        NSLog(@\"%@\", output);\n    }\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-searchLinkableBoards-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar teamID = teamID_example; // {String} Team ID\nvar q = q_example; // {String} The search term. Must have at least one character\n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully. Returned data: ' + data);\n  }\n};\napi.searchLinkableBoards(teamID, q, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-searchLinkableBoards-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-searchLinkableBoards-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class searchLinkableBoardsExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var teamID = teamID_example;  // String | Team ID (default to null)\n            var q = q_example;  // String | The search term. Must have at least one character (default to null)\n\n            try {\n                array[Object] result = apiInstance.searchLinkableBoards(teamID, q);\n                Debug.WriteLine(result);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.searchLinkableBoards: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-searchLinkableBoards-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$teamID = teamID_example; // String | Team ID\n$q = q_example; // String | The search term. Must have at least one character\n\ntry {\n    $result = $api_instance->searchLinkableBoards($teamID, $q);\n    print_r($result);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->searchLinkableBoards: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-searchLinkableBoards-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $teamID = teamID_example; # String | Team ID\nmy $q = q_example; # String | The search term. Must have at least one character\n\neval {\n    my $result = $api_instance->searchLinkableBoards(teamID => $teamID, q => $q);\n    print Dumper($result);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->searchLinkableBoards: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-searchLinkableBoards-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nteamID = teamID_example # String | Team ID (default to null)\nq = q_example # String | The search term. Must have at least one character (default to null)\n\ntry:\n    api_response = api_instance.search_linkable_boards(teamID, q)\n    pprint(api_response)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->searchLinkableBoards: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-searchLinkableBoards-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let teamID = teamID_example; // String\n    let q = q_example; // String\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.searchLinkableBoards(teamID, q, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">teamID*</td>\n<td>\n\n\n    <div id=\"d2e199_searchLinkableBoards_teamID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nTeam ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n\n                            <div class=\"methodsubtabletitle\">Query parameters</div>\n                            <table id=\"methodsubtable\">\n                              <tr>\n                                <th width=\"150px\">Name</th>\n                                <th>Description</th>\n                              </tr>\n                                <tr><td style=\"width:150px;\">q*</td>\n<td>\n\n\n    <div id=\"d2e199_searchLinkableBoards_q\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nThe search term. Must have at least one character\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-searchLinkableBoards-title-200\"></h3>\n                            <p id=\"examples-Default-searchLinkableBoards-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-searchLinkableBoards-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-searchLinkableBoards-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-searchLinkableBoards-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-searchLinkableBoards-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-searchLinkableBoards-200-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-searchLinkableBoards-200-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-searchLinkableBoards-200-schema\">\n                                  <div id=\"responses-Default-searchLinkableBoards-schema-200\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = ;\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-searchLinkableBoards-200-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-searchLinkableBoards-schema-200');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-searchLinkableBoards-200-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-searchLinkableBoards-title-default\"></h3>\n                            <p id=\"examples-Default-searchLinkableBoards-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-searchLinkableBoards-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-searchLinkableBoards-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-searchLinkableBoards-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-searchLinkableBoards-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-searchLinkableBoards-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-searchLinkableBoards-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-searchLinkableBoards-default-schema\">\n                                  <div id=\"responses-Default-searchLinkableBoards-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-searchLinkableBoards-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-searchLinkableBoards-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-searchLinkableBoards-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-searchMyChannels\">\n                      <article id=\"api-Default-searchMyChannels-0\" data-group=\"User\" data-name=\"searchMyChannels\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>searchMyChannels</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Returns the user available channels</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"get\"><code><span class=\"pln\">/teams/{teamID}/channels</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-searchMyChannels-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-searchMyChannels-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-searchMyChannels-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-searchMyChannels-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-searchMyChannels-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-searchMyChannels-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-searchMyChannels-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-searchMyChannels-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-searchMyChannels-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-searchMyChannels-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-searchMyChannels-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-searchMyChannels-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-searchMyChannels-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X GET \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n \"http://localhost/api/v2/teams/{teamID}/channels?search=search_example\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-searchMyChannels-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String teamID = teamID_example; // String | Team ID\n        String search = search_example; // String | string to filter channels list\n\n        try {\n            array[Channel] result = apiInstance.searchMyChannels(teamID, search);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#searchMyChannels\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-searchMyChannels-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String teamID = teamID_example; // String | Team ID\n        String search = search_example; // String | string to filter channels list\n\n        try {\n            array[Channel] result = apiInstance.searchMyChannels(teamID, search);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#searchMyChannels\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-searchMyChannels-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-searchMyChannels-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *teamID = teamID_example; // Team ID (default to null)\nString *search = search_example; // string to filter channels list (optional) (default to null)\n\n[apiInstance searchMyChannelsWith:teamID\n    search:search\n              completionHandler: ^(array[Channel] output, NSError* error) {\n    if (output) {\n        NSLog(@\"%@\", output);\n    }\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-searchMyChannels-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar teamID = teamID_example; // {String} Team ID\nvar opts = {\n  'search': search_example // {String} string to filter channels list\n};\n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully. Returned data: ' + data);\n  }\n};\napi.searchMyChannels(teamID, opts, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-searchMyChannels-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-searchMyChannels-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class searchMyChannelsExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var teamID = teamID_example;  // String | Team ID (default to null)\n            var search = search_example;  // String | string to filter channels list (optional)  (default to null)\n\n            try {\n                array[Channel] result = apiInstance.searchMyChannels(teamID, search);\n                Debug.WriteLine(result);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.searchMyChannels: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-searchMyChannels-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$teamID = teamID_example; // String | Team ID\n$search = search_example; // String | string to filter channels list\n\ntry {\n    $result = $api_instance->searchMyChannels($teamID, $search);\n    print_r($result);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->searchMyChannels: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-searchMyChannels-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $teamID = teamID_example; # String | Team ID\nmy $search = search_example; # String | string to filter channels list\n\neval {\n    my $result = $api_instance->searchMyChannels(teamID => $teamID, search => $search);\n    print Dumper($result);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->searchMyChannels: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-searchMyChannels-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nteamID = teamID_example # String | Team ID (default to null)\nsearch = search_example # String | string to filter channels list (optional) (default to null)\n\ntry:\n    api_response = api_instance.search_my_channels(teamID, search=search)\n    pprint(api_response)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->searchMyChannels: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-searchMyChannels-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let teamID = teamID_example; // String\n    let search = search_example; // String\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.searchMyChannels(teamID, search, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">teamID*</td>\n<td>\n\n\n    <div id=\"d2e199_searchMyChannels_teamID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nTeam ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n\n                            <div class=\"methodsubtabletitle\">Query parameters</div>\n                            <table id=\"methodsubtable\">\n                              <tr>\n                                <th width=\"150px\">Name</th>\n                                <th>Description</th>\n                              </tr>\n                                <tr><td style=\"width:150px;\">search</td>\n<td>\n\n\n    <div id=\"d2e199_searchMyChannels_search\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nstring to filter channels list\n                    </div>\n            </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-searchMyChannels-title-200\"></h3>\n                            <p id=\"examples-Default-searchMyChannels-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-searchMyChannels-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-searchMyChannels-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-searchMyChannels-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-searchMyChannels-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-searchMyChannels-200-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-searchMyChannels-200-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-searchMyChannels-200-schema\">\n                                  <div id=\"responses-Default-searchMyChannels-schema-200\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = ;\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-searchMyChannels-200-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-searchMyChannels-schema-200');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-searchMyChannels-200-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-searchMyChannels-title-default\"></h3>\n                            <p id=\"examples-Default-searchMyChannels-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-searchMyChannels-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-searchMyChannels-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-searchMyChannels-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-searchMyChannels-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-searchMyChannels-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-searchMyChannels-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-searchMyChannels-default-schema\">\n                                  <div id=\"responses-Default-searchMyChannels-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-searchMyChannels-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-searchMyChannels-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-searchMyChannels-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-undeleteBlock\">\n                      <article id=\"api-Default-undeleteBlock-0\" data-group=\"User\" data-name=\"undeleteBlock\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>undeleteBlock</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Undeletes a block</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"post\"><code><span class=\"pln\">/boards/{boardID}/blocks/{blockID}/undelete</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-undeleteBlock-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-undeleteBlock-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-undeleteBlock-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-undeleteBlock-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-undeleteBlock-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-undeleteBlock-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-undeleteBlock-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-undeleteBlock-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-undeleteBlock-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-undeleteBlock-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-undeleteBlock-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-undeleteBlock-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-undeleteBlock-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X POST \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n \"http://localhost/api/v2/boards/{boardID}/blocks/{blockID}/undelete\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-undeleteBlock-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String boardID = boardID_example; // String | Board ID\n        String blockID = blockID_example; // String | ID of block to undelete\n\n        try {\n            Object result = apiInstance.undeleteBlock(boardID, blockID);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#undeleteBlock\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-undeleteBlock-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String boardID = boardID_example; // String | Board ID\n        String blockID = blockID_example; // String | ID of block to undelete\n\n        try {\n            Object result = apiInstance.undeleteBlock(boardID, blockID);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#undeleteBlock\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-undeleteBlock-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-undeleteBlock-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *boardID = boardID_example; // Board ID (default to null)\nString *blockID = blockID_example; // ID of block to undelete (default to null)\n\n[apiInstance undeleteBlockWith:boardID\n    blockID:blockID\n              completionHandler: ^(Object output, NSError* error) {\n    if (output) {\n        NSLog(@\"%@\", output);\n    }\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-undeleteBlock-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar boardID = boardID_example; // {String} Board ID\nvar blockID = blockID_example; // {String} ID of block to undelete\n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully. Returned data: ' + data);\n  }\n};\napi.undeleteBlock(boardID, blockID, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-undeleteBlock-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-undeleteBlock-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class undeleteBlockExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var boardID = boardID_example;  // String | Board ID (default to null)\n            var blockID = blockID_example;  // String | ID of block to undelete (default to null)\n\n            try {\n                Object result = apiInstance.undeleteBlock(boardID, blockID);\n                Debug.WriteLine(result);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.undeleteBlock: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-undeleteBlock-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$boardID = boardID_example; // String | Board ID\n$blockID = blockID_example; // String | ID of block to undelete\n\ntry {\n    $result = $api_instance->undeleteBlock($boardID, $blockID);\n    print_r($result);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->undeleteBlock: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-undeleteBlock-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $boardID = boardID_example; # String | Board ID\nmy $blockID = blockID_example; # String | ID of block to undelete\n\neval {\n    my $result = $api_instance->undeleteBlock(boardID => $boardID, blockID => $blockID);\n    print Dumper($result);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->undeleteBlock: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-undeleteBlock-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nboardID = boardID_example # String | Board ID (default to null)\nblockID = blockID_example # String | ID of block to undelete (default to null)\n\ntry:\n    api_response = api_instance.undelete_block(boardID, blockID)\n    pprint(api_response)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->undeleteBlock: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-undeleteBlock-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let boardID = boardID_example; // String\n    let blockID = blockID_example; // String\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.undeleteBlock(boardID, blockID, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">boardID*</td>\n<td>\n\n\n    <div id=\"d2e199_undeleteBlock_boardID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nBoard ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                                  <tr><td style=\"width:150px;\">blockID*</td>\n<td>\n\n\n    <div id=\"d2e199_undeleteBlock_blockID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nID of block to undelete\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-undeleteBlock-title-200\"></h3>\n                            <p id=\"examples-Default-undeleteBlock-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-undeleteBlock-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-undeleteBlock-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-undeleteBlock-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-undeleteBlock-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-undeleteBlock-200-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-undeleteBlock-200-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-undeleteBlock-200-schema\">\n                                  <div id=\"responses-Default-undeleteBlock-schema-200\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=BlockPatch is a patch for modify blocks, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-undeleteBlock-200-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-undeleteBlock-schema-200');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-undeleteBlock-200-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-undeleteBlock-title-404\"></h3>\n                            <p id=\"examples-Default-undeleteBlock-description-404\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault404_description = `block not found`;\n                              var responseDefault404_description_break = responseDefault404_description.indexOf('\\n');\n                              if (responseDefault404_description_break == -1) {\n                                $(\"#examples-Default-undeleteBlock-title-404\").text(\"Status: 404 - \" + responseDefault404_description);\n                              } else {\n                                $(\"#examples-Default-undeleteBlock-title-404\").text(\"Status: 404 - \" + responseDefault404_description.substring(0, responseDefault404_description_break));\n                                $(\"#examples-Default-undeleteBlock-description-404\").html(responseDefault404_description.substring(responseDefault404_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-undeleteBlock-404\" class=\"nav nav-tabs nav-tabs-examples\" >\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-undeleteBlock-404-wrapper\" style='margin-bottom: 10px;'>\n                            </div>\n                            <h3 id=\"examples-Default-undeleteBlock-title-default\"></h3>\n                            <p id=\"examples-Default-undeleteBlock-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-undeleteBlock-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-undeleteBlock-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-undeleteBlock-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-undeleteBlock-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-undeleteBlock-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-undeleteBlock-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-undeleteBlock-default-schema\">\n                                  <div id=\"responses-Default-undeleteBlock-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-undeleteBlock-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-undeleteBlock-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-undeleteBlock-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-undeleteBoard\">\n                      <article id=\"api-Default-undeleteBoard-0\" data-group=\"User\" data-name=\"undeleteBoard\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>undeleteBoard</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Undeletes a board</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"post\"><code><span class=\"pln\">/boards/{boardID}/undelete</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-undeleteBoard-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-undeleteBoard-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-undeleteBoard-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-undeleteBoard-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-undeleteBoard-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-undeleteBoard-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-undeleteBoard-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-undeleteBoard-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-undeleteBoard-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-undeleteBoard-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-undeleteBoard-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-undeleteBoard-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-undeleteBoard-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X POST \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n \"http://localhost/api/v2/boards/{boardID}/undelete\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-undeleteBoard-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String boardID = boardID_example; // String | ID of board to undelete\n\n        try {\n            apiInstance.undeleteBoard(boardID);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#undeleteBoard\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-undeleteBoard-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String boardID = boardID_example; // String | ID of board to undelete\n\n        try {\n            apiInstance.undeleteBoard(boardID);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#undeleteBoard\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-undeleteBoard-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-undeleteBoard-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *boardID = boardID_example; // ID of board to undelete (default to null)\n\n[apiInstance undeleteBoardWith:boardID\n              completionHandler: ^(NSError* error) {\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-undeleteBoard-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar boardID = boardID_example; // {String} ID of board to undelete\n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully.');\n  }\n};\napi.undeleteBoard(boardID, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-undeleteBoard-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-undeleteBoard-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class undeleteBoardExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var boardID = boardID_example;  // String | ID of board to undelete (default to null)\n\n            try {\n                apiInstance.undeleteBoard(boardID);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.undeleteBoard: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-undeleteBoard-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$boardID = boardID_example; // String | ID of board to undelete\n\ntry {\n    $api_instance->undeleteBoard($boardID);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->undeleteBoard: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-undeleteBoard-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $boardID = boardID_example; # String | ID of board to undelete\n\neval {\n    $api_instance->undeleteBoard(boardID => $boardID);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->undeleteBoard: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-undeleteBoard-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nboardID = boardID_example # String | ID of board to undelete (default to null)\n\ntry:\n    api_instance.undelete_board(boardID)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->undeleteBoard: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-undeleteBoard-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let boardID = boardID_example; // String\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.undeleteBoard(boardID, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">boardID*</td>\n<td>\n\n\n    <div id=\"d2e199_undeleteBoard_boardID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nID of board to undelete\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-undeleteBoard-title-200\"></h3>\n                            <p id=\"examples-Default-undeleteBoard-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-undeleteBoard-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-undeleteBoard-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-undeleteBoard-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-undeleteBoard-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-undeleteBoard-200-wrapper\" style='margin-bottom: 10px;'>\n                            </div>\n                            <h3 id=\"examples-Default-undeleteBoard-title-default\"></h3>\n                            <p id=\"examples-Default-undeleteBoard-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-undeleteBoard-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-undeleteBoard-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-undeleteBoard-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-undeleteBoard-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-undeleteBoard-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-undeleteBoard-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-undeleteBoard-default-schema\">\n                                  <div id=\"responses-Default-undeleteBoard-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-undeleteBoard-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-undeleteBoard-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-undeleteBoard-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-updateBlocks\">\n                      <article id=\"api-Default-updateBlocks-0\" data-group=\"User\" data-name=\"updateBlocks\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>updateBlocks</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Insert blocks. The specified IDs will only be used to link\nblocks with existing ones, the rest will be replaced by server\ngenerated IDs</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"post\"><code><span class=\"pln\">/boards/{boardID}/blocks</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-updateBlocks-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-updateBlocks-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-updateBlocks-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-updateBlocks-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-updateBlocks-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-updateBlocks-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-updateBlocks-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-updateBlocks-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-updateBlocks-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-updateBlocks-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-updateBlocks-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-updateBlocks-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-updateBlocks-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X POST \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n -H \"Content-Type: application/json\" \\\n \"http://localhost/api/v2/boards/{boardID}/blocks?disable_notify=\" \\\n -d ''\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-updateBlocks-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String boardID = boardID_example; // String | Board ID\n        array[Object] body = ; // array[Object] | \n        oas_any_type_not_mapped disableNotify = ; // oas_any_type_not_mapped | Disables notifications (for bulk inserting)\n\n        try {\n            array[Object] result = apiInstance.updateBlocks(boardID, body, disableNotify);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#updateBlocks\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-updateBlocks-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String boardID = boardID_example; // String | Board ID\n        array[Object] body = ; // array[Object] | \n        oas_any_type_not_mapped disableNotify = ; // oas_any_type_not_mapped | Disables notifications (for bulk inserting)\n\n        try {\n            array[Object] result = apiInstance.updateBlocks(boardID, body, disableNotify);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#updateBlocks\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-updateBlocks-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-updateBlocks-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *boardID = boardID_example; // Board ID (default to null)\narray[Object] *body = ; // \noas_any_type_not_mapped *disableNotify = ; // Disables notifications (for bulk inserting) (optional) (default to null)\n\n[apiInstance updateBlocksWith:boardID\n    body:body\n    disableNotify:disableNotify\n              completionHandler: ^(array[Object] output, NSError* error) {\n    if (output) {\n        NSLog(@\"%@\", output);\n    }\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-updateBlocks-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar boardID = boardID_example; // {String} Board ID\nvar body = ; // {array[Object]} \nvar opts = {\n  'disableNotify':  // {oas_any_type_not_mapped} Disables notifications (for bulk inserting)\n};\n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully. Returned data: ' + data);\n  }\n};\napi.updateBlocks(boardID, body, opts, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-updateBlocks-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-updateBlocks-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class updateBlocksExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var boardID = boardID_example;  // String | Board ID (default to null)\n            var body = new array[Object](); // array[Object] | \n            var disableNotify = new oas_any_type_not_mapped(); // oas_any_type_not_mapped | Disables notifications (for bulk inserting) (optional)  (default to null)\n\n            try {\n                array[Object] result = apiInstance.updateBlocks(boardID, body, disableNotify);\n                Debug.WriteLine(result);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.updateBlocks: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-updateBlocks-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$boardID = boardID_example; // String | Board ID\n$body = ; // array[Object] | \n$disableNotify = ; // oas_any_type_not_mapped | Disables notifications (for bulk inserting)\n\ntry {\n    $result = $api_instance->updateBlocks($boardID, $body, $disableNotify);\n    print_r($result);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->updateBlocks: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-updateBlocks-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $boardID = boardID_example; # String | Board ID\nmy $body = [WWW::OPenAPIClient::Object::array[Object]->new()]; # array[Object] | \nmy $disableNotify = ; # oas_any_type_not_mapped | Disables notifications (for bulk inserting)\n\neval {\n    my $result = $api_instance->updateBlocks(boardID => $boardID, body => $body, disableNotify => $disableNotify);\n    print Dumper($result);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->updateBlocks: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-updateBlocks-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nboardID = boardID_example # String | Board ID (default to null)\nbody =  # array[Object] | \ndisableNotify =  # oas_any_type_not_mapped | Disables notifications (for bulk inserting) (optional) (default to null)\n\ntry:\n    api_response = api_instance.update_blocks(boardID, body, disableNotify=disableNotify)\n    pprint(api_response)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->updateBlocks: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-updateBlocks-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let boardID = boardID_example; // String\n    let body = ; // array[Object]\n    let disableNotify = ; // oas_any_type_not_mapped\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.updateBlocks(boardID, body, disableNotify, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">boardID*</td>\n<td>\n\n\n    <div id=\"d2e199_updateBlocks_boardID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nBoard ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n                            <div class=\"methodsubtabletitle\">Body parameters</div>\n                            <table id=\"methodsubtable\">\n                              <tr>\n                                <th width=\"150px\">Name</th>\n                                <th>Description</th>\n                              </tr>\n                                <tr><td style=\"width:150px;\">body <span style=\"color:red;\">*</span></td>\n<td>\n<p class=\"marked\">array of blocks to insert or update</p>\n<script>\n$(document).ready(function() {\n  var schemaWrapper = {\n  \"description\" : \"array of blocks to insert or update\",\n  \"content\" : {\n    \"application/json\" : {\n      \"schema\" : {\n        \"type\" : \"array\",\n        \"items\" : {\n          \"$ref\" : \"#/components/schemas/Block\"\n        }\n      }\n    }\n  },\n  \"required\" : true\n};\n\n  var schema = findNode('schema',schemaWrapper).schema;\n  if (!schema) {\n    schema = schemaWrapper.schema;\n  }\n  if (schema.$ref != null) {\n    schema = defsParser.$refs.get(schema.$ref);\n  } else {\n    schemaWrapper.definitions = Object.assign({}, defs);\n    $RefParser.dereference(schemaWrapper).catch(function(err) {\n      console.log(err);\n    });\n  }\n\n  var view = new JSONSchemaView(schema,2,{isBodyParam: true});\n  var result = $('#d2e199_updateBlocks_body');\n  result.empty();\n  result.append(view.render());\n});\n</script>\n<div id=\"d2e199_updateBlocks_body\"></div>\n</td>\n</tr>\n\n                            </table>\n\n\n                            <div class=\"methodsubtabletitle\">Query parameters</div>\n                            <table id=\"methodsubtable\">\n                              <tr>\n                                <th width=\"150px\">Name</th>\n                                <th>Description</th>\n                              </tr>\n                                <tr><td style=\"width:150px;\">disable_notify</td>\n<td>\n\n\n    <div id=\"d2e199_updateBlocks_disableNotify\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    oas_any_type_not_mapped\n                </span>\n\n                    <div class=\"inner description marked\">\nDisables notifications (for bulk inserting)\n                    </div>\n            </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-updateBlocks-title-200\"></h3>\n                            <p id=\"examples-Default-updateBlocks-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-updateBlocks-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-updateBlocks-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-updateBlocks-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-updateBlocks-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-updateBlocks-200-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-updateBlocks-200-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-updateBlocks-200-schema\">\n                                  <div id=\"responses-Default-updateBlocks-schema-200\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = ;\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-updateBlocks-200-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-updateBlocks-schema-200');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-updateBlocks-200-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-updateBlocks-title-default\"></h3>\n                            <p id=\"examples-Default-updateBlocks-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-updateBlocks-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-updateBlocks-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-updateBlocks-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-updateBlocks-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-updateBlocks-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-updateBlocks-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-updateBlocks-default-schema\">\n                                  <div id=\"responses-Default-updateBlocks-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-updateBlocks-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-updateBlocks-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-updateBlocks-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-updateCategory\">\n                      <article id=\"api-Default-updateCategory-0\" data-group=\"User\" data-name=\"updateCategory\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>updateCategory</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Create a category for boards</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"put\"><code><span class=\"pln\">/teams/{teamID}/categories/{categoryID}</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-updateCategory-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-updateCategory-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-updateCategory-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-updateCategory-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-updateCategory-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-updateCategory-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-updateCategory-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-updateCategory-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-updateCategory-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-updateCategory-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-updateCategory-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-updateCategory-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-updateCategory-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X PUT \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n -H \"Content-Type: application/json\" \\\n \"http://localhost/api/v2/teams/{teamID}/categories/{categoryID}\" \\\n -d ''\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-updateCategory-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String teamID = teamID_example; // String | Team ID\n        String categoryID = categoryID_example; // String | Category ID\n        Object body = Object; // Object | \n\n        try {\n            Object result = apiInstance.updateCategory(teamID, categoryID, body);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#updateCategory\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-updateCategory-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String teamID = teamID_example; // String | Team ID\n        String categoryID = categoryID_example; // String | Category ID\n        Object body = Object; // Object | \n\n        try {\n            Object result = apiInstance.updateCategory(teamID, categoryID, body);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#updateCategory\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-updateCategory-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-updateCategory-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *teamID = teamID_example; // Team ID (default to null)\nString *categoryID = categoryID_example; // Category ID (default to null)\nObject *body = Object; // \n\n[apiInstance updateCategoryWith:teamID\n    categoryID:categoryID\n    body:body\n              completionHandler: ^(Object output, NSError* error) {\n    if (output) {\n        NSLog(@\"%@\", output);\n    }\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-updateCategory-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar teamID = teamID_example; // {String} Team ID\nvar categoryID = categoryID_example; // {String} Category ID\nvar body = Object; // {Object} \n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully. Returned data: ' + data);\n  }\n};\napi.updateCategory(teamID, categoryID, body, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-updateCategory-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-updateCategory-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class updateCategoryExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var teamID = teamID_example;  // String | Team ID (default to null)\n            var categoryID = categoryID_example;  // String | Category ID (default to null)\n            var body = Object;  // Object | \n\n            try {\n                Object result = apiInstance.updateCategory(teamID, categoryID, body);\n                Debug.WriteLine(result);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.updateCategory: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-updateCategory-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$teamID = teamID_example; // String | Team ID\n$categoryID = categoryID_example; // String | Category ID\n$body = Object; // Object | \n\ntry {\n    $result = $api_instance->updateCategory($teamID, $categoryID, $body);\n    print_r($result);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->updateCategory: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-updateCategory-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $teamID = teamID_example; # String | Team ID\nmy $categoryID = categoryID_example; # String | Category ID\nmy $body = WWW::OPenAPIClient::Object::Object->new(); # Object | \n\neval {\n    my $result = $api_instance->updateCategory(teamID => $teamID, categoryID => $categoryID, body => $body);\n    print Dumper($result);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->updateCategory: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-updateCategory-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nteamID = teamID_example # String | Team ID (default to null)\ncategoryID = categoryID_example # String | Category ID (default to null)\nbody = Object # Object | \n\ntry:\n    api_response = api_instance.update_category(teamID, categoryID, body)\n    pprint(api_response)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->updateCategory: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-updateCategory-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let teamID = teamID_example; // String\n    let categoryID = categoryID_example; // String\n    let body = Object; // Object\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.updateCategory(teamID, categoryID, body, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">teamID*</td>\n<td>\n\n\n    <div id=\"d2e199_updateCategory_teamID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nTeam ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                                  <tr><td style=\"width:150px;\">categoryID*</td>\n<td>\n\n\n    <div id=\"d2e199_updateCategory_categoryID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nCategory ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n                            <div class=\"methodsubtabletitle\">Body parameters</div>\n                            <table id=\"methodsubtable\">\n                              <tr>\n                                <th width=\"150px\">Name</th>\n                                <th>Description</th>\n                              </tr>\n                                <tr><td style=\"width:150px;\">body <span style=\"color:red;\">*</span></td>\n<td>\n<p class=\"marked\">category to update</p>\n<script>\n$(document).ready(function() {\n  var schemaWrapper = {\n  \"description\" : \"category to update\",\n  \"content\" : {\n    \"application/json\" : {\n      \"schema\" : {\n        \"$ref\" : \"#/components/schemas/Category\"\n      }\n    }\n  },\n  \"required\" : true\n};\n\n  var schema = findNode('schema',schemaWrapper).schema;\n  if (!schema) {\n    schema = schemaWrapper.schema;\n  }\n  if (schema.$ref != null) {\n    schema = defsParser.$refs.get(schema.$ref);\n  } else {\n    schemaWrapper.definitions = Object.assign({}, defs);\n    $RefParser.dereference(schemaWrapper).catch(function(err) {\n      console.log(err);\n    });\n  }\n\n  var view = new JSONSchemaView(schema,2,{isBodyParam: true});\n  var result = $('#d2e199_updateCategory_body');\n  result.empty();\n  result.append(view.render());\n});\n</script>\n<div id=\"d2e199_updateCategory_body\"></div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-updateCategory-title-200\"></h3>\n                            <p id=\"examples-Default-updateCategory-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-updateCategory-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-updateCategory-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-updateCategory-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-updateCategory-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-updateCategory-200-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-updateCategory-200-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-updateCategory-200-schema\">\n                                  <div id=\"responses-Default-updateCategory-schema-200\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=Category is a board category, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-updateCategory-200-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-updateCategory-schema-200');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-updateCategory-200-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-updateCategory-title-default\"></h3>\n                            <p id=\"examples-Default-updateCategory-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-updateCategory-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-updateCategory-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-updateCategory-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-updateCategory-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-updateCategory-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-updateCategory-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-updateCategory-default-schema\">\n                                  <div id=\"responses-Default-updateCategory-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-updateCategory-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-updateCategory-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-updateCategory-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-updateCategoryBoard\">\n                      <article id=\"api-Default-updateCategoryBoard-0\" data-group=\"User\" data-name=\"updateCategoryBoard\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>updateCategoryBoard</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Set the category of a board</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"post\"><code><span class=\"pln\">/teams/{teamID}/categories/{categoryID}/boards/{boardID}</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-updateCategoryBoard-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-updateCategoryBoard-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-updateCategoryBoard-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-updateCategoryBoard-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-updateCategoryBoard-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-updateCategoryBoard-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-updateCategoryBoard-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-updateCategoryBoard-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-updateCategoryBoard-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-updateCategoryBoard-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-updateCategoryBoard-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-updateCategoryBoard-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-updateCategoryBoard-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X POST \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n \"http://localhost/api/v2/teams/{teamID}/categories/{categoryID}/boards/{boardID}\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-updateCategoryBoard-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String teamID = teamID_example; // String | Team ID\n        String categoryID = categoryID_example; // String | Category ID\n        String boardID = boardID_example; // String | Board ID\n\n        try {\n            apiInstance.updateCategoryBoard(teamID, categoryID, boardID);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#updateCategoryBoard\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-updateCategoryBoard-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String teamID = teamID_example; // String | Team ID\n        String categoryID = categoryID_example; // String | Category ID\n        String boardID = boardID_example; // String | Board ID\n\n        try {\n            apiInstance.updateCategoryBoard(teamID, categoryID, boardID);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#updateCategoryBoard\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-updateCategoryBoard-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-updateCategoryBoard-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *teamID = teamID_example; // Team ID (default to null)\nString *categoryID = categoryID_example; // Category ID (default to null)\nString *boardID = boardID_example; // Board ID (default to null)\n\n[apiInstance updateCategoryBoardWith:teamID\n    categoryID:categoryID\n    boardID:boardID\n              completionHandler: ^(NSError* error) {\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-updateCategoryBoard-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar teamID = teamID_example; // {String} Team ID\nvar categoryID = categoryID_example; // {String} Category ID\nvar boardID = boardID_example; // {String} Board ID\n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully.');\n  }\n};\napi.updateCategoryBoard(teamID, categoryID, boardID, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-updateCategoryBoard-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-updateCategoryBoard-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class updateCategoryBoardExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var teamID = teamID_example;  // String | Team ID (default to null)\n            var categoryID = categoryID_example;  // String | Category ID (default to null)\n            var boardID = boardID_example;  // String | Board ID (default to null)\n\n            try {\n                apiInstance.updateCategoryBoard(teamID, categoryID, boardID);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.updateCategoryBoard: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-updateCategoryBoard-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$teamID = teamID_example; // String | Team ID\n$categoryID = categoryID_example; // String | Category ID\n$boardID = boardID_example; // String | Board ID\n\ntry {\n    $api_instance->updateCategoryBoard($teamID, $categoryID, $boardID);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->updateCategoryBoard: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-updateCategoryBoard-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $teamID = teamID_example; # String | Team ID\nmy $categoryID = categoryID_example; # String | Category ID\nmy $boardID = boardID_example; # String | Board ID\n\neval {\n    $api_instance->updateCategoryBoard(teamID => $teamID, categoryID => $categoryID, boardID => $boardID);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->updateCategoryBoard: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-updateCategoryBoard-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nteamID = teamID_example # String | Team ID (default to null)\ncategoryID = categoryID_example # String | Category ID (default to null)\nboardID = boardID_example # String | Board ID (default to null)\n\ntry:\n    api_instance.update_category_board(teamID, categoryID, boardID)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->updateCategoryBoard: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-updateCategoryBoard-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let teamID = teamID_example; // String\n    let categoryID = categoryID_example; // String\n    let boardID = boardID_example; // String\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.updateCategoryBoard(teamID, categoryID, boardID, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">teamID*</td>\n<td>\n\n\n    <div id=\"d2e199_updateCategoryBoard_teamID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nTeam ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                                  <tr><td style=\"width:150px;\">categoryID*</td>\n<td>\n\n\n    <div id=\"d2e199_updateCategoryBoard_categoryID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nCategory ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                                  <tr><td style=\"width:150px;\">boardID*</td>\n<td>\n\n\n    <div id=\"d2e199_updateCategoryBoard_boardID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nBoard ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-updateCategoryBoard-title-200\"></h3>\n                            <p id=\"examples-Default-updateCategoryBoard-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-updateCategoryBoard-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-updateCategoryBoard-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-updateCategoryBoard-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-updateCategoryBoard-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-updateCategoryBoard-200-wrapper\" style='margin-bottom: 10px;'>\n                            </div>\n                            <h3 id=\"examples-Default-updateCategoryBoard-title-default\"></h3>\n                            <p id=\"examples-Default-updateCategoryBoard-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-updateCategoryBoard-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-updateCategoryBoard-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-updateCategoryBoard-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-updateCategoryBoard-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-updateCategoryBoard-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-updateCategoryBoard-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-updateCategoryBoard-default-schema\">\n                                  <div id=\"responses-Default-updateCategoryBoard-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-updateCategoryBoard-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-updateCategoryBoard-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-updateCategoryBoard-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-updateMember\">\n                      <article id=\"api-Default-updateMember-0\" data-group=\"User\" data-name=\"updateMember\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>updateMember</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Updates a board member</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"put\"><code><span class=\"pln\">/boards/{boardID}/members/{userID}</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-updateMember-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-updateMember-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-updateMember-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-updateMember-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-updateMember-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-updateMember-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-updateMember-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-updateMember-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-updateMember-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-updateMember-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-updateMember-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-updateMember-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-updateMember-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X PUT \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n -H \"Content-Type: application/json\" \\\n \"http://localhost/api/v2/boards/{boardID}/members/{userID}\" \\\n -d ''\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-updateMember-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String boardID = boardID_example; // String | Board ID\n        String userID = userID_example; // String | User ID\n        Object body = Object; // Object | \n\n        try {\n            Object result = apiInstance.updateMember(boardID, userID, body);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#updateMember\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-updateMember-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String boardID = boardID_example; // String | Board ID\n        String userID = userID_example; // String | User ID\n        Object body = Object; // Object | \n\n        try {\n            Object result = apiInstance.updateMember(boardID, userID, body);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#updateMember\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-updateMember-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-updateMember-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *boardID = boardID_example; // Board ID (default to null)\nString *userID = userID_example; // User ID (default to null)\nObject *body = Object; // \n\n[apiInstance updateMemberWith:boardID\n    userID:userID\n    body:body\n              completionHandler: ^(Object output, NSError* error) {\n    if (output) {\n        NSLog(@\"%@\", output);\n    }\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-updateMember-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar boardID = boardID_example; // {String} Board ID\nvar userID = userID_example; // {String} User ID\nvar body = Object; // {Object} \n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully. Returned data: ' + data);\n  }\n};\napi.updateMember(boardID, userID, body, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-updateMember-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-updateMember-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class updateMemberExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var boardID = boardID_example;  // String | Board ID (default to null)\n            var userID = userID_example;  // String | User ID (default to null)\n            var body = Object;  // Object | \n\n            try {\n                Object result = apiInstance.updateMember(boardID, userID, body);\n                Debug.WriteLine(result);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.updateMember: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-updateMember-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$boardID = boardID_example; // String | Board ID\n$userID = userID_example; // String | User ID\n$body = Object; // Object | \n\ntry {\n    $result = $api_instance->updateMember($boardID, $userID, $body);\n    print_r($result);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->updateMember: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-updateMember-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $boardID = boardID_example; # String | Board ID\nmy $userID = userID_example; # String | User ID\nmy $body = WWW::OPenAPIClient::Object::Object->new(); # Object | \n\neval {\n    my $result = $api_instance->updateMember(boardID => $boardID, userID => $userID, body => $body);\n    print Dumper($result);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->updateMember: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-updateMember-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nboardID = boardID_example # String | Board ID (default to null)\nuserID = userID_example # String | User ID (default to null)\nbody = Object # Object | \n\ntry:\n    api_response = api_instance.update_member(boardID, userID, body)\n    pprint(api_response)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->updateMember: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-updateMember-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let boardID = boardID_example; // String\n    let userID = userID_example; // String\n    let body = Object; // Object\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.updateMember(boardID, userID, body, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">boardID*</td>\n<td>\n\n\n    <div id=\"d2e199_updateMember_boardID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nBoard ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                                  <tr><td style=\"width:150px;\">userID*</td>\n<td>\n\n\n    <div id=\"d2e199_updateMember_userID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nUser ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n                            <div class=\"methodsubtabletitle\">Body parameters</div>\n                            <table id=\"methodsubtable\">\n                              <tr>\n                                <th width=\"150px\">Name</th>\n                                <th>Description</th>\n                              </tr>\n                                <tr><td style=\"width:150px;\">body <span style=\"color:red;\">*</span></td>\n<td>\n<p class=\"marked\">membership to replace the current one with</p>\n<script>\n$(document).ready(function() {\n  var schemaWrapper = {\n  \"description\" : \"membership to replace the current one with\",\n  \"content\" : {\n    \"application/json\" : {\n      \"schema\" : {\n        \"$ref\" : \"#/components/schemas/BoardMember\"\n      }\n    }\n  },\n  \"required\" : true\n};\n\n  var schema = findNode('schema',schemaWrapper).schema;\n  if (!schema) {\n    schema = schemaWrapper.schema;\n  }\n  if (schema.$ref != null) {\n    schema = defsParser.$refs.get(schema.$ref);\n  } else {\n    schemaWrapper.definitions = Object.assign({}, defs);\n    $RefParser.dereference(schemaWrapper).catch(function(err) {\n      console.log(err);\n    });\n  }\n\n  var view = new JSONSchemaView(schema,2,{isBodyParam: true});\n  var result = $('#d2e199_updateMember_body');\n  result.empty();\n  result.append(view.render());\n});\n</script>\n<div id=\"d2e199_updateMember_body\"></div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-updateMember-title-200\"></h3>\n                            <p id=\"examples-Default-updateMember-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-updateMember-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-updateMember-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-updateMember-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-updateMember-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-updateMember-200-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-updateMember-200-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-updateMember-200-schema\">\n                                  <div id=\"responses-Default-updateMember-schema-200\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=BoardMember stores the information of the membership of a user on a board, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-updateMember-200-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-updateMember-schema-200');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-updateMember-200-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-updateMember-title-default\"></h3>\n                            <p id=\"examples-Default-updateMember-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-updateMember-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-updateMember-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-updateMember-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-updateMember-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-updateMember-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-updateMember-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-updateMember-default-schema\">\n                                  <div id=\"responses-Default-updateMember-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-updateMember-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-updateMember-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-updateMember-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-updateUserConfig\">\n                      <article id=\"api-Default-updateUserConfig-0\" data-group=\"User\" data-name=\"updateUserConfig\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>updateUserConfig</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Updates user config</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"patch\"><code><span class=\"pln\">/users/{userID}/config</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-updateUserConfig-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-updateUserConfig-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-updateUserConfig-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-updateUserConfig-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-updateUserConfig-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-updateUserConfig-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-updateUserConfig-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-updateUserConfig-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-updateUserConfig-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-updateUserConfig-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-updateUserConfig-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-updateUserConfig-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-updateUserConfig-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X PATCH \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n -H \"Content-Type: application/json\" \\\n \"http://localhost/api/v2/users/{userID}/config\" \\\n -d ''\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-updateUserConfig-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String userID = userID_example; // String | User ID\n        Object body = Object; // Object | \n\n        try {\n            apiInstance.updateUserConfig(userID, body);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#updateUserConfig\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-updateUserConfig-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String userID = userID_example; // String | User ID\n        Object body = Object; // Object | \n\n        try {\n            apiInstance.updateUserConfig(userID, body);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#updateUserConfig\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-updateUserConfig-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-updateUserConfig-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *userID = userID_example; // User ID (default to null)\nObject *body = Object; // \n\n[apiInstance updateUserConfigWith:userID\n    body:body\n              completionHandler: ^(NSError* error) {\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-updateUserConfig-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar userID = userID_example; // {String} User ID\nvar body = Object; // {Object} \n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully.');\n  }\n};\napi.updateUserConfig(userID, body, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-updateUserConfig-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-updateUserConfig-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class updateUserConfigExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var userID = userID_example;  // String | User ID (default to null)\n            var body = Object;  // Object | \n\n            try {\n                apiInstance.updateUserConfig(userID, body);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.updateUserConfig: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-updateUserConfig-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$userID = userID_example; // String | User ID\n$body = Object; // Object | \n\ntry {\n    $api_instance->updateUserConfig($userID, $body);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->updateUserConfig: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-updateUserConfig-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $userID = userID_example; # String | User ID\nmy $body = WWW::OPenAPIClient::Object::Object->new(); # Object | \n\neval {\n    $api_instance->updateUserConfig(userID => $userID, body => $body);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->updateUserConfig: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-updateUserConfig-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nuserID = userID_example # String | User ID (default to null)\nbody = Object # Object | \n\ntry:\n    api_instance.update_user_config(userID, body)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->updateUserConfig: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-updateUserConfig-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let userID = userID_example; // String\n    let body = Object; // Object\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.updateUserConfig(userID, body, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">userID*</td>\n<td>\n\n\n    <div id=\"d2e199_updateUserConfig_userID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nUser ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n                            <div class=\"methodsubtabletitle\">Body parameters</div>\n                            <table id=\"methodsubtable\">\n                              <tr>\n                                <th width=\"150px\">Name</th>\n                                <th>Description</th>\n                              </tr>\n                                <tr><td style=\"width:150px;\">body <span style=\"color:red;\">*</span></td>\n<td>\n<p class=\"marked\">User config patch to apply</p>\n<script>\n$(document).ready(function() {\n  var schemaWrapper = {\n  \"description\" : \"User config patch to apply\",\n  \"content\" : {\n    \"application/json\" : {\n      \"schema\" : {\n        \"$ref\" : \"#/components/schemas/UserPreferencesPatch\"\n      }\n    }\n  },\n  \"required\" : true\n};\n\n  var schema = findNode('schema',schemaWrapper).schema;\n  if (!schema) {\n    schema = schemaWrapper.schema;\n  }\n  if (schema.$ref != null) {\n    schema = defsParser.$refs.get(schema.$ref);\n  } else {\n    schemaWrapper.definitions = Object.assign({}, defs);\n    $RefParser.dereference(schemaWrapper).catch(function(err) {\n      console.log(err);\n    });\n  }\n\n  var view = new JSONSchemaView(schema,2,{isBodyParam: true});\n  var result = $('#d2e199_updateUserConfig_body');\n  result.empty();\n  result.append(view.render());\n});\n</script>\n<div id=\"d2e199_updateUserConfig_body\"></div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-updateUserConfig-title-200\"></h3>\n                            <p id=\"examples-Default-updateUserConfig-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-updateUserConfig-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-updateUserConfig-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-updateUserConfig-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-updateUserConfig-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-updateUserConfig-200-wrapper\" style='margin-bottom: 10px;'>\n                            </div>\n                            <h3 id=\"examples-Default-updateUserConfig-title-default\"></h3>\n                            <p id=\"examples-Default-updateUserConfig-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-updateUserConfig-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-updateUserConfig-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-updateUserConfig-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-updateUserConfig-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-updateUserConfig-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-updateUserConfig-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-updateUserConfig-default-schema\">\n                                  <div id=\"responses-Default-updateUserConfig-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-updateUserConfig-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-updateUserConfig-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-updateUserConfig-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                    <div id=\"api-Default-uploadFile\">\n                      <article id=\"api-Default-uploadFile-0\" data-group=\"User\" data-name=\"uploadFile\" data-version=\"0\">\n                        <div class=\"pull-left\">\n                          <h1>uploadFile</h1>\n                          <p></p>\n                        </div>\n                        <div class=\"pull-right\"></div>\n                        <div class=\"clearfix\"></div>\n                        <p></p>\n                        <p class=\"marked\">Upload a binary file, attached to a root block</p>\n                        <p></p>\n                        <br />\n                        <pre class=\"prettyprint language-html prettyprinted\" data-type=\"post\"><code><span class=\"pln\">/teams/{teamID}/boards/{boardID}/files</span></code></pre>\n                        <p>\n                          <h3>Usage and SDK Samples</h3>\n                        </p>\n                        <ul class=\"nav nav-tabs nav-tabs-examples\">\n                          <li class=\"active\"><a href=\"#examples-Default-uploadFile-0-curl\">Curl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-uploadFile-0-java\">Java</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-uploadFile-0-android\">Android</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-uploadFile-0-groovy\">Groovy</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-uploadFile-0-objc\">Obj-C</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-uploadFile-0-javascript\">JavaScript</a></li>\n                          <!--<li class=\"\"><a href=\"#examples-Default-uploadFile-0-angular\">Angular</a></li>-->\n                          <li class=\"\"><a href=\"#examples-Default-uploadFile-0-csharp\">C#</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-uploadFile-0-php\">PHP</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-uploadFile-0-perl\">Perl</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-uploadFile-0-python\">Python</a></li>\n                          <li class=\"\"><a href=\"#examples-Default-uploadFile-0-rust\">Rust</a></li>\n                        </ul>\n\n                        <div class=\"tab-content\">\n                          <div class=\"tab-pane active\" id=\"examples-Default-uploadFile-0-curl\">\n                            <pre class=\"prettyprint\"><code class=\"language-bsh\">curl -X POST \\\n-H \"Authorization: [[apiKey]]\" \\\n -H \"Accept: application/json\" \\\n -H \"Content-Type: multipart/form-data\" \\\n \"http://localhost/api/v2/teams/{teamID}/boards/{boardID}/files\"\n</code></pre>\n                          </div>\n                          <div class=\"tab-pane\" id=\"examples-Default-uploadFile-0-java\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.*;\nimport org.openapitools.client.auth.*;\nimport org.openapitools.client.model.*;\nimport org.openapitools.client.api.DefaultApi;\n\nimport java.io.File;\nimport java.util.*;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        ApiClient defaultClient = Configuration.getDefaultApiClient();\n        \n        // Configure API key authorization: BearerAuth\n        ApiKeyAuth BearerAuth = (ApiKeyAuth) defaultClient.getAuthentication(\"BearerAuth\");\n        BearerAuth.setApiKey(\"YOUR API KEY\");\n        // Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n        //BearerAuth.setApiKeyPrefix(\"Token\");\n\n        // Create an instance of the API class\n        DefaultApi apiInstance = new DefaultApi();\n        String teamID = teamID_example; // String | ID of the team\n        String boardID = boardID_example; // String | Board ID\n        File uploaded file = BINARY_DATA_HERE; // File | The file to upload\n\n        try {\n            Object result = apiInstance.uploadFile(teamID, boardID, uploaded file);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#uploadFile\");\n            e.printStackTrace();\n        }\n    }\n}\n</code></pre>\n                          </div>\n\n                          <div class=\"tab-pane\" id=\"examples-Default-uploadFile-0-android\">\n                            <pre class=\"prettyprint\"><code class=\"language-java\">import org.openapitools.client.api.DefaultApi;\n\npublic class DefaultApiExample {\n    public static void main(String[] args) {\n        DefaultApi apiInstance = new DefaultApi();\n        String teamID = teamID_example; // String | ID of the team\n        String boardID = boardID_example; // String | Board ID\n        File uploaded file = BINARY_DATA_HERE; // File | The file to upload\n\n        try {\n            Object result = apiInstance.uploadFile(teamID, boardID, uploaded file);\n            System.out.println(result);\n        } catch (ApiException e) {\n            System.err.println(\"Exception when calling DefaultApi#uploadFile\");\n            e.printStackTrace();\n        }\n    }\n}</code></pre>\n                          </div>\n  <!--\n  <div class=\"tab-pane\" id=\"examples-Default-uploadFile-0-groovy\">\n  <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n  </div> -->\n                            <div class=\"tab-pane\" id=\"examples-Default-uploadFile-0-objc\">\n                              <pre class=\"prettyprint\"><code class=\"language-cpp\">Configuration *apiConfig = [Configuration sharedConfig];\n\n// Configure API key authorization: (authentication scheme: BearerAuth)\n[apiConfig setApiKey:@\"YOUR_API_KEY\" forApiKeyIdentifier:@\"Authorization\"];\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n//[apiConfig setApiKeyPrefix:@\"Bearer\" forApiKeyIdentifier:@\"Authorization\"];\n\n\n// Create an instance of the API class\nDefaultApi *apiInstance = [[DefaultApi alloc] init];\nString *teamID = teamID_example; // ID of the team (default to null)\nString *boardID = boardID_example; // Board ID (default to null)\nFile *uploaded file = BINARY_DATA_HERE; // The file to upload (optional) (default to null)\n\n[apiInstance uploadFileWith:teamID\n    boardID:boardID\n    uploaded file:uploaded file\n              completionHandler: ^(Object output, NSError* error) {\n    if (output) {\n        NSLog(@\"%@\", output);\n    }\n    if (error) {\n        NSLog(@\"Error: %@\", error);\n    }\n}];\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-uploadFile-0-javascript\">\n                              <pre class=\"prettyprint\"><code class=\"language-js\">var FocalboardServer = require('focalboard_server');\nvar defaultClient = FocalboardServer.ApiClient.instance;\n\n// Configure API key authorization: BearerAuth\nvar BearerAuth = defaultClient.authentications['BearerAuth'];\nBearerAuth.apiKey = \"YOUR API KEY\";\n// Uncomment the following line to set a prefix for the API key, e.g. \"Token\" (defaults to null)\n//BearerAuth.apiKeyPrefix['Authorization'] = \"Token\";\n\n// Create an instance of the API class\nvar api = new FocalboardServer.DefaultApi()\nvar teamID = teamID_example; // {String} ID of the team\nvar boardID = boardID_example; // {String} Board ID\nvar opts = {\n  'uploaded file': BINARY_DATA_HERE // {File} The file to upload\n};\n\nvar callback = function(error, data, response) {\n  if (error) {\n    console.error(error);\n  } else {\n    console.log('API called successfully. Returned data: ' + data);\n  }\n};\napi.uploadFile(teamID, boardID, opts, callback);\n</code></pre>\n                            </div>\n\n                            <!--<div class=\"tab-pane\" id=\"examples-Default-uploadFile-0-angular\">\n              <pre class=\"prettyprint language-json prettyprinted\" data-type=\"json\"><code>Coming Soon!</code></pre>\n            </div>-->\n                            <div class=\"tab-pane\" id=\"examples-Default-uploadFile-0-csharp\">\n                              <pre class=\"prettyprint\"><code class=\"language-cs\">using System;\nusing System.Diagnostics;\nusing Org.OpenAPITools.Api;\nusing Org.OpenAPITools.Client;\nusing Org.OpenAPITools.Model;\n\nnamespace Example\n{\n    public class uploadFileExample\n    {\n        public void main()\n        {\n            // Configure API key authorization: BearerAuth\n            Configuration.Default.ApiKey.Add(\"Authorization\", \"YOUR_API_KEY\");\n            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n            // Configuration.Default.ApiKeyPrefix.Add(\"Authorization\", \"Bearer\");\n\n            // Create an instance of the API class\n            var apiInstance = new DefaultApi();\n            var teamID = teamID_example;  // String | ID of the team (default to null)\n            var boardID = boardID_example;  // String | Board ID (default to null)\n            var uploaded file = BINARY_DATA_HERE;  // File | The file to upload (optional)  (default to null)\n\n            try {\n                Object result = apiInstance.uploadFile(teamID, boardID, uploaded file);\n                Debug.WriteLine(result);\n            } catch (Exception e) {\n                Debug.Print(\"Exception when calling DefaultApi.uploadFile: \" + e.Message );\n            }\n        }\n    }\n}\n</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-uploadFile-0-php\">\n                              <pre class=\"prettyprint\"><code class=\"language-php\"><&#63;php\nrequire_once(__DIR__ . '/vendor/autoload.php');\n\n// Configure API key authorization: BearerAuth\nOpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKey('Authorization', 'YOUR_API_KEY');\n// Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n// OpenAPITools\\Client\\Configuration::getDefaultConfiguration()->setApiKeyPrefix('Authorization', 'Bearer');\n\n// Create an instance of the API class\n$api_instance = new OpenAPITools\\Client\\Api\\DefaultApi();\n$teamID = teamID_example; // String | ID of the team\n$boardID = boardID_example; // String | Board ID\n$uploaded file = BINARY_DATA_HERE; // File | The file to upload\n\ntry {\n    $result = $api_instance->uploadFile($teamID, $boardID, $uploaded file);\n    print_r($result);\n} catch (Exception $e) {\n    echo 'Exception when calling DefaultApi->uploadFile: ', $e->getMessage(), PHP_EOL;\n}\n?></code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-uploadFile-0-perl\">\n                              <pre class=\"prettyprint\"><code class=\"language-perl\">use Data::Dumper;\nuse WWW::OPenAPIClient::Configuration;\nuse WWW::OPenAPIClient::DefaultApi;\n\n# Configure API key authorization: BearerAuth\n$WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';\n# uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n#$WWW::OPenAPIClient::Configuration::api_key_prefix->{'Authorization'} = \"Bearer\";\n\n# Create an instance of the API class\nmy $api_instance = WWW::OPenAPIClient::DefaultApi->new();\nmy $teamID = teamID_example; # String | ID of the team\nmy $boardID = boardID_example; # String | Board ID\nmy $uploaded file = BINARY_DATA_HERE; # File | The file to upload\n\neval {\n    my $result = $api_instance->uploadFile(teamID => $teamID, boardID => $boardID, uploaded file => $uploaded file);\n    print Dumper($result);\n};\nif ($@) {\n    warn \"Exception when calling DefaultApi->uploadFile: $@\\n\";\n}</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-uploadFile-0-python\">\n                              <pre class=\"prettyprint\"><code class=\"language-python\">from __future__ import print_statement\nimport time\nimport openapi_client\nfrom openapi_client.rest import ApiException\nfrom pprint import pprint\n\n# Configure API key authorization: BearerAuth\nopenapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'\n# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed\n# openapi_client.configuration.api_key_prefix['Authorization'] = 'Bearer'\n\n# Create an instance of the API class\napi_instance = openapi_client.DefaultApi()\nteamID = teamID_example # String | ID of the team (default to null)\nboardID = boardID_example # String | Board ID (default to null)\nuploaded file = BINARY_DATA_HERE # File | The file to upload (optional) (default to null)\n\ntry:\n    api_response = api_instance.upload_file(teamID, boardID, uploaded file=uploaded file)\n    pprint(api_response)\nexcept ApiException as e:\n    print(\"Exception when calling DefaultApi->uploadFile: %s\\n\" % e)</code></pre>\n                            </div>\n\n                            <div class=\"tab-pane\" id=\"examples-Default-uploadFile-0-rust\">\n                              <pre class=\"prettyprint\"><code class=\"language-rust\">extern crate DefaultApi;\n\npub fn main() {\n    let teamID = teamID_example; // String\n    let boardID = boardID_example; // String\n    let uploaded file = BINARY_DATA_HERE; // File\n\n    let mut context = DefaultApi::Context::default();\n    let result = client.uploadFile(teamID, boardID, uploaded file, &context).wait();\n\n    println!(\"{:?}\", result);\n}\n</code></pre>\n                            </div>\n                          </div>\n\n                          <h2>Scopes</h2>\n                          <table>\n                            \n                          </table>\n\n                          <h2>Parameters</h2>\n\n                            <div class=\"methodsubtabletitle\">Path parameters</div>\n                            <table id=\"methodsubtable\">\n                                <tr>\n                                  <th width=\"150px\">Name</th>\n                                  <th>Description</th>\n                                </tr>\n                                  <tr><td style=\"width:150px;\">teamID*</td>\n<td>\n\n\n    <div id=\"d2e199_uploadFile_teamID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nID of the team\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                                  <tr><td style=\"width:150px;\">boardID*</td>\n<td>\n\n\n    <div id=\"d2e199_uploadFile_boardID\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    String\n                </span>\n\n                    <div class=\"inner description marked\">\nBoard ID\n                    </div>\n            </div>\n                <div class=\"inner required\">\n                    Required\n                </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n\n                            <div class=\"methodsubtabletitle\">Form parameters</div>\n                            <table id=\"methodsubtable\">\n                              <tr>\n                                <th width=\"150px\">Name</th>\n                                <th>Description</th>\n                              </tr>\n                                  <tr><td style=\"width:150px;\">uploaded file</td>\n<td>\n\n\n    <div id=\"d2e199_uploadFile_uploaded file\">\n        <div class=\"json-schema-view\">\n            <div class=\"primitive\">\n                <span class=\"type\">\n                    File\n                </span>\n                    <span class=\"format\">\n                        (binary)\n                    </span>\n\n                    <div class=\"inner description marked\">\nThe file to upload\n                    </div>\n            </div>\n        </div>\n    </div>\n</td>\n</tr>\n\n                            </table>\n\n\n                          <h2>Responses</h2>\n                            <h3 id=\"examples-Default-uploadFile-title-200\"></h3>\n                            <p id=\"examples-Default-uploadFile-description-200\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault200_description = `success`;\n                              var responseDefault200_description_break = responseDefault200_description.indexOf('\\n');\n                              if (responseDefault200_description_break == -1) {\n                                $(\"#examples-Default-uploadFile-title-200\").text(\"Status: 200 - \" + responseDefault200_description);\n                              } else {\n                                $(\"#examples-Default-uploadFile-title-200\").text(\"Status: 200 - \" + responseDefault200_description.substring(0, responseDefault200_description_break));\n                                $(\"#examples-Default-uploadFile-description-200\").html(responseDefault200_description.substring(responseDefault200_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-uploadFile-200\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-uploadFile-200-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-uploadFile-200-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-uploadFile-200-schema\">\n                                  <div id=\"responses-Default-uploadFile-schema-200\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=FileUploadResponse is the response to a file upload, x-go-package=github.com/mattermost/focalboard/server/api};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-uploadFile-200-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-uploadFile-schema-200');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-uploadFile-200-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                            <h3 id=\"examples-Default-uploadFile-title-404\"></h3>\n                            <p id=\"examples-Default-uploadFile-description-404\" class=\"marked\"></p>\n                            <script>\n                              var responseDefault404_description = `board not found`;\n                              var responseDefault404_description_break = responseDefault404_description.indexOf('\\n');\n                              if (responseDefault404_description_break == -1) {\n                                $(\"#examples-Default-uploadFile-title-404\").text(\"Status: 404 - \" + responseDefault404_description);\n                              } else {\n                                $(\"#examples-Default-uploadFile-title-404\").text(\"Status: 404 - \" + responseDefault404_description.substring(0, responseDefault404_description_break));\n                                $(\"#examples-Default-uploadFile-description-404\").html(responseDefault404_description.substring(responseDefault404_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-uploadFile-404\" class=\"nav nav-tabs nav-tabs-examples\" >\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-uploadFile-404-wrapper\" style='margin-bottom: 10px;'>\n                            </div>\n                            <h3 id=\"examples-Default-uploadFile-title-default\"></h3>\n                            <p id=\"examples-Default-uploadFile-description-default\" class=\"marked\"></p>\n                            <script>\n                              var responseDefaultdefault_description = `internal error`;\n                              var responseDefaultdefault_description_break = responseDefaultdefault_description.indexOf('\\n');\n                              if (responseDefaultdefault_description_break == -1) {\n                                $(\"#examples-Default-uploadFile-title-default\").text(\"Status: default - \" + responseDefaultdefault_description);\n                              } else {\n                                $(\"#examples-Default-uploadFile-title-default\").text(\"Status: default - \" + responseDefaultdefault_description.substring(0, responseDefaultdefault_description_break));\n                                $(\"#examples-Default-uploadFile-description-default\").html(responseDefaultdefault_description.substring(responseDefaultdefault_description_break));\n                              }\n                            </script>\n\n\n                            <ul id=\"responses-detail-Default-uploadFile-default\" class=\"nav nav-tabs nav-tabs-examples\" >\n                                <li class=\"active\">\n                                  <a data-toggle=\"tab\" href=\"#responses-Default-uploadFile-default-schema\">Schema</a>\n                                </li>\n\n\n\n\n                            </ul>\n\n\n                            <div class=\"tab-content\" id=\"responses-Default-uploadFile-default-wrapper\" style='margin-bottom: 10px;'>\n                                <div class=\"tab-pane active\" id=\"responses-Default-uploadFile-default-schema\">\n                                  <div id=\"responses-Default-uploadFile-schema-default\" class=\"exampleStyle\">\n                                    <script>\n                                      $(document).ready(function() {\n                                        var schemaWrapper = {description=ErrorResponse is an error response, x-go-package=github.com/mattermost/focalboard/server/model};\n                                        var schema = findNode('schema',schemaWrapper).schema;\n                                        if (!schema) {\n                                          schema = schemaWrapper.schema;\n                                        }\n                                        if (schema.$ref != null) {\n                                          schema = defsParser.$refs.get(schema.$ref);\n                                          Object.keys(schema.properties).forEach( (item) => {\n                                            if (schema.properties[item].$ref != null) {\n                                              schema.properties[item] = defsParser.$refs.get(schema.properties[item].$ref);\n                                            }\n                                          });\n                                        } else if (schema.items != null && schema.items.$ref != null) {\n                                            schema.items = defsParser.$refs.get(schema.items.$ref);\n                                        } else {\n                                          schemaWrapper.definitions = Object.assign({}, defs);\n                                          $RefParser.dereference(schemaWrapper).catch(function(err) {\n                                            console.log(err);\n                                          });\n                                        }\n\n                                        var view = new JSONSchemaView(schema, 3);\n                                        $('#responses-Default-uploadFile-default-schema-data').val(JSON.stringify(schema));\n                                        var result = $('#responses-Default-uploadFile-schema-default');\n                                        result.empty();\n                                        result.append(view.render());\n                                      });\n                                    </script>\n                                  </div>\n                                  <input id='responses-Default-uploadFile-default-schema-data' type='hidden' value=''></input>\n                                </div>\n                            </div>\n                        </article>\n                      </div>\n                      <hr>\n                  </section>\n          </div>\n          <div id=\"footer\">\n            <div id=\"api-_footer\">\n              <p>Suggestions, contact, support and error reporting;\n                  <div class=\"app-desc\">Information URL: <a href=\"https://www.focalboard.com\">https://www.focalboard.com</a></div>\n                  <div class=\"app-desc\">Contact Info: <a href=\"api@focalboard.com\">api@focalboard.com</a></div>\n              </p>\n                <div class=\"license-info\">Custom</div>\n                <div class=\"license-url\">https://github.com/mattermost/focalboard/blob/main/LICENSE.txt</div>\n            </div>\n          </div>\n      </div>\n    </div>\n  </div>\n  <script>\n(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(\"JSONFormatter\", [], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"JSONFormatter\"] = factory();\n\telse\n\t\troot[\"JSONFormatter\"] = factory();\n})(this, function() {\nreturn /******/ (function(modules) { // webpackBootstrap\n/******/ \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/******/\n/******/ \t\t// Create a new module (and put it into the cache)\n/******/ \t\tvar module = installedModules[moduleId] = {\n/******/ \t\t\texports: {},\n/******/ \t\t\tid: moduleId,\n/******/ \t\t\tloaded: false\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.loaded = 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// __webpack_public_path__\n/******/ \t__webpack_require__.p = \"dist\";\n/******/\n/******/ \t// Load entry module and return exports\n/******/ \treturn __webpack_require__(0);\n/******/ })\n/************************************************************************/\n/******/ ([\n/* 0 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tmodule.exports = __webpack_require__(1);\n\n\n/***/ },\n/* 1 */\n/***/ function(module, exports, __webpack_require__) {\n\n\t\"use strict\";\n\t__webpack_require__(2);\n\tvar helpers_ts_1 = __webpack_require__(6);\n\tvar DATE_STRING_REGEX = /(^\\d{1,4}[\\.|\\\\/|-]\\d{1,2}[\\.|\\\\/|-]\\d{1,4})(\\s*(?:0?[1-9]:[0-5]|1(?=[012])\\d:[0-5])\\d\\s*[ap]m)?$/;\n\tvar PARTIAL_DATE_REGEX = /\\d{2}:\\d{2}:\\d{2} GMT-\\d{4}/;\n\tvar JSON_DATE_REGEX = /\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}.\\d{3}Z/;\n\t// When toggling, don't animated removal or addition of more than a few items\n\tvar MAX_ANIMATED_TOGGLE_ITEMS = 10;\n\tvar requestAnimationFrame = window.requestAnimationFrame || function (cb) { cb(); return 0; };\n\t;\n\tvar _defaultConfig = {\n\t    hoverPreviewEnabled: false,\n\t    hoverPreviewArrayCount: 100,\n\t    hoverPreviewFieldCount: 5,\n\t    animateOpen: true,\n\t    animateClose: true,\n\t    theme: null\n\t};\n\tmodule.exports = (function () {\n\t    /**\n\t     * @param {object} json The JSON object you want to render. It has to be an\n\t     * object or array. Do NOT pass raw JSON string.\n\t     *\n\t     * @param {number} [open=1] his number indicates up to how many levels the\n\t     * rendered tree should expand. Set it to `0` to make the whole tree collapsed\n\t     * or set it to `Infinity` to expand the tree deeply\n\t     *\n\t     * @param {object} [config=defaultConfig] -\n\t     *  defaultConfig = {\n\t     *   hoverPreviewEnabled: false,\n\t     *   hoverPreviewArrayCount: 100,\n\t     *   hoverPreviewFieldCount: 5\n\t     * }\n\t     *\n\t     * Available configurations:\n\t     *  #####Hover Preview\n\t     * * `hoverPreviewEnabled`:  enable preview on hover\n\t     * * `hoverPreviewArrayCount`: number of array items to show in preview Any\n\t     *    array larger than this number will be shown as `Array[XXX]` where `XXX`\n\t     *    is length of the array.\n\t     * * `hoverPreviewFieldCount`: number of object properties to show for object\n\t     *   preview. Any object with more properties that thin number will be\n\t     *   truncated.\n\t     *\n\t     * @param {string} [key=undefined] The key that this object in it's parent\n\t     * context\n\t    */\n\t    function JSONFormatter(json, open, config, key) {\n\t        if (open === void 0) { open = 1; }\n\t        if (config === void 0) { config = _defaultConfig; }\n\t        this.json = json;\n\t        this.open = open;\n\t        this.config = config;\n\t        this.key = key;\n\t        // Hold the open state after the toggler is used\n\t        this._isOpen = null;\n\t        // Setting default values for config object\n\t        if (this.config.hoverPreviewEnabled === undefined) {\n\t            this.config.hoverPreviewEnabled = _defaultConfig.hoverPreviewEnabled;\n\t        }\n\t        if (this.config.hoverPreviewArrayCount === undefined) {\n\t            this.config.hoverPreviewArrayCount = _defaultConfig.hoverPreviewArrayCount;\n\t        }\n\t        if (this.config.hoverPreviewFieldCount === undefined) {\n\t            this.config.hoverPreviewFieldCount = _defaultConfig.hoverPreviewFieldCount;\n\t        }\n\t    }\n\t    Object.defineProperty(JSONFormatter.prototype, \"isOpen\", {\n\t        /*\n\t         * is formatter open?\n\t        */\n\t        get: function () {\n\t            if (this._isOpen !== null) {\n\t                return this._isOpen;\n\t            }\n\t            else {\n\t                return this.open > 0;\n\t            }\n\t        },\n\t        /*\n\t         * set open state (from toggler)\n\t        */\n\t        set: function (value) {\n\t            this._isOpen = value;\n\t        },\n\t        enumerable: true,\n\t        configurable: true\n\t    });\n\t    Object.defineProperty(JSONFormatter.prototype, \"isDate\", {\n\t        /*\n\t         * is this a date string?\n\t        */\n\t        get: function () {\n\t            return (this.type === 'string') &&\n\t                (DATE_STRING_REGEX.test(this.json) ||\n\t                    JSON_DATE_REGEX.test(this.json) ||\n\t                    PARTIAL_DATE_REGEX.test(this.json));\n\t        },\n\t        enumerable: true,\n\t        configurable: true\n\t    });\n\t    Object.defineProperty(JSONFormatter.prototype, \"isUrl\", {\n\t        /*\n\t         * is this a URL string?\n\t        */\n\t        get: function () {\n\t            return this.type === 'string' && (this.json.indexOf('http') === 0);\n\t        },\n\t        enumerable: true,\n\t        configurable: true\n\t    });\n\t    Object.defineProperty(JSONFormatter.prototype, \"isArray\", {\n\t        /*\n\t         * is this an array?\n\t        */\n\t        get: function () {\n\t            return Array.isArray(this.json);\n\t        },\n\t        enumerable: true,\n\t        configurable: true\n\t    });\n\t    Object.defineProperty(JSONFormatter.prototype, \"isObject\", {\n\t        /*\n\t         * is this an object?\n\t         * Note: In this context arrays are object as well\n\t        */\n\t        get: function () {\n\t            return helpers_ts_1.isObject(this.json);\n\t        },\n\t        enumerable: true,\n\t        configurable: true\n\t    });\n\t    Object.defineProperty(JSONFormatter.prototype, \"isEmptyObject\", {\n\t        /*\n\t         * is this an empty object with no properties?\n\t        */\n\t        get: function () {\n\t            return !this.keys.length && !this.isArray;\n\t        },\n\t        enumerable: true,\n\t        configurable: true\n\t    });\n\t    Object.defineProperty(JSONFormatter.prototype, \"isEmpty\", {\n\t        /*\n\t         * is this an empty object or array?\n\t        */\n\t        get: function () {\n\t            return this.isEmptyObject || (this.keys && !this.keys.length && this.isArray);\n\t        },\n\t        enumerable: true,\n\t        configurable: true\n\t    });\n\t    Object.defineProperty(JSONFormatter.prototype, \"hasKey\", {\n\t        /*\n\t         * did we receive a key argument?\n\t         * This means that the formatter was called as a sub formatter of a parent formatter\n\t        */\n\t        get: function () {\n\t            return typeof this.key !== 'undefined';\n\t        },\n\t        enumerable: true,\n\t        configurable: true\n\t    });\n\t    Object.defineProperty(JSONFormatter.prototype, \"constructorName\", {\n\t        /*\n\t         * if this is an object, get constructor function name\n\t        */\n\t        get: function () {\n\t            return helpers_ts_1.getObjectName(this.json);\n\t        },\n\t        enumerable: true,\n\t        configurable: true\n\t    });\n\t    Object.defineProperty(JSONFormatter.prototype, \"type\", {\n\t        /*\n\t         * get type of this value\n\t         * Possible values: all JavaScript primitive types plus \"array\" and \"null\"\n\t        */\n\t        get: function () {\n\t            return helpers_ts_1.getType(this.json);\n\t        },\n\t        enumerable: true,\n\t        configurable: true\n\t    });\n\t    Object.defineProperty(JSONFormatter.prototype, \"keys\", {\n\t        /*\n\t         * get object keys\n\t         * If there is an empty key we pad it wit quotes to make it visible\n\t        */\n\t        get: function () {\n\t            if (this.isObject) {\n\t                return Object.keys(this.json).map(function (key) { return key ? key : '\"\"'; });\n\t            }\n\t            else {\n\t                return [];\n\t            }\n\t        },\n\t        enumerable: true,\n\t        configurable: true\n\t    });\n\t    /**\n\t     * Toggles `isOpen` state\n\t     *\n\t    */\n\t    JSONFormatter.prototype.toggleOpen = function () {\n\t        this.isOpen = !this.isOpen;\n\t        if (this.element) {\n\t            if (this.isOpen) {\n\t                this.appendChildren(this.config.animateOpen);\n\t            }\n\t            else {\n\t                this.removeChildren(this.config.animateClose);\n\t            }\n\t            this.element.classList.toggle(helpers_ts_1.cssClass('open'));\n\t        }\n\t    };\n\t    /**\n\t    * Open all children up to a certain depth.\n\t    * Allows actions such as expand all/collapse all\n\t    *\n\t    */\n\t    JSONFormatter.prototype.openAtDepth = function (depth) {\n\t        if (depth === void 0) { depth = 1; }\n\t        if (depth < 0) {\n\t            return;\n\t        }\n\t        this.open = depth;\n\t        this.isOpen = (depth !== 0);\n\t        if (this.element) {\n\t            this.removeChildren(false);\n\t            if (depth === 0) {\n\t                this.element.classList.remove(helpers_ts_1.cssClass('open'));\n\t            }\n\t            else {\n\t                this.appendChildren(this.config.animateOpen);\n\t                this.element.classList.add(helpers_ts_1.cssClass('open'));\n\t            }\n\t        }\n\t    };\n\t    /**\n\t     * Generates inline preview\n\t     *\n\t     * @returns {string}\n\t    */\n\t    JSONFormatter.prototype.getInlinepreview = function () {\n\t        var _this = this;\n\t        if (this.isArray) {\n\t            // if array length is greater then 100 it shows \"Array[101]\"\n\t            if (this.json.length > this.config.hoverPreviewArrayCount) {\n\t                return \"Array[\" + this.json.length + \"]\";\n\t            }\n\t            else {\n\t                return \"[\" + this.json.map(helpers_ts_1.getPreview).join(', ') + \"]\";\n\t            }\n\t        }\n\t        else {\n\t            var keys = this.keys;\n\t            // the first five keys (like Chrome Developer Tool)\n\t            var narrowKeys = keys.slice(0, this.config.hoverPreviewFieldCount);\n\t            // json value schematic information\n\t            var kvs = narrowKeys.map(function (key) { return (key + \":\" + helpers_ts_1.getPreview(_this.json[key])); });\n\t            // if keys count greater then 5 then show ellipsis\n\t            var ellipsis = keys.length >= this.config.hoverPreviewFieldCount ? '…' : '';\n\t            return \"{\" + kvs.join(', ') + ellipsis + \"}\";\n\t        }\n\t    };\n\t    /**\n\t     * Renders an HTML element and installs event listeners\n\t     *\n\t     * @returns {HTMLDivElement}\n\t    */\n\t    JSONFormatter.prototype.render = function () {\n\t        // construct the root element and assign it to this.element\n\t        this.element = helpers_ts_1.createElement('div', 'row');\n\t        // construct the toggler link\n\t        var togglerLink = helpers_ts_1.createElement('a', 'toggler-link');\n\t        // if this is an object we need a wrapper span (toggler)\n\t        if (this.isObject) {\n\t            togglerLink.appendChild(helpers_ts_1.createElement('span', 'toggler'));\n\t        }\n\t        // if this is child of a parent formatter we need to append the key\n\t        if (this.hasKey) {\n\t            togglerLink.appendChild(helpers_ts_1.createElement('span', 'key', this.key + \":\"));\n\t        }\n\t        // Value for objects and arrays\n\t        if (this.isObject) {\n\t            // construct the value holder element\n\t            var value = helpers_ts_1.createElement('span', 'value');\n\t            // we need a wrapper span for objects\n\t            var objectWrapperSpan = helpers_ts_1.createElement('span');\n\t            // get constructor name and append it to wrapper span\n\t            var constructorName = helpers_ts_1.createElement('span', 'constructor-name', this.constructorName);\n\t            objectWrapperSpan.appendChild(constructorName);\n\t            // if it's an array append the array specific elements like brackets and length\n\t            if (this.isArray) {\n\t                var arrayWrapperSpan = helpers_ts_1.createElement('span');\n\t                arrayWrapperSpan.appendChild(helpers_ts_1.createElement('span', 'bracket', '['));\n\t                arrayWrapperSpan.appendChild(helpers_ts_1.createElement('span', 'number', (this.json.length)));\n\t                arrayWrapperSpan.appendChild(helpers_ts_1.createElement('span', 'bracket', ']'));\n\t                objectWrapperSpan.appendChild(arrayWrapperSpan);\n\t            }\n\t            // append object wrapper span to toggler link\n\t            value.appendChild(objectWrapperSpan);\n\t            togglerLink.appendChild(value);\n\t        }\n\t        else {\n\t            // make a value holder element\n\t            var value = this.isUrl ? helpers_ts_1.createElement('a') : helpers_ts_1.createElement('span');\n\t            // add type and other type related CSS classes\n\t            value.classList.add(helpers_ts_1.cssClass(this.type));\n\t            if (this.isDate) {\n\t                value.classList.add(helpers_ts_1.cssClass('date'));\n\t            }\n\t            if (this.isUrl) {\n\t                value.classList.add(helpers_ts_1.cssClass('url'));\n\t                value.setAttribute('href', this.json);\n\t            }\n\t            // Append value content to value element\n\t            var valuePreview = helpers_ts_1.getValuePreview(this.json, this.json);\n\t            value.appendChild(document.createTextNode(valuePreview));\n\t            // append the value element to toggler link\n\t            togglerLink.appendChild(value);\n\t        }\n\t        // if hover preview is enabled, append the inline preview element\n\t        if (this.isObject && this.config.hoverPreviewEnabled) {\n\t            var preview = helpers_ts_1.createElement('span', 'preview-text');\n\t            preview.appendChild(document.createTextNode(this.getInlinepreview()));\n\t            togglerLink.appendChild(preview);\n\t        }\n\t        // construct a children element\n\t        var children = helpers_ts_1.createElement('div', 'children');\n\t        // set CSS classes for children\n\t        if (this.isObject) {\n\t            children.classList.add(helpers_ts_1.cssClass('object'));\n\t        }\n\t        if (this.isArray) {\n\t            children.classList.add(helpers_ts_1.cssClass('array'));\n\t        }\n\t        if (this.isEmpty) {\n\t            children.classList.add(helpers_ts_1.cssClass('empty'));\n\t        }\n\t        // set CSS classes for root element\n\t        if (this.config && this.config.theme) {\n\t            this.element.classList.add(helpers_ts_1.cssClass(this.config.theme));\n\t        }\n\t        if (this.isOpen) {\n\t            this.element.classList.add(helpers_ts_1.cssClass('open'));\n\t        }\n\t        // append toggler and children elements to root element\n\t        this.element.appendChild(togglerLink);\n\t        this.element.appendChild(children);\n\t        // if formatter is set to be open call appendChildren\n\t        if (this.isObject && this.isOpen) {\n\t            this.appendChildren();\n\t        }\n\t        // add event listener for toggling\n\t        if (this.isObject) {\n\t            togglerLink.addEventListener('click', this.toggleOpen.bind(this));\n\t        }\n\t        return this.element;\n\t    };\n\t    /**\n\t     * Appends all the children to children element\n\t     * Animated option is used when user triggers this via a click\n\t    */\n\t    JSONFormatter.prototype.appendChildren = function (animated) {\n\t        var _this = this;\n\t        if (animated === void 0) { animated = false; }\n\t        var children = this.element.querySelector(\"div.\" + helpers_ts_1.cssClass('children'));\n\t        if (!children || this.isEmpty) {\n\t            return;\n\t        }\n\t        if (animated) {\n\t            var index_1 = 0;\n\t            var addAChild_1 = function () {\n\t                var key = _this.keys[index_1];\n\t                var formatter = new JSONFormatter(_this.json[key], _this.open - 1, _this.config, key);\n\t                children.appendChild(formatter.render());\n\t                index_1 += 1;\n\t                if (index_1 < _this.keys.length) {\n\t                    if (index_1 > MAX_ANIMATED_TOGGLE_ITEMS) {\n\t                        addAChild_1();\n\t                    }\n\t                    else {\n\t                        requestAnimationFrame(addAChild_1);\n\t                    }\n\t                }\n\t            };\n\t            requestAnimationFrame(addAChild_1);\n\t        }\n\t        else {\n\t            this.keys.forEach(function (key) {\n\t                var formatter = new JSONFormatter(_this.json[key], _this.open - 1, _this.config, key);\n\t                children.appendChild(formatter.render());\n\t            });\n\t        }\n\t    };\n\t    /**\n\t     * Removes all the children from children element\n\t     * Animated option is used when user triggers this via a click\n\t    */\n\t    JSONFormatter.prototype.removeChildren = function (animated) {\n\t        if (animated === void 0) { animated = false; }\n\t        var childrenElement = this.element.querySelector(\"div.\" + helpers_ts_1.cssClass('children'));\n\t        if (animated) {\n\t            var childrenRemoved_1 = 0;\n\t            var removeAChild_1 = function () {\n\t                if (childrenElement && childrenElement.children.length) {\n\t                    childrenElement.removeChild(childrenElement.children[0]);\n\t                    childrenRemoved_1 += 1;\n\t                    if (childrenRemoved_1 > MAX_ANIMATED_TOGGLE_ITEMS) {\n\t                        removeAChild_1();\n\t                    }\n\t                    else {\n\t                        requestAnimationFrame(removeAChild_1);\n\t                    }\n\t                }\n\t            };\n\t            requestAnimationFrame(removeAChild_1);\n\t        }\n\t        else {\n\t            if (childrenElement) {\n\t                childrenElement.innerHTML = '';\n\t            }\n\t        }\n\t    };\n\t    return JSONFormatter;\n\t}());\n\n\n/***/ },\n/* 2 */\n/***/ function(module, exports, __webpack_require__) {\n\n\t// style-loader: Adds some css to the DOM by adding a <style> tag\n\n\t// load the styles\n\tvar content = __webpack_require__(3);\n\tif(typeof content === 'string') content = [[module.id, content, '']];\n\t// add the styles to the DOM\n\tvar update = __webpack_require__(5)(content, {\"sourceMap\":true});\n\tif(content.locals) module.exports = content.locals;\n\t// Hot Module Replacement\n\tif(false) {\n\t\t// When the styles change, update the <style> tags\n\t\tif(!content.locals) {\n\t\t\tmodule.hot.accept(\"!!./../node_modules/css-loader/index.js?sourceMap!./../node_modules/less-loader/index.js?sourceMap!./style.less\", function() {\n\t\t\t\tvar newContent = require(\"!!./../node_modules/css-loader/index.js?sourceMap!./../node_modules/less-loader/index.js?sourceMap!./style.less\");\n\t\t\t\tif(typeof newContent === 'string') newContent = [[module.id, newContent, '']];\n\t\t\t\tupdate(newContent);\n\t\t\t});\n\t\t}\n\t\t// When the module is disposed, remove the <style> tags\n\t\tmodule.hot.dispose(function() { update(); });\n\t}\n\n/***/ },\n/* 3 */\n/***/ function(module, exports, __webpack_require__) {\n\n\texports = module.exports = __webpack_require__(4)();\n\t// imports\n\n\n\t// module\n\texports.push([module.id, \".json-formatter-row {\\n  font-family: monospace;\\n}\\n.json-formatter-row,\\n.json-formatter-row a,\\n.json-formatter-row a:hover {\\n  color: black;\\n  text-decoration: none;\\n}\\n.json-formatter-row .json-formatter-row {\\n  margin-left: 1rem;\\n}\\n.json-formatter-row .json-formatter-children.json-formatter-empty {\\n  opacity: 0.5;\\n  margin-left: 1rem;\\n}\\n.json-formatter-row .json-formatter-children.json-formatter-empty:after {\\n  display: none;\\n}\\n.json-formatter-row .json-formatter-children.json-formatter-empty.json-formatter-object:after {\\n  content: \\\"No properties\\\";\\n}\\n.json-formatter-row .json-formatter-children.json-formatter-empty.json-formatter-array:after {\\n  content: \\\"[]\\\";\\n}\\n.json-formatter-row .json-formatter-string {\\n  color: green;\\n  white-space: pre;\\n  word-wrap: break-word;\\n}\\n.json-formatter-row .json-formatter-number {\\n  color: blue;\\n}\\n.json-formatter-row .json-formatter-boolean {\\n  color: red;\\n}\\n.json-formatter-row .json-formatter-null {\\n  color: #855A00;\\n}\\n.json-formatter-row .json-formatter-undefined {\\n  color: #ca0b69;\\n}\\n.json-formatter-row .json-formatter-function {\\n  color: #FF20ED;\\n}\\n.json-formatter-row .json-formatter-date {\\n  background-color: rgba(0, 0, 0, 0.05);\\n}\\n.json-formatter-row .json-formatter-url {\\n  text-decoration: underline;\\n  color: blue;\\n  cursor: pointer;\\n}\\n.json-formatter-row .json-formatter-bracket {\\n  color: blue;\\n}\\n.json-formatter-row .json-formatter-key {\\n  color: #00008B;\\n  cursor: pointer;\\n  padding-right: 0.2rem;\\n}\\n.json-formatter-row .json-formatter-constructor-name {\\n  cursor: pointer;\\n}\\n.json-formatter-row .json-formatter-toggler {\\n  line-height: 1.2rem;\\n  font-size: 0.7rem;\\n  vertical-align: middle;\\n  opacity: 0.6;\\n  cursor: pointer;\\n  padding-right: 0.2rem;\\n}\\n.json-formatter-row .json-formatter-toggler:after {\\n  display: inline-block;\\n  transition: transform 100ms ease-in;\\n  content: \\\"\\\\25BA\\\";\\n}\\n.json-formatter-row > a > .json-formatter-preview-text {\\n  opacity: 0;\\n  transition: opacity 0.15s ease-in;\\n  font-style: italic;\\n}\\n.json-formatter-row:hover > a > .json-formatter-preview-text {\\n  opacity: 0.6;\\n}\\n.json-formatter-row.json-formatter-open > .json-formatter-toggler-link .json-formatter-toggler:after {\\n  transform: rotate(90deg);\\n}\\n.json-formatter-row.json-formatter-open > .json-formatter-children:after {\\n  display: inline-block;\\n}\\n.json-formatter-row.json-formatter-open > a > .json-formatter-preview-text {\\n  display: none;\\n}\\n.json-formatter-row.json-formatter-open.json-formatter-empty:after {\\n  display: block;\\n}\\n.json-formatter-dark.json-formatter-row {\\n  font-family: monospace;\\n}\\n.json-formatter-dark.json-formatter-row,\\n.json-formatter-dark.json-formatter-row a,\\n.json-formatter-dark.json-formatter-row a:hover {\\n  color: white;\\n  text-decoration: none;\\n}\\n.json-formatter-dark.json-formatter-row .json-formatter-row {\\n  margin-left: 1rem;\\n}\\n.json-formatter-dark.json-formatter-row .json-formatter-children.json-formatter-empty {\\n  opacity: 0.5;\\n  margin-left: 1rem;\\n}\\n.json-formatter-dark.json-formatter-row .json-formatter-children.json-formatter-empty:after {\\n  display: none;\\n}\\n.json-formatter-dark.json-formatter-row .json-formatter-children.json-formatter-empty.json-formatter-object:after {\\n  content: \\\"No properties\\\";\\n}\\n.json-formatter-dark.json-formatter-row .json-formatter-children.json-formatter-empty.json-formatter-array:after {\\n  content: \\\"[]\\\";\\n}\\n.json-formatter-dark.json-formatter-row .json-formatter-string {\\n  color: #31F031;\\n  white-space: pre;\\n  word-wrap: break-word;\\n}\\n.json-formatter-dark.json-formatter-row .json-formatter-number {\\n  color: #66C2FF;\\n}\\n.json-formatter-dark.json-formatter-row .json-formatter-boolean {\\n  color: #EC4242;\\n}\\n.json-formatter-dark.json-formatter-row .json-formatter-null {\\n  color: #EEC97D;\\n}\\n.json-formatter-dark.json-formatter-row .json-formatter-undefined {\\n  color: #ef8fbe;\\n}\\n.json-formatter-dark.json-formatter-row .json-formatter-function {\\n  color: #FD48CB;\\n}\\n.json-formatter-dark.json-formatter-row .json-formatter-date {\\n  background-color: rgba(255, 255, 255, 0.05);\\n}\\n.json-formatter-dark.json-formatter-row .json-formatter-url {\\n  text-decoration: underline;\\n  color: #027BFF;\\n  cursor: pointer;\\n}\\n.json-formatter-dark.json-formatter-row .json-formatter-bracket {\\n  color: #9494FF;\\n}\\n.json-formatter-dark.json-formatter-row .json-formatter-key {\\n  color: #23A0DB;\\n  cursor: pointer;\\n  padding-right: 0.2rem;\\n}\\n.json-formatter-dark.json-formatter-row .json-formatter-constructor-name {\\n  cursor: pointer;\\n}\\n.json-formatter-dark.json-formatter-row .json-formatter-toggler {\\n  line-height: 1.2rem;\\n  font-size: 0.7rem;\\n  vertical-align: middle;\\n  opacity: 0.6;\\n  cursor: pointer;\\n  padding-right: 0.2rem;\\n}\\n.json-formatter-dark.json-formatter-row .json-formatter-toggler:after {\\n  display: inline-block;\\n  transition: transform 100ms ease-in;\\n  content: \\\"\\\\25BA\\\";\\n}\\n.json-formatter-dark.json-formatter-row > a > .json-formatter-preview-text {\\n  opacity: 0;\\n  transition: opacity 0.15s ease-in;\\n  font-style: italic;\\n}\\n.json-formatter-dark.json-formatter-row:hover > a > .json-formatter-preview-text {\\n  opacity: 0.6;\\n}\\n.json-formatter-dark.json-formatter-row.json-formatter-open > .json-formatter-toggler-link .json-formatter-toggler:after {\\n  transform: rotate(90deg);\\n}\\n.json-formatter-dark.json-formatter-row.json-formatter-open > .json-formatter-children:after {\\n  display: inline-block;\\n}\\n.json-formatter-dark.json-formatter-row.json-formatter-open > a > .json-formatter-preview-text {\\n  display: none;\\n}\\n.json-formatter-dark.json-formatter-row.json-formatter-open.json-formatter-empty:after {\\n  display: block;\\n}\\n\", \"\", {\"version\":3,\"sources\":[\"/./src/style.less\",\"/./src/style.less\"],\"names\":[],\"mappings\":\"AA0GA;EA3FE,uBAAA;CCbD;ADcC;;;EACE,aAAA;EACA,sBAAA;CCVH;ADkGD;EApFI,kBAAA;CCXH;ADeG;EACE,aAAA;EACA,kBAAA;CCbL;ADeK;EAAU,cAAA;CCZf;ADaK;EAAgC,yBAAA;CCVrC;ADWK;EAA+B,cAAA;CCRpC;ADkFD;EArEI,aAAA;EACA,iBAAA;EACA,sBAAA;CCVH;AD6ED;EAjE2B,YAAA;CCT1B;AD0ED;EAhE4B,WAAA;CCP3B;ADuED;EA/DyB,eAAA;CCLxB;ADoED;EA9D8B,eAAA;CCH7B;ADiED;EA7D6B,eAAA;CCD5B;AD8DD;EA5DyB,sCAAA;CCCxB;AD2DD;EA1DI,2BAAA;EACA,YAAA;EACA,gBAAA;CCEH;ADsDD;EArD4B,YAAA;CCE3B;ADmDD;EAnDI,eAAA;EACA,gBAAA;EACA,sBAAA;CCGH;AD8CD;EA9CI,gBAAA;CCGH;AD2CD;EA1CI,oBAAA;EACA,kBAAA;EACA,uBAAA;EACA,aAAA;EACA,gBAAA;EACA,sBAAA;CCEH;ADAG;EACE,sBAAA;EACA,oCAAA;EACA,iBAAA;CCEL;AD8BD;EA1BI,WAAA;EACA,kCAAA;EACA,mBAAA;CCDH;ADGC;EACE,aAAA;CCDH;ADKC;EAEI,yBAAA;CCJL;ADEC;EAKI,sBAAA;CCJL;ADDC;EAQI,cAAA;CCJL;ADMG;EACE,eAAA;CCJL;ADeD;EAhGE,uBAAA;CCoFD;ADnFC;;;EACE,aAAA;EACA,sBAAA;CCuFH;ADMD;EAzFI,kBAAA;CCsFH;ADlFG;EACE,aAAA;EACA,kBAAA;CCoFL;ADlFK;EAAU,cAAA;CCqFf;ADpFK;EAAgC,yBAAA;CCuFrC;ADtFK;EAA+B,cAAA;CCyFpC;ADVD;EA1EI,eAAA;EACA,iBAAA;EACA,sBAAA;CCuFH;ADfD;EAtE2B,eAAA;CCwF1B;ADlBD;EArE4B,eAAA;CC0F3B;ADrBD;EApEyB,eAAA;CC4FxB;ADxBD;EAnE8B,eAAA;CC8F7B;AD3BD;EAlE6B,eAAA;CCgG5B;AD9BD;EAjEyB,4CAAA;CCkGxB;ADjCD;EA/DI,2BAAA;EACA,eAAA;EACA,gBAAA;CCmGH;ADtCD;EA1D4B,eAAA;CCmG3B;ADzCD;EAxDI,eAAA;EACA,gBAAA;EACA,sBAAA;CCoGH;AD9CD;EAnDI,gBAAA;CCoGH;ADjDD;EA/CI,oBAAA;EACA,kBAAA;EACA,uBAAA;EACA,aAAA;EACA,gBAAA;EACA,sBAAA;CCmGH;ADjGG;EACE,sBAAA;EACA,oCAAA;EACA,iBAAA;CCmGL;AD9DD;EA/BI,WAAA;EACA,kCAAA;EACA,mBAAA;CCgGH;AD9FC;EACE,aAAA;CCgGH;AD5FC;EAEI,yBAAA;CC6FL;AD/FC;EAKI,sBAAA;CC6FL;ADlGC;EAQI,cAAA;CC6FL;AD3FG;EACE,eAAA;CC6FL\",\"file\":\"style.less\",\"sourcesContent\":[\".theme(\\n  @default-color: black,\\n  @string-color: green,\\n  @number-color: blue,\\n  @boolean-color: red,\\n  @null-color: #855A00,\\n  @undefined-color: rgb(202, 11, 105),\\n  @function-color: #FF20ED,\\n  @rotate-time: 100ms,\\n  @toggler-opacity: 0.6,\\n  @toggler-color: #45376F,\\n  @bracket-color: blue,\\n  @key-color: #00008B,\\n  @url-color: blue ){\\n\\n  font-family: monospace;\\n  &, a, a:hover {\\n    color: @default-color;\\n    text-decoration: none;\\n  }\\n\\n  .json-formatter-row {\\n    margin-left: 1rem;\\n  }\\n\\n  .json-formatter-children {\\n    &.json-formatter-empty {\\n      opacity: 0.5;\\n      margin-left: 1rem;\\n\\n      &:after { display: none; }\\n      &.json-formatter-object:after { content: \\\"No properties\\\"; }\\n      &.json-formatter-array:after { content: \\\"[]\\\"; }\\n    }\\n  }\\n\\n  .json-formatter-string {\\n    color: @string-color;\\n    white-space: pre;\\n    word-wrap: break-word;\\n  }\\n  .json-formatter-number { color: @number-color; }\\n  .json-formatter-boolean { color: @boolean-color; }\\n  .json-formatter-null { color: @null-color; }\\n  .json-formatter-undefined { color: @undefined-color; }\\n  .json-formatter-function { color: @function-color; }\\n  .json-formatter-date { background-color: fade(@default-color, 5%); }\\n  .json-formatter-url {\\n    text-decoration: underline;\\n    color: @url-color;\\n    cursor: pointer;\\n  }\\n\\n  .json-formatter-bracket { color: @bracket-color; }\\n  .json-formatter-key {\\n    color: @key-color;\\n    cursor: pointer;\\n    padding-right: 0.2rem;\\n  }\\n  .json-formatter-constructor-name {\\n    cursor: pointer;\\n  }\\n\\n  .json-formatter-toggler {\\n    line-height: 1.2rem;\\n    font-size: 0.7rem;\\n    vertical-align: middle;\\n    opacity: @toggler-opacity;\\n    cursor: pointer;\\n    padding-right: 0.2rem;\\n\\n    &:after {\\n      display: inline-block;\\n      transition: transform @rotate-time ease-in;\\n      content: \\\"►\\\";\\n    }\\n  }\\n\\n  // Inline preview on hover (optional)\\n  > a > .json-formatter-preview-text {\\n    opacity: 0;\\n    transition: opacity .15s ease-in;\\n    font-style: italic;\\n  }\\n  &:hover > a > .json-formatter-preview-text {\\n    opacity: 0.6;\\n  }\\n\\n  // Open state\\n  &.json-formatter-open {\\n    > .json-formatter-toggler-link .json-formatter-toggler:after{\\n      transform: rotate(90deg);\\n    }\\n    > .json-formatter-children:after {\\n      display: inline-block;\\n    }\\n    > a > .json-formatter-preview-text {\\n      display: none;\\n    }\\n    &.json-formatter-empty:after {\\n      display: block;\\n    }\\n  }\\n}\\n\\n// Default theme\\n.json-formatter-row {\\n  .theme();\\n}\\n\\n// Dark theme\\n.json-formatter-dark.json-formatter-row {\\n  .theme(\\n    @default-color: white,\\n    @string-color: #31F031,\\n    @number-color: #66C2FF,\\n    @boolean-color: #EC4242,\\n    @null-color: #EEC97D,\\n    @undefined-color: rgb(239, 143, 190),\\n    @function-color: #FD48CB,\\n    @rotate-time: 100ms,\\n    @toggler-opacity: 0.6,\\n    @toggler-color: #45376F,\\n    @bracket-color: #9494FF,\\n    @key-color: #23A0DB,\\n    @url-color: #027BFF);\\n}\\n\",\".json-formatter-row {\\n  font-family: monospace;\\n}\\n.json-formatter-row,\\n.json-formatter-row a,\\n.json-formatter-row a:hover {\\n  color: black;\\n  text-decoration: none;\\n}\\n.json-formatter-row .json-formatter-row {\\n  margin-left: 1rem;\\n}\\n.json-formatter-row .json-formatter-children.json-formatter-empty {\\n  opacity: 0.5;\\n  margin-left: 1rem;\\n}\\n.json-formatter-row .json-formatter-children.json-formatter-empty:after {\\n  display: none;\\n}\\n.json-formatter-row .json-formatter-children.json-formatter-empty.json-formatter-object:after {\\n  content: \\\"No properties\\\";\\n}\\n.json-formatter-row .json-formatter-children.json-formatter-empty.json-formatter-array:after {\\n  content: \\\"[]\\\";\\n}\\n.json-formatter-row .json-formatter-string {\\n  color: green;\\n  white-space: pre;\\n  word-wrap: break-word;\\n}\\n.json-formatter-row .json-formatter-number {\\n  color: blue;\\n}\\n.json-formatter-row .json-formatter-boolean {\\n  color: red;\\n}\\n.json-formatter-row .json-formatter-null {\\n  color: #855A00;\\n}\\n.json-formatter-row .json-formatter-undefined {\\n  color: #ca0b69;\\n}\\n.json-formatter-row .json-formatter-function {\\n  color: #FF20ED;\\n}\\n.json-formatter-row .json-formatter-date {\\n  background-color: rgba(0, 0, 0, 0.05);\\n}\\n.json-formatter-row .json-formatter-url {\\n  text-decoration: underline;\\n  color: blue;\\n  cursor: pointer;\\n}\\n.json-formatter-row .json-formatter-bracket {\\n  color: blue;\\n}\\n.json-formatter-row .json-formatter-key {\\n  color: #00008B;\\n  cursor: pointer;\\n  padding-right: 0.2rem;\\n}\\n.json-formatter-row .json-formatter-constructor-name {\\n  cursor: pointer;\\n}\\n.json-formatter-row .json-formatter-toggler {\\n  line-height: 1.2rem;\\n  font-size: 0.7rem;\\n  vertical-align: middle;\\n  opacity: 0.6;\\n  cursor: pointer;\\n  padding-right: 0.2rem;\\n}\\n.json-formatter-row .json-formatter-toggler:after {\\n  display: inline-block;\\n  transition: transform 100ms ease-in;\\n  content: \\\"►\\\";\\n}\\n.json-formatter-row > a > .json-formatter-preview-text {\\n  opacity: 0;\\n  transition: opacity 0.15s ease-in;\\n  font-style: italic;\\n}\\n.json-formatter-row:hover > a > .json-formatter-preview-text {\\n  opacity: 0.6;\\n}\\n.json-formatter-row.json-formatter-open > .json-formatter-toggler-link .json-formatter-toggler:after {\\n  transform: rotate(90deg);\\n}\\n.json-formatter-row.json-formatter-open > .json-formatter-children:after {\\n  display: inline-block;\\n}\\n.json-formatter-row.json-formatter-open > a > .json-formatter-preview-text {\\n  display: none;\\n}\\n.json-formatter-row.json-formatter-open.json-formatter-empty:after {\\n  display: block;\\n}\\n.json-formatter-dark.json-formatter-row {\\n  font-family: monospace;\\n}\\n.json-formatter-dark.json-formatter-row,\\n.json-formatter-dark.json-formatter-row a,\\n.json-formatter-dark.json-formatter-row a:hover {\\n  color: white;\\n  text-decoration: none;\\n}\\n.json-formatter-dark.json-formatter-row .json-formatter-row {\\n  margin-left: 1rem;\\n}\\n.json-formatter-dark.json-formatter-row .json-formatter-children.json-formatter-empty {\\n  opacity: 0.5;\\n  margin-left: 1rem;\\n}\\n.json-formatter-dark.json-formatter-row .json-formatter-children.json-formatter-empty:after {\\n  display: none;\\n}\\n.json-formatter-dark.json-formatter-row .json-formatter-children.json-formatter-empty.json-formatter-object:after {\\n  content: \\\"No properties\\\";\\n}\\n.json-formatter-dark.json-formatter-row .json-formatter-children.json-formatter-empty.json-formatter-array:after {\\n  content: \\\"[]\\\";\\n}\\n.json-formatter-dark.json-formatter-row .json-formatter-string {\\n  color: #31F031;\\n  white-space: pre;\\n  word-wrap: break-word;\\n}\\n.json-formatter-dark.json-formatter-row .json-formatter-number {\\n  color: #66C2FF;\\n}\\n.json-formatter-dark.json-formatter-row .json-formatter-boolean {\\n  color: #EC4242;\\n}\\n.json-formatter-dark.json-formatter-row .json-formatter-null {\\n  color: #EEC97D;\\n}\\n.json-formatter-dark.json-formatter-row .json-formatter-undefined {\\n  color: #ef8fbe;\\n}\\n.json-formatter-dark.json-formatter-row .json-formatter-function {\\n  color: #FD48CB;\\n}\\n.json-formatter-dark.json-formatter-row .json-formatter-date {\\n  background-color: rgba(255, 255, 255, 0.05);\\n}\\n.json-formatter-dark.json-formatter-row .json-formatter-url {\\n  text-decoration: underline;\\n  color: #027BFF;\\n  cursor: pointer;\\n}\\n.json-formatter-dark.json-formatter-row .json-formatter-bracket {\\n  color: #9494FF;\\n}\\n.json-formatter-dark.json-formatter-row .json-formatter-key {\\n  color: #23A0DB;\\n  cursor: pointer;\\n  padding-right: 0.2rem;\\n}\\n.json-formatter-dark.json-formatter-row .json-formatter-constructor-name {\\n  cursor: pointer;\\n}\\n.json-formatter-dark.json-formatter-row .json-formatter-toggler {\\n  line-height: 1.2rem;\\n  font-size: 0.7rem;\\n  vertical-align: middle;\\n  opacity: 0.6;\\n  cursor: pointer;\\n  padding-right: 0.2rem;\\n}\\n.json-formatter-dark.json-formatter-row .json-formatter-toggler:after {\\n  display: inline-block;\\n  transition: transform 100ms ease-in;\\n  content: \\\"►\\\";\\n}\\n.json-formatter-dark.json-formatter-row > a > .json-formatter-preview-text {\\n  opacity: 0;\\n  transition: opacity 0.15s ease-in;\\n  font-style: italic;\\n}\\n.json-formatter-dark.json-formatter-row:hover > a > .json-formatter-preview-text {\\n  opacity: 0.6;\\n}\\n.json-formatter-dark.json-formatter-row.json-formatter-open > .json-formatter-toggler-link .json-formatter-toggler:after {\\n  transform: rotate(90deg);\\n}\\n.json-formatter-dark.json-formatter-row.json-formatter-open > .json-formatter-children:after {\\n  display: inline-block;\\n}\\n.json-formatter-dark.json-formatter-row.json-formatter-open > a > .json-formatter-preview-text {\\n  display: none;\\n}\\n.json-formatter-dark.json-formatter-row.json-formatter-open.json-formatter-empty:after {\\n  display: block;\\n}\\n\"],\"sourceRoot\":\"webpack://\"}]);\n\n\t// exports\n\n\n/***/ },\n/* 4 */\n/***/ function(module, exports) {\n\n\t/*\n\t\tMIT License http://www.opensource.org/licenses/mit-license.php\n\t\tAuthor Tobias Koppers @sokra\n\t*/\n\t// css base code, injected by the css-loader\n\tmodule.exports = function() {\n\t\tvar list = [];\n\n\t\t// return the list of modules as css string\n\t\tlist.toString = function toString() {\n\t\t\tvar result = [];\n\t\t\tfor(var i = 0; i < this.length; i++) {\n\t\t\t\tvar item = this[i];\n\t\t\t\tif(item[2]) {\n\t\t\t\t\tresult.push(\"@media \" + item[2] + \"{\" + item[1] + \"}\");\n\t\t\t\t} else {\n\t\t\t\t\tresult.push(item[1]);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn result.join(\"\");\n\t\t};\n\n\t\t// import a list of modules into the list\n\t\tlist.i = function(modules, mediaQuery) {\n\t\t\tif(typeof modules === \"string\")\n\t\t\t\tmodules = [[null, modules, \"\"]];\n\t\t\tvar alreadyImportedModules = {};\n\t\t\tfor(var i = 0; i < this.length; i++) {\n\t\t\t\tvar id = this[i][0];\n\t\t\t\tif(typeof id === \"number\")\n\t\t\t\t\talreadyImportedModules[id] = true;\n\t\t\t}\n\t\t\tfor(i = 0; i < modules.length; i++) {\n\t\t\t\tvar item = modules[i];\n\t\t\t\t// skip already imported module\n\t\t\t\t// this implementation is not 100% perfect for weird media query combinations\n\t\t\t\t//  when a module is imported multiple times with different media queries.\n\t\t\t\t//  I hope this will never occur (Hey this way we have smaller bundles)\n\t\t\t\tif(typeof item[0] !== \"number\" || !alreadyImportedModules[item[0]]) {\n\t\t\t\t\tif(mediaQuery && !item[2]) {\n\t\t\t\t\t\titem[2] = mediaQuery;\n\t\t\t\t\t} else if(mediaQuery) {\n\t\t\t\t\t\titem[2] = \"(\" + item[2] + \") and (\" + mediaQuery + \")\";\n\t\t\t\t\t}\n\t\t\t\t\tlist.push(item);\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t\treturn list;\n\t};\n\n\n/***/ },\n/* 5 */\n/***/ function(module, exports, __webpack_require__) {\n\n\t/*\n\t\tMIT License http://www.opensource.org/licenses/mit-license.php\n\t\tAuthor Tobias Koppers @sokra\n\t*/\n\tvar stylesInDom = {},\n\t\tmemoize = function(fn) {\n\t\t\tvar memo;\n\t\t\treturn function () {\n\t\t\t\tif (typeof memo === \"undefined\") memo = fn.apply(this, arguments);\n\t\t\t\treturn memo;\n\t\t\t};\n\t\t},\n\t\tisOldIE = memoize(function() {\n\t\t\treturn /msie [6-9]\\b/.test(window.navigator.userAgent.toLowerCase());\n\t\t}),\n\t\tgetHeadElement = memoize(function () {\n\t\t\treturn document.head || document.getElementsByTagName(\"head\")[0];\n\t\t}),\n\t\tsingletonElement = null,\n\t\tsingletonCounter = 0,\n\t\tstyleElementsInsertedAtTop = [];\n\n\tmodule.exports = function(list, options) {\n\t\tif(false) {\n\t\t\tif(typeof document !== \"object\") throw new Error(\"The style-loader cannot be used in a non-browser environment\");\n\t\t}\n\n\t\toptions = options || {};\n\t\t// Force single-tag solution on IE6-9, which has a hard limit on the # of <style>\n\t\t// tags it will allow on a page\n\t\tif (typeof options.singleton === \"undefined\") options.singleton = isOldIE();\n\n\t\t// By default, add <style> tags to the bottom of <head>.\n\t\tif (typeof options.insertAt === \"undefined\") options.insertAt = \"bottom\";\n\n\t\tvar styles = listToStyles(list);\n\t\taddStylesToDom(styles, options);\n\n\t\treturn function update(newList) {\n\t\t\tvar mayRemove = [];\n\t\t\tfor(var i = 0; i < styles.length; i++) {\n\t\t\t\tvar item = styles[i];\n\t\t\t\tvar domStyle = stylesInDom[item.id];\n\t\t\t\tdomStyle.refs--;\n\t\t\t\tmayRemove.push(domStyle);\n\t\t\t}\n\t\t\tif(newList) {\n\t\t\t\tvar newStyles = listToStyles(newList);\n\t\t\t\taddStylesToDom(newStyles, options);\n\t\t\t}\n\t\t\tfor(var i = 0; i < mayRemove.length; i++) {\n\t\t\t\tvar domStyle = mayRemove[i];\n\t\t\t\tif(domStyle.refs === 0) {\n\t\t\t\t\tfor(var j = 0; j < domStyle.parts.length; j++)\n\t\t\t\t\t\tdomStyle.parts[j]();\n\t\t\t\t\tdelete stylesInDom[domStyle.id];\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t}\n\n\tfunction addStylesToDom(styles, options) {\n\t\tfor(var i = 0; i < styles.length; i++) {\n\t\t\tvar item = styles[i];\n\t\t\tvar domStyle = stylesInDom[item.id];\n\t\t\tif(domStyle) {\n\t\t\t\tdomStyle.refs++;\n\t\t\t\tfor(var j = 0; j < domStyle.parts.length; j++) {\n\t\t\t\t\tdomStyle.parts[j](item.parts[j]);\n\t\t\t\t}\n\t\t\t\tfor(; j < item.parts.length; j++) {\n\t\t\t\t\tdomStyle.parts.push(addStyle(item.parts[j], options));\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tvar parts = [];\n\t\t\t\tfor(var j = 0; j < item.parts.length; j++) {\n\t\t\t\t\tparts.push(addStyle(item.parts[j], options));\n\t\t\t\t}\n\t\t\t\tstylesInDom[item.id] = {id: item.id, refs: 1, parts: parts};\n\t\t\t}\n\t\t}\n\t}\n\n\tfunction listToStyles(list) {\n\t\tvar styles = [];\n\t\tvar newStyles = {};\n\t\tfor(var i = 0; i < list.length; i++) {\n\t\t\tvar item = list[i];\n\t\t\tvar id = item[0];\n\t\t\tvar css = item[1];\n\t\t\tvar media = item[2];\n\t\t\tvar sourceMap = item[3];\n\t\t\tvar part = {css: css, media: media, sourceMap: sourceMap};\n\t\t\tif(!newStyles[id])\n\t\t\t\tstyles.push(newStyles[id] = {id: id, parts: [part]});\n\t\t\telse\n\t\t\t\tnewStyles[id].parts.push(part);\n\t\t}\n\t\treturn styles;\n\t}\n\n\tfunction insertStyleElement(options, styleElement) {\n\t\tvar head = getHeadElement();\n\t\tvar lastStyleElementInsertedAtTop = styleElementsInsertedAtTop[styleElementsInsertedAtTop.length - 1];\n\t\tif (options.insertAt === \"top\") {\n\t\t\tif(!lastStyleElementInsertedAtTop) {\n\t\t\t\thead.insertBefore(styleElement, head.firstChild);\n\t\t\t} else if(lastStyleElementInsertedAtTop.nextSibling) {\n\t\t\t\thead.insertBefore(styleElement, lastStyleElementInsertedAtTop.nextSibling);\n\t\t\t} else {\n\t\t\t\thead.appendChild(styleElement);\n\t\t\t}\n\t\t\tstyleElementsInsertedAtTop.push(styleElement);\n\t\t} else if (options.insertAt === \"bottom\") {\n\t\t\thead.appendChild(styleElement);\n\t\t} else {\n\t\t\tthrow new Error(\"Invalid value for parameter 'insertAt'. Must be 'top' or 'bottom'.\");\n\t\t}\n\t}\n\n\tfunction removeStyleElement(styleElement) {\n\t\tstyleElement.parentNode.removeChild(styleElement);\n\t\tvar idx = styleElementsInsertedAtTop.indexOf(styleElement);\n\t\tif(idx >= 0) {\n\t\t\tstyleElementsInsertedAtTop.splice(idx, 1);\n\t\t}\n\t}\n\n\tfunction createStyleElement(options) {\n\t\tvar styleElement = document.createElement(\"style\");\n\t\tstyleElement.type = \"text/css\";\n\t\tinsertStyleElement(options, styleElement);\n\t\treturn styleElement;\n\t}\n\n\tfunction createLinkElement(options) {\n\t\tvar linkElement = document.createElement(\"link\");\n\t\tlinkElement.rel = \"stylesheet\";\n\t\tinsertStyleElement(options, linkElement);\n\t\treturn linkElement;\n\t}\n\n\tfunction addStyle(obj, options) {\n\t\tvar styleElement, update, remove;\n\n\t\tif (options.singleton) {\n\t\t\tvar styleIndex = singletonCounter++;\n\t\t\tstyleElement = singletonElement || (singletonElement = createStyleElement(options));\n\t\t\tupdate = applyToSingletonTag.bind(null, styleElement, styleIndex, false);\n\t\t\tremove = applyToSingletonTag.bind(null, styleElement, styleIndex, true);\n\t\t} else if(obj.sourceMap &&\n\t\t\ttypeof URL === \"function\" &&\n\t\t\ttypeof URL.createObjectURL === \"function\" &&\n\t\t\ttypeof URL.revokeObjectURL === \"function\" &&\n\t\t\ttypeof Blob === \"function\" &&\n\t\t\ttypeof btoa === \"function\") {\n\t\t\tstyleElement = createLinkElement(options);\n\t\t\tupdate = updateLink.bind(null, styleElement);\n\t\t\tremove = function() {\n\t\t\t\tremoveStyleElement(styleElement);\n\t\t\t\tif(styleElement.href)\n\t\t\t\t\tURL.revokeObjectURL(styleElement.href);\n\t\t\t};\n\t\t} else {\n\t\t\tstyleElement = createStyleElement(options);\n\t\t\tupdate = applyToTag.bind(null, styleElement);\n\t\t\tremove = function() {\n\t\t\t\tremoveStyleElement(styleElement);\n\t\t\t};\n\t\t}\n\n\t\tupdate(obj);\n\n\t\treturn function updateStyle(newObj) {\n\t\t\tif(newObj) {\n\t\t\t\tif(newObj.css === obj.css && newObj.media === obj.media && newObj.sourceMap === obj.sourceMap)\n\t\t\t\t\treturn;\n\t\t\t\tupdate(obj = newObj);\n\t\t\t} else {\n\t\t\t\tremove();\n\t\t\t}\n\t\t};\n\t}\n\n\tvar replaceText = (function () {\n\t\tvar textStore = [];\n\n\t\treturn function (index, replacement) {\n\t\t\ttextStore[index] = replacement;\n\t\t\treturn textStore.filter(Boolean).join('\\n');\n\t\t};\n\t})();\n\n\tfunction applyToSingletonTag(styleElement, index, remove, obj) {\n\t\tvar css = remove ? \"\" : obj.css;\n\n\t\tif (styleElement.styleSheet) {\n\t\t\tstyleElement.styleSheet.cssText = replaceText(index, css);\n\t\t} else {\n\t\t\tvar cssNode = document.createTextNode(css);\n\t\t\tvar childNodes = styleElement.childNodes;\n\t\t\tif (childNodes[index]) styleElement.removeChild(childNodes[index]);\n\t\t\tif (childNodes.length) {\n\t\t\t\tstyleElement.insertBefore(cssNode, childNodes[index]);\n\t\t\t} else {\n\t\t\t\tstyleElement.appendChild(cssNode);\n\t\t\t}\n\t\t}\n\t}\n\n\tfunction applyToTag(styleElement, obj) {\n\t\tvar css = obj.css;\n\t\tvar media = obj.media;\n\n\t\tif(media) {\n\t\t\tstyleElement.setAttribute(\"media\", media)\n\t\t}\n\n\t\tif(styleElement.styleSheet) {\n\t\t\tstyleElement.styleSheet.cssText = css;\n\t\t} else {\n\t\t\twhile(styleElement.firstChild) {\n\t\t\t\tstyleElement.removeChild(styleElement.firstChild);\n\t\t\t}\n\t\t\tstyleElement.appendChild(document.createTextNode(css));\n\t\t}\n\t}\n\n\tfunction updateLink(linkElement, obj) {\n\t\tvar css = obj.css;\n\t\tvar sourceMap = obj.sourceMap;\n\n\t\tif(sourceMap) {\n            // https://developer.mozilla.org/en/docs/Web/API/WindowBase64/Base64_encoding_and_decoding\n\t\t\tcss += \"\\n/*# sourceMappingURL=data:application/json;base64,\" + btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap)))) + \" */\";\n\t\t}\n\n\t\tvar blob = new Blob([css], { type: \"text/css\" });\n\n\t\tvar oldSrc = linkElement.href;\n\n\t\tlinkElement.href = URL.createObjectURL(blob);\n\n\t\tif(oldSrc)\n\t\t\tURL.revokeObjectURL(oldSrc);\n\t}\n\n\n/***/ },\n/* 6 */\n/***/ function(module, exports) {\n\n\t\"use strict\";\n\t/*\n\t * Escapes `\"` characters from string\n\t */\n\tfunction escapeString(str) {\n\t    return str.replace('\"', '\\\"');\n\t}\n\t/*\n\t * Determines if a value is an object\n\t */\n\tfunction isObject(value) {\n\t    var type = typeof value;\n\t    return !!value && (type == 'object');\n\t}\n\texports.isObject = isObject;\n\t/*\n\t * Gets constructor name of an object.\n\t *\n\t */\n\tfunction getObjectName(object) {\n\t    if (object === undefined) {\n\t        return '';\n\t    }\n\t    if (object === null || (typeof object === 'object' && !object.constructor)) {\n\t        return 'Object';\n\t    }\n\t    var funcNameRegex = /function ([^(]*)/;\n\t    var results = (funcNameRegex).exec((object).constructor.toString());\n\t    if (results && results.length > 1) {\n\t        return results[1];\n\t    } else {\n\t        return '';\n\t    }\n\t}\n\texports.getObjectName = getObjectName;\n\t/*\n\t * Gets type of an object. Returns \"null\" for null objects\n\t */\n\tfunction getType(object) {\n\t    if (object === null) {\n\t        return 'null';\n\t    }\n\t    return typeof object;\n\t}\n\texports.getType = getType;\n\t/*\n\t * Generates inline preview for a JavaScript object based on a value\n\t*/\n\tfunction getValuePreview(object, value) {\n\t    var type = getType(object);\n\t    if (type === 'null' || type === 'undefined') {\n\t        return type;\n\t    }\n\t    if (type === 'string') {\n\t        value = '\"' + escapeString(value) + '\"';\n\t    }\n\t    if (type === 'function') {\n\t        // Remove content of the function\n\t        return object.toString()\n\t            .replace(/[\\r\\n]/g, '')\n\t            .replace(/\\{.*\\}/, '') + '{…}';\n\t    }\n\t    return value;\n\t}\n\texports.getValuePreview = getValuePreview;\n\t/*\n\t * Generates inline preview for a JavaScript object\n\t*/\n\tfunction getPreview(object) {\n\t    var value = '';\n\t    if (isObject(object)) {\n\t        value = getObjectName(object);\n\t        if (Array.isArray(object))\n\t            value += '[' + object.length + ']';\n\t    }\n\t    else {\n\t        value = getValuePreview(object, object);\n\t    }\n\t    return value;\n\t}\n\texports.getPreview = getPreview;\n\t/*\n\t * Generates a prefixed CSS class name\n\t*/\n\tfunction cssClass(className) {\n\t    return \"json-formatter-\" + className;\n\t}\n\texports.cssClass = cssClass;\n\t/*\n\t  * Creates a new DOM element with given type and class\n\t  * TODO: move me to helpers\n\t*/\n\tfunction createElement(type, className, content) {\n\t    var el = document.createElement(type);\n\t    if (className) {\n\t        el.classList.add(cssClass(className));\n\t    }\n\t    if (content !== undefined) {\n\t        if (content instanceof Node) {\n\t            el.appendChild(content);\n\t        }\n\t        else {\n\t            el.appendChild(document.createTextNode(String(content)));\n\t        }\n\t    }\n\t    return el;\n\t}\n\texports.createElement = createElement;\n\n\n/***/ }\n/******/ ])\n});\n;\n//# sourceMappingURL=json-formatter.js.map\n\n</script>\n\n  <script>\n\n(function(f){if(typeof exports===\"object\"&&typeof module!==\"undefined\"){module.exports=f()}else if(typeof define===\"function\"&&define.amd){define([],f)}else{var g;if(typeof window!==\"undefined\"){g=window}else if(typeof global!==\"undefined\"){g=global}else if(typeof self!==\"undefined\"){g=self}else{g=this}g.JSONSchemaView = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require==\"function\"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error(\"Cannot find module '\"+o+\"'\");throw f.code=\"MODULE_NOT_FOUND\",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require==\"function\"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){\n'use strict';\n/*\n * Converts anyOf, allOf and oneOf to human readable string\n*/\nObject.defineProperty(exports, '__esModule', {\n  value: true\n});\nexports.convertXOf = convertXOf;\nexports._if = _if;\n\nfunction convertXOf(type) {\n  return type.substring(0, 3) + ' of';\n}\n\n/*\n * if condition for ES6 template strings\n * to be used only in template string\n *\n * @example mystr = `Random is ${_if(Math.random() > 0.5)`greater than 0.5``\n *\n * @param {boolean} condition\n *\n * @returns {function} the template function\n*/\n\nfunction _if(condition) {\n  return condition ? normal : empty;\n}\n\nfunction empty() {\n  return '';\n}\nfunction normal(template) {\n  for (var _len = arguments.length, expressions = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {\n    expressions[_key - 1] = arguments[_key];\n  }\n\n  return template.slice(1).reduce(function (accumulator, part, i) {\n    return accumulator + expressions[i] + part;\n  }, template[0]);\n}\n\n},{}],2:[function(require,module,exports){\n'use strict';\n\n/* globals JSONSchemaView */\n\nObject.defineProperty(exports, '__esModule', {\n  value: true\n});\n\nvar _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();\n\nvar _templateObject = _taggedTemplateLiteral(['\\n        <div class=\"any\">\\n          ', '\\n\\n          <span class=\"type type-any\">&lt;any&gt;</span>\\n\\n          ', '\\n\\t\\t \\n\\t\\t  ', '\\n\\t\\t  ', '\\n\\t\\t   ', '\\n        </div>\\n      '], ['\\n        <div class=\"any\">\\n          ', '\\n\\n          <span class=\"type type-any\">&lt;any&gt;</span>\\n\\n          ', '\\n\\t\\t \\n\\t\\t  ', '\\n\\t\\t  ', '\\n\\t\\t   ', '\\n        </div>\\n      ']),\n    _templateObject2 = _taggedTemplateLiteral(['\\n            <a class=\"title\"><span class=\"toggle-handle\"></span>', ' </a>\\n          '], ['\\n            <a class=\"title\"><span class=\"toggle-handle\"></span>', ' </a>\\n          ']),\n    _templateObject3 = _taggedTemplateLiteral(['\\n            <div class=\"inner description marked\">', '</div>\\n          '], ['\\n            <div class=\"inner description marked\">', '</div>\\n          ']),\n    _templateObject4 = _taggedTemplateLiteral(['\\n            <div class=\"inner required\">Required: ', '</div>\\n          '], ['\\n            <div class=\"inner required\">Required: ', '</div>\\n          ']),\n    _templateObject5 = _taggedTemplateLiteral(['\\n            <div class=\"inner default\">Default: ', '</div>\\n          '], ['\\n            <div class=\"inner default\">Default: ', '</div>\\n          ']),\n    _templateObject6 = _taggedTemplateLiteral(['\\n            <div class=\"inner pattern\">Pattern: ', '</div>\\n          '], ['\\n            <div class=\"inner pattern\">Pattern: ', '</div>\\n          ']),\n    _templateObject7 = _taggedTemplateLiteral(['\\n        <div class=\"primitive\">\\n          ', '\\n\\n            <span class=\"type\">', '</span>\\n\\n         \\n\\n          ', '\\n\\n          ', '\\n\\n          ', '\\n\\n          ', '\\n\\n          ', '\\n\\n          ', '\\n\\n          ', '\\n\\n          ', '\\n\\t\\t  \\n\\t\\t \\n\\t\\t  \\n\\t\\t   ', '\\n\\t\\t  ', '\\n\\t\\t   ', '\\n\\n          ', '\\n\\n          ', '\\n          ', '\\n          ', '\\n        </div>\\n      '], ['\\n        <div class=\"primitive\">\\n          ', '\\n\\n            <span class=\"type\">', '</span>\\n\\n         \\n\\n          ', '\\n\\n          ', '\\n\\n          ', '\\n\\n          ', '\\n\\n          ', '\\n\\n          ', '\\n\\n          ', '\\n\\n          ', '\\n\\t\\t  \\n\\t\\t \\n\\t\\t  \\n\\t\\t   ', '\\n\\t\\t  ', '\\n\\t\\t   ', '\\n\\n          ', '\\n\\n          ', '\\n          ', '\\n          ', '\\n        </div>\\n      ']),\n    _templateObject8 = _taggedTemplateLiteral(['\\n            <span class=\"format\">(', ')</span>\\n          '], ['\\n            <span class=\"format\">(', ')</span>\\n          ']),\n    _templateObject9 = _taggedTemplateLiteral(['\\n            <span class=\"range minimum\">minimum:', '</span>\\n          '], ['\\n            <span class=\"range minimum\">minimum:', '</span>\\n          ']),\n    _templateObject10 = _taggedTemplateLiteral(['\\n            <span class=\"range exclusiveMinimum\">(ex)minimum:', '</span>\\n          '], ['\\n            <span class=\"range exclusiveMinimum\">(ex)minimum:', '</span>\\n          ']),\n    _templateObject11 = _taggedTemplateLiteral(['\\n            <span class=\"range maximum\">maximum:', '</span>\\n          '], ['\\n            <span class=\"range maximum\">maximum:', '</span>\\n          ']),\n    _templateObject12 = _taggedTemplateLiteral(['\\n            <span class=\"range exclusiveMaximum\">(ex)maximum:', '</span>\\n          '], ['\\n            <span class=\"range exclusiveMaximum\">(ex)maximum:', '</span>\\n          ']),\n    _templateObject13 = _taggedTemplateLiteral(['\\n            <span class=\"range minLength\">minLength:', '</span>\\n          '], ['\\n            <span class=\"range minLength\">minLength:', '</span>\\n          ']),\n    _templateObject14 = _taggedTemplateLiteral(['\\n            <span class=\"range maxLength\">maxLength:', '</span>\\n          '], ['\\n            <span class=\"range maxLength\">maxLength:', '</span>\\n          ']),\n    _templateObject15 = _taggedTemplateLiteral(['\\n            <div class=\"inner required\">Required</div>\\n          '], ['\\n            <div class=\"inner required\">Required</div>\\n          ']),\n    _templateObject16 = _taggedTemplateLiteral(['\\n            ', '\\n          '], ['\\n            ', '\\n          ']),\n    _templateObject17 = _taggedTemplateLiteral(['', ''], ['', '']),\n    _templateObject18 = _taggedTemplateLiteral(['\\n        <div class=\"array\">\\n          <a class=\"title\"><span class=\"toggle-handle\"></span>', '<span class=\"opening bracket\">[</span>', '</a>\\n          ', '\\n          <div class=\"inner\">\\n            ', '\\n          </div>\\n\\n          ', '\\n\\n          ', '\\n          ', '\\n          ', '\\n\\n          ', '\\n        </div>\\n      '], ['\\n        <div class=\"array\">\\n          <a class=\"title\"><span class=\"toggle-handle\"></span>', '<span class=\"opening bracket\">[</span>', '</a>\\n          ', '\\n          <div class=\"inner\">\\n            ', '\\n          </div>\\n\\n          ', '\\n\\n          ', '\\n          ', '\\n          ', '\\n\\n          ', '\\n        </div>\\n      ']),\n    _templateObject19 = _taggedTemplateLiteral(['<span class=\"closing bracket\">]</span>'], ['<span class=\"closing bracket\">]</span>']),\n    _templateObject20 = _taggedTemplateLiteral(['\\n          <span>\\n            <span title=\"items range\">(', '..', ')</span>\\n            ', '\\n          </span>\\n          '], ['\\n          <span>\\n            <span title=\"items range\">(', '..', ')</span>\\n            ', '\\n          </span>\\n          ']),\n    _templateObject21 = _taggedTemplateLiteral(['<span title=\"unique\" class=\"uniqueItems\">♦</span>'], ['<span title=\"unique\" class=\"uniqueItems\">♦</span>']),\n    _templateObject22 = _taggedTemplateLiteral(['\\n              <div class=\"description\">', '</div>\\n            '], ['\\n              <div class=\"description\">', '</div>\\n            ']),\n    _templateObject23 = _taggedTemplateLiteral(['\\n          <span class=\"closing bracket\">]</span>\\n          '], ['\\n          <span class=\"closing bracket\">]</span>\\n          ']),\n    _templateObject24 = _taggedTemplateLiteral(['\\n        <div class=\"object\">\\n          <a class=\"title\"><span\\n            class=\"toggle-handle\"></span>', ' <span\\n            class=\"opening brace\">{</span>', '</a>\\n\\n          <div class=\"inner\">\\n            ', '\\n            <!-- children go here -->\\n\\t\\t  \\n\\t\\t   ', '\\n\\t\\t  ', '\\n\\t\\t  \\n\\t\\t\\t', '\\n          </div>\\n\\n          ', '\\n\\n          ', '\\n          ', '\\n          ', '\\n\\n          ', '\\n        </div>\\n      '], ['\\n        <div class=\"object\">\\n          <a class=\"title\"><span\\n            class=\"toggle-handle\"></span>', ' <span\\n            class=\"opening brace\">{</span>', '</a>\\n\\n          <div class=\"inner\">\\n            ', '\\n            <!-- children go here -->\\n\\t\\t  \\n\\t\\t   ', '\\n\\t\\t  ', '\\n\\t\\t  \\n\\t\\t\\t', '\\n          </div>\\n\\n          ', '\\n\\n          ', '\\n          ', '\\n          ', '\\n\\n          ', '\\n        </div>\\n      ']),\n    _templateObject25 = _taggedTemplateLiteral(['\\n              <span class=\"closing brace\" ng-if=\"isCollapsed\">}</span>\\n          '], ['\\n              <span class=\"closing brace\" ng-if=\"isCollapsed\">}</span>\\n          ']),\n    _templateObject26 = _taggedTemplateLiteral(['\\n            <div class=\"required\">Required: ', '</div>\\n          '], ['\\n            <div class=\"required\">Required: ', '</div>\\n          ']),\n   _templateObject266 = _taggedTemplateLiteral(['\\n            <div class=\"required\">Required: ', '</div>\\n          '], ['\\n            <div class=\"required\">Required: ', '</div>\\n          ']),\n    _templateObject27 = _taggedTemplateLiteral(['\\n            <div class=\"default\">Default: ', '</div>\\n          '], ['\\n            <div class=\"default\">Default: ', '</div>\\n          ']),\n    _templateObject28 = _taggedTemplateLiteral(['\\n              <div class=\"pattern\">Pattern: ', '</div>\\n            '], ['\\n              <div class=\"pattern\">Pattern: ', '</div>\\n            ']),\n    _templateObject29 = _taggedTemplateLiteral(['\\n          <span class=\"closing brace\">}</span>\\n          '], ['\\n          <span class=\"closing brace\">}</span>\\n          ']),\n    _templateObject30 = _taggedTemplateLiteral(['\\n        <div class=\"inner enums\">\\n          <b>Enum:</b>\\n        </div>\\n      '], ['\\n        <div class=\"inner enums\">\\n          <b>Enum:</b>\\n        </div>\\n      ']),\n    _templateObject31 = _taggedTemplateLiteral(['\\n              <div class=\"inner example\">Example: ', '</div>\\n            '], ['\\n              <div class=\"inner example\">Example: ', '</div>\\n            ']),\n    _templateObject32 = _taggedTemplateLiteral(['\\n            <div class=\"inner readOnly\">Read Only</div>\\n          '], ['\\n            <div class=\"inner readOnly\">Read Only</div>\\n          ']),\n    _templateObject33 = _taggedTemplateLiteral(['\\n            <div class=\"inner nullable\">Nullable</div>\\n          '], ['\\n            <div class=\"inner nullable\">Nullable</div>\\n          ']);\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }\n\nfunction _taggedTemplateLiteral(strings, raw) { return Object.freeze(Object.defineProperties(strings, { raw: { value: Object.freeze(raw) } })); }\n\nvar _helpersJs = require('./helpers.js');\n\n/**\n * @class JSONSchemaView\n *\n * A pure JavaScript component for rendering JSON Schema in HTML.\n*/\n\nvar JSONSchemaView = (function () {\n\n  /**\n   * @param {object} schema The JSON Schema object\n   *\n   * @param {number} [open=1] his number indicates up to how many levels the\n   * rendered tree should expand. Set it to `0` to make the whole tree collapsed\n   * or set it to `Infinity` to expand the tree deeply\n   * @param {object} options.\n   *  theme {string}: one of the following options: ['dark']\n  */\n\n  function JSONSchemaView(schema, open) {\n    var _this = this;\n\n    var options = arguments.length <= 2 || arguments[2] === undefined ? { theme: null } : arguments[2];\n\n    _classCallCheck(this, JSONSchemaView);\n\n    this.schema = schema; //console.log(schema);\n    this.open = open;\n    this.options = options;\n    this.isCollapsed = open <= 0;\n\n    // if schema is an empty object which means any JSON\n    this.isAny = typeof schema === 'object' && !Array.isArray(schema) && !Object.keys(schema).filter(function (k) {\n      return ['title', 'description'].indexOf(k) === -1;\n    }).length;\n\n    // Determine if a schema is an array\n    this.isArray = !this.isAny && this.schema && this.schema.type === 'array';\n\n    this.isObject = this.schema && (this.schema.type === 'object' || this.schema.properties || this.schema.anyOf || this.schema.oneof || this.schema.allOf);\n\n    // Determine if a schema is a primitive\n    this.isPrimitive = !this.isAny && !this.isArray && !this.isObject;\n\n    //\n    this.showToggle = this.schema.description || this.schema.title || this.isPrimitive && (this.schema.minimum || this.schema.maximum || this.schema.exclusiveMinimum || this.schema.exclusiveMaximum);\n\n    // populate isRequired property down to properties\n    if (this.schema && Array.isArray(this.schema.required)) {\n      this.schema.required.forEach(function (requiredProperty) {\n        if (typeof _this.schema.properties[requiredProperty] === 'object') {\n          _this.schema.properties[requiredProperty].isRequired = true;\n        }\n      });\n    }\n\n    // Determine if a schema is a read-only\n    this.isReadOnly = this.schema && this.schema.readOnly === true;\n\n    // Determine if a schema is nullable\n    this.isNullable = this.schema && this.schema[\"x-nullable\"] === true;\n  }\n\n  /*\n   * Returns the template with populated properties.\n   * This template does not have the children\n  */\n\n  _createClass(JSONSchemaView, [{\n    key: 'template',\n    value: function template() {\n      if (!this.schema) {\n        return '';\n      }\n\n      return ('\\n      <!-- Any -->\\n      ' + (0, _helpersJs._if)(this.isAny)(_templateObject, (0, _helpersJs._if)(this.showToggle)(_templateObject2, this.schema.title || ''), (0, _helpersJs._if)(this.schema.description && !this.isCollapsed)(_templateObject3, marked(this.schema.description || \"\")), (0, _helpersJs._if)(this.schema.required && !this.isCollapsed)(_templateObject4, this.schema.required), (0, _helpersJs._if)(this.schema['default'] && !this.isCollapsed)(_templateObject5, this.schema['default']), (0, _helpersJs._if)(this.schema.pattern && !this.isCollapsed)(_templateObject6, this.schema.pattern),  (0, _helpersJs._if)(this.schema.example && !this.isCollapsed)(_templateObject31, this.schema.example)) + '\\n\\n      <!-- Primitive -->\\n      ' + (0, _helpersJs._if)(this.isPrimitive)(_templateObject7, (0, _helpersJs._if)(this.showToggle)(_templateObject2, this.schema.title || ''), this.schema.type, (0, _helpersJs._if)(!this.isCollapsed && this.schema.format)(_templateObject8, this.schema.format), (0, _helpersJs._if)(!this.isCollapsed && this.schema.minimum)(_templateObject9, this.schema.minimum), (0, _helpersJs._if)(!this.isCollapsed && this.schema.exclusiveMinimum)(_templateObject10, this.schema.exclusiveMinimum), (0, _helpersJs._if)(!this.isCollapsed && this.schema.maximum)(_templateObject11, this.schema.maximum), (0, _helpersJs._if)(!this.isCollapsed && this.schema.exclusiveMaximum)(_templateObject12, this.schema.exclusiveMaximum), (0, _helpersJs._if)(!this.isCollapsed && this.schema.minLength)(_templateObject13, this.schema.minLength), (0, _helpersJs._if)(!this.isCollapsed && this.schema.maxLength)(_templateObject14, this.schema.maxLength), (0, _helpersJs._if)(this.schema.description && !this.isCollapsed)(_templateObject3, marked(this.schema.description || \"\")), (0, _helpersJs._if)(this.schema.required && !this.isCollapsed)(_templateObject15), (0, _helpersJs._if)(this.schema.readOnly && !this.isCollapsed)(_templateObject32), (0, _helpersJs._if)(this.schema[\"x-nullable\"] === true && !this.isCollapsed)(_templateObject33), (0, _helpersJs._if)(this.schema['default'] && !this.isCollapsed)(_templateObject5, this.schema['default']), (0, _helpersJs._if)(this.schema.pattern && !this.isCollapsed)(_templateObject6, this.schema.pattern), (0, _helpersJs._if)(this.schema.example && !this.isCollapsed)(_templateObject31, this.schema.example), (0, _helpersJs._if)(!this.isCollapsed && this.schema['enum'])(_templateObject16, this['enum'](this.schema, this.isCollapsed, this.open)), (0, _helpersJs._if)(this.schema.allOf && !this.isCollapsed)(_templateObject17, this.xOf(this.schema, 'allOf')), (0, _helpersJs._if)(this.schema.oneOf && !this.isCollapsed)(_templateObject17, this.xOf(this.schema, 'oneOf')), (0, _helpersJs._if)(this.schema.anyOf && !this.isCollapsed)(_templateObject17, this.xOf(this.schema, 'anyOf'))) + '\\n\\n\\n      <!-- Array -->\\n      ' + (0, _helpersJs._if)(this.isArray)(_templateObject18, this.schema.title || '', (0, _helpersJs._if)(this.isCollapsed)(_templateObject19), (0, _helpersJs._if)(!this.isCollapsed && (this.schema.uniqueItems || this.schema.minItems || this.schema.maxItems))(_templateObject20, this.schema.minItems || 0, this.schema.maxItems || '∞', (0, _helpersJs._if)(!this.isCollapsed && this.schema.uniqueItems)(_templateObject21)), (0, _helpersJs._if)(!this.isCollapsed && this.schema.description)(_templateObject22, marked(this.schema.description || \"\")), (0, _helpersJs._if)(!this.isCollapsed && this.schema['enum'])(_templateObject16, this['enum'](this.schema, this.isCollapsed, this.open)), (0, _helpersJs._if)(this.schema.allOf && !this.isCollapsed)(_templateObject17, this.xOf(this.schema, 'allOf')), (0, _helpersJs._if)(this.schema.oneOf && !this.isCollapsed)(_templateObject17, this.xOf(this.schema, 'oneOf')), (0, _helpersJs._if)(this.schema.anyOf && !this.isCollapsed)(_templateObject17, this.xOf(this.schema, 'anyOf')), (0, _helpersJs._if)(!this.isCollapsed)(_templateObject23)) + '\\n\\n      <!-- Object -->\\n      ' + (0, _helpersJs._if)(!this.isPrimitive && !this.isArray && !this.isAny)(_templateObject24, this.schema.title || '', (0, _helpersJs._if)(this.isCollapsed)(_templateObject25), (0, _helpersJs._if)(!this.isCollapsed && this.schema.description)(_templateObject22, marked(this.schema.description || \"\")),\n\n        (0, _helpersJs._if)(this.schema.required && !this.isCollapsed && this.options.isBodyParam != true)(_templateObject26, this.schema.required),\n        (0, _helpersJs._if)(this.schema.required && !this.isCollapsed && this.options.isBodyParam == true)(_templateObject266, this.schema.required),\n\n        (0, _helpersJs._if)(this.schema['default'] && !this.isCollapsed)(_templateObject27, this.schema['default']),\n        (0, _helpersJs._if)(!this.isCollapsed && this.schema.pattern)(_templateObject28, this.schema.pattern),\n        (0, _helpersJs._if)(!this.isCollapsed && this.schema.example)(_templateObject31, \"<pre>\" + JSON.stringify(this.schema.example, null, 4) + \"</pre>\"),\n        (0, _helpersJs._if)(!this.isCollapsed && this.schema['enum'])(_templateObject16, this['enum'](this.schema, this.isCollapsed, this.open)), (0, _helpersJs._if)(this.schema.allOf && !this.isCollapsed)(_templateObject17, this.xOf(this.schema, 'allOf')), (0, _helpersJs._if)(this.schema.oneOf && !this.isCollapsed)(_templateObject17, this.xOf(this.schema, 'oneOf')), (0, _helpersJs._if)(this.schema.anyOf && !this.isCollapsed)(_templateObject17, this.xOf(this.schema, 'anyOf')), (0, _helpersJs._if)(!this.isCollapsed)(_templateObject29)) + '\\n').replace(/\\s*\\n/g, '\\n').replace(/(\\<\\!\\-\\-).+/g, '').trim();\n    }\n\n    /*\n     * Template for oneOf, anyOf and allOf\n    */\n  }, {\n    key: 'xOf',\n    value: function xOf(schema, type) {\n      return '\\n      <div class=\"inner ' + type + '\">\\n        <b>' + (0, _helpersJs.convertXOf)(type) + ':</b>\\n      </div>\\n    ';\n    }\n\n    /*\n     * Template for enums\n    */\n  }, {\n    key: 'enum',\n    value: function _enum(schema, isCollapsed, open) {\n      return '\\n      ' + (0, _helpersJs._if)(!isCollapsed && schema['enum'])(_templateObject30) + '\\n    ';\n    }\n\n    /*\n     * Toggles the 'collapsed' state\n    */\n  }, {\n    key: 'toggle',\n    value: function toggle() {\n      this.isCollapsed = !this.isCollapsed;\n      this.render();\n    }\n\n    /*\n     * Renders the element and returns it\n    */\n  }, {\n    key: 'render',\n    value: function render() {\n      if (!this.element) {\n        this.element = document.createElement('div');\n        this.element.classList.add('json-schema-view');\n      }\n\n      if (this.isCollapsed) {\n        this.element.classList.add('collapsed');\n      } else {\n        this.element.classList.remove('collapsed');\n      }\n\n      if (this.options.theme) {\n        this.element.classList.add('json-schema-view-' + this.options.theme);\n      }\n\n      this.element.innerHTML = this.template();\n\n      if (!this.schema) {\n        return this.element;\n      }\n\n      if (!this.isCollapsed) {\n        this.appendChildren(this.element);\n      }\n\n      // add event listener for toggling\n      if (this.element.querySelector('a.title')) {\n        this.element.querySelector('a.title').addEventListener('click', this.toggle.bind(this));\n      }\n      return this.element;\n    }\n\n    /*\n     * Appends children to given element based on current schema\n    */\n  }, {\n    key: 'appendChildren',\n    value: function appendChildren(element) {\n      var _this2 = this;\n\n      var inner = element.querySelector('.inner');\n\n      if (!inner) {\n        return;\n      }\n\n      if (this.schema['enum']) {\n        var tempDiv = document.createElement('span');;\n        tempDiv.classList.add('inner');\n        tempDiv.innerHTML = '<code>' + this.schema['enum'].join('</code>, <code>') + '</code>';\n        element.querySelector('.enums.inner').appendChild(tempDiv);\n      }\n\n      if (this.isArray) {\n        var view = new JSONSchemaView(this.schema.items, Infinity);\n        inner.appendChild(view.render());\n      }\n\n      if (typeof this.schema.properties === 'object') {\n        Object.keys(this.schema.properties).forEach(function (propertyName) {\n          var property = _this2.schema.properties[propertyName];\n          var tempDiv = document.createElement('div');;\n          tempDiv.innerHTML = '<div class=\"property\">\\n          <span class=\"name\">' + propertyName + ':</span>\\n        </div>';\n          var view = new JSONSchemaView(property, _this2.open - 1);\n          tempDiv.querySelector('.property').appendChild(view.render());\n\n          inner.appendChild(tempDiv.querySelector('.property'));\n        });\n      }\n\n      if (this.schema.allOf) {\n        appendXOf.call(this, 'allOf');\n      }\n      if (this.schema.oneOf) {\n        appendXOf.call(this, 'oneOf');\n      }\n      if (this.schema.anyOf) {\n        appendXOf.call(this, 'anyOf');\n      }\n\n      function appendXOf(type) {\n        var _this3 = this;\n\n        var innerAllOf = element.querySelector('.inner.' + type);\n\n        this.schema[type].forEach(function (schema) {\n          var inner = document.createElement('div');\n          inner.classList.add('inner');\n          var view = new JSONSchemaView(schema, _this3.open - 1);\n          inner.appendChild(view.render());\n          innerAllOf.appendChild(inner);\n        });\n      }\n    }\n  }]);\n\n  return JSONSchemaView;\n})();\n\nexports['default'] = JSONSchemaView;\nmodule.exports = exports['default'];\n\n},{\"./helpers.js\":1}]},{},[2])(2)\n});\n//# sourceMappingURL=data:application/json;charset:utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy9icm93c2VyaWZ5L25vZGVfbW9kdWxlcy9icm93c2VyLXBhY2svX3ByZWx1ZGUuanMiLCJDOi9Vc2Vycy9qYW1lc2hpL0Rlc2t0b3AvanNvbi1zY2hlbWEtdmlldy1qcy1tYXN0ZXIvc3JjL2hlbHBlcnMuanMiLCJDOi9Vc2Vycy9qYW1lc2hpL0Rlc2t0b3AvanNvbi1zY2hlbWEtdmlldy1qcy1tYXN0ZXIvc3JjL2luZGV4LmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBO0FDQUEsWUFBWSxDQUFDOzs7Ozs7Ozs7O0FBSU4sU0FBUyxVQUFVLENBQUMsSUFBSSxFQUFFO0FBQy9CLFNBQU8sSUFBSSxDQUFDLFNBQVMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLEdBQUcsS0FBSyxDQUFDO0NBQ3JDOzs7Ozs7Ozs7Ozs7O0FBWU0sU0FBUyxHQUFHLENBQUMsU0FBUyxFQUFFO0FBQzdCLFNBQU8sU0FBUyxHQUFHLE1BQU0sR0FBRyxLQUFLLENBQUM7Q0FDbkM7O0FBQ0QsU0FBUyxLQUFLLEdBQUU7QUFDZCxTQUFPLEVBQUUsQ0FBQztDQUNYO0FBQ0QsU0FBUyxNQUFNLENBQUUsUUFBUSxFQUFrQjtvQ0FBYixXQUFXO0FBQVgsZUFBVzs7O0FBQ3ZDLFNBQU8sUUFBUSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsVUFBQyxXQUFXLEVBQUUsSUFBSSxFQUFFLENBQUMsRUFBSztBQUN4RCxXQUFPLFdBQVcsR0FBRyxXQUFXLENBQUMsQ0FBQyxDQUFDLEdBQUcsSUFBSSxDQUFDO0dBQzVDLEVBQUUsUUFBUSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7Q0FDakI7OztBQzVCRCxZQUFZLENBQUM7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozt5QkFPTixjQUFjOzs7Ozs7OztJQVFBLGNBQWM7Ozs7Ozs7Ozs7OztBQVd0QixXQVhRLGNBQWMsQ0FXckIsTUFBTSxFQUFFLElBQUks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LENBQUMsTUFBTSxFQUFFLE9BQUssSUFBSSxHQUFHLENBQUMsQ0FBQyxDQUFDO0FBQ3ZELGVBQUssQ0FBQyxXQUFXLENBQUMsSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDLENBQUM7QUFDakMsb0JBQVUsQ0FBQyxXQUFXLENBQUMsS0FBSyxDQUFDLENBQUM7U0FDL0IsQ0FBQyxDQUFDO09BQ0o7S0FDRjs7O1NBOVZrQixjQUFjOzs7cUJBQWQsY0FBYyIsImZpbGUiOiJnZW5lcmF0ZWQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlc0NvbnRlbnQiOlsiKGZ1bmN0aW9uIGUodCxuLHIpe2Z1bmN0aW9uIHMobyx1KXtpZighbltvXSl7aWYoIXRbb10pe3ZhciBhPXR5cGVvZiByZXF1aXJlPT1cImZ1bmN0aW9uXCImJnJlcXVpcmU7aWYoIXUmJmEpcmV0dXJuIGEobywhMCk7aWYoaSlyZXR1cm4gaShvLCEwKTt2YXIgZj1uZXcgRXJyb3IoXCJDYW5ub3QgZmluZCBtb2R1bGUgJ1wiK28rXCInXCIpO3Rocm93IGYuY29kZT1cIk1PRFVMRV9OT1RfRk9VTkRcIixmfXZhciBsPW5bb109e2V4cG9ydHM6e319O3Rbb11bMF0uY2FsbChsLmV4cG9ydHMsZnVuY3Rpb24oZSl7dmFyIG49dFtvXVsxXVtlXTtyZXR1cm4gcyhuP246ZSl9LGwsbC5leHBvcnRzLGUsdCxuLHIpfXJldHVybiBuW29dLmV4cG9ydHN9dmFyIGk9dHlwZW9mIHJlcXVpcmU9PVwiZnVuY3Rpb25cIiYmcmVxdWlyZTtmb3IodmFyIG89MDtvPHIubGVuZ3RoO28rKylzKHJbb10pO3JldHVybiBzfSkiLCIndXNlIHN0cmljdCc7XG4vKlxuICogQ29udmVydHMgYW55T2YsIGFsbE9mIGFuZCBvbmVPZiB0byBodW1hbiByZWFkYWJsZSBzdHJpbmdcbiovXG5leHBvcnQgZnVuY3Rpb24gY29udmVydFhPZih0eXBlKSB7XG4gIHJldHVybiB0eXBlLnN1YnN0cmluZygwLCAzKSArICcgb2YnO1xufVxuXG4vKlxuICogaWYgY29uZGl0aW9uIGZvciBFUzYgdGVtcGxhdGUgc3RyaW5nc1xuICogdG8gYmUgdXNlZCBvbmx5IGluIHRlbXBsYXRlIHN0cmluZ1xuICpcbiAqIEBleGFtcGxlIG15c3RyID0gYFJhbmRvbSBpcyAke19pZihNYXRoLnJhbmRvbSgpID4gMC41KWBncmVhdGVyIHRoYW4gMC41YGBcbiAqXG4gKiBAcGFyYW0ge2Jvb2xlYW59IGNvbmRpdGlvblxuICpcbiAqIEByZXR1cm5zIHtmdW5jdGlvbn0gdGhlIHRlbXBsYXRlIGZ1bmN0aW9uXG4qL1xuZXhwb3J0IGZ1bmN0aW9uIF9pZihjb25kaXRpb24pIHtcbiAgcmV0dXJuIGNvbmRpdGlvbiA/IG5vcm1hbCA6IGVtcHR5O1xufVxuZnVuY3Rpb24gZW1wdHkoKXtcbiAgcmV0dXJuICcnO1xufVxuZnVuY3Rpb24gbm9ybWFsICh0ZW1wbGF0ZSwgLi4uZXhwcmVzc2lvbnMpIHtcbiAgcmV0dXJuIHRlbXBsYXRlLnNsaWNlKDEpLnJlZHVjZSgoYWNjdW11bGF0b3IsIHBhcnQsIGkpID0+IHtcbiAgICByZXR1cm4gYWNjdW11bGF0b3IgKyBleHByZXNzaW9uc1tpXSArIHBhcnQ7XG4gIH0sIHRlbXBsYXRlWzBdKTtcbn0iLCIndXNlIHN0cmljdCc7XG5cbi8qIGdsb2JhbHMgSlNPTlNjaGVtYVZpZXcgKi9cblxuaW1wb3J0IHtcbiAgY29udmVydFhPZixcbiAgX2lmXG59IGZyb20gJy4vaGVscGVycy5qcyc7XG5cblxuLyoqXG4gKiBAY2xhc3MgSlNPTlNjaGVtYVZpZXdcbiAqXG4gKiBBIHB1cmUgSmF2YVNjcmlwdCBjb21wb25lbnQgZm9yIHJlbmRlcmluZyBKU09OIFNjaGVtYSBpbiBIVE1MLlxuKi9cbmV4cG9ydCBkZWZhdWx0IGNsYXNzIEpTT05TY2hlbWFWaWV3IHtcblxuICAvKipcbiAgICogQHBhcmFtIHtvYmplY3R9IHNjaGVtYSBUaGUgSlNPTiBTY2hlbWEgb2JqZWN0XG4gICAqXG4gICAqIEBwYXJhbSB7bnVtYmVyfSBbb3Blbj0xXSBoaXMgbnVtYmVyIGluZGljYXRlcyB1cCB0byBob3cgbWFueSBsZXZlbHMgdGhlXG4gICAqIHJlbmRlcmVkIHRyZWUgc2hvdWxkIGV4cGFuZC4gU2V0IGl0IHRvIGAwYCB0byBtYWtlIHRoZSB3aG9sZSB0cmVlIGNvbGxhcHNlZFxuICAgKiBvciBzZXQgaXQgdG8gYEluZmluaXR5YCB0byBleHBhbmQgdGhlIHRyZWUgZGVlcGx5XG4gICAqIEBwYXJhbSB7b2JqZWN0fSBvcHRpb25zLlxuICAgKiAgdGhlbWUge3N0cmluZ306IG9uZSBvZiB0aGUgZm9sbG93aW5nIG9wdGlvbnM6IFsnZGFyayddXG4gICovXG4gIGNvbnN0cnVjdG9yKHNjaGVtYSwgb3Blbiwgb3B0aW9ucyA9IHt0aGVtZTogbnVsbH0pIHtcbiAgICB0aGlzLnNjaGVtYSA9IHNjaGVtYTtcbiAgICB0aGlzLm9wZW4gPSBvcGVuO1xuICAgIHRoaXMub3B0aW9ucyA9IG9wdGlvbnM7XG4gICAgdGhpcy5pc0NvbGxhcHNlZCA9IG9wZW4gPD0gMDtcblxuICAgIC8vIGlmIHNjaGVtYSBpcyBhbiBlbXB0eSBvYmplY3Qgd2hpY2ggbWVhbnMgYW55IEpPU05cbiAgICB0aGlzLmlzQW55ID0gdHlwZW9mIHNjaGVtYSA9PT0gJ29iamVjdCcgJiZcbiAgICAgICFBcnJheS5pc0FycmF5KHNjaGVtYSkgJiZcbiAgICAgICFPYmplY3Qua2V5cyhzY2hlbWEpXG4gICAgICAuZmlsdGVyKGs9PiBbJ3RpdGxlJywgJ2Rlc2NyaXB0aW9uJ10uaW5kZXhPZihrKSA9PT0gLTEpLmxlbmd0aDtcblxuICAgIC8vIERldGVybWluZSBpZiBhIHNjaGVtYSBpcyBhbiBhcnJheVxuICAgIHRoaXMuaXNBcnJheSA9ICF0aGlzLmlzQW55ICYmIHRoaXMuc2NoZW1hICYmIHRoaXMuc2NoZW1hLnR5cGUgPT09ICdhcnJheSc7XG5cbiAgICB0aGlzLmlzT2JqZWN0ID0gdGhpcy5zY2hlbWEgJiZcbiAgICAgICh0aGlzLnNjaGVtYS50eXBlID09PSAnb2JqZWN0JyB8fFxuICAgICAgIHRoaXMuc2NoZW1hLnByb3BlcnRpZXMgfHxcbiAgICAgICB0aGlzLnNjaGVtYS5hbnlPZiB8fFxuICAgICAgIHRoaXMuc2NoZW1hLm9uZW9mIHx8XG4gICAgICAgdGhpcy5zY2hlbWEuYWxsT2YpO1xuXG4gICAgLy8gRGV0ZXJtaW5lIGlmIGEgc2NoZW1hIGlzIGEgcHJpbWl0aXZlXG4gICAgdGhpcy5pc1ByaW1pdGl2ZSA9ICF0aGlzLmlzQW55ICYmICF0aGlzLmlzQXJyYXkgJiYgIXRoaXMuaXNPYmplY3Q7XG5cbiAgICAvL1xuICAgIHRoaXMuc2hvd1RvZ2dsZSA9IHRoaXMuc2NoZW1hLmRlc2NyaXB0aW9uIHx8XG4gICAgICB0aGlzLnNjaGVtYS50aXRsZSB8fFxuICAgICAgKHRoaXMuaXNQcmltaXRpdmUgJiYgKFxuICAgICAgICB0aGlzLnNjaGVtYS5taW5pbXVtIHx8XG4gICAgICAgIHRoaXMuc2NoZW1hLm1heGltdW0gfHxcbiAgICAgICAgdGhpcy5zY2hlbWEuZXhjbHVzaXZlTWluaW11bSB8fFxuICAgICAgICB0aGlzLnNjaGVtYS5leGNsdXNpdmVNYXhpbXVtKVxuICAgICAgKTtcblxuICAgIC8vIHBvcHVsYXRlIGlzUmVxdWlyZWQgcHJvcGVydHkgZG93biB0byBwcm9wZXJ0aWVzXG4gICAgaWYgKHRoaXMuc2NoZW1hICYmIEFycmF5LmlzQXJyYXkodGhpcy5zY2hlbWEucmVxdWlyZWQpKSB7XG4gICAgICB0aGlzLnNjaGVtYS5yZXF1aXJlZC5mb3JFYWNoKHJlcXVpcmVkUHJvcGVydHkgPT4ge1xuICAgICAgICBpZiAodHlwZW9mIHRoaXMuc2NoZW1hLnByb3BlcnRpZXNbcmVxdWlyZWRQcm9wZXJ0eV0gPT09ICdvYmplY3QnKSB7XG4gICAgICAgICAgdGhpcy5zY2hlbWEucHJvcGVydGllc1tyZXF1aXJlZFByb3BlcnR5XS5pc1JlcXVpcmVkID0gdHJ1ZTtcbiAgICAgICAgfVxuICAgICAgfSk7XG4gICAgfVxuICB9XG5cbiAgLypcbiAgICogUmV0dXJucyB0aGUgdGVtcGxhdGUgd2l0aCBwb3B1bGF0ZWQgcHJvcGVydGllcy5cbiAgICogVGhpcyB0ZW1wbGF0ZSBkb2VzIG5vdCBoYXZlIHRoZSBjaGlsZHJlblxuICAqL1xuICB0ZW1wbGF0ZSgpIHtcbiAgICBpZiAoIXRoaXMuc2NoZW1hKSB7XG4gICAgICByZXR1cm4gJyc7XG4gICAgfVxuXG4gICAgcmV0dXJuIGBcbiAgICAgIDwhLS0gQW55IC0tPlxuICAgICAgJHtfaWYodGhpcy5pc0FueSlgXG4gICAgICAgIDxkaXYgY2xhc3M9XCJhbnlcIj5cbiAgICAgICAgICAke19pZih0aGlzLnNob3dUb2dnbGUpYFxuICAgICAgICAgICAgPGEgY2xhc3M9XCJ0aXRsZVwiPjxzcGFuIGNsYXNzPVwidG9nZ2xlLWhhbmRsZVwiPjwvc3Bhbj4ke3RoaXMuc2NoZW1hLnRpdGxlIHx8ICcnfSA8L2E+XG4gICAgICAgICAgYH1cblxuICAgICAgICAgIDxzcGFuIGNsYXNzPVwidHlwZSB0eXBlLWFueVwiPiZsdDthbnkmZ3Q7PC9zcGFuPlxuXG4gICAgICAgICAgJHtfaWYodGhpcy5zY2hlbWEuZGVzY3JpcHRpb24gJiYgIXRoaXMuaXNDb2xsYXBzZWQpYFxuICAgICAgICAgICAgPGRpdiBjbGFzcz1cImlubmVyIGRlc2NyaXB0aW9uXCI+JHt0aGlzLnNjaGVtYS5kZXNjcmlwdGlvbn08L2Rpdj5cbiAgICAgICAgICBgfVxuXHRcdCBcblx0XHQgICR7X2lmKHRoaXMuc2NoZW1hLnJlcXVpcmVkICYmICF0aGlzLmlzQ29sbGFwc2VkKWBcbiAgICAgICAgICAgIDxkaXYgY2xhc3M9XCJpbm5lciByZXF1aXJlZFwiPlJlcXVpcmVkOiAke3RoaXMuc2NoZW1hLnJlcXVpcmVkfTwvZGl2PlxuICAgICAgICAgIGB9XG5cdFx0ICAke19pZih0aGlzLnNjaGVtYS5kZWZhdWx0ICYmICF0aGlzLmlzQ29sbGFwc2VkKWBcbiAgICAgICAgICAgIDxkaXYgY2xhc3M9XCJpbm5lciBkZWZhdWx0XCI+RGVmYXVsdDogJHt0aGlzLnNjaGVtYS5kZWZhdWx0fTwvZGl2PlxuICAgICAgICAgIGB9XG5cdFx0ICAgJHtfaWYodGhpcy5zY2hlbWEucGF0dGVybiAmJiAhdGhpcy5pc0NvbGxhcHNlZClgXG4gICAgICAgICAgICA8ZGl2IGNsYXNzPVwiaW5uZXIgcGF0dGVyblwiPlBhdHRlcm46ICR7dGhpcy5zY2hlbWEucGF0dGVybn08L2Rpdj5cbiAgICAgICAgICBgfVxuICAgICAgICA8L2Rpdj5cbiAgICAgIGB9XG5cdFx0ICAgJHtfaWYodGhpcy5zY2hlbWEuZXhhbXBsZSAmJiAhdGhpcy5pc0NvbGxhcHNlZClgXG4gICAgICAgICAgICA8ZGl2IGNsYXNzPVwiaW5uZXIgZXhhbXBsZVwiPkV4YW1wbGU6ICR7dGhpcy5zY2hlbWEuZXhhbXBsZX08L2Rpdj5cbiAgICAgICAgICBgfVxuICAgICAgICA8L2Rpdj5cbiAgICAgIGB9XG5cbiAgICAgIDwhLS0gUHJpbWl0aXZlIC0tPlxuICAgICAgJHtfaWYodGhpcy5pc1ByaW1pdGl2ZSlgXG4gICAgICAgIDxkaXYgY2xhc3M9XCJwcmltaXRpdmVcIj5cbiAgICAgICAgICAke19pZih0aGlzLnNob3dUb2dnbGUpYFxuICAgICAgICAgICAgPGEgY2xhc3M9XCJ0aXRsZVwiPjxzcGFuIGNsYXNzPVwidG9nZ2xlLWhhbmRsZVwiPjwvc3Bhbj4ke3RoaXMuc2NoZW1hLnRpdGxlIHx8ICcnfSA8L2E+XG4gICAgICAgICAgYH1cblxuICAgICAgICAgICAgPHNwYW4gY2xhc3M9XCJ0eXBlXCI+JHt0aGlzLnNjaGVtYS50eXBlfTwvc3Bhbj5cblxuICAgICAgICAgXG5cbiAgICAgICAgICAke19pZighdGhpcy5pc0NvbGxhcHNlZCAmJiB0aGlzLnNjaGVtYS5mb3JtYXQpYFxuICAgICAgICAgICAgPHNwYW4gY2xhc3M9XCJmb3JtYXRcIj4oJHt0aGlzLnNjaGVtYS5mb3JtYXR9KTwvc3Bhbj5cbiAgICAgICAgICBgfVxuXG4gICAgICAgICAgJHtfaWYoIXRoaXMuaXNDb2xsYXBzZWQgJiYgdGhpcy5zY2hlbWEubWluaW11bSlgXG4gICAgICAgICAgICA8c3BhbiBjbGFzcz1cInJhbmdlIG1pbmltdW1cIj5taW5pbXVtOiR7dGhpcy5zY2hlbWEubWluaW11bX08L3NwYW4+XG4gICAgICAgICAgYH1cblxuICAgICAgICAgICR7X2lmKCF0aGlzLmlzQ29sbGFwc2VkICYmIHRoaXMuc2NoZW1hLmV4Y2x1c2l2ZU1pbmltdW0pYFxuICAgICAgICAgICAgPHNwYW4gY2xhc3M9XCJyYW5nZSBleGNsdXNpdmVNaW5pbXVtXCI+KGV4KW1pbmltdW06JHt0aGlzLnNjaGVtYS5leGNsdXNpdmVNaW5pbXVtfTwvc3Bhbj5cbiAgICAgICAgICBgfVxuXG4gICAgICAgICAgJHtfaWYoIXRoaXMuaXNDb2xsYXBzZWQgJiYgdGhpcy5zY2hlbWEubWF4aW11bSlgXG4gICAgICAgICAgICA8c3BhbiBjbGFzcz1cInJhbmdlIG1heGltdW1cIj5tYXhpbXVtOiR7dGhpcy5zY2hlbWEubWF4aW11bX08L3NwYW4+XG4gICAgICAgICAgYH1cblxuICAgICAgICAgICR7X2lmKCF0aGlzLmlzQ29sbGFwc2VkICYmIHRoaXMuc2NoZW1hLmV4Y2x1c2l2ZU1heGltdW0pYFxuICAgICAgICAgICAgPHNwYW4gY2xhc3M9XCJyYW5nZSBleGNsdXNpdmVNYXhpbXVtXCI+KGV4KW1heGltdW06JHt0aGlzLnNjaGVtYS5leGNsdXNpdmVNYXhpbXVtfTwvc3Bhbj5cbiAgICAgICAgICBgfVxuXG4gICAgICAgICAgJHtfaWYoIXRoaXMuaXNDb2xsYXBzZWQgJiYgdGhpcy5zY2hlbWEubWluTGVuZ3RoKWBcbiAgICAgICAgICAgIDxzcGFuIGNsYXNzPVwicmFuZ2UgbWluTGVuZ3RoXCI+bWluTGVuZ3RoOiR7dGhpcy5zY2hlbWEubWluTGVuZ3RofTwvc3Bhbj5cbiAgICAgICAgICBgfVxuXG4gICAgICAgICAgJHtfaWYoIXRoaXMuaXNDb2xsYXBzZWQgJiYgdGhpcy5zY2hlbWEubWF4TGVuZ3RoKWBcbiAgICAgICAgICAgIDxzcGFuIGNsYXNzPVwicmFuZ2UgbWF4TGVuZ3RoXCI+bWF4TGVuZ3RoOiR7dGhpcy5zY2hlbWEubWF4TGVuZ3RofTwvc3Bhbj5cbiAgICAgICAgICBgfVxuXG4gICAgICAgICAgJHtfaWYodGhpcy5zY2hlbWEuZGVzY3JpcHRpb24gJiYgIXRoaXMuaXNDb2xsYXBzZWQpYFxuICAgICAgICAgICAgPGRpdiBjbGFzcz1cImlubmVyIGRlc2NyaXB0aW9uXCI+JHt0aGlzLnNjaGVtYS5kZXNjcmlwdGlvbn08L2Rpdj5cbiAgICAgICAgICBgfVxuXHRcdCAgXG5cdFx0IFxuXHRcdCAgXG5cdFx0ICAgJHtfaWYodGhpcy5zY2hlbWEucmVxdWlyZWQgJiYgIXRoaXMuaXNDb2xsYXBzZWQpYFxuICAgICAgICAgICAgPGRpdiBjbGFzcz1cImlubmVyIHJlcXVpcmVkXCI+UmVxdWlyZWQ8L2Rpdj5cbiAgICAgICAgICBgfVxuXHRcdCAgJHtfaWYodGhpcy5zY2hlbWEuZGVmYXVsdCAmJiAhdGhpcy5pc0NvbGxhcHNlZClgXG4gICAgICAgICAgICA8ZGl2IGNsYXNzPVwiaW5uZXIgZGVmYXVsdFwiPkRlZmF1bHQ6ICR7dGhpcy5zY2hlbWEuZGVmYXVsdH08L2Rpdj5cbiAgICAgICAgICBgfVxuXHRcdCAgICR7X2lmKHRoaXMuc2NoZW1hLnBhdHRlcm4gJiYgIXRoaXMuaXNDb2xsYXBzZWQpYFxuICAgICAgICAgICAgPGRpdiBjbGFzcz1cImlubmVyIHBhdHRlcm5cIj5QYXR0ZXJuOiAke3RoaXMuc2NoZW1hLnBhdHRlcm59PC9kaXY+XG4gICAgICAgICAgYH1cblxuICAgICAgICAgICR7X2lmKCF0aGlzLmlzQ29sbGFwc2VkICYmIHRoaXMuc2NoZW1hLmVudW0pYFxuICAgICAgICAgICAgJHt0aGlzLmVudW0odGhpcy5zY2hlbWEsIHRoaXMuaXNDb2xsYXBzZWQsIHRoaXMub3Blbil9XG4gICAgICAgICAgYH1cblxuICAgICAgICAgICR7X2lmKHRoaXMuc2NoZW1hLmFsbE9mICYmICF0aGlzLmlzQ29sbGFwc2VkKWAke3RoaXMueE9mKHRoaXMuc2NoZW1hLCAnYWxsT2YnKX1gfVxuICAgICAgICAgICR7X2lmKHRoaXMuc2NoZW1hLm9uZU9mICYmICF0aGlzLmlzQ29sbGFwc2VkKWAke3RoaXMueE9mKHRoaXMuc2NoZW1hLCAnb25lT2YnKX1gfVxuICAgICAgICAgICR7X2lmKHRoaXMuc2NoZW1hLmFueU9mICYmICF0aGlzLmlzQ29sbGFwc2VkKWAke3RoaXMueE9mKHRoaXMuc2NoZW1hLCAnYW55T2YnKX1gfVxuICAgICAgICA8L2Rpdj5cbiAgICAgIGB9XG5cblxuICAgICAgPCEtLSBBcnJheSAtLT5cbiAgICAgICR7X2lmKHRoaXMuaXNBcnJheSlgXG4gICAgICAgIDxkaXYgY2xhc3M9XCJhcnJheVwiPlxuICAgICAgICAgIDxhIGNsYXNzPVwidGl0bGVcIj48c3BhbiBjbGFzcz1cInRvZ2dsZS1oYW5kbGVcIj48L3NwYW4+JHt0aGlzLnNjaGVtYS50aXRsZSB8fCAnJ308c3BhbiBjbGFzcz1cIm9wZW5pbmcgYnJhY2tldFwiPls8L3NwYW4+JHtfaWYodGhpcy5pc0NvbGxhcHNlZClgPHNwYW4gY2xhc3M9XCJjbG9zaW5nIGJyYWNrZXRcIj5dPC9zcGFuPmB9PC9hPlxuICAgICAgICAgICR7X2lmKCF0aGlzLmlzQ29sbGFwc2VkICYmICh0aGlzLnNjaGVtYS51bmlxdWVJdGVtcyB8fCB0aGlzLnNjaGVtYS5taW5JdGVtcyB8fCB0aGlzLnNjaGVtYS5tYXhJdGVtcykpYFxuICAgICAgICAgIDxzcGFuPlxuICAgICAgICAgICAgPHNwYW4gdGl0bGU9XCJpdGVtcyByYW5nZVwiPigke3RoaXMuc2NoZW1hLm1pbkl0ZW1zIHx8IDB9Li4ke3RoaXMuc2NoZW1hLm1heEl0ZW1zIHx8ICc4J30pPC9zcGFuPlxuICAgICAgICAgICAgJHtfaWYoIXRoaXMuaXNDb2xsYXBzZWQgJiYgdGhpcy5zY2hlbWEudW5pcXVlSXRlbXMpYDxzcGFuIHRpdGxlPVwidW5pcXVlXCIgY2xhc3M9XCJ1bmlxdWVJdGVtc1wiPj88L3NwYW4+YH1cbiAgICAgICAgICA8L3NwYW4+XG4gICAgICAgICAgYH1cbiAgICAgICAgICA8ZGl2IGNsYXNzPVwiaW5uZXJcIj5cbiAgICAgICAgICAgICR7X2lmKCF0aGlzLmlzQ29sbGFwc2VkICYmIHRoaXMuc2NoZW1hLmRlc2NyaXB0aW9uKWBcbiAgICAgICAgICAgICAgPGRpdiBjbGFzcz1cImRlc2NyaXB0aW9uXCI+JHt0aGlzLnNjaGVtYS5kZXNjcmlwdGlvbn08L2Rpdj5cbiAgICAgICAgICAgIGB9XG4gICAgICAgICAgPC9kaXY+XG5cbiAgICAgICAgICAke19pZighdGhpcy5pc0NvbGxhcHNlZCAmJiB0aGlzLnNjaGVtYS5lbnVtKWBcbiAgICAgICAgICAgICR7dGhpcy5lbnVtKHRoaXMuc2NoZW1hLCB0aGlzLmlzQ29sbGFwc2VkLCB0aGlzLm9wZW4pfVxuICAgICAgICAgIGB9XG5cbiAgICAgICAgICAke19pZih0aGlzLnNjaGVtYS5hbGxPZiAmJiAhdGhpcy5pc0NvbGxhcHNlZClgJHt0aGlzLnhPZih0aGlzLnNjaGVtYSwgJ2FsbE9mJyl9YH1cbiAgICAgICAgICAke19pZih0aGlzLnNjaGVtYS5vbmVPZiAmJiAhdGhpcy5pc0NvbGxhcHNlZClgJHt0aGlzLnhPZih0aGlzLnNjaGVtYSwgJ29uZU9mJyl9YH1cbiAgICAgICAgICAke19pZih0aGlzLnNjaGVtYS5hbnlPZiAmJiAhdGhpcy5pc0NvbGxhcHNlZClgJHt0aGlzLnhPZih0aGlzLnNjaGVtYSwgJ2FueU9mJyl9YH1cblxuICAgICAgICAgICR7X2lmKCF0aGlzLmlzQ29sbGFwc2VkKWBcbiAgICAgICAgICA8c3BhbiBjbGFzcz1cImNsb3NpbmcgYnJhY2tldFwiPl08L3NwYW4+XG4gICAgICAgICAgYH1cbiAgICAgICAgPC9kaXY+XG4gICAgICBgfVxuXG4gICAgICA8IS0tIE9iamVjdCAtLT5cbiAgICAgICR7X2lmKCF0aGlzLmlzUHJpbWl0aXZlICYmICF0aGlzLmlzQXJyYXkgJiYgIXRoaXMuaXNBbnkpYFxuICAgICAgICA8ZGl2IGNsYXNzPVwib2JqZWN0XCI+XG4gICAgICAgICAgPGEgY2xhc3M9XCJ0aXRsZVwiPjxzcGFuXG4gICAgICAgICAgICBjbGFzcz1cInRvZ2dsZS1oYW5kbGVcIj48L3NwYW4+JHt0aGlzLnNjaGVtYS50aXRsZSB8fCAnJ30gPHNwYW5cbiAgICAgICAgICAgIGNsYXNzPVwib3BlbmluZyBicmFjZVwiPns8L3NwYW4+JHtfaWYodGhpcy5pc0NvbGxhcHNlZClgXG4gICAgICAgICAgICAgIDxzcGFuIGNsYXNzPVwiY2xvc2luZyBicmFjZVwiIG5nLWlmPVwiaXNDb2xsYXBzZWRcIj59PC9zcGFuPlxuICAgICAgICAgIGB9PC9hPlxuXG4gICAgICAgICAgPGRpdiBjbGFzcz1cImlubmVyXCI+XG4gICAgICAgICAgICAke19pZighdGhpcy5pc0NvbGxhcHNlZCAmJiB0aGlzLnNjaGVtYS5kZXNjcmlwdGlvbilgXG4gICAgICAgICAgICAgIDxkaXYgY2xhc3M9XCJkZXNjcmlwdGlvblwiPiR7dGhpcy5zY2hlbWEuZGVzY3JpcHRpb259PC9kaXY+XG4gICAgICAgICAgICBgfVxuICAgICAgICAgICAgPCEtLSBjaGlsZHJlbiBnbyBoZXJlIC0tPlxuXHRcdCAgXG5cdFx0ICAgJHtfaWYodGhpcy5zY2hlbWEucmVxdWlyZWQgJiYgIXRoaXMuaXNDb2xsYXBzZWQpYFxuICAgICAgICAgICAgPGRpdiBjbGFzcz1cInJlcXVpcmVkXCI+RGVmYXVsdCBGaWVsZHM6ICR7dGhpcy5zY2hlbWEucmVxdWlyZWR9PC9kaXY+XG4gICAgICAgICAgYH1cblx0XHQgICR7X2lmKHRoaXMuc2NoZW1hLmRlZmF1bHQgJiYgIXRoaXMuaXNDb2xsYXBzZWQpYFxuICAgICAgICAgICAgPGRpdiBjbGFzcz1cImRlZmF1bHRcIj5EZWZhdWx0OiAke3RoaXMuc2NoZW1hLmRlZmF1bHR9PC9kaXY+XG4gICAgICAgICAgYH1cblx0XHQgIFxuXHRcdFx0JHtfaWYoIXRoaXMuaXNDb2xsYXBzZWQgJiYgdGhpcy5zY2hlbWEucGF0dGVybilgXG4gICAgICAgICAgICAgIDxkaXYgY2xhc3M9XCJwYXR0ZXJuXCI+UGF0dGVybjogJHt0aGlzLnNjaGVtYS5wYXR0ZXJufTwvZGl2PlxuICAgICAgICAgICAgYH1cblx0XHRcdCR7X2lmKCF0aGlzLmlzQ29sbGFwc2VkICYmIHRoaXMuc2NoZW1hLmV4YW1wbGUpYFxuICAgICAgICAgICAgICA8ZGl2IGNsYXNzPVwiZXhhbXBsZVwiPkV4YW1wbGU6ICR7dGhpcy5zY2hlbWEuZXhhbXBsZX08L2Rpdj5cbiAgICAgICAgICAgIGB9XG4gICAgICAgICAgPC9kaXY+XG5cbiAgICAgICAgICAke19pZighdGhpcy5pc0NvbGxhcHNlZCAmJiB0aGlzLnNjaGVtYS5lbnVtKWBcbiAgICAgICAgICAgICR7dGhpcy5lbnVtKHRoaXMuc2NoZW1hLCB0aGlzLmlzQ29sbGFwc2VkLCB0aGlzLm9wZW4pfVxuICAgICAgICAgIGB9XG5cbiAgICAgICAgICAke19pZih0aGlzLnNjaGVtYS5hbGxPZiAmJiAhdGhpcy5pc0NvbGxhcHNlZClgJHt0aGlzLnhPZih0aGlzLnNjaGVtYSwgJ2FsbE9mJyl9YH1cbiAgICAgICAgICAke19pZih0aGlzLnNjaGVtYS5vbmVPZiAmJiAhdGhpcy5pc0NvbGxhcHNlZClgJHt0aGlzLnhPZih0aGlzLnNjaGVtYSwgJ29uZU9mJyl9YH1cbiAgICAgICAgICAke19pZih0aGlzLnNjaGVtYS5hbnlPZiAmJiAhdGhpcy5pc0NvbGxhcHNlZClgJHt0aGlzLnhPZih0aGlzLnNjaGVtYSwgJ2FueU9mJyl9YH1cblxuICAgICAgICAgICR7X2lmKCF0aGlzLmlzQ29sbGFwc2VkKWBcbiAgICAgICAgICA8c3BhbiBjbGFzcz1cImNsb3NpbmcgYnJhY2VcIj59PC9zcGFuPlxuICAgICAgICAgIGB9XG4gICAgICAgIDwvZGl2PlxuICAgICAgYH1cbmAucmVwbGFjZSgvXFxzKlxcbi9nLCAnXFxuJykucmVwbGFjZSgvKFxcPFxcIVxcLVxcLSkuKy9nLCAnJykudHJpbSgpO1xuICB9XG5cbiAgLypcbiAgICogVGVtcGxhdGUgZm9yIG9uZU9mLCBhbnlPZiBhbmQgYWxsT2ZcbiAgKi9cbiAgeE9mKHNjaGVtYSwgdHlwZSkge1xuICAgIHJldHVybiBgXG4gICAgICA8ZGl2IGNsYXNzPVwiaW5uZXIgJHt0eXBlfVwiPlxuICAgICAgICA8Yj4ke2NvbnZlcnRYT2YodHlwZSl9OjwvYj5cbiAgICAgIDwvZGl2PlxuICAgIGA7XG4gIH1cblxuICAvKlxuICAgKiBUZW1wbGF0ZSBmb3IgZW51bXNcbiAgKi9cbiAgZW51bShzY2hlbWEsIGlzQ29sbGFwc2VkLCBvcGVuKSB7XG4gICAgcmV0dXJuIGBcbiAgICAgICR7X2lmKCFpc0NvbGxhcHNlZCAmJiBzY2hlbWEuZW51bSlgXG4gICAgICAgIDxkaXYgY2xhc3M9XCJpbm5lciBlbnVtc1wiPlxuICAgICAgICAgIDxiPkVudW06PC9iPlxuICAgICAgICA8L2Rpdj5cbiAgICAgIGB9XG4gICAgYDtcbiAgfVxuXG4gIC8qXG4gICAqIFRvZ2dsZXMgdGhlICdjb2xsYXBzZWQnIHN0YXRlXG4gICovXG4gIHRvZ2dsZSgpIHtcbiAgICB0aGlzLmlzQ29sbGFwc2VkID0gIXRoaXMuaXNDb2xsYXBzZWQ7XG4gICAgdGhpcy5yZW5kZXIoKTtcbiAgfVxuXG4gIC8qXG4gICAqIFJlbmRlcnMgdGhlIGVsZW1lbnQgYW5kIHJldHVybnMgaXRcbiAgKi9cbiAgcmVuZGVyKCkge1xuICAgIGlmICghdGhpcy5lbGVtZW50KSB7XG4gICAgICB0aGlzLmVsZW1lbnQgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdkaXYnKTtcbiAgICAgIHRoaXMuZWxlbWVudC5jbGFzc0xpc3QuYWRkKCdqc29uLXNjaGVtYS12aWV3Jyk7XG4gICAgfVxuXG4gICAgaWYgKHRoaXMuaXNDb2xsYXBzZWQpIHtcbiAgICAgIHRoaXMuZWxlbWVudC5jbGFzc0xpc3QuYWRkKCdjb2xsYXBzZWQnKTtcbiAgICB9IGVsc2Uge1xuICAgICAgdGhpcy5lbGVtZW50LmNsYXNzTGlzdC5yZW1vdmUoJ2NvbGxhcHNlZCcpO1xuICAgIH1cblxuICAgIGlmICh0aGlzLm9wdGlvbnMudGhlbWUpIHtcbiAgICAgIHRoaXMuZWxlbWVudC5jbGFzc0xpc3QuYWRkKGBqc29uLXNjaGVtYS12aWV3LSR7dGhpcy5vcHRpb25zLnRoZW1lfWApO1xuICAgIH1cblxuICAgIHRoaXMuZWxlbWVudC5pbm5lckhUTUwgPSB0aGlzLnRlbXBsYXRlKCk7XG5cbiAgICBpZiAoIXRoaXMuc2NoZW1hKSB7XG4gICAgICByZXR1cm4gdGhpcy5lbGVtZW50O1xuICAgIH1cblxuICAgIGlmICghdGhpcy5pc0NvbGxhcHNlZCkge1xuICAgICAgdGhpcy5hcHBlbmRDaGlsZHJlbih0aGlzLmVsZW1lbnQpO1xuICAgIH1cblxuICAgIC8vIGFkZCBldmVudCBsaXN0ZW5lciBmb3IgdG9nZ2xpbmdcbiAgICBpZiAodGhpcy5lbGVtZW50LnF1ZXJ5U2VsZWN0b3IoJ2EudGl0bGUnKSkge1xuICAgICAgdGhpcy5lbGVtZW50LnF1ZXJ5U2VsZWN0b3IoJ2EudGl0bGUnKS5hZGRFdmVudExpc3RlbmVyKCdjbGljaycsIHRoaXMudG9nZ2xlLmJpbmQodGhpcykpO1xuICAgIH1cbiAgICByZXR1cm4gdGhpcy5lbGVtZW50O1xuICB9XG5cbiAgLypcbiAgICogQXBwZW5kcyBjaGlsZHJlbiB0byBnaXZlbiBlbGVtZW50IGJhc2VkIG9uIGN1cnJlbnQgc2NoZW1hXG4gICovXG4gIGFwcGVuZENoaWxkcmVuKGVsZW1lbnQpIHtcbiAgICBjb25zdCBpbm5lciA9IGVsZW1lbnQucXVlcnlTZWxlY3RvcignLmlubmVyJyk7XG5cbiAgICBpZiAoIWlubmVyKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgaWYgKHRoaXMuc2NoZW1hLmVudW0pIHtcbiAgICAgIGNvbnN0IGZvcm1hdHRlciA9IG5ldyBKU09ORm9ybWF0dGVyKHRoaXMuc2NoZW1hLmVudW0sIHRoaXMub3BlbiAtIDEpO1xuICAgICAgY29uc3QgZm9ybWF0dGVyRWwgPSBmb3JtYXR0ZXIucmVuZGVyKCk7XG4gICAgICBmb3JtYXR0ZXJFbC5jbGFzc0xpc3QuYWRkKCdpbm5lcicpO1xuICAgICAgZWxlbWVudC5xdWVyeVNlbGVjdG9yKCcuZW51bXMuaW5uZXInKS5hcHBlbmRDaGlsZChmb3JtYXR0ZXJFbCk7XG5cbiAgICB9XG5cbiAgICBpZiAodGhpcy5pc0FycmF5KSB7XG4gICAgICBjb25zdCB2aWV3ID0gbmV3IEpTT05TY2hlbWFWaWV3KHRoaXMuc2NoZW1hLml0ZW1zLCB0aGlzLm9wZW4gLSAxKVxuICAgICAgaW5uZXIuYXBwZW5kQ2hpbGQodmlldy5yZW5kZXIoKSk7XG4gICAgfVxuXG4gICAgaWYgKHR5cGVvZiB0aGlzLnNjaGVtYS5wcm9wZXJ0aWVzID09PSAnb2JqZWN0Jykge1xuICAgICAgT2JqZWN0LmtleXModGhpcy5zY2hlbWEucHJvcGVydGllcykuZm9yRWFjaChwcm9wZXJ0eU5hbWUgPT4ge1xuICAgICAgICBjb25zdCBwcm9wZXJ0eSA9IHRoaXMuc2NoZW1hLnByb3BlcnRpZXNbcHJvcGVydHlOYW1lXTtcbiAgICAgICAgY29uc3QgdGVtcERpdiA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ2RpdicpOztcbiAgICAgICAgdGVtcERpdi5pbm5lckhUTUwgPSBgPGRpdiBjbGFzcz1cInByb3BlcnR5XCI+XG4gICAgICAgICAgPHNwYW4gY2xhc3M9XCJuYW1lXCI+JHtwcm9wZXJ0eU5hbWV9Ojwvc3Bhbj5cbiAgICAgICAgPC9kaXY+YDtcbiAgICAgICAgY29uc3QgdmlldyA9IG5ldyBKU09OU2NoZW1hVmlldyhwcm9wZXJ0eSwgdGhpcy5vcGVuIC0gMSk7XG4gICAgICAgIHRlbXBEaXYucXVlcnlTZWxlY3RvcignLnByb3BlcnR5JykuYXBwZW5kQ2hpbGQodmlldy5yZW5kZXIoKSk7XG5cbiAgICAgICAgaW5uZXIuYXBwZW5kQ2hpbGQodGVtcERpdi5xdWVyeVNlbGVjdG9yKCcucHJvcGVydHknKSk7XG4gICAgICB9KTtcbiAgICB9XG5cbiAgICBpZiAodGhpcy5zY2hlbWEuYWxsT2YpIHsgYXBwZW5kWE9mLmNhbGwodGhpcywgJ2FsbE9mJyk7IH1cbiAgICBpZiAodGhpcy5zY2hlbWEub25lT2YpIHsgYXBwZW5kWE9mLmNhbGwodGhpcywgJ29uZU9mJyk7IH1cbiAgICBpZiAodGhpcy5zY2hlbWEuYW55T2YpIHsgYXBwZW5kWE9mLmNhbGwodGhpcywgJ2FueU9mJyk7IH1cblxuICAgIGZ1bmN0aW9uIGFwcGVuZFhPZih0eXBlKSB7XG4gICAgICBjb25zdCBpbm5lckFsbE9mID0gZWxlbWVudC5xdWVyeVNlbGVjdG9yKGAuaW5uZXIuJHt0eXBlfWApO1xuXG4gICAgICB0aGlzLnNjaGVtYVt0eXBlXS5mb3JFYWNoKHNjaGVtYSA9PiB7XG4gICAgICAgIGNvbnN0IGlubmVyID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnZGl2Jyk7XG4gICAgICAgIGlubmVyLmNsYXNzTGlzdC5hZGQoJ2lubmVyJyk7XG4gICAgICAgIGNvbnN0IHZpZXcgPSBuZXcgSlNPTlNjaGVtYVZpZXcoc2NoZW1hLCB0aGlzLm9wZW4gLSAxKTtcbiAgICAgICAgaW5uZXIuYXBwZW5kQ2hpbGQodmlldy5yZW5kZXIoKSk7XG4gICAgICAgIGlubmVyQWxsT2YuYXBwZW5kQ2hpbGQoaW5uZXIpO1xuICAgICAgfSk7XG4gICAgfVxuICB9XG59XG4iXX0=\n\n</script>\n\n  <script>\n(function(f){if(typeof exports===\"object\"&&typeof module!==\"undefined\"){module.exports=f()}else if(typeof define===\"function\"&&define.amd){define([],f)}else{var g;if(typeof window!==\"undefined\"){g=window}else if(typeof global!==\"undefined\"){g=global}else if(typeof self!==\"undefined\"){g=self}else{g=this}g.$RefParser = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require==\"function\"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error(\"Cannot find module '\"+o+\"'\");throw f.code=\"MODULE_NOT_FOUND\",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require==\"function\"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){\n/** !\n * JSON Schema $Ref Parser v3.1.2\n *\n * @link https://github.com/BigstickCarpet/json-schema-ref-parser\n * @license MIT\n */\n\"use strict\";function bundle(e,r){debug(\"Bundling $ref pointers in %s\",e.$refs._root$Ref.path);var t=[];crawl(e,\"schema\",e.$refs._root$Ref.path+\"#\",\"#\",t,e.$refs,r),remap(t)}function crawl(e,r,t,f,n,i,a){var o=null===r?e:e[r];if(o&&\"object\"==typeof o)if($Ref.is$Ref(o))inventory$Ref(e,r,t,f,n,i,a);else{var l=Object.keys(o),u=l.indexOf(\"definitions\");u>0&&l.splice(0,0,l.splice(u,1)[0]),l.forEach(function(e){var r=Pointer.join(t,e),l=Pointer.join(f,e),u=o[e];$Ref.is$Ref(u)?inventory$Ref(o,e,t,l,n,i,a):crawl(o,e,r,l,n,i,a)})}}function inventory$Ref(e,r,t,f,n,i,a){if(!n.some(function(t){return t.parent===e&&t.key===r})){var o=null===r?e:e[r],l=url.resolve(t,o.$ref),u=i._resolve(l,a),h=Pointer.parse(f).length,s=url.stripHash(u.path),$=url.getHash(u.path),c=s!==i._root$Ref.path,p=$Ref.isExtended$Ref(o);n.push({$ref:o,parent:e,key:r,pathFromRoot:f,depth:h,file:s,hash:$,value:u.value,circular:u.circular,extended:p,external:c}),crawl(u.value,null,u.path,f,n,i,a)}}function remap(e){e.sort(function(e,r){return e.file!==r.file?e.file<r.file?-1:1:e.hash!==r.hash?e.hash<r.hash?-1:1:e.circular!==r.circular?e.circular?-1:1:e.extended!==r.extended?e.extended?1:-1:e.depth!==r.depth?e.depth-r.depth:r.pathFromRoot.lastIndexOf(\"/definitions\")-e.pathFromRoot.lastIndexOf(\"/definitions\")});var r,t,f;e.forEach(function(e){debug('Re-mapping $ref pointer \"%s\" at %s',e.$ref.$ref,e.pathFromRoot),e.external?e.file===r&&e.hash===t?e.$ref.$ref=f:e.file===r&&0===e.hash.indexOf(t+\"/\")?e.$ref.$ref=Pointer.join(f,Pointer.parse(e.hash)):(r=e.file,t=e.hash,f=e.pathFromRoot,e.$ref=e.parent[e.key]=$Ref.dereference(e.$ref,e.value),e.circular&&(e.$ref.$ref=e.pathFromRoot)):e.$ref.$ref=e.hash,debug(\"    new value: %s\",e.$ref&&e.$ref.$ref?e.$ref.$ref:\"[object Object]\")})}var $Ref=require(\"./ref\"),Pointer=require(\"./pointer\"),debug=require(\"./util/debug\"),url=require(\"./util/url\");module.exports=bundle;\n\n},{\"./pointer\":10,\"./ref\":11,\"./util/debug\":16,\"./util/url\":19}],2:[function(require,module,exports){\n\"use strict\";function dereference(e,r){debug(\"Dereferencing $ref pointers in %s\",e.$refs._root$Ref.path);var c=crawl(e.schema,e.$refs._root$Ref.path,\"#\",[],e.$refs,r);e.$refs.circular=c.circular,e.schema=c.value}function crawl(e,r,c,u,f,i){var n,a={value:e,circular:!1};return e&&\"object\"==typeof e&&(u.push(e),$Ref.isAllowed$Ref(e,i)?(n=dereference$Ref(e,r,c,u,f,i),a.circular=n.circular,a.value=n.value):Object.keys(e).forEach(function(l){var o=Pointer.join(r,l),t=Pointer.join(c,l),d=e[l],$=!1;$Ref.isAllowed$Ref(d,i)?(n=dereference$Ref(d,o,t,u,f,i),$=n.circular,e[l]=n.value):-1===u.indexOf(d)?(n=crawl(d,o,t,u,f,i),$=n.circular,e[l]=n.value):$=foundCircularReference(o,f,i),a.circular=a.circular||$}),u.pop()),a}function dereference$Ref(e,r,c,u,f,i){debug('Dereferencing $ref pointer \"%s\" at %s',e.$ref,r);var n=url.resolve(r,e.$ref),a=f._resolve(n,i),l=a.circular,o=l||-1!==u.indexOf(a.value);o&&foundCircularReference(r,f,i);var t=$Ref.dereference(e,a.value);if(!o){var d=crawl(t,a.path,c,u,f,i);o=d.circular,t=d.value}return o&&!l&&\"ignore\"===i.dereference.circular&&(t=e),l&&(t.$ref=c),{circular:o,value:t}}function foundCircularReference(e,r,c){if(r.circular=!0,!c.dereference.circular)throw ono.reference(\"Circular $ref pointer found at %s\",e);return!0}var $Ref=require(\"./ref\"),Pointer=require(\"./pointer\"),ono=require(\"ono\"),debug=require(\"./util/debug\"),url=require(\"./util/url\");module.exports=dereference;\n\n},{\"./pointer\":10,\"./ref\":11,\"./util/debug\":16,\"./util/url\":19,\"ono\":69}],3:[function(require,module,exports){\n(function (Buffer){\n\"use strict\";function $RefParser(){this.schema=null,this.$refs=new $Refs}function normalizeArgs(e){var r,t,a,s;return e=Array.prototype.slice.call(e),\"function\"==typeof e[e.length-1]&&(s=e.pop()),\"string\"==typeof e[0]?(r=e[0],\"object\"==typeof e[2]?(t=e[1],a=e[2]):(t=void 0,a=e[1])):(r=\"\",t=e[0],a=e[1]),a instanceof Options||(a=new Options(a)),{path:r,schema:t,options:a,callback:s}}var Promise=require(\"./util/promise\"),Options=require(\"./options\"),$Refs=require(\"./refs\"),parse=require(\"./parse\"),resolveExternal=require(\"./resolve-external\"),bundle=require(\"./bundle\"),dereference=require(\"./dereference\"),url=require(\"./util/url\"),maybe=require(\"call-me-maybe\"),ono=require(\"ono\");module.exports=$RefParser,module.exports.YAML=require(\"./util/yaml\"),$RefParser.parse=function(e,r,t){var a=this,s=new a;return s.parse.apply(s,arguments)},$RefParser.prototype.parse=function(e,r,t){var a,s=normalizeArgs(arguments);if(!s.path&&!s.schema){var n=ono(\"Expected a file path, URL, or object. Got %s\",s.path||s.schema);return maybe(s.callback,Promise.reject(n))}this.schema=null,this.$refs=new $Refs,url.isFileSystemPath(s.path)&&(s.path=url.fromFileSystemPath(s.path)),s.path=url.resolve(url.cwd(),s.path),s.schema&&\"object\"==typeof s.schema?(this.$refs._add(s.path,s.schema),a=Promise.resolve(s.schema)):a=parse(s.path,this.$refs,s.options);var o=this;return a.then(function(e){if(!e||\"object\"!=typeof e||Buffer.isBuffer(e))throw ono.syntax('\"%s\" is not a valid JSON Schema',o.$refs._root$Ref.path||e);return o.schema=e,maybe(s.callback,Promise.resolve(o.schema))})[\"catch\"](function(e){return maybe(s.callback,Promise.reject(e))})},$RefParser.resolve=function(e,r,t){var a=this,s=new a;return s.resolve.apply(s,arguments)},$RefParser.prototype.resolve=function(e,r,t){var a=this,s=normalizeArgs(arguments);return this.parse(s.path,s.schema,s.options).then(function(){return resolveExternal(a,s.options)}).then(function(){return maybe(s.callback,Promise.resolve(a.$refs))})[\"catch\"](function(e){return maybe(s.callback,Promise.reject(e))})},$RefParser.bundle=function(e,r,t){var a=this,s=new a;return s.bundle.apply(s,arguments)},$RefParser.prototype.bundle=function(e,r,t){var a=this,s=normalizeArgs(arguments);return this.resolve(s.path,s.schema,s.options).then(function(){return bundle(a,s.options),maybe(s.callback,Promise.resolve(a.schema))})[\"catch\"](function(e){return maybe(s.callback,Promise.reject(e))})},$RefParser.dereference=function(e,r,t){var a=this,s=new a;return s.dereference.apply(s,arguments)},$RefParser.prototype.dereference=function(e,r,t){var a=this,s=normalizeArgs(arguments);return this.resolve(s.path,s.schema,s.options).then(function(){return dereference(a,s.options),maybe(s.callback,Promise.resolve(a.schema))})[\"catch\"](function(e){return maybe(s.callback,Promise.reject(e))})};\n\n}).call(this,{\"isBuffer\":require(\"../node_modules/is-buffer/index.js\")})\n\n},{\"../node_modules/is-buffer/index.js\":36,\"./bundle\":1,\"./dereference\":2,\"./options\":4,\"./parse\":5,\"./refs\":12,\"./resolve-external\":13,\"./util/promise\":18,\"./util/url\":19,\"./util/yaml\":20,\"call-me-maybe\":27,\"ono\":69}],4:[function(require,module,exports){\n\"use strict\";function $RefParserOptions(e){merge(this,$RefParserOptions.defaults),merge(this,e)}function merge(e,r){if(isMergeable(r))for(var s=Object.keys(r),a=0;a<s.length;a++){var t=s[a],i=r[t],o=e[t];isMergeable(i)?e[t]=merge(o||{},i):void 0!==i&&(e[t]=i)}return e}function isMergeable(e){return e&&\"object\"==typeof e&&!Array.isArray(e)&&!(e instanceof RegExp)&&!(e instanceof Date)}var jsonParser=require(\"./parsers/json\"),yamlParser=require(\"./parsers/yaml\"),textParser=require(\"./parsers/text\"),binaryParser=require(\"./parsers/binary\"),fileResolver=require(\"./resolvers/file\"),httpResolver=require(\"./resolvers/http\"),zschemaValidator=require(\"./validators/z-schema\");module.exports=$RefParserOptions,$RefParserOptions.defaults={parse:{json:jsonParser,yaml:yamlParser,text:textParser,binary:binaryParser},resolve:{file:fileResolver,http:httpResolver,external:!0},dereference:{circular:!0},validate:{zschema:zschemaValidator}};\n\n},{\"./parsers/binary\":6,\"./parsers/json\":7,\"./parsers/text\":8,\"./parsers/yaml\":9,\"./resolvers/file\":14,\"./resolvers/http\":15,\"./validators/z-schema\":21}],5:[function(require,module,exports){\n(function (Buffer){\n\"use strict\";function parse(r,e,n){try{r=url.stripHash(r);var t=e._add(r),u={url:r,extension:url.getExtension(r)};return readFile(u,n).then(function(r){return t.pathType=r.plugin.name,u.data=r.result,parseFile(u,n)}).then(function(r){return t.value=r.result,r.result})}catch(i){return Promise.reject(i)}}function readFile(r,e){return new Promise(function(n,t){function u(e){t(!e||e instanceof SyntaxError?ono.syntax('Unable to resolve $ref pointer \"%s\"',r.url):e)}debug(\"Reading %s\",r.url);var i=plugins.all(e.resolve);i=plugins.filter(i,\"canRead\",r),plugins.sort(i),plugins.run(i,\"read\",r).then(n,u)})}function parseFile(r,e){return new Promise(function(n,t){function u(e){!e.plugin.allowEmpty&&isEmpty(e.result)?t(ono.syntax('Error parsing \"%s\" as %s. \\nParsed value is empty',r.url,e.plugin.name)):n(e)}function i(e){e?(e=e instanceof Error?e:new Error(e),t(ono.syntax(e,\"Error parsing %s\",r.url))):t(ono.syntax(\"Unable to parse %s\",r.url))}debug(\"Parsing %s\",r.url);var s=plugins.all(e.parse),l=plugins.filter(s,\"canParse\",r),o=l.length>0?l:s;plugins.sort(o),plugins.run(o,\"parse\",r).then(u,i)})}function isEmpty(r){return void 0===r||\"object\"==typeof r&&0===Object.keys(r).length||\"string\"==typeof r&&0===r.trim().length||Buffer.isBuffer(r)&&0===r.length}var ono=require(\"ono\"),debug=require(\"./util/debug\"),url=require(\"./util/url\"),plugins=require(\"./util/plugins\"),Promise=require(\"./util/promise\");module.exports=parse;\n\n}).call(this,{\"isBuffer\":require(\"../node_modules/is-buffer/index.js\")})\n\n},{\"../node_modules/is-buffer/index.js\":36,\"./util/debug\":16,\"./util/plugins\":17,\"./util/promise\":18,\"./util/url\":19,\"ono\":69}],6:[function(require,module,exports){\n(function (Buffer){\n\"use strict\";var BINARY_REGEXP=/\\.(jpeg|jpg|gif|png|bmp|ico)$/i;module.exports={order:400,allowEmpty:!0,canParse:function(r){return Buffer.isBuffer(r.data)&&BINARY_REGEXP.test(r.url)},parse:function(r){return Buffer.isBuffer(r.data)?r.data:new Buffer(r.data)}};\n\n}).call(this,require(\"buffer\").Buffer)\n\n},{\"buffer\":25}],7:[function(require,module,exports){\n(function (Buffer){\n\"use strict\";var Promise=require(\"../util/promise\");module.exports={order:100,allowEmpty:!0,canParse:\".json\",parse:function(r){return new Promise(function(e,t){var i=r.data;Buffer.isBuffer(i)&&(i=i.toString()),e(\"string\"==typeof i?0===i.trim().length?void 0:JSON.parse(i):i)})}};\n\n}).call(this,{\"isBuffer\":require(\"../../node_modules/is-buffer/index.js\")})\n\n},{\"../../node_modules/is-buffer/index.js\":36,\"../util/promise\":18}],8:[function(require,module,exports){\n(function (Buffer){\n\"use strict\";var TEXT_REGEXP=/\\.(txt|htm|html|md|xml|js|min|map|css|scss|less|svg)$/i;module.exports={order:300,allowEmpty:!0,encoding:\"utf8\",canParse:function(t){return(\"string\"==typeof t.data||Buffer.isBuffer(t.data))&&TEXT_REGEXP.test(t.url)},parse:function(t){if(\"string\"==typeof t.data)return t.data;if(Buffer.isBuffer(t.data))return t.data.toString(this.encoding);throw new Error(\"data is not text\")}};\n\n}).call(this,{\"isBuffer\":require(\"../../node_modules/is-buffer/index.js\")})\n\n},{\"../../node_modules/is-buffer/index.js\":36}],9:[function(require,module,exports){\n(function (Buffer){\n\"use strict\";var Promise=require(\"../util/promise\"),YAML=require(\"../util/yaml\");module.exports={order:200,allowEmpty:!0,canParse:[\".yaml\",\".yml\",\".json\"],parse:function(r){return new Promise(function(e,t){var i=r.data;Buffer.isBuffer(i)&&(i=i.toString()),e(\"string\"==typeof i?YAML.parse(i):i)})}};\n\n}).call(this,{\"isBuffer\":require(\"../../node_modules/is-buffer/index.js\")})\n\n},{\"../../node_modules/is-buffer/index.js\":36,\"../util/promise\":18,\"../util/yaml\":20}],10:[function(require,module,exports){\n\"use strict\";function Pointer(e,r){this.$ref=e,this.path=r,this.value=void 0,this.circular=!1}function resolveIf$Ref(e,r){if($Ref.isAllowed$Ref(e.value,r)){var t=url.resolve(e.path,e.value.$ref);if(t!==e.path){var s=e.$ref.$refs._resolve(t,r);return $Ref.isExtended$Ref(e.value)?e.value=$Ref.dereference(e.value,s.value):(e.$ref=s.$ref,e.path=s.path,e.value=s.value),!0}e.circular=!0}}function setValue(e,r,t){if(!e.value||\"object\"!=typeof e.value)throw ono.syntax('Error assigning $ref pointer \"%s\". \\nCannot set \"%s\" of a non-object.',e.path,r);return\"-\"===r&&Array.isArray(e.value)?e.value.push(t):e.value[r]=t,t}module.exports=Pointer;var $Ref=require(\"./ref\"),url=require(\"./util/url\"),ono=require(\"ono\"),slashes=/\\//g,tildes=/~/g,escapedSlash=/~1/g,escapedTilde=/~0/g;Pointer.prototype.resolve=function(e,r){var t=Pointer.parse(this.path);this.value=e;for(var s=0;s<t.length;s++){resolveIf$Ref(this,r)&&(this.path=Pointer.join(this.path,t.slice(s)));var i=t[s];if(void 0===this.value[i])throw ono.syntax('Error resolving $ref pointer \"%s\". \\nToken \"%s\" does not exist.',this.path,i);this.value=this.value[i]}return resolveIf$Ref(this,r),this},Pointer.prototype.set=function(e,r,t){var s,i=Pointer.parse(this.path);if(0===i.length)return this.value=r,r;this.value=e;for(var a=0;a<i.length-1;a++)resolveIf$Ref(this,t),s=i[a],this.value&&void 0!==this.value[s]?this.value=this.value[s]:this.value=setValue(this,s,{});return resolveIf$Ref(this,t),s=i[i.length-1],setValue(this,s,r),e},Pointer.parse=function(e){var r=url.getHash(e).substr(1);if(!r)return[];r=r.split(\"/\");for(var t=0;t<r.length;t++)r[t]=decodeURI(r[t].replace(escapedSlash,\"/\").replace(escapedTilde,\"~\"));if(\"\"!==r[0])throw ono.syntax('Invalid $ref pointer \"%s\". Pointers must begin with \"#/\"',r);return r.slice(1)},Pointer.join=function(e,r){-1===e.indexOf(\"#\")&&(e+=\"#\"),r=Array.isArray(r)?r:[r];for(var t=0;t<r.length;t++){var s=r[t];e+=\"/\"+encodeURI(s.replace(tildes,\"~0\").replace(slashes,\"~1\"))}return e};\n\n},{\"./ref\":11,\"./util/url\":19,\"ono\":69}],11:[function(require,module,exports){\n\"use strict\";function $Ref(){this.path=void 0,this.value=void 0,this.$refs=void 0,this.pathType=void 0}module.exports=$Ref;var Pointer=require(\"./pointer\");$Ref.prototype.exists=function(e,t){try{return this.resolve(e,t),!0}catch(r){return!1}},$Ref.prototype.get=function(e,t){return this.resolve(e,t).value},$Ref.prototype.resolve=function(e,t){var r=new Pointer(this,e);return r.resolve(this.value,t)},$Ref.prototype.set=function(e,t){var r=new Pointer(this,e);this.value=r.set(this.value,t)},$Ref.is$Ref=function(e){return e&&\"object\"==typeof e&&\"string\"==typeof e.$ref&&e.$ref.length>0},$Ref.isExternal$Ref=function(e){return $Ref.is$Ref(e)&&\"#\"!==e.$ref[0]},$Ref.isAllowed$Ref=function(e,t){return!$Ref.is$Ref(e)||\"#\"!==e.$ref[0]&&t&&!t.resolve.external?void 0:!0},$Ref.isExtended$Ref=function(e){return $Ref.is$Ref(e)&&Object.keys(e).length>1},$Ref.dereference=function(e,t){if(t&&\"object\"==typeof t&&$Ref.isExtended$Ref(e)){var r={};return Object.keys(e).forEach(function(t){\"$ref\"!==t&&(r[t]=e[t])}),Object.keys(t).forEach(function(e){e in r||(r[e]=t[e])}),r}return t};\n\n},{\"./pointer\":10}],12:[function(require,module,exports){\n\"use strict\";function $Refs(){this.circular=!1,this._$refs={},this._root$Ref=null}function getPaths(e,r){var t=Object.keys(e);return r=Array.isArray(r[0])?r[0]:Array.prototype.slice.call(r),r.length>0&&r[0]&&(t=t.filter(function(t){return-1!==r.indexOf(e[t].pathType)})),t.map(function(r){return{encoded:r,decoded:\"file\"===e[r].pathType?url.toFileSystemPath(r,!0):r}})}var ono=require(\"ono\"),$Ref=require(\"./ref\"),url=require(\"./util/url\");module.exports=$Refs,$Refs.prototype.paths=function(e){var r=getPaths(this._$refs,arguments);return r.map(function(e){return e.decoded})},$Refs.prototype.values=function(e){var r=this._$refs,t=getPaths(r,arguments);return t.reduce(function(e,t){return e[t.decoded]=r[t.encoded].value,e},{})},$Refs.prototype.toJSON=$Refs.prototype.values,$Refs.prototype.exists=function(e,r){try{return this._resolve(e,r),!0}catch(t){return!1}},$Refs.prototype.get=function(e,r){return this._resolve(e,r).value},$Refs.prototype.set=function(e,r){e=url.resolve(this._root$Ref.path,e);var t=url.stripHash(e),o=this._$refs[t];if(!o)throw ono('Error resolving $ref pointer \"%s\". \\n\"%s\" not found.',e,t);o.set(e,r)},$Refs.prototype._add=function(e,r){var t=url.stripHash(e),o=new $Ref;return o.path=t,o.value=r,o.$refs=this,this._$refs[t]=o,this._root$Ref=this._root$Ref||o,o},$Refs.prototype._resolve=function(e,r){e=url.resolve(this._root$Ref.path,e);var t=url.stripHash(e),o=this._$refs[t];if(!o)throw ono('Error resolving $ref pointer \"%s\". \\n\"%s\" not found.',e,t);return o.resolve(e,r)},$Refs.prototype._get$Ref=function(e){e=url.resolve(this._root$Ref.path,e);var r=url.stripHash(e);return this._$refs[r]};\n\n},{\"./ref\":11,\"./util/url\":19,\"ono\":69}],13:[function(require,module,exports){\n\"use strict\";function resolveExternal(e,r){if(!r.resolve.external)return Promise.resolve();try{debug(\"Resolving $ref pointers in %s\",e.$refs._root$Ref.path);var s=crawl(e.schema,e.$refs._root$Ref.path+\"#\",e.$refs,r);return Promise.all(s)}catch(t){return Promise.reject(t)}}function crawl(e,r,s,t){var o=[];return e&&\"object\"==typeof e&&($Ref.isExternal$Ref(e)?o.push(resolve$Ref(e,r,s,t)):Object.keys(e).forEach(function(i){var n=Pointer.join(r,i),l=e[i];$Ref.isExternal$Ref(l)?o.push(resolve$Ref(l,n,s,t)):o=o.concat(crawl(l,n,s,t))})),o}function resolve$Ref(e,r,s,t){debug('Resolving $ref pointer \"%s\" at %s',e.$ref,r);var o=url.resolve(r,e.$ref),i=url.stripHash(o);return e=s._$refs[i],e?Promise.resolve(e.value):parse(o,s,t).then(function(e){debug(\"Resolving $ref pointers in %s\",i);var r=crawl(e,i+\"#\",s,t);return Promise.all(r)})}var Promise=require(\"./util/promise\"),$Ref=require(\"./ref\"),Pointer=require(\"./pointer\"),parse=require(\"./parse\"),debug=require(\"./util/debug\"),url=require(\"./util/url\");module.exports=resolveExternal;\n\n},{\"./parse\":5,\"./pointer\":10,\"./ref\":11,\"./util/debug\":16,\"./util/promise\":18,\"./util/url\":19}],14:[function(require,module,exports){\n\"use strict\";var fs=require(\"fs\"),ono=require(\"ono\"),Promise=require(\"../util/promise\"),url=require(\"../util/url\"),debug=require(\"../util/debug\");module.exports={order:100,canRead:function(r){return url.isFileSystemPath(r.url)},read:function(r){return new Promise(function(e,i){var u;try{u=url.toFileSystemPath(r.url)}catch(o){i(ono.uri(o,\"Malformed URI: %s\",r.url))}debug(\"Opening file: %s\",u);try{fs.readFile(u,function(r,o){r?i(ono(r,'Error opening file \"%s\"',u)):e(o)})}catch(o){i(ono(o,'Error opening file \"%s\"',u))}})}};\n\n},{\"../util/debug\":16,\"../util/promise\":18,\"../util/url\":19,\"fs\":24,\"ono\":69}],15:[function(require,module,exports){\n(function (process,Buffer){\n\"use strict\";function download(e,t,o){return new Promise(function(r,n){e=url.parse(e),o=o||[],o.push(e.href),get(e,t).then(function(s){if(s.statusCode>=400)throw ono({status:s.statusCode},\"HTTP ERROR %d\",s.statusCode);if(s.statusCode>=300)if(o.length>t.redirects)n(ono({status:s.statusCode},\"Error downloading %s. \\nToo many redirects: \\n  %s\",o[0],o.join(\" \\n  \")));else{if(!s.headers.location)throw ono({status:s.statusCode},\"HTTP %d redirect with no location header\",s.statusCode);debug(\"HTTP %d redirect %s -> %s\",s.statusCode,e.href,s.headers.location);var u=url.resolve(e,s.headers.location);download(u,t,o).then(r,n)}else r(s.body||new Buffer(0))})[\"catch\"](function(t){n(ono(t,\"Error downloading\",e.href))})})}function get(e,t){return new Promise(function(o,r){debug(\"GET\",e.href);var n=\"https:\"===e.protocol?https:http,s=n.get({hostname:e.hostname,port:e.port,path:e.path,auth:e.auth,headers:t.headers||{},withCredentials:t.withCredentials});\"function\"==typeof s.setTimeout&&s.setTimeout(t.timeout),s.on(\"timeout\",function(){s.abort()}),s.on(\"error\",r),s.once(\"response\",function(e){e.body=new Buffer(0),e.on(\"data\",function(t){e.body=Buffer.concat([e.body,new Buffer(t)])}),e.on(\"error\",r),e.on(\"end\",function(){o(e)})})})}var http=require(\"http\"),https=require(\"https\"),ono=require(\"ono\"),url=require(\"../util/url\"),debug=require(\"../util/debug\"),Promise=require(\"../util/promise\");module.exports={order:200,headers:null,timeout:5e3,redirects:5,withCredentials:!1,canRead:function(e){return url.isHttp(e.url)},read:function(e){var t=url.parse(e.url);return process.browser&&!t.protocol&&(t.protocol=url.parse(location.href).protocol),download(t,this)}};\n\n}).call(this,require('_process'),require(\"buffer\").Buffer)\n\n},{\"../util/debug\":16,\"../util/promise\":18,\"../util/url\":19,\"_process\":71,\"buffer\":25,\"http\":87,\"https\":33,\"ono\":69}],16:[function(require,module,exports){\n\"use strict\";var debug=require(\"debug\");module.exports=debug(\"json-schema-ref-parser\");\n\n},{\"debug\":29}],17:[function(require,module,exports){\n\"use strict\";function getResult(e,r,t,n){var u=e[r];if(\"function\"==typeof u)return u.apply(e,[t,n]);if(!n){if(u instanceof RegExp)return u.test(t.url);if(\"string\"==typeof u)return u===t.extension;if(Array.isArray(u))return-1!==u.indexOf(t.extension)}return u}var Promise=require(\"./promise\"),debug=require(\"./debug\");exports.all=function(e){return Object.keys(e).filter(function(r){return\"object\"==typeof e[r]}).map(function(r){return e[r].name=r,e[r]})},exports.filter=function(e,r,t){return e.filter(function(e){return!!getResult(e,r,t)})},exports.sort=function(e){return e.forEach(function(e){e.order=e.order||Number.MAX_SAFE_INTEGER}),e.sort(function(e,r){return e.order-r.order})},exports.run=function(e,r,t){var n,u,i=0;return new Promise(function(o,f){function s(){if(n=e[i++],!n)return f(u);try{debug(\"  %s\",n.name);var o=getResult(n,r,t,c);o&&\"function\"==typeof o.then?o.then(a,p):void 0!==o&&a(o)}catch(s){p(s)}}function c(e,r){e?p(e):a(r)}function a(e){debug(\"    success\"),o({plugin:n,result:e})}function p(e){debug(\"    %s\",e.message||e),u=e,s()}s()})};\n\n},{\"./debug\":16,\"./promise\":18}],18:[function(require,module,exports){\n\"use strict\";module.exports=\"function\"==typeof Promise?Promise:require(\"es6-promise\").Promise;\n\n},{\"es6-promise\":31}],19:[function(require,module,exports){\n(function (process){\n\"use strict\";var isWindows=/^win/.test(process.platform),forwardSlashPattern=/\\//g,protocolPattern=/^([a-z0-9.+-]+):\\/\\//i,url=module.exports,urlEncodePatterns=[/\\?/g,\"%3F\",/\\#/g,\"%23\",isWindows?/\\\\/g:/\\//,\"/\"],urlDecodePatterns=[/\\%23/g,\"#\",/\\%24/g,\"$\",/\\%26/g,\"&\",/\\%2C/g,\",\",/\\%40/g,\"@\"];exports.parse=require(\"url\").parse,exports.resolve=require(\"url\").resolve,exports.cwd=function(){return process.browser?location.href:process.cwd()+\"/\"},exports.getProtocol=function(r){var e=protocolPattern.exec(r);return e?e[1].toLowerCase():void 0},exports.getExtension=function(r){var e=r.lastIndexOf(\".\");return e>=0?r.substr(e).toLowerCase():\"\"},exports.getHash=function(r){var e=r.indexOf(\"#\");return e>=0?r.substr(e):\"#\"},exports.stripHash=function(r){var e=r.indexOf(\"#\");return e>=0&&(r=r.substr(0,e)),r},exports.isHttp=function(r){var e=url.getProtocol(r);return\"http\"===e||\"https\"===e?!0:void 0===e?process.browser:!1},exports.isFileSystemPath=function(r){if(process.browser)return!1;var e=url.getProtocol(r);return void 0===e||\"file\"===e},exports.fromFileSystemPath=function(r){for(var e=0;e<urlEncodePatterns.length;e+=2)r=r.replace(urlEncodePatterns[e],urlEncodePatterns[e+1]);return encodeURI(r)},exports.toFileSystemPath=function(r,e){r=decodeURI(r);for(var t=0;t<urlDecodePatterns.length;t+=2)r=r.replace(urlDecodePatterns[t],urlDecodePatterns[t+1]);var o=\"file://\"===r.substr(0,7).toLowerCase();return o&&(r=\"/\"===r[7]?r.substr(8):r.substr(7),isWindows&&\"/\"===r[1]&&(r=r[0]+\":\"+r.substr(1)),e?r=\"file:///\"+r:(o=!1,r=isWindows?r:\"/\"+r)),isWindows&&!o&&(r=r.replace(forwardSlashPattern,\"\\\\\")),r};\n\n}).call(this,require('_process'))\n\n},{\"_process\":71,\"url\":93}],20:[function(require,module,exports){\n\"use strict\";var yaml=require(\"js-yaml\"),ono=require(\"ono\");module.exports={parse:function(r,e){try{return yaml.safeLoad(r)}catch(o){throw o instanceof Error?o:ono(o,o.message)}},stringify:function(r,e,o){try{var t=(\"string\"==typeof o?o.length:o)||2;return yaml.safeDump(r,{indent:t})}catch(n){throw n instanceof Error?n:ono(n,n.message)}}};\n\n},{\"js-yaml\":38,\"ono\":69}],21:[function(require,module,exports){\n\"use strict\";module.exports={order:100,canValidate:function(e){return!!e.resolved},validate:function(e){}};\n\n},{}],22:[function(require,module,exports){\nvar lookup=\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";!function(t){\"use strict\";function r(t){var r=t.charCodeAt(0);return r===h||r===u?62:r===c||r===f?63:o>r?-1:o+10>r?r-o+26+26:i+26>r?r-i:A+26>r?r-A+26:void 0}function e(t){function e(t){i[f++]=t}var n,h,c,o,A,i;if(t.length%4>0)throw new Error(\"Invalid string. Length must be a multiple of 4\");var u=t.length;A=\"=\"===t.charAt(u-2)?2:\"=\"===t.charAt(u-1)?1:0,i=new a(3*t.length/4-A),c=A>0?t.length-4:t.length;var f=0;for(n=0,h=0;c>n;n+=4,h+=3)o=r(t.charAt(n))<<18|r(t.charAt(n+1))<<12|r(t.charAt(n+2))<<6|r(t.charAt(n+3)),e((16711680&o)>>16),e((65280&o)>>8),e(255&o);return 2===A?(o=r(t.charAt(n))<<2|r(t.charAt(n+1))>>4,e(255&o)):1===A&&(o=r(t.charAt(n))<<10|r(t.charAt(n+1))<<4|r(t.charAt(n+2))>>2,e(o>>8&255),e(255&o)),i}function n(t){function r(t){return lookup.charAt(t)}function e(t){return r(t>>18&63)+r(t>>12&63)+r(t>>6&63)+r(63&t)}var n,a,h,c=t.length%3,o=\"\";for(n=0,h=t.length-c;h>n;n+=3)a=(t[n]<<16)+(t[n+1]<<8)+t[n+2],o+=e(a);switch(c){case 1:a=t[t.length-1],o+=r(a>>2),o+=r(a<<4&63),o+=\"==\";break;case 2:a=(t[t.length-2]<<8)+t[t.length-1],o+=r(a>>10),o+=r(a>>4&63),o+=r(a<<2&63),o+=\"=\"}return o}var a=\"undefined\"!=typeof Uint8Array?Uint8Array:Array,h=\"+\".charCodeAt(0),c=\"/\".charCodeAt(0),o=\"0\".charCodeAt(0),A=\"a\".charCodeAt(0),i=\"A\".charCodeAt(0),u=\"-\".charCodeAt(0),f=\"_\".charCodeAt(0);t.toByteArray=e,t.fromByteArray=n}(\"undefined\"==typeof exports?this.base64js={}:exports);\n\n},{}],23:[function(require,module,exports){\n\n},{}],24:[function(require,module,exports){\n\n},{}],25:[function(require,module,exports){\n(function (global){\n/*!\n * The buffer module from node.js, for the browser.\n *\n * @author   Feross Aboukhadijeh <feross@feross.org> <http://feross.org>\n * @license  MIT\n */\n\"use strict\";function typedArraySupport(){function t(){}try{var e=new Uint8Array(1);return e.foo=function(){return 42},e.constructor=t,42===e.foo()&&e.constructor===t&&\"function\"==typeof e.subarray&&0===e.subarray(1,1).byteLength}catch(r){return!1}}function kMaxLength(){return Buffer.TYPED_ARRAY_SUPPORT?2147483647:1073741823}function Buffer(t){return this instanceof Buffer?(Buffer.TYPED_ARRAY_SUPPORT||(this.length=0,this.parent=void 0),\"number\"==typeof t?fromNumber(this,t):\"string\"==typeof t?fromString(this,t,arguments.length>1?arguments[1]:\"utf8\"):fromObject(this,t)):arguments.length>1?new Buffer(t,arguments[1]):new Buffer(t)}function fromNumber(t,e){if(t=allocate(t,0>e?0:0|checked(e)),!Buffer.TYPED_ARRAY_SUPPORT)for(var r=0;e>r;r++)t[r]=0;return t}function fromString(t,e,r){\"string\"==typeof r&&\"\"!==r||(r=\"utf8\");var n=0|byteLength(e,r);return t=allocate(t,n),t.write(e,r),t}function fromObject(t,e){if(Buffer.isBuffer(e))return fromBuffer(t,e);if(isArray(e))return fromArray(t,e);if(null==e)throw new TypeError(\"must start with number, buffer, array or string\");if(\"undefined\"!=typeof ArrayBuffer){if(e.buffer instanceof ArrayBuffer)return fromTypedArray(t,e);if(e instanceof ArrayBuffer)return fromArrayBuffer(t,e)}return e.length?fromArrayLike(t,e):fromJsonObject(t,e)}function fromBuffer(t,e){var r=0|checked(e.length);return t=allocate(t,r),e.copy(t,0,0,r),t}function fromArray(t,e){var r=0|checked(e.length);t=allocate(t,r);for(var n=0;r>n;n+=1)t[n]=255&e[n];return t}function fromTypedArray(t,e){var r=0|checked(e.length);t=allocate(t,r);for(var n=0;r>n;n+=1)t[n]=255&e[n];return t}function fromArrayBuffer(t,e){return Buffer.TYPED_ARRAY_SUPPORT?(e.byteLength,t=Buffer._augment(new Uint8Array(e))):t=fromTypedArray(t,new Uint8Array(e)),t}function fromArrayLike(t,e){var r=0|checked(e.length);t=allocate(t,r);for(var n=0;r>n;n+=1)t[n]=255&e[n];return t}function fromJsonObject(t,e){var r,n=0;\"Buffer\"===e.type&&isArray(e.data)&&(r=e.data,n=0|checked(r.length)),t=allocate(t,n);for(var f=0;n>f;f+=1)t[f]=255&r[f];return t}function allocate(t,e){Buffer.TYPED_ARRAY_SUPPORT?(t=Buffer._augment(new Uint8Array(e)),t.__proto__=Buffer.prototype):(t.length=e,t._isBuffer=!0);var r=0!==e&&e<=Buffer.poolSize>>>1;return r&&(t.parent=rootParent),t}function checked(t){if(t>=kMaxLength())throw new RangeError(\"Attempt to allocate Buffer larger than maximum size: 0x\"+kMaxLength().toString(16)+\" bytes\");return 0|t}function SlowBuffer(t,e){if(!(this instanceof SlowBuffer))return new SlowBuffer(t,e);var r=new Buffer(t,e);return delete r.parent,r}function byteLength(t,e){\"string\"!=typeof t&&(t=\"\"+t);var r=t.length;if(0===r)return 0;for(var n=!1;;)switch(e){case\"ascii\":case\"binary\":case\"raw\":case\"raws\":return r;case\"utf8\":case\"utf-8\":return utf8ToBytes(t).length;case\"ucs2\":case\"ucs-2\":case\"utf16le\":case\"utf-16le\":return 2*r;case\"hex\":return r>>>1;case\"base64\":return base64ToBytes(t).length;default:if(n)return utf8ToBytes(t).length;e=(\"\"+e).toLowerCase(),n=!0}}function slowToString(t,e,r){var n=!1;if(e=0|e,r=void 0===r||r===1/0?this.length:0|r,t||(t=\"utf8\"),0>e&&(e=0),r>this.length&&(r=this.length),e>=r)return\"\";for(;;)switch(t){case\"hex\":return hexSlice(this,e,r);case\"utf8\":case\"utf-8\":return utf8Slice(this,e,r);case\"ascii\":return asciiSlice(this,e,r);case\"binary\":return binarySlice(this,e,r);case\"base64\":return base64Slice(this,e,r);case\"ucs2\":case\"ucs-2\":case\"utf16le\":case\"utf-16le\":return utf16leSlice(this,e,r);default:if(n)throw new TypeError(\"Unknown encoding: \"+t);t=(t+\"\").toLowerCase(),n=!0}}function hexWrite(t,e,r,n){r=Number(r)||0;var f=t.length-r;n?(n=Number(n),n>f&&(n=f)):n=f;var i=e.length;if(i%2!==0)throw new Error(\"Invalid hex string\");n>i/2&&(n=i/2);for(var o=0;n>o;o++){var u=parseInt(e.substr(2*o,2),16);if(isNaN(u))throw new Error(\"Invalid hex string\");t[r+o]=u}return o}function utf8Write(t,e,r,n){return blitBuffer(utf8ToBytes(e,t.length-r),t,r,n)}function asciiWrite(t,e,r,n){return blitBuffer(asciiToBytes(e),t,r,n)}function binaryWrite(t,e,r,n){return asciiWrite(t,e,r,n)}function base64Write(t,e,r,n){return blitBuffer(base64ToBytes(e),t,r,n)}function ucs2Write(t,e,r,n){return blitBuffer(utf16leToBytes(e,t.length-r),t,r,n)}function base64Slice(t,e,r){return 0===e&&r===t.length?base64.fromByteArray(t):base64.fromByteArray(t.slice(e,r))}function utf8Slice(t,e,r){r=Math.min(t.length,r);for(var n=[],f=e;r>f;){var i=t[f],o=null,u=i>239?4:i>223?3:i>191?2:1;if(r>=f+u){var s,a,h,c;switch(u){case 1:128>i&&(o=i);break;case 2:s=t[f+1],128===(192&s)&&(c=(31&i)<<6|63&s,c>127&&(o=c));break;case 3:s=t[f+1],a=t[f+2],128===(192&s)&&128===(192&a)&&(c=(15&i)<<12|(63&s)<<6|63&a,c>2047&&(55296>c||c>57343)&&(o=c));break;case 4:s=t[f+1],a=t[f+2],h=t[f+3],128===(192&s)&&128===(192&a)&&128===(192&h)&&(c=(15&i)<<18|(63&s)<<12|(63&a)<<6|63&h,c>65535&&1114112>c&&(o=c))}}null===o?(o=65533,u=1):o>65535&&(o-=65536,n.push(o>>>10&1023|55296),o=56320|1023&o),n.push(o),f+=u}return decodeCodePointsArray(n)}function decodeCodePointsArray(t){var e=t.length;if(MAX_ARGUMENTS_LENGTH>=e)return String.fromCharCode.apply(String,t);for(var r=\"\",n=0;e>n;)r+=String.fromCharCode.apply(String,t.slice(n,n+=MAX_ARGUMENTS_LENGTH));return r}function asciiSlice(t,e,r){var n=\"\";r=Math.min(t.length,r);for(var f=e;r>f;f++)n+=String.fromCharCode(127&t[f]);return n}function binarySlice(t,e,r){var n=\"\";r=Math.min(t.length,r);for(var f=e;r>f;f++)n+=String.fromCharCode(t[f]);return n}function hexSlice(t,e,r){var n=t.length;(!e||0>e)&&(e=0),(!r||0>r||r>n)&&(r=n);for(var f=\"\",i=e;r>i;i++)f+=toHex(t[i]);return f}function utf16leSlice(t,e,r){for(var n=t.slice(e,r),f=\"\",i=0;i<n.length;i+=2)f+=String.fromCharCode(n[i]+256*n[i+1]);return f}function checkOffset(t,e,r){if(t%1!==0||0>t)throw new RangeError(\"offset is not uint\");if(t+e>r)throw new RangeError(\"Trying to access beyond buffer length\")}function checkInt(t,e,r,n,f,i){if(!Buffer.isBuffer(t))throw new TypeError(\"buffer must be a Buffer instance\");if(e>f||i>e)throw new RangeError(\"value is out of bounds\");if(r+n>t.length)throw new RangeError(\"index out of range\")}function objectWriteUInt16(t,e,r,n){0>e&&(e=65535+e+1);for(var f=0,i=Math.min(t.length-r,2);i>f;f++)t[r+f]=(e&255<<8*(n?f:1-f))>>>8*(n?f:1-f)}function objectWriteUInt32(t,e,r,n){0>e&&(e=4294967295+e+1);for(var f=0,i=Math.min(t.length-r,4);i>f;f++)t[r+f]=e>>>8*(n?f:3-f)&255}function checkIEEE754(t,e,r,n,f,i){if(e>f||i>e)throw new RangeError(\"value is out of bounds\");if(r+n>t.length)throw new RangeError(\"index out of range\");if(0>r)throw new RangeError(\"index out of range\")}function writeFloat(t,e,r,n,f){return f||checkIEEE754(t,e,r,4,3.4028234663852886e38,-3.4028234663852886e38),ieee754.write(t,e,r,n,23,4),r+4}function writeDouble(t,e,r,n,f){return f||checkIEEE754(t,e,r,8,1.7976931348623157e308,-1.7976931348623157e308),ieee754.write(t,e,r,n,52,8),r+8}function base64clean(t){if(t=stringtrim(t).replace(INVALID_BASE64_RE,\"\"),t.length<2)return\"\";for(;t.length%4!==0;)t+=\"=\";return t}function stringtrim(t){return t.trim?t.trim():t.replace(/^\\s+|\\s+$/g,\"\")}function toHex(t){return 16>t?\"0\"+t.toString(16):t.toString(16)}function utf8ToBytes(t,e){e=e||1/0;for(var r,n=t.length,f=null,i=[],o=0;n>o;o++){if(r=t.charCodeAt(o),r>55295&&57344>r){if(!f){if(r>56319){(e-=3)>-1&&i.push(239,191,189);continue}if(o+1===n){(e-=3)>-1&&i.push(239,191,189);continue}f=r;continue}if(56320>r){(e-=3)>-1&&i.push(239,191,189),f=r;continue}r=(f-55296<<10|r-56320)+65536}else f&&(e-=3)>-1&&i.push(239,191,189);if(f=null,128>r){if((e-=1)<0)break;i.push(r)}else if(2048>r){if((e-=2)<0)break;i.push(r>>6|192,63&r|128)}else if(65536>r){if((e-=3)<0)break;i.push(r>>12|224,r>>6&63|128,63&r|128)}else{if(!(1114112>r))throw new Error(\"Invalid code point\");if((e-=4)<0)break;i.push(r>>18|240,r>>12&63|128,r>>6&63|128,63&r|128)}}return i}function asciiToBytes(t){for(var e=[],r=0;r<t.length;r++)e.push(255&t.charCodeAt(r));return e}function utf16leToBytes(t,e){for(var r,n,f,i=[],o=0;o<t.length&&!((e-=2)<0);o++)r=t.charCodeAt(o),n=r>>8,f=r%256,i.push(f),i.push(n);return i}function base64ToBytes(t){return base64.toByteArray(base64clean(t))}function blitBuffer(t,e,r,n){for(var f=0;n>f&&!(f+r>=e.length||f>=t.length);f++)e[f+r]=t[f];return f}var base64=require(\"base64-js\"),ieee754=require(\"ieee754\"),isArray=require(\"isarray\");exports.Buffer=Buffer,exports.SlowBuffer=SlowBuffer,exports.INSPECT_MAX_BYTES=50,Buffer.poolSize=8192;var rootParent={};Buffer.TYPED_ARRAY_SUPPORT=void 0!==global.TYPED_ARRAY_SUPPORT?global.TYPED_ARRAY_SUPPORT:typedArraySupport(),Buffer.TYPED_ARRAY_SUPPORT?(Buffer.prototype.__proto__=Uint8Array.prototype,Buffer.__proto__=Uint8Array):(Buffer.prototype.length=void 0,Buffer.prototype.parent=void 0),Buffer.isBuffer=function(t){return!(null==t||!t._isBuffer)},Buffer.compare=function(t,e){if(!Buffer.isBuffer(t)||!Buffer.isBuffer(e))throw new TypeError(\"Arguments must be Buffers\");if(t===e)return 0;for(var r=t.length,n=e.length,f=0,i=Math.min(r,n);i>f&&t[f]===e[f];)++f;return f!==i&&(r=t[f],n=e[f]),n>r?-1:r>n?1:0},Buffer.isEncoding=function(t){switch(String(t).toLowerCase()){case\"hex\":case\"utf8\":case\"utf-8\":case\"ascii\":case\"binary\":case\"base64\":case\"raw\":case\"ucs2\":case\"ucs-2\":case\"utf16le\":case\"utf-16le\":return!0;default:return!1}},Buffer.concat=function(t,e){if(!isArray(t))throw new TypeError(\"list argument must be an Array of Buffers.\");if(0===t.length)return new Buffer(0);var r;if(void 0===e)for(e=0,r=0;r<t.length;r++)e+=t[r].length;var n=new Buffer(e),f=0;for(r=0;r<t.length;r++){var i=t[r];i.copy(n,f),f+=i.length}return n},Buffer.byteLength=byteLength,Buffer.prototype.toString=function(){var t=0|this.length;return 0===t?\"\":0===arguments.length?utf8Slice(this,0,t):slowToString.apply(this,arguments)},Buffer.prototype.equals=function(t){if(!Buffer.isBuffer(t))throw new TypeError(\"Argument must be a Buffer\");return this===t?!0:0===Buffer.compare(this,t)},Buffer.prototype.inspect=function(){var t=\"\",e=exports.INSPECT_MAX_BYTES;return this.length>0&&(t=this.toString(\"hex\",0,e).match(/.{2}/g).join(\" \"),this.length>e&&(t+=\" ... \")),\"<Buffer \"+t+\">\"},Buffer.prototype.compare=function(t){if(!Buffer.isBuffer(t))throw new TypeError(\"Argument must be a Buffer\");return this===t?0:Buffer.compare(this,t)},Buffer.prototype.indexOf=function(t,e){function r(t,e,r){for(var n=-1,f=0;r+f<t.length;f++)if(t[r+f]===e[-1===n?0:f-n]){if(-1===n&&(n=f),f-n+1===e.length)return r+n}else n=-1;return-1}if(e>2147483647?e=2147483647:-2147483648>e&&(e=-2147483648),e>>=0,0===this.length)return-1;if(e>=this.length)return-1;if(0>e&&(e=Math.max(this.length+e,0)),\"string\"==typeof t)return 0===t.length?-1:String.prototype.indexOf.call(this,t,e);if(Buffer.isBuffer(t))return r(this,t,e);if(\"number\"==typeof t)return Buffer.TYPED_ARRAY_SUPPORT&&\"function\"===Uint8Array.prototype.indexOf?Uint8Array.prototype.indexOf.call(this,t,e):r(this,[t],e);throw new TypeError(\"val must be string, number or Buffer\")},Buffer.prototype.get=function(t){return console.log(\".get() is deprecated. Access using array indexes instead.\"),this.readUInt8(t)},Buffer.prototype.set=function(t,e){return console.log(\".set() is deprecated. Access using array indexes instead.\"),this.writeUInt8(t,e)},Buffer.prototype.write=function(t,e,r,n){if(void 0===e)n=\"utf8\",r=this.length,e=0;else if(void 0===r&&\"string\"==typeof e)n=e,r=this.length,e=0;else if(isFinite(e))e=0|e,isFinite(r)?(r=0|r,void 0===n&&(n=\"utf8\")):(n=r,r=void 0);else{var f=n;n=e,e=0|r,r=f}var i=this.length-e;if((void 0===r||r>i)&&(r=i),t.length>0&&(0>r||0>e)||e>this.length)throw new RangeError(\"attempt to write outside buffer bounds\");n||(n=\"utf8\");for(var o=!1;;)switch(n){case\"hex\":return hexWrite(this,t,e,r);case\"utf8\":case\"utf-8\":return utf8Write(this,t,e,r);case\"ascii\":return asciiWrite(this,t,e,r);case\"binary\":return binaryWrite(this,t,e,r);case\"base64\":return base64Write(this,t,e,r);case\"ucs2\":case\"ucs-2\":case\"utf16le\":case\"utf-16le\":return ucs2Write(this,t,e,r);default:if(o)throw new TypeError(\"Unknown encoding: \"+n);n=(\"\"+n).toLowerCase(),o=!0}},Buffer.prototype.toJSON=function(){return{type:\"Buffer\",data:Array.prototype.slice.call(this._arr||this,0)}};var MAX_ARGUMENTS_LENGTH=4096;Buffer.prototype.slice=function(t,e){var r=this.length;t=~~t,e=void 0===e?r:~~e,0>t?(t+=r,0>t&&(t=0)):t>r&&(t=r),0>e?(e+=r,0>e&&(e=0)):e>r&&(e=r),t>e&&(e=t);var n;if(Buffer.TYPED_ARRAY_SUPPORT)n=Buffer._augment(this.subarray(t,e));else{var f=e-t;n=new Buffer(f,void 0);for(var i=0;f>i;i++)n[i]=this[i+t]}return n.length&&(n.parent=this.parent||this),n},Buffer.prototype.readUIntLE=function(t,e,r){t=0|t,e=0|e,r||checkOffset(t,e,this.length);for(var n=this[t],f=1,i=0;++i<e&&(f*=256);)n+=this[t+i]*f;return n},Buffer.prototype.readUIntBE=function(t,e,r){t=0|t,e=0|e,r||checkOffset(t,e,this.length);for(var n=this[t+--e],f=1;e>0&&(f*=256);)n+=this[t+--e]*f;return n},Buffer.prototype.readUInt8=function(t,e){return e||checkOffset(t,1,this.length),this[t]},Buffer.prototype.readUInt16LE=function(t,e){return e||checkOffset(t,2,this.length),this[t]|this[t+1]<<8},Buffer.prototype.readUInt16BE=function(t,e){return e||checkOffset(t,2,this.length),this[t]<<8|this[t+1]},Buffer.prototype.readUInt32LE=function(t,e){return e||checkOffset(t,4,this.length),(this[t]|this[t+1]<<8|this[t+2]<<16)+16777216*this[t+3]},Buffer.prototype.readUInt32BE=function(t,e){return e||checkOffset(t,4,this.length),16777216*this[t]+(this[t+1]<<16|this[t+2]<<8|this[t+3])},Buffer.prototype.readIntLE=function(t,e,r){t=0|t,e=0|e,r||checkOffset(t,e,this.length);for(var n=this[t],f=1,i=0;++i<e&&(f*=256);)n+=this[t+i]*f;return f*=128,n>=f&&(n-=Math.pow(2,8*e)),n},Buffer.prototype.readIntBE=function(t,e,r){t=0|t,e=0|e,r||checkOffset(t,e,this.length);for(var n=e,f=1,i=this[t+--n];n>0&&(f*=256);)i+=this[t+--n]*f;return f*=128,i>=f&&(i-=Math.pow(2,8*e)),i},Buffer.prototype.readInt8=function(t,e){return e||checkOffset(t,1,this.length),128&this[t]?-1*(255-this[t]+1):this[t]},Buffer.prototype.readInt16LE=function(t,e){e||checkOffset(t,2,this.length);var r=this[t]|this[t+1]<<8;return 32768&r?4294901760|r:r},Buffer.prototype.readInt16BE=function(t,e){e||checkOffset(t,2,this.length);var r=this[t+1]|this[t]<<8;return 32768&r?4294901760|r:r},Buffer.prototype.readInt32LE=function(t,e){return e||checkOffset(t,4,this.length),this[t]|this[t+1]<<8|this[t+2]<<16|this[t+3]<<24},Buffer.prototype.readInt32BE=function(t,e){return e||checkOffset(t,4,this.length),this[t]<<24|this[t+1]<<16|this[t+2]<<8|this[t+3]},Buffer.prototype.readFloatLE=function(t,e){return e||checkOffset(t,4,this.length),ieee754.read(this,t,!0,23,4)},Buffer.prototype.readFloatBE=function(t,e){return e||checkOffset(t,4,this.length),ieee754.read(this,t,!1,23,4)},Buffer.prototype.readDoubleLE=function(t,e){return e||checkOffset(t,8,this.length),ieee754.read(this,t,!0,52,8)},Buffer.prototype.readDoubleBE=function(t,e){return e||checkOffset(t,8,this.length),ieee754.read(this,t,!1,52,8)},Buffer.prototype.writeUIntLE=function(t,e,r,n){t=+t,e=0|e,r=0|r,n||checkInt(this,t,e,r,Math.pow(2,8*r),0);var f=1,i=0;for(this[e]=255&t;++i<r&&(f*=256);)this[e+i]=t/f&255;return e+r},Buffer.prototype.writeUIntBE=function(t,e,r,n){t=+t,e=0|e,r=0|r,n||checkInt(this,t,e,r,Math.pow(2,8*r),0);var f=r-1,i=1;for(this[e+f]=255&t;--f>=0&&(i*=256);)this[e+f]=t/i&255;return e+r},Buffer.prototype.writeUInt8=function(t,e,r){return t=+t,e=0|e,r||checkInt(this,t,e,1,255,0),Buffer.TYPED_ARRAY_SUPPORT||(t=Math.floor(t)),this[e]=255&t,e+1},Buffer.prototype.writeUInt16LE=function(t,e,r){return t=+t,e=0|e,r||checkInt(this,t,e,2,65535,0),Buffer.TYPED_ARRAY_SUPPORT?(this[e]=255&t,this[e+1]=t>>>8):objectWriteUInt16(this,t,e,!0),e+2},Buffer.prototype.writeUInt16BE=function(t,e,r){return t=+t,e=0|e,r||checkInt(this,t,e,2,65535,0),Buffer.TYPED_ARRAY_SUPPORT?(this[e]=t>>>8,this[e+1]=255&t):objectWriteUInt16(this,t,e,!1),e+2},Buffer.prototype.writeUInt32LE=function(t,e,r){return t=+t,e=0|e,r||checkInt(this,t,e,4,4294967295,0),Buffer.TYPED_ARRAY_SUPPORT?(this[e+3]=t>>>24,this[e+2]=t>>>16,this[e+1]=t>>>8,this[e]=255&t):objectWriteUInt32(this,t,e,!0),e+4},Buffer.prototype.writeUInt32BE=function(t,e,r){return t=+t,e=0|e,r||checkInt(this,t,e,4,4294967295,0),Buffer.TYPED_ARRAY_SUPPORT?(this[e]=t>>>24,this[e+1]=t>>>16,this[e+2]=t>>>8,this[e+3]=255&t):objectWriteUInt32(this,t,e,!1),e+4},Buffer.prototype.writeIntLE=function(t,e,r,n){if(t=+t,e=0|e,!n){var f=Math.pow(2,8*r-1);checkInt(this,t,e,r,f-1,-f)}var i=0,o=1,u=0>t?1:0;for(this[e]=255&t;++i<r&&(o*=256);)this[e+i]=(t/o>>0)-u&255;return e+r},Buffer.prototype.writeIntBE=function(t,e,r,n){if(t=+t,e=0|e,!n){var f=Math.pow(2,8*r-1);checkInt(this,t,e,r,f-1,-f)}var i=r-1,o=1,u=0>t?1:0;for(this[e+i]=255&t;--i>=0&&(o*=256);)this[e+i]=(t/o>>0)-u&255;return e+r},Buffer.prototype.writeInt8=function(t,e,r){return t=+t,e=0|e,r||checkInt(this,t,e,1,127,-128),Buffer.TYPED_ARRAY_SUPPORT||(t=Math.floor(t)),0>t&&(t=255+t+1),this[e]=255&t,e+1},Buffer.prototype.writeInt16LE=function(t,e,r){return t=+t,e=0|e,r||checkInt(this,t,e,2,32767,-32768),Buffer.TYPED_ARRAY_SUPPORT?(this[e]=255&t,this[e+1]=t>>>8):objectWriteUInt16(this,t,e,!0),e+2},Buffer.prototype.writeInt16BE=function(t,e,r){return t=+t,e=0|e,r||checkInt(this,t,e,2,32767,-32768),Buffer.TYPED_ARRAY_SUPPORT?(this[e]=t>>>8,this[e+1]=255&t):objectWriteUInt16(this,t,e,!1),e+2},Buffer.prototype.writeInt32LE=function(t,e,r){return t=+t,e=0|e,r||checkInt(this,t,e,4,2147483647,-2147483648),Buffer.TYPED_ARRAY_SUPPORT?(this[e]=255&t,this[e+1]=t>>>8,this[e+2]=t>>>16,this[e+3]=t>>>24):objectWriteUInt32(this,t,e,!0),e+4},Buffer.prototype.writeInt32BE=function(t,e,r){return t=+t,e=0|e,r||checkInt(this,t,e,4,2147483647,-2147483648),0>t&&(t=4294967295+t+1),Buffer.TYPED_ARRAY_SUPPORT?(this[e]=t>>>24,this[e+1]=t>>>16,this[e+2]=t>>>8,this[e+3]=255&t):objectWriteUInt32(this,t,e,!1),e+4},Buffer.prototype.writeFloatLE=function(t,e,r){return writeFloat(this,t,e,!0,r)},Buffer.prototype.writeFloatBE=function(t,e,r){return writeFloat(this,t,e,!1,r)},Buffer.prototype.writeDoubleLE=function(t,e,r){return writeDouble(this,t,e,!0,r)},Buffer.prototype.writeDoubleBE=function(t,e,r){return writeDouble(this,t,e,!1,r)},Buffer.prototype.copy=function(t,e,r,n){if(r||(r=0),n||0===n||(n=this.length),e>=t.length&&(e=t.length),e||(e=0),n>0&&r>n&&(n=r),n===r)return 0;if(0===t.length||0===this.length)return 0;if(0>e)throw new RangeError(\"targetStart out of bounds\");if(0>r||r>=this.length)throw new RangeError(\"sourceStart out of bounds\");if(0>n)throw new RangeError(\"sourceEnd out of bounds\");n>this.length&&(n=this.length),t.length-e<n-r&&(n=t.length-e+r);var f,i=n-r;if(this===t&&e>r&&n>e)for(f=i-1;f>=0;f--)t[f+e]=this[f+r];else if(1e3>i||!Buffer.TYPED_ARRAY_SUPPORT)for(f=0;i>f;f++)t[f+e]=this[f+r];else t._set(this.subarray(r,r+i),e);return i},Buffer.prototype.fill=function(t,e,r){if(t||(t=0),e||(e=0),r||(r=this.length),e>r)throw new RangeError(\"end < start\");if(r!==e&&0!==this.length){if(0>e||e>=this.length)throw new RangeError(\"start out of bounds\");if(0>r||r>this.length)throw new RangeError(\"end out of bounds\");var n;if(\"number\"==typeof t)for(n=e;r>n;n++)this[n]=t;else{var f=utf8ToBytes(t.toString()),i=f.length;for(n=e;r>n;n++)this[n]=f[n%i]}return this}},Buffer.prototype.toArrayBuffer=function(){if(\"undefined\"!=typeof Uint8Array){if(Buffer.TYPED_ARRAY_SUPPORT)return new Buffer(this).buffer;for(var t=new Uint8Array(this.length),e=0,r=t.length;r>e;e+=1)t[e]=this[e];return t.buffer}throw new TypeError(\"Buffer.toArrayBuffer not supported in this browser\")};var BP=Buffer.prototype;Buffer._augment=function(t){return t.constructor=Buffer,t._isBuffer=!0,t._set=t.set,t.get=BP.get,t.set=BP.set,t.write=BP.write,t.toString=BP.toString,t.toLocaleString=BP.toString,t.toJSON=BP.toJSON,t.equals=BP.equals,t.compare=BP.compare,t.indexOf=BP.indexOf,t.copy=BP.copy,t.slice=BP.slice,t.readUIntLE=BP.readUIntLE,t.readUIntBE=BP.readUIntBE,t.readUInt8=BP.readUInt8,t.readUInt16LE=BP.readUInt16LE,t.readUInt16BE=BP.readUInt16BE,t.readUInt32LE=BP.readUInt32LE,t.readUInt32BE=BP.readUInt32BE,t.readIntLE=BP.readIntLE,t.readIntBE=BP.readIntBE,t.readInt8=BP.readInt8,t.readInt16LE=BP.readInt16LE,t.readInt16BE=BP.readInt16BE,t.readInt32LE=BP.readInt32LE,t.readInt32BE=BP.readInt32BE,t.readFloatLE=BP.readFloatLE,t.readFloatBE=BP.readFloatBE,t.readDoubleLE=BP.readDoubleLE,t.readDoubleBE=BP.readDoubleBE,t.writeUInt8=BP.writeUInt8,t.writeUIntLE=BP.writeUIntLE,t.writeUIntBE=BP.writeUIntBE,t.writeUInt16LE=BP.writeUInt16LE,t.writeUInt16BE=BP.writeUInt16BE,t.writeUInt32LE=BP.writeUInt32LE,t.writeUInt32BE=BP.writeUInt32BE,t.writeIntLE=BP.writeIntLE,t.writeIntBE=BP.writeIntBE,t.writeInt8=BP.writeInt8,t.writeInt16LE=BP.writeInt16LE,t.writeInt16BE=BP.writeInt16BE,t.writeInt32LE=BP.writeInt32LE,t.writeInt32BE=BP.writeInt32BE,t.writeFloatLE=BP.writeFloatLE,t.writeFloatBE=BP.writeFloatBE,t.writeDoubleLE=BP.writeDoubleLE,t.writeDoubleBE=BP.writeDoubleBE,t.fill=BP.fill,t.inspect=BP.inspect,t.toArrayBuffer=BP.toArrayBuffer,t};var INVALID_BASE64_RE=/[^+\\/0-9A-Za-z-_]/g;\n\n}).call(this,typeof global !== \"undefined\" ? global : typeof self !== \"undefined\" ? self : typeof window !== \"undefined\" ? window : {})\n\n},{\"base64-js\":22,\"ieee754\":34,\"isarray\":37}],26:[function(require,module,exports){\nmodule.exports={100:\"Continue\",101:\"Switching Protocols\",102:\"Processing\",200:\"OK\",201:\"Created\",202:\"Accepted\",203:\"Non-Authoritative Information\",204:\"No Content\",205:\"Reset Content\",206:\"Partial Content\",207:\"Multi-Status\",208:\"Already Reported\",226:\"IM Used\",300:\"Multiple Choices\",301:\"Moved Permanently\",302:\"Found\",303:\"See Other\",304:\"Not Modified\",305:\"Use Proxy\",307:\"Temporary Redirect\",308:\"Permanent Redirect\",400:\"Bad Request\",401:\"Unauthorized\",402:\"Payment Required\",403:\"Forbidden\",404:\"Not Found\",405:\"Method Not Allowed\",406:\"Not Acceptable\",407:\"Proxy Authentication Required\",408:\"Request Timeout\",409:\"Conflict\",410:\"Gone\",411:\"Length Required\",412:\"Precondition Failed\",413:\"Payload Too Large\",414:\"URI Too Long\",415:\"Unsupported Media Type\",416:\"Range Not Satisfiable\",417:\"Expectation Failed\",418:\"I'm a teapot\",421:\"Misdirected Request\",422:\"Unprocessable Entity\",423:\"Locked\",424:\"Failed Dependency\",425:\"Unordered Collection\",426:\"Upgrade Required\",428:\"Precondition Required\",429:\"Too Many Requests\",431:\"Request Header Fields Too Large\",500:\"Internal Server Error\",501:\"Not Implemented\",502:\"Bad Gateway\",503:\"Service Unavailable\",504:\"Gateway Timeout\",505:\"HTTP Version Not Supported\",506:\"Variant Also Negotiates\",507:\"Insufficient Storage\",508:\"Loop Detected\",509:\"Bandwidth Limit Exceeded\",510:\"Not Extended\",511:\"Network Authentication Required\"};\n\n},{}],27:[function(require,module,exports){\n(function (process,global){\n\"use strict\";var next=global.process&&process.nextTick||global.setImmediate||function(n){setTimeout(n,0)};module.exports=function(n,t){return n?void t.then(function(t){next(function(){n(null,t)})},function(t){next(function(){n(t)})}):t};\n\n}).call(this,require('_process'),typeof global !== \"undefined\" ? global : typeof self !== \"undefined\" ? self : typeof window !== \"undefined\" ? window : {})\n\n},{\"_process\":71}],28:[function(require,module,exports){\n(function (Buffer){\nfunction isArray(r){return Array.isArray?Array.isArray(r):\"[object Array]\"===objectToString(r)}function isBoolean(r){return\"boolean\"==typeof r}function isNull(r){return null===r}function isNullOrUndefined(r){return null==r}function isNumber(r){return\"number\"==typeof r}function isString(r){return\"string\"==typeof r}function isSymbol(r){return\"symbol\"==typeof r}function isUndefined(r){return void 0===r}function isRegExp(r){return\"[object RegExp]\"===objectToString(r)}function isObject(r){return\"object\"==typeof r&&null!==r}function isDate(r){return\"[object Date]\"===objectToString(r)}function isError(r){return\"[object Error]\"===objectToString(r)||r instanceof Error}function isFunction(r){return\"function\"==typeof r}function isPrimitive(r){return null===r||\"boolean\"==typeof r||\"number\"==typeof r||\"string\"==typeof r||\"symbol\"==typeof r||\"undefined\"==typeof r}function objectToString(r){return Object.prototype.toString.call(r)}exports.isArray=isArray,exports.isBoolean=isBoolean,exports.isNull=isNull,exports.isNullOrUndefined=isNullOrUndefined,exports.isNumber=isNumber,exports.isString=isString,exports.isSymbol=isSymbol,exports.isUndefined=isUndefined,exports.isRegExp=isRegExp,exports.isObject=isObject,exports.isDate=isDate,exports.isError=isError,exports.isFunction=isFunction,exports.isPrimitive=isPrimitive,exports.isBuffer=Buffer.isBuffer;\n\n}).call(this,{\"isBuffer\":require(\"../../is-buffer/index.js\")})\n\n},{\"../../is-buffer/index.js\":36}],29:[function(require,module,exports){\nfunction useColors(){return\"WebkitAppearance\"in document.documentElement.style||window.console&&(console.firebug||console.exception&&console.table)||navigator.userAgent.toLowerCase().match(/firefox\\/(\\d+)/)&&parseInt(RegExp.$1,10)>=31}function formatArgs(){var o=arguments,e=this.useColors;if(o[0]=(e?\"%c\":\"\")+this.namespace+(e?\" %c\":\" \")+o[0]+(e?\"%c \":\" \")+\"+\"+exports.humanize(this.diff),!e)return o;var r=\"color: \"+this.color;o=[o[0],r,\"color: inherit\"].concat(Array.prototype.slice.call(o,1));var t=0,s=0;return o[0].replace(/%[a-z%]/g,function(o){\"%%\"!==o&&(t++,\"%c\"===o&&(s=t))}),o.splice(s,0,r),o}function log(){return\"object\"==typeof console&&console.log&&Function.prototype.apply.call(console.log,console,arguments)}function save(o){try{null==o?exports.storage.removeItem(\"debug\"):exports.storage.debug=o}catch(e){}}function load(){var o;try{o=exports.storage.debug}catch(e){}return o}function localstorage(){try{return window.localStorage}catch(o){}}exports=module.exports=require(\"./debug\"),exports.log=log,exports.formatArgs=formatArgs,exports.save=save,exports.load=load,exports.useColors=useColors,exports.storage=\"undefined\"!=typeof chrome&&\"undefined\"!=typeof chrome.storage?chrome.storage.local:localstorage(),exports.colors=[\"lightseagreen\",\"forestgreen\",\"goldenrod\",\"dodgerblue\",\"darkorchid\",\"crimson\"],exports.formatters.j=function(o){return JSON.stringify(o)},exports.enable(load());\n\n},{\"./debug\":30}],30:[function(require,module,exports){\nfunction selectColor(){return exports.colors[prevColor++%exports.colors.length]}function debug(e){function r(){}function o(){var e=o,r=+new Date,s=r-(prevTime||r);e.diff=s,e.prev=prevTime,e.curr=r,prevTime=r,null==e.useColors&&(e.useColors=exports.useColors()),null==e.color&&e.useColors&&(e.color=selectColor());var t=Array.prototype.slice.call(arguments);t[0]=exports.coerce(t[0]),\"string\"!=typeof t[0]&&(t=[\"%o\"].concat(t));var n=0;t[0]=t[0].replace(/%([a-z%])/g,function(r,o){if(\"%%\"===r)return r;n++;var s=exports.formatters[o];if(\"function\"==typeof s){var p=t[n];r=s.call(e,p),t.splice(n,1),n--}return r}),\"function\"==typeof exports.formatArgs&&(t=exports.formatArgs.apply(e,t));var p=o.log||exports.log||console.log.bind(console);p.apply(e,t)}r.enabled=!1,o.enabled=!0;var s=exports.enabled(e)?o:r;return s.namespace=e,s}function enable(e){exports.save(e);for(var r=(e||\"\").split(/[\\s,]+/),o=r.length,s=0;o>s;s++)r[s]&&(e=r[s].replace(/\\*/g,\".*?\"),\"-\"===e[0]?exports.skips.push(new RegExp(\"^\"+e.substr(1)+\"$\")):exports.names.push(new RegExp(\"^\"+e+\"$\")))}function disable(){exports.enable(\"\")}function enabled(e){var r,o;for(r=0,o=exports.skips.length;o>r;r++)if(exports.skips[r].test(e))return!1;for(r=0,o=exports.names.length;o>r;r++)if(exports.names[r].test(e))return!0;return!1}function coerce(e){return e instanceof Error?e.stack||e.message:e}exports=module.exports=debug,exports.coerce=coerce,exports.disable=disable,exports.enable=enable,exports.enabled=enabled,exports.humanize=require(\"ms\"),exports.names=[],exports.skips=[],exports.formatters={};var prevColor=0,prevTime;\n\n},{\"ms\":68}],31:[function(require,module,exports){\n(function (process,global){\n/*!\n * @overview es6-promise - a tiny implementation of Promises/A+.\n * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald)\n * @license   Licensed under MIT license\n *            See https://raw.githubusercontent.com/jakearchibald/es6-promise/master/LICENSE\n * @version   3.1.2\n */\n(function(){\"use strict\";function t(t){return\"function\"==typeof t||\"object\"==typeof t&&null!==t}function e(t){return\"function\"==typeof t}function n(t){W=t}function r(t){H=t}function o(){return function(){process.nextTick(a)}}function i(){return function(){U(a)}}function s(){var t=0,e=new Q(a),n=document.createTextNode(\"\");return e.observe(n,{characterData:!0}),function(){n.data=t=++t%2}}function u(){var t=new MessageChannel;return t.port1.onmessage=a,function(){t.port2.postMessage(0)}}function c(){return function(){setTimeout(a,1)}}function a(){for(var t=0;G>t;t+=2){var e=X[t],n=X[t+1];e(n),X[t]=void 0,X[t+1]=void 0}G=0}function f(){try{var t=require,e=t(\"vertx\");return U=e.runOnLoop||e.runOnContext,i()}catch(n){return c()}}function l(t,e){var n=this,r=n._state;if(r===et&&!t||r===nt&&!e)return this;var o=new this.constructor(p),i=n._result;if(r){var s=arguments[r-1];H(function(){C(r,o,s,i)})}else j(n,o,t,e);return o}function h(t){var e=this;if(t&&\"object\"==typeof t&&t.constructor===e)return t;var n=new e(p);return g(n,t),n}function p(){}function _(){return new TypeError(\"You cannot resolve a promise with itself\")}function v(){return new TypeError(\"A promises callback cannot return that same promise.\")}function d(t){try{return t.then}catch(e){return rt.error=e,rt}}function y(t,e,n,r){try{t.call(e,n,r)}catch(o){return o}}function m(t,e,n){H(function(t){var r=!1,o=y(n,e,function(n){r||(r=!0,e!==n?g(t,n):E(t,n))},function(e){r||(r=!0,S(t,e))},\"Settle: \"+(t._label||\" unknown promise\"));!r&&o&&(r=!0,S(t,o))},t)}function w(t,e){e._state===et?E(t,e._result):e._state===nt?S(t,e._result):j(e,void 0,function(e){g(t,e)},function(e){S(t,e)})}function b(t,n,r){n.constructor===t.constructor&&r===Z&&constructor.resolve===$?w(t,n):r===rt?S(t,rt.error):void 0===r?E(t,n):e(r)?m(t,n,r):E(t,n)}function g(e,n){e===n?S(e,_()):t(n)?b(e,n,d(n)):E(e,n)}function A(t){t._onerror&&t._onerror(t._result),T(t)}function E(t,e){t._state===tt&&(t._result=e,t._state=et,0!==t._subscribers.length&&H(T,t))}function S(t,e){t._state===tt&&(t._state=nt,t._result=e,H(A,t))}function j(t,e,n,r){var o=t._subscribers,i=o.length;t._onerror=null,o[i]=e,o[i+et]=n,o[i+nt]=r,0===i&&t._state&&H(T,t)}function T(t){var e=t._subscribers,n=t._state;if(0!==e.length){for(var r,o,i=t._result,s=0;s<e.length;s+=3)r=e[s],o=e[s+n],r?C(n,r,o,i):o(i);t._subscribers.length=0}}function P(){this.error=null}function x(t,e){try{return t(e)}catch(n){return ot.error=n,ot}}function C(t,n,r,o){var i,s,u,c,a=e(r);if(a){if(i=x(r,o),i===ot?(c=!0,s=i.error,i=null):u=!0,n===i)return void S(n,v())}else i=o,u=!0;n._state!==tt||(a&&u?g(n,i):c?S(n,s):t===et?E(n,i):t===nt&&S(n,i))}function M(t,e){try{e(function(e){g(t,e)},function(e){S(t,e)})}catch(n){S(t,n)}}function O(t){return new ft(this,t).promise}function k(t){function e(t){g(o,t)}function n(t){S(o,t)}var r=this,o=new r(p);if(!B(t))return S(o,new TypeError(\"You must pass an array to race.\")),o;for(var i=t.length,s=0;o._state===tt&&i>s;s++)j(r.resolve(t[s]),void 0,e,n);return o}function Y(t){var e=this,n=new e(p);return S(n,t),n}function q(){throw new TypeError(\"You must pass a resolver function as the first argument to the promise constructor\")}function F(){throw new TypeError(\"Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.\")}function D(t){this._id=ct++,this._state=void 0,this._result=void 0,this._subscribers=[],p!==t&&(\"function\"!=typeof t&&q(),this instanceof D?M(this,t):F())}function K(t,e){this._instanceConstructor=t,this.promise=new t(p),Array.isArray(e)?(this._input=e,this.length=e.length,this._remaining=e.length,this._result=new Array(this.length),0===this.length?E(this.promise,this._result):(this.length=this.length||0,this._enumerate(),0===this._remaining&&E(this.promise,this._result))):S(this.promise,this._validationError())}function L(){var t;if(\"undefined\"!=typeof global)t=global;else if(\"undefined\"!=typeof self)t=self;else try{t=Function(\"return this\")()}catch(e){throw new Error(\"polyfill failed because global object is unavailable in this environment\")}var n=t.Promise;n&&\"[object Promise]\"===Object.prototype.toString.call(n.resolve())&&!n.cast||(t.Promise=at)}var N;N=Array.isArray?Array.isArray:function(t){return\"[object Array]\"===Object.prototype.toString.call(t)};var U,W,z,B=N,G=0,H=function(t,e){X[G]=t,X[G+1]=e,G+=2,2===G&&(W?W(a):z())},I=\"undefined\"!=typeof window?window:void 0,J=I||{},Q=J.MutationObserver||J.WebKitMutationObserver,R=\"undefined\"!=typeof process&&\"[object process]\"==={}.toString.call(process),V=\"undefined\"!=typeof Uint8ClampedArray&&\"undefined\"!=typeof importScripts&&\"undefined\"!=typeof MessageChannel,X=new Array(1e3);z=R?o():Q?s():V?u():void 0===I&&\"function\"==typeof require?f():c();var Z=l,$=h,tt=void 0,et=1,nt=2,rt=new P,ot=new P,it=O,st=k,ut=Y,ct=0,at=D;D.all=it,D.race=st,D.resolve=$,D.reject=ut,D._setScheduler=n,D._setAsap=r,D._asap=H,D.prototype={constructor:D,then:Z,\"catch\":function(t){return this.then(null,t)}};var ft=K;K.prototype._validationError=function(){return new Error(\"Array Methods must be provided an Array\")},K.prototype._enumerate=function(){for(var t=this.length,e=this._input,n=0;this._state===tt&&t>n;n++)this._eachEntry(e[n],n)},K.prototype._eachEntry=function(t,e){var n=this._instanceConstructor,r=n.resolve;if(r===$){var o=d(t);if(o===Z&&t._state!==tt)this._settledAt(t._state,e,t._result);else if(\"function\"!=typeof o)this._remaining--,this._result[e]=t;else if(n===at){var i=new n(p);b(i,t,o),this._willSettleAt(i,e)}else this._willSettleAt(new n(function(e){e(t)}),e)}else this._willSettleAt(r(t),e)},K.prototype._settledAt=function(t,e,n){var r=this.promise;r._state===tt&&(this._remaining--,t===nt?S(r,n):this._result[e]=n),0===this._remaining&&E(r,this._result)},K.prototype._willSettleAt=function(t,e){var n=this;j(t,void 0,function(t){n._settledAt(et,e,t)},function(t){n._settledAt(nt,e,t)})};var lt=L,ht={Promise:at,polyfill:lt};\"function\"==typeof define&&define.amd?define(function(){return ht}):\"undefined\"!=typeof module&&module.exports?module.exports=ht:\"undefined\"!=typeof this&&(this.ES6Promise=ht),lt()}).call(this);\n\n}).call(this,require('_process'),typeof global !== \"undefined\" ? global : typeof self !== \"undefined\" ? self : typeof window !== \"undefined\" ? window : {})\n\n},{\"_process\":71}],32:[function(require,module,exports){\nfunction EventEmitter(){this._events=this._events||{},this._maxListeners=this._maxListeners||void 0}function isFunction(e){return\"function\"==typeof e}function isNumber(e){return\"number\"==typeof e}function isObject(e){return\"object\"==typeof e&&null!==e}function isUndefined(e){return void 0===e}module.exports=EventEmitter,EventEmitter.EventEmitter=EventEmitter,EventEmitter.prototype._events=void 0,EventEmitter.prototype._maxListeners=void 0,EventEmitter.defaultMaxListeners=10,EventEmitter.prototype.setMaxListeners=function(e){if(!isNumber(e)||0>e||isNaN(e))throw TypeError(\"n must be a positive number\");return this._maxListeners=e,this},EventEmitter.prototype.emit=function(e){var t,i,n,s,r,o;if(this._events||(this._events={}),\"error\"===e&&(!this._events.error||isObject(this._events.error)&&!this._events.error.length)){if(t=arguments[1],t instanceof Error)throw t;throw TypeError('Uncaught, unspecified \"error\" event.')}if(i=this._events[e],isUndefined(i))return!1;if(isFunction(i))switch(arguments.length){case 1:i.call(this);break;case 2:i.call(this,arguments[1]);break;case 3:i.call(this,arguments[1],arguments[2]);break;default:s=Array.prototype.slice.call(arguments,1),i.apply(this,s)}else if(isObject(i))for(s=Array.prototype.slice.call(arguments,1),o=i.slice(),n=o.length,r=0;n>r;r++)o[r].apply(this,s);return!0},EventEmitter.prototype.addListener=function(e,t){var i;if(!isFunction(t))throw TypeError(\"listener must be a function\");return this._events||(this._events={}),this._events.newListener&&this.emit(\"newListener\",e,isFunction(t.listener)?t.listener:t),this._events[e]?isObject(this._events[e])?this._events[e].push(t):this._events[e]=[this._events[e],t]:this._events[e]=t,isObject(this._events[e])&&!this._events[e].warned&&(i=isUndefined(this._maxListeners)?EventEmitter.defaultMaxListeners:this._maxListeners,i&&i>0&&this._events[e].length>i&&(this._events[e].warned=!0,console.error(\"(node) warning: possible EventEmitter memory leak detected. %d listeners added. Use emitter.setMaxListeners() to increase limit.\",this._events[e].length),\"function\"==typeof console.trace&&console.trace())),this},EventEmitter.prototype.on=EventEmitter.prototype.addListener,EventEmitter.prototype.once=function(e,t){function i(){this.removeListener(e,i),n||(n=!0,t.apply(this,arguments))}if(!isFunction(t))throw TypeError(\"listener must be a function\");var n=!1;return i.listener=t,this.on(e,i),this},EventEmitter.prototype.removeListener=function(e,t){var i,n,s,r;if(!isFunction(t))throw TypeError(\"listener must be a function\");if(!this._events||!this._events[e])return this;if(i=this._events[e],s=i.length,n=-1,i===t||isFunction(i.listener)&&i.listener===t)delete this._events[e],this._events.removeListener&&this.emit(\"removeListener\",e,t);else if(isObject(i)){for(r=s;r-- >0;)if(i[r]===t||i[r].listener&&i[r].listener===t){n=r;break}if(0>n)return this;1===i.length?(i.length=0,delete this._events[e]):i.splice(n,1),this._events.removeListener&&this.emit(\"removeListener\",e,t)}return this},EventEmitter.prototype.removeAllListeners=function(e){var t,i;if(!this._events)return this;if(!this._events.removeListener)return 0===arguments.length?this._events={}:this._events[e]&&delete this._events[e],this;if(0===arguments.length){for(t in this._events)\"removeListener\"!==t&&this.removeAllListeners(t);return this.removeAllListeners(\"removeListener\"),this._events={},this}if(i=this._events[e],isFunction(i))this.removeListener(e,i);else if(i)for(;i.length;)this.removeListener(e,i[i.length-1]);return delete this._events[e],this},EventEmitter.prototype.listeners=function(e){var t;return t=this._events&&this._events[e]?isFunction(this._events[e])?[this._events[e]]:this._events[e].slice():[]},EventEmitter.prototype.listenerCount=function(e){if(this._events){var t=this._events[e];if(isFunction(t))return 1;if(t)return t.length}return 0},EventEmitter.listenerCount=function(e,t){return e.listenerCount(t)};\n\n},{}],33:[function(require,module,exports){\nvar http=require(\"http\"),https=module.exports;for(var key in http)http.hasOwnProperty(key)&&(https[key]=http[key]);https.request=function(t,e){return t||(t={}),t.scheme=\"https\",t.protocol=\"https:\",http.request.call(this,t,e)};\n\n},{\"http\":87}],34:[function(require,module,exports){\nexports.read=function(a,o,t,r,h){var M,p,w=8*h-r-1,f=(1<<w)-1,e=f>>1,i=-7,N=t?h-1:0,n=t?-1:1,s=a[o+N];for(N+=n,M=s&(1<<-i)-1,s>>=-i,i+=w;i>0;M=256*M+a[o+N],N+=n,i-=8);for(p=M&(1<<-i)-1,M>>=-i,i+=r;i>0;p=256*p+a[o+N],N+=n,i-=8);if(0===M)M=1-e;else{if(M===f)return p?NaN:(s?-1:1)*(1/0);p+=Math.pow(2,r),M-=e}return(s?-1:1)*p*Math.pow(2,M-r)},exports.write=function(a,o,t,r,h,M){var p,w,f,e=8*M-h-1,i=(1<<e)-1,N=i>>1,n=23===h?Math.pow(2,-24)-Math.pow(2,-77):0,s=r?0:M-1,u=r?1:-1,l=0>o||0===o&&0>1/o?1:0;for(o=Math.abs(o),isNaN(o)||o===1/0?(w=isNaN(o)?1:0,p=i):(p=Math.floor(Math.log(o)/Math.LN2),o*(f=Math.pow(2,-p))<1&&(p--,f*=2),o+=p+N>=1?n/f:n*Math.pow(2,1-N),o*f>=2&&(p++,f/=2),p+N>=i?(w=0,p=i):p+N>=1?(w=(o*f-1)*Math.pow(2,h),p+=N):(w=o*Math.pow(2,N-1)*Math.pow(2,h),p=0));h>=8;a[t+s]=255&w,s+=u,w/=256,h-=8);for(p=p<<h|w,e+=h;e>0;a[t+s]=255&p,s+=u,p/=256,e-=8);a[t+s-u]|=128*l};\n\n},{}],35:[function(require,module,exports){\n\"function\"==typeof Object.create?module.exports=function(t,e){t.super_=e,t.prototype=Object.create(e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}})}:module.exports=function(t,e){t.super_=e;var o=function(){};o.prototype=e.prototype,t.prototype=new o,t.prototype.constructor=t};\n\n},{}],36:[function(require,module,exports){\nmodule.exports=function(r){return!(null==r||!(r._isBuffer||r.constructor&&\"function\"==typeof r.constructor.isBuffer&&r.constructor.isBuffer(r)))};\n\n},{}],37:[function(require,module,exports){\nvar toString={}.toString;module.exports=Array.isArray||function(r){return\"[object Array]\"==toString.call(r)};\n\n},{}],38:[function(require,module,exports){\n\"use strict\";var yaml=require(\"./lib/js-yaml.js\");module.exports=yaml;\n\n},{\"./lib/js-yaml.js\":39}],39:[function(require,module,exports){\n\"use strict\";function deprecated(e){return function(){throw new Error(\"Function \"+e+\" is deprecated and cannot be used.\")}}var loader=require(\"./js-yaml/loader\"),dumper=require(\"./js-yaml/dumper\");module.exports.Type=require(\"./js-yaml/type\"),module.exports.Schema=require(\"./js-yaml/schema\"),module.exports.FAILSAFE_SCHEMA=require(\"./js-yaml/schema/failsafe\"),module.exports.JSON_SCHEMA=require(\"./js-yaml/schema/json\"),module.exports.CORE_SCHEMA=require(\"./js-yaml/schema/core\"),module.exports.DEFAULT_SAFE_SCHEMA=require(\"./js-yaml/schema/default_safe\"),module.exports.DEFAULT_FULL_SCHEMA=require(\"./js-yaml/schema/default_full\"),module.exports.load=loader.load,module.exports.loadAll=loader.loadAll,module.exports.safeLoad=loader.safeLoad,module.exports.safeLoadAll=loader.safeLoadAll,module.exports.dump=dumper.dump,module.exports.safeDump=dumper.safeDump,module.exports.YAMLException=require(\"./js-yaml/exception\"),module.exports.MINIMAL_SCHEMA=require(\"./js-yaml/schema/failsafe\"),module.exports.SAFE_SCHEMA=require(\"./js-yaml/schema/default_safe\"),module.exports.DEFAULT_SCHEMA=require(\"./js-yaml/schema/default_full\"),module.exports.scan=deprecated(\"scan\"),module.exports.parse=deprecated(\"parse\"),module.exports.compose=deprecated(\"compose\"),module.exports.addConstructor=deprecated(\"addConstructor\");\n\n},{\"./js-yaml/dumper\":41,\"./js-yaml/exception\":42,\"./js-yaml/loader\":43,\"./js-yaml/schema\":45,\"./js-yaml/schema/core\":46,\"./js-yaml/schema/default_full\":47,\"./js-yaml/schema/default_safe\":48,\"./js-yaml/schema/failsafe\":49,\"./js-yaml/schema/json\":50,\"./js-yaml/type\":51}],40:[function(require,module,exports){\n\"use strict\";function isNothing(e){return\"undefined\"==typeof e||null===e}function isObject(e){return\"object\"==typeof e&&null!==e}function toArray(e){return Array.isArray(e)?e:isNothing(e)?[]:[e]}function extend(e,t){var r,o,n,i;if(t)for(i=Object.keys(t),r=0,o=i.length;o>r;r+=1)n=i[r],e[n]=t[n];return e}function repeat(e,t){var r,o=\"\";for(r=0;t>r;r+=1)o+=e;return o}function isNegativeZero(e){return 0===e&&Number.NEGATIVE_INFINITY===1/e}module.exports.isNothing=isNothing,module.exports.isObject=isObject,module.exports.toArray=toArray,module.exports.repeat=repeat,module.exports.isNegativeZero=isNegativeZero,module.exports.extend=extend;\n\n},{}],41:[function(require,module,exports){\n\"use strict\";function compileStyleMap(e,t){var n,i,r,E,o,l,a;if(null===t)return{};for(n={},i=Object.keys(t),r=0,E=i.length;E>r;r+=1)o=i[r],l=String(t[o]),\"!!\"===o.slice(0,2)&&(o=\"tag:yaml.org,2002:\"+o.slice(2)),a=e.compiledTypeMap[o],a&&_hasOwnProperty.call(a.styleAliases,l)&&(l=a.styleAliases[l]),n[o]=l;return n}function encodeHex(e){var t,n,i;if(t=e.toString(16).toUpperCase(),255>=e)n=\"x\",i=2;else if(65535>=e)n=\"u\",i=4;else{if(!(4294967295>=e))throw new YAMLException(\"code point within a string may not be greater than 0xFFFFFFFF\");n=\"U\",i=8}return\"\\\\\"+n+common.repeat(\"0\",i-t.length)+t}function State(e){this.schema=e.schema||DEFAULT_FULL_SCHEMA,this.indent=Math.max(1,e.indent||2),this.skipInvalid=e.skipInvalid||!1,this.flowLevel=common.isNothing(e.flowLevel)?-1:e.flowLevel,this.styleMap=compileStyleMap(this.schema,e.styles||null),this.sortKeys=e.sortKeys||!1,this.lineWidth=e.lineWidth||80,this.noRefs=e.noRefs||!1,this.noCompatMode=e.noCompatMode||!1,this.implicitTypes=this.schema.compiledImplicit,this.explicitTypes=this.schema.compiledExplicit,this.tag=null,this.result=\"\",this.duplicates=[],this.usedDuplicates=null}function indentString(e,t){for(var n,i=common.repeat(\" \",t),r=0,E=-1,o=\"\",l=e.length;l>r;)E=e.indexOf(\"\\n\",r),-1===E?(n=e.slice(r),r=l):(n=e.slice(r,E+1),r=E+1),n.length&&\"\\n\"!==n&&(o+=i),o+=n;return o}function generateNextLine(e,t){return\"\\n\"+common.repeat(\" \",e.indent*t)}function testImplicitResolving(e,t){var n,i,r;for(n=0,i=e.implicitTypes.length;i>n;n+=1)if(r=e.implicitTypes[n],r.resolve(t))return!0;return!1}function isWhitespace(e){return e===CHAR_SPACE||e===CHAR_TAB}function isPrintable(e){return e>=32&&126>=e||e>=161&&55295>=e&&8232!==e&&8233!==e||e>=57344&&65533>=e&&65279!==e||e>=65536&&1114111>=e}function isPlainSafe(e){return isPrintable(e)&&65279!==e&&e!==CHAR_COMMA&&e!==CHAR_LEFT_SQUARE_BRACKET&&e!==CHAR_RIGHT_SQUARE_BRACKET&&e!==CHAR_LEFT_CURLY_BRACKET&&e!==CHAR_RIGHT_CURLY_BRACKET&&e!==CHAR_COLON&&e!==CHAR_SHARP}function isPlainSafeFirst(e){return isPrintable(e)&&65279!==e&&!isWhitespace(e)&&e!==CHAR_MINUS&&e!==CHAR_QUESTION&&e!==CHAR_COLON&&e!==CHAR_COMMA&&e!==CHAR_LEFT_SQUARE_BRACKET&&e!==CHAR_RIGHT_SQUARE_BRACKET&&e!==CHAR_LEFT_CURLY_BRACKET&&e!==CHAR_RIGHT_CURLY_BRACKET&&e!==CHAR_SHARP&&e!==CHAR_AMPERSAND&&e!==CHAR_ASTERISK&&e!==CHAR_EXCLAMATION&&e!==CHAR_VERTICAL_LINE&&e!==CHAR_GREATER_THAN&&e!==CHAR_SINGLE_QUOTE&&e!==CHAR_DOUBLE_QUOTE&&e!==CHAR_PERCENT&&e!==CHAR_COMMERCIAL_AT&&e!==CHAR_GRAVE_ACCENT}function chooseScalarStyle(e,t,n,i,r){var E,o,l=!1,a=!1,s=-1!==i,c=-1,A=isPlainSafeFirst(e.charCodeAt(0))&&!isWhitespace(e.charCodeAt(e.length-1));if(t)for(E=0;E<e.length;E++){if(o=e.charCodeAt(E),!isPrintable(o))return STYLE_DOUBLE;A=A&&isPlainSafe(o)}else{for(E=0;E<e.length;E++){if(o=e.charCodeAt(E),o===CHAR_LINE_FEED)l=!0,s&&(a=a||E-c-1>i&&\" \"!==e[c+1],c=E);else if(!isPrintable(o))return STYLE_DOUBLE;A=A&&isPlainSafe(o)}a=a||s&&E-c-1>i&&\" \"!==e[c+1]}return l||a?\" \"===e[0]&&n>9?STYLE_DOUBLE:a?STYLE_FOLDED:STYLE_LITERAL:A&&!r(e)?STYLE_PLAIN:STYLE_SINGLE}function writeScalar(e,t,n,i){e.dump=function(){function r(t){return testImplicitResolving(e,t)}if(0===t.length)return\"''\";if(!e.noCompatMode&&-1!==DEPRECATED_BOOLEANS_SYNTAX.indexOf(t))return\"'\"+t+\"'\";var E=e.indent*Math.max(1,n),o=-1===e.lineWidth?-1:Math.max(Math.min(e.lineWidth,40),e.lineWidth-E),l=i||e.flowLevel>-1&&n>=e.flowLevel;switch(chooseScalarStyle(t,l,e.indent,o,r)){case STYLE_PLAIN:return t;case STYLE_SINGLE:return\"'\"+t.replace(/'/g,\"''\")+\"'\";case STYLE_LITERAL:return\"|\"+blockHeader(t,e.indent)+dropEndingNewline(indentString(t,E));case STYLE_FOLDED:return\">\"+blockHeader(t,e.indent)+dropEndingNewline(indentString(foldString(t,o),E));case STYLE_DOUBLE:return'\"'+escapeString(t,o)+'\"';default:throw new YAMLException(\"impossible error: invalid scalar style\")}}()}function blockHeader(e,t){var n=\" \"===e[0]?String(t):\"\",i=\"\\n\"===e[e.length-1],r=i&&(\"\\n\"===e[e.length-2]||\"\\n\"===e),E=r?\"+\":i?\"\":\"-\";return n+E+\"\\n\"}function dropEndingNewline(e){return\"\\n\"===e[e.length-1]?e.slice(0,-1):e}function foldString(e,t){for(var n,i,r=/(\\n+)([^\\n]*)/g,E=function(){var n=e.indexOf(\"\\n\");return n=-1!==n?n:e.length,r.lastIndex=n,foldLine(e.slice(0,n),t)}(),o=\"\\n\"===e[0]||\" \"===e[0];i=r.exec(e);){var l=i[1],a=i[2];n=\" \"===a[0],E+=l+(o||n||\"\"===a?\"\":\"\\n\")+foldLine(a,t),o=n}return E}function foldLine(e,t){if(\"\"===e||\" \"===e[0])return e;for(var n,i,r=/ [^ ]/g,E=0,o=0,l=0,a=\"\";n=r.exec(e);)l=n.index,l-E>t&&(i=o>E?o:l,a+=\"\\n\"+e.slice(E,i),E=i+1),o=l;return a+=\"\\n\",a+=e.length-E>t&&o>E?e.slice(E,o)+\"\\n\"+e.slice(o+1):e.slice(E),a.slice(1)}function escapeString(e){for(var t,n,i=\"\",r=0;r<e.length;r++)t=e.charCodeAt(r),n=ESCAPE_SEQUENCES[t],i+=!n&&isPrintable(t)?e[r]:n||encodeHex(t);return i}function writeFlowSequence(e,t,n){var i,r,E=\"\",o=e.tag;for(i=0,r=n.length;r>i;i+=1)writeNode(e,t,n[i],!1,!1)&&(0!==i&&(E+=\", \"),E+=e.dump);e.tag=o,e.dump=\"[\"+E+\"]\"}function writeBlockSequence(e,t,n,i){var r,E,o=\"\",l=e.tag;for(r=0,E=n.length;E>r;r+=1)writeNode(e,t+1,n[r],!0,!0)&&(i&&0===r||(o+=generateNextLine(e,t)),o+=\"- \"+e.dump);e.tag=l,e.dump=o||\"[]\"}function writeFlowMapping(e,t,n){var i,r,E,o,l,a=\"\",s=e.tag,c=Object.keys(n);for(i=0,r=c.length;r>i;i+=1)l=\"\",0!==i&&(l+=\", \"),E=c[i],o=n[E],writeNode(e,t,E,!1,!1)&&(e.dump.length>1024&&(l+=\"? \"),l+=e.dump+\": \",writeNode(e,t,o,!1,!1)&&(l+=e.dump,a+=l));e.tag=s,e.dump=\"{\"+a+\"}\"}function writeBlockMapping(e,t,n,i){var r,E,o,l,a,s,c=\"\",A=e.tag,u=Object.keys(n);if(e.sortKeys===!0)u.sort();else if(\"function\"==typeof e.sortKeys)u.sort(e.sortKeys);else if(e.sortKeys)throw new YAMLException(\"sortKeys must be a boolean or a function\");for(r=0,E=u.length;E>r;r+=1)s=\"\",i&&0===r||(s+=generateNextLine(e,t)),o=u[r],l=n[o],writeNode(e,t+1,o,!0,!0,!0)&&(a=null!==e.tag&&\"?\"!==e.tag||e.dump&&e.dump.length>1024,a&&(s+=e.dump&&CHAR_LINE_FEED===e.dump.charCodeAt(0)?\"?\":\"? \"),s+=e.dump,a&&(s+=generateNextLine(e,t)),writeNode(e,t+1,l,!0,a)&&(s+=e.dump&&CHAR_LINE_FEED===e.dump.charCodeAt(0)?\":\":\": \",s+=e.dump,c+=s));e.tag=A,e.dump=c||\"{}\"}function detectType(e,t,n){var i,r,E,o,l,a;for(r=n?e.explicitTypes:e.implicitTypes,E=0,o=r.length;o>E;E+=1)if(l=r[E],(l.instanceOf||l.predicate)&&(!l.instanceOf||\"object\"==typeof t&&t instanceof l.instanceOf)&&(!l.predicate||l.predicate(t))){if(e.tag=n?l.tag:\"?\",l.represent){if(a=e.styleMap[l.tag]||l.defaultStyle,\"[object Function]\"===_toString.call(l.represent))i=l.represent(t,a);else{if(!_hasOwnProperty.call(l.represent,a))throw new YAMLException(\"!<\"+l.tag+'> tag resolver accepts not \"'+a+'\" style');i=l.represent[a](t,a)}e.dump=i}return!0}return!1}function writeNode(e,t,n,i,r,E){e.tag=null,e.dump=n,detectType(e,n,!1)||detectType(e,n,!0);var o=_toString.call(e.dump);i&&(i=e.flowLevel<0||e.flowLevel>t);var l,a,s=\"[object Object]\"===o||\"[object Array]\"===o;if(s&&(l=e.duplicates.indexOf(n),a=-1!==l),(null!==e.tag&&\"?\"!==e.tag||a||2!==e.indent&&t>0)&&(r=!1),a&&e.usedDuplicates[l])e.dump=\"*ref_\"+l;else{if(s&&a&&!e.usedDuplicates[l]&&(e.usedDuplicates[l]=!0),\"[object Object]\"===o)i&&0!==Object.keys(e.dump).length?(writeBlockMapping(e,t,e.dump,r),a&&(e.dump=\"&ref_\"+l+e.dump)):(writeFlowMapping(e,t,e.dump),a&&(e.dump=\"&ref_\"+l+\" \"+e.dump));else if(\"[object Array]\"===o)i&&0!==e.dump.length?(writeBlockSequence(e,t,e.dump,r),a&&(e.dump=\"&ref_\"+l+e.dump)):(writeFlowSequence(e,t,e.dump),a&&(e.dump=\"&ref_\"+l+\" \"+e.dump));else{if(\"[object String]\"!==o){if(e.skipInvalid)return!1;throw new YAMLException(\"unacceptable kind of an object to dump \"+o)}\"?\"!==e.tag&&writeScalar(e,e.dump,t,E)}null!==e.tag&&\"?\"!==e.tag&&(e.dump=\"!<\"+e.tag+\"> \"+e.dump)}return!0}function getDuplicateReferences(e,t){var n,i,r=[],E=[];for(inspectNode(e,r,E),n=0,i=E.length;i>n;n+=1)t.duplicates.push(r[E[n]]);t.usedDuplicates=new Array(i)}function inspectNode(e,t,n){var i,r,E;if(null!==e&&\"object\"==typeof e)if(r=t.indexOf(e),-1!==r)-1===n.indexOf(r)&&n.push(r);else if(t.push(e),Array.isArray(e))for(r=0,E=e.length;E>r;r+=1)inspectNode(e[r],t,n);else for(i=Object.keys(e),r=0,E=i.length;E>r;r+=1)inspectNode(e[i[r]],t,n)}function dump(e,t){t=t||{};var n=new State(t);return n.noRefs||getDuplicateReferences(e,n),writeNode(n,0,e,!0,!0)?n.dump+\"\\n\":\"\"}function safeDump(e,t){return dump(e,common.extend({schema:DEFAULT_SAFE_SCHEMA},t))}var common=require(\"./common\"),YAMLException=require(\"./exception\"),DEFAULT_FULL_SCHEMA=require(\"./schema/default_full\"),DEFAULT_SAFE_SCHEMA=require(\"./schema/default_safe\"),_toString=Object.prototype.toString,_hasOwnProperty=Object.prototype.hasOwnProperty,CHAR_TAB=9,CHAR_LINE_FEED=10,CHAR_SPACE=32,CHAR_EXCLAMATION=33,CHAR_DOUBLE_QUOTE=34,CHAR_SHARP=35,CHAR_PERCENT=37,CHAR_AMPERSAND=38,CHAR_SINGLE_QUOTE=39,CHAR_ASTERISK=42,CHAR_COMMA=44,CHAR_MINUS=45,CHAR_COLON=58,CHAR_GREATER_THAN=62,CHAR_QUESTION=63,CHAR_COMMERCIAL_AT=64,CHAR_LEFT_SQUARE_BRACKET=91,CHAR_RIGHT_SQUARE_BRACKET=93,CHAR_GRAVE_ACCENT=96,CHAR_LEFT_CURLY_BRACKET=123,CHAR_VERTICAL_LINE=124,CHAR_RIGHT_CURLY_BRACKET=125,ESCAPE_SEQUENCES={};ESCAPE_SEQUENCES[0]=\"\\\\0\",ESCAPE_SEQUENCES[7]=\"\\\\a\",ESCAPE_SEQUENCES[8]=\"\\\\b\",ESCAPE_SEQUENCES[9]=\"\\\\t\",ESCAPE_SEQUENCES[10]=\"\\\\n\",ESCAPE_SEQUENCES[11]=\"\\\\v\",ESCAPE_SEQUENCES[12]=\"\\\\f\",ESCAPE_SEQUENCES[13]=\"\\\\r\",ESCAPE_SEQUENCES[27]=\"\\\\e\",ESCAPE_SEQUENCES[34]='\\\\\"',ESCAPE_SEQUENCES[92]=\"\\\\\\\\\",ESCAPE_SEQUENCES[133]=\"\\\\N\",ESCAPE_SEQUENCES[160]=\"\\\\_\",ESCAPE_SEQUENCES[8232]=\"\\\\L\",ESCAPE_SEQUENCES[8233]=\"\\\\P\";var DEPRECATED_BOOLEANS_SYNTAX=[\"y\",\"Y\",\"yes\",\"Yes\",\"YES\",\"on\",\"On\",\"ON\",\"n\",\"N\",\"no\",\"No\",\"NO\",\"off\",\"Off\",\"OFF\"],STYLE_PLAIN=1,STYLE_SINGLE=2,STYLE_LITERAL=3,STYLE_FOLDED=4,STYLE_DOUBLE=5;module.exports.dump=dump,module.exports.safeDump=safeDump;\n\n},{\"./common\":40,\"./exception\":42,\"./schema/default_full\":47,\"./schema/default_safe\":48}],42:[function(require,module,exports){\n\"use strict\";function YAMLException(t,r){Error.call(this),Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=(new Error).stack||\"\",this.name=\"YAMLException\",this.reason=t,this.mark=r,this.message=(this.reason||\"(unknown reason)\")+(this.mark?\" \"+this.mark.toString():\"\")}YAMLException.prototype=Object.create(Error.prototype),YAMLException.prototype.constructor=YAMLException,YAMLException.prototype.toString=function(t){var r=this.name+\": \";return r+=this.reason||\"(unknown reason)\",!t&&this.mark&&(r+=\" \"+this.mark.toString()),r},module.exports=YAMLException;\n\n},{}],43:[function(require,module,exports){\n\"use strict\";function is_EOL(e){return 10===e||13===e}function is_WHITE_SPACE(e){return 9===e||32===e}function is_WS_OR_EOL(e){return 9===e||32===e||10===e||13===e}function is_FLOW_INDICATOR(e){return 44===e||91===e||93===e||123===e||125===e}function fromHexCode(e){var t;return e>=48&&57>=e?e-48:(t=32|e,t>=97&&102>=t?t-97+10:-1)}function escapedHexLen(e){return 120===e?2:117===e?4:85===e?8:0}function fromDecimalCode(e){return e>=48&&57>=e?e-48:-1}function simpleEscapeSequence(e){return 48===e?\"\\x00\":97===e?\"\u0007\":98===e?\"\\b\":116===e?\"   \":9===e?\"   \":110===e?\"\\n\":118===e?\"\\x0B\":102===e?\"\\f\":114===e?\"\\r\":101===e?\"\u001b\":32===e?\" \":34===e?'\"':47===e?\"/\":92===e?\"\\\\\":78===e?\"\":95===e?\" \":76===e?\"\\u2028\":80===e?\"\\u2029\":\"\"}function charFromCodepoint(e){return 65535>=e?String.fromCharCode(e):String.fromCharCode((e-65536>>10)+55296,(e-65536&1023)+56320)}function State(e,t){this.input=e,this.filename=t.filename||null,this.schema=t.schema||DEFAULT_FULL_SCHEMA,this.onWarning=t.onWarning||null,this.legacy=t.legacy||!1,this.json=t.json||!1,this.listener=t.listener||null,this.implicitTypes=this.schema.compiledImplicit,this.typeMap=this.schema.compiledTypeMap,this.length=e.length,this.position=0,this.line=0,this.lineStart=0,this.lineIndent=0,this.documents=[]}function generateError(e,t){return new YAMLException(t,new Mark(e.filename,e.input,e.position,e.line,e.position-e.lineStart))}function throwError(e,t){throw generateError(e,t)}function throwWarning(e,t){e.onWarning&&e.onWarning.call(null,generateError(e,t))}function captureSegment(e,t,n,i){var o,r,a,s;if(n>t){if(s=e.input.slice(t,n),i)for(o=0,r=s.length;r>o;o+=1)a=s.charCodeAt(o),9===a||a>=32&&1114111>=a||throwError(e,\"expected valid JSON character\");else PATTERN_NON_PRINTABLE.test(s)&&throwError(e,\"the stream contains non-printable characters\");e.result+=s}}function mergeMappings(e,t,n,i){var o,r,a,s;for(common.isObject(n)||throwError(e,\"cannot merge mappings; the provided source object is unacceptable\"),o=Object.keys(n),a=0,s=o.length;s>a;a+=1)r=o[a],_hasOwnProperty.call(t,r)||(t[r]=n[r],i[r]=!0)}function storeMappingPair(e,t,n,i,o,r){var a,s;if(o=String(o),null===t&&(t={}),\"tag:yaml.org,2002:merge\"===i)if(Array.isArray(r))for(a=0,s=r.length;s>a;a+=1)mergeMappings(e,t,r[a],n);else mergeMappings(e,t,r,n);else e.json||_hasOwnProperty.call(n,o)||!_hasOwnProperty.call(t,o)||throwError(e,\"duplicated mapping key\"),t[o]=r,delete n[o];return t}function readLineBreak(e){var t;t=e.input.charCodeAt(e.position),10===t?e.position++:13===t?(e.position++,10===e.input.charCodeAt(e.position)&&e.position++):throwError(e,\"a line break is expected\"),e.line+=1,e.lineStart=e.position}function skipSeparationSpace(e,t,n){for(var i=0,o=e.input.charCodeAt(e.position);0!==o;){for(;is_WHITE_SPACE(o);)o=e.input.charCodeAt(++e.position);if(t&&35===o)do o=e.input.charCodeAt(++e.position);while(10!==o&&13!==o&&0!==o);if(!is_EOL(o))break;for(readLineBreak(e),o=e.input.charCodeAt(e.position),i++,e.lineIndent=0;32===o;)e.lineIndent++,o=e.input.charCodeAt(++e.position)}return-1!==n&&0!==i&&e.lineIndent<n&&throwWarning(e,\"deficient indentation\"),i}function testDocumentSeparator(e){var t,n=e.position;return t=e.input.charCodeAt(n),(45===t||46===t)&&t===e.input.charCodeAt(n+1)&&t===e.input.charCodeAt(n+2)&&(n+=3,t=e.input.charCodeAt(n),0===t||is_WS_OR_EOL(t))}function writeFoldedLines(e,t){1===t?e.result+=\" \":t>1&&(e.result+=common.repeat(\"\\n\",t-1))}function readPlainScalar(e,t,n){var i,o,r,a,s,p,c,l,u,d=e.kind,h=e.result;if(u=e.input.charCodeAt(e.position),is_WS_OR_EOL(u)||is_FLOW_INDICATOR(u)||35===u||38===u||42===u||33===u||124===u||62===u||39===u||34===u||37===u||64===u||96===u)return!1;if((63===u||45===u)&&(o=e.input.charCodeAt(e.position+1),is_WS_OR_EOL(o)||n&&is_FLOW_INDICATOR(o)))return!1;for(e.kind=\"scalar\",e.result=\"\",r=a=e.position,s=!1;0!==u;){if(58===u){if(o=e.input.charCodeAt(e.position+1),is_WS_OR_EOL(o)||n&&is_FLOW_INDICATOR(o))break}else if(35===u){if(i=e.input.charCodeAt(e.position-1),is_WS_OR_EOL(i))break}else{if(e.position===e.lineStart&&testDocumentSeparator(e)||n&&is_FLOW_INDICATOR(u))break;if(is_EOL(u)){if(p=e.line,c=e.lineStart,l=e.lineIndent,skipSeparationSpace(e,!1,-1),e.lineIndent>=t){s=!0,u=e.input.charCodeAt(e.position);continue}e.position=a,e.line=p,e.lineStart=c,e.lineIndent=l;break}}s&&(captureSegment(e,r,a,!1),writeFoldedLines(e,e.line-p),r=a=e.position,s=!1),is_WHITE_SPACE(u)||(a=e.position+1),u=e.input.charCodeAt(++e.position)}return captureSegment(e,r,a,!1),e.result?!0:(e.kind=d,e.result=h,!1)}function readSingleQuotedScalar(e,t){var n,i,o;if(n=e.input.charCodeAt(e.position),39!==n)return!1;for(e.kind=\"scalar\",e.result=\"\",e.position++,i=o=e.position;0!==(n=e.input.charCodeAt(e.position));)if(39===n){if(captureSegment(e,i,e.position,!0),n=e.input.charCodeAt(++e.position),39!==n)return!0;i=o=e.position,e.position++}else is_EOL(n)?(captureSegment(e,i,o,!0),writeFoldedLines(e,skipSeparationSpace(e,!1,t)),i=o=e.position):e.position===e.lineStart&&testDocumentSeparator(e)?throwError(e,\"unexpected end of the document within a single quoted scalar\"):(e.position++,o=e.position);throwError(e,\"unexpected end of the stream within a single quoted scalar\")}function readDoubleQuotedScalar(e,t){var n,i,o,r,a,s;if(s=e.input.charCodeAt(e.position),34!==s)return!1;for(e.kind=\"scalar\",e.result=\"\",e.position++,n=i=e.position;0!==(s=e.input.charCodeAt(e.position));){if(34===s)return captureSegment(e,n,e.position,!0),e.position++,!0;if(92===s){if(captureSegment(e,n,e.position,!0),s=e.input.charCodeAt(++e.position),is_EOL(s))skipSeparationSpace(e,!1,t);else if(256>s&&simpleEscapeCheck[s])e.result+=simpleEscapeMap[s],e.position++;else if((a=escapedHexLen(s))>0){for(o=a,r=0;o>0;o--)s=e.input.charCodeAt(++e.position),(a=fromHexCode(s))>=0?r=(r<<4)+a:throwError(e,\"expected hexadecimal character\");e.result+=charFromCodepoint(r),e.position++}else throwError(e,\"unknown escape sequence\");n=i=e.position}else is_EOL(s)?(captureSegment(e,n,i,!0),writeFoldedLines(e,skipSeparationSpace(e,!1,t)),n=i=e.position):e.position===e.lineStart&&testDocumentSeparator(e)?throwError(e,\"unexpected end of the document within a double quoted scalar\"):(e.position++,i=e.position)}throwError(e,\"unexpected end of the stream within a double quoted scalar\")}function readFlowCollection(e,t){var n,i,o,r,a,s,p,c,l,u,d,h=!0,f=e.tag,_=e.anchor,A={};if(d=e.input.charCodeAt(e.position),91===d)r=93,p=!1,i=[];else{if(123!==d)return!1;r=125,p=!0,i={}}for(null!==e.anchor&&(e.anchorMap[e.anchor]=i),d=e.input.charCodeAt(++e.position);0!==d;){if(skipSeparationSpace(e,!0,t),d=e.input.charCodeAt(e.position),d===r)return e.position++,e.tag=f,e.anchor=_,e.kind=p?\"mapping\":\"sequence\",e.result=i,!0;h||throwError(e,\"missed comma between flow collection entries\"),l=c=u=null,a=s=!1,63===d&&(o=e.input.charCodeAt(e.position+1),is_WS_OR_EOL(o)&&(a=s=!0,e.position++,skipSeparationSpace(e,!0,t))),n=e.line,composeNode(e,t,CONTEXT_FLOW_IN,!1,!0),l=e.tag,c=e.result,skipSeparationSpace(e,!0,t),d=e.input.charCodeAt(e.position),!s&&e.line!==n||58!==d||(a=!0,d=e.input.charCodeAt(++e.position),skipSeparationSpace(e,!0,t),composeNode(e,t,CONTEXT_FLOW_IN,!1,!0),u=e.result),p?storeMappingPair(e,i,A,l,c,u):a?i.push(storeMappingPair(e,null,A,l,c,u)):i.push(c),skipSeparationSpace(e,!0,t),d=e.input.charCodeAt(e.position),44===d?(h=!0,d=e.input.charCodeAt(++e.position)):h=!1}throwError(e,\"unexpected end of the stream within a flow collection\")}function readBlockScalar(e,t){var n,i,o,r,a=CHOMPING_CLIP,s=!1,p=!1,c=t,l=0,u=!1;if(r=e.input.charCodeAt(e.position),124===r)i=!1;else{if(62!==r)return!1;i=!0}for(e.kind=\"scalar\",e.result=\"\";0!==r;)if(r=e.input.charCodeAt(++e.position),43===r||45===r)CHOMPING_CLIP===a?a=43===r?CHOMPING_KEEP:CHOMPING_STRIP:throwError(e,\"repeat of a chomping mode identifier\");else{if(!((o=fromDecimalCode(r))>=0))break;0===o?throwError(e,\"bad explicit indentation width of a block scalar; it cannot be less than one\"):p?throwError(e,\"repeat of an indentation width identifier\"):(c=t+o-1,p=!0)}if(is_WHITE_SPACE(r)){do r=e.input.charCodeAt(++e.position);while(is_WHITE_SPACE(r));if(35===r)do r=e.input.charCodeAt(++e.position);while(!is_EOL(r)&&0!==r)}for(;0!==r;){for(readLineBreak(e),e.lineIndent=0,r=e.input.charCodeAt(e.position);(!p||e.lineIndent<c)&&32===r;)e.lineIndent++,r=e.input.charCodeAt(++e.position);if(!p&&e.lineIndent>c&&(c=e.lineIndent),is_EOL(r))l++;else{if(e.lineIndent<c){a===CHOMPING_KEEP?e.result+=common.repeat(\"\\n\",s?1+l:l):a===CHOMPING_CLIP&&s&&(e.result+=\"\\n\");break}for(i?is_WHITE_SPACE(r)?(u=!0,e.result+=common.repeat(\"\\n\",s?1+l:l)):u?(u=!1,e.result+=common.repeat(\"\\n\",l+1)):0===l?s&&(e.result+=\" \"):e.result+=common.repeat(\"\\n\",l):e.result+=common.repeat(\"\\n\",s?1+l:l),s=!0,p=!0,l=0,n=e.position;!is_EOL(r)&&0!==r;)r=e.input.charCodeAt(++e.position);captureSegment(e,n,e.position,!1)}}return!0}function readBlockSequence(e,t){var n,i,o,r=e.tag,a=e.anchor,s=[],p=!1;for(null!==e.anchor&&(e.anchorMap[e.anchor]=s),o=e.input.charCodeAt(e.position);0!==o&&45===o&&(i=e.input.charCodeAt(e.position+1),is_WS_OR_EOL(i));)if(p=!0,e.position++,skipSeparationSpace(e,!0,-1)&&e.lineIndent<=t)s.push(null),o=e.input.charCodeAt(e.position);else if(n=e.line,composeNode(e,t,CONTEXT_BLOCK_IN,!1,!0),s.push(e.result),skipSeparationSpace(e,!0,-1),o=e.input.charCodeAt(e.position),(e.line===n||e.lineIndent>t)&&0!==o)throwError(e,\"bad indentation of a sequence entry\");else if(e.lineIndent<t)break;return p?(e.tag=r,e.anchor=a,e.kind=\"sequence\",e.result=s,!0):!1}function readBlockMapping(e,t,n){var i,o,r,a,s=e.tag,p=e.anchor,c={},l={},u=null,d=null,h=null,f=!1,_=!1;for(null!==e.anchor&&(e.anchorMap[e.anchor]=c),a=e.input.charCodeAt(e.position);0!==a;){if(i=e.input.charCodeAt(e.position+1),r=e.line,63!==a&&58!==a||!is_WS_OR_EOL(i)){if(!composeNode(e,n,CONTEXT_FLOW_OUT,!1,!0))break;if(e.line===r){for(a=e.input.charCodeAt(e.position);is_WHITE_SPACE(a);)a=e.input.charCodeAt(++e.position);if(58===a)a=e.input.charCodeAt(++e.position),is_WS_OR_EOL(a)||throwError(e,\"a whitespace character is expected after the key-value separator within a block mapping\"),f&&(storeMappingPair(e,c,l,u,d,null),u=d=h=null),_=!0,f=!1,o=!1,u=e.tag,d=e.result;else{if(!_)return e.tag=s,e.anchor=p,!0;throwError(e,\"can not read an implicit mapping pair; a colon is missed\")}}else{if(!_)return e.tag=s,e.anchor=p,!0;throwError(e,\"can not read a block mapping entry; a multiline key may not be an implicit key\")}}else 63===a?(f&&(storeMappingPair(e,c,l,u,d,null),u=d=h=null),_=!0,f=!0,o=!0):f?(f=!1,o=!0):throwError(e,\"incomplete explicit mapping pair; a key node is missed\"),e.position+=1,a=i;if((e.line===r||e.lineIndent>t)&&(composeNode(e,t,CONTEXT_BLOCK_OUT,!0,o)&&(f?d=e.result:h=e.result),f||(storeMappingPair(e,c,l,u,d,h),u=d=h=null),skipSeparationSpace(e,!0,-1),a=e.input.charCodeAt(e.position)),e.lineIndent>t&&0!==a)throwError(e,\"bad indentation of a mapping entry\");else if(e.lineIndent<t)break}return f&&storeMappingPair(e,c,l,u,d,null),_&&(e.tag=s,e.anchor=p,e.kind=\"mapping\",e.result=c),_}function readTagProperty(e){var t,n,i,o,r=!1,a=!1;if(o=e.input.charCodeAt(e.position),33!==o)return!1;if(null!==e.tag&&throwError(e,\"duplication of a tag property\"),o=e.input.charCodeAt(++e.position),60===o?(r=!0,o=e.input.charCodeAt(++e.position)):33===o?(a=!0,n=\"!!\",o=e.input.charCodeAt(++e.position)):n=\"!\",t=e.position,r){do o=e.input.charCodeAt(++e.position);while(0!==o&&62!==o);e.position<e.length?(i=e.input.slice(t,e.position),o=e.input.charCodeAt(++e.position)):throwError(e,\"unexpected end of the stream within a verbatim tag\")}else{for(;0!==o&&!is_WS_OR_EOL(o);)33===o&&(a?throwError(e,\"tag suffix cannot contain exclamation marks\"):(n=e.input.slice(t-1,e.position+1),PATTERN_TAG_HANDLE.test(n)||throwError(e,\"named tag handle cannot contain such characters\"),a=!0,t=e.position+1)),o=e.input.charCodeAt(++e.position);i=e.input.slice(t,e.position),PATTERN_FLOW_INDICATORS.test(i)&&throwError(e,\"tag suffix cannot contain flow indicator characters\")}return i&&!PATTERN_TAG_URI.test(i)&&throwError(e,\"tag name cannot contain such characters: \"+i),r?e.tag=i:_hasOwnProperty.call(e.tagMap,n)?e.tag=e.tagMap[n]+i:\"!\"===n?e.tag=\"!\"+i:\"!!\"===n?e.tag=\"tag:yaml.org,2002:\"+i:throwError(e,'undeclared tag handle \"'+n+'\"'),!0}function readAnchorProperty(e){var t,n;if(n=e.input.charCodeAt(e.position),38!==n)return!1;for(null!==e.anchor&&throwError(e,\"duplication of an anchor property\"),n=e.input.charCodeAt(++e.position),t=e.position;0!==n&&!is_WS_OR_EOL(n)&&!is_FLOW_INDICATOR(n);)n=e.input.charCodeAt(++e.position);return e.position===t&&throwError(e,\"name of an anchor node must contain at least one character\"),e.anchor=e.input.slice(t,e.position),!0}function readAlias(e){var t,n,i;if(i=e.input.charCodeAt(e.position),42!==i)return!1;for(i=e.input.charCodeAt(++e.position),t=e.position;0!==i&&!is_WS_OR_EOL(i)&&!is_FLOW_INDICATOR(i);)i=e.input.charCodeAt(++e.position);return e.position===t&&throwError(e,\"name of an alias node must contain at least one character\"),n=e.input.slice(t,e.position),e.anchorMap.hasOwnProperty(n)||throwError(e,'unidentified alias \"'+n+'\"'),e.result=e.anchorMap[n],skipSeparationSpace(e,!0,-1),!0}function composeNode(e,t,n,i,o){var r,a,s,p,c,l,u,d,h=1,f=!1,_=!1;if(null!==e.listener&&e.listener(\"open\",e),e.tag=null,e.anchor=null,e.kind=null,e.result=null,r=a=s=CONTEXT_BLOCK_OUT===n||CONTEXT_BLOCK_IN===n,i&&skipSeparationSpace(e,!0,-1)&&(f=!0,e.lineIndent>t?h=1:e.lineIndent===t?h=0:e.lineIndent<t&&(h=-1)),1===h)for(;readTagProperty(e)||readAnchorProperty(e);)skipSeparationSpace(e,!0,-1)?(f=!0,s=r,e.lineIndent>t?h=1:e.lineIndent===t?h=0:e.lineIndent<t&&(h=-1)):s=!1;if(s&&(s=f||o),1!==h&&CONTEXT_BLOCK_OUT!==n||(u=CONTEXT_FLOW_IN===n||CONTEXT_FLOW_OUT===n?t:t+1,d=e.position-e.lineStart,1===h?s&&(readBlockSequence(e,d)||readBlockMapping(e,d,u))||readFlowCollection(e,u)?_=!0:(a&&readBlockScalar(e,u)||readSingleQuotedScalar(e,u)||readDoubleQuotedScalar(e,u)?_=!0:readAlias(e)?(_=!0,null===e.tag&&null===e.anchor||throwError(e,\"alias node should not have any properties\")):readPlainScalar(e,u,CONTEXT_FLOW_IN===n)&&(_=!0,null===e.tag&&(e.tag=\"?\")),null!==e.anchor&&(e.anchorMap[e.anchor]=e.result)):0===h&&(_=s&&readBlockSequence(e,d))),null!==e.tag&&\"!\"!==e.tag)if(\"?\"===e.tag){for(p=0,c=e.implicitTypes.length;c>p;p+=1)if(l=e.implicitTypes[p],l.resolve(e.result)){e.result=l.construct(e.result),e.tag=l.tag,null!==e.anchor&&(e.anchorMap[e.anchor]=e.result);break}}else _hasOwnProperty.call(e.typeMap,e.tag)?(l=e.typeMap[e.tag],null!==e.result&&l.kind!==e.kind&&throwError(e,\"unacceptable node kind for !<\"+e.tag+'> tag; it should be \"'+l.kind+'\", not \"'+e.kind+'\"'),l.resolve(e.result)?(e.result=l.construct(e.result),null!==e.anchor&&(e.anchorMap[e.anchor]=e.result)):throwError(e,\"cannot resolve a node with !<\"+e.tag+\"> explicit tag\")):throwError(e,\"unknown tag !<\"+e.tag+\">\");return null!==e.listener&&e.listener(\"close\",e),null!==e.tag||null!==e.anchor||_}function readDocument(e){var t,n,i,o,r=e.position,a=!1;for(e.version=null,e.checkLineBreaks=e.legacy,e.tagMap={},e.anchorMap={};0!==(o=e.input.charCodeAt(e.position))&&(skipSeparationSpace(e,!0,-1),o=e.input.charCodeAt(e.position),!(e.lineIndent>0||37!==o));){for(a=!0,o=e.input.charCodeAt(++e.position),t=e.position;0!==o&&!is_WS_OR_EOL(o);)o=e.input.charCodeAt(++e.position);for(n=e.input.slice(t,e.position),i=[],n.length<1&&throwError(e,\"directive name must not be less than one character in length\");0!==o;){for(;is_WHITE_SPACE(o);)o=e.input.charCodeAt(++e.position);if(35===o){do o=e.input.charCodeAt(++e.position);while(0!==o&&!is_EOL(o));break}if(is_EOL(o))break;for(t=e.position;0!==o&&!is_WS_OR_EOL(o);)o=e.input.charCodeAt(++e.position);i.push(e.input.slice(t,e.position))}0!==o&&readLineBreak(e),_hasOwnProperty.call(directiveHandlers,n)?directiveHandlers[n](e,n,i):throwWarning(e,'unknown document directive \"'+n+'\"')}return skipSeparationSpace(e,!0,-1),0===e.lineIndent&&45===e.input.charCodeAt(e.position)&&45===e.input.charCodeAt(e.position+1)&&45===e.input.charCodeAt(e.position+2)?(e.position+=3,skipSeparationSpace(e,!0,-1)):a&&throwError(e,\"directives end mark is expected\"),composeNode(e,e.lineIndent-1,CONTEXT_BLOCK_OUT,!1,!0),skipSeparationSpace(e,!0,-1),e.checkLineBreaks&&PATTERN_NON_ASCII_LINE_BREAKS.test(e.input.slice(r,e.position))&&throwWarning(e,\"non-ASCII line breaks are interpreted as content\"),e.documents.push(e.result),e.position===e.lineStart&&testDocumentSeparator(e)?void(46===e.input.charCodeAt(e.position)&&(e.position+=3,skipSeparationSpace(e,!0,-1))):void(e.position<e.length-1&&throwError(e,\"end of the stream or a document separator is expected\"))}function loadDocuments(e,t){e=String(e),t=t||{},0!==e.length&&(10!==e.charCodeAt(e.length-1)&&13!==e.charCodeAt(e.length-1)&&(e+=\"\\n\"),65279===e.charCodeAt(0)&&(e=e.slice(1)));var n=new State(e,t);for(n.input+=\"\\x00\";32===n.input.charCodeAt(n.position);)n.lineIndent+=1,n.position+=1;for(;n.position<n.length-1;)readDocument(n);return n.documents}function loadAll(e,t,n){var i,o,r=loadDocuments(e,n);for(i=0,o=r.length;o>i;i+=1)t(r[i])}function load(e,t){var n=loadDocuments(e,t);if(0!==n.length){if(1===n.length)return n[0];throw new YAMLException(\"expected a single document in the stream, but found more\")}}function safeLoadAll(e,t,n){loadAll(e,t,common.extend({schema:DEFAULT_SAFE_SCHEMA},n))}function safeLoad(e,t){return load(e,common.extend({schema:DEFAULT_SAFE_SCHEMA},t))}for(var common=require(\"./common\"),YAMLException=require(\"./exception\"),Mark=require(\"./mark\"),DEFAULT_SAFE_SCHEMA=require(\"./schema/default_safe\"),DEFAULT_FULL_SCHEMA=require(\"./schema/default_full\"),_hasOwnProperty=Object.prototype.hasOwnProperty,CONTEXT_FLOW_IN=1,CONTEXT_FLOW_OUT=2,CONTEXT_BLOCK_IN=3,CONTEXT_BLOCK_OUT=4,CHOMPING_CLIP=1,CHOMPING_STRIP=2,CHOMPING_KEEP=3,PATTERN_NON_PRINTABLE=/[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F\\x7F-\\x84\\x86-\\x9F\\uFFFE\\uFFFF]|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?:[^\\uD800-\\uDBFF]|^)[\\uDC00-\\uDFFF]/,PATTERN_NON_ASCII_LINE_BREAKS=/[\\x85\\u2028\\u2029]/,PATTERN_FLOW_INDICATORS=/[,\\[\\]\\{\\}]/,PATTERN_TAG_HANDLE=/^(?:!|!!|![a-z\\-]+!)$/i,PATTERN_TAG_URI=/^(?:!|[^,\\[\\]\\{\\}])(?:%[0-9a-f]{2}|[0-9a-z\\-#;\\/\\?:@&=\\+\\$,_\\.!~\\*'\\(\\)\\[\\]])*$/i,simpleEscapeCheck=new Array(256),simpleEscapeMap=new Array(256),i=0;256>i;i++)simpleEscapeCheck[i]=simpleEscapeSequence(i)?1:0,simpleEscapeMap[i]=simpleEscapeSequence(i);var directiveHandlers={YAML:function(e,t,n){var i,o,r;null!==e.version&&throwError(e,\"duplication of %YAML directive\"),1!==n.length&&throwError(e,\"YAML directive accepts exactly one argument\"),i=/^([0-9]+)\\.([0-9]+)$/.exec(n[0]),null===i&&throwError(e,\"ill-formed argument of the YAML directive\"),o=parseInt(i[1],10),r=parseInt(i[2],10),1!==o&&throwError(e,\"unacceptable YAML version of the document\"),e.version=n[0],e.checkLineBreaks=2>r,1!==r&&2!==r&&throwWarning(e,\"unsupported YAML version of the document\")},TAG:function(e,t,n){var i,o;2!==n.length&&throwError(e,\"TAG directive accepts exactly two arguments\"),i=n[0],o=n[1],PATTERN_TAG_HANDLE.test(i)||throwError(e,\"ill-formed tag handle (first argument) of the TAG directive\"),_hasOwnProperty.call(e.tagMap,i)&&throwError(e,'there is a previously declared suffix for \"'+i+'\" tag handle'),PATTERN_TAG_URI.test(o)||throwError(e,\"ill-formed tag prefix (second argument) of the TAG directive\"),e.tagMap[i]=o}};module.exports.loadAll=loadAll,module.exports.load=load,module.exports.safeLoadAll=safeLoadAll,module.exports.safeLoad=safeLoad;\n\n},{\"./common\":40,\"./exception\":42,\"./mark\":44,\"./schema/default_full\":47,\"./schema/default_safe\":48}],44:[function(require,module,exports){\n\"use strict\";function Mark(t,i,n,e,r){this.name=t,this.buffer=i,this.position=n,this.line=e,this.column=r}var common=require(\"./common\");Mark.prototype.getSnippet=function(t,i){var n,e,r,o,s;if(!this.buffer)return null;for(t=t||4,i=i||75,n=\"\",e=this.position;e>0&&-1===\"\\x00\\r\\n\\u2028\\u2029\".indexOf(this.buffer.charAt(e-1));)if(e-=1,this.position-e>i/2-1){n=\" ... \",e+=5;break}for(r=\"\",o=this.position;o<this.buffer.length&&-1===\"\\x00\\r\\n\\u2028\\u2029\".indexOf(this.buffer.charAt(o));)if(o+=1,o-this.position>i/2-1){r=\" ... \",o-=5;break}return s=this.buffer.slice(e,o),common.repeat(\" \",t)+n+s+r+\"\\n\"+common.repeat(\" \",t+this.position-e+n.length)+\"^\"},Mark.prototype.toString=function(t){var i,n=\"\";return this.name&&(n+='in \"'+this.name+'\" '),n+=\"at line \"+(this.line+1)+\", column \"+(this.column+1),t||(i=this.getSnippet(),i&&(n+=\":\\n\"+i)),n},module.exports=Mark;\n\n},{\"./common\":40}],45:[function(require,module,exports){\n\"use strict\";function compileList(i,e,t){var c=[];return i.include.forEach(function(i){t=compileList(i,e,t)}),i[e].forEach(function(i){t.forEach(function(e,t){e.tag===i.tag&&c.push(t)}),t.push(i)}),t.filter(function(i,e){return-1===c.indexOf(e)})}function compileMap(){function i(i){c[i.tag]=i}var e,t,c={};for(e=0,t=arguments.length;t>e;e+=1)arguments[e].forEach(i);return c}function Schema(i){this.include=i.include||[],this.implicit=i.implicit||[],this.explicit=i.explicit||[],this.implicit.forEach(function(i){if(i.loadKind&&\"scalar\"!==i.loadKind)throw new YAMLException(\"There is a non-scalar type in the implicit list of a schema. Implicit resolving of such types is not supported.\")}),this.compiledImplicit=compileList(this,\"implicit\",[]),this.compiledExplicit=compileList(this,\"explicit\",[]),this.compiledTypeMap=compileMap(this.compiledImplicit,this.compiledExplicit)}var common=require(\"./common\"),YAMLException=require(\"./exception\"),Type=require(\"./type\");Schema.DEFAULT=null,Schema.create=function(){var i,e;switch(arguments.length){case 1:i=Schema.DEFAULT,e=arguments[0];break;case 2:i=arguments[0],e=arguments[1];break;default:throw new YAMLException(\"Wrong number of arguments for Schema.create function\")}if(i=common.toArray(i),e=common.toArray(e),!i.every(function(i){return i instanceof Schema}))throw new YAMLException(\"Specified list of super schemas (or a single Schema object) contains a non-Schema object.\");if(!e.every(function(i){return i instanceof Type}))throw new YAMLException(\"Specified list of YAML types (or a single Type object) contains a non-Type object.\");return new Schema({include:i,explicit:e})},module.exports=Schema;\n\n},{\"./common\":40,\"./exception\":42,\"./type\":51}],46:[function(require,module,exports){\n\"use strict\";var Schema=require(\"../schema\");module.exports=new Schema({include:[require(\"./json\")]});\n\n},{\"../schema\":45,\"./json\":50}],47:[function(require,module,exports){\n\"use strict\";var Schema=require(\"../schema\");module.exports=Schema.DEFAULT=new Schema({include:[require(\"./default_safe\")],explicit:[require(\"../type/js/undefined\"),require(\"../type/js/regexp\"),require(\"../type/js/function\")]});\n\n},{\"../schema\":45,\"../type/js/function\":56,\"../type/js/regexp\":57,\"../type/js/undefined\":58,\"./default_safe\":48}],48:[function(require,module,exports){\n\"use strict\";var Schema=require(\"../schema\");module.exports=new Schema({include:[require(\"./core\")],implicit:[require(\"../type/timestamp\"),require(\"../type/merge\")],explicit:[require(\"../type/binary\"),require(\"../type/omap\"),require(\"../type/pairs\"),require(\"../type/set\")]});\n\n},{\"../schema\":45,\"../type/binary\":52,\"../type/merge\":60,\"../type/omap\":62,\"../type/pairs\":63,\"../type/set\":65,\"../type/timestamp\":67,\"./core\":46}],49:[function(require,module,exports){\n\"use strict\";var Schema=require(\"../schema\");module.exports=new Schema({explicit:[require(\"../type/str\"),require(\"../type/seq\"),require(\"../type/map\")]});\n\n},{\"../schema\":45,\"../type/map\":59,\"../type/seq\":64,\"../type/str\":66}],50:[function(require,module,exports){\n\"use strict\";var Schema=require(\"../schema\");module.exports=new Schema({include:[require(\"./failsafe\")],implicit:[require(\"../type/null\"),require(\"../type/bool\"),require(\"../type/int\"),require(\"../type/float\")]});\n\n},{\"../schema\":45,\"../type/bool\":53,\"../type/float\":54,\"../type/int\":55,\"../type/null\":61,\"./failsafe\":49}],51:[function(require,module,exports){\n\"use strict\";function compileStyleAliases(e){var t={};return null!==e&&Object.keys(e).forEach(function(n){e[n].forEach(function(e){t[String(e)]=n})}),t}function Type(e,t){if(t=t||{},Object.keys(t).forEach(function(t){if(-1===TYPE_CONSTRUCTOR_OPTIONS.indexOf(t))throw new YAMLException('Unknown option \"'+t+'\" is met in definition of \"'+e+'\" YAML type.')}),this.tag=e,this.kind=t.kind||null,this.resolve=t.resolve||function(){return!0},this.construct=t.construct||function(e){return e},this.instanceOf=t.instanceOf||null,this.predicate=t.predicate||null,this.represent=t.represent||null,this.defaultStyle=t.defaultStyle||null,this.styleAliases=compileStyleAliases(t.styleAliases||null),-1===YAML_NODE_KINDS.indexOf(this.kind))throw new YAMLException('Unknown kind \"'+this.kind+'\" is specified for \"'+e+'\" YAML type.')}var YAMLException=require(\"./exception\"),TYPE_CONSTRUCTOR_OPTIONS=[\"kind\",\"resolve\",\"construct\",\"instanceOf\",\"predicate\",\"represent\",\"defaultStyle\",\"styleAliases\"],YAML_NODE_KINDS=[\"scalar\",\"sequence\",\"mapping\"];module.exports=Type;\n\n},{\"./exception\":42}],52:[function(require,module,exports){\n\"use strict\";function resolveYamlBinary(r){if(null===r)return!1;var e,n,u=0,t=r.length,a=BASE64_MAP;for(n=0;t>n;n++)if(e=a.indexOf(r.charAt(n)),!(e>64)){if(0>e)return!1;u+=6}return u%8===0}function constructYamlBinary(r){var e,n,u=r.replace(/[\\r\\n=]/g,\"\"),t=u.length,a=BASE64_MAP,f=0,i=[];for(e=0;t>e;e++)e%4===0&&e&&(i.push(f>>16&255),i.push(f>>8&255),i.push(255&f)),f=f<<6|a.indexOf(u.charAt(e));return n=t%4*6,0===n?(i.push(f>>16&255),i.push(f>>8&255),i.push(255&f)):18===n?(i.push(f>>10&255),i.push(f>>2&255)):12===n&&i.push(f>>4&255),NodeBuffer?new NodeBuffer(i):i}function representYamlBinary(r){var e,n,u=\"\",t=0,a=r.length,f=BASE64_MAP;for(e=0;a>e;e++)e%3===0&&e&&(u+=f[t>>18&63],u+=f[t>>12&63],u+=f[t>>6&63],u+=f[63&t]),t=(t<<8)+r[e];return n=a%3,0===n?(u+=f[t>>18&63],u+=f[t>>12&63],u+=f[t>>6&63],u+=f[63&t]):2===n?(u+=f[t>>10&63],u+=f[t>>4&63],u+=f[t<<2&63],u+=f[64]):1===n&&(u+=f[t>>2&63],u+=f[t<<4&63],u+=f[64],u+=f[64]),u}function isBinary(r){return NodeBuffer&&NodeBuffer.isBuffer(r)}var NodeBuffer;try{var _require=require;NodeBuffer=_require(\"buffer\").Buffer}catch(__){}var Type=require(\"../type\"),BASE64_MAP=\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\\n\\r\";module.exports=new Type(\"tag:yaml.org,2002:binary\",{kind:\"scalar\",resolve:resolveYamlBinary,construct:constructYamlBinary,predicate:isBinary,represent:representYamlBinary});\n\n},{\"../type\":51}],53:[function(require,module,exports){\n\"use strict\";function resolveYamlBoolean(e){if(null===e)return!1;var r=e.length;return 4===r&&(\"true\"===e||\"True\"===e||\"TRUE\"===e)||5===r&&(\"false\"===e||\"False\"===e||\"FALSE\"===e)}function constructYamlBoolean(e){return\"true\"===e||\"True\"===e||\"TRUE\"===e}function isBoolean(e){return\"[object Boolean]\"===Object.prototype.toString.call(e)}var Type=require(\"../type\");module.exports=new Type(\"tag:yaml.org,2002:bool\",{kind:\"scalar\",resolve:resolveYamlBoolean,construct:constructYamlBoolean,predicate:isBoolean,represent:{lowercase:function(e){return e?\"true\":\"false\"},uppercase:function(e){return e?\"TRUE\":\"FALSE\"},camelcase:function(e){return e?\"True\":\"False\"}},defaultStyle:\"lowercase\"});\n\n},{\"../type\":51}],54:[function(require,module,exports){\n\"use strict\";function resolveYamlFloat(e){return null===e?!1:!!YAML_FLOAT_PATTERN.test(e)}function constructYamlFloat(e){var r,t,a,n;return r=e.replace(/_/g,\"\").toLowerCase(),t=\"-\"===r[0]?-1:1,n=[],\"+-\".indexOf(r[0])>=0&&(r=r.slice(1)),\".inf\"===r?1===t?Number.POSITIVE_INFINITY:Number.NEGATIVE_INFINITY:\".nan\"===r?NaN:r.indexOf(\":\")>=0?(r.split(\":\").forEach(function(e){n.unshift(parseFloat(e,10))}),r=0,a=1,n.forEach(function(e){r+=e*a,a*=60}),t*r):t*parseFloat(r,10)}function representYamlFloat(e,r){var t;if(isNaN(e))switch(r){case\"lowercase\":return\".nan\";case\"uppercase\":return\".NAN\";case\"camelcase\":return\".NaN\"}else if(Number.POSITIVE_INFINITY===e)switch(r){case\"lowercase\":return\".inf\";case\"uppercase\":return\".INF\";case\"camelcase\":return\".Inf\"}else if(Number.NEGATIVE_INFINITY===e)switch(r){case\"lowercase\":return\"-.inf\";case\"uppercase\":return\"-.INF\";case\"camelcase\":return\"-.Inf\"}else if(common.isNegativeZero(e))return\"-0.0\";return t=e.toString(10),SCIENTIFIC_WITHOUT_DOT.test(t)?t.replace(\"e\",\".e\"):t}function isFloat(e){return\"[object Number]\"===Object.prototype.toString.call(e)&&(e%1!==0||common.isNegativeZero(e))}var common=require(\"../common\"),Type=require(\"../type\"),YAML_FLOAT_PATTERN=new RegExp(\"^(?:[-+]?(?:[0-9][0-9_]*)\\\\.[0-9_]*(?:[eE][-+][0-9]+)?|\\\\.[0-9_]+(?:[eE][-+][0-9]+)?|[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\\\\.[0-9_]*|[-+]?\\\\.(?:inf|Inf|INF)|\\\\.(?:nan|NaN|NAN))$\"),SCIENTIFIC_WITHOUT_DOT=/^[-+]?[0-9]+e/;module.exports=new Type(\"tag:yaml.org,2002:float\",{kind:\"scalar\",resolve:resolveYamlFloat,construct:constructYamlFloat,predicate:isFloat,represent:representYamlFloat,defaultStyle:\"lowercase\"});\n\n},{\"../common\":40,\"../type\":51}],55:[function(require,module,exports){\n\"use strict\";function isHexCode(e){return e>=48&&57>=e||e>=65&&70>=e||e>=97&&102>=e}function isOctCode(e){return e>=48&&55>=e}function isDecCode(e){return e>=48&&57>=e}function resolveYamlInteger(e){if(null===e)return!1;var r,t=e.length,n=0,i=!1;if(!t)return!1;if(r=e[n],\"-\"!==r&&\"+\"!==r||(r=e[++n]),\"0\"===r){if(n+1===t)return!0;if(r=e[++n],\"b\"===r){for(n++;t>n;n++)if(r=e[n],\"_\"!==r){if(\"0\"!==r&&\"1\"!==r)return!1;i=!0}return i}if(\"x\"===r){for(n++;t>n;n++)if(r=e[n],\"_\"!==r){if(!isHexCode(e.charCodeAt(n)))return!1;i=!0}return i}for(;t>n;n++)if(r=e[n],\"_\"!==r){if(!isOctCode(e.charCodeAt(n)))return!1;i=!0}return i}for(;t>n;n++)if(r=e[n],\"_\"!==r){if(\":\"===r)break;if(!isDecCode(e.charCodeAt(n)))return!1;i=!0}return i?\":\"!==r?!0:/^(:[0-5]?[0-9])+$/.test(e.slice(n)):!1}function constructYamlInteger(e){var r,t,n=e,i=1,o=[];return-1!==n.indexOf(\"_\")&&(n=n.replace(/_/g,\"\")),r=n[0],\"-\"!==r&&\"+\"!==r||(\"-\"===r&&(i=-1),n=n.slice(1),r=n[0]),\"0\"===n?0:\"0\"===r?\"b\"===n[1]?i*parseInt(n.slice(2),2):\"x\"===n[1]?i*parseInt(n,16):i*parseInt(n,8):-1!==n.indexOf(\":\")?(n.split(\":\").forEach(function(e){o.unshift(parseInt(e,10))}),n=0,t=1,o.forEach(function(e){n+=e*t,t*=60}),i*n):i*parseInt(n,10)}function isInteger(e){return\"[object Number]\"===Object.prototype.toString.call(e)&&e%1===0&&!common.isNegativeZero(e)}var common=require(\"../common\"),Type=require(\"../type\");module.exports=new Type(\"tag:yaml.org,2002:int\",{kind:\"scalar\",resolve:resolveYamlInteger,construct:constructYamlInteger,predicate:isInteger,represent:{binary:function(e){return\"0b\"+e.toString(2)},octal:function(e){return\"0\"+e.toString(8)},decimal:function(e){return e.toString(10)},hexadecimal:function(e){return\"0x\"+e.toString(16).toUpperCase()}},defaultStyle:\"decimal\",styleAliases:{binary:[2,\"bin\"],octal:[8,\"oct\"],decimal:[10,\"dec\"],hexadecimal:[16,\"hex\"]}});\n\n},{\"../common\":40,\"../type\":51}],56:[function(require,module,exports){\n\"use strict\";function resolveJavascriptFunction(e){if(null===e)return!1;try{var r=\"(\"+e+\")\",n=esprima.parse(r,{range:!0});return\"Program\"===n.type&&1===n.body.length&&\"ExpressionStatement\"===n.body[0].type&&\"FunctionExpression\"===n.body[0].expression.type}catch(t){return!1}}function constructJavascriptFunction(e){var r,n=\"(\"+e+\")\",t=esprima.parse(n,{range:!0}),o=[];if(\"Program\"!==t.type||1!==t.body.length||\"ExpressionStatement\"!==t.body[0].type||\"FunctionExpression\"!==t.body[0].expression.type)throw new Error(\"Failed to resolve function\");return t.body[0].expression.params.forEach(function(e){o.push(e.name)}),r=t.body[0].expression.body.range,new Function(o,n.slice(r[0]+1,r[1]-1))}function representJavascriptFunction(e){return e.toString()}function isFunction(e){return\"[object Function]\"===Object.prototype.toString.call(e)}var esprima;try{var _require=require;esprima=_require(\"esprima\")}catch(_){\"undefined\"!=typeof window&&(esprima=window.esprima)}var Type=require(\"../../type\");module.exports=new Type(\"tag:yaml.org,2002:js/function\",{kind:\"scalar\",resolve:resolveJavascriptFunction,construct:constructJavascriptFunction,predicate:isFunction,represent:representJavascriptFunction});\n\n},{\"../../type\":51}],57:[function(require,module,exports){\n\"use strict\";function resolveJavascriptRegExp(e){if(null===e)return!1;if(0===e.length)return!1;var r=e,t=/\\/([gim]*)$/.exec(e),n=\"\";if(\"/\"===r[0]){if(t&&(n=t[1]),n.length>3)return!1;if(\"/\"!==r[r.length-n.length-1])return!1}return!0}function constructJavascriptRegExp(e){var r=e,t=/\\/([gim]*)$/.exec(e),n=\"\";return\"/\"===r[0]&&(t&&(n=t[1]),r=r.slice(1,r.length-n.length-1)),new RegExp(r,n)}function representJavascriptRegExp(e){var r=\"/\"+e.source+\"/\";return e.global&&(r+=\"g\"),e.multiline&&(r+=\"m\"),e.ignoreCase&&(r+=\"i\"),r}function isRegExp(e){return\"[object RegExp]\"===Object.prototype.toString.call(e)}var Type=require(\"../../type\");module.exports=new Type(\"tag:yaml.org,2002:js/regexp\",{kind:\"scalar\",resolve:resolveJavascriptRegExp,construct:constructJavascriptRegExp,predicate:isRegExp,represent:representJavascriptRegExp});\n\n},{\"../../type\":51}],58:[function(require,module,exports){\n\"use strict\";function resolveJavascriptUndefined(){return!0}function constructJavascriptUndefined(){}function representJavascriptUndefined(){return\"\"}function isUndefined(e){return\"undefined\"==typeof e}var Type=require(\"../../type\");module.exports=new Type(\"tag:yaml.org,2002:js/undefined\",{kind:\"scalar\",resolve:resolveJavascriptUndefined,construct:constructJavascriptUndefined,predicate:isUndefined,represent:representJavascriptUndefined});\n\n},{\"../../type\":51}],59:[function(require,module,exports){\n\"use strict\";var Type=require(\"../type\");module.exports=new Type(\"tag:yaml.org,2002:map\",{kind:\"mapping\",construct:function(e){return null!==e?e:{}}});\n\n},{\"../type\":51}],60:[function(require,module,exports){\n\"use strict\";function resolveYamlMerge(e){return\"<<\"===e||null===e}var Type=require(\"../type\");module.exports=new Type(\"tag:yaml.org,2002:merge\",{kind:\"scalar\",resolve:resolveYamlMerge});\n\n},{\"../type\":51}],61:[function(require,module,exports){\n\"use strict\";function resolveYamlNull(l){if(null===l)return!0;var e=l.length;return 1===e&&\"~\"===l||4===e&&(\"null\"===l||\"Null\"===l||\"NULL\"===l)}function constructYamlNull(){return null}function isNull(l){return null===l}var Type=require(\"../type\");module.exports=new Type(\"tag:yaml.org,2002:null\",{kind:\"scalar\",resolve:resolveYamlNull,construct:constructYamlNull,predicate:isNull,represent:{canonical:function(){return\"~\"},lowercase:function(){return\"null\"},uppercase:function(){return\"NULL\"},camelcase:function(){return\"Null\"}},defaultStyle:\"lowercase\"});\n\n},{\"../type\":51}],62:[function(require,module,exports){\n\"use strict\";function resolveYamlOmap(r){if(null===r)return!0;var t,e,n,o,u,a=[],l=r;for(t=0,e=l.length;e>t;t+=1){if(n=l[t],u=!1,\"[object Object]\"!==_toString.call(n))return!1;for(o in n)if(_hasOwnProperty.call(n,o)){if(u)return!1;u=!0}if(!u)return!1;if(-1!==a.indexOf(o))return!1;a.push(o)}return!0}function constructYamlOmap(r){return null!==r?r:[]}var Type=require(\"../type\"),_hasOwnProperty=Object.prototype.hasOwnProperty,_toString=Object.prototype.toString;module.exports=new Type(\"tag:yaml.org,2002:omap\",{kind:\"sequence\",resolve:resolveYamlOmap,construct:constructYamlOmap});\n\n},{\"../type\":51}],63:[function(require,module,exports){\n\"use strict\";function resolveYamlPairs(r){if(null===r)return!0;var e,t,n,l,o,a=r;for(o=new Array(a.length),e=0,t=a.length;t>e;e+=1){if(n=a[e],\"[object Object]\"!==_toString.call(n))return!1;if(l=Object.keys(n),1!==l.length)return!1;o[e]=[l[0],n[l[0]]]}return!0}function constructYamlPairs(r){if(null===r)return[];var e,t,n,l,o,a=r;for(o=new Array(a.length),e=0,t=a.length;t>e;e+=1)n=a[e],l=Object.keys(n),o[e]=[l[0],n[l[0]]];return o}var Type=require(\"../type\"),_toString=Object.prototype.toString;module.exports=new Type(\"tag:yaml.org,2002:pairs\",{kind:\"sequence\",resolve:resolveYamlPairs,construct:constructYamlPairs});\n\n},{\"../type\":51}],64:[function(require,module,exports){\n\"use strict\";var Type=require(\"../type\");module.exports=new Type(\"tag:yaml.org,2002:seq\",{kind:\"sequence\",construct:function(e){return null!==e?e:[]}});\n\n},{\"../type\":51}],65:[function(require,module,exports){\n\"use strict\";function resolveYamlSet(e){if(null===e)return!0;var r,t=e;for(r in t)if(_hasOwnProperty.call(t,r)&&null!==t[r])return!1;return!0}function constructYamlSet(e){return null!==e?e:{}}var Type=require(\"../type\"),_hasOwnProperty=Object.prototype.hasOwnProperty;module.exports=new Type(\"tag:yaml.org,2002:set\",{kind:\"mapping\",resolve:resolveYamlSet,construct:constructYamlSet});\n\n},{\"../type\":51}],66:[function(require,module,exports){\n\"use strict\";var Type=require(\"../type\");module.exports=new Type(\"tag:yaml.org,2002:str\",{kind:\"scalar\",construct:function(r){return null!==r?r:\"\"}});\n\n},{\"../type\":51}],67:[function(require,module,exports){\n\"use strict\";function resolveYamlTimestamp(e){return null===e?!1:null!==YAML_DATE_REGEXP.exec(e)?!0:null!==YAML_TIMESTAMP_REGEXP.exec(e)}function constructYamlTimestamp(e){var t,r,n,l,a,m,s,T,i,E,u=0,o=null;if(t=YAML_DATE_REGEXP.exec(e),null===t&&(t=YAML_TIMESTAMP_REGEXP.exec(e)),null===t)throw new Error(\"Date resolve error\");if(r=+t[1],n=+t[2]-1,l=+t[3],!t[4])return new Date(Date.UTC(r,n,l));if(a=+t[4],m=+t[5],s=+t[6],t[7]){for(u=t[7].slice(0,3);u.length<3;)u+=\"0\";u=+u}return t[9]&&(T=+t[10],i=+(t[11]||0),o=6e4*(60*T+i),\"-\"===t[9]&&(o=-o)),E=new Date(Date.UTC(r,n,l,a,m,s,u)),o&&E.setTime(E.getTime()-o),E}function representYamlTimestamp(e){return e.toISOString()}var Type=require(\"../type\"),YAML_DATE_REGEXP=new RegExp(\"^([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9])$\"),YAML_TIMESTAMP_REGEXP=new RegExp(\"^([0-9][0-9][0-9][0-9])-([0-9][0-9]?)-([0-9][0-9]?)(?:[Tt]|[ \\\\t]+)([0-9][0-9]?):([0-9][0-9]):([0-9][0-9])(?:\\\\.([0-9]*))?(?:[ \\\\t]*(Z|([-+])([0-9][0-9]?)(?::([0-9][0-9]))?))?$\");module.exports=new Type(\"tag:yaml.org,2002:timestamp\",{kind:\"scalar\",resolve:resolveYamlTimestamp,construct:constructYamlTimestamp,instanceOf:Date,represent:representYamlTimestamp});\n\n},{\"../type\":51}],68:[function(require,module,exports){\nfunction parse(e){if(e=\"\"+e,!(e.length>1e4)){var a=/^((?:\\d+)?\\.?\\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec(e);if(a){var r=parseFloat(a[1]),c=(a[2]||\"ms\").toLowerCase();switch(c){case\"years\":case\"year\":case\"yrs\":case\"yr\":case\"y\":return r*y;case\"days\":case\"day\":case\"d\":return r*d;case\"hours\":case\"hour\":case\"hrs\":case\"hr\":case\"h\":return r*h;case\"minutes\":case\"minute\":case\"mins\":case\"min\":case\"m\":return r*m;case\"seconds\":case\"second\":case\"secs\":case\"sec\":case\"s\":return r*s;case\"milliseconds\":case\"millisecond\":case\"msecs\":case\"msec\":case\"ms\":return r}}}}function short(e){return e>=d?Math.round(e/d)+\"d\":e>=h?Math.round(e/h)+\"h\":e>=m?Math.round(e/m)+\"m\":e>=s?Math.round(e/s)+\"s\":e+\"ms\"}function long(e){return plural(e,d,\"day\")||plural(e,h,\"hour\")||plural(e,m,\"minute\")||plural(e,s,\"second\")||e+\" ms\"}function plural(s,e,a){return e>s?void 0:1.5*e>s?Math.floor(s/e)+\" \"+a:Math.ceil(s/e)+\" \"+a+\"s\"}var s=1e3,m=60*s,h=60*m,d=24*h,y=365.25*d;module.exports=function(s,e){return e=e||{},\"string\"==typeof s?parse(s):e[\"long\"]?long(s):short(s)};\n\n},{}],69:[function(require,module,exports){\n/**!\n * Ono v2.2.1\n *\n * @link https://github.com/BigstickCarpet/ono\n * @license MIT\n */\n\"use strict\";function create(e){return function(r,t,o,n){var c,a=module.exports.formatter;\"string\"==typeof r?(c=a.apply(null,arguments),r=t=void 0):c=\"string\"==typeof t?a.apply(null,slice.call(arguments,1)):a.apply(null,slice.call(arguments,2)),r instanceof Error||(t=r,r=void 0),r&&(c+=(c?\" \\n\":\"\")+r.message);var i=new e(c);return extendError(i,r),extendToJSON(i),extend(i,t),i}}function extendError(e,r){r&&(extendStack(e,r),extend(e,r,!0))}function extendToJSON(e){e.toJSON=errorToJSON,e.inspect=errorToString}function extend(e,r,t){if(r&&\"object\"==typeof r)for(var o=Object.keys(r),n=0;n<o.length;n++){var c=o[n];if(!(t&&vendorSpecificErrorProperties.indexOf(c)>=0))try{e[c]=r[c]}catch(a){}}}function errorToJSON(){var e={},r=Object.keys(this);r=r.concat(vendorSpecificErrorProperties);for(var t=0;t<r.length;t++){var o=r[t],n=this[o],c=typeof n;\"undefined\"!==c&&\"function\"!==c&&(e[o]=n)}return e}function errorToString(){return JSON.stringify(this,null,2).replace(/\\\\n/g,\"\\n\")}function extendStack(e,r){if(hasLazyStack(r))extendStackProperty(e,r);else{var t=r.stack;t&&(e.stack+=\" \\n\\n\"+r.stack)}}function hasLazyStack(e){if(!supportsLazyStack)return!1;var r=Object.getOwnPropertyDescriptor(e,\"stack\");return r?\"function\"==typeof r.get:!1}function extendStackProperty(e,r){var t=Object.getOwnPropertyDescriptor(r,\"stack\");if(t){var o=Object.getOwnPropertyDescriptor(e,\"stack\");Object.defineProperty(e,\"stack\",{get:function(){return o.get.apply(e)+\" \\n\\n\"+r.stack},enumerable:!1,configurable:!0})}}var util=require(\"util\"),slice=Array.prototype.slice,vendorSpecificErrorProperties=[\"name\",\"message\",\"description\",\"number\",\"fileName\",\"lineNumber\",\"columnNumber\",\"sourceURL\",\"line\",\"column\",\"stack\"];module.exports=create(Error),module.exports.error=create(Error),module.exports.eval=create(EvalError),module.exports.range=create(RangeError),module.exports.reference=create(ReferenceError),module.exports.syntax=create(SyntaxError),module.exports.type=create(TypeError),module.exports.uri=create(URIError),module.exports.formatter=util.format;var supportsLazyStack=function(){return!(!Object.getOwnPropertyDescriptor||!Object.defineProperty||\"undefined\"!=typeof navigator&&/Android/.test(navigator.userAgent))}();\n\n},{\"util\":97}],70:[function(require,module,exports){\n(function (process){\n\"use strict\";function nextTick(e){for(var s=new Array(arguments.length-1),n=0;n<s.length;)s[n++]=arguments[n];process.nextTick(function(){e.apply(null,s)})}!process.version||0===process.version.indexOf(\"v0.\")||0===process.version.indexOf(\"v1.\")&&0!==process.version.indexOf(\"v1.8.\")?module.exports=nextTick:module.exports=process.nextTick;\n\n}).call(this,require('_process'))\n\n},{\"_process\":71}],71:[function(require,module,exports){\nfunction cleanUpNextTick(){draining=!1,currentQueue.length?queue=currentQueue.concat(queue):queueIndex=-1,queue.length&&drainQueue()}function drainQueue(){if(!draining){var e=setTimeout(cleanUpNextTick);draining=!0;for(var n=queue.length;n;){for(currentQueue=queue,queue=[];++queueIndex<n;)currentQueue&&currentQueue[queueIndex].run();queueIndex=-1,n=queue.length}currentQueue=null,draining=!1,clearTimeout(e)}}function Item(e,n){this.fun=e,this.array=n}function noop(){}var process=module.exports={},queue=[],draining=!1,currentQueue,queueIndex=-1;process.nextTick=function(e){var n=new Array(arguments.length-1);if(arguments.length>1)for(var r=1;r<arguments.length;r++)n[r-1]=arguments[r];queue.push(new Item(e,n)),1!==queue.length||draining||setTimeout(drainQueue,0)},Item.prototype.run=function(){this.fun.apply(null,this.array)},process.title=\"browser\",process.browser=!0,process.env={},process.argv=[],process.version=\"\",process.versions={},process.on=noop,process.addListener=noop,process.once=noop,process.off=noop,process.removeListener=noop,process.removeAllListeners=noop,process.emit=noop,process.binding=function(e){throw new Error(\"process.binding is not supported\")},process.cwd=function(){return\"/\"},process.chdir=function(e){throw new Error(\"process.chdir is not supported\")},process.umask=function(){return 0};\n\n},{}],72:[function(require,module,exports){\n(function (global){\n/*! https://mths.be/punycode v1.4.1 by @mathias */\n!function(e){function o(e){throw new RangeError(T[e])}function n(e,o){for(var n=e.length,r=[];n--;)r[n]=o(e[n]);return r}function r(e,o){var r=e.split(\"@\"),t=\"\";r.length>1&&(t=r[0]+\"@\",e=r[1]),e=e.replace(S,\".\");var u=e.split(\".\"),i=n(u,o).join(\".\");return t+i}function t(e){for(var o,n,r=[],t=0,u=e.length;u>t;)o=e.charCodeAt(t++),o>=55296&&56319>=o&&u>t?(n=e.charCodeAt(t++),56320==(64512&n)?r.push(((1023&o)<<10)+(1023&n)+65536):(r.push(o),t--)):r.push(o);return r}function u(e){return n(e,function(e){var o=\"\";return e>65535&&(e-=65536,o+=P(e>>>10&1023|55296),e=56320|1023&e),o+=P(e)}).join(\"\")}function i(e){return 10>e-48?e-22:26>e-65?e-65:26>e-97?e-97:b}function f(e,o){return e+22+75*(26>e)-((0!=o)<<5)}function c(e,o,n){var r=0;for(e=n?M(e/j):e>>1,e+=M(e/o);e>L*C>>1;r+=b)e=M(e/L);return M(r+(L+1)*e/(e+m))}function l(e){var n,r,t,f,l,s,d,a,p,h,v=[],g=e.length,w=0,m=I,j=A;for(r=e.lastIndexOf(E),0>r&&(r=0),t=0;r>t;++t)e.charCodeAt(t)>=128&&o(\"not-basic\"),v.push(e.charCodeAt(t));for(f=r>0?r+1:0;g>f;){for(l=w,s=1,d=b;f>=g&&o(\"invalid-input\"),a=i(e.charCodeAt(f++)),(a>=b||a>M((x-w)/s))&&o(\"overflow\"),w+=a*s,p=j>=d?y:d>=j+C?C:d-j,!(p>a);d+=b)h=b-p,s>M(x/h)&&o(\"overflow\"),s*=h;n=v.length+1,j=c(w-l,n,0==l),M(w/n)>x-m&&o(\"overflow\"),m+=M(w/n),w%=n,v.splice(w++,0,m)}return u(v)}function s(e){var n,r,u,i,l,s,d,a,p,h,v,g,w,m,j,F=[];for(e=t(e),g=e.length,n=I,r=0,l=A,s=0;g>s;++s)v=e[s],128>v&&F.push(P(v));for(u=i=F.length,i&&F.push(E);g>u;){for(d=x,s=0;g>s;++s)v=e[s],v>=n&&d>v&&(d=v);for(w=u+1,d-n>M((x-r)/w)&&o(\"overflow\"),r+=(d-n)*w,n=d,s=0;g>s;++s)if(v=e[s],n>v&&++r>x&&o(\"overflow\"),v==n){for(a=r,p=b;h=l>=p?y:p>=l+C?C:p-l,!(h>a);p+=b)j=a-h,m=b-h,F.push(P(f(h+j%m,0))),a=M(j/m);F.push(P(f(a,0))),l=c(r,w,u==i),r=0,++u}++r,++n}return F.join(\"\")}function d(e){return r(e,function(e){return F.test(e)?l(e.slice(4).toLowerCase()):e})}function a(e){return r(e,function(e){return O.test(e)?\"xn--\"+s(e):e})}var p=\"object\"==typeof exports&&exports&&!exports.nodeType&&exports,h=\"object\"==typeof module&&module&&!module.nodeType&&module,v=\"object\"==typeof global&&global;v.global!==v&&v.window!==v&&v.self!==v||(e=v);var g,w,x=2147483647,b=36,y=1,C=26,m=38,j=700,A=72,I=128,E=\"-\",F=/^xn--/,O=/[^\\x20-\\x7E]/,S=/[\\x2E\\u3002\\uFF0E\\uFF61]/g,T={overflow:\"Overflow: input needs wider integers to process\",\"not-basic\":\"Illegal input >= 0x80 (not a basic code point)\",\"invalid-input\":\"Invalid input\"},L=b-y,M=Math.floor,P=String.fromCharCode;if(g={version:\"1.4.1\",ucs2:{decode:t,encode:u},decode:l,encode:s,toASCII:a,toUnicode:d},\"function\"==typeof define&&\"object\"==typeof define.amd&&define.amd)define(\"punycode\",function(){return g});else if(p&&h)if(module.exports==p)h.exports=g;else for(w in g)g.hasOwnProperty(w)&&(p[w]=g[w]);else e.punycode=g}(this);\n\n}).call(this,typeof global !== \"undefined\" ? global : typeof self !== \"undefined\" ? self : typeof window !== \"undefined\" ? window : {})\n\n},{}],73:[function(require,module,exports){\n\"use strict\";function hasOwnProperty(r,e){return Object.prototype.hasOwnProperty.call(r,e)}module.exports=function(r,e,t,n){e=e||\"&\",t=t||\"=\";var o={};if(\"string\"!=typeof r||0===r.length)return o;var a=/\\+/g;r=r.split(e);var s=1e3;n&&\"number\"==typeof n.maxKeys&&(s=n.maxKeys);var p=r.length;s>0&&p>s&&(p=s);for(var y=0;p>y;++y){var u,c,i,l,f=r[y].replace(a,\"%20\"),v=f.indexOf(t);v>=0?(u=f.substr(0,v),c=f.substr(v+1)):(u=f,c=\"\"),i=decodeURIComponent(u),l=decodeURIComponent(c),hasOwnProperty(o,i)?isArray(o[i])?o[i].push(l):o[i]=[o[i],l]:o[i]=l}return o};var isArray=Array.isArray||function(r){return\"[object Array]\"===Object.prototype.toString.call(r)};\n\n},{}],74:[function(require,module,exports){\n\"use strict\";function map(r,e){if(r.map)return r.map(e);for(var t=[],n=0;n<r.length;n++)t.push(e(r[n],n));return t}var stringifyPrimitive=function(r){switch(typeof r){case\"string\":return r;case\"boolean\":return r?\"true\":\"false\";case\"number\":return isFinite(r)?r:\"\";default:return\"\"}};module.exports=function(r,e,t,n){return e=e||\"&\",t=t||\"=\",null===r&&(r=void 0),\"object\"==typeof r?map(objectKeys(r),function(n){var i=encodeURIComponent(stringifyPrimitive(n))+t;return isArray(r[n])?map(r[n],function(r){return i+encodeURIComponent(stringifyPrimitive(r))}).join(e):i+encodeURIComponent(stringifyPrimitive(r[n]))}).join(e):n?encodeURIComponent(stringifyPrimitive(n))+t+encodeURIComponent(stringifyPrimitive(r)):\"\"};var isArray=Array.isArray||function(r){return\"[object Array]\"===Object.prototype.toString.call(r)},objectKeys=Object.keys||function(r){var e=[];for(var t in r)Object.prototype.hasOwnProperty.call(r,t)&&e.push(t);return e};\n\n},{}],75:[function(require,module,exports){\n\"use strict\";exports.decode=exports.parse=require(\"./decode\"),exports.encode=exports.stringify=require(\"./encode\");\n\n},{\"./decode\":73,\"./encode\":74}],76:[function(require,module,exports){\nmodule.exports=require(\"./lib/_stream_duplex.js\");\n\n},{\"./lib/_stream_duplex.js\":77}],77:[function(require,module,exports){\n\"use strict\";function Duplex(e){return this instanceof Duplex?(Readable.call(this,e),Writable.call(this,e),e&&e.readable===!1&&(this.readable=!1),e&&e.writable===!1&&(this.writable=!1),this.allowHalfOpen=!0,e&&e.allowHalfOpen===!1&&(this.allowHalfOpen=!1),void this.once(\"end\",onend)):new Duplex(e)}function onend(){this.allowHalfOpen||this._writableState.ended||processNextTick(onEndNT,this)}function onEndNT(e){e.end()}function forEach(e,t){for(var r=0,i=e.length;i>r;r++)t(e[r],r)}var objectKeys=Object.keys||function(e){var t=[];for(var r in e)t.push(r);return t};module.exports=Duplex;var processNextTick=require(\"process-nextick-args\"),util=require(\"core-util-is\");util.inherits=require(\"inherits\");var Readable=require(\"./_stream_readable\"),Writable=require(\"./_stream_writable\");util.inherits(Duplex,Readable);for(var keys=objectKeys(Writable.prototype),v=0;v<keys.length;v++){var method=keys[v];Duplex.prototype[method]||(Duplex.prototype[method]=Writable.prototype[method])}\n\n},{\"./_stream_readable\":79,\"./_stream_writable\":81,\"core-util-is\":28,\"inherits\":35,\"process-nextick-args\":70}],78:[function(require,module,exports){\n\"use strict\";function PassThrough(r){return this instanceof PassThrough?void Transform.call(this,r):new PassThrough(r)}module.exports=PassThrough;var Transform=require(\"./_stream_transform\"),util=require(\"core-util-is\");util.inherits=require(\"inherits\"),util.inherits(PassThrough,Transform),PassThrough.prototype._transform=function(r,s,i){i(null,r)};\n\n},{\"./_stream_transform\":80,\"core-util-is\":28,\"inherits\":35}],79:[function(require,module,exports){\n(function (process){\n\"use strict\";function ReadableState(e,t){Duplex=Duplex||require(\"./_stream_duplex\"),e=e||{},this.objectMode=!!e.objectMode,t instanceof Duplex&&(this.objectMode=this.objectMode||!!e.readableObjectMode);var r=e.highWaterMark,n=this.objectMode?16:16384;this.highWaterMark=r||0===r?r:n,this.highWaterMark=~~this.highWaterMark,this.buffer=[],this.length=0,this.pipes=null,this.pipesCount=0,this.flowing=null,this.ended=!1,this.endEmitted=!1,this.reading=!1,this.sync=!0,this.needReadable=!1,this.emittedReadable=!1,this.readableListening=!1,this.resumeScheduled=!1,this.defaultEncoding=e.defaultEncoding||\"utf8\",this.ranOut=!1,this.awaitDrain=0,this.readingMore=!1,this.decoder=null,this.encoding=null,e.encoding&&(StringDecoder||(StringDecoder=require(\"string_decoder/\").StringDecoder),this.decoder=new StringDecoder(e.encoding),this.encoding=e.encoding)}function Readable(e){return Duplex=Duplex||require(\"./_stream_duplex\"),this instanceof Readable?(this._readableState=new ReadableState(e,this),this.readable=!0,e&&\"function\"==typeof e.read&&(this._read=e.read),void Stream.call(this)):new Readable(e)}function readableAddChunk(e,t,r,n,a){var i=chunkInvalid(t,r);if(i)e.emit(\"error\",i);else if(null===r)t.reading=!1,onEofChunk(e,t);else if(t.objectMode||r&&r.length>0)if(t.ended&&!a){var d=new Error(\"stream.push() after EOF\");e.emit(\"error\",d)}else if(t.endEmitted&&a){var d=new Error(\"stream.unshift() after end event\");e.emit(\"error\",d)}else{var o;!t.decoder||a||n||(r=t.decoder.write(r),o=!t.objectMode&&0===r.length),a||(t.reading=!1),o||(t.flowing&&0===t.length&&!t.sync?(e.emit(\"data\",r),e.read(0)):(t.length+=t.objectMode?1:r.length,a?t.buffer.unshift(r):t.buffer.push(r),t.needReadable&&emitReadable(e))),maybeReadMore(e,t)}else a||(t.reading=!1);return needMoreData(t)}function needMoreData(e){return!e.ended&&(e.needReadable||e.length<e.highWaterMark||0===e.length)}function computeNewHighWaterMark(e){return e>=MAX_HWM?e=MAX_HWM:(e--,e|=e>>>1,e|=e>>>2,e|=e>>>4,e|=e>>>8,e|=e>>>16,e++),e}function howMuchToRead(e,t){return 0===t.length&&t.ended?0:t.objectMode?0===e?0:1:null===e||isNaN(e)?t.flowing&&t.buffer.length?t.buffer[0].length:t.length:0>=e?0:(e>t.highWaterMark&&(t.highWaterMark=computeNewHighWaterMark(e)),e>t.length?t.ended?t.length:(t.needReadable=!0,0):e)}function chunkInvalid(e,t){var r=null;return Buffer.isBuffer(t)||\"string\"==typeof t||null===t||void 0===t||e.objectMode||(r=new TypeError(\"Invalid non-string/buffer chunk\")),r}function onEofChunk(e,t){if(!t.ended){if(t.decoder){var r=t.decoder.end();r&&r.length&&(t.buffer.push(r),t.length+=t.objectMode?1:r.length)}t.ended=!0,emitReadable(e)}}function emitReadable(e){var t=e._readableState;t.needReadable=!1,t.emittedReadable||(debug(\"emitReadable\",t.flowing),t.emittedReadable=!0,t.sync?processNextTick(emitReadable_,e):emitReadable_(e))}function emitReadable_(e){debug(\"emit readable\"),e.emit(\"readable\"),flow(e)}function maybeReadMore(e,t){t.readingMore||(t.readingMore=!0,processNextTick(maybeReadMore_,e,t))}function maybeReadMore_(e,t){for(var r=t.length;!t.reading&&!t.flowing&&!t.ended&&t.length<t.highWaterMark&&(debug(\"maybeReadMore read 0\"),e.read(0),r!==t.length);)r=t.length;t.readingMore=!1}function pipeOnDrain(e){return function(){var t=e._readableState;debug(\"pipeOnDrain\",t.awaitDrain),t.awaitDrain&&t.awaitDrain--,0===t.awaitDrain&&EElistenerCount(e,\"data\")&&(t.flowing=!0,flow(e))}}function nReadingNextTick(e){debug(\"readable nexttick read 0\"),e.read(0)}function resume(e,t){t.resumeScheduled||(t.resumeScheduled=!0,processNextTick(resume_,e,t))}function resume_(e,t){t.reading||(debug(\"resume read 0\"),e.read(0)),t.resumeScheduled=!1,e.emit(\"resume\"),flow(e),t.flowing&&!t.reading&&e.read(0)}function flow(e){var t=e._readableState;if(debug(\"flow\",t.flowing),t.flowing)do var r=e.read();while(null!==r&&t.flowing)}function fromList(e,t){var r,n=t.buffer,a=t.length,i=!!t.decoder,d=!!t.objectMode;if(0===n.length)return null;if(0===a)r=null;else if(d)r=n.shift();else if(!e||e>=a)r=i?n.join(\"\"):1===n.length?n[0]:Buffer.concat(n,a),n.length=0;else if(e<n[0].length){var o=n[0];r=o.slice(0,e),n[0]=o.slice(e)}else if(e===n[0].length)r=n.shift();else{r=i?\"\":new Buffer(e);for(var l=0,u=0,s=n.length;s>u&&e>l;u++){var o=n[0],h=Math.min(e-l,o.length);i?r+=o.slice(0,h):o.copy(r,l,0,h),h<o.length?n[0]=o.slice(h):n.shift(),l+=h}}return r}function endReadable(e){var t=e._readableState;if(t.length>0)throw new Error(\"endReadable called on non-empty stream\");t.endEmitted||(t.ended=!0,processNextTick(endReadableNT,t,e))}function endReadableNT(e,t){e.endEmitted||0!==e.length||(e.endEmitted=!0,t.readable=!1,t.emit(\"end\"))}function forEach(e,t){for(var r=0,n=e.length;n>r;r++)t(e[r],r)}function indexOf(e,t){for(var r=0,n=e.length;n>r;r++)if(e[r]===t)return r;return-1}module.exports=Readable;var processNextTick=require(\"process-nextick-args\"),isArray=require(\"isarray\"),Buffer=require(\"buffer\").Buffer;Readable.ReadableState=ReadableState;var EE=require(\"events\"),EElistenerCount=function(e,t){return e.listeners(t).length},Stream;!function(){try{Stream=require(\"stream\")}catch(e){}finally{Stream||(Stream=require(\"events\").EventEmitter)}}();var Buffer=require(\"buffer\").Buffer,util=require(\"core-util-is\");util.inherits=require(\"inherits\");var debugUtil=require(\"util\"),debug=void 0;debug=debugUtil&&debugUtil.debuglog?debugUtil.debuglog(\"stream\"):function(){};var StringDecoder;util.inherits(Readable,Stream);var Duplex,Duplex;Readable.prototype.push=function(e,t){var r=this._readableState;return r.objectMode||\"string\"!=typeof e||(t=t||r.defaultEncoding,t!==r.encoding&&(e=new Buffer(e,t),t=\"\")),readableAddChunk(this,r,e,t,!1)},Readable.prototype.unshift=function(e){var t=this._readableState;return readableAddChunk(this,t,e,\"\",!0)},Readable.prototype.isPaused=function(){return this._readableState.flowing===!1},Readable.prototype.setEncoding=function(e){return StringDecoder||(StringDecoder=require(\"string_decoder/\").StringDecoder),this._readableState.decoder=new StringDecoder(e),this._readableState.encoding=e,this};var MAX_HWM=8388608;Readable.prototype.read=function(e){debug(\"read\",e);var t=this._readableState,r=e;if((\"number\"!=typeof e||e>0)&&(t.emittedReadable=!1),0===e&&t.needReadable&&(t.length>=t.highWaterMark||t.ended))return debug(\"read: emitReadable\",t.length,t.ended),0===t.length&&t.ended?endReadable(this):emitReadable(this),null;if(e=howMuchToRead(e,t),0===e&&t.ended)return 0===t.length&&endReadable(this),null;var n=t.needReadable;debug(\"need readable\",n),(0===t.length||t.length-e<t.highWaterMark)&&(n=!0,debug(\"length less than watermark\",n)),(t.ended||t.reading)&&(n=!1,debug(\"reading or ended\",n)),n&&(debug(\"do read\"),t.reading=!0,t.sync=!0,0===t.length&&(t.needReadable=!0),this._read(t.highWaterMark),t.sync=!1),n&&!t.reading&&(e=howMuchToRead(r,t));var a;return a=e>0?fromList(e,t):null,null===a&&(t.needReadable=!0,e=0),t.length-=e,0!==t.length||t.ended||(t.needReadable=!0),r!==e&&t.ended&&0===t.length&&endReadable(this),null!==a&&this.emit(\"data\",a),a},Readable.prototype._read=function(e){this.emit(\"error\",new Error(\"not implemented\"))},Readable.prototype.pipe=function(e,t){function r(e){debug(\"onunpipe\"),e===s&&a()}function n(){debug(\"onend\"),e.end()}function a(){debug(\"cleanup\"),e.removeListener(\"close\",o),e.removeListener(\"finish\",l),e.removeListener(\"drain\",c),e.removeListener(\"error\",d),e.removeListener(\"unpipe\",r),s.removeListener(\"end\",n),s.removeListener(\"end\",a),s.removeListener(\"data\",i),b=!0,!h.awaitDrain||e._writableState&&!e._writableState.needDrain||c()}function i(t){debug(\"ondata\");var r=e.write(t);!1===r&&(1!==h.pipesCount||h.pipes[0]!==e||1!==s.listenerCount(\"data\")||b||(debug(\"false write response, pause\",s._readableState.awaitDrain),s._readableState.awaitDrain++),s.pause())}function d(t){debug(\"onerror\",t),u(),e.removeListener(\"error\",d),0===EElistenerCount(e,\"error\")&&e.emit(\"error\",t)}function o(){e.removeListener(\"finish\",l),u()}function l(){debug(\"onfinish\"),e.removeListener(\"close\",o),u()}function u(){debug(\"unpipe\"),s.unpipe(e)}var s=this,h=this._readableState;switch(h.pipesCount){case 0:h.pipes=e;break;case 1:h.pipes=[h.pipes,e];break;default:h.pipes.push(e)}h.pipesCount+=1,debug(\"pipe count=%d opts=%j\",h.pipesCount,t);var f=(!t||t.end!==!1)&&e!==process.stdout&&e!==process.stderr,p=f?n:a;h.endEmitted?processNextTick(p):s.once(\"end\",p),e.on(\"unpipe\",r);var c=pipeOnDrain(s);e.on(\"drain\",c);var b=!1;return s.on(\"data\",i),e._events&&e._events.error?isArray(e._events.error)?e._events.error.unshift(d):e._events.error=[d,e._events.error]:e.on(\"error\",d),e.once(\"close\",o),e.once(\"finish\",l),e.emit(\"pipe\",s),h.flowing||(debug(\"pipe resume\"),s.resume()),e},Readable.prototype.unpipe=function(e){var t=this._readableState;if(0===t.pipesCount)return this;if(1===t.pipesCount)return e&&e!==t.pipes?this:(e||(e=t.pipes),t.pipes=null,t.pipesCount=0,t.flowing=!1,e&&e.emit(\"unpipe\",this),this);if(!e){var r=t.pipes,n=t.pipesCount;t.pipes=null,t.pipesCount=0,t.flowing=!1;for(var a=0;n>a;a++)r[a].emit(\"unpipe\",this);return this}var i=indexOf(t.pipes,e);return-1===i?this:(t.pipes.splice(i,1),t.pipesCount-=1,1===t.pipesCount&&(t.pipes=t.pipes[0]),e.emit(\"unpipe\",this),this)},Readable.prototype.on=function(e,t){var r=Stream.prototype.on.call(this,e,t);if(\"data\"===e&&!1!==this._readableState.flowing&&this.resume(),\"readable\"===e&&!this._readableState.endEmitted){var n=this._readableState;n.readableListening||(n.readableListening=!0,n.emittedReadable=!1,n.needReadable=!0,n.reading?n.length&&emitReadable(this,n):processNextTick(nReadingNextTick,this))}return r},Readable.prototype.addListener=Readable.prototype.on,Readable.prototype.resume=function(){var e=this._readableState;return e.flowing||(debug(\"resume\"),e.flowing=!0,resume(this,e)),this},Readable.prototype.pause=function(){return debug(\"call pause flowing=%j\",this._readableState.flowing),!1!==this._readableState.flowing&&(debug(\"pause\"),this._readableState.flowing=!1,this.emit(\"pause\")),this},Readable.prototype.wrap=function(e){var t=this._readableState,r=!1,n=this;e.on(\"end\",function(){if(debug(\"wrapped end\"),t.decoder&&!t.ended){var e=t.decoder.end();e&&e.length&&n.push(e)}n.push(null)}),e.on(\"data\",function(a){if(debug(\"wrapped data\"),t.decoder&&(a=t.decoder.write(a)),(!t.objectMode||null!==a&&void 0!==a)&&(t.objectMode||a&&a.length)){var i=n.push(a);i||(r=!0,e.pause())}});for(var a in e)void 0===this[a]&&\"function\"==typeof e[a]&&(this[a]=function(t){return function(){return e[t].apply(e,arguments)}}(a));var i=[\"error\",\"close\",\"destroy\",\"pause\",\"resume\"];return forEach(i,function(t){e.on(t,n.emit.bind(n,t))}),n._read=function(t){debug(\"wrapped _read\",t),r&&(r=!1,e.resume())},n},Readable._fromList=fromList;\n\n}).call(this,require('_process'))\n\n},{\"./_stream_duplex\":77,\"_process\":71,\"buffer\":25,\"core-util-is\":28,\"events\":32,\"inherits\":35,\"isarray\":37,\"process-nextick-args\":70,\"stream\":86,\"string_decoder/\":91,\"util\":23}],80:[function(require,module,exports){\n\"use strict\";function TransformState(r){this.afterTransform=function(t,n){return afterTransform(r,t,n)},this.needTransform=!1,this.transforming=!1,this.writecb=null,this.writechunk=null,this.writeencoding=null}function afterTransform(r,t,n){var e=r._transformState;e.transforming=!1;var i=e.writecb;if(!i)return r.emit(\"error\",new Error(\"no writecb in Transform class\"));e.writechunk=null,e.writecb=null,null!==n&&void 0!==n&&r.push(n),i(t);var a=r._readableState;a.reading=!1,(a.needReadable||a.length<a.highWaterMark)&&r._read(a.highWaterMark)}function Transform(r){if(!(this instanceof Transform))return new Transform(r);Duplex.call(this,r),this._transformState=new TransformState(this);var t=this;this._readableState.needReadable=!0,this._readableState.sync=!1,r&&(\"function\"==typeof r.transform&&(this._transform=r.transform),\"function\"==typeof r.flush&&(this._flush=r.flush)),this.once(\"prefinish\",function(){\"function\"==typeof this._flush?this._flush(function(r){done(t,r)}):done(t)})}function done(r,t){if(t)return r.emit(\"error\",t);var n=r._writableState,e=r._transformState;if(n.length)throw new Error(\"calling transform done when ws.length != 0\");if(e.transforming)throw new Error(\"calling transform done when still transforming\");return r.push(null)}module.exports=Transform;var Duplex=require(\"./_stream_duplex\"),util=require(\"core-util-is\");util.inherits=require(\"inherits\"),util.inherits(Transform,Duplex),Transform.prototype.push=function(r,t){return this._transformState.needTransform=!1,Duplex.prototype.push.call(this,r,t)},Transform.prototype._transform=function(r,t,n){throw new Error(\"not implemented\")},Transform.prototype._write=function(r,t,n){var e=this._transformState;if(e.writecb=n,e.writechunk=r,e.writeencoding=t,!e.transforming){var i=this._readableState;(e.needTransform||i.needReadable||i.length<i.highWaterMark)&&this._read(i.highWaterMark)}},Transform.prototype._read=function(r){var t=this._transformState;null!==t.writechunk&&t.writecb&&!t.transforming?(t.transforming=!0,this._transform(t.writechunk,t.writeencoding,t.afterTransform)):t.needTransform=!0};\n\n},{\"./_stream_duplex\":77,\"core-util-is\":28,\"inherits\":35}],81:[function(require,module,exports){\n(function (process){\n\"use strict\";function nop(){}function WriteReq(e,t,r){this.chunk=e,this.encoding=t,this.callback=r,this.next=null}function WritableState(e,t){Duplex=Duplex||require(\"./_stream_duplex\"),e=e||{},this.objectMode=!!e.objectMode,t instanceof Duplex&&(this.objectMode=this.objectMode||!!e.writableObjectMode);var r=e.highWaterMark,i=this.objectMode?16:16384;this.highWaterMark=r||0===r?r:i,this.highWaterMark=~~this.highWaterMark,this.needDrain=!1,this.ending=!1,this.ended=!1,this.finished=!1;var n=e.decodeStrings===!1;this.decodeStrings=!n,this.defaultEncoding=e.defaultEncoding||\"utf8\",this.length=0,this.writing=!1,this.corked=0,this.sync=!0,this.bufferProcessing=!1,this.onwrite=function(e){onwrite(t,e)},this.writecb=null,this.writelen=0,this.bufferedRequest=null,this.lastBufferedRequest=null,this.pendingcb=0,this.prefinished=!1,this.errorEmitted=!1,this.bufferedRequestCount=0,this.corkedRequestsFree=new CorkedRequest(this),this.corkedRequestsFree.next=new CorkedRequest(this)}function Writable(e){return Duplex=Duplex||require(\"./_stream_duplex\"),this instanceof Writable||this instanceof Duplex?(this._writableState=new WritableState(e,this),this.writable=!0,e&&(\"function\"==typeof e.write&&(this._write=e.write),\"function\"==typeof e.writev&&(this._writev=e.writev)),void Stream.call(this)):new Writable(e)}function writeAfterEnd(e,t){var r=new Error(\"write after end\");e.emit(\"error\",r),processNextTick(t,r)}function validChunk(e,t,r,i){var n=!0;if(!Buffer.isBuffer(r)&&\"string\"!=typeof r&&null!==r&&void 0!==r&&!t.objectMode){var s=new TypeError(\"Invalid non-string/buffer chunk\");e.emit(\"error\",s),processNextTick(i,s),n=!1}return n}function decodeChunk(e,t,r){return e.objectMode||e.decodeStrings===!1||\"string\"!=typeof t||(t=new Buffer(t,r)),t}function writeOrBuffer(e,t,r,i,n){r=decodeChunk(t,r,i),Buffer.isBuffer(r)&&(i=\"buffer\");var s=t.objectMode?1:r.length;t.length+=s;var f=t.length<t.highWaterMark;if(f||(t.needDrain=!0),t.writing||t.corked){var u=t.lastBufferedRequest;t.lastBufferedRequest=new WriteReq(r,i,n),u?u.next=t.lastBufferedRequest:t.bufferedRequest=t.lastBufferedRequest,t.bufferedRequestCount+=1}else doWrite(e,t,!1,s,r,i,n);return f}function doWrite(e,t,r,i,n,s,f){t.writelen=i,t.writecb=f,t.writing=!0,t.sync=!0,r?e._writev(n,t.onwrite):e._write(n,s,t.onwrite),t.sync=!1}function onwriteError(e,t,r,i,n){--t.pendingcb,r?processNextTick(n,i):n(i),e._writableState.errorEmitted=!0,e.emit(\"error\",i)}function onwriteStateUpdate(e){e.writing=!1,e.writecb=null,e.length-=e.writelen,e.writelen=0}function onwrite(e,t){var r=e._writableState,i=r.sync,n=r.writecb;if(onwriteStateUpdate(r),t)onwriteError(e,r,i,t,n);else{var s=needFinish(r);s||r.corked||r.bufferProcessing||!r.bufferedRequest||clearBuffer(e,r),i?asyncWrite(afterWrite,e,r,s,n):afterWrite(e,r,s,n)}}function afterWrite(e,t,r,i){r||onwriteDrain(e,t),t.pendingcb--,i(),finishMaybe(e,t)}function onwriteDrain(e,t){0===t.length&&t.needDrain&&(t.needDrain=!1,e.emit(\"drain\"))}function clearBuffer(e,t){t.bufferProcessing=!0;var r=t.bufferedRequest;if(e._writev&&r&&r.next){var i=t.bufferedRequestCount,n=new Array(i),s=t.corkedRequestsFree;s.entry=r;for(var f=0;r;)n[f]=r,r=r.next,f+=1;doWrite(e,t,!0,t.length,n,\"\",s.finish),t.pendingcb++,t.lastBufferedRequest=null,t.corkedRequestsFree=s.next,s.next=null}else{for(;r;){var u=r.chunk,o=r.encoding,a=r.callback,c=t.objectMode?1:u.length;if(doWrite(e,t,!1,c,u,o,a),r=r.next,t.writing)break}null===r&&(t.lastBufferedRequest=null)}t.bufferedRequestCount=0,t.bufferedRequest=r,t.bufferProcessing=!1}function needFinish(e){return e.ending&&0===e.length&&null===e.bufferedRequest&&!e.finished&&!e.writing}function prefinish(e,t){t.prefinished||(t.prefinished=!0,e.emit(\"prefinish\"))}function finishMaybe(e,t){var r=needFinish(t);return r&&(0===t.pendingcb?(prefinish(e,t),t.finished=!0,e.emit(\"finish\")):prefinish(e,t)),r}function endWritable(e,t,r){t.ending=!0,finishMaybe(e,t),r&&(t.finished?processNextTick(r):e.once(\"finish\",r)),t.ended=!0,e.writable=!1}function CorkedRequest(e){var t=this;this.next=null,this.entry=null,this.finish=function(r){var i=t.entry;for(t.entry=null;i;){var n=i.callback;e.pendingcb--,n(r),i=i.next}e.corkedRequestsFree?e.corkedRequestsFree.next=t:e.corkedRequestsFree=t}}module.exports=Writable;var processNextTick=require(\"process-nextick-args\"),asyncWrite=!process.browser&&[\"v0.10\",\"v0.9.\"].indexOf(process.version.slice(0,5))>-1?setImmediate:processNextTick,Buffer=require(\"buffer\").Buffer;Writable.WritableState=WritableState;var util=require(\"core-util-is\");util.inherits=require(\"inherits\");var internalUtil={deprecate:require(\"util-deprecate\")},Stream;!function(){try{Stream=require(\"stream\")}catch(e){}finally{Stream||(Stream=require(\"events\").EventEmitter)}}();var Buffer=require(\"buffer\").Buffer;util.inherits(Writable,Stream);var Duplex;WritableState.prototype.getBuffer=function(){for(var e=this.bufferedRequest,t=[];e;)t.push(e),e=e.next;return t},function(){try{Object.defineProperty(WritableState.prototype,\"buffer\",{get:internalUtil.deprecate(function(){return this.getBuffer()},\"_writableState.buffer is deprecated. Use _writableState.getBuffer instead.\")})}catch(e){}}();var Duplex;Writable.prototype.pipe=function(){this.emit(\"error\",new Error(\"Cannot pipe. Not readable.\"))},Writable.prototype.write=function(e,t,r){var i=this._writableState,n=!1;return\"function\"==typeof t&&(r=t,t=null),Buffer.isBuffer(e)?t=\"buffer\":t||(t=i.defaultEncoding),\"function\"!=typeof r&&(r=nop),i.ended?writeAfterEnd(this,r):validChunk(this,i,e,r)&&(i.pendingcb++,n=writeOrBuffer(this,i,e,t,r)),n},Writable.prototype.cork=function(){var e=this._writableState;e.corked++},Writable.prototype.uncork=function(){var e=this._writableState;e.corked&&(e.corked--,e.writing||e.corked||e.finished||e.bufferProcessing||!e.bufferedRequest||clearBuffer(this,e))},Writable.prototype.setDefaultEncoding=function(e){if(\"string\"==typeof e&&(e=e.toLowerCase()),!([\"hex\",\"utf8\",\"utf-8\",\"ascii\",\"binary\",\"base64\",\"ucs2\",\"ucs-2\",\"utf16le\",\"utf-16le\",\"raw\"].indexOf((e+\"\").toLowerCase())>-1))throw new TypeError(\"Unknown encoding: \"+e);this._writableState.defaultEncoding=e},Writable.prototype._write=function(e,t,r){r(new Error(\"not implemented\"))},Writable.prototype._writev=null,Writable.prototype.end=function(e,t,r){var i=this._writableState;\"function\"==typeof e?(r=e,e=null,t=null):\"function\"==typeof t&&(r=t,t=null),null!==e&&void 0!==e&&this.write(e,t),i.corked&&(i.corked=1,this.uncork()),i.ending||i.finished||endWritable(this,i,r)};\n\n}).call(this,require('_process'))\n\n},{\"./_stream_duplex\":77,\"_process\":71,\"buffer\":25,\"core-util-is\":28,\"events\":32,\"inherits\":35,\"process-nextick-args\":70,\"stream\":86,\"util-deprecate\":95}],82:[function(require,module,exports){\nmodule.exports=require(\"./lib/_stream_passthrough.js\");\n\n},{\"./lib/_stream_passthrough.js\":78}],83:[function(require,module,exports){\nvar Stream=function(){try{return require(\"stream\")}catch(r){}}();exports=module.exports=require(\"./lib/_stream_readable.js\"),exports.Stream=Stream||exports,exports.Readable=exports,exports.Writable=require(\"./lib/_stream_writable.js\"),exports.Duplex=require(\"./lib/_stream_duplex.js\"),exports.Transform=require(\"./lib/_stream_transform.js\"),exports.PassThrough=require(\"./lib/_stream_passthrough.js\");\n\n},{\"./lib/_stream_duplex.js\":77,\"./lib/_stream_passthrough.js\":78,\"./lib/_stream_readable.js\":79,\"./lib/_stream_transform.js\":80,\"./lib/_stream_writable.js\":81,\"stream\":86}],84:[function(require,module,exports){\nmodule.exports=require(\"./lib/_stream_transform.js\");\n\n},{\"./lib/_stream_transform.js\":80}],85:[function(require,module,exports){\nmodule.exports=require(\"./lib/_stream_writable.js\");\n\n},{\"./lib/_stream_writable.js\":81}],86:[function(require,module,exports){\nfunction Stream(){EE.call(this)}module.exports=Stream;var EE=require(\"events\").EventEmitter,inherits=require(\"inherits\");inherits(Stream,EE),Stream.Readable=require(\"readable-stream/readable.js\"),Stream.Writable=require(\"readable-stream/writable.js\"),Stream.Duplex=require(\"readable-stream/duplex.js\"),Stream.Transform=require(\"readable-stream/transform.js\"),Stream.PassThrough=require(\"readable-stream/passthrough.js\"),Stream.Stream=Stream,Stream.prototype.pipe=function(e,r){function t(r){e.writable&&!1===e.write(r)&&m.pause&&m.pause()}function n(){m.readable&&m.resume&&m.resume()}function a(){u||(u=!0,e.end())}function o(){u||(u=!0,\"function\"==typeof e.destroy&&e.destroy())}function i(e){if(s(),0===EE.listenerCount(this,\"error\"))throw e}function s(){m.removeListener(\"data\",t),e.removeListener(\"drain\",n),m.removeListener(\"end\",a),m.removeListener(\"close\",o),m.removeListener(\"error\",i),e.removeListener(\"error\",i),m.removeListener(\"end\",s),m.removeListener(\"close\",s),e.removeListener(\"close\",s)}var m=this;m.on(\"data\",t),e.on(\"drain\",n),e._isStdio||r&&r.end===!1||(m.on(\"end\",a),m.on(\"close\",o));var u=!1;return m.on(\"error\",i),e.on(\"error\",i),m.on(\"end\",s),m.on(\"close\",s),e.on(\"close\",s),e.emit(\"pipe\",m),e};\n\n},{\"events\":32,\"inherits\":35,\"readable-stream/duplex.js\":76,\"readable-stream/passthrough.js\":82,\"readable-stream/readable.js\":83,\"readable-stream/transform.js\":84,\"readable-stream/writable.js\":85}],87:[function(require,module,exports){\n(function (global){\nvar ClientRequest=require(\"./lib/request\"),extend=require(\"xtend\"),statusCodes=require(\"builtin-status-codes\"),url=require(\"url\"),http=exports;http.request=function(t,e){t=\"string\"==typeof t?url.parse(t):extend(t);var r=-1===global.location.protocol.search(/^https?:$/)?\"http:\":\"\",s=t.protocol||r,o=t.hostname||t.host,n=t.port,u=t.path||\"/\";o&&-1!==o.indexOf(\":\")&&(o=\"[\"+o+\"]\"),t.url=(o?s+\"//\"+o:\"\")+(n?\":\"+n:\"\")+u,t.method=(t.method||\"GET\").toUpperCase(),t.headers=t.headers||{};var C=new ClientRequest(t);return e&&C.on(\"response\",e),C},http.get=function(t,e){var r=http.request(t,e);return r.end(),r},http.Agent=function(){},http.Agent.defaultMaxSockets=4,http.STATUS_CODES=statusCodes,http.METHODS=[\"CHECKOUT\",\"CONNECT\",\"COPY\",\"DELETE\",\"GET\",\"HEAD\",\"LOCK\",\"M-SEARCH\",\"MERGE\",\"MKACTIVITY\",\"MKCOL\",\"MOVE\",\"NOTIFY\",\"OPTIONS\",\"PATCH\",\"POST\",\"PROPFIND\",\"PROPPATCH\",\"PURGE\",\"PUT\",\"REPORT\",\"SEARCH\",\"SUBSCRIBE\",\"TRACE\",\"UNLOCK\",\"UNSUBSCRIBE\"];\n\n}).call(this,typeof global !== \"undefined\" ? global : typeof self !== \"undefined\" ? self : typeof window !== \"undefined\" ? window : {})\n\n},{\"./lib/request\":89,\"builtin-status-codes\":26,\"url\":93,\"xtend\":98}],88:[function(require,module,exports){\n(function (global){\nfunction checkTypeSupport(e){try{return xhr.responseType=e,xhr.responseType===e}catch(r){}return!1}function isFunction(e){return\"function\"==typeof e}exports.fetch=isFunction(global.fetch)&&isFunction(global.ReadableByteStream),exports.blobConstructor=!1;try{new Blob([new ArrayBuffer(1)]),exports.blobConstructor=!0}catch(e){}var xhr=new global.XMLHttpRequest;xhr.open(\"GET\",global.location.host?\"/\":\"https://example.com\");var haveArrayBuffer=\"undefined\"!=typeof global.ArrayBuffer,haveSlice=haveArrayBuffer&&isFunction(global.ArrayBuffer.prototype.slice);exports.arraybuffer=haveArrayBuffer&&checkTypeSupport(\"arraybuffer\"),exports.msstream=!exports.fetch&&haveSlice&&checkTypeSupport(\"ms-stream\"),exports.mozchunkedarraybuffer=!exports.fetch&&haveArrayBuffer&&checkTypeSupport(\"moz-chunked-arraybuffer\"),exports.overrideMimeType=isFunction(xhr.overrideMimeType),exports.vbArray=isFunction(global.VBArray),xhr=null;\n\n}).call(this,typeof global !== \"undefined\" ? global : typeof self !== \"undefined\" ? self : typeof window !== \"undefined\" ? window : {})\n\n},{}],89:[function(require,module,exports){\n(function (process,global,Buffer){\nfunction decideMode(e){return capability.fetch?\"fetch\":capability.mozchunkedarraybuffer?\"moz-chunked-arraybuffer\":capability.msstream?\"ms-stream\":capability.arraybuffer&&e?\"arraybuffer\":capability.vbArray&&e?\"text:vbarray\":\"text\"}function statusValid(e){try{var t=e.status;return null!==t&&0!==t}catch(r){return!1}}var capability=require(\"./capability\"),inherits=require(\"inherits\"),response=require(\"./response\"),stream=require(\"stream\"),toArrayBuffer=require(\"to-arraybuffer\"),IncomingMessage=response.IncomingMessage,rStates=response.readyStates,ClientRequest=module.exports=function(e){var t=this;stream.Writable.call(t),t._opts=e,t._body=[],t._headers={},e.auth&&t.setHeader(\"Authorization\",\"Basic \"+new Buffer(e.auth).toString(\"base64\")),Object.keys(e.headers).forEach(function(r){t.setHeader(r,e.headers[r])});var r;if(\"prefer-streaming\"===e.mode)r=!1;else if(\"allow-wrong-content-type\"===e.mode)r=!capability.overrideMimeType;else{if(e.mode&&\"default\"!==e.mode&&\"prefer-fast\"!==e.mode)throw new Error(\"Invalid value for opts.mode\");r=!0}t._mode=decideMode(r),t.on(\"finish\",function(){t._onFinish()})};inherits(ClientRequest,stream.Writable),ClientRequest.prototype.setHeader=function(e,t){var r=this,o=e.toLowerCase();-1===unsafeHeaders.indexOf(o)&&(r._headers[o]={name:e,value:t})},ClientRequest.prototype.getHeader=function(e){var t=this;return t._headers[e.toLowerCase()].value},ClientRequest.prototype.removeHeader=function(e){var t=this;delete t._headers[e.toLowerCase()]},ClientRequest.prototype._onFinish=function(){var e=this;if(!e._destroyed){var t,r=e._opts,o=e._headers;if(\"POST\"!==r.method&&\"PUT\"!==r.method&&\"PATCH\"!==r.method||(t=capability.blobConstructor?new global.Blob(e._body.map(function(e){return toArrayBuffer(e)}),{type:(o[\"content-type\"]||{}).value||\"\"}):Buffer.concat(e._body).toString()),\"fetch\"===e._mode){var n=Object.keys(o).map(function(e){return[o[e].name,o[e].value]});global.fetch(e._opts.url,{method:e._opts.method,headers:n,body:t,mode:\"cors\",credentials:r.withCredentials?\"include\":\"same-origin\"}).then(function(t){e._fetchResponse=t,e._connect()},function(t){e.emit(\"error\",t)})}else{var s=e._xhr=new global.XMLHttpRequest;try{s.open(e._opts.method,e._opts.url,!0)}catch(i){return void process.nextTick(function(){e.emit(\"error\",i)})}\"responseType\"in s&&(s.responseType=e._mode.split(\":\")[0]),\"withCredentials\"in s&&(s.withCredentials=!!r.withCredentials),\"text\"===e._mode&&\"overrideMimeType\"in s&&s.overrideMimeType(\"text/plain; charset=x-user-defined\"),Object.keys(o).forEach(function(e){s.setRequestHeader(o[e].name,o[e].value)}),e._response=null,s.onreadystatechange=function(){switch(s.readyState){case rStates.LOADING:case rStates.DONE:e._onXHRProgress()}},\"moz-chunked-arraybuffer\"===e._mode&&(s.onprogress=function(){e._onXHRProgress()}),s.onerror=function(){e._destroyed||e.emit(\"error\",new Error(\"XHR error\"))};try{s.send(t)}catch(i){return void process.nextTick(function(){e.emit(\"error\",i)})}}}},ClientRequest.prototype._onXHRProgress=function(){var e=this;statusValid(e._xhr)&&!e._destroyed&&(e._response||e._connect(),e._response._onXHRProgress())},ClientRequest.prototype._connect=function(){var e=this;e._destroyed||(e._response=new IncomingMessage(e._xhr,e._fetchResponse,e._mode),e.emit(\"response\",e._response))},ClientRequest.prototype._write=function(e,t,r){var o=this;o._body.push(e),r()},ClientRequest.prototype.abort=ClientRequest.prototype.destroy=function(){var e=this;e._destroyed=!0,e._response&&(e._response._destroyed=!0),e._xhr&&e._xhr.abort()},ClientRequest.prototype.end=function(e,t,r){var o=this;\"function\"==typeof e&&(r=e,e=void 0),stream.Writable.prototype.end.call(o,e,t,r)},ClientRequest.prototype.flushHeaders=function(){},ClientRequest.prototype.setTimeout=function(){},ClientRequest.prototype.setNoDelay=function(){},ClientRequest.prototype.setSocketKeepAlive=function(){};var unsafeHeaders=[\"accept-charset\",\"accept-encoding\",\"access-control-request-headers\",\"access-control-request-method\",\"connection\",\"content-length\",\"cookie\",\"cookie2\",\"date\",\"dnt\",\"expect\",\"host\",\"keep-alive\",\"origin\",\"referer\",\"te\",\"trailer\",\"transfer-encoding\",\"upgrade\",\"user-agent\",\"via\"];\n\n}).call(this,require('_process'),typeof global !== \"undefined\" ? global : typeof self !== \"undefined\" ? self : typeof window !== \"undefined\" ? window : {},require(\"buffer\").Buffer)\n\n},{\"./capability\":88,\"./response\":90,\"_process\":71,\"buffer\":25,\"inherits\":35,\"stream\":86,\"to-arraybuffer\":92}],90:[function(require,module,exports){\n(function (process,global,Buffer){\nvar capability=require(\"./capability\"),inherits=require(\"inherits\"),stream=require(\"stream\"),rStates=exports.readyStates={UNSENT:0,OPENED:1,HEADERS_RECEIVED:2,LOADING:3,DONE:4},IncomingMessage=exports.IncomingMessage=function(e,r,s){function a(){u.read().then(function(e){if(!t._destroyed){if(e.done)return void t.push(null);t.push(new Buffer(e.value)),a()}})}var t=this;if(stream.Readable.call(t),t._mode=s,t.headers={},t.rawHeaders=[],t.trailers={},t.rawTrailers=[],t.on(\"end\",function(){process.nextTick(function(){t.emit(\"close\")})}),\"fetch\"===s){t._fetchResponse=r,t.statusCode=r.status,t.statusMessage=r.statusText;for(var n,o,i=r.headers[Symbol.iterator]();n=(o=i.next()).value,!o.done;)t.headers[n[0].toLowerCase()]=n[1],t.rawHeaders.push(n[0],n[1]);var u=r.body.getReader();a()}else{t._xhr=e,t._pos=0,t.statusCode=e.status,t.statusMessage=e.statusText;var h=e.getAllResponseHeaders().split(/\\r?\\n/);if(h.forEach(function(e){var r=e.match(/^([^:]+):\\s*(.*)/);if(r){var s=r[1].toLowerCase();\"set-cookie\"===s?(void 0===t.headers[s]&&(t.headers[s]=[]),t.headers[s].push(r[2])):void 0!==t.headers[s]?t.headers[s]+=\", \"+r[2]:t.headers[s]=r[2],t.rawHeaders.push(r[1],r[2])}}),t._charset=\"x-user-defined\",!capability.overrideMimeType){var d=t.rawHeaders[\"mime-type\"];if(d){var f=d.match(/;\\s*charset=([^;])(;|$)/);f&&(t._charset=f[1].toLowerCase())}t._charset||(t._charset=\"utf-8\")}}};inherits(IncomingMessage,stream.Readable),IncomingMessage.prototype._read=function(){},IncomingMessage.prototype._onXHRProgress=function(){var e=this,r=e._xhr,s=null;switch(e._mode){case\"text:vbarray\":if(r.readyState!==rStates.DONE)break;try{s=new global.VBArray(r.responseBody).toArray()}catch(a){}if(null!==s){e.push(new Buffer(s));break}case\"text\":try{s=r.responseText}catch(a){e._mode=\"text:vbarray\";break}if(s.length>e._pos){var t=s.substr(e._pos);if(\"x-user-defined\"===e._charset){for(var n=new Buffer(t.length),o=0;o<t.length;o++)n[o]=255&t.charCodeAt(o);e.push(n)}else e.push(t,e._charset);e._pos=s.length}break;case\"arraybuffer\":if(r.readyState!==rStates.DONE)break;s=r.response,e.push(new Buffer(new Uint8Array(s)));break;case\"moz-chunked-arraybuffer\":if(s=r.response,r.readyState!==rStates.LOADING||!s)break;e.push(new Buffer(new Uint8Array(s)));break;case\"ms-stream\":if(s=r.response,r.readyState!==rStates.LOADING)break;var i=new global.MSStreamReader;i.onprogress=function(){i.result.byteLength>e._pos&&(e.push(new Buffer(new Uint8Array(i.result.slice(e._pos)))),e._pos=i.result.byteLength)},i.onload=function(){e.push(null)},i.readAsArrayBuffer(s)}e._xhr.readyState===rStates.DONE&&\"ms-stream\"!==e._mode&&e.push(null)};\n\n}).call(this,require('_process'),typeof global !== \"undefined\" ? global : typeof self !== \"undefined\" ? self : typeof window !== \"undefined\" ? window : {},require(\"buffer\").Buffer)\n\n},{\"./capability\":88,\"_process\":71,\"buffer\":25,\"inherits\":35,\"stream\":86}],91:[function(require,module,exports){\nfunction assertEncoding(e){if(e&&!isBufferEncoding(e))throw new Error(\"Unknown encoding: \"+e)}function passThroughWrite(e){return e.toString(this.encoding)}function utf16DetectIncompleteChar(e){this.charReceived=e.length%2,this.charLength=this.charReceived?2:0}function base64DetectIncompleteChar(e){this.charReceived=e.length%3,this.charLength=this.charReceived?3:0}var Buffer=require(\"buffer\").Buffer,isBufferEncoding=Buffer.isEncoding||function(e){switch(e&&e.toLowerCase()){case\"hex\":case\"utf8\":case\"utf-8\":case\"ascii\":case\"binary\":case\"base64\":case\"ucs2\":case\"ucs-2\":case\"utf16le\":case\"utf-16le\":case\"raw\":return!0;default:return!1}},StringDecoder=exports.StringDecoder=function(e){switch(this.encoding=(e||\"utf8\").toLowerCase().replace(/[-_]/,\"\"),assertEncoding(e),this.encoding){case\"utf8\":this.surrogateSize=3;break;case\"ucs2\":case\"utf16le\":this.surrogateSize=2,this.detectIncompleteChar=utf16DetectIncompleteChar;break;case\"base64\":this.surrogateSize=3,this.detectIncompleteChar=base64DetectIncompleteChar;break;default:return void(this.write=passThroughWrite)}this.charBuffer=new Buffer(6),this.charReceived=0,this.charLength=0};StringDecoder.prototype.write=function(e){for(var t=\"\";this.charLength;){var r=e.length>=this.charLength-this.charReceived?this.charLength-this.charReceived:e.length;if(e.copy(this.charBuffer,this.charReceived,0,r),this.charReceived+=r,this.charReceived<this.charLength)return\"\";e=e.slice(r,e.length),t=this.charBuffer.slice(0,this.charLength).toString(this.encoding);var h=t.charCodeAt(t.length-1);if(!(h>=55296&&56319>=h)){if(this.charReceived=this.charLength=0,0===e.length)return t;break}this.charLength+=this.surrogateSize,t=\"\"}this.detectIncompleteChar(e);var i=e.length;this.charLength&&(e.copy(this.charBuffer,0,e.length-this.charReceived,i),i-=this.charReceived),t+=e.toString(this.encoding,0,i);var i=t.length-1,h=t.charCodeAt(i);if(h>=55296&&56319>=h){var c=this.surrogateSize;return this.charLength+=c,this.charReceived+=c,this.charBuffer.copy(this.charBuffer,c,0,c),e.copy(this.charBuffer,0,0,c),t.substring(0,i)}return t},StringDecoder.prototype.detectIncompleteChar=function(e){for(var t=e.length>=3?3:e.length;t>0;t--){var r=e[e.length-t];if(1==t&&r>>5==6){this.charLength=2;break}if(2>=t&&r>>4==14){this.charLength=3;break}if(3>=t&&r>>3==30){this.charLength=4;break}}this.charReceived=t},StringDecoder.prototype.end=function(e){var t=\"\";if(e&&e.length&&(t=this.write(e)),this.charReceived){var r=this.charReceived,h=this.charBuffer,i=this.encoding;t+=h.slice(0,r).toString(i)}return t};\n\n},{\"buffer\":25}],92:[function(require,module,exports){\nvar Buffer=require(\"buffer\").Buffer;module.exports=function(e){if(e instanceof Uint8Array){if(0===e.byteOffset&&e.byteLength===e.buffer.byteLength)return e.buffer;if(\"function\"==typeof e.buffer.slice)return e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength)}if(Buffer.isBuffer(e)){for(var f=new Uint8Array(e.length),r=e.length,t=0;r>t;t++)f[t]=e[t];return f.buffer}throw new Error(\"Argument must be a Buffer\")};\n\n},{\"buffer\":25}],93:[function(require,module,exports){\n\"use strict\";function Url(){this.protocol=null,this.slashes=null,this.auth=null,this.host=null,this.port=null,this.hostname=null,this.hash=null,this.search=null,this.query=null,this.pathname=null,this.path=null,this.href=null}function urlParse(t,s,e){if(t&&util.isObject(t)&&t instanceof Url)return t;var h=new Url;return h.parse(t,s,e),h}function urlFormat(t){return util.isString(t)&&(t=urlParse(t)),t instanceof Url?t.format():Url.prototype.format.call(t)}function urlResolve(t,s){return urlParse(t,!1,!0).resolve(s)}function urlResolveObject(t,s){return t?urlParse(t,!1,!0).resolveObject(s):s}var punycode=require(\"punycode\"),util=require(\"./util\");exports.parse=urlParse,exports.resolve=urlResolve,exports.resolveObject=urlResolveObject,exports.format=urlFormat,exports.Url=Url;var protocolPattern=/^([a-z0-9.+-]+:)/i,portPattern=/:[0-9]*$/,simplePathPattern=/^(\\/\\/?(?!\\/)[^\\?\\s]*)(\\?[^\\s]*)?$/,delims=[\"<\",\">\",'\"',\"`\",\" \",\"\\r\",\"\\n\",\"    \"],unwise=[\"{\",\"}\",\"|\",\"\\\\\",\"^\",\"`\"].concat(delims),autoEscape=[\"'\"].concat(unwise),nonHostChars=[\"%\",\"/\",\"?\",\";\",\"#\"].concat(autoEscape),hostEndingChars=[\"/\",\"?\",\"#\"],hostnameMaxLen=255,hostnamePartPattern=/^[+a-z0-9A-Z_-]{0,63}$/,hostnamePartStart=/^([+a-z0-9A-Z_-]{0,63})(.*)$/,unsafeProtocol={javascript:!0,\"javascript:\":!0},hostlessProtocol={javascript:!0,\"javascript:\":!0},slashedProtocol={http:!0,https:!0,ftp:!0,gopher:!0,file:!0,\"http:\":!0,\"https:\":!0,\"ftp:\":!0,\"gopher:\":!0,\"file:\":!0},querystring=require(\"querystring\");Url.prototype.parse=function(t,s,e){if(!util.isString(t))throw new TypeError(\"Parameter 'url' must be a string, not \"+typeof t);var h=t.indexOf(\"?\"),r=-1!==h&&h<t.indexOf(\"#\")?\"?\":\"#\",a=t.split(r),o=/\\\\/g;a[0]=a[0].replace(o,\"/\"),t=a.join(r);var n=t;if(n=n.trim(),!e&&1===t.split(\"#\").length){var i=simplePathPattern.exec(n);if(i)return this.path=n,this.href=n,this.pathname=i[1],i[2]?(this.search=i[2],s?this.query=querystring.parse(this.search.substr(1)):this.query=this.search.substr(1)):s&&(this.search=\"\",this.query={}),this}var l=protocolPattern.exec(n);if(l){l=l[0];var u=l.toLowerCase();this.protocol=u,n=n.substr(l.length)}if(e||l||n.match(/^\\/\\/[^@\\/]+@[^@\\/]+/)){var p=\"//\"===n.substr(0,2);!p||l&&hostlessProtocol[l]||(n=n.substr(2),this.slashes=!0)}if(!hostlessProtocol[l]&&(p||l&&!slashedProtocol[l])){for(var c=-1,f=0;f<hostEndingChars.length;f++){var m=n.indexOf(hostEndingChars[f]);-1!==m&&(-1===c||c>m)&&(c=m)}var v,g;g=-1===c?n.lastIndexOf(\"@\"):n.lastIndexOf(\"@\",c),-1!==g&&(v=n.slice(0,g),n=n.slice(g+1),this.auth=decodeURIComponent(v)),c=-1;for(var f=0;f<nonHostChars.length;f++){var m=n.indexOf(nonHostChars[f]);-1!==m&&(-1===c||c>m)&&(c=m)}-1===c&&(c=n.length),this.host=n.slice(0,c),n=n.slice(c),this.parseHost(),this.hostname=this.hostname||\"\";var y=\"[\"===this.hostname[0]&&\"]\"===this.hostname[this.hostname.length-1];if(!y)for(var P=this.hostname.split(/\\./),f=0,d=P.length;d>f;f++){var q=P[f];if(q&&!q.match(hostnamePartPattern)){for(var b=\"\",O=0,j=q.length;j>O;O++)b+=q.charCodeAt(O)>127?\"x\":q[O];if(!b.match(hostnamePartPattern)){var x=P.slice(0,f),U=P.slice(f+1),C=q.match(hostnamePartStart);C&&(x.push(C[1]),U.unshift(C[2])),U.length&&(n=\"/\"+U.join(\".\")+n),this.hostname=x.join(\".\");break}}}this.hostname.length>hostnameMaxLen?this.hostname=\"\":this.hostname=this.hostname.toLowerCase(),y||(this.hostname=punycode.toASCII(this.hostname));var A=this.port?\":\"+this.port:\"\",w=this.hostname||\"\";this.host=w+A,this.href+=this.host,y&&(this.hostname=this.hostname.substr(1,this.hostname.length-2),\"/\"!==n[0]&&(n=\"/\"+n))}if(!unsafeProtocol[u])for(var f=0,d=autoEscape.length;d>f;f++){var E=autoEscape[f];if(-1!==n.indexOf(E)){var I=encodeURIComponent(E);I===E&&(I=escape(E)),n=n.split(E).join(I)}}var R=n.indexOf(\"#\");-1!==R&&(this.hash=n.substr(R),n=n.slice(0,R));var S=n.indexOf(\"?\");if(-1!==S?(this.search=n.substr(S),this.query=n.substr(S+1),s&&(this.query=querystring.parse(this.query)),n=n.slice(0,S)):s&&(this.search=\"\",this.query={}),n&&(this.pathname=n),slashedProtocol[u]&&this.hostname&&!this.pathname&&(this.pathname=\"/\"),this.pathname||this.search){var A=this.pathname||\"\",k=this.search||\"\";this.path=A+k}return this.href=this.format(),this},Url.prototype.format=function(){var t=this.auth||\"\";t&&(t=encodeURIComponent(t),t=t.replace(/%3A/i,\":\"),t+=\"@\");var s=this.protocol||\"\",e=this.pathname||\"\",h=this.hash||\"\",r=!1,a=\"\";this.host?r=t+this.host:this.hostname&&(r=t+(-1===this.hostname.indexOf(\":\")?this.hostname:\"[\"+this.hostname+\"]\"),this.port&&(r+=\":\"+this.port)),this.query&&util.isObject(this.query)&&Object.keys(this.query).length&&(a=querystring.stringify(this.query));var o=this.search||a&&\"?\"+a||\"\";return s&&\":\"!==s.substr(-1)&&(s+=\":\"),this.slashes||(!s||slashedProtocol[s])&&r!==!1?(r=\"//\"+(r||\"\"),e&&\"/\"!==e.charAt(0)&&(e=\"/\"+e)):r||(r=\"\"),h&&\"#\"!==h.charAt(0)&&(h=\"#\"+h),o&&\"?\"!==o.charAt(0)&&(o=\"?\"+o),e=e.replace(/[?#]/g,function(t){return encodeURIComponent(t)}),o=o.replace(\"#\",\"%23\"),s+r+e+o+h},Url.prototype.resolve=function(t){return this.resolveObject(urlParse(t,!1,!0)).format()},Url.prototype.resolveObject=function(t){if(util.isString(t)){var s=new Url;s.parse(t,!1,!0),t=s}for(var e=new Url,h=Object.keys(this),r=0;r<h.length;r++){var a=h[r];e[a]=this[a]}if(e.hash=t.hash,\"\"===t.href)return e.href=e.format(),e;if(t.slashes&&!t.protocol){for(var o=Object.keys(t),n=0;n<o.length;n++){var i=o[n];\"protocol\"!==i&&(e[i]=t[i])}return slashedProtocol[e.protocol]&&e.hostname&&!e.pathname&&(e.path=e.pathname=\"/\"),e.href=e.format(),e}if(t.protocol&&t.protocol!==e.protocol){if(!slashedProtocol[t.protocol]){for(var l=Object.keys(t),u=0;u<l.length;u++){var p=l[u];e[p]=t[p]}return e.href=e.format(),e}if(e.protocol=t.protocol,t.host||hostlessProtocol[t.protocol])e.pathname=t.pathname;else{for(var c=(t.pathname||\"\").split(\"/\");c.length&&!(t.host=c.shift()););t.host||(t.host=\"\"),t.hostname||(t.hostname=\"\"),\"\"!==c[0]&&c.unshift(\"\"),c.length<2&&c.unshift(\"\"),e.pathname=c.join(\"/\")}if(e.search=t.search,e.query=t.query,e.host=t.host||\"\",e.auth=t.auth,e.hostname=t.hostname||t.host,e.port=t.port,e.pathname||e.search){var f=e.pathname||\"\",m=e.search||\"\";e.path=f+m}return e.slashes=e.slashes||t.slashes,e.href=e.format(),e}var v=e.pathname&&\"/\"===e.pathname.charAt(0),g=t.host||t.pathname&&\"/\"===t.pathname.charAt(0),y=g||v||e.host&&t.pathname,P=y,d=e.pathname&&e.pathname.split(\"/\")||[],c=t.pathname&&t.pathname.split(\"/\")||[],q=e.protocol&&!slashedProtocol[e.protocol];if(q&&(e.hostname=\"\",e.port=null,e.host&&(\"\"===d[0]?d[0]=e.host:d.unshift(e.host)),e.host=\"\",t.protocol&&(t.hostname=null,t.port=null,t.host&&(\"\"===c[0]?c[0]=t.host:c.unshift(t.host)),t.host=null),y=y&&(\"\"===c[0]||\"\"===d[0])),g)e.host=t.host||\"\"===t.host?t.host:e.host,e.hostname=t.hostname||\"\"===t.hostname?t.hostname:e.hostname,e.search=t.search,e.query=t.query,d=c;else if(c.length)d||(d=[]),d.pop(),d=d.concat(c),e.search=t.search,e.query=t.query;else if(!util.isNullOrUndefined(t.search)){if(q){e.hostname=e.host=d.shift();var b=e.host&&e.host.indexOf(\"@\")>0?e.host.split(\"@\"):!1;b&&(e.auth=b.shift(),e.host=e.hostname=b.shift())}return e.search=t.search,e.query=t.query,util.isNull(e.pathname)&&util.isNull(e.search)||(e.path=(e.pathname?e.pathname:\"\")+(e.search?e.search:\"\")),e.href=e.format(),e}if(!d.length)return e.pathname=null,e.search?e.path=\"/\"+e.search:e.path=null,e.href=e.format(),e;for(var O=d.slice(-1)[0],j=(e.host||t.host||d.length>1)&&(\".\"===O||\"..\"===O)||\"\"===O,x=0,U=d.length;U>=0;U--)O=d[U],\".\"===O?d.splice(U,1):\"..\"===O?(d.splice(U,1),x++):x&&(d.splice(U,1),x--);if(!y&&!P)for(;x--;x)d.unshift(\"..\");!y||\"\"===d[0]||d[0]&&\"/\"===d[0].charAt(0)||d.unshift(\"\"),j&&\"/\"!==d.join(\"/\").substr(-1)&&d.push(\"\");var C=\"\"===d[0]||d[0]&&\"/\"===d[0].charAt(0);if(q){e.hostname=e.host=C?\"\":d.length?d.shift():\"\";var b=e.host&&e.host.indexOf(\"@\")>0?e.host.split(\"@\"):!1;b&&(e.auth=b.shift(),e.host=e.hostname=b.shift())}return y=y||e.host&&d.length,y&&!C&&d.unshift(\"\"),d.length?e.pathname=d.join(\"/\"):(e.pathname=null,e.path=null),util.isNull(e.pathname)&&util.isNull(e.search)||(e.path=(e.pathname?e.pathname:\"\")+(e.search?e.search:\"\")),e.auth=t.auth||e.auth,e.slashes=e.slashes||t.slashes,e.href=e.format(),e},Url.prototype.parseHost=function(){var t=this.host,s=portPattern.exec(t);s&&(s=s[0],\":\"!==s&&(this.port=s.substr(1)),t=t.substr(0,t.length-s.length)),t&&(this.hostname=t)};\n\n},{\"./util\":94,\"punycode\":72,\"querystring\":75}],94:[function(require,module,exports){\n\"use strict\";module.exports={isString:function(n){return\"string\"==typeof n},isObject:function(n){return\"object\"==typeof n&&null!==n},isNull:function(n){return null===n},isNullOrUndefined:function(n){return null==n}};\n\n},{}],95:[function(require,module,exports){\n(function (global){\nfunction deprecate(r,e){function o(){if(!t){if(config(\"throwDeprecation\"))throw new Error(e);config(\"traceDeprecation\")?console.trace(e):console.warn(e),t=!0}return r.apply(this,arguments)}if(config(\"noDeprecation\"))return r;var t=!1;return o}function config(r){try{if(!global.localStorage)return!1}catch(e){return!1}var o=global.localStorage[r];return null==o?!1:\"true\"===String(o).toLowerCase()}module.exports=deprecate;\n\n}).call(this,typeof global !== \"undefined\" ? global : typeof self !== \"undefined\" ? self : typeof window !== \"undefined\" ? window : {})\n\n},{}],96:[function(require,module,exports){\nmodule.exports=function(o){return o&&\"object\"==typeof o&&\"function\"==typeof o.copy&&\"function\"==typeof o.fill&&\"function\"==typeof o.readUInt8};\n\n},{}],97:[function(require,module,exports){\n(function (process,global){\nfunction inspect(e,r){var t={seen:[],stylize:stylizeNoColor};return arguments.length>=3&&(t.depth=arguments[2]),arguments.length>=4&&(t.colors=arguments[3]),isBoolean(r)?t.showHidden=r:r&&exports._extend(t,r),isUndefined(t.showHidden)&&(t.showHidden=!1),isUndefined(t.depth)&&(t.depth=2),isUndefined(t.colors)&&(t.colors=!1),isUndefined(t.customInspect)&&(t.customInspect=!0),t.colors&&(t.stylize=stylizeWithColor),formatValue(t,e,t.depth)}function stylizeWithColor(e,r){var t=inspect.styles[r];return t?\"\u001b[\"+inspect.colors[t][0]+\"m\"+e+\"\u001b[\"+inspect.colors[t][1]+\"m\":e}function stylizeNoColor(e,r){return e}function arrayToHash(e){var r={};return e.forEach(function(e,t){r[e]=!0}),r}function formatValue(e,r,t){if(e.customInspect&&r&&isFunction(r.inspect)&&r.inspect!==exports.inspect&&(!r.constructor||r.constructor.prototype!==r)){var n=r.inspect(t,e);return isString(n)||(n=formatValue(e,n,t)),n}var i=formatPrimitive(e,r);if(i)return i;var o=Object.keys(r),s=arrayToHash(o);if(e.showHidden&&(o=Object.getOwnPropertyNames(r)),isError(r)&&(o.indexOf(\"message\")>=0||o.indexOf(\"description\")>=0))return formatError(r);if(0===o.length){if(isFunction(r)){var u=r.name?\": \"+r.name:\"\";return e.stylize(\"[Function\"+u+\"]\",\"special\")}if(isRegExp(r))return e.stylize(RegExp.prototype.toString.call(r),\"regexp\");if(isDate(r))return e.stylize(Date.prototype.toString.call(r),\"date\");if(isError(r))return formatError(r)}var c=\"\",a=!1,l=[\"{\",\"}\"];if(isArray(r)&&(a=!0,l=[\"[\",\"]\"]),isFunction(r)){var p=r.name?\": \"+r.name:\"\";c=\" [Function\"+p+\"]\"}if(isRegExp(r)&&(c=\" \"+RegExp.prototype.toString.call(r)),isDate(r)&&(c=\" \"+Date.prototype.toUTCString.call(r)),isError(r)&&(c=\" \"+formatError(r)),0===o.length&&(!a||0==r.length))return l[0]+c+l[1];if(0>t)return isRegExp(r)?e.stylize(RegExp.prototype.toString.call(r),\"regexp\"):e.stylize(\"[Object]\",\"special\");e.seen.push(r);var f;return f=a?formatArray(e,r,t,s,o):o.map(function(n){return formatProperty(e,r,t,s,n,a)}),e.seen.pop(),reduceToSingleString(f,c,l)}function formatPrimitive(e,r){if(isUndefined(r))return e.stylize(\"undefined\",\"undefined\");if(isString(r)){var t=\"'\"+JSON.stringify(r).replace(/^\"|\"$/g,\"\").replace(/'/g,\"\\\\'\").replace(/\\\\\"/g,'\"')+\"'\";return e.stylize(t,\"string\")}return isNumber(r)?e.stylize(\"\"+r,\"number\"):isBoolean(r)?e.stylize(\"\"+r,\"boolean\"):isNull(r)?e.stylize(\"null\",\"null\"):void 0}function formatError(e){return\"[\"+Error.prototype.toString.call(e)+\"]\"}function formatArray(e,r,t,n,i){for(var o=[],s=0,u=r.length;u>s;++s)hasOwnProperty(r,String(s))?o.push(formatProperty(e,r,t,n,String(s),!0)):o.push(\"\");return i.forEach(function(i){i.match(/^\\d+$/)||o.push(formatProperty(e,r,t,n,i,!0))}),o}function formatProperty(e,r,t,n,i,o){var s,u,c;if(c=Object.getOwnPropertyDescriptor(r,i)||{value:r[i]},c.get?u=c.set?e.stylize(\"[Getter/Setter]\",\"special\"):e.stylize(\"[Getter]\",\"special\"):c.set&&(u=e.stylize(\"[Setter]\",\"special\")),hasOwnProperty(n,i)||(s=\"[\"+i+\"]\"),u||(e.seen.indexOf(c.value)<0?(u=isNull(t)?formatValue(e,c.value,null):formatValue(e,c.value,t-1),u.indexOf(\"\\n\")>-1&&(u=o?u.split(\"\\n\").map(function(e){return\"  \"+e}).join(\"\\n\").substr(2):\"\\n\"+u.split(\"\\n\").map(function(e){return\"   \"+e}).join(\"\\n\"))):u=e.stylize(\"[Circular]\",\"special\")),isUndefined(s)){if(o&&i.match(/^\\d+$/))return u;s=JSON.stringify(\"\"+i),s.match(/^\"([a-zA-Z_][a-zA-Z_0-9]*)\"$/)?(s=s.substr(1,s.length-2),s=e.stylize(s,\"name\")):(s=s.replace(/'/g,\"\\\\'\").replace(/\\\\\"/g,'\"').replace(/(^\"|\"$)/g,\"'\"),s=e.stylize(s,\"string\"))}return s+\": \"+u}function reduceToSingleString(e,r,t){var n=0,i=e.reduce(function(e,r){return n++,r.indexOf(\"\\n\")>=0&&n++,e+r.replace(/\\u001b\\[\\d\\d?m/g,\"\").length+1},0);return i>60?t[0]+(\"\"===r?\"\":r+\"\\n \")+\" \"+e.join(\",\\n  \")+\" \"+t[1]:t[0]+r+\" \"+e.join(\", \")+\" \"+t[1]}function isArray(e){return Array.isArray(e)}function isBoolean(e){return\"boolean\"==typeof e}function isNull(e){return null===e}function isNullOrUndefined(e){return null==e}function isNumber(e){return\"number\"==typeof e}function isString(e){return\"string\"==typeof e}function isSymbol(e){return\"symbol\"==typeof e}function isUndefined(e){return void 0===e}function isRegExp(e){return isObject(e)&&\"[object RegExp]\"===objectToString(e)}function isObject(e){return\"object\"==typeof e&&null!==e}function isDate(e){return isObject(e)&&\"[object Date]\"===objectToString(e)}function isError(e){return isObject(e)&&(\"[object Error]\"===objectToString(e)||e instanceof Error)}function isFunction(e){return\"function\"==typeof e}function isPrimitive(e){return null===e||\"boolean\"==typeof e||\"number\"==typeof e||\"string\"==typeof e||\"symbol\"==typeof e||\"undefined\"==typeof e}function objectToString(e){return Object.prototype.toString.call(e)}function pad(e){return 10>e?\"0\"+e.toString(10):e.toString(10)}function timestamp(){var e=new Date,r=[pad(e.getHours()),pad(e.getMinutes()),pad(e.getSeconds())].join(\":\");return[e.getDate(),months[e.getMonth()],r].join(\" \")}function hasOwnProperty(e,r){return Object.prototype.hasOwnProperty.call(e,r)}var formatRegExp=/%[sdj%]/g;exports.format=function(e){if(!isString(e)){for(var r=[],t=0;t<arguments.length;t++)r.push(inspect(arguments[t]));return r.join(\" \")}for(var t=1,n=arguments,i=n.length,o=String(e).replace(formatRegExp,function(e){if(\"%%\"===e)return\"%\";if(t>=i)return e;switch(e){case\"%s\":return String(n[t++]);case\"%d\":return Number(n[t++]);case\"%j\":try{return JSON.stringify(n[t++])}catch(r){return\"[Circular]\"}default:return e}}),s=n[t];i>t;s=n[++t])o+=isNull(s)||!isObject(s)?\" \"+s:\" \"+inspect(s);return o},exports.deprecate=function(e,r){function t(){if(!n){if(process.throwDeprecation)throw new Error(r);process.traceDeprecation?console.trace(r):console.error(r),n=!0}return e.apply(this,arguments)}if(isUndefined(global.process))return function(){return exports.deprecate(e,r).apply(this,arguments)};if(process.noDeprecation===!0)return e;var n=!1;return t};var debugs={},debugEnviron;exports.debuglog=function(e){if(isUndefined(debugEnviron)&&(debugEnviron=process.env.NODE_DEBUG||\"\"),e=e.toUpperCase(),!debugs[e])if(new RegExp(\"\\\\b\"+e+\"\\\\b\",\"i\").test(debugEnviron)){var r=process.pid;debugs[e]=function(){var t=exports.format.apply(exports,arguments);console.error(\"%s %d: %s\",e,r,t)}}else debugs[e]=function(){};return debugs[e]},exports.inspect=inspect,inspect.colors={bold:[1,22],italic:[3,23],underline:[4,24],inverse:[7,27],white:[37,39],grey:[90,39],black:[30,39],blue:[34,39],cyan:[36,39],green:[32,39],magenta:[35,39],red:[31,39],yellow:[33,39]},inspect.styles={special:\"cyan\",number:\"yellow\",\"boolean\":\"yellow\",undefined:\"grey\",\"null\":\"bold\",string:\"green\",date:\"magenta\",regexp:\"red\"},exports.isArray=isArray,exports.isBoolean=isBoolean,exports.isNull=isNull,exports.isNullOrUndefined=isNullOrUndefined,exports.isNumber=isNumber,exports.isString=isString,exports.isSymbol=isSymbol,exports.isUndefined=isUndefined,exports.isRegExp=isRegExp,exports.isObject=isObject,exports.isDate=isDate,exports.isError=isError,exports.isFunction=isFunction,exports.isPrimitive=isPrimitive,exports.isBuffer=require(\"./support/isBuffer\");var months=[\"Jan\",\"Feb\",\"Mar\",\"Apr\",\"May\",\"Jun\",\"Jul\",\"Aug\",\"Sep\",\"Oct\",\"Nov\",\"Dec\"];exports.log=function(){console.log(\"%s - %s\",timestamp(),exports.format.apply(exports,arguments))},exports.inherits=require(\"inherits\"),exports._extend=function(e,r){if(!r||!isObject(r))return e;for(var t=Object.keys(r),n=t.length;n--;)e[t[n]]=r[t[n]];return e};\n\n}).call(this,require('_process'),typeof global !== \"undefined\" ? global : typeof self !== \"undefined\" ? self : typeof window !== \"undefined\" ? window : {})\n\n},{\"./support/isBuffer\":96,\"_process\":71,\"inherits\":35}],98:[function(require,module,exports){\nfunction extend(){for(var r={},e=0;e<arguments.length;e++){var t=arguments[e];for(var n in t)hasOwnProperty.call(t,n)&&(r[n]=t[n])}return r}module.exports=extend;var hasOwnProperty=Object.prototype.hasOwnProperty;\n\n},{}]},{},[3])(3)\n});\n</script>\n\n  <script>\n// https://github.com/isaacs/json-stringify-safe v5.0.1\nfunction stringify(obj, replacer, spaces, cycleReplacer) {\n  return JSON.stringify(obj, serializer(replacer, cycleReplacer), spaces)\n}\n\nfunction serializer(replacer, cycleReplacer) {\n  var stack = [], keys = []\n\n  if (cycleReplacer == null) cycleReplacer = function(key, value) {\n    if (stack[0] === value) return \"[Circular ~]\"\n    return \"[Circular ~.\" + keys.slice(0, stack.indexOf(value)).join(\".\") + \"]\"\n  }\n\n  return function(key, value) {\n    if (stack.length > 0) {\n      var thisPos = stack.indexOf(this)\n      ~thisPos ? stack.splice(thisPos + 1) : stack.push(this)\n      ~thisPos ? keys.splice(thisPos, Infinity, key) : keys.push(key)\n      if (~stack.indexOf(value)) value = cycleReplacer.call(this, key, value)\n    }\n    else stack.push(value)\n\n    return replacer == null ? value : replacer.call(this, key, value)\n  }\n}\n</script>\n\n  <script>\n/* Web Font Loader v1.6.24 - (c) Adobe Systems, Google. License: Apache 2.0 */\n(function(){function aa(a,b,d){return a.call.apply(a.bind,arguments)}function ba(a,b,d){if(!a)throw Error();if(2<arguments.length){var c=Array.prototype.slice.call(arguments,2);return function(){var d=Array.prototype.slice.call(arguments);Array.prototype.unshift.apply(d,c);return a.apply(b,d)}}return function(){return a.apply(b,arguments)}}function p(a,b,d){p=Function.prototype.bind&&-1!=Function.prototype.bind.toString().indexOf(\"native code\")?aa:ba;return p.apply(null,arguments)}var q=Date.now||function(){return+new Date};function ca(a,b){this.a=a;this.m=b||a;this.c=this.m.document}var da=!!window.FontFace;function t(a,b,d,c){b=a.c.createElement(b);if(d)for(var e in d)d.hasOwnProperty(e)&&(\"style\"==e?b.style.cssText=d[e]:b.setAttribute(e,d[e]));c&&b.appendChild(a.c.createTextNode(c));return b}function u(a,b,d){a=a.c.getElementsByTagName(b)[0];a||(a=document.documentElement);a.insertBefore(d,a.lastChild)}function v(a){a.parentNode&&a.parentNode.removeChild(a)}\nfunction w(a,b,d){b=b||[];d=d||[];for(var c=a.className.split(/\\s+/),e=0;e<b.length;e+=1){for(var f=!1,g=0;g<c.length;g+=1)if(b[e]===c[g]){f=!0;break}f||c.push(b[e])}b=[];for(e=0;e<c.length;e+=1){f=!1;for(g=0;g<d.length;g+=1)if(c[e]===d[g]){f=!0;break}f||b.push(c[e])}a.className=b.join(\" \").replace(/\\s+/g,\" \").replace(/^\\s+|\\s+$/,\"\")}function y(a,b){for(var d=a.className.split(/\\s+/),c=0,e=d.length;c<e;c++)if(d[c]==b)return!0;return!1}\nfunction z(a){if(\"string\"===typeof a.f)return a.f;var b=a.m.location.protocol;\"about:\"==b&&(b=a.a.location.protocol);return\"https:\"==b?\"https:\":\"http:\"}function ea(a){return a.m.location.hostname||a.a.location.hostname}\nfunction A(a,b,d){function c(){k&&e&&f&&(k(g),k=null)}b=t(a,\"link\",{rel:\"stylesheet\",href:b,media:\"all\"});var e=!1,f=!0,g=null,k=d||null;da?(b.onload=function(){e=!0;c()},b.onerror=function(){e=!0;g=Error(\"Stylesheet failed to load\");c()}):setTimeout(function(){e=!0;c()},0);u(a,\"head\",b)}\nfunction B(a,b,d,c){var e=a.c.getElementsByTagName(\"head\")[0];if(e){var f=t(a,\"script\",{src:b}),g=!1;f.onload=f.onreadystatechange=function(){g||this.readyState&&\"loaded\"!=this.readyState&&\"complete\"!=this.readyState||(g=!0,d&&d(null),f.onload=f.onreadystatechange=null,\"HEAD\"==f.parentNode.tagName&&e.removeChild(f))};e.appendChild(f);setTimeout(function(){g||(g=!0,d&&d(Error(\"Script load timeout\")))},c||5E3);return f}return null};function C(){this.a=0;this.c=null}function D(a){a.a++;return function(){a.a--;E(a)}}function F(a,b){a.c=b;E(a)}function E(a){0==a.a&&a.c&&(a.c(),a.c=null)};function G(a){this.a=a||\"-\"}G.prototype.c=function(a){for(var b=[],d=0;d<arguments.length;d++)b.push(arguments[d].replace(/[\\W_]+/g,\"\").toLowerCase());return b.join(this.a)};function H(a,b){this.c=a;this.f=4;this.a=\"n\";var d=(b||\"n4\").match(/^([nio])([1-9])$/i);d&&(this.a=d[1],this.f=parseInt(d[2],10))}function fa(a){return I(a)+\" \"+(a.f+\"00\")+\" 300px \"+J(a.c)}function J(a){var b=[];a=a.split(/,\\s*/);for(var d=0;d<a.length;d++){var c=a[d].replace(/['\"]/g,\"\");-1!=c.indexOf(\" \")||/^\\d/.test(c)?b.push(\"'\"+c+\"'\"):b.push(c)}return b.join(\",\")}function K(a){return a.a+a.f}function I(a){var b=\"normal\";\"o\"===a.a?b=\"oblique\":\"i\"===a.a&&(b=\"italic\");return b}\nfunction ga(a){var b=4,d=\"n\",c=null;a&&((c=a.match(/(normal|oblique|italic)/i))&&c[1]&&(d=c[1].substr(0,1).toLowerCase()),(c=a.match(/([1-9]00|normal|bold)/i))&&c[1]&&(/bold/i.test(c[1])?b=7:/[1-9]00/.test(c[1])&&(b=parseInt(c[1].substr(0,1),10))));return d+b};function ha(a,b){this.c=a;this.f=a.m.document.documentElement;this.h=b;this.a=new G(\"-\");this.j=!1!==b.events;this.g=!1!==b.classes}function ia(a){a.g&&w(a.f,[a.a.c(\"wf\",\"loading\")]);L(a,\"loading\")}function M(a){if(a.g){var b=y(a.f,a.a.c(\"wf\",\"active\")),d=[],c=[a.a.c(\"wf\",\"loading\")];b||d.push(a.a.c(\"wf\",\"inactive\"));w(a.f,d,c)}L(a,\"inactive\")}function L(a,b,d){if(a.j&&a.h[b])if(d)a.h[b](d.c,K(d));else a.h[b]()};function ja(){this.c={}}function ka(a,b,d){var c=[],e;for(e in b)if(b.hasOwnProperty(e)){var f=a.c[e];f&&c.push(f(b[e],d))}return c};function N(a,b){this.c=a;this.f=b;this.a=t(this.c,\"span\",{\"aria-hidden\":\"true\"},this.f)}function O(a){u(a.c,\"body\",a.a)}function P(a){return\"display:block;position:absolute;top:-9999px;left:-9999px;font-size:300px;width:auto;height:auto;line-height:normal;margin:0;padding:0;font-variant:normal;white-space:nowrap;font-family:\"+J(a.c)+\";\"+(\"font-style:\"+I(a)+\";font-weight:\"+(a.f+\"00\")+\";\")};function Q(a,b,d,c,e,f){this.g=a;this.j=b;this.a=c;this.c=d;this.f=e||3E3;this.h=f||void 0}Q.prototype.start=function(){var a=this.c.m.document,b=this,d=q(),c=new Promise(function(c,e){function k(){q()-d>=b.f?e():a.fonts.load(fa(b.a),b.h).then(function(a){1<=a.length?c():setTimeout(k,25)},function(){e()})}k()}),e=new Promise(function(a,c){setTimeout(c,b.f)});Promise.race([e,c]).then(function(){b.g(b.a)},function(){b.j(b.a)})};function R(a,b,d,c,e,f,g){this.v=a;this.B=b;this.c=d;this.a=c;this.s=g||\"BESbswy\";this.f={};this.w=e||3E3;this.u=f||null;this.o=this.j=this.h=this.g=null;this.g=new N(this.c,this.s);this.h=new N(this.c,this.s);this.j=new N(this.c,this.s);this.o=new N(this.c,this.s);a=new H(this.a.c+\",serif\",K(this.a));a=P(a);this.g.a.style.cssText=a;a=new H(this.a.c+\",sans-serif\",K(this.a));a=P(a);this.h.a.style.cssText=a;a=new H(\"serif\",K(this.a));a=P(a);this.j.a.style.cssText=a;a=new H(\"sans-serif\",K(this.a));a=\nP(a);this.o.a.style.cssText=a;O(this.g);O(this.h);O(this.j);O(this.o)}var S={D:\"serif\",C:\"sans-serif\"},T=null;function U(){if(null===T){var a=/AppleWebKit\\/([0-9]+)(?:\\.([0-9]+))/.exec(window.navigator.userAgent);T=!!a&&(536>parseInt(a[1],10)||536===parseInt(a[1],10)&&11>=parseInt(a[2],10))}return T}R.prototype.start=function(){this.f.serif=this.j.a.offsetWidth;this.f[\"sans-serif\"]=this.o.a.offsetWidth;this.A=q();la(this)};\nfunction ma(a,b,d){for(var c in S)if(S.hasOwnProperty(c)&&b===a.f[S[c]]&&d===a.f[S[c]])return!0;return!1}function la(a){var b=a.g.a.offsetWidth,d=a.h.a.offsetWidth,c;(c=b===a.f.serif&&d===a.f[\"sans-serif\"])||(c=U()&&ma(a,b,d));c?q()-a.A>=a.w?U()&&ma(a,b,d)&&(null===a.u||a.u.hasOwnProperty(a.a.c))?V(a,a.v):V(a,a.B):na(a):V(a,a.v)}function na(a){setTimeout(p(function(){la(this)},a),50)}function V(a,b){setTimeout(p(function(){v(this.g.a);v(this.h.a);v(this.j.a);v(this.o.a);b(this.a)},a),0)};function W(a,b,d){this.c=a;this.a=b;this.f=0;this.o=this.j=!1;this.s=d}var X=null;W.prototype.g=function(a){var b=this.a;b.g&&w(b.f,[b.a.c(\"wf\",a.c,K(a).toString(),\"active\")],[b.a.c(\"wf\",a.c,K(a).toString(),\"loading\"),b.a.c(\"wf\",a.c,K(a).toString(),\"inactive\")]);L(b,\"fontactive\",a);this.o=!0;oa(this)};\nW.prototype.h=function(a){var b=this.a;if(b.g){var d=y(b.f,b.a.c(\"wf\",a.c,K(a).toString(),\"active\")),c=[],e=[b.a.c(\"wf\",a.c,K(a).toString(),\"loading\")];d||c.push(b.a.c(\"wf\",a.c,K(a).toString(),\"inactive\"));w(b.f,c,e)}L(b,\"fontinactive\",a);oa(this)};function oa(a){0==--a.f&&a.j&&(a.o?(a=a.a,a.g&&w(a.f,[a.a.c(\"wf\",\"active\")],[a.a.c(\"wf\",\"loading\"),a.a.c(\"wf\",\"inactive\")]),L(a,\"active\")):M(a.a))};function pa(a){this.j=a;this.a=new ja;this.h=0;this.f=this.g=!0}pa.prototype.load=function(a){this.c=new ca(this.j,a.context||this.j);this.g=!1!==a.events;this.f=!1!==a.classes;qa(this,new ha(this.c,a),a)};\nfunction ra(a,b,d,c,e){var f=0==--a.h;(a.f||a.g)&&setTimeout(function(){var a=e||null,k=c||null||{};if(0===d.length&&f)M(b.a);else{b.f+=d.length;f&&(b.j=f);var h,m=[];for(h=0;h<d.length;h++){var l=d[h],n=k[l.c],r=b.a,x=l;r.g&&w(r.f,[r.a.c(\"wf\",x.c,K(x).toString(),\"loading\")]);L(r,\"fontloading\",x);r=null;null===X&&(X=window.FontFace?(x=/Gecko.*Firefox\\/(\\d+)/.exec(window.navigator.userAgent))?42<parseInt(x[1],10):!0:!1);X?r=new Q(p(b.g,b),p(b.h,b),b.c,l,b.s,n):r=new R(p(b.g,b),p(b.h,b),b.c,l,b.s,a,\nn);m.push(r)}for(h=0;h<m.length;h++)m[h].start()}},0)}function qa(a,b,d){var c=[],e=d.timeout;ia(b);var c=ka(a.a,d,a.c),f=new W(a.c,b,e);a.h=c.length;b=0;for(d=c.length;b<d;b++)c[b].load(function(b,c,d){ra(a,f,b,c,d)})};function sa(a,b){this.c=a;this.a=b}function ta(a,b,d){var c=z(a.c);a=(a.a.api||\"fast.fonts.net/jsapi\").replace(/^.*http(s?):(\\/\\/)?/,\"\");return c+\"//\"+a+\"/\"+b+\".js\"+(d?\"?v=\"+d:\"\")}\nsa.prototype.load=function(a){function b(){if(e[\"__mti_fntLst\"+d]){var c=e[\"__mti_fntLst\"+d](),g=[],k;if(c)for(var h=0;h<c.length;h++){var m=c[h].fontfamily;void 0!=c[h].fontStyle&&void 0!=c[h].fontWeight?(k=c[h].fontStyle+c[h].fontWeight,g.push(new H(m,k))):g.push(new H(m))}a(g)}else setTimeout(function(){b()},50)}var d=this.a.projectId,c=this.a.version;if(d){var e=this.c.m;B(this.c,ta(this,d,c),function(c){c?a([]):b()}).id=\"__MonotypeAPIScript__\"+d}else a([])};function ua(a,b){this.c=a;this.a=b}ua.prototype.load=function(a){var b,d,c=this.a.urls||[],e=this.a.families||[],f=this.a.testStrings||{},g=new C;b=0;for(d=c.length;b<d;b++)A(this.c,c[b],D(g));var k=[];b=0;for(d=e.length;b<d;b++)if(c=e[b].split(\":\"),c[1])for(var h=c[1].split(\",\"),m=0;m<h.length;m+=1)k.push(new H(c[0],h[m]));else k.push(new H(c[0]));F(g,function(){a(k,f)})};function va(a,b,d){a?this.c=a:this.c=b+wa;this.a=[];this.f=[];this.g=d||\"\"}var wa=\"//fonts.googleapis.com/css\";function xa(a,b){for(var d=b.length,c=0;c<d;c++){var e=b[c].split(\":\");3==e.length&&a.f.push(e.pop());var f=\"\";2==e.length&&\"\"!=e[1]&&(f=\":\");a.a.push(e.join(f))}}\nfunction ya(a){if(0==a.a.length)throw Error(\"No fonts to load!\");if(-1!=a.c.indexOf(\"kit=\"))return a.c;for(var b=a.a.length,d=[],c=0;c<b;c++)d.push(a.a[c].replace(/ /g,\"+\"));b=a.c+\"?family=\"+d.join(\"%7C\");0<a.f.length&&(b+=\"&subset=\"+a.f.join(\",\"));0<a.g.length&&(b+=\"&text=\"+encodeURIComponent(a.g));return b};function za(a){this.f=a;this.a=[];this.c={}}\nvar Aa={latin:\"BESbswy\",cyrillic:\"\\u0439\\u044f\\u0416\",greek:\"\\u03b1\\u03b2\\u03a3\",khmer:\"\\u1780\\u1781\\u1782\",Hanuman:\"\\u1780\\u1781\\u1782\"},Ba={thin:\"1\",extralight:\"2\",\"extra-light\":\"2\",ultralight:\"2\",\"ultra-light\":\"2\",light:\"3\",regular:\"4\",book:\"4\",medium:\"5\",\"semi-bold\":\"6\",semibold:\"6\",\"demi-bold\":\"6\",demibold:\"6\",bold:\"7\",\"extra-bold\":\"8\",extrabold:\"8\",\"ultra-bold\":\"8\",ultrabold:\"8\",black:\"9\",heavy:\"9\",l:\"3\",r:\"4\",b:\"7\"},Ca={i:\"i\",italic:\"i\",n:\"n\",normal:\"n\"},Da=/^(thin|(?:(?:extra|ultra)-?)?light|regular|book|medium|(?:(?:semi|demi|extra|ultra)-?)?bold|black|heavy|l|r|b|[1-9]00)?(n|i|normal|italic)?$/;\nfunction Ea(a){for(var b=a.f.length,d=0;d<b;d++){var c=a.f[d].split(\":\"),e=c[0].replace(/\\+/g,\" \"),f=[\"n4\"];if(2<=c.length){var g;var k=c[1];g=[];if(k)for(var k=k.split(\",\"),h=k.length,m=0;m<h;m++){var l;l=k[m];if(l.match(/^[\\w-]+$/)){var n=Da.exec(l.toLowerCase());if(null==n)l=\"\";else{l=n[2];l=null==l||\"\"==l?\"n\":Ca[l];n=n[1];if(null==n||\"\"==n)n=\"4\";else var r=Ba[n],n=r?r:isNaN(n)?\"4\":n.substr(0,1);l=[l,n].join(\"\")}}else l=\"\";l&&g.push(l)}0<g.length&&(f=g);3==c.length&&(c=c[2],g=[],c=c?c.split(\",\"):\ng,0<c.length&&(c=Aa[c[0]])&&(a.c[e]=c))}a.c[e]||(c=Aa[e])&&(a.c[e]=c);for(c=0;c<f.length;c+=1)a.a.push(new H(e,f[c]))}};function Fa(a,b){this.c=a;this.a=b}var Ga={Arimo:!0,Cousine:!0,Tinos:!0};Fa.prototype.load=function(a){var b=new C,d=this.c,c=new va(this.a.api,z(d),this.a.text),e=this.a.families;xa(c,e);var f=new za(e);Ea(f);A(d,ya(c),D(b));F(b,function(){a(f.a,f.c,Ga)})};function Ha(a,b){this.c=a;this.a=b}Ha.prototype.load=function(a){var b=this.a.id,d=this.c.m;b?B(this.c,(this.a.api||\"https://use.typekit.net\")+\"/\"+b+\".js\",function(b){if(b)a([]);else if(d.Typekit&&d.Typekit.config&&d.Typekit.config.fn){b=d.Typekit.config.fn;for(var e=[],f=0;f<b.length;f+=2)for(var g=b[f],k=b[f+1],h=0;h<k.length;h++)e.push(new H(g,k[h]));try{d.Typekit.load({events:!1,classes:!1,async:!0})}catch(m){}a(e)}},2E3):a([])};function Ia(a,b){this.c=a;this.f=b;this.a=[]}Ia.prototype.load=function(a){var b=this.f.id,d=this.c.m,c=this;b?(d.__webfontfontdeckmodule__||(d.__webfontfontdeckmodule__={}),d.__webfontfontdeckmodule__[b]=function(b,d){for(var g=0,k=d.fonts.length;g<k;++g){var h=d.fonts[g];c.a.push(new H(h.name,ga(\"font-weight:\"+h.weight+\";font-style:\"+h.style)))}a(c.a)},B(this.c,z(this.c)+(this.f.api||\"//f.fontdeck.com/s/css/js/\")+ea(this.c)+\"/\"+b+\".js\",function(b){b&&a([])})):a([])};var Y=new pa(window);Y.a.c.custom=function(a,b){return new ua(b,a)};Y.a.c.fontdeck=function(a,b){return new Ia(b,a)};Y.a.c.monotype=function(a,b){return new sa(b,a)};Y.a.c.typekit=function(a,b){return new Ha(b,a)};Y.a.c.google=function(a,b){return new Fa(b,a)};var Z={load:p(Y.load,Y)};\"function\"===typeof define&&define.amd?define(function(){return Z}):\"undefined\"!==typeof module&&module.exports?module.exports=Z:(window.WebFont=Z,window.WebFontConfig&&Y.load(window.WebFontConfig));}());\n</script>\n\n  <script>\n    var schemaWrapper = { \"components\": { \"schemas\" : defs}};\n    defsParser = new $RefParser();\n    defsParser.dereference(schemaWrapper).catch(function(err) {\n      console.log(err);\n    });\n  </script>\n  <script>\n  $(document).ready(function () {\n    $('.nav-tabs-examples').find('a:first').tab('show');\n    $(this).scrollspy({ target: '#scrollingNav', offset: 18 });\n  });\n  </script>\n</body>\n</html>\n"
  },
  {
    "path": "server/swagger/swagger.yml",
    "content": "basePath: /api/v2\nconsumes:\n    - application/json\ndefinitions:\n    Block:\n        description: Block is the basic data unit\n        x-go-package: github.com/mattermost/focalboard/server/model\n    BlockPatch:\n        description: BlockPatch is a patch for modify blocks\n        x-go-package: github.com/mattermost/focalboard/server/model\n    BlockPatchBatch:\n        description: BlockPatchBatch is a batch of IDs and patches for modify blocks\n        x-go-package: github.com/mattermost/focalboard/server/model\n    Board:\n        description: Board groups a set of blocks and its layout\n        x-go-package: github.com/mattermost/focalboard/server/model\n    BoardInsight:\n        description: BoardInsight gives insight into activities in a Board\n        x-go-package: github.com/mattermost/focalboard/server/model\n    BoardMember:\n        description: BoardMember stores the information of the membership of a user on a board\n        x-go-package: github.com/mattermost/focalboard/server/model\n    BoardMemberHistoryEntry:\n        description: BoardMemberHistoryEntry stores the information of the membership of a user on a board\n        x-go-package: github.com/mattermost/focalboard/server/model\n    BoardMetadata:\n        description: BoardMetadata contains metadata for a Board\n        x-go-package: github.com/mattermost/focalboard/server/model\n    BoardPatch:\n        description: BoardPatch is a patch for modify boards\n        x-go-package: github.com/mattermost/focalboard/server/model\n    BoardsAndBlocks:\n        description: |-\n            BoardsAndBlocks is used to operate over boards and blocks at the\n            same time\n        x-go-package: github.com/mattermost/focalboard/server/model\n    BoardsCloudLimits:\n        description: |-\n            BoardsCloudLimits is the representation of the limits for the\n            Boards server\n        x-go-package: github.com/mattermost/focalboard/server/model\n    BoardsStatistics:\n        description: BoardsStatistics is the representation of the statistics for the Boards server\n        x-go-package: github.com/mattermost/focalboard/server/model\n    Card:\n        title: Card represents a group of content blocks and properties.\n        x-go-package: github.com/mattermost/focalboard/server/model\n    CardPatch:\n        description: CardPatch is a patch for modifying cards\n        x-go-package: github.com/mattermost/focalboard/server/model\n    Category:\n        description: Category is a board category\n        x-go-package: github.com/mattermost/focalboard/server/model\n    CategoryBoards:\n        description: CategoryBoards is a board category and associated boards\n        x-go-package: github.com/mattermost/focalboard/server/model\n    ChangePasswordRequest:\n        description: ChangePasswordRequest is a user password change request\n        x-go-package: github.com/mattermost/focalboard/server/model\n    ClientConfig:\n        description: ClientConfig is the client configuration\n        x-go-package: github.com/mattermost/focalboard/server/model\n    DeleteBoardsAndBlocks:\n        description: |-\n            DeleteBoardsAndBlocks is used to list the boards and blocks to\n            delete on a request\n        x-go-package: github.com/mattermost/focalboard/server/model\n    ErrorResponse:\n        description: ErrorResponse is an error response\n        x-go-package: github.com/mattermost/focalboard/server/model\n    FileUploadResponse:\n        description: FileUploadResponse is the response to a file upload\n        x-go-package: github.com/mattermost/focalboard/server/api\n    LoginRequest:\n        description: LoginRequest is a login request\n        x-go-package: github.com/mattermost/focalboard/server/model\n    LoginResponse:\n        description: LoginResponse is a login response\n        x-go-package: github.com/mattermost/focalboard/server/model\n    NotificationHint:\n        description: |-\n            NotificationHint provides a hint that a block has been modified and has subscribers that\n            should be notified.\n        x-go-package: github.com/mattermost/focalboard/server/model\n    PatchBoardsAndBlocks:\n        description: |-\n            PatchBoardsAndBlocks is used to patch multiple boards and blocks on\n            a single request\n        x-go-package: github.com/mattermost/focalboard/server/model\n    RegisterRequest:\n        description: RegisterRequest is a user registration request\n        x-go-package: github.com/mattermost/focalboard/server/model\n    Sharing:\n        description: Sharing is sharing information for a root block\n        x-go-package: github.com/mattermost/focalboard/server/model\n    Subscriber:\n        description: Subscriber is an entity (e.g. user, channel) that can subscribe to events from boards, cards, etc\n        x-go-package: github.com/mattermost/focalboard/server/model\n    Subscription:\n        title: Subscription is a subscription to a board, card, etc, for a user or channel.\n        x-go-package: github.com/mattermost/focalboard/server/model\n    Team:\n        description: Team is information global to a team\n        x-go-package: github.com/mattermost/focalboard/server/model\n    User:\n        description: User is a user\n        x-go-package: github.com/mattermost/focalboard/server/model\n    UserPreferencesPatch:\n        description: UserPreferencesPatch is a user property patch\n        x-go-package: github.com/mattermost/focalboard/server/model\nhost: localhost\ninfo:\n    contact:\n        email: api@focalboard.com\n        name: Focalboard\n        url: https://www.focalboard.com\n    description: Focalboard Server\n    license:\n        name: Custom\n        url: https://github.com/mattermost/focalboard/blob/main/LICENSE.txt\n    title: Focalboard Server\n    version: 2.0.0\npaths:\n    /api/v2/teams/{teamID}/notifyadminupgrade:\n        get:\n            operationId: handleNotifyAdminUpgrade\n            parameters:\n                - description: Team ID\n                  in: path\n                  name: teamID\n                  required: true\n                  type: string\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n            summary: Notifies admins for upgrade request.\n    /boards:\n        post:\n            description: Creates a new board\n            operationId: createBoard\n            parameters:\n                - description: the board to create\n                  in: body\n                  name: Body\n                  required: true\n                  schema:\n                    $ref: '#/definitions/Board'\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                    schema:\n                        $ref: '#/definitions/Board'\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n    /boards-and-blocks:\n        delete:\n            description: Deletes boards and blocks\n            operationId: deleteBoardsAndBlocks\n            parameters:\n                - description: the boards and blocks to delete\n                  in: body\n                  name: Body\n                  required: true\n                  schema:\n                    $ref: '#/definitions/DeleteBoardsAndBlocks'\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n        patch:\n            description: Patches a set of related boards and blocks\n            operationId: patchBoardsAndBlocks\n            parameters:\n                - description: the patches for the boards and blocks\n                  in: body\n                  name: Body\n                  required: true\n                  schema:\n                    $ref: '#/definitions/PatchBoardsAndBlocks'\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                    schema:\n                        $ref: '#/definitions/BoardsAndBlocks'\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n        post:\n            description: Creates new boards and blocks\n            operationId: insertBoardsAndBlocks\n            parameters:\n                - description: the boards and blocks to create\n                  in: body\n                  name: Body\n                  required: true\n                  schema:\n                    $ref: '#/definitions/BoardsAndBlocks'\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                    schema:\n                        $ref: '#/definitions/BoardsAndBlocks'\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n    /boards/{boardID}:\n        delete:\n            description: Removes a board\n            operationId: deleteBoard\n            parameters:\n                - description: Board ID\n                  in: path\n                  name: boardID\n                  required: true\n                  type: string\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                \"404\":\n                    description: board not found\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n        get:\n            description: Returns a board\n            operationId: getBoard\n            parameters:\n                - description: Board ID\n                  in: path\n                  name: boardID\n                  required: true\n                  type: string\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                    schema:\n                        $ref: '#/definitions/Board'\n                \"404\":\n                    description: board not found\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n        patch:\n            description: Partially updates a board\n            operationId: patchBoard\n            parameters:\n                - description: Board ID\n                  in: path\n                  name: boardID\n                  required: true\n                  type: string\n                - description: board patch to apply\n                  in: body\n                  name: Body\n                  required: true\n                  schema:\n                    $ref: '#/definitions/BoardPatch'\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                    schema:\n                        $ref: '#/definitions/Board'\n                \"404\":\n                    description: board not found\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n    /boards/{boardID}/archive/export:\n        get:\n            operationId: archiveExportBoard\n            parameters:\n                - description: Id of board to export\n                  in: path\n                  name: boardID\n                  required: true\n                  type: string\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n            summary: Exports an archive of all blocks for one boards.\n    /boards/{boardID}/blocks:\n        get:\n            description: Returns blocks\n            operationId: getBlocks\n            parameters:\n                - description: Board ID\n                  in: path\n                  name: boardID\n                  required: true\n                  type: string\n                - description: ID of parent block, omit to specify all blocks\n                  in: query\n                  name: parent_id\n                  type: string\n                - description: Type of blocks to return, omit to specify all types\n                  in: query\n                  name: type\n                  type: string\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                    schema:\n                        items:\n                            $ref: '#/definitions/Block'\n                        type: array\n                \"404\":\n                    description: board not found\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n        post:\n            description: |-\n                Insert blocks. The specified IDs will only be used to link\n                blocks with existing ones, the rest will be replaced by server\n                generated IDs\n            operationId: updateBlocks\n            parameters:\n                - description: Board ID\n                  in: path\n                  name: boardID\n                  required: true\n                  type: string\n                - description: Disables notifications (for bulk inserting)\n                  in: query\n                  name: disable_notify\n                  type: bool\n                - description: array of blocks to insert or update\n                  in: body\n                  name: Body\n                  required: true\n                  schema:\n                    items:\n                        $ref: '#/definitions/Block'\n                    type: array\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                    schema:\n                        items:\n                            $ref: '#/definitions/Block'\n                        type: array\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n    /boards/{boardID}/blocks/:\n        patch:\n            description: Partially updates batch of blocks\n            operationId: patchBlocks\n            parameters:\n                - description: Workspace ID\n                  in: path\n                  name: boardID\n                  required: true\n                  type: string\n                - description: Disables notifications (for bulk patching)\n                  in: query\n                  name: disable_notify\n                  type: bool\n                - description: block Ids and block patches to apply\n                  in: body\n                  name: Body\n                  required: true\n                  schema:\n                    $ref: '#/definitions/BlockPatchBatch'\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n    /boards/{boardID}/blocks/{blockID}:\n        delete:\n            description: Deletes a block\n            operationId: deleteBlock\n            parameters:\n                - description: Board ID\n                  in: path\n                  name: boardID\n                  required: true\n                  type: string\n                - description: ID of block to delete\n                  in: path\n                  name: blockID\n                  required: true\n                  type: string\n                - description: Disables notifications (for bulk deletion)\n                  in: query\n                  name: disable_notify\n                  type: bool\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                \"404\":\n                    description: block not found\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n        patch:\n            description: Partially updates a block\n            operationId: patchBlock\n            parameters:\n                - description: Board ID\n                  in: path\n                  name: boardID\n                  required: true\n                  type: string\n                - description: ID of block to patch\n                  in: path\n                  name: blockID\n                  required: true\n                  type: string\n                - description: Disables notifications (for bulk patching)\n                  in: query\n                  name: disable_notify\n                  type: bool\n                - description: block patch to apply\n                  in: body\n                  name: Body\n                  required: true\n                  schema:\n                    $ref: '#/definitions/BlockPatch'\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                \"404\":\n                    description: block not found\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n    /boards/{boardID}/blocks/{blockID}/duplicate:\n        post:\n            description: Returns the new created blocks\n            operationId: duplicateBlock\n            parameters:\n                - description: Board ID\n                  in: path\n                  name: boardID\n                  required: true\n                  type: string\n                - description: Block ID\n                  in: path\n                  name: blockID\n                  required: true\n                  type: string\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                    schema:\n                        items:\n                            $ref: '#/definitions/Block'\n                        type: array\n                \"404\":\n                    description: board or block not found\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n    /boards/{boardID}/blocks/{blockID}/undelete:\n        post:\n            description: Undeletes a block\n            operationId: undeleteBlock\n            parameters:\n                - description: Board ID\n                  in: path\n                  name: boardID\n                  required: true\n                  type: string\n                - description: ID of block to undelete\n                  in: path\n                  name: blockID\n                  required: true\n                  type: string\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                    schema:\n                        $ref: '#/definitions/BlockPatch'\n                \"404\":\n                    description: block not found\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n    /boards/{boardID}/cards:\n        get:\n            operationId: getCards\n            parameters:\n                - description: Board ID\n                  in: path\n                  name: boardID\n                  required: true\n                  type: string\n                - description: The page to select (default=0)\n                  in: query\n                  name: page\n                  type: integer\n                - description: Number of cards to return per page(default=100)\n                  in: query\n                  name: per_page\n                  type: integer\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                    schema:\n                        items:\n                            $ref: '#/definitions/Card'\n                        type: array\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n            summary: Fetches cards for the specified board.\n        post:\n            operationId: createCard\n            parameters:\n                - description: Board ID\n                  in: path\n                  name: boardID\n                  required: true\n                  type: string\n                - description: the card to create\n                  in: body\n                  name: Body\n                  required: true\n                  schema:\n                    $ref: '#/definitions/Card'\n                - description: Disables notifications (for bulk data inserting)\n                  in: query\n                  name: disable_notify\n                  type: bool\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                    schema:\n                        $ref: '#/definitions/Card'\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n            summary: Creates a new card for the specified board.\n    /boards/{boardID}/duplicate:\n        post:\n            description: Returns the new created board and all the blocks\n            operationId: duplicateBoard\n            parameters:\n                - description: Board ID\n                  in: path\n                  name: boardID\n                  required: true\n                  type: string\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                    schema:\n                        $ref: '#/definitions/BoardsAndBlocks'\n                \"404\":\n                    description: board not found\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n    /boards/{boardID}/join:\n        post:\n            description: Become a member of a board\n            operationId: joinBoard\n            parameters:\n                - description: Board ID\n                  in: path\n                  name: boardID\n                  required: true\n                  type: string\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                    schema:\n                        $ref: '#/definitions/BoardMember'\n                \"403\":\n                    description: access denied\n                \"404\":\n                    description: board not found\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n    /boards/{boardID}/leave:\n        post:\n            description: Remove your own membership from a board\n            operationId: leaveBoard\n            parameters:\n                - description: Board ID\n                  in: path\n                  name: boardID\n                  required: true\n                  type: string\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                \"403\":\n                    description: access denied\n                \"404\":\n                    description: board not found\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n    /boards/{boardID}/members:\n        get:\n            description: Returns the members of the board\n            operationId: getMembersForBoard\n            parameters:\n                - description: Board ID\n                  in: path\n                  name: boardID\n                  required: true\n                  type: string\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                    schema:\n                        items:\n                            $ref: '#/definitions/BoardMember'\n                        type: array\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n        post:\n            description: Adds a new member to a board\n            operationId: addMember\n            parameters:\n                - description: Board ID\n                  in: path\n                  name: boardID\n                  required: true\n                  type: string\n                - description: membership to replace the current one with\n                  in: body\n                  name: Body\n                  required: true\n                  schema:\n                    $ref: '#/definitions/BoardMember'\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                    schema:\n                        $ref: '#/definitions/BoardMember'\n                \"404\":\n                    description: board not found\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n    /boards/{boardID}/members/{userID}:\n        delete:\n            description: Deletes a member from a board\n            operationId: deleteMember\n            parameters:\n                - description: Board ID\n                  in: path\n                  name: boardID\n                  required: true\n                  type: string\n                - description: User ID\n                  in: path\n                  name: userID\n                  required: true\n                  type: string\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                \"404\":\n                    description: board not found\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n        put:\n            description: Updates a board member\n            operationId: updateMember\n            parameters:\n                - description: Board ID\n                  in: path\n                  name: boardID\n                  required: true\n                  type: string\n                - description: User ID\n                  in: path\n                  name: userID\n                  required: true\n                  type: string\n                - description: membership to replace the current one with\n                  in: body\n                  name: Body\n                  required: true\n                  schema:\n                    $ref: '#/definitions/BoardMember'\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                    schema:\n                        $ref: '#/definitions/BoardMember'\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n    /boards/{boardID}/metadata:\n        get:\n            description: Returns a board's metadata\n            operationId: getBoardMetadata\n            parameters:\n                - description: Board ID\n                  in: path\n                  name: boardID\n                  required: true\n                  type: string\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                    schema:\n                        $ref: '#/definitions/BoardMetadata'\n                \"404\":\n                    description: board not found\n                \"501\":\n                    description: required license not found\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n    /boards/{boardID}/sharing:\n        get:\n            description: Returns sharing information for a board\n            operationId: getSharing\n            parameters:\n                - description: Board ID\n                  in: path\n                  name: boardID\n                  required: true\n                  type: string\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                    schema:\n                        $ref: '#/definitions/Sharing'\n                \"404\":\n                    description: board not found\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n        post:\n            description: Sets sharing information for a board\n            operationId: postSharing\n            parameters:\n                - description: Board ID\n                  in: path\n                  name: boardID\n                  required: true\n                  type: string\n                - description: sharing information for a root block\n                  in: body\n                  name: Body\n                  required: true\n                  schema:\n                    $ref: '#/definitions/Sharing'\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n    /boards/{boardID}/undelete:\n        post:\n            description: Undeletes a board\n            operationId: undeleteBoard\n            parameters:\n                - description: ID of board to undelete\n                  in: path\n                  name: boardID\n                  required: true\n                  type: string\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n    /boards/search:\n        get:\n            description: Returns the boards that match with a search term\n            operationId: searchAllBoards\n            parameters:\n                - description: The search term. Must have at least one character\n                  in: query\n                  name: q\n                  required: true\n                  type: string\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                    schema:\n                        items:\n                            $ref: '#/definitions/Board'\n                        type: array\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n    /cards/{cardID}:\n        get:\n            operationId: getCard\n            parameters:\n                - description: Card ID\n                  in: path\n                  name: cardID\n                  required: true\n                  type: string\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                    schema:\n                        $ref: '#/definitions/Card'\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n            summary: Fetches the specified card.\n    /cards/{cardID}/cards:\n        patch:\n            operationId: patchCard\n            parameters:\n                - description: Card ID\n                  in: path\n                  name: cardID\n                  required: true\n                  type: string\n                - description: the card patch\n                  in: body\n                  name: Body\n                  required: true\n                  schema:\n                    $ref: '#/definitions/CardPatch'\n                - description: Disables notifications (for bulk data patching)\n                  in: query\n                  name: disable_notify\n                  type: bool\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                    schema:\n                        $ref: '#/definitions/Card'\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n            summary: Patches the specified card.\n    /clientConfig:\n        get:\n            description: Returns the client configuration\n            operationId: getClientConfig\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                    schema:\n                        $ref: '#/definitions/ClientConfig'\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n    /files/teams/{teamID}/{boardID}/{filename}:\n        get:\n            description: Returns the contents of an uploaded file\n            operationId: getFile\n            parameters:\n                - description: Team ID\n                  in: path\n                  name: teamID\n                  required: true\n                  type: string\n                - description: Board ID\n                  in: path\n                  name: boardID\n                  required: true\n                  type: string\n                - description: name of the file\n                  in: path\n                  name: filename\n                  required: true\n                  type: string\n            produces:\n                - application/json\n                - image/jpg\n                - image/png\n                - image/gif\n            responses:\n                \"200\":\n                    description: success\n                \"404\":\n                    description: file not found\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n    /hello:\n        get:\n            operationId: hello\n            produces:\n                - text/plain\n            responses:\n                \"200\":\n                    description: success\n            summary: Responds with `Hello` if the web service is running.\n    /limits:\n        get:\n            operationId: cloudLimits\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                    schema:\n                        $ref: '#/definitions/BoardsCloudLimits'\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n            summary: Fetches the cloud limits of the server.\n    /login:\n        post:\n            description: Login user\n            operationId: login\n            parameters:\n                - description: Login request\n                  in: body\n                  name: body\n                  required: true\n                  schema:\n                    $ref: '#/definitions/LoginRequest'\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                    schema:\n                        $ref: '#/definitions/LoginResponse'\n                \"401\":\n                    description: invalid login\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n                \"500\":\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n    /logout:\n        post:\n            description: Logout user\n            operationId: logout\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                \"500\":\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n    /ping:\n        get:\n            operationId: ping\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n            summary: Responds with server metadata if the web service is running.\n    /register:\n        post:\n            description: Register new user\n            operationId: register\n            parameters:\n                - description: Register request\n                  in: body\n                  name: body\n                  required: true\n                  schema:\n                    $ref: '#/definitions/RegisterRequest'\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                \"401\":\n                    description: invalid registration token\n                \"500\":\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n    /statistics:\n        get:\n            operationId: handleStatistics\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                    schema:\n                        $ref: '#/definitions/BoardStatistics'\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n            summary: Fetches the statistic  of the server.\n    /subscriptions:\n        post:\n            operationId: createSubscription\n            parameters:\n                - description: subscription definition\n                  in: body\n                  name: Body\n                  required: true\n                  schema:\n                    $ref: '#/definitions/Subscription'\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                    schema:\n                        $ref: '#/definitions/User'\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n            summary: Creates a subscription to a block for a user. The user will receive change notifications for the block.\n    /subscriptions/{blockID}/{subscriberID}:\n        delete:\n            operationId: deleteSubscription\n            parameters:\n                - description: Block ID\n                  in: path\n                  name: blockID\n                  required: true\n                  type: string\n                - description: Subscriber ID\n                  in: path\n                  name: subscriberID\n                  required: true\n                  type: string\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n            summary: Deletes a subscription a user has for a a block. The user will no longer receive change notifications for the block.\n    /subscriptions/{subscriberID}:\n        get:\n            operationId: getSubscriptions\n            parameters:\n                - description: Subscriber ID\n                  in: path\n                  name: subscriberID\n                  required: true\n                  type: string\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                    schema:\n                        items:\n                            $ref: '#/definitions/User'\n                        type: array\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n            summary: Gets subscriptions for a user.\n    /team/{teamID}/onboard:\n        post:\n            operationId: onboard\n            parameters:\n                - description: Team ID\n                  in: path\n                  name: teamID\n                  required: true\n                  type: string\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                    schema:\n                        properties:\n                            boardID:\n                                description: Board ID\n                                type: string\n                            teamID:\n                                description: Team ID\n                                type: string\n                        type: object\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n            summary: Onboards a user on Boards.\n    /teams:\n        get:\n            description: Returns information of all the teams\n            operationId: getTeams\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                    schema:\n                        items:\n                            $ref: '#/definitions/Team'\n                        type: array\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n    /teams/{teamID}:\n        get:\n            description: Returns information of the root team\n            operationId: getTeam\n            parameters:\n                - description: Team ID\n                  in: path\n                  name: teamID\n                  required: true\n                  type: string\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                    schema:\n                        $ref: '#/definitions/Team'\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n    /teams/{teamID}/archive/export:\n        get:\n            operationId: archiveExportTeam\n            parameters:\n                - description: Id of team\n                  in: path\n                  name: teamID\n                  required: true\n                  type: string\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n            summary: Exports an archive of all blocks for all the boards in a team.\n    /teams/{teamID}/archive/import:\n        post:\n            consumes:\n                - multipart/form-data\n            operationId: archiveImport\n            parameters:\n                - description: Team ID\n                  in: path\n                  name: teamID\n                  required: true\n                  type: string\n                - description: archive file to import\n                  in: formData\n                  name: file\n                  required: true\n                  type: file\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n            summary: Import an archive of boards.\n    /teams/{teamID}/boards:\n        get:\n            description: Returns team boards\n            operationId: getBoards\n            parameters:\n                - description: Team ID\n                  in: path\n                  name: teamID\n                  required: true\n                  type: string\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                    schema:\n                        items:\n                            $ref: '#/definitions/Board'\n                        type: array\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n    /teams/{teamID}/boards/{boardID}/files:\n        post:\n            consumes:\n                - multipart/form-data\n            description: Upload a binary file, attached to a root block\n            operationId: uploadFile\n            parameters:\n                - description: ID of the team\n                  in: path\n                  name: teamID\n                  required: true\n                  type: string\n                - description: Board ID\n                  in: path\n                  name: boardID\n                  required: true\n                  type: string\n                - description: The file to upload\n                  in: formData\n                  name: uploaded file\n                  type: file\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                    schema:\n                        $ref: '#/definitions/FileUploadResponse'\n                \"404\":\n                    description: board not found\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n    /teams/{teamID}/boards/insights:\n        get:\n            description: Returns team boards insights\n            operationId: handleTeamBoardsInsights\n            parameters:\n                - description: Team ID\n                  in: path\n                  name: teamID\n                  required: true\n                  type: string\n                - description: duration of data to calculate insights for\n                  in: query\n                  name: time_range\n                  required: true\n                  type: string\n                - description: page offset for top boards\n                  in: query\n                  name: page\n                  required: true\n                  type: string\n                - description: limit for boards in a page.\n                  in: query\n                  name: per_page\n                  required: true\n                  type: string\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                    schema:\n                        items:\n                            $ref: '#/definitions/BoardInsight'\n                        type: array\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n    /teams/{teamID}/boards/search:\n        get:\n            description: Returns the boards that match with a search term in the team\n            operationId: searchBoards\n            parameters:\n                - description: Team ID\n                  in: path\n                  name: teamID\n                  required: true\n                  type: string\n                - description: The search term. Must have at least one character\n                  in: query\n                  name: q\n                  required: true\n                  type: string\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                    schema:\n                        items:\n                            $ref: '#/definitions/Board'\n                        type: array\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n    /teams/{teamID}/boards/search/linkable:\n        get:\n            description: |-\n                Returns the boards that match with a search term in the team and the\n                user has permission to manage members\n            operationId: searchLinkableBoards\n            parameters:\n                - description: Team ID\n                  in: path\n                  name: teamID\n                  required: true\n                  type: string\n                - description: The search term. Must have at least one character\n                  in: query\n                  name: q\n                  required: true\n                  type: string\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                    schema:\n                        items:\n                            $ref: '#/definitions/Board'\n                        type: array\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n    /teams/{teamID}/categories:\n        get:\n            description: Gets the user's board categories\n            operationId: getUserCategoryBoards\n            parameters:\n                - description: Team ID\n                  in: path\n                  name: teamID\n                  required: true\n                  type: string\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                    schema:\n                        items:\n                            $ref: '#/definitions/CategoryBoards'\n                        type: array\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n        post:\n            description: Create a category for boards\n            operationId: createCategory\n            parameters:\n                - description: Team ID\n                  in: path\n                  name: teamID\n                  required: true\n                  type: string\n                - description: category to create\n                  in: body\n                  name: Body\n                  required: true\n                  schema:\n                    $ref: '#/definitions/Category'\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                    schema:\n                        $ref: '#/definitions/Category'\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n    /teams/{teamID}/categories/{categoryID}:\n        delete:\n            description: Delete a category\n            operationId: deleteCategory\n            parameters:\n                - description: Team ID\n                  in: path\n                  name: teamID\n                  required: true\n                  type: string\n                - description: Category ID\n                  in: path\n                  name: categoryID\n                  required: true\n                  type: string\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n        put:\n            description: Create a category for boards\n            operationId: updateCategory\n            parameters:\n                - description: Team ID\n                  in: path\n                  name: teamID\n                  required: true\n                  type: string\n                - description: Category ID\n                  in: path\n                  name: categoryID\n                  required: true\n                  type: string\n                - description: category to update\n                  in: body\n                  name: Body\n                  required: true\n                  schema:\n                    $ref: '#/definitions/Category'\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                    schema:\n                        $ref: '#/definitions/Category'\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n    /teams/{teamID}/categories/{categoryID}/boards/{boardID}:\n        post:\n            description: Set the category of a board\n            operationId: updateCategoryBoard\n            parameters:\n                - description: Team ID\n                  in: path\n                  name: teamID\n                  required: true\n                  type: string\n                - description: Category ID\n                  in: path\n                  name: categoryID\n                  required: true\n                  type: string\n                - description: Board ID\n                  in: path\n                  name: boardID\n                  required: true\n                  type: string\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n    /teams/{teamID}/channels:\n        get:\n            description: Returns the user available channels\n            operationId: searchMyChannels\n            parameters:\n                - description: Team ID\n                  in: path\n                  name: teamID\n                  required: true\n                  type: string\n                - description: string to filter channels list\n                  in: query\n                  name: search\n                  type: string\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                    schema:\n                        items:\n                            $ref: '#/definitions/Channel'\n                        type: array\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n    /teams/{teamID}/channels/{channelID}:\n        get:\n            description: Returns the requested channel\n            operationId: getChannel\n            parameters:\n                - description: Team ID\n                  in: path\n                  name: teamID\n                  required: true\n                  type: string\n                - description: Channel ID\n                  in: path\n                  name: channelID\n                  required: true\n                  type: string\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                    schema:\n                        items:\n                            $ref: '#/definitions/Channel'\n                        type: array\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n    /teams/{teamID}/regenerate_signup_token:\n        post:\n            description: Regenerates the signup token for the root team\n            operationId: regenerateSignupToken\n            parameters:\n                - description: Team ID\n                  in: path\n                  name: teamID\n                  required: true\n                  type: string\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n    /teams/{teamID}/templates:\n        get:\n            description: Returns team templates\n            operationId: getTemplates\n            parameters:\n                - description: Team ID\n                  in: path\n                  name: teamID\n                  required: true\n                  type: string\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                    schema:\n                        items:\n                            $ref: '#/definitions/Board'\n                        type: array\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n    /teams/{teamID}/users:\n        get:\n            description: Returns team users\n            operationId: getTeamUsers\n            parameters:\n                - description: Team ID\n                  in: path\n                  name: teamID\n                  required: true\n                  type: string\n                - description: string to filter users list\n                  in: query\n                  name: search\n                  type: string\n                - description: exclude bot users\n                  in: query\n                  name: exclude_bots\n                  type: boolean\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                    schema:\n                        items:\n                            $ref: '#/definitions/User'\n                        type: array\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n    /users:\n        post:\n            description: Returns a user[]\n            operationId: getUsersList\n            parameters:\n                - description: User ID\n                  in: path\n                  name: userID\n                  required: true\n                  type: string\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                    schema:\n                        $ref: '#/definitions/User'\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n    /users/{userID}:\n        get:\n            description: Returns a user\n            operationId: getUser\n            parameters:\n                - description: User ID\n                  in: path\n                  name: userID\n                  required: true\n                  type: string\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                    schema:\n                        $ref: '#/definitions/User'\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n    /users/{userID}/changepassword:\n        post:\n            description: Change a user's password\n            operationId: changePassword\n            parameters:\n                - description: User ID\n                  in: path\n                  name: userID\n                  required: true\n                  type: string\n                - description: Change password request\n                  in: body\n                  name: body\n                  required: true\n                  schema:\n                    $ref: '#/definitions/ChangePasswordRequest'\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                \"400\":\n                    description: invalid request\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n                \"500\":\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n    /users/{userID}/config:\n        patch:\n            description: Updates user config\n            operationId: updateUserConfig\n            parameters:\n                - description: User ID\n                  in: path\n                  name: userID\n                  required: true\n                  type: string\n                - description: User config patch to apply\n                  in: body\n                  name: Body\n                  required: true\n                  schema:\n                    $ref: '#/definitions/UserPreferencesPatch'\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n    /users/me:\n        get:\n            description: Returns the currently logged-in user\n            operationId: getMe\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                    schema:\n                        $ref: '#/definitions/User'\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n    /users/me/boards/insights:\n        get:\n            description: Returns user boards insights\n            operationId: getUserBoardsInsights\n            parameters:\n                - description: Team ID\n                  in: path\n                  name: teamID\n                  required: true\n                  type: string\n                - description: duration of data to calculate insights for\n                  in: query\n                  name: time_range\n                  required: true\n                  type: string\n                - description: page offset for top boards\n                  in: query\n                  name: page\n                  required: true\n                  type: string\n                - description: limit for boards in a page.\n                  in: query\n                  name: per_page\n                  required: true\n                  type: string\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                    schema:\n                        items:\n                            $ref: '#/definitions/BoardInsight'\n                        type: array\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n    /users/me/config:\n        get:\n            description: Returns an array of user preferences\n            operationId: getUserConfig\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                    schema:\n                        $ref: '#/definitions/Preferences'\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\n    /users/me/memberships:\n        get:\n            description: Returns the currently users board memberships\n            operationId: getMyMemberships\n            produces:\n                - application/json\n            responses:\n                \"200\":\n                    description: success\n                    schema:\n                        items:\n                            $ref: '#/definitions/BoardMember'\n                        type: array\n                default:\n                    description: internal error\n                    schema:\n                        $ref: '#/definitions/ErrorResponse'\n            security:\n                - BearerAuth: []\nproduces:\n    - application/json\nschemes:\n    - http\n    - https\nsecurityDefinitions:\n    BearerAuth:\n        description: 'Pass session token using Bearer authentication, e.g. set header \"Authorization: Bearer <session token>\"'\n        in: header\n        name: Authorization\n        type: apiKey\nswagger: \"2.0\"\n"
  },
  {
    "path": "server/utils/callbackqueue.go",
    "content": "package utils\n\nimport (\n\t\"context\"\n\t\"runtime/debug\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\n// CallbackFunc is a func that can enqueued in the callback queue and will be\n// called when dequeued.\ntype CallbackFunc func() error\n\n// CallbackQueue provides a simple thread pool for processing callbacks. Callbacks will\n// be executed in the order in which they are enqueued, but no guarantees are provided\n// regarding the order in which they finish (unless poolSize == 1).\ntype CallbackQueue struct {\n\tname     string\n\tpoolSize int\n\n\tqueue chan CallbackFunc\n\tdone  chan struct{}\n\talive chan int\n\n\tidone uint32\n\n\tlogger mlog.LoggerIFace\n}\n\n// NewCallbackQueue creates a new CallbackQueue and starts a thread pool to service it.\nfunc NewCallbackQueue(name string, queueSize int, poolSize int, logger mlog.LoggerIFace) *CallbackQueue {\n\tcn := &CallbackQueue{\n\t\tname:     name,\n\t\tpoolSize: poolSize,\n\t\tqueue:    make(chan CallbackFunc, queueSize),\n\t\tdone:     make(chan struct{}),\n\t\talive:    make(chan int, poolSize),\n\t\tlogger:   logger,\n\t}\n\n\tfor i := 0; i < poolSize; i++ {\n\t\tgo cn.loop(i)\n\t}\n\n\treturn cn\n}\n\n// Shutdown stops accepting enqueues and exits all pool threads. This method waits\n// as long as the context allows for the threads to exit.\n// Returns true if the pool exited, false on timeout.\nfunc (cn *CallbackQueue) Shutdown(context context.Context) bool {\n\tif !atomic.CompareAndSwapUint32(&cn.idone, 0, 1) {\n\t\t// already shutdown\n\t\treturn true\n\t}\n\n\t// signal threads to exit\n\tclose(cn.done)\n\n\t// wait for the threads to exit or timeout\n\tcount := 0\n\tfor count < cn.poolSize {\n\t\tselect {\n\t\tcase <-cn.alive:\n\t\t\tcount++\n\t\tcase <-context.Done():\n\t\t\treturn false\n\t\t}\n\t}\n\n\t// try to drain any remaining callbacks\n\tfor {\n\t\tselect {\n\t\tcase f := <-cn.queue:\n\t\t\tcn.exec(f)\n\t\tcase <-context.Done():\n\t\t\treturn false\n\t\tdefault:\n\t\t\treturn true\n\t\t}\n\t}\n}\n\n// Enqueue adds a callback to the queue.\nfunc (cn *CallbackQueue) Enqueue(f CallbackFunc) {\n\tif atomic.LoadUint32(&cn.idone) != 0 {\n\t\tcn.logger.Debug(\"CallbackQueue skipping enqueue, notifier is shutdown\", mlog.String(\"name\", cn.name))\n\t\treturn\n\t}\n\n\tselect {\n\tcase cn.queue <- f:\n\tdefault:\n\t\tstart := time.Now()\n\t\tcn.queue <- f\n\t\tdur := time.Since(start)\n\t\tcn.logger.Warn(\"CallbackQueue queue backlog\", mlog.String(\"name\", cn.name), mlog.Duration(\"wait_time\", dur))\n\t}\n}\n\nfunc (cn *CallbackQueue) loop(id int) {\n\tdefer func() {\n\t\tcn.logger.Trace(\"CallbackQueue thread exited\", mlog.String(\"name\", cn.name), mlog.Int(\"id\", id))\n\t\tcn.alive <- id\n\t}()\n\n\tfor {\n\t\tselect {\n\t\tcase f := <-cn.queue:\n\t\t\tcn.exec(f)\n\t\tcase <-cn.done:\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (cn *CallbackQueue) exec(f CallbackFunc) {\n\t// don't let a panic in the callback exit the thread.\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tstack := debug.Stack()\n\t\t\tcn.logger.Error(\"CallbackQueue callback panic\",\n\t\t\t\tmlog.String(\"name\", cn.name),\n\t\t\t\tmlog.Any(\"panic\", r),\n\t\t\t\tmlog.String(\"stack\", string(stack)),\n\t\t\t)\n\t\t}\n\t}()\n\n\tif err := f(); err != nil {\n\t\tcn.logger.Error(\"CallbackQueue callback error\", mlog.String(\"name\", cn.name), mlog.Err(err))\n\t}\n}\n"
  },
  {
    "path": "server/utils/callbackqueue_test.go",
    "content": "package utils\n\nimport (\n\t\"context\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nfunc Test_newChangeNotifier(t *testing.T) {\n\tlogger := mlog.CreateConsoleTestLogger(t)\n\n\tt.Run(\"startup, shutdown\", func(t *testing.T) {\n\t\tcn := NewCallbackQueue(\"test1\", 100, 5, logger)\n\n\t\tvar callbackCount int32\n\t\tcallback := func() error {\n\t\t\tatomic.AddInt32(&callbackCount, 1)\n\t\t\treturn nil\n\t\t}\n\n\t\tconst loops = 500\n\t\tfor i := 0; i < loops; i++ {\n\t\t\tcn.Enqueue(callback)\n\t\t\t// don't peg the cpu\n\t\t\tif i%20 == 0 {\n\t\t\t\ttime.Sleep(time.Millisecond * 1)\n\t\t\t}\n\t\t}\n\n\t\tctx, cancel := context.WithTimeout(context.Background(), time.Second*10)\n\t\tdefer cancel()\n\t\tok := cn.Shutdown(ctx)\n\t\tassert.True(t, ok, \"shutdown should return true (no timeout)\")\n\n\t\tassert.Equal(t, int32(loops), atomic.LoadInt32(&callbackCount))\n\t})\n\n\tt.Run(\"handle panic\", func(t *testing.T) {\n\t\tcn := NewCallbackQueue(\"test2\", 100, 5, logger)\n\n\t\tvar callbackCount int32\n\t\tcallback := func() error {\n\t\t\tatomic.AddInt32(&callbackCount, 1)\n\t\t\tpanic(\"oh no!\")\n\t\t}\n\n\t\tconst loops = 5\n\t\tfor i := 0; i < loops; i++ {\n\t\t\tcn.Enqueue(callback)\n\t\t}\n\n\t\tctx, cancel := context.WithTimeout(context.Background(), time.Second*10)\n\t\tdefer cancel()\n\t\tok := cn.Shutdown(ctx)\n\t\tassert.True(t, ok, \"shutdown should return true (no timeout)\")\n\n\t\tassert.Equal(t, int32(loops), atomic.LoadInt32(&callbackCount))\n\t})\n}\n"
  },
  {
    "path": "server/utils/debug.go",
    "content": "package utils\n\nimport (\n\t\"os\"\n\t\"strings\"\n)\n\n// IsRunningUnitTests returns true if this instance of FocalBoard is running unit or integration tests.\nfunc IsRunningUnitTests() bool {\n\ttesting := os.Getenv(\"FOCALBOARD_UNIT_TESTING\")\n\tif testing == \"\" {\n\t\treturn false\n\t}\n\n\tswitch strings.ToLower(testing) {\n\tcase \"1\", \"t\", \"y\", \"true\", \"yes\":\n\t\treturn true\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "server/utils/links.go",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\npackage utils\n\nimport \"fmt\"\n\n// MakeCardLink creates fully qualified card links based on card id and parents.\nfunc MakeCardLink(serverRoot string, teamID string, boardID string, cardID string) string {\n\treturn fmt.Sprintf(\"%s/team/%s/%s/0/%s\", serverRoot, teamID, boardID, cardID)\n}\n\nfunc MakeBoardLink(serverRoot string, teamID string, board string) string {\n\treturn fmt.Sprintf(\"%s/team/%s/%s\", serverRoot, teamID, board)\n}\n"
  },
  {
    "path": "server/utils/testUtils.go",
    "content": "package utils\n\nimport \"github.com/stretchr/testify/mock\"\n\nvar Anything = mock.MatchedBy(func(interface{}) bool { return true })\n"
  },
  {
    "path": "server/utils/utils.go",
    "content": "package utils\n\nimport (\n\t\"encoding/json\"\n\t\"path\"\n\t\"reflect\"\n\t\"time\"\n\n\tmmModel \"github.com/mattermost/mattermost/server/public/model\"\n)\n\ntype IDType byte\n\nconst (\n\tIDTypeNone       IDType = '7'\n\tIDTypeTeam       IDType = 't'\n\tIDTypeBoard      IDType = 'b'\n\tIDTypeCard       IDType = 'c'\n\tIDTypeView       IDType = 'v'\n\tIDTypeSession    IDType = 's'\n\tIDTypeUser       IDType = 'u'\n\tIDTypeToken      IDType = 'k'\n\tIDTypeBlock      IDType = 'a'\n\tIDTypeAttachment IDType = 'i'\n)\n\n// NewId is a globally unique identifier.  It is a [A-Z0-9] string 27\n// characters long.  It is a UUID version 4 Guid that is zbased32 encoded\n// with the padding stripped off, and a one character alpha prefix indicating the\n// type of entity or a `7` if unknown type.\nfunc NewID(idType IDType) string {\n\treturn string(idType) + mmModel.NewId()\n}\n\n// GetMillis is a convenience method to get milliseconds since epoch.\nfunc GetMillis() int64 {\n\treturn mmModel.GetMillis()\n}\n\n// GetMillisForTime is a convenience method to get milliseconds since epoch for provided Time.\nfunc GetMillisForTime(thisTime time.Time) int64 {\n\treturn mmModel.GetMillisForTime(thisTime)\n}\n\n// GetTimeForMillis is a convenience method to get time.Time for milliseconds since epoch.\nfunc GetTimeForMillis(millis int64) time.Time {\n\treturn mmModel.GetTimeForMillis(millis)\n}\n\n// SecondsToMillis is a convenience method to convert seconds to milliseconds.\nfunc SecondsToMillis(seconds int64) int64 {\n\treturn seconds * 1000\n}\n\nfunc StructToMap(v interface{}) (m map[string]interface{}) {\n\tb, _ := json.Marshal(v)\n\t_ = json.Unmarshal(b, &m)\n\treturn\n}\n\nfunc intersection(a []interface{}, b []interface{}) []interface{} {\n\tset := make([]interface{}, 0)\n\thash := make(map[interface{}]bool)\n\tav := reflect.ValueOf(a)\n\tbv := reflect.ValueOf(b)\n\n\tfor i := 0; i < av.Len(); i++ {\n\t\tel := av.Index(i).Interface()\n\t\thash[el] = true\n\t}\n\n\tfor i := 0; i < bv.Len(); i++ {\n\t\tel := bv.Index(i).Interface()\n\t\tif _, found := hash[el]; found {\n\t\t\tset = append(set, el)\n\t\t}\n\t}\n\n\treturn set\n}\n\nfunc Intersection(x ...[]interface{}) []interface{} {\n\tif len(x) == 0 {\n\t\treturn nil\n\t}\n\n\tif len(x) == 1 {\n\t\treturn x[0]\n\t}\n\n\tresult := x[0]\n\ti := 1\n\tfor i < len(x) {\n\t\tresult = intersection(result, x[i])\n\t\ti++\n\t}\n\n\treturn result\n}\n\nfunc IsCloudLicense(license *mmModel.License) bool {\n\treturn license != nil &&\n\t\tlicense.Features != nil &&\n\t\tlicense.Features.Cloud != nil &&\n\t\t*license.Features.Cloud\n}\n\nfunc DedupeStringArr(arr []string) []string {\n\thashMap := map[string]bool{}\n\n\tfor _, item := range arr {\n\t\thashMap[item] = true\n\t}\n\n\tdedupedArr := make([]string, len(hashMap))\n\ti := 0\n\tfor key := range hashMap {\n\t\tdedupedArr[i] = key\n\t\ti++\n\t}\n\n\treturn dedupedArr\n}\n\nfunc GetBaseFilePath() string {\n\treturn path.Join(\"boards\", time.Now().Format(\"20060102\"))\n}\n"
  },
  {
    "path": "server/web/webserver.go",
    "content": "package web\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"path\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"text/template\"\n\n\t\"github.com/gorilla/mux\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\n// RoutedService defines the interface that is needed for any service to\n// register themself in the web server to provide new endpoints. (see\n// AddRoutes).\ntype RoutedService interface {\n\tRegisterRoutes(*mux.Router)\n}\n\n// Server is the structure responsible for managing our http web server.\ntype Server struct {\n\thttp.Server\n\n\tbaseURL    string\n\trootPath   string\n\tbasePrefix string\n\tport       int\n\tssl        bool\n\tlogger     mlog.LoggerIFace\n}\n\n// NewServer creates a new instance of the webserver.\nfunc NewServer(rootPath string, serverRoot string, port int, ssl, localOnly bool, logger mlog.LoggerIFace) *Server {\n\tr := mux.NewRouter()\n\n\tbasePrefix := os.Getenv(\"FOCALBOARD_HTTP_SERVER_BASEPATH\")\n\tif basePrefix != \"\" {\n\t\tr = r.PathPrefix(basePrefix).Subrouter()\n\t}\n\n\tvar addr string\n\tif localOnly {\n\t\taddr = fmt.Sprintf(`localhost:%d`, port)\n\t} else {\n\t\taddr = fmt.Sprintf(`:%d`, port)\n\t}\n\n\tbaseURL := \"\"\n\turl, err := url.Parse(serverRoot)\n\tif err != nil {\n\t\tlogger.Error(\"Invalid ServerRoot setting\", mlog.Err(err))\n\t}\n\tbaseURL = url.Path\n\n\tws := &Server{\n\t\t// (TODO: Add ReadHeaderTimeout)\n\t\tServer: http.Server{ //nolint:gosec\n\t\t\tAddr:    addr,\n\t\t\tHandler: r,\n\t\t},\n\t\tbaseURL:    baseURL,\n\t\trootPath:   rootPath,\n\t\tport:       port,\n\t\tssl:        ssl,\n\t\tlogger:     logger,\n\t\tbasePrefix: basePrefix,\n\t}\n\n\treturn ws\n}\n\nfunc (ws *Server) Router() *mux.Router {\n\treturn ws.Server.Handler.(*mux.Router)\n}\n\n// AddRoutes allows services to register themself in the webserver router and provide new endpoints.\nfunc (ws *Server) AddRoutes(rs RoutedService) {\n\trs.RegisterRoutes(ws.Router())\n}\n\nfunc (ws *Server) registerRoutes() {\n\tws.Router().PathPrefix(\"/static\").Handler(http.StripPrefix(ws.basePrefix+\"/static/\", http.FileServer(http.Dir(filepath.Join(ws.rootPath, \"static\")))))\n\tws.Router().PathPrefix(\"/\").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Set(\"Content-Type\", \"text/html; charset=utf-8\")\n\t\tindexTemplate, err := template.New(\"index\").ParseFiles(path.Join(ws.rootPath, \"index.html\"))\n\t\tif err != nil {\n\t\t\tws.logger.Log(errorOrWarn(), \"Unable to serve the index.html file\", mlog.Err(err))\n\t\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\t\treturn\n\t\t}\n\t\terr = indexTemplate.ExecuteTemplate(w, \"index.html\", map[string]string{\"BaseURL\": ws.baseURL})\n\t\tif err != nil {\n\t\t\tws.logger.Log(errorOrWarn(), \"Unable to serve the index.html file\", mlog.Err(err))\n\t\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\t\treturn\n\t\t}\n\t})\n}\n\n// Start runs the web server and start listening for connections.\nfunc (ws *Server) Start() {\n\tws.registerRoutes()\n\tif ws.port == -1 {\n\t\tws.logger.Debug(\"server not bind to any port\")\n\t\treturn\n\t}\n\n\tisSSL := ws.ssl && fileExists(\"./cert/cert.pem\") && fileExists(\"./cert/key.pem\")\n\tif isSSL {\n\t\tws.logger.Info(\"https server started\", mlog.Int(\"port\", ws.port))\n\t\tgo func() {\n\t\t\tif err := ws.ListenAndServeTLS(\"./cert/cert.pem\", \"./cert/key.pem\"); err != nil {\n\t\t\t\tws.logger.Fatal(\"ListenAndServeTLS\", mlog.Err(err))\n\t\t\t}\n\t\t}()\n\n\t\treturn\n\t}\n\n\tws.logger.Info(\"http server started\", mlog.Int(\"port\", ws.port))\n\tgo func() {\n\t\tif err := ws.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {\n\t\t\tws.logger.Fatal(\"ListenAndServeTLS\", mlog.Err(err))\n\t\t}\n\t\tws.logger.Info(\"http server stopped\")\n\t}()\n}\n\nfunc (ws *Server) Shutdown() error {\n\treturn ws.Close()\n}\n\n// fileExists returns true if a file exists at the path.\nfunc fileExists(path string) bool {\n\t_, err := os.Stat(path)\n\tif os.IsNotExist(err) {\n\t\treturn false\n\t}\n\n\treturn err == nil\n}\n\n// errorOrWarn returns a `warn` level if this server instance is running unit tests, otherwise `error`.\nfunc errorOrWarn() mlog.Level {\n\tunitTesting := strings.ToLower(strings.TrimSpace(os.Getenv(\"FOCALBOARD_UNIT_TESTING\")))\n\tif unitTesting == \"1\" || unitTesting == \"y\" || unitTesting == \"t\" {\n\t\treturn mlog.LvlWarn\n\t}\n\treturn mlog.LvlError\n}\n"
  },
  {
    "path": "server/web/webserver_test.go",
    "content": "package web\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nfunc Test_NewServer(t *testing.T) {\n\ttests := []struct {\n\t\tname               string\n\t\trootPath           string\n\t\tserverRoot         string\n\t\tssl                bool\n\t\tport               int\n\t\tlocalOnly          bool\n\t\tlogger             mlog.LoggerIFace\n\t\texpectedBaseURL    string\n\t\texpectedServerAddr string\n\t}{\n\t\t{\n\t\t\tname:               \"should return server with given properties\",\n\t\t\trootPath:           \"./test/path/to/root\",\n\t\t\tserverRoot:         \"https://some-fake-server.com/fake-url\",\n\t\t\tssl:                false,\n\t\t\tport:               9999, // fake port number\n\t\t\tlocalOnly:          false,\n\t\t\tlogger:             &mlog.Logger{},\n\t\t\texpectedBaseURL:    \"/fake-url\",\n\t\t\texpectedServerAddr: \":9999\",\n\t\t},\n\t\t{\n\t\t\tname:               \"should return local server with given properties\",\n\t\t\trootPath:           \"./test/path/to/root\",\n\t\t\tserverRoot:         \"https://some-fake-server.com/fake-url\",\n\t\t\tssl:                false,\n\t\t\tport:               3000, // fake port number\n\t\t\tlocalOnly:          true,\n\t\t\tlogger:             &mlog.Logger{},\n\t\t\texpectedBaseURL:    \"/fake-url\",\n\t\t\texpectedServerAddr: \"localhost:3000\",\n\t\t},\n\t\t{\n\t\t\tname:               \"should match Server properties when ssl true\",\n\t\t\trootPath:           \"./test/path/to/root\",\n\t\t\tserverRoot:         \"https://some-fake-server.com/fake-url\",\n\t\t\tssl:                true,\n\t\t\tport:               8000, // fake port number\n\t\t\tlocalOnly:          false,\n\t\t\tlogger:             &mlog.Logger{},\n\t\t\texpectedBaseURL:    \"/fake-url\",\n\t\t\texpectedServerAddr: \":8000\",\n\t\t},\n\t\t{\n\t\t\tname:               \"should return local server when ssl true\",\n\t\t\trootPath:           \"./test/path/to/root\",\n\t\t\tserverRoot:         \"https://localhost:8080/fake-url\",\n\t\t\tssl:                true,\n\t\t\tport:               9999, // fake port number\n\t\t\tlocalOnly:          true,\n\t\t\tlogger:             &mlog.Logger{},\n\t\t\texpectedBaseURL:    \"/fake-url\",\n\t\t\texpectedServerAddr: \"localhost:9999\",\n\t\t},\n\t\t{\n\t\t\tname:               \"should return '/' as base url is not good!\",\n\t\t\trootPath:           \"\",\n\t\t\tserverRoot:         \"https://localhost:8080/#!@$@#@\",\n\t\t\tssl:                true,\n\t\t\tport:               9999, // fake port number\n\t\t\tlocalOnly:          true,\n\t\t\tlogger:             &mlog.Logger{},\n\t\t\texpectedBaseURL:    \"/\",\n\t\t\texpectedServerAddr: \"localhost:9999\",\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tws := NewServer(test.rootPath, test.serverRoot, test.port, test.ssl, test.localOnly, test.logger)\n\n\t\t\trequire.NotNil(t, ws, \"The webserver object is nil!\")\n\n\t\t\trequire.Equal(t, test.expectedBaseURL, ws.baseURL, \"baseURL does not match\")\n\t\t\trequire.Equal(t, test.rootPath, ws.rootPath, \"rootPath does not match\")\n\t\t\trequire.Equal(t, test.port, ws.port, \"rootPath does not match\")\n\t\t\trequire.Equal(t, test.ssl, ws.ssl, \"logger pointer does not match\")\n\t\t\trequire.Equal(t, test.logger, ws.logger, \"logger pointer does not match\")\n\n\t\t\tif test.localOnly == true {\n\t\t\t\trequire.Equal(t, test.expectedServerAddr, ws.Server.Addr, \"localhost address not as matching!\")\n\t\t\t} else {\n\t\t\t\trequire.Equal(t, test.expectedServerAddr, ws.Server.Addr, \"server address not matching!\")\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "server/ws/adapter.go",
    "content": "//go:generate mockgen -destination=mocks/mockstore.go -package mocks . Store\npackage ws\n\nimport (\n\t\"github.com/mattermost/focalboard/server/model\"\n)\n\nconst (\n\twebsocketActionAuth                     = \"AUTH\"\n\twebsocketActionSubscribeTeam            = \"SUBSCRIBE_TEAM\"\n\twebsocketActionUnsubscribeTeam          = \"UNSUBSCRIBE_TEAM\"\n\twebsocketActionSubscribeBlocks          = \"SUBSCRIBE_BLOCKS\"\n\twebsocketActionUnsubscribeBlocks        = \"UNSUBSCRIBE_BLOCKS\"\n\twebsocketActionUpdateBoard              = \"UPDATE_BOARD\"\n\twebsocketActionUpdateMember             = \"UPDATE_MEMBER\"\n\twebsocketActionDeleteMember             = \"DELETE_MEMBER\"\n\twebsocketActionUpdateBlock              = \"UPDATE_BLOCK\"\n\twebsocketActionUpdateConfig             = \"UPDATE_CLIENT_CONFIG\"\n\twebsocketActionUpdateCategory           = \"UPDATE_CATEGORY\"\n\twebsocketActionUpdateCategoryBoard      = \"UPDATE_BOARD_CATEGORY\"\n\twebsocketActionUpdateSubscription       = \"UPDATE_SUBSCRIPTION\"\n\twebsocketActionUpdateCardLimitTimestamp = \"UPDATE_CARD_LIMIT_TIMESTAMP\"\n\twebsocketActionReorderCategories        = \"REORDER_CATEGORIES\"\n\twebsocketActionReorderCategoryBoards    = \"REORDER_CATEGORY_BOARDS\"\n)\n\ntype Store interface {\n\tGetBlock(blockID string) (*model.Block, error)\n\tGetMembersForBoard(boardID string) ([]*model.BoardMember, error)\n}\n\ntype Adapter interface {\n\tBroadcastBlockChange(teamID string, block *model.Block)\n\tBroadcastBlockDelete(teamID, blockID, boardID string)\n\tBroadcastBoardChange(teamID string, board *model.Board)\n\tBroadcastBoardDelete(teamID, boardID string)\n\tBroadcastMemberChange(teamID, boardID string, member *model.BoardMember)\n\tBroadcastMemberDelete(teamID, boardID, userID string)\n\tBroadcastConfigChange(clientConfig model.ClientConfig)\n\tBroadcastCategoryChange(category model.Category)\n\tBroadcastCategoryBoardChange(teamID, userID string, blockCategory []*model.BoardCategoryWebsocketData)\n\tBroadcastCardLimitTimestampChange(cardLimitTimestamp int64)\n\tBroadcastSubscriptionChange(teamID string, subscription *model.Subscription)\n\tBroadcastCategoryReorder(teamID, userID string, categoryOrder []string)\n\tBroadcastCategoryBoardsReorder(teamID, userID, categoryID string, boardsOrder []string)\n}\n"
  },
  {
    "path": "server/ws/common.go",
    "content": "package ws\n\nimport (\n\t\"github.com/mattermost/focalboard/server/model\"\n)\n\n// UpdateCategoryMessage is sent on block updates.\ntype UpdateCategoryMessage struct {\n\tAction          string                              `json:\"action\"`\n\tTeamID          string                              `json:\"teamId\"`\n\tCategory        *model.Category                     `json:\"category,omitempty\"`\n\tBoardCategories []*model.BoardCategoryWebsocketData `json:\"blockCategories,omitempty\"`\n}\n\n// UpdateBlockMsg is sent on block updates.\ntype UpdateBlockMsg struct {\n\tAction string       `json:\"action\"`\n\tTeamID string       `json:\"teamId\"`\n\tBlock  *model.Block `json:\"block\"`\n}\n\n// UpdateBoardMsg is sent on block updates.\ntype UpdateBoardMsg struct {\n\tAction string       `json:\"action\"`\n\tTeamID string       `json:\"teamId\"`\n\tBoard  *model.Board `json:\"board\"`\n}\n\n// UpdateMemberMsg is sent on membership updates.\ntype UpdateMemberMsg struct {\n\tAction string             `json:\"action\"`\n\tTeamID string             `json:\"teamId\"`\n\tMember *model.BoardMember `json:\"member\"`\n}\n\n// UpdateSubscription is sent on subscription updates.\ntype UpdateSubscription struct {\n\tAction       string              `json:\"action\"`\n\tSubscription *model.Subscription `json:\"subscription\"`\n}\n\n// UpdateClientConfig is sent on block updates.\ntype UpdateClientConfig struct {\n\tAction       string             `json:\"action\"`\n\tClientConfig model.ClientConfig `json:\"clientconfig\"`\n}\n\n// UpdateClientConfig is sent on block updates.\ntype UpdateCardLimitTimestamp struct {\n\tAction    string `json:\"action\"`\n\tTimestamp int64  `json:\"timestamp\"`\n}\n\n// WebsocketCommand is an incoming command from the client.\ntype WebsocketCommand struct {\n\tAction    string   `json:\"action\"`\n\tTeamID    string   `json:\"teamId\"`\n\tToken     string   `json:\"token\"`\n\tReadToken string   `json:\"readToken\"`\n\tBlockIDs  []string `json:\"blockIds\"`\n}\n\ntype CategoryReorderMessage struct {\n\tAction        string   `json:\"action\"`\n\tCategoryOrder []string `json:\"categoryOrder\"`\n\tTeamID        string   `json:\"teamId\"`\n}\n\ntype CategoryBoardReorderMessage struct {\n\tAction     string   `json:\"action\"`\n\tCategoryID string   `json:\"CategoryId\"`\n\tBoardOrder []string `json:\"BoardOrder\"`\n\tTeamID     string   `json:\"teamId\"`\n}\n"
  },
  {
    "path": "server/ws/helpers_test.go",
    "content": "package ws\n\nimport (\n\t\"testing\"\n\n\tauthMocks \"github.com/mattermost/focalboard/server/auth/mocks\"\n\twsMocks \"github.com/mattermost/focalboard/server/ws/mocks\"\n\n\tmmModel \"github.com/mattermost/mattermost/server/public/model\"\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n\n\t\"github.com/golang/mock/gomock\"\n)\n\ntype TestHelper struct {\n\tapi   *wsMocks.MockAPI\n\tauth  *authMocks.MockAuthInterface\n\tstore *wsMocks.MockStore\n\tctrl  *gomock.Controller\n\tpa    *PluginAdapter\n}\n\nfunc SetupTestHelper(t *testing.T) *TestHelper {\n\tctrl := gomock.NewController(t)\n\tmockAPI := wsMocks.NewMockAPI(ctrl)\n\tmockAuth := authMocks.NewMockAuthInterface(ctrl)\n\tmockStore := wsMocks.NewMockStore(ctrl)\n\n\tmockAPI.EXPECT().LogDebug(gomock.Any(), gomock.Any()).AnyTimes()\n\tmockAPI.EXPECT().LogInfo(gomock.Any(), gomock.Any()).AnyTimes()\n\tmockAPI.EXPECT().LogError(gomock.Any(), gomock.Any()).AnyTimes()\n\tmockAPI.EXPECT().LogWarn(gomock.Any(), gomock.Any()).AnyTimes()\n\n\treturn &TestHelper{\n\t\tapi:   mockAPI,\n\t\tauth:  mockAuth,\n\t\tstore: mockStore,\n\t\tctrl:  ctrl,\n\t\tpa:    NewPluginAdapter(mockAPI, mockAuth, mockStore, mlog.CreateConsoleTestLogger(t)),\n\t}\n}\n\nfunc (th *TestHelper) ReceiveWebSocketMessage(webConnID, userID, action string, data map[string]interface{}) {\n\treq := &mmModel.WebSocketRequest{Action: websocketMessagePrefix + action, Data: data}\n\n\tth.pa.WebSocketMessageHasBeenPosted(webConnID, userID, req)\n}\n\nfunc (th *TestHelper) SubscribeWebConnToTeam(webConnID, userID, teamID string) {\n\tth.auth.EXPECT().\n\t\tDoesUserHaveTeamAccess(userID, teamID).\n\t\tReturn(true)\n\n\tmsgData := map[string]interface{}{\"teamId\": teamID}\n\tth.ReceiveWebSocketMessage(webConnID, userID, websocketActionSubscribeTeam, msgData)\n}\n\nfunc (th *TestHelper) UnsubscribeWebConnFromTeam(webConnID, userID, teamID string) {\n\tmsgData := map[string]interface{}{\"teamId\": teamID}\n\tth.ReceiveWebSocketMessage(webConnID, userID, websocketActionUnsubscribeTeam, msgData)\n}\n"
  },
  {
    "path": "server/ws/mocks/mockpluginapi.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: github.com/mattermost/mattermost-server/v6/plugin (interfaces: API)\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tio \"io\"\n\thttp \"net/http\"\n\treflect \"reflect\"\n\n\tgomock \"github.com/golang/mock/gomock\"\n\tmodel \"github.com/mattermost/mattermost/server/public/model\"\n)\n\n// MockAPI is a mock of API interface.\ntype MockAPI struct {\n\tctrl     *gomock.Controller\n\trecorder *MockAPIMockRecorder\n}\n\n// MockAPIMockRecorder is the mock recorder for MockAPI.\ntype MockAPIMockRecorder struct {\n\tmock *MockAPI\n}\n\n// NewMockAPI creates a new mock instance.\nfunc NewMockAPI(ctrl *gomock.Controller) *MockAPI {\n\tmock := &MockAPI{ctrl: ctrl}\n\tmock.recorder = &MockAPIMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockAPI) EXPECT() *MockAPIMockRecorder {\n\treturn m.recorder\n}\n\n// AddChannelMember mocks base method.\nfunc (m *MockAPI) AddChannelMember(arg0, arg1 string) (*model.ChannelMember, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"AddChannelMember\", arg0, arg1)\n\tret0, _ := ret[0].(*model.ChannelMember)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// AddChannelMember indicates an expected call of AddChannelMember.\nfunc (mr *MockAPIMockRecorder) AddChannelMember(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"AddChannelMember\", reflect.TypeOf((*MockAPI)(nil).AddChannelMember), arg0, arg1)\n}\n\n// AddReaction mocks base method.\nfunc (m *MockAPI) AddReaction(arg0 *model.Reaction) (*model.Reaction, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"AddReaction\", arg0)\n\tret0, _ := ret[0].(*model.Reaction)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// AddReaction indicates an expected call of AddReaction.\nfunc (mr *MockAPIMockRecorder) AddReaction(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"AddReaction\", reflect.TypeOf((*MockAPI)(nil).AddReaction), arg0)\n}\n\n// AddUserToChannel mocks base method.\nfunc (m *MockAPI) AddUserToChannel(arg0, arg1, arg2 string) (*model.ChannelMember, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"AddUserToChannel\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(*model.ChannelMember)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// AddUserToChannel indicates an expected call of AddUserToChannel.\nfunc (mr *MockAPIMockRecorder) AddUserToChannel(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"AddUserToChannel\", reflect.TypeOf((*MockAPI)(nil).AddUserToChannel), arg0, arg1, arg2)\n}\n\n// CopyFileInfos mocks base method.\nfunc (m *MockAPI) CopyFileInfos(arg0 string, arg1 []string) ([]string, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"CopyFileInfos\", arg0, arg1)\n\tret0, _ := ret[0].([]string)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// CopyFileInfos indicates an expected call of CopyFileInfos.\nfunc (mr *MockAPIMockRecorder) CopyFileInfos(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"CopyFileInfos\", reflect.TypeOf((*MockAPI)(nil).CopyFileInfos), arg0, arg1)\n}\n\n// CreateBot mocks base method.\nfunc (m *MockAPI) CreateBot(arg0 *model.Bot) (*model.Bot, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"CreateBot\", arg0)\n\tret0, _ := ret[0].(*model.Bot)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// CreateBot indicates an expected call of CreateBot.\nfunc (mr *MockAPIMockRecorder) CreateBot(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"CreateBot\", reflect.TypeOf((*MockAPI)(nil).CreateBot), arg0)\n}\n\n// CreateChannel mocks base method.\nfunc (m *MockAPI) CreateChannel(arg0 *model.Channel) (*model.Channel, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"CreateChannel\", arg0)\n\tret0, _ := ret[0].(*model.Channel)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// CreateChannel indicates an expected call of CreateChannel.\nfunc (mr *MockAPIMockRecorder) CreateChannel(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"CreateChannel\", reflect.TypeOf((*MockAPI)(nil).CreateChannel), arg0)\n}\n\n// CreateChannelSidebarCategory mocks base method.\nfunc (m *MockAPI) CreateChannelSidebarCategory(arg0, arg1 string, arg2 *model.SidebarCategoryWithChannels) (*model.SidebarCategoryWithChannels, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"CreateChannelSidebarCategory\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(*model.SidebarCategoryWithChannels)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// CreateChannelSidebarCategory indicates an expected call of CreateChannelSidebarCategory.\nfunc (mr *MockAPIMockRecorder) CreateChannelSidebarCategory(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"CreateChannelSidebarCategory\", reflect.TypeOf((*MockAPI)(nil).CreateChannelSidebarCategory), arg0, arg1, arg2)\n}\n\n// CreateCommand mocks base method.\nfunc (m *MockAPI) CreateCommand(arg0 *model.Command) (*model.Command, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"CreateCommand\", arg0)\n\tret0, _ := ret[0].(*model.Command)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// CreateCommand indicates an expected call of CreateCommand.\nfunc (mr *MockAPIMockRecorder) CreateCommand(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"CreateCommand\", reflect.TypeOf((*MockAPI)(nil).CreateCommand), arg0)\n}\n\n// CreateOAuthApp mocks base method.\nfunc (m *MockAPI) CreateOAuthApp(arg0 *model.OAuthApp) (*model.OAuthApp, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"CreateOAuthApp\", arg0)\n\tret0, _ := ret[0].(*model.OAuthApp)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// CreateOAuthApp indicates an expected call of CreateOAuthApp.\nfunc (mr *MockAPIMockRecorder) CreateOAuthApp(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"CreateOAuthApp\", reflect.TypeOf((*MockAPI)(nil).CreateOAuthApp), arg0)\n}\n\n// CreatePost mocks base method.\nfunc (m *MockAPI) CreatePost(arg0 *model.Post) (*model.Post, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"CreatePost\", arg0)\n\tret0, _ := ret[0].(*model.Post)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// CreatePost indicates an expected call of CreatePost.\nfunc (mr *MockAPIMockRecorder) CreatePost(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"CreatePost\", reflect.TypeOf((*MockAPI)(nil).CreatePost), arg0)\n}\n\n// CreateSession mocks base method.\nfunc (m *MockAPI) CreateSession(arg0 *model.Session) (*model.Session, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"CreateSession\", arg0)\n\tret0, _ := ret[0].(*model.Session)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// CreateSession indicates an expected call of CreateSession.\nfunc (mr *MockAPIMockRecorder) CreateSession(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"CreateSession\", reflect.TypeOf((*MockAPI)(nil).CreateSession), arg0)\n}\n\n// CreateTeam mocks base method.\nfunc (m *MockAPI) CreateTeam(arg0 *model.Team) (*model.Team, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"CreateTeam\", arg0)\n\tret0, _ := ret[0].(*model.Team)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// CreateTeam indicates an expected call of CreateTeam.\nfunc (mr *MockAPIMockRecorder) CreateTeam(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"CreateTeam\", reflect.TypeOf((*MockAPI)(nil).CreateTeam), arg0)\n}\n\n// CreateTeamMember mocks base method.\nfunc (m *MockAPI) CreateTeamMember(arg0, arg1 string) (*model.TeamMember, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"CreateTeamMember\", arg0, arg1)\n\tret0, _ := ret[0].(*model.TeamMember)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// CreateTeamMember indicates an expected call of CreateTeamMember.\nfunc (mr *MockAPIMockRecorder) CreateTeamMember(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"CreateTeamMember\", reflect.TypeOf((*MockAPI)(nil).CreateTeamMember), arg0, arg1)\n}\n\n// CreateTeamMembers mocks base method.\nfunc (m *MockAPI) CreateTeamMembers(arg0 string, arg1 []string, arg2 string) ([]*model.TeamMember, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"CreateTeamMembers\", arg0, arg1, arg2)\n\tret0, _ := ret[0].([]*model.TeamMember)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// CreateTeamMembers indicates an expected call of CreateTeamMembers.\nfunc (mr *MockAPIMockRecorder) CreateTeamMembers(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"CreateTeamMembers\", reflect.TypeOf((*MockAPI)(nil).CreateTeamMembers), arg0, arg1, arg2)\n}\n\n// CreateTeamMembersGracefully mocks base method.\nfunc (m *MockAPI) CreateTeamMembersGracefully(arg0 string, arg1 []string, arg2 string) ([]*model.TeamMemberWithError, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"CreateTeamMembersGracefully\", arg0, arg1, arg2)\n\tret0, _ := ret[0].([]*model.TeamMemberWithError)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// CreateTeamMembersGracefully indicates an expected call of CreateTeamMembersGracefully.\nfunc (mr *MockAPIMockRecorder) CreateTeamMembersGracefully(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"CreateTeamMembersGracefully\", reflect.TypeOf((*MockAPI)(nil).CreateTeamMembersGracefully), arg0, arg1, arg2)\n}\n\n// CreateUploadSession mocks base method.\nfunc (m *MockAPI) CreateUploadSession(arg0 *model.UploadSession) (*model.UploadSession, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"CreateUploadSession\", arg0)\n\tret0, _ := ret[0].(*model.UploadSession)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// CreateUploadSession indicates an expected call of CreateUploadSession.\nfunc (mr *MockAPIMockRecorder) CreateUploadSession(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"CreateUploadSession\", reflect.TypeOf((*MockAPI)(nil).CreateUploadSession), arg0)\n}\n\n// CreateUser mocks base method.\nfunc (m *MockAPI) CreateUser(arg0 *model.User) (*model.User, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"CreateUser\", arg0)\n\tret0, _ := ret[0].(*model.User)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// CreateUser indicates an expected call of CreateUser.\nfunc (mr *MockAPIMockRecorder) CreateUser(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"CreateUser\", reflect.TypeOf((*MockAPI)(nil).CreateUser), arg0)\n}\n\n// CreateUserAccessToken mocks base method.\nfunc (m *MockAPI) CreateUserAccessToken(arg0 *model.UserAccessToken) (*model.UserAccessToken, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"CreateUserAccessToken\", arg0)\n\tret0, _ := ret[0].(*model.UserAccessToken)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// CreateUserAccessToken indicates an expected call of CreateUserAccessToken.\nfunc (mr *MockAPIMockRecorder) CreateUserAccessToken(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"CreateUserAccessToken\", reflect.TypeOf((*MockAPI)(nil).CreateUserAccessToken), arg0)\n}\n\n// DeleteChannel mocks base method.\nfunc (m *MockAPI) DeleteChannel(arg0 string) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"DeleteChannel\", arg0)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// DeleteChannel indicates an expected call of DeleteChannel.\nfunc (mr *MockAPIMockRecorder) DeleteChannel(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"DeleteChannel\", reflect.TypeOf((*MockAPI)(nil).DeleteChannel), arg0)\n}\n\n// DeleteChannelMember mocks base method.\nfunc (m *MockAPI) DeleteChannelMember(arg0, arg1 string) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"DeleteChannelMember\", arg0, arg1)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// DeleteChannelMember indicates an expected call of DeleteChannelMember.\nfunc (mr *MockAPIMockRecorder) DeleteChannelMember(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"DeleteChannelMember\", reflect.TypeOf((*MockAPI)(nil).DeleteChannelMember), arg0, arg1)\n}\n\n// DeleteCommand mocks base method.\nfunc (m *MockAPI) DeleteCommand(arg0 string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"DeleteCommand\", arg0)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// DeleteCommand indicates an expected call of DeleteCommand.\nfunc (mr *MockAPIMockRecorder) DeleteCommand(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"DeleteCommand\", reflect.TypeOf((*MockAPI)(nil).DeleteCommand), arg0)\n}\n\n// DeleteEphemeralPost mocks base method.\nfunc (m *MockAPI) DeleteEphemeralPost(arg0, arg1 string) {\n\tm.ctrl.T.Helper()\n\tm.ctrl.Call(m, \"DeleteEphemeralPost\", arg0, arg1)\n}\n\n// DeleteEphemeralPost indicates an expected call of DeleteEphemeralPost.\nfunc (mr *MockAPIMockRecorder) DeleteEphemeralPost(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"DeleteEphemeralPost\", reflect.TypeOf((*MockAPI)(nil).DeleteEphemeralPost), arg0, arg1)\n}\n\n// DeleteOAuthApp mocks base method.\nfunc (m *MockAPI) DeleteOAuthApp(arg0 string) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"DeleteOAuthApp\", arg0)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// DeleteOAuthApp indicates an expected call of DeleteOAuthApp.\nfunc (mr *MockAPIMockRecorder) DeleteOAuthApp(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"DeleteOAuthApp\", reflect.TypeOf((*MockAPI)(nil).DeleteOAuthApp), arg0)\n}\n\n// DeletePost mocks base method.\nfunc (m *MockAPI) DeletePost(arg0 string) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"DeletePost\", arg0)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// DeletePost indicates an expected call of DeletePost.\nfunc (mr *MockAPIMockRecorder) DeletePost(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"DeletePost\", reflect.TypeOf((*MockAPI)(nil).DeletePost), arg0)\n}\n\n// DeletePreferencesForUser mocks base method.\nfunc (m *MockAPI) DeletePreferencesForUser(arg0 string, arg1 []model.Preference) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"DeletePreferencesForUser\", arg0, arg1)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// DeletePreferencesForUser indicates an expected call of DeletePreferencesForUser.\nfunc (mr *MockAPIMockRecorder) DeletePreferencesForUser(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"DeletePreferencesForUser\", reflect.TypeOf((*MockAPI)(nil).DeletePreferencesForUser), arg0, arg1)\n}\n\n// DeleteTeam mocks base method.\nfunc (m *MockAPI) DeleteTeam(arg0 string) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"DeleteTeam\", arg0)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// DeleteTeam indicates an expected call of DeleteTeam.\nfunc (mr *MockAPIMockRecorder) DeleteTeam(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"DeleteTeam\", reflect.TypeOf((*MockAPI)(nil).DeleteTeam), arg0)\n}\n\n// DeleteTeamMember mocks base method.\nfunc (m *MockAPI) DeleteTeamMember(arg0, arg1, arg2 string) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"DeleteTeamMember\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// DeleteTeamMember indicates an expected call of DeleteTeamMember.\nfunc (mr *MockAPIMockRecorder) DeleteTeamMember(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"DeleteTeamMember\", reflect.TypeOf((*MockAPI)(nil).DeleteTeamMember), arg0, arg1, arg2)\n}\n\n// DeleteUser mocks base method.\nfunc (m *MockAPI) DeleteUser(arg0 string) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"DeleteUser\", arg0)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// DeleteUser indicates an expected call of DeleteUser.\nfunc (mr *MockAPIMockRecorder) DeleteUser(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"DeleteUser\", reflect.TypeOf((*MockAPI)(nil).DeleteUser), arg0)\n}\n\n// DisablePlugin mocks base method.\nfunc (m *MockAPI) DisablePlugin(arg0 string) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"DisablePlugin\", arg0)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// DisablePlugin indicates an expected call of DisablePlugin.\nfunc (mr *MockAPIMockRecorder) DisablePlugin(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"DisablePlugin\", reflect.TypeOf((*MockAPI)(nil).DisablePlugin), arg0)\n}\n\n// EnablePlugin mocks base method.\nfunc (m *MockAPI) EnablePlugin(arg0 string) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"EnablePlugin\", arg0)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// EnablePlugin indicates an expected call of EnablePlugin.\nfunc (mr *MockAPIMockRecorder) EnablePlugin(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"EnablePlugin\", reflect.TypeOf((*MockAPI)(nil).EnablePlugin), arg0)\n}\n\n// EnsureBotUser mocks base method.\nfunc (m *MockAPI) EnsureBotUser(arg0 *model.Bot) (string, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"EnsureBotUser\", arg0)\n\tret0, _ := ret[0].(string)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// EnsureBotUser indicates an expected call of EnsureBotUser.\nfunc (mr *MockAPIMockRecorder) EnsureBotUser(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"EnsureBotUser\", reflect.TypeOf((*MockAPI)(nil).EnsureBotUser), arg0)\n}\n\n// ExecuteSlashCommand mocks base method.\nfunc (m *MockAPI) ExecuteSlashCommand(arg0 *model.CommandArgs) (*model.CommandResponse, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ExecuteSlashCommand\", arg0)\n\tret0, _ := ret[0].(*model.CommandResponse)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ExecuteSlashCommand indicates an expected call of ExecuteSlashCommand.\nfunc (mr *MockAPIMockRecorder) ExecuteSlashCommand(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ExecuteSlashCommand\", reflect.TypeOf((*MockAPI)(nil).ExecuteSlashCommand), arg0)\n}\n\n// ExtendSessionExpiry mocks base method.\nfunc (m *MockAPI) ExtendSessionExpiry(arg0 string, arg1 int64) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ExtendSessionExpiry\", arg0, arg1)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// ExtendSessionExpiry indicates an expected call of ExtendSessionExpiry.\nfunc (mr *MockAPIMockRecorder) ExtendSessionExpiry(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ExtendSessionExpiry\", reflect.TypeOf((*MockAPI)(nil).ExtendSessionExpiry), arg0, arg1)\n}\n\n// GetBot mocks base method.\nfunc (m *MockAPI) GetBot(arg0 string, arg1 bool) (*model.Bot, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetBot\", arg0, arg1)\n\tret0, _ := ret[0].(*model.Bot)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetBot indicates an expected call of GetBot.\nfunc (mr *MockAPIMockRecorder) GetBot(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetBot\", reflect.TypeOf((*MockAPI)(nil).GetBot), arg0, arg1)\n}\n\n// GetBots mocks base method.\nfunc (m *MockAPI) GetBots(arg0 *model.BotGetOptions) ([]*model.Bot, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetBots\", arg0)\n\tret0, _ := ret[0].([]*model.Bot)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetBots indicates an expected call of GetBots.\nfunc (mr *MockAPIMockRecorder) GetBots(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetBots\", reflect.TypeOf((*MockAPI)(nil).GetBots), arg0)\n}\n\n// GetBundlePath mocks base method.\nfunc (m *MockAPI) GetBundlePath() (string, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetBundlePath\")\n\tret0, _ := ret[0].(string)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetBundlePath indicates an expected call of GetBundlePath.\nfunc (mr *MockAPIMockRecorder) GetBundlePath() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetBundlePath\", reflect.TypeOf((*MockAPI)(nil).GetBundlePath))\n}\n\n// GetChannel mocks base method.\nfunc (m *MockAPI) GetChannel(arg0 string) (*model.Channel, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetChannel\", arg0)\n\tret0, _ := ret[0].(*model.Channel)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetChannel indicates an expected call of GetChannel.\nfunc (mr *MockAPIMockRecorder) GetChannel(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetChannel\", reflect.TypeOf((*MockAPI)(nil).GetChannel), arg0)\n}\n\n// GetChannelByName mocks base method.\nfunc (m *MockAPI) GetChannelByName(arg0, arg1 string, arg2 bool) (*model.Channel, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetChannelByName\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(*model.Channel)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetChannelByName indicates an expected call of GetChannelByName.\nfunc (mr *MockAPIMockRecorder) GetChannelByName(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetChannelByName\", reflect.TypeOf((*MockAPI)(nil).GetChannelByName), arg0, arg1, arg2)\n}\n\n// GetChannelByNameForTeamName mocks base method.\nfunc (m *MockAPI) GetChannelByNameForTeamName(arg0, arg1 string, arg2 bool) (*model.Channel, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetChannelByNameForTeamName\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(*model.Channel)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetChannelByNameForTeamName indicates an expected call of GetChannelByNameForTeamName.\nfunc (mr *MockAPIMockRecorder) GetChannelByNameForTeamName(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetChannelByNameForTeamName\", reflect.TypeOf((*MockAPI)(nil).GetChannelByNameForTeamName), arg0, arg1, arg2)\n}\n\n// GetChannelMember mocks base method.\nfunc (m *MockAPI) GetChannelMember(arg0, arg1 string) (*model.ChannelMember, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetChannelMember\", arg0, arg1)\n\tret0, _ := ret[0].(*model.ChannelMember)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetChannelMember indicates an expected call of GetChannelMember.\nfunc (mr *MockAPIMockRecorder) GetChannelMember(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetChannelMember\", reflect.TypeOf((*MockAPI)(nil).GetChannelMember), arg0, arg1)\n}\n\n// GetChannelMembers mocks base method.\nfunc (m *MockAPI) GetChannelMembers(arg0 string, arg1, arg2 int) (model.ChannelMembers, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetChannelMembers\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(model.ChannelMembers)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetChannelMembers indicates an expected call of GetChannelMembers.\nfunc (mr *MockAPIMockRecorder) GetChannelMembers(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetChannelMembers\", reflect.TypeOf((*MockAPI)(nil).GetChannelMembers), arg0, arg1, arg2)\n}\n\n// GetChannelMembersByIds mocks base method.\nfunc (m *MockAPI) GetChannelMembersByIds(arg0 string, arg1 []string) (model.ChannelMembers, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetChannelMembersByIds\", arg0, arg1)\n\tret0, _ := ret[0].(model.ChannelMembers)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetChannelMembersByIds indicates an expected call of GetChannelMembersByIds.\nfunc (mr *MockAPIMockRecorder) GetChannelMembersByIds(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetChannelMembersByIds\", reflect.TypeOf((*MockAPI)(nil).GetChannelMembersByIds), arg0, arg1)\n}\n\n// GetChannelMembersForUser mocks base method.\nfunc (m *MockAPI) GetChannelMembersForUser(arg0, arg1 string, arg2, arg3 int) ([]*model.ChannelMember, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetChannelMembersForUser\", arg0, arg1, arg2, arg3)\n\tret0, _ := ret[0].([]*model.ChannelMember)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetChannelMembersForUser indicates an expected call of GetChannelMembersForUser.\nfunc (mr *MockAPIMockRecorder) GetChannelMembersForUser(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetChannelMembersForUser\", reflect.TypeOf((*MockAPI)(nil).GetChannelMembersForUser), arg0, arg1, arg2, arg3)\n}\n\n// GetChannelSidebarCategories mocks base method.\nfunc (m *MockAPI) GetChannelSidebarCategories(arg0, arg1 string) (*model.OrderedSidebarCategories, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetChannelSidebarCategories\", arg0, arg1)\n\tret0, _ := ret[0].(*model.OrderedSidebarCategories)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetChannelSidebarCategories indicates an expected call of GetChannelSidebarCategories.\nfunc (mr *MockAPIMockRecorder) GetChannelSidebarCategories(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetChannelSidebarCategories\", reflect.TypeOf((*MockAPI)(nil).GetChannelSidebarCategories), arg0, arg1)\n}\n\n// GetChannelStats mocks base method.\nfunc (m *MockAPI) GetChannelStats(arg0 string) (*model.ChannelStats, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetChannelStats\", arg0)\n\tret0, _ := ret[0].(*model.ChannelStats)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetChannelStats indicates an expected call of GetChannelStats.\nfunc (mr *MockAPIMockRecorder) GetChannelStats(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetChannelStats\", reflect.TypeOf((*MockAPI)(nil).GetChannelStats), arg0)\n}\n\n// GetChannelsForTeamForUser mocks base method.\nfunc (m *MockAPI) GetChannelsForTeamForUser(arg0, arg1 string, arg2 bool) ([]*model.Channel, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetChannelsForTeamForUser\", arg0, arg1, arg2)\n\tret0, _ := ret[0].([]*model.Channel)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetChannelsForTeamForUser indicates an expected call of GetChannelsForTeamForUser.\nfunc (mr *MockAPIMockRecorder) GetChannelsForTeamForUser(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetChannelsForTeamForUser\", reflect.TypeOf((*MockAPI)(nil).GetChannelsForTeamForUser), arg0, arg1, arg2)\n}\n\n// GetCommand mocks base method.\nfunc (m *MockAPI) GetCommand(arg0 string) (*model.Command, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetCommand\", arg0)\n\tret0, _ := ret[0].(*model.Command)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetCommand indicates an expected call of GetCommand.\nfunc (mr *MockAPIMockRecorder) GetCommand(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetCommand\", reflect.TypeOf((*MockAPI)(nil).GetCommand), arg0)\n}\n\n// GetConfig mocks base method.\nfunc (m *MockAPI) GetConfig() *model.Config {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetConfig\")\n\tret0, _ := ret[0].(*model.Config)\n\treturn ret0\n}\n\n// GetConfig indicates an expected call of GetConfig.\nfunc (mr *MockAPIMockRecorder) GetConfig() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetConfig\", reflect.TypeOf((*MockAPI)(nil).GetConfig))\n}\n\n// GetDiagnosticId mocks base method.\nfunc (m *MockAPI) GetDiagnosticId() string {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetDiagnosticId\")\n\tret0, _ := ret[0].(string)\n\treturn ret0\n}\n\n// GetDiagnosticId indicates an expected call of GetDiagnosticId.\nfunc (mr *MockAPIMockRecorder) GetDiagnosticId() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetDiagnosticId\", reflect.TypeOf((*MockAPI)(nil).GetDiagnosticId))\n}\n\n// GetDirectChannel mocks base method.\nfunc (m *MockAPI) GetDirectChannel(arg0, arg1 string) (*model.Channel, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetDirectChannel\", arg0, arg1)\n\tret0, _ := ret[0].(*model.Channel)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetDirectChannel indicates an expected call of GetDirectChannel.\nfunc (mr *MockAPIMockRecorder) GetDirectChannel(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetDirectChannel\", reflect.TypeOf((*MockAPI)(nil).GetDirectChannel), arg0, arg1)\n}\n\n// GetEmoji mocks base method.\nfunc (m *MockAPI) GetEmoji(arg0 string) (*model.Emoji, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetEmoji\", arg0)\n\tret0, _ := ret[0].(*model.Emoji)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetEmoji indicates an expected call of GetEmoji.\nfunc (mr *MockAPIMockRecorder) GetEmoji(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetEmoji\", reflect.TypeOf((*MockAPI)(nil).GetEmoji), arg0)\n}\n\n// GetEmojiByName mocks base method.\nfunc (m *MockAPI) GetEmojiByName(arg0 string) (*model.Emoji, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetEmojiByName\", arg0)\n\tret0, _ := ret[0].(*model.Emoji)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetEmojiByName indicates an expected call of GetEmojiByName.\nfunc (mr *MockAPIMockRecorder) GetEmojiByName(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetEmojiByName\", reflect.TypeOf((*MockAPI)(nil).GetEmojiByName), arg0)\n}\n\n// GetEmojiImage mocks base method.\nfunc (m *MockAPI) GetEmojiImage(arg0 string) ([]byte, string, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetEmojiImage\", arg0)\n\tret0, _ := ret[0].([]byte)\n\tret1, _ := ret[1].(string)\n\tret2, _ := ret[2].(*model.AppError)\n\treturn ret0, ret1, ret2\n}\n\n// GetEmojiImage indicates an expected call of GetEmojiImage.\nfunc (mr *MockAPIMockRecorder) GetEmojiImage(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetEmojiImage\", reflect.TypeOf((*MockAPI)(nil).GetEmojiImage), arg0)\n}\n\n// GetEmojiList mocks base method.\nfunc (m *MockAPI) GetEmojiList(arg0 string, arg1, arg2 int) ([]*model.Emoji, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetEmojiList\", arg0, arg1, arg2)\n\tret0, _ := ret[0].([]*model.Emoji)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetEmojiList indicates an expected call of GetEmojiList.\nfunc (mr *MockAPIMockRecorder) GetEmojiList(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetEmojiList\", reflect.TypeOf((*MockAPI)(nil).GetEmojiList), arg0, arg1, arg2)\n}\n\n// GetFile mocks base method.\nfunc (m *MockAPI) GetFile(arg0 string) ([]byte, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetFile\", arg0)\n\tret0, _ := ret[0].([]byte)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetFile indicates an expected call of GetFile.\nfunc (mr *MockAPIMockRecorder) GetFile(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetFile\", reflect.TypeOf((*MockAPI)(nil).GetFile), arg0)\n}\n\n// GetFileInfo mocks base method.\nfunc (m *MockAPI) GetFileInfo(arg0 string) (*model.FileInfo, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetFileInfo\", arg0)\n\tret0, _ := ret[0].(*model.FileInfo)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetFileInfo indicates an expected call of GetFileInfo.\nfunc (mr *MockAPIMockRecorder) GetFileInfo(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetFileInfo\", reflect.TypeOf((*MockAPI)(nil).GetFileInfo), arg0)\n}\n\n// GetFileInfos mocks base method.\nfunc (m *MockAPI) GetFileInfos(arg0, arg1 int, arg2 *model.GetFileInfosOptions) ([]*model.FileInfo, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetFileInfos\", arg0, arg1, arg2)\n\tret0, _ := ret[0].([]*model.FileInfo)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetFileInfos indicates an expected call of GetFileInfos.\nfunc (mr *MockAPIMockRecorder) GetFileInfos(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetFileInfos\", reflect.TypeOf((*MockAPI)(nil).GetFileInfos), arg0, arg1, arg2)\n}\n\n// GetFileLink mocks base method.\nfunc (m *MockAPI) GetFileLink(arg0 string) (string, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetFileLink\", arg0)\n\tret0, _ := ret[0].(string)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetFileLink indicates an expected call of GetFileLink.\nfunc (mr *MockAPIMockRecorder) GetFileLink(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetFileLink\", reflect.TypeOf((*MockAPI)(nil).GetFileLink), arg0)\n}\n\n// GetGroup mocks base method.\nfunc (m *MockAPI) GetGroup(arg0 string) (*model.Group, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetGroup\", arg0)\n\tret0, _ := ret[0].(*model.Group)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetGroup indicates an expected call of GetGroup.\nfunc (mr *MockAPIMockRecorder) GetGroup(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetGroup\", reflect.TypeOf((*MockAPI)(nil).GetGroup), arg0)\n}\n\n// GetGroupByName mocks base method.\nfunc (m *MockAPI) GetGroupByName(arg0 string) (*model.Group, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetGroupByName\", arg0)\n\tret0, _ := ret[0].(*model.Group)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetGroupByName indicates an expected call of GetGroupByName.\nfunc (mr *MockAPIMockRecorder) GetGroupByName(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetGroupByName\", reflect.TypeOf((*MockAPI)(nil).GetGroupByName), arg0)\n}\n\n// GetGroupChannel mocks base method.\nfunc (m *MockAPI) GetGroupChannel(arg0 []string) (*model.Channel, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetGroupChannel\", arg0)\n\tret0, _ := ret[0].(*model.Channel)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetGroupChannel indicates an expected call of GetGroupChannel.\nfunc (mr *MockAPIMockRecorder) GetGroupChannel(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetGroupChannel\", reflect.TypeOf((*MockAPI)(nil).GetGroupChannel), arg0)\n}\n\n// GetGroupMemberUsers mocks base method.\nfunc (m *MockAPI) GetGroupMemberUsers(arg0 string, arg1, arg2 int) ([]*model.User, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetGroupMemberUsers\", arg0, arg1, arg2)\n\tret0, _ := ret[0].([]*model.User)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetGroupMemberUsers indicates an expected call of GetGroupMemberUsers.\nfunc (mr *MockAPIMockRecorder) GetGroupMemberUsers(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetGroupMemberUsers\", reflect.TypeOf((*MockAPI)(nil).GetGroupMemberUsers), arg0, arg1, arg2)\n}\n\n// GetGroupsBySource mocks base method.\nfunc (m *MockAPI) GetGroupsBySource(arg0 model.GroupSource) ([]*model.Group, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetGroupsBySource\", arg0)\n\tret0, _ := ret[0].([]*model.Group)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetGroupsBySource indicates an expected call of GetGroupsBySource.\nfunc (mr *MockAPIMockRecorder) GetGroupsBySource(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetGroupsBySource\", reflect.TypeOf((*MockAPI)(nil).GetGroupsBySource), arg0)\n}\n\n// GetGroupsForUser mocks base method.\nfunc (m *MockAPI) GetGroupsForUser(arg0 string) ([]*model.Group, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetGroupsForUser\", arg0)\n\tret0, _ := ret[0].([]*model.Group)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetGroupsForUser indicates an expected call of GetGroupsForUser.\nfunc (mr *MockAPIMockRecorder) GetGroupsForUser(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetGroupsForUser\", reflect.TypeOf((*MockAPI)(nil).GetGroupsForUser), arg0)\n}\n\n// GetLDAPUserAttributes mocks base method.\nfunc (m *MockAPI) GetLDAPUserAttributes(arg0 string, arg1 []string) (map[string]string, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetLDAPUserAttributes\", arg0, arg1)\n\tret0, _ := ret[0].(map[string]string)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetLDAPUserAttributes indicates an expected call of GetLDAPUserAttributes.\nfunc (mr *MockAPIMockRecorder) GetLDAPUserAttributes(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetLDAPUserAttributes\", reflect.TypeOf((*MockAPI)(nil).GetLDAPUserAttributes), arg0, arg1)\n}\n\n// GetLicense mocks base method.\nfunc (m *MockAPI) GetLicense() *model.License {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetLicense\")\n\tret0, _ := ret[0].(*model.License)\n\treturn ret0\n}\n\n// GetLicense indicates an expected call of GetLicense.\nfunc (mr *MockAPIMockRecorder) GetLicense() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetLicense\", reflect.TypeOf((*MockAPI)(nil).GetLicense))\n}\n\n// GetOAuthApp mocks base method.\nfunc (m *MockAPI) GetOAuthApp(arg0 string) (*model.OAuthApp, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetOAuthApp\", arg0)\n\tret0, _ := ret[0].(*model.OAuthApp)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetOAuthApp indicates an expected call of GetOAuthApp.\nfunc (mr *MockAPIMockRecorder) GetOAuthApp(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetOAuthApp\", reflect.TypeOf((*MockAPI)(nil).GetOAuthApp), arg0)\n}\n\n// GetPluginConfig mocks base method.\nfunc (m *MockAPI) GetPluginConfig() map[string]interface{} {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetPluginConfig\")\n\tret0, _ := ret[0].(map[string]interface{})\n\treturn ret0\n}\n\n// GetPluginConfig indicates an expected call of GetPluginConfig.\nfunc (mr *MockAPIMockRecorder) GetPluginConfig() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetPluginConfig\", reflect.TypeOf((*MockAPI)(nil).GetPluginConfig))\n}\n\n// GetPluginStatus mocks base method.\nfunc (m *MockAPI) GetPluginStatus(arg0 string) (*model.PluginStatus, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetPluginStatus\", arg0)\n\tret0, _ := ret[0].(*model.PluginStatus)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetPluginStatus indicates an expected call of GetPluginStatus.\nfunc (mr *MockAPIMockRecorder) GetPluginStatus(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetPluginStatus\", reflect.TypeOf((*MockAPI)(nil).GetPluginStatus), arg0)\n}\n\n// GetPlugins mocks base method.\nfunc (m *MockAPI) GetPlugins() ([]*model.Manifest, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetPlugins\")\n\tret0, _ := ret[0].([]*model.Manifest)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetPlugins indicates an expected call of GetPlugins.\nfunc (mr *MockAPIMockRecorder) GetPlugins() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetPlugins\", reflect.TypeOf((*MockAPI)(nil).GetPlugins))\n}\n\n// GetPost mocks base method.\nfunc (m *MockAPI) GetPost(arg0 string) (*model.Post, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetPost\", arg0)\n\tret0, _ := ret[0].(*model.Post)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetPost indicates an expected call of GetPost.\nfunc (mr *MockAPIMockRecorder) GetPost(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetPost\", reflect.TypeOf((*MockAPI)(nil).GetPost), arg0)\n}\n\n// GetPostThread mocks base method.\nfunc (m *MockAPI) GetPostThread(arg0 string) (*model.PostList, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetPostThread\", arg0)\n\tret0, _ := ret[0].(*model.PostList)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetPostThread indicates an expected call of GetPostThread.\nfunc (mr *MockAPIMockRecorder) GetPostThread(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetPostThread\", reflect.TypeOf((*MockAPI)(nil).GetPostThread), arg0)\n}\n\n// GetPostsAfter mocks base method.\nfunc (m *MockAPI) GetPostsAfter(arg0, arg1 string, arg2, arg3 int) (*model.PostList, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetPostsAfter\", arg0, arg1, arg2, arg3)\n\tret0, _ := ret[0].(*model.PostList)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetPostsAfter indicates an expected call of GetPostsAfter.\nfunc (mr *MockAPIMockRecorder) GetPostsAfter(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetPostsAfter\", reflect.TypeOf((*MockAPI)(nil).GetPostsAfter), arg0, arg1, arg2, arg3)\n}\n\n// GetPostsBefore mocks base method.\nfunc (m *MockAPI) GetPostsBefore(arg0, arg1 string, arg2, arg3 int) (*model.PostList, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetPostsBefore\", arg0, arg1, arg2, arg3)\n\tret0, _ := ret[0].(*model.PostList)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetPostsBefore indicates an expected call of GetPostsBefore.\nfunc (mr *MockAPIMockRecorder) GetPostsBefore(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetPostsBefore\", reflect.TypeOf((*MockAPI)(nil).GetPostsBefore), arg0, arg1, arg2, arg3)\n}\n\n// GetPostsForChannel mocks base method.\nfunc (m *MockAPI) GetPostsForChannel(arg0 string, arg1, arg2 int) (*model.PostList, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetPostsForChannel\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(*model.PostList)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetPostsForChannel indicates an expected call of GetPostsForChannel.\nfunc (mr *MockAPIMockRecorder) GetPostsForChannel(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetPostsForChannel\", reflect.TypeOf((*MockAPI)(nil).GetPostsForChannel), arg0, arg1, arg2)\n}\n\n// GetPostsSince mocks base method.\nfunc (m *MockAPI) GetPostsSince(arg0 string, arg1 int64) (*model.PostList, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetPostsSince\", arg0, arg1)\n\tret0, _ := ret[0].(*model.PostList)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetPostsSince indicates an expected call of GetPostsSince.\nfunc (mr *MockAPIMockRecorder) GetPostsSince(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetPostsSince\", reflect.TypeOf((*MockAPI)(nil).GetPostsSince), arg0, arg1)\n}\n\n// GetPreferencesForUser mocks base method.\nfunc (m *MockAPI) GetPreferencesForUser(arg0 string) ([]model.Preference, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetPreferencesForUser\", arg0)\n\tret0, _ := ret[0].([]model.Preference)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetPreferencesForUser indicates an expected call of GetPreferencesForUser.\nfunc (mr *MockAPIMockRecorder) GetPreferencesForUser(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetPreferencesForUser\", reflect.TypeOf((*MockAPI)(nil).GetPreferencesForUser), arg0)\n}\n\n// GetProfileImage mocks base method.\nfunc (m *MockAPI) GetProfileImage(arg0 string) ([]byte, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetProfileImage\", arg0)\n\tret0, _ := ret[0].([]byte)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetProfileImage indicates an expected call of GetProfileImage.\nfunc (mr *MockAPIMockRecorder) GetProfileImage(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetProfileImage\", reflect.TypeOf((*MockAPI)(nil).GetProfileImage), arg0)\n}\n\n// GetPublicChannelsForTeam mocks base method.\nfunc (m *MockAPI) GetPublicChannelsForTeam(arg0 string, arg1, arg2 int) ([]*model.Channel, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetPublicChannelsForTeam\", arg0, arg1, arg2)\n\tret0, _ := ret[0].([]*model.Channel)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetPublicChannelsForTeam indicates an expected call of GetPublicChannelsForTeam.\nfunc (mr *MockAPIMockRecorder) GetPublicChannelsForTeam(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetPublicChannelsForTeam\", reflect.TypeOf((*MockAPI)(nil).GetPublicChannelsForTeam), arg0, arg1, arg2)\n}\n\n// GetReactions mocks base method.\nfunc (m *MockAPI) GetReactions(arg0 string) ([]*model.Reaction, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetReactions\", arg0)\n\tret0, _ := ret[0].([]*model.Reaction)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetReactions indicates an expected call of GetReactions.\nfunc (mr *MockAPIMockRecorder) GetReactions(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetReactions\", reflect.TypeOf((*MockAPI)(nil).GetReactions), arg0)\n}\n\n// GetServerVersion mocks base method.\nfunc (m *MockAPI) GetServerVersion() string {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetServerVersion\")\n\tret0, _ := ret[0].(string)\n\treturn ret0\n}\n\n// GetServerVersion indicates an expected call of GetServerVersion.\nfunc (mr *MockAPIMockRecorder) GetServerVersion() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetServerVersion\", reflect.TypeOf((*MockAPI)(nil).GetServerVersion))\n}\n\n// GetSession mocks base method.\nfunc (m *MockAPI) GetSession(arg0 string) (*model.Session, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetSession\", arg0)\n\tret0, _ := ret[0].(*model.Session)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetSession indicates an expected call of GetSession.\nfunc (mr *MockAPIMockRecorder) GetSession(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetSession\", reflect.TypeOf((*MockAPI)(nil).GetSession), arg0)\n}\n\n// GetSystemInstallDate mocks base method.\nfunc (m *MockAPI) GetSystemInstallDate() (int64, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetSystemInstallDate\")\n\tret0, _ := ret[0].(int64)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetSystemInstallDate indicates an expected call of GetSystemInstallDate.\nfunc (mr *MockAPIMockRecorder) GetSystemInstallDate() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetSystemInstallDate\", reflect.TypeOf((*MockAPI)(nil).GetSystemInstallDate))\n}\n\n// GetTeam mocks base method.\nfunc (m *MockAPI) GetTeam(arg0 string) (*model.Team, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetTeam\", arg0)\n\tret0, _ := ret[0].(*model.Team)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetTeam indicates an expected call of GetTeam.\nfunc (mr *MockAPIMockRecorder) GetTeam(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetTeam\", reflect.TypeOf((*MockAPI)(nil).GetTeam), arg0)\n}\n\n// GetTeamByName mocks base method.\nfunc (m *MockAPI) GetTeamByName(arg0 string) (*model.Team, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetTeamByName\", arg0)\n\tret0, _ := ret[0].(*model.Team)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetTeamByName indicates an expected call of GetTeamByName.\nfunc (mr *MockAPIMockRecorder) GetTeamByName(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetTeamByName\", reflect.TypeOf((*MockAPI)(nil).GetTeamByName), arg0)\n}\n\n// GetTeamIcon mocks base method.\nfunc (m *MockAPI) GetTeamIcon(arg0 string) ([]byte, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetTeamIcon\", arg0)\n\tret0, _ := ret[0].([]byte)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetTeamIcon indicates an expected call of GetTeamIcon.\nfunc (mr *MockAPIMockRecorder) GetTeamIcon(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetTeamIcon\", reflect.TypeOf((*MockAPI)(nil).GetTeamIcon), arg0)\n}\n\n// GetTeamMember mocks base method.\nfunc (m *MockAPI) GetTeamMember(arg0, arg1 string) (*model.TeamMember, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetTeamMember\", arg0, arg1)\n\tret0, _ := ret[0].(*model.TeamMember)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetTeamMember indicates an expected call of GetTeamMember.\nfunc (mr *MockAPIMockRecorder) GetTeamMember(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetTeamMember\", reflect.TypeOf((*MockAPI)(nil).GetTeamMember), arg0, arg1)\n}\n\n// GetTeamMembers mocks base method.\nfunc (m *MockAPI) GetTeamMembers(arg0 string, arg1, arg2 int) ([]*model.TeamMember, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetTeamMembers\", arg0, arg1, arg2)\n\tret0, _ := ret[0].([]*model.TeamMember)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetTeamMembers indicates an expected call of GetTeamMembers.\nfunc (mr *MockAPIMockRecorder) GetTeamMembers(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetTeamMembers\", reflect.TypeOf((*MockAPI)(nil).GetTeamMembers), arg0, arg1, arg2)\n}\n\n// GetTeamMembersForUser mocks base method.\nfunc (m *MockAPI) GetTeamMembersForUser(arg0 string, arg1, arg2 int) ([]*model.TeamMember, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetTeamMembersForUser\", arg0, arg1, arg2)\n\tret0, _ := ret[0].([]*model.TeamMember)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetTeamMembersForUser indicates an expected call of GetTeamMembersForUser.\nfunc (mr *MockAPIMockRecorder) GetTeamMembersForUser(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetTeamMembersForUser\", reflect.TypeOf((*MockAPI)(nil).GetTeamMembersForUser), arg0, arg1, arg2)\n}\n\n// GetTeamStats mocks base method.\nfunc (m *MockAPI) GetTeamStats(arg0 string) (*model.TeamStats, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetTeamStats\", arg0)\n\tret0, _ := ret[0].(*model.TeamStats)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetTeamStats indicates an expected call of GetTeamStats.\nfunc (mr *MockAPIMockRecorder) GetTeamStats(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetTeamStats\", reflect.TypeOf((*MockAPI)(nil).GetTeamStats), arg0)\n}\n\n// GetTeams mocks base method.\nfunc (m *MockAPI) GetTeams() ([]*model.Team, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetTeams\")\n\tret0, _ := ret[0].([]*model.Team)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetTeams indicates an expected call of GetTeams.\nfunc (mr *MockAPIMockRecorder) GetTeams() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetTeams\", reflect.TypeOf((*MockAPI)(nil).GetTeams))\n}\n\n// GetTeamsForUser mocks base method.\nfunc (m *MockAPI) GetTeamsForUser(arg0 string) ([]*model.Team, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetTeamsForUser\", arg0)\n\tret0, _ := ret[0].([]*model.Team)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetTeamsForUser indicates an expected call of GetTeamsForUser.\nfunc (mr *MockAPIMockRecorder) GetTeamsForUser(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetTeamsForUser\", reflect.TypeOf((*MockAPI)(nil).GetTeamsForUser), arg0)\n}\n\n// GetTeamsUnreadForUser mocks base method.\nfunc (m *MockAPI) GetTeamsUnreadForUser(arg0 string) ([]*model.TeamUnread, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetTeamsUnreadForUser\", arg0)\n\tret0, _ := ret[0].([]*model.TeamUnread)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetTeamsUnreadForUser indicates an expected call of GetTeamsUnreadForUser.\nfunc (mr *MockAPIMockRecorder) GetTeamsUnreadForUser(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetTeamsUnreadForUser\", reflect.TypeOf((*MockAPI)(nil).GetTeamsUnreadForUser), arg0)\n}\n\n// GetTelemetryId mocks base method.\nfunc (m *MockAPI) GetTelemetryId() string {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetTelemetryId\")\n\tret0, _ := ret[0].(string)\n\treturn ret0\n}\n\n// GetTelemetryId indicates an expected call of GetTelemetryId.\nfunc (mr *MockAPIMockRecorder) GetTelemetryId() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetTelemetryId\", reflect.TypeOf((*MockAPI)(nil).GetTelemetryId))\n}\n\n// GetUnsanitizedConfig mocks base method.\nfunc (m *MockAPI) GetUnsanitizedConfig() *model.Config {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetUnsanitizedConfig\")\n\tret0, _ := ret[0].(*model.Config)\n\treturn ret0\n}\n\n// GetUnsanitizedConfig indicates an expected call of GetUnsanitizedConfig.\nfunc (mr *MockAPIMockRecorder) GetUnsanitizedConfig() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetUnsanitizedConfig\", reflect.TypeOf((*MockAPI)(nil).GetUnsanitizedConfig))\n}\n\n// GetUploadSession mocks base method.\nfunc (m *MockAPI) GetUploadSession(arg0 string) (*model.UploadSession, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetUploadSession\", arg0)\n\tret0, _ := ret[0].(*model.UploadSession)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetUploadSession indicates an expected call of GetUploadSession.\nfunc (mr *MockAPIMockRecorder) GetUploadSession(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetUploadSession\", reflect.TypeOf((*MockAPI)(nil).GetUploadSession), arg0)\n}\n\n// GetUser mocks base method.\nfunc (m *MockAPI) GetUser(arg0 string) (*model.User, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetUser\", arg0)\n\tret0, _ := ret[0].(*model.User)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetUser indicates an expected call of GetUser.\nfunc (mr *MockAPIMockRecorder) GetUser(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetUser\", reflect.TypeOf((*MockAPI)(nil).GetUser), arg0)\n}\n\n// GetUserByEmail mocks base method.\nfunc (m *MockAPI) GetUserByEmail(arg0 string) (*model.User, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetUserByEmail\", arg0)\n\tret0, _ := ret[0].(*model.User)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetUserByEmail indicates an expected call of GetUserByEmail.\nfunc (mr *MockAPIMockRecorder) GetUserByEmail(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetUserByEmail\", reflect.TypeOf((*MockAPI)(nil).GetUserByEmail), arg0)\n}\n\n// GetUserByUsername mocks base method.\nfunc (m *MockAPI) GetUserByUsername(arg0 string) (*model.User, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetUserByUsername\", arg0)\n\tret0, _ := ret[0].(*model.User)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetUserByUsername indicates an expected call of GetUserByUsername.\nfunc (mr *MockAPIMockRecorder) GetUserByUsername(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetUserByUsername\", reflect.TypeOf((*MockAPI)(nil).GetUserByUsername), arg0)\n}\n\n// GetUserStatus mocks base method.\nfunc (m *MockAPI) GetUserStatus(arg0 string) (*model.Status, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetUserStatus\", arg0)\n\tret0, _ := ret[0].(*model.Status)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetUserStatus indicates an expected call of GetUserStatus.\nfunc (mr *MockAPIMockRecorder) GetUserStatus(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetUserStatus\", reflect.TypeOf((*MockAPI)(nil).GetUserStatus), arg0)\n}\n\n// GetUserStatusesByIds mocks base method.\nfunc (m *MockAPI) GetUserStatusesByIds(arg0 []string) ([]*model.Status, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetUserStatusesByIds\", arg0)\n\tret0, _ := ret[0].([]*model.Status)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetUserStatusesByIds indicates an expected call of GetUserStatusesByIds.\nfunc (mr *MockAPIMockRecorder) GetUserStatusesByIds(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetUserStatusesByIds\", reflect.TypeOf((*MockAPI)(nil).GetUserStatusesByIds), arg0)\n}\n\n// GetUsers mocks base method.\nfunc (m *MockAPI) GetUsers(arg0 *model.UserGetOptions) ([]*model.User, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetUsers\", arg0)\n\tret0, _ := ret[0].([]*model.User)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetUsers indicates an expected call of GetUsers.\nfunc (mr *MockAPIMockRecorder) GetUsers(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetUsers\", reflect.TypeOf((*MockAPI)(nil).GetUsers), arg0)\n}\n\n// GetUsersByUsernames mocks base method.\nfunc (m *MockAPI) GetUsersByUsernames(arg0 []string) ([]*model.User, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetUsersByUsernames\", arg0)\n\tret0, _ := ret[0].([]*model.User)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetUsersByUsernames indicates an expected call of GetUsersByUsernames.\nfunc (mr *MockAPIMockRecorder) GetUsersByUsernames(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetUsersByUsernames\", reflect.TypeOf((*MockAPI)(nil).GetUsersByUsernames), arg0)\n}\n\n// GetUsersInChannel mocks base method.\nfunc (m *MockAPI) GetUsersInChannel(arg0, arg1 string, arg2, arg3 int) ([]*model.User, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetUsersInChannel\", arg0, arg1, arg2, arg3)\n\tret0, _ := ret[0].([]*model.User)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetUsersInChannel indicates an expected call of GetUsersInChannel.\nfunc (mr *MockAPIMockRecorder) GetUsersInChannel(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetUsersInChannel\", reflect.TypeOf((*MockAPI)(nil).GetUsersInChannel), arg0, arg1, arg2, arg3)\n}\n\n// GetUsersInTeam mocks base method.\nfunc (m *MockAPI) GetUsersInTeam(arg0 string, arg1, arg2 int) ([]*model.User, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetUsersInTeam\", arg0, arg1, arg2)\n\tret0, _ := ret[0].([]*model.User)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// GetUsersInTeam indicates an expected call of GetUsersInTeam.\nfunc (mr *MockAPIMockRecorder) GetUsersInTeam(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetUsersInTeam\", reflect.TypeOf((*MockAPI)(nil).GetUsersInTeam), arg0, arg1, arg2)\n}\n\n// HasPermissionTo mocks base method.\nfunc (m *MockAPI) HasPermissionTo(arg0 string, arg1 *model.Permission) bool {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"HasPermissionTo\", arg0, arg1)\n\tret0, _ := ret[0].(bool)\n\treturn ret0\n}\n\n// HasPermissionTo indicates an expected call of HasPermissionTo.\nfunc (mr *MockAPIMockRecorder) HasPermissionTo(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"HasPermissionTo\", reflect.TypeOf((*MockAPI)(nil).HasPermissionTo), arg0, arg1)\n}\n\n// HasPermissionToChannel mocks base method.\nfunc (m *MockAPI) HasPermissionToChannel(arg0, arg1 string, arg2 *model.Permission) bool {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"HasPermissionToChannel\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(bool)\n\treturn ret0\n}\n\n// HasPermissionToChannel indicates an expected call of HasPermissionToChannel.\nfunc (mr *MockAPIMockRecorder) HasPermissionToChannel(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"HasPermissionToChannel\", reflect.TypeOf((*MockAPI)(nil).HasPermissionToChannel), arg0, arg1, arg2)\n}\n\n// HasPermissionToTeam mocks base method.\nfunc (m *MockAPI) HasPermissionToTeam(arg0, arg1 string, arg2 *model.Permission) bool {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"HasPermissionToTeam\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(bool)\n\treturn ret0\n}\n\n// HasPermissionToTeam indicates an expected call of HasPermissionToTeam.\nfunc (mr *MockAPIMockRecorder) HasPermissionToTeam(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"HasPermissionToTeam\", reflect.TypeOf((*MockAPI)(nil).HasPermissionToTeam), arg0, arg1, arg2)\n}\n\n// InstallPlugin mocks base method.\nfunc (m *MockAPI) InstallPlugin(arg0 io.Reader, arg1 bool) (*model.Manifest, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"InstallPlugin\", arg0, arg1)\n\tret0, _ := ret[0].(*model.Manifest)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// InstallPlugin indicates an expected call of InstallPlugin.\nfunc (mr *MockAPIMockRecorder) InstallPlugin(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"InstallPlugin\", reflect.TypeOf((*MockAPI)(nil).InstallPlugin), arg0, arg1)\n}\n\n// IsEnterpriseReady mocks base method.\nfunc (m *MockAPI) IsEnterpriseReady() bool {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"IsEnterpriseReady\")\n\tret0, _ := ret[0].(bool)\n\treturn ret0\n}\n\n// IsEnterpriseReady indicates an expected call of IsEnterpriseReady.\nfunc (mr *MockAPIMockRecorder) IsEnterpriseReady() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"IsEnterpriseReady\", reflect.TypeOf((*MockAPI)(nil).IsEnterpriseReady))\n}\n\n// KVCompareAndDelete mocks base method.\nfunc (m *MockAPI) KVCompareAndDelete(arg0 string, arg1 []byte) (bool, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"KVCompareAndDelete\", arg0, arg1)\n\tret0, _ := ret[0].(bool)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// KVCompareAndDelete indicates an expected call of KVCompareAndDelete.\nfunc (mr *MockAPIMockRecorder) KVCompareAndDelete(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"KVCompareAndDelete\", reflect.TypeOf((*MockAPI)(nil).KVCompareAndDelete), arg0, arg1)\n}\n\n// KVCompareAndSet mocks base method.\nfunc (m *MockAPI) KVCompareAndSet(arg0 string, arg1, arg2 []byte) (bool, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"KVCompareAndSet\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(bool)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// KVCompareAndSet indicates an expected call of KVCompareAndSet.\nfunc (mr *MockAPIMockRecorder) KVCompareAndSet(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"KVCompareAndSet\", reflect.TypeOf((*MockAPI)(nil).KVCompareAndSet), arg0, arg1, arg2)\n}\n\n// KVDelete mocks base method.\nfunc (m *MockAPI) KVDelete(arg0 string) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"KVDelete\", arg0)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// KVDelete indicates an expected call of KVDelete.\nfunc (mr *MockAPIMockRecorder) KVDelete(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"KVDelete\", reflect.TypeOf((*MockAPI)(nil).KVDelete), arg0)\n}\n\n// KVDeleteAll mocks base method.\nfunc (m *MockAPI) KVDeleteAll() *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"KVDeleteAll\")\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// KVDeleteAll indicates an expected call of KVDeleteAll.\nfunc (mr *MockAPIMockRecorder) KVDeleteAll() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"KVDeleteAll\", reflect.TypeOf((*MockAPI)(nil).KVDeleteAll))\n}\n\n// KVGet mocks base method.\nfunc (m *MockAPI) KVGet(arg0 string) ([]byte, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"KVGet\", arg0)\n\tret0, _ := ret[0].([]byte)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// KVGet indicates an expected call of KVGet.\nfunc (mr *MockAPIMockRecorder) KVGet(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"KVGet\", reflect.TypeOf((*MockAPI)(nil).KVGet), arg0)\n}\n\n// KVList mocks base method.\nfunc (m *MockAPI) KVList(arg0, arg1 int) ([]string, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"KVList\", arg0, arg1)\n\tret0, _ := ret[0].([]string)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// KVList indicates an expected call of KVList.\nfunc (mr *MockAPIMockRecorder) KVList(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"KVList\", reflect.TypeOf((*MockAPI)(nil).KVList), arg0, arg1)\n}\n\n// KVSet mocks base method.\nfunc (m *MockAPI) KVSet(arg0 string, arg1 []byte) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"KVSet\", arg0, arg1)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// KVSet indicates an expected call of KVSet.\nfunc (mr *MockAPIMockRecorder) KVSet(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"KVSet\", reflect.TypeOf((*MockAPI)(nil).KVSet), arg0, arg1)\n}\n\n// KVSetWithExpiry mocks base method.\nfunc (m *MockAPI) KVSetWithExpiry(arg0 string, arg1 []byte, arg2 int64) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"KVSetWithExpiry\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// KVSetWithExpiry indicates an expected call of KVSetWithExpiry.\nfunc (mr *MockAPIMockRecorder) KVSetWithExpiry(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"KVSetWithExpiry\", reflect.TypeOf((*MockAPI)(nil).KVSetWithExpiry), arg0, arg1, arg2)\n}\n\n// KVSetWithOptions mocks base method.\nfunc (m *MockAPI) KVSetWithOptions(arg0 string, arg1 []byte, arg2 model.PluginKVSetOptions) (bool, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"KVSetWithOptions\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(bool)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// KVSetWithOptions indicates an expected call of KVSetWithOptions.\nfunc (mr *MockAPIMockRecorder) KVSetWithOptions(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"KVSetWithOptions\", reflect.TypeOf((*MockAPI)(nil).KVSetWithOptions), arg0, arg1, arg2)\n}\n\n// ListBuiltInCommands mocks base method.\nfunc (m *MockAPI) ListBuiltInCommands() ([]*model.Command, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ListBuiltInCommands\")\n\tret0, _ := ret[0].([]*model.Command)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ListBuiltInCommands indicates an expected call of ListBuiltInCommands.\nfunc (mr *MockAPIMockRecorder) ListBuiltInCommands() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ListBuiltInCommands\", reflect.TypeOf((*MockAPI)(nil).ListBuiltInCommands))\n}\n\n// ListCommands mocks base method.\nfunc (m *MockAPI) ListCommands(arg0 string) ([]*model.Command, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ListCommands\", arg0)\n\tret0, _ := ret[0].([]*model.Command)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ListCommands indicates an expected call of ListCommands.\nfunc (mr *MockAPIMockRecorder) ListCommands(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ListCommands\", reflect.TypeOf((*MockAPI)(nil).ListCommands), arg0)\n}\n\n// ListCustomCommands mocks base method.\nfunc (m *MockAPI) ListCustomCommands(arg0 string) ([]*model.Command, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ListCustomCommands\", arg0)\n\tret0, _ := ret[0].([]*model.Command)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ListCustomCommands indicates an expected call of ListCustomCommands.\nfunc (mr *MockAPIMockRecorder) ListCustomCommands(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ListCustomCommands\", reflect.TypeOf((*MockAPI)(nil).ListCustomCommands), arg0)\n}\n\n// ListPluginCommands mocks base method.\nfunc (m *MockAPI) ListPluginCommands(arg0 string) ([]*model.Command, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ListPluginCommands\", arg0)\n\tret0, _ := ret[0].([]*model.Command)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// ListPluginCommands indicates an expected call of ListPluginCommands.\nfunc (mr *MockAPIMockRecorder) ListPluginCommands(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ListPluginCommands\", reflect.TypeOf((*MockAPI)(nil).ListPluginCommands), arg0)\n}\n\n// LoadPluginConfiguration mocks base method.\nfunc (m *MockAPI) LoadPluginConfiguration(arg0 interface{}) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"LoadPluginConfiguration\", arg0)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// LoadPluginConfiguration indicates an expected call of LoadPluginConfiguration.\nfunc (mr *MockAPIMockRecorder) LoadPluginConfiguration(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"LoadPluginConfiguration\", reflect.TypeOf((*MockAPI)(nil).LoadPluginConfiguration), arg0)\n}\n\n// LogDebug mocks base method.\nfunc (m *MockAPI) LogDebug(arg0 string, arg1 ...interface{}) {\n\tm.ctrl.T.Helper()\n\tvarargs := []interface{}{arg0}\n\tfor _, a := range arg1 {\n\t\tvarargs = append(varargs, a)\n\t}\n\tm.ctrl.Call(m, \"LogDebug\", varargs...)\n}\n\n// LogDebug indicates an expected call of LogDebug.\nfunc (mr *MockAPIMockRecorder) LogDebug(arg0 interface{}, arg1 ...interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\tvarargs := append([]interface{}{arg0}, arg1...)\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"LogDebug\", reflect.TypeOf((*MockAPI)(nil).LogDebug), varargs...)\n}\n\n// LogError mocks base method.\nfunc (m *MockAPI) LogError(arg0 string, arg1 ...interface{}) {\n\tm.ctrl.T.Helper()\n\tvarargs := []interface{}{arg0}\n\tfor _, a := range arg1 {\n\t\tvarargs = append(varargs, a)\n\t}\n\tm.ctrl.Call(m, \"LogError\", varargs...)\n}\n\n// LogError indicates an expected call of LogError.\nfunc (mr *MockAPIMockRecorder) LogError(arg0 interface{}, arg1 ...interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\tvarargs := append([]interface{}{arg0}, arg1...)\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"LogError\", reflect.TypeOf((*MockAPI)(nil).LogError), varargs...)\n}\n\n// LogInfo mocks base method.\nfunc (m *MockAPI) LogInfo(arg0 string, arg1 ...interface{}) {\n\tm.ctrl.T.Helper()\n\tvarargs := []interface{}{arg0}\n\tfor _, a := range arg1 {\n\t\tvarargs = append(varargs, a)\n\t}\n\tm.ctrl.Call(m, \"LogInfo\", varargs...)\n}\n\n// LogInfo indicates an expected call of LogInfo.\nfunc (mr *MockAPIMockRecorder) LogInfo(arg0 interface{}, arg1 ...interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\tvarargs := append([]interface{}{arg0}, arg1...)\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"LogInfo\", reflect.TypeOf((*MockAPI)(nil).LogInfo), varargs...)\n}\n\n// LogWarn mocks base method.\nfunc (m *MockAPI) LogWarn(arg0 string, arg1 ...interface{}) {\n\tm.ctrl.T.Helper()\n\tvarargs := []interface{}{arg0}\n\tfor _, a := range arg1 {\n\t\tvarargs = append(varargs, a)\n\t}\n\tm.ctrl.Call(m, \"LogWarn\", varargs...)\n}\n\n// LogWarn indicates an expected call of LogWarn.\nfunc (mr *MockAPIMockRecorder) LogWarn(arg0 interface{}, arg1 ...interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\tvarargs := append([]interface{}{arg0}, arg1...)\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"LogWarn\", reflect.TypeOf((*MockAPI)(nil).LogWarn), varargs...)\n}\n\n// OpenInteractiveDialog mocks base method.\nfunc (m *MockAPI) OpenInteractiveDialog(arg0 model.OpenDialogRequest) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"OpenInteractiveDialog\", arg0)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// OpenInteractiveDialog indicates an expected call of OpenInteractiveDialog.\nfunc (mr *MockAPIMockRecorder) OpenInteractiveDialog(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"OpenInteractiveDialog\", reflect.TypeOf((*MockAPI)(nil).OpenInteractiveDialog), arg0)\n}\n\n// PatchBot mocks base method.\nfunc (m *MockAPI) PatchBot(arg0 string, arg1 *model.BotPatch) (*model.Bot, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"PatchBot\", arg0, arg1)\n\tret0, _ := ret[0].(*model.Bot)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// PatchBot indicates an expected call of PatchBot.\nfunc (mr *MockAPIMockRecorder) PatchBot(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"PatchBot\", reflect.TypeOf((*MockAPI)(nil).PatchBot), arg0, arg1)\n}\n\n// PermanentDeleteBot mocks base method.\nfunc (m *MockAPI) PermanentDeleteBot(arg0 string) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"PermanentDeleteBot\", arg0)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// PermanentDeleteBot indicates an expected call of PermanentDeleteBot.\nfunc (mr *MockAPIMockRecorder) PermanentDeleteBot(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"PermanentDeleteBot\", reflect.TypeOf((*MockAPI)(nil).PermanentDeleteBot), arg0)\n}\n\n// PluginHTTP mocks base method.\nfunc (m *MockAPI) PluginHTTP(arg0 *http.Request) *http.Response {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"PluginHTTP\", arg0)\n\tret0, _ := ret[0].(*http.Response)\n\treturn ret0\n}\n\n// PluginHTTP indicates an expected call of PluginHTTP.\nfunc (mr *MockAPIMockRecorder) PluginHTTP(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"PluginHTTP\", reflect.TypeOf((*MockAPI)(nil).PluginHTTP), arg0)\n}\n\n// PublishPluginClusterEvent mocks base method.\nfunc (m *MockAPI) PublishPluginClusterEvent(arg0 model.PluginClusterEvent, arg1 model.PluginClusterEventSendOptions) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"PublishPluginClusterEvent\", arg0, arg1)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// PublishPluginClusterEvent indicates an expected call of PublishPluginClusterEvent.\nfunc (mr *MockAPIMockRecorder) PublishPluginClusterEvent(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"PublishPluginClusterEvent\", reflect.TypeOf((*MockAPI)(nil).PublishPluginClusterEvent), arg0, arg1)\n}\n\n// PublishUserTyping mocks base method.\nfunc (m *MockAPI) PublishUserTyping(arg0, arg1, arg2 string) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"PublishUserTyping\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// PublishUserTyping indicates an expected call of PublishUserTyping.\nfunc (mr *MockAPIMockRecorder) PublishUserTyping(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"PublishUserTyping\", reflect.TypeOf((*MockAPI)(nil).PublishUserTyping), arg0, arg1, arg2)\n}\n\n// PublishWebSocketEvent mocks base method.\nfunc (m *MockAPI) PublishWebSocketEvent(arg0 string, arg1 map[string]interface{}, arg2 *model.WebsocketBroadcast) {\n\tm.ctrl.T.Helper()\n\tm.ctrl.Call(m, \"PublishWebSocketEvent\", arg0, arg1, arg2)\n}\n\n// PublishWebSocketEvent indicates an expected call of PublishWebSocketEvent.\nfunc (mr *MockAPIMockRecorder) PublishWebSocketEvent(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"PublishWebSocketEvent\", reflect.TypeOf((*MockAPI)(nil).PublishWebSocketEvent), arg0, arg1, arg2)\n}\n\n// ReadFile mocks base method.\nfunc (m *MockAPI) ReadFile(arg0 string) ([]byte, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ReadFile\", arg0)\n\tret0, _ := ret[0].([]byte)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// ReadFile indicates an expected call of ReadFile.\nfunc (mr *MockAPIMockRecorder) ReadFile(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ReadFile\", reflect.TypeOf((*MockAPI)(nil).ReadFile), arg0)\n}\n\n// RegisterCollectionAndTopic mocks base method.\nfunc (m *MockAPI) RegisterCollectionAndTopic(arg0, arg1 string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"RegisterCollectionAndTopic\", arg0, arg1)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// RegisterCollectionAndTopic indicates an expected call of RegisterCollectionAndTopic.\nfunc (mr *MockAPIMockRecorder) RegisterCollectionAndTopic(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"RegisterCollectionAndTopic\", reflect.TypeOf((*MockAPI)(nil).RegisterCollectionAndTopic), arg0, arg1)\n}\n\n// RegisterCommand mocks base method.\nfunc (m *MockAPI) RegisterCommand(arg0 *model.Command) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"RegisterCommand\", arg0)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// RegisterCommand indicates an expected call of RegisterCommand.\nfunc (mr *MockAPIMockRecorder) RegisterCommand(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"RegisterCommand\", reflect.TypeOf((*MockAPI)(nil).RegisterCommand), arg0)\n}\n\n// RemovePlugin mocks base method.\nfunc (m *MockAPI) RemovePlugin(arg0 string) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"RemovePlugin\", arg0)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// RemovePlugin indicates an expected call of RemovePlugin.\nfunc (mr *MockAPIMockRecorder) RemovePlugin(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"RemovePlugin\", reflect.TypeOf((*MockAPI)(nil).RemovePlugin), arg0)\n}\n\n// RemoveReaction mocks base method.\nfunc (m *MockAPI) RemoveReaction(arg0 *model.Reaction) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"RemoveReaction\", arg0)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// RemoveReaction indicates an expected call of RemoveReaction.\nfunc (mr *MockAPIMockRecorder) RemoveReaction(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"RemoveReaction\", reflect.TypeOf((*MockAPI)(nil).RemoveReaction), arg0)\n}\n\n// RemoveTeamIcon mocks base method.\nfunc (m *MockAPI) RemoveTeamIcon(arg0 string) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"RemoveTeamIcon\", arg0)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// RemoveTeamIcon indicates an expected call of RemoveTeamIcon.\nfunc (mr *MockAPIMockRecorder) RemoveTeamIcon(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"RemoveTeamIcon\", reflect.TypeOf((*MockAPI)(nil).RemoveTeamIcon), arg0)\n}\n\n// RemoveUserCustomStatus mocks base method.\nfunc (m *MockAPI) RemoveUserCustomStatus(arg0 string) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"RemoveUserCustomStatus\", arg0)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// RemoveUserCustomStatus indicates an expected call of RemoveUserCustomStatus.\nfunc (mr *MockAPIMockRecorder) RemoveUserCustomStatus(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"RemoveUserCustomStatus\", reflect.TypeOf((*MockAPI)(nil).RemoveUserCustomStatus), arg0)\n}\n\n// RequestTrialLicense mocks base method.\nfunc (m *MockAPI) RequestTrialLicense(arg0 string, arg1 int, arg2, arg3 bool) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"RequestTrialLicense\", arg0, arg1, arg2, arg3)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// RequestTrialLicense indicates an expected call of RequestTrialLicense.\nfunc (mr *MockAPIMockRecorder) RequestTrialLicense(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"RequestTrialLicense\", reflect.TypeOf((*MockAPI)(nil).RequestTrialLicense), arg0, arg1, arg2, arg3)\n}\n\n// RevokeSession mocks base method.\nfunc (m *MockAPI) RevokeSession(arg0 string) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"RevokeSession\", arg0)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// RevokeSession indicates an expected call of RevokeSession.\nfunc (mr *MockAPIMockRecorder) RevokeSession(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"RevokeSession\", reflect.TypeOf((*MockAPI)(nil).RevokeSession), arg0)\n}\n\n// RevokeUserAccessToken mocks base method.\nfunc (m *MockAPI) RevokeUserAccessToken(arg0 string) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"RevokeUserAccessToken\", arg0)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// RevokeUserAccessToken indicates an expected call of RevokeUserAccessToken.\nfunc (mr *MockAPIMockRecorder) RevokeUserAccessToken(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"RevokeUserAccessToken\", reflect.TypeOf((*MockAPI)(nil).RevokeUserAccessToken), arg0)\n}\n\n// RolesGrantPermission mocks base method.\nfunc (m *MockAPI) RolesGrantPermission(arg0 []string, arg1 string) bool {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"RolesGrantPermission\", arg0, arg1)\n\tret0, _ := ret[0].(bool)\n\treturn ret0\n}\n\n// RolesGrantPermission indicates an expected call of RolesGrantPermission.\nfunc (mr *MockAPIMockRecorder) RolesGrantPermission(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"RolesGrantPermission\", reflect.TypeOf((*MockAPI)(nil).RolesGrantPermission), arg0, arg1)\n}\n\n// SaveConfig mocks base method.\nfunc (m *MockAPI) SaveConfig(arg0 *model.Config) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"SaveConfig\", arg0)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// SaveConfig indicates an expected call of SaveConfig.\nfunc (mr *MockAPIMockRecorder) SaveConfig(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SaveConfig\", reflect.TypeOf((*MockAPI)(nil).SaveConfig), arg0)\n}\n\n// SavePluginConfig mocks base method.\nfunc (m *MockAPI) SavePluginConfig(arg0 map[string]interface{}) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"SavePluginConfig\", arg0)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// SavePluginConfig indicates an expected call of SavePluginConfig.\nfunc (mr *MockAPIMockRecorder) SavePluginConfig(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SavePluginConfig\", reflect.TypeOf((*MockAPI)(nil).SavePluginConfig), arg0)\n}\n\n// SearchChannels mocks base method.\nfunc (m *MockAPI) SearchChannels(arg0, arg1 string) ([]*model.Channel, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"SearchChannels\", arg0, arg1)\n\tret0, _ := ret[0].([]*model.Channel)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// SearchChannels indicates an expected call of SearchChannels.\nfunc (mr *MockAPIMockRecorder) SearchChannels(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SearchChannels\", reflect.TypeOf((*MockAPI)(nil).SearchChannels), arg0, arg1)\n}\n\n// SearchPostsInTeam mocks base method.\nfunc (m *MockAPI) SearchPostsInTeam(arg0 string, arg1 []*model.SearchParams) ([]*model.Post, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"SearchPostsInTeam\", arg0, arg1)\n\tret0, _ := ret[0].([]*model.Post)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// SearchPostsInTeam indicates an expected call of SearchPostsInTeam.\nfunc (mr *MockAPIMockRecorder) SearchPostsInTeam(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SearchPostsInTeam\", reflect.TypeOf((*MockAPI)(nil).SearchPostsInTeam), arg0, arg1)\n}\n\n// SearchPostsInTeamForUser mocks base method.\nfunc (m *MockAPI) SearchPostsInTeamForUser(arg0, arg1 string, arg2 model.SearchParameter) (*model.PostSearchResults, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"SearchPostsInTeamForUser\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(*model.PostSearchResults)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// SearchPostsInTeamForUser indicates an expected call of SearchPostsInTeamForUser.\nfunc (mr *MockAPIMockRecorder) SearchPostsInTeamForUser(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SearchPostsInTeamForUser\", reflect.TypeOf((*MockAPI)(nil).SearchPostsInTeamForUser), arg0, arg1, arg2)\n}\n\n// SearchTeams mocks base method.\nfunc (m *MockAPI) SearchTeams(arg0 string) ([]*model.Team, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"SearchTeams\", arg0)\n\tret0, _ := ret[0].([]*model.Team)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// SearchTeams indicates an expected call of SearchTeams.\nfunc (mr *MockAPIMockRecorder) SearchTeams(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SearchTeams\", reflect.TypeOf((*MockAPI)(nil).SearchTeams), arg0)\n}\n\n// SearchUsers mocks base method.\nfunc (m *MockAPI) SearchUsers(arg0 *model.UserSearch) ([]*model.User, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"SearchUsers\", arg0)\n\tret0, _ := ret[0].([]*model.User)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// SearchUsers indicates an expected call of SearchUsers.\nfunc (mr *MockAPIMockRecorder) SearchUsers(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SearchUsers\", reflect.TypeOf((*MockAPI)(nil).SearchUsers), arg0)\n}\n\n// SendEphemeralPost mocks base method.\nfunc (m *MockAPI) SendEphemeralPost(arg0 string, arg1 *model.Post) *model.Post {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"SendEphemeralPost\", arg0, arg1)\n\tret0, _ := ret[0].(*model.Post)\n\treturn ret0\n}\n\n// SendEphemeralPost indicates an expected call of SendEphemeralPost.\nfunc (mr *MockAPIMockRecorder) SendEphemeralPost(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SendEphemeralPost\", reflect.TypeOf((*MockAPI)(nil).SendEphemeralPost), arg0, arg1)\n}\n\n// SendMail mocks base method.\nfunc (m *MockAPI) SendMail(arg0, arg1, arg2 string) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"SendMail\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// SendMail indicates an expected call of SendMail.\nfunc (mr *MockAPIMockRecorder) SendMail(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SendMail\", reflect.TypeOf((*MockAPI)(nil).SendMail), arg0, arg1, arg2)\n}\n\n// SetProfileImage mocks base method.\nfunc (m *MockAPI) SetProfileImage(arg0 string, arg1 []byte) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"SetProfileImage\", arg0, arg1)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// SetProfileImage indicates an expected call of SetProfileImage.\nfunc (mr *MockAPIMockRecorder) SetProfileImage(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SetProfileImage\", reflect.TypeOf((*MockAPI)(nil).SetProfileImage), arg0, arg1)\n}\n\n// SetTeamIcon mocks base method.\nfunc (m *MockAPI) SetTeamIcon(arg0 string, arg1 []byte) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"SetTeamIcon\", arg0, arg1)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// SetTeamIcon indicates an expected call of SetTeamIcon.\nfunc (mr *MockAPIMockRecorder) SetTeamIcon(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SetTeamIcon\", reflect.TypeOf((*MockAPI)(nil).SetTeamIcon), arg0, arg1)\n}\n\n// SetUserStatusTimedDND mocks base method.\nfunc (m *MockAPI) SetUserStatusTimedDND(arg0 string, arg1 int64) (*model.Status, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"SetUserStatusTimedDND\", arg0, arg1)\n\tret0, _ := ret[0].(*model.Status)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// SetUserStatusTimedDND indicates an expected call of SetUserStatusTimedDND.\nfunc (mr *MockAPIMockRecorder) SetUserStatusTimedDND(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"SetUserStatusTimedDND\", reflect.TypeOf((*MockAPI)(nil).SetUserStatusTimedDND), arg0, arg1)\n}\n\n// UnregisterCommand mocks base method.\nfunc (m *MockAPI) UnregisterCommand(arg0, arg1 string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UnregisterCommand\", arg0, arg1)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// UnregisterCommand indicates an expected call of UnregisterCommand.\nfunc (mr *MockAPIMockRecorder) UnregisterCommand(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UnregisterCommand\", reflect.TypeOf((*MockAPI)(nil).UnregisterCommand), arg0, arg1)\n}\n\n// UpdateBotActive mocks base method.\nfunc (m *MockAPI) UpdateBotActive(arg0 string, arg1 bool) (*model.Bot, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdateBotActive\", arg0, arg1)\n\tret0, _ := ret[0].(*model.Bot)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// UpdateBotActive indicates an expected call of UpdateBotActive.\nfunc (mr *MockAPIMockRecorder) UpdateBotActive(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdateBotActive\", reflect.TypeOf((*MockAPI)(nil).UpdateBotActive), arg0, arg1)\n}\n\n// UpdateChannel mocks base method.\nfunc (m *MockAPI) UpdateChannel(arg0 *model.Channel) (*model.Channel, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdateChannel\", arg0)\n\tret0, _ := ret[0].(*model.Channel)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// UpdateChannel indicates an expected call of UpdateChannel.\nfunc (mr *MockAPIMockRecorder) UpdateChannel(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdateChannel\", reflect.TypeOf((*MockAPI)(nil).UpdateChannel), arg0)\n}\n\n// UpdateChannelMemberNotifications mocks base method.\nfunc (m *MockAPI) UpdateChannelMemberNotifications(arg0, arg1 string, arg2 map[string]string) (*model.ChannelMember, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdateChannelMemberNotifications\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(*model.ChannelMember)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// UpdateChannelMemberNotifications indicates an expected call of UpdateChannelMemberNotifications.\nfunc (mr *MockAPIMockRecorder) UpdateChannelMemberNotifications(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdateChannelMemberNotifications\", reflect.TypeOf((*MockAPI)(nil).UpdateChannelMemberNotifications), arg0, arg1, arg2)\n}\n\n// UpdateChannelMemberRoles mocks base method.\nfunc (m *MockAPI) UpdateChannelMemberRoles(arg0, arg1, arg2 string) (*model.ChannelMember, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdateChannelMemberRoles\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(*model.ChannelMember)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// UpdateChannelMemberRoles indicates an expected call of UpdateChannelMemberRoles.\nfunc (mr *MockAPIMockRecorder) UpdateChannelMemberRoles(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdateChannelMemberRoles\", reflect.TypeOf((*MockAPI)(nil).UpdateChannelMemberRoles), arg0, arg1, arg2)\n}\n\n// UpdateChannelSidebarCategories mocks base method.\nfunc (m *MockAPI) UpdateChannelSidebarCategories(arg0, arg1 string, arg2 []*model.SidebarCategoryWithChannels) ([]*model.SidebarCategoryWithChannels, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdateChannelSidebarCategories\", arg0, arg1, arg2)\n\tret0, _ := ret[0].([]*model.SidebarCategoryWithChannels)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// UpdateChannelSidebarCategories indicates an expected call of UpdateChannelSidebarCategories.\nfunc (mr *MockAPIMockRecorder) UpdateChannelSidebarCategories(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdateChannelSidebarCategories\", reflect.TypeOf((*MockAPI)(nil).UpdateChannelSidebarCategories), arg0, arg1, arg2)\n}\n\n// UpdateCommand mocks base method.\nfunc (m *MockAPI) UpdateCommand(arg0 string, arg1 *model.Command) (*model.Command, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdateCommand\", arg0, arg1)\n\tret0, _ := ret[0].(*model.Command)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// UpdateCommand indicates an expected call of UpdateCommand.\nfunc (mr *MockAPIMockRecorder) UpdateCommand(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdateCommand\", reflect.TypeOf((*MockAPI)(nil).UpdateCommand), arg0, arg1)\n}\n\n// UpdateEphemeralPost mocks base method.\nfunc (m *MockAPI) UpdateEphemeralPost(arg0 string, arg1 *model.Post) *model.Post {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdateEphemeralPost\", arg0, arg1)\n\tret0, _ := ret[0].(*model.Post)\n\treturn ret0\n}\n\n// UpdateEphemeralPost indicates an expected call of UpdateEphemeralPost.\nfunc (mr *MockAPIMockRecorder) UpdateEphemeralPost(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdateEphemeralPost\", reflect.TypeOf((*MockAPI)(nil).UpdateEphemeralPost), arg0, arg1)\n}\n\n// UpdateOAuthApp mocks base method.\nfunc (m *MockAPI) UpdateOAuthApp(arg0 *model.OAuthApp) (*model.OAuthApp, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdateOAuthApp\", arg0)\n\tret0, _ := ret[0].(*model.OAuthApp)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// UpdateOAuthApp indicates an expected call of UpdateOAuthApp.\nfunc (mr *MockAPIMockRecorder) UpdateOAuthApp(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdateOAuthApp\", reflect.TypeOf((*MockAPI)(nil).UpdateOAuthApp), arg0)\n}\n\n// UpdatePost mocks base method.\nfunc (m *MockAPI) UpdatePost(arg0 *model.Post) (*model.Post, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdatePost\", arg0)\n\tret0, _ := ret[0].(*model.Post)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// UpdatePost indicates an expected call of UpdatePost.\nfunc (mr *MockAPIMockRecorder) UpdatePost(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdatePost\", reflect.TypeOf((*MockAPI)(nil).UpdatePost), arg0)\n}\n\n// UpdatePreferencesForUser mocks base method.\nfunc (m *MockAPI) UpdatePreferencesForUser(arg0 string, arg1 []model.Preference) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdatePreferencesForUser\", arg0, arg1)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// UpdatePreferencesForUser indicates an expected call of UpdatePreferencesForUser.\nfunc (mr *MockAPIMockRecorder) UpdatePreferencesForUser(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdatePreferencesForUser\", reflect.TypeOf((*MockAPI)(nil).UpdatePreferencesForUser), arg0, arg1)\n}\n\n// UpdateTeam mocks base method.\nfunc (m *MockAPI) UpdateTeam(arg0 *model.Team) (*model.Team, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdateTeam\", arg0)\n\tret0, _ := ret[0].(*model.Team)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// UpdateTeam indicates an expected call of UpdateTeam.\nfunc (mr *MockAPIMockRecorder) UpdateTeam(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdateTeam\", reflect.TypeOf((*MockAPI)(nil).UpdateTeam), arg0)\n}\n\n// UpdateTeamMemberRoles mocks base method.\nfunc (m *MockAPI) UpdateTeamMemberRoles(arg0, arg1, arg2 string) (*model.TeamMember, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdateTeamMemberRoles\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(*model.TeamMember)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// UpdateTeamMemberRoles indicates an expected call of UpdateTeamMemberRoles.\nfunc (mr *MockAPIMockRecorder) UpdateTeamMemberRoles(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdateTeamMemberRoles\", reflect.TypeOf((*MockAPI)(nil).UpdateTeamMemberRoles), arg0, arg1, arg2)\n}\n\n// UpdateUser mocks base method.\nfunc (m *MockAPI) UpdateUser(arg0 *model.User) (*model.User, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdateUser\", arg0)\n\tret0, _ := ret[0].(*model.User)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// UpdateUser indicates an expected call of UpdateUser.\nfunc (mr *MockAPIMockRecorder) UpdateUser(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdateUser\", reflect.TypeOf((*MockAPI)(nil).UpdateUser), arg0)\n}\n\n// UpdateUserActive mocks base method.\nfunc (m *MockAPI) UpdateUserActive(arg0 string, arg1 bool) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdateUserActive\", arg0, arg1)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// UpdateUserActive indicates an expected call of UpdateUserActive.\nfunc (mr *MockAPIMockRecorder) UpdateUserActive(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdateUserActive\", reflect.TypeOf((*MockAPI)(nil).UpdateUserActive), arg0, arg1)\n}\n\n// UpdateUserCustomStatus mocks base method.\nfunc (m *MockAPI) UpdateUserCustomStatus(arg0 string, arg1 *model.CustomStatus) *model.AppError {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdateUserCustomStatus\", arg0, arg1)\n\tret0, _ := ret[0].(*model.AppError)\n\treturn ret0\n}\n\n// UpdateUserCustomStatus indicates an expected call of UpdateUserCustomStatus.\nfunc (mr *MockAPIMockRecorder) UpdateUserCustomStatus(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdateUserCustomStatus\", reflect.TypeOf((*MockAPI)(nil).UpdateUserCustomStatus), arg0, arg1)\n}\n\n// UpdateUserStatus mocks base method.\nfunc (m *MockAPI) UpdateUserStatus(arg0, arg1 string) (*model.Status, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UpdateUserStatus\", arg0, arg1)\n\tret0, _ := ret[0].(*model.Status)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// UpdateUserStatus indicates an expected call of UpdateUserStatus.\nfunc (mr *MockAPIMockRecorder) UpdateUserStatus(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UpdateUserStatus\", reflect.TypeOf((*MockAPI)(nil).UpdateUserStatus), arg0, arg1)\n}\n\n// UploadData mocks base method.\nfunc (m *MockAPI) UploadData(arg0 *model.UploadSession, arg1 io.Reader) (*model.FileInfo, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UploadData\", arg0, arg1)\n\tret0, _ := ret[0].(*model.FileInfo)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// UploadData indicates an expected call of UploadData.\nfunc (mr *MockAPIMockRecorder) UploadData(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UploadData\", reflect.TypeOf((*MockAPI)(nil).UploadData), arg0, arg1)\n}\n\n// UploadFile mocks base method.\nfunc (m *MockAPI) UploadFile(arg0 []byte, arg1, arg2 string) (*model.FileInfo, *model.AppError) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"UploadFile\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(*model.FileInfo)\n\tret1, _ := ret[1].(*model.AppError)\n\treturn ret0, ret1\n}\n\n// UploadFile indicates an expected call of UploadFile.\nfunc (mr *MockAPIMockRecorder) UploadFile(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"UploadFile\", reflect.TypeOf((*MockAPI)(nil).UploadFile), arg0, arg1, arg2)\n}\n"
  },
  {
    "path": "server/ws/mocks/mockstore.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: github.com/mattermost/focalboard/server/ws (interfaces: Store)\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\treflect \"reflect\"\n\n\tgomock \"github.com/golang/mock/gomock\"\n\tmodel \"github.com/mattermost/focalboard/server/model\"\n)\n\n// MockStore is a mock of Store interface.\ntype MockStore struct {\n\tctrl     *gomock.Controller\n\trecorder *MockStoreMockRecorder\n}\n\n// MockStoreMockRecorder is the mock recorder for MockStore.\ntype MockStoreMockRecorder struct {\n\tmock *MockStore\n}\n\n// NewMockStore creates a new mock instance.\nfunc NewMockStore(ctrl *gomock.Controller) *MockStore {\n\tmock := &MockStore{ctrl: ctrl}\n\tmock.recorder = &MockStoreMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockStore) EXPECT() *MockStoreMockRecorder {\n\treturn m.recorder\n}\n\n// GetBlock mocks base method.\nfunc (m *MockStore) GetBlock(arg0 string) (*model.Block, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetBlock\", arg0)\n\tret0, _ := ret[0].(*model.Block)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetBlock indicates an expected call of GetBlock.\nfunc (mr *MockStoreMockRecorder) GetBlock(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetBlock\", reflect.TypeOf((*MockStore)(nil).GetBlock), arg0)\n}\n\n// GetMembersForBoard mocks base method.\nfunc (m *MockStore) GetMembersForBoard(arg0 string) ([]*model.BoardMember, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetMembersForBoard\", arg0)\n\tret0, _ := ret[0].([]*model.BoardMember)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetMembersForBoard indicates an expected call of GetMembersForBoard.\nfunc (mr *MockStoreMockRecorder) GetMembersForBoard(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetMembersForBoard\", reflect.TypeOf((*MockStore)(nil).GetMembersForBoard), arg0)\n}\n"
  },
  {
    "path": "server/ws/plugin_adapter.go",
    "content": "//go:generate mockgen -destination=mocks/mockpluginapi.go -package mocks github.com/mattermost/mattermost-server/v6/plugin API\npackage ws\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/mattermost/focalboard/server/auth\"\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n\n\tmmModel \"github.com/mattermost/mattermost/server/public/model\"\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nconst websocketMessagePrefix = \"custom_focalboard_\"\n\nvar errMissingTeamInCommand = fmt.Errorf(\"command doesn't contain teamId\")\n\ntype PluginAdapterInterface interface {\n\tAdapter\n\tOnWebSocketConnect(webConnID, userID string)\n\tOnWebSocketDisconnect(webConnID, userID string)\n\tWebSocketMessageHasBeenPosted(webConnID, userID string, req *mmModel.WebSocketRequest)\n\tBroadcastConfigChange(clientConfig model.ClientConfig)\n\tBroadcastBlockChange(teamID string, block *model.Block)\n\tBroadcastBlockDelete(teamID, blockID, parentID string)\n\tBroadcastSubscriptionChange(teamID string, subscription *model.Subscription)\n\tBroadcastCardLimitTimestampChange(cardLimitTimestamp int64)\n\tHandleClusterEvent(ev mmModel.PluginClusterEvent)\n}\n\ntype PluginAdapter struct {\n\tapi            servicesAPI\n\tauth           auth.AuthInterface\n\tstaleThreshold time.Duration\n\tstore          Store\n\tlogger         mlog.LoggerIFace\n\n\tlistenersMU       sync.RWMutex\n\tlisteners         map[string]*PluginAdapterClient\n\tlistenersByUserID map[string][]*PluginAdapterClient\n\n\tsubscriptionsMU  sync.RWMutex\n\tlistenersByTeam  map[string][]*PluginAdapterClient\n\tlistenersByBlock map[string][]*PluginAdapterClient\n}\n\n// servicesAPI is the interface required by the PluginAdapter to interact with\n// the mattermost-server.\ntype servicesAPI interface {\n\tPublishWebSocketEvent(event string, payload map[string]interface{}, broadcast *mmModel.WebsocketBroadcast)\n\tPublishPluginClusterEvent(ev mmModel.PluginClusterEvent, opts mmModel.PluginClusterEventSendOptions) error\n}\n\nfunc NewPluginAdapter(api servicesAPI, auth auth.AuthInterface, store Store, logger mlog.LoggerIFace) *PluginAdapter {\n\treturn &PluginAdapter{\n\t\tapi:               api,\n\t\tauth:              auth,\n\t\tstore:             store,\n\t\tstaleThreshold:    5 * time.Minute,\n\t\tlogger:            logger,\n\t\tlisteners:         make(map[string]*PluginAdapterClient),\n\t\tlistenersByUserID: make(map[string][]*PluginAdapterClient),\n\t\tlistenersByTeam:   make(map[string][]*PluginAdapterClient),\n\t\tlistenersByBlock:  make(map[string][]*PluginAdapterClient),\n\t\tlistenersMU:       sync.RWMutex{},\n\t\tsubscriptionsMU:   sync.RWMutex{},\n\t}\n}\n\nfunc (pa *PluginAdapter) GetListenerByWebConnID(webConnID string) (pac *PluginAdapterClient, ok bool) {\n\tpa.listenersMU.RLock()\n\tdefer pa.listenersMU.RUnlock()\n\n\tpac, ok = pa.listeners[webConnID]\n\treturn\n}\n\nfunc (pa *PluginAdapter) GetListenersByUserID(userID string) []*PluginAdapterClient {\n\tpa.listenersMU.RLock()\n\tdefer pa.listenersMU.RUnlock()\n\n\treturn pa.listenersByUserID[userID]\n}\n\nfunc (pa *PluginAdapter) GetListenersByTeam(teamID string) []*PluginAdapterClient {\n\tpa.subscriptionsMU.RLock()\n\tdefer pa.subscriptionsMU.RUnlock()\n\n\treturn pa.listenersByTeam[teamID]\n}\n\nfunc (pa *PluginAdapter) GetListenersByBlock(blockID string) []*PluginAdapterClient {\n\tpa.subscriptionsMU.RLock()\n\tdefer pa.subscriptionsMU.RUnlock()\n\n\treturn pa.listenersByBlock[blockID]\n}\n\nfunc (pa *PluginAdapter) addListener(pac *PluginAdapterClient) {\n\tpa.listenersMU.Lock()\n\tdefer pa.listenersMU.Unlock()\n\n\tpa.listeners[pac.webConnID] = pac\n\tpa.listenersByUserID[pac.userID] = append(pa.listenersByUserID[pac.userID], pac)\n}\n\nfunc (pa *PluginAdapter) removeListener(pac *PluginAdapterClient) {\n\tpa.listenersMU.Lock()\n\tdefer pa.listenersMU.Unlock()\n\n\t// team subscriptions\n\tfor _, team := range pac.teams {\n\t\tpa.removeListenerFromTeam(pac, team)\n\t}\n\n\t// block subscriptions\n\tfor _, block := range pac.blocks {\n\t\tpa.removeListenerFromBlock(pac, block)\n\t}\n\n\t// user ID list\n\tnewUserListeners := []*PluginAdapterClient{}\n\tfor _, listener := range pa.listenersByUserID[pac.userID] {\n\t\tif listener.webConnID != pac.webConnID {\n\t\t\tnewUserListeners = append(newUserListeners, listener)\n\t\t}\n\t}\n\tpa.listenersByUserID[pac.userID] = newUserListeners\n\n\tdelete(pa.listeners, pac.webConnID)\n}\n\nfunc (pa *PluginAdapter) removeExpiredForUserID(userID string) {\n\tfor _, pac := range pa.GetListenersByUserID(userID) {\n\t\tif !pac.isActive() && pac.hasExpired(pa.staleThreshold) {\n\t\t\tpa.removeListener(pac)\n\t\t}\n\t}\n}\n\nfunc (pa *PluginAdapter) removeListenerFromTeam(pac *PluginAdapterClient, teamID string) {\n\tnewTeamListeners := []*PluginAdapterClient{}\n\tfor _, listener := range pa.GetListenersByTeam(teamID) {\n\t\tif listener.webConnID != pac.webConnID {\n\t\t\tnewTeamListeners = append(newTeamListeners, listener)\n\t\t}\n\t}\n\tpa.subscriptionsMU.Lock()\n\tpa.listenersByTeam[teamID] = newTeamListeners\n\tpa.subscriptionsMU.Unlock()\n\n\tpac.unsubscribeFromTeam(teamID)\n}\n\nfunc (pa *PluginAdapter) removeListenerFromBlock(pac *PluginAdapterClient, blockID string) {\n\tnewBlockListeners := []*PluginAdapterClient{}\n\tfor _, listener := range pa.GetListenersByBlock(blockID) {\n\t\tif listener.webConnID != pac.webConnID {\n\t\t\tnewBlockListeners = append(newBlockListeners, listener)\n\t\t}\n\t}\n\tpa.subscriptionsMU.Lock()\n\tpa.listenersByBlock[blockID] = newBlockListeners\n\tpa.subscriptionsMU.Unlock()\n\n\tpac.unsubscribeFromBlock(blockID)\n}\n\nfunc (pa *PluginAdapter) subscribeListenerToTeam(pac *PluginAdapterClient, teamID string) {\n\tif pac.isSubscribedToTeam(teamID) {\n\t\treturn\n\t}\n\n\tpa.subscriptionsMU.Lock()\n\tpa.listenersByTeam[teamID] = append(pa.listenersByTeam[teamID], pac)\n\tpa.subscriptionsMU.Unlock()\n\n\tpac.subscribeToTeam(teamID)\n}\n\nfunc (pa *PluginAdapter) unsubscribeListenerFromTeam(pac *PluginAdapterClient, teamID string) {\n\tif !pac.isSubscribedToTeam(teamID) {\n\t\treturn\n\t}\n\n\tpa.removeListenerFromTeam(pac, teamID)\n}\n\nfunc (pa *PluginAdapter) getUserIDsForTeam(teamID string) []string {\n\tuserMap := map[string]bool{}\n\tfor _, pac := range pa.GetListenersByTeam(teamID) {\n\t\tif pac.isActive() {\n\t\t\tuserMap[pac.userID] = true\n\t\t}\n\t}\n\n\tuserIDs := []string{}\n\tfor userID := range userMap {\n\t\tif pa.auth.DoesUserHaveTeamAccess(userID, teamID) {\n\t\t\tuserIDs = append(userIDs, userID)\n\t\t}\n\t}\n\n\treturn userIDs\n}\n\nfunc (pa *PluginAdapter) getUserIDsForTeamAndBoard(teamID, boardID string, ensureUserIDs ...string) []string {\n\tuserMap := map[string]bool{}\n\tfor _, pac := range pa.GetListenersByTeam(teamID) {\n\t\tif pac.isActive() {\n\t\t\tuserMap[pac.userID] = true\n\t\t}\n\t}\n\n\tmembers, err := pa.store.GetMembersForBoard(boardID)\n\tif err != nil {\n\t\tpa.logger.Error(\"error getting members for board\",\n\t\t\tmlog.String(\"method\", \"getUserIDsForTeamAndBoard\"),\n\t\t\tmlog.String(\"teamID\", teamID),\n\t\t\tmlog.String(\"boardID\", boardID),\n\t\t)\n\t\treturn nil\n\t}\n\n\t// the list of users would be the intersection between the ones\n\t// that are connected to the team and the board members that need\n\t// to see the updates\n\tuserIDs := []string{}\n\tfor _, member := range members {\n\t\tfor userID := range userMap {\n\t\t\tif userID == member.UserID && pa.auth.DoesUserHaveTeamAccess(userID, teamID) {\n\t\t\t\tuserIDs = append(userIDs, userID)\n\t\t\t}\n\t\t}\n\t}\n\n\t// if we don't have to make sure that some IDs are included, we\n\t// can return at this point\n\tif len(ensureUserIDs) == 0 {\n\t\treturn userIDs\n\t}\n\n\tcompleteUserMap := map[string]bool{}\n\tfor _, id := range userIDs {\n\t\tcompleteUserMap[id] = true\n\t}\n\tfor _, id := range ensureUserIDs {\n\t\tcompleteUserMap[id] = true\n\t}\n\n\tcompleteUserIDs := []string{}\n\tfor id := range completeUserMap {\n\t\tcompleteUserIDs = append(completeUserIDs, id)\n\t}\n\n\treturn completeUserIDs\n}\n\n//nolint:unused\nfunc (pa *PluginAdapter) unsubscribeListenerFromBlocks(pac *PluginAdapterClient, blockIDs []string) {\n\tfor _, blockID := range blockIDs {\n\t\tif pac.isSubscribedToBlock(blockID) {\n\t\t\tpa.removeListenerFromBlock(pac, blockID)\n\t\t}\n\t}\n}\n\nfunc (pa *PluginAdapter) OnWebSocketConnect(webConnID, userID string) {\n\tif existingPAC, ok := pa.GetListenerByWebConnID(webConnID); ok {\n\t\tpa.logger.Debug(\"inactive connection found for webconn, reusing\",\n\t\t\tmlog.String(\"webConnID\", webConnID),\n\t\t\tmlog.String(\"userID\", userID),\n\t\t)\n\t\tatomic.StoreInt64(&existingPAC.inactiveAt, 0)\n\t\treturn\n\t}\n\n\tnewPAC := &PluginAdapterClient{\n\t\tinactiveAt: 0,\n\t\twebConnID:  webConnID,\n\t\tuserID:     userID,\n\t\tteams:      []string{},\n\t\tblocks:     []string{},\n\t}\n\n\tpa.addListener(newPAC)\n\tpa.removeExpiredForUserID(userID)\n}\n\nfunc (pa *PluginAdapter) OnWebSocketDisconnect(webConnID, userID string) {\n\tpac, ok := pa.GetListenerByWebConnID(webConnID)\n\tif !ok {\n\t\tpa.logger.Debug(\"received a disconnect for an unregistered webconn\",\n\t\t\tmlog.String(\"webConnID\", webConnID),\n\t\t\tmlog.String(\"userID\", userID),\n\t\t)\n\t\treturn\n\t}\n\n\tatomic.StoreInt64(&pac.inactiveAt, mmModel.GetMillis())\n}\n\nfunc commandFromRequest(req *mmModel.WebSocketRequest) (*WebsocketCommand, error) {\n\tc := &WebsocketCommand{Action: strings.TrimPrefix(req.Action, websocketMessagePrefix)}\n\n\tif teamID, ok := req.Data[\"teamId\"]; ok {\n\t\tc.TeamID = teamID.(string)\n\t} else {\n\t\treturn nil, errMissingTeamInCommand\n\t}\n\n\tif readToken, ok := req.Data[\"readToken\"]; ok {\n\t\tc.ReadToken = readToken.(string)\n\t}\n\n\tif blockIDs, ok := req.Data[\"blockIds\"]; ok {\n\t\tc.BlockIDs = blockIDs.([]string)\n\t}\n\n\treturn c, nil\n}\n\nfunc (pa *PluginAdapter) WebSocketMessageHasBeenPosted(webConnID, userID string, req *mmModel.WebSocketRequest) {\n\tpac, ok := pa.GetListenerByWebConnID(webConnID)\n\tif !ok {\n\t\tpa.logger.Debug(\"received a message for an unregistered webconn\",\n\t\t\tmlog.String(\"webConnID\", webConnID),\n\t\t\tmlog.String(\"userID\", userID),\n\t\t\tmlog.String(\"action\", req.Action),\n\t\t)\n\t\treturn\n\t}\n\n\t// only process messages using the plugin actions\n\tif !strings.HasPrefix(req.Action, websocketMessagePrefix) {\n\t\treturn\n\t}\n\n\tcommand, err := commandFromRequest(req)\n\tif err != nil {\n\t\tpa.logger.Error(\"error getting command from request\",\n\t\t\tmlog.String(\"action\", req.Action),\n\t\t\tmlog.String(\"webConnID\", webConnID),\n\t\t\tmlog.String(\"userID\", userID),\n\t\t\tmlog.Err(err),\n\t\t)\n\t\treturn\n\t}\n\n\tswitch command.Action {\n\t// The block-related commands are not implemented in the adapter\n\t// as there is no such thing as unauthenticated websocket\n\t// connections in plugin mode. Only a debug line is logged\n\tcase websocketActionSubscribeBlocks, websocketActionUnsubscribeBlocks:\n\t\tpa.logger.Debug(`Command not implemented in plugin mode`,\n\t\t\tmlog.String(\"command\", command.Action),\n\t\t\tmlog.String(\"webConnID\", webConnID),\n\t\t\tmlog.String(\"userID\", userID),\n\t\t\tmlog.String(\"teamID\", command.TeamID),\n\t\t)\n\n\tcase websocketActionSubscribeTeam:\n\t\tpa.logger.Debug(`Command not implemented in plugin mode`,\n\t\t\tmlog.String(\"command\", command.Action),\n\t\t\tmlog.String(\"webConnID\", webConnID),\n\t\t\tmlog.String(\"userID\", userID),\n\t\t\tmlog.String(\"teamID\", command.TeamID),\n\t\t)\n\n\t\tif !pa.auth.DoesUserHaveTeamAccess(userID, command.TeamID) {\n\t\t\treturn\n\t\t}\n\n\t\tpa.subscribeListenerToTeam(pac, command.TeamID)\n\tcase websocketActionUnsubscribeTeam:\n\t\tpa.logger.Debug(`Command: UNSUBSCRIBE_WORKSPACE`,\n\t\t\tmlog.String(\"webConnID\", webConnID),\n\t\t\tmlog.String(\"userID\", userID),\n\t\t\tmlog.String(\"teamID\", command.TeamID),\n\t\t)\n\n\t\tpa.unsubscribeListenerFromTeam(pac, command.TeamID)\n\t}\n}\n\n// sendMessageToAll will send a websocket message to all clients on all nodes.\nfunc (pa *PluginAdapter) sendMessageToAll(event string, payload map[string]interface{}) {\n\t// Empty &mmModel.WebsocketBroadcast will send to all users\n\tpa.api.PublishWebSocketEvent(event, payload, &mmModel.WebsocketBroadcast{})\n}\n\nfunc (pa *PluginAdapter) BroadcastConfigChange(pluginConfig model.ClientConfig) {\n\tpa.sendMessageToAll(websocketActionUpdateConfig, utils.StructToMap(pluginConfig))\n}\n\n// sendUserMessageSkipCluster sends the message to specific users.\nfunc (pa *PluginAdapter) sendUserMessageSkipCluster(event string, payload map[string]interface{}, userIDs ...string) {\n\tfor _, userID := range userIDs {\n\t\tpa.api.PublishWebSocketEvent(event, payload, &mmModel.WebsocketBroadcast{UserId: userID})\n\t}\n}\n\n// sendTeamMessageSkipCluster sends a message to all the users\n// with a websocket client subscribed to a given team.\nfunc (pa *PluginAdapter) sendTeamMessageSkipCluster(event, teamID string, payload map[string]interface{}) {\n\tuserIDs := pa.getUserIDsForTeam(teamID)\n\tpa.sendUserMessageSkipCluster(event, payload, userIDs...)\n}\n\n// sendTeamMessage sends and propagates a message that is aimed\n// for all the users that are subscribed to a given team.\nfunc (pa *PluginAdapter) sendTeamMessage(event, teamID string, payload map[string]interface{}, ensureUserIDs ...string) {\n\tgo func() {\n\t\tclusterMessage := &ClusterMessage{\n\t\t\tTeamID:      teamID,\n\t\t\tPayload:     payload,\n\t\t\tEnsureUsers: ensureUserIDs,\n\t\t}\n\n\t\tpa.sendMessageToCluster(clusterMessage)\n\t}()\n\n\tpa.sendTeamMessageSkipCluster(event, teamID, payload)\n}\n\n// sendBoardMessageSkipCluster sends a message to all the users\n// subscribed to a given team that belong to one of its boards.\nfunc (pa *PluginAdapter) sendBoardMessageSkipCluster(teamID, boardID string, payload map[string]interface{}, ensureUserIDs ...string) {\n\tuserIDs := pa.getUserIDsForTeamAndBoard(teamID, boardID, ensureUserIDs...)\n\tpa.sendUserMessageSkipCluster(websocketActionUpdateBoard, payload, userIDs...)\n}\n\n// sendBoardMessage sends and propagates a message that is aimed for\n// all the users that are subscribed to the board's team and are\n// members of it too.\nfunc (pa *PluginAdapter) sendBoardMessage(teamID, boardID string, payload map[string]interface{}, ensureUserIDs ...string) {\n\tgo func() {\n\t\tclusterMessage := &ClusterMessage{\n\t\t\tTeamID:      teamID,\n\t\t\tBoardID:     boardID,\n\t\t\tPayload:     payload,\n\t\t\tEnsureUsers: ensureUserIDs,\n\t\t}\n\n\t\tpa.sendMessageToCluster(clusterMessage)\n\t}()\n\n\tpa.sendBoardMessageSkipCluster(teamID, boardID, payload, ensureUserIDs...)\n}\n\nfunc (pa *PluginAdapter) BroadcastBlockChange(teamID string, block *model.Block) {\n\tpa.logger.Trace(\"BroadcastingBlockChange\",\n\t\tmlog.String(\"teamID\", teamID),\n\t\tmlog.String(\"boardID\", block.BoardID),\n\t\tmlog.String(\"blockID\", block.ID),\n\t)\n\n\tmessage := UpdateBlockMsg{\n\t\tAction: websocketActionUpdateBlock,\n\t\tTeamID: teamID,\n\t\tBlock:  block,\n\t}\n\n\tpa.sendBoardMessage(teamID, block.BoardID, utils.StructToMap(message))\n}\n\nfunc (pa *PluginAdapter) BroadcastCategoryChange(category model.Category) {\n\tpa.logger.Debug(\"BroadcastCategoryChange\",\n\t\tmlog.String(\"userID\", category.UserID),\n\t\tmlog.String(\"teamID\", category.TeamID),\n\t\tmlog.String(\"categoryID\", category.ID),\n\t)\n\n\tmessage := UpdateCategoryMessage{\n\t\tAction:   websocketActionUpdateCategory,\n\t\tTeamID:   category.TeamID,\n\t\tCategory: &category,\n\t}\n\n\tpayload := utils.StructToMap(message)\n\n\tgo func() {\n\t\tclusterMessage := &ClusterMessage{\n\t\t\tPayload: payload,\n\t\t\tUserID:  category.UserID,\n\t\t}\n\n\t\tpa.sendMessageToCluster(clusterMessage)\n\t}()\n\n\tpa.sendUserMessageSkipCluster(websocketActionUpdateCategory, payload, category.UserID)\n}\n\nfunc (pa *PluginAdapter) BroadcastCategoryReorder(teamID, userID string, categoryOrder []string) {\n\tpa.logger.Debug(\"BroadcastCategoryReorder\",\n\t\tmlog.String(\"userID\", userID),\n\t\tmlog.String(\"teamID\", teamID),\n\t)\n\n\tmessage := CategoryReorderMessage{\n\t\tAction:        websocketActionReorderCategories,\n\t\tCategoryOrder: categoryOrder,\n\t\tTeamID:        teamID,\n\t}\n\tpayload := utils.StructToMap(message)\n\tgo func() {\n\t\tclusterMessage := &ClusterMessage{\n\t\t\tPayload: payload,\n\t\t\tUserID:  userID,\n\t\t}\n\n\t\tpa.sendMessageToCluster(clusterMessage)\n\t}()\n\n\tpa.sendUserMessageSkipCluster(message.Action, payload, userID)\n}\n\nfunc (pa *PluginAdapter) BroadcastCategoryBoardsReorder(teamID, userID, categoryID string, boardsOrder []string) {\n\tpa.logger.Debug(\"BroadcastCategoryBoardsReorder\",\n\t\tmlog.String(\"userID\", userID),\n\t\tmlog.String(\"teamID\", teamID),\n\t\tmlog.String(\"categoryID\", categoryID),\n\t)\n\n\tmessage := CategoryBoardReorderMessage{\n\t\tAction:     websocketActionReorderCategoryBoards,\n\t\tCategoryID: categoryID,\n\t\tBoardOrder: boardsOrder,\n\t\tTeamID:     teamID,\n\t}\n\tpayload := utils.StructToMap(message)\n\tgo func() {\n\t\tclusterMessage := &ClusterMessage{\n\t\t\tPayload: payload,\n\t\t\tUserID:  userID,\n\t\t}\n\n\t\tpa.sendMessageToCluster(clusterMessage)\n\t}()\n\n\tpa.sendUserMessageSkipCluster(message.Action, payload, userID)\n}\n\nfunc (pa *PluginAdapter) BroadcastCategoryBoardChange(teamID, userID string, boardCategories []*model.BoardCategoryWebsocketData) {\n\tpa.logger.Debug(\n\t\t\"BroadcastCategoryBoardChange\",\n\t\tmlog.String(\"userID\", userID),\n\t\tmlog.String(\"teamID\", teamID),\n\t\tmlog.Int(\"numEntries\", len(boardCategories)),\n\t)\n\n\tmessage := UpdateCategoryMessage{\n\t\tAction:          websocketActionUpdateCategoryBoard,\n\t\tTeamID:          teamID,\n\t\tBoardCategories: boardCategories,\n\t}\n\n\tpayload := utils.StructToMap(message)\n\n\tgo func() {\n\t\tclusterMessage := &ClusterMessage{\n\t\t\tPayload: payload,\n\t\t\tUserID:  userID,\n\t\t}\n\n\t\tpa.sendMessageToCluster(clusterMessage)\n\t}()\n\n\tpa.sendUserMessageSkipCluster(websocketActionUpdateCategoryBoard, utils.StructToMap(message), userID)\n}\n\nfunc (pa *PluginAdapter) BroadcastBlockDelete(teamID, blockID, boardID string) {\n\tnow := utils.GetMillis()\n\tblock := &model.Block{}\n\tblock.ID = blockID\n\tblock.BoardID = boardID\n\tblock.UpdateAt = now\n\tblock.DeleteAt = now\n\n\tpa.BroadcastBlockChange(teamID, block)\n}\n\nfunc (pa *PluginAdapter) BroadcastBoardChange(teamID string, board *model.Board) {\n\tpa.logger.Debug(\"BroadcastingBoardChange\",\n\t\tmlog.String(\"teamID\", teamID),\n\t\tmlog.String(\"boardID\", board.ID),\n\t)\n\n\tmessage := UpdateBoardMsg{\n\t\tAction: websocketActionUpdateBoard,\n\t\tTeamID: teamID,\n\t\tBoard:  board,\n\t}\n\n\tpa.sendBoardMessage(teamID, board.ID, utils.StructToMap(message))\n}\n\nfunc (pa *PluginAdapter) BroadcastBoardDelete(teamID, boardID string) {\n\tnow := utils.GetMillis()\n\tboard := &model.Board{}\n\tboard.ID = boardID\n\tboard.TeamID = teamID\n\tboard.UpdateAt = now\n\tboard.DeleteAt = now\n\n\tpa.BroadcastBoardChange(teamID, board)\n}\n\nfunc (pa *PluginAdapter) BroadcastMemberChange(teamID, boardID string, member *model.BoardMember) {\n\tpa.logger.Debug(\"BroadcastingMemberChange\",\n\t\tmlog.String(\"teamID\", teamID),\n\t\tmlog.String(\"boardID\", boardID),\n\t\tmlog.String(\"userID\", member.UserID),\n\t)\n\n\tmessage := UpdateMemberMsg{\n\t\tAction: websocketActionUpdateMember,\n\t\tTeamID: teamID,\n\t\tMember: member,\n\t}\n\n\tpa.sendBoardMessage(teamID, boardID, utils.StructToMap(message), member.UserID)\n}\n\nfunc (pa *PluginAdapter) BroadcastMemberDelete(teamID, boardID, userID string) {\n\tpa.logger.Debug(\"BroadcastingMemberDelete\",\n\t\tmlog.String(\"teamID\", teamID),\n\t\tmlog.String(\"boardID\", boardID),\n\t\tmlog.String(\"userID\", userID),\n\t)\n\n\tmessage := UpdateMemberMsg{\n\t\tAction: websocketActionDeleteMember,\n\t\tTeamID: teamID,\n\t\tMember: &model.BoardMember{UserID: userID, BoardID: boardID},\n\t}\n\n\t// when fetching the members of the board that should receive the\n\t// member deletion message, the deleted member will not be one of\n\t// them, so we need to ensure they receive the message\n\tpa.sendBoardMessage(teamID, boardID, utils.StructToMap(message), userID)\n}\n\nfunc (pa *PluginAdapter) BroadcastSubscriptionChange(teamID string, subscription *model.Subscription) {\n\tpa.logger.Debug(\"BroadcastingSubscriptionChange\",\n\t\tmlog.String(\"TeamID\", teamID),\n\t\tmlog.String(\"blockID\", subscription.BlockID),\n\t\tmlog.String(\"subscriberID\", subscription.SubscriberID),\n\t)\n\n\tmessage := UpdateSubscription{\n\t\tAction:       websocketActionUpdateSubscription,\n\t\tSubscription: subscription,\n\t}\n\n\tpa.sendTeamMessage(websocketActionUpdateSubscription, teamID, utils.StructToMap(message))\n}\n\nfunc (pa *PluginAdapter) BroadcastCardLimitTimestampChange(cardLimitTimestamp int64) {\n\tpa.logger.Debug(\"BroadcastCardLimitTimestampChange\",\n\t\tmlog.Int(\"cardLimitTimestamp\", cardLimitTimestamp),\n\t)\n\n\tmessage := UpdateCardLimitTimestamp{\n\t\tAction:    websocketActionUpdateCardLimitTimestamp,\n\t\tTimestamp: cardLimitTimestamp,\n\t}\n\n\tpa.sendMessageToAll(websocketActionUpdateCardLimitTimestamp, utils.StructToMap(message))\n}\n"
  },
  {
    "path": "server/ws/plugin_adapter_client.go",
    "content": "package ws\n\nimport (\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\tmmModel \"github.com/mattermost/mattermost/server/public/model\"\n)\n\ntype PluginAdapterClient struct {\n\tinactiveAt int64\n\twebConnID  string\n\tuserID     string\n\tteams      []string\n\tblocks     []string\n\tmu         sync.RWMutex\n}\n\nfunc (pac *PluginAdapterClient) isActive() bool {\n\treturn atomic.LoadInt64(&pac.inactiveAt) == 0\n}\n\nfunc (pac *PluginAdapterClient) hasExpired(threshold time.Duration) bool {\n\treturn !mmModel.GetTimeForMillis(atomic.LoadInt64(&pac.inactiveAt)).Add(threshold).After(time.Now())\n}\n\nfunc (pac *PluginAdapterClient) subscribeToTeam(teamID string) {\n\tpac.mu.Lock()\n\tdefer pac.mu.Unlock()\n\n\tpac.teams = append(pac.teams, teamID)\n}\n\nfunc (pac *PluginAdapterClient) unsubscribeFromTeam(teamID string) {\n\tpac.mu.Lock()\n\tdefer pac.mu.Unlock()\n\n\tnewClientTeams := []string{}\n\tfor _, id := range pac.teams {\n\t\tif id != teamID {\n\t\t\tnewClientTeams = append(newClientTeams, id)\n\t\t}\n\t}\n\tpac.teams = newClientTeams\n}\n\nfunc (pac *PluginAdapterClient) unsubscribeFromBlock(blockID string) {\n\tpac.mu.Lock()\n\tdefer pac.mu.Unlock()\n\n\tnewClientBlocks := []string{}\n\tfor _, id := range pac.blocks {\n\t\tif id != blockID {\n\t\t\tnewClientBlocks = append(newClientBlocks, id)\n\t\t}\n\t}\n\tpac.blocks = newClientBlocks\n}\n\nfunc (pac *PluginAdapterClient) isSubscribedToTeam(teamID string) bool {\n\tpac.mu.RLock()\n\tdefer pac.mu.RUnlock()\n\n\tfor _, id := range pac.teams {\n\t\tif id == teamID {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\n//nolint:unused\nfunc (pac *PluginAdapterClient) isSubscribedToBlock(blockID string) bool {\n\tpac.mu.RLock()\n\tdefer pac.mu.RUnlock()\n\n\tfor _, id := range pac.blocks {\n\t\tif id == blockID {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n"
  },
  {
    "path": "server/ws/plugin_adapter_cluster.go",
    "content": "package ws\n\nimport (\n\t\"encoding/json\"\n\n\tmmModel \"github.com/mattermost/mattermost/server/public/model\"\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\ntype ClusterMessage struct {\n\tTeamID      string\n\tBoardID     string\n\tUserID      string\n\tPayload     map[string]interface{}\n\tEnsureUsers []string\n}\n\nfunc (pa *PluginAdapter) sendMessageToCluster(clusterMessage *ClusterMessage) {\n\tconst id = \"websocket_message\"\n\tb, err := json.Marshal(clusterMessage)\n\tif err != nil {\n\t\tpa.logger.Error(\"couldn't get JSON bytes from cluster message\",\n\t\t\tmlog.String(\"id\", id),\n\t\t\tmlog.Err(err),\n\t\t)\n\t\treturn\n\t}\n\n\tevent := mmModel.PluginClusterEvent{Id: id, Data: b}\n\topts := mmModel.PluginClusterEventSendOptions{\n\t\tSendType: mmModel.PluginClusterEventSendTypeReliable,\n\t}\n\n\tif err := pa.api.PublishPluginClusterEvent(event, opts); err != nil {\n\t\tpa.logger.Error(\"error publishing cluster event\",\n\t\t\tmlog.String(\"id\", id),\n\t\t\tmlog.Err(err),\n\t\t)\n\t}\n}\n\nfunc (pa *PluginAdapter) HandleClusterEvent(ev mmModel.PluginClusterEvent) {\n\tpa.logger.Debug(\"received cluster event\", mlog.String(\"id\", ev.Id))\n\n\tvar clusterMessage ClusterMessage\n\tif err := json.Unmarshal(ev.Data, &clusterMessage); err != nil {\n\t\tpa.logger.Error(\"cannot unmarshal cluster message data\",\n\t\t\tmlog.String(\"id\", ev.Id),\n\t\t\tmlog.Err(err),\n\t\t)\n\t\treturn\n\t}\n\n\tif clusterMessage.BoardID != \"\" {\n\t\tpa.sendBoardMessageSkipCluster(clusterMessage.TeamID, clusterMessage.BoardID, clusterMessage.Payload, clusterMessage.EnsureUsers...)\n\t\treturn\n\t}\n\n\tvar action string\n\tif actionRaw, ok := clusterMessage.Payload[\"action\"]; ok {\n\t\tif s, ok := actionRaw.(string); ok {\n\t\t\taction = s\n\t\t}\n\t}\n\tif action == \"\" {\n\t\t// no action was specified in the event; assume block change and warn.\n\t\tpa.logger.Warn(\"cannot determine action from cluster message data\",\n\t\t\tmlog.String(\"id\", ev.Id),\n\t\t\tmlog.Map(\"payload\", clusterMessage.Payload),\n\t\t)\n\t\treturn\n\t}\n\n\tif clusterMessage.UserID != \"\" {\n\t\tpa.sendUserMessageSkipCluster(action, clusterMessage.Payload, clusterMessage.UserID)\n\t\treturn\n\t}\n\n\tpa.sendTeamMessageSkipCluster(action, clusterMessage.TeamID, clusterMessage.Payload)\n}\n"
  },
  {
    "path": "server/ws/plugin_adapter_test.go",
    "content": "package ws\n\nimport (\n\t\"sync\"\n\t\"testing\"\n\n\t\"github.com/mattermost/focalboard/server/model\"\n\n\tmmModel \"github.com/mattermost/mattermost/server/public/model\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestPluginAdapterTeamSubscription(t *testing.T) {\n\tth := SetupTestHelper(t)\n\n\twebConnID := mmModel.NewId()\n\tuserID := mmModel.NewId()\n\tteamID := mmModel.NewId()\n\n\tvar pac *PluginAdapterClient\n\tt.Run(\"Should correctly add a connection\", func(t *testing.T) {\n\t\trequire.Empty(t, th.pa.listeners)\n\t\trequire.Empty(t, th.pa.listenersByTeam)\n\t\tth.pa.OnWebSocketConnect(webConnID, userID)\n\t\trequire.Len(t, th.pa.listeners, 1)\n\n\t\tvar ok bool\n\t\tpac, ok = th.pa.listeners[webConnID]\n\t\trequire.True(t, ok)\n\t\trequire.NotNil(t, pac)\n\t\trequire.Equal(t, userID, pac.userID)\n\t\trequire.Empty(t, th.pa.listenersByTeam)\n\t})\n\n\tt.Run(\"Should correctly subscribe to a team\", func(t *testing.T) {\n\t\trequire.False(t, pac.isSubscribedToTeam(teamID))\n\n\t\tth.SubscribeWebConnToTeam(pac.webConnID, pac.userID, teamID)\n\n\t\trequire.Len(t, th.pa.listenersByTeam[teamID], 1)\n\t\trequire.Contains(t, th.pa.listenersByTeam[teamID], pac)\n\t\trequire.Len(t, pac.teams, 1)\n\t\trequire.Contains(t, pac.teams, teamID)\n\n\t\trequire.True(t, pac.isSubscribedToTeam(teamID))\n\t})\n\n\tt.Run(\"Subscribing again to a subscribed team would have no effect\", func(t *testing.T) {\n\t\trequire.True(t, pac.isSubscribedToTeam(teamID))\n\n\t\tth.SubscribeWebConnToTeam(pac.webConnID, pac.userID, teamID)\n\n\t\trequire.Len(t, th.pa.listenersByTeam[teamID], 1)\n\t\trequire.Contains(t, th.pa.listenersByTeam[teamID], pac)\n\t\trequire.Len(t, pac.teams, 1)\n\t\trequire.Contains(t, pac.teams, teamID)\n\n\t\trequire.True(t, pac.isSubscribedToTeam(teamID))\n\t})\n\n\tt.Run(\"Should correctly unsubscribe to a team\", func(t *testing.T) {\n\t\trequire.True(t, pac.isSubscribedToTeam(teamID))\n\n\t\tth.UnsubscribeWebConnFromTeam(pac.webConnID, pac.userID, teamID)\n\n\t\trequire.Empty(t, th.pa.listenersByTeam[teamID])\n\t\trequire.Empty(t, pac.teams)\n\n\t\trequire.False(t, pac.isSubscribedToTeam(teamID))\n\t})\n\n\tt.Run(\"Unsubscribing again to an unsubscribed team would have no effect\", func(t *testing.T) {\n\t\trequire.False(t, pac.isSubscribedToTeam(teamID))\n\n\t\tth.UnsubscribeWebConnFromTeam(pac.webConnID, pac.userID, teamID)\n\n\t\trequire.Empty(t, th.pa.listenersByTeam[teamID])\n\t\trequire.Empty(t, pac.teams)\n\n\t\trequire.False(t, pac.isSubscribedToTeam(teamID))\n\t})\n\n\tt.Run(\"Should correctly be marked as inactive if disconnected\", func(t *testing.T) {\n\t\trequire.Len(t, th.pa.listeners, 1)\n\t\trequire.True(t, th.pa.listeners[webConnID].isActive())\n\n\t\tth.pa.OnWebSocketDisconnect(webConnID, userID)\n\n\t\trequire.Len(t, th.pa.listeners, 1)\n\t\trequire.False(t, th.pa.listeners[webConnID].isActive())\n\t})\n\n\tt.Run(\"Should be marked back as active if reconnect\", func(t *testing.T) {\n\t\trequire.Len(t, th.pa.listeners, 1)\n\t\trequire.False(t, th.pa.listeners[webConnID].isActive())\n\n\t\tth.pa.OnWebSocketConnect(webConnID, userID)\n\n\t\trequire.Len(t, th.pa.listeners, 1)\n\t\trequire.True(t, th.pa.listeners[webConnID].isActive())\n\t})\n}\n\nfunc TestPluginAdapterClientReconnect(t *testing.T) {\n\tth := SetupTestHelper(t)\n\n\twebConnID := mmModel.NewId()\n\tuserID := mmModel.NewId()\n\tteamID := mmModel.NewId()\n\n\tvar pac *PluginAdapterClient\n\tt.Run(\"A user should be able to reconnect within the accepted threshold and keep their subscriptions\", func(t *testing.T) {\n\t\t// create the connection\n\t\trequire.Len(t, th.pa.listeners, 0)\n\t\trequire.Len(t, th.pa.listenersByUserID[userID], 0)\n\t\tth.pa.OnWebSocketConnect(webConnID, userID)\n\t\trequire.Len(t, th.pa.listeners, 1)\n\t\trequire.Len(t, th.pa.listenersByUserID[userID], 1)\n\t\tvar ok bool\n\t\tpac, ok = th.pa.listeners[webConnID]\n\t\trequire.True(t, ok)\n\t\trequire.NotNil(t, pac)\n\n\t\tth.SubscribeWebConnToTeam(pac.webConnID, pac.userID, teamID)\n\t\trequire.True(t, pac.isSubscribedToTeam(teamID))\n\n\t\t// disconnect\n\t\tth.pa.OnWebSocketDisconnect(webConnID, userID)\n\t\trequire.False(t, pac.isActive())\n\t\trequire.Len(t, th.pa.listeners, 1)\n\t\trequire.Len(t, th.pa.listenersByUserID[userID], 1)\n\n\t\t// reconnect right away. The connection should still be subscribed\n\t\tth.pa.OnWebSocketConnect(webConnID, userID)\n\t\trequire.Len(t, th.pa.listeners, 1)\n\t\trequire.Len(t, th.pa.listenersByUserID[userID], 1)\n\t\trequire.True(t, pac.isActive())\n\t\trequire.True(t, pac.isSubscribedToTeam(teamID))\n\t})\n\n\tt.Run(\"Should remove old inactive connection when user connects with a different ID\", func(t *testing.T) {\n\t\t// we set the stale threshold to zero so inactive connections always get deleted\n\t\toldStaleThreshold := th.pa.staleThreshold\n\t\tth.pa.staleThreshold = 0\n\t\tdefer func() { th.pa.staleThreshold = oldStaleThreshold }()\n\t\tth.pa.OnWebSocketDisconnect(webConnID, userID)\n\t\trequire.Len(t, th.pa.listeners, 1)\n\t\trequire.Len(t, th.pa.listenersByUserID[userID], 1)\n\t\trequire.Equal(t, webConnID, th.pa.listenersByUserID[userID][0].webConnID)\n\n\t\tnewWebConnID := mmModel.NewId()\n\t\tth.pa.OnWebSocketConnect(newWebConnID, userID)\n\n\t\trequire.Len(t, th.pa.listeners, 1)\n\t\trequire.Len(t, th.pa.listenersByUserID[userID], 1)\n\t\trequire.Contains(t, th.pa.listeners, newWebConnID)\n\t\trequire.NotContains(t, th.pa.listeners, webConnID)\n\t\trequire.Equal(t, newWebConnID, th.pa.listenersByUserID[userID][0].webConnID)\n\n\t\t// if the same ID connects again, it should have no subscriptions\n\t\tth.pa.OnWebSocketConnect(webConnID, userID)\n\t\trequire.Len(t, th.pa.listeners, 2)\n\t\trequire.Len(t, th.pa.listenersByUserID[userID], 2)\n\t\treconnectedPAC, ok := th.pa.listeners[webConnID]\n\t\trequire.True(t, ok)\n\t\trequire.False(t, reconnectedPAC.isSubscribedToTeam(teamID))\n\t})\n\n\tt.Run(\"Should not remove active connections when user connects with a different ID\", func(t *testing.T) {\n\t\t// we set the stale threshold to zero so inactive connections always get deleted\n\t\toldStaleThreshold := th.pa.staleThreshold\n\t\tth.pa.staleThreshold = 0\n\t\tdefer func() { th.pa.staleThreshold = oldStaleThreshold }()\n\n\t\t// currently we have two listeners for userID, both active\n\t\trequire.Len(t, th.pa.listeners, 2)\n\n\t\t// a new user connects\n\t\tth.pa.OnWebSocketConnect(mmModel.NewId(), userID)\n\n\t\t// and we should have three connections, all of them active\n\t\trequire.Len(t, th.pa.listeners, 3)\n\n\t\tfor _, listener := range th.pa.listeners {\n\t\t\trequire.True(t, listener.isActive())\n\t\t}\n\t})\n}\n\nfunc TestGetUserIDsForTeam(t *testing.T) {\n\tth := SetupTestHelper(t)\n\n\t// we have two teams\n\tteamID1 := mmModel.NewId()\n\tteamID2 := mmModel.NewId()\n\n\t// user 1 has two connections\n\tuserID1 := mmModel.NewId()\n\twebConnID1 := mmModel.NewId()\n\twebConnID2 := mmModel.NewId()\n\n\t// user 2 has one connection\n\tuserID2 := mmModel.NewId()\n\twebConnID3 := mmModel.NewId()\n\n\twg := new(sync.WaitGroup)\n\twg.Add(3)\n\n\tgo func(wg *sync.WaitGroup) {\n\t\tth.pa.OnWebSocketConnect(webConnID1, userID1)\n\t\tth.SubscribeWebConnToTeam(webConnID1, userID1, teamID1)\n\t\twg.Done()\n\t}(wg)\n\n\tgo func(wg *sync.WaitGroup) {\n\t\tth.pa.OnWebSocketConnect(webConnID2, userID1)\n\t\tth.SubscribeWebConnToTeam(webConnID2, userID1, teamID2)\n\t\twg.Done()\n\t}(wg)\n\n\tgo func(wg *sync.WaitGroup) {\n\t\tth.pa.OnWebSocketConnect(webConnID3, userID2)\n\t\tth.SubscribeWebConnToTeam(webConnID3, userID2, teamID2)\n\t\twg.Done()\n\t}(wg)\n\n\twg.Wait()\n\n\tt.Run(\"should find that only user1 is connected to team 1\", func(t *testing.T) {\n\t\tth.auth.EXPECT().\n\t\t\tDoesUserHaveTeamAccess(userID1, teamID1).\n\t\t\tReturn(true).\n\t\t\tTimes(1)\n\n\t\tuserIDs := th.pa.getUserIDsForTeam(teamID1)\n\t\trequire.ElementsMatch(t, []string{userID1}, userIDs)\n\t})\n\n\tt.Run(\"should find that both users are connected to team 2\", func(t *testing.T) {\n\t\tth.auth.EXPECT().\n\t\t\tDoesUserHaveTeamAccess(userID1, teamID2).\n\t\t\tReturn(true).\n\t\t\tTimes(1)\n\t\tth.auth.EXPECT().\n\t\t\tDoesUserHaveTeamAccess(userID2, teamID2).\n\t\t\tReturn(true).\n\t\t\tTimes(1)\n\n\t\tuserIDs := th.pa.getUserIDsForTeam(teamID2)\n\t\trequire.ElementsMatch(t, []string{userID1, userID2}, userIDs)\n\t})\n\n\tt.Run(\"should ignore user1 if webConn 2 inactive when getting team 2 user ids\", func(t *testing.T) {\n\t\tth.pa.OnWebSocketDisconnect(webConnID2, userID1)\n\n\t\tth.auth.EXPECT().\n\t\t\tDoesUserHaveTeamAccess(userID2, teamID2).\n\t\t\tReturn(true).\n\t\t\tTimes(1)\n\n\t\tuserIDs := th.pa.getUserIDsForTeam(teamID2)\n\t\trequire.ElementsMatch(t, []string{userID2}, userIDs)\n\t})\n\n\tt.Run(\"should still find user 1 in team 1 after the webConn 2 disconnection\", func(t *testing.T) {\n\t\tth.auth.EXPECT().\n\t\t\tDoesUserHaveTeamAccess(userID1, teamID1).\n\t\t\tReturn(true).\n\t\t\tTimes(1)\n\n\t\tuserIDs := th.pa.getUserIDsForTeam(teamID1)\n\t\trequire.ElementsMatch(t, []string{userID1}, userIDs)\n\t})\n\n\tt.Run(\"should find again both users if the webConn 2 comes back\", func(t *testing.T) {\n\t\tth.pa.OnWebSocketConnect(webConnID2, userID1)\n\n\t\tth.auth.EXPECT().\n\t\t\tDoesUserHaveTeamAccess(userID1, teamID2).\n\t\t\tReturn(true).\n\t\t\tTimes(1)\n\t\tth.auth.EXPECT().\n\t\t\tDoesUserHaveTeamAccess(userID2, teamID2).\n\t\t\tReturn(true).\n\t\t\tTimes(1)\n\n\t\tuserIDs := th.pa.getUserIDsForTeam(teamID2)\n\t\trequire.ElementsMatch(t, []string{userID1, userID2}, userIDs)\n\t})\n\n\tt.Run(\"should only find user 1 if user 2 has an active connection but is not a team member anymore\", func(t *testing.T) {\n\t\tth.auth.EXPECT().\n\t\t\tDoesUserHaveTeamAccess(userID1, teamID2).\n\t\t\tReturn(true).\n\t\t\tTimes(1)\n\n\t\t// userID2 does not have team access\n\t\tth.auth.EXPECT().\n\t\t\tDoesUserHaveTeamAccess(userID2, teamID2).\n\t\t\tReturn(false).\n\t\t\tTimes(1)\n\n\t\tuserIDs := th.pa.getUserIDsForTeam(teamID2)\n\t\trequire.ElementsMatch(t, []string{userID1}, userIDs)\n\t})\n}\n\nfunc TestGetUserIDsForTeamAndBoard(t *testing.T) {\n\tth := SetupTestHelper(t)\n\n\t// we have two teams\n\tteamID1 := mmModel.NewId()\n\tboardID1 := mmModel.NewId()\n\tteamID2 := mmModel.NewId()\n\tboardID2 := mmModel.NewId()\n\n\t// user 1 has two connections\n\tuserID1 := mmModel.NewId()\n\twebConnID1 := mmModel.NewId()\n\twebConnID2 := mmModel.NewId()\n\n\t// user 2 has one connection\n\tuserID2 := mmModel.NewId()\n\twebConnID3 := mmModel.NewId()\n\n\twg := new(sync.WaitGroup)\n\twg.Add(3)\n\n\tgo func(wg *sync.WaitGroup) {\n\t\tth.pa.OnWebSocketConnect(webConnID1, userID1)\n\t\tth.SubscribeWebConnToTeam(webConnID1, userID1, teamID1)\n\t\twg.Done()\n\t}(wg)\n\n\tgo func(wg *sync.WaitGroup) {\n\t\tth.pa.OnWebSocketConnect(webConnID2, userID1)\n\t\tth.SubscribeWebConnToTeam(webConnID2, userID1, teamID2)\n\t\twg.Done()\n\t}(wg)\n\n\tgo func(wg *sync.WaitGroup) {\n\t\tth.pa.OnWebSocketConnect(webConnID3, userID2)\n\t\tth.SubscribeWebConnToTeam(webConnID3, userID2, teamID2)\n\t\twg.Done()\n\t}(wg)\n\n\twg.Wait()\n\n\tt.Run(\"should find that only user1 is connected to team 1 and board 1\", func(t *testing.T) {\n\t\tmockedMembers := []*model.BoardMember{{UserID: userID1}}\n\t\tth.store.EXPECT().\n\t\t\tGetMembersForBoard(boardID1).\n\t\t\tReturn(mockedMembers, nil).\n\t\t\tTimes(1)\n\n\t\tth.auth.EXPECT().\n\t\t\tDoesUserHaveTeamAccess(userID1, teamID1).\n\t\t\tReturn(true).\n\t\t\tTimes(1)\n\n\t\tuserIDs := th.pa.getUserIDsForTeamAndBoard(teamID1, boardID1)\n\t\trequire.ElementsMatch(t, []string{userID1}, userIDs)\n\t})\n\n\tt.Run(\"should find that both users are connected to team 2 and board 2\", func(t *testing.T) {\n\t\tmockedMembers := []*model.BoardMember{{UserID: userID1}, {UserID: userID2}}\n\t\tth.store.EXPECT().\n\t\t\tGetMembersForBoard(boardID2).\n\t\t\tReturn(mockedMembers, nil).\n\t\t\tTimes(1)\n\n\t\tth.auth.EXPECT().\n\t\t\tDoesUserHaveTeamAccess(userID1, teamID2).\n\t\t\tReturn(true).\n\t\t\tTimes(1)\n\t\tth.auth.EXPECT().\n\t\t\tDoesUserHaveTeamAccess(userID2, teamID2).\n\t\t\tReturn(true).\n\t\t\tTimes(1)\n\n\t\tuserIDs := th.pa.getUserIDsForTeamAndBoard(teamID2, boardID2)\n\t\trequire.ElementsMatch(t, []string{userID1, userID2}, userIDs)\n\t})\n\n\tt.Run(\"should find that only one user is connected to team 2 and board 2 if there is only one membership with both connected\", func(t *testing.T) {\n\t\tmockedMembers := []*model.BoardMember{{UserID: userID1}}\n\t\tth.store.EXPECT().\n\t\t\tGetMembersForBoard(boardID2).\n\t\t\tReturn(mockedMembers, nil).\n\t\t\tTimes(1)\n\n\t\tth.auth.EXPECT().\n\t\t\tDoesUserHaveTeamAccess(userID1, teamID2).\n\t\t\tReturn(true).\n\t\t\tTimes(1)\n\n\t\tuserIDs := th.pa.getUserIDsForTeamAndBoard(teamID2, boardID2)\n\t\trequire.ElementsMatch(t, []string{userID1}, userIDs)\n\t})\n\n\tt.Run(\"should find only one if the other is inactive\", func(t *testing.T) {\n\t\tth.pa.OnWebSocketDisconnect(webConnID3, userID2)\n\t\tdefer th.pa.OnWebSocketConnect(webConnID3, userID2)\n\n\t\tmockedMembers := []*model.BoardMember{{UserID: userID1}, {UserID: userID2}}\n\t\tth.store.EXPECT().\n\t\t\tGetMembersForBoard(boardID2).\n\t\t\tReturn(mockedMembers, nil).\n\t\t\tTimes(1)\n\n\t\tth.auth.EXPECT().\n\t\t\tDoesUserHaveTeamAccess(userID1, teamID2).\n\t\t\tReturn(true).\n\t\t\tTimes(1)\n\n\t\tuserIDs := th.pa.getUserIDsForTeamAndBoard(teamID2, boardID2)\n\t\trequire.ElementsMatch(t, []string{userID1}, userIDs)\n\t})\n\n\tt.Run(\"should include a user that is not present if it's ensured\", func(t *testing.T) {\n\t\tuserID3 := mmModel.NewId()\n\t\tmockedMembers := []*model.BoardMember{{UserID: userID1}, {UserID: userID2}}\n\t\tth.store.EXPECT().\n\t\t\tGetMembersForBoard(boardID2).\n\t\t\tReturn(mockedMembers, nil).\n\t\t\tTimes(1)\n\n\t\tth.auth.EXPECT().\n\t\t\tDoesUserHaveTeamAccess(userID1, teamID2).\n\t\t\tReturn(true).\n\t\t\tTimes(1)\n\t\tth.auth.EXPECT().\n\t\t\tDoesUserHaveTeamAccess(userID2, teamID2).\n\t\t\tReturn(true).\n\t\t\tTimes(1)\n\n\t\tuserIDs := th.pa.getUserIDsForTeamAndBoard(teamID2, boardID2, userID3)\n\t\trequire.ElementsMatch(t, []string{userID1, userID2, userID3}, userIDs)\n\t})\n\n\tt.Run(\"should not include a user that, although present, has no team access anymore\", func(t *testing.T) {\n\t\tmockedMembers := []*model.BoardMember{{UserID: userID1}, {UserID: userID2}}\n\t\tth.store.EXPECT().\n\t\t\tGetMembersForBoard(boardID2).\n\t\t\tReturn(mockedMembers, nil).\n\t\t\tTimes(1)\n\n\t\tth.auth.EXPECT().\n\t\t\tDoesUserHaveTeamAccess(userID1, teamID2).\n\t\t\tReturn(true).\n\t\t\tTimes(1)\n\n\t\t// userID2 has no team access\n\t\tth.auth.EXPECT().\n\t\t\tDoesUserHaveTeamAccess(userID2, teamID2).\n\t\t\tReturn(false).\n\t\t\tTimes(1)\n\n\t\tuserIDs := th.pa.getUserIDsForTeamAndBoard(teamID2, boardID2)\n\t\trequire.ElementsMatch(t, []string{userID1}, userIDs)\n\t})\n}\n\nfunc TestParallelSubscriptionsOnMultipleConnections(t *testing.T) {\n\tth := SetupTestHelper(t)\n\n\tteamID1 := mmModel.NewId()\n\tteamID2 := mmModel.NewId()\n\tteamID3 := mmModel.NewId()\n\tteamID4 := mmModel.NewId()\n\n\tuserID := mmModel.NewId()\n\twebConnID1 := mmModel.NewId()\n\twebConnID2 := mmModel.NewId()\n\n\tth.pa.OnWebSocketConnect(webConnID1, userID)\n\tpac1, ok := th.pa.GetListenerByWebConnID(webConnID1)\n\trequire.True(t, ok)\n\n\tth.pa.OnWebSocketConnect(webConnID2, userID)\n\tpac2, ok := th.pa.GetListenerByWebConnID(webConnID2)\n\trequire.True(t, ok)\n\n\twg := new(sync.WaitGroup)\n\twg.Add(4)\n\n\tgo func(wg *sync.WaitGroup) {\n\t\tth.SubscribeWebConnToTeam(webConnID1, userID, teamID1)\n\t\trequire.True(t, pac1.isSubscribedToTeam(teamID1))\n\n\t\tth.SubscribeWebConnToTeam(webConnID2, userID, teamID1)\n\t\trequire.True(t, pac2.isSubscribedToTeam(teamID1))\n\n\t\tth.UnsubscribeWebConnFromTeam(webConnID1, userID, teamID1)\n\t\trequire.False(t, pac1.isSubscribedToTeam(teamID1))\n\n\t\tth.UnsubscribeWebConnFromTeam(webConnID2, userID, teamID1)\n\t\trequire.False(t, pac2.isSubscribedToTeam(teamID1))\n\n\t\twg.Done()\n\t}(wg)\n\n\tgo func(wg *sync.WaitGroup) {\n\t\tth.SubscribeWebConnToTeam(webConnID1, userID, teamID2)\n\t\trequire.True(t, pac1.isSubscribedToTeam(teamID2))\n\n\t\tth.SubscribeWebConnToTeam(webConnID2, userID, teamID2)\n\t\trequire.True(t, pac2.isSubscribedToTeam(teamID2))\n\n\t\tth.UnsubscribeWebConnFromTeam(webConnID1, userID, teamID2)\n\t\trequire.False(t, pac1.isSubscribedToTeam(teamID2))\n\n\t\tth.UnsubscribeWebConnFromTeam(webConnID2, userID, teamID2)\n\t\trequire.False(t, pac2.isSubscribedToTeam(teamID2))\n\n\t\twg.Done()\n\t}(wg)\n\n\tgo func(wg *sync.WaitGroup) {\n\t\tth.SubscribeWebConnToTeam(webConnID1, userID, teamID3)\n\t\trequire.True(t, pac1.isSubscribedToTeam(teamID3))\n\n\t\tth.SubscribeWebConnToTeam(webConnID2, userID, teamID3)\n\t\trequire.True(t, pac2.isSubscribedToTeam(teamID3))\n\n\t\tth.UnsubscribeWebConnFromTeam(webConnID1, userID, teamID3)\n\t\trequire.False(t, pac1.isSubscribedToTeam(teamID3))\n\n\t\tth.UnsubscribeWebConnFromTeam(webConnID2, userID, teamID3)\n\t\trequire.False(t, pac2.isSubscribedToTeam(teamID3))\n\n\t\twg.Done()\n\t}(wg)\n\n\tgo func(wg *sync.WaitGroup) {\n\t\tth.SubscribeWebConnToTeam(webConnID1, userID, teamID4)\n\t\trequire.True(t, pac1.isSubscribedToTeam(teamID4))\n\n\t\tth.SubscribeWebConnToTeam(webConnID2, userID, teamID4)\n\t\trequire.True(t, pac2.isSubscribedToTeam(teamID4))\n\n\t\tth.UnsubscribeWebConnFromTeam(webConnID1, userID, teamID4)\n\t\trequire.False(t, pac1.isSubscribedToTeam(teamID4))\n\n\t\tth.UnsubscribeWebConnFromTeam(webConnID2, userID, teamID4)\n\t\trequire.False(t, pac2.isSubscribedToTeam(teamID4))\n\n\t\twg.Done()\n\t}(wg)\n\n\twg.Wait()\n}\n"
  },
  {
    "path": "server/ws/server.go",
    "content": "package ws\n\nimport (\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"sync\"\n\n\t\"github.com/gorilla/mux\"\n\t\"github.com/gorilla/websocket\"\n\t\"github.com/mattermost/focalboard/server/auth\"\n\t\"github.com/mattermost/focalboard/server/model\"\n\t\"github.com/mattermost/focalboard/server/utils\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nfunc (wss *websocketSession) WriteJSON(v interface{}) error {\n\twss.mu.Lock()\n\tdefer wss.mu.Unlock()\n\terr := wss.conn.WriteJSON(v)\n\treturn err\n}\n\nfunc (wss *websocketSession) isSubscribedToTeam(teamID string) bool {\n\tfor _, id := range wss.teams {\n\t\tif id == teamID {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc (wss *websocketSession) isSubscribedToBlock(blockID string) bool {\n\tfor _, id := range wss.blocks {\n\t\tif id == blockID {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\n// Server is a WebSocket server.\ntype Server struct {\n\tupgrader         websocket.Upgrader\n\tlisteners        map[*websocketSession]bool\n\tlistenersByTeam  map[string][]*websocketSession\n\tlistenersByBlock map[string][]*websocketSession\n\tmu               sync.RWMutex\n\tauth             *auth.Auth\n\tsingleUserToken  string\n\tisMattermostAuth bool\n\tlogger           mlog.LoggerIFace\n\tstore            Store\n}\n\ntype websocketSession struct {\n\tconn   *websocket.Conn\n\tuserID string\n\tmu     sync.Mutex\n\tteams  []string\n\tblocks []string\n}\n\nfunc (wss *websocketSession) isAuthenticated() bool {\n\treturn wss.userID != \"\"\n}\n\n// NewServer creates a new Server.\nfunc NewServer(auth *auth.Auth, singleUserToken string, isMattermostAuth bool, logger mlog.LoggerIFace, store Store) *Server {\n\treturn &Server{\n\t\tlisteners:        make(map[*websocketSession]bool),\n\t\tlistenersByTeam:  make(map[string][]*websocketSession),\n\t\tlistenersByBlock: make(map[string][]*websocketSession),\n\t\tupgrader: websocket.Upgrader{\n\t\t\tCheckOrigin: func(r *http.Request) bool {\n\t\t\t\treturn true\n\t\t\t},\n\t\t},\n\t\tauth:             auth,\n\t\tsingleUserToken:  singleUserToken,\n\t\tisMattermostAuth: isMattermostAuth,\n\t\tlogger:           logger,\n\t\tstore:            store,\n\t}\n}\n\n// RegisterRoutes registers routes.\nfunc (ws *Server) RegisterRoutes(r *mux.Router) {\n\tr.HandleFunc(\"/ws\", ws.handleWebSocket)\n}\n\nfunc (ws *Server) handleWebSocket(w http.ResponseWriter, r *http.Request) {\n\t// Upgrade initial GET request to a websocket\n\tclient, err := ws.upgrader.Upgrade(w, r, nil)\n\tif err != nil {\n\t\tws.logger.Error(\"ERROR upgrading to websocket\", mlog.Err(err))\n\t\treturn\n\t}\n\n\t// create an empty session with websocket client\n\twsSession := &websocketSession{\n\t\tconn:   client,\n\t\tuserID: \"\",\n\t\tmu:     sync.Mutex{},\n\t\tteams:  []string{},\n\t\tblocks: []string{},\n\t}\n\n\tif ws.isMattermostAuth {\n\t\twsSession.userID = r.Header.Get(\"Mattermost-User-Id\")\n\t}\n\n\tws.addListener(wsSession)\n\n\t// Make sure we close the connection when the function returns\n\tdefer func() {\n\t\tws.logger.Debug(\"DISCONNECT WebSocket\", mlog.Stringer(\"client\", wsSession.conn.RemoteAddr()))\n\n\t\t// Remove session from listeners\n\t\tws.removeListener(wsSession)\n\t\twsSession.conn.Close()\n\t}()\n\n\t// Simple message handling loop\n\tfor {\n\t\t_, p, err := wsSession.conn.ReadMessage()\n\t\tif err != nil {\n\t\t\tws.logger.Error(\"ERROR WebSocket\",\n\t\t\t\tmlog.Stringer(\"client\", wsSession.conn.RemoteAddr()),\n\t\t\t\tmlog.Err(err),\n\t\t\t)\n\t\t\tws.removeListener(wsSession)\n\t\t\tbreak\n\t\t}\n\n\t\tvar command WebsocketCommand\n\n\t\terr = json.Unmarshal(p, &command)\n\t\tif err != nil {\n\t\t\t// handle this error\n\t\t\tws.logger.Error(`ERROR webSocket parsing command`, mlog.String(\"json\", string(p)))\n\n\t\t\tcontinue\n\t\t}\n\n\t\tif command.Action == websocketActionAuth {\n\t\t\tws.logger.Debug(`Command: AUTH`, mlog.Stringer(\"client\", wsSession.conn.RemoteAddr()))\n\t\t\tws.authenticateListener(wsSession, command.Token)\n\n\t\t\tcontinue\n\t\t}\n\n\t\t// if the client wants to subscribe to a set of blocks and it\n\t\t// is sending a read token, we don't need to check for\n\t\t// authentication\n\t\tif command.Action == websocketActionSubscribeBlocks {\n\t\t\tws.logger.Debug(`Command: SUBSCRIBE_BLOCKS`,\n\t\t\t\tmlog.String(\"teamID\", command.TeamID),\n\t\t\t\tmlog.Stringer(\"client\", wsSession.conn.RemoteAddr()),\n\t\t\t)\n\n\t\t\tif !ws.isCommandReadTokenValid(command) {\n\t\t\t\tws.logger.Error(`Rejected invalid read token`,\n\t\t\t\t\tmlog.Stringer(\"client\", wsSession.conn.RemoteAddr()),\n\t\t\t\t\tmlog.String(\"action\", command.Action),\n\t\t\t\t\tmlog.String(\"readToken\", command.ReadToken),\n\t\t\t\t)\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tws.subscribeListenerToBlocks(wsSession, command.BlockIDs)\n\t\t\tcontinue\n\t\t}\n\n\t\tif command.Action == websocketActionUnsubscribeBlocks {\n\t\t\tws.logger.Debug(`Command: UNSUBSCRIBE_BLOCKS`,\n\t\t\t\tmlog.String(\"teamID\", command.TeamID),\n\t\t\t\tmlog.Stringer(\"client\", wsSession.conn.RemoteAddr()),\n\t\t\t)\n\n\t\t\tif !ws.isCommandReadTokenValid(command) {\n\t\t\t\tws.logger.Error(`Rejected invalid read token`,\n\t\t\t\t\tmlog.Stringer(\"client\", wsSession.conn.RemoteAddr()),\n\t\t\t\t\tmlog.String(\"action\", command.Action),\n\t\t\t\t\tmlog.String(\"readToken\", command.ReadToken),\n\t\t\t\t)\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tws.unsubscribeListenerFromBlocks(wsSession, command.BlockIDs)\n\t\t\tcontinue\n\t\t}\n\n\t\t// if the command is not authenticated at this point, it will\n\t\t// not be processed\n\t\tif !wsSession.isAuthenticated() {\n\t\t\tws.logger.Error(`Rejected unauthenticated message`,\n\t\t\t\tmlog.Stringer(\"client\", wsSession.conn.RemoteAddr()),\n\t\t\t\tmlog.String(\"action\", command.Action),\n\t\t\t)\n\n\t\t\tcontinue\n\t\t}\n\n\t\tswitch command.Action {\n\t\tcase websocketActionSubscribeTeam:\n\t\t\tws.logger.Debug(`Command: SUBSCRIBE_TEAM`,\n\t\t\t\tmlog.String(\"teamID\", command.TeamID),\n\t\t\t\tmlog.Stringer(\"client\", wsSession.conn.RemoteAddr()),\n\t\t\t)\n\n\t\t\t// if single user mode, check that the userID is valid and\n\t\t\t// assume that the user has permission if so\n\t\t\tif len(ws.singleUserToken) != 0 {\n\t\t\t\tif wsSession.userID != model.SingleUser {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\t// if not in single user mode validate that the session\n\t\t\t\t// has permissions to the team\n\t\t\t} else {\n\t\t\t\tws.logger.Debug(\"Not single user mode\")\n\t\t\t\tif !ws.auth.DoesUserHaveTeamAccess(wsSession.userID, command.TeamID) {\n\t\t\t\t\tws.logger.Error(\"WS user doesn't have team access\", mlog.String(\"teamID\", command.TeamID), mlog.String(\"userID\", wsSession.userID))\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tws.subscribeListenerToTeam(wsSession, command.TeamID)\n\t\tcase websocketActionUnsubscribeTeam:\n\t\t\tws.logger.Debug(`Command: UNSUBSCRIBE_TEAM`,\n\t\t\t\tmlog.String(\"teamID\", command.TeamID),\n\t\t\t\tmlog.Stringer(\"client\", wsSession.conn.RemoteAddr()),\n\t\t\t)\n\n\t\t\tws.unsubscribeListenerFromTeam(wsSession, command.TeamID)\n\t\tdefault:\n\t\t\tws.logger.Error(`ERROR webSocket command, invalid action`, mlog.String(\"action\", command.Action))\n\t\t}\n\t}\n}\n\n// isCommandReadTokenValid ensures that a command contains a read\n// token and a set of block ids that said token is valid for.\nfunc (ws *Server) isCommandReadTokenValid(command WebsocketCommand) bool {\n\tif len(command.TeamID) == 0 {\n\t\treturn false\n\t}\n\n\tboardID := \"\"\n\t// all the blocks must be part of the same board\n\tfor _, blockID := range command.BlockIDs {\n\t\tblock, err := ws.store.GetBlock(blockID)\n\t\tif err != nil {\n\t\t\treturn false\n\t\t}\n\n\t\tif boardID == \"\" {\n\t\t\tboardID = block.BoardID\n\t\t\tcontinue\n\t\t}\n\n\t\tif boardID != block.BoardID {\n\t\t\treturn false\n\t\t}\n\t}\n\n\t// the read token must be valid for the board\n\tisValid, err := ws.auth.IsValidReadToken(boardID, command.ReadToken)\n\tif err != nil {\n\t\tws.logger.Error(`ERROR when checking token validity`,\n\t\t\tmlog.String(\"teamID\", command.TeamID),\n\t\t\tmlog.Err(err),\n\t\t)\n\t\treturn false\n\t}\n\n\treturn isValid\n}\n\n// addListener adds a listener to the websocket server. The listener\n// should not receive any update from the server until it subscribes\n// itself to some entity changes. Adding a listener to the server\n// doesn't mean that it's authenticated in any way.\nfunc (ws *Server) addListener(listener *websocketSession) {\n\tws.mu.Lock()\n\tdefer ws.mu.Unlock()\n\tws.listeners[listener] = true\n}\n\n// removeListener removes a listener and all its subscriptions, if\n// any, from the websockets server.\nfunc (ws *Server) removeListener(listener *websocketSession) {\n\tws.mu.Lock()\n\tdefer ws.mu.Unlock()\n\n\t// remove the listener from its subscriptions, if any\n\n\t// team subscriptions\n\tfor _, team := range listener.teams {\n\t\tws.removeListenerFromTeam(listener, team)\n\t}\n\n\t// block subscriptions\n\tfor _, block := range listener.blocks {\n\t\tws.removeListenerFromBlock(listener, block)\n\t}\n\n\tdelete(ws.listeners, listener)\n}\n\n// subscribeListenerToTeam safely modifies the listener and the\n// server to subscribe the listener to a given team updates.\nfunc (ws *Server) subscribeListenerToTeam(listener *websocketSession, teamID string) {\n\tif listener.isSubscribedToTeam(teamID) {\n\t\treturn\n\t}\n\n\tws.mu.Lock()\n\tdefer ws.mu.Unlock()\n\n\tws.listenersByTeam[teamID] = append(ws.listenersByTeam[teamID], listener)\n\tlistener.teams = append(listener.teams, teamID)\n}\n\n// unsubscribeListenerFromTeam safely modifies the listener and\n// the server data structures to remove the link between the listener\n// and a given team ID.\nfunc (ws *Server) unsubscribeListenerFromTeam(listener *websocketSession, teamID string) {\n\tif !listener.isSubscribedToTeam(teamID) {\n\t\treturn\n\t}\n\n\tws.mu.Lock()\n\tdefer ws.mu.Unlock()\n\n\tws.removeListenerFromTeam(listener, teamID)\n}\n\n// subscribeListenerToBlocks safely modifies the listener and the\n// server to subscribe the listener to a given set of block updates.\nfunc (ws *Server) subscribeListenerToBlocks(listener *websocketSession, blockIDs []string) {\n\tws.mu.Lock()\n\tdefer ws.mu.Unlock()\n\n\tfor _, blockID := range blockIDs {\n\t\tif listener.isSubscribedToBlock(blockID) {\n\t\t\tcontinue\n\t\t}\n\n\t\tws.listenersByBlock[blockID] = append(ws.listenersByBlock[blockID], listener)\n\t\tlistener.blocks = append(listener.blocks, blockID)\n\t}\n}\n\n// unsubscribeListenerFromBlocks safely modifies the listener and the\n// server data structures to remove the link between the listener and\n// a given set of block IDs.\nfunc (ws *Server) unsubscribeListenerFromBlocks(listener *websocketSession, blockIDs []string) {\n\tws.mu.Lock()\n\tdefer ws.mu.Unlock()\n\n\tfor _, blockID := range blockIDs {\n\t\tif listener.isSubscribedToBlock(blockID) {\n\t\t\tws.removeListenerFromBlock(listener, blockID)\n\t\t}\n\t}\n}\n\n// removeListenerFromTeam removes the listener from both its own\n// block subscribed list and the server listeners by team map.\nfunc (ws *Server) removeListenerFromTeam(listener *websocketSession, teamID string) {\n\t// we remove the listener from the team index\n\tnewTeamListeners := []*websocketSession{}\n\tfor _, l := range ws.listenersByTeam[teamID] {\n\t\tif l != listener {\n\t\t\tnewTeamListeners = append(newTeamListeners, l)\n\t\t}\n\t}\n\tws.listenersByTeam[teamID] = newTeamListeners\n\n\t// we remove the team from the listener subscription list\n\tnewListenerTeams := []string{}\n\tfor _, id := range listener.teams {\n\t\tif id != teamID {\n\t\t\tnewListenerTeams = append(newListenerTeams, id)\n\t\t}\n\t}\n\tlistener.teams = newListenerTeams\n}\n\n// removeListenerFromBlock removes the listener from both its own\n// block subscribed list and the server listeners by block map.\nfunc (ws *Server) removeListenerFromBlock(listener *websocketSession, blockID string) {\n\t// we remove the listener from the block index\n\tnewBlockListeners := []*websocketSession{}\n\tfor _, l := range ws.listenersByBlock[blockID] {\n\t\tif l != listener {\n\t\t\tnewBlockListeners = append(newBlockListeners, l)\n\t\t}\n\t}\n\tws.listenersByBlock[blockID] = newBlockListeners\n\n\t// we remove the block from the listener subscription list\n\tnewListenerBlocks := []string{}\n\tfor _, id := range listener.blocks {\n\t\tif id != blockID {\n\t\t\tnewListenerBlocks = append(newListenerBlocks, id)\n\t\t}\n\t}\n\tlistener.blocks = newListenerBlocks\n}\n\nfunc (ws *Server) getUserIDForToken(token string) string {\n\tif len(ws.singleUserToken) > 0 {\n\t\tif token == ws.singleUserToken {\n\t\t\treturn model.SingleUser\n\t\t} else {\n\t\t\treturn \"\"\n\t\t}\n\t}\n\n\tsession, err := ws.auth.GetSession(token)\n\tif session == nil || err != nil {\n\t\treturn \"\"\n\t}\n\n\treturn session.UserID\n}\n\nfunc (ws *Server) authenticateListener(wsSession *websocketSession, token string) {\n\tws.logger.Debug(\"authenticateListener\",\n\t\tmlog.String(\"token\", token),\n\t\tmlog.String(\"wsSession.userID\", wsSession.userID),\n\t)\n\tif wsSession.isAuthenticated() {\n\t\t// Do not allow multiple auth calls (for security)\n\t\tws.logger.Debug(\n\t\t\t\"authenticateListener: Ignoring already authenticated session\",\n\t\t\tmlog.String(\"userID\", wsSession.userID),\n\t\t\tmlog.Stringer(\"client\", wsSession.conn.RemoteAddr()),\n\t\t)\n\t\treturn\n\t}\n\n\t// Authenticate session\n\tuserID := ws.getUserIDForToken(token)\n\tif userID == \"\" {\n\t\twsSession.conn.Close()\n\t\treturn\n\t}\n\n\t// Authenticated\n\twsSession.userID = userID\n\tws.logger.Debug(\"authenticateListener: Authenticated\", mlog.String(\"userID\", userID), mlog.Stringer(\"client\", wsSession.conn.RemoteAddr()))\n}\n\n// getListenersForBlock returns the listeners subscribed to a\n// block changes.\nfunc (ws *Server) getListenersForBlock(blockID string) []*websocketSession {\n\treturn ws.listenersByBlock[blockID]\n}\n\n// getListenersForUser returns the listener for a user subscribed to a\n// team changes.\nfunc (ws *Server) getListenerForUser(teamID, userID string) *websocketSession {\n\tfor _, listener := range ws.listenersByTeam[teamID] {\n\t\tif listener.userID == userID {\n\t\t\treturn listener\n\t\t}\n\t}\n\treturn nil\n}\n\n// getListenersForTeamAndBoard returns the listeners subscribed to a\n// team changes and members of a given board.\nfunc (ws *Server) getListenersForTeamAndBoard(teamID, boardID string, ensureUsers ...string) []*websocketSession {\n\tmembers, err := ws.store.GetMembersForBoard(boardID)\n\tif err != nil {\n\t\tws.logger.Error(\"error getting members for board\",\n\t\t\tmlog.String(\"method\", \"getListenersForTeamAndBoard\"),\n\t\t\tmlog.String(\"teamID\", teamID),\n\t\t\tmlog.String(\"boardID\", boardID),\n\t\t)\n\t\treturn nil\n\t}\n\n\tmemberMap := map[string]bool{}\n\tfor _, member := range members {\n\t\tmemberMap[member.UserID] = true\n\t}\n\tfor _, id := range ensureUsers {\n\t\tmemberMap[id] = true\n\t}\n\n\tmemberIDs := []string{}\n\tfor id := range memberMap {\n\t\tmemberIDs = append(memberIDs, id)\n\t}\n\n\tlisteners := []*websocketSession{}\n\tfor _, memberID := range memberIDs {\n\t\tfor _, listener := range ws.listenersByTeam[teamID] {\n\t\t\tif listener.userID == memberID {\n\t\t\t\tlisteners = append(listeners, listener)\n\t\t\t}\n\t\t}\n\t}\n\treturn listeners\n}\n\n// BroadcastBlockDelete broadcasts delete messages to clients.\nfunc (ws *Server) BroadcastBlockDelete(teamID, blockID, boardID string) {\n\tnow := utils.GetMillis()\n\tblock := &model.Block{}\n\tblock.ID = blockID\n\tblock.BoardID = boardID\n\tblock.UpdateAt = now\n\tblock.DeleteAt = now\n\n\tws.BroadcastBlockChange(teamID, block)\n}\n\n// BroadcastBlockChange broadcasts update messages to clients.\nfunc (ws *Server) BroadcastBlockChange(teamID string, block *model.Block) {\n\tblockIDsToNotify := []string{block.ID, block.ParentID}\n\n\tmessage := UpdateBlockMsg{\n\t\tAction: websocketActionUpdateBlock,\n\t\tTeamID: teamID,\n\t\tBlock:  block,\n\t}\n\n\tlisteners := ws.getListenersForTeamAndBoard(teamID, block.BoardID)\n\tws.logger.Trace(\"listener(s) for teamID\",\n\t\tmlog.Int(\"listener_count\", len(listeners)),\n\t\tmlog.String(\"teamID\", teamID),\n\t\tmlog.String(\"boardID\", block.BoardID),\n\t)\n\n\tfor _, blockID := range blockIDsToNotify {\n\t\tlisteners = append(listeners, ws.getListenersForBlock(blockID)...)\n\t\tws.logger.Trace(\"listener(s) for blockID\",\n\t\t\tmlog.Int(\"listener_count\", len(listeners)),\n\t\t\tmlog.String(\"blockID\", blockID),\n\t\t)\n\t}\n\n\tfor _, listener := range listeners {\n\t\tws.logger.Debug(\"Broadcast block change\",\n\t\t\tmlog.String(\"teamID\", teamID),\n\t\t\tmlog.String(\"blockID\", block.ID),\n\t\t\tmlog.Stringer(\"remoteAddr\", listener.conn.RemoteAddr()),\n\t\t)\n\n\t\terr := listener.WriteJSON(message)\n\t\tif err != nil {\n\t\t\tws.logger.Error(\"broadcast error\", mlog.Err(err))\n\t\t\tlistener.conn.Close()\n\t\t}\n\t}\n}\n\nfunc (ws *Server) BroadcastCategoryChange(category model.Category) {\n\tmessage := UpdateCategoryMessage{\n\t\tAction:   websocketActionUpdateCategory,\n\t\tTeamID:   category.TeamID,\n\t\tCategory: &category,\n\t}\n\n\tlistener := ws.getListenerForUser(category.TeamID, category.UserID)\n\tif listener != nil {\n\t\tws.logger.Debug(\"Broadcast category change\",\n\t\t\tmlog.String(\"userID\", category.UserID),\n\t\t\tmlog.String(\"teamID\", category.TeamID),\n\t\t\tmlog.String(\"categoryID\", category.ID),\n\t\t\tmlog.Stringer(\"remoteAddr\", listener.conn.RemoteAddr()),\n\t\t)\n\n\t\tif err := listener.WriteJSON(message); err != nil {\n\t\t\tws.logger.Error(\"broadcast category change error\", mlog.Err(err))\n\t\t\tlistener.conn.Close()\n\t\t}\n\t}\n}\n\nfunc (ws *Server) BroadcastCategoryReorder(teamID, userID string, categoryOrder []string) {\n\tmessage := CategoryReorderMessage{\n\t\tAction:        websocketActionReorderCategories,\n\t\tCategoryOrder: categoryOrder,\n\t\tTeamID:        teamID,\n\t}\n\n\tlistener := ws.getListenerForUser(teamID, userID)\n\tif listener != nil {\n\t\tws.logger.Debug(\"Broadcast category order change\",\n\t\t\tmlog.String(\"userID\", userID),\n\t\t\tmlog.String(\"teamID\", teamID),\n\t\t\tmlog.Stringer(\"remoteAddr\", listener.conn.RemoteAddr()),\n\t\t)\n\n\t\tif err := listener.WriteJSON(message); err != nil {\n\t\t\tws.logger.Error(\"broadcast category order change error\", mlog.Err(err))\n\t\t\tlistener.conn.Close()\n\t\t}\n\t}\n}\n\nfunc (ws *Server) BroadcastCategoryBoardsReorder(teamID, userID, categoryID string, boardOrder []string) {\n\tmessage := CategoryBoardReorderMessage{\n\t\tAction:     websocketActionReorderCategoryBoards,\n\t\tCategoryID: categoryID,\n\t\tBoardOrder: boardOrder,\n\t\tTeamID:     teamID,\n\t}\n\n\tlistener := ws.getListenerForUser(teamID, userID)\n\tif listener != nil {\n\t\tws.logger.Debug(\"Broadcast board category order change\",\n\t\t\tmlog.String(\"userID\", userID),\n\t\t\tmlog.String(\"teamID\", teamID),\n\t\t\tmlog.String(\"categoryID\", categoryID),\n\t\t\tmlog.Stringer(\"remoteAddr\", listener.conn.RemoteAddr()),\n\t\t)\n\n\t\tif err := listener.WriteJSON(message); err != nil {\n\t\t\tws.logger.Error(\"broadcast category boards order change error\", mlog.Err(err))\n\t\t\tlistener.conn.Close()\n\t\t}\n\t}\n}\n\nfunc (ws *Server) BroadcastCategoryBoardChange(teamID, userID string, boardCategories []*model.BoardCategoryWebsocketData) {\n\tmessage := UpdateCategoryMessage{\n\t\tAction:          websocketActionUpdateCategoryBoard,\n\t\tTeamID:          teamID,\n\t\tBoardCategories: boardCategories,\n\t}\n\n\tlistener := ws.getListenerForUser(teamID, userID)\n\tif listener != nil {\n\t\tws.logger.Debug(\"Broadcast category board change\",\n\t\t\tmlog.String(\"userID\", userID),\n\t\t\tmlog.String(\"teamID\", teamID),\n\t\t\tmlog.Int(\"numEntries\", len(boardCategories)),\n\t\t\tmlog.Stringer(\"remoteAddr\", listener.conn.RemoteAddr()),\n\t\t)\n\n\t\tif err := listener.WriteJSON(message); err != nil {\n\t\t\tws.logger.Error(\"broadcast category change error\", mlog.Err(err))\n\t\t\tlistener.conn.Close()\n\t\t}\n\t}\n}\n\n// BroadcastConfigChange broadcasts update messages to clients.\nfunc (ws *Server) BroadcastConfigChange(clientConfig model.ClientConfig) {\n\tmessage := UpdateClientConfig{\n\t\tAction:       websocketActionUpdateConfig,\n\t\tClientConfig: clientConfig,\n\t}\n\n\tlisteners := ws.listeners\n\tws.logger.Debug(\"broadcasting config change to listener(s)\",\n\t\tmlog.Int(\"listener_count\", len(listeners)),\n\t)\n\n\tfor listener := range listeners {\n\t\tws.logger.Debug(\"Broadcast Config change\",\n\t\t\tmlog.Stringer(\"remoteAddr\", listener.conn.RemoteAddr()),\n\t\t)\n\t\terr := listener.WriteJSON(message)\n\t\tif err != nil {\n\t\t\tws.logger.Error(\"broadcast error\", mlog.Err(err))\n\t\t\tlistener.conn.Close()\n\t\t}\n\t}\n}\n\nfunc (ws *Server) BroadcastBoardChange(teamID string, board *model.Board) {\n\tmessage := UpdateBoardMsg{\n\t\tAction: websocketActionUpdateBoard,\n\t\tTeamID: teamID,\n\t\tBoard:  board,\n\t}\n\n\tlisteners := ws.getListenersForTeamAndBoard(teamID, board.ID)\n\tws.logger.Trace(\"listener(s) for teamID and boardID\",\n\t\tmlog.Int(\"listener_count\", len(listeners)),\n\t\tmlog.String(\"teamID\", teamID),\n\t\tmlog.String(\"boardID\", board.ID),\n\t)\n\n\tfor _, listener := range listeners {\n\t\tws.logger.Debug(\"Broadcast board change\",\n\t\t\tmlog.String(\"teamID\", teamID),\n\t\t\tmlog.String(\"boardID\", board.ID),\n\t\t\tmlog.Stringer(\"remoteAddr\", listener.conn.RemoteAddr()),\n\t\t)\n\n\t\terr := listener.WriteJSON(message)\n\t\tif err != nil {\n\t\t\tws.logger.Error(\"broadcast error\", mlog.Err(err))\n\t\t\tlistener.conn.Close()\n\t\t}\n\t}\n}\n\nfunc (ws *Server) BroadcastBoardDelete(teamID, boardID string) {\n\tnow := utils.GetMillis()\n\tboard := &model.Board{}\n\tboard.ID = boardID\n\tboard.TeamID = teamID\n\tboard.UpdateAt = now\n\tboard.DeleteAt = now\n\n\tws.BroadcastBoardChange(teamID, board)\n}\n\nfunc (ws *Server) BroadcastMemberChange(teamID, boardID string, member *model.BoardMember) {\n\tmessage := UpdateMemberMsg{\n\t\tAction: websocketActionUpdateMember,\n\t\tTeamID: teamID,\n\t\tMember: member,\n\t}\n\n\tlisteners := ws.getListenersForTeamAndBoard(teamID, boardID)\n\tws.logger.Trace(\"listener(s) for teamID and boardID\",\n\t\tmlog.Int(\"listener_count\", len(listeners)),\n\t\tmlog.String(\"teamID\", teamID),\n\t\tmlog.String(\"boardID\", boardID),\n\t)\n\n\tfor _, listener := range listeners {\n\t\tws.logger.Debug(\"Broadcast member change\",\n\t\t\tmlog.String(\"teamID\", teamID),\n\t\t\tmlog.String(\"boardID\", boardID),\n\t\t\tmlog.Stringer(\"remoteAddr\", listener.conn.RemoteAddr()),\n\t\t)\n\n\t\terr := listener.WriteJSON(message)\n\t\tif err != nil {\n\t\t\tws.logger.Error(\"broadcast error\", mlog.Err(err))\n\t\t\tlistener.conn.Close()\n\t\t}\n\t}\n}\n\nfunc (ws *Server) BroadcastMemberDelete(teamID, boardID, userID string) {\n\tmessage := UpdateMemberMsg{\n\t\tAction: websocketActionDeleteMember,\n\t\tTeamID: teamID,\n\t\tMember: &model.BoardMember{UserID: userID, BoardID: boardID},\n\t}\n\n\t// when fetching the members of the board that should receive the\n\t// member deletion message, the deleted member will not be one of\n\t// them, so we need to ensure they receive the message\n\tlisteners := ws.getListenersForTeamAndBoard(teamID, boardID, userID)\n\tws.logger.Trace(\"listener(s) for teamID and boardID\",\n\t\tmlog.Int(\"listener_count\", len(listeners)),\n\t\tmlog.String(\"teamID\", teamID),\n\t\tmlog.String(\"boardID\", boardID),\n\t)\n\n\tfor _, listener := range listeners {\n\t\tws.logger.Debug(\"Broadcast member removal\",\n\t\t\tmlog.String(\"teamID\", teamID),\n\t\t\tmlog.String(\"boardID\", boardID),\n\t\t\tmlog.Stringer(\"remoteAddr\", listener.conn.RemoteAddr()),\n\t\t)\n\n\t\terr := listener.WriteJSON(message)\n\t\tif err != nil {\n\t\t\tws.logger.Error(\"broadcast error\", mlog.Err(err))\n\t\t\tlistener.conn.Close()\n\t\t}\n\t}\n}\n\nfunc (ws *Server) BroadcastSubscriptionChange(workspaceID string, subscription *model.Subscription) {\n\t// not implemented for standalone server.\n}\n\nfunc (ws *Server) BroadcastCardLimitTimestampChange(cardLimitTimestamp int64) {\n\t// not implemented for standalone server.\n}\n"
  },
  {
    "path": "server/ws/server_test.go",
    "content": "package ws\n\nimport (\n\t\"sync\"\n\t\"testing\"\n\n\t\"github.com/mattermost/focalboard/server/auth\"\n\t\"github.com/mattermost/focalboard/server/model\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n\n\t\"github.com/gorilla/websocket\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestTeamSubscription(t *testing.T) {\n\tserver := NewServer(&auth.Auth{}, \"token\", false, &mlog.Logger{}, nil)\n\tsession := &websocketSession{\n\t\tconn:   &websocket.Conn{},\n\t\tmu:     sync.Mutex{},\n\t\tteams:  []string{},\n\t\tblocks: []string{},\n\t}\n\tteamID := \"fake-team-id\"\n\n\tt.Run(\"Should correctly add a session\", func(t *testing.T) {\n\t\tserver.addListener(session)\n\t\trequire.Len(t, server.listeners, 1)\n\t\trequire.Empty(t, server.listenersByTeam)\n\t\trequire.Empty(t, session.teams)\n\t})\n\n\tt.Run(\"Should correctly subscribe to a team\", func(t *testing.T) {\n\t\trequire.False(t, session.isSubscribedToTeam(teamID))\n\n\t\tserver.subscribeListenerToTeam(session, teamID)\n\n\t\trequire.Len(t, server.listenersByTeam[teamID], 1)\n\t\trequire.Contains(t, server.listenersByTeam[teamID], session)\n\t\trequire.Len(t, session.teams, 1)\n\t\trequire.Contains(t, session.teams, teamID)\n\n\t\trequire.True(t, session.isSubscribedToTeam(teamID))\n\t})\n\n\tt.Run(\"Subscribing again to a subscribed team would have no effect\", func(t *testing.T) {\n\t\trequire.True(t, session.isSubscribedToTeam(teamID))\n\n\t\tserver.subscribeListenerToTeam(session, teamID)\n\n\t\trequire.Len(t, server.listenersByTeam[teamID], 1)\n\t\trequire.Contains(t, server.listenersByTeam[teamID], session)\n\t\trequire.Len(t, session.teams, 1)\n\t\trequire.Contains(t, session.teams, teamID)\n\n\t\trequire.True(t, session.isSubscribedToTeam(teamID))\n\t})\n\n\tt.Run(\"Should correctly unsubscribe to a team\", func(t *testing.T) {\n\t\trequire.True(t, session.isSubscribedToTeam(teamID))\n\n\t\tserver.unsubscribeListenerFromTeam(session, teamID)\n\n\t\trequire.Empty(t, server.listenersByTeam[teamID])\n\t\trequire.Empty(t, session.teams)\n\n\t\trequire.False(t, session.isSubscribedToTeam(teamID))\n\t})\n\n\tt.Run(\"Unsubscribing again to an unsubscribed team would have no effect\", func(t *testing.T) {\n\t\trequire.False(t, session.isSubscribedToTeam(teamID))\n\n\t\tserver.unsubscribeListenerFromTeam(session, teamID)\n\n\t\trequire.Empty(t, server.listenersByTeam[teamID])\n\t\trequire.Empty(t, session.teams)\n\n\t\trequire.False(t, session.isSubscribedToTeam(teamID))\n\t})\n\n\tt.Run(\"Should correctly be removed from the server\", func(t *testing.T) {\n\t\tserver.removeListener(session)\n\n\t\trequire.Empty(t, server.listeners)\n\t})\n\n\tt.Run(\"If subscribed to teams and removed, should be removed from the teams subscription list\", func(t *testing.T) {\n\t\tteamID2 := \"other-fake-team-id\"\n\n\t\tserver.addListener(session)\n\t\tserver.subscribeListenerToTeam(session, teamID)\n\t\tserver.subscribeListenerToTeam(session, teamID2)\n\n\t\trequire.Len(t, server.listeners, 1)\n\t\trequire.Contains(t, server.listenersByTeam[teamID], session)\n\t\trequire.Contains(t, server.listenersByTeam[teamID2], session)\n\n\t\tserver.removeListener(session)\n\n\t\trequire.Empty(t, server.listeners)\n\t\trequire.Empty(t, server.listenersByTeam[teamID])\n\t\trequire.Empty(t, server.listenersByTeam[teamID2])\n\t})\n\n\tt.Run(\"Subscribe users to team retrieve by user\", func(t *testing.T) {\n\t\tuserID1 := \"fake-user-id\"\n\t\tuserSession1 := &websocketSession{\n\t\t\tconn:   &websocket.Conn{},\n\t\t\tmu:     sync.Mutex{},\n\t\t\tuserID: userID1,\n\t\t\tteams:  []string{},\n\t\t\tblocks: []string{},\n\t\t}\n\t\tuserID2 := \"fake-user-id2\"\n\t\tuserSession2 := &websocketSession{\n\t\t\tconn:   &websocket.Conn{},\n\t\t\tmu:     sync.Mutex{},\n\t\t\tuserID: userID2,\n\t\t\tteams:  []string{},\n\t\t\tblocks: []string{},\n\t\t}\n\t\tteamID := \"fake-team-id\"\n\n\t\tserver.addListener(session)\n\t\tserver.subscribeListenerToTeam(session, teamID)\n\t\tserver.addListener(userSession1)\n\t\tserver.subscribeListenerToTeam(userSession1, teamID)\n\t\tserver.addListener(userSession2)\n\t\tserver.subscribeListenerToTeam(userSession2, teamID)\n\n\t\trequire.Len(t, server.listeners, 3)\n\t\trequire.Len(t, server.listenersByTeam[teamID], 3)\n\n\t\tlistener := server.getListenerForUser(teamID, userID1)\n\t\trequire.NotNil(t, listener)\n\t\trequire.Equal(t, listener.userID, userID1)\n\n\t\tserver.removeListener(session)\n\t\tserver.removeListener(userSession1)\n\t\tserver.removeListener(userSession2)\n\n\t\trequire.Empty(t, server.listeners)\n\t\trequire.Empty(t, server.listenersByTeam[teamID])\n\t\trequire.Empty(t, server.getListenerForUser(teamID, userID1))\n\t})\n}\n\nfunc TestBlocksSubscription(t *testing.T) {\n\tserver := NewServer(&auth.Auth{}, \"token\", false, &mlog.Logger{}, nil)\n\tsession := &websocketSession{\n\t\tconn:   &websocket.Conn{},\n\t\tmu:     sync.Mutex{},\n\t\tteams:  []string{},\n\t\tblocks: []string{},\n\t}\n\tblockID1 := \"block1\"\n\tblockID2 := \"block2\"\n\tblockID3 := \"block3\"\n\tblockIDs := []string{blockID1, blockID2, blockID3}\n\n\tt.Run(\"Should correctly add a session\", func(t *testing.T) {\n\t\tserver.addListener(session)\n\t\trequire.Len(t, server.listeners, 1)\n\t\trequire.Empty(t, server.listenersByTeam)\n\t\trequire.Empty(t, session.teams)\n\t})\n\n\tt.Run(\"Should correctly subscribe to a set of blocks\", func(t *testing.T) {\n\t\trequire.False(t, session.isSubscribedToBlock(blockID1))\n\t\trequire.False(t, session.isSubscribedToBlock(blockID2))\n\t\trequire.False(t, session.isSubscribedToBlock(blockID3))\n\n\t\tserver.subscribeListenerToBlocks(session, blockIDs)\n\n\t\trequire.Len(t, server.listenersByBlock[blockID1], 1)\n\t\trequire.Contains(t, server.listenersByBlock[blockID1], session)\n\t\trequire.Len(t, server.listenersByBlock[blockID2], 1)\n\t\trequire.Contains(t, server.listenersByBlock[blockID2], session)\n\t\trequire.Len(t, server.listenersByBlock[blockID3], 1)\n\t\trequire.Contains(t, server.listenersByBlock[blockID3], session)\n\t\trequire.Len(t, session.blocks, 3)\n\t\trequire.ElementsMatch(t, blockIDs, session.blocks)\n\n\t\trequire.True(t, session.isSubscribedToBlock(blockID1))\n\t\trequire.True(t, session.isSubscribedToBlock(blockID2))\n\t\trequire.True(t, session.isSubscribedToBlock(blockID3))\n\n\t\tt.Run(\"Subscribing again to a subscribed block would have no effect\", func(t *testing.T) {\n\t\t\trequire.True(t, session.isSubscribedToBlock(blockID1))\n\t\t\trequire.True(t, session.isSubscribedToBlock(blockID2))\n\t\t\trequire.True(t, session.isSubscribedToBlock(blockID3))\n\n\t\t\tserver.subscribeListenerToBlocks(session, blockIDs)\n\n\t\t\trequire.Len(t, server.listenersByBlock[blockID1], 1)\n\t\t\trequire.Contains(t, server.listenersByBlock[blockID1], session)\n\t\t\trequire.Len(t, server.listenersByBlock[blockID2], 1)\n\t\t\trequire.Contains(t, server.listenersByBlock[blockID2], session)\n\t\t\trequire.Len(t, server.listenersByBlock[blockID3], 1)\n\t\t\trequire.Contains(t, server.listenersByBlock[blockID3], session)\n\t\t\trequire.Len(t, session.blocks, 3)\n\t\t\trequire.ElementsMatch(t, blockIDs, session.blocks)\n\n\t\t\trequire.True(t, session.isSubscribedToBlock(blockID1))\n\t\t\trequire.True(t, session.isSubscribedToBlock(blockID2))\n\t\t\trequire.True(t, session.isSubscribedToBlock(blockID3))\n\t\t})\n\t})\n\n\tt.Run(\"Should correctly unsubscribe to a set of blocks\", func(t *testing.T) {\n\t\trequire.True(t, session.isSubscribedToBlock(blockID1))\n\t\trequire.True(t, session.isSubscribedToBlock(blockID2))\n\t\trequire.True(t, session.isSubscribedToBlock(blockID3))\n\n\t\tserver.unsubscribeListenerFromBlocks(session, blockIDs)\n\n\t\trequire.Empty(t, server.listenersByBlock[blockID1])\n\t\trequire.Empty(t, server.listenersByBlock[blockID2])\n\t\trequire.Empty(t, server.listenersByBlock[blockID3])\n\t\trequire.Empty(t, session.blocks)\n\n\t\trequire.False(t, session.isSubscribedToBlock(blockID1))\n\t\trequire.False(t, session.isSubscribedToBlock(blockID2))\n\t\trequire.False(t, session.isSubscribedToBlock(blockID3))\n\t})\n\n\tt.Run(\"Unsubscribing again to an unsubscribed block would have no effect\", func(t *testing.T) {\n\t\trequire.False(t, session.isSubscribedToBlock(blockID1))\n\n\t\tserver.unsubscribeListenerFromBlocks(session, []string{blockID1})\n\n\t\trequire.Empty(t, server.listenersByBlock[blockID1])\n\t\trequire.Empty(t, session.blocks)\n\n\t\trequire.False(t, session.isSubscribedToBlock(blockID1))\n\t})\n\n\tt.Run(\"Should correctly be removed from the server\", func(t *testing.T) {\n\t\tserver.removeListener(session)\n\n\t\trequire.Empty(t, server.listeners)\n\t})\n\n\tt.Run(\"If subscribed to blocks and removed, should be removed from the blocks subscription list\", func(t *testing.T) {\n\t\tserver.addListener(session)\n\t\tserver.subscribeListenerToBlocks(session, blockIDs)\n\n\t\trequire.Len(t, server.listeners, 1)\n\t\trequire.Len(t, server.listenersByBlock[blockID1], 1)\n\t\trequire.Contains(t, server.listenersByBlock[blockID1], session)\n\t\trequire.Len(t, server.listenersByBlock[blockID2], 1)\n\t\trequire.Contains(t, server.listenersByBlock[blockID2], session)\n\t\trequire.Len(t, server.listenersByBlock[blockID3], 1)\n\t\trequire.Contains(t, server.listenersByBlock[blockID3], session)\n\t\trequire.Len(t, session.blocks, 3)\n\t\trequire.ElementsMatch(t, blockIDs, session.blocks)\n\n\t\tserver.removeListener(session)\n\n\t\trequire.Empty(t, server.listeners)\n\t\trequire.Empty(t, server.listenersByBlock[blockID1])\n\t\trequire.Empty(t, server.listenersByBlock[blockID2])\n\t\trequire.Empty(t, server.listenersByBlock[blockID3])\n\t})\n}\n\nfunc TestGetUserIDForTokenInSingleUserMode(t *testing.T) {\n\tsingleUserToken := \"single-user-token\"\n\tserver := NewServer(&auth.Auth{}, \"token\", false, &mlog.Logger{}, nil)\n\tserver.singleUserToken = singleUserToken\n\n\tt.Run(\"Should return nothing if the token is empty\", func(t *testing.T) {\n\t\trequire.Empty(t, server.getUserIDForToken(\"\"))\n\t})\n\n\tt.Run(\"Should return nothing if the token is invalid\", func(t *testing.T) {\n\t\trequire.Empty(t, server.getUserIDForToken(\"invalid-token\"))\n\t})\n\n\tt.Run(\"Should return the single user ID if the token is correct\", func(t *testing.T) {\n\t\trequire.Equal(t, model.SingleUser, server.getUserIDForToken(singleUserToken))\n\t})\n}\n"
  },
  {
    "path": "server-config.json",
    "content": "{\n    \"serverRoot\": \"http://localhost:8000\",\n    \"port\": 8000,\n    \"dbtype\": \"sqlite3\",\n    \"dbconfig\": \"./focalboard.db\",\n    \"postgres_dbconfig\": \"dbname=focalboard sslmode=disable\",\n    \"useSSL\": false,\n    \"webpath\": \"./pack\",\n    \"filespath\": \"./files\",\n    \"telemetry\": true,\n    \"prometheusaddress\": \":9092\",\n    \"session_expire_time\": 2592000,\n    \"session_refresh_time\": 18000,\n    \"localOnly\": false,\n    \"enableLocalMode\": true,\n    \"localModeSocketLocation\": \"/var/tmp/focalboard_local.socket\"\n}\n"
  },
  {
    "path": "webapp/.eslintignore",
    "content": "node_modules/\n"
  },
  {
    "path": "webapp/.eslintrc.json",
    "content": "{\n  \"extends\": [\n    \"plugin:mattermost/react\",\n    \"plugin:cypress/recommended\",\n    \"plugin:jquery/deprecated\"\n  ],\n  \"plugins\": [\n    \"react\",\n    \"babel\",\n    \"mattermost\",\n    \"import\",\n    \"cypress\",\n    \"jquery\",\n    \"no-only-tests\"\n  ],\n  \"parser\": \"@typescript-eslint/parser\",\n  \"env\": {\n    \"jest\": true,\n    \"cypress/globals\": true\n  },\n  \"settings\": {\n    \"import/resolver\": \"webpack\",\n    \"react\": {\n        \"pragma\": \"React\",\n        \"version\": \"detect\"\n    }\n  },\n  \"rules\": {\n    \"max-lines\": \"off\",\n    \"no-unused-expressions\": 0,\n    \"babel/no-unused-expressions\": [2, {\"allowShortCircuit\": true}],\n    \"eol-last\": [\"error\", \"always\"],\n    \"import/no-unresolved\": 2,\n    \"import/order\": [\n      2,\n      {\n        \"newlines-between\": \"always-and-inside-groups\",\n        \"groups\": [\n          \"builtin\",\n          \"external\",\n          [\n            \"internal\",\n            \"parent\"\n          ],\n          \"sibling\",\n          \"index\"\n        ]\n      }\n    ],\n    \"no-undefined\": 0,\n    \"react/jsx-filename-extension\": 0,\n    \"react/prop-types\": [\n      2,\n      {\n        \"ignore\": [\n          \"location\",\n          \"history\",\n          \"component\"\n        ]\n      }\n    ],\n    \"react/no-string-refs\": 2,\n    \"no-only-tests/no-only-tests\": [\"error\", {\"focus\": [\"only\", \"skip\"]}],\n    \"max-nested-callbacks\": [\"error\", {\"max\": 5}],\n\t\"no-shadow\": \"off\",\n    \"@typescript-eslint/no-shadow\": \"error\"\t\n  },\n  \"overrides\": [\n    {\n      \"files\": [\"**/*.tsx\", \"**/*.ts\"],\n      \"extends\": [\n        \"plugin:@typescript-eslint/recommended\"\n      ],\n      \"rules\": {\n        \"import/no-unresolved\": 0, // ts handles this better\n\t\t\"camelcase\": 0,\n\t\t\"semi\": \"off\",\n        \"@typescript-eslint/naming-convention\": [\n          2,\n          {\n            \"selector\": \"function\",\n            \"format\": [\"camelCase\", \"PascalCase\"]\n          },\n          {\n            \"selector\": \"variable\",\n            \"format\": [\"camelCase\", \"PascalCase\", \"UPPER_CASE\"]\n          },\n          {\n            \"selector\": \"parameter\",\n            \"format\": [\"camelCase\", \"PascalCase\"],\n            \"leadingUnderscore\": \"allow\"\n          },\n          {\n            \"selector\": \"typeLike\",\n            \"format\": [\"PascalCase\"]\n          }\n        ],\n        \"@typescript-eslint/no-non-null-assertion\": 0,\n        \"@typescript-eslint/no-unused-vars\": [\n          2,\n          {\n            \"vars\": \"all\",\n            \"args\": \"after-used\"\n          }\n        ],\n        \"@typescript-eslint/member-delimiter-style\": [2, {\"multiline\": {\"delimiter\": \"none\"}, \"singleline\": {\"delimiter\": \"comma\"}}],\n        \"@typescript-eslint/no-var-requires\": 0,\n        \"@typescript-eslint/no-empty-function\": 0,\n        \"@typescript-eslint/prefer-interface\": 0,\n        \"@typescript-eslint/explicit-function-return-type\": 0,\n        \"@typescript-eslint/semi\": [2, \"never\"],\n\t\t\"@typescript-eslint/indent\": [\n          2,\n          4,\n          {\n            \"SwitchCase\": 0\n          }\n        ],\n        \"no-use-before-define\": \"off\",\n        \"@typescript-eslint/no-use-before-define\": [\n          2,\n          {\n            \"classes\": false,\n            \"functions\": false,\n            \"variables\": false\n          }\n        ],\n        \"no-useless-constructor\": 0,\n        \"@typescript-eslint/no-useless-constructor\": 2,\n        \"react/jsx-filename-extension\": 0\n      }\n    },\n    {\n      \"files\": [\"tests/**\", \"**/*.test.*\"],\n      \"env\": {\n        \"jest\": true\n      },\n      \"rules\": {\n        \"func-names\": 0,\n        \"global-require\": 0,\n        \"new-cap\": 0,\n        \"prefer-arrow-callback\": 0,\n        \"no-import-assign\": 0\n      }\n    },\n    {\n      \"files\": [\"cypress/**\"],\n      \"rules\": {\n        \"cypress/no-unnecessary-waiting\": 0,\n        \"func-names\": 0,\n        \"import/no-unresolved\": 0,\n        \"max-nested-callbacks\": 0,\n        \"no-process-env\": 0,\n        \"no-unused-expressions\": 0\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "webapp/.nvmrc",
    "content": "20.11\n"
  },
  {
    "path": "webapp/.prettierignore",
    "content": "# Ignore all js/ts files:\n*.ts\n*.tsx\n*.js"
  },
  {
    "path": "webapp/.prettierrc.json",
    "content": "{\n\t\"tabWidth\": 4\n}\n"
  },
  {
    "path": "webapp/.stylelintrc.json",
    "content": "{\n  \"extends\": \"stylelint-config-sass-guidelines\",\n  \"rules\": {\n    \"indentation\": 4,\n    \"selector-class-pattern\": \"[a-zA-Z_-]+\",\n    \"max-nesting-depth\": 4,\n    \"selector-max-compound-selectors\": 6,\n    \"selector-max-id\": 1,\n    \"selector-no-qualifying-type\": null,\n    \"order/properties-alphabetical-order\": null,\n    \"declaration-block-no-duplicate-properties\": true,\n    \"property-disallowed-list\": [\"z-index\"]\n  }\n}\n"
  },
  {
    "path": "webapp/NOTICE.txt",
    "content": "Focalboard\n© 2015-present Mattermost, Inc.  All Rights Reserved.  See LICENSE.txt for license information.\n\nNOTICES:\n--------\n\nThis document includes a list of open source components used in Focalboard web app, including those that have been modified.\n\n-----\n\nThe following software may be included in this product: @babel/code-frame, @babel/helper-module-imports, @babel/helper-validator-identifier, @babel/highlight, @babel/runtime, @babel/types. A copy of the source code may be downloaded from https://github.com/babel/babel.git (@babel/code-frame), https://github.com/babel/babel.git (@babel/helper-module-imports), https://github.com/babel/babel.git (@babel/helper-validator-identifier), https://github.com/babel/babel.git (@babel/highlight), https://github.com/babel/babel.git (@babel/runtime), https://github.com/babel/babel.git (@babel/types). This software contains the following license and notice below:\n\nMIT License\n\nCopyright (c) 2014-present Sebastian McKenzie and other contributors\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: @cypress/listr-verbose-renderer, listr, listr-silent-renderer, listr-update-renderer, listr-verbose-renderer. A copy of the source code may be downloaded from https://github.com/SamVerschueren/listr-verbose-renderer.git (@cypress/listr-verbose-renderer), https://github.com/SamVerschueren/listr.git (listr), https://github.com/SamVerschueren/listr-silent-renderer.git (listr-silent-renderer), https://github.com/SamVerschueren/listr-update-renderer.git (listr-update-renderer), https://github.com/SamVerschueren/listr-verbose-renderer.git (listr-verbose-renderer). This software contains the following license and notice below:\n\nThe MIT License (MIT)\n\nCopyright (c) Sam Verschueren <sam.verschueren@gmail.com> (github.com/SamVerschueren)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: @cypress/request, aws-sign2, forever-agent, oauth-sign, tunnel-agent. A copy of the source code may be downloaded from https://github.com/cypress-io/request.git (@cypress/request), https://github.com/mikeal/aws-sign (aws-sign2), https://github.com/mikeal/forever-agent (forever-agent), https://github.com/mikeal/oauth-sign (oauth-sign), https://github.com/mikeal/tunnel-agent (tunnel-agent). This software contains the following license and notice below:\n\nApache License\n\nVersion 2.0, January 2004\n\nhttp://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n\"License\" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.\n\n\"Licensor\" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.\n\n\"Legal Entity\" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, \"control\" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.\n\n\"You\" (or \"Your\") shall mean an individual or Legal Entity exercising permissions granted by this License.\n\n\"Source\" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.\n\n\"Object\" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.\n\n\"Work\" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).\n\n\"Derivative Works\" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.\n\n\"Contribution\" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, \"submitted\" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as \"Not a Contribution.\"\n\n\"Contributor\" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.\n\n2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.\n\n3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.\n\n4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:\n\nYou must give any other recipients of the Work or Derivative Works a copy of this License; and\n\nYou must cause any modified files to carry prominent notices stating that You changed the files; and\n\nYou must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and\n\nIf the Work includes a \"NOTICE\" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.\n\n5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.\n\n6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.\n\n8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONS\n\n-----\n\nThe following software may be included in this product: @cypress/xvfb. A copy of the source code may be downloaded from https://github.com/cypress-io/xvfb.git. This software contains the following license and notice below:\n\nOriginal Work Copyright (C) 2012 ProxV, Inc.\nModified Work Copyright (c) 2015 Cypress.io, LLC\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: @emotion/cache, @emotion/core, @emotion/css, @emotion/hash, @emotion/memoize, @emotion/serialize, @emotion/sheet, @emotion/stylis, @emotion/unitless, @emotion/utils, @emotion/weak-memoize, babel-plugin-emotion. A copy of the source code may be downloaded from https://github.com/emotion-js/emotion/tree/master/packages/cache (@emotion/cache), https://github.com/emotion-js/emotion/tree/master/packages/core (@emotion/core), https://github.com/emotion-js/emotion/tree/master/packages/css (@emotion/css), https://github.com/emotion-js/emotion/tree/master/packages/hash (@emotion/hash), https://github.com/emotion-js/emotion/tree/master/packages/memoize (@emotion/memoize), https://github.com/emotion-js/emotion/tree/master/packages/serialize (@emotion/serialize), https://github.com/emotion-js/emotion/tree/master/packages/sheet (@emotion/sheet), https://github.com/emotion-js/emotion/tree/master/packages/stylis (@emotion/stylis), https://github.com/emotion-js/emotion/tree/master/packages/unitless (@emotion/unitless), https://github.com/emotion-js/emotion/tree/master/packages/serialize (@emotion/utils), https://github.com/emotion-js/emotion/tree/master/packages/weak-memoize (@emotion/weak-memoize), https://github.com/emotion-js/emotion/tree/master/packages/babel-plugin-emotion (babel-plugin-emotion). This software contains the following license and notice below:\n\nMIT License\n\nCopyright (c) Emotion team and other contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n-----\n\nThe following software may be included in this product: @formatjs/ecma402-abstract, @formatjs/intl, @formatjs/intl-datetimeformat, @formatjs/intl-displaynames, @formatjs/intl-listformat, @formatjs/intl-relativetimeformat. A copy of the source code may be downloaded from git@github.com:formatjs/formatjs.git (@formatjs/ecma402-abstract), git@github.com:formatjs/formatjs.git (@formatjs/intl), git+https://github.com/formatjs/formatjs.git (@formatjs/intl-datetimeformat), git+https://github.com/formatjs/formatjs.git (@formatjs/intl-displaynames), git@github.com:formatjs/formatjs.git (@formatjs/intl-listformat), git@github.com:formatjs/formatjs.git (@formatjs/intl-relativetimeformat). This software contains the following license and notice below:\n\nMIT License\n\nCopyright (c) 2019 FormatJS\n\nPermission 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\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE 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\nThe following software may be included in this product: @samverschueren/stream-to-observable. A copy of the source code may be downloaded from https://github.com/SamVerschueren/stream-to-observable.git. This software contains the following license and notice below:\n\nThe MIT License (MIT)\n\nCopyright (c) James Talmage <james@talmage.io> (github.com/jamestalmage), Sam Verschueren <sam.verschueren@gmail.com> (github.com/SamVerschueren)\n\nPermission 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\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE 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\nThe following software may be included in this product: @types/codemirror, @types/hoist-non-react-statics, @types/parse-json, @types/prop-types, @types/sizzle, @types/tern. A copy of the source code may be downloaded from https://github.com/DefinitelyTyped/DefinitelyTyped.git (@types/codemirror), https://github.com/DefinitelyTyped/DefinitelyTyped.git (@types/hoist-non-react-statics), https://www.github.com/DefinitelyTyped/DefinitelyTyped.git (@types/parse-json), https://github.com/DefinitelyTyped/DefinitelyTyped.git (@types/prop-types), https://github.com/DefinitelyTyped/DefinitelyTyped.git (@types/sizzle), https://github.com/DefinitelyTyped/DefinitelyTyped.git (@types/tern). This software contains the following license and notice below:\n\nMIT License\n\n    Copyright (c) Microsoft Corporation. All rights reserved.\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\nThe following software may be included in this product: @types/estree, @types/marked, @types/react, @types/sinonjs__fake-timers. A copy of the source code may be downloaded from https://github.com/DefinitelyTyped/DefinitelyTyped.git (@types/estree), https://github.com/DefinitelyTyped/DefinitelyTyped.git (@types/marked), https://github.com/DefinitelyTyped/DefinitelyTyped.git (@types/react), https://github.com/DefinitelyTyped/DefinitelyTyped.git (@types/sinonjs__fake-timers). This software contains the following license and notice below:\n\nMIT License\n\n    Copyright (c) Microsoft Corporation.\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\nThe following software may be included in this product: ajv. A copy of the source code may be downloaded from https://github.com/ajv-validator/ajv.git. This software contains the following license and notice below:\n\nThe MIT License (MIT)\n\nCopyright (c) 2015-2017 Evgeny Poberezkin\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n-----\n\nThe following software may be included in this product: ansi-escapes, ansi-regex, ansi-styles, any-observable, callsites, chalk, has-flag, indent-string, is-fullwidth-code-point, is-installed-globally, is-observable, is-path-inside, is-stream, log-update, mimic-fn, npm-run-path, p-map, parent-module, path-key, path-type, resolve-from, shebang-regex, string-width, strip-ansi, strip-final-newline, supports-color, untildify, wrap-ansi. A copy of the source code may be downloaded from https://github.com/sindresorhus/ansi-escapes.git (ansi-escapes), https://github.com/chalk/ansi-regex.git (ansi-regex), https://github.com/chalk/ansi-styles.git (ansi-styles), https://github.com/sindresorhus/any-observable.git (any-observable), https://github.com/sindresorhus/callsites.git (callsites), https://github.com/chalk/chalk.git (chalk), https://github.com/sindresorhus/has-flag.git (has-flag), https://github.com/sindresorhus/indent-string.git (indent-string), https://github.com/sindresorhus/is-fullwidth-code-point.git (is-fullwidth-code-point), https://github.com/sindresorhus/is-installed-globally.git (is-installed-globally), https://github.com/sindresorhus/is-observable.git (is-observable), https://github.com/sindresorhus/is-path-inside.git (is-path-inside), https://github.com/sindresorhus/is-stream.git (is-stream), https://github.com/sindresorhus/log-update.git (log-update), https://github.com/sindresorhus/mimic-fn.git (mimic-fn), https://github.com/sindresorhus/npm-run-path.git (npm-run-path), https://github.com/sindresorhus/p-map.git (p-map), https://github.com/sindresorhus/parent-module.git (parent-module), https://github.com/sindresorhus/path-key.git (path-key), https://github.com/sindresorhus/path-type.git (path-type), https://github.com/sindresorhus/resolve-from.git (resolve-from), https://github.com/sindresorhus/shebang-regex.git (shebang-regex), https://github.com/sindresorhus/string-width.git (string-width), https://github.com/chalk/strip-ansi.git (strip-ansi), https://github.com/sindresorhus/strip-final-newline.git (strip-final-newline), https://github.com/chalk/supports-color.git (supports-color), https://github.com/sindresorhus/untildify.git (untildify), https://github.com/chalk/wrap-ansi.git (wrap-ansi). This software contains the following license and notice below:\n\nMIT License\n\nCopyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)\n\nPermission 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\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE 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\nThe following software may be included in this product: ansi-regex, ansi-styles, chalk, cli-cursor, cli-truncate, code-point-at, elegant-spinner, escape-string-regexp, figures, has-ansi, is-fullwidth-code-point, is-stream, log-symbols, number-is-nan, object-assign, onetime, path-is-absolute, pify, restore-cursor, string-width, strip-ansi, supports-color. A copy of the source code may be downloaded from https://github.com/chalk/ansi-regex.git (ansi-regex), https://github.com/chalk/ansi-styles.git (ansi-styles), https://github.com/chalk/chalk.git (chalk), https://github.com/sindresorhus/cli-cursor.git (cli-cursor), https://github.com/sindresorhus/cli-truncate.git (cli-truncate), https://github.com/sindresorhus/code-point-at.git (code-point-at), https://github.com/sindresorhus/elegant-spinner.git (elegant-spinner), https://github.com/sindresorhus/escape-string-regexp.git (escape-string-regexp), https://github.com/sindresorhus/figures.git (figures), https://github.com/sindresorhus/has-ansi.git (has-ansi), https://github.com/sindresorhus/is-fullwidth-code-point.git (is-fullwidth-code-point), https://github.com/sindresorhus/is-stream.git (is-stream), https://github.com/sindresorhus/log-symbols.git (log-symbols), https://github.com/sindresorhus/number-is-nan.git (number-is-nan), https://github.com/sindresorhus/object-assign.git (object-assign), https://github.com/sindresorhus/onetime.git (onetime), https://github.com/sindresorhus/path-is-absolute.git (path-is-absolute), https://github.com/sindresorhus/pify.git (pify), https://github.com/sindresorhus/restore-cursor.git (restore-cursor), https://github.com/sindresorhus/string-width.git (string-width), https://github.com/chalk/strip-ansi.git (strip-ansi), https://github.com/chalk/supports-color.git (supports-color). This software contains the following license and notice below:\n\nThe MIT License (MIT)\n\nCopyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: arch. A copy of the source code may be downloaded from git://github.com/feross/arch.git. This software contains the following license and notice below:\n\nThe MIT License (MIT)\n\nCopyright (c) Feross Aboukhadijeh\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: asn1. A copy of the source code may be downloaded from git://github.com/joyent/node-asn1.git. This software contains the following license and notice below:\n\nCopyright (c) 2011 Mark Cavage, All rights reserved.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE\n\n-----\n\nThe following software may be included in this product: async. A copy of the source code may be downloaded from https://github.com/caolan/async.git. This software contains the following license and notice below:\n\nCopyright (c) 2010-2018 Caolan McMahon\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: asynckit. A copy of the source code may be downloaded from git+https://github.com/alexindigo/asynckit.git. This software contains the following license and notice below:\n\nThe MIT License (MIT)\n\nCopyright (c) 2016 Alex Indigo\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n-----\n\nThe following software may be included in this product: at-least-node. A copy of the source code may be downloaded from git+https://github.com/RyanZim/at-least-node.git. This software contains the following license and notice below:\n\nThe ISC License\nCopyright (c) 2020 Ryan Zimmerman <opensrc@ryanzim.com>\n\nPermission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-----\n\nThe following software may be included in this product: aws4. A copy of the source code may be downloaded from https://github.com/mhart/aws4.git. This software contains the following license and notice below:\n\nCopyright 2013 Michael Hart (michael.hart.au@gmail.com)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies\nof the Software, and to permit persons to whom the Software is furnished to do\nso, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n-----\n\nThe following software may be included in this product: babel-plugin-macros. A copy of the source code may be downloaded from https://github.com/kentcdodds/babel-plugin-macros.git. This software contains the following license and notice below:\n\nThe MIT License (MIT)\nCopyright (c) 2017 Kent C. Dodds\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n-----\n\nThe following software may be included in this product: balanced-match. A copy of the source code may be downloaded from git://github.com/juliangruber/balanced-match.git. This software contains the following license and notice below:\n\n(MIT)\n\nCopyright (c) 2013 Julian Gruber &lt;julian@juliangruber.com&gt;\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies\nof the Software, and to permit persons to whom the Software is furnished to do\nso, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n-----\n\nThe following software may be included in this product: bcrypt-pbkdf. A copy of the source code may be downloaded from git://github.com/joyent/node-bcrypt-pbkdf.git. This software contains the following license and notice below:\n\nThe Blowfish portions are under the following license:\n\nBlowfish block cipher for OpenBSD\nCopyright 1997 Niels Provos <provos@physnet.uni-hamburg.de>\nAll rights reserved.\n\nImplementation advice by David Mazieres <dm@lcs.mit.edu>.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions\nare met:\n1. Redistributions of source code must retain the above copyright\n   notice, this list of conditions and the following disclaimer.\n2. Redistributions in binary form must reproduce the above copyright\n   notice, this list of conditions and the following disclaimer in the\n   documentation and/or other materials provided with the distribution.\n3. The name of the author may not be used to endorse or promote products\n   derived from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR\nIMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\nOF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\nIN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,\nINCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\nNOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF\nTHIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n\nThe bcrypt_pbkdf portions are under the following license:\n\nCopyright (c) 2013 Ted Unangst <tedu@openbsd.org>\n\nPermission to use, copy, modify, and distribute this software for any\npurpose with or without fee is hereby granted, provided that the above\ncopyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\nWITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\nANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\nWHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\nACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\nOR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n\n\nPerformance improvements (Javascript-specific):\n\nCopyright 2016, Joyent Inc\nAuthor: Alex Wilson <alex.wilson@joyent.com>\n\nPermission to use, copy, modify, and distribute this software for any\npurpose with or without fee is hereby granted, provided that the above\ncopyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\nWITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\nANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\nWHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\nACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\nOR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-----\n\nThe following software may be included in this product: blob-util. A copy of the source code may be downloaded from git://github.com/nolanlawson/blob-util.git. This software contains the following license and notice below:\n\nApache License\n                          Version 2.0, January 2004\n                       http://www.apache.org/licenses/\n\n  TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n  1. Definitions.\n\n     \"License\" shall mean the terms and conditions for use, reproduction,\n     and distribution as defined by Sections 1 through 9 of this document.\n\n     \"Licensor\" shall mean the copyright owner or entity authorized by\n     the copyright owner that is granting the License.\n\n     \"Legal Entity\" shall mean the union of the acting entity and all\n     other entities that control, are controlled by, or are under common\n     control with that entity. For the purposes of this definition,\n     \"control\" means (i) the power, direct or indirect, to cause the\n     direction or management of such entity, whether by contract or\n     otherwise, or (ii) ownership of fifty percent (50%) or more of the\n     outstanding shares, or (iii) beneficial ownership of such entity.\n\n     \"You\" (or \"Your\") shall mean an individual or Legal Entity\n     exercising permissions granted by this License.\n\n     \"Source\" form shall mean the preferred form for making modifications,\n     including but not limited to software source code, documentation\n     source, and configuration files.\n\n     \"Object\" form shall mean any form resulting from mechanical\n     transformation or translation of a Source form, including but\n     not limited to compiled object code, generated documentation,\n     and conversions to other media types.\n\n     \"Work\" shall mean the work of authorship, whether in Source or\n     Object form, made available under the License, as indicated by a\n     copyright notice that is included in or attached to the work\n     (an example is provided in the Appendix below).\n\n     \"Derivative Works\" shall mean any work, whether in Source or Object\n     form, that is based on (or derived from) the Work and for which the\n     editorial revisions, annotations, elaborations, or other modifications\n     represent, as a whole, an original work of authorship. For the purposes\n     of this License, Derivative Works shall not include works that remain\n     separable from, or merely link (or bind by name) to the interfaces of,\n     the Work and Derivative Works thereof.\n\n     \"Contribution\" shall mean any work of authorship, including\n     the original version of the Work and any modifications or additions\n     to that Work or Derivative Works thereof, that is intentionally\n     submitted to Licensor for inclusion in the Work by the copyright owner\n     or by an individual or Legal Entity authorized to submit on behalf of\n     the copyright owner. For the purposes of this definition, \"submitted\"\n     means any form of electronic, verbal, or written communication sent\n     to the Licensor or its representatives, including but not limited to\n     communication on electronic mailing lists, source code control systems,\n     and issue tracking systems that are managed by, or on behalf of, the\n     Licensor for the purpose of discussing and improving the Work, but\n     excluding communication that is conspicuously marked or otherwise\n     designated in writing by the copyright owner as \"Not a Contribution.\"\n\n     \"Contributor\" shall mean Licensor and any individual or Legal Entity\n     on behalf of whom a Contribution has been received by Licensor and\n     subsequently incorporated within the Work.\n\n  2. Grant of Copyright License. Subject to the terms and conditions of\n     this License, each Contributor hereby grants to You a perpetual,\n     worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n     copyright license to reproduce, prepare Derivative Works of,\n     publicly display, publicly perform, sublicense, and distribute the\n     Work and such Derivative Works in Source or Object form.\n\n  3. Grant of Patent License. Subject to the terms and conditions of\n     this License, each Contributor hereby grants to You a perpetual,\n     worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n     (except as stated in this section) patent license to make, have made,\n     use, offer to sell, sell, import, and otherwise transfer the Work,\n     where such license applies only to those patent claims licensable\n     by such Contributor that are necessarily infringed by their\n     Contribution(s) alone or by combination of their Contribution(s)\n     with the Work to which such Contribution(s) was submitted. If You\n     institute patent litigation against any entity (including a\n     cross-claim or counterclaim in a lawsuit) alleging that the Work\n     or a Contribution incorporated within the Work constitutes direct\n     or contributory patent infringement, then any patent licenses\n     granted to You under this License for that Work shall terminate\n     as of the date such litigation is filed.\n\n  4. Redistribution. You may reproduce and distribute copies of the\n     Work or Derivative Works thereof in any medium, with or without\n     modifications, and in Source or Object form, provided that You\n     meet the following conditions:\n\n     (a) You must give any other recipients of the Work or\n         Derivative Works a copy of this License; and\n\n     (b) You must cause any modified files to carry prominent notices\n         stating that You changed the files; and\n\n     (c) You must retain, in the Source form of any Derivative Works\n         that You distribute, all copyright, patent, trademark, and\n         attribution notices from the Source form of the Work,\n         excluding those notices that do not pertain to any part of\n         the Derivative Works; and\n\n     (d) If the Work includes a \"NOTICE\" text file as part of its\n         distribution, then any Derivative Works that You distribute must\n         include a readable copy of the attribution notices contained\n         within such NOTICE file, excluding those notices that do not\n         pertain to any part of the Derivative Works, in at least one\n         of the following places: within a NOTICE text file distributed\n         as part of the Derivative Works; within the Source form or\n         documentation, if provided along with the Derivative Works; or,\n         within a display generated by the Derivative Works, if and\n         wherever such third-party notices normally appear. The contents\n         of the NOTICE file are for informational purposes only and\n         do not modify the License. You may add Your own attribution\n         notices within Derivative Works that You distribute, alongside\n         or as an addendum to the NOTICE text from the Work, provided\n         that such additional attribution notices cannot be construed\n         as modifying the License.\n\n     You may add Your own copyright statement to Your modifications and\n     may provide additional or different license terms and conditions\n     for use, reproduction, or distribution of Your modifications, or\n     for any such Derivative Works as a whole, provided Your use,\n     reproduction, and distribution of the Work otherwise complies with\n     the conditions stated in this License.\n\n  5. Submission of Contributions. Unless You explicitly state otherwise,\n     any Contribution intentionally submitted for inclusion in the Work\n     by You to the Licensor shall be under the terms and conditions of\n     this License, without any additional terms or conditions.\n     Notwithstanding the above, nothing herein shall supersede or modify\n     the terms of any separate license agreement you may have executed\n     with Licensor regarding such Contributions.\n\n  6. Trademarks. This License does not grant permission to use the trade\n     names, trademarks, service marks, or product names of the Licensor,\n     except as required for reasonable and customary use in describing the\n     origin of the Work and reproducing the content of the NOTICE file.\n\n  7. Disclaimer of Warranty. Unless required by applicable law or\n     agreed to in writing, Licensor provides the Work (and each\n     Contributor provides its Contributions) on an \"AS IS\" BASIS,\n     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n     implied, including, without limitation, any warranties or conditions\n     of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n     PARTICULAR PURPOSE. You are solely responsible for determining the\n     appropriateness of using or redistributing the Work and assume any\n     risks associated with Your exercise of permissions under this License.\n\n  8. Limitation of Liability. In no event and under no legal theory,\n     whether in tort (including negligence), contract, or otherwise,\n     unless required by applicable law (such as deliberate and grossly\n     negligent acts) or agreed to in writing, shall any Contributor be\n     liable to You for damages, including any direct, indirect, special,\n     incidental, or consequential damages of any character arising as a\n     result of this License or out of the use or inability to use the\n     Work (including but not limited to damages for loss of goodwill,\n     work stoppage, computer failure or malfunction, or any and all\n     other commercial damages or losses), even if such Contributor\n     has been advised of the possibility of such damages.\n\n  9. Accepting Warranty or Additional Liability. While redistributing\n     the Work or Derivative Works thereof, You may choose to offer,\n     and charge a fee for, acceptance of support, warranty, indemnity,\n     or other liability obligations and/or rights consistent with this\n     License. However, in accepting such obligations, You may act only\n     on Your own behalf and on Your sole responsibility, not on behalf\n     of any other Contributor, and only if You agree to indemnify,\n     defend, and hold each Contributor harmless for any liability\n     incurred by, or claims asserted against, such Contributor by reason\n     of your accepting any such warranty or additional liability.\n\n  END OF TERMS AND CONDITIONS\n\n  APPENDIX: How to apply the Apache License to your work.\n\n     To apply the Apache License to your work, attach the following\n     boilerplate notice, with the fields enclosed by brackets \"[]\"\n     replaced with your own identifying information. (Don't include\n     the brackets!)  The text should be enclosed in the appropriate\n     comment syntax for the file format. We also recommend that a\n     file or class name and description of purpose be included on the\n     same \"printed page\" as the copyright notice for easier\n     identification within third-party archives.\n\n  Copyright [yyyy] [name of copyright owner]\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-----\n\nThe following software may be included in this product: bluebird. A copy of the source code may be downloaded from git://github.com/petkaantonov/bluebird.git. This software contains the following license and notice below:\n\nThe MIT License (MIT)\n\nCopyright (c) 2013-2018 Petka Antonov\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: brace-expansion. A copy of the source code may be downloaded from git://github.com/juliangruber/brace-expansion.git. This software contains the following license and notice below:\n\nMIT License\n\nCopyright (c) 2013 Julian Gruber <julian@juliangruber.com>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n-----\n\nThe following software may be included in this product: buffer-crc32. A copy of the source code may be downloaded from git://github.com/brianloveswords/buffer-crc32.git. This software contains the following license and notice below:\n\nThe MIT License\n\nCopyright (c) 2013 Brian J. Brennan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to use,\ncopy, modify, merge, publish, distribute, sublicense, and/or sell copies of the\nSoftware, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\nINCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR\nPURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE\nFOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: buffer-from. A copy of the source code may be downloaded from https://github.com/LinusU/buffer-from.git. This software contains the following license and notice below:\n\nMIT License\n\nCopyright (c) 2016, 2018 Linus Unnebäck\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n-----\n\nThe following software may be included in this product: cachedir. A copy of the source code may be downloaded from https://github.com/LinusU/node-cachedir.git. This software contains the following license and notice below:\n\nThe MIT License (MIT)\n\nCopyright (c) 2013-2014, 2016, 2018 Linus Unnebäck\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: caseless. A copy of the source code may be downloaded from https://github.com/mikeal/caseless. This software contains the following license and notice below:\n\nApache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n1. Definitions.\n\"License\" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.\n\"Licensor\" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.\n\"Legal Entity\" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, \"control\" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.\n\"You\" (or \"Your\") shall mean an individual or Legal Entity exercising permissions granted by this License.\n\"Source\" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.\n\"Object\" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.\n\"Work\" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).\n\"Derivative Works\" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.\n\"Contribution\" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, \"submitted\" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as \"Not a Contribution.\"\n\"Contributor\" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.\n2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.\n3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.\n4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:\nYou must give any other recipients of the Work or Derivative Works a copy of this License; and\nYou must cause any modified files to carry prominent notices stating that You changed the files; and\nYou must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and\nIf the Work includes a \"NOTICE\" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.\n5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.\n6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.\n7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.\n8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.\n9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.\nEND OF TERMS AND CONDITIONS\n\n-----\n\nThe following software may be included in this product: check-more-types. A copy of the source code may be downloaded from https://github.com/kensho/check-more-types.git. This software contains the following license and notice below:\n\nThe MIT License (MIT)\n\nCopyright (c) 2014 Kensho\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: ci-info, is-ci. A copy of the source code may be downloaded from https://github.com/watson/ci-info.git (ci-info), https://github.com/watson/is-ci.git (is-ci). This software contains the following license and notice below:\n\nThe MIT License (MIT)\n\nCopyright (c) 2016-2018 Thomas Watson Steen\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n-----\n\nThe following software may be included in this product: cli-table3. A copy of the source code may be downloaded from https://github.com/cli-table/cli-table3.git. This software contains the following license and notice below:\n\nMIT License\n\nCopyright (c) 2014 James Talmage <james.talmage@jrtechnical.com>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n-----\n\nThe following software may be included in this product: codemirror. A copy of the source code may be downloaded from https://github.com/codemirror/CodeMirror.git. This software contains the following license and notice below:\n\nMIT License\n\nCopyright (C) 2017 by Marijn Haverbeke <marijnh@gmail.com> and others\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: codemirror-spell-checker. A copy of the source code may be downloaded from https://github.com/NextStepWebs/codemirror-spell-checker. This software contains the following license and notice below:\n\nThe MIT License (MIT)\n\nCopyright (c) 2015 Wes Cossick\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n-----\n\nThe following software may be included in this product: color-convert. A copy of the source code may be downloaded from https://github.com/Qix-/color-convert.git. This software contains the following license and notice below:\n\nCopyright (c) 2011-2016 Heather Arthur <fayearthur@gmail.com>\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: color-name. A copy of the source code may be downloaded from git@github.com:colorjs/color-name.git. This software contains the following license and notice below:\n\nThe MIT License (MIT)\nCopyright (c) 2015 Dmitry Ivanov\n\nPermission 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\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE 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\nThe following software may be included in this product: colors. A copy of the source code may be downloaded from http://github.com/Marak/colors.js.git. This software contains the following license and notice below:\n\nMIT License\n\nOriginal Library\n  - Copyright (c) Marak Squires\n\nAdditional Functionality\n - Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: combined-stream, delayed-stream. A copy of the source code may be downloaded from git://github.com/felixge/node-combined-stream.git (combined-stream), git://github.com/felixge/node-delayed-stream.git (delayed-stream). This software contains the following license and notice below:\n\nCopyright (c) 2011 Debuggable Limited <felix@debuggable.com>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: commander. A copy of the source code may be downloaded from https://github.com/tj/commander.js.git. This software contains the following license and notice below:\n\n(The MIT License)\n\nCopyright (c) 2011 TJ Holowaychuk <tj@vision-media.ca>\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n'Software'), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: common-tags. A copy of the source code may be downloaded from https://github.com/declandewet/common-tags. This software contains the following license and notice below:\n\nLicense (MIT)\n-------------\n\nCopyright © Declan de Wet\n\nPermission 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\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE 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\nThe following software may be included in this product: concat-map, is-typedarray, minimist. A copy of the source code may be downloaded from git://github.com/substack/node-concat-map.git (concat-map), git://github.com/hughsk/is-typedarray.git (is-typedarray), git://github.com/substack/minimist.git (minimist). This software contains the following license and notice below:\n\nThis software is released under the MIT license:\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: concat-stream. A copy of the source code may be downloaded from http://github.com/maxogden/concat-stream.git. This software contains the following license and notice below:\n\nThe MIT License\n\nCopyright (c) 2013 Max Ogden\n\nPermission is hereby granted, free of charge,\nto any person obtaining a copy of this software and\nassociated documentation files (the \"Software\"), to\ndeal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify,\nmerge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom\nthe Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice\nshall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\nOF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR\nANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: convert-source-map. A copy of the source code may be downloaded from git://github.com/thlorenz/convert-source-map.git. This software contains the following license and notice below:\n\nCopyright 2013 Thorsten Lorenz.\nAll rights reserved.\n\nPermission is hereby granted, free of charge, to any person\nobtaining a copy of this software and associated documentation\nfiles (the \"Software\"), to deal in the Software without\nrestriction, including without limitation the rights to use,\ncopy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the\nSoftware is furnished to do so, subject to the following\nconditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\nOF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\nHOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\nWHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\nFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\nOTHER DEALINGS IN THE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: core-util-is. A copy of the source code may be downloaded from git://github.com/isaacs/core-util-is. This software contains the following license and notice below:\n\nCopyright Node.js contributors. All rights reserved.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to\ndeal in the Software without restriction, including without limitation the\nrights to use, copy, modify, merge, publish, distribute, sublicense, and/or\nsell copies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\nFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\nIN THE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: cosmiconfig. A copy of the source code may be downloaded from git+https://github.com/davidtheclark/cosmiconfig.git. This software contains the following license and notice below:\n\nThe MIT License (MIT)\n\nCopyright (c) 2015 David Clark\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n-----\n\nThe following software may be included in this product: cross-spawn. A copy of the source code may be downloaded from git@github.com:moxystudio/node-cross-spawn.git. This software contains the following license and notice below:\n\nThe MIT License (MIT)\n\nCopyright (c) 2018 Made With MOXY Lda <hello@moxy.studio>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: csstype. A copy of the source code may be downloaded from https://github.com/frenic/csstype. This software contains the following license and notice below:\n\nCopyright (c) 2017-2018 Fredrik Nicol\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n-----\n\nThe following software may be included in this product: dashdash. A copy of the source code may be downloaded from git://github.com/trentm/node-dashdash.git. This software contains the following license and notice below:\n\n# This is the MIT license\n\nCopyright (c) 2013 Trent Mick. All rights reserved.\nCopyright (c) 2013 Joyent Inc. All rights reserved.\n\nPermission is hereby granted, free of charge, to any person obtaining a\ncopy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be included\nin all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\nOR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: date-fns. A copy of the source code may be downloaded from https://github.com/date-fns/date-fns. This software contains the following license and notice below:\n\n# License\n\ndate-fns is licensed under the [MIT license](http://kossnocorp.mit-license.org).\nRead more about MIT at [TLDRLegal](https://tldrlegal.com/license/mit-license).\n\n-----\n\nThe following software may be included in this product: debug. A copy of the source code may be downloaded from git://github.com/visionmedia/debug.git. This software contains the following license and notice below:\n\n(The MIT License)\n\nCopyright (c) 2014 TJ Holowaychuk <tj@vision-media.ca>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software\nand associated documentation files (the 'Software'), to deal in the Software without restriction,\nincluding without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,\nand/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial\nportions of the Software.\n\nTHE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT\nLIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\nWHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: dom-helpers. A copy of the source code may be downloaded from git+https://github.com/react-bootstrap/dom-helpers.git. This software contains the following license and notice below:\n\nThe MIT License (MIT)\n\nCopyright (c) 2015 Jason Quense\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n-----\n\nThe following software may be included in this product: easymde. A copy of the source code may be downloaded from https://github.com/Ionaru/easy-markdown-editor.git. This software contains the following license and notice below:\n\nThe MIT License (MIT)\n\nCopyright (c) 2015 Sparksuite, Inc.\nCopyright (c) 2017 Jeroen Akkerman.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n-----\n\nThe following software may be included in this product: ecc-jsbn. A copy of the source code may be downloaded from https://github.com/quartzjer/ecc-jsbn.git. This software contains the following license and notice below:\n\nThe MIT License (MIT)\n\nCopyright (c) 2014 Jeremie Miller\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n-----\n\nThe following software may be included in this product: emoji-mart. A copy of the source code may be downloaded from git@github.com:missive/emoji-mart.git. This software contains the following license and notice below:\n\nCopyright (c) 2016, Missive\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.\n\n2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.\n\n3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n-----\n\nThe following software may be included in this product: end-of-stream, pump. A copy of the source code may be downloaded from git://github.com/mafintosh/end-of-stream.git (end-of-stream), git://github.com/mafintosh/pump.git (pump). This software contains the following license and notice below:\n\nThe MIT License (MIT)\n\nCopyright (c) 2014 Mathias Buus\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: error-ex, is-arrayish. A copy of the source code may be downloaded from https://github.com/qix-/node-error-ex.git (error-ex), https://github.com/qix-/node-is-arrayish.git (is-arrayish). This software contains the following license and notice below:\n\nThe MIT License (MIT)\n\nCopyright (c) 2015 JD Ballard\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: eventemitter2. A copy of the source code may be downloaded from git://github.com/hij1nx/EventEmitter2.git. This software contains the following license and notice below:\n\nThe MIT License (MIT)\n\nCopyright (c) 2016 Paolo Fragomeni <http://www.github.com/0x00a> and Contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the 'Software'), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is furnished\nto do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN\nAN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: execa, get-stream, global-dirs, import-fresh, log-symbols, onetime, parse-json, pretty-bytes. A copy of the source code may be downloaded from https://github.com/sindresorhus/execa.git (execa), https://github.com/sindresorhus/get-stream.git (get-stream), https://github.com/sindresorhus/global-dirs.git (global-dirs), https://github.com/sindresorhus/import-fresh.git (import-fresh), https://github.com/sindresorhus/log-symbols.git (log-symbols), https://github.com/sindresorhus/onetime.git (onetime), https://github.com/sindresorhus/parse-json.git (parse-json), https://github.com/sindresorhus/pretty-bytes.git (pretty-bytes). This software contains the following license and notice below:\n\nMIT License\n\nCopyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)\n\nPermission 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\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE 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\nThe following software may be included in this product: executable. A copy of the source code may be downloaded from https://github.com/kevva/executable.git. This software contains the following license and notice below:\n\nThe MIT License (MIT)\n\nCopyright (c) Kevin Mårtensson <kevinmartensson@gmail.com>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: extend. A copy of the source code may be downloaded from https://github.com/justmoon/node-extend.git. This software contains the following license and notice below:\n\nThe MIT License (MIT)\n\nCopyright (c) 2014 Stefan Thomas\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: extract-zip. A copy of the source code may be downloaded from https://github.com/maxogden/extract-zip.git. This software contains the following license and notice below:\n\nCopyright (c) 2014 Max Ogden and other contributors\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, 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\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n-----\n\nThe following software may be included in this product: extsprintf, jsprim. A copy of the source code may be downloaded from git://github.com/davepacheco/node-extsprintf.git (extsprintf), git://github.com/joyent/node-jsprim.git (jsprim). This software contains the following license and notice below:\n\nCopyright (c) 2012, Joyent, Inc. All rights reserved.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE\n\n-----\n\nThe following software may be included in this product: fast-deep-equal, json-schema-traverse. A copy of the source code may be downloaded from git+https://github.com/epoberezkin/fast-deep-equal.git (fast-deep-equal), git+https://github.com/epoberezkin/json-schema-traverse.git (json-schema-traverse). This software contains the following license and notice below:\n\nMIT License\n\nCopyright (c) 2017 Evgeny Poberezkin\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n-----\n\nThe following software may be included in this product: fast-json-stable-stringify. A copy of the source code may be downloaded from git://github.com/epoberezkin/fast-json-stable-stringify.git. This software contains the following license and notice below:\n\nThis software is released under the MIT license:\n\nCopyright (c) 2017 Evgeny Poberezkin\nCopyright (c) 2013 James Halliday\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: fast-memoize. A copy of the source code may be downloaded from git+https://github.com/caiogondim/fast-memoize.git. This software contains the following license and notice below:\n\nThe MIT License (MIT)\n\nCopyright (c) 2016 Caio Gondim\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n-----\n\nThe following software may be included in this product: fd-slicer. A copy of the source code may be downloaded from git://github.com/andrewrk/node-fd-slicer.git. This software contains the following license and notice below:\n\nCopyright (c) 2014 Andrew Kelley\n\nPermission is hereby granted, free of charge, to any person\nobtaining a copy of this software and associated documentation files\n(the \"Software\"), to deal in the Software without restriction,\nincluding without limitation the rights to use, copy, modify, merge,\npublish, distribute, sublicense, and/or sell copies of the Software,\nand to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\nBE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\nACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n-----\n\nThe following software may be included in this product: find-root. A copy of the source code may be downloaded from git@github.com:js-n/find-root.git. This software contains the following license and notice below:\n\nCopyright © 2017 jsdnxx\n\nPermission 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\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE 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\nThe following software may be included in this product: form-data. A copy of the source code may be downloaded from git://github.com/form-data/form-data.git. This software contains the following license and notice below:\n\nCopyright (c) 2012 Felix Geisendörfer (felix@debuggable.com) and contributors\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\n all 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\n THE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: fs-extra. A copy of the source code may be downloaded from https://github.com/jprichardson/node-fs-extra. This software contains the following license and notice below:\n\n(The MIT License)\n\nCopyright (c) 2011-2017 JP Richardson\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files\n(the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify,\n merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is\n furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE\nWARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS\nOR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\n ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: fs.realpath. A copy of the source code may be downloaded from git+https://github.com/isaacs/fs.realpath.git. This software contains the following license and notice below:\n\nThe ISC License\n\nCopyright (c) Isaac Z. Schlueter and Contributors\n\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or without fee is hereby granted, provided that the above\ncopyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\nWITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\nANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\nWHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\nACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR\nIN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n----\n\nThis library bundles a version of the `fs.realpath` and `fs.realpathSync`\nmethods from Node.js v0.10 under the terms of the Node.js MIT license.\n\nNode's license follows, also included at the header of `old.js` which contains\nthe licensed code:\n\n  Copyright Joyent, Inc. and other Node contributors.\n\n  Permission is hereby granted, free of charge, to any person obtaining a\n  copy of this software and associated documentation files (the \"Software\"),\n  to deal in the Software without restriction, including without limitation\n  the rights to use, copy, modify, merge, publish, distribute, sublicense,\n  and/or sell copies of the Software, and to permit persons to whom the\n  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\n  all 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\n  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n  DEALINGS IN THE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: function-bind. A copy of the source code may be downloaded from git://github.com/Raynos/function-bind.git. This software contains the following license and notice below:\n\nCopyright (c) 2013 Raynos.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: getos. A copy of the source code may be downloaded from https://github.com/retrohacker/getos.git. This software contains the following license and notice below:\n\nThe MIT License (MIT)\n\nCopyright (c) 2016 William Blankenship\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n-----\n\nThe following software may be included in this product: getpass, http-signature, sshpk. A copy of the source code may be downloaded from https://github.com/arekinath/node-getpass.git (getpass), git://github.com/joyent/node-http-signature.git (http-signature), git+https://github.com/joyent/node-sshpk.git (sshpk). This software contains the following license and notice below:\n\nCopyright Joyent, Inc. All rights reserved.\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to\ndeal in the Software without restriction, including without limitation the\nrights to use, copy, modify, merge, publish, distribute, sublicense, and/or\nsell copies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\nFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\nIN THE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: glob. A copy of the source code may be downloaded from git://github.com/isaacs/node-glob.git. This software contains the following license and notice below:\n\nThe ISC License\n\nCopyright (c) Isaac Z. Schlueter and Contributors\n\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or without fee is hereby granted, provided that the above\ncopyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\nWITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\nANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\nWHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\nACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR\nIN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n## Glob Logo\n\nGlob's logo created by Tanya Brassie <http://tanyabrassie.com/>, licensed\nunder a Creative Commons Attribution-ShareAlike 4.0 International License\nhttps://creativecommons.org/licenses/by-sa/4.0/\n\n-----\n\nThe following software may be included in this product: graceful-fs. A copy of the source code may be downloaded from https://github.com/isaacs/node-graceful-fs. This software contains the following license and notice below:\n\nThe ISC License\n\nCopyright (c) Isaac Z. Schlueter, Ben Noordhuis, and Contributors\n\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or without fee is hereby granted, provided that the above\ncopyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\nWITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\nANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\nWHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\nACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR\nIN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-----\n\nThe following software may be included in this product: har-schema. A copy of the source code may be downloaded from https://github.com/ahmadnassri/har-schema.git. This software contains the following license and notice below:\n\nCopyright (c) 2015, Ahmad Nassri <ahmad@ahmadnassri.com>\n\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or without fee is hereby granted, provided that the above\ncopyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\nWITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\nANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\nWHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\nACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\nOR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-----\n\nThe following software may be included in this product: har-validator. A copy of the source code may be downloaded from https://github.com/ahmadnassri/node-har-validator.git. This software contains the following license and notice below:\n\nMIT License\n\nCopyright (c) 2018 Ahmad Nassri <ahmad@ahmadnassri.com>\n\nPermission 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\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE 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\nThe following software may be included in this product: history, react-router, react-router-dom. A copy of the source code may be downloaded from https://github.com/ReactTraining/history.git (history), https://github.com/ReactTraining/react-router.git (react-router), https://github.com/ReactTraining/react-router.git (react-router-dom). This software contains the following license and notice below:\n\nMIT License\n\nCopyright (c) React Training 2016-2018\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n-----\n\nThe following software may be included in this product: hoist-non-react-statics. A copy of the source code may be downloaded from git://github.com/mridgway/hoist-non-react-statics.git. This software contains the following license and notice below:\n\nSoftware License Agreement (BSD License)\n========================================\n\nCopyright (c) 2015, Yahoo! Inc. All rights reserved.\n----------------------------------------------------\n\nRedistribution and use of this software in source and binary forms, with or\nwithout modification, are permitted provided that the following conditions are\nmet:\n\n  * Redistributions of source code must retain the above copyright notice, this\n    list of conditions and the following disclaimer.\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  * Neither the name of Yahoo! Inc. nor the names of YUI's contributors may be\n    used to endorse or promote products derived from this software without\n    specific prior written permission of Yahoo! Inc.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\nANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON\nANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n-----\n\nThe following software may be included in this product: human-signals. A copy of the source code may be downloaded from https://github.com/ehmicky/human-signals.git. This software contains the following license and notice below:\n\nApache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright 2019 ehmicky <ehmicky@gmail.com>\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-----\n\nThe following software may be included in this product: inflight. A copy of the source code may be downloaded from https://github.com/npm/inflight.git. This software contains the following license and notice below:\n\nThe ISC License\n\nCopyright (c) Isaac Z. Schlueter\n\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or without fee is hereby granted, provided that the above\ncopyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\nWITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\nANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\nWHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\nACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR\nIN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-----\n\nThe following software may be included in this product: inherits. A copy of the source code may be downloaded from git://github.com/isaacs/inherits. This software contains the following license and notice below:\n\nThe ISC License\n\nCopyright (c) Isaac Z. Schlueter\n\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or without fee is hereby granted, provided that the above\ncopyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND\nFITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\nPERFORMANCE OF THIS SOFTWARE.\n\n-----\n\nThe following software may be included in this product: ini, isexe, json-stringify-safe, minimatch, once, rimraf, which, wrappy. A copy of the source code may be downloaded from git://github.com/isaacs/ini.git (ini), git+https://github.com/isaacs/isexe.git (isexe), git://github.com/isaacs/json-stringify-safe (json-stringify-safe), git://github.com/isaacs/minimatch.git (minimatch), git://github.com/isaacs/once (once), git://github.com/isaacs/rimraf.git (rimraf), git://github.com/isaacs/node-which.git (which), https://github.com/npm/wrappy (wrappy). This software contains the following license and notice below:\n\nThe ISC License\n\nCopyright (c) Isaac Z. Schlueter and Contributors\n\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or without fee is hereby granted, provided that the above\ncopyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\nWITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\nANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\nWHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\nACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR\nIN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-----\n\nThe following software may be included in this product: intl-messageformat, intl-messageformat-parser. A copy of the source code may be downloaded from git@github.com:formatjs/formatjs.git (intl-messageformat), git://github.com/formatjs/formatjs.git (intl-messageformat-parser). This software contains the following license and notice below:\n\nCopyright (c) 2019, Oath Inc.\n\nLicensed under the terms of the New BSD license. See below for terms.\n\nRedistribution and use of this software in source and binary forms,\nwith or without modification, are permitted provided that the following\nconditions are met:\n\n- Redistributions of source code must retain the above\n  copyright notice, this list of conditions and the\n  following disclaimer.\n\n- Redistributions in binary form must reproduce the above\n  copyright notice, this list of conditions and the\n  following disclaimer in the documentation and/or other\n  materials provided with the distribution.\n\n- Neither the name of Oath Inc. nor the names of its\n  contributors may be used to endorse or promote products\n  derived from this software without specific prior\n  written permission of Oath Inc.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\nIS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED\nTO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A\nPARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n-----\n\nThe following software may be included in this product: is-core-module. A copy of the source code may be downloaded from git+https://github.com/inspect-js/is-core-module.git. This software contains the following license and notice below:\n\nThe MIT License (MIT)\n\nCopyright (c) 2014 Dave Justice\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: is-promise. A copy of the source code may be downloaded from https://github.com/then/is-promise.git. This software contains the following license and notice below:\n\nCopyright (c) 2014 Forbes Lindesay\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: isstream. A copy of the source code may be downloaded from https://github.com/rvagg/isstream.git. This software contains the following license and notice below:\n\nThe MIT License (MIT)\n=====================\n\nCopyright (c) 2015 Rod Vagg\n---------------------------\n\nPermission 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\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE 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\nThe following software may be included in this product: js-tokens. A copy of the source code may be downloaded from https://github.com/lydell/js-tokens.git. This software contains the following license and notice below:\n\nThe MIT License (MIT)\n\nCopyright (c) 2014, 2015, 2016, 2017, 2018 Simon Lydell\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: jsbn. A copy of the source code may be downloaded from https://github.com/andyperlitch/jsbn.git. This software contains the following license and notice below:\n\nLicensing\n---------\n\nThis software is covered under the following copyright:\n\n/*\n * Copyright (c) 2003-2005  Tom Wu\n * All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining\n * a copy of this software and associated documentation files (the\n * \"Software\"), to deal in the Software without restriction, including\n * without limitation the rights to use, copy, modify, merge, publish,\n * distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so, subject to\n * the following conditions:\n *\n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS-IS\" AND WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY\n * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.\n *\n * IN NO EVENT SHALL TOM WU BE LIABLE FOR ANY SPECIAL, INCIDENTAL,\n * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER\n * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF\n * THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT\n * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n *\n * In addition, the following condition applies:\n *\n * All redistributions must retain an intact copy of this copyright notice\n * and disclaimer.\n */\n\nAddress all questions regarding this license to:\n\n  Tom Wu\n  tjw@cs.Stanford.EDU\n\n-----\n\nThe following software may be included in this product: json-parse-even-better-errors. A copy of the source code may be downloaded from https://github.com/npm/json-parse-even-better-errors. This software contains the following license and notice below:\n\nCopyright 2017 Kat Marchán\nCopyright npm, Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a\ncopy of this software and associated documentation files (the \"Software\"),\nto deal in the Software without restriction, including without limitation\nthe rights to use, copy, modify, merge, publish, distribute, sublicense,\nand/or sell copies of the Software, and to permit persons to whom the\nSoftware is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\nFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n\n---\n\nThis library is a fork of 'better-json-errors' by Kat Marchán, extended and\ndistributed under the terms of the MIT license above.\n\n-----\n\nThe following software may be included in this product: jsonfile. A copy of the source code may be downloaded from git@github.com:jprichardson/node-jsonfile.git. This software contains the following license and notice below:\n\n(The MIT License)\n\nCopyright (c) 2012-2015, JP Richardson <jprichardson@gmail.com>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files\n(the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify,\n merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is\n furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE\nWARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS\nOR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\n ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: lazy-ass. A copy of the source code may be downloaded from https://github.com/bahmutov/lazy-ass.git. This software contains the following license and notice below:\n\nCopyright (c) 2014 Gleb Bahmutov\n\nPermission is hereby granted, free of charge, to any person\nobtaining a copy of this software and associated documentation\nfiles (the \"Software\"), to deal in the Software without\nrestriction, including without limitation the rights to use,\ncopy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the\nSoftware is furnished to do so, subject to the following\nconditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\nOF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\nHOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\nWHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\nFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\nOTHER DEALINGS IN THE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: lines-and-columns. A copy of the source code may be downloaded from https://github.com/eventualbuddha/lines-and-columns.git. This software contains the following license and notice below:\n\nThe MIT License (MIT)\n\nCopyright (c) 2015 Brian Donovan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: lodash. A copy of the source code may be downloaded from https://github.com/lodash/lodash.git. This software contains the following license and notice below:\n\nCopyright OpenJS Foundation and other contributors <https://openjsf.org/>\n\nBased on Underscore.js, copyright Jeremy Ashkenas,\nDocumentCloud and Investigative Reporters & Editors <http://underscorejs.org/>\n\nThis software consists of voluntary contributions made by many\nindividuals. For exact contribution history, see the revision history\navailable at https://github.com/lodash/lodash\n\nThe following license applies to all parts of this software except as\ndocumented below:\n\n====\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n====\n\nCopyright and related rights for sample code are waived via CC0. Sample\ncode is defined as all source code displayed within the prose of the\ndocumentation.\n\nCC0: http://creativecommons.org/publicdomain/zero/1.0/\n\n====\n\nFiles located in the node_modules and vendor directories are externally\nmaintained libraries used by this software which have their own\nlicenses; we recommend you read them, as their terms may differ from the\nterms above.\n\n-----\n\nThe following software may be included in this product: lodash.once. A copy of the source code may be downloaded from https://github.com/lodash/lodash.git. This software contains the following license and notice below:\n\nCopyright jQuery Foundation and other contributors <https://jquery.org/>\n\nBased on Underscore.js, copyright Jeremy Ashkenas,\nDocumentCloud and Investigative Reporters & Editors <http://underscorejs.org/>\n\nThis software consists of voluntary contributions made by many\nindividuals. For exact contribution history, see the revision history\navailable at https://github.com/lodash/lodash\n\nThe following license applies to all parts of this software except as\ndocumented below:\n\n====\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n====\n\nCopyright and related rights for sample code are waived via CC0. Sample\ncode is defined as all source code displayed within the prose of the\ndocumentation.\n\nCC0: http://creativecommons.org/publicdomain/zero/1.0/\n\n====\n\nFiles located in the node_modules and vendor directories are externally\nmaintained libraries used by this software which have their own\nlicenses; we recommend you read them, as their terms may differ from the\nterms above.\n\n-----\n\nThe following software may be included in this product: loose-envify. A copy of the source code may be downloaded from git://github.com/zertosh/loose-envify.git. This software contains the following license and notice below:\n\nThe MIT License (MIT)\n\nCopyright (c) 2015 Andres Suarez <zertosh@gmail.com>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: marked. A copy of the source code may be downloaded from git://github.com/markedjs/marked.git. This software contains the following license and notice below:\n\n# License information\n\n## Contribution License Agreement\n\nIf you contribute code to this project, you are implicitly allowing your code\nto be distributed under the MIT license. You are also implicitly verifying that\nall code is your original work. `</legalese>`\n\n## Marked\n\nCopyright (c) 2018+, MarkedJS (https://github.com/markedjs/)\nCopyright (c) 2011-2018, Christopher Jeffrey (https://github.com/chjj/)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n## Markdown\n\nCopyright © 2004, John Gruber\nhttp://daringfireball.net/\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:\n\n* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.\n* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.\n* Neither the name “Markdown” nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.\n\nThis software is provided by the copyright holders and contributors “as is” and any express or implied warranties, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose are disclaimed. In no event shall the copyright owner or contributors be liable for any direct, indirect, incidental, special, exemplary, or consequential damages (including, but not limited to, procurement of substitute goods or services; loss of use, data, or profits; or business interruption) however caused and on any theory of liability, whether in contract, strict liability, or tort (including negligence or otherwise) arising in any way out of the use of this software, even if advised of the possibility of such damage.\n\n-----\n\nThe following software may be included in this product: memoize-one, tiny-invariant, tiny-warning. A copy of the source code may be downloaded from https://github.com/alexreardon/memoize-one.git (memoize-one), https://github.com/alexreardon/tiny-invariant.git (tiny-invariant), https://github.com/alexreardon/tiny-warning.git (tiny-warning). This software contains the following license and notice below:\n\nMIT License\n\nCopyright (c) 2019 Alexander Reardon\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n-----\n\nThe following software may be included in this product: merge-stream. A copy of the source code may be downloaded from https://github.com/grncdr/merge-stream.git. This software contains the following license and notice below:\n\nThe MIT License (MIT)\n\nCopyright (c) Stephen Sugden <me@stephensugden.com> (stephensugden.com)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: mime-db. A copy of the source code may be downloaded from https://github.com/jshttp/mime-db.git. This software contains the following license and notice below:\n\nThe MIT License (MIT)\n\nCopyright (c) 2014 Jonathan Ong me@jongleberry.com\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: mime-types. A copy of the source code may be downloaded from https://github.com/jshttp/mime-types.git. This software contains the following license and notice below:\n\n(The MIT License)\n\nCopyright (c) 2014 Jonathan Ong <me@jongleberry.com>\nCopyright (c) 2015 Douglas Christopher Wilson <doug@somethingdoug.com>\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n'Software'), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: mini-create-react-context. A copy of the source code may be downloaded from https://github.com/StringEpsilon/mini-create-react-context. This software contains the following license and notice below:\n\nCopyright (c) 2019-present StringEpsilon <StringEpsilon@gmail.com>\n\nCopyright (c) 2017-2019 James Kyle <me@thejameskyle.com>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n-----\n\nThe following software may be included in this product: mkdirp. A copy of the source code may be downloaded from https://github.com/substack/node-mkdirp.git. This software contains the following license and notice below:\n\nCopyright 2010 James Halliday (mail@substack.net)\n\nThis project is free software released under the MIT/X11 license:\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: moment. A copy of the source code may be downloaded from https://github.com/moment/moment.git. This software contains the following license and notice below:\n\nCopyright (c) JS Foundation and other contributors\n\nPermission is hereby granted, free of charge, to any person\nobtaining a copy of this software and associated documentation\nfiles (the \"Software\"), to deal in the Software without\nrestriction, including without limitation the rights to use,\ncopy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the\nSoftware is furnished to do so, subject to the following\nconditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\nOF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\nHOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\nWHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\nFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\nOTHER DEALINGS IN THE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: ms. A copy of the source code may be downloaded from https://github.com/vercel/ms.git. This software contains the following license and notice below:\n\nThe MIT License (MIT)\n\nCopyright (c) 2020 Vercel, Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n-----\n\nThe following software may be included in this product: ms. A copy of the source code may be downloaded from https://github.com/zeit/ms.git. This software contains the following license and notice below:\n\nThe MIT License (MIT)\n\nCopyright (c) 2016 Zeit, Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n-----\n\nThe following software may be included in this product: nanoevents. A copy of the source code may be downloaded from https://github.com/ai/nanoevents.git. This software contains the following license and notice below:\n\nThe MIT License (MIT)\n\nCopyright 2016 Andrey Sitnik <andrey@sitnik.ru>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: path-parse. A copy of the source code may be downloaded from https://github.com/jbgutierrez/path-parse.git. This software contains the following license and notice below:\n\nThe MIT License (MIT)\n\nCopyright (c) 2015 Javier Blanco\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n-----\n\nThe following software may be included in this product: path-to-regexp. A copy of the source code may be downloaded from https://github.com/pillarjs/path-to-regexp.git. This software contains the following license and notice below:\n\nThe MIT License (MIT)\n\nCopyright (c) 2014 Blake Embrey (hello@blakeembrey.com)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: pend. A copy of the source code may be downloaded from git://github.com/andrewrk/node-pend.git. This software contains the following license and notice below:\n\nThe MIT License (Expat)\n\nCopyright (c) 2014 Andrew Kelley\n\nPermission is hereby granted, free of charge, to any person\nobtaining a copy of this software and associated documentation files\n(the \"Software\"), to deal in the Software without restriction,\nincluding without limitation the rights to use, copy, modify, merge,\npublish, distribute, sublicense, and/or sell copies of the Software,\nand to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\nBE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\nACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n-----\n\nThe following software may be included in this product: performance-now. A copy of the source code may be downloaded from git://github.com/braveg1rl/performance-now.git. This software contains the following license and notice below:\n\nCopyright (c) 2013 Braveg1rl\n\nPermission 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\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE 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\nThe following software may be included in this product: process-nextick-args. A copy of the source code may be downloaded from https://github.com/calvinmetcalf/process-nextick-args.git. This software contains the following license and notice below:\n\n# Copyright (c) 2015 Calvin Metcalf\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\n**THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.**\n\n-----\n\nThe following software may be included in this product: prop-types. A copy of the source code may be downloaded from https://github.com/facebook/prop-types.git. This software contains the following license and notice below:\n\nMIT License\n\nCopyright (c) 2013-present, Facebook, Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n-----\n\nThe following software may be included in this product: psl. A copy of the source code may be downloaded from git@github.com:lupomontero/psl.git. This software contains the following license and notice below:\n\nThe MIT License (MIT)\n\nCopyright (c) 2017 Lupo Montero lupomontero@gmail.com\n\nPermission 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\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE 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\nThe following software may be included in this product: qs. A copy of the source code may be downloaded from https://github.com/ljharb/qs.git. This software contains the following license and notice below:\n\nCopyright (c) 2014 Nathan LaFreniere and other contributors.\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above copyright\n      notice, this list of conditions and the following disclaimer in the\n      documentation and/or other materials provided with the distribution.\n    * The names of any contributors may not be used to endorse or promote\n      products derived from this software without specific prior written\n      permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY\nDIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\nON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n                                  *   *   *\n\nThe complete list of contributors can be found at: https://github.com/hapijs/qs/graphs/contributors\n\n-----\n\nThe following software may be included in this product: querystring. A copy of the source code may be downloaded from git://github.com/Gozala/querystring.git. This software contains the following license and notice below:\n\nCopyright 2012 Irakli Gozalishvili. All rights reserved.\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to\ndeal in the Software without restriction, including without limitation the\nrights to use, copy, modify, merge, publish, distribute, sublicense, and/or\nsell copies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\nFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\nIN THE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: ramda. A copy of the source code may be downloaded from git://github.com/ramda/ramda.git. This software contains the following license and notice below:\n\nThe MIT License (MIT)\n\nCopyright (c) 2013-2018 Scott Sauyet and Michael Hurley\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: react, react-dom, react-is, scheduler. A copy of the source code may be downloaded from git+https://github.com/facebook/react.git (react), git+https://github.com/facebook/react.git (react-dom), https://github.com/facebook/react.git (react-is), https://github.com/facebook/react.git (scheduler). This software contains the following license and notice below:\n\nMIT License\n\nCopyright (c) Facebook, Inc. and its affiliates.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n-----\n\nThe following software may be included in this product: react-input-autosize. A copy of the source code may be downloaded from https://github.com/JedWatson/react-input-autosize.git. This software contains the following license and notice below:\n\nThe MIT License (MIT)\n\nCopyright (c) 2018 Jed Watson\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n-----\n\nThe following software may be included in this product: react-intl. A copy of the source code may be downloaded from git@github.com:formatjs/formatjs.git. This software contains the following license and notice below:\n\nCopyright 2019 Oath Inc.\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n\n    * Redistributions in binary form must reproduce the above copyright\n      notice, this list of conditions and the following disclaimer in the\n      documentation and/or other materials provided with the distribution.\n\n    * Neither the name of the Oath Inc. nor the\n      names of its contributors may be used to endorse or promote products\n      derived from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL Oath INC. BE LIABLE FOR ANY\nDIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\nON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n-----\n\nThe following software may be included in this product: react-simplemde-editor. A copy of the source code may be downloaded from https://github.com/RIP21/react-simplemde-editor. This software contains the following license and notice below:\n\nMIT License\n\nCopyright (c) 2017 Ben Lodge and Andrii Los\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n-----\n\nThe following software may be included in this product: react-transition-group. A copy of the source code may be downloaded from https://github.com/reactjs/react-transition-group.git. This software contains the following license and notice below:\n\nBSD 3-Clause License\n\nCopyright (c) 2018, React Community\nForked from React (https://github.com/facebook/react) Copyright 2013-present, Facebook, Inc.\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, 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\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n-----\n\nThe following software may be included in this product: readable-stream. A copy of the source code may be downloaded from git://github.com/nodejs/readable-stream. This software contains the following license and notice below:\n\nNode.js is licensed for use as follows:\n\n\"\"\"\nCopyright Node.js contributors. All rights reserved.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to\ndeal in the Software without restriction, including without limitation the\nrights to use, copy, modify, merge, publish, distribute, sublicense, and/or\nsell copies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\nFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\nIN THE SOFTWARE.\n\"\"\"\n\nThis license applies to parts of Node.js originating from the\nhttps://github.com/joyent/node repository:\n\n\"\"\"\nCopyright Joyent, Inc. and other Node contributors. All rights reserved.\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to\ndeal in the Software without restriction, including without limitation the\nrights to use, copy, modify, merge, publish, distribute, sublicense, and/or\nsell copies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\nFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\nIN THE SOFTWARE.\n\"\"\"\n\n-----\n\nThe following software may be included in this product: regenerator-runtime. A copy of the source code may be downloaded from https://github.com/facebook/regenerator/tree/master/packages/regenerator-runtime. This software contains the following license and notice below:\n\nMIT License\n\nCopyright (c) 2014-present, Facebook, Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n-----\n\nThe following software may be included in this product: request-progress. A copy of the source code may be downloaded from git://github.com/IndigoUnited/node-request-progress. This software contains the following license and notice below:\n\nCopyright (c) 2012 IndigoUnited\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is furnished\nto do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: resolve. A copy of the source code may be downloaded from git://github.com/browserify/resolve.git. This software contains the following license and notice below:\n\nMIT License\n\nCopyright (c) 2012 James Halliday\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n-----\n\nThe following software may be included in this product: resolve-pathname, value-equal. A copy of the source code may be downloaded from https://github.com/mjackson/resolve-pathname.git (resolve-pathname), https://github.com/mjackson/value-equal.git (value-equal). This software contains the following license and notice below:\n\nMIT License\n\nCopyright (c) Michael Jackson 2016-2018\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n-----\n\nThe following software may be included in this product: rxjs. A copy of the source code may be downloaded from https://github.com/reactivex/rxjs.git. This software contains the following license and notice below:\n\nApache License\n                         Version 2.0, January 2004\n                      http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n    \"License\" shall mean the terms and conditions for use, reproduction,\n    and distribution as defined by Sections 1 through 9 of this document.\n\n    \"Licensor\" shall mean the copyright owner or entity authorized by\n    the copyright owner that is granting the License.\n\n    \"Legal Entity\" shall mean the union of the acting entity and all\n    other entities that control, are controlled by, or are under common\n    control with that entity. For the purposes of this definition,\n    \"control\" means (i) the power, direct or indirect, to cause the\n    direction or management of such entity, whether by contract or\n    otherwise, or (ii) ownership of fifty percent (50%) or more of the\n    outstanding shares, or (iii) beneficial ownership of such entity.\n\n    \"You\" (or \"Your\") shall mean an individual or Legal Entity\n    exercising permissions granted by this License.\n\n    \"Source\" form shall mean the preferred form for making modifications,\n    including but not limited to software source code, documentation\n    source, and configuration files.\n\n    \"Object\" form shall mean any form resulting from mechanical\n    transformation or translation of a Source form, including but\n    not limited to compiled object code, generated documentation,\n    and conversions to other media types.\n\n    \"Work\" shall mean the work of authorship, whether in Source or\n    Object form, made available under the License, as indicated by a\n    copyright notice that is included in or attached to the work\n    (an example is provided in the Appendix below).\n\n    \"Derivative Works\" shall mean any work, whether in Source or Object\n    form, that is based on (or derived from) the Work and for which the\n    editorial revisions, annotations, elaborations, or other modifications\n    represent, as a whole, an original work of authorship. For the purposes\n    of this License, Derivative Works shall not include works that remain\n    separable from, or merely link (or bind by name) to the interfaces of,\n    the Work and Derivative Works thereof.\n\n    \"Contribution\" shall mean any work of authorship, including\n    the original version of the Work and any modifications or additions\n    to that Work or Derivative Works thereof, that is intentionally\n    submitted to Licensor for inclusion in the Work by the copyright owner\n    or by an individual or Legal Entity authorized to submit on behalf of\n    the copyright owner. For the purposes of this definition, \"submitted\"\n    means any form of electronic, verbal, or written communication sent\n    to the Licensor or its representatives, including but not limited to\n    communication on electronic mailing lists, source code control systems,\n    and issue tracking systems that are managed by, or on behalf of, the\n    Licensor for the purpose of discussing and improving the Work, but\n    excluding communication that is conspicuously marked or otherwise\n    designated in writing by the copyright owner as \"Not a Contribution.\"\n\n    \"Contributor\" shall mean Licensor and any individual or Legal Entity\n    on behalf of whom a Contribution has been received by Licensor and\n    subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n    this License, each Contributor hereby grants to You a perpetual,\n    worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n    copyright license to reproduce, prepare Derivative Works of,\n    publicly display, publicly perform, sublicense, and distribute the\n    Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n    this License, each Contributor hereby grants to You a perpetual,\n    worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n    (except as stated in this section) patent license to make, have made,\n    use, offer to sell, sell, import, and otherwise transfer the Work,\n    where such license applies only to those patent claims licensable\n    by such Contributor that are necessarily infringed by their\n    Contribution(s) alone or by combination of their Contribution(s)\n    with the Work to which such Contribution(s) was submitted. If You\n    institute patent litigation against any entity (including a\n    cross-claim or counterclaim in a lawsuit) alleging that the Work\n    or a Contribution incorporated within the Work constitutes direct\n    or contributory patent infringement, then any patent licenses\n    granted to You under this License for that Work shall terminate\n    as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n    Work or Derivative Works thereof in any medium, with or without\n    modifications, and in Source or Object form, provided that You\n    meet the following conditions:\n\n    (a) You must give any other recipients of the Work or\n        Derivative Works a copy of this License; and\n\n    (b) You must cause any modified files to carry prominent notices\n        stating that You changed the files; and\n\n    (c) You must retain, in the Source form of any Derivative Works\n        that You distribute, all copyright, patent, trademark, and\n        attribution notices from the Source form of the Work,\n        excluding those notices that do not pertain to any part of\n        the Derivative Works; and\n\n    (d) If the Work includes a \"NOTICE\" text file as part of its\n        distribution, then any Derivative Works that You distribute must\n        include a readable copy of the attribution notices contained\n        within such NOTICE file, excluding those notices that do not\n        pertain to any part of the Derivative Works, in at least one\n        of the following places: within a NOTICE text file distributed\n        as part of the Derivative Works; within the Source form or\n        documentation, if provided along with the Derivative Works; or,\n        within a display generated by the Derivative Works, if and\n        wherever such third-party notices normally appear. The contents\n        of the NOTICE file are for informational purposes only and\n        do not modify the License. You may add Your own attribution\n        notices within Derivative Works that You distribute, alongside\n        or as an addendum to the NOTICE text from the Work, provided\n        that such additional attribution notices cannot be construed\n        as modifying the License.\n\n    You may add Your own copyright statement to Your modifications and\n    may provide additional or different license terms and conditions\n    for use, reproduction, or distribution of Your modifications, or\n    for any such Derivative Works as a whole, provided Your use,\n    reproduction, and distribution of the Work otherwise complies with\n    the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n    any Contribution intentionally submitted for inclusion in the Work\n    by You to the Licensor shall be under the terms and conditions of\n    this License, without any additional terms or conditions.\n    Notwithstanding the above, nothing herein shall supersede or modify\n    the terms of any separate license agreement you may have executed\n    with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n    names, trademarks, service marks, or product names of the Licensor,\n    except as required for reasonable and customary use in describing the\n    origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n    agreed to in writing, Licensor provides the Work (and each\n    Contributor provides its Contributions) on an \"AS IS\" BASIS,\n    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n    implied, including, without limitation, any warranties or conditions\n    of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n    PARTICULAR PURPOSE. You are solely responsible for determining the\n    appropriateness of using or redistributing the Work and assume any\n    risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n    whether in tort (including negligence), contract, or otherwise,\n    unless required by applicable law (such as deliberate and grossly\n    negligent acts) or agreed to in writing, shall any Contributor be\n    liable to You for damages, including any direct, indirect, special,\n    incidental, or consequential damages of any character arising as a\n    result of this License or out of the use or inability to use the\n    Work (including but not limited to damages for loss of goodwill,\n    work stoppage, computer failure or malfunction, or any and all\n    other commercial damages or losses), even if such Contributor\n    has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n    the Work or Derivative Works thereof, You may choose to offer,\n    and charge a fee for, acceptance of support, warranty, indemnity,\n    or other liability obligations and/or rights consistent with this\n    License. However, in accepting such obligations, You may act only\n    on Your own behalf and on Your sole responsibility, not on behalf\n    of any other Contributor, and only if You agree to indemnify,\n    defend, and hold each Contributor harmless for any liability\n    incurred by, or claims asserted against, such Contributor by reason\n    of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n    To apply the Apache License to your work, attach the following\n    boilerplate notice, with the fields enclosed by brackets \"[]\"\n    replaced with your own identifying information. (Don't include\n    the brackets!)  The text should be enclosed in the appropriate\n    comment syntax for the file format. We also recommend that a\n    file or class name and description of purpose be included on the\n    same \"printed page\" as the copyright notice for easier\n    identification within third-party archives.\n\n Copyright (c) 2015-2018 Google, Inc., Netflix, Inc., Microsoft Corp. and contributors\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-----\n\nThe following software may be included in this product: safe-buffer. A copy of the source code may be downloaded from git://github.com/feross/safe-buffer.git. This software contains the following license and notice below:\n\nThe MIT License (MIT)\n\nCopyright (c) Feross Aboukhadijeh\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: safer-buffer. A copy of the source code may be downloaded from git+https://github.com/ChALkeR/safer-buffer.git. This software contains the following license and notice below:\n\nMIT License\n\nCopyright (c) 2018 Nikita Skovoroda <chalkerx@gmail.com>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n-----\n\nThe following software may be included in this product: shallow-equal. A copy of the source code may be downloaded from https://github.com/moroshko/shallow-equal.git. This software contains the following license and notice below:\n\nThe MIT License (MIT)\n\nCopyright © 2016 Misha Moroshko\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the “Software”), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies\nof the Software, and to permit persons to whom the Software is furnished to do\nso, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n-----\n\nThe following software may be included in this product: shebang-command. A copy of the source code may be downloaded from https://github.com/kevva/shebang-command.git. This software contains the following license and notice below:\n\nMIT License\n\nCopyright (c) Kevin Mårtensson <kevinmartensson@gmail.com> (github.com/kevva)\n\nPermission 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\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE 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\nThe following software may be included in this product: signal-exit. A copy of the source code may be downloaded from https://github.com/tapjs/signal-exit.git. This software contains the following license and notice below:\n\nThe ISC License\n\nCopyright (c) 2015, Contributors\n\nPermission to use, copy, modify, and/or distribute this software\nfor any purpose with or without fee is hereby granted, provided\nthat the above copyright notice and this permission notice\nappear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\nWITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES\nOF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE\nLIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES\nOR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,\nWHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,\nARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-----\n\nThe following software may be included in this product: slice-ansi. A copy of the source code may be downloaded from https://github.com/chalk/slice-ansi.git. This software contains the following license and notice below:\n\n(The MIT License)\n\nCopyright (c) 2015 DC <threedeecee@gmail.com>\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n'Software'), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: source-map. A copy of the source code may be downloaded from http://github.com/mozilla/source-map.git. This software contains the following license and notice below:\n\nCopyright (c) 2009-2011, Mozilla Foundation and contributors\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, 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 names of the Mozilla Foundation nor the names of project\n  contributors may be used to endorse or promote products derived from this\n  software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n-----\n\nThe following software may be included in this product: string_decoder. A copy of the source code may be downloaded from git://github.com/nodejs/string_decoder.git. This software contains the following license and notice below:\n\nNode.js is licensed for use as follows:\n\n\"\"\"\nCopyright Node.js contributors. All rights reserved.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to\ndeal in the Software without restriction, including without limitation the\nrights to use, copy, modify, merge, publish, distribute, sublicense, and/or\nsell copies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\nFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\nIN THE SOFTWARE.\n\"\"\"\n\nThis license applies to parts of Node.js originating from the\nhttps://github.com/joyent/node repository:\n\n\"\"\"\nCopyright Joyent, Inc. and other Node contributors. All rights reserved.\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to\ndeal in the Software without restriction, including without limitation the\nrights to use, copy, modify, merge, publish, distribute, sublicense, and/or\nsell copies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\nFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\nIN THE SOFTWARE.\n\"\"\"\n\n-----\n\nThe following software may be included in this product: symbol-observable. A copy of the source code may be downloaded from https://github.com/blesh/symbol-observable.git. This software contains the following license and notice below:\n\nThe MIT License (MIT)\n\nCopyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)\nCopyright (c) Ben Lesh <ben@benlesh.com>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: tmp. A copy of the source code may be downloaded from https://github.com/raszi/node-tmp.git. This software contains the following license and notice below:\n\nThe MIT License (MIT)\n\nCopyright (c) 2014 KARASZI István\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n-----\n\nThe following software may be included in this product: to-fast-properties. A copy of the source code may be downloaded from https://github.com/sindresorhus/to-fast-properties.git. This software contains the following license and notice below:\n\nMIT License\n\nCopyright (c) 2014 Petka Antonov\n              2015 Sindre Sorhus\n\nPermission 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\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE 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\nThe following software may be included in this product: tough-cookie. A copy of the source code may be downloaded from git://github.com/salesforce/tough-cookie.git. This software contains the following license and notice below:\n\nCopyright (c) 2015, Salesforce.com, Inc.\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.\n\n2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.\n\n3. Neither the name of Salesforce.com nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n-----\n\nThe following software may be included in this product: tslib. A copy of the source code may be downloaded from https://github.com/Microsoft/tslib.git. This software contains the following license and notice below:\n\nCopyright (c) Microsoft Corporation.\n\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or without fee is hereby granted.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\nPERFORMANCE OF THIS SOFTWARE.\n\n-----\n\nThe following software may be included in this product: tweetnacl. A copy of the source code may be downloaded from https://github.com/dchest/tweetnacl-js.git. This software contains the following license and notice below:\n\nThis is free and unencumbered software released into the public domain.\n\nAnyone is free to copy, modify, publish, use, compile, sell, or\ndistribute this software, either in source code form or as a compiled\nbinary, for any purpose, commercial or non-commercial, and by any\nmeans.\n\nIn jurisdictions that recognize copyright laws, the author or authors\nof this software dedicate any and all copyright interest in the\nsoftware to the public domain. We make this dedication for the benefit\nof the public at large and to the detriment of our heirs and\nsuccessors. We intend this dedication to be an overt act of\nrelinquishment in perpetuity of all present and future rights to this\nsoftware under copyright law.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR\nOTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\nOTHER DEALINGS IN THE SOFTWARE.\n\nFor more information, please refer to <http://unlicense.org>\n\n-----\n\nThe following software may be included in this product: typedarray. A copy of the source code may be downloaded from git://github.com/substack/typedarray.git. This software contains the following license and notice below:\n\n/*\n Copyright (c) 2010, Linden Research, Inc.\n Copyright (c) 2012, Joshua Bell\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\n all 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\n THE SOFTWARE.\n $/LicenseInfo$\n */\n\n// Original can be found at:\n//   https://bitbucket.org/lindenlab/llsd\n// Modifications by Joshua Bell inexorabletash@gmail.com\n//   https://github.com/inexorabletash/polyfill\n\n// ES3/ES5 implementation of the Krhonos Typed Array Specification\n//   Ref: http://www.khronos.org/registry/typedarray/specs/latest/\n//   Date: 2011-02-01\n//\n// Variations:\n//  * Allows typed_array.get/set() as alias for subscripts (typed_array[])\n\n-----\n\nThe following software may be included in this product: universalify. A copy of the source code may be downloaded from git+https://github.com/RyanZim/universalify.git. This software contains the following license and notice below:\n\n(The MIT License)\n\nCopyright (c) 2017, Ryan Zimmerman <opensrc@ryanzim.com>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the 'Software'), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: uri-js. A copy of the source code may be downloaded from http://github.com/garycourt/uri-js. This software contains the following license and notice below:\n\nCopyright 2011 Gary Court. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:\n\n1.\tRedistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.\n\n2.\tRedistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY GARY COURT \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GARY COURT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nThe views and conclusions contained in the software and documentation are those of the authors and should not be interpreted as representing official policies, either expressed or implied, of Gary Court.\n\n-----\n\nThe following software may be included in this product: url. A copy of the source code may be downloaded from https://github.com/defunctzombie/node-url.git. This software contains the following license and notice below:\n\nThe MIT License (MIT)\n\nCopyright Joyent, Inc. and other Node contributors.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n-----\n\nThe following software may be included in this product: util-deprecate. A copy of the source code may be downloaded from git://github.com/TooTallNate/util-deprecate.git. This software contains the following license and notice below:\n\n(The MIT License)\n\nCopyright (c) 2014 Nathan Rajlich <nathan@tootallnate.net>\n\nPermission is hereby granted, free of charge, to any person\nobtaining a copy of this software and associated documentation\nfiles (the \"Software\"), to deal in the Software without\nrestriction, including without limitation the rights to use,\ncopy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the\nSoftware is furnished to do so, subject to the following\nconditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\nOF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\nHOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\nWHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\nFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\nOTHER DEALINGS IN THE SOFTWARE.\n\n-----\n\nThe following software may be included in this product: uuid. A copy of the source code may be downloaded from https://github.com/uuidjs/uuid.git. This software contains the following license and notice below:\n\nThe MIT License (MIT)\n\nCopyright (c) 2010-2016 Robert Kieffer and other contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n-----\n\nThe following software may be included in this product: verror. A copy of the source code may be downloaded from git://github.com/davepacheco/node-verror.git. This software contains the following license and notice below:\n\nCopyright (c) 2016, Joyent, Inc. All rights reserved.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE\n\n-----\n\nThe following software may be included in this product: yaml. A copy of the source code may be downloaded from https://github.com/eemeli/yaml.git. This software contains the following license and notice below:\n\nCopyright 2018 Eemeli Aro <eemeli@gmail.com>\n\nPermission to use, copy, modify, and/or distribute this software for any purpose\nwith or without fee is hereby granted, provided that the above copyright notice\nand this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND\nFITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS\nOF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER\nTORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF\nTHIS SOFTWARE.\n\n-----\n\nThe following software may be included in this product: yauzl. A copy of the source code may be downloaded from https://github.com/thejoshwolfe/yauzl.git. This software contains the following license and notice below:\n\nThe MIT License (MIT)\n\nCopyright (c) 2014 Josh Wolfe\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n-----\n\nThe following software may be included in this product: react-day-picker. A copy of the source code may be downloaded from https://github.com/gpbl/react-day-picker.git. This software contains the following license and notice below:\n\nThe MIT License (MIT)\n\nCopyright (c) 2014 Giampaolo Bellavite\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
  },
  {
    "path": "webapp/__mocks__/fileMock.js",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nmodule.exports = 'test-file-stub';\n"
  },
  {
    "path": "webapp/__mocks__/styleMock.js",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nmodule.exports = {};\n"
  },
  {
    "path": "webapp/cypress/config.json",
    "content": "{\n    \"serverRoot\": \"http://localhost:8088\",\n    \"port\": 8088,\n    \"dbtype\": \"sqlite3\",\n    \"dbconfig\": \"file::memory:?cache=shared&_busy_timeout=5000\",\n    \"useSSL\": false,\n    \"webpath\": \"../pack\",\n    \"filespath\": \"../../files\",\n    \"telemetry\": false,\n    \"webhook_update\": [],\n    \"session_expire_time\": 2592000,\n    \"session_refresh_time\": 18000\n}\n"
  },
  {
    "path": "webapp/cypress/global.d.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\ndeclare namespace Cypress {\n    type LoginData = {\n        username: string\n        password: string\n    }\n    type UserData = LoginData & {\n        email: string\n    }\n    interface Chainable {\n        apiRegisterUser: (data: UserData, token?: string, failOnError?: boolean) => Chainable\n        apiLoginUser: (data: LoginData) => Chainable\n        apiGetMe: () => Chainable<string>\n        apiChangePassword: (userId: string, oldPassword: string, newPassword: string) => Chainable\n        apiInitServer: () => Chainable\n        apiDeleteBoard: (id: string) => Chainable\n        apiResetBoards: () => Chainable\n        apiSkipTour: (userID: string) => Chainable\n\n        uiCreateNewBoard: (title?: string) => Chainable\n        uiAddNewGroup: (name?: string) => Chainable\n        uiAddNewCard: (title?: string, columnIndex?: number) => Chainable\n\n        /**\n         * Create a board on a given menu item.\n         *\n         * @param {string} item - one of the template menu options, ex. 'Empty board'\n         */\n        uiCreateBoard: (item: string) => Chainable\n        uiCreateEmptyBoard: () => Chainable\n    }\n}\n"
  },
  {
    "path": "webapp/cypress/integration/cardBadges.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\ndescribe('Card badges', () => {\n    beforeEach(() => {\n        cy.apiInitServer()\n        cy.apiResetBoards()\n        cy.apiGetMe().then((userID) => cy.apiSkipTour(userID))\n        localStorage.setItem('welcomePageViewed', 'true')\n        localStorage.setItem('language', 'en')\n    })\n\n    it('Shows and hides card badges', () => {\n        cy.visit('/')\n\n        // Create new board\n        cy.uiCreateNewBoard('Testing')\n\n        // Add a new card\n        cy.uiAddNewCard('Card')\n\n        // Add some comments\n        cy.log('**Add some comments**')\n        addComment('Some comment')\n        addComment('Another comment')\n        addComment('Additional comment')\n\n        // Add card description\n        cy.log('**Add card description**')\n        cy.findByText('Add a description...').click()\n        cy.findByRole('combobox').type('## Header\\n- [ ] one\\n- [x] two{esc}')\n\n        // Add checkboxes\n        cy.log('**Add checkboxes**')\n        cy.findByRole('button', {name: 'Add content'}).click()\n        cy.findByRole('button', {name: 'checkbox'}).click()\n        cy.focused().type('three{enter}')\n        cy.focused().type('four{enter}')\n        cy.focused().type('{esc}')\n        cy.findByDisplayValue('three').prev().click()\n\n        // Close card dialog\n        cy.log('**Close card dialog**')\n        cy.findByRole('button', {name: 'Close dialog'}).click()\n        cy.findByRole('dialog').should('not.exist')\n\n        // Show card badges\n        cy.log('**Show card badges**')\n        cy.findByRole('button', {name: 'Properties menu'}).click()\n        cy.findByRole('button', {name: 'Comments and description'}).click()\n        cy.findByTitle('This card has a description').should('exist')\n        cy.findByTitle('Comments').contains('3').should('exist')\n        cy.findByTitle('Checkboxes').contains('2/4').should('exist')\n\n        // Hide card badges\n        cy.log('**Hide card badges**')\n        cy.findByRole('button', {name: 'Comments and description'}).click()\n        cy.findByRole('button', {name: 'Properties menu'}).click()\n        cy.findByTitle('This card has a description').should('not.exist')\n        cy.findByTitle('Comments').should('not.exist')\n        cy.findByTitle('Checkboxes').should('not.exist')\n    })\n\n    const addComment = (text: string) => {\n        cy.findByText('Add a comment...').click()\n        cy.findByRole('combobox').type(text).blur()\n        cy.findByRole('button', {name: 'Send'}).click()\n    }\n})\n"
  },
  {
    "path": "webapp/cypress/integration/cardURLProperty.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\ndescribe('Card URL Property', () => {\n    beforeEach(() => {\n        cy.apiInitServer()\n        cy.apiResetBoards()\n        cy.apiGetMe().then((userID) => cy.apiSkipTour(userID))\n        localStorage.setItem('welcomePageViewed', 'true')\n        localStorage.setItem('language', 'en')\n    })\n\n    const url = 'https://mattermost.com'\n    const changedURL = 'https://mattermost.com/blog'\n\n    it('Allows to create and edit URL property', () => {\n        cy.visit('/')\n\n        // Create new board\n        cy.uiCreateNewBoard('Testing')\n\n        // Add a new card\n        cy.uiAddNewCard('Card')\n\n        // Add URL property\n        cy.log('**Add URL property**')\n        cy.findByRole('button', {name: '+ Add a property'}).click()\n        cy.findByRole('button', {name: 'URL'}).click()\n        cy.findByRole('textbox', {name: 'URL'}).type('{enter}')\n\n        // Enter URL\n        cy.log('**Enter URL**')\n        cy.findByPlaceholderText('Empty').type(`${url}{enter}`)\n\n        // Check buttons\n        cy.log('**Check buttons**')\n        cy.findByRole('link', {name: url}).realHover()\n        cy.findByRole('button', {name: 'Edit'}).should('exist')\n        cy.findByRole('button', {name: 'Copy'}).should('exist')\n\n        // Change URL\n        cy.log('**Change URL**')\n        cy.findByRole('link', {name: url}).realHover()\n        cy.findByRole('button', {name: 'Edit'}).click()\n        cy.findByRole('textbox', {name: url}).clear().type(`${changedURL}{enter}`)\n        cy.findByRole('link', {name: changedURL}).should('exist')\n\n        // Close card dialog\n        cy.log('**Close card dialog**')\n        cy.findByRole('button', {name: 'Close dialog'}).click()\n        cy.findByRole('dialog').should('not.exist')\n\n        // Show URL property\n        showURLProperty()\n\n        // Copy URL to clipboard\n        cy.log('**Copy URL to clipboard**')\n        cy.document().then((doc) => cy.spy(doc, 'execCommand')).as('exec')\n        cy.findByRole('link', {name: changedURL}).realHover()\n        cy.findByRole('button', {name: 'Edit'}).should('not.exist')\n        cy.findByRole('button', {name: 'Copy'}).click()\n        cy.findByText('Copied!').should('exist')\n        cy.findByText('Copied!').should('not.exist')\n        cy.get('@exec').should('have.been.calledOnceWith', 'copy')\n\n        // Add table view\n        addView('Table')\n\n        // Check buttons\n        cy.log('**Check buttons**')\n        cy.findByRole('link', {name: changedURL}).realHover()\n        cy.findByRole('button', {name: 'Edit'}).should('exist')\n        cy.findByRole('button', {name: 'Copy'}).should('not.exist')\n\n        // Add gallery view\n        addView('Gallery')\n        showURLProperty()\n\n        // Check buttons\n        cy.log('**Check buttons**')\n        cy.findByRole('link', {name: changedURL}).realHover()\n        cy.findByRole('button', {name: 'Edit'}).should('not.exist')\n        cy.findByRole('button', {name: 'Copy'}).should('exist')\n\n        // Add calendar view\n        addView('Calendar')\n        showURLProperty()\n\n        // Check buttons\n        cy.log('**Check buttons**')\n        cy.findByRole('link', {name: changedURL}).realHover()\n        cy.findByRole('button', {name: 'Edit'}).should('not.exist')\n        cy.findByRole('button', {name: 'Copy'}).should('exist')\n    })\n\n    type ViewType = 'Board' | 'Table' | 'Gallery' | 'Calendar'\n\n    const addView = (type: ViewType) => {\n        cy.log(`**Add ${type} view**`)\n\n        cy.findByRole('button', {name: 'View menu'}).click()\n        cy.findByText('Add view').realHover()\n        cy.findByRole('button', {name: type}).click()\n        cy.findByRole('textbox', {name: `${type} view`}).should('exist')\n    }\n\n    const showURLProperty = () => {\n        cy.log('**Show URL property**')\n        cy.findByRole('button', {name: 'Properties'}).click()\n        cy.findByRole('button', {name: 'URL'}).click()\n        cy.findByRole('link', {name: changedURL}).should('exist')\n    }\n})\n"
  },
  {
    "path": "webapp/cypress/integration/createBoard.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\ndescribe('Create and delete board / card', () => {\n    const timestamp = new Date().toLocaleString()\n    const boardTitle = `Test Board (${timestamp})`\n    const cardTitle = `Test Card (${timestamp})`\n\n    beforeEach(() => {\n        cy.apiInitServer()\n        cy.apiResetBoards()\n        cy.apiGetMe().then((userID) => cy.apiSkipTour(userID))\n        localStorage.setItem('welcomePageViewed', 'true')\n        localStorage.setItem('language', 'en')\n    })\n\n    it('MM-T4274 Create an Empty Board', () => {\n        cy.visit('/')\n\n        cy.contains('+ Add board').should('exist').click()\n\n        // Tests for template selector\n        cy.contains('Use this template').should('exist')\n\n        // Some options are present\n        cy.contains('Meeting Agenda').should('exist')\n        cy.contains('Personal Goals').should('exist')\n        cy.contains('Project Tasks').should('exist')\n\n        // Create empty board\n        cy.contains('Create an empty board').should('exist').click({force: true})\n        cy.get('.BoardComponent').should('exist')\n        cy.get('.Editable.title').invoke('attr', 'placeholder').should('contain', 'Untitled board')\n\n        // Change Title\n        cy.get('.Editable.title').\n            type('Testing').\n            type('{enter}').\n            should('have.value', 'Testing')\n    })\n\n    it('Can create and delete a board and a card', () => {\n        // Visit a page and create new empty board\n        cy.visit('/')\n        cy.uiCreateEmptyBoard()\n\n        // Change board title\n        cy.log('**Change board title**')\n        cy.get('.Editable.title').\n            type(boardTitle).\n            type('{enter}').\n            should('have.value', boardTitle)\n\n        // Hide and show the sidebar\n        cy.log('**Hide and show the sidebar**')\n        cy.get('.sidebarSwitcher').click()\n        cy.get('.Sidebar .heading').should('not.exist')\n        cy.get('.Sidebar .show-button').click()\n        cy.get('.Sidebar .heading').should('exist')\n\n        // Rename board view\n        cy.log('**Rename board view**')\n        const boardViewTitle = `Test board (${timestamp})`\n        cy.get(\".ViewHeader>.viewSelector>.Editable[title='Board view']\").should('exist')\n        cy.get('.ViewHeader>.viewSelector>.Editable').\n            clear().\n            type(boardViewTitle).\n            type('{esc}')\n        cy.get(`.ViewHeader .Editable[title='${boardViewTitle}']`).should('exist')\n\n        // Create card\n        cy.log('**Create card**')\n        cy.get('.ViewHeader').contains('New').click()\n        cy.get('.CardDetail').should('exist')\n\n        //Check title has focus when card is created\n        cy.log('**Check title has focus when card is created**')\n        cy.get('.CardDetail .EditableArea.title').\n            should('have.focus')\n\n        // Change card title\n        cy.log('**Change card title**')\n        // eslint-disable-next-line cypress/no-unnecessary-waiting\n        cy.get('.CardDetail .EditableArea.title').\n            click().\n            should('have.focus').\n            wait(1000).\n            type(cardTitle).\n            should('have.value', cardTitle)\n\n        // Close card dialog\n        cy.log('**Close card dialog**')\n        cy.get('.Dialog Button[title=\\'Close dialog\\']').\n            should('be.visible').\n            click().\n            wait(500)\n\n        // Create a card by clicking on the + button\n        cy.log('**Create a card by clicking on the + button**')\n        cy.get('.KanbanColumnHeader button .AddIcon').click()\n        cy.get('.CardDetail').should('exist')\n        cy.get('.Dialog.dialog-back .wrapper').click({force: true})\n\n        // Create table view\n        cy.log('**Create table view**')\n        cy.get('.ViewHeader').get('.DropdownIcon').first().parent().click()\n        cy.get('.ViewHeader').contains('Add view').realHover()\n        cy.get('.ViewHeader').\n            contains('Add view').\n            parent().\n            contains('Table').\n            click()\n        cy.get(\".ViewHeader .Editable[title='Table view']\").should('exist')\n        cy.get(`.TableRow [value='${cardTitle}']`).should('exist')\n\n        // Rename table view\n        cy.log('**Rename table view**')\n        const tableViewTitle = `Test table (${timestamp})`\n        cy.get(\".ViewHeader .Editable[title='Table view']\").\n            clear().\n            type(tableViewTitle).\n            type('{esc}')\n        cy.get(`.ViewHeader .Editable[title='${tableViewTitle}']`).should('exist')\n\n        // Sort the table\n        cy.log('**Sort the table**')\n        cy.get('.ViewHeader').contains('Sort').click()\n        cy.get('.ViewHeader').\n            contains('Sort').\n            parent().\n            contains('Name').\n            click()\n\n        // Delete board\n        cy.log('**Delete board**')\n        cy.get('.Sidebar .octo-sidebar-list').then((el) => {\n            cy.log(el.text())\n        })\n        cy.get('.Sidebar .octo-sidebar-list').\n            contains(boardTitle).\n            parent().\n            find('.MenuWrapper').\n            find('button.IconButton').\n            click({force: true})\n        cy.contains('Delete board').click({force: true})\n        cy.get('.DeleteBoardDialog button.danger').click({force: true})\n        cy.contains(boardTitle).should('not.exist')\n    })\n\n    it('MM-T4433 Scrolls the kanban board when dragging card to edge', () => {\n        // Visit a page and create new empty board\n        cy.visit('/')\n        cy.wait(500)\n        cy.uiCreateEmptyBoard()\n\n        // Create 10 empty groups\n        cy.log('**Create new empty groups**')\n        for (let i = 0; i < 10; i++) {\n            cy.contains('+ Add a group').scrollIntoView().should('be.visible').click()\n            cy.get('.KanbanColumnHeader .Editable[value=\\'New group\\']').should('have.length', i + 1)\n        }\n\n        // Create empty card in last group\n        cy.log('**Create new empty card in first group**')\n        cy.get('.octo-board-column').last().contains('+ New').scrollIntoView().click()\n        cy.get('.Dialog').should('exist')\n        cy.get('.Dialog Button[title=\\'Close dialog\\']').should('be.visible').click()\n        cy.get('.KanbanCard').scrollIntoView().should('exist')\n\n        // Drag card to right corner and expect scroll to occur\n        // eslint-disable-next-line cypress/no-unnecessary-waiting\n        cy.get('.Kanban').invoke('scrollLeft').should('not.equal', 0).wait(1000)\n\n        // wait necessary to let state change propagate\n        // eslint-disable-next-line cypress/no-unnecessary-waiting\n        cy.get('.KanbanCard').\n            trigger('dragstart').\n            wait(500)\n\n        // wait necessary to trigger scroll animation for some time\n        // eslint-disable-next-line cypress/no-unnecessary-waiting\n        cy.get('.Kanban').\n            trigger('dragover', {clientX: 400, clientY: Cypress.config().viewportHeight / 2}).\n            wait(4500).\n            trigger('dragend')\n\n        cy.get('.Kanban').invoke('scrollLeft').should('equal', 0)\n    })\n\n    it('GH-2520 make cut/undo/redo work in comments', () => {\n        const isMAC = navigator.userAgent.indexOf('Mac') !== -1\n        const ctrlKey = isMAC ? 'meta' : 'ctrl'\n\n        // Visit a page and create new empty board\n        cy.visit('/')\n        cy.uiCreateEmptyBoard()\n\n        // Create card\n        cy.log('**Create card**')\n        cy.get('.ViewHeader').contains('New').click()\n        cy.get('.CardDetail').should('exist')\n\n        cy.wait(1000)\n\n        cy.log('**Add comment**')\n        cy.get('.CommentsList').\n            findAllByTestId('preview-element').\n            click().\n            get('.CommentsList .MarkdownEditorInput').\n            type('Test Text')\n\n        cy.log('**Cut comment**')\n        cy.get('.CommentsList .MarkdownEditorInput').\n            type('{selectAll}').\n            trigger('cut').\n            should('have.text', '')\n\n        cy.log('**Undo comment**')\n        cy.get('.CommentsList .MarkdownEditorInput').\n            type(`{${ctrlKey}+z}`).\n            should('have.text', 'Test Text')\n\n        cy.log('**Redo comment**')\n        cy.get('.CommentsList .MarkdownEditorInput').\n            type(`{shift+${ctrlKey}+z}`).\n            should('have.text', '')\n    })\n})\n"
  },
  {
    "path": "webapp/cypress/integration/groupByProperty.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\ndescribe('Group board by different properties', () => {\n    beforeEach(() => {\n        cy.apiInitServer()\n        cy.apiResetBoards()\n        cy.apiGetMe().then((userID) => cy.apiSkipTour(userID))\n        localStorage.setItem('welcomePageViewed', 'true')\n        localStorage.setItem('language', 'en')\n    })\n\n    it('MM-T4291 Group by different property', () => {\n        cy.visit('/')\n\n        // Create new board\n        cy.uiCreateNewBoard('Testing')\n\n        // Add a new group\n        cy.uiAddNewGroup('Group 1')\n\n        // Add a new card to the group\n        cy.log('**Add a new card to the group**')\n        cy.findAllByRole('button', {name: '+ New'}).eq(1).click()\n        cy.findByRole('dialog').should('exist')\n        cy.findByTestId('select-non-editable').findByText('Group 1').should('exist')\n        cy.get('#mainBoardBody').findByText('Untitled').should('exist')\n\n        // Add new select property\n        cy.log('**Add new select property**')\n        cy.findAllByRole('button', {name: '+ Add a property'}).click()\n        cy.findAllByRole('button', {name: 'Select'}).click()\n        cy.findByRole('textbox', {name: 'Select'}).type('{enter}')\n        cy.findByRole('dialog').findByRole('button', {name: 'Select'}).should('exist')\n\n        // Close card dialog\n        cy.log('**Close card dialog**')\n        cy.findByRole('button', {name: 'Close dialog'}).click()\n        cy.findByRole('dialog').should('not.exist')\n\n        // Group by new select property\n        cy.log('**Group by new select property**')\n        cy.findByRole('button', {name: /Group by:/}).click()\n        cy.findByRole('button', {name: 'Status'}).get('.CheckIcon').should('exist')\n        cy.findByRole('button', {name: 'Select'}).click()\n        cy.findByTitle(/empty Select property/).contains('No Select')\n        cy.get('#mainBoardBody').findByText('Untitled').should('exist')\n\n        // Add another new group\n        cy.log('**Add another new group**')\n        cy.findByRole('button', {name: '+ Add a group'}).click()\n        cy.findByRole('textbox', {name: 'New group'}).should('exist')\n\n        // Add a new card to another group\n        cy.log('**Add a new card to another group**')\n        cy.findAllByRole('button', {name: '+ New'}).eq(1).click()\n        cy.findByRole('dialog').should('exist')\n        cy.findAllByTestId('select-non-editable').last().findByText('New group').should('exist')\n    })\n})\n"
  },
  {
    "path": "webapp/cypress/integration/loginActions.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\ndescribe('Login actions', () => {\n    const username = Cypress.env('username')\n    const email = Cypress.env('email')\n    const password = Cypress.env('password')\n\n    beforeEach(() => {\n        localStorage.setItem('language', 'en')\n    })\n\n    it('Can perform login/register actions', () => {\n        // Redirects to login page\n        cy.log('**Redirects to login page (except plugin mode) **')\n        cy.visit('/')\n        cy.location('pathname').should('eq', '/login')\n        cy.get('.LoginPage').contains('Log in')\n        cy.get('#login-username').should('exist')\n        cy.get('#login-password').should('exist')\n        cy.get('button').contains('Log in')\n        cy.get('a').contains('create an account', {matchCase: false})\n\n        // Can register a user\n        cy.log('**Can register a user**')\n        cy.visit('/login')\n        cy.get('a').contains('create an account', {matchCase: false}).click()\n        cy.location('pathname').should('eq', '/register')\n        cy.get('.RegisterPage').contains('Sign up')\n        cy.get('#login-email').type(email)\n        cy.get('#login-username').type(username)\n        cy.get('#login-password').type(password)\n        cy.get('button').contains('Register').click()\n        workspaceIsAvailable()\n\n        // Can log out user\n        cy.log('**Can log out user**')\n        cy.get('.SidebarUserMenu').click()\n        cy.get('.menu-name').contains('Log out').click()\n        cy.location('pathname').should('eq', '/login')\n\n        // User should not be logged in automatically\n        cy.log('**User should not be logged in automatically**')\n        cy.visit('/')\n        cy.location('pathname').should('eq', '/login')\n\n        // Can log in registered user\n        cy.log('**Can log in registered user**')\n        loginUser(password)\n\n        // Can change password\n        cy.log('**Can change password**')\n        const newPassword = 'new_password'\n        cy.get('.SidebarUserMenu').click()\n        cy.get('.menu-name').contains('Change password').click()\n        cy.location('pathname').should('eq', '/change_password')\n        cy.get('.ChangePasswordPage').contains('Change Password')\n        cy.get('#login-oldpassword').type(password)\n        cy.get('#login-newpassword').type(newPassword)\n        cy.get('button').contains('Change password').click()\n        cy.get('.succeeded').click()\n        workspaceIsAvailable()\n        logoutUser()\n\n        // Can log in user with new password\n        cy.log('**Can log in user with new password**')\n        loginUser(newPassword).then(() => resetPassword(newPassword))\n        logoutUser()\n\n        // Can't register second user without invite link\n        cy.log('**Can\\'t register second user without invite link**')\n        cy.visit('/register')\n        cy.get('#login-email').type(email)\n        cy.get('#login-username').type(username)\n        cy.get('#login-password').type(password)\n        cy.get('button').contains('Register').click()\n        cy.get('.error').contains('Invalid registration link').should('exist')\n\n        // Can register second user using invite link\n        cy.log('**Can register second user using invite link**')\n\n        // Copy invite link\n        cy.log('**Copy invite link**')\n        loginUser(password)\n        cy.get('.Sidebar .SidebarUserMenu').click()\n        cy.get('.menu-name').contains('Invite users').click()\n        cy.get('.Button').contains('Copy link').click()\n        cy.get('.Button').contains('Copied').should('exist')\n\n        cy.get('a.shareUrl').invoke('attr', 'href').then((inviteLink) => {\n            logoutUser()\n\n            // Register a new user\n            cy.log('**Register new user**')\n            cy.visit(inviteLink as string)\n            cy.get('#login-email').type('new-user@mail.com')\n            cy.get('#login-username').type('new-user')\n            cy.get('#login-password').type('new-password')\n            cy.get('button').contains('Register').click()\n            workspaceIsAvailable()\n        })\n    })\n\n    const workspaceIsAvailable = () => {\n        cy.location('pathname').should('eq', '/')\n        cy.get('.Workspace').should('exist')\n        return cy.get('.Sidebar').should('exist')\n    }\n\n    const loginUser = (withPassword: string) => {\n        cy.visit('/login')\n        cy.get('#login-username').type(username)\n        cy.get('#login-password').type(withPassword)\n        cy.get('button').contains('Log in').click()\n        return workspaceIsAvailable()\n    }\n\n    const logoutUser = () => {\n        cy.log('**Log out existing user**')\n        cy.get('.SidebarUserMenu').click()\n        cy.get('.menu-name').contains('Log out').click()\n        cy.location('pathname').should('eq', '/login')\n    }\n\n    const resetPassword = (oldPassword: string) => {\n        cy.apiGetMe().then((userId) => cy.apiChangePassword(userId, oldPassword, password))\n    }\n})\n"
  },
  {
    "path": "webapp/cypress/integration/manageGroups.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\ndescribe('Manage groups', () => {\n    beforeEach(() => {\n        cy.apiInitServer()\n        cy.apiResetBoards()\n        cy.apiGetMe().then((userID) => cy.apiSkipTour(userID))\n        localStorage.setItem('welcomePageViewed', 'true')\n        localStorage.setItem('language', 'en')\n    })\n\n    it('MM-T4284 Adding a group', () => {\n        // Visit a page and create new empty board\n        cy.visit('/')\n        cy.uiCreateEmptyBoard()\n\n        cy.contains('+ Add a group').click({force: true})\n        cy.get('.KanbanColumnHeader .Editable[value=\\'New group\\']').should('exist')\n\n        cy.get('.KanbanColumnHeader .Editable[value=\\'New group\\']').\n            clear().\n            type('Group 1').\n            blur()\n        cy.get('.KanbanColumnHeader .Editable[value=\\'Group 1\\']').should('exist')\n    })\n\n    it('MM-T4285 Adding group color', () => {\n        // Visit a page and create new empty board\n        cy.visit('/')\n        cy.uiCreateEmptyBoard()\n\n        cy.contains('+ Add a group').click({force: true})\n        cy.get('.KanbanColumnHeader .Editable[value=\\'New group\\']').should('exist')\n\n        cy.get('.KanbanColumnHeader').last().within(() => {\n            cy.get('.icon-dots-horizontal').click({force: true})\n            cy.get('.menu-options').should('exist').within(() => {\n                cy.contains('Hide').should('exist')\n                cy.contains('Delete').should('exist')\n\n                // Some colours\n                cy.contains('Brown').should('exist')\n                cy.contains('Gray').should('exist')\n                cy.contains('Orange').should('exist')\n\n                // Click on green\n                cy.contains('Green').should('be.visible').click().wait(1000) // eslint-disable-line cypress/no-unnecessary-waiting\n            })\n        })\n\n        cy.get('.KanbanColumnHeader').last().within(() => {\n            cy.get('.Label.propColorGreen').should('exist')\n        })\n    })\n\n    it('MM-T4287 Hiding/unhiding a group', () => {\n        // Step 1: Create an empty board and add a group\n        cy.visit('/')\n        cy.uiCreateEmptyBoard()\n\n        cy.contains('+ Add a group').click({force: true})\n        cy.get('.KanbanColumnHeader .Editable[value=\\'New group\\']').should('exist')\n\n        cy.get('.KanbanColumnHeader .Editable[value=\\'New group\\']').\n            clear().\n            type('Group 1').\n            blur()\n\n        cy.get('.KanbanColumnHeader .Editable[value=\\'Group 1\\']').should('exist')\n\n        // Step 2: Click on the three dots next to \"Group 1\"\n        cy.get('.KanbanColumnHeader').last().within(() => {\n            cy.get('.icon-dots-horizontal').click({force: true})\n            cy.get('.menu-options').should('exist').within(() => {\n                cy.contains('Hide').should('exist')\n                cy.contains('Delete').should('exist')\n\n                // Some colours\n                cy.contains('Brown').should('exist')\n                cy.contains('Gray').should('exist')\n                cy.contains('Orange').should('exist')\n            })\n        })\n\n        // Step 3: Click on \"Hide\"\n        cy.contains('Hide').click({force: true})\n        cy.get('.octo-board-hidden-item').contains('Group 1').should('exist')\n        cy.get('.KanbanColumnHeader .Editable[value=\\'Group 1\\']').should('not.exist')\n\n        // Step 4: Click \"Group 1\", then click \"Show\" in the dropdown\n        cy.contains('Group 1').click({force: true})\n        cy.contains('Show').click({force: true})\n        cy.get('.octo-board-hidden-item').contains('Group 1').should('not.exist')\n        cy.get('.KanbanColumnHeader .Editable[value=\\'Group 1\\']').should('exist')\n    })\n})\n"
  },
  {
    "path": "webapp/cypress/plugins/index.js",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n/// <reference types=\"cypress\" />\n// ***********************************************************\n// This example plugins/index.js can be used to load plugins\n//\n// You can change the location of this file or turn off loading\n// the plugins file with the 'pluginsFile' configuration option.\n//\n// You can read more here:\n// https://on.cypress.io/plugins-guide\n// ***********************************************************\n\n// This function is called when a project is opened or re-opened (e.g. due to\n// the project's config changing)\n\n/**\n * @type {Cypress.PluginConfig}\n */\nmodule.exports = (on, config) => {\n    // `on` is used to hook into various events Cypress emits\n    // `config` is the resolved Cypress config\n    on('task', {\n        failed: require('cypress-failed-log/src/failed')(),\n    });\n};\n\n"
  },
  {
    "path": "webapp/cypress/support/api_commands.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {Board} from '../../src/blocks/board'\nimport {UserConfigPatch} from '../../src/user'\nimport {versionProperty} from '../../src/store/users'\n\nCypress.Commands.add('apiRegisterUser', (data: Cypress.UserData, token?: string, failOnError?: boolean) => {\n    return cy.request({\n        method: 'POST',\n        url: '/api/v2/register',\n        body: {\n            ...data,\n            token,\n        },\n        headers: {\n            'X-Requested-With': 'XMLHttpRequest',\n        },\n        failOnStatusCode: failOnError,\n    })\n})\n\nCypress.Commands.add('apiLoginUser', (data: Cypress.LoginData) => {\n    return cy.request({\n        method: 'POST',\n        url: '/api/v2/login',\n        body: {\n            ...data,\n            type: 'normal',\n        },\n        headers: {\n            'X-Requested-With': 'XMLHttpRequest',\n        },\n    }).then((response) => {\n        expect(response.body).to.have.property('token')\n        localStorage.setItem('focalboardSessionId', response.body.token)\n    })\n})\n\nconst headers = () => ({\n    headers: {\n        'X-Requested-With': 'XMLHttpRequest',\n        Authorization: `Bearer ${localStorage.getItem('focalboardSessionId')}`,\n    },\n})\n\nCypress.Commands.add('apiInitServer', () => {\n    const data: Cypress.UserData = {\n        username: Cypress.env('username'),\n        password: Cypress.env('password'),\n        email: Cypress.env('email'),\n    }\n    return cy.apiRegisterUser(data, '', false).apiLoginUser(data)\n})\n\nCypress.Commands.add('apiDeleteBoard', (id: string) => {\n    return cy.request({\n        method: 'DELETE',\n        url: `/api/v2/boards/${encodeURIComponent(id)}`,\n        ...headers(),\n    })\n})\n\nconst deleteBoards = (ids: string[]) => {\n    if (ids.length === 0) {\n        return\n    }\n    const [id, ...other] = ids\n    cy.apiDeleteBoard(id).then(() => deleteBoards(other))\n}\n\nCypress.Commands.add('apiResetBoards', () => {\n    return cy.request({\n        method: 'GET',\n        url: '/api/v2/teams/0/boards',\n        ...headers(),\n    }).then((response) => {\n        if (Array.isArray(response.body)) {\n            const boards = response.body as Board[]\n            const toDelete = boards.filter((b) => !b.isTemplate).map((b) => b.id)\n            deleteBoards(toDelete)\n        }\n    })\n})\n\nCypress.Commands.add('apiSkipTour', (userID: string) => {\n    const body: UserConfigPatch = {\n        updatedFields: {\n            welcomePageViewed: '1',\n            [versionProperty]: 'true',\n        },\n    }\n\n    return cy.request({\n        method: 'PUT',\n        url: `/api/v2/users/${encodeURIComponent(userID)}/config`,\n        ...headers(),\n        body,\n    })\n})\n\nCypress.Commands.add('apiGetMe', () => {\n    return cy.request({\n        method: 'GET',\n        url: '/api/v2/users/me',\n        ...headers(),\n    }).then((response) => response.body.id)\n})\n\nCypress.Commands.add('apiChangePassword', (userId: string, oldPassword: string, newPassword: string) => {\n    const body = {oldPassword, newPassword}\n    return cy.request({\n        method: 'POST',\n        url: `/api/v2/users/${encodeURIComponent(userId)}/changepassword`,\n        ...headers(),\n        body,\n    })\n})\n\nCypress.Commands.add('uiCreateNewBoard', (title?: string) => {\n    cy.log('**Create new empty board**')\n    cy.uiCreateEmptyBoard()\n\n    cy.findByPlaceholderText('Untitled board').should('exist')\n    cy.wait(10)\n    if (title) {\n        cy.log('**Rename board**')\n        cy.findByPlaceholderText('Untitled board').type(`${title}{enter}`)\n        cy.findByRole('textbox', {name: title}).should('exist')\n    }\n    cy.wait(500)\n})\n\nCypress.Commands.add('uiAddNewGroup', (name?: string) => {\n    cy.log('**Add a new group**')\n    cy.findByRole('button', {name: '+ Add a group'}).click()\n    cy.findByRole('textbox', {name: 'New group'}).should('exist')\n\n    if (name) {\n        cy.log('**Rename group**')\n        cy.findByRole('textbox', {name: 'New group'}).type(`{selectall}${name}{enter}`)\n        cy.findByRole('textbox', {name}).should('exist')\n    }\n    cy.wait(500)\n})\n\nCypress.Commands.add('uiAddNewCard', (title?: string, columnIndex?: number) => {\n    cy.log('**Add a new card**')\n    cy.findByRole('button', {name: '+ New'}).eq(columnIndex || 0).click()\n    cy.findByRole('dialog').should('exist')\n\n    if (title) {\n        cy.log('**Change card title**')\n        cy.findByPlaceholderText('Untitled').type(title)\n    }\n})\n"
  },
  {
    "path": "webapp/cypress/support/index.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport '@testing-library/cypress/add-commands'\n\nimport 'cypress-real-events/support'\n\nimport './api_commands'\nimport './ui_commands'\n\nimport 'cypress-failed-log'\n"
  },
  {
    "path": "webapp/cypress/support/ui_commands.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\n/* eslint-disable cypress/no-unnecessary-waiting */\n\nCypress.Commands.add('uiCreateBoard', (item: string) => {\n    cy.log(`Create new board: ${item}`)\n\n    cy.contains('+ Add board').should('be.visible').click()\n\n    cy.contains(item).click()\n\n    cy.contains('Use this template').click({force: true}).wait(1000)\n})\n\nCypress.Commands.add('uiCreateEmptyBoard', () => {\n    cy.log('Create new empty board')\n\n    cy.contains('+ Add board').should('be.visible').click().wait(500)\n    return cy.contains('Create an empty board').click({force: true}).wait(1000)\n})\n"
  },
  {
    "path": "webapp/cypress/tsconfig.json",
    "content": "{\n    \"extends\": \"../tsconfig.json\",\n    \"compilerOptions\": {\n        \"noEmit\": true,\n        \"types\": [\n            \"cypress\",\n            \"cypress-real-events\",\n            \"@testing-library/cypress\"\n        ]\n    },\n    \"include\": [\n        \"**/*.ts\"\n    ]\n}\n"
  },
  {
    "path": "webapp/cypress.json",
    "content": "{\n    \"chromeWebSecurity\": false,\n    \"baseUrl\": \"http://localhost:8088\",\n    \"testFiles\": [\n        \"**/login*.ts\",\n        \"**/create*.ts\",\n        \"**/manage*.ts\",\n        \"**/group*.ts\",\n        \"**/card*.ts\"\n    ],\n    \"env\": {\n        \"username\": \"test-user\",\n        \"password\": \"test-password\",\n        \"email\": \"test@mail.com\"\n    },\n    \"video\": false,\n    \"viewportWidth\": 1600,\n    \"viewportHeight\": 1200\n}\n"
  },
  {
    "path": "webapp/html-templates/deveditor.ejs",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n\t<meta charset=\"UTF-8\">\n    <meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0'>\n\n    <title><%= htmlWebpackPlugin.options.title %></title>\n\n    <link rel=\"icon\" href=\"/static/favicon.svg?v=1\" />\n</head>\n\n<body class=\"focalboard-body\">\n\t<div id=\"focalboard-app\">\n\t</div>\n\n\t<div id=\"focalboard-root-portal\">\n\t</div>\n</body>\n\n</html>\n"
  },
  {
    "path": "webapp/html-templates/page.ejs",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n\t<meta charset=\"UTF-8\">\n    <meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0'>\n\n    <title><%= htmlWebpackPlugin.options.title %></title>\n\n    <script>window.baseURL = '{{.BaseURL}}'</script>\n    <link rel=\"icon\" href=\"{{.BaseURL}}/static/favicon.svg?v=1\" />\n</head>\n\n<body class=\"focalboard-body\">\n\t<div id=\"focalboard-app\">\n\t</div>\n\n\t<div id=\"focalboard-root-portal\">\n\t</div>\n</body>\n\n</html>\n"
  },
  {
    "path": "webapp/i18n/ar.json",
    "content": "{\n  \"AppBar.Tooltip\": \"اخيار الالواح المرتبطة\",\n  \"Attachment.Attachment-title\": \"المرفق\",\n  \"AttachmentBlock.DeleteAction\": \"حذف\",\n  \"AttachmentBlock.addElement\": \"اضافة {type}\",\n  \"AttachmentBlock.delete\": \"تم حذف المرفق.\",\n  \"AttachmentBlock.failed\": \"تعذر تحميل هذا الملف حيث تم الوصول إلى الحد الأقصى لحجم الملفات.\",\n  \"AttachmentBlock.upload\": \"يتم الآن تحميل المرفق.\",\n  \"AttachmentBlock.uploadSuccess\": \"تم تحميل المرفق.\",\n  \"AttachmentElement.delete-confirmation-dialog-button-text\": \"حذف\",\n  \"AttachmentElement.download\": \"تحميل\",\n  \"AttachmentElement.upload-percentage\": \"جاري التحمي... ({uploadPercent}%)\",\n  \"BoardComponent.add-a-group\": \"+ إضافة مجموعة\",\n  \"BoardComponent.delete\": \"حذف\",\n  \"BoardComponent.hidden-columns\": \"الأعمدة المخفية\",\n  \"BoardComponent.hide\": \"إخفاء\",\n  \"BoardComponent.new\": \"+ جديد\",\n  \"BoardComponent.no-property\": \"لا يوجد {property}\",\n  \"BoardComponent.no-property-title\": \"يستخدم هذا العمود للعناصر بلا خواص{property} مبينة . و لا يمكن ازالته.\",\n  \"BoardComponent.show\": \"عرض\",\n  \"BoardMember.schemeAdmin\": \"المسؤول\",\n  \"BoardMember.schemeCommenter\": \"المعلق\",\n  \"BoardMember.schemeEditor\": \"محرر\",\n  \"BoardMember.schemeNone\": \"لا شيء\",\n  \"BoardMember.schemeViewer\": \"مشاهد\",\n  \"BoardMember.unlinkChannel\": \"إلغاء الربط\",\n  \"BoardPage.newVersion\": \"يتوفر نسخة جديدة من الألواح. اضغط هنا للتحميل.\",\n  \"BoardPage.syncFailed\": \"تعذر الوصول إلي اللوح , ربما يكون محذوفا .\",\n  \"BoardTemplateSelector.add-template\": \"قالب جديد\",\n  \"BoardTemplateSelector.create-empty-board\": \"أنشيء لوح فارغ\",\n  \"BoardTemplateSelector.delete-template\": \"حذف\",\n  \"BoardTemplateSelector.description\": \"اضافة لوح جديد للقائمة الجانبية يمكنك استخدام اي قالب من القوالب المتاحة او انشا ء لوح جديد.\",\n  \"BoardTemplateSelector.edit-template\": \"تحرير\",\n  \"BoardTemplateSelector.plugin.no-content-description\": \"اضافة لوح جديد للقائمة الجانبية يمكنك استخدام اي قالب من القوالب المتاحة او انشا ء لوح جديد.\",\n  \"BoardTemplateSelector.plugin.no-content-title\": \"لوح جديد\",\n  \"BoardTemplateSelector.title\": \"لوح جديد\",\n  \"BoardTemplateSelector.use-this-template\": \"استخدم هذا القالب\",\n  \"BoardsSwitcher.Title\": \"بحث في الالواح\",\n  \"BoardsUnfurl.Limited\": \"تعزر اظهار بيانات اخرى بسبب ارشفة الكارت\",\n  \"BoardsUnfurl.Remainder\": \"{remainder}+ المزيد\",\n  \"BoardsUnfurl.Updated\": \"تم التحديث {time}\",\n  \"Calculations.Options.average.displayName\": \"متوسط\",\n  \"Calculations.Options.average.label\": \"متوسط\",\n  \"Calculations.Options.count.displayName\": \"الاجمالي\",\n  \"Calculations.Options.count.label\": \"الاجمالي\",\n  \"Calculations.Options.countChecked.displayName\": \"تم التدقيق\",\n  \"Calculations.Options.countChecked.label\": \"اجمالي ما تم تدقيقه\",\n  \"Calculations.Options.countUnchecked.displayName\": \"لم يتم تدقيقه\",\n  \"Calculations.Options.countUnchecked.label\": \"اجمالي ما تم تدقيقه\",\n  \"Calculations.Options.countUniqueValue.displayName\": \"فريد\",\n  \"Calculations.Options.countUniqueValue.label\": \"اجمالي القيم الفريدة\",\n  \"Calculations.Options.countValue.displayName\": \"القيم\",\n  \"Calculations.Options.countValue.label\": \"اجمالي القيم\",\n  \"Calculations.Options.dateRange.displayName\": \"النطاق الزمني\",\n  \"Calculations.Options.dateRange.label\": \"النطاق الزمني\",\n  \"Calculations.Options.earliest.displayName\": \"الاقدم\",\n  \"Calculations.Options.earliest.label\": \"الاقدم\",\n  \"Calculations.Options.latest.displayName\": \"الأحدث\",\n  \"Calculations.Options.latest.label\": \"الأحدث\",\n  \"Calculations.Options.max.displayName\": \"الاقصى\",\n  \"Calculations.Options.max.label\": \"الاقصى\",\n  \"Calculations.Options.median.displayName\": \"المتوسط\",\n  \"Calculations.Options.median.label\": \"المتوسط\",\n  \"Calculations.Options.min.displayName\": \"الأدنى\",\n  \"Calculations.Options.min.label\": \"الأدنى\",\n  \"Calculations.Options.none.displayName\": \"احسب\",\n  \"Calculations.Options.none.label\": \"لا شيء\",\n  \"Calculations.Options.percentChecked.displayName\": \"تم التدقيق\",\n  \"Calculations.Options.percentChecked.label\": \"نسبة ما تم تدقيقه\",\n  \"Calculations.Options.percentUnchecked.displayName\": \"لم يتم تدقيقه\",\n  \"Calculations.Options.percentUnchecked.label\": \"سبة ما لم يتم تدقيقه\",\n  \"Calculations.Options.range.displayName\": \"النطاق الزمني\",\n  \"Calculations.Options.range.label\": \"النطاق الزمني\",\n  \"Calculations.Options.sum.displayName\": \"مجموع\",\n  \"Calculations.Options.sum.label\": \"مجموع\",\n  \"CalendarCard.untitled\": \"بدون اسم\",\n  \"CardActionsMenu.copiedLink\": \"تم النسخ!\",\n  \"CardActionsMenu.copyLink\": \"نسخ الرابط\",\n  \"CardActionsMenu.delete\": \"حذف\",\n  \"CardActionsMenu.duplicate\": \"تكرار\",\n  \"CardBadges.title-checkboxes\": \"تم اخيتاره\",\n  \"CardBadges.title-comments\": \"التعليقات\",\n  \"CardBadges.title-description\": \"البطاقة لها تفاصيل\",\n  \"CardDetail.Follow\": \"يتبع\",\n  \"CardDetail.Following\": \"التالية\",\n  \"CardDetail.add-content\": \"إضافة محتوى\",\n  \"CardDetail.add-icon\": \"إضافة أيقونة\",\n  \"CardDetail.add-property\": \"اضافة خاصية\",\n  \"CardDetail.addCardText\": \"اضافة محوى البطاقة\",\n  \"CardDetail.limited-body\": \"تحتاج رخصة Professional او Enterprise لعرض البطاقات المؤرشفة , حدث الآن و تمتع بعدد غير محدود من الالواح و البطاقات و المزيد.\",\n  \"CardDetail.limited-button\": \"الترقية\",\n  \"CardDetail.limited-title\": \"هذه البطاقة مخفية\",\n  \"CardDetail.moveContent\": \"نقل بيانات البطاقة\",\n  \"CardDetail.new-comment-placeholder\": \"إضافة تعليق…\",\n  \"CardDetailProperty.confirm-delete-heading\": \"سيتم حذف هذه الخاصية\",\n  \"CardDetailProperty.confirm-delete-subtext\": \"هل تريد حذف هذه الخاصية {propertyName}؟ حذف سيتم علي جميع البطاقات في هذا اللوح.\",\n  \"CardDetailProperty.confirm-property-name-change-subtext\": \"هل تريد تعديل هذه الخاصية \\\"{propertyName}\\\" {customText}؟ حذف سيتم علي {numOfCards} من البطاقات في هذا اللوح, و قد يتسبب في خسارة بعض البيانات.\",\n  \"CardDetailProperty.confirm-property-type-change\": \"سيتم تعديل نوع الخاصية\",\n  \"CardDetailProperty.delete-action-button\": \"حذف\",\n  \"CardDetailProperty.property-change-action-button\": \"تغيير نوع الخاصية\",\n  \"CardDetailProperty.property-changed\": \"تم تعديل الخاصية بنجاح!\",\n  \"CardDetailProperty.property-deleted\": \"تم حذف {propertyName} بنجاح!\",\n  \"CardDetailProperty.property-name-change-subtext\": \"النوع من \\\"{oldPropType}\\\" إلى \\\"{newPropType}\\\"\",\n  \"CardDetial.limited-link\": \"لمعرفة المزيد حول اسعارنا.\",\n  \"CardDialog.delete-confirmation-dialog-button-text\": \"حذف\",\n  \"CardDialog.delete-confirmation-dialog-heading\": \"سيتم حذف البطاقة!\",\n  \"CardDialog.editing-template\": \"انت الآن تقوم بتعديل قالب.\",\n  \"CardDialog.nocard\": \"تعذر الوصول للبطاقة او بطاقة غير موجودة.\",\n  \"Categories.CreateCategoryDialog.CancelText\": \"إلغاء\",\n  \"Categories.CreateCategoryDialog.CreateText\": \"إنشاء\",\n  \"Categories.CreateCategoryDialog.Placeholder\": \"عرف فئتك\",\n  \"Categories.CreateCategoryDialog.UpdateText\": \"تحديث\",\n  \"CenterPanel.Login\": \"تسجيل الدخول\",\n  \"CenterPanel.Share\": \"مشاركة\",\n  \"ColorOption.selectColor\": \"اختر {color} اللون\",\n  \"Comment.delete\": \"حذف\",\n  \"CommentsList.send\": \"إرسال\",\n  \"ConfirmationDialog.cancel-action\": \"إلغاء\",\n  \"ConfirmationDialog.confirm-action\": \"تأكيد\",\n  \"ContentBlock.Delete\": \"حذف\",\n  \"ContentBlock.DeleteAction\": \"حذف\",\n  \"ContentBlock.addElement\": \"إضافة {type}\",\n  \"ContentBlock.checkbox\": \"اختيار\",\n  \"ContentBlock.divider\": \"فاصل\",\n  \"ContentBlock.editCardCheckbox\": \"مختار\",\n  \"ContentBlock.editCardCheckboxText\": \"تحرير محوى البطاقة\",\n  \"ContentBlock.editCardText\": \"تحرير محوى البطاقة\",\n  \"ContentBlock.editText\": \"تحرير المحتوى...\",\n  \"ContentBlock.image\": \"صورة\",\n  \"ContentBlock.insertAbove\": \"إدراج بالأعلى\",\n  \"ContentBlock.moveDown\": \"ازاحة للأسفل\",\n  \"ContentBlock.moveUp\": \"إزاحة للأعلى\",\n  \"ContentBlock.text\": \"نص\",\n  \"DateRange.clear\": \"مسح\",\n  \"DateRange.empty\": \"فارغ\",\n  \"DateRange.endDate\": \"تاريخ الإنتهاء\",\n  \"DateRange.today\": \"اليوم\",\n  \"DeleteBoardDialog.confirm-cancel\": \"إلغاء\",\n  \"DeleteBoardDialog.confirm-delete\": \"حذف\",\n  \"DeleteBoardDialog.confirm-info\": \"هل تريد حذف اللوح \\\"{boardTitle}\\\"؟ سيتم حذف جميع البطاقات داخل اللوح.\",\n  \"DeleteBoardDialog.confirm-info-template\": \"هل تريد حذف قالب اللوح \\\"{boardTitle}\\\"؟\",\n  \"DeleteBoardDialog.confirm-tite\": \"نعم احذف اللوح\",\n  \"DeleteBoardDialog.confirm-tite-template\": \"نعم احذف قالب اللوح\",\n  \"Dialog.closeDialog\": \"اغلق\",\n  \"EditableDayPicker.today\": \"اليوم\",\n  \"Error.websocket-closed\": \"مشكلة في الاتصال بالخادم, برجاء التأكد من الشبكة . أو تأكد من إعدادات الخادم او الوكيل.\",\n  \"Filter.contains\": \"يحتوي على\",\n  \"Filter.ends-with\": \"ينتهي بـ\",\n  \"Filter.includes\": \"يحتوي على\",\n  \"Filter.is\": \"يطابق\",\n  \"Filter.is-empty\": \"فارغ\",\n  \"Filter.is-not-empty\": \"ليس فارغًا\",\n  \"Filter.is-not-set\": \"بدون تحديد\",\n  \"Filter.is-set\": \"معين\",\n  \"Filter.not-contains\": \"لا يحتوى على\",\n  \"Filter.not-ends-with\": \"لا ينتهى بـ\",\n  \"Filter.not-includes\": \"لا تشمل\",\n  \"Filter.not-starts-with\": \"لا تبدأ بـ\",\n  \"Filter.starts-with\": \"تبدأ بـ\",\n  \"FilterByText.placeholder\": \"فرز\",\n  \"FilterComponent.add-filter\": \"+ إضافة فلتر\",\n  \"FilterComponent.delete\": \"حذف\",\n  \"FindBoardsDialog.IntroText\": \"البحث في الألواح\",\n  \"FindBoardsDialog.NoResultsFor\": \"لا يوجد نتيجة للبحث \\\"{searchQuery}\\\"\",\n  \"FindBoardsDialog.NoResultsSubtext\": \"اختر بحث آخر أو تأكد من الأخطاء الإملائية.\",\n  \"FindBoardsDialog.Title\": \"البحث عن ألواح\",\n  \"GroupBy.ungroup\": \"إلغاء التجميع\",\n  \"KanbanCard.untitled\": \"بدون عنوان\",\n  \"Mutator.new-card-from-template\": \"بطاقة جديدة من نموذج\",\n  \"Mutator.new-template-from-card\": \"نموذج جديد من بطاقة\",\n  \"OnboardingTour.AddComments.Title\": \"إضافة تعليقات\",\n  \"OnboardingTour.AddDescription.Title\": \"اضافة وصف\",\n  \"OnboardingTour.AddProperties.Title\": \"إضافة خواص\",\n  \"OnboardingTour.AddView.Body\": \"انتقل هنا لإنشاء عرض جديد لتنظيم لوحتك باستخدام تخطيطات مختلفة.\",\n  \"OnboardingTour.AddView.Title\": \"إضافة عرض جديد\",\n  \"OnboardingTour.CopyLink.Title\": \"نسخ الرابط\",\n  \"PropertyMenu.Delete\": \"حذف\",\n  \"PropertyMenu.changeType\": \"تغيير نوع الخاصية\",\n  \"PropertyMenu.selectType\": \"اختيار نوع الخاصية\",\n  \"PropertyMenu.typeTitle\": \"النوع\",\n  \"PropertyType.CreatedBy\": \"تم الإنشاء بواسطة\",\n  \"PropertyType.Date\": \"التاريخ\",\n  \"PropertyType.Email\": \"البريد الإلكتروني\",\n  \"PropertyType.Number\": \"رقم\",\n  \"PropertyType.Person\": \"شخص\",\n  \"PropertyType.Text\": \"نص\",\n  \"RegistrationLink.copiedLink\": \"منسوخ!\",\n  \"RegistrationLink.copyLink\": \"انسخ الرابط\",\n  \"ShareBoard.copiedLink\": \"منسوخ!\",\n  \"ShareBoard.copyLink\": \"انسخ الرابط\",\n  \"Sidebar.about\": \"عن Focalboard\",\n  \"Sidebar.changePassword\": \"تغيير الكلمة السرية\",\n  \"Sidebar.invite-users\": \"دعوة المستخدمين\",\n  \"Sidebar.logout\": \"الخروج\",\n  \"Sidebar.set-language\": \"ضبط اللغة\",\n  \"Sidebar.settings\": \"الإعدادت\",\n  \"TableComponent.add-icon\": \"إضافة أيقونة\",\n  \"TableComponent.name\": \"الإسم\",\n  \"TableComponent.plus-new\": \"+ جديد\",\n  \"TableHeaderMenu.delete\": \"حذف\",\n  \"TableHeaderMenu.hide\": \"إخفاء\",\n  \"TableRow.open\": \"افتح\",\n  \"View.Table\": \"جدول\",\n  \"ViewHeader.add-template\": \"نموذج جديد\",\n  \"ViewHeader.delete-template\": \"حذف\",\n  \"ViewHeader.new\": \"جديز\",\n  \"ViewTitle.pick-icon\": \"اختر أيقونة\",\n  \"default-properties.badges\": \"التعليقات و التفاصيل\",\n  \"default-properties.title\": \"العنوان\",\n  \"error.back-to-home\": \"الرجوع للواجهة الأساسية\",\n  \"error.back-to-team\": \"الرجوع إلي الفريق\",\n  \"error.board-not-found\": \"لوح غير موجود.\",\n  \"error.go-login\": \"تسجيل الدخول\",\n  \"login.log-in-button\": \"لِج\",\n  \"login.log-in-title\": \"لِج\"\n}\n"
  },
  {
    "path": "webapp/i18n/ars.json",
    "content": "{}\n"
  },
  {
    "path": "webapp/i18n/ca.json",
    "content": "{\n  \"Attachment.Attachment-title\": \"Adjunt\",\n  \"AttachmentBlock.DeleteAction\": \"esborra\",\n  \"AttachmentBlock.addElement\": \"afegir {type}\",\n  \"AttachmentBlock.delete\": \"Adjunt esborrat.\",\n  \"AttachmentBlock.failed\": \"Aquest fitxer no pot ser afegit ja que el límit de tamany de fitxer ha estat assolit.\",\n  \"AttachmentBlock.upload\": \"Adjunt afegint-se.\",\n  \"AttachmentBlock.uploadSuccess\": \"Adjunt afegit.\",\n  \"AttachmentElement.delete-confirmation-dialog-button-text\": \"Esborra\",\n  \"AttachmentElement.download\": \"Descarrega\",\n  \"AttachmentElement.upload-percentage\": \"Afegint...({uploadPercent}%)\",\n  \"BoardComponent.add-a-group\": \"+ Afegir un grup\",\n  \"BoardComponent.delete\": \"Eliminar\",\n  \"BoardComponent.hidden-columns\": \"Columnes ocultes\",\n  \"BoardComponent.hide\": \"Amagar\",\n  \"BoardComponent.new\": \"+ Nou\",\n  \"BoardComponent.no-property\": \"Sense {property}\",\n  \"BoardComponent.no-property-title\": \"Els elements amb una propietat {property} buida anirán aquí. Aquesta col·lumna no pot eliminar-se.\",\n  \"BoardComponent.show\": \"Mostrar\",\n  \"BoardMember.schemeAdmin\": \"Admin\",\n  \"BoardMember.schemeCommenter\": \"Comentarista\",\n  \"BoardMember.schemeEditor\": \"Editor\",\n  \"BoardMember.schemeNone\": \"Cap\",\n  \"BoardMember.schemeViewer\": \"Visualitzador\",\n  \"BoardPage.newVersion\": \"Una nova versió de Boards és disponible, clica aquí per recarregar.\",\n  \"BoardPage.syncFailed\": \"El tauler podria ser eliminat o revocat l'accés.\",\n  \"BoardTemplateSelector.add-template\": \"Crea una nova plantilla\",\n  \"BoardTemplateSelector.create-empty-board\": \"Crea un taulell buit\",\n  \"BoardTemplateSelector.delete-template\": \"Esborra\",\n  \"BoardTemplateSelector.description\": \"Afegeix el taulell a la barra lateral usant alguna de les plantilles definides a sota o comença des de zero.\",\n  \"BoardTemplateSelector.edit-template\": \"Edita\",\n  \"BoardTemplateSelector.plugin.no-content-description\": \"Afegeix el taulell a la barra lateral usant alguna de les plantilles definides a sota o comença des de zero.\",\n  \"BoardTemplateSelector.plugin.no-content-title\": \"Crea un taulell\",\n  \"BoardTemplateSelector.title\": \"Crea un taulell\",\n  \"BoardTemplateSelector.use-this-template\": \"Utilitza aquesta plantilla\",\n  \"BoardsSwitcher.Title\": \"Busca taulells\",\n  \"BoardsUnfurl.Updated\": \"Actualitzat {time}\",\n  \"Calculations.Options.average.displayName\": \"Promig\",\n  \"Calculations.Options.average.label\": \"Promig\",\n  \"Calculations.Options.countChecked.displayName\": \"Comprovat\",\n  \"Calculations.Options.countUniqueValue.displayName\": \"Únic\",\n  \"Calculations.Options.countUniqueValue.label\": \"Compta valors únics\",\n  \"Calculations.Options.countValue.displayName\": \"Valors\",\n  \"Calculations.Options.dateRange.displayName\": \"Rang\",\n  \"Calculations.Options.dateRange.label\": \"Rang\",\n  \"Calculations.Options.earliest.displayName\": \"Proper\",\n  \"Calculations.Options.earliest.label\": \"Proper\",\n  \"Calculations.Options.latest.displayName\": \"Últim\",\n  \"Calculations.Options.latest.label\": \"Últim\",\n  \"Calculations.Options.max.displayName\": \"Màxim\",\n  \"Calculations.Options.max.label\": \"Màxim\",\n  \"Calculations.Options.min.displayName\": \"Mínim\",\n  \"Calculations.Options.min.label\": \"Mínim\",\n  \"Calculations.Options.none.displayName\": \"Calcula\",\n  \"Calculations.Options.none.label\": \"Cap\",\n  \"Calculations.Options.percentChecked.displayName\": \"Completat\",\n  \"Calculations.Options.percentChecked.label\": \"Percentatge completat\",\n  \"Calculations.Options.percentUnchecked.displayName\": \"No finalitzat\",\n  \"Calculations.Options.percentUnchecked.label\": \"Percentatge no finalitzat\",\n  \"Calculations.Options.range.displayName\": \"Rang\",\n  \"Calculations.Options.range.label\": \"Rang\",\n  \"Calculations.Options.sum.displayName\": \"Suma\",\n  \"Calculations.Options.sum.label\": \"Suma\",\n  \"CalendarCard.untitled\": \"Sense títol\",\n  \"CardActionsMenu.copiedLink\": \"Copiat!\",\n  \"CardActionsMenu.copyLink\": \"Còpia l'enllaç\",\n  \"CardActionsMenu.delete\": \"Esborra\",\n  \"CardActionsMenu.duplicate\": \"Duplica\",\n  \"CardBadges.title-comments\": \"Comentaris\",\n  \"CardBadges.title-description\": \"Aquesta tarjeta té una descripció\",\n  \"CardDetail.Attach\": \"Adjunta\",\n  \"CardDetail.Follow\": \"Segueix\",\n  \"CardDetail.Following\": \"Segueix\",\n  \"CardDetail.add-content\": \"Afegeix contingut\",\n  \"CardDetail.add-icon\": \"Afegeix icona\",\n  \"CardDetail.add-property\": \"+ Afegeix propietat\",\n  \"CardDetail.addCardText\": \"afegeix text a la targeta\",\n  \"CardDetail.moveContent\": \"Mou el contingut de la targeta\",\n  \"CardDetail.new-comment-placeholder\": \"Afegeix un comentari...\",\n  \"CardDialog.editing-template\": \"Estas editant una plantilla.\",\n  \"CardDialog.nocard\": \"Aquesta targeta no existeix o és innaccesible.\",\n  \"Comment.delete\": \"Eliminar\",\n  \"CommentsList.send\": \"Enviar\",\n  \"ContentBlock.Delete\": \"Eliminar\",\n  \"ContentBlock.DeleteAction\": \"eliminar\",\n  \"ContentBlock.addElement\": \"afegeix {type}\",\n  \"ContentBlock.checkbox\": \"casella de verificació\",\n  \"ContentBlock.divider\": \"divisor\",\n  \"ContentBlock.editCardCheckbox\": \"casella de verificació conmutada\",\n  \"ContentBlock.editCardCheckboxText\": \"edita el contigut de la targeta\",\n  \"ContentBlock.editCardText\": \"edita el text de la targeta\",\n  \"ContentBlock.editText\": \"Edita el text...\",\n  \"ContentBlock.image\": \"imatge\",\n  \"ContentBlock.insertAbove\": \"Insereix damunt\",\n  \"ContentBlock.moveDown\": \"Mou abaix\",\n  \"ContentBlock.moveUp\": \"Mou adalt\",\n  \"ContentBlock.text\": \"text\",\n  \"Dialog.closeDialog\": \"Tanca la finestra\",\n  \"EditableDayPicker.today\": \"Avui\",\n  \"Filter.includes\": \"inclou\",\n  \"Filter.is-empty\": \"esta buit\",\n  \"Filter.is-not-empty\": \"no està buit\",\n  \"Filter.not-includes\": \"no inclou\",\n  \"FilterComponent.add-filter\": \"+ Afegeix filtre\",\n  \"FilterComponent.delete\": \"Eliminar\",\n  \"GroupBy.ungroup\": \"Desagrupar\",\n  \"KanbanCard.untitled\": \"Sense títol\",\n  \"Mutator.new-card-from-template\": \"nova targeta des de plantilla\",\n  \"Mutator.new-template-from-card\": \"nova plantilla des de targeta\",\n  \"PropertyMenu.Delete\": \"Eliminar\",\n  \"PropertyMenu.changeType\": \"Canviar el tipus de propietat\",\n  \"PropertyMenu.typeTitle\": \"Tipus\",\n  \"PropertyType.Checkbox\": \"casella de verificació\",\n  \"PropertyType.CreatedBy\": \"Creada per\",\n  \"PropertyType.CreatedTime\": \"Moment de creació\",\n  \"PropertyType.Date\": \"Data\",\n  \"PropertyType.Email\": \"Correu electrònic\",\n  \"PropertyType.MultiSelect\": \"Selecció múltiple\",\n  \"PropertyType.Number\": \"Número\",\n  \"PropertyType.Person\": \"Persona\",\n  \"PropertyType.Phone\": \"Telèfon\",\n  \"PropertyType.Select\": \"Selecciona\",\n  \"PropertyType.Text\": \"Text\",\n  \"PropertyType.UpdatedBy\": \"Última actualització feta per\",\n  \"PropertyType.UpdatedTime\": \"Moment d'actualització\",\n  \"RegistrationLink.confirmRegenerateToken\": \"Això invalidarà enllaços compartits anteriorment. Continuar?\",\n  \"RegistrationLink.copiedLink\": \"Copiat!\",\n  \"RegistrationLink.copyLink\": \"Copiar enllaç\",\n  \"RegistrationLink.description\": \"Comparteix aquest enllaç per crear comptes per altres:\",\n  \"RegistrationLink.regenerateToken\": \"Regenerar token\",\n  \"RegistrationLink.tokenRegenerated\": \"Enllaç de registre regenerat\",\n  \"ShareBoard.confirmRegenerateToken\": \"Això invalidarà enllaços compartits anteriorment. Continuar?\",\n  \"ShareBoard.copiedLink\": \"Copiat!\",\n  \"ShareBoard.copyLink\": \"Copiar enllaç\",\n  \"ShareBoard.tokenRegenrated\": \"Token regenerat\",\n  \"Sidebar.about\": \"Sobre Focalboard\",\n  \"Sidebar.add-board\": \"+ Afegir tauler\",\n  \"Sidebar.changePassword\": \"Canvi de contrasenya\",\n  \"Sidebar.delete-board\": \"Eliminar el tauler\",\n  \"Sidebar.export-archive\": \"Arxiu d'exportació\",\n  \"Sidebar.import-archive\": \"Arxiu d'importació\",\n  \"Sidebar.invite-users\": \"Convida usuaris\",\n  \"Sidebar.logout\": \"Tanca sessió\",\n  \"Sidebar.random-icons\": \"Icones aleatòries\",\n  \"Sidebar.set-language\": \"Seleccionar idioma\",\n  \"Sidebar.set-theme\": \"Definir un tema\",\n  \"Sidebar.settings\": \"Paràmetres\",\n  \"Sidebar.untitled-board\": \"(Tauler sense títol)\",\n  \"TableComponent.add-icon\": \"Afegeix icona\",\n  \"TableComponent.name\": \"Nom\",\n  \"TableComponent.plus-new\": \"+ Nou\",\n  \"TableHeaderMenu.delete\": \"Eliminar\",\n  \"TableHeaderMenu.duplicate\": \"Duplicar\",\n  \"TableHeaderMenu.hide\": \"Amagar\",\n  \"TableHeaderMenu.insert-left\": \"Insereix a l'esquerra\",\n  \"TableHeaderMenu.insert-right\": \"Insereix a la dreta\",\n  \"TableHeaderMenu.sort-ascending\": \"Ordena ascendent\",\n  \"TableHeaderMenu.sort-descending\": \"Ordena descendent\",\n  \"TableRow.open\": \"Obrir\",\n  \"View.AddView\": \"Afegeix vista\",\n  \"View.Board\": \"Tauler\",\n  \"View.DeleteView\": \"Eliminar vista\",\n  \"View.DuplicateView\": \"Duplicar vista\",\n  \"View.NewBoardTitle\": \"Vista de tauler\",\n  \"View.NewCalendarTitle\": \"Vista de calendari\",\n  \"View.NewGalleryTitle\": \"Vista de galeria\",\n  \"View.NewTableTitle\": \"Vista de tauler\",\n  \"View.Table\": \"Taula\",\n  \"ViewHeader.add-template\": \"Nova plantilla\",\n  \"ViewHeader.delete-template\": \"Eliminar\",\n  \"ViewHeader.edit-template\": \"Editar\",\n  \"ViewHeader.empty-card\": \"Targeta buida\",\n  \"ViewHeader.export-complete\": \"Exportació completada!\",\n  \"ViewHeader.export-csv\": \"Exportació a CSV\",\n  \"ViewHeader.export-failed\": \"L'exportació ha fallat!\",\n  \"ViewHeader.filter\": \"Filtrar\",\n  \"ViewHeader.group-by\": \"Agrupar per: {property}\",\n  \"ViewHeader.new\": \"Nou\",\n  \"ViewHeader.properties\": \"Propietats\",\n  \"ViewHeader.search-text\": \"Cerca tarjetes\",\n  \"ViewHeader.select-a-template\": \"Selecciona una plantilla\",\n  \"ViewHeader.sort\": \"Ordenar\",\n  \"ViewHeader.untitled\": \"Sense títol\",\n  \"ViewTitle.hide-description\": \"amagar descripció\",\n  \"ViewTitle.pick-icon\": \"Seleccionar icona\",\n  \"ViewTitle.random-icon\": \"Aleatori\",\n  \"ViewTitle.remove-icon\": \"Eliminar icona\",\n  \"ViewTitle.show-description\": \"mostra la descripció\",\n  \"ViewTitle.untitled-board\": \"Tauler sense títol\",\n  \"Workspace.editing-board-template\": \"Estàs editant una plantilla de tauler.\",\n  \"default-properties.title\": \"Títol\",\n  \"login.log-in-button\": \"Inicia sessió\",\n  \"login.log-in-title\": \"Inicia sessió\",\n  \"login.register-button\": \"o crea un compte si no en tens\",\n  \"register.login-button\": \"o inicia sessió si ja tens un compte\",\n  \"register.signup-title\": \"Registrat un compte\"\n}\n"
  },
  {
    "path": "webapp/i18n/de.json",
    "content": "{\n  \"AdminBadge.SystemAdmin\": \"Administrator\",\n  \"AdminBadge.TeamAdmin\": \"Teamadministrator\",\n  \"AppBar.Tooltip\": \"Verknüpfte Boards umschalten\",\n  \"Attachment.Attachment-title\": \"Anhang\",\n  \"AttachmentBlock.DeleteAction\": \"Löschen\",\n  \"AttachmentBlock.addElement\": \"{type} hinzufügen\",\n  \"AttachmentBlock.delete\": \"Anhang gelöscht.\",\n  \"AttachmentBlock.failed\": \"Kann Datei nicht hochladen, da das Limit für Dateigröße erreicht ist.\",\n  \"AttachmentBlock.upload\": \"Anhang wird hochgeladen.\",\n  \"AttachmentBlock.uploadSuccess\": \"Anhang hochgeladen.\",\n  \"AttachmentElement.delete-confirmation-dialog-button-text\": \"Löschen\",\n  \"AttachmentElement.download\": \"Herunterladen\",\n  \"AttachmentElement.upload-percentage\": \"Hochladen...({uploadPercent}%)\",\n  \"BoardComponent.add-a-group\": \"+ Hinzufügen einer Gruppe\",\n  \"BoardComponent.delete\": \"Löschen\",\n  \"BoardComponent.hidden-columns\": \"Versteckte Spalten\",\n  \"BoardComponent.hide\": \"Ausblenden\",\n  \"BoardComponent.new\": \"+ Neu\",\n  \"BoardComponent.no-property\": \"Keine {property}\",\n  \"BoardComponent.no-property-title\": \"Elemente mit einer leeren {property} Eigenschaft erscheinen hier. Diese Spalte kann nicht entfernt werden.\",\n  \"BoardComponent.show\": \"Anzeigen\",\n  \"BoardMember.schemeAdmin\": \"Administrator\",\n  \"BoardMember.schemeCommenter\": \"Kommentator\",\n  \"BoardMember.schemeEditor\": \"Bearbeiter\",\n  \"BoardMember.schemeNone\": \"Keine\",\n  \"BoardMember.schemeViewer\": \"Leser\",\n  \"BoardMember.unlinkChannel\": \"Verknüpfung aufheben\",\n  \"BoardPage.newVersion\": \"Eine neue Version von Boards ist verfügbar, klicke hier, um neu zu laden.\",\n  \"BoardPage.syncFailed\": \"Das Board kann gelöscht oder der Zugang entzogen werden.\",\n  \"BoardTemplateSelector.add-template\": \"Neue Vorlage erstellen\",\n  \"BoardTemplateSelector.create-empty-board\": \"Leeres Board erstellen\",\n  \"BoardTemplateSelector.delete-template\": \"Löschen\",\n  \"BoardTemplateSelector.description\": \"Füge ein Board hinzu, indem du eine der unten definierten Vorlagen verwendest oder ganz neu beginnst.\",\n  \"BoardTemplateSelector.edit-template\": \"Bearbeiten\",\n  \"BoardTemplateSelector.plugin.no-content-description\": \"Füge ein Board zur Seitenleiste hinzu, indem du eine der Vorlagen unten verwendest oder starte mit einem leeren Board.\",\n  \"BoardTemplateSelector.plugin.no-content-title\": \"Erstelle ein Board\",\n  \"BoardTemplateSelector.title\": \"Erstelle ein Board\",\n  \"BoardTemplateSelector.use-this-template\": \"Verwende diese Vorlage\",\n  \"BoardsSwitcher.Title\": \"Finde Boards\",\n  \"BoardsUnfurl.Limited\": \"Weitere Details sind versteckt, da die Karte archiviert wurde\",\n  \"BoardsUnfurl.Remainder\": \"+{remainder} mehr\",\n  \"BoardsUnfurl.Updated\": \"Aktualisiert {time}\",\n  \"Calculations.Options.average.displayName\": \"Durchschnitt\",\n  \"Calculations.Options.average.label\": \"Durchschnitt\",\n  \"Calculations.Options.count.displayName\": \"Zählen\",\n  \"Calculations.Options.count.label\": \"Zählen\",\n  \"Calculations.Options.countChecked.displayName\": \"Geprüft\",\n  \"Calculations.Options.countChecked.label\": \"Zähle Markierte\",\n  \"Calculations.Options.countUnchecked.displayName\": \"Ungeprüft\",\n  \"Calculations.Options.countUnchecked.label\": \"Zähle Unmarkierte\",\n  \"Calculations.Options.countUniqueValue.displayName\": \"Eindeutig\",\n  \"Calculations.Options.countUniqueValue.label\": \"Zähle eindeutige Werte\",\n  \"Calculations.Options.countValue.displayName\": \"Werte\",\n  \"Calculations.Options.countValue.label\": \"Zähle Wert\",\n  \"Calculations.Options.dateRange.displayName\": \"Bereich\",\n  \"Calculations.Options.dateRange.label\": \"Bereich\",\n  \"Calculations.Options.earliest.displayName\": \"Früheste\",\n  \"Calculations.Options.earliest.label\": \"Früheste\",\n  \"Calculations.Options.latest.displayName\": \"Neueste\",\n  \"Calculations.Options.latest.label\": \"Neueste\",\n  \"Calculations.Options.max.displayName\": \"Max\",\n  \"Calculations.Options.max.label\": \"Max\",\n  \"Calculations.Options.median.displayName\": \"Median\",\n  \"Calculations.Options.median.label\": \"Median\",\n  \"Calculations.Options.min.displayName\": \"Min\",\n  \"Calculations.Options.min.label\": \"Min\",\n  \"Calculations.Options.none.displayName\": \"Berechnen\",\n  \"Calculations.Options.none.label\": \"Keine\",\n  \"Calculations.Options.percentChecked.displayName\": \"Geprüft\",\n  \"Calculations.Options.percentChecked.label\": \"Prozentsatz Markiert\",\n  \"Calculations.Options.percentUnchecked.displayName\": \"Ungeprüft\",\n  \"Calculations.Options.percentUnchecked.label\": \"Prozentsatz Unmarkiert\",\n  \"Calculations.Options.range.displayName\": \"Bereich\",\n  \"Calculations.Options.range.label\": \"Bereich\",\n  \"Calculations.Options.sum.displayName\": \"Summe\",\n  \"Calculations.Options.sum.label\": \"Summe\",\n  \"CalendarCard.untitled\": \"Ohne Titel\",\n  \"CardActionsMenu.copiedLink\": \"Kopiert!\",\n  \"CardActionsMenu.copyLink\": \"Verknüpfung kopieren\",\n  \"CardActionsMenu.delete\": \"Löschen\",\n  \"CardActionsMenu.duplicate\": \"Duplizieren\",\n  \"CardBadges.title-checkboxes\": \"Checkboxen\",\n  \"CardBadges.title-comments\": \"Kommentare\",\n  \"CardBadges.title-description\": \"Diese Karte hat eine Beschreibung\",\n  \"CardDetail.Attach\": \"Anhängen\",\n  \"CardDetail.Follow\": \"Folgen\",\n  \"CardDetail.Following\": \"Folgend\",\n  \"CardDetail.add-content\": \"Inhalt hinzufügen\",\n  \"CardDetail.add-icon\": \"Symbol hinzufügen\",\n  \"CardDetail.add-property\": \"+ Eigenschaft hinzufügen\",\n  \"CardDetail.addCardText\": \"Kartentext hinzufügen\",\n  \"CardDetail.limited-body\": \"Aktualisiere auf unseren Professional oder Enterprise Plan.\",\n  \"CardDetail.limited-button\": \"Aktualisiere\",\n  \"CardDetail.limited-title\": \"Diese Karte ist versteckt\",\n  \"CardDetail.moveContent\": \"Karteninhalt verschieben\",\n  \"CardDetail.new-comment-placeholder\": \"Kommentar hinzufügen...\",\n  \"CardDetailProperty.confirm-delete-heading\": \"Eigenschaft löschen bestätigen\",\n  \"CardDetailProperty.confirm-delete-subtext\": \"Bist du sicher, dass du die Eigenschaft \\\"{propertyName}\\\" löschen möchtest? Wenn du diese löscht, wird die Eigenschaft von allen Karten in diesem Board gelöscht.\",\n  \"CardDetailProperty.confirm-property-name-change-subtext\": \"Bist du sicher, dass du die Eigenschaft \\\"{propertyName}\\\" {customText} ändern möchtest? Dies wird Werte auf {numOfCards} Karten in diesem Board ändern und kann dazu führen, dass diese verloren gehen.\",\n  \"CardDetailProperty.confirm-property-type-change\": \"Bestätige Eigenschaftsänderung\",\n  \"CardDetailProperty.delete-action-button\": \"Löschen\",\n  \"CardDetailProperty.property-change-action-button\": \"Ändere Eigenschaft\",\n  \"CardDetailProperty.property-changed\": \"Eigenschaft erfolgreich geändert!\",\n  \"CardDetailProperty.property-deleted\": \"{propertyName} erfolgreich gelöscht!\",\n  \"CardDetailProperty.property-name-change-subtext\": \"Typ von \\\"{oldPropType}\\\" zu \\\"{newPropType}\\\"\",\n  \"CardDetial.limited-link\": \"Erfahre mehr über unsere Pläne.\",\n  \"CardDialog.delete-confirmation-dialog-attachment\": \"Löschen des Anhangs bestätigen\",\n  \"CardDialog.delete-confirmation-dialog-button-text\": \"Löschen\",\n  \"CardDialog.delete-confirmation-dialog-heading\": \"Karte löschen bestätigen\",\n  \"CardDialog.editing-template\": \"Du bearbeitest eine Vorlage.\",\n  \"CardDialog.nocard\": \"Diese Karte existiert nicht oder ist nicht verfügbar.\",\n  \"Categories.CreateCategoryDialog.CancelText\": \"Abbrechen\",\n  \"Categories.CreateCategoryDialog.CreateText\": \"Erstellen\",\n  \"Categories.CreateCategoryDialog.Placeholder\": \"Benenne deine Kategorie\",\n  \"Categories.CreateCategoryDialog.UpdateText\": \"Aktualisieren\",\n  \"CenterPanel.Login\": \"Anmeldung\",\n  \"CenterPanel.Share\": \"Teilen\",\n  \"ChannelIntro.CreateBoard\": \"Erstelle ein Board\",\n  \"ColorOption.selectColor\": \"Wähle Farbe {color}\",\n  \"Comment.delete\": \"Löschen\",\n  \"CommentsList.send\": \"Abschicken\",\n  \"ConfirmPerson.empty\": \"Leer\",\n  \"ConfirmPerson.search\": \"Suche...\",\n  \"ConfirmationDialog.cancel-action\": \"Abbrechen\",\n  \"ConfirmationDialog.confirm-action\": \"Bestätigen\",\n  \"ContentBlock.Delete\": \"Löschen\",\n  \"ContentBlock.DeleteAction\": \"löschen\",\n  \"ContentBlock.addElement\": \"{type} hinzufügen\",\n  \"ContentBlock.checkbox\": \"Checkbox\",\n  \"ContentBlock.divider\": \"Teiler\",\n  \"ContentBlock.editCardCheckbox\": \"Umschaltbare Checkbox\",\n  \"ContentBlock.editCardCheckboxText\": \"Kartentext bearbeiten\",\n  \"ContentBlock.editCardText\": \"Kartentext bearbeiten\",\n  \"ContentBlock.editText\": \"Text bearbeiten ...\",\n  \"ContentBlock.image\": \"Bild\",\n  \"ContentBlock.insertAbove\": \"Darüber einfügen\",\n  \"ContentBlock.moveBlock\": \"Karteninhalt verschieben\",\n  \"ContentBlock.moveDown\": \"Nach unten bewegen\",\n  \"ContentBlock.moveUp\": \"Nach oben bewegen\",\n  \"ContentBlock.text\": \"Text\",\n  \"DateFilter.empty\": \"Leer\",\n  \"DateRange.clear\": \"Leeren\",\n  \"DateRange.empty\": \"Leer\",\n  \"DateRange.endDate\": \"Enddatum\",\n  \"DateRange.today\": \"Heute\",\n  \"DeleteBoardDialog.confirm-cancel\": \"Abbrechen\",\n  \"DeleteBoardDialog.confirm-delete\": \"Löschen\",\n  \"DeleteBoardDialog.confirm-info\": \"Bist du sicher, dass du das Board \\\"{boardTitle}\\\" löschen möchtest? Wenn du es löschen, werden allen Karten auf diesem Board gelöscht.\",\n  \"DeleteBoardDialog.confirm-info-template\": \"Bist du sicher, dass du die Board-Vorlage \\\"{boardTitle}\\\" löschen willst?\",\n  \"DeleteBoardDialog.confirm-tite\": \"Board löschen bestätigen\",\n  \"DeleteBoardDialog.confirm-tite-template\": \"Board Vorlage wirklich löschen\",\n  \"Dialog.closeDialog\": \"Dialog schließen\",\n  \"EditableDayPicker.today\": \"Heute\",\n  \"Error.mobileweb\": \"Die Unterstützung für das mobile Web befindet sich derzeit in einer frühen Betaphase. Möglicherweise sind nicht alle Funktionen vorhanden.\",\n  \"Error.websocket-closed\": \"Websocket-Verbindung geschlossen, Verbindung unterbrochen. Wenn dieses Problem weiterhin besteht, überprüfe bitte die Konfiguration deines Servers oder Web-Proxys.\",\n  \"Filter.contains\": \"enthält\",\n  \"Filter.ends-with\": \"endet mit\",\n  \"Filter.includes\": \"beinhaltet\",\n  \"Filter.is\": \"ist\",\n  \"Filter.is-after\": \"ist nach\",\n  \"Filter.is-before\": \"ist vor\",\n  \"Filter.is-empty\": \"ist leer\",\n  \"Filter.is-not-empty\": \"ist nicht leer\",\n  \"Filter.is-not-set\": \"ist nicht gesetzt\",\n  \"Filter.is-set\": \"ist gesetzt\",\n  \"Filter.isafter\": \"ist nach\",\n  \"Filter.isbefore\": \"ist vor\",\n  \"Filter.not-contains\": \"enthält nicht\",\n  \"Filter.not-ends-with\": \"endet nicht mit\",\n  \"Filter.not-includes\": \"beinhaltet nicht\",\n  \"Filter.not-starts-with\": \"beginnt nicht mit\",\n  \"Filter.starts-with\": \"beginnt mit\",\n  \"FilterByText.placeholder\": \"Filtertext\",\n  \"FilterComponent.add-filter\": \"+ Filter hinzufügen\",\n  \"FilterComponent.delete\": \"Löschen\",\n  \"FilterValue.empty\": \"(leer)\",\n  \"FindBoardsDialog.IntroText\": \"Suche nach Boards\",\n  \"FindBoardsDialog.NoResultsFor\": \"Keine Ergebnisse für \\\"{searchQuery}\\\"\",\n  \"FindBoardsDialog.NoResultsSubtext\": \"Prüfe die Schreibweise oder versuche eine weitere Suche.\",\n  \"FindBoardsDialog.SubTitle\": \"Tippe um ein Board zu finden. Benutze <b>HOCH/RUNTER</b> zum Browsen. <b>ENTER</b> zur Auswahl, <b>ESC</b> zum Schließen\",\n  \"FindBoardsDialog.Title\": \"Finde Boards\",\n  \"GroupBy.hideEmptyGroups\": \"Verstecke {count} leere Gruppen\",\n  \"GroupBy.showHiddenGroups\": \"Zeige {count} versteckte Gruppen\",\n  \"GroupBy.ungroup\": \"Gruppierung aufheben\",\n  \"HideBoard.MenuOption\": \"Board verstecken\",\n  \"KanbanCard.untitled\": \"Unbenannt\",\n  \"MentionSuggestion.is-not-board-member\": \"(kein Board Mitglied)\",\n  \"Mutator.new-board-from-template\": \"Neues Board aus Vorlage\",\n  \"Mutator.new-card-from-template\": \"neue Karte aus Vorlage\",\n  \"Mutator.new-template-from-card\": \"neue Vorlage aus Karte\",\n  \"OnboardingTour.AddComments.Body\": \"Du kannst Themen kommentieren und sogar deine Mattermost-Kollegen @erwähnen, um deren Aufmerksamkeit zu erhalten.\",\n  \"OnboardingTour.AddComments.Title\": \"Kommentare hinzufügen\",\n  \"OnboardingTour.AddDescription.Body\": \"Füge deiner Karte eine Beschreibung hinzu, damit deine Teamkollegen wissen, worum es in der Karte geht.\",\n  \"OnboardingTour.AddDescription.Title\": \"Beschreibung hinzufügen\",\n  \"OnboardingTour.AddProperties.Body\": \"Füge den Karten verschiedene Eigenschaften hinzu, um sie noch leistungsfähiger zu machen.\",\n  \"OnboardingTour.AddProperties.Title\": \"Eigenschaften hinzufügen\",\n  \"OnboardingTour.AddView.Body\": \"Hier kannst Du eine neue Ansicht erstellen, um dein Board mit verschiedenen Layouts zu organisieren.\",\n  \"OnboardingTour.AddView.Title\": \"Eine neue Ansicht hinzufügen\",\n  \"OnboardingTour.CopyLink.Body\": \"Du kannst deine Karten mit Teamkollegen teilen, indem Du den Link kopierst und in einen Kanal, eine Direktnachricht oder eine Gruppennachricht einfügst.\",\n  \"OnboardingTour.CopyLink.Title\": \"Link kopieren\",\n  \"OnboardingTour.OpenACard.Body\": \"Öffne eine Karte und entdecke die Möglichkeiten, die Boards bei der Organisation deiner Arbeit bietet.\",\n  \"OnboardingTour.OpenACard.Title\": \"Eine Karte öffnen\",\n  \"OnboardingTour.ShareBoard.Body\": \"Du kannst dein Board intern, innerhalb deines Teams, freigeben oder es öffentlich veröffentlichen, damit es auch außerhalb deines Unternehmens sichtbar ist.\",\n  \"OnboardingTour.ShareBoard.Title\": \"Board teilen\",\n  \"PersonProperty.board-members\": \"Board Mitglieder\",\n  \"PersonProperty.me\": \"Ich\",\n  \"PersonProperty.non-board-members\": \"Keine Board Mitglieder\",\n  \"PropertyMenu.Delete\": \"Löschen\",\n  \"PropertyMenu.changeType\": \"Eigenschaftstyp ändern\",\n  \"PropertyMenu.selectType\": \"Eigenschaftstyp auswählen\",\n  \"PropertyMenu.typeTitle\": \"Art\",\n  \"PropertyType.Checkbox\": \"Checkbox\",\n  \"PropertyType.CreatedBy\": \"Erstellt von\",\n  \"PropertyType.CreatedTime\": \"Erstellzeit\",\n  \"PropertyType.Date\": \"Datum\",\n  \"PropertyType.Email\": \"E-Mail\",\n  \"PropertyType.MultiPerson\": \"Mehrere Personen\",\n  \"PropertyType.MultiSelect\": \"Mehrfachauswahl\",\n  \"PropertyType.Number\": \"Zahl\",\n  \"PropertyType.Person\": \"Person\",\n  \"PropertyType.Phone\": \"Telefon\",\n  \"PropertyType.Select\": \"Auswählen\",\n  \"PropertyType.Text\": \"Text\",\n  \"PropertyType.Unknown\": \"Unbekannt\",\n  \"PropertyType.UpdatedBy\": \"Aktualisiert durch\",\n  \"PropertyType.UpdatedTime\": \"Letzte Aktualisierung\",\n  \"PropertyType.Url\": \"URL\",\n  \"PropertyValueElement.empty\": \"Leer\",\n  \"RegistrationLink.confirmRegenerateToken\": \"Diese Aktion widerruft zuvor geteilte Links. Trotzdem fortfahren?\",\n  \"RegistrationLink.copiedLink\": \"Kopiert!\",\n  \"RegistrationLink.copyLink\": \"Link kopieren\",\n  \"RegistrationLink.description\": \"Teile diesen Link mit anderen zur Accounterstellung:\",\n  \"RegistrationLink.regenerateToken\": \"Token neu generieren\",\n  \"RegistrationLink.tokenRegenerated\": \"Registrierungslink neu generiert\",\n  \"ShareBoard.PublishDescription\": \"Veröffentliche und teile einen \\\"Nur Lesen\\\"-Link mit jedem im Web.\",\n  \"ShareBoard.PublishTitle\": \"Im Web veröffentlichen\",\n  \"ShareBoard.ShareInternal\": \"Intern teilen\",\n  \"ShareBoard.ShareInternalDescription\": \"Benutzer mit Berechtigungen können diesen Link benutzen.\",\n  \"ShareBoard.Title\": \"Board teilen\",\n  \"ShareBoard.confirmRegenerateToken\": \"Diese Aktion invalidiert zuvor geteilte Links. Trotzdem fortfahren?\",\n  \"ShareBoard.copiedLink\": \"Kopiert!\",\n  \"ShareBoard.copyLink\": \"Link kopieren\",\n  \"ShareBoard.regenerate\": \"Token neu erstellen\",\n  \"ShareBoard.searchPlaceholder\": \"Suche nach Personen und Kanälen\",\n  \"ShareBoard.teamPermissionsText\": \"Jeder im {teamName} Team\",\n  \"ShareBoard.tokenRegenrated\": \"Token neu generiert\",\n  \"ShareBoard.userPermissionsRemoveMemberText\": \"Mitglied entfernen\",\n  \"ShareBoard.userPermissionsYouText\": \"(Du)\",\n  \"ShareTemplate.Title\": \"Vorlage teilen\",\n  \"ShareTemplate.searchPlaceholder\": \"Benutzer suchen\",\n  \"Sidebar.about\": \"Über Focalboard\",\n  \"Sidebar.add-board\": \"+ Board hinzufügen\",\n  \"Sidebar.changePassword\": \"Passwort ändern\",\n  \"Sidebar.delete-board\": \"Board löschen\",\n  \"Sidebar.duplicate-board\": \"Board kopieren\",\n  \"Sidebar.export-archive\": \"Archiv exportieren\",\n  \"Sidebar.import\": \"Importieren\",\n  \"Sidebar.import-archive\": \"Archiv importieren\",\n  \"Sidebar.invite-users\": \"Nutzer einladen\",\n  \"Sidebar.logout\": \"Ausloggen\",\n  \"Sidebar.new-category.badge\": \"Neu\",\n  \"Sidebar.new-category.drag-boards-cta\": \"Board hierher ziehen...\",\n  \"Sidebar.no-boards-in-category\": \"Keine Boards vorhanden\",\n  \"Sidebar.product-tour\": \"Produkttour\",\n  \"Sidebar.random-icons\": \"Zufällige Icons\",\n  \"Sidebar.set-language\": \"Sprache übernehmen\",\n  \"Sidebar.set-theme\": \"Theme übernehmen\",\n  \"Sidebar.settings\": \"Einstellungen\",\n  \"Sidebar.template-from-board\": \"Neue Vorlage aus Board\",\n  \"Sidebar.untitled-board\": \"(Unbenanntes Board)\",\n  \"Sidebar.untitled-view\": \"(Ansicht ohne Titel)\",\n  \"SidebarCategories.BlocksMenu.Move\": \"Bewege nach...\",\n  \"SidebarCategories.CategoryMenu.CreateNew\": \"Erstelle neue Kategorie\",\n  \"SidebarCategories.CategoryMenu.Delete\": \"Lösche Kategorie\",\n  \"SidebarCategories.CategoryMenu.DeleteModal.Body\": \"Boards in <b>{categoryName}</b> werden zurück zu den Board-Kategorien bewegt. Du wirst von keinen Boards entfernt.\",\n  \"SidebarCategories.CategoryMenu.DeleteModal.Title\": \"Diese Kategorie löschen?\",\n  \"SidebarCategories.CategoryMenu.Update\": \"Kategorie umbenennen\",\n  \"SidebarTour.ManageCategories.Body\": \"Erstelle und verwalte eigene Kategorien. Diese sind benutzer-spezifisch, daher beeinflusst das Verschieben eines Boards in deine Kategorie andere Mitglieder, die das gleiche Board nutzen, nicht.\",\n  \"SidebarTour.ManageCategories.Title\": \"Verwalte Kategorien\",\n  \"SidebarTour.SearchForBoards.Body\": \"Öffne den Board Wechsler /Cmd/Strg + K) um schnell Boards zu finden und zu deiner Seitenleiste hinzuzufügen.\",\n  \"SidebarTour.SearchForBoards.Title\": \"Suche nach Boards\",\n  \"SidebarTour.SidebarCategories.Body\": \"Alle deine Boards sind jetzt unter deiner neuen Seitenleiste organisiert. Kein Wechseln mehr zwischen Arbeitsbereichen. Eigene Kategorien werden einmalig auf Basis deiner bisherigen Arbeitsbereiche automatisch im Rahmen des Upgrades auf 7.2 erstellt. Diese können entfernt oder nach deinem Bedarf bearbeitet werden.\",\n  \"SidebarTour.SidebarCategories.Link\": \"Erfahre mehr\",\n  \"SidebarTour.SidebarCategories.Title\": \"Seitenleisten Kategorien\",\n  \"SiteStats.total_boards\": \"Boards gesamt\",\n  \"SiteStats.total_cards\": \"Karten gesamt\",\n  \"TableComponent.add-icon\": \"Symbol hinzufügen\",\n  \"TableComponent.name\": \"Name\",\n  \"TableComponent.plus-new\": \"+ Neu\",\n  \"TableHeaderMenu.delete\": \"Löschen\",\n  \"TableHeaderMenu.duplicate\": \"Duplizieren\",\n  \"TableHeaderMenu.hide\": \"Verstecken\",\n  \"TableHeaderMenu.insert-left\": \"Links einfügen\",\n  \"TableHeaderMenu.insert-right\": \"Rechts einfügen\",\n  \"TableHeaderMenu.sort-ascending\": \"Aufsteigend sortieren\",\n  \"TableHeaderMenu.sort-descending\": \"Absteigend sortieren\",\n  \"TableRow.DuplicateCard\": \"Kopiere Karte\",\n  \"TableRow.MoreOption\": \"Weitere Aktionen\",\n  \"TableRow.open\": \"Öffnen\",\n  \"TopBar.give-feedback\": \"Feedback geben\",\n  \"URLProperty.copiedLink\": \"Kopiert!\",\n  \"URLProperty.copy\": \"Kopieren\",\n  \"URLProperty.edit\": \"Bearbeiten\",\n  \"UndoRedoHotKeys.canRedo\": \"Wiederherstellen\",\n  \"UndoRedoHotKeys.canRedo-with-description\": \"{description} wiederherstellen\",\n  \"UndoRedoHotKeys.canUndo\": \"Rückgängig\",\n  \"UndoRedoHotKeys.canUndo-with-description\": \"{description} rückgängig machen\",\n  \"UndoRedoHotKeys.cannotRedo\": \"Nichts wiederherstellbar\",\n  \"UndoRedoHotKeys.cannotUndo\": \"Nichts zum rückgängig machen\",\n  \"ValueSelector.noOptions\": \"Keine Optionen. Fange an zu tippen, um die erste Option hinzuzufügen!\",\n  \"ValueSelector.valueSelector\": \"Werteselektor\",\n  \"ValueSelectorLabel.openMenu\": \"Menü öffnen\",\n  \"VersionMessage.help\": \"Finde raus, was es Neues in dieser Version gibt.\",\n  \"VersionMessage.learn-more\": \"Erfahre mehr\",\n  \"View.AddView\": \"Ansicht hinzufügen\",\n  \"View.Board\": \"Board\",\n  \"View.DeleteView\": \"Ansicht löschen\",\n  \"View.DuplicateView\": \"Ansicht duplizieren\",\n  \"View.Gallery\": \"Galerie\",\n  \"View.NewBoardTitle\": \"Boardansicht\",\n  \"View.NewCalendarTitle\": \"Kalenderansicht\",\n  \"View.NewGalleryTitle\": \"Galerie Ansicht\",\n  \"View.NewTableTitle\": \"Tabellenansicht\",\n  \"View.NewTemplateDefaultTitle\": \"Unbenannte Vorlage\",\n  \"View.NewTemplateTitle\": \"Unbenannt\",\n  \"View.Table\": \"Tabelle\",\n  \"ViewHeader.add-template\": \"+ Neue Vorlage\",\n  \"ViewHeader.delete-template\": \"Löschen\",\n  \"ViewHeader.display-by\": \"Anzeige durch: {property}\",\n  \"ViewHeader.edit-template\": \"Bearbeiten\",\n  \"ViewHeader.empty-card\": \"Leere Karte\",\n  \"ViewHeader.export-board-archive\": \"Board Archiv exportieren\",\n  \"ViewHeader.export-complete\": \"Export abgeschlossen!\",\n  \"ViewHeader.export-csv\": \"Als CSV exportieren\",\n  \"ViewHeader.export-failed\": \"Export fehlgeschlagen!\",\n  \"ViewHeader.filter\": \"Filter\",\n  \"ViewHeader.group-by\": \"Gruppiere nach: {property}\",\n  \"ViewHeader.new\": \"Neu\",\n  \"ViewHeader.properties\": \"Eigenschaften\",\n  \"ViewHeader.properties-menu\": \"Eigenschaften Menü\",\n  \"ViewHeader.search-text\": \"Suche Karten\",\n  \"ViewHeader.select-a-template\": \"Vorlage auswählen\",\n  \"ViewHeader.set-default-template\": \"Als Standard eingestellt\",\n  \"ViewHeader.sort\": \"Sortieren\",\n  \"ViewHeader.untitled\": \"Unbenannt\",\n  \"ViewHeader.view-header-menu\": \"Kopfmenü ansehen\",\n  \"ViewHeader.view-menu\": \"Ansichten Menü\",\n  \"ViewLimitDialog.Heading\": \"Ansichten pro Board Limit erreicht\",\n  \"ViewLimitDialog.PrimaryButton.Title.Admin\": \"Aktualisieren\",\n  \"ViewLimitDialog.PrimaryButton.Title.RegularUser\": \"Admin benachrichtigen\",\n  \"ViewLimitDialog.Subtext.Admin\": \"Aktualisiere auf unseren Professional oder Enterprise Plan.\",\n  \"ViewLimitDialog.Subtext.Admin.PricingPageLink\": \"Erfahre mehr über unsere Pläne.\",\n  \"ViewLimitDialog.Subtext.RegularUser\": \"Benachrichtige deinen Admin um auf unseren Professional oder Enterprise Plan zu aktualisieren.\",\n  \"ViewLimitDialog.UpgradeImg.AltText\": \"Bild aktualisieren\",\n  \"ViewLimitDialog.notifyAdmin.Success\": \"Dein Admin wurde benachrichtigt\",\n  \"ViewTitle.hide-description\": \"Beschreibung ausblenden\",\n  \"ViewTitle.pick-icon\": \"Symbol auswählen\",\n  \"ViewTitle.random-icon\": \"Zufällig\",\n  \"ViewTitle.remove-icon\": \"Symbol entfernen\",\n  \"ViewTitle.show-description\": \"Beschreibung anzeigen\",\n  \"ViewTitle.untitled-board\": \"Unbenanntes Board\",\n  \"WelcomePage.Description\": \"Boards ist ein Projektmanagement-Tool, das die Definition, Organisation, Verfolgung und Verwaltung von Arbeit in verschiedenen Teams mit Hilfe einer vertrauten Kanban-Board-Ansicht unterstützt.\",\n  \"WelcomePage.Explore.Button\": \"Rundgang\",\n  \"WelcomePage.Heading\": \"Willkommen bei Boards\",\n  \"WelcomePage.NoThanks.Text\": \"Nein danke, ich werde es selbst herausfinden\",\n  \"WelcomePage.StartUsingIt.Text\": \"Verwende es\",\n  \"Workspace.editing-board-template\": \"Du bearbeitest eine Board Vorlage.\",\n  \"badge.guest\": \"Gast\",\n  \"boardPage.confirm-join-button\": \"Teilnehmen\",\n  \"boardPage.confirm-join-text\": \"Du bist dabei einem privaten Board zu betreten, ohne dass du explizit durch den Board-Administrator hinzugefügt wurdest. Bist du sicher, dass du diesem privaten Board beitreten willst?\",\n  \"boardPage.confirm-join-title\": \"Privatem Board beitreten\",\n  \"boardSelector.confirm-link-board\": \"Verknüpfe Board mit Kanal\",\n  \"boardSelector.confirm-link-board-button\": \"Ja, verknüpfe Board\",\n  \"boardSelector.confirm-link-board-subtext\": \"Wenn du \\\"{boardName}\\\" mit diesem Kanal verknüpfst, werden alle Mitglieder des Kanals (aktuelle und neue) das Board bearbeiten können. Dies schließt Mitglieder aus, die Gast sind. Du kannst die Verknüpfung eine Boards mit einem Kanal jederzeit entfernen.\",\n  \"boardSelector.confirm-link-board-subtext-with-other-channel\": \"Wenn du \\\"{boardName}\\\" mit dem Kanal verknüpfst, werden alle Mitglieder des Kanals (aktuelle und neue) das Board bearbeiten können. Dies schließt Mitglieder aus, die Gast sind.{lineBreak} Dieses Board ist mit einem anderen Kanal verknüpft. Die Verknüpfung wird getrennt, wenn du es hier verknüpfst.\",\n  \"boardSelector.create-a-board\": \"Erstelle ein Board\",\n  \"boardSelector.link\": \"Verknüpfung\",\n  \"boardSelector.search-for-boards\": \"Suche nach Boards\",\n  \"boardSelector.title\": \"Verknüpfe Boards\",\n  \"boardSelector.unlink\": \"Verknüpfung aufheben\",\n  \"calendar.month\": \"Monat\",\n  \"calendar.today\": \"HEUTE\",\n  \"calendar.week\": \"Woche\",\n  \"centerPanel.undefined\": \"Kein(e) {propertyName}\",\n  \"centerPanel.unknown-user\": \"Unbekannter Benutzer\",\n  \"cloudMessage.learn-more\": \"Erfahre mehr\",\n  \"createImageBlock.failed\": \"Kann Datei nicht hochladen, da das Limit für Dateigröße erreicht ist.\",\n  \"default-properties.badges\": \"Kommentare und Beschreibung\",\n  \"default-properties.title\": \"Titel\",\n  \"error.back-to-home\": \"Zurück zur Startseite\",\n  \"error.back-to-team\": \"Zurück zum Team\",\n  \"error.board-not-found\": \"Board nicht gefunden.\",\n  \"error.go-login\": \"Anmeldung\",\n  \"error.invalid-read-only-board\": \"Du hast keine Zugriff auf dieses Board. Melde dich an um auf das Board zu zugreifen.\",\n  \"error.not-logged-in\": \"Deine Sitzung könnte abgelaufen sein oder du bist nicht angemeldet. Melde dich nochmal an um auf das Board zuzugreifen.\",\n  \"error.page.title\": \"Entschuldigung, etwas ist schief gelaufen\",\n  \"error.team-undefined\": \"Kein gültiges Team.\",\n  \"error.unknown\": \"Ein Fehler ist aufgetreten.\",\n  \"generic.previous\": \"Zurück\",\n  \"guest-no-board.subtitle\": \"Du hast noch keinen Zugang zu einem Board in diesem Team. Bitte warte, bis dich jemand zu einem Board hinzufügt.\",\n  \"guest-no-board.title\": \"Noch keine Boards\",\n  \"imagePaste.upload-failed\": \"Einige Dateien nicht hochgeladen, da das Limit für Dateigröße erreicht ist.\",\n  \"limitedCard.title\": \"Versteckte Karten\",\n  \"login.log-in-button\": \"Anmelden\",\n  \"login.log-in-title\": \"Anmelden\",\n  \"login.register-button\": \"oder erstelle einen Account wenn du noch keines hast\",\n  \"new_channel_modal.create_board.empty_board_description\": \"Neues leeres Board erstellen\",\n  \"new_channel_modal.create_board.empty_board_title\": \"Leeres Board\",\n  \"new_channel_modal.create_board.select_template_placeholder\": \"Vorlage auswählen\",\n  \"new_channel_modal.create_board.title\": \"Erstelle ein Board für diesen Kanal\",\n  \"notification-box-card-limit-reached.close-tooltip\": \"Für 10 Tage schlummern\",\n  \"notification-box-card-limit-reached.contact-link\": \"Benachrichtige deinen Admin\",\n  \"notification-box-card-limit-reached.link\": \"Wechsel auf einen kostenpflichtigen Plan\",\n  \"notification-box-card-limit-reached.title\": \"{cards} vom Board versteckte Karten\",\n  \"notification-box-cards-hidden.title\": \"Diese Aktion verdeckt eine andere Karte\",\n  \"notification-box.card-limit-reached.not-admin.text\": \"Um auf archivierte Karten zuzugreifen, kannst du {contactLink} um auf einen bezahlten Plan zu wechseln.\",\n  \"notification-box.card-limit-reached.text\": \"Kartenlimit erreicht. Um ältere Karten zu betrachten, {link}\",\n  \"person.add-user-to-board\": \"Füge {username} zum Board hinzu\",\n  \"person.add-user-to-board-confirm-button\": \"Zum Board hinzufügen\",\n  \"person.add-user-to-board-permissions\": \"Berechtigungen\",\n  \"person.add-user-to-board-question\": \"Möchtest du {username} zum Board hinzufügen?\",\n  \"person.add-user-to-board-warning\": \"{username} ist kein Mitglied des Boards und wird keine Benachrichtigungen darüber erhalten.\",\n  \"register.login-button\": \"oder melde dich an, wenn du bereits ein Konto hast\",\n  \"register.signup-title\": \"Registriere dich für deinen Account\",\n  \"rhs-board-non-admin-msg\": \"Du bist kein Administrator des Boards\",\n  \"rhs-boards.add\": \"Hinzufügen\",\n  \"rhs-boards.dm\": \"DN\",\n  \"rhs-boards.gm\": \"GN\",\n  \"rhs-boards.header.dm\": \"diese Direktnachricht\",\n  \"rhs-boards.header.gm\": \"diese Gruppennachricht\",\n  \"rhs-boards.last-update-at\": \"Letzte Aktualisierung um: {datetime}\",\n  \"rhs-boards.link-boards-to-channel\": \"Verknüpfe Board mit {channelName}\",\n  \"rhs-boards.linked-boards\": \"Verknüpfte Boards\",\n  \"rhs-boards.no-boards-linked-to-channel\": \"Bisher sind keine Boards mit {channelName} verknüpft\",\n  \"rhs-boards.no-boards-linked-to-channel-description\": \"Boards ist ein Projektmanagement Werkzeug, das hilft Aufgaben über Teams hinweg zu definieren, organisieren, verfolgen und verwalten, ähnlich den bekannten Kanban Boards.\",\n  \"rhs-boards.unlink-board\": \"Board Verknüpfung aufheben\",\n  \"rhs-boards.unlink-board1\": \"Board Verknüpfung aufheben\",\n  \"rhs-channel-boards-header.title\": \"Boards\",\n  \"share-board.publish\": \"Veröffentlichen\",\n  \"share-board.share\": \"Teilen\",\n  \"shareBoard.channels-select-group\": \"Kanäle\",\n  \"shareBoard.confirm-change-team-role.body\": \"Jeder in diesem Board, der eine niedrigere Berechtigung als die Rolle \\\"{role}\\\" hat, wird <b>nun zu {role}</b> befördert. Bist du sicher, dass du die Mindestrolle für das Board ändern willst?\",\n  \"shareBoard.confirm-change-team-role.confirmBtnText\": \"Minimale Rolle des Boards ändern\",\n  \"shareBoard.confirm-change-team-role.title\": \"Minimale Rolle des Boards ändern\",\n  \"shareBoard.confirm-link-channel\": \"Verknüpfe Board mit Kanal\",\n  \"shareBoard.confirm-link-channel-button\": \"Verknüpfe Kanal\",\n  \"shareBoard.confirm-link-channel-button-with-other-channel\": \"Verknüpfung lösen und hier verknüpfen\",\n  \"shareBoard.confirm-link-channel-subtext\": \"Wenn du einen Kanal mit einem Board verknüpfst, werden alle Mitglieder des Kanals (aktuelle und neue) das Board bearbeiten können. Dies schließt Mitglieder aus, die Gast sind.\",\n  \"shareBoard.confirm-link-channel-subtext-with-other-channel\": \"Wenn du einen Kanal mit einem Board verknüpfst, werden alle Mitglieder des Kanals (aktuelle und neue) dies bearbeiten können. Dies schließt Mitglieder aus, die Gast sind.{lineBreak}Dieses Board ist aktuell mit einem anderen Kanal verknüpft. Die Verknüpfung wird aufgehoben, wenn du es hier verknüpfst.\",\n  \"shareBoard.confirm-unlink.body\": \"Wenn du einen Kanal von einem Board trennst, werden alle Mitglieder des Kanals (aktuelle und neue) den Zugriff auf das Board verlieren außer die Berechtigungen wurden individuell vergeben.\",\n  \"shareBoard.confirm-unlink.confirmBtnText\": \"Kanal trennen\",\n  \"shareBoard.confirm-unlink.title\": \"Kanal vom Board trennen\",\n  \"shareBoard.lastAdmin\": \"Boards müssen mindestens eine Administrator haben\",\n  \"shareBoard.members-select-group\": \"Mitglieder\",\n  \"shareBoard.unknown-channel-display-name\": \"Unbekannter Kanal\",\n  \"tutorial_tip.finish_tour\": \"Erledigt\",\n  \"tutorial_tip.got_it\": \"Alles klar\",\n  \"tutorial_tip.ok\": \"Weiter\",\n  \"tutorial_tip.out\": \"Diese Tipps nicht mehr anzeigen.\",\n  \"tutorial_tip.seen\": \"Schon mal gesehen?\"\n}\n"
  },
  {
    "path": "webapp/i18n/el.json",
    "content": "{\n  \"BoardComponent.add-a-group\": \"+ Προσθήκη ομάδας\",\n  \"BoardComponent.delete\": \"Διαγραφή\",\n  \"BoardComponent.hidden-columns\": \"Κρυφές στήλες\",\n  \"BoardComponent.hide\": \"Απόκρυψη\",\n  \"BoardComponent.new\": \"+ Νέο\",\n  \"BoardComponent.show\": \"Εμφάνιση\",\n  \"CardDetail.add-content\": \"Προσθήκη περιεχομένου\",\n  \"CardDetail.add-icon\": \"Προσθήκη εικονιδίου\",\n  \"CardDetail.new-comment-placeholder\": \"Προσθήκη σχολίου ...\",\n  \"CardDialog.nocard\": \"Αυτή η κάρτα δεν υπάρχει ή δεν είναι προσβάσιμη\",\n  \"Comment.delete\": \"Διαγραφή\",\n  \"CommentsList.send\": \"Αποστολή\",\n  \"ContentBlock.Delete\": \"Διαγραφή\",\n  \"ContentBlock.DeleteAction\": \"διαγραφή\",\n  \"ContentBlock.editText\": \"Επεξεργασία κειμένου ...\",\n  \"ContentBlock.image\": \"εικόνα\",\n  \"ContentBlock.insertAbove\": \"Εισαγωγή από πάνω\",\n  \"ContentBlock.moveDown\": \"Μετακίνηση κάτω\",\n  \"ContentBlock.moveUp\": \"Μετακίνηση επάνω\",\n  \"ContentBlock.text\": \"κείμενο\",\n  \"EditableDayPicker.today\": \"Σήμερα\",\n  \"Filter.includes\": \"περιέχει\",\n  \"Filter.is-empty\": \"είναι άδειο\",\n  \"Filter.is-not-empty\": \"δεν είναι άδειο\",\n  \"Filter.not-includes\": \"δεν περιέχει\",\n  \"FilterComponent.add-filter\": \"+ Προσθήκη φίλτρου\",\n  \"FilterComponent.delete\": \"Διαγραφή\",\n  \"KanbanCard.untitled\": \"Χωρίς τίτλο\",\n  \"Mutator.new-card-from-template\": \"νέα κάρτα από πρότυπο\",\n  \"Mutator.new-template-from-card\": \"νέο πρότυπο από την κάρτα\",\n  \"PropertyMenu.Delete\": \"Διαγραφή\",\n  \"PropertyMenu.typeTitle\": \"Τύπος\",\n  \"PropertyType.CreatedBy\": \"Δημιουργήθηκε από\",\n  \"PropertyType.CreatedTime\": \"Χρόνος δημιουργίας\",\n  \"PropertyType.Date\": \"Ημερομηνία\",\n  \"PropertyType.Email\": \"Email\",\n  \"PropertyType.Number\": \"Αριθμός\",\n  \"PropertyType.Phone\": \"Τηλέφωνο\",\n  \"PropertyType.Text\": \"Κείμενο\",\n  \"PropertyType.UpdatedBy\": \"Ενημερώθηκε από\",\n  \"PropertyType.UpdatedTime\": \"Ώρα Ενημέρωσης\",\n  \"RegistrationLink.copyLink\": \"Αντιγραφή συνδέσμου\",\n  \"RegistrationLink.description\": \"Μοιραστείτε αυτόν τον σύνδεσμο με άλλους για να δημιουργήσουν λογαριασμούς:\",\n  \"ViewTitle.pick-icon\": \"Επιλογή εικονιδίου\",\n  \"ViewTitle.random-icon\": \"Τυχαίο\",\n  \"ViewTitle.remove-icon\": \"Αφαίρεση εικονιδίου\",\n  \"default-properties.title\": \"Τίτλος\"\n}\n"
  },
  {
    "path": "webapp/i18n/en.json",
    "content": "{\n  \"AdminBadge.SystemAdmin\": \"Admin\",\n  \"AdminBadge.TeamAdmin\": \"Team Admin\",\n  \"AppBar.Tooltip\": \"Toggle Linked Boards\",\n  \"Attachment.Attachment-title\": \"Attachment\",\n  \"AttachmentBlock.DeleteAction\": \"delete\",\n  \"AttachmentBlock.addElement\": \"add {type}\",\n  \"AttachmentBlock.delete\": \"Attachment deleted.\",\n  \"AttachmentBlock.failed\": \"This file couldn't be uploaded as the file size limit has been reached.\",\n  \"AttachmentBlock.upload\": \"Attachment uploading.\",\n  \"AttachmentBlock.uploadSuccess\": \"Attachment uploaded.\",\n  \"AttachmentElement.delete-confirmation-dialog-button-text\": \"Delete\",\n  \"AttachmentElement.download\": \"Download\",\n  \"AttachmentElement.upload-percentage\": \"Uploading...({uploadPercent}%)\",\n  \"BoardComponent.add-a-group\": \"+ Add a group\",\n  \"BoardComponent.delete\": \"Delete\",\n  \"BoardComponent.hidden-columns\": \"Hidden columns\",\n  \"BoardComponent.hide\": \"Hide\",\n  \"BoardComponent.new\": \"+ New\",\n  \"BoardComponent.no-property\": \"No {property}\",\n  \"BoardComponent.no-property-title\": \"Items with an empty {property} property will go here. This column can't be removed.\",\n  \"BoardComponent.show\": \"Show\",\n  \"BoardMember.schemeAdmin\": \"Admin\",\n  \"BoardMember.schemeCommenter\": \"Commenter\",\n  \"BoardMember.schemeEditor\": \"Editor\",\n  \"BoardMember.schemeNone\": \"None\",\n  \"BoardMember.schemeViewer\": \"Viewer\",\n  \"BoardMember.unlinkChannel\": \"Unlink\",\n  \"BoardPage.newVersion\": \"A new version of Boards is available, click here to reload.\",\n  \"BoardPage.syncFailed\": \"Board may be deleted or access revoked.\",\n  \"BoardTemplateSelector.add-template\": \"Create new template\",\n  \"BoardTemplateSelector.create-empty-board\": \"Create an empty board\",\n  \"BoardTemplateSelector.delete-template\": \"Delete\",\n  \"BoardTemplateSelector.description\": \"Add a board to the sidebar using any of the templates defined below or start from scratch.\",\n  \"BoardTemplateSelector.edit-template\": \"Edit\",\n  \"BoardTemplateSelector.plugin.no-content-description\": \"Add a board to the sidebar using any of the templates defined below or start from scratch.\",\n  \"BoardTemplateSelector.plugin.no-content-title\": \"Create a board\",\n  \"BoardTemplateSelector.title\": \"Create a board\",\n  \"BoardTemplateSelector.use-this-template\": \"Use this template\",\n  \"BoardsSwitcher.Title\": \"Find boards\",\n  \"BoardsUnfurl.Limited\": \"Additional details are hidden due to the card being archived\",\n  \"BoardsUnfurl.Remainder\": \"+{remainder} more\",\n  \"BoardsUnfurl.Updated\": \"Updated {time}\",\n  \"Calculations.Options.average.displayName\": \"Average\",\n  \"Calculations.Options.average.label\": \"Average\",\n  \"Calculations.Options.count.displayName\": \"Count\",\n  \"Calculations.Options.count.label\": \"Count\",\n  \"Calculations.Options.countChecked.displayName\": \"Checked\",\n  \"Calculations.Options.countChecked.label\": \"Count checked\",\n  \"Calculations.Options.countUnchecked.displayName\": \"Unchecked\",\n  \"Calculations.Options.countUnchecked.label\": \"Count unchecked\",\n  \"Calculations.Options.countUniqueValue.displayName\": \"Unique\",\n  \"Calculations.Options.countUniqueValue.label\": \"Count unique values\",\n  \"Calculations.Options.countValue.displayName\": \"Values\",\n  \"Calculations.Options.countValue.label\": \"Count value\",\n  \"Calculations.Options.dateRange.displayName\": \"Range\",\n  \"Calculations.Options.dateRange.label\": \"Range\",\n  \"Calculations.Options.earliest.displayName\": \"Earliest\",\n  \"Calculations.Options.earliest.label\": \"Earliest\",\n  \"Calculations.Options.latest.displayName\": \"Latest\",\n  \"Calculations.Options.latest.label\": \"Latest\",\n  \"Calculations.Options.max.displayName\": \"Max\",\n  \"Calculations.Options.max.label\": \"Max\",\n  \"Calculations.Options.median.displayName\": \"Median\",\n  \"Calculations.Options.median.label\": \"Median\",\n  \"Calculations.Options.min.displayName\": \"Min\",\n  \"Calculations.Options.min.label\": \"Min\",\n  \"Calculations.Options.none.displayName\": \"Calculate\",\n  \"Calculations.Options.none.label\": \"None\",\n  \"Calculations.Options.percentChecked.displayName\": \"Checked\",\n  \"Calculations.Options.percentChecked.label\": \"Percent checked\",\n  \"Calculations.Options.percentUnchecked.displayName\": \"Unchecked\",\n  \"Calculations.Options.percentUnchecked.label\": \"Percent unchecked\",\n  \"Calculations.Options.range.displayName\": \"Range\",\n  \"Calculations.Options.range.label\": \"Range\",\n  \"Calculations.Options.sum.displayName\": \"Sum\",\n  \"Calculations.Options.sum.label\": \"Sum\",\n  \"CalendarCard.untitled\": \"Untitled\",\n  \"CardActionsMenu.copiedLink\": \"Copied!\",\n  \"CardActionsMenu.copyLink\": \"Copy link\",\n  \"CardActionsMenu.delete\": \"Delete\",\n  \"CardActionsMenu.duplicate\": \"Duplicate\",\n  \"CardBadges.title-checkboxes\": \"Checkboxes\",\n  \"CardBadges.title-comments\": \"Comments\",\n  \"CardBadges.title-description\": \"This card has a description\",\n  \"CardDetail.Attach\": \"Attach\",\n  \"CardDetail.Follow\": \"Follow\",\n  \"CardDetail.Following\": \"Following\",\n  \"CardDetail.add-content\": \"Add content\",\n  \"CardDetail.add-icon\": \"Add icon\",\n  \"CardDetail.add-property\": \"+ Add a property\",\n  \"CardDetail.addCardText\": \"add card text\",\n  \"CardDetail.limited-body\": \"Upgrade to our Professional or Enterprise plan.\",\n  \"CardDetail.limited-button\": \"Upgrade\",\n  \"CardDetail.limited-title\": \"This card is hidden\",\n  \"CardDetail.moveContent\": \"Move card content\",\n  \"CardDetail.new-comment-placeholder\": \"Add a comment...\",\n  \"CardDetailProperty.confirm-delete-heading\": \"Confirm delete property\",\n  \"CardDetailProperty.confirm-delete-subtext\": \"Are you sure you want to delete the property \\\"{propertyName}\\\"? Deleting it will delete the property from all cards in this board.\",\n  \"CardDetailProperty.confirm-property-name-change-subtext\": \"Are you sure you want to change property \\\"{propertyName}\\\" {customText}? This will affect value(s) across {numOfCards} card(s) in this board, and can result in data loss.\",\n  \"CardDetailProperty.confirm-property-type-change\": \"Confirm property type change\",\n  \"CardDetailProperty.delete-action-button\": \"Delete\",\n  \"CardDetailProperty.property-change-action-button\": \"Change property\",\n  \"CardDetailProperty.property-changed\": \"Changed property successfully!\",\n  \"CardDetailProperty.property-deleted\": \"Deleted {propertyName} successfully!\",\n  \"CardDetailProperty.property-name-change-subtext\": \"type from \\\"{oldPropType}\\\" to \\\"{newPropType}\\\"\",\n  \"CardDetial.limited-link\": \"Learn more about our plans.\",\n  \"CardDialog.delete-confirmation-dialog-attachment\": \"Confirm attachment delete\",\n  \"CardDialog.delete-confirmation-dialog-button-text\": \"Delete\",\n  \"CardDialog.delete-confirmation-dialog-heading\": \"Confirm card delete\",\n  \"CardDialog.editing-template\": \"You're editing a template.\",\n  \"CardDialog.nocard\": \"This card doesn't exist or is inaccessible.\",\n  \"Categories.CreateCategoryDialog.CancelText\": \"Cancel\",\n  \"Categories.CreateCategoryDialog.CreateText\": \"Create\",\n  \"Categories.CreateCategoryDialog.Placeholder\": \"Name your category\",\n  \"Categories.CreateCategoryDialog.UpdateText\": \"Update\",\n  \"CenterPanel.Login\": \"Login\",\n  \"CenterPanel.Share\": \"Share\",\n  \"ChannelIntro.CreateBoard\": \"Create a board\",\n  \"ColorOption.selectColor\": \"Select {color} Color\",\n  \"Comment.delete\": \"Delete\",\n  \"CommentsList.send\": \"Send\",\n  \"ConfirmPerson.empty\": \"Empty\",\n  \"ConfirmPerson.search\": \"Search...\",\n  \"ConfirmationDialog.cancel-action\": \"Cancel\",\n  \"ConfirmationDialog.confirm-action\": \"Confirm\",\n  \"ContentBlock.Delete\": \"Delete\",\n  \"ContentBlock.DeleteAction\": \"delete\",\n  \"ContentBlock.addElement\": \"add {type}\",\n  \"ContentBlock.checkbox\": \"checkbox\",\n  \"ContentBlock.divider\": \"divider\",\n  \"ContentBlock.editCardCheckbox\": \"toggled-checkbox\",\n  \"ContentBlock.editCardCheckboxText\": \"edit card text\",\n  \"ContentBlock.editCardText\": \"edit card text\",\n  \"ContentBlock.editText\": \"Edit text...\",\n  \"ContentBlock.errorText\": \"You've exceeded the size limit for this content. Please shorten it to avoid losing data.\",\n  \"ContentBlock.image\": \"image\",\n  \"ContentBlock.insertAbove\": \"Insert above\",\n  \"ContentBlock.moveBlock\": \"move card content\",\n  \"ContentBlock.moveDown\": \"Move down\",\n  \"ContentBlock.moveUp\": \"Move up\",\n  \"ContentBlock.text\": \"text\",\n  \"DateFilter.empty\": \"Empty\",\n  \"DateRange.clear\": \"Clear\",\n  \"DateRange.empty\": \"Empty\",\n  \"DateRange.endDate\": \"End date\",\n  \"DateRange.today\": \"Today\",\n  \"DeleteBoardDialog.confirm-cancel\": \"Cancel\",\n  \"DeleteBoardDialog.confirm-delete\": \"Delete\",\n  \"DeleteBoardDialog.confirm-info\": \"Are you sure you want to delete the board “{boardTitle}”? Deleting it will delete all cards in the board.\",\n  \"DeleteBoardDialog.confirm-info-template\": \"Are you sure you want to delete the board template “{boardTitle}”?\",\n  \"DeleteBoardDialog.confirm-tite\": \"Confirm delete board\",\n  \"DeleteBoardDialog.confirm-tite-template\": \"Confirm delete board template\",\n  \"Dialog.closeDialog\": \"Close dialog\",\n  \"EditableDayPicker.today\": \"Today\",\n  \"Error.mobileweb\": \"Mobile web support is currently in early beta. Not all functionality may be present.\",\n  \"Error.websocket-closed\": \"Websocket connection closed, connection interrupted. If this persists, check your server or web proxy configuration.\",\n  \"Filter.contains\": \"contains\",\n  \"Filter.ends-with\": \"ends with\",\n  \"Filter.includes\": \"includes\",\n  \"Filter.is\": \"is\",\n  \"Filter.is-after\": \"is after\",\n  \"Filter.is-before\": \"is before\",\n  \"Filter.is-empty\": \"is empty\",\n  \"Filter.is-not-empty\": \"is not empty\",\n  \"Filter.is-not-set\": \"is not set\",\n  \"Filter.is-set\": \"is set\",\n  \"Filter.isafter\": \"is after\",\n  \"Filter.isbefore\": \"is before\",\n  \"Filter.not-contains\": \"doesn't contain\",\n  \"Filter.not-ends-with\": \"doesn't end with\",\n  \"Filter.not-includes\": \"doesn't include\",\n  \"Filter.not-starts-with\": \"doesn't start with\",\n  \"Filter.starts-with\": \"starts with\",\n  \"FilterByText.placeholder\": \"filter text\",\n  \"FilterComponent.add-filter\": \"+ Add filter\",\n  \"FilterComponent.delete\": \"Delete\",\n  \"FilterValue.empty\": \"(empty)\",\n  \"FindBoardsDialog.IntroText\": \"Search for boards\",\n  \"FindBoardsDialog.NoResultsFor\": \"No results for \\\"{searchQuery}\\\"\",\n  \"FindBoardsDialog.NoResultsSubtext\": \"Check the spelling or try another search.\",\n  \"FindBoardsDialog.SubTitle\": \"Type to find a board. Use <b>UP/DOWN</b> to browse. <b>ENTER</b> to select, <b>ESC</b> to dismiss\",\n  \"FindBoardsDialog.Title\": \"Find Boards\",\n  \"GroupBy.hideEmptyGroups\": \"Hide {count} empty groups\",\n  \"GroupBy.showHiddenGroups\": \"Show {count} hidden groups\",\n  \"GroupBy.ungroup\": \"Ungroup\",\n  \"HideBoard.MenuOption\": \"Hide board\",\n  \"KanbanCard.untitled\": \"Untitled\",\n  \"MentionSuggestion.is-not-board-member\": \"(not board member)\",\n  \"Mutator.new-board-from-template\": \"new board from template\",\n  \"Mutator.new-card-from-template\": \"new card from template\",\n  \"Mutator.new-template-from-card\": \"new template from card\",\n  \"OnboardingTour.AddComments.Body\": \"You can comment on issues, and even @mention your fellow Mattermost users to get their attention.\",\n  \"OnboardingTour.AddComments.Title\": \"Add comments\",\n  \"OnboardingTour.AddDescription.Body\": \"Add a description to your card so your teammates know what the card is about.\",\n  \"OnboardingTour.AddDescription.Title\": \"Add description\",\n  \"OnboardingTour.AddProperties.Body\": \"Add various properties to cards to make them more powerful.\",\n  \"OnboardingTour.AddProperties.Title\": \"Add properties\",\n  \"OnboardingTour.AddView.Body\": \"Go here to create a new view to organise your board using different layouts.\",\n  \"OnboardingTour.AddView.Title\": \"Add a new view\",\n  \"OnboardingTour.CopyLink.Body\": \"You can share your cards with teammates by copying the link and pasting it in a channel, direct message, or group message.\",\n  \"OnboardingTour.CopyLink.Title\": \"Copy link\",\n  \"OnboardingTour.OpenACard.Body\": \"Open a card to explore the powerful ways that Boards can help you organize your work.\",\n  \"OnboardingTour.OpenACard.Title\": \"Open a card\",\n  \"OnboardingTour.ShareBoard.Body\": \"You can share your board internally, within your team, or publish it publicly for visibility outside of your organization.\",\n  \"OnboardingTour.ShareBoard.Title\": \"Share board\",\n  \"PersonProperty.board-members\": \"Board members\",\n  \"PersonProperty.me\": \"Me\",\n  \"PersonProperty.non-board-members\": \"Not board members\",\n  \"PropertyMenu.Delete\": \"Delete\",\n  \"PropertyMenu.changeType\": \"Change property type\",\n  \"PropertyMenu.selectType\": \"Select property type\",\n  \"PropertyMenu.typeTitle\": \"Type\",\n  \"PropertyType.Checkbox\": \"Checkbox\",\n  \"PropertyType.CreatedBy\": \"Created by\",\n  \"PropertyType.CreatedTime\": \"Created time\",\n  \"PropertyType.Date\": \"Date\",\n  \"PropertyType.Email\": \"Email\",\n  \"PropertyType.MultiPerson\": \"Multi person\",\n  \"PropertyType.MultiSelect\": \"Multi select\",\n  \"PropertyType.Number\": \"Number\",\n  \"PropertyType.Person\": \"Person\",\n  \"PropertyType.Phone\": \"Phone\",\n  \"PropertyType.Select\": \"Select\",\n  \"PropertyType.Text\": \"Text\",\n  \"PropertyType.Unknown\": \"Unknown\",\n  \"PropertyType.UpdatedBy\": \"Last updated by\",\n  \"PropertyType.UpdatedTime\": \"Last updated time\",\n  \"PropertyType.Url\": \"URL\",\n  \"PropertyValueElement.empty\": \"Empty\",\n  \"RegistrationLink.confirmRegenerateToken\": \"This will invalidate previously shared links. Continue?\",\n  \"RegistrationLink.copiedLink\": \"Copied!\",\n  \"RegistrationLink.copyLink\": \"Copy link\",\n  \"RegistrationLink.description\": \"Share this link for others to create accounts:\",\n  \"RegistrationLink.regenerateToken\": \"Regenerate token\",\n  \"RegistrationLink.tokenRegenerated\": \"Registration link regenerated\",\n  \"ShareBoard.PublishDescription\": \"Publish and share a read-only link with everyone on the web.\",\n  \"ShareBoard.PublishTitle\": \"Publish to the web\",\n  \"ShareBoard.ShareInternal\": \"Share internally\",\n  \"ShareBoard.ShareInternalDescription\": \"Users who have permissions will be able to use this link.\",\n  \"ShareBoard.Title\": \"Share Board\",\n  \"ShareBoard.confirmRegenerateToken\": \"This will invalidate previously shared links. Continue?\",\n  \"ShareBoard.copiedLink\": \"Copied!\",\n  \"ShareBoard.copyLink\": \"Copy link\",\n  \"ShareBoard.regenerate\": \"Regenerate token\",\n  \"ShareBoard.searchPlaceholder\": \"Search for people and channels\",\n  \"ShareBoard.teamPermissionsText\": \"Everyone at {teamName} team\",\n  \"ShareBoard.tokenRegenrated\": \"Token regenerated\",\n  \"ShareBoard.userPermissionsRemoveMemberText\": \"Remove member\",\n  \"ShareBoard.userPermissionsYouText\": \"(You)\",\n  \"ShareTemplate.Title\": \"Share template\",\n  \"ShareTemplate.searchPlaceholder\": \"Search for people\",\n  \"Sidebar.about\": \"About Focalboard\",\n  \"Sidebar.add-board\": \"+ Add board\",\n  \"Sidebar.changePassword\": \"Change password\",\n  \"Sidebar.delete-board\": \"Delete board\",\n  \"Sidebar.duplicate-board\": \"Duplicate board\",\n  \"Sidebar.export-archive\": \"Export archive\",\n  \"Sidebar.import\": \"Import\",\n  \"Sidebar.import-archive\": \"Import archive\",\n  \"Sidebar.invite-users\": \"Invite users\",\n  \"Sidebar.logout\": \"Log out\",\n  \"Sidebar.new-category.badge\": \"New\",\n  \"Sidebar.new-category.drag-boards-cta\": \"Drag boards here...\",\n  \"Sidebar.no-boards-in-category\": \"No boards inside\",\n  \"Sidebar.product-tour\": \"Product tour\",\n  \"Sidebar.random-icons\": \"Random icons\",\n  \"Sidebar.set-language\": \"Set language\",\n  \"Sidebar.set-theme\": \"Set theme\",\n  \"Sidebar.settings\": \"Settings\",\n  \"Sidebar.template-from-board\": \"New template from board\",\n  \"Sidebar.untitled-board\": \"(Untitled Board)\",\n  \"Sidebar.untitled-view\": \"(Untitled View)\",\n  \"SidebarCategories.BlocksMenu.Move\": \"Move To...\",\n  \"SidebarCategories.CategoryMenu.CreateNew\": \"Create New Category\",\n  \"SidebarCategories.CategoryMenu.Delete\": \"Delete Category\",\n  \"SidebarCategories.CategoryMenu.DeleteModal.Body\": \"Boards in <b>{categoryName}</b> will move back to the Boards categories. You're not removed from any boards.\",\n  \"SidebarCategories.CategoryMenu.DeleteModal.Title\": \"Delete this category?\",\n  \"SidebarCategories.CategoryMenu.Update\": \"Rename Category\",\n  \"SidebarTour.ManageCategories.Body\": \"Create and manage custom categories. Categories are user-specific, so moving a board to your category won’t impact other members using the same board.\",\n  \"SidebarTour.ManageCategories.Title\": \"Manage categories\",\n  \"SidebarTour.SearchForBoards.Body\": \"Open the board switcher (Cmd/Ctrl + K) to quickly search and add boards to your sidebar.\",\n  \"SidebarTour.SearchForBoards.Title\": \"Search for boards\",\n  \"SidebarTour.SidebarCategories.Body\": \"All your boards are now organized under your new sidebar. No more switching between workspaces. One-time custom categories based on your prior workspaces may have automatically been created for you as part of your v7.2 upgrade. These can be removed or edited to your preference.\",\n  \"SidebarTour.SidebarCategories.Link\": \"Learn more\",\n  \"SidebarTour.SidebarCategories.Title\": \"Sidebar categories\",\n  \"SiteStats.total_boards\": \"Total boards\",\n  \"SiteStats.total_cards\": \"Total cards\",\n  \"TableComponent.add-icon\": \"Add icon\",\n  \"TableComponent.name\": \"Name\",\n  \"TableComponent.plus-new\": \"+ New\",\n  \"TableHeaderMenu.delete\": \"Delete\",\n  \"TableHeaderMenu.duplicate\": \"Duplicate\",\n  \"TableHeaderMenu.hide\": \"Hide\",\n  \"TableHeaderMenu.insert-left\": \"Insert left\",\n  \"TableHeaderMenu.insert-right\": \"Insert right\",\n  \"TableHeaderMenu.sort-ascending\": \"Sort ascending\",\n  \"TableHeaderMenu.sort-descending\": \"Sort descending\",\n  \"TableRow.DuplicateCard\": \"duplicate card\",\n  \"TableRow.MoreOption\": \"More actions\",\n  \"TableRow.open\": \"Open\",\n  \"TopBar.give-feedback\": \"Give feedback\",\n  \"URLProperty.copiedLink\": \"Copied!\",\n  \"URLProperty.copy\": \"Copy\",\n  \"URLProperty.edit\": \"Edit\",\n  \"UndoRedoHotKeys.canRedo\": \"Redo\",\n  \"UndoRedoHotKeys.canRedo-with-description\": \"Redo {description}\",\n  \"UndoRedoHotKeys.canUndo\": \"Undo\",\n  \"UndoRedoHotKeys.canUndo-with-description\": \"Undo {description}\",\n  \"UndoRedoHotKeys.cannotRedo\": \"Nothing to Redo\",\n  \"UndoRedoHotKeys.cannotUndo\": \"Nothing to Undo\",\n  \"ValueSelector.noOptions\": \"No options. Start typing to add the first one!\",\n  \"ValueSelector.valueSelector\": \"Value selector\",\n  \"ValueSelectorLabel.openMenu\": \"Open menu\",\n  \"VersionMessage.help\": \"Check out what's new in this version.\",\n  \"VersionMessage.learn-more\": \"Learn more\",\n  \"View.AddView\": \"Add view\",\n  \"View.Board\": \"Board\",\n  \"View.DeleteView\": \"Delete view\",\n  \"View.DuplicateView\": \"Duplicate view\",\n  \"View.Gallery\": \"Gallery\",\n  \"View.NewBoardTitle\": \"Board view\",\n  \"View.NewCalendarTitle\": \"Calendar view\",\n  \"View.NewGalleryTitle\": \"Gallery view\",\n  \"View.NewTableTitle\": \"Table view\",\n  \"View.NewTemplateDefaultTitle\": \"Untitled Template\",\n  \"View.NewTemplateTitle\": \"Untitled\",\n  \"View.Table\": \"Table\",\n  \"ViewHeader.add-template\": \"New template\",\n  \"ViewHeader.delete-template\": \"Delete\",\n  \"ViewHeader.display-by\": \"Display by: {property}\",\n  \"ViewHeader.edit-template\": \"Edit\",\n  \"ViewHeader.empty-card\": \"Empty card\",\n  \"ViewHeader.export-board-archive\": \"Export board archive\",\n  \"ViewHeader.export-complete\": \"Export complete!\",\n  \"ViewHeader.export-csv\": \"Export to CSV\",\n  \"ViewHeader.export-failed\": \"Export failed!\",\n  \"ViewHeader.filter\": \"Filter\",\n  \"ViewHeader.group-by\": \"Group by: {property}\",\n  \"ViewHeader.new\": \"New\",\n  \"ViewHeader.properties\": \"Properties\",\n  \"ViewHeader.properties-menu\": \"Properties menu\",\n  \"ViewHeader.search-text\": \"Search cards\",\n  \"ViewHeader.select-a-template\": \"Select a template\",\n  \"ViewHeader.set-default-template\": \"Set as default\",\n  \"ViewHeader.sort\": \"Sort\",\n  \"ViewHeader.untitled\": \"Untitled\",\n  \"ViewHeader.view-header-menu\": \"View header menu\",\n  \"ViewHeader.view-menu\": \"View menu\",\n  \"ViewLimitDialog.Heading\": \"Views per board limit reached\",\n  \"ViewLimitDialog.PrimaryButton.Title.Admin\": \"Upgrade\",\n  \"ViewLimitDialog.PrimaryButton.Title.RegularUser\": \"Notify Admin\",\n  \"ViewLimitDialog.Subtext.Admin\": \"Upgrade to our Professional or Enterprise plan.\",\n  \"ViewLimitDialog.Subtext.Admin.PricingPageLink\": \"Learn more about our plans.\",\n  \"ViewLimitDialog.Subtext.RegularUser\": \"Notify your Admin to upgrade to our Professional or Enterprise plan.\",\n  \"ViewLimitDialog.UpgradeImg.AltText\": \"upgrade image\",\n  \"ViewLimitDialog.notifyAdmin.Success\": \"Your admin has been notified\",\n  \"ViewTitle.hide-description\": \"hide description\",\n  \"ViewTitle.pick-icon\": \"Pick icon\",\n  \"ViewTitle.random-icon\": \"Random\",\n  \"ViewTitle.remove-icon\": \"Remove icon\",\n  \"ViewTitle.show-description\": \"show description\",\n  \"ViewTitle.untitled-board\": \"Untitled board\",\n  \"WelcomePage.Description\": \"Boards is a project management tool that helps define, organize, track, and manage work across teams using a familiar Kanban board view.\",\n  \"WelcomePage.Explore.Button\": \"Take a tour\",\n  \"WelcomePage.Heading\": \"Welcome To Boards\",\n  \"WelcomePage.NoThanks.Text\": \"No thanks, I'll figure it out myself\",\n  \"WelcomePage.StartUsingIt.Text\": \"Start using it\",\n  \"Workspace.editing-board-template\": \"You're editing a board template.\",\n  \"badge.guest\": \"Guest\",\n  \"boardPage.confirm-join-button\": \"Join\",\n  \"boardPage.confirm-join-text\": \"You are about to join a private board without explicitly being added by the board admin. Are you sure you wish to join this private board?\",\n  \"boardPage.confirm-join-title\": \"Join private board\",\n  \"boardSelector.confirm-link-board\": \"Link board to channel\",\n  \"boardSelector.confirm-link-board-button\": \"Yes, link board\",\n  \"boardSelector.confirm-link-board-subtext\": \"When you link \\\"{boardName}\\\" to the channel, all members of the channel (existing and new) will be able to edit it. This excludes members who are guests. You can unlink a board from a channel at any time.\",\n  \"boardSelector.confirm-link-board-subtext-with-other-channel\": \"When you link \\\"{boardName}\\\" to the channel, all members of the channel (existing and new) will be able to edit it. This excludes members who are guests.{lineBreak} This board is currently linked to another channel. It will be unlinked if you choose to link it here.\",\n  \"boardSelector.create-a-board\": \"Create a board\",\n  \"boardSelector.link\": \"Link\",\n  \"boardSelector.search-for-boards\": \"Search for boards\",\n  \"boardSelector.title\": \"Link boards\",\n  \"boardSelector.unlink\": \"Unlink\",\n  \"calendar.month\": \"Month\",\n  \"calendar.today\": \"TODAY\",\n  \"calendar.week\": \"Week\",\n  \"centerPanel.undefined\": \"No {propertyName}\",\n  \"centerPanel.unknown-user\": \"Unknown user\",\n  \"cloudMessage.learn-more\": \"Learn more\",\n  \"createImageBlock.failed\": \"This file couldn't be uploaded as the file size limit has been reached.\",\n  \"default-properties.badges\": \"Comments and description\",\n  \"default-properties.title\": \"Title\",\n  \"error.back-to-home\": \"Back to home\",\n  \"error.back-to-team\": \"Back to team\",\n  \"error.board-not-found\": \"Board not found.\",\n  \"error.go-login\": \"Log in\",\n  \"error.invalid-read-only-board\": \"You don't have access to this board. Log in to access Boards.\",\n  \"error.not-logged-in\": \"Your session may have expired or you're not logged in. Log in again to access Boards.\",\n  \"error.page.title\": \"Sorry, something went wrong\",\n  \"error.team-undefined\": \"Not a valid team.\",\n  \"error.unknown\": \"An error occurred.\",\n  \"generic.previous\": \"Previous\",\n  \"guest-no-board.subtitle\": \"You don't have access to any board in this team yet, please wait until somebody adds you to any board.\",\n  \"guest-no-board.title\": \"No boards yet\",\n  \"imagePaste.upload-failed\": \"Some files weren't uploaded because the file size limit has been reached.\",\n  \"limitedCard.title\": \"Cards hidden\",\n  \"login.log-in-button\": \"Log in\",\n  \"login.log-in-title\": \"Log in\",\n  \"login.register-button\": \"or create an account if you don't have one\",\n  \"new_channel_modal.create_board.empty_board_description\": \"Create a new empty board\",\n  \"new_channel_modal.create_board.empty_board_title\": \"Empty board\",\n  \"new_channel_modal.create_board.select_template_placeholder\": \"Select a template\",\n  \"new_channel_modal.create_board.title\": \"Create a board for this channel\",\n  \"notification-box-card-limit-reached.close-tooltip\": \"Snooze for 10 days\",\n  \"notification-box-card-limit-reached.contact-link\": \"notify your admin\",\n  \"notification-box-card-limit-reached.link\": \"Upgrade to a paid plan\",\n  \"notification-box-card-limit-reached.title\": \"{cards} cards hidden from board\",\n  \"notification-box-cards-hidden.title\": \"This action has hidden another card\",\n  \"notification-box.card-limit-reached.not-admin.text\": \"To access archived cards, you can {contactLink} to upgrade to a paid plan.\",\n  \"notification-box.card-limit-reached.text\": \"Card limit reached, to view older cards, {link}\",\n  \"person.add-user-to-board\": \"Add {username} to board\",\n  \"person.add-user-to-board-confirm-button\": \"Add to board\",\n  \"person.add-user-to-board-permissions\": \"Permissions\",\n  \"person.add-user-to-board-question\": \"Do you want to add {username} to the board?\",\n  \"person.add-user-to-board-warning\": \"{username} isn't a member of the board, and won't receive any notifications about it.\",\n  \"register.login-button\": \"or log in if you already have an account\",\n  \"register.signup-title\": \"Sign up for your account\",\n  \"rhs-board-non-admin-msg\": \"You're not an admin of the board\",\n  \"rhs-boards.add\": \"Add\",\n  \"rhs-boards.dm\": \"DM\",\n  \"rhs-boards.gm\": \"GM\",\n  \"rhs-boards.header.dm\": \"this direct message\",\n  \"rhs-boards.header.gm\": \"this group message\",\n  \"rhs-boards.last-update-at\": \"Last update at: {datetime}\",\n  \"rhs-boards.link-boards-to-channel\": \"Link boards to {channelName}\",\n  \"rhs-boards.linked-boards\": \"Linked boards\",\n  \"rhs-boards.no-boards-linked-to-channel\": \"No boards are linked to {channelName} yet\",\n  \"rhs-boards.no-boards-linked-to-channel-description\": \"Boards is a project management tool that helps define, organize, track and manage work across teams, using a familiar kanban board view.\",\n  \"rhs-boards.unlink-board\": \"Unlink board\",\n  \"rhs-boards.unlink-board1\": \"Unlink board\",\n  \"rhs-channel-boards-header.title\": \"Boards\",\n  \"share-board.publish\": \"Publish\",\n  \"share-board.share\": \"Share\",\n  \"shareBoard.channels-select-group\": \"Channels\",\n  \"shareBoard.confirm-change-team-role.body\": \"Everyone on this board with a lower permission than the \\\"{role}\\\" role will <b>now be promoted to {role}</b>. Are you sure you want to change the minimum role for the board?\",\n  \"shareBoard.confirm-change-team-role.confirmBtnText\": \"Change minimum board role\",\n  \"shareBoard.confirm-change-team-role.title\": \"Change minimum board role\",\n  \"shareBoard.confirm-link-channel\": \"Link board to channel\",\n  \"shareBoard.confirm-link-channel-button\": \"Link channel\",\n  \"shareBoard.confirm-link-channel-button-with-other-channel\": \"Unlink and link here\",\n  \"shareBoard.confirm-link-channel-subtext\": \"When you link a channel to a board, all members of the channel (existing and new) will be able to edit it. This excludes members who are guests.\",\n  \"shareBoard.confirm-link-channel-subtext-with-other-channel\": \"When you link a channel to a board, all members of the channel (existing and new) will be able to edit it. This excludes members who are guests.{lineBreak}This board is currently linked to another channel. It will be unlinked if you choose to link it here.\",\n  \"shareBoard.confirm-unlink.body\": \"When you unlink a channel from a board, all members of the channel (existing and new) will lose access to it unless they're given permission separately.\",\n  \"shareBoard.confirm-unlink.confirmBtnText\": \"Unlink channel\",\n  \"shareBoard.confirm-unlink.title\": \"Unlink channel from board\",\n  \"shareBoard.lastAdmin\": \"Boards must have at least one Administrator\",\n  \"shareBoard.members-select-group\": \"Members\",\n  \"shareBoard.unknown-channel-display-name\": \"Unknown channel\",\n  \"tutorial_tip.finish_tour\": \"Done\",\n  \"tutorial_tip.got_it\": \"Got it\",\n  \"tutorial_tip.ok\": \"Next\",\n  \"tutorial_tip.out\": \"Opt out of these tips.\",\n  \"tutorial_tip.seen\": \"Seen this before?\"\n}\n"
  },
  {
    "path": "webapp/i18n/en_AU.json",
    "content": "{\n  \"AdminBadge.SystemAdmin\": \"Admin\",\n  \"AdminBadge.TeamAdmin\": \"Team Admin\",\n  \"AppBar.Tooltip\": \"Toggle Linked Boards\",\n  \"Attachment.Attachment-title\": \"Attachment\",\n  \"AttachmentBlock.DeleteAction\": \"delete\",\n  \"AttachmentBlock.addElement\": \"add {type}\",\n  \"AttachmentBlock.delete\": \"Attachment deleted.\",\n  \"AttachmentBlock.failed\": \"This file couldn't be uploaded as the file size limit has been reached.\",\n  \"AttachmentBlock.upload\": \"Attachment uploading.\",\n  \"AttachmentBlock.uploadSuccess\": \"Attachment uploaded.\",\n  \"AttachmentElement.delete-confirmation-dialog-button-text\": \"Delete\",\n  \"AttachmentElement.download\": \"Download\",\n  \"AttachmentElement.upload-percentage\": \"Uploading...({uploadPercent}%)\",\n  \"BoardComponent.add-a-group\": \"+ Add a group\",\n  \"BoardComponent.delete\": \"Delete\",\n  \"BoardComponent.hidden-columns\": \"Hidden columns\",\n  \"BoardComponent.hide\": \"Hide\",\n  \"BoardComponent.new\": \"+ New\",\n  \"BoardComponent.no-property\": \"No {property}\",\n  \"BoardComponent.no-property-title\": \"Items with an empty {property} property will go here. This column can't be removed.\",\n  \"BoardComponent.show\": \"Show\",\n  \"BoardMember.schemeAdmin\": \"Admin\",\n  \"BoardMember.schemeCommenter\": \"Commenter\",\n  \"BoardMember.schemeEditor\": \"Editor\",\n  \"BoardMember.schemeNone\": \"None\",\n  \"BoardMember.schemeViewer\": \"Viewer\",\n  \"BoardMember.unlinkChannel\": \"Unlink\",\n  \"BoardPage.newVersion\": \"A new version of Boards is available, click here to reload.\",\n  \"BoardPage.syncFailed\": \"Board may be deleted or access revoked.\",\n  \"BoardTemplateSelector.add-template\": \"Create new template\",\n  \"BoardTemplateSelector.create-empty-board\": \"Create an empty board\",\n  \"BoardTemplateSelector.delete-template\": \"Delete\",\n  \"BoardTemplateSelector.description\": \"Add a board to the sidebar using any of the templates defined below or start from scratch.\",\n  \"BoardTemplateSelector.edit-template\": \"Edit\",\n  \"BoardTemplateSelector.plugin.no-content-description\": \"Add a board to the sidebar using any of the templates defined below or start from scratch.\",\n  \"BoardTemplateSelector.plugin.no-content-title\": \"Create a board\",\n  \"BoardTemplateSelector.title\": \"Create a board\",\n  \"BoardTemplateSelector.use-this-template\": \"Use this template\",\n  \"BoardsSwitcher.Title\": \"Find boards\",\n  \"BoardsUnfurl.Limited\": \"Additional details are hidden due to the card being archived\",\n  \"BoardsUnfurl.Remainder\": \"+{remainder} more\",\n  \"BoardsUnfurl.Updated\": \"Updated {time}\",\n  \"Calculations.Options.average.displayName\": \"Average\",\n  \"Calculations.Options.average.label\": \"Average\",\n  \"Calculations.Options.count.displayName\": \"Count\",\n  \"Calculations.Options.count.label\": \"Count\",\n  \"Calculations.Options.countChecked.displayName\": \"Checked\",\n  \"Calculations.Options.countChecked.label\": \"Count checked\",\n  \"Calculations.Options.countUnchecked.displayName\": \"Unchecked\",\n  \"Calculations.Options.countUnchecked.label\": \"Count unchecked\",\n  \"Calculations.Options.countUniqueValue.displayName\": \"Unique\",\n  \"Calculations.Options.countUniqueValue.label\": \"Count unique values\",\n  \"Calculations.Options.countValue.displayName\": \"Values\",\n  \"Calculations.Options.countValue.label\": \"Count value\",\n  \"Calculations.Options.dateRange.displayName\": \"Range\",\n  \"Calculations.Options.dateRange.label\": \"Range\",\n  \"Calculations.Options.earliest.displayName\": \"Earliest\",\n  \"Calculations.Options.earliest.label\": \"Earliest\",\n  \"Calculations.Options.latest.displayName\": \"Latest\",\n  \"Calculations.Options.latest.label\": \"Latest\",\n  \"Calculations.Options.max.displayName\": \"Max\",\n  \"Calculations.Options.max.label\": \"Max\",\n  \"Calculations.Options.median.displayName\": \"Median\",\n  \"Calculations.Options.median.label\": \"Median\",\n  \"Calculations.Options.min.displayName\": \"Min\",\n  \"Calculations.Options.min.label\": \"Min\",\n  \"Calculations.Options.none.displayName\": \"Calculate\",\n  \"Calculations.Options.none.label\": \"None\",\n  \"Calculations.Options.percentChecked.displayName\": \"Checked\",\n  \"Calculations.Options.percentChecked.label\": \"Percent checked\",\n  \"Calculations.Options.percentUnchecked.displayName\": \"Unchecked\",\n  \"Calculations.Options.percentUnchecked.label\": \"Percent unchecked\",\n  \"Calculations.Options.range.displayName\": \"Range\",\n  \"Calculations.Options.range.label\": \"Range\",\n  \"Calculations.Options.sum.displayName\": \"Sum\",\n  \"Calculations.Options.sum.label\": \"Sum\",\n  \"CalendarCard.untitled\": \"Untitled\",\n  \"CardActionsMenu.copiedLink\": \"Copied\",\n  \"CardActionsMenu.copyLink\": \"Copy link\",\n  \"CardActionsMenu.delete\": \"Delete\",\n  \"CardActionsMenu.duplicate\": \"Duplicate\",\n  \"CardBadges.title-checkboxes\": \"Checkboxes\",\n  \"CardBadges.title-comments\": \"Comments\",\n  \"CardBadges.title-description\": \"This card has a description\",\n  \"CardDetail.Attach\": \"Attach\",\n  \"CardDetail.Follow\": \"Follow\",\n  \"CardDetail.Following\": \"Following\",\n  \"CardDetail.add-content\": \"Add content\",\n  \"CardDetail.add-icon\": \"Add icon\",\n  \"CardDetail.add-property\": \"+ Add a property\",\n  \"CardDetail.addCardText\": \"add card text\",\n  \"CardDetail.limited-body\": \"Upgrade to the Professional or Enterprise plan.\",\n  \"CardDetail.limited-button\": \"Upgrade\",\n  \"CardDetail.limited-title\": \"This card is hidden\",\n  \"CardDetail.moveContent\": \"Move card content\",\n  \"CardDetail.new-comment-placeholder\": \"Add a comment\",\n  \"CardDetailProperty.confirm-delete-heading\": \"Confirm property deletion\",\n  \"CardDetailProperty.confirm-delete-subtext\": \"Are you sure you want to delete the property '{propertyName}'? This will remove the property from all cards in this board.\",\n  \"CardDetailProperty.confirm-property-name-change-subtext\": \"Are you sure you want to change property '{propertyName}' {customText}? This will affect value(s) across {numOfCards} card(s) in this board, and may result in loss of data.\",\n  \"CardDetailProperty.confirm-property-type-change\": \"Confirm change of property type\",\n  \"CardDetailProperty.delete-action-button\": \"Delete\",\n  \"CardDetailProperty.property-change-action-button\": \"Change property\",\n  \"CardDetailProperty.property-changed\": \"Property changed successfully!\",\n  \"CardDetailProperty.property-deleted\": \"{propertyName} deleted successfully!\",\n  \"CardDetailProperty.property-name-change-subtext\": \"type from '{oldPropType}' to '{newPropType}'\",\n  \"CardDetial.limited-link\": \"Learn more about our plans.\",\n  \"CardDialog.delete-confirmation-dialog-attachment\": \"Confirm attachment deletion\",\n  \"CardDialog.delete-confirmation-dialog-button-text\": \"Delete\",\n  \"CardDialog.delete-confirmation-dialog-heading\": \"Confirm card deletion\",\n  \"CardDialog.editing-template\": \"You're editing a template.\",\n  \"CardDialog.nocard\": \"This card doesn't exist or is inaccessible.\",\n  \"Categories.CreateCategoryDialog.CancelText\": \"Cancel\",\n  \"Categories.CreateCategoryDialog.CreateText\": \"Create\",\n  \"Categories.CreateCategoryDialog.Placeholder\": \"Name your category\",\n  \"Categories.CreateCategoryDialog.UpdateText\": \"Update\",\n  \"CenterPanel.Login\": \"Login\",\n  \"CenterPanel.Share\": \"Share\",\n  \"ChannelIntro.CreateBoard\": \"Create a board\",\n  \"ColorOption.selectColor\": \"Select {color} Colour\",\n  \"Comment.delete\": \"Delete\",\n  \"CommentsList.send\": \"Send\",\n  \"ConfirmPerson.empty\": \"Empty\",\n  \"ConfirmPerson.search\": \"Search...\",\n  \"ConfirmationDialog.cancel-action\": \"Cancel\",\n  \"ConfirmationDialog.confirm-action\": \"Confirm\",\n  \"ContentBlock.Delete\": \"Delete\",\n  \"ContentBlock.DeleteAction\": \"delete\",\n  \"ContentBlock.addElement\": \"add {type}\",\n  \"ContentBlock.checkbox\": \"checkbox\",\n  \"ContentBlock.divider\": \"divider\",\n  \"ContentBlock.editCardCheckbox\": \"toggled-checkbox\",\n  \"ContentBlock.editCardCheckboxText\": \"edit card text\",\n  \"ContentBlock.editCardText\": \"edit card text\",\n  \"ContentBlock.editText\": \"Edit text\",\n  \"ContentBlock.image\": \"image\",\n  \"ContentBlock.insertAbove\": \"Insert above\",\n  \"ContentBlock.moveBlock\": \"move card content\",\n  \"ContentBlock.moveDown\": \"Move down\",\n  \"ContentBlock.moveUp\": \"Move up\",\n  \"ContentBlock.text\": \"text\",\n  \"DateFilter.empty\": \"Empty\",\n  \"DateRange.clear\": \"Clear\",\n  \"DateRange.empty\": \"Empty\",\n  \"DateRange.endDate\": \"End date\",\n  \"DateRange.today\": \"Today\",\n  \"DeleteBoardDialog.confirm-cancel\": \"Cancel\",\n  \"DeleteBoardDialog.confirm-delete\": \"Delete\",\n  \"DeleteBoardDialog.confirm-info\": \"Are you sure you want to delete the board '{boardTitle}'? This will remove all cards in the board.\",\n  \"DeleteBoardDialog.confirm-info-template\": \"Are you sure you want to delete the board template ‘{boardTitle}’?\",\n  \"DeleteBoardDialog.confirm-tite\": \"Confirm board deletion\",\n  \"DeleteBoardDialog.confirm-tite-template\": \"Confirm deletion of board template\",\n  \"Dialog.closeDialog\": \"Close dialog\",\n  \"EditableDayPicker.today\": \"Today\",\n  \"Error.mobileweb\": \"Mobile web support is currently in early beta. Not all functionality may be present.\",\n  \"Error.websocket-closed\": \"Websocket connection closed, connection interrupted. If this persists, check your server or web proxy configuration.\",\n  \"Filter.contains\": \"contains\",\n  \"Filter.ends-with\": \"ends with\",\n  \"Filter.includes\": \"includes\",\n  \"Filter.is\": \"is\",\n  \"Filter.is-after\": \"is after\",\n  \"Filter.is-before\": \"is before\",\n  \"Filter.is-empty\": \"is empty\",\n  \"Filter.is-not-empty\": \"is not empty\",\n  \"Filter.is-not-set\": \"is not set\",\n  \"Filter.is-set\": \"is set\",\n  \"Filter.isafter\": \"is after\",\n  \"Filter.isbefore\": \"is before\",\n  \"Filter.not-contains\": \"does not contain\",\n  \"Filter.not-ends-with\": \"does not end with\",\n  \"Filter.not-includes\": \"doesn't include\",\n  \"Filter.not-starts-with\": \"does not start with\",\n  \"Filter.starts-with\": \"starts with\",\n  \"FilterByText.placeholder\": \"filter text\",\n  \"FilterComponent.add-filter\": \"+ Add filter\",\n  \"FilterComponent.delete\": \"Delete\",\n  \"FilterValue.empty\": \"(empty)\",\n  \"FindBoardsDialog.IntroText\": \"Search for boards\",\n  \"FindBoardsDialog.NoResultsFor\": \"No results for '\\\\{searchQuery}'\\\\\",\n  \"FindBoardsDialog.NoResultsSubtext\": \"Check the spelling or try another search.\",\n  \"FindBoardsDialog.SubTitle\": \"Type to find a board. Use <b>UP/DOWN</b> to browse. <b>ENTER</b> to select, <b>ESC</b> to dismiss\",\n  \"FindBoardsDialog.Title\": \"Find Boards\",\n  \"GroupBy.hideEmptyGroups\": \"Hide {count} empty groups\",\n  \"GroupBy.showHiddenGroups\": \"Show {count} hidden groups\",\n  \"GroupBy.ungroup\": \"Ungroup\",\n  \"HideBoard.MenuOption\": \"Hide board\",\n  \"KanbanCard.untitled\": \"Untitled\",\n  \"MentionSuggestion.is-not-board-member\": \"(not board member)\",\n  \"Mutator.new-board-from-template\": \"new board from template\",\n  \"Mutator.new-card-from-template\": \"new card from template\",\n  \"Mutator.new-template-from-card\": \"new template from card\",\n  \"OnboardingTour.AddComments.Body\": \"You can comment on issues, and even @mention your fellow Mattermost users to get their attention.\",\n  \"OnboardingTour.AddComments.Title\": \"Add comments\",\n  \"OnboardingTour.AddDescription.Body\": \"Add a description to your card so your teammates know what the card is about.\",\n  \"OnboardingTour.AddDescription.Title\": \"Add description\",\n  \"OnboardingTour.AddProperties.Body\": \"Add various properties to cards to make them more powerful.\",\n  \"OnboardingTour.AddProperties.Title\": \"Add properties\",\n  \"OnboardingTour.AddView.Body\": \"Go here to create a new view to organise your board using different layouts.\",\n  \"OnboardingTour.AddView.Title\": \"Add a new view\",\n  \"OnboardingTour.CopyLink.Body\": \"You can share your cards with teammates by copying the link and pasting it in a channel, direct message or group message.\",\n  \"OnboardingTour.CopyLink.Title\": \"Copy link\",\n  \"OnboardingTour.OpenACard.Body\": \"Open a card to explore the powerful ways that Boards can help you organise your work.\",\n  \"OnboardingTour.OpenACard.Title\": \"Open a card\",\n  \"OnboardingTour.ShareBoard.Body\": \"You can share your board internally, within your team or publish it publicly for visibility outside of your organisation.\",\n  \"OnboardingTour.ShareBoard.Title\": \"Share board\",\n  \"PersonProperty.board-members\": \"Board members\",\n  \"PersonProperty.me\": \"Me\",\n  \"PersonProperty.non-board-members\": \"Not board members\",\n  \"PropertyMenu.Delete\": \"Delete\",\n  \"PropertyMenu.changeType\": \"Change property type\",\n  \"PropertyMenu.selectType\": \"Select property type\",\n  \"PropertyMenu.typeTitle\": \"Type\",\n  \"PropertyType.Checkbox\": \"Checkbox\",\n  \"PropertyType.CreatedBy\": \"Created by\",\n  \"PropertyType.CreatedTime\": \"Time created\",\n  \"PropertyType.Date\": \"Date\",\n  \"PropertyType.Email\": \"Email\",\n  \"PropertyType.MultiPerson\": \"Multi person\",\n  \"PropertyType.MultiSelect\": \"Multi select\",\n  \"PropertyType.Number\": \"Number\",\n  \"PropertyType.Person\": \"Person\",\n  \"PropertyType.Phone\": \"Phone\",\n  \"PropertyType.Select\": \"Select\",\n  \"PropertyType.Text\": \"Text\",\n  \"PropertyType.Unknown\": \"Unknown\",\n  \"PropertyType.UpdatedBy\": \"Last updated by\",\n  \"PropertyType.UpdatedTime\": \"Time last updated\",\n  \"PropertyType.Url\": \"URL\",\n  \"PropertyValueElement.empty\": \"Empty\",\n  \"RegistrationLink.confirmRegenerateToken\": \"This will invalidate previously shared links. Continue?\",\n  \"RegistrationLink.copiedLink\": \"Copied!\",\n  \"RegistrationLink.copyLink\": \"Copy link\",\n  \"RegistrationLink.description\": \"Share this link for others to create accounts:\",\n  \"RegistrationLink.regenerateToken\": \"Regenerate token\",\n  \"RegistrationLink.tokenRegenerated\": \"Registration link regenerated\",\n  \"ShareBoard.PublishDescription\": \"Publish and share a read-only link with everyone on the web.\",\n  \"ShareBoard.PublishTitle\": \"Publish to the web\",\n  \"ShareBoard.ShareInternal\": \"Share internally\",\n  \"ShareBoard.ShareInternalDescription\": \"Users who have permissions will be able to use this link.\",\n  \"ShareBoard.Title\": \"Share Board\",\n  \"ShareBoard.confirmRegenerateToken\": \"This will invalidate previously shared links. Continue?\",\n  \"ShareBoard.copiedLink\": \"Copied!\",\n  \"ShareBoard.copyLink\": \"Copy link\",\n  \"ShareBoard.regenerate\": \"Regenerate token\",\n  \"ShareBoard.searchPlaceholder\": \"Search for people and channels\",\n  \"ShareBoard.teamPermissionsText\": \"Everyone at {teamName} team\",\n  \"ShareBoard.tokenRegenrated\": \"Token regenerated\",\n  \"ShareBoard.userPermissionsRemoveMemberText\": \"Remove member\",\n  \"ShareBoard.userPermissionsYouText\": \"(You)\",\n  \"ShareTemplate.Title\": \"Share template\",\n  \"ShareTemplate.searchPlaceholder\": \"Search for people\",\n  \"Sidebar.about\": \"About Focalboard\",\n  \"Sidebar.add-board\": \"+ Add board\",\n  \"Sidebar.changePassword\": \"Change password\",\n  \"Sidebar.delete-board\": \"Delete board\",\n  \"Sidebar.duplicate-board\": \"Duplicate board\",\n  \"Sidebar.export-archive\": \"Export archive\",\n  \"Sidebar.import\": \"Import\",\n  \"Sidebar.import-archive\": \"Import archive\",\n  \"Sidebar.invite-users\": \"Invite users\",\n  \"Sidebar.logout\": \"Log out\",\n  \"Sidebar.new-category.badge\": \"New\",\n  \"Sidebar.new-category.drag-boards-cta\": \"Drag boards here...\",\n  \"Sidebar.no-boards-in-category\": \"No boards inside\",\n  \"Sidebar.product-tour\": \"Product tour\",\n  \"Sidebar.random-icons\": \"Random icons\",\n  \"Sidebar.set-language\": \"Set language\",\n  \"Sidebar.set-theme\": \"Set theme\",\n  \"Sidebar.settings\": \"Settings\",\n  \"Sidebar.template-from-board\": \"New template from board\",\n  \"Sidebar.untitled-board\": \"(Untitled Board)\",\n  \"Sidebar.untitled-view\": \"(Untitled View)\",\n  \"SidebarCategories.BlocksMenu.Move\": \"Move To...\",\n  \"SidebarCategories.CategoryMenu.CreateNew\": \"Create New Category\",\n  \"SidebarCategories.CategoryMenu.Delete\": \"Delete Category\",\n  \"SidebarCategories.CategoryMenu.DeleteModal.Body\": \"Boards in <b>{categoryName}</b> will move back to the Boards categories. You're not removed from any boards.\",\n  \"SidebarCategories.CategoryMenu.DeleteModal.Title\": \"Delete this category?\",\n  \"SidebarCategories.CategoryMenu.Update\": \"Rename Category\",\n  \"SidebarTour.ManageCategories.Body\": \"Create and manage custom categories. Categories are user-specific, so moving a board to your category won’t impact other members using the same board.\",\n  \"SidebarTour.ManageCategories.Title\": \"Manage categories\",\n  \"SidebarTour.SearchForBoards.Body\": \"Open the board switcher (Cmd/Ctrl + K) to quickly search and add boards to your sidebar.\",\n  \"SidebarTour.SearchForBoards.Title\": \"Search for boards\",\n  \"SidebarTour.SidebarCategories.Body\": \"All your boards are now organised under your new sidebar. No more switching between workspaces. One-time custom categories based on your prior workspaces may have automatically been created for you as part of your v7.2 upgrade. These can be removed or edited to your preference.\",\n  \"SidebarTour.SidebarCategories.Link\": \"Learn more\",\n  \"SidebarTour.SidebarCategories.Title\": \"Sidebar categories\",\n  \"SiteStats.total_boards\": \"Total boards\",\n  \"SiteStats.total_cards\": \"Total cards\",\n  \"TableComponent.add-icon\": \"Add icon\",\n  \"TableComponent.name\": \"Name\",\n  \"TableComponent.plus-new\": \"+ New\",\n  \"TableHeaderMenu.delete\": \"Delete\",\n  \"TableHeaderMenu.duplicate\": \"Duplicate\",\n  \"TableHeaderMenu.hide\": \"Hide\",\n  \"TableHeaderMenu.insert-left\": \"Insert left\",\n  \"TableHeaderMenu.insert-right\": \"Insert right\",\n  \"TableHeaderMenu.sort-ascending\": \"Sort ascending\",\n  \"TableHeaderMenu.sort-descending\": \"Sort descending\",\n  \"TableRow.DuplicateCard\": \"duplicate card\",\n  \"TableRow.MoreOption\": \"More actions\",\n  \"TableRow.open\": \"Open\",\n  \"TopBar.give-feedback\": \"Give feedback\",\n  \"URLProperty.copiedLink\": \"Copied\",\n  \"URLProperty.copy\": \"Copy\",\n  \"URLProperty.edit\": \"Edit\",\n  \"UndoRedoHotKeys.canRedo\": \"Redo\",\n  \"UndoRedoHotKeys.canRedo-with-description\": \"Redo {description}\",\n  \"UndoRedoHotKeys.canUndo\": \"Undo\",\n  \"UndoRedoHotKeys.canUndo-with-description\": \"Undo {description}\",\n  \"UndoRedoHotKeys.cannotRedo\": \"Nothing to Redo\",\n  \"UndoRedoHotKeys.cannotUndo\": \"Nothing to Undo\",\n  \"ValueSelector.noOptions\": \"No options. Start typing to add the first one!\",\n  \"ValueSelector.valueSelector\": \"Value selector\",\n  \"ValueSelectorLabel.openMenu\": \"Open menu\",\n  \"VersionMessage.help\": \"Check out what's new in this version.\",\n  \"VersionMessage.learn-more\": \"Learn more\",\n  \"View.AddView\": \"Add view\",\n  \"View.Board\": \"Board\",\n  \"View.DeleteView\": \"Delete view\",\n  \"View.DuplicateView\": \"Duplicate view\",\n  \"View.Gallery\": \"Gallery\",\n  \"View.NewBoardTitle\": \"Board view\",\n  \"View.NewCalendarTitle\": \"Calendar view\",\n  \"View.NewGalleryTitle\": \"Gallery view\",\n  \"View.NewTableTitle\": \"Table view\",\n  \"View.NewTemplateDefaultTitle\": \"Untitled Template\",\n  \"View.NewTemplateTitle\": \"Untitled\",\n  \"View.Table\": \"Table\",\n  \"ViewHeader.add-template\": \"New template\",\n  \"ViewHeader.delete-template\": \"Delete\",\n  \"ViewHeader.display-by\": \"Display by: {property}\",\n  \"ViewHeader.edit-template\": \"Edit\",\n  \"ViewHeader.empty-card\": \"Empty card\",\n  \"ViewHeader.export-board-archive\": \"Export board archive\",\n  \"ViewHeader.export-complete\": \"Export complete!\",\n  \"ViewHeader.export-csv\": \"Export to CSV\",\n  \"ViewHeader.export-failed\": \"Export failed\",\n  \"ViewHeader.filter\": \"Filter\",\n  \"ViewHeader.group-by\": \"Group by: {property}\",\n  \"ViewHeader.new\": \"New\",\n  \"ViewHeader.properties\": \"Properties\",\n  \"ViewHeader.properties-menu\": \"Properties menu\",\n  \"ViewHeader.search-text\": \"Search cards\",\n  \"ViewHeader.select-a-template\": \"Select a template\",\n  \"ViewHeader.set-default-template\": \"Set as default\",\n  \"ViewHeader.sort\": \"Sort\",\n  \"ViewHeader.untitled\": \"Untitled\",\n  \"ViewHeader.view-header-menu\": \"View header menu\",\n  \"ViewHeader.view-menu\": \"View menu\",\n  \"ViewLimitDialog.Heading\": \"Views per board limit reached\",\n  \"ViewLimitDialog.PrimaryButton.Title.Admin\": \"Upgrade\",\n  \"ViewLimitDialog.PrimaryButton.Title.RegularUser\": \"Notify Admin\",\n  \"ViewLimitDialog.Subtext.Admin\": \"Upgrade to the Professional or Enterprise plan.\",\n  \"ViewLimitDialog.Subtext.Admin.PricingPageLink\": \"Learn more about our plans.\",\n  \"ViewLimitDialog.Subtext.RegularUser\": \"Ask your Admin to upgrade to the Professional or Enterprise plan.\",\n  \"ViewLimitDialog.UpgradeImg.AltText\": \"upgrade image\",\n  \"ViewLimitDialog.notifyAdmin.Success\": \"Your admin has been contacted\",\n  \"ViewTitle.hide-description\": \"hide description\",\n  \"ViewTitle.pick-icon\": \"Pick icon\",\n  \"ViewTitle.random-icon\": \"Random\",\n  \"ViewTitle.remove-icon\": \"Remove icon\",\n  \"ViewTitle.show-description\": \"show description\",\n  \"ViewTitle.untitled-board\": \"Untitled board\",\n  \"WelcomePage.Description\": \"Boards is a project management tool that helps define, organise, track and manage work across teams using a familiar Kanban board view.\",\n  \"WelcomePage.Explore.Button\": \"Take a tour\",\n  \"WelcomePage.Heading\": \"Welcome To Boards\",\n  \"WelcomePage.NoThanks.Text\": \"No thanks, I'll figure it out myself\",\n  \"WelcomePage.StartUsingIt.Text\": \"Start using it\",\n  \"Workspace.editing-board-template\": \"You're editing a board template.\",\n  \"badge.guest\": \"Guest\",\n  \"boardPage.confirm-join-button\": \"Join\",\n  \"boardPage.confirm-join-text\": \"You are about to join a private board without explicitly being added by the board admin. Are you sure you wish to join this private board?\",\n  \"boardPage.confirm-join-title\": \"Join private board\",\n  \"boardSelector.confirm-link-board\": \"Link board to channel\",\n  \"boardSelector.confirm-link-board-button\": \"Link board\",\n  \"boardSelector.confirm-link-board-subtext\": \"When you link '\\\\{boardName}'\\\\ to the channel, all members of the channel (existing and new) will be able to edit it. This excludes members who are guests. You can unlink a board from a channel at any time.\",\n  \"boardSelector.confirm-link-board-subtext-with-other-channel\": \"When you link '\\\\{boardName}'\\\\ to the channel, all members of the channel (existing and new) will be able to edit it. This excludes members who are guests.{lineBreak} This board is currently linked to another channel. It will be unlinked if you choose to link it here.\",\n  \"boardSelector.create-a-board\": \"Create a board\",\n  \"boardSelector.link\": \"Link\",\n  \"boardSelector.search-for-boards\": \"Search for boards\",\n  \"boardSelector.title\": \"Link boards\",\n  \"boardSelector.unlink\": \"Unlink\",\n  \"calendar.month\": \"Month\",\n  \"calendar.today\": \"TODAY\",\n  \"calendar.week\": \"Week\",\n  \"centerPanel.undefined\": \"No {propertyName}\",\n  \"centerPanel.unknown-user\": \"Unknown user\",\n  \"cloudMessage.learn-more\": \"Learn more\",\n  \"createImageBlock.failed\": \"This file couldn't be uploaded as the file size limit has been reached.\",\n  \"default-properties.badges\": \"Comments and description\",\n  \"default-properties.title\": \"Title\",\n  \"error.back-to-home\": \"Back to home\",\n  \"error.back-to-team\": \"Back to team\",\n  \"error.board-not-found\": \"Board not found.\",\n  \"error.go-login\": \"Log in\",\n  \"error.invalid-read-only-board\": \"You don't have access to this board. Log in to access Boards.\",\n  \"error.not-logged-in\": \"Your session may have expired or you're not logged in. Log in again to access Boards.\",\n  \"error.page.title\": \"An error occurred\",\n  \"error.team-undefined\": \"Invalid team.\",\n  \"error.unknown\": \"An error occurred.\",\n  \"generic.previous\": \"Previous\",\n  \"guest-no-board.subtitle\": \"You don't have access to any board in this team yet, please wait until somebody adds you to any board.\",\n  \"guest-no-board.title\": \"No boards yet\",\n  \"imagePaste.upload-failed\": \"Some files weren't uploaded because the file size limit has been reached.\",\n  \"limitedCard.title\": \"Cards hidden\",\n  \"login.log-in-button\": \"Log in\",\n  \"login.log-in-title\": \"Log in\",\n  \"login.register-button\": \"or create an account if you don't have one\",\n  \"new_channel_modal.create_board.empty_board_description\": \"Create a new empty board\",\n  \"new_channel_modal.create_board.empty_board_title\": \"Empty board\",\n  \"new_channel_modal.create_board.select_template_placeholder\": \"Select a template\",\n  \"new_channel_modal.create_board.title\": \"Create a board for this channel\",\n  \"notification-box-card-limit-reached.close-tooltip\": \"Snooze for 10 days\",\n  \"notification-box-card-limit-reached.contact-link\": \"Contact your adminstrator\",\n  \"notification-box-card-limit-reached.link\": \"Upgrade to a paid plan\",\n  \"notification-box-card-limit-reached.title\": \"{cards} cards hidden from board\",\n  \"notification-box-cards-hidden.title\": \"This action has hidden another card\",\n  \"notification-box.card-limit-reached.not-admin.text\": \"To access archived cards, you can {contactLink} to upgrade to a paid plan.\",\n  \"notification-box.card-limit-reached.text\": \"Card limit reached, to view older cards, {link}\",\n  \"person.add-user-to-board\": \"Add {username} to board\",\n  \"person.add-user-to-board-confirm-button\": \"Add to board\",\n  \"person.add-user-to-board-permissions\": \"Permissions\",\n  \"person.add-user-to-board-question\": \"Do you want to add {username} to the board?\",\n  \"person.add-user-to-board-warning\": \"{username} isn't a member of the board and won't receive any notifications for it.\",\n  \"register.login-button\": \"or log in if you already have an account\",\n  \"register.signup-title\": \"Sign up for your account\",\n  \"rhs-board-non-admin-msg\": \"You're not an admin of the board\",\n  \"rhs-boards.add\": \"Add\",\n  \"rhs-boards.dm\": \"DM\",\n  \"rhs-boards.gm\": \"GM\",\n  \"rhs-boards.header.dm\": \"this direct message\",\n  \"rhs-boards.header.gm\": \"this group message\",\n  \"rhs-boards.last-update-at\": \"Last update at: {datetime}\",\n  \"rhs-boards.link-boards-to-channel\": \"Link boards to {channelName}\",\n  \"rhs-boards.linked-boards\": \"Linked boards\",\n  \"rhs-boards.no-boards-linked-to-channel\": \"No boards are linked to {channelName} yet\",\n  \"rhs-boards.no-boards-linked-to-channel-description\": \"Boards is a project management tool that helps define, organise, track and manage work across teams using a familiar kanban board view.\",\n  \"rhs-boards.unlink-board\": \"Unlink board\",\n  \"rhs-boards.unlink-board1\": \"Unlink board\",\n  \"rhs-channel-boards-header.title\": \"Boards\",\n  \"share-board.publish\": \"Publish\",\n  \"share-board.share\": \"Share\",\n  \"shareBoard.channels-select-group\": \"Channels\",\n  \"shareBoard.confirm-change-team-role.body\": \"Everyone on this board with a lower permission than the '\\\\{role}'\\\\ role will <b>now be promoted to {role}</b>. Are you sure you want to change the minimum role for the board?\",\n  \"shareBoard.confirm-change-team-role.confirmBtnText\": \"Change minimum board role\",\n  \"shareBoard.confirm-change-team-role.title\": \"Change minimum board role\",\n  \"shareBoard.confirm-link-channel\": \"Link board to channel\",\n  \"shareBoard.confirm-link-channel-button\": \"Link channel\",\n  \"shareBoard.confirm-link-channel-button-with-other-channel\": \"Unlink and link here\",\n  \"shareBoard.confirm-link-channel-subtext\": \"When you link a channel to a board, all members of the channel (existing and new) will be able to edit it. This excludes members who are guests.\",\n  \"shareBoard.confirm-link-channel-subtext-with-other-channel\": \"When you link a channel to a board, all members of the channel (existing and new) will be able to edit it. This excludes members who are guests.{lineBreak}This board is currently linked to another channel. It will be unlinked if you choose to link it here.\",\n  \"shareBoard.confirm-unlink.body\": \"When you unlink a channel from a board, all members of the channel (existing and new) will lose access to it unless they're given permission separately.\",\n  \"shareBoard.confirm-unlink.confirmBtnText\": \"Unlink channel\",\n  \"shareBoard.confirm-unlink.title\": \"Unlink channel from board\",\n  \"shareBoard.lastAdmin\": \"Boards must have at least one Administrator\",\n  \"shareBoard.members-select-group\": \"Members\",\n  \"shareBoard.unknown-channel-display-name\": \"Unknown channel\",\n  \"tutorial_tip.finish_tour\": \"Done\",\n  \"tutorial_tip.got_it\": \"Got it\",\n  \"tutorial_tip.ok\": \"Next\",\n  \"tutorial_tip.out\": \"Opt out of these tips.\",\n  \"tutorial_tip.seen\": \"Have you seen this before?\"\n}\n"
  },
  {
    "path": "webapp/i18n/es.json",
    "content": "{\n  \"AppBar.Tooltip\": \"Alternar tableros vinculados\",\n  \"Attachment.Attachment-title\": \"Archivos adjuntos\",\n  \"AttachmentBlock.DeleteAction\": \"borrar\",\n  \"AttachmentBlock.addElement\": \"agregar {type}\",\n  \"AttachmentBlock.delete\": \"Archivo adjunto eliminado.\",\n  \"AttachmentBlock.failed\": \"Este archivo no puede subirse debido a que excede el límite de tamaño de archivo.\",\n  \"AttachmentBlock.upload\": \"Subiendo archivo adjunto.\",\n  \"AttachmentBlock.uploadSuccess\": \"Archivo adjunto subido.\",\n  \"AttachmentElement.delete-confirmation-dialog-button-text\": \"Borrar\",\n  \"AttachmentElement.download\": \"Descargar\",\n  \"AttachmentElement.upload-percentage\": \"Subiendo...({uploadPercent}%)\",\n  \"BoardComponent.add-a-group\": \"+ Añadir un grupo\",\n  \"BoardComponent.delete\": \"Borrar\",\n  \"BoardComponent.hidden-columns\": \"Columnas Ocultas\",\n  \"BoardComponent.hide\": \"Ocultar\",\n  \"BoardComponent.new\": \"+ Nuevo\",\n  \"BoardComponent.no-property\": \"Sin {property}\",\n  \"BoardComponent.no-property-title\": \"Elementos sin la propiedad {property} irán aquí. Esta columna no se puede eliminar.\",\n  \"BoardComponent.show\": \"Mostrar\",\n  \"BoardMember.schemeAdmin\": \"Administrador\",\n  \"BoardMember.schemeEditor\": \"Editor\",\n  \"BoardMember.schemeNone\": \"Ninguno\",\n  \"BoardMember.schemeViewer\": \"Visualizador\",\n  \"BoardMember.unlinkChannel\": \"Desvincular\",\n  \"BoardPage.newVersion\": \"Una nueva versión de Boards está disponible, haz clic aquí para recargar.\",\n  \"BoardPage.syncFailed\": \"El tablero puede haber sido eliminado o el acceso revocado.\",\n  \"BoardTemplateSelector.add-template\": \"Crear nueva plantilla\",\n  \"BoardTemplateSelector.create-empty-board\": \"Crear un tablero vacío\",\n  \"BoardTemplateSelector.delete-template\": \"Eliminar\",\n  \"BoardTemplateSelector.description\": \"Agregar un tablero a la barra lateral usando alguna de las plantillas definidas a continuación o empezar desde cero.\",\n  \"BoardTemplateSelector.edit-template\": \"Editar\",\n  \"BoardTemplateSelector.plugin.no-content-description\": \"Agregar un tablero a la barra lateral usando alguna de las plantillas definidas a continuación o empezar desde cero.\",\n  \"BoardTemplateSelector.plugin.no-content-title\": \"Crear un tablero\",\n  \"BoardTemplateSelector.title\": \"Crear un tablero\",\n  \"BoardTemplateSelector.use-this-template\": \"Utiliza esta plantilla\",\n  \"BoardsSwitcher.Title\": \"Encontrar tableros\",\n  \"BoardsUnfurl.Limited\": \"Los detalles adicionales están ocultos debido a que la tarjeta ha sido archivada\",\n  \"BoardsUnfurl.Updated\": \"Actualizado {time}\",\n  \"Calculations.Options.average.displayName\": \"Promedio\",\n  \"Calculations.Options.average.label\": \"Promedio\",\n  \"Calculations.Options.count.displayName\": \"Contar\",\n  \"Calculations.Options.count.label\": \"Contar\",\n  \"Calculations.Options.countChecked.displayName\": \"Marcado\",\n  \"Calculations.Options.countChecked.label\": \"Contar marcados\",\n  \"Calculations.Options.countUnchecked.displayName\": \"Deseleccionado\",\n  \"Calculations.Options.countUnchecked.label\": \"Contar no marcados\",\n  \"Calculations.Options.countUniqueValue.displayName\": \"Único\",\n  \"Calculations.Options.countUniqueValue.label\": \"Contar valores únicos\",\n  \"Calculations.Options.countValue.displayName\": \"Valores\",\n  \"Calculations.Options.dateRange.displayName\": \"Rango\",\n  \"Calculations.Options.dateRange.label\": \"Rango\",\n  \"Calculations.Options.earliest.displayName\": \"Más antiguo\",\n  \"Calculations.Options.earliest.label\": \"Más antiguo\",\n  \"Calculations.Options.latest.displayName\": \"Último\",\n  \"Calculations.Options.latest.label\": \"Último\",\n  \"Calculations.Options.max.displayName\": \"Máx\",\n  \"Calculations.Options.max.label\": \"Máx\",\n  \"Calculations.Options.median.displayName\": \"Mediana\",\n  \"Calculations.Options.median.label\": \"Mediana\",\n  \"Calculations.Options.min.displayName\": \"Mín\",\n  \"Calculations.Options.min.label\": \"Mín\",\n  \"Calculations.Options.none.displayName\": \"Calcular\",\n  \"Calculations.Options.none.label\": \"Ninguna\",\n  \"Calculations.Options.percentChecked.displayName\": \"Marcado\",\n  \"Calculations.Options.percentChecked.label\": \"Porcentaje marcado\",\n  \"Calculations.Options.percentUnchecked.displayName\": \"Desmarcado\",\n  \"Calculations.Options.percentUnchecked.label\": \"Porcentaje desmarcado\",\n  \"Calculations.Options.range.displayName\": \"Rango\",\n  \"Calculations.Options.range.label\": \"Rango\",\n  \"Calculations.Options.sum.displayName\": \"Suma\",\n  \"Calculations.Options.sum.label\": \"Suma\",\n  \"CalendarCard.untitled\": \"Sin título\",\n  \"CardActionsMenu.copiedLink\": \"¡Copiado!\",\n  \"CardActionsMenu.copyLink\": \"Copiar hipervínculo\",\n  \"CardActionsMenu.delete\": \"Eliminar\",\n  \"CardActionsMenu.duplicate\": \"Duplicar\",\n  \"CardBadges.title-checkboxes\": \"Casillas de verificación\",\n  \"CardBadges.title-comments\": \"Comentarios\",\n  \"CardBadges.title-description\": \"Esta tarjeta tiene una descripción\",\n  \"CardDetail.Attach\": \"Adjuntar\",\n  \"CardDetail.Follow\": \"Seguir\",\n  \"CardDetail.Following\": \"Siguiendo\",\n  \"CardDetail.add-content\": \"Añadir contenido\",\n  \"CardDetail.add-icon\": \"Añadir icono\",\n  \"CardDetail.add-property\": \"+ Añadir propiedad\",\n  \"CardDetail.addCardText\": \"agregar texto a la tarjeta\",\n  \"CardDetail.limited-body\": \"Mejorar a nuestro plan Professional o Enterprise.\",\n  \"CardDetail.limited-button\": \"Mejorar\",\n  \"CardDetail.limited-title\": \"Esta tarjeta está oculta\",\n  \"CardDetail.moveContent\": \"Mover contenido de la tarjeta\",\n  \"CardDetail.new-comment-placeholder\": \"Añadir un comentario...\",\n  \"CardDetailProperty.confirm-delete-heading\": \"Confirmar eliminación de la propiedad\",\n  \"CardDetailProperty.confirm-delete-subtext\": \"¿Estás seguro de que quieres eliminar la propiedad \\\"{propertyName}\\\"? Al eliminarla también se removerá la propiedad en todas las tarjetas de este tablero.\",\n  \"CardDetailProperty.confirm-property-name-change-subtext\": \"¿Estás seguro de que quieres cambiar la propiedad \\\"{propertyName}\\\" {customText}? Esto puede afectar a los valores en {numOfCards} tarjeta(s) en este tablero, lo que puede resultar en una pérdida de datos.\",\n  \"CardDetailProperty.confirm-property-type-change\": \"Confirmar cambio de tipo de la propiedad\",\n  \"CardDetailProperty.delete-action-button\": \"Eliminar\",\n  \"CardDetailProperty.property-change-action-button\": \"Modificar propiedad\",\n  \"CardDetailProperty.property-changed\": \"¡Propiedad modificada exitosamente!\",\n  \"CardDetailProperty.property-deleted\": \"¡La propiedad {propertyName} ha sido eliminada exitosamente!\",\n  \"CardDetial.limited-link\": \"Aprende más sobre nuestros planes.\",\n  \"CardDialog.delete-confirmation-dialog-attachment\": \"Confirmar eliminación del archivo adjunto\",\n  \"CardDialog.delete-confirmation-dialog-button-text\": \"Eliminar\",\n  \"CardDialog.delete-confirmation-dialog-heading\": \"Confirmar eliminación de la tarjeta\",\n  \"CardDialog.editing-template\": \"Estás editando una plantilla.\",\n  \"CardDialog.nocard\": \"Esta tarjeta no existe o es inaccesible.\",\n  \"Categories.CreateCategoryDialog.CancelText\": \"Cancelar\",\n  \"Categories.CreateCategoryDialog.CreateText\": \"Crear\",\n  \"Categories.CreateCategoryDialog.Placeholder\": \"Pon nombre a la categoría\",\n  \"Categories.CreateCategoryDialog.UpdateText\": \"Actualizar\",\n  \"CenterPanel.Login\": \"Ingresar\",\n  \"CenterPanel.Share\": \"Compartir\",\n  \"ChannelIntro.CreateBoard\": \"Crear un tablero\",\n  \"ColorOption.selectColor\": \"Seleccionar {color} Color\",\n  \"Comment.delete\": \"Borrar\",\n  \"CommentsList.send\": \"Enviar\",\n  \"ContentBlock.Delete\": \"Borrar\",\n  \"ContentBlock.DeleteAction\": \"borrar\",\n  \"ContentBlock.addElement\": \"añadir {type}\",\n  \"ContentBlock.checkbox\": \"casilla de selección\",\n  \"ContentBlock.divider\": \"divisor\",\n  \"ContentBlock.editCardCheckbox\": \"casilla de verificación conmutada\",\n  \"ContentBlock.editCardCheckboxText\": \"editar texto de la tarjeta\",\n  \"ContentBlock.editCardText\": \"editar texto de la tarjeta\",\n  \"ContentBlock.editText\": \"Editar texto...\",\n  \"ContentBlock.image\": \"imagen\",\n  \"ContentBlock.insertAbove\": \"Insertar encima\",\n  \"ContentBlock.moveDown\": \"Mover hacia abajo\",\n  \"ContentBlock.moveUp\": \"Mover hacia arriba\",\n  \"ContentBlock.text\": \"texto\",\n  \"Dialog.closeDialog\": \"Cerrar diálogo\",\n  \"EditableDayPicker.today\": \"Hoy\",\n  \"Error.websocket-closed\": \"Conexión de Websocket cerrada, conexión interrumpida. Si esto persiste, verifique la configuración de su servidor o proxy web.\",\n  \"Filter.includes\": \"incluye\",\n  \"Filter.is-empty\": \"está vacío\",\n  \"Filter.is-not-empty\": \"no está vacío\",\n  \"Filter.not-includes\": \"no incluye\",\n  \"FilterComponent.add-filter\": \"+ Añadir filtro\",\n  \"FilterComponent.delete\": \"Borrar\",\n  \"GroupBy.ungroup\": \"Desagrupar\",\n  \"KanbanCard.untitled\": \"Sin título\",\n  \"Mutator.new-card-from-template\": \"nueva tarjeta desde una plantilla\",\n  \"Mutator.new-template-from-card\": \"nueva plantilla desde una tarjeta\",\n  \"PropertyMenu.Delete\": \"Borrar\",\n  \"PropertyMenu.changeType\": \"Cambiar el tipo de propiedad\",\n  \"PropertyMenu.typeTitle\": \"Tipo\",\n  \"PropertyType.Checkbox\": \"Casilla de selección\",\n  \"PropertyType.CreatedBy\": \"Creado por\",\n  \"PropertyType.CreatedTime\": \"Hora de creación\",\n  \"PropertyType.Date\": \"Fecha\",\n  \"PropertyType.Email\": \"Email\",\n  \"PropertyType.MultiSelect\": \"Selección Múltiple\",\n  \"PropertyType.Number\": \"Número\",\n  \"PropertyType.Person\": \"Persona\",\n  \"PropertyType.Phone\": \"Teléfono\",\n  \"PropertyType.Select\": \"Selector\",\n  \"PropertyType.Text\": \"Texto\",\n  \"PropertyType.UpdatedBy\": \"Última actualización por\",\n  \"PropertyType.UpdatedTime\": \"Hora de última actualización\",\n  \"RegistrationLink.confirmRegenerateToken\": \"Esto invalidará los enlaces compartidos previos. ¿Continuar?\",\n  \"RegistrationLink.copiedLink\": \"¡Copiado!\",\n  \"RegistrationLink.copyLink\": \"Copiar enlace\",\n  \"RegistrationLink.description\": \"Comparte este enlace para que otros se creen sus cuentas:\",\n  \"RegistrationLink.regenerateToken\": \"Regenerar token\",\n  \"RegistrationLink.tokenRegenerated\": \"Enlace de registro regenerado\",\n  \"ShareBoard.confirmRegenerateToken\": \"Esto invalidará los enlaces compartidos previos. ¿Continuar?\",\n  \"ShareBoard.copiedLink\": \"¡Copiado!\",\n  \"ShareBoard.copyLink\": \"Copiar enlace\",\n  \"ShareBoard.tokenRegenrated\": \"Token regenerado\",\n  \"Sidebar.about\": \"Sobre Focalboard\",\n  \"Sidebar.add-board\": \"+ Añadir panel\",\n  \"Sidebar.changePassword\": \"Cambiar contraseña\",\n  \"Sidebar.delete-board\": \"Borrar Panel\",\n  \"Sidebar.export-archive\": \"Exportar Archivo\",\n  \"Sidebar.import-archive\": \"Importar Archivo\",\n  \"Sidebar.invite-users\": \"Invitar usuarios\",\n  \"Sidebar.logout\": \"Cerrar sesión\",\n  \"Sidebar.random-icons\": \"Íconos random\",\n  \"Sidebar.set-language\": \"Establecer idioma\",\n  \"Sidebar.set-theme\": \"Establecer apariencia\",\n  \"Sidebar.settings\": \"Configuración\",\n  \"Sidebar.untitled-board\": \"(Panel sin titulo)\",\n  \"TableComponent.add-icon\": \"Añadir Icono\",\n  \"TableComponent.name\": \"Nombre\",\n  \"TableComponent.plus-new\": \"+ Nueva\",\n  \"TableHeaderMenu.delete\": \"Borrar\",\n  \"TableHeaderMenu.duplicate\": \"Duplicar\",\n  \"TableHeaderMenu.hide\": \"Ocultar\",\n  \"TableHeaderMenu.insert-left\": \"Insertar a la izquierda\",\n  \"TableHeaderMenu.insert-right\": \"Insertar a la derecha\",\n  \"TableHeaderMenu.sort-ascending\": \"Orden ascendente\",\n  \"TableHeaderMenu.sort-descending\": \"Orden descendente\",\n  \"TableRow.open\": \"Abrir\",\n  \"TopBar.give-feedback\": \"Dar feedback\",\n  \"ValueSelector.valueSelector\": \"Valorar el selector\",\n  \"ValueSelectorLabel.openMenu\": \"Abrir menú\",\n  \"View.AddView\": \"Añadir vista\",\n  \"View.Board\": \"Panel\",\n  \"View.DeleteView\": \"Eliminar vista\",\n  \"View.DuplicateView\": \"Duplicar vista\",\n  \"View.NewBoardTitle\": \"Vista de panel\",\n  \"View.NewGalleryTitle\": \"Vista de galería\",\n  \"View.NewTableTitle\": \"Vista de tabla\",\n  \"View.Table\": \"Tabla\",\n  \"ViewHeader.add-template\": \"+ Nueva plantilla\",\n  \"ViewHeader.delete-template\": \"Borrar\",\n  \"ViewHeader.edit-template\": \"Editar\",\n  \"ViewHeader.empty-card\": \"Tarjeta vacía\",\n  \"ViewHeader.export-board-archive\": \"Exportar archivo de tablero\",\n  \"ViewHeader.export-complete\": \"¡Se ha completado la exportación!\",\n  \"ViewHeader.export-csv\": \"Exportar a CSV\",\n  \"ViewHeader.export-failed\": \"¡Ha fallado la exportación!\",\n  \"ViewHeader.filter\": \"Filtrar\",\n  \"ViewHeader.group-by\": \"Agrupar por: {property}\",\n  \"ViewHeader.new\": \"Nueva\",\n  \"ViewHeader.properties\": \"Propiedades\",\n  \"ViewHeader.search-text\": \"Texto de búsqueda\",\n  \"ViewHeader.select-a-template\": \"Seleccionar una plantilla\",\n  \"ViewHeader.sort\": \"Ordenar\",\n  \"ViewHeader.untitled\": \"Sin título\",\n  \"ViewTitle.hide-description\": \"ocultar descripción\",\n  \"ViewTitle.pick-icon\": \"Escoger Icono\",\n  \"ViewTitle.random-icon\": \"Aleatorio\",\n  \"ViewTitle.remove-icon\": \"Quitar Icono\",\n  \"ViewTitle.show-description\": \"mostrar descripción\",\n  \"ViewTitle.untitled-board\": \"Panel sin título\",\n  \"default-properties.title\": \"Título\"\n}\n"
  },
  {
    "path": "webapp/i18n/et.json",
    "content": "{\n  \"BoardComponent.add-a-group\": \"+ Lisa grupp\",\n  \"BoardComponent.delete\": \"Kustuta\",\n  \"BoardComponent.hidden-columns\": \"Peidetud veerud\",\n  \"BoardComponent.hide\": \"Peida\",\n  \"BoardComponent.new\": \"+ Uus\",\n  \"BoardComponent.no-property\": \"{property} pole\",\n  \"BoardComponent.show\": \"Näita\",\n  \"BoardsUnfurl.Updated\": \"Uuendatud {time}\",\n  \"Calculations.Options.average.displayName\": \"Keskmine\",\n  \"Calculations.Options.average.label\": \"Keskmine\",\n  \"Calculations.Options.count.displayName\": \"Arv\",\n  \"Calculations.Options.count.label\": \"Arv\",\n  \"Calculations.Options.countUniqueValue.displayName\": \"Unikaalne\",\n  \"Calculations.Options.countValue.displayName\": \"Väärtused\",\n  \"Calculations.Options.dateRange.displayName\": \"Vahemik\",\n  \"Calculations.Options.dateRange.label\": \"Vahemik\",\n  \"Calculations.Options.earliest.displayName\": \"Varaseim\",\n  \"Calculations.Options.earliest.label\": \"Varaseim\",\n  \"Calculations.Options.latest.displayName\": \"Viimased\",\n  \"Calculations.Options.latest.label\": \"Viimased\",\n  \"Calculations.Options.max.displayName\": \"Maks\",\n  \"Calculations.Options.max.label\": \"Maks\",\n  \"Calculations.Options.min.displayName\": \"Min\",\n  \"Calculations.Options.min.label\": \"Min\",\n  \"Calculations.Options.none.displayName\": \"Arvuta\",\n  \"Calculations.Options.none.label\": \"Pole\",\n  \"Calculations.Options.range.displayName\": \"Vahemik\",\n  \"Calculations.Options.range.label\": \"Vahemik\",\n  \"Calculations.Options.sum.displayName\": \"Sum\",\n  \"Calculations.Options.sum.label\": \"Sum\",\n  \"CardDetail.Follow\": \"Jälgi\",\n  \"CardDetail.Following\": \"Jälgimisel\",\n  \"CardDetail.add-content\": \"Lisa sisu\",\n  \"CardDetail.add-icon\": \"Lisa ikoon\",\n  \"CardDetail.add-property\": \"+ Lisa omadus\",\n  \"CardDetail.addCardText\": \"lisa kaardi tekst\",\n  \"CardDetail.moveContent\": \"liiguta kaardi sisu\",\n  \"CardDetail.new-comment-placeholder\": \"Lisa kommentaar...\",\n  \"CardDetailProperty.confirm-delete-heading\": \"Kinnita omaduse kustutamine\",\n  \"CardDetailProperty.delete-action-button\": \"Kustuta\",\n  \"CardDetailProperty.property-change-action-button\": \"Muuda omadust\",\n  \"CardDialog.editing-template\": \"Sa muudad malli.\",\n  \"CardDialog.nocard\": \"Seda kaarti pole olemas või see pole ligipääsetav.\",\n  \"Comment.delete\": \"Kustuta\",\n  \"CommentsList.send\": \"Saada\",\n  \"ConfirmationDialog.cancel-action\": \"Loobu\",\n  \"ConfirmationDialog.confirm-action\": \"Kinnita\",\n  \"ContentBlock.Delete\": \"Kustuta\",\n  \"ContentBlock.DeleteAction\": \"Kustuta\",\n  \"ContentBlock.addElement\": \"lisa {type}\",\n  \"ContentBlock.divider\": \"eraldaja\",\n  \"ContentBlock.editCardCheckboxText\": \"lisa kaardi tekst\",\n  \"ContentBlock.editCardText\": \"muuda kaardi teksti\",\n  \"ContentBlock.editText\": \"Muuda teksti...\",\n  \"ContentBlock.image\": \"pilt\",\n  \"ContentBlock.insertAbove\": \"Sisesta üles\",\n  \"ContentBlock.moveDown\": \"Liiguta alla\",\n  \"ContentBlock.moveUp\": \"Liiguta üles\",\n  \"ContentBlock.text\": \"tekst\",\n  \"DeleteBoardDialog.confirm-cancel\": \"Loobu\",\n  \"DeleteBoardDialog.confirm-delete\": \"Kustuta\",\n  \"EditableDayPicker.today\": \"Täna\",\n  \"Filter.includes\": \"sisaldab\",\n  \"Filter.is-empty\": \"on tühi\",\n  \"Filter.is-not-empty\": \"pole tühi\",\n  \"Filter.not-includes\": \"ei sisalda\",\n  \"FilterComponent.add-filter\": \"+ Lisa filter\",\n  \"FilterComponent.delete\": \"Kustuta\",\n  \"KanbanCard.untitled\": \"Nimetu\",\n  \"Mutator.new-card-from-template\": \"malli põhjal uus kaart\",\n  \"PropertyMenu.Delete\": \"Kustuta\",\n  \"PropertyMenu.typeTitle\": \"Liik\",\n  \"PropertyType.CreatedBy\": \"Lisas\",\n  \"PropertyType.CreatedTime\": \"Lisamise aeg\",\n  \"PropertyType.Date\": \"Kuupäev\",\n  \"PropertyType.Email\": \"E-post\",\n  \"PropertyType.MultiSelect\": \"Mitme valimine\",\n  \"PropertyType.Number\": \"Number\",\n  \"PropertyType.Person\": \"Isik\",\n  \"PropertyType.Phone\": \"Telefon\",\n  \"PropertyType.Select\": \"Vali\",\n  \"PropertyType.Text\": \"Tekst\",\n  \"PropertyType.UpdatedBy\": \"Viimati uuendati\",\n  \"PropertyType.UpdatedTime\": \"Viimane uuendamise aeg\",\n  \"PropertyValueElement.empty\": \"Tühi\",\n  \"RegistrationLink.copiedLink\": \"Kopeeritud!\",\n  \"RegistrationLink.copyLink\": \"Kopeeri link\",\n  \"ShareBoard.copiedLink\": \"Kopeeritud!\",\n  \"ShareBoard.copyLink\": \"Kopeeri link\",\n  \"Sidebar.about\": \"Focalboardi info\",\n  \"Sidebar.changePassword\": \"Muuda parooli\",\n  \"Sidebar.invite-users\": \"Kutsu kasutajaid\",\n  \"Sidebar.logout\": \"Logi välja\",\n  \"Sidebar.random-icons\": \"Juhuslikud ikoonid\",\n  \"Sidebar.set-language\": \"Määra keel\",\n  \"Sidebar.set-theme\": \"Määra kujundus\",\n  \"Sidebar.settings\": \"Seaded\",\n  \"TableComponent.add-icon\": \"Lisa ikoon\",\n  \"TableComponent.name\": \"Nimi\",\n  \"TableComponent.plus-new\": \"+ Uus\",\n  \"TableHeaderMenu.delete\": \"Kustuta\",\n  \"TableHeaderMenu.duplicate\": \"Tee koopia\",\n  \"TableHeaderMenu.hide\": \"Peida\",\n  \"TableHeaderMenu.insert-left\": \"Sisesta vasakule\",\n  \"TableHeaderMenu.insert-right\": \"Sisesta paremale\",\n  \"TableHeaderMenu.sort-ascending\": \"Sorteeri kasvavalt\",\n  \"TableHeaderMenu.sort-descending\": \"Sorteeri kahanevalt\",\n  \"TableRow.open\": \"Ava\",\n  \"TopBar.give-feedback\": \"Anna tagasisidet\",\n  \"ValueSelector.valueSelector\": \"Väärtuse valija\",\n  \"ValueSelectorLabel.openMenu\": \"Ava menüü\",\n  \"View.AddView\": \"Lisa vaade\",\n  \"View.DeleteView\": \"Kustuta vaade\",\n  \"View.DuplicateView\": \"Tee vaatest koopia\",\n  \"View.Gallery\": \"Galerii\",\n  \"View.NewCalendarTitle\": \"Kalendri vaade\",\n  \"View.NewGalleryTitle\": \"Galerii vaade\",\n  \"View.NewTableTitle\": \"Tabeli vaade\",\n  \"View.Table\": \"Tabel\",\n  \"ViewHeader.add-template\": \"Uus mall\",\n  \"ViewHeader.delete-template\": \"Kustuta\",\n  \"ViewHeader.edit-template\": \"Muuda\",\n  \"ViewHeader.empty-card\": \"Tühi kaart\",\n  \"ViewHeader.export-complete\": \"Eksportimine on valmis!\",\n  \"ViewHeader.export-csv\": \"Ekspordi CSV-na\",\n  \"ViewHeader.export-failed\": \"Eksportimine ebaõnnestus!\",\n  \"ViewHeader.filter\": \"Filter\",\n  \"ViewHeader.group-by\": \"Grupeeri: {property}\",\n  \"ViewHeader.new\": \"Uus\",\n  \"ViewHeader.properties\": \"Omadused\",\n  \"ViewHeader.search-text\": \"Otsi teksti\",\n  \"ViewHeader.select-a-template\": \"Vali mall\",\n  \"ViewHeader.set-default-template\": \"Määra vaikeväärtuseks\",\n  \"ViewHeader.sort\": \"Sorteeri\",\n  \"ViewHeader.untitled\": \"Nimetu\",\n  \"ViewTitle.hide-description\": \"peida kirjeldus\",\n  \"ViewTitle.pick-icon\": \"Vali ikoon\",\n  \"ViewTitle.random-icon\": \"Juhuslik\",\n  \"ViewTitle.remove-icon\": \"Eemalda ikoon\",\n  \"ViewTitle.show-description\": \"näita kirjeldust\",\n  \"calendar.month\": \"Kuu\",\n  \"calendar.today\": \"TÄNA\",\n  \"calendar.week\": \"Nädal\",\n  \"default-properties.title\": \"Pealkiri\",\n  \"login.log-in-button\": \"Logi sisse\",\n  \"login.log-in-title\": \"Logi sisse\",\n  \"login.register-button\": \"või loo konto, kui sul seda veel pole\",\n  \"register.login-button\": \"või logi sisse, kui sul juba on konto\",\n  \"register.signup-title\": \"Loo omale konto\"\n}\n"
  },
  {
    "path": "webapp/i18n/fa.json",
    "content": "{\n  \"AppBar.Tooltip\": \"تغییر وضعیت تخته‌های مرتبط\",\n  \"Attachment.Attachment-title\": \"ضمیمه\",\n  \"AttachmentBlock.DeleteAction\": \"حذف\",\n  \"AttachmentBlock.addElement\": \"افزودن {type}\",\n  \"AttachmentBlock.delete\": \"ضمیمه حذف شد.\",\n  \"AttachmentBlock.failed\": \"به دلیل محدودیت حجم، این پرونده نمی‌تواند بارگذاری شود.\",\n  \"AttachmentBlock.upload\": \"بارگذاری ضمیمه.\",\n  \"AttachmentBlock.uploadSuccess\": \"ضمیمه بارگذاری شد.\",\n  \"AttachmentElement.delete-confirmation-dialog-button-text\": \"حذف\",\n  \"AttachmentElement.download\": \"بارگیری\",\n  \"AttachmentElement.upload-percentage\": \"بارگذاری...({uploadPercent}%)\",\n  \"BoardComponent.add-a-group\": \"+ افزودن گروه\",\n  \"BoardComponent.delete\": \"حذف\",\n  \"BoardComponent.hidden-columns\": \"ستون های مخفی\",\n  \"BoardComponent.hide\": \"مخفی\",\n  \"BoardComponent.new\": \"+ جدید\",\n  \"BoardComponent.no-property\": \"بدون {property}\",\n  \"BoardComponent.no-property-title\": \"موارد با {property} خالی اینجا نمایش داده میشوند. این ستون قابل حذف نیست.\",\n  \"BoardComponent.show\": \"نمایش\",\n  \"BoardMember.schemeAdmin\": \"مدیر\",\n  \"BoardMember.schemeEditor\": \"ویرایشگر\",\n  \"BoardMember.schemeNone\": \"هیچکدام\",\n  \"BoardMember.schemeViewer\": \"بیننده\",\n  \"BoardPage.newVersion\": \"نسخه جدیدی از برنامه Boards موجود است، برای بارگیری مجدد اینجا را کلیک کنید.\",\n  \"BoardPage.syncFailed\": \"تابلو ممکن است حذف شود یا دسترسی آن لغو شود.\",\n  \"BoardTemplateSelector.add-template\": \"ایجاد قالب جدید\",\n  \"BoardTemplateSelector.create-empty-board\": \"ایجاد تابلو خالی\",\n  \"BoardTemplateSelector.delete-template\": \"حذف\",\n  \"BoardTemplateSelector.description\": \"با استفاده از الگوهای زیر، یک تابلو به نوار کناری اضافه کنید یا از اول شروع کنید.\",\n  \"BoardTemplateSelector.edit-template\": \"ویرایش\",\n  \"BoardTemplateSelector.plugin.no-content-description\": \"با استفاده از الگوهای زیر، یک تابلو به نوار کناری اضافه کنید یا از اول شروع کنید.\",\n  \"BoardTemplateSelector.plugin.no-content-title\": \"ایجاد یک تابلو\",\n  \"BoardTemplateSelector.title\": \"ایجاد یک تابلو\",\n  \"BoardTemplateSelector.use-this-template\": \"از این قالب استفاده کنید\",\n  \"BoardsSwitcher.Title\": \"جستجوی تابلوها\",\n  \"BoardsUnfurl.Remainder\": \"+{remainder} بیشتر\",\n  \"BoardsUnfurl.Updated\": \"به روز شد {time}\",\n  \"Calculations.Options.average.displayName\": \"میانگین\",\n  \"Calculations.Options.average.label\": \"میانگین\",\n  \"Calculations.Options.count.displayName\": \"تعداد\",\n  \"Calculations.Options.count.label\": \"تعداد\",\n  \"Calculations.Options.countChecked.displayName\": \"نشان‌دار\",\n  \"Calculations.Options.countChecked.label\": \"تعداد نشان‌دارها\",\n  \"Calculations.Options.countUnchecked.displayName\": \"بی‌نشان\",\n  \"Calculations.Options.countUnchecked.label\": \"تعداد نشان‌دارها\",\n  \"Calculations.Options.countUniqueValue.displayName\": \"یکتا\",\n  \"Calculations.Options.countUniqueValue.label\": \"تعداد مقادیر یکتا\",\n  \"Calculations.Options.countValue.displayName\": \"مقادیر\",\n  \"Calculations.Options.countValue.label\": \"تعداد مقادیر\",\n  \"Calculations.Options.dateRange.displayName\": \"دامنه\",\n  \"Calculations.Options.dateRange.label\": \"دامنه\",\n  \"Calculations.Options.earliest.displayName\": \"اولین\",\n  \"Calculations.Options.earliest.label\": \"اولین\",\n  \"Calculations.Options.latest.displayName\": \"آخرین\",\n  \"Calculations.Options.latest.label\": \"آخرین\",\n  \"Calculations.Options.max.displayName\": \"حداکثر\",\n  \"Calculations.Options.max.label\": \"حداکثر\",\n  \"Calculations.Options.median.displayName\": \"میانه\",\n  \"Calculations.Options.median.label\": \"میانه\",\n  \"Calculations.Options.min.displayName\": \"حداقل\",\n  \"Calculations.Options.min.label\": \"حداقل\",\n  \"Calculations.Options.none.displayName\": \"محاسبه\",\n  \"Calculations.Options.none.label\": \"هیچ یک\",\n  \"Calculations.Options.percentChecked.displayName\": \"بررسی شد\",\n  \"Calculations.Options.percentChecked.label\": \"درصد انتخاب‌شده‌ها\",\n  \"Calculations.Options.percentUnchecked.displayName\": \"بدون علامت\",\n  \"Calculations.Options.percentUnchecked.label\": \"درصد بی‌نشان\",\n  \"Calculations.Options.range.displayName\": \"بازه\",\n  \"Calculations.Options.range.label\": \"بازه\",\n  \"Calculations.Options.sum.displayName\": \"جمع\",\n  \"Calculations.Options.sum.label\": \"جمع\",\n  \"CardBadges.title-checkboxes\": \"چک باکس ها\",\n  \"CardBadges.title-comments\": \"نظرات\",\n  \"CardBadges.title-description\": \"این کارت دارای توضیحات است\",\n  \"CardDetail.Follow\": \"دنبال کردن\",\n  \"CardDetail.Following\": \"ذیل\",\n  \"CardDetail.add-content\": \"محتوا اضافه کنید\",\n  \"CardDetail.add-icon\": \"اضافه کردن نماد\",\n  \"CardDetail.add-property\": \"+ اضافه کردن یک ویژگی\",\n  \"CardDetail.addCardText\": \"متن کارت را اضافه کنید\",\n  \"CardDetail.moveContent\": \"انتقال محتوای کارت\",\n  \"CardDetail.new-comment-placeholder\": \"افزودن نظر...\",\n  \"CardDetailProperty.confirm-delete-heading\": \"تایید حذف ویژگی\",\n  \"CardDetailProperty.confirm-delete-subtext\": \"آیا مطمئن هستید که می خواهید ویژگی \\\"{propertyName}\\\" را حذف کنید؟ با حذف آن، اموال از تمام کارت های موجود در این تابلو حذف می شود.\",\n  \"CardDetailProperty.confirm-property-name-change-subtext\": \"آیا مطمئن هستید که می خواهید ویژگی \\\"{propertyName}\\\" {customText} را تغییر دهید؟ این روی مقدار(های) کارت(های) {numOfCards} در این برد تأثیر می گذارد و می تواند منجر به از دست رفتن داده شود.\",\n  \"CardDetailProperty.confirm-property-type-change\": \"تایید تغییر نوع ویژگی\",\n  \"CardDetailProperty.delete-action-button\": \"حذف\",\n  \"CardDetailProperty.property-change-action-button\": \"تغییر ویژگی\",\n  \"CardDetailProperty.property-changed\": \"تغییر ویژگی با موفقیت!\",\n  \"CardDetailProperty.property-deleted\": \"{propertyName} با موفقیت حذف شد!\",\n  \"CardDetailProperty.property-name-change-subtext\": \"از \\\"{oldPropType}\\\" به \\\"{newPropType}\\\" تایپ کنید\",\n  \"CardDialog.delete-confirmation-dialog-button-text\": \"حذف\",\n  \"CardDialog.delete-confirmation-dialog-heading\": \"تایید حذف کارت\",\n  \"CardDialog.editing-template\": \"شما در حال ویرایش یک الگو هستید.\",\n  \"CardDialog.nocard\": \"این کارت وجود ندارد یا غیرقابل دسترسی است.\",\n  \"Categories.CreateCategoryDialog.CancelText\": \"لغو کنید\",\n  \"Categories.CreateCategoryDialog.CreateText\": \"ايجاد كردن\",\n  \"Categories.CreateCategoryDialog.Placeholder\": \"دسته خود را نام ببرید\",\n  \"Categories.CreateCategoryDialog.UpdateText\": \"به روز رسانی\",\n  \"CenterPanel.Login\": \"وارد شدن\",\n  \"CenterPanel.Share\": \"اشتراک گذاری\",\n  \"ColorOption.selectColor\": \"رنگ {color} را انتخاب کنید\",\n  \"Comment.delete\": \"حذف\",\n  \"CommentsList.send\": \"ارسال\",\n  \"ConfirmationDialog.cancel-action\": \"لغو کنید\",\n  \"ConfirmationDialog.confirm-action\": \"تایید\",\n  \"ContentBlock.Delete\": \"حذف\",\n  \"ContentBlock.DeleteAction\": \"حذف\",\n  \"GroupBy.hideEmptyGroups\": \"پنهان‌کردن {count} گروه خالی\",\n  \"GroupBy.showHiddenGroups\": \"نمایش {count} گروه پنهان‌شده\",\n  \"ViewHeader.view-menu\": \"نمایش فهرست\",\n  \"ViewLimitDialog.Heading\": \"محدودیت تعداد نما برای تخته رسید\",\n  \"ViewLimitDialog.PrimaryButton.Title.Admin\": \"ارتقا\",\n  \"ViewLimitDialog.UpgradeImg.AltText\": \"ارتقا عکس\",\n  \"ViewLimitDialog.notifyAdmin.Success\": \"مدیر شما مطلع شد\",\n  \"ViewTitle.hide-description\": \"پنهان کردن توضیحات\",\n  \"ViewTitle.pick-icon\": \"انتخاب تصویرک\",\n  \"ViewTitle.random-icon\": \"تصادفی\",\n  \"ViewTitle.remove-icon\": \"پاک‌کردن تصویرک\",\n  \"ViewTitle.show-description\": \"نمایش توضیحات\",\n  \"ViewTitle.untitled-board\": \"تخته بدون عنوان\",\n  \"badge.guest\": \"مهمان\"\n}\n"
  },
  {
    "path": "webapp/i18n/fr.json",
    "content": "{\n  \"AppBar.Tooltip\": \"Activer les panneaux liés\",\n  \"BoardComponent.add-a-group\": \"+ Ajouter un groupe\",\n  \"BoardComponent.delete\": \"Supprimer\",\n  \"BoardComponent.hidden-columns\": \"Colonnes cachées\",\n  \"BoardComponent.hide\": \"Cacher\",\n  \"BoardComponent.new\": \"+ Nouveau\",\n  \"BoardComponent.no-property\": \"Pas de {property}\",\n  \"BoardComponent.no-property-title\": \"Les éléments sans propriété {property} seront placés ici. Cette colonne ne peut pas être supprimée.\",\n  \"BoardComponent.show\": \"Montrer\",\n  \"BoardMember.schemeAdmin\": \"Admin\",\n  \"BoardMember.schemeCommenter\": \"Commentateur\",\n  \"BoardMember.schemeEditor\": \"Éditeur\",\n  \"BoardMember.schemeNone\": \"Aucun\",\n  \"BoardMember.schemeViewer\": \"Lecteur\",\n  \"BoardMember.unlinkChannel\": \"Détacher\",\n  \"BoardPage.newVersion\": \"Une nouvelle version de Boards est disponible, cliquez ici pour recharger.\",\n  \"BoardPage.syncFailed\": \"Le tableau a peut-être été supprimé ou vos droits d'accès révoqués.\",\n  \"BoardTemplateSelector.add-template\": \"Nouveau modèle\",\n  \"BoardTemplateSelector.create-empty-board\": \"Créer un tableau vide\",\n  \"BoardTemplateSelector.delete-template\": \"Supprimer\",\n  \"BoardTemplateSelector.description\": \"Ajoutez un tableau à la barre latérale en utilisant l'un des modèles définis ci-dessous ou recommencez de zéro.\",\n  \"BoardTemplateSelector.edit-template\": \"Éditer\",\n  \"BoardTemplateSelector.plugin.no-content-description\": \"Ajouter un tableau à la barre latérale en utilisant l'un des modèles ci-dessous ou commencer à partir de zéro.\",\n  \"BoardTemplateSelector.plugin.no-content-title\": \"Créer un tableau\",\n  \"BoardTemplateSelector.title\": \"Créer un tableau\",\n  \"BoardTemplateSelector.use-this-template\": \"Utiliser ce modèle\",\n  \"BoardsSwitcher.Title\": \"Rechercher des tableaux\",\n  \"BoardsUnfurl.Limited\": \"Les détails supplémentaires sont masqués car la carte est archivée\",\n  \"BoardsUnfurl.Remainder\": \"+{remainder} plus\",\n  \"BoardsUnfurl.Updated\": \"Mis à jour {time}\",\n  \"Calculations.Options.average.displayName\": \"Moyenne\",\n  \"Calculations.Options.average.label\": \"Moyenne\",\n  \"Calculations.Options.count.displayName\": \"Compter\",\n  \"Calculations.Options.count.label\": \"Compter\",\n  \"Calculations.Options.countChecked.displayName\": \"Coché\",\n  \"Calculations.Options.countChecked.label\": \"Total coché\",\n  \"Calculations.Options.countUnchecked.displayName\": \"Décoché\",\n  \"Calculations.Options.countUnchecked.label\": \"Total décoché\",\n  \"Calculations.Options.countUniqueValue.displayName\": \"Unique\",\n  \"Calculations.Options.countUniqueValue.label\": \"Compter les valeurs uniques\",\n  \"Calculations.Options.countValue.displayName\": \"Valeurs\",\n  \"Calculations.Options.countValue.label\": \"Calculer la valeur\",\n  \"Calculations.Options.dateRange.displayName\": \"Il y a\",\n  \"Calculations.Options.dateRange.label\": \"Intervalle\",\n  \"Calculations.Options.earliest.displayName\": \"Plus ancien\",\n  \"Calculations.Options.earliest.label\": \"Plus ancien\",\n  \"Calculations.Options.latest.displayName\": \"Plus récent\",\n  \"Calculations.Options.latest.label\": \"Plus récent\",\n  \"Calculations.Options.max.displayName\": \"Maximum\",\n  \"Calculations.Options.max.label\": \"Maximum\",\n  \"Calculations.Options.median.displayName\": \"Médiane\",\n  \"Calculations.Options.median.label\": \"Médiane\",\n  \"Calculations.Options.min.displayName\": \"Minimum\",\n  \"Calculations.Options.min.label\": \"Minimum\",\n  \"Calculations.Options.none.displayName\": \"Calculer\",\n  \"Calculations.Options.none.label\": \"Aucun\",\n  \"Calculations.Options.percentChecked.displayName\": \"Coché\",\n  \"Calculations.Options.percentChecked.label\": \"Pourcentage coché\",\n  \"Calculations.Options.percentUnchecked.displayName\": \"Décoché\",\n  \"Calculations.Options.percentUnchecked.label\": \"Pourcentage décoché\",\n  \"Calculations.Options.range.displayName\": \"Curseur\",\n  \"Calculations.Options.range.label\": \"Curseur\",\n  \"Calculations.Options.sum.displayName\": \"Somme\",\n  \"Calculations.Options.sum.label\": \"Somme\",\n  \"CalendarCard.untitled\": \"Sans titre\",\n  \"CardActionsMenu.copiedLink\": \"Copié !\",\n  \"CardActionsMenu.copyLink\": \"Copier le lien\",\n  \"CardActionsMenu.delete\": \"Supprimer\",\n  \"CardActionsMenu.duplicate\": \"Dupliquer\",\n  \"CardBadges.title-checkboxes\": \"Cases à cocher\",\n  \"CardBadges.title-comments\": \"Commentaires\",\n  \"CardBadges.title-description\": \"Cette carte a une description\",\n  \"CardDetail.Follow\": \"Suivre\",\n  \"CardDetail.Following\": \"Suivi\",\n  \"CardDetail.add-content\": \"Ajouter du contenu\",\n  \"CardDetail.add-icon\": \"Ajouter une icône\",\n  \"CardDetail.add-property\": \"+ Ajouter une propriété\",\n  \"CardDetail.addCardText\": \"ajouter une carte texte\",\n  \"CardDetail.limited-body\": \"Passez à notre offre Professionnel ou Entreprise pour afficher les cartes archivées, avoir des vues illimitées par tableau, des cartes illimitées et plus encore.\",\n  \"CardDetail.limited-button\": \"Mise à niveau\",\n  \"CardDetail.limited-title\": \"Cette carte est masquée\",\n  \"CardDetail.moveContent\": \"Déplacer le contenu de la carte\",\n  \"CardDetail.new-comment-placeholder\": \"Ajouter un commentaire...\",\n  \"CardDetailProperty.confirm-delete-heading\": \"Confirmer la suppression de la propriété\",\n  \"CardDetailProperty.confirm-delete-subtext\": \"Êtes-vous sûr de vouloir supprimer la propriété « {propertyName} » ? La suppression retirera la propriété de toutes les cartes dans ce tableau.\",\n  \"CardDetailProperty.confirm-property-name-change-subtext\": \"Voulez-vous vraiment modifier le type de la propriété \\\"{propertyName}\\\" {customText} ? Cela affectera la ou les valeur(s) sur {numOfCards} carte(s) dans ce tableau et peut entraîner une perte de données.\",\n  \"CardDetailProperty.confirm-property-type-change\": \"Confirmer le changement de type de propriété\",\n  \"CardDetailProperty.delete-action-button\": \"Supprimer\",\n  \"CardDetailProperty.property-change-action-button\": \"Modifier la propriété\",\n  \"CardDetailProperty.property-changed\": \"Propriété modifiée avec succès !\",\n  \"CardDetailProperty.property-deleted\": \"{propertyName} supprimé avec succès !\",\n  \"CardDetailProperty.property-name-change-subtext\": \"de \\\"{oldPropType}\\\" à \\\"{newPropType}\\\"\",\n  \"CardDetial.limited-link\": \"En savoir plus sur nos offres.\",\n  \"CardDialog.delete-confirmation-dialog-button-text\": \"Supprimer\",\n  \"CardDialog.delete-confirmation-dialog-heading\": \"Confirmer la suppression de la carte !\",\n  \"CardDialog.editing-template\": \"Vous éditez un modèle.\",\n  \"CardDialog.nocard\": \"Cette carte n'existe pas ou n'est pas accessible.\",\n  \"Categories.CreateCategoryDialog.CancelText\": \"Annuler\",\n  \"Categories.CreateCategoryDialog.CreateText\": \"Créer\",\n  \"Categories.CreateCategoryDialog.Placeholder\": \"Nommez votre catégorie\",\n  \"Categories.CreateCategoryDialog.UpdateText\": \"Mettre à jour\",\n  \"CenterPanel.Login\": \"Connexion\",\n  \"CenterPanel.Share\": \"Partager\",\n  \"ColorOption.selectColor\": \"Choisir la couleur {color}\",\n  \"Comment.delete\": \"Supprimer\",\n  \"CommentsList.send\": \"Envoyer\",\n  \"ConfirmationDialog.cancel-action\": \"Annuler\",\n  \"ConfirmationDialog.confirm-action\": \"Confirmer\",\n  \"ContentBlock.Delete\": \"Supprimer\",\n  \"ContentBlock.DeleteAction\": \"supprimer\",\n  \"ContentBlock.addElement\": \"ajouter {type}\",\n  \"ContentBlock.checkbox\": \"case à cocher\",\n  \"ContentBlock.divider\": \"séparateur\",\n  \"ContentBlock.editCardCheckbox\": \"case cochée\",\n  \"ContentBlock.editCardCheckboxText\": \"éditer le texte de la carte\",\n  \"ContentBlock.editCardText\": \"éditer le texte de la carte\",\n  \"ContentBlock.editText\": \"Éditer le texte...\",\n  \"ContentBlock.image\": \"image\",\n  \"ContentBlock.insertAbove\": \"Insérer au-dessus\",\n  \"ContentBlock.moveDown\": \"Déplacer vers le bas\",\n  \"ContentBlock.moveUp\": \"Déplacer vers le haut\",\n  \"ContentBlock.text\": \"texte\",\n  \"DateRange.clear\": \"Supprimer\",\n  \"DateRange.empty\": \"Vide\",\n  \"DateRange.endDate\": \"Date de fin\",\n  \"DateRange.today\": \"Aujourd'hui\",\n  \"DeleteBoardDialog.confirm-cancel\": \"Annuler\",\n  \"DeleteBoardDialog.confirm-delete\": \"Supprimer\",\n  \"DeleteBoardDialog.confirm-info\": \"Êtes-vous sûr de vouloir supprimer le tableau «{boardTitle}» ? Cela supprimera toutes les cartes dans ce tableau.\",\n  \"DeleteBoardDialog.confirm-info-template\": \"Voulez-vous vraiment supprimer le modèle de tableau “{boardTitle}” ?\",\n  \"DeleteBoardDialog.confirm-tite\": \"Confirmer la suppression du tableau\",\n  \"DeleteBoardDialog.confirm-tite-template\": \"Confirmer la suppression du modèle\",\n  \"Dialog.closeDialog\": \"Fermer la boîte de dialogue\",\n  \"EditableDayPicker.today\": \"Aujourd'hui\",\n  \"Error.mobileweb\": \"La prise en charge de la version mobile est actuellement en version bêta. Certaines fonctionnalités peuvent manquer.\",\n  \"Error.websocket-closed\": \"Connexion au websocket fermé, connexion interrompue. Si cela persiste, vérifiez la configuration de votre serveur ou de votre proxy web.\",\n  \"Filter.contains\": \"contient\",\n  \"Filter.ends-with\": \"se termine par\",\n  \"Filter.includes\": \"inclus\",\n  \"Filter.is\": \"est\",\n  \"Filter.is-empty\": \"est vide\",\n  \"Filter.is-not-empty\": \"n'est pas vide\",\n  \"Filter.is-not-set\": \"n'est pas renseigné\",\n  \"Filter.is-set\": \"est renseigné\",\n  \"Filter.not-contains\": \"ne contient pas\",\n  \"Filter.not-ends-with\": \"ne se termine pas par\",\n  \"Filter.not-includes\": \"n'inclut pas\",\n  \"Filter.not-starts-with\": \"ne commence pas par\",\n  \"Filter.starts-with\": \"commence par\",\n  \"FilterByText.placeholder\": \"filtre de texte\",\n  \"FilterComponent.add-filter\": \"+ Ajouter un filtre\",\n  \"FilterComponent.delete\": \"Supprimer\",\n  \"FindBoardsDialog.IntroText\": \"Rechercher des tableaux\",\n  \"FindBoardsDialog.NoResultsFor\": \"Pas de résultats pour \\\"{searchQuery}\\\"\",\n  \"FindBoardsDialog.NoResultsSubtext\": \"Vérifiez l'orthographe ou essayez une autre recherche.\",\n  \"FindBoardsDialog.SubTitle\": \"Recherchez ci-dessous pour trouver un tableau. Utilisez <b>HAUT/BAS</b> pour naviguer. <b>ENTRER</b> pour sélectionner, <b>ECHAP</b> pour annuler\",\n  \"FindBoardsDialog.Title\": \"Rechercher des tableaux\",\n  \"GroupBy.hideEmptyGroups\": \"Masquer {count} groupes vides\",\n  \"GroupBy.showHiddenGroups\": \"Afficher les {count} groupes masqués\",\n  \"GroupBy.ungroup\": \"Dégrouper\",\n  \"HideBoard.MenuOption\": \"Cacher le tableau\",\n  \"KanbanCard.untitled\": \"Sans titre\",\n  \"Mutator.new-board-from-template\": \"nouveau tableau à partir du modèle\",\n  \"Mutator.new-card-from-template\": \"nouvelle carte depuis un modèle\",\n  \"Mutator.new-template-from-card\": \"nouveau modèle depuis une carte\",\n  \"OnboardingTour.AddComments.Body\": \"Vous pouvez commenter les bugs et même @mentionner les utilisateurs de Mattermost pour attirer leur attention.\",\n  \"OnboardingTour.AddComments.Title\": \"Ajouter des commentaires\",\n  \"OnboardingTour.AddDescription.Body\": \"Ajouter une description à votre carte pour que votre équipe sachent de quoi il s'agit.\",\n  \"OnboardingTour.AddDescription.Title\": \"Ajouter une description\",\n  \"OnboardingTour.AddProperties.Body\": \"Ajouter diverses propriétés aux cartes pour les rendre plus efficace !\",\n  \"OnboardingTour.AddProperties.Title\": \"Ajouter des propriétés\",\n  \"OnboardingTour.AddView.Body\": \"Allez ici pour créer une nouvelle vue pour organiser votre tableau en utilisant différentes mises en page.\",\n  \"OnboardingTour.AddView.Title\": \"Ajouter une nouvelle vue\",\n  \"OnboardingTour.CopyLink.Body\": \"Vous pouvez partager vos cartes avec votre équipe en copiant le lien et en le collant dans un canal, un message direct ou un message de groupe.\",\n  \"OnboardingTour.CopyLink.Title\": \"Copier le lien\",\n  \"OnboardingTour.OpenACard.Body\": \"Ouvrir une carte pour découvrir les façons dont les tableaux peuvent vous aider à organiser votre travail.\",\n  \"OnboardingTour.OpenACard.Title\": \"Ouvrir une carte\",\n  \"OnboardingTour.ShareBoard.Body\": \"Vous pouvez partager votre tableau en interne, au sein de votre équipe ou le publier publiquement pour une visibilité en dehors de votre organisation.\",\n  \"OnboardingTour.ShareBoard.Title\": \"Partager un tableau\",\n  \"PropertyMenu.Delete\": \"Supprimer\",\n  \"PropertyMenu.changeType\": \"Changer le type de la propriété\",\n  \"PropertyMenu.selectType\": \"Sélectionner le type de propriété\",\n  \"PropertyMenu.typeTitle\": \"Type\",\n  \"PropertyType.Checkbox\": \"Case à cocher\",\n  \"PropertyType.CreatedBy\": \"Créé par\",\n  \"PropertyType.CreatedTime\": \"Date de création\",\n  \"PropertyType.Date\": \"Date\",\n  \"PropertyType.Email\": \"Adresse e-mail\",\n  \"PropertyType.MultiSelect\": \"Sélection multiple\",\n  \"PropertyType.Number\": \"Nombre\",\n  \"PropertyType.Person\": \"Personne\",\n  \"PropertyType.Phone\": \"Téléphone\",\n  \"PropertyType.Select\": \"Liste\",\n  \"PropertyType.Text\": \"Texte\",\n  \"PropertyType.Unknown\": \"Inconnue\",\n  \"PropertyType.UpdatedBy\": \"Dernière mise à jour par\",\n  \"PropertyType.UpdatedTime\": \"Date de dernière mise à jour\",\n  \"PropertyType.Url\": \"URL\",\n  \"PropertyValueElement.empty\": \"Vide\",\n  \"RegistrationLink.confirmRegenerateToken\": \"Ceci va désactiver les liens de partages existants. Continuer ?\",\n  \"RegistrationLink.copiedLink\": \"Copié !\",\n  \"RegistrationLink.copyLink\": \"Copier le lien\",\n  \"RegistrationLink.description\": \"Partagez ce lien avec des personnes pour leur permettre de créer un compte :\",\n  \"RegistrationLink.regenerateToken\": \"Générer un nouveau jeton\",\n  \"RegistrationLink.tokenRegenerated\": \"Un nouveau lien d'inscription a été créé\",\n  \"ShareBoard.PublishDescription\": \"Publiez et partagez un lien en lecture seule avec tout le monde sur le Web.\",\n  \"ShareBoard.PublishTitle\": \"Publier sur le web\",\n  \"ShareBoard.ShareInternal\": \"Partager en interne\",\n  \"ShareBoard.ShareInternalDescription\": \"Les utilisateurs qui ont des autorisations pourront utiliser ce lien.\",\n  \"ShareBoard.Title\": \"Partager le tableau\",\n  \"ShareBoard.confirmRegenerateToken\": \"Ceci va désactiver les liens de partages existants. Continuer ?\",\n  \"ShareBoard.copiedLink\": \"Copié !\",\n  \"ShareBoard.copyLink\": \"Copier le lien\",\n  \"ShareBoard.regenerate\": \"Régénérer le jeton\",\n  \"ShareBoard.searchPlaceholder\": \"Rechercher des membres et des canaux\",\n  \"ShareBoard.teamPermissionsText\": \"Tout le monde à l'équipe {teamName}\",\n  \"ShareBoard.tokenRegenrated\": \"Le jeton a été recréé\",\n  \"ShareBoard.userPermissionsRemoveMemberText\": \"Supprimer un membre\",\n  \"ShareBoard.userPermissionsYouText\": \"(Vous)\",\n  \"ShareTemplate.Title\": \"Partager un modèle\",\n  \"ShareTemplate.searchPlaceholder\": \"Recherche de personnes\",\n  \"Sidebar.about\": \"À propos de Focalboard\",\n  \"Sidebar.add-board\": \"+ Ajouter un tableau\",\n  \"Sidebar.changePassword\": \"Modifier le mot de passe\",\n  \"Sidebar.delete-board\": \"Supprimer le tableau\",\n  \"Sidebar.duplicate-board\": \"Dupliquer une carte\",\n  \"Sidebar.export-archive\": \"Exporter une archive\",\n  \"Sidebar.import\": \"Importer\",\n  \"Sidebar.import-archive\": \"Importer une archive\",\n  \"Sidebar.invite-users\": \"Inviter des utilisateurs\",\n  \"Sidebar.logout\": \"Se déconnecter\",\n  \"Sidebar.no-boards-in-category\": \"Aucun tableaux\",\n  \"Sidebar.product-tour\": \"Visite guidée\",\n  \"Sidebar.random-icons\": \"Icônes aléatoires\",\n  \"Sidebar.set-language\": \"Choisir la langue\",\n  \"Sidebar.set-theme\": \"Choisir le thème\",\n  \"Sidebar.settings\": \"Réglages\",\n  \"Sidebar.template-from-board\": \"Nouveau modèle de tableau\",\n  \"Sidebar.untitled-board\": \"(Tableau sans titre)\",\n  \"Sidebar.untitled-view\": \"(Vue sans titre)\",\n  \"SidebarCategories.BlocksMenu.Move\": \"Déplacer vers ...\",\n  \"SidebarCategories.CategoryMenu.CreateNew\": \"Créer une nouvelle catégorie\",\n  \"SidebarCategories.CategoryMenu.Delete\": \"Supprimer la catégorie\",\n  \"SidebarCategories.CategoryMenu.DeleteModal.Body\": \"Les tableaux de <b>{categoryName}</b> reviendront à la catégorie par défaut. Aucun des tableaux ne seront supprimés.\",\n  \"SidebarCategories.CategoryMenu.DeleteModal.Title\": \"Supprimer cette catégorie ?\",\n  \"SidebarCategories.CategoryMenu.Update\": \"Renommer la catégorie\",\n  \"SidebarTour.ManageCategories.Body\": \"Créez et gérez des catégories personnalisées. Les catégories sont spécifiques à l'utilisateur, donc déplacer un tableau vers votre catégorie n'aura pas d'impact sur les autres membres utilisant le même tableau.\",\n  \"SidebarTour.ManageCategories.Title\": \"Gérer les catégories\",\n  \"SidebarTour.SearchForBoards.Body\": \"Ouvrez le sélecteur de tableau (Cmd/Ctrl + K) pour rechercher et ajouter rapidement des tableaux à votre barre latérale.\",\n  \"SidebarTour.SearchForBoards.Title\": \"Rechercher des cartes\",\n  \"SidebarTour.SidebarCategories.Body\": \"Tous vos tableaux sont maintenant organisés sous votre nouvelle barre latérale. Plus besoin de basculer entre les espaces de travail. Des catégories personnalisées uniques basées sur vos espaces de travail précédents peuvent avoir été automatiquement créées pour vous dans le cadre de la mise à jour 7.2. Ceux-ci peuvent être supprimés ou modifiés selon vos préférences.\",\n  \"SidebarTour.SidebarCategories.Link\": \"En savoir plus\",\n  \"SidebarTour.SidebarCategories.Title\": \"Catégories de la barre latérale\",\n  \"TableComponent.add-icon\": \"Ajouter une icône\",\n  \"TableComponent.name\": \"Nom\",\n  \"TableComponent.plus-new\": \"+ Nouveau\",\n  \"TableHeaderMenu.delete\": \"Supprimer\",\n  \"TableHeaderMenu.duplicate\": \"Dupliquer\",\n  \"TableHeaderMenu.hide\": \"Cacher\",\n  \"TableHeaderMenu.insert-left\": \"Insérer à gauche\",\n  \"TableHeaderMenu.insert-right\": \"Insérer à droite\",\n  \"TableHeaderMenu.sort-ascending\": \"Tri ascendant\",\n  \"TableHeaderMenu.sort-descending\": \"Tri descendant\",\n  \"TableRow.open\": \"Ouvrir\",\n  \"TopBar.give-feedback\": \"Donner un avis\",\n  \"URLProperty.copiedLink\": \"Copié !\",\n  \"URLProperty.copy\": \"Copie\",\n  \"URLProperty.edit\": \"Modifier\",\n  \"UndoRedoHotKeys.canRedo\": \"Refaire\",\n  \"UndoRedoHotKeys.canRedo-with-description\": \"Refaire {description}\",\n  \"UndoRedoHotKeys.canUndo\": \"Annuler\",\n  \"UndoRedoHotKeys.canUndo-with-description\": \"Annuler {description}\",\n  \"UndoRedoHotKeys.cannotRedo\": \"Rien à refaire\",\n  \"UndoRedoHotKeys.cannotUndo\": \"Rien à annuler\",\n  \"ValueSelector.noOptions\": \"Aucune option. Commencez à taper pour ajouter la première !\",\n  \"ValueSelector.valueSelector\": \"Sélecteur de value\",\n  \"ValueSelectorLabel.openMenu\": \"Ouvrir le menu\",\n  \"VersionMessage.help\": \"Découvrez les nouveautés de cette version.\",\n  \"View.AddView\": \"Ajouter une vue\",\n  \"View.Board\": \"Tableau\",\n  \"View.DeleteView\": \"Supprimer la vue\",\n  \"View.DuplicateView\": \"Dupliquer la vue\",\n  \"View.Gallery\": \"Galerie\",\n  \"View.NewBoardTitle\": \"Vue en tableau\",\n  \"View.NewCalendarTitle\": \"Vue calendrier\",\n  \"View.NewGalleryTitle\": \"Vue en galerie\",\n  \"View.NewTableTitle\": \"Vue en table\",\n  \"View.NewTemplateDefaultTitle\": \"Modèle sans titre\",\n  \"View.NewTemplateTitle\": \"Sans titre\",\n  \"View.Table\": \"Table\",\n  \"ViewHeader.add-template\": \"Nouveau modèle\",\n  \"ViewHeader.delete-template\": \"Supprimer\",\n  \"ViewHeader.display-by\": \"Afficher par : {property}\",\n  \"ViewHeader.edit-template\": \"Éditer\",\n  \"ViewHeader.empty-card\": \"Carte vide\",\n  \"ViewHeader.export-board-archive\": \"Exporter une archive du tableau\",\n  \"ViewHeader.export-complete\": \"Export terminé !\",\n  \"ViewHeader.export-csv\": \"Exporter au format CSV\",\n  \"ViewHeader.export-failed\": \"L'export a échoué !\",\n  \"ViewHeader.filter\": \"Filtre\",\n  \"ViewHeader.group-by\": \"Grouper par : {property}\",\n  \"ViewHeader.new\": \"Nouveau\",\n  \"ViewHeader.properties\": \"Propriétés\",\n  \"ViewHeader.properties-menu\": \"Menu propriétés\",\n  \"ViewHeader.search-text\": \"Rechercher des cartes\",\n  \"ViewHeader.select-a-template\": \"Sélectionner un modèle\",\n  \"ViewHeader.set-default-template\": \"Définir par défaut\",\n  \"ViewHeader.sort\": \"Trier\",\n  \"ViewHeader.untitled\": \"Sans titre\",\n  \"ViewHeader.view-header-menu\": \"Afficher le menu d'en-tête\",\n  \"ViewHeader.view-menu\": \"Afficher le menu\",\n  \"ViewLimitDialog.Heading\": \"Limite de vues par tableau atteinte\",\n  \"ViewLimitDialog.PrimaryButton.Title.Admin\": \"Mise à niveau\",\n  \"ViewLimitDialog.PrimaryButton.Title.RegularUser\": \"Notifier l'Admin\",\n  \"ViewLimitDialog.Subtext.Admin\": \"Passez à notre offre Professionnel ou Entreprise pour afficher les cartes archivées, avoir des vues illimitées par tableau, des cartes illimitées et plus encore.\",\n  \"ViewLimitDialog.Subtext.Admin.PricingPageLink\": \"En savoir plus sur nos offres.\",\n  \"ViewLimitDialog.Subtext.RegularUser\": \"Informez votre administrateur qu'il peut passer à notre offre professionnel ou d'entreprise pour avoir un nombre illimité de vues par tableau, un nombre illimité de cartes, et plus encore.\",\n  \"ViewLimitDialog.UpgradeImg.AltText\": \"mise à jour de l'image\",\n  \"ViewLimitDialog.notifyAdmin.Success\": \"Votre administrateur a été notifié\",\n  \"ViewTitle.hide-description\": \"cacher la description\",\n  \"ViewTitle.pick-icon\": \"Choisir une icône\",\n  \"ViewTitle.random-icon\": \"Aléatoire\",\n  \"ViewTitle.remove-icon\": \"Supprimer l'icône\",\n  \"ViewTitle.show-description\": \"montrer la description\",\n  \"ViewTitle.untitled-board\": \"Tableau sans titre\",\n  \"WelcomePage.Description\": \"Boards est un outil de gestion de projet qui permet d'organiser, de suivre et de gérer le travail entre équipes en utilisant des tableaux Kanban.\",\n  \"WelcomePage.Explore.Button\": \"Tutoriel\",\n  \"WelcomePage.Heading\": \"Bienvenue sur Boards\",\n  \"WelcomePage.NoThanks.Text\": \"Non merci, je vais me renseigner moi-même\",\n  \"WelcomePage.StartUsingIt.Text\": \"Commencez à l'utiliser\",\n  \"Workspace.editing-board-template\": \"Vous éditez un modèle de tableau.\",\n  \"badge.guest\": \"Invité\",\n  \"boardSelector.confirm-link-board\": \"Lier la carte au canal\",\n  \"boardSelector.confirm-link-board-button\": \"Oui, lier ce tableau\",\n  \"boardSelector.confirm-link-board-subtext\": \"Lorsque vous liez \\\"{boardName}\\\" au canal, tous les membres du canal (existants et nouveaux) pourront le modifier. Vous pouvez dissocier un tableau d'un canal à tout moment.\",\n  \"boardSelector.confirm-link-board-subtext-with-other-channel\": \"Lorsque vous liez \\\"{boardName}\\\" au canal, tous les membres du canal (existants et nouveaux) pourront le modifier.{lineBreak} Ce tableau est actuellement lié à un autre canal. Il sera dissocié si vous choisissez de le lier ici.\",\n  \"boardSelector.create-a-board\": \"Créer un tableau\",\n  \"boardSelector.link\": \"Lien\",\n  \"boardSelector.search-for-boards\": \"Rechercher des tableaux\",\n  \"boardSelector.title\": \"Lier les tableaux\",\n  \"boardSelector.unlink\": \"Détacher\",\n  \"calendar.month\": \"Mois\",\n  \"calendar.today\": \"AUJOURD'HUI\",\n  \"calendar.week\": \"Semaine\",\n  \"cloudMessage.learn-more\": \"En savoir plus\",\n  \"createImageBlock.failed\": \"Impossible de télécharger le fichier. Limite de taille de fichier atteinte.\",\n  \"default-properties.badges\": \"Commentaires et description\",\n  \"default-properties.title\": \"Titre\",\n  \"error.back-to-home\": \"Retour à la page d'accueil\",\n  \"error.back-to-team\": \"Retour à l'équipe\",\n  \"error.board-not-found\": \"Tableau non trouvé.\",\n  \"error.go-login\": \"Connexion\",\n  \"error.invalid-read-only-board\": \"Vous n'avez pas accès à ce tableau. Connectez-vous pour y accéder.\",\n  \"error.not-logged-in\": \"Votre session a peut-être expiré ou vous n’êtes pas connecté. Connectez-vous à nouveau pour accéder aux tableaux d'administration.\",\n  \"error.page.title\": \"Désolé, quelque chose s'est mal passé\",\n  \"error.team-undefined\": \"Ce n'est pas une équipe valable.\",\n  \"error.unknown\": \"Une erreur s'est produite.\",\n  \"generic.previous\": \"Précédent\",\n  \"guest-no-board.subtitle\": \"Vous n'avez pas encore accès à un tableau dans cette équipe, veuillez patienter jusqu'à ce que quelqu'un vous ajoute à un tableau.\",\n  \"guest-no-board.title\": \"Pas encore de tableaux\",\n  \"imagePaste.upload-failed\": \"Certains fichiers n'ont pas été téléchargés. Limite de taille de fichier atteinte\",\n  \"limitedCard.title\": \"Cartes cachées\",\n  \"login.log-in-button\": \"Connexion\",\n  \"login.log-in-title\": \"Connexion\",\n  \"login.register-button\": \"ou créez un compte si vous n'en avez pas\",\n  \"notification-box-card-limit-reached.close-tooltip\": \"Oublier pendant 10 jours\",\n  \"notification-box-card-limit-reached.contact-link\": \"informez votre administrateur\",\n  \"notification-box-card-limit-reached.link\": \"Passer à une offre payante\",\n  \"notification-box-card-limit-reached.title\": \"{cards} cartes cachées du tableau\",\n  \"notification-box-cards-hidden.title\": \"Cette action a caché une autre carte\",\n  \"notification-box.card-limit-reached.not-admin.text\": \"Pour accéder aux cartes archivées, vous pouvez {contactLink} pour passer à une offre payante.\",\n  \"notification-box.card-limit-reached.text\": \"Limite de cartes atteinte, pour afficher les anciennes cartes, {link}\",\n  \"person.add-user-to-board\": \"Ajouter {username} au tableau\",\n  \"person.add-user-to-board-confirm-button\": \"Ajouter au tableau\",\n  \"person.add-user-to-board-permissions\": \"Permissions\",\n  \"person.add-user-to-board-question\": \"Voulez-vous ajouter {username} au tableau ?\",\n  \"person.add-user-to-board-warning\": \"{username} n'est pas membre du conseil d'administration et ne recevra aucune notification à ce sujet.\",\n  \"register.login-button\": \"ou connectez-vous si vous avez déjà un compte\",\n  \"register.signup-title\": \"Inscrivez-vous pour créer un compte\",\n  \"rhs-board-non-admin-msg\": \"Vous n'êtes pas administrateur du forum\",\n  \"rhs-boards.add\": \"Ajouter\",\n  \"rhs-boards.dm\": \"DM\",\n  \"rhs-boards.gm\": \"GM\",\n  \"rhs-boards.header.dm\": \"ce message direct\",\n  \"rhs-boards.header.gm\": \"ce message de groupe\",\n  \"rhs-boards.last-update-at\": \"Dernière mise à jour à : {datetime}\",\n  \"rhs-boards.link-boards-to-channel\": \"Lier les tableaux à {channelName}\",\n  \"rhs-boards.linked-boards\": \"Tableaux liés\",\n  \"rhs-boards.no-boards-linked-to-channel\": \"Aucun tableau n'est lié à {channelName} pour le moment\",\n  \"rhs-boards.no-boards-linked-to-channel-description\": \"Boards est un outil de gestion de projet qui permet d'organiser, de suivre et de gérer le travail entre équipes en utilisant des tableaux Kanban.\",\n  \"rhs-boards.unlink-board\": \"Dissocier le tableau\",\n  \"rhs-boards.unlink-board1\": \"Dissocier le tableau\",\n  \"rhs-channel-boards-header.title\": \"Tableaux\",\n  \"share-board.publish\": \"Publier\",\n  \"share-board.share\": \"Partager\",\n  \"shareBoard.channels-select-group\": \"Canaux\",\n  \"shareBoard.confirm-change-team-role.body\": \"Tous les membres de ce forum avec une autorisation inférieure au rôle \\\"{role}\\\" seront <b>maintenant promus au {role}</b>. Êtes-vous sûr de vouloir modifier le rôle minimum ?\",\n  \"shareBoard.confirm-link-channel\": \"Lier le tableau au canal\",\n  \"shareBoard.confirm-link-channel-button\": \"Lien canal\",\n  \"shareBoard.confirm-link-channel-button-with-other-channel\": \"Dissocier et lier ici\",\n  \"shareBoard.confirm-link-channel-subtext\": \"Lorsque vous liez une chaîne à un tableau, tous les membres de la chaîne (existants et nouveaux) pourront le modifier.\",\n  \"shareBoard.confirm-link-channel-subtext-with-other-channel\": \"Lorsque vous liez un canal à un tableau, tous les membres du canal (existants et nouveaux) pourront le modifier.{lineBreak}Ce tableau est actuellement lié à un autre canal. Il sera dissocié si vous choisissez de le lier ici.\",\n  \"shareBoard.confirm-unlink.body\": \"Lorsque vous dissociez une chaîne d'un tableau, tous les membres de la chaîne (existants et nouveaux) n'y auront plus accès, sauf s'ils en ont reçu l'autorisation séparément.\",\n  \"shareBoard.confirm-unlink.confirmBtnText\": \"Dissocier le canal\",\n  \"shareBoard.confirm-unlink.title\": \"Dissocier le canal du tableau\",\n  \"shareBoard.lastAdmin\": \"Les conseils doivent avoir au moins un administrateur\",\n  \"shareBoard.members-select-group\": \"Membres\",\n  \"shareBoard.unknown-channel-display-name\": \"Canal inconnu\",\n  \"tutorial_tip.finish_tour\": \"Terminé\",\n  \"tutorial_tip.got_it\": \"J'ai compris\",\n  \"tutorial_tip.ok\": \"Suivant\",\n  \"tutorial_tip.out\": \"Désactiver les conseils.\",\n  \"tutorial_tip.seen\": \"Déjà vu ?\"\n}\n"
  },
  {
    "path": "webapp/i18n/he.json",
    "content": "{\n  \"BoardComponent.add-a-group\": \"+ הוספת קבוצה\",\n  \"BoardComponent.delete\": \"מחיקה\",\n  \"BoardComponent.hidden-columns\": \"עמודות מוסתרות\",\n  \"BoardComponent.hide\": \"הסתרה\",\n  \"BoardComponent.new\": \"+ חדש\",\n  \"BoardComponent.no-property\": \"ללא {property}\",\n  \"BoardComponent.no-property-title\": \"יופיעו כאן פריטים עם מאפיין {property}  ריק. עמודה זו אינה ניתנת להסרה.\",\n  \"BoardComponent.show\": \"הצגה\",\n  \"BoardMember.schemeAdmin\": \"מנהל מערכת\",\n  \"BoardMember.schemeCommenter\": \"משתמש מגיב\",\n  \"BoardMember.schemeEditor\": \"עורך\",\n  \"BoardMember.schemeNone\": \"ללא\",\n  \"BoardMember.schemeViewer\": \"צופה\",\n  \"BoardMember.unlinkChannel\": \"ניתוק\",\n  \"BoardPage.newVersion\": \"ישנה גרסה חדשה וזמינה של לוחות, לטעינה מחדש לחץ כאן.\",\n  \"BoardPage.syncFailed\": \"ייתכן שהלוח נמחק או שנשללה ממך גישה\",\n  \"BoardTemplateSelector.add-template\": \"יצירת תבנית חדשה\",\n  \"BoardTemplateSelector.create-empty-board\": \"יצירת לוח ריק\",\n  \"BoardTemplateSelector.delete-template\": \"מחיקה\",\n  \"BoardTemplateSelector.description\": \"הוספת לוח לתפריט הצד באמצעות אחת התבניות, או יצירת תבנית חדשה\",\n  \"BoardTemplateSelector.edit-template\": \"עריכה\",\n  \"BoardTemplateSelector.plugin.no-content-description\": \"הוספת לוח לתפריט הצד באמצעות אחת מן התבניות שהוגדרו או יצירת תבנית חדשה.\",\n  \"BoardTemplateSelector.plugin.no-content-title\": \"יצירת לוח\",\n  \"BoardTemplateSelector.title\": \"יצירת לוח\",\n  \"BoardTemplateSelector.use-this-template\": \"שימוש בתבנית זו\",\n  \"BoardsSwitcher.Title\": \"חיפוש לוחות\",\n  \"BoardsUnfurl.Limited\": \"פרטים נוספים מוסתרים מאחר והכרטיס הועבר לארכיון\",\n  \"BoardsUnfurl.Remainder\": \"עוד {remainder}\",\n  \"BoardsUnfurl.Updated\": \"{time} מעודכן\",\n  \"Calculations.Options.average.displayName\": \"ממוצע\",\n  \"Calculations.Options.average.label\": \"ממוצע\",\n  \"Calculations.Options.count.displayName\": \"כמות\",\n  \"Calculations.Options.count.label\": \"כמות\",\n  \"Calculations.Options.countChecked.displayName\": \"סומנו\",\n  \"Calculations.Options.countChecked.label\": \"כמות שנבחרו\",\n  \"Calculations.Options.countUnchecked.displayName\": \"הסרת סימון\",\n  \"Calculations.Options.countUnchecked.label\": \"כמות שלא סומנו\",\n  \"Calculations.Options.countUniqueValue.displayName\": \"ייחודי\",\n  \"Calculations.Options.countUniqueValue.label\": \"כמות ערכים יחידניים\",\n  \"Calculations.Options.countValue.displayName\": \"ערכים\",\n  \"Calculations.Options.countValue.label\": \"כמות ערכים\",\n  \"Calculations.Options.dateRange.displayName\": \"טווח\",\n  \"Calculations.Options.dateRange.label\": \"טווח\",\n  \"Calculations.Options.earliest.displayName\": \"המוקדם ביותר\",\n  \"Calculations.Options.earliest.label\": \"המוקדם ביותר\",\n  \"Calculations.Options.latest.displayName\": \"המאוחר ביותר\",\n  \"Calculations.Options.latest.label\": \"המאוחר ביותר\",\n  \"Calculations.Options.max.displayName\": \"מקסימום\",\n  \"Calculations.Options.max.label\": \"מקסימום\",\n  \"Calculations.Options.median.displayName\": \"חציון\",\n  \"Calculations.Options.median.label\": \"חציון\",\n  \"Calculations.Options.min.displayName\": \"מינימום\",\n  \"Calculations.Options.min.label\": \"מינימום\",\n  \"Calculations.Options.none.displayName\": \"חישוב\",\n  \"Calculations.Options.none.label\": \"ללא\",\n  \"Calculations.Options.percentChecked.displayName\": \"סומן\",\n  \"Calculations.Options.percentChecked.label\": \"אחוז ערכים שסומנו\",\n  \"Calculations.Options.percentUnchecked.displayName\": \"לא סומנו\",\n  \"Calculations.Options.percentUnchecked.label\": \"אחוזים שלא סומנו\",\n  \"Calculations.Options.range.displayName\": \"טווח\",\n  \"Calculations.Options.range.label\": \"טווח\",\n  \"Calculations.Options.sum.displayName\": \"סכום\",\n  \"Calculations.Options.sum.label\": \"סכום\",\n  \"CalendarCard.untitled\": \"ללא כותרת\",\n  \"CardActionsMenu.copiedLink\": \"הועתק!\",\n  \"CardActionsMenu.copyLink\": \"העתקת קישור\",\n  \"CardActionsMenu.delete\": \"מחיקה\",\n  \"CardActionsMenu.duplicate\": \"שיכפול\",\n  \"CardBadges.title-checkboxes\": \"תיבות סימון\",\n  \"CardBadges.title-comments\": \"הערות\",\n  \"CardBadges.title-description\": \"כרטיס זה מכיל תיאור\",\n  \"CardDetail.Follow\": \"מעקב\",\n  \"CardDetail.Following\": \"עוקב\",\n  \"CardDetail.add-content\": \"הוספת מידע\",\n  \"CardDetail.add-icon\": \"הוספת אייקון\",\n  \"CardDetail.add-property\": \"+ הוספת תכונה\",\n  \"CardDetail.addCardText\": \"הוספת מלל הכרטיס\",\n  \"CardDetail.limited-body\": \"שדרג למסלול Professional או למסלול Enterprise ותוכל לצפות בכרטיסיות שהועברו לארכיון, לצפות במבטים על לוחות ללא הגבלה, לפתוח כרטיסיות ללא הגבלה ועוד.\",\n  \"CardDetail.limited-button\": \"שדרוג\",\n  \"CardDetail.limited-title\": \"כרטיס זה מוסתר\",\n  \"CardDetail.moveContent\": \"הזזת תוכן הכרטיס\",\n  \"CardDetail.new-comment-placeholder\": \"הוספת הערה...\",\n  \"CardDetailProperty.confirm-delete-heading\": \"אישור מחיקת תכונה\",\n  \"CardDetailProperty.confirm-delete-subtext\": \"האם הינך בטוח שאתה מעוניין במחיקת התכונה \\\"{propertyName}\\\"? מחיקת התכונה תגרור את מחיקתה בכל הכרטיסים שבלוח זה.\",\n  \"CardDetailProperty.confirm-property-name-change-subtext\": \"האם הינך בטוח ברצונך לשנות את התכונה \\\"{propertyName}\\\"' \\\"{customText}\\\"? זה ישפיע על ערך.ים לאורך {numOfCards} כרטיס.ים בלוח זה, ויכול להוביל לאיבוד מידע.\",\n  \"CardDetailProperty.confirm-property-type-change\": \"אישור שינוי סוג תכונה\",\n  \"CardDetailProperty.delete-action-button\": \"מחיקה\",\n  \"CardDetailProperty.property-change-action-button\": \"שינוי תכונה\",\n  \"CardDetailProperty.property-changed\": \"תכונה השתנתה בהצלחה!\",\n  \"CardDetailProperty.property-deleted\": \"מחיקת {propertyName} בוצעה בהצלחה!\",\n  \"CardDetailProperty.property-name-change-subtext\": \"סוג מ \\\"{oldPropType}\\\" אל \\\"{newPropType}\\\"\",\n  \"CardDetial.limited-link\": \"מידע נוסף לגבי המסלולים שלנו.\",\n  \"CardDialog.delete-confirmation-dialog-button-text\": \"מחיקה\",\n  \"CardDialog.delete-confirmation-dialog-heading\": \"מחיקת כרטיס מאושרת!\",\n  \"CardDialog.editing-template\": \"הינך מעדכן תבנית.\",\n  \"CardDialog.nocard\": \"כרטיס זה אינו קיים או ללא גישה.\",\n  \"Categories.CreateCategoryDialog.CancelText\": \"ביטול\",\n  \"Categories.CreateCategoryDialog.CreateText\": \"יצירה\",\n  \"Categories.CreateCategoryDialog.Placeholder\": \"שם הקטגוריה שלך\",\n  \"Categories.CreateCategoryDialog.UpdateText\": \"עדכון\",\n  \"CenterPanel.Login\": \"כניסה\",\n  \"CenterPanel.Share\": \"שיתוף\",\n  \"ColorOption.selectColor\": \"בחירת צבע {color}\",\n  \"Comment.delete\": \"מחיקה\",\n  \"CommentsList.send\": \"שליחה\",\n  \"ConfirmationDialog.cancel-action\": \"ביטול\",\n  \"ConfirmationDialog.confirm-action\": \"אישור\",\n  \"ContentBlock.Delete\": \"מחיקה\",\n  \"ContentBlock.DeleteAction\": \"מחיקה\",\n  \"ContentBlock.addElement\": \"הוספת {type}\",\n  \"ContentBlock.checkbox\": \"תיבת סימון\",\n  \"ContentBlock.divider\": \"מפריד\",\n  \"ContentBlock.editCardCheckbox\": \"תיבת סימון מתחלפת\",\n  \"ContentBlock.editCardCheckboxText\": \"עריכת מלל כרטיס\",\n  \"ContentBlock.editCardText\": \"עריכת מלל כרטיס\"\n}\n"
  },
  {
    "path": "webapp/i18n/hr.json",
    "content": "{\n  \"AdminBadge.SystemAdmin\": \"Administrator\",\n  \"AdminBadge.TeamAdmin\": \"Tim administratora\",\n  \"AppBar.Tooltip\": \"Uklj./Isklj. povezane ploče\",\n  \"Attachment.Attachment-title\": \"Prilog\",\n  \"AttachmentBlock.DeleteAction\": \"izbriši\",\n  \"AttachmentBlock.addElement\": \"dodaj {type}\",\n  \"AttachmentBlock.delete\": \"Prilog je izbrisan.\",\n  \"AttachmentBlock.failed\": \"Nije moguće prenijeti datoteku jer je dosegnuta granica veličine datoteke.\",\n  \"AttachmentBlock.upload\": \"Prijenos priloga.\",\n  \"AttachmentBlock.uploadSuccess\": \"Prilog je prenesen.\",\n  \"AttachmentElement.delete-confirmation-dialog-button-text\": \"Izbriši\",\n  \"AttachmentElement.download\": \"Preuzmi\",\n  \"AttachmentElement.upload-percentage\": \"Prijenos … ({uploadPercent} %)\",\n  \"BoardComponent.add-a-group\": \"+ Dodaj grupu\",\n  \"BoardComponent.delete\": \"Izbriši\",\n  \"BoardComponent.hidden-columns\": \"Skriveni stupci\",\n  \"BoardComponent.hide\": \"Sakrij\",\n  \"BoardComponent.new\": \"+ Novo\",\n  \"BoardComponent.no-property\": \"Bez svojstva {property}\",\n  \"BoardComponent.no-property-title\": \"Elementi s praznim svojstvom {property} smjestit će se ovdje. Ovaj se stupac ne može ukloniti.\",\n  \"BoardComponent.show\": \"Prikaži\",\n  \"BoardMember.schemeAdmin\": \"Administrator\",\n  \"BoardMember.schemeCommenter\": \"Komentator\",\n  \"BoardMember.schemeEditor\": \"Urednik\",\n  \"BoardMember.schemeNone\": \"Ništa\",\n  \"BoardMember.schemeViewer\": \"Gledatelj\",\n  \"BoardMember.unlinkChannel\": \"Odspoji\",\n  \"BoardPage.newVersion\": \"Dostupna je nova verzija za „Ploče”. Pritisni ovdje za ponovno učitavanje.\",\n  \"BoardPage.syncFailed\": \"Ploča se može izbrisati ili pristup opozvati.\",\n  \"BoardTemplateSelector.add-template\": \"Stvori novi predložak\",\n  \"BoardTemplateSelector.create-empty-board\": \"Stvori praznu ploču\",\n  \"BoardTemplateSelector.delete-template\": \"Izbriši\",\n  \"BoardTemplateSelector.description\": \"Za početak odaberi predložak. Prilagodi predložak kako bi odgovarao tvojim potrebama ili stvori praznu ploču.\",\n  \"BoardTemplateSelector.edit-template\": \"Uredi\",\n  \"BoardTemplateSelector.plugin.no-content-description\": \"Dodaj ploču u bočnu traku koristeći bilo koji od niže dolje definiranih predložaka ili počni ispočetka.\",\n  \"BoardTemplateSelector.plugin.no-content-title\": \"Stvori ploču\",\n  \"BoardTemplateSelector.title\": \"Stvori ploču\",\n  \"BoardTemplateSelector.use-this-template\": \"Koristi ovaj predložak\",\n  \"BoardsSwitcher.Title\": \"Pronađi ploče\",\n  \"BoardsUnfurl.Limited\": \"Dodatni detalji su skriveni jer je kartica arhivirana\",\n  \"BoardsUnfurl.Remainder\": \"+ još {remainder}\",\n  \"BoardsUnfurl.Updated\": \"Aktulaizirano {time}\",\n  \"Calculations.Options.average.displayName\": \"Prosjek\",\n  \"Calculations.Options.average.label\": \"Prosjek\",\n  \"Calculations.Options.count.displayName\": \"Broji\",\n  \"Calculations.Options.count.label\": \"Broji\",\n  \"Calculations.Options.countChecked.displayName\": \"Označeno\",\n  \"Calculations.Options.countChecked.label\": \"Broji označene\",\n  \"Calculations.Options.countUnchecked.displayName\": \"Neoznačeno\",\n  \"Calculations.Options.countUnchecked.label\": \"Broji neoznačene\",\n  \"Calculations.Options.countUniqueValue.displayName\": \"Jedinstveno\",\n  \"Calculations.Options.countUniqueValue.label\": \"Broji jedinstvene vrijednosti\",\n  \"Calculations.Options.countValue.displayName\": \"Vrijednosti\",\n  \"Calculations.Options.countValue.label\": \"Broji vrijednost\",\n  \"Calculations.Options.dateRange.displayName\": \"Raspon\",\n  \"Calculations.Options.dateRange.label\": \"Raspon\",\n  \"Calculations.Options.earliest.displayName\": \"Najraniji\",\n  \"Calculations.Options.earliest.label\": \"Najraniji\",\n  \"Calculations.Options.latest.displayName\": \"Najnoviji\",\n  \"Calculations.Options.latest.label\": \"Najnoviji\",\n  \"Calculations.Options.max.displayName\": \"Maks.\",\n  \"Calculations.Options.max.label\": \"Maks.\",\n  \"Calculations.Options.median.displayName\": \"Medijan\",\n  \"Calculations.Options.median.label\": \"Medijan\",\n  \"Calculations.Options.min.displayName\": \"Min.\",\n  \"Calculations.Options.min.label\": \"Min.\",\n  \"Calculations.Options.none.displayName\": \"Izračunaj\",\n  \"Calculations.Options.none.label\": \"Ništa\",\n  \"Calculations.Options.percentChecked.displayName\": \"Provjereno\",\n  \"Calculations.Options.percentChecked.label\": \"Provjeren postotak\",\n  \"Calculations.Options.percentUnchecked.displayName\": \"Neprovjereno\",\n  \"Calculations.Options.percentUnchecked.label\": \"Neprovjeren postotak\",\n  \"Calculations.Options.range.displayName\": \"Raspon\",\n  \"Calculations.Options.range.label\": \"Raspon\",\n  \"Calculations.Options.sum.displayName\": \"Zbroj\",\n  \"Calculations.Options.sum.label\": \"Zbroj\",\n  \"CalendarCard.untitled\": \"Bez naslova\",\n  \"CardActionsMenu.copiedLink\": \"Kopirano!\",\n  \"CardActionsMenu.copyLink\": \"Kopiraj poveznicu\",\n  \"CardActionsMenu.delete\": \"Izbriši\",\n  \"CardActionsMenu.duplicate\": \"Dupliciraj\",\n  \"CardBadges.title-checkboxes\": \"Označiva polja\",\n  \"CardBadges.title-comments\": \"Komentari\",\n  \"CardBadges.title-description\": \"Ova kartica ima opis\",\n  \"CardDetail.Attach\": \"Priloži\",\n  \"CardDetail.Follow\": \"Prati\",\n  \"CardDetail.Following\": \"Pratiš\",\n  \"CardDetail.add-content\": \"Dodaj sadržaj\",\n  \"CardDetail.add-icon\": \"Dodaj ikonu\",\n  \"CardDetail.add-property\": \"+ Dodaj svojstvo\",\n  \"CardDetail.addCardText\": \"dodaj tekst kartice\",\n  \"CardDetail.limited-body\": \"Nadogradi na našu profesionalnu tarifu ili na tarifu za poduzeća.\",\n  \"CardDetail.limited-button\": \"Nadogradi\",\n  \"CardDetail.limited-title\": \"Ova je kartica skrivena\",\n  \"CardDetail.moveContent\": \"Pomakni sadržaj kartice\",\n  \"CardDetail.new-comment-placeholder\": \"Dodaj komentar …\",\n  \"CardDetailProperty.confirm-delete-heading\": \"Potvrdi brisanje svojstva\",\n  \"CardDetailProperty.confirm-delete-subtext\": \"Stvarno želiš izbrisati svojstvo „{propertyName}”? Brisanjem će se izbrisati svojstvo sa svih kartica na ovoj ploči.\",\n  \"CardDetailProperty.confirm-property-name-change-subtext\": \"Stvarno želiš promijeniti svojstvo „{propertyName}” {customText}? To će utjecati na vrijednosti na {numOfCards} kartica na ovoj ploči i može prouzročiti gubitak podataka.\",\n  \"CardDetailProperty.confirm-property-type-change\": \"Potvrdi promjenu vrste svojstva\",\n  \"CardDetailProperty.delete-action-button\": \"Izbriši\",\n  \"CardDetailProperty.property-change-action-button\": \"Promijeni svojstvo\",\n  \"CardDetailProperty.property-changed\": \"Promjena svojstva uspjela!\",\n  \"CardDetailProperty.property-deleted\": \"Svojstvo {propertyName} uspješno izbrisano!\",\n  \"CardDetailProperty.property-name-change-subtext\": \"vrste „{oldPropType}” u „{newPropType}”\",\n  \"CardDetial.limited-link\": \"Saznaj više o našim tarifma.\",\n  \"CardDialog.delete-confirmation-dialog-attachment\": \"Potvrdi brisanje priloga\",\n  \"CardDialog.delete-confirmation-dialog-button-text\": \"Izbriši\",\n  \"CardDialog.delete-confirmation-dialog-heading\": \"Potvrdi brisanje kartice\",\n  \"CardDialog.editing-template\": \"Uređuješ predložak.\",\n  \"CardDialog.nocard\": \"Ova kartica ne postoji ili je nedostupna.\",\n  \"Categories.CreateCategoryDialog.CancelText\": \"Odustani\",\n  \"Categories.CreateCategoryDialog.CreateText\": \"Stvori\",\n  \"Categories.CreateCategoryDialog.Placeholder\": \"Zadaj ime kategoriji\",\n  \"Categories.CreateCategoryDialog.UpdateText\": \"Aktualiziraj\",\n  \"CenterPanel.Login\": \"Prijava\",\n  \"CenterPanel.Share\": \"Dijeli\",\n  \"ChannelIntro.CreateBoard\": \"Stvori ploču\",\n  \"ColorOption.selectColor\": \"Odaberi boju {color}\",\n  \"Comment.delete\": \"Izbriši\",\n  \"CommentsList.send\": \"Pošalji\",\n  \"ConfirmPerson.empty\": \"Prazno\",\n  \"ConfirmPerson.search\": \"Traži …\",\n  \"ConfirmationDialog.cancel-action\": \"Odustani\",\n  \"ConfirmationDialog.confirm-action\": \"Potvrdi\",\n  \"ContentBlock.Delete\": \"Izbriši\",\n  \"ContentBlock.DeleteAction\": \"izbriši\",\n  \"ContentBlock.addElement\": \"dodaj {type}\",\n  \"ContentBlock.checkbox\": \"označivo polje\",\n  \"ContentBlock.divider\": \"razdjeljivač\",\n  \"ContentBlock.editCardCheckbox\": \"uklj./isklj. označivo polje\",\n  \"ContentBlock.editCardCheckboxText\": \"uredi tekst kartice\",\n  \"ContentBlock.editCardText\": \"uredi tekst kartice\",\n  \"ContentBlock.editText\": \"Uredi tekst …\",\n  \"ContentBlock.image\": \"slika\",\n  \"ContentBlock.insertAbove\": \"Umetni iznad\",\n  \"ContentBlock.moveBlock\": \"premjesti sadržaj kartice\",\n  \"ContentBlock.moveDown\": \"Pomakni dolje\",\n  \"ContentBlock.moveUp\": \"Pomakni gore\",\n  \"ContentBlock.text\": \"tekst\",\n  \"DateFilter.empty\": \"Prazno\",\n  \"DateRange.clear\": \"Isprazni\",\n  \"DateRange.empty\": \"Prazno\",\n  \"DateRange.endDate\": \"Datum kraja\",\n  \"DateRange.today\": \"Danas\",\n  \"DeleteBoardDialog.confirm-cancel\": \"Odustani\",\n  \"DeleteBoardDialog.confirm-delete\": \"Izbriši\",\n  \"DeleteBoardDialog.confirm-info\": \"Stvarno želiš izbrisati ploču „{boardTitle}”? Brisanjem će se izbrisati sve kartice na ploči.\",\n  \"DeleteBoardDialog.confirm-info-template\": \"Stvrano želiš izbrisati predložak ploče „{boardTitle}”?\",\n  \"DeleteBoardDialog.confirm-tite\": \"Potvrdi brisanje ploče\",\n  \"DeleteBoardDialog.confirm-tite-template\": \"Potvrdi brisanje predloška za ploče\",\n  \"Dialog.closeDialog\": \"Zatvori dijalog\",\n  \"EditableDayPicker.today\": \"Danas\",\n  \"Error.mobileweb\": \"Web podrška za mobilne uređaje trenutačno se nalazi u ranoj beta verziji. Nekih funkcionalsnosti možda još nema.\",\n  \"Error.websocket-closed\": \"Veza s websocketom je zatvorena, veza je prekinuta. Ako problem ustraje, provjeri konfiguraciju poslužitelja ili web proxyja.\",\n  \"Filter.contains\": \"sadrži\",\n  \"Filter.ends-with\": \"završava sa\",\n  \"Filter.includes\": \"uključuje\",\n  \"Filter.is\": \"je\",\n  \"Filter.is-after\": \"je nakon\",\n  \"Filter.is-before\": \"je prije\",\n  \"Filter.is-empty\": \"je prazno\",\n  \"Filter.is-not-empty\": \"nije prazno\",\n  \"Filter.is-not-set\": \"nije postavljeno\",\n  \"Filter.is-set\": \"je postavljeno\",\n  \"Filter.isafter\": \"je nakon\",\n  \"Filter.isbefore\": \"je prije\",\n  \"Filter.not-contains\": \"ne sadrži\",\n  \"Filter.not-ends-with\": \"ne završava sa\",\n  \"Filter.not-includes\": \"ne uključuje\",\n  \"Filter.not-starts-with\": \"ne počinje sa\",\n  \"Filter.starts-with\": \"počinje sa\",\n  \"FilterByText.placeholder\": \"filtriraj tekst\",\n  \"FilterComponent.add-filter\": \"+ Dodaj filtar\",\n  \"FilterComponent.delete\": \"Izbriši\",\n  \"FilterValue.empty\": \"(prazno)\",\n  \"FindBoardsDialog.IntroText\": \"Traži ploče\",\n  \"FindBoardsDialog.NoResultsFor\": \"Nema rezultata za „{searchQuery}”\",\n  \"FindBoardsDialog.NoResultsSubtext\": \"Provjeri pravopis ili pretraži s jednim drugim pojmom.\",\n  \"FindBoardsDialog.SubTitle\": \"Utipkaj ime za pronalaženje ploče. Koristi <b>GORE/DOLJE</b> za pretraživanje. <b>ENTER</b> za odabiranje, <b>ESC</b> za prekid\",\n  \"FindBoardsDialog.Title\": \"Pronađi ploče\",\n  \"GroupBy.hideEmptyGroups\": \"Sakrij {count} prazne grupe\",\n  \"GroupBy.showHiddenGroups\": \"Prikaži {count} skrivene grupe\",\n  \"GroupBy.ungroup\": \"Razgrupiraj\",\n  \"HideBoard.MenuOption\": \"Sakrij ploču\",\n  \"KanbanCard.untitled\": \"Bez naslova\",\n  \"MentionSuggestion.is-not-board-member\": \"(nije član u ploči)\",\n  \"Mutator.new-board-from-template\": \"nova ploča iz predloška\",\n  \"Mutator.new-card-from-template\": \"nova kartica iz predloška\",\n  \"Mutator.new-template-from-card\": \"novi predložak iz kartice\",\n  \"OnboardingTour.AddComments.Body\": \"Probleme možeš komentirati. Možeš čak i @spomenuti svoje Mattermost kolege za privlačenje njihove pozornosti.\",\n  \"OnboardingTour.AddComments.Title\": \"Dodaj komentare\",\n  \"OnboardingTour.AddDescription.Body\": \"Dodaj opis za tvoju karticu kako bi tvoji članovi tima znali o čemu se radi.\",\n  \"OnboardingTour.AddDescription.Title\": \"Dodaj opis\",\n  \"OnboardingTour.AddProperties.Body\": \"Dodaj razna svojstva karticama kako bi bile još snažnije.\",\n  \"OnboardingTour.AddProperties.Title\": \"Dodaj svojstva\",\n  \"OnboardingTour.AddView.Body\": \"Prijeđi ovamo za stvaranje novog prikaza za organiziranje tvoje ploče koristeći različite rasporede.\",\n  \"OnboardingTour.AddView.Title\": \"Dodaj novi prikaz\",\n  \"OnboardingTour.CopyLink.Body\": \"Svoje kartice možeš dijeliti s članovima tima pomuću kopiranja i umetanja poveznice u kanal, izravnu poruku ili grupnu poruku.\",\n  \"OnboardingTour.CopyLink.Title\": \"Kopiraj poveznicu\",\n  \"OnboardingTour.OpenACard.Body\": \"Otvori jednu karticu i istraži načine kako ti Ploče mogu pomoći organizirati tvoj rad.\",\n  \"OnboardingTour.OpenACard.Title\": \"Otvori jednu karticu\",\n  \"OnboardingTour.ShareBoard.Body\": \"Tvoju ploču možeš dijeliti interno, unutar tvog tima ili je javno objaviti radi vidljivosti izvan tvoje organizacije.\",\n  \"OnboardingTour.ShareBoard.Title\": \"Dijeli ploču\",\n  \"PersonProperty.board-members\": \"Članovi ploče\",\n  \"PersonProperty.me\": \"Ja\",\n  \"PersonProperty.non-board-members\": \"Ne članovi ploče\",\n  \"PropertyMenu.Delete\": \"Izbriši\",\n  \"PropertyMenu.changeType\": \"Promijei vrstu svojstva\",\n  \"PropertyMenu.selectType\": \"Odaberi vrstu svojstva\",\n  \"PropertyMenu.typeTitle\": \"Vrsta\",\n  \"PropertyType.Checkbox\": \"Označivo polje\",\n  \"PropertyType.CreatedBy\": \"Stvoreno od\",\n  \"PropertyType.CreatedTime\": \"Vrijeme stvaranja\",\n  \"PropertyType.Date\": \"Datum\",\n  \"PropertyType.Email\": \"E-mail adresa\",\n  \"PropertyType.MultiPerson\": \"Više osoba\",\n  \"PropertyType.MultiSelect\": \"Višestruki odabir\",\n  \"PropertyType.Number\": \"Broj\",\n  \"PropertyType.Person\": \"Osoba\",\n  \"PropertyType.Phone\": \"Telefon\",\n  \"PropertyType.Select\": \"Odaberi\",\n  \"PropertyType.Text\": \"Tekst\",\n  \"PropertyType.Unknown\": \"Nepoznato\",\n  \"PropertyType.UpdatedBy\": \"Autor zadnjeg aktualiziranja\",\n  \"PropertyType.UpdatedTime\": \"Vrijme zadnjeg aktualiziranja\",\n  \"PropertyType.Url\": \"URL\",\n  \"PropertyValueElement.empty\": \"Prazno\",\n  \"RegistrationLink.confirmRegenerateToken\": \"Ovo će poništiti prethodno dijeljene poveznice. Nastaviti?\",\n  \"RegistrationLink.copiedLink\": \"Kopirano!\",\n  \"RegistrationLink.copyLink\": \"Kopiraj poveznicu\",\n  \"RegistrationLink.description\": \"Dijeli ovu poveznicu kako bi drugi mogli stvoriti račune:\",\n  \"RegistrationLink.regenerateToken\": \"Ponovo generiraj token\",\n  \"RegistrationLink.tokenRegenerated\": \"Poveznica za registraciju je ponovo generirana\",\n  \"ShareBoard.PublishDescription\": \"Objavi i dijeli poveznicu „samo za čitanje” sa svima na webu.\",\n  \"ShareBoard.PublishTitle\": \"Objavi na webu\",\n  \"ShareBoard.ShareInternal\": \"Dijeli interno\",\n  \"ShareBoard.ShareInternalDescription\": \"Korisnici koji imaju prava moći će koristiti ovu poveznicu.\",\n  \"ShareBoard.Title\": \"Dijeli ploču\",\n  \"ShareBoard.confirmRegenerateToken\": \"Ovo će poništiti prethodno dijeljene poveznice. Nastaviti?\",\n  \"ShareBoard.copiedLink\": \"Kopirano!\",\n  \"ShareBoard.copyLink\": \"Kopiraj poveznicu\",\n  \"ShareBoard.regenerate\": \"Ponovo generiraj token\",\n  \"ShareBoard.searchPlaceholder\": \"Traži ljude\",\n  \"ShareBoard.teamPermissionsText\": \"Svatko u timu {teamName}\",\n  \"ShareBoard.tokenRegenrated\": \"Token je ponovo generiran\",\n  \"ShareBoard.userPermissionsRemoveMemberText\": \"Ukloni člana\",\n  \"ShareBoard.userPermissionsYouText\": \"(Ti)\",\n  \"ShareTemplate.Title\": \"Dijeli predložak\",\n  \"ShareTemplate.searchPlaceholder\": \"Traži osobe\",\n  \"Sidebar.about\": \"O programu Focalboard\",\n  \"Sidebar.add-board\": \"+ Dodaj ploču\",\n  \"Sidebar.changePassword\": \"Promijeni lozinku\",\n  \"Sidebar.delete-board\": \"Izbriši ploču\",\n  \"Sidebar.duplicate-board\": \"Dupliciraj ploču\",\n  \"Sidebar.export-archive\": \"Izvezi arhivu\",\n  \"Sidebar.import\": \"Uvezi\",\n  \"Sidebar.import-archive\": \"Uvezi arhivu\",\n  \"Sidebar.invite-users\": \"Pozovi korisnika\",\n  \"Sidebar.logout\": \"Odjavi se\",\n  \"Sidebar.new-category.badge\": \"Nova\",\n  \"Sidebar.new-category.drag-boards-cta\": \"Povuci ploče ovamo …\",\n  \"Sidebar.no-boards-in-category\": \"Nema ploča u kategoriji\",\n  \"Sidebar.product-tour\": \"Pregled proizvoda\",\n  \"Sidebar.random-icons\": \"Slučajne ikone\",\n  \"Sidebar.set-language\": \"Postavi jezik\",\n  \"Sidebar.set-theme\": \"Postavi temu\",\n  \"Sidebar.settings\": \"Postavke\",\n  \"Sidebar.template-from-board\": \"Novi predložak iz ploče\",\n  \"Sidebar.untitled-board\": \"(Ploča bez naslova)\",\n  \"Sidebar.untitled-view\": \"(Neimenovani prikaz)\",\n  \"SidebarCategories.BlocksMenu.Move\": \"Premjesti u …\",\n  \"SidebarCategories.CategoryMenu.CreateNew\": \"Stvori novu kategoriju\",\n  \"SidebarCategories.CategoryMenu.Delete\": \"Izbriši kategoriju\",\n  \"SidebarCategories.CategoryMenu.DeleteModal.Body\": \"Ploče u kategoriji <b>{categoryName}</b> vratit će se u kategorije ploča. Nećeš biti uklonjen/a s nijedne ploče.\",\n  \"SidebarCategories.CategoryMenu.DeleteModal.Title\": \"Izbrisati ovu kategoriju?\",\n  \"SidebarCategories.CategoryMenu.Update\": \"Preimenuj kategoriju\",\n  \"SidebarTour.ManageCategories.Body\": \"Stvori vlastite kategorije i upravljaj njima. Kategorije se spremaju za svakog korisnika zasebno, tako da premještanje ploče u tvoju kategoriju neće utjecati na druge članove koji koriste istu ploču.\",\n  \"SidebarTour.ManageCategories.Title\": \"Upravljaj kategorijama\",\n  \"SidebarTour.SearchForBoards.Body\": \"Otvori sklopku ploča (Cmd/Ctrl + K) za brzo pretraživanje i dodavanje ploča u bočnu traku.\",\n  \"SidebarTour.SearchForBoards.Title\": \"Traži ploče\",\n  \"SidebarTour.SidebarCategories.Body\": \"Sve tvoje ploče se sada nalaze u tvojoj novoj bočnoj traci. Nema više prebacivanja između radnih prostora. Jednokratne prilagođene kategorije temeljene na tvojim prethodnim radnim prostorima su možda automatski stvorene tijekom nadogradnje na v7.2. Ako želiš, možeš ih ukloniti ili urediti.\",\n  \"SidebarTour.SidebarCategories.Link\": \"Saznaj više\",\n  \"SidebarTour.SidebarCategories.Title\": \"Kategorije u bočnoj traci\",\n  \"SiteStats.total_boards\": \"Ukupno ploča\",\n  \"SiteStats.total_cards\": \"Ukupno kartica\",\n  \"TableComponent.add-icon\": \"Dodaj ikonu\",\n  \"TableComponent.name\": \"Ime\",\n  \"TableComponent.plus-new\": \"+ Novo\",\n  \"TableHeaderMenu.delete\": \"Izbriši\",\n  \"TableHeaderMenu.duplicate\": \"Dupliciraj\",\n  \"TableHeaderMenu.hide\": \"Sakrij\",\n  \"TableHeaderMenu.insert-left\": \"Umetni lijevo\",\n  \"TableHeaderMenu.insert-right\": \"Umetni desno\",\n  \"TableHeaderMenu.sort-ascending\": \"Razvrstaj uzlazno\",\n  \"TableHeaderMenu.sort-descending\": \"Razvrstaj silazno\",\n  \"TableRow.DuplicateCard\": \"dupliciraj karticu\",\n  \"TableRow.MoreOption\": \"Daljnje radnje\",\n  \"TableRow.open\": \"Otvori\",\n  \"TopBar.give-feedback\": \"Pošalji povratne informacije\",\n  \"URLProperty.copiedLink\": \"Kopirano!\",\n  \"URLProperty.copy\": \"Kopiraj\",\n  \"URLProperty.edit\": \"Uredi\",\n  \"UndoRedoHotKeys.canRedo\": \"Ponovi\",\n  \"UndoRedoHotKeys.canRedo-with-description\": \"Ponovi {description}\",\n  \"UndoRedoHotKeys.canUndo\": \"Poništi\",\n  \"UndoRedoHotKeys.canUndo-with-description\": \"Poništi {description}\",\n  \"UndoRedoHotKeys.cannotRedo\": \"Ništa se ne može ponoviti\",\n  \"UndoRedoHotKeys.cannotUndo\": \"Ništa se ne može poništiti\",\n  \"ValueSelector.noOptions\": \"Nema opcija. Za dodavanje prve opcije počni tipkati!\",\n  \"ValueSelector.valueSelector\": \"Selektor vrijednosti\",\n  \"ValueSelectorLabel.openMenu\": \"Otvori izbornik\",\n  \"VersionMessage.help\": \"Provjeri što je novo u ovoj verziji.\",\n  \"VersionMessage.learn-more\": \"Saznaj više\",\n  \"View.AddView\": \"Dodaj prikaz\",\n  \"View.Board\": \"Ploča\",\n  \"View.DeleteView\": \"Izbriši prikaz\",\n  \"View.DuplicateView\": \"Dupliciraj prikaz\",\n  \"View.Gallery\": \"Galerija\",\n  \"View.NewBoardTitle\": \"Prikaz ploče\",\n  \"View.NewCalendarTitle\": \"Prikaz kalendara\",\n  \"View.NewGalleryTitle\": \"Prikaz galerije\",\n  \"View.NewTableTitle\": \"Prikaz tablice\",\n  \"View.NewTemplateDefaultTitle\": \"Predložak bez naslova\",\n  \"View.NewTemplateTitle\": \"Bez naslova\",\n  \"View.Table\": \"Tablica\",\n  \"ViewHeader.add-template\": \"Novi predložak\",\n  \"ViewHeader.delete-template\": \"Izbriši\",\n  \"ViewHeader.display-by\": \"Prikaži prema svojstvu: {property}\",\n  \"ViewHeader.edit-template\": \"Uredi\",\n  \"ViewHeader.empty-card\": \"Prazna kartica\",\n  \"ViewHeader.export-board-archive\": \"Izvezi arhivu ploče\",\n  \"ViewHeader.export-complete\": \"Izvoz je završen!\",\n  \"ViewHeader.export-csv\": \"Izvezi u CSV\",\n  \"ViewHeader.export-failed\": \"Izvoz neuspio!\",\n  \"ViewHeader.filter\": \"Filtar\",\n  \"ViewHeader.group-by\": \"Grupiraj prema svojstvu: {property}\",\n  \"ViewHeader.new\": \"Novo\",\n  \"ViewHeader.properties\": \"Svojstva\",\n  \"ViewHeader.properties-menu\": \"Izbornik svojstava\",\n  \"ViewHeader.search-text\": \"Traži kartice\",\n  \"ViewHeader.select-a-template\": \"Odaberi predložak\",\n  \"ViewHeader.set-default-template\": \"Postavi kao zadano\",\n  \"ViewHeader.sort\": \"Razvrstaj\",\n  \"ViewHeader.untitled\": \"Bez naslova\",\n  \"ViewHeader.view-header-menu\": \"Prikaz izbornika zaglavlja\",\n  \"ViewHeader.view-menu\": \"Prikaz izbornika\",\n  \"ViewLimitDialog.Heading\": \"Ograničenje prikaza po ploči dosegnuta\",\n  \"ViewLimitDialog.PrimaryButton.Title.Admin\": \"Nadogradi\",\n  \"ViewLimitDialog.PrimaryButton.Title.RegularUser\": \"Obavijesti aministratora\",\n  \"ViewLimitDialog.Subtext.Admin\": \"Nadogradi na našu profesionalnu tarifu ili na tarifu za poduzeća.\",\n  \"ViewLimitDialog.Subtext.Admin.PricingPageLink\": \"Saznaj više o našim tarifama.\",\n  \"ViewLimitDialog.Subtext.RegularUser\": \"Obavijesti svog administratora da nadogradi na našu profesionalnu tarifu ili na tarifu za poduzeća.\",\n  \"ViewLimitDialog.UpgradeImg.AltText\": \"nadogradi sliku\",\n  \"ViewLimitDialog.notifyAdmin.Success\": \"Tvoj je administrator obaviješten\",\n  \"ViewTitle.hide-description\": \"sakrij opis\",\n  \"ViewTitle.pick-icon\": \"Odaberi ikonu\",\n  \"ViewTitle.random-icon\": \"Slučajno\",\n  \"ViewTitle.remove-icon\": \"Ukloni ikonu\",\n  \"ViewTitle.show-description\": \"prikaži opis\",\n  \"ViewTitle.untitled-board\": \"Bezimena ploča\",\n  \"WelcomePage.Description\": \"„Ploče” je alat za upravljanje projektima koji pomaže definirati, organizirati, pratiti i upravljati radovima timova, koristeći poznati Kanban prikaz ploče.\",\n  \"WelcomePage.Explore.Button\": \"Uvod u rad programa\",\n  \"WelcomePage.Heading\": \"Dobro došao, dobro došla u „Ploče”\",\n  \"WelcomePage.NoThanks.Text\": \"Ne hvala\",\n  \"WelcomePage.StartUsingIt.Text\": \"Počni ga koristiti\",\n  \"Workspace.editing-board-template\": \"Uređuješ predložak ploče.\",\n  \"badge.guest\": \"Gost\",\n  \"boardPage.confirm-join-button\": \"Pridruži se\",\n  \"boardPage.confirm-join-text\": \"Pridružit ćeš se privatnoj ploči bez da te je administrator ploče izričito dodao. Stvarno se želiš pridružiti ovoj privatnoj ploči?\",\n  \"boardPage.confirm-join-title\": \"Pridruži se privatnoj ploči\",\n  \"boardSelector.confirm-link-board\": \"Poveži ploču s kanalom\",\n  \"boardSelector.confirm-link-board-button\": \"Da, poveži ploču\",\n  \"boardSelector.confirm-link-board-subtext\": \"Kad povežeš ploču „{boardName}” s kanalom, svi članovi kanala (postojeći i novi) moći će je uređivati. To ne vrijedi za članove koji su gosti. Vezu između ploče i kanala možeš raskinuti u bilo kojem trenutku.\",\n  \"boardSelector.confirm-link-board-subtext-with-other-channel\": \"Kad povežeš ploču „{boardName}” s kanalom, svi članovi kanala (postojeći i novi) moći će ga uređivati. To ne vrijedi za članove koji su gosti.{lineBreak}Ova je ploča trenutačno povezana s drugim kanalom. Veza će se raskinuti ako odlučiš je ovdje povezati.\",\n  \"boardSelector.create-a-board\": \"Stvori ploču\",\n  \"boardSelector.link\": \"Poveži\",\n  \"boardSelector.search-for-boards\": \"Traži ploče\",\n  \"boardSelector.title\": \"Poveži ploče\",\n  \"boardSelector.unlink\": \"Odspoji\",\n  \"calendar.month\": \"Mjesec\",\n  \"calendar.today\": \"DANAS\",\n  \"calendar.week\": \"Tjedan\",\n  \"centerPanel.undefined\": \"Bez {propertyName}\",\n  \"centerPanel.unknown-user\": \"Nepoznat korisnik\",\n  \"cloudMessage.learn-more\": \"Saznaj više\",\n  \"createImageBlock.failed\": \"Nije moguće prenijeti ovu datoteku jer je dosegnuta granica veličine datoteke.\",\n  \"default-properties.badges\": \"Komentari i opis\",\n  \"default-properties.title\": \"Naslov\",\n  \"error.back-to-home\": \"Natrag na početnu stranicu\",\n  \"error.back-to-team\": \"Natrag u tim\",\n  \"error.board-not-found\": \"Ploča nije pronađena.\",\n  \"error.go-login\": \"Prijavi se\",\n  \"error.invalid-read-only-board\": \"Nemaš pristup ovoj ploči. Prijavi se za pristup pločama.\",\n  \"error.not-logged-in\": \"Tvoja sesija je možda istekla ili nisi prijavljen/a. Ponovo se prijavi za pristup pločama.\",\n  \"error.page.title\": \"Oprosti, dogodila se greška\",\n  \"error.team-undefined\": \"Nije valjani tim.\",\n  \"error.unknown\": \"Dogodila se greška.\",\n  \"generic.previous\": \"Prethodno\",\n  \"guest-no-board.subtitle\": \"Još nemaš pristup nijednoj ploči u ovom timu, pričekaj dok te netko ne doda u bilo koju ploču.\",\n  \"guest-no-board.title\": \"Još nema ploča\",\n  \"imagePaste.upload-failed\": \"Neke datoteke nisu prenesene jer je dosegnuta granica veličine datoteke.\",\n  \"limitedCard.title\": \"Skrivene kartice\",\n  \"login.log-in-button\": \"Prijavi se\",\n  \"login.log-in-title\": \"Prijavi se\",\n  \"login.register-button\": \"ili stvori račun, ako ga još nemaš\",\n  \"new_channel_modal.create_board.empty_board_description\": \"Stvori novu praznu ploču\",\n  \"new_channel_modal.create_board.empty_board_title\": \"Prazna ploča\",\n  \"new_channel_modal.create_board.select_template_placeholder\": \"Odaberi predložak\",\n  \"new_channel_modal.create_board.title\": \"Stvori ploču za ovaj kanal\",\n  \"notification-box-card-limit-reached.close-tooltip\": \"Postavi pripravno stanje na 10 dana\",\n  \"notification-box-card-limit-reached.contact-link\": \"obavijesti svog administratora\",\n  \"notification-box-card-limit-reached.link\": \"Nadogradi na plaćenu tarifu\",\n  \"notification-box-card-limit-reached.title\": \"Skrivene kartice iz ploče: {cards}\",\n  \"notification-box-cards-hidden.title\": \"Ova radnja je sakrlia jednu drugu karticu\",\n  \"notification-box.card-limit-reached.not-admin.text\": \"Za pristupanje arhiviranim karticama, možeš {contactLink} za nadogradnju na plaćenu tarifu.\",\n  \"notification-box.card-limit-reached.text\": \"Dosegnuto je ograničenje broja kartica. Za pregled starijih kartica, {link}\",\n  \"person.add-user-to-board\": \"Dodaj korisničko ime {username} u ploču\",\n  \"person.add-user-to-board-confirm-button\": \"Dodaj u ploču\",\n  \"person.add-user-to-board-permissions\": \"Prava\",\n  \"person.add-user-to-board-question\": \"Želiš li dodati korisničko ime {username} u ploču?\",\n  \"person.add-user-to-board-warning\": \"{username} nije član ploče i neće primati obavijesti o njoj.\",\n  \"register.login-button\": \"ili se prijavi ako već imaš račun\",\n  \"register.signup-title\": \"Prijavi se na svoj račun\",\n  \"rhs-board-non-admin-msg\": \"Nisi administrator ploče\",\n  \"rhs-boards.add\": \"Dodaj\",\n  \"rhs-boards.dm\": \"DP\",\n  \"rhs-boards.gm\": \"GP\",\n  \"rhs-boards.header.dm\": \"ovu izravnu poruku\",\n  \"rhs-boards.header.gm\": \"ovu grupnu poruku\",\n  \"rhs-boards.last-update-at\": \"Zadnje aktualiziranje: {datetime}\",\n  \"rhs-boards.link-boards-to-channel\": \"Poveži ploče s kanalom {channelName}\",\n  \"rhs-boards.linked-boards\": \"Povezane ploče\",\n  \"rhs-boards.no-boards-linked-to-channel\": \"Do sada nije povezana nijedna ploča s kanalom {channelName}\",\n  \"rhs-boards.no-boards-linked-to-channel-description\": \"Ploče su alat za upravljanje projektima koji pomaže definirati, organizirati, pratiti i upravljati rad timova, koristeći poznati kanban prikaz ploče.\",\n  \"rhs-boards.unlink-board\": \"Odspoji ploču\",\n  \"rhs-boards.unlink-board1\": \"Odspoji ploču\",\n  \"rhs-channel-boards-header.title\": \"Ploče\",\n  \"share-board.publish\": \"Objavi\",\n  \"share-board.share\": \"Dijeli\",\n  \"shareBoard.channels-select-group\": \"Kanali\",\n  \"shareBoard.confirm-change-team-role.body\": \"Svatko na ovoj ploči s manje prava od uloge „{role}” <b>će sada biti promaknut u {role}</b>. Stvarno želiš promijeniti najmanju ulogu za ploču?\",\n  \"shareBoard.confirm-change-team-role.confirmBtnText\": \"Promijeni najmanju ulogu ploče\",\n  \"shareBoard.confirm-change-team-role.title\": \"Promijeni najmanju ulogu ploče\",\n  \"shareBoard.confirm-link-channel\": \"Poveži ploču s kanalom\",\n  \"shareBoard.confirm-link-channel-button\": \"Poveži kanal\",\n  \"shareBoard.confirm-link-channel-button-with-other-channel\": \"Odspoji i poveži ovamo\",\n  \"shareBoard.confirm-link-channel-subtext\": \"Kad povežeš kanal s pločom, svi članovi kanala (postojeći i novi) moći će ga uređivati. To ne vrijedi za članove koji su gosti.\",\n  \"shareBoard.confirm-link-channel-subtext-with-other-channel\": \"Kad povežeš kanal s pločom, svi članovi kanala (postojeći i novi) moći će ga uređivati. To ne vrijedi za članove koji su gosti.{lineBreak}Ova je ploča trenutačno povezana s drugim kanalom. Veza će se raskinuti ako odlučiš je ovdje povezati.\",\n  \"shareBoard.confirm-unlink.body\": \"Kad odspojiš kanal od ploče, svi članovi kanala (postojeći i novi) izgubit će pristup kanalu, osim ako su im se prava dala zasebno.\",\n  \"shareBoard.confirm-unlink.confirmBtnText\": \"Odspoji kanal\",\n  \"shareBoard.confirm-unlink.title\": \"Odspoji kanal od ploče\",\n  \"shareBoard.lastAdmin\": \"Ploče moraju imati barem jednog administratora\",\n  \"shareBoard.members-select-group\": \"Članovi\",\n  \"shareBoard.unknown-channel-display-name\": \"Nepoznat kanal\",\n  \"tutorial_tip.finish_tour\": \"Gotovo\",\n  \"tutorial_tip.got_it\": \"Razumijem\",\n  \"tutorial_tip.ok\": \"Dalje\",\n  \"tutorial_tip.out\": \"Deaktiviraj ove savjete.\",\n  \"tutorial_tip.seen\": \"Ovaj savjet već poznaš?\"\n}\n"
  },
  {
    "path": "webapp/i18n/hu.json",
    "content": "{\n  \"AppBar.Tooltip\": \"Kapcsolt táblák kapcsolása\",\n  \"Attachment.Attachment-title\": \"Melléklet\",\n  \"AttachmentBlock.DeleteAction\": \"törlés\",\n  \"AttachmentBlock.addElement\": \"{type} hozzáadása\",\n  \"AttachmentBlock.delete\": \"Melléklet sikeresen törölve.\",\n  \"AttachmentBlock.failed\": \"Nem sikerült feltölteni a fájlt. A csatolmány mérete túl nagy.\",\n  \"AttachmentBlock.upload\": \"Melléklet feltöltése.\",\n  \"AttachmentBlock.uploadSuccess\": \"A melléklet feltöltése sikeres.\",\n  \"AttachmentElement.delete-confirmation-dialog-button-text\": \"Törlés\",\n  \"AttachmentElement.download\": \"Letöltés\",\n  \"AttachmentElement.upload-percentage\": \"Feltöltés...({uploadPercent}%)\",\n  \"BoardComponent.add-a-group\": \"+ Csoport hozzáadása\",\n  \"BoardComponent.delete\": \"Törlés\",\n  \"BoardComponent.hidden-columns\": \"Rejtett oszlopok\",\n  \"BoardComponent.hide\": \"Elrejtés\",\n  \"BoardComponent.new\": \"+ Új\",\n  \"BoardComponent.no-property\": \"Nincs {property}\",\n  \"BoardComponent.no-property-title\": \"Elemek üres {property} tulajdonsággal kerülnek ide. Ez az oszlop nem eltávolítható.\",\n  \"BoardComponent.show\": \"Mutat\",\n  \"BoardMember.schemeAdmin\": \"Admin\",\n  \"BoardMember.schemeCommenter\": \"Véleményező\",\n  \"BoardMember.schemeEditor\": \"Szerkesztő\",\n  \"BoardMember.schemeNone\": \"Nincs\",\n  \"BoardMember.schemeViewer\": \"Megtekintő\",\n  \"BoardMember.unlinkChannel\": \"Leválasztás\",\n  \"BoardPage.newVersion\": \"Elérhető a Táblák egy új verziója, kattintson ide az újratöltéshez.\",\n  \"BoardPage.syncFailed\": \"A tábla törölve lett vagy hozzáférés vissza lett vonva.\",\n  \"BoardTemplateSelector.add-template\": \"Új sablon létrehozása\",\n  \"BoardTemplateSelector.create-empty-board\": \"Üres tábla készítése\",\n  \"BoardTemplateSelector.delete-template\": \"Törlés\",\n  \"BoardTemplateSelector.description\": \"Adjon hozzá egy táblát az oldalsávhoz az alább meghatározott sablonok bármelyikével, vagy kezdje elölről.\",\n  \"BoardTemplateSelector.edit-template\": \"Szerkesztés\",\n  \"BoardTemplateSelector.plugin.no-content-description\": \"Adjon hozzá egy táblát az oldalsávhoz az alább megadott sablonok bármelyikével, vagy kezdje elölről.\",\n  \"BoardTemplateSelector.plugin.no-content-title\": \"Tábla létrehozása\",\n  \"BoardTemplateSelector.title\": \"Tábla létrehozása\",\n  \"BoardTemplateSelector.use-this-template\": \"Használja ezt a sablont\",\n  \"BoardsSwitcher.Title\": \"Táblák keresése\",\n  \"BoardsUnfurl.Limited\": \"A kártya archiválása miatt a további részletek rejtve vannak\",\n  \"BoardsUnfurl.Remainder\": \"+{remainder} további\",\n  \"BoardsUnfurl.Updated\": \"Frissítve {time}\",\n  \"Calculations.Options.average.displayName\": \"Átlag\",\n  \"Calculations.Options.average.label\": \"Átlag\",\n  \"Calculations.Options.count.displayName\": \"Mennyiség\",\n  \"Calculations.Options.count.label\": \"Mennyiség\",\n  \"Calculations.Options.countChecked.displayName\": \"Kijelölt\",\n  \"Calculations.Options.countChecked.label\": \"Kijelöltek száma\",\n  \"Calculations.Options.countUnchecked.displayName\": \"Nem kijelölt\",\n  \"Calculations.Options.countUnchecked.label\": \"Nem kijelöltek száma\",\n  \"Calculations.Options.countUniqueValue.displayName\": \"Egyedi\",\n  \"Calculations.Options.countUniqueValue.label\": \"Egyedi értékek száma\",\n  \"Calculations.Options.countValue.displayName\": \"Értékek\",\n  \"Calculations.Options.countValue.label\": \"Értékek száma\",\n  \"Calculations.Options.dateRange.displayName\": \"Tartomány\",\n  \"Calculations.Options.dateRange.label\": \"Tartomány\",\n  \"Calculations.Options.earliest.displayName\": \"Korábbi\",\n  \"Calculations.Options.earliest.label\": \"Korábbi\",\n  \"Calculations.Options.latest.displayName\": \"Későbbi\",\n  \"Calculations.Options.latest.label\": \"Későbbi\",\n  \"Calculations.Options.max.displayName\": \"Max\",\n  \"Calculations.Options.max.label\": \"Max\",\n  \"Calculations.Options.median.displayName\": \"Közép\",\n  \"Calculations.Options.median.label\": \"Közép\",\n  \"Calculations.Options.min.displayName\": \"Min\",\n  \"Calculations.Options.min.label\": \"Min\",\n  \"Calculations.Options.none.displayName\": \"Kiszámítás\",\n  \"Calculations.Options.none.label\": \"Nincs\",\n  \"Calculations.Options.percentChecked.displayName\": \"Kijelölt\",\n  \"Calculations.Options.percentChecked.label\": \"Kijelöltek aránya\",\n  \"Calculations.Options.percentUnchecked.displayName\": \"Nem kijelölt\",\n  \"Calculations.Options.percentUnchecked.label\": \"Nem kijelöltek aránya\",\n  \"Calculations.Options.range.displayName\": \"Tartomány\",\n  \"Calculations.Options.range.label\": \"Tartomány\",\n  \"Calculations.Options.sum.displayName\": \"Összeg\",\n  \"Calculations.Options.sum.label\": \"Összeg\",\n  \"CalendarCard.untitled\": \"Névtelen\",\n  \"CardActionsMenu.copiedLink\": \"Másolva!\",\n  \"CardActionsMenu.copyLink\": \"Link másolása\",\n  \"CardActionsMenu.delete\": \"Törlés\",\n  \"CardActionsMenu.duplicate\": \"Duplikálás\",\n  \"CardBadges.title-checkboxes\": \"Teendők\",\n  \"CardBadges.title-comments\": \"Megjegyzések\",\n  \"CardBadges.title-description\": \"Ennek a kártyának van leírása\",\n  \"CardDetail.Attach\": \"Csatolás\",\n  \"CardDetail.Follow\": \"Követés\",\n  \"CardDetail.Following\": \"Követés\",\n  \"CardDetail.add-content\": \"Tartalom hozzáadása\",\n  \"CardDetail.add-icon\": \"Ikon hozzáadása\",\n  \"CardDetail.add-property\": \"+ Tulajdonság hozzáadása\",\n  \"CardDetail.addCardText\": \"kártya szövegének hozzáadása\",\n  \"CardDetail.limited-body\": \"Az archivált kártyák megtekintéséhez, a táblánkénti korlátlan megtekintéshez, korlátlan számú kártyához és még sok máshoz váltson a Professional vagy Enterprise csomagra.\",\n  \"CardDetail.limited-button\": \"Kiadás váltása\",\n  \"CardDetail.limited-title\": \"Ez a kártya rejtett\",\n  \"CardDetail.moveContent\": \"Kártya tartalmának mozgatása\",\n  \"CardDetail.new-comment-placeholder\": \"Megjegyzés hozzáadása...\",\n  \"CardDetailProperty.confirm-delete-heading\": \"Tulajdonság törlésének jóváhagyása\",\n  \"CardDetailProperty.confirm-delete-subtext\": \"Biztos benne, hogy törölni szeretné a \\\"{propertyName}\\\" tulajdonságot? A törléssel a tulajdonság minden kártyáról el lesz távolítva.\",\n  \"CardDetailProperty.confirm-property-name-change-subtext\": \"Biztosan szeretné megváltoztatni a \\\"{propertyName}\\\" tulajdonság {customText}? Ez érint {numOfCards} kártya adatát ebben a táblában, és akár adatvesztéssel is járhat.\",\n  \"CardDetailProperty.confirm-property-type-change\": \"Hagyja jóvá a tulajdonság típusának módosítását\",\n  \"CardDetailProperty.delete-action-button\": \"Törlés\",\n  \"CardDetailProperty.property-change-action-button\": \"Tulajdonság módosítása\",\n  \"CardDetailProperty.property-changed\": \"Tulajdonság sikeresen módosult!\",\n  \"CardDetailProperty.property-deleted\": \"{propertyName} törlése sikeres!\",\n  \"CardDetailProperty.property-name-change-subtext\": \"típus erről: \\\"{oldPropType}\\\" erre: \\\"{newPropType}\\\"\",\n  \"CardDetial.limited-link\": \"Tudjon meg többet csomagjainkról.\",\n  \"CardDialog.delete-confirmation-dialog-attachment\": \"Erősítse meg a csatolmány törlését!\",\n  \"CardDialog.delete-confirmation-dialog-button-text\": \"Törlés\",\n  \"CardDialog.delete-confirmation-dialog-heading\": \"Hagyja jóvá a kártya törlését!\",\n  \"CardDialog.editing-template\": \"Ön egy sablont szerkeszt.\",\n  \"CardDialog.nocard\": \"Ez a kártya nem létezik vagy elérhetetlen.\",\n  \"Categories.CreateCategoryDialog.CancelText\": \"Mégsem\",\n  \"Categories.CreateCategoryDialog.CreateText\": \"Létrehozás\",\n  \"Categories.CreateCategoryDialog.Placeholder\": \"Nevezze el a kategóriáját\",\n  \"Categories.CreateCategoryDialog.UpdateText\": \"Frissítés\",\n  \"CenterPanel.Login\": \"Bejelentkezés\",\n  \"CenterPanel.Share\": \"Megosztás\",\n  \"ColorOption.selectColor\": \"{color} szín kiválasztása\",\n  \"Comment.delete\": \"Törlés\",\n  \"CommentsList.send\": \"Küldés\",\n  \"ConfirmationDialog.cancel-action\": \"Mégsem\",\n  \"ConfirmationDialog.confirm-action\": \"Jóváhagyás\",\n  \"ContentBlock.Delete\": \"Törlés\",\n  \"ContentBlock.DeleteAction\": \"törlés\",\n  \"ContentBlock.addElement\": \"{type} hozzáadása\",\n  \"ContentBlock.checkbox\": \"jelölőnégyzet\",\n  \"ContentBlock.divider\": \"elválasztó\",\n  \"ContentBlock.editCardCheckbox\": \"három állású jelölőnégyzet\",\n  \"ContentBlock.editCardCheckboxText\": \"kártya szövegének szerkesztése\",\n  \"ContentBlock.editCardText\": \"kártya szövegének szerkesztése\",\n  \"ContentBlock.editText\": \"Szöveg szerkesztése...\",\n  \"ContentBlock.image\": \"kép\",\n  \"ContentBlock.insertAbove\": \"Beszúrás fölé\",\n  \"ContentBlock.moveBlock\": \"kártya tartalmának áthelyezése\",\n  \"ContentBlock.moveDown\": \"Mozgatás le\",\n  \"ContentBlock.moveUp\": \"Mozgatás fel\",\n  \"ContentBlock.text\": \"szöveg\",\n  \"DateRange.clear\": \"Törlés\",\n  \"DateRange.empty\": \"Üres\",\n  \"DateRange.endDate\": \"Vége dátum\",\n  \"DateRange.today\": \"Ma\",\n  \"DeleteBoardDialog.confirm-cancel\": \"Mégsem\",\n  \"DeleteBoardDialog.confirm-delete\": \"Törlés\",\n  \"DeleteBoardDialog.confirm-info\": \"Biztos benne, hogy törölni szeretné a “{boardTitle}” táblát? A törlésével az összes benne lévő kártya is törlődni fog.\",\n  \"DeleteBoardDialog.confirm-info-template\": \"Biztos, hogy törölni szeretné a \\\"{boardTitle}\\\" tábla sablont?\",\n  \"DeleteBoardDialog.confirm-tite\": \"Tábla törlésének jóváhagyása\",\n  \"DeleteBoardDialog.confirm-tite-template\": \"Tábla sablon törlésének jóváhagyása\",\n  \"Dialog.closeDialog\": \"Ablak bezárása\",\n  \"EditableDayPicker.today\": \"Ma\",\n  \"Error.mobileweb\": \"Mobil web támogatás jelenleg előzetes béta állapotban van. Nem minden funkcionalitás érhető el.\",\n  \"Error.websocket-closed\": \"Websocket kapcsolat bezárult, kapcsolat megszakadt, Ha ez továbbra is fennáll, akkor ellenőrizze le a kiszolgáló vagy web proxy beállítását.\",\n  \"Filter.contains\": \"tartalmazza\",\n  \"Filter.ends-with\": \"végződik\",\n  \"Filter.includes\": \"tartalmazza\",\n  \"Filter.is\": \"egy\",\n  \"Filter.is-empty\": \"üres\",\n  \"Filter.is-not-empty\": \"nem üres\",\n  \"Filter.is-not-set\": \"nincs megadva\",\n  \"Filter.is-set\": \"meg van adva\",\n  \"Filter.not-contains\": \"nem tartalmazza\",\n  \"Filter.not-ends-with\": \"nem végződik\",\n  \"Filter.not-includes\": \"nem tartalmazza\",\n  \"Filter.not-starts-with\": \"nem kezdődik\",\n  \"Filter.starts-with\": \"kezdődik\",\n  \"FilterByText.placeholder\": \"szöveg szűrése\",\n  \"FilterComponent.add-filter\": \"+ Szűrő hozzáadása\",\n  \"FilterComponent.delete\": \"Törlés\",\n  \"FindBoardsDialog.IntroText\": \"Táblák keresése\",\n  \"FindBoardsDialog.NoResultsFor\": \"Nincs találat a \\\"{searchQuery}\\\" kereséshez\",\n  \"FindBoardsDialog.NoResultsSubtext\": \"Ellenőrizze az elgépelést vagy próbáljon egy új keresést.\",\n  \"FindBoardsDialog.SubTitle\": \"Gépeljen, hogy megtalálja a táblát. Használja a <b>FEL/LE</b> gombokat a böngészéshez. <b>ENTER</b> gombot a kiválasztáshoz és <b>ESC</b> gombot az eldobáshoz\",\n  \"FindBoardsDialog.Title\": \"Táblák keresése\",\n  \"GroupBy.hideEmptyGroups\": \"{count} üres csoport elrejtése\",\n  \"GroupBy.showHiddenGroups\": \"{count} rejtett csoport megjelenítése\",\n  \"GroupBy.ungroup\": \"Csoportosítás megszüntetése\",\n  \"HideBoard.MenuOption\": \"Tábla elrejtése\",\n  \"KanbanCard.untitled\": \"Névtelen\",\n  \"MentionSuggestion.is-not-board-member\": \"(nem tagja a táblának)\",\n  \"Mutator.new-board-from-template\": \"új tábla sablon alapján\",\n  \"Mutator.new-card-from-template\": \"új kártya sablonból\",\n  \"Mutator.new-template-from-card\": \"új sablon kártyából\",\n  \"OnboardingTour.AddComments.Body\": \"Hozzászólhat a témákhoz, sőt, más Mattermost felhasználó társát is @megemlítheti, hogy felhívja a figyelmüket.\",\n  \"OnboardingTour.AddComments.Title\": \"Megjegyzés hozzáadása\",\n  \"OnboardingTour.AddDescription.Body\": \"Adjon leírást a kártyájához, hogy csapattársai tudják, miről szól a kártya.\",\n  \"OnboardingTour.AddDescription.Title\": \"Leírás hozzáadása\",\n  \"OnboardingTour.AddProperties.Body\": \"Adjon hozzá különböző tulajdonságokat a kártyákhoz, hogy hatékonyabbá tegye őket!\",\n  \"OnboardingTour.AddProperties.Title\": \"Tulajdonságok hozzáadása\",\n  \"OnboardingTour.AddView.Body\": \"Menjen ide, és hozzon létre új nézetet, hogy különböző elrendezésekkel rendszerezze a tábláját.\",\n  \"OnboardingTour.AddView.Title\": \"Új nézet hozzáadása\",\n  \"OnboardingTour.CopyLink.Body\": \"Megoszthatja kártyáit csapattársaival a link másolásával és beillesztésével egy csatornán, közvetlen üzenetben vagy csoportos üzenetben.\",\n  \"OnboardingTour.CopyLink.Title\": \"Link másolása\",\n  \"OnboardingTour.OpenACard.Body\": \"Nyisson meg egy kártyát, hogy felfedezze, milyen hatékony módon segíthetnek a táblák a munkája megszervezésében.\",\n  \"OnboardingTour.OpenACard.Title\": \"Kártya megnyitása\",\n  \"OnboardingTour.ShareBoard.Body\": \"A táblát megoszthatja belsőleg, a csapatán belül, vagy nyilvánosan is közzéteheti, hogy a szervezeten kívül is látható legyen.\",\n  \"OnboardingTour.ShareBoard.Title\": \"Tábla megosztása\",\n  \"PersonProperty.board-members\": \"Tábla tagjai\",\n  \"PersonProperty.non-board-members\": \"Nem tábla tagok\",\n  \"PropertyMenu.Delete\": \"Törlés\",\n  \"PropertyMenu.changeType\": \"Tulajdonság típusának módosítása\",\n  \"PropertyMenu.selectType\": \"Tulajdonság típusának kiválasztása\",\n  \"PropertyMenu.typeTitle\": \"Típus\",\n  \"PropertyType.Checkbox\": \"Jelölőnégyzet\",\n  \"PropertyType.CreatedBy\": \"Létrehozta\",\n  \"PropertyType.CreatedTime\": \"Létrehozás ideje\",\n  \"PropertyType.Date\": \"Dátum\",\n  \"PropertyType.Email\": \"E-mail\",\n  \"PropertyType.MultiPerson\": \"Több személy\",\n  \"PropertyType.MultiSelect\": \"Több kiválasztós\",\n  \"PropertyType.Number\": \"Szám\",\n  \"PropertyType.Person\": \"Személy\",\n  \"PropertyType.Phone\": \"Telefon\",\n  \"PropertyType.Select\": \"Kiválasztás\",\n  \"PropertyType.Text\": \"Szöveg\",\n  \"PropertyType.Unknown\": \"Ismeretlen\",\n  \"PropertyType.UpdatedBy\": \"Utoljára frissítette\",\n  \"PropertyType.UpdatedTime\": \"Utolsó frissítés ideje\",\n  \"PropertyType.Url\": \"URL\",\n  \"PropertyValueElement.empty\": \"Üres\",\n  \"RegistrationLink.confirmRegenerateToken\": \"Ez érvényteleníteni fogja a korábban megosztott linkeket. Folytassuk?\",\n  \"RegistrationLink.copiedLink\": \"Másolt!\",\n  \"RegistrationLink.copyLink\": \"Link másolása\",\n  \"RegistrationLink.description\": \"Ossza meg ezt a linket másokkat a fiók létrehozásához:\",\n  \"RegistrationLink.regenerateToken\": \"Token újragenerálása\",\n  \"RegistrationLink.tokenRegenerated\": \"Regisztrációs link újragenerálva\",\n  \"ShareBoard.PublishDescription\": \"Csak olvasható link közzététele és megosztása mindenkivel a weben.\",\n  \"ShareBoard.PublishTitle\": \"Közzététel a webre\",\n  \"ShareBoard.ShareInternal\": \"Megosztás belsőleg\",\n  \"ShareBoard.ShareInternalDescription\": \"A jogosultságokkal rendelkező felhasználók használhatják ezt a linket.\",\n  \"ShareBoard.Title\": \"Tábla megosztása\",\n  \"ShareBoard.confirmRegenerateToken\": \"Ez érvényteleníteni fogja a korábban megosztott linkeket. Folytassuk?\",\n  \"ShareBoard.copiedLink\": \"Másolt!\",\n  \"ShareBoard.copyLink\": \"Link másolása\",\n  \"ShareBoard.regenerate\": \"Token újragenerálása\",\n  \"ShareBoard.searchPlaceholder\": \"Személyek és csatornák keresése\",\n  \"ShareBoard.teamPermissionsText\": \"Mindenki a {teamName} Csapatban\",\n  \"ShareBoard.tokenRegenrated\": \"Token újragenerálva\",\n  \"ShareBoard.userPermissionsRemoveMemberText\": \"Tag eltávolítása\",\n  \"ShareBoard.userPermissionsYouText\": \"(Ön)\",\n  \"ShareTemplate.Title\": \"Sablon megosztása\",\n  \"ShareTemplate.searchPlaceholder\": \"Személyek keresése\",\n  \"Sidebar.about\": \"Focalboard névjegye\",\n  \"Sidebar.add-board\": \"+ Tábla hozzáadása\",\n  \"Sidebar.changePassword\": \"Jelszó módosítása\",\n  \"Sidebar.delete-board\": \"Tábla törlése\",\n  \"Sidebar.duplicate-board\": \"Tábla duplikálása\",\n  \"Sidebar.export-archive\": \"Archiváltak exportálása\",\n  \"Sidebar.import\": \"Importálás\",\n  \"Sidebar.import-archive\": \"Archiváltak importálása\",\n  \"Sidebar.invite-users\": \"Felhasználók meghívása\",\n  \"Sidebar.logout\": \"Kijelentkezés\",\n  \"Sidebar.new-category.badge\": \"Új\",\n  \"Sidebar.new-category.drag-boards-cta\": \"Húzza a táblákat ide...\",\n  \"Sidebar.no-boards-in-category\": \"Nincsennek bent táblák\",\n  \"Sidebar.product-tour\": \"Termék bemutató\",\n  \"Sidebar.random-icons\": \"Véletlen ikonok\",\n  \"Sidebar.set-language\": \"Nyelv megadása\",\n  \"Sidebar.set-theme\": \"Téma megadása\",\n  \"Sidebar.settings\": \"Beállítások\",\n  \"Sidebar.template-from-board\": \"Új sablon a táblából\",\n  \"Sidebar.untitled-board\": \"(Névtelen tábla)\",\n  \"Sidebar.untitled-view\": \"(Névtelen nézet)\",\n  \"SidebarCategories.BlocksMenu.Move\": \"Áthelyezés...\",\n  \"SidebarCategories.CategoryMenu.CreateNew\": \"Új kategória létrehozása\",\n  \"SidebarCategories.CategoryMenu.Delete\": \"Kategória törlése\",\n  \"SidebarCategories.CategoryMenu.DeleteModal.Body\": \"A <b>{categoryName}</b> kategóriában lévő táblák visszakerülnek a Táblák kategóriákba. Ön egyik táblából sem lesz eltávolítva.\",\n  \"SidebarCategories.CategoryMenu.DeleteModal.Title\": \"Törli ezt a kategóriát?\",\n  \"SidebarCategories.CategoryMenu.Update\": \"Kategória átnevezése\",\n  \"SidebarTour.ManageCategories.Body\": \"Egyéni kategóriák létrehozása és kezelése. A kategóriák felhasználó-specifikusak, így egy tábla áthelyezése a kategóriájába nem befolyásolja az ugyanazt a táblát használó többi tagot.\",\n  \"SidebarTour.ManageCategories.Title\": \"Kategóriák kezelése\",\n  \"SidebarTour.SearchForBoards.Body\": \"A táblaváltó megnyitásával (Cmd/Ctrl + K) gyorsan kereshet és adhat hozzá táblákat az oldalsávjához.\",\n  \"SidebarTour.SearchForBoards.Title\": \"Tábla keresése\",\n  \"SidebarTour.SidebarCategories.Body\": \"Az összes tábláját mostantól az új oldalsávja alá rendezi. Nincs többé váltás a munkaterületek között. A v7.2 frissítés részeként automatikusan létrejöhettek az Ön számára a korábbi munkaterületek alapján létrehozott egyszeri egyéni kategóriák. Ezeket eltávolíthatja vagy szerkesztheti tetszése szerint.\",\n  \"SidebarTour.SidebarCategories.Link\": \"Tudjon meg többet\",\n  \"SidebarTour.SidebarCategories.Title\": \"Oldalsáv kategóriák\",\n  \"SiteStats.total_boards\": \"Összes tábla\",\n  \"SiteStats.total_cards\": \"Összes kártya\",\n  \"TableComponent.add-icon\": \"Ikon hozzáadása\",\n  \"TableComponent.name\": \"Név\",\n  \"TableComponent.plus-new\": \"+ Új\",\n  \"TableHeaderMenu.delete\": \"Törlés\",\n  \"TableHeaderMenu.duplicate\": \"Duplikálás\",\n  \"TableHeaderMenu.hide\": \"Elrejtés\",\n  \"TableHeaderMenu.insert-left\": \"Beillesztés balra\",\n  \"TableHeaderMenu.insert-right\": \"Beillesztés jobbra\",\n  \"TableHeaderMenu.sort-ascending\": \"Rendezés növekvő sorrendben\",\n  \"TableHeaderMenu.sort-descending\": \"Rendezés csökkenő sorrendben\",\n  \"TableRow.MoreOption\": \"További műveletek\",\n  \"TableRow.open\": \"Megnyitás\",\n  \"TopBar.give-feedback\": \"Visszajelzés\",\n  \"URLProperty.copiedLink\": \"Másolva!\",\n  \"URLProperty.copy\": \"Másolás\",\n  \"URLProperty.edit\": \"Szerkesztés\",\n  \"UndoRedoHotKeys.canRedo\": \"Újracsinálás\",\n  \"UndoRedoHotKeys.canRedo-with-description\": \"Újracsinálás {description}\",\n  \"UndoRedoHotKeys.canUndo\": \"Visszavonás\",\n  \"UndoRedoHotKeys.canUndo-with-description\": \"Visszavonás {description}\",\n  \"UndoRedoHotKeys.cannotRedo\": \"Nincs mit újracsinálni\",\n  \"UndoRedoHotKeys.cannotUndo\": \"Nincs mit visszavonni\",\n  \"ValueSelector.noOptions\": \"Nincsenek lehetőségek. Kezdjen el gépelni, hogy hozzáadja az elsőt!\",\n  \"ValueSelector.valueSelector\": \"Érték kiválasztó\",\n  \"ValueSelectorLabel.openMenu\": \"Menü megnyitása\",\n  \"VersionMessage.help\": \"Tekintse meg ezen verzió újdonságait.\",\n  \"View.AddView\": \"Nézet hozzáadása\",\n  \"View.Board\": \"Tábla\",\n  \"View.DeleteView\": \"Nézet törlése\",\n  \"View.DuplicateView\": \"Nézet duplikálása\",\n  \"View.Gallery\": \"Galéria\",\n  \"View.NewBoardTitle\": \"Tábla nézet\",\n  \"View.NewCalendarTitle\": \"Naptár nézet\",\n  \"View.NewGalleryTitle\": \"Galéria nézet\",\n  \"View.NewTableTitle\": \"Táblázat nézet\",\n  \"View.NewTemplateDefaultTitle\": \"Névtelen sablon\",\n  \"View.NewTemplateTitle\": \"Névtelen\",\n  \"View.Table\": \"Táblázat\",\n  \"ViewHeader.add-template\": \"Új sablon\",\n  \"ViewHeader.delete-template\": \"Törlés\",\n  \"ViewHeader.display-by\": \"Rendezés: {property}\",\n  \"ViewHeader.edit-template\": \"Szerkesztés\",\n  \"ViewHeader.empty-card\": \"Üres kártya\",\n  \"ViewHeader.export-board-archive\": \"Archivált tábla exportálása\",\n  \"ViewHeader.export-complete\": \"Exportálás kész!\",\n  \"ViewHeader.export-csv\": \"Exportálás CSV-be\",\n  \"ViewHeader.export-failed\": \"Exportálás meghiúsult!\",\n  \"ViewHeader.filter\": \"Szűrő\",\n  \"ViewHeader.group-by\": \"Csoportosítás: {property}\",\n  \"ViewHeader.new\": \"Új\",\n  \"ViewHeader.properties\": \"Tulajdonságok\",\n  \"ViewHeader.properties-menu\": \"Tulajdonságok menü\",\n  \"ViewHeader.search-text\": \"Kártya keresése\",\n  \"ViewHeader.select-a-template\": \"Sablon kiválasztása\",\n  \"ViewHeader.set-default-template\": \"Beállítás alapértelmezettnek\",\n  \"ViewHeader.sort\": \"Rendezés\",\n  \"ViewHeader.untitled\": \"Névtelen\",\n  \"ViewHeader.view-header-menu\": \"Fejléc menü megjelenítése\",\n  \"ViewHeader.view-menu\": \"Megtekintés menü\",\n  \"ViewLimitDialog.Heading\": \"Táblánkénti megtekintések korlátja elérve\",\n  \"ViewLimitDialog.PrimaryButton.Title.Admin\": \"Előfizetés váltása\",\n  \"ViewLimitDialog.PrimaryButton.Title.RegularUser\": \"Admin értesítése\",\n  \"ViewLimitDialog.Subtext.Admin\": \"A Professional vagy Enterprise csomagra való váltással korlátlan számú megtekintést kaphat táblánként, korlátlan számú kártyát és még sok mást.\",\n  \"ViewLimitDialog.Subtext.Admin.PricingPageLink\": \"Tudjon meg többet a csomagjainkról.\",\n  \"ViewLimitDialog.Subtext.RegularUser\": \"Értesítse a rendszergazdát, hogy frissíthessen a Professional vagy Enterprise csomagra, hogy korlátlan megtekintést kapjon táblánként, korlátlan számú kártyát és még többet.\",\n  \"ViewLimitDialog.UpgradeImg.AltText\": \"előfizetés váltás kép\",\n  \"ViewLimitDialog.notifyAdmin.Success\": \"A rendszergazdája értesítve lett\",\n  \"ViewTitle.hide-description\": \"leírás elrejtése\",\n  \"ViewTitle.pick-icon\": \"Válasszon ikont\",\n  \"ViewTitle.random-icon\": \"Véletlen\",\n  \"ViewTitle.remove-icon\": \"Ikon eltávolítása\",\n  \"ViewTitle.show-description\": \"leírás mutatása\",\n  \"ViewTitle.untitled-board\": \"Névtelen tábla\",\n  \"WelcomePage.Description\": \"A Táblák egy projekt kezelő segédeszköz ami segít azonosítani, rendezni, követni és vezetni a munkát csapatok között, egy ismerős kanban táblás nézet segítségével.\",\n  \"WelcomePage.Explore.Button\": \"Nézze meg a bemutatót\",\n  \"WelcomePage.Heading\": \"Üdvözöljük a Táblákban\",\n  \"WelcomePage.NoThanks.Text\": \"Nem, köszönöm, majd kitalálom magam\",\n  \"WelcomePage.StartUsingIt.Text\": \"Kezdjük el használni\",\n  \"Workspace.editing-board-template\": \"Ön egy sablon táblát szerkeszt.\",\n  \"badge.guest\": \"Vendég\",\n  \"boardSelector.confirm-link-board\": \"Kösse össze a táblát csatornával\",\n  \"boardSelector.confirm-link-board-button\": \"Igen, kösse össze a táblát\",\n  \"boardSelector.confirm-link-board-subtext\": \"Ha a \\\"{boardName}\\\" táblát összekapcsolja a csatornával, a csatorna minden tagja (meglévő és új) képes lesz szerkeszteni azt. Ez nem vonatkozik a vendég tagokra. A táblát bármikor leválaszthatja a csatornáról.\",\n  \"boardSelector.confirm-link-board-subtext-with-other-channel\": \"Amikor a \\\"{boardName}\\\" táblát összekapcsolja a csatornával, a csatorna minden tagja (meglévő és új) képes lesz szerkeszteni azt. Ez nem vonatkozik a vendég tagokra.{lineBreak} A tábla jelenleg egy másik csatornához van kapcsolva. Le lesz választva, amennyiben úgy dönt, hogy ide kapcsolja.\",\n  \"boardSelector.create-a-board\": \"Tábla létrehozása\",\n  \"boardSelector.link\": \"Összekapcsolás\",\n  \"boardSelector.search-for-boards\": \"Táblák keresése\",\n  \"boardSelector.title\": \"Táblák összekapcsolása\",\n  \"boardSelector.unlink\": \"Leválasztás\",\n  \"calendar.month\": \"Hónap\",\n  \"calendar.today\": \"MA\",\n  \"calendar.week\": \"Hét\",\n  \"cloudMessage.learn-more\": \"Tudjon meg többet\",\n  \"createImageBlock.failed\": \"Nem sikerült feltölteni a fájlt. Fájlméret korlát elérve.\",\n  \"default-properties.badges\": \"Megjegyzések és leírás\",\n  \"default-properties.title\": \"Cím\",\n  \"error.back-to-home\": \"Vissza a kezdőlapra\",\n  \"error.back-to-team\": \"Vissza a csapatba\",\n  \"error.board-not-found\": \"Tábla nem található.\",\n  \"error.go-login\": \"Bejelentkezés\",\n  \"error.invalid-read-only-board\": \"Önnek nincs hozzáférése ehhez a táblához. Jelentkezz be a Táblák eléréséhez.\",\n  \"error.not-logged-in\": \"Lehet, hogy lejárt a munkamenete, vagy nincs bejelentkezve. Jelentkezzen be újra a Táblákhoz való hozzáféréshez.\",\n  \"error.page.title\": \"Sajnálom, valami rosszul sikerült\",\n  \"error.team-undefined\": \"Nem egy érvényes csapat.\",\n  \"error.unknown\": \"Hiba lépett fel.\",\n  \"generic.previous\": \"Előző\",\n  \"guest-no-board.subtitle\": \"Ebben a csapatban még nincs hozzáférése egyik táblához sem, kérjük, várjon, amíg valaki felveszi Önt bármelyik táblához.\",\n  \"guest-no-board.title\": \"Még nincsenek táblák\",\n  \"imagePaste.upload-failed\": \"Néhány fájl nem került feltöltésre. Fájlméret korlát elérve\",\n  \"limitedCard.title\": \"Rejtett kártyák\",\n  \"login.log-in-button\": \"Bejelentkezés\",\n  \"login.log-in-title\": \"Bejelentkezés\",\n  \"login.register-button\": \"vagy hozzon létre egy fiókot ha még nincs\",\n  \"notification-box-card-limit-reached.close-tooltip\": \"Altatás 10 napig\",\n  \"notification-box-card-limit-reached.contact-link\": \"értesítheti a rendszergazdát\",\n  \"notification-box-card-limit-reached.link\": \"Váltson fizetős csomagra\",\n  \"notification-box-card-limit-reached.title\": \"{cards} kártya rejtve a táblán\",\n  \"notification-box-cards-hidden.title\": \"Ez a művelet elrejtett egy másik kártyát\",\n  \"notification-box.card-limit-reached.not-admin.text\": \"Az archivált kártyák eléréséhez {contactLink}, hogy váltson fizetős csomagra.\",\n  \"notification-box.card-limit-reached.text\": \"A kártyák limitjét elérte, a régebbi kártyák megtekintéséhez {link}\",\n  \"person.add-user-to-board\": \"{username} hozzáadása a táblához\",\n  \"person.add-user-to-board-confirm-button\": \"Hozzáadás a táblához\",\n  \"person.add-user-to-board-permissions\": \"Jogosultságok\",\n  \"person.add-user-to-board-question\": \"Biztosan hozzá szeretné adni {username} felhasználót a táblához?\",\n  \"person.add-user-to-board-warning\": \"{username} nem tagja a táblának, és nem kap értesítést róla.\",\n  \"register.login-button\": \"vagy jelentkezzen be ha már van fiókja\",\n  \"register.signup-title\": \"Regisztráljon fiókjáért\",\n  \"rhs-board-non-admin-msg\": \"Ön nem rendszergazdája a táblának\",\n  \"rhs-boards.add\": \"Hozzáadás\",\n  \"rhs-boards.dm\": \"KÜ\",\n  \"rhs-boards.gm\": \"CSÜ\",\n  \"rhs-boards.header.dm\": \"ez egy Közvetlen Üzenet\",\n  \"rhs-boards.header.gm\": \"ez egy Csoportos Üzenet\",\n  \"rhs-boards.last-update-at\": \"Utolsó frissítés: {datetime}\",\n  \"rhs-boards.link-boards-to-channel\": \"Táblák összekapcsolása a {channelName} csatornával\",\n  \"rhs-boards.linked-boards\": \"Összekapcsolt kártyák\",\n  \"rhs-boards.no-boards-linked-to-channel\": \"Még nincs {channelName} csatornához kapcsolódó tábla\",\n  \"rhs-boards.no-boards-linked-to-channel-description\": \"A Boards egy projekt kezelő eszköz, amely segít meghatározni, szervezni, nyomon követni és kezelni a munkát a csapatokon belül, egy ismerős kanban tábla nézet segítségével.\",\n  \"rhs-boards.unlink-board\": \"Tábla leválasztása\",\n  \"rhs-boards.unlink-board1\": \"Tábla leválasztása\",\n  \"rhs-channel-boards-header.title\": \"Táblák\",\n  \"share-board.publish\": \"Közzététel\",\n  \"share-board.share\": \"Megosztás\",\n  \"shareBoard.channels-select-group\": \"Csatornák\",\n  \"shareBoard.confirm-change-team-role.body\": \"Mindenki ebben a táblában, akinek alacsonyabb jogosultsága van, mint a \\\"{role}\\\" szerepkör, <b>mostantól {role}</b> lesz. Biztos, hogy meg akarja változtatni a tábla minimális szerepkörét?\",\n  \"shareBoard.confirm-change-team-role.confirmBtnText\": \"Minimális szerepkör módosítása\",\n  \"shareBoard.confirm-change-team-role.title\": \"Minimális szerepkör módosítása\",\n  \"shareBoard.confirm-link-channel\": \"Tábla összekapcsolása csatornával\",\n  \"shareBoard.confirm-link-channel-button\": \"Csatorna összekapcsolása\",\n  \"shareBoard.confirm-link-channel-button-with-other-channel\": \"Leválasztás és ide kapcsolás\",\n  \"shareBoard.confirm-link-channel-subtext\": \"Ha egy csatornát összekapcsol egy táblával, a csatorna minden tagja (meglévő és új) képes lesz szerkeszteni azt. Ez nem vonatkozik a vendég tagokra.\",\n  \"shareBoard.confirm-link-channel-subtext-with-other-channel\": \"Ha egy csatornát összekapcsol egy táblával, a csatorna minden tagja (meglévő és új) képes lesz szerkeszteni azt. Ez nem vonatkozik a vendég tagokra.{lineBreak}A tábla jelenleg egy másik csatornához van kapcsolva. Le lesz választva, amennyiben úgy dönt, hogy ide kapcsolja.\",\n  \"shareBoard.confirm-unlink.body\": \"Ha egy csatornát leválaszt egy tábláról, a csatorna minden tagja (meglévő és új) elveszíti a hozzáférést, kivéve, ha külön engedélyt kapott rá.\",\n  \"shareBoard.confirm-unlink.confirmBtnText\": \"Csatorna leválasztása\",\n  \"shareBoard.confirm-unlink.title\": \"Csatorna leválasztása a tábláról\",\n  \"shareBoard.lastAdmin\": \"A tábláknak legalább egy Adminisztárorral kell rendelkezniük\",\n  \"shareBoard.members-select-group\": \"Tagok\",\n  \"shareBoard.unknown-channel-display-name\": \"Ismeretlen csatorna\",\n  \"tutorial_tip.finish_tour\": \"Kész\",\n  \"tutorial_tip.got_it\": \"Értettem\",\n  \"tutorial_tip.ok\": \"Következő\",\n  \"tutorial_tip.out\": \"Ezen tippek kikapcsolása.\",\n  \"tutorial_tip.seen\": \"Ezt látta már?\"\n}\n"
  },
  {
    "path": "webapp/i18n/id.json",
    "content": "{\n  \"BoardComponent.add-a-group\": \"+ Tambahkan kelompok\",\n  \"BoardComponent.delete\": \"Hapus\",\n  \"BoardComponent.hidden-columns\": \"Kolom-kolom yang disembunyikan\",\n  \"BoardComponent.hide\": \"Sembunyikan\",\n  \"BoardComponent.new\": \"+ Buat\",\n  \"BoardComponent.no-property\": \"Tidak ada {property}\",\n  \"BoardComponent.no-property-title\": \"Item dengan properti {property} yang kosong akan berpindah ke sini. Kolom ini tidak dapat dihapus.\",\n  \"BoardComponent.show\": \"Tampilkan\",\n  \"BoardPage.newVersion\": \"Versi baru Board tersedia, klik di sini untuk memuat ulang.\",\n  \"BoardPage.syncFailed\": \"Papan mungkin terhapus atau akses Anda ke papan ditolak.\",\n  \"BoardTemplateSelector.add-template\": \"Template baru\",\n  \"BoardTemplateSelector.create-empty-board\": \"Buat board kosong\",\n  \"BoardTemplateSelector.delete-template\": \"Hapus\",\n  \"BoardTemplateSelector.description\": \"Pilih template untuk membantu Anda memulai. Sesuaikan template dengan mudah agar sesuai dengan kebutuhan Anda, atau buat papan kosong untuk memulai dari awal.\",\n  \"BoardTemplateSelector.edit-template\": \"Ubah\",\n  \"BoardTemplateSelector.plugin.no-content-description\": \"Tambahkan papan ke bilah sisi menggunakan salah satu kerangka yang ditentukan di bawah atau mulai dari awal.{lineBreak} Anggota \\\"{workspaceName}\\\" akan memiliki akses ke papan yang dibuat di sini.\",\n  \"BoardTemplateSelector.plugin.no-content-title\": \"Buat Papan di {workspaceName}\",\n  \"BoardTemplateSelector.title\": \"Buat Papan\",\n  \"CardDetail.add-content\": \"Tambahkan isi\",\n  \"CardDetail.add-icon\": \"Tambahkan ikon\",\n  \"CardDetail.add-property\": \"+ Tambahkan properti\",\n  \"CardDetail.addCardText\": \"tambahkan teks kartu\",\n  \"CardDetail.moveContent\": \"pindahkan isi kartu\",\n  \"CardDetail.new-comment-placeholder\": \"Tambahkan komentar...\",\n  \"CardDialog.editing-template\": \"Anda sedang menyunting sebuah template.\",\n  \"CardDialog.nocard\": \"Kartu ini tidak ada atau tidak dapat diakses.\",\n  \"ColorOption.selectColor\": \"Pilih Warna {color}\",\n  \"Comment.delete\": \"Hapus\",\n  \"CommentsList.send\": \"Kirim\",\n  \"ContentBlock.Delete\": \"Hapus\",\n  \"ContentBlock.DeleteAction\": \"hapus\",\n  \"ContentBlock.addElement\": \"tambahkan {type}\",\n  \"ContentBlock.checkbox\": \"kotak centang\",\n  \"ContentBlock.divider\": \"pembagi\",\n  \"ContentBlock.editCardCheckbox\": \"kotakpilihan-beralih\",\n  \"ContentBlock.editCardCheckboxText\": \"sunting teks kartu\",\n  \"ContentBlock.editCardText\": \"sunting teks kartu\",\n  \"ContentBlock.editText\": \"Sunting teks...\",\n  \"ContentBlock.image\": \"gambar\",\n  \"ContentBlock.insertAbove\": \"Masukkan di atas\",\n  \"ContentBlock.moveDown\": \"Turunkan\",\n  \"ContentBlock.moveUp\": \"Naikkan\",\n  \"ContentBlock.text\": \"teks\",\n  \"Dialog.closeDialog\": \"Tutup dialog\",\n  \"EditableDayPicker.today\": \"Hari Ini\",\n  \"Error.websocket-closed\": \"Koneksi ke soket web tertutup, koneksi terganggu. Jika hal ini terus berlanjut, periksa konfigurasi server atau proxy web Anda.\",\n  \"Filter.includes\": \"termasuk\",\n  \"Filter.is-empty\": \"kosong\",\n  \"Filter.is-not-empty\": \"tidak kosong\",\n  \"Filter.not-includes\": \"tidak termasuk\",\n  \"FilterComponent.add-filter\": \"+ Tambahkan saringan\",\n  \"FilterComponent.delete\": \"Hapus\",\n  \"GroupBy.ungroup\": \"Pisahkan grup\",\n  \"KanbanCard.untitled\": \"Tidak berjudul\",\n  \"Mutator.new-card-from-template\": \"kartu baru dari template\",\n  \"Mutator.new-template-from-card\": \"template baru dari kartu\",\n  \"PropertyMenu.Delete\": \"Hapus\",\n  \"PropertyMenu.changeType\": \"Ubah jenis properti\",\n  \"PropertyMenu.typeTitle\": \"Jenis\",\n  \"PropertyType.Checkbox\": \"Kotak Centang\",\n  \"PropertyType.CreatedBy\": \"Dibuat oleh\",\n  \"PropertyType.CreatedTime\": \"Waktu dibuat\",\n  \"PropertyType.Date\": \"Tanggal\",\n  \"PropertyType.Email\": \"Surel\",\n  \"PropertyType.MultiSelect\": \"Banyak Pilihan\",\n  \"PropertyType.Number\": \"Angka\",\n  \"PropertyType.Person\": \"Orang\",\n  \"PropertyType.Phone\": \"Telepon\",\n  \"PropertyType.Select\": \"Pilihan\",\n  \"PropertyType.Text\": \"Teks\",\n  \"PropertyType.UpdatedBy\": \"Diperbarui oleh\",\n  \"PropertyType.UpdatedTime\": \"Waktu diperbarui\",\n  \"RegistrationLink.confirmRegenerateToken\": \"Ini akan membuat tautan yang sebelumnya dibagikan tidak valid. Lanjutkan?\",\n  \"RegistrationLink.copiedLink\": \"Disalin!\",\n  \"RegistrationLink.copyLink\": \"Salin tautan\",\n  \"RegistrationLink.description\": \"Bagikan tautan ini untuk membuat akun lainnya:\",\n  \"RegistrationLink.regenerateToken\": \"Buat ulang token\",\n  \"RegistrationLink.tokenRegenerated\": \"Tautan pendaftaran dibuat ulang\",\n  \"ShareBoard.confirmRegenerateToken\": \"Ini akan membuat tautan yang sebelumnya dibagikan tidak valid. Lanjutkan?\",\n  \"ShareBoard.copiedLink\": \"Disalin!\",\n  \"ShareBoard.copyLink\": \"Salin tautan\",\n  \"ShareBoard.tokenRegenrated\": \"Token dibuat ulang\",\n  \"Sidebar.about\": \"Tentang Focalboard\",\n  \"Sidebar.add-board\": \"+ Tambahkan papan\",\n  \"Sidebar.changePassword\": \"Ubah kata sandi\",\n  \"Sidebar.delete-board\": \"Hapus papan\",\n  \"Sidebar.export-archive\": \"Ekpor arsip\",\n  \"Sidebar.import-archive\": \"Impor arsip\",\n  \"Sidebar.invite-users\": \"Undang pengguna\",\n  \"Sidebar.logout\": \"Keluar\",\n  \"Sidebar.random-icons\": \"Ikon acak\",\n  \"Sidebar.set-language\": \"Tetapkan bahasa\",\n  \"Sidebar.set-theme\": \"Tetapkan tema\",\n  \"Sidebar.settings\": \"Pengaturan\",\n  \"Sidebar.untitled-board\": \"(Papan Tak Berjudul)\",\n  \"TableComponent.add-icon\": \"Tambahkan ikon\",\n  \"TableComponent.name\": \"Nama\",\n  \"TableComponent.plus-new\": \"+ Buat\",\n  \"TableHeaderMenu.delete\": \"Hapus\",\n  \"TableHeaderMenu.duplicate\": \"Duplikasikan\",\n  \"TableHeaderMenu.hide\": \"Sembunyikan\",\n  \"TableHeaderMenu.insert-left\": \"Masukkan di kiri\",\n  \"TableHeaderMenu.insert-right\": \"Masukkan di kanan\",\n  \"TableHeaderMenu.sort-ascending\": \"Urutkan ke atas\",\n  \"TableHeaderMenu.sort-descending\": \"Urutkan ke bawah\",\n  \"TableRow.open\": \"Buka\",\n  \"TopBar.give-feedback\": \"Beri Masukan\",\n  \"ValueSelector.valueSelector\": \"Pilihan nilai\",\n  \"ValueSelectorLabel.openMenu\": \"Menu terbuka\",\n  \"View.AddView\": \"Tambahkan tampilan\",\n  \"View.Board\": \"Papan\",\n  \"View.DeleteView\": \"Hapus tampilan\",\n  \"View.DuplicateView\": \"Duplikasikan tampilan\",\n  \"View.NewBoardTitle\": \"Tampilan papan\",\n  \"View.NewGalleryTitle\": \"Tampilan galeri\",\n  \"View.NewTableTitle\": \"Tampilan tabel\",\n  \"View.Table\": \"Tabel\",\n  \"ViewHeader.add-template\": \"Template baru\",\n  \"ViewHeader.delete-template\": \"Hapus\",\n  \"ViewHeader.edit-template\": \"Sunting\",\n  \"ViewHeader.empty-card\": \"Kartu kosong\",\n  \"ViewHeader.export-board-archive\": \"Ekspor arsip papan\",\n  \"ViewHeader.export-complete\": \"Ekspor selesai!\",\n  \"ViewHeader.export-csv\": \"Ekspor ke CSV\",\n  \"ViewHeader.export-failed\": \"Ekspor gagal!\",\n  \"ViewHeader.filter\": \"Penyaringan\",\n  \"ViewHeader.group-by\": \"Kelompokkan berdasarkan: {property}\",\n  \"ViewHeader.new\": \"Buat\",\n  \"ViewHeader.properties\": \"Properti-properti\",\n  \"ViewHeader.search-text\": \"Cari teks\",\n  \"ViewHeader.select-a-template\": \"Pilih sebuah template\",\n  \"ViewHeader.sort\": \"Pengurutan\",\n  \"ViewHeader.untitled\": \"Tak berjudul\",\n  \"ViewTitle.hide-description\": \"sembunyikan deskripsi\",\n  \"ViewTitle.pick-icon\": \"Pilih ikon\",\n  \"ViewTitle.random-icon\": \"Acak\",\n  \"ViewTitle.remove-icon\": \"Hapus ikon\",\n  \"ViewTitle.show-description\": \"tampilkan deskripsi\",\n  \"ViewTitle.untitled-board\": \"Papan tak berjudul\",\n  \"WelcomePage.Description\": \"Papan adalah alat manajemen proyek yang membantu Anda untuk mendefinisikan, mengatur, memantau, dan mengelola pekerjaan dalam tim, menggunakan tampilan kanban yang mudah dipahami\",\n  \"WelcomePage.Explore.Button\": \"Jelajahi\",\n  \"WelcomePage.Heading\": \"Selamat Datang di Papan\",\n  \"Workspace.editing-board-template\": \"Anda sedang menyunting sebuah template papan.\",\n  \"default-properties.title\": \"Judul\",\n  \"login.log-in-button\": \"Masuk\",\n  \"login.log-in-title\": \"Masuk\",\n  \"login.register-button\": \"atau buat sebuah akun jika Anda belum memilikinya\",\n  \"register.login-button\": \"atau masuk jika Anda sudah memiliki akun\",\n  \"register.signup-title\": \"Daftar untuk mendapatkan akun Anda\"\n}\n"
  },
  {
    "path": "webapp/i18n/it.json",
    "content": "{\n  \"BoardComponent.add-a-group\": \"+ Aggiungi un gruppo\",\n  \"BoardComponent.delete\": \"Elimina\",\n  \"BoardComponent.hidden-columns\": \"Campi nascosti\",\n  \"BoardComponent.hide\": \"Nascondi\",\n  \"BoardComponent.new\": \"+ Nuovo\",\n  \"BoardComponent.no-property\": \"No {property}\",\n  \"BoardComponent.no-property-title\": \"Gli oggetti senza alcuna proprietà {property} andranno qui. Questo campo non può essere rimosso.\",\n  \"BoardComponent.show\": \"Mostra\",\n  \"BoardMember.schemeAdmin\": \"Amministratore\",\n  \"BoardMember.schemeCommenter\": \"Commentatore\",\n  \"BoardMember.schemeEditor\": \"Editore\",\n  \"BoardMember.schemeNone\": \"Niente\",\n  \"BoardMember.schemeViewer\": \"Vista\",\n  \"BoardMember.unlinkChannel\": \"Rimuovi collegamento\",\n  \"BoardPage.newVersion\": \"Una nuova versione di Board è disponibile, clicca qui per ricaricare.\",\n  \"BoardPage.syncFailed\": \"La board potrebbe essere cancellata o l'accesso revocato.\",\n  \"BoardTemplateSelector.add-template\": \"Nuovo modello\",\n  \"BoardTemplateSelector.create-empty-board\": \"Crea una board vuota\",\n  \"BoardTemplateSelector.delete-template\": \"Elimina\",\n  \"BoardTemplateSelector.description\": \"Scegli un modello per iniziare. Personalizza facilmente il modello in base alle tue esigenze o crea una board vuota per iniziare da zero.\",\n  \"BoardTemplateSelector.edit-template\": \"Modifica\",\n  \"BoardTemplateSelector.plugin.no-content-description\": \"Aggiungi una bacheca alla barra laterale utilizzando uno dei modelli definiti di seguito o inizia da zero.{lineBreak} I membri di \\\"{teamName}\\\" avranno accesso alle bacheche create qui.\",\n  \"BoardTemplateSelector.plugin.no-content-title\": \"Crea una bacheca\",\n  \"BoardTemplateSelector.title\": \"Crea una bacheca\",\n  \"BoardTemplateSelector.use-this-template\": \"Usa questo modello\",\n  \"BoardsSwitcher.Title\": \"Trova bacheche\",\n  \"BoardsUnfurl.Limited\": \"Altri dettagli non sono nascosti in quanto la scheda è archiviata\",\n  \"BoardsUnfurl.Remainder\": \"+{remainder} di più\",\n  \"BoardsUnfurl.Updated\": \"Aggiornato {time}\",\n  \"Calculations.Options.average.displayName\": \"Media\",\n  \"Calculations.Options.average.label\": \"Media\",\n  \"Calculations.Options.count.displayName\": \"Conta\",\n  \"Calculations.Options.count.label\": \"Conta\",\n  \"Calculations.Options.countChecked.displayName\": \"Controllato\",\n  \"Calculations.Options.countChecked.label\": \"Conteggio selezionati\",\n  \"Calculations.Options.countUnchecked.displayName\": \"Non selezionato\",\n  \"Calculations.Options.countUnchecked.label\": \"Conteggio non selezionato\",\n  \"Calculations.Options.countUniqueValue.displayName\": \"Unico\",\n  \"Calculations.Options.countUniqueValue.label\": \"Conta i valori unici\",\n  \"Calculations.Options.countValue.displayName\": \"Valori\",\n  \"Calculations.Options.countValue.label\": \"Conteggio valori\",\n  \"Calculations.Options.dateRange.displayName\": \"Intervallo\",\n  \"Calculations.Options.dateRange.label\": \"Intervallo\",\n  \"Calculations.Options.earliest.displayName\": \"Primo\",\n  \"Calculations.Options.earliest.label\": \"Primo\",\n  \"Calculations.Options.latest.displayName\": \"Ultimo\",\n  \"Calculations.Options.latest.label\": \"Ultimo\",\n  \"Calculations.Options.max.displayName\": \"Massimo\",\n  \"Calculations.Options.max.label\": \"Massimo\",\n  \"Calculations.Options.median.displayName\": \"Mediana\",\n  \"Calculations.Options.median.label\": \"Mediana\",\n  \"Calculations.Options.min.displayName\": \"Minimo\",\n  \"Calculations.Options.min.label\": \"Minimo\",\n  \"Calculations.Options.none.displayName\": \"Calcola\",\n  \"Calculations.Options.none.label\": \"Nulla\",\n  \"Calculations.Options.percentChecked.displayName\": \"Controllato\",\n  \"Calculations.Options.percentChecked.label\": \"Percentuale selezionata\",\n  \"Calculations.Options.percentUnchecked.displayName\": \"Non selezionato\",\n  \"Calculations.Options.percentUnchecked.label\": \"Percentuale non selezionata\",\n  \"Calculations.Options.range.displayName\": \"Intervallo\",\n  \"Calculations.Options.range.label\": \"Intervallo\",\n  \"Calculations.Options.sum.displayName\": \"Somma\",\n  \"Calculations.Options.sum.label\": \"Somma\",\n  \"CalendarCard.untitled\": \"Senza titolo\",\n  \"CardActionsMenu.copiedLink\": \"Copiato!\",\n  \"CardActionsMenu.copyLink\": \"Copia collegamento\",\n  \"CardActionsMenu.delete\": \"Elimina\",\n  \"CardActionsMenu.duplicate\": \"Duplica\",\n  \"CardBadges.title-checkboxes\": \"Checkboxes\",\n  \"CardBadges.title-comments\": \"Commenti\",\n  \"CardBadges.title-description\": \"Questa scheda ha una descrizione\",\n  \"CardDetail.Follow\": \"Segui\",\n  \"CardDetail.Following\": \"Prossimo\",\n  \"CardDetail.add-content\": \"Aggiungi contenuto\",\n  \"CardDetail.add-icon\": \"Aggiungi icona\",\n  \"CardDetail.add-property\": \"+ Aggiungi una proprietà\",\n  \"CardDetail.addCardText\": \"aggiungi testo alla scheda\",\n  \"CardDetail.limited-button\": \"Aggiorna\",\n  \"CardDetail.limited-title\": \"Questa scheda è nascosta\",\n  \"CardDetail.moveContent\": \"Sposta il contenuto della scheda\",\n  \"CardDetail.new-comment-placeholder\": \"Aggiungi un commento...\",\n  \"CardDetailProperty.confirm-delete-heading\": \"Conferma l'eliminazione della proprietà\",\n  \"CardDetailProperty.confirm-delete-subtext\": \"Sei sicuro di voler eliminare la proprietà \\\"{propertyName}\\\"? Rimuovendola, verranno eliminate le proprietà da tutte le carte in questa board.\",\n  \"CardDetailProperty.confirm-property-name-change-subtext\": \"Sei sicuro di voler cambiare la proprietà \\\"{propertyName}\\\" {customText}? Questo influirà su {numOfCards} schede in questa bacheca e alcuni dati potrebbero andare persi.\",\n  \"CardDetailProperty.confirm-property-type-change\": \"Conferma la modifica del tipo di proprietà\",\n  \"CardDetailProperty.delete-action-button\": \"Rimuovi\",\n  \"CardDetailProperty.property-change-action-button\": \"Cambia proprietà\",\n  \"CardDetailProperty.property-changed\": \"Cambiata proprietà con successo!\",\n  \"CardDetailProperty.property-deleted\": \"{propertyName} rimossa con successo!\",\n  \"CardDetailProperty.property-name-change-subtext\": \"tipo da \\\"{oldPropType}\\\" a \\\"{newPropType}\\\"\",\n  \"CardDetial.limited-link\": \"Scopri di più sui nostri piani.\",\n  \"CardDialog.delete-confirmation-dialog-button-text\": \"Elimina\",\n  \"CardDialog.delete-confirmation-dialog-heading\": \"Conferma l'eliminazione della scheda!\",\n  \"CardDialog.editing-template\": \"Stai modificando un template.\",\n  \"CardDialog.nocard\": \"Questa scheda non esiste o è inaccessibile.\",\n  \"Categories.CreateCategoryDialog.CancelText\": \"Cancella\",\n  \"Categories.CreateCategoryDialog.CreateText\": \"Crea\",\n  \"Categories.CreateCategoryDialog.Placeholder\": \"Nomina la tua categoria\",\n  \"Categories.CreateCategoryDialog.UpdateText\": \"Aggiorna\",\n  \"CenterPanel.Login\": \"Login\",\n  \"CenterPanel.Share\": \"Condividi\",\n  \"ColorOption.selectColor\": \"Seleziona{color} Colore\",\n  \"Comment.delete\": \"Elimina\",\n  \"CommentsList.send\": \"Invia\",\n  \"ConfirmationDialog.cancel-action\": \"Annulla\",\n  \"ConfirmationDialog.confirm-action\": \"Conferma\",\n  \"ContentBlock.Delete\": \"Elimina\",\n  \"ContentBlock.DeleteAction\": \"elimina\",\n  \"ContentBlock.addElement\": \"aggiungi {type}\",\n  \"ContentBlock.checkbox\": \"casella di controllo\",\n  \"ContentBlock.divider\": \"divisore\",\n  \"ContentBlock.editCardCheckbox\": \"casella di controllo spuntata\",\n  \"ContentBlock.editCardCheckboxText\": \"modifica il testo della scheda\",\n  \"ContentBlock.editCardText\": \"modifica il testo della scheda\",\n  \"ContentBlock.editText\": \"Modifica il testo...\",\n  \"ContentBlock.image\": \"immagine\",\n  \"ContentBlock.insertAbove\": \"Inserisci sopra\",\n  \"ContentBlock.moveDown\": \"Sposta giù\",\n  \"ContentBlock.moveUp\": \"Sposta su\",\n  \"ContentBlock.text\": \"testo\",\n  \"DateRange.clear\": \"Pulisci\",\n  \"DateRange.empty\": \"Vuoto\",\n  \"DateRange.endDate\": \"Data di scadenza\",\n  \"DateRange.today\": \"Oggi\",\n  \"DeleteBoardDialog.confirm-cancel\": \"Annulla\",\n  \"DeleteBoardDialog.confirm-delete\": \"Elimina\",\n  \"DeleteBoardDialog.confirm-info\": \"Sei sicuro di voler eliminare la bacheca \\\"{boardTitle}\\\"? Eliminandola, rimuoverai tutte le schede in bacheca.\",\n  \"DeleteBoardDialog.confirm-tite\": \"Conferma la rimozione della bacheca\",\n  \"DeleteBoardDialog.confirm-tite-template\": \"Confermi di eliminare il template della bacheca\",\n  \"Dialog.closeDialog\": \"Chiudi finestra di dialogo\",\n  \"EditableDayPicker.today\": \"Oggi\",\n  \"Error.mobileweb\": \"Il supporto web mobile è attualmente in fase di beta iniziale. Non tutte le funzionalità potrebbero essere presenti.\",\n  \"Error.websocket-closed\": \"Connessione interrotta col Websocket. Se il problema persiste, controlla la configurazione del tuo server o del proxy web.\",\n  \"Filter.contains\": \"contiene\",\n  \"Filter.ends-with\": \"termina con\",\n  \"Filter.includes\": \"include\",\n  \"Filter.is\": \"è\",\n  \"Filter.is-empty\": \"è vuoto\",\n  \"Filter.is-not-empty\": \"non è vuoto\",\n  \"Filter.is-not-set\": \"non configurato\",\n  \"Filter.is-set\": \"configurato\",\n  \"Filter.not-contains\": \"non contiene\",\n  \"Filter.not-ends-with\": \"non termina con\",\n  \"Filter.not-includes\": \"non include\",\n  \"Filter.not-starts-with\": \"non inizia con\",\n  \"Filter.starts-with\": \"inizia con\",\n  \"FilterComponent.add-filter\": \"+ Aggiungi un filtro\",\n  \"FilterComponent.delete\": \"Elimina\",\n  \"FindBoardsDialog.IntroText\": \"Cerca per bacheca\",\n  \"FindBoardsDialog.NoResultsFor\": \"Nessun risultato per \\\"{searchQuery}\\\"\",\n  \"FindBoardsDialog.NoResultsSubtext\": \"Controlla l'ortografia o prova con un'altra ricerca.\",\n  \"FindBoardsDialog.SubTitle\": \"Scrivi per trovare una bacheca. Utilizza i tasti <b>SU/GIÙ</b> per navigare. <b>INVIO</b> per selezionare, <b>ESC</b> per annullare\",\n  \"FindBoardsDialog.Title\": \"Trova bacheche\",\n  \"GroupBy.hideEmptyGroups\": \"Nascondi {count} gruppi vuoti\",\n  \"GroupBy.showHiddenGroups\": \"Mostra {count} gruppi nascosti\",\n  \"GroupBy.ungroup\": \"Dividi\",\n  \"HideBoard.MenuOption\": \"Nascondi bacheca\",\n  \"KanbanCard.untitled\": \"Senza titolo\",\n  \"Mutator.new-board-from-template\": \"nuova bacheca da template\",\n  \"Mutator.new-card-from-template\": \"nuova scheda da modello\",\n  \"Mutator.new-template-from-card\": \"nuovo modello da scheda\",\n  \"OnboardingTour.AddComments.Body\": \"Puoi commentare sui issues e anche @menzionare il tuo compagno su Mattermost per attirare la loro attenzione.\",\n  \"OnboardingTour.AddComments.Title\": \"Aggiungi  commenti\",\n  \"OnboardingTour.AddDescription.Body\": \"Aggiungi una descrizione alla tua scheda in modo da far sapere che cosa riguarda.\",\n  \"OnboardingTour.AddDescription.Title\": \"Aggiungi una descrizione\",\n  \"OnboardingTour.AddProperties.Body\": \"Aggiungi varie proprietà alle schede per renderle ancora più potenti!\",\n  \"OnboardingTour.AddProperties.Title\": \"Aggiungi proprietà\",\n  \"OnboardingTour.AddView.Body\": \"Vai qui in modo da creare una nuova vista per organizzare le tue bacheche utilizzando differenti layout.\",\n  \"OnboardingTour.AddView.Title\": \"Aggiungi una nuova vista\",\n  \"OnboardingTour.CopyLink.Body\": \"Puoi condividere le schede con i tuoi colleghi copiando il link e incollandolo in un canale, messaggio privato o messaggio di gruppo.\",\n  \"OnboardingTour.CopyLink.Title\": \"Copia link\",\n  \"OnboardingTour.OpenACard.Body\": \"Apri una scheda per esplorare i potenti modi in cui una Bacheca può aiutarti nell'organizzare il lavoro.\",\n  \"OnboardingTour.OpenACard.Title\": \"Apri una scheda\",\n  \"OnboardingTour.ShareBoard.Body\": \"Puoi condividere la bacheca internamente, solo con il tuo team, oppure pubblicarlo per tutti gli utenti fuori dalla tua organizzazione.\",\n  \"OnboardingTour.ShareBoard.Title\": \"Condividi bacheca\",\n  \"PersonProperty.board-members\": \"Membri bacheca\",\n  \"PropertyMenu.Delete\": \"Elimina\",\n  \"PropertyMenu.changeType\": \"Cambia il tipo di proprietà\",\n  \"PropertyMenu.selectType\": \"Seleziona il tipo di proprietà\",\n  \"PropertyMenu.typeTitle\": \"Tipo\",\n  \"PropertyType.Checkbox\": \"Casella di controllo\",\n  \"PropertyType.CreatedBy\": \"Creato da\",\n  \"PropertyType.CreatedTime\": \"Orario di creazione\",\n  \"PropertyType.Date\": \"Data\",\n  \"PropertyType.Email\": \"Email\",\n  \"PropertyType.MultiSelect\": \"Selezione Multipla\",\n  \"PropertyType.Number\": \"Numero\",\n  \"PropertyType.Person\": \"Persona\",\n  \"PropertyType.Phone\": \"Telefono\",\n  \"PropertyType.Select\": \"Seleziona\",\n  \"PropertyType.Text\": \"Testo\",\n  \"PropertyType.UpdatedBy\": \"Aggiornato da\",\n  \"PropertyType.UpdatedTime\": \"Ora di aggiornamento\",\n  \"PropertyValueElement.empty\": \"Vuoto\",\n  \"RegistrationLink.confirmRegenerateToken\": \"Questo invaliderà i link condivisi in precedenza. Continuare?\",\n  \"RegistrationLink.copiedLink\": \"Copiato!\",\n  \"RegistrationLink.copyLink\": \"Copia link\",\n  \"RegistrationLink.description\": \"Condividi questo link per creare nuovi account:\",\n  \"RegistrationLink.regenerateToken\": \"Rigenera il token\",\n  \"RegistrationLink.tokenRegenerated\": \"Link di registrazione ricreato\",\n  \"ShareBoard.PublishDescription\": \"Pubblica e condividi un link di sola lettura con chiunque\",\n  \"ShareBoard.PublishTitle\": \"Pubblica\",\n  \"ShareBoard.Title\": \"Condividi bacheca\",\n  \"ShareBoard.confirmRegenerateToken\": \"Questo invaliderà i link condivisi in precedenza. Continuare?\",\n  \"ShareBoard.copiedLink\": \"Copiato!\",\n  \"ShareBoard.copyLink\": \"Copia link\",\n  \"ShareBoard.regenerate\": \"Rigenera token\",\n  \"ShareBoard.teamPermissionsText\": \"Tutti nel team {teamName}\",\n  \"ShareBoard.tokenRegenrated\": \"Token rigenerato\",\n  \"ShareBoard.userPermissionsRemoveMemberText\": \"Rimuovi utente\",\n  \"ShareBoard.userPermissionsYouText\": \"(Tu)\",\n  \"ShareTemplate.Title\": \"Condividi template\",\n  \"Sidebar.about\": \"Informazioni su Focalboard\",\n  \"Sidebar.add-board\": \"+ Aggiungi Contenitore\",\n  \"Sidebar.changePassword\": \"Cambia password\",\n  \"Sidebar.delete-board\": \"Elimina contenitore\",\n  \"Sidebar.duplicate-board\": \"Duplica bacheca\",\n  \"Sidebar.export-archive\": \"Esporta archivio\",\n  \"Sidebar.import\": \"Importa\",\n  \"Sidebar.import-archive\": \"Importa archivio\",\n  \"Sidebar.invite-users\": \"Invita utenti\",\n  \"Sidebar.logout\": \"Logout\",\n  \"Sidebar.no-boards-in-category\": \"Nessuna bacheca all'interno\",\n  \"Sidebar.random-icons\": \"Icone casuali\",\n  \"Sidebar.set-language\": \"Imposta la lingua\",\n  \"Sidebar.set-theme\": \"Imposta il tema\",\n  \"Sidebar.settings\": \"Impostazioni\",\n  \"Sidebar.template-from-board\": \"Nuovo template dalla bacheca\",\n  \"Sidebar.untitled-board\": \"(Contenitore senza titolo)\",\n  \"SidebarCategories.BlocksMenu.Move\": \"Sposta a...\",\n  \"SidebarCategories.CategoryMenu.CreateNew\": \"Crea nuova categoria\",\n  \"SidebarCategories.CategoryMenu.Delete\": \"Elimina categoria\",\n  \"SidebarCategories.CategoryMenu.DeleteModal.Body\": \"Le bacheche in <b>{categoryName}</b> andranno nelle categorie precedenti. Non hai rimosso alcuna bacheca.\",\n  \"SidebarCategories.CategoryMenu.DeleteModal.Title\": \"Eliminare questa categoria?\",\n  \"SidebarCategories.CategoryMenu.Update\": \"Rinomina categoria\",\n  \"TableComponent.add-icon\": \"Aggiungi icona\",\n  \"TableComponent.name\": \"Nome\",\n  \"TableComponent.plus-new\": \"+ Nuovo\",\n  \"TableHeaderMenu.delete\": \"Elimina\",\n  \"TableHeaderMenu.duplicate\": \"Duplica\",\n  \"TableHeaderMenu.hide\": \"Nascondi\",\n  \"TableHeaderMenu.insert-left\": \"Inserisci a sinistra\",\n  \"TableHeaderMenu.insert-right\": \"Inserisci a destra\",\n  \"TableHeaderMenu.sort-ascending\": \"Ordine crescente\",\n  \"TableHeaderMenu.sort-descending\": \"Ordine decrescente\",\n  \"TableRow.open\": \"Apri\",\n  \"TopBar.give-feedback\": \"Dai un feedback\",\n  \"ValueSelector.noOptions\": \"Nessuna opzione. Inizia a digitare per aggiungere la prima!\",\n  \"ValueSelector.valueSelector\": \"Seleziona valore\",\n  \"ValueSelectorLabel.openMenu\": \"Apri menu\",\n  \"View.AddView\": \"Aggiungi Vista\",\n  \"View.Board\": \"Contenitore\",\n  \"View.DeleteView\": \"Elimina Vista\",\n  \"View.DuplicateView\": \"Duplica Vista\",\n  \"View.Gallery\": \"Galleria\",\n  \"View.NewBoardTitle\": \"Vista Contenitore\",\n  \"View.NewCalendarTitle\": \"Vista calendario\",\n  \"View.NewGalleryTitle\": \"Vista gallery\",\n  \"View.NewTableTitle\": \"Vista tabella\",\n  \"View.Table\": \"Tabella\",\n  \"ViewHeader.add-template\": \"+ Nuovo modello\",\n  \"ViewHeader.delete-template\": \"Elimina\",\n  \"ViewHeader.display-by\": \"Visualizzato da: {property}\",\n  \"ViewHeader.edit-template\": \"Modifica\",\n  \"ViewHeader.empty-card\": \"Scheda vuota\",\n  \"ViewHeader.export-board-archive\": \"Esporta archivio board\",\n  \"ViewHeader.export-complete\": \"Esportazione completata!\",\n  \"ViewHeader.export-csv\": \"Esporta in formato CSV\",\n  \"ViewHeader.export-failed\": \"Esportazione fallita!\",\n  \"ViewHeader.filter\": \"Filtro\",\n  \"ViewHeader.group-by\": \"Raggruppa per: {property}\",\n  \"ViewHeader.new\": \"Nuovo\",\n  \"ViewHeader.properties\": \"Proprietà\",\n  \"ViewHeader.properties-menu\": \"Menù delle proprietà\",\n  \"ViewHeader.search-text\": \"Cerca testo\",\n  \"ViewHeader.select-a-template\": \"Seleziona un modello\",\n  \"ViewHeader.set-default-template\": \"Imposta come predefinito\",\n  \"ViewHeader.sort\": \"Ordina\",\n  \"ViewHeader.untitled\": \"Senza titolo\",\n  \"ViewHeader.view-header-menu\": \"Vedi menù header\",\n  \"ViewHeader.view-menu\": \"Visualizza menù\",\n  \"ViewTitle.hide-description\": \"nascondi descrizione\",\n  \"ViewTitle.pick-icon\": \"Scegli un'icona\",\n  \"ViewTitle.random-icon\": \"Casuale\",\n  \"ViewTitle.remove-icon\": \"Rimuovi icona\",\n  \"ViewTitle.show-description\": \"mostra descrizione\",\n  \"ViewTitle.untitled-board\": \"Contenitore senza titolo\",\n  \"WelcomePage.Description\": \"Boards è uno strumento organizzativo per progetti che aiuta a definire, organizzare, tenere traccia e controllo del lavoro tra gruppi, usando una vista familiare a scheda Kanban\",\n  \"WelcomePage.Explore.Button\": \"Esplora\",\n  \"WelcomePage.Heading\": \"Benvenuto in Boards\",\n  \"WelcomePage.NoThanks.Text\": \"No grazie, farò da solo\",\n  \"Workspace.editing-board-template\": \"Stai modificando un modello di una bacheca.\",\n  \"calendar.month\": \"Mese\",\n  \"calendar.today\": \"OGGI\",\n  \"calendar.week\": \"Settimana\",\n  \"default-properties.badges\": \"Commenti e Descrizione\",\n  \"default-properties.title\": \"Titolo\",\n  \"error.page.title\": \"Mi dispiace, qualcosa è andato storto\",\n  \"generic.previous\": \"Precedente\",\n  \"login.log-in-button\": \"Login\",\n  \"login.log-in-title\": \"Login\",\n  \"login.register-button\": \"oppure crea un account se non ne hai già uno\",\n  \"register.login-button\": \"oppure fai il login se hai un account\",\n  \"register.signup-title\": \"Registrati per un tuo account\",\n  \"shareBoard.lastAdmin\": \"Le bacheche devono avere almeno un amministratore\",\n  \"tutorial_tip.finish_tour\": \"Fatto\",\n  \"tutorial_tip.ok\": \"Prossimo\",\n  \"tutorial_tip.out\": \"Togli i suggerimenti\",\n  \"tutorial_tip.seen\": \"Hai mai visto questo prima d'ora?\"\n}\n"
  },
  {
    "path": "webapp/i18n/ja.json",
    "content": "{\n  \"AppBar.Tooltip\": \"リンク先Boardの切替え\",\n  \"Attachment.Attachment-title\": \"添付する\",\n  \"AttachmentBlock.DeleteAction\": \"削除\",\n  \"AttachmentBlock.addElement\": \"{type} を追加\",\n  \"AttachmentBlock.delete\": \"添付ファイルを削除しました。\",\n  \"AttachmentBlock.failed\": \"ファイルサイズの制限に達したため、ファイルをアップロードできませんでした。\",\n  \"AttachmentBlock.upload\": \"添付ファイルをアップロードしています。\",\n  \"AttachmentBlock.uploadSuccess\": \"添付ファイルをアップロードしました。\",\n  \"AttachmentElement.delete-confirmation-dialog-button-text\": \"削除\",\n  \"AttachmentElement.download\": \"ダウンロード\",\n  \"AttachmentElement.upload-percentage\": \"アップロード中...({uploadPercent}%)\",\n  \"BoardComponent.add-a-group\": \"+ グループを追加する\",\n  \"BoardComponent.delete\": \"削除\",\n  \"BoardComponent.hidden-columns\": \"非表示\",\n  \"BoardComponent.hide\": \"非表示\",\n  \"BoardComponent.new\": \"+ 新規\",\n  \"BoardComponent.no-property\": \"{property} 無し\",\n  \"BoardComponent.no-property-title\": \"{property}が空のアイテムがここに表示されます。このカラムは削除できません。\",\n  \"BoardComponent.show\": \"表示\",\n  \"BoardMember.schemeAdmin\": \"管理者\",\n  \"BoardMember.schemeCommenter\": \"コメンター\",\n  \"BoardMember.schemeEditor\": \"編集者\",\n  \"BoardMember.schemeNone\": \"なし\",\n  \"BoardMember.schemeViewer\": \"閲覧者\",\n  \"BoardMember.unlinkChannel\": \"リンク解除\",\n  \"BoardPage.newVersion\": \"Boardsの新しいバージョンが利用可能です。ここをクリックして再読み込みしてください。\",\n  \"BoardPage.syncFailed\": \"Boardが削除されたか、アクセスが取り消されました。\",\n  \"BoardTemplateSelector.add-template\": \"テンプレート新規作成\",\n  \"BoardTemplateSelector.create-empty-board\": \"空のBoardを作成\",\n  \"BoardTemplateSelector.delete-template\": \"削除する\",\n  \"BoardTemplateSelector.description\": \"以下のテンプレートを使用するか、空の状態から作成することで、サイドバーにBoardを追加できます。\",\n  \"BoardTemplateSelector.edit-template\": \"編集\",\n  \"BoardTemplateSelector.plugin.no-content-description\": \"以下のテンプレートを使用するか、空の状態から作成することで、サイドバーにBoardを追加できます。\",\n  \"BoardTemplateSelector.plugin.no-content-title\": \"Boardを作成する\",\n  \"BoardTemplateSelector.title\": \"Boardを作成する\",\n  \"BoardTemplateSelector.use-this-template\": \"このテンプレートを使う\",\n  \"BoardsSwitcher.Title\": \"Board検索\",\n  \"BoardsUnfurl.Limited\": \"カードがアーカイブされているため詳細は表示されません\",\n  \"BoardsUnfurl.Remainder\": \"残り +{remainder}\",\n  \"BoardsUnfurl.Updated\": \"更新日時 {time}\",\n  \"Calculations.Options.average.displayName\": \"平均\",\n  \"Calculations.Options.average.label\": \"平均\",\n  \"Calculations.Options.count.displayName\": \"カウント\",\n  \"Calculations.Options.count.label\": \"カウント\",\n  \"Calculations.Options.countChecked.displayName\": \"チェック済み\",\n  \"Calculations.Options.countChecked.label\": \"チェック済みの数\",\n  \"Calculations.Options.countUnchecked.displayName\": \"未チェック\",\n  \"Calculations.Options.countUnchecked.label\": \"未チェックの数\",\n  \"Calculations.Options.countUniqueValue.displayName\": \"ユニーク\",\n  \"Calculations.Options.countUniqueValue.label\": \"ユニーク値の数\",\n  \"Calculations.Options.countValue.displayName\": \"値\",\n  \"Calculations.Options.countValue.label\": \"値の数\",\n  \"Calculations.Options.dateRange.displayName\": \"範囲\",\n  \"Calculations.Options.dateRange.label\": \"範囲\",\n  \"Calculations.Options.earliest.displayName\": \"最初\",\n  \"Calculations.Options.earliest.label\": \"最初\",\n  \"Calculations.Options.latest.displayName\": \"最新\",\n  \"Calculations.Options.latest.label\": \"最新\",\n  \"Calculations.Options.max.displayName\": \"最大\",\n  \"Calculations.Options.max.label\": \"最大\",\n  \"Calculations.Options.median.displayName\": \"中央値\",\n  \"Calculations.Options.median.label\": \"中央値\",\n  \"Calculations.Options.min.displayName\": \"最小\",\n  \"Calculations.Options.min.label\": \"最小\",\n  \"Calculations.Options.none.displayName\": \"計算\",\n  \"Calculations.Options.none.label\": \"なし\",\n  \"Calculations.Options.percentChecked.displayName\": \"チェック済み\",\n  \"Calculations.Options.percentChecked.label\": \"チェック済みの割合\",\n  \"Calculations.Options.percentUnchecked.displayName\": \"未チェック\",\n  \"Calculations.Options.percentUnchecked.label\": \"未チェックの割合\",\n  \"Calculations.Options.range.displayName\": \"範囲\",\n  \"Calculations.Options.range.label\": \"範囲\",\n  \"Calculations.Options.sum.displayName\": \"合計\",\n  \"Calculations.Options.sum.label\": \"合計\",\n  \"CalendarCard.untitled\": \"無題\",\n  \"CardActionsMenu.copiedLink\": \"コピーしました!\",\n  \"CardActionsMenu.copyLink\": \"リンクをコピー\",\n  \"CardActionsMenu.delete\": \"削除\",\n  \"CardActionsMenu.duplicate\": \"複製\",\n  \"CardBadges.title-checkboxes\": \"チェックボックス\",\n  \"CardBadges.title-comments\": \"コメント\",\n  \"CardBadges.title-description\": \"このカードには説明があります\",\n  \"CardDetail.Attach\": \"添付\",\n  \"CardDetail.Follow\": \"フォローする\",\n  \"CardDetail.Following\": \"フォロー中\",\n  \"CardDetail.add-content\": \"内容を追加する\",\n  \"CardDetail.add-icon\": \"アイコンを追加する\",\n  \"CardDetail.add-property\": \"+ プロパティを追加\",\n  \"CardDetail.addCardText\": \"カードテキストを追加する\",\n  \"CardDetail.limited-body\": \"ProfessionalプランまたはEnterpriseプランにアップグレードしてください。\",\n  \"CardDetail.limited-button\": \"アップグレード\",\n  \"CardDetail.limited-title\": \"このカードは表示できません\",\n  \"CardDetail.moveContent\": \"カード内容の移動\",\n  \"CardDetail.new-comment-placeholder\": \"コメントを追加する...\",\n  \"CardDetailProperty.confirm-delete-heading\": \"プロパティの削除を確定する\",\n  \"CardDetailProperty.confirm-delete-subtext\": \"本当にプロパティ \\\"{propertyName}\\\" を削除しますか？ 削除すると、このBoardのすべてのカードからそのプロパティが削除されます。\",\n  \"CardDetailProperty.confirm-property-name-change-subtext\": \"本当にプロパティ \\\"{propertyName}\\\" の \\\"{customText}\\\" に変更しますか? これは、このBoardの{numOfCards}カード全体の値に影響し、データの損失につながる恐れがあります。\",\n  \"CardDetailProperty.confirm-property-type-change\": \"プロパティ種別の変更を確定する\",\n  \"CardDetailProperty.delete-action-button\": \"削除\",\n  \"CardDetailProperty.property-change-action-button\": \"プロパティの変更\",\n  \"CardDetailProperty.property-changed\": \"プロパティが変更されました!\",\n  \"CardDetailProperty.property-deleted\": \"{propertyName} が正常に削除されました!\",\n  \"CardDetailProperty.property-name-change-subtext\": \"種別を \\\"{oldPropType}\\\" から\\\"{newPropType}\\\" に\",\n  \"CardDetial.limited-link\": \"各プランの詳細についてはこちらをご覧ください。\",\n  \"CardDialog.delete-confirmation-dialog-attachment\": \"添付ファイルを削除する\",\n  \"CardDialog.delete-confirmation-dialog-button-text\": \"削除\",\n  \"CardDialog.delete-confirmation-dialog-heading\": \"カード削除の確認\",\n  \"CardDialog.editing-template\": \"テンプレートを編集しています。\",\n  \"CardDialog.nocard\": \"このカードは存在しないか、アクセスできません。\",\n  \"Categories.CreateCategoryDialog.CancelText\": \"キャンセル\",\n  \"Categories.CreateCategoryDialog.CreateText\": \"作成\",\n  \"Categories.CreateCategoryDialog.Placeholder\": \"カテゴリ名を入力してください\",\n  \"Categories.CreateCategoryDialog.UpdateText\": \"更新\",\n  \"CenterPanel.Login\": \"ログイン\",\n  \"CenterPanel.Share\": \"共有\",\n  \"ChannelIntro.CreateBoard\": \"Boardを作成する\",\n  \"ColorOption.selectColor\": \"{color} 色を選択\",\n  \"Comment.delete\": \"削除\",\n  \"CommentsList.send\": \"送信\",\n  \"ConfirmPerson.empty\": \"空\",\n  \"ConfirmPerson.search\": \"検索中...\",\n  \"ConfirmationDialog.cancel-action\": \"キャンセル\",\n  \"ConfirmationDialog.confirm-action\": \"確認\",\n  \"ContentBlock.Delete\": \"削除\",\n  \"ContentBlock.DeleteAction\": \"削除する\",\n  \"ContentBlock.addElement\": \"{type} を追加する\",\n  \"ContentBlock.checkbox\": \"チェックボックス\",\n  \"ContentBlock.divider\": \"仕切り\",\n  \"ContentBlock.editCardCheckbox\": \"切替えられたチェックボックス\",\n  \"ContentBlock.editCardCheckboxText\": \"カードテキストの編集\",\n  \"ContentBlock.editCardText\": \"カードテキストの編集\",\n  \"ContentBlock.editText\": \"テキストを編集する...\",\n  \"ContentBlock.image\": \"画像\",\n  \"ContentBlock.insertAbove\": \"上に挿入する\",\n  \"ContentBlock.moveBlock\": \"カード内容の移動\",\n  \"ContentBlock.moveDown\": \"下へ移動する\",\n  \"ContentBlock.moveUp\": \"上へ移動する\",\n  \"ContentBlock.text\": \"テキスト\",\n  \"DateRange.clear\": \"クリア\",\n  \"DateRange.empty\": \"空\",\n  \"DateRange.endDate\": \"終了日\",\n  \"DateRange.today\": \"今日\",\n  \"DeleteBoardDialog.confirm-cancel\": \"キャンセル\",\n  \"DeleteBoardDialog.confirm-delete\": \"削除\",\n  \"DeleteBoardDialog.confirm-info\": \"本当にBoard \\\"{boardTitle}\\\" を削除しますか？ 削除すると、このBoardのすべてのカードが削除されます。\",\n  \"DeleteBoardDialog.confirm-info-template\": \"Boardテンプレート \\\"{boardTitle}\\\" を本当に削除しますか?\",\n  \"DeleteBoardDialog.confirm-tite\": \"Boardの削除を確定する\",\n  \"DeleteBoardDialog.confirm-tite-template\": \"Boardテンプレートの削除を確定する\",\n  \"Dialog.closeDialog\": \"ダイアログを閉じる\",\n  \"EditableDayPicker.today\": \"今日\",\n  \"Error.mobileweb\": \"モバイルウェブのサポートは現在、初期ベータ版です。一部の機能が利用できない場合があります。\",\n  \"Error.websocket-closed\": \"ウェブソケット接続が閉じられ、接続が中断されました。この問題が解決しない場合は、サーバーまたはウェブプロキシの設定を確認してください。\",\n  \"Filter.contains\": \"を含む\",\n  \"Filter.ends-with\": \"で終わる\",\n  \"Filter.includes\": \"を含む\",\n  \"Filter.is\": \"と一致する\",\n  \"Filter.is-empty\": \"が空である\",\n  \"Filter.is-not-empty\": \"が空でない\",\n  \"Filter.is-not-set\": \"が未設定\",\n  \"Filter.is-set\": \"が設定済み\",\n  \"Filter.not-contains\": \"を含まない\",\n  \"Filter.not-ends-with\": \"で終わらない\",\n  \"Filter.not-includes\": \"を含まない\",\n  \"Filter.not-starts-with\": \"で始まらない\",\n  \"Filter.starts-with\": \"で始まる\",\n  \"FilterByText.placeholder\": \"フィルター文字列\",\n  \"FilterComponent.add-filter\": \"+ フィルターを追加する\",\n  \"FilterComponent.delete\": \"削除\",\n  \"FilterValue.empty\": \"(空)\",\n  \"FindBoardsDialog.IntroText\": \"Boardを検索\",\n  \"FindBoardsDialog.NoResultsFor\": \"\\\"{searchQuery}\\\"に対する結果はありません\",\n  \"FindBoardsDialog.NoResultsSubtext\": \"スペルを確認し、再度検索してください。\",\n  \"FindBoardsDialog.SubTitle\": \"Boardを検索するために文字を入力してください。<b>UP/DOWN</b>で閲覧、<b>ENTER</b>で選択、<b>ESC</b>でキャンセル\",\n  \"FindBoardsDialog.Title\": \"Boardを探す\",\n  \"GroupBy.hideEmptyGroups\": \"{count} 個の空のグループを隠す\",\n  \"GroupBy.showHiddenGroups\": \"{count} 個の非表示グループを表示する\",\n  \"GroupBy.ungroup\": \"グループ解除\",\n  \"HideBoard.MenuOption\": \"Boardを隠す\",\n  \"KanbanCard.untitled\": \"無題\",\n  \"MentionSuggestion.is-not-board-member\": \"(not board member)\",\n  \"Mutator.new-board-from-template\": \"テンプレートからの新しいBoard\",\n  \"Mutator.new-card-from-template\": \"テンプレートから新しいカードを作成\",\n  \"Mutator.new-template-from-card\": \"カードから新しいテンプレートを作成\",\n  \"OnboardingTour.AddComments.Body\": \"問題にコメントしたり、仲間のMattermostユーザーの注意を引くために@メンションすることもできます。\",\n  \"OnboardingTour.AddComments.Title\": \"コメントを追加する\",\n  \"OnboardingTour.AddDescription.Body\": \"カードに説明を追加し、チームメイトに何のカードかわかるようにしましょう。\",\n  \"OnboardingTour.AddDescription.Title\": \"説明を追加する\",\n  \"OnboardingTour.AddProperties.Body\": \"カードに様々なプロパティを追加することで、より便利になります。\",\n  \"OnboardingTour.AddProperties.Title\": \"プロパティを追加する\",\n  \"OnboardingTour.AddView.Body\": \"異なるレイアウトでBoardを整理するための新しいビューを作成するには、ここに移動します。\",\n  \"OnboardingTour.AddView.Title\": \"新しいビューを追加する\",\n  \"OnboardingTour.CopyLink.Body\": \"リンクをコピーしてチャンネル、ダイレクトメッセージ、グループメッセージに貼り付けることで、カードをチームメイトと共有することができます。\",\n  \"OnboardingTour.CopyLink.Title\": \"リンクをコピー\",\n  \"OnboardingTour.OpenACard.Body\": \"カードを開き、あなたの仕事を整理するのに役立つBoardの便利な使い方を探ってみてください。\",\n  \"OnboardingTour.OpenACard.Title\": \"カードを開く\",\n  \"OnboardingTour.ShareBoard.Body\": \"作成したBoardは、社内やチーム内で共有することも、組織外から見えるように公開することも可能です。\",\n  \"OnboardingTour.ShareBoard.Title\": \"Boardを共有\",\n  \"PersonProperty.board-members\": \"Board members\",\n  \"PersonProperty.me\": \"私\",\n  \"PersonProperty.non-board-members\": \"Not board members\",\n  \"PropertyMenu.Delete\": \"削除\",\n  \"PropertyMenu.changeType\": \"プロパティのタイプを変更する\",\n  \"PropertyMenu.selectType\": \"プロパティタイプの選択\",\n  \"PropertyMenu.typeTitle\": \"タイプ\",\n  \"PropertyType.Checkbox\": \"チェックボックス\",\n  \"PropertyType.CreatedBy\": \"作成者\",\n  \"PropertyType.CreatedTime\": \"作成日時\",\n  \"PropertyType.Date\": \"日付\",\n  \"PropertyType.Email\": \"メールアドレス\",\n  \"PropertyType.MultiPerson\": \"複数人\",\n  \"PropertyType.MultiSelect\": \"マルチセレクト\",\n  \"PropertyType.Number\": \"数字\",\n  \"PropertyType.Person\": \"人物\",\n  \"PropertyType.Phone\": \"電話番号\",\n  \"PropertyType.Select\": \"セレクト\",\n  \"PropertyType.Text\": \"テキスト\",\n  \"PropertyType.Unknown\": \"不明\",\n  \"PropertyType.UpdatedBy\": \"更新者\",\n  \"PropertyType.UpdatedTime\": \"更新日時\",\n  \"PropertyType.Url\": \"URL\",\n  \"PropertyValueElement.empty\": \"空\",\n  \"RegistrationLink.confirmRegenerateToken\": \"実行すると以前に共有されたリンクは無効になります。続行しますか？\",\n  \"RegistrationLink.copiedLink\": \"コピーしました!\",\n  \"RegistrationLink.copyLink\": \"リンクをコピー\",\n  \"RegistrationLink.description\": \"アカウントを作成には、このリンクを共有してください:\",\n  \"RegistrationLink.regenerateToken\": \"トークンを再生成する\",\n  \"RegistrationLink.tokenRegenerated\": \"登録リンクが再生成されました\",\n  \"ShareBoard.PublishDescription\": \"Web上の全員へ \\\"読み取り専用\\\" のリンクを公開および共有する。\",\n  \"ShareBoard.PublishTitle\": \"Web上へ公開する\",\n  \"ShareBoard.ShareInternal\": \"内部で共有する\",\n  \"ShareBoard.ShareInternalDescription\": \"権限のあるユーザーは、このリンクを使用することができます。\",\n  \"ShareBoard.Title\": \"Boardを共有\",\n  \"ShareBoard.confirmRegenerateToken\": \"実行すると以前に共有されたリンクは無効になります。続行しますか?\",\n  \"ShareBoard.copiedLink\": \"コピーしました!\",\n  \"ShareBoard.copyLink\": \"リンクをコピー\",\n  \"ShareBoard.regenerate\": \"トークンを再生成する\",\n  \"ShareBoard.searchPlaceholder\": \"人とチャンネルを検索\",\n  \"ShareBoard.teamPermissionsText\": \"{teamName}チームの全員\",\n  \"ShareBoard.tokenRegenrated\": \"トークンが再生成されました\",\n  \"ShareBoard.userPermissionsRemoveMemberText\": \"メンバーを削除する\",\n  \"ShareBoard.userPermissionsYouText\": \"(あなた)\",\n  \"ShareTemplate.Title\": \"テンプレートを共有する\",\n  \"ShareTemplate.searchPlaceholder\": \"人を検索\",\n  \"Sidebar.about\": \"Focalboardについて\",\n  \"Sidebar.add-board\": \"+ Boardを追加\",\n  \"Sidebar.changePassword\": \"パスワードを変更する\",\n  \"Sidebar.delete-board\": \"Boardを削除\",\n  \"Sidebar.duplicate-board\": \"Boardを複製する\",\n  \"Sidebar.export-archive\": \"エクスポート\",\n  \"Sidebar.import\": \"インポート\",\n  \"Sidebar.import-archive\": \"インポート\",\n  \"Sidebar.invite-users\": \"ユーザーを招待する\",\n  \"Sidebar.logout\": \"ログアウト\",\n  \"Sidebar.new-category.badge\": \"新規\",\n  \"Sidebar.new-category.drag-boards-cta\": \"ここにBoardをドラッグ...\",\n  \"Sidebar.no-boards-in-category\": \"カテゴリ内にBoardがありません\",\n  \"Sidebar.product-tour\": \"プロダクトツアー\",\n  \"Sidebar.random-icons\": \"ランダムアイコン\",\n  \"Sidebar.set-language\": \"言語設定\",\n  \"Sidebar.set-theme\": \"テーマ設定\",\n  \"Sidebar.settings\": \"設定\",\n  \"Sidebar.template-from-board\": \"Boardからの新しいテンプレート\",\n  \"Sidebar.untitled-board\": \"(無題のBoard)\",\n  \"Sidebar.untitled-view\": \"(無題のビュー)\",\n  \"SidebarCategories.BlocksMenu.Move\": \"移動...\",\n  \"SidebarCategories.CategoryMenu.CreateNew\": \"新しいカテゴリを作成する\",\n  \"SidebarCategories.CategoryMenu.Delete\": \"カテゴリを削除する\",\n  \"SidebarCategories.CategoryMenu.DeleteModal.Body\": \"<b>{categoryName}</b> にあるBoardは、Boards カテゴリに戻されます。どのBoardからも削除されることはありません。\",\n  \"SidebarCategories.CategoryMenu.DeleteModal.Title\": \"このカテゴリを削除しますか?\",\n  \"SidebarCategories.CategoryMenu.Update\": \"カテゴリ名を変更する\",\n  \"SidebarTour.ManageCategories.Body\": \"カスタムカテゴリーを作成し、管理することができます。カテゴリはユーザーごとに設定されるため、Boardを自分のカテゴリに移動しても、同じBoardを使用している他のメンバーには影響がありません。\",\n  \"SidebarTour.ManageCategories.Title\": \"カテゴリー管理\",\n  \"SidebarTour.SearchForBoards.Body\": \"Board切替（Cmd/Ctrl + K）により、素早くBoardを検索し、サイドバーに追加することができます。\",\n  \"SidebarTour.SearchForBoards.Title\": \"Boardを検索\",\n  \"SidebarTour.SidebarCategories.Body\": \"すべてのBoardが新しいサイドバーの下に整理されました。もう、ワークスペースを切り替える必要はありません。v7.2へのアップグレードに伴い、以前のワークスペースに基づいたカスタムカテゴリーが自動的に作成されている場合があります。これらは、お好みで削除したり編集することができます。\",\n  \"SidebarTour.SidebarCategories.Link\": \"詳細\",\n  \"SidebarTour.SidebarCategories.Title\": \"サイドバーカテゴリー\",\n  \"SiteStats.total_boards\": \"Board総数\",\n  \"SiteStats.total_cards\": \"カード数\",\n  \"TableComponent.add-icon\": \"アイコンを追加する\",\n  \"TableComponent.name\": \"名前\",\n  \"TableComponent.plus-new\": \"+ 新規\",\n  \"TableHeaderMenu.delete\": \"削除\",\n  \"TableHeaderMenu.duplicate\": \"複製\",\n  \"TableHeaderMenu.hide\": \"非表示\",\n  \"TableHeaderMenu.insert-left\": \"左に挿入\",\n  \"TableHeaderMenu.insert-right\": \"右に挿入\",\n  \"TableHeaderMenu.sort-ascending\": \"昇順でソート\",\n  \"TableHeaderMenu.sort-descending\": \"降順でソート\",\n  \"TableRow.DuplicateCard\": \"カードを複製する\",\n  \"TableRow.MoreOption\": \"その他のアクション\",\n  \"TableRow.open\": \"開く\",\n  \"TopBar.give-feedback\": \"フィードバックを送る\",\n  \"URLProperty.copiedLink\": \"コピーしました！\",\n  \"URLProperty.copy\": \"コピー\",\n  \"URLProperty.edit\": \"編集\",\n  \"UndoRedoHotKeys.canRedo\": \"やり直す\",\n  \"UndoRedoHotKeys.canRedo-with-description\": \"{description} をやり直す\",\n  \"UndoRedoHotKeys.canUndo\": \"元に戻す\",\n  \"UndoRedoHotKeys.canUndo-with-description\": \"{description} を元に戻す\",\n  \"UndoRedoHotKeys.cannotRedo\": \"やり直しする操作がありません\",\n  \"UndoRedoHotKeys.cannotUndo\": \"元に戻す操作がありません\",\n  \"ValueSelector.noOptions\": \"オプションがありません。最初の一つを追加するために入力を開始してください!\",\n  \"ValueSelector.valueSelector\": \"値選択\",\n  \"ValueSelectorLabel.openMenu\": \"メニューを開く\",\n  \"VersionMessage.help\": \"このバージョンの新機能を確認する。\",\n  \"View.AddView\": \"ビューを追加\",\n  \"View.Board\": \"Board\",\n  \"View.DeleteView\": \"ビューを削除\",\n  \"View.DuplicateView\": \"ビューを複製\",\n  \"View.Gallery\": \"ギャラリー\",\n  \"View.NewBoardTitle\": \"Board表示\",\n  \"View.NewCalendarTitle\": \"カレンダー表示\",\n  \"View.NewGalleryTitle\": \"ギャラリービュー\",\n  \"View.NewTableTitle\": \"テーブル表示\",\n  \"View.NewTemplateDefaultTitle\": \"無題のテンプレート\",\n  \"View.NewTemplateTitle\": \"無題\",\n  \"View.Table\": \"テーブル\",\n  \"ViewHeader.add-template\": \"新しいテンプレート\",\n  \"ViewHeader.delete-template\": \"削除\",\n  \"ViewHeader.display-by\": \"表示対象: {property}\",\n  \"ViewHeader.edit-template\": \"編集\",\n  \"ViewHeader.empty-card\": \"空のカード\",\n  \"ViewHeader.export-board-archive\": \"Boardアーカイブのエクスポート\",\n  \"ViewHeader.export-complete\": \"エクスポートが完了しました！\",\n  \"ViewHeader.export-csv\": \"CSVエクスポート\",\n  \"ViewHeader.export-failed\": \"エクスポートが失敗しました！\",\n  \"ViewHeader.filter\": \"フィルター\",\n  \"ViewHeader.group-by\": \"{property} でグループ化\",\n  \"ViewHeader.new\": \"新規\",\n  \"ViewHeader.properties\": \"プロパティ\",\n  \"ViewHeader.properties-menu\": \"プロパティメニュー\",\n  \"ViewHeader.search-text\": \"カード検索\",\n  \"ViewHeader.select-a-template\": \"テンプレート選択\",\n  \"ViewHeader.set-default-template\": \"デフォルトとして設定\",\n  \"ViewHeader.sort\": \"ソート\",\n  \"ViewHeader.untitled\": \"無題\",\n  \"ViewHeader.view-header-menu\": \"ヘッダーメニューを見る\",\n  \"ViewHeader.view-menu\": \"メニューを見る\",\n  \"ViewLimitDialog.Heading\": \"Boardごとのビュー数制限に達しました\",\n  \"ViewLimitDialog.PrimaryButton.Title.Admin\": \"アップグレード\",\n  \"ViewLimitDialog.PrimaryButton.Title.RegularUser\": \"管理者に通知する\",\n  \"ViewLimitDialog.Subtext.Admin\": \"ProfessionalプランまたはEnterpriseプランにアップグレードしてください。\",\n  \"ViewLimitDialog.Subtext.Admin.PricingPageLink\": \"各プランの詳細についてはこちらをご覧ください。\",\n  \"ViewLimitDialog.Subtext.RegularUser\": \"ProfessionalプランまたはEnterpriseプランへアップグレードするよう管理者に連絡してください。\",\n  \"ViewLimitDialog.UpgradeImg.AltText\": \"アップグレードイメージ\",\n  \"ViewLimitDialog.notifyAdmin.Success\": \"管理者に通知されました\",\n  \"ViewTitle.hide-description\": \"説明を非表示\",\n  \"ViewTitle.pick-icon\": \"アイコンを選ぶ\",\n  \"ViewTitle.random-icon\": \"ランダム\",\n  \"ViewTitle.remove-icon\": \"アイコンを削除する\",\n  \"ViewTitle.show-description\": \"説明を表示\",\n  \"ViewTitle.untitled-board\": \"無題のBoard\",\n  \"WelcomePage.Description\": \"Boardsは、よく知られたKanban形式のビューを使用して、チーム全体の作業を定義、整理、追跡、管理するためのプロジェクト管理ツールです。\",\n  \"WelcomePage.Explore.Button\": \"ツアーに参加する\",\n  \"WelcomePage.Heading\": \"Boardへようこそ\",\n  \"WelcomePage.NoThanks.Text\": \"いいえ、自分で調べます\",\n  \"WelcomePage.StartUsingIt.Text\": \"利用を開始する\",\n  \"Workspace.editing-board-template\": \"Boardのテンプレートを編集しています。\",\n  \"badge.guest\": \"ゲスト\",\n  \"boardSelector.confirm-link-board\": \"Boardをチャンネルへリンク\",\n  \"boardSelector.confirm-link-board-button\": \"はい、Boardをリンクします\",\n  \"boardSelector.confirm-link-board-subtext\": \"\\\"{boardName}\\\" をチャンネルにリンクすると、チャンネルの(既存/新規)メンバー全員がBoardを編集できるようになります。ただし、ゲストユーザーは除外されます。Boardとチャンネルのリンク解除はいつでも可能です。\",\n  \"boardSelector.confirm-link-board-subtext-with-other-channel\": \"\\\"{boardName}\\\" をチャンネルにリンクすると、チャンネルの(既存/新規)メンバー全員がBoardを編集できるようになります。ただし、ゲストユーザーは除外されます。{lineBreak} このBoardは現在他のチャンネルにリンクされています。ここにリンクさせると、他のチャンネルとのリンクは解除されます。\",\n  \"boardSelector.create-a-board\": \"Boardを作成\",\n  \"boardSelector.link\": \"リンク\",\n  \"boardSelector.search-for-boards\": \"Boardを検索\",\n  \"boardSelector.title\": \"Boardをリンク\",\n  \"boardSelector.unlink\": \"リンク解除\",\n  \"calendar.month\": \"月\",\n  \"calendar.today\": \"今日\",\n  \"calendar.week\": \"週\",\n  \"centerPanel.undefined\": \"{propertyName} 無し\",\n  \"centerPanel.unknown-user\": \"不明なユーザー\",\n  \"cloudMessage.learn-more\": \"さらに詳しく\",\n  \"createImageBlock.failed\": \"ファイルサイズの上限に達しているため、ファイルをアップロードできませんでした。\",\n  \"default-properties.badges\": \"コメントと説明\",\n  \"default-properties.title\": \"タイトル\",\n  \"error.back-to-home\": \"ホームへ戻る\",\n  \"error.back-to-team\": \"チームに戻る\",\n  \"error.board-not-found\": \"Boardが見つかりませんでした。\",\n  \"error.go-login\": \"ログイン\",\n  \"error.invalid-read-only-board\": \"このBoardにアクセスできません。アクセスするにはBoardsにログインしてください。\",\n  \"error.not-logged-in\": \"セッションの有効期限が切れているか、ログインしていない可能性があります。Boardsにアクセスするには再度ログインしてください。\",\n  \"error.page.title\": \"申し訳ありませんが、何か問題が発生しました\",\n  \"error.team-undefined\": \"有効なチームではありません。\",\n  \"error.unknown\": \"エラーが発生しました。\",\n  \"generic.previous\": \"前へ\",\n  \"guest-no-board.subtitle\": \"あなたはまだこのチームのどのBoardにもアクセスできません。誰かがあなたをBoardに追加するまでお待ちください。\",\n  \"guest-no-board.title\": \"まだBoardsはありません\",\n  \"imagePaste.upload-failed\": \"ファイルサイズの制限に達しているため、一部のファイルをアップロードできませんでした。\",\n  \"limitedCard.title\": \"非表示カード\",\n  \"login.log-in-button\": \"ログイン\",\n  \"login.log-in-title\": \"ログイン\",\n  \"login.register-button\": \"アカウントをお持ちでない方はアカウントを作成してください\",\n  \"new_channel_modal.create_board.empty_board_description\": \"空のBoardを新規作成する\",\n  \"new_channel_modal.create_board.empty_board_title\": \"空のBoard\",\n  \"new_channel_modal.create_board.select_template_placeholder\": \"テンプレートを選択\",\n  \"new_channel_modal.create_board.title\": \"このチャンネル用のBoardを作成する\",\n  \"notification-box-card-limit-reached.close-tooltip\": \"10日間のスヌーズ\",\n  \"notification-box-card-limit-reached.contact-link\": \"管理者に通知する\",\n  \"notification-box-card-limit-reached.link\": \"有料プランへのアップグレード\",\n  \"notification-box-card-limit-reached.title\": \"Boardから {cards} カードが非表示になっています\",\n  \"notification-box-cards-hidden.title\": \"このアクションにより他のカードが非表示になります\",\n  \"notification-box.card-limit-reached.not-admin.text\": \"アーカイブされたカードにアクセスするには、{contactLink}から有料プランにアップグレードしてください。\",\n  \"notification-box.card-limit-reached.text\": \"カード数の制限に達しました。古いカードを閲覧するには、{link}\",\n  \"person.add-user-to-board\": \"{username} をBoardに追加\",\n  \"person.add-user-to-board-confirm-button\": \"Boardに追加\",\n  \"person.add-user-to-board-permissions\": \"権限\",\n  \"person.add-user-to-board-question\": \"{username} をBoardに追加しますか？\",\n  \"person.add-user-to-board-warning\": \"{username} はBoardのメンバーではないので、それに関する通知を受け取ることはありません。\",\n  \"register.login-button\": \"または、すでにアカウントをお持ちの方はログインしてください\",\n  \"register.signup-title\": \"アカウント登録\",\n  \"rhs-board-non-admin-msg\": \"あなたはBoardの管理者ではありません\",\n  \"rhs-boards.add\": \"追加\",\n  \"rhs-boards.dm\": \"DM\",\n  \"rhs-boards.gm\": \"GM\",\n  \"rhs-boards.header.dm\": \"このダイレクトメッセージ\",\n  \"rhs-boards.header.gm\": \"このグループメッセージ\",\n  \"rhs-boards.last-update-at\": \"最終更新: {datetime}\",\n  \"rhs-boards.link-boards-to-channel\": \"Boardsを{channelName}へリンクする\",\n  \"rhs-boards.linked-boards\": \"リンク済みBoards\",\n  \"rhs-boards.no-boards-linked-to-channel\": \"{channelName}にリンクされたBoardsはまだありません\",\n  \"rhs-boards.no-boards-linked-to-channel-description\": \"Boardsは、よく知られたKanban形式のビューを使用して、チーム全体の作業を定義、生理、追跡、管理するためのプロジェクト管理ツールです。\",\n  \"rhs-boards.unlink-board\": \"Boardのリンクを解除\",\n  \"rhs-boards.unlink-board1\": \"Boardのリンクを解除\",\n  \"rhs-channel-boards-header.title\": \"Boards\",\n  \"share-board.publish\": \"公開\",\n  \"share-board.share\": \"共有\",\n  \"shareBoard.channels-select-group\": \"Channels\",\n  \"shareBoard.confirm-change-team-role.body\": \"このBoardで \\\"{role}\\\" より弱い権限のユーザー全員が <b>{role}</b> に昇格します。本当にBoardの最低限のロールを変更しますか？\",\n  \"shareBoard.confirm-change-team-role.confirmBtnText\": \"最低限のロールを変更\",\n  \"shareBoard.confirm-change-team-role.title\": \"最低限のロールを変更\",\n  \"shareBoard.confirm-link-channel\": \"Boardをチャンネルへリンク\",\n  \"shareBoard.confirm-link-channel-button\": \"チャンネルにリンク\",\n  \"shareBoard.confirm-link-channel-button-with-other-channel\": \"リンク解除とリンクはこちら\",\n  \"shareBoard.confirm-link-channel-subtext\": \"チャンネルをBoardにリンクすると、チャンネルの(既存/新規)メンバー全員がBoardを編集できるようになります。ただし、ゲストユーザーは除外されます。\",\n  \"shareBoard.confirm-link-channel-subtext-with-other-channel\": \"チャンネルをBoardにリンクすると、チャンネルの(既存/新規)メンバー全員がBoardを編集できるようになります。ただし、ゲストユーザーは除外されます。{lineBreak} このBoardは現在他のチャンネルにリンクされています。ここにリンクさせると、他のチャンネルとのリンクは解除されます。\",\n  \"shareBoard.confirm-unlink.body\": \"Boardからチャンネルへのリンクを解除すると、別途権限を付与されない限り、チャンネルの(既存/新規)メンバー全員がBoardへアクセスできなくなります。\",\n  \"shareBoard.confirm-unlink.confirmBtnText\": \"チャンネルとのリンクを解除\",\n  \"shareBoard.confirm-unlink.title\": \"Boardからチャンネルへのリンクを解除する\",\n  \"shareBoard.lastAdmin\": \"Boardsには少なくとも1名の管理者が必要です\",\n  \"shareBoard.members-select-group\": \"メンバー\",\n  \"shareBoard.unknown-channel-display-name\": \"不明なチャンネル\",\n  \"tutorial_tip.finish_tour\": \"完了\",\n  \"tutorial_tip.got_it\": \"了解\",\n  \"tutorial_tip.ok\": \"次へ\",\n  \"tutorial_tip.out\": \"これらのコツを表示しません。\",\n  \"tutorial_tip.seen\": \"以前に見たことがありますか?\"\n}\n"
  },
  {
    "path": "webapp/i18n/ka.json",
    "content": "{\n  \"AppBar.Tooltip\": \"დაკავშირებული დაფების გადართვა\",\n  \"BoardComponent.add-a-group\": \"+ ჯგუფის დამატება\",\n  \"BoardComponent.delete\": \"წაშლა\",\n  \"BoardComponent.hidden-columns\": \"დამალული სვეტები\",\n  \"BoardComponent.hide\": \"დამალვა\",\n  \"BoardComponent.new\": \"+ ახალი\",\n  \"BoardComponent.no-property\": \"არ არის {Property}\",\n  \"BoardComponent.no-property-title\": \"ცარიელი {property} საკუთრების მქონე ელემენტები აქ წავა. ამ სვეტის წაშლა შეუძლებელია.\",\n  \"BoardComponent.show\": \"ჩვენება\",\n  \"BoardMember.schemeAdmin\": \"ადმინისტრატორი\",\n  \"BoardMember.schemeCommenter\": \"კომენტატორი\",\n  \"BoardMember.schemeEditor\": \"რედაქტორი\",\n  \"BoardMember.schemeNone\": \"არცერთი\",\n  \"BoardMember.schemeViewer\": \"მაყურებელი\",\n  \"BoardMember.unlinkChannel\": \"კავშირის გაუქმება\",\n  \"BoardPage.newVersion\": \"დაფების ახალი ვერსია ხელმისაწვდომია, დააწკაპუნეთ აქ გადასატვირთად.\",\n  \"BoardPage.syncFailed\": \"დაფა შეიძლება წაშლილია ან გაუქმებულია წვდომა.\",\n  \"BoardTemplateSelector.add-template\": \"ახალი შაბლონი\",\n  \"BoardTemplateSelector.create-empty-board\": \"შექმენით ცარიელი დაფა\",\n  \"BoardTemplateSelector.delete-template\": \"წაშლა\",\n  \"BoardTemplateSelector.description\": \"დაამატეთ დაფა გვერდითა ზოლში ქვემოთ განსაზღვრული რომელიმე შაბლონის გამოყენებით ან დაიწყეთ ნულიდან.\",\n  \"BoardTemplateSelector.edit-template\": \"რედაქტირება\",\n  \"BoardTemplateSelector.plugin.no-content-description\": \"დაამატეთ დაფა გვერდითა ზოლში ქვემოთ განსაზღვრული რომელიმე შაბლონის გამოყენებით ან დაიწყეთ ნულიდან.\",\n  \"BoardTemplateSelector.plugin.no-content-title\": \"შექმენით დაფა\",\n  \"BoardTemplateSelector.title\": \"შექმენით დაფა\",\n  \"BoardTemplateSelector.use-this-template\": \"გამოიყენეთ ეს შაბლონი\",\n  \"BoardsSwitcher.Title\": \"დაფების ძებნა\",\n  \"BoardsUnfurl.Limited\": \"დამატებითი დეტალები დამალულია ბარათის დაარქივების გამო\",\n  \"BoardsUnfurl.Remainder\": \"+{remainder} მეტი\",\n  \"BoardsUnfurl.Updated\": \"განახლებული {time}\",\n  \"Calculations.Options.average.displayName\": \"საშუალო\",\n  \"Calculations.Options.average.label\": \"საშუალო\",\n  \"Calculations.Options.count.displayName\": \"დათვლა\",\n  \"Calculations.Options.count.label\": \"დათვლა\",\n  \"Calculations.Options.countChecked.displayName\": \"შემოწმებული\",\n  \"Calculations.Options.countChecked.label\": \"რაოდენობა შემოწმებულია\",\n  \"Calculations.Options.countUnchecked.displayName\": \"შეუმოწმებელი\",\n  \"Calculations.Options.countUnchecked.label\": \"რაოდენობა შეუმოწმებელია\",\n  \"Calculations.Options.countUniqueValue.displayName\": \"უნიკალური\",\n  \"Calculations.Options.countUniqueValue.label\": \"უნიკალური ღირებულების დათვლა\",\n  \"Calculations.Options.countValue.displayName\": \"ღირებულებები\",\n  \"Calculations.Options.countValue.label\": \"დათვალეთ მნიშვნელობა\",\n  \"Calculations.Options.dateRange.displayName\": \"დიაპაზონი\",\n  \"Calculations.Options.dateRange.label\": \"დიაპაზონი\",\n  \"Calculations.Options.earliest.displayName\": \"ყველაზე ადრეული\",\n  \"Calculations.Options.earliest.label\": \"ყველაზე ადრეული\",\n  \"Calculations.Options.latest.displayName\": \"უახლესი\",\n  \"Calculations.Options.latest.label\": \"უახლესი\",\n  \"Calculations.Options.max.displayName\": \"მაქსიმალური\",\n  \"Calculations.Options.max.label\": \"მაქსიმალური\",\n  \"Calculations.Options.median.displayName\": \"საშუალო\",\n  \"Calculations.Options.median.label\": \"საშუალო\",\n  \"Calculations.Options.min.displayName\": \"მინიმალური\",\n  \"Calculations.Options.min.label\": \"მინიმალური\",\n  \"Calculations.Options.none.displayName\": \"გამოთვლა\",\n  \"Calculations.Options.none.label\": \"გამოთვლა\",\n  \"Calculations.Options.percentChecked.displayName\": \"შემოწმებული\",\n  \"Calculations.Options.percentChecked.label\": \"პროცენტი შემოწმებულია\",\n  \"Calculations.Options.percentUnchecked.displayName\": \"შეუმოწმებელი\",\n  \"Calculations.Options.percentUnchecked.label\": \"პროცენტი შეუმოწმებელია\",\n  \"Calculations.Options.range.displayName\": \"დიაპაზონი\",\n  \"Calculations.Options.range.label\": \"დიაპაზონი\",\n  \"Calculations.Options.sum.displayName\": \"ჯამი\",\n  \"Calculations.Options.sum.label\": \"ჯამი\",\n  \"CalendarCard.untitled\": \"უსათაურო\",\n  \"CardActionsMenu.copiedLink\": \"დაკოპირებულია!\",\n  \"CardActionsMenu.copyLink\": \"Ბმულის კოპირება\",\n  \"CardActionsMenu.delete\": \"წაშლა\",\n  \"CardActionsMenu.duplicate\": \"დუბლიკატი\",\n  \"CardBadges.title-checkboxes\": \"მოსანიშნი ველები\",\n  \"CardBadges.title-comments\": \"კომენტარები\",\n  \"CardBadges.title-description\": \"ამ ბარათს აქვს აღწერა\",\n  \"CardDetail.Follow\": \"გაყოლა\",\n  \"CardDetail.Following\": \"მომდევნო\",\n  \"CardDetail.add-content\": \"დაამატეთ შინაარსი\",\n  \"CardDetail.add-icon\": \"ხატულის დამატება\",\n  \"CardDetail.add-property\": \"+ დაამატეთ Property\",\n  \"CardDetail.addCardText\": \"ბარათის ტექსტის დამატება\",\n  \"CardDetail.limited-body\": \"ბარათის ტექსტის დამატება განაახლეთ ჩვენს პროფესიონალურ ან საწარმოს გეგმაში დაარქივებული ბარათების სანახავად, თითო დაფაზე ულიმიტო ნახვები, ულიმიტო ბარათები და სხვა.\",\n  \"CardDetail.limited-button\": \"განახლება\",\n  \"CardDetail.limited-title\": \"ეს ბარათი დამალულია\",\n  \"CardDetail.moveContent\": \"ბარათის შინაარსის გადატანა\",\n  \"CardDetail.new-comment-placeholder\": \"კომენტარის დამატება...\",\n  \"CardDetailProperty.confirm-delete-heading\": \"დაადასტურეთ Property-ის წაშლა\",\n  \"CardDetailProperty.confirm-delete-subtext\": \"დარწმუნებული ხართ, რომ გსურთ წაშალოთ Property „{propertyName}“? მისი წაშლა წაშლის Property-ის ამ დაფის ყველა ბარათიდან.\",\n  \"CardDetailProperty.confirm-property-name-change-subtext\": \"დარწმუნებული ხართ, რომ გსურთ შეცვალოთ თვისება „{propertyName}“ {customText}? ეს გავლენას მოახდენს მნიშვნელობა(ებ)ზე {numOfCards} ბარათ(ებ)ში ამ დაფაზე და შეიძლება გამოიწვიოს მონაცემთა დაკარგვა.\",\n  \"CardDetailProperty.confirm-property-type-change\": \"დაადასტურეთ Property-ის ტიპის ცვლილება\",\n  \"CardDetailProperty.delete-action-button\": \"წაშლა\",\n  \"CardDetailProperty.property-change-action-button\": \"Property-ის შეცვლა\",\n  \"CardDetailProperty.property-changed\": \"Property წარმატებით შეიცვალა!\",\n  \"CardDetailProperty.property-deleted\": \"{propertyName} წარმატებით წაიშალა!\",\n  \"CardDetailProperty.property-name-change-subtext\": \"აკრიფეთ „{oldPropType}“-დან „{newPropType}“-მდე\",\n  \"CardDetial.limited-link\": \"შეიტყვეთ მეტი ჩვენი გეგმების შესახებ.\",\n  \"CardDialog.delete-confirmation-dialog-button-text\": \"წაშლა\",\n  \"CardDialog.delete-confirmation-dialog-heading\": \"დაადასტურეთ ბარათის წაშლა!\",\n  \"CardDialog.editing-template\": \"თქვენ არედაქტირებთ შაბლონს.\",\n  \"CardDialog.nocard\": \"ეს ბარათი არ არსებობს ან მიუწვდომელია.\",\n  \"Categories.CreateCategoryDialog.CancelText\": \"გაუქმება\",\n  \"Categories.CreateCategoryDialog.CreateText\": \"Შექმნა\",\n  \"Categories.CreateCategoryDialog.Placeholder\": \"მიეცით სახელი თქვენს კატეგორიას\",\n  \"Categories.CreateCategoryDialog.UpdateText\": \"განახლება\",\n  \"CenterPanel.Login\": \"შესვლა\",\n  \"CenterPanel.Share\": \"გაზიარება\",\n  \"ColorOption.selectColor\": \"აირჩიეთ {color} ფერი\",\n  \"Comment.delete\": \"წაშლა\",\n  \"CommentsList.send\": \"გაგზავნა\",\n  \"ConfirmationDialog.cancel-action\": \"გაუქმება\",\n  \"ConfirmationDialog.confirm-action\": \"დადასტურება\",\n  \"ContentBlock.Delete\": \"წაშლა\",\n  \"ContentBlock.DeleteAction\": \"წაშლა\",\n  \"ContentBlock.addElement\": \"დაამატეთ {type}\",\n  \"ContentBlock.checkbox\": \"მონიშვნის ველი\",\n  \"ContentBlock.divider\": \"გამყოფი\",\n  \"ContentBlock.editCardCheckbox\": \"მონიშნული-მონიშვნის ველი\",\n  \"ContentBlock.editCardCheckboxText\": \"ბარათის ტექსტის რედაქტირება\",\n  \"ContentBlock.editCardText\": \"ბარათის ტექსტის რედაქტირება\",\n  \"ContentBlock.editText\": \"ტექსტის რედაქტირება...\",\n  \"ContentBlock.image\": \"გამოსახულება\"\n}\n"
  },
  {
    "path": "webapp/i18n/kab.json",
    "content": "{}\n"
  },
  {
    "path": "webapp/i18n/kk.json",
    "content": "{\n  \"BoardComponent.add-a-group\": \"+ Гіруп қосу\",\n  \"BoardComponent.delete\": \"Жою\",\n  \"BoardComponent.hidden-columns\": \"Жасырын бағандар\",\n  \"BoardComponent.hide\": \"Жасыру\",\n  \"BoardComponent.new\": \"+ Жаңа\",\n  \"BoardComponent.no-property\": \"{property} жоқ\",\n  \"BoardComponent.no-property-title\": \"Бос {property} сипаты бар Item'дер осында болады. Бұл бағанды жою мүмкін емес.\",\n  \"BoardComponent.show\": \"Көрсету\",\n  \"BoardPage.newVersion\": \"Boards'тың жаңа нұсқасы қолжетімді, қайта жүктеу үшін осы жерді басыңыз.\",\n  \"BoardPage.syncFailed\": \"Тақта жойылуы немесе кіруге тыйым салынуы мүмкін.\",\n  \"BoardsUnfurl.Remainder\": \"+{remainder} көбірек\",\n  \"BoardsUnfurl.Updated\": \"Жүктелді {time}\",\n  \"Calculations.Options.average.displayName\": \"Орташа\",\n  \"Calculations.Options.average.label\": \"Орташа\",\n  \"Calculations.Options.count.displayName\": \"Санау\",\n  \"Calculations.Options.count.label\": \"Санау\",\n  \"Calculations.Options.countChecked.displayName\": \"Тексерілді\",\n  \"Calculations.Options.countChecked.label\": \"Санақ Тексерілді\",\n  \"Calculations.Options.countUnchecked.displayName\": \"Тексерілмеген\",\n  \"Calculations.Options.countUnchecked.label\": \"Санақ Тексерілмеген\",\n  \"Calculations.Options.countUniqueValue.displayName\": \"Бірегей\",\n  \"Calculations.Options.countUniqueValue.label\": \"Бірегей Мәндерді Санау\",\n  \"Calculations.Options.countValue.displayName\": \"Мәндер\",\n  \"Calculations.Options.countValue.label\": \"Мәнін Есептеу\",\n  \"Calculations.Options.dateRange.displayName\": \"Ранжы\",\n  \"Calculations.Options.dateRange.label\": \"Ранжы\",\n  \"Calculations.Options.earliest.displayName\": \"Бұрынғысы\",\n  \"Calculations.Options.earliest.label\": \"Бұрынғысы\",\n  \"Calculations.Options.latest.displayName\": \"Соңғы\",\n  \"Calculations.Options.latest.label\": \"Соңғы\",\n  \"Calculations.Options.max.displayName\": \"Max\",\n  \"Calculations.Options.max.label\": \"Max\",\n  \"Calculations.Options.median.displayName\": \"Median\",\n  \"Calculations.Options.median.label\": \"Median\",\n  \"Calculations.Options.min.displayName\": \"Min\",\n  \"Calculations.Options.min.label\": \"Min\",\n  \"Calculations.Options.none.displayName\": \"Есептеу\",\n  \"Calculations.Options.none.label\": \"Жоқ\",\n  \"Calculations.Options.percentChecked.displayName\": \"Тексерілді\",\n  \"Calculations.Options.percentChecked.label\": \"Пайыз Тексерілді\",\n  \"Calculations.Options.percentUnchecked.displayName\": \"Тексерілмеген\",\n  \"Calculations.Options.percentUnchecked.label\": \"Пайыз Тексерілмеген\",\n  \"Calculations.Options.range.displayName\": \"Ранжы\",\n  \"Calculations.Options.range.label\": \"Ранжы\",\n  \"Calculations.Options.sum.displayName\": \"Сома\",\n  \"Calculations.Options.sum.label\": \"Сома\",\n  \"CardDetail.Follow\": \"Follow\",\n  \"CardDetail.Following\": \"Following\",\n  \"CardDetail.add-content\": \"Кәнтен қосу\",\n  \"CardDetail.add-icon\": \"Икон қосу\",\n  \"CardDetail.add-property\": \"+ Қасиет қосу\",\n  \"CardDetail.addCardText\": \"Кәрте мәтінін қосу\",\n  \"CardDetail.moveContent\": \"Кәрте кәнтенін жылжыту\",\n  \"CardDetail.new-comment-placeholder\": \"Пікір қосу...\",\n  \"CardDetailProperty.confirm-delete-heading\": \"Сипатты Жоюды Растаңыз\",\n  \"CardDetailProperty.confirm-delete-subtext\": \"\\\"{propertyName}\\\" сипатын шынымен жойғыныз келе ме? Оны жойсаныз бұл сипат осы тақтадағы барлық кәртелерден жойылады.\",\n  \"CardDetailProperty.confirm-property-name-change-subtext\": \"\\\"{propertyName}\\\" {customText} сипатты шынымен өзгерткініз келе ме? Бұл осы тақтадағы {numOfCards} кәрте(лердің) мән(дер)іне әсер етеді және деректердің жағалуына әкелуі мүмкін.\",\n  \"CardDetailProperty.confirm-property-type-change\": \"Сипат түрін өзгертуді растаңыз!\",\n  \"CardDetailProperty.delete-action-button\": \"Жою\",\n  \"CardDetailProperty.property-change-action-button\": \"Property'ді Өзгерту\",\n  \"CardDetailProperty.property-changed\": \"Property сәтті өзгертілді!\",\n  \"CardDetailProperty.property-deleted\": \"{propertyName} Сәтті Жойылды!\",\n  \"CardDetailProperty.property-name-change-subtext\": \"\\\"{oldPropType}\\\" тен \\\"{newPropType}\\\"'қа дейін теріңіз\",\n  \"CardDialog.editing-template\": \"Сіз үлгіні өзгертудесіз.\",\n  \"CardDialog.nocard\": \"Бұл кәрте жоқ немесе қолжетімсіз.\",\n  \"ColorOption.selectColor\": \"{color} Түсті Танданыз\",\n  \"Comment.delete\": \"Жою\",\n  \"CommentsList.send\": \"Жіберу\",\n  \"ConfirmationDialog.cancel-action\": \"Болдырмау\",\n  \"ConfirmationDialog.confirm-action\": \"Растау\",\n  \"ContentBlock.Delete\": \"Жою\",\n  \"ContentBlock.DeleteAction\": \"жою\",\n  \"ContentBlock.addElement\": \"{type} қосу\",\n  \"ContentBlock.checkbox\": \"құсбелгі\",\n  \"ContentBlock.divider\": \"бөлгіш\",\n  \"ContentBlock.editCardCheckbox\": \"тандалған құсбелгі\",\n  \"ContentBlock.editCardCheckboxText\": \"кәрте мәтінін өзгерту\",\n  \"ContentBlock.editCardText\": \"кәрте мәтінін өзгерту\",\n  \"ContentBlock.editText\": \"Мәтінді өзгерту...\",\n  \"ContentBlock.image\": \"сурет\",\n  \"ContentBlock.insertAbove\": \"Жоғарға еңгізу\",\n  \"ContentBlock.moveDown\": \"Түсіру\",\n  \"ContentBlock.moveUp\": \"Көтеру\",\n  \"ContentBlock.text\": \"мәтін\",\n  \"DeleteBoardDialog.confirm-cancel\": \"Болдырмау\",\n  \"DeleteBoardDialog.confirm-delete\": \"Жою\",\n  \"DeleteBoardDialog.confirm-info\": \"\\\"{boardTitle}\\\" тақтасын шынымен жойғыңыз келе ме? Оны жою тақтадағы барлық кәртелерді жояды.\",\n  \"DeleteBoardDialog.confirm-tite\": \"Тақтаны Жоюды Растаңыз\",\n  \"Dialog.closeDialog\": \"Диалогты жабу\",\n  \"EditableDayPicker.today\": \"Бүгін\",\n  \"Error.mobileweb\": \"Mobile web қолдау қазір бастапқы бетада. Барлық мүмкіншілктер болмауы мүмкін.\",\n  \"Error.websocket-closed\": \"Websocket қосылымы жабылды, байланыс үзілді. Бұл әлі сақталса, серверді немесе web proxy кәнфиғуратенін тексеріңіз.\",\n  \"Filter.includes\": \"кірістіреді\",\n  \"Filter.is-empty\": \"бос\",\n  \"Filter.is-not-empty\": \"іші бос емес\",\n  \"Filter.not-includes\": \"кірістірмейді\",\n  \"FilterComponent.add-filter\": \"+ Филтір қосу\",\n  \"FilterComponent.delete\": \"Жою\",\n  \"GroupBy.ungroup\": \"Гірупсіздендіру\",\n  \"KanbanCard.untitled\": \"Атаусыз\",\n  \"Mutator.new-card-from-template\": \"үлгіден жаңа кәрте жасау\",\n  \"Mutator.new-template-from-card\": \"кәртеден жаңа үлгі\",\n  \"PropertyMenu.Delete\": \"Жою\",\n  \"PropertyMenu.changeType\": \"Property түрін өзгерту\",\n  \"PropertyMenu.selectType\": \"Property түрін тандау\",\n  \"PropertyMenu.typeTitle\": \"Түрі\",\n  \"PropertyType.Checkbox\": \"Құсбелгі\",\n  \"PropertyType.CreatedBy\": \"Жасаған\",\n  \"PropertyType.CreatedTime\": \"Жасалған уақыты\",\n  \"PropertyType.Date\": \"Даты\",\n  \"PropertyType.Email\": \"Email\",\n  \"PropertyType.MultiSelect\": \"Multi таңдау\",\n  \"PropertyType.Number\": \"Нөмір\",\n  \"PropertyType.Person\": \"Тұлға\",\n  \"PropertyType.Phone\": \"Телефон\",\n  \"PropertyType.Select\": \"Таңдау\",\n  \"PropertyType.Text\": \"Мәтін\",\n  \"PropertyType.UpdatedBy\": \"Соңғы өзгерткен\",\n  \"PropertyType.UpdatedTime\": \"Соңғы өзгертілген уақыты\",\n  \"PropertyValueElement.empty\": \"Бос\",\n  \"RegistrationLink.confirmRegenerateToken\": \"Бұл бұрын таратылған сілтемелерді жарамсыз етеді. Жалғастырасыз ба?\",\n  \"RegistrationLink.copiedLink\": \"Көшірілді!\",\n  \"RegistrationLink.copyLink\": \"Сілтемені көшіру\",\n  \"RegistrationLink.description\": \"Басқалар аққаунт жасау үшін осы сілтемені тарату:\",\n  \"RegistrationLink.regenerateToken\": \"Токенді регенераттау\",\n  \"RegistrationLink.tokenRegenerated\": \"Тіркелу сілтемесі регенератталды\",\n  \"ShareBoard.confirmRegenerateToken\": \"Бұл бұрын таратылған сілтемелерді жарамсыз етеді. Жалғастырасыз ба?\",\n  \"ShareBoard.copiedLink\": \"Көшірілді!\",\n  \"ShareBoard.copyLink\": \"Сілтемені көшіру\",\n  \"ShareBoard.tokenRegenrated\": \"Токен регенератталды\",\n  \"Sidebar.about\": \"Focalboard туралы\",\n  \"Sidebar.add-board\": \"+ Тақта қосу\",\n  \"Sidebar.changePassword\": \"Кілтсөзді өзгерту\",\n  \"Sidebar.delete-board\": \"Тақтаны жою\",\n  \"Sidebar.export-archive\": \"Мұрағатты экспорттау\",\n  \"Sidebar.import-archive\": \"Мұрағатты импорттау\",\n  \"Sidebar.invite-users\": \"Қолданушыларды шақыру\",\n  \"Sidebar.logout\": \"Шығу\",\n  \"Sidebar.random-icons\": \"Рандом икондар\",\n  \"Sidebar.set-language\": \"Тілді таңдау\",\n  \"Sidebar.set-theme\": \"Теміні орнату\",\n  \"Sidebar.settings\": \"Баптаулар\",\n  \"Sidebar.untitled-board\": \"(Атаусыз Тақта)\",\n  \"TableComponent.add-icon\": \"Иконды қосу\",\n  \"TableComponent.name\": \"Атауы\",\n  \"TableComponent.plus-new\": \"+ Қосу\",\n  \"TableHeaderMenu.delete\": \"Жою\",\n  \"TableHeaderMenu.duplicate\": \"Көшірмесін жасау\",\n  \"TableHeaderMenu.hide\": \"Жасыру\",\n  \"TableHeaderMenu.insert-left\": \"Солға еңгізу\",\n  \"TableHeaderMenu.insert-right\": \"Оңға еңгізу\",\n  \"TableHeaderMenu.sort-ascending\": \"Өсуі бойынша сұрыптау\",\n  \"TableHeaderMenu.sort-descending\": \"Кему бойынша сұрыптау\",\n  \"TableRow.open\": \"Ашу\",\n  \"TopBar.give-feedback\": \"Feedback беру\",\n  \"ValueSelector.noOptions\": \"Оптендер жоқ. Біріншісін қосу үшін теруді бастаныз!\",\n  \"ValueSelector.valueSelector\": \"Мән селектірі\",\n  \"ValueSelectorLabel.openMenu\": \"Мәзірді ашу\",\n  \"View.AddView\": \"Көріністі қосу\",\n  \"View.Board\": \"Тақта\",\n  \"View.DeleteView\": \"Көріністі жою\",\n  \"View.DuplicateView\": \"Көріністің көшірмесін жасау\",\n  \"View.Gallery\": \"Гәлері\",\n  \"View.NewBoardTitle\": \"Тақта көрінісі\",\n  \"View.NewCalendarTitle\": \"Күнтізбе Көрінісі\",\n  \"View.NewGalleryTitle\": \"Гәлері көрінісі\",\n  \"View.NewTableTitle\": \"Тақта көрінісі\",\n  \"View.Table\": \"Кесте\",\n  \"ViewHeader.add-template\": \"Жаңа үлгі\",\n  \"ViewHeader.delete-template\": \"Жою\",\n  \"ViewHeader.display-by\": \"{property} бойынша көрсету\",\n  \"ViewHeader.edit-template\": \"Өзгерту\",\n  \"ViewHeader.empty-card\": \"Кәртені тазарту\",\n  \"ViewHeader.export-board-archive\": \"Тақта мұрағатын экспорттау\",\n  \"ViewHeader.export-complete\": \"Экспорт аяқталды!\",\n  \"ViewHeader.export-csv\": \"CSV'ге экспорттау\",\n  \"ViewHeader.export-failed\": \"Экспорт сәтсіз аяқталды!\",\n  \"ViewHeader.filter\": \"Филтір\",\n  \"ViewHeader.group-by\": \"{property} бойынша гіруптеу\",\n  \"ViewHeader.new\": \"Жаңа\",\n  \"ViewHeader.properties\": \"Property'лер\",\n  \"ViewHeader.search-text\": \"Мәтінді іздеу\",\n  \"ViewHeader.select-a-template\": \"Үлгіні таңдау\",\n  \"ViewHeader.set-default-template\": \"Әдепкі ретінде орнату\",\n  \"ViewHeader.sort\": \"Сұрыптау\",\n  \"ViewHeader.untitled\": \"Атаусыз\",\n  \"ViewTitle.hide-description\": \"сипаттаманы жасыру\",\n  \"ViewTitle.pick-icon\": \"Иконды таңдау\",\n  \"ViewTitle.random-icon\": \"Рандом\",\n  \"ViewTitle.remove-icon\": \"Иконды кетіру\",\n  \"ViewTitle.show-description\": \"Сипаттаманы көрсету\",\n  \"ViewTitle.untitled-board\": \"Атаусыз тақта\",\n  \"WelcomePage.Description\": \"Тақталар дегеніміз танымал қанбан (kanban) тақта көрінісін қолданып, тимдер арасындағы жұмысты анықтауға, ұйымдастыруға, қадағалауға және басқаруға көмектесетін жобаны басқару құралы\",\n  \"WelcomePage.Explore.Button\": \"Зерттеу\",\n  \"WelcomePage.Heading\": \"Тақталарға Қош Келдініз\",\n  \"Workspace.editing-board-template\": \"Сіз тақта үлгісін өзгертудесіз.\",\n  \"calendar.month\": \"Ай\",\n  \"calendar.today\": \"БҮГІН\",\n  \"calendar.week\": \"Апта\",\n  \"default-properties.title\": \"Атау\",\n  \"login.log-in-button\": \"Кіру\",\n  \"login.log-in-title\": \"Кіру\",\n  \"login.register-button\": \"немесе сізде жоқ болса аққаунт жасаныз\",\n  \"register.login-button\": \"немесе аққаунтыныз болса кірініз\",\n  \"register.signup-title\": \"Аққаунт жасау үшін тіркелініз\"\n}\n"
  },
  {
    "path": "webapp/i18n/ko.json",
    "content": "{\n  \"AppBar.Tooltip\": \"링크된 보드로 이동\",\n  \"Attachment.Attachment-title\": \"첨부\",\n  \"AttachmentBlock.DeleteAction\": \"삭제\",\n  \"AttachmentBlock.addElement\": \"{type} 추가\",\n  \"AttachmentBlock.delete\": \"첨부 파일이 삭제되었습니다.\",\n  \"AttachmentBlock.failed\": \"첨부 파일 크기 제한을 초과하기 때문에 업로드할 수 없습니다.\",\n  \"AttachmentBlock.upload\": \"첨부 파일을 업로드 중입니다.\",\n  \"AttachmentBlock.uploadSuccess\": \"첨부 파일이 성공적으로 업로드 되었습니다.\",\n  \"AttachmentElement.delete-confirmation-dialog-button-text\": \"삭제\",\n  \"AttachmentElement.download\": \"다운로드\",\n  \"AttachmentElement.upload-percentage\": \"업로드 중...({uploadPercent}%)\",\n  \"BoardComponent.add-a-group\": \"+ 그룹 추가하기\",\n  \"BoardComponent.delete\": \"삭제하기\",\n  \"BoardComponent.hidden-columns\": \"숨겨진 열\",\n  \"BoardComponent.hide\": \"숨기기\",\n  \"BoardComponent.new\": \"+ 추가하기\",\n  \"BoardComponent.no-property\": \"{property} 속성 없음\",\n  \"BoardComponent.no-property-title\": \"{property} 속성이 빈 항목은 여기로 이동됩니다. 이 열은 제거할 수 없습니다.\",\n  \"BoardComponent.show\": \"보이기\",\n  \"BoardMember.schemeAdmin\": \"관리자\",\n  \"BoardMember.schemeCommenter\": \"댓글 작성자\",\n  \"BoardMember.schemeEditor\": \"편집자\",\n  \"BoardMember.schemeNone\": \"없음\",\n  \"BoardMember.schemeViewer\": \"열람자\",\n  \"BoardMember.unlinkChannel\": \"링크 해제\",\n  \"BoardPage.newVersion\": \"새 버전의 보드가 존재합니다, 여기를 눌러 다시 불러오세요.\",\n  \"BoardPage.syncFailed\": \"보드가 삭제되었거나 권한이 거부되었습니다.\",\n  \"BoardTemplateSelector.add-template\": \"새 템플릿 만들기\",\n  \"BoardTemplateSelector.create-empty-board\": \"빈 보드 만들기\",\n  \"BoardTemplateSelector.delete-template\": \"삭제\",\n  \"BoardTemplateSelector.description\": \"아래에 정의된 템플릿을 사용하여 사이드바에 보드를 추가하거나 처음부터 시작하십시오.\",\n  \"BoardTemplateSelector.edit-template\": \"편집\",\n  \"BoardTemplateSelector.plugin.no-content-description\": \"아래에 정의된 템플릿을 사용하여 사이드바에 보드를 추가하거나 처음부터 시작하십시오.\",\n  \"BoardTemplateSelector.plugin.no-content-title\": \"보드 만들기\",\n  \"BoardTemplateSelector.title\": \"보드 만들기\",\n  \"BoardTemplateSelector.use-this-template\": \"이 템플릿 사용하기\",\n  \"BoardsSwitcher.Title\": \"보드 찾기\",\n  \"BoardsUnfurl.Limited\": \"보관 중인 카드로 인해 추가 세부정보가 숨겨져 있습니다\",\n  \"BoardsUnfurl.Remainder\": \"+{remainder} 추가\",\n  \"BoardsUnfurl.Updated\": \"{time}에 수정됨\",\n  \"Calculations.Options.average.displayName\": \"평균\",\n  \"Calculations.Options.average.label\": \"평균\",\n  \"Calculations.Options.count.displayName\": \"개수\",\n  \"Calculations.Options.count.label\": \"개수\",\n  \"Calculations.Options.countChecked.displayName\": \"확인됨\",\n  \"Calculations.Options.countChecked.label\": \"확인된 수\",\n  \"Calculations.Options.countUnchecked.displayName\": \"확인되지 않음\",\n  \"Calculations.Options.countUnchecked.label\": \"확인되지 않은 개수\",\n  \"Calculations.Options.countUniqueValue.displayName\": \"고윳값\",\n  \"Calculations.Options.countUniqueValue.label\": \"고유 값 계산\",\n  \"Calculations.Options.countValue.displayName\": \"값\",\n  \"Calculations.Options.countValue.label\": \"계산 값\",\n  \"Calculations.Options.dateRange.displayName\": \"범위\",\n  \"Calculations.Options.dateRange.label\": \"범위\",\n  \"Calculations.Options.earliest.displayName\": \"이른 순으로\",\n  \"Calculations.Options.earliest.label\": \"이른 순으로\",\n  \"Calculations.Options.latest.displayName\": \"늦은 순으로\",\n  \"Calculations.Options.latest.label\": \"늦은 순으로\",\n  \"Calculations.Options.max.displayName\": \"최대\",\n  \"Calculations.Options.max.label\": \"최대\",\n  \"Calculations.Options.median.displayName\": \"중앙값\",\n  \"Calculations.Options.median.label\": \"중앙값\",\n  \"Calculations.Options.min.displayName\": \"최소\",\n  \"Calculations.Options.min.label\": \"최소\",\n  \"Calculations.Options.none.displayName\": \"계산하기\",\n  \"Calculations.Options.none.label\": \"없음\",\n  \"Calculations.Options.percentChecked.displayName\": \"확인됨\",\n  \"Calculations.Options.percentChecked.label\": \"선택된 비율\",\n  \"Calculations.Options.percentUnchecked.displayName\": \"확인되지 않음\",\n  \"Calculations.Options.percentUnchecked.label\": \"선택되지 않은 비율\",\n  \"Calculations.Options.range.displayName\": \"범위\",\n  \"Calculations.Options.range.label\": \"범위\",\n  \"Calculations.Options.sum.displayName\": \"더하기\",\n  \"Calculations.Options.sum.label\": \"더하기\",\n  \"CalendarCard.untitled\": \"제목 없음\",\n  \"CardActionsMenu.copiedLink\": \"복사!\",\n  \"CardActionsMenu.copyLink\": \"링크 복사하기\",\n  \"CardActionsMenu.delete\": \"삭제\",\n  \"CardActionsMenu.duplicate\": \"복제하기\",\n  \"CardBadges.title-checkboxes\": \"체크박스\",\n  \"CardBadges.title-comments\": \"댓글\",\n  \"CardBadges.title-description\": \"이 카드에는 설명이 있습니다\",\n  \"CardDetail.Attach\": \"첨부\",\n  \"CardDetail.Follow\": \"팔로우하기\",\n  \"CardDetail.Following\": \"팔로우 중\",\n  \"CardDetail.add-content\": \"콘텐츠 추가하기\",\n  \"CardDetail.add-icon\": \"아이콘 추가하기\",\n  \"CardDetail.add-property\": \"+ 속성 추가하기\",\n  \"CardDetail.addCardText\": \"카드 텍스트 추가하기\",\n  \"CardDetail.limited-body\": \"Professional 또는 Enterprise 플랜으로 업그레이드하여 보관된 카드를 보거나 보드당 무제한 보기, 카드 무제한 보기 등을 할 수 있습니다.\",\n  \"CardDetail.limited-button\": \"업그레이드\",\n  \"CardDetail.limited-title\": \"숨겨진 카드가 있습니다\",\n  \"CardDetail.moveContent\": \"카드 내용 이동하기\",\n  \"CardDetail.new-comment-placeholder\": \"댓글 추가하기...\",\n  \"CardDetailProperty.confirm-delete-heading\": \"속성 삭제 확인\",\n  \"CardDetailProperty.confirm-delete-subtext\": \"정말로 \\\"{propertyName}\\\"속성을 삭제할까요? 보드의 모든 카드에서 이 속성이 삭제됩니다.\",\n  \"CardDetailProperty.confirm-property-name-change-subtext\": \"정말로 \\\"{propertyName}\\\"속성을 {customText}로 바꾸시겠습니까? 이 보드에 있는 {numOfCards}개의 카드가 수정되며, 데이터가 손실될 수 있습니다.\",\n  \"CardDetailProperty.confirm-property-type-change\": \"속성 유형 변경 확인하기\",\n  \"CardDetailProperty.delete-action-button\": \"삭제하기\",\n  \"CardDetailProperty.property-change-action-button\": \"속성 변경하기\",\n  \"CardDetailProperty.property-changed\": \"성공적으로 속성이 변경되었습니다!\",\n  \"CardDetailProperty.property-deleted\": \"{propertyName}을(를) 성공적으로 삭제했습니다!\",\n  \"CardDetailProperty.property-name-change-subtext\": \"유형을 \\\"{oldPropType}\\\"에서 \\\"{newPropType}\\\"로\",\n  \"CardDetial.limited-link\": \"우리 계획에 대해 더 알아보기.\",\n  \"CardDialog.delete-confirmation-dialog-attachment\": \"첨부 파일 삭제 확인!\",\n  \"CardDialog.delete-confirmation-dialog-button-text\": \"삭제\",\n  \"CardDialog.delete-confirmation-dialog-heading\": \"카드 삭제 확인!\",\n  \"CardDialog.editing-template\": \"템플릿을 수정하는 중입니다.\",\n  \"CardDialog.nocard\": \"이 카드는 존재하지 않거나 사용할 수 없습니다.\",\n  \"Categories.CreateCategoryDialog.CancelText\": \"취소\",\n  \"Categories.CreateCategoryDialog.CreateText\": \"생성\",\n  \"Categories.CreateCategoryDialog.Placeholder\": \"카테고리 이름 지정\",\n  \"Categories.CreateCategoryDialog.UpdateText\": \"업데이트\",\n  \"CenterPanel.Login\": \"로그인\",\n  \"CenterPanel.Share\": \"공유\",\n  \"ColorOption.selectColor\": \"{color} 색 선택하기\",\n  \"Comment.delete\": \"삭제하기\",\n  \"CommentsList.send\": \"보내기\",\n  \"ConfirmationDialog.cancel-action\": \"취소하기\",\n  \"ConfirmationDialog.confirm-action\": \"결정하기\",\n  \"ContentBlock.Delete\": \"삭제하기\",\n  \"ContentBlock.DeleteAction\": \"삭제하기\",\n  \"ContentBlock.addElement\": \"{type} 추가하기\",\n  \"ContentBlock.checkbox\": \"체크박스\",\n  \"ContentBlock.divider\": \"구분선\",\n  \"ContentBlock.editCardCheckbox\": \"토글 체크박스\",\n  \"ContentBlock.editCardCheckboxText\": \"카드 텍스트 수정하기\",\n  \"ContentBlock.editCardText\": \"카드 텍스트 수정하기\",\n  \"ContentBlock.editText\": \"텍스트 수정하기...\",\n  \"ContentBlock.image\": \"이미지\",\n  \"ContentBlock.insertAbove\": \"위에 삽입하기\",\n  \"ContentBlock.moveBlock\": \"카드 내용 이동\",\n  \"ContentBlock.moveDown\": \"아래로 이동하기\",\n  \"ContentBlock.moveUp\": \"위로 이동하기\",\n  \"ContentBlock.text\": \"텍스트\",\n  \"DateRange.clear\": \"지우기\",\n  \"DateRange.empty\": \"비어 있음\",\n  \"DateRange.endDate\": \"종료일자\",\n  \"DateRange.today\": \"오늘\",\n  \"DeleteBoardDialog.confirm-cancel\": \"취소\",\n  \"DeleteBoardDialog.confirm-delete\": \"삭제\",\n  \"DeleteBoardDialog.confirm-info\": \"“{boardTitle}” 보드를 삭제하시겠습니까? 이 보드에 있는 모든 카드들이 삭제됩니다.\",\n  \"DeleteBoardDialog.confirm-info-template\": \"{boardTitle} 보드 템플릿을 삭제 하시겠습니까?\",\n  \"DeleteBoardDialog.confirm-tite\": \"보드 삭제 확인하기\",\n  \"DeleteBoardDialog.confirm-tite-template\": \"보드 템플릿 삭제 확인하기\",\n  \"Dialog.closeDialog\": \"대화창 닫기\",\n  \"EditableDayPicker.today\": \"오늘\",\n  \"Error.mobileweb\": \"모바일 웹 지원은 현재 초기 베타 버전입니다. 모든 기능이 있는 것은 아닙니다.\",\n  \"Error.websocket-closed\": \"웹소켓 연결이 닫혀서 연결이 중단되었습니다. 이 문제가 지속되면, 서버 또는 웹 프록시 구성을 확인하세요.\",\n  \"Filter.contains\": \"필터를 포함하다\",\n  \"Filter.ends-with\": \"~로 끝나다\",\n  \"Filter.includes\": \"~를 포함한다\",\n  \"Filter.is\": \"~이다\",\n  \"Filter.is-empty\": \"비어있음\",\n  \"Filter.is-not-empty\": \"비어 있지 않음\",\n  \"Filter.is-not-set\": \"미설정\",\n  \"Filter.is-set\": \"설정\",\n  \"Filter.not-contains\": \"~를 포함하지 않음\",\n  \"Filter.not-ends-with\": \"~로 끝나지 않음\",\n  \"Filter.not-includes\": \"~를 포함하지 않음\",\n  \"Filter.not-starts-with\": \"~로 시작하지 않음\",\n  \"Filter.starts-with\": \"~로 시작함\",\n  \"FilterByText.placeholder\": \"필터값\",\n  \"FilterComponent.add-filter\": \"+ 필터 추가\",\n  \"FilterComponent.delete\": \"삭제\",\n  \"FindBoardsDialog.IntroText\": \"보드에서 검색\",\n  \"FindBoardsDialog.NoResultsFor\": \"\\\"{searchQuery}\\\" 에 대한 검색 결과가 없습니다\",\n  \"FindBoardsDialog.NoResultsSubtext\": \"글자를 확인 하시거나 다른 단어로 검색해 주세요.\",\n  \"FindBoardsDialog.SubTitle\": \"보드를 찾으려면 입력하십시오. <b>UP/DOWN</b> 버튼을 이용해서 보드를 찾아주세요. 선택은 <b>ENTER</b> , 해제는 <b>ESC</b>\",\n  \"FindBoardsDialog.Title\": \"보드 찾기\",\n  \"GroupBy.hideEmptyGroups\": \"{count} 그룹 숨기기\",\n  \"GroupBy.showHiddenGroups\": \"{count} 숨김 그룹 보기\",\n  \"GroupBy.ungroup\": \"그룹 해제\",\n  \"HideBoard.MenuOption\": \"보드 숨기기\",\n  \"KanbanCard.untitled\": \"제목 없음\",\n  \"MentionSuggestion.is-not-board-member\": \"(보드 멤버가 아님)\",\n  \"Mutator.new-board-from-template\": \"템플릿에서 신규 보드 만들기\",\n  \"Mutator.new-card-from-template\": \"템플릿에서 신규 카드 만들기\",\n  \"Mutator.new-template-from-card\": \"카드에서 신규 템플릿 만들기\",\n  \"OnboardingTour.AddComments.Body\": \"@mention 을 이용하여 이슈에 대해 메터머스트 사용자에게 알릴 수 있습니다.\",\n  \"OnboardingTour.AddComments.Title\": \"댓글 작성\",\n  \"OnboardingTour.AddDescription.Body\": \"팀원들이 카드의 내용을 알 수 있도록 카드에 설명을 추가합니다.\",\n  \"OnboardingTour.AddDescription.Title\": \"설명 추가\",\n  \"OnboardingTour.AddProperties.Body\": \"카드에 다양한 속성을 추가하여 더욱 강력해 질 수 있습니다!\",\n  \"OnboardingTour.AddProperties.Title\": \"속성 추가\",\n  \"OnboardingTour.AddView.Body\": \"다른 레이아웃을 사용하여 보드를 구성할 새 보기를 작성하려면 여기로 이동하십시오.\",\n  \"OnboardingTour.AddView.Title\": \"뷰 추가\",\n  \"OnboardingTour.CopyLink.Body\": \"링크를 복사하여 채널, 다이렉트 메시지 또는 그룹 메시지에 붙여넣어 팀원들과 카드를 공유할 수 있습니다.\",\n  \"OnboardingTour.CopyLink.Title\": \"링크 복사\",\n  \"OnboardingTour.OpenACard.Body\": \"카드를 열어 보드가 작업을 구성하는 데 도움이 될 수 있는 강력한 방법을 알아보십시오.\",\n  \"OnboardingTour.OpenACard.Title\": \"카드 열기\",\n  \"OnboardingTour.ShareBoard.Body\": \"보드를 팀 내에서 내부적으로 공유하거나 조직 외부에서 볼 수 있도록 공개적으로 게시할 수 있습니다.\",\n  \"OnboardingTour.ShareBoard.Title\": \"보드 공유\",\n  \"PersonProperty.board-members\": \"보드 멤버\",\n  \"PersonProperty.non-board-members\": \"보드 멤버가 아님\",\n  \"PropertyMenu.Delete\": \"삭제\",\n  \"PropertyMenu.changeType\": \"속성 유형 변경\",\n  \"PropertyMenu.selectType\": \"속성 유형 선택\",\n  \"PropertyMenu.typeTitle\": \"유형\",\n  \"PropertyType.Checkbox\": \"체크박스\",\n  \"PropertyType.CreatedBy\": \"만든 사람\",\n  \"PropertyType.CreatedTime\": \"생성 시간\",\n  \"PropertyType.Date\": \"날짜\",\n  \"PropertyType.Email\": \"전자우편\",\n  \"PropertyType.MultiPerson\": \"다중 사용자\",\n  \"PropertyType.MultiSelect\": \"다중 선택하기\",\n  \"PropertyType.Number\": \"숫자\",\n  \"PropertyType.Person\": \"사람\",\n  \"PropertyType.Phone\": \"전화번호\",\n  \"PropertyType.Select\": \"선택\",\n  \"PropertyType.Text\": \"텍스트\",\n  \"PropertyType.Unknown\": \"알 수 없는 유형\",\n  \"PropertyType.UpdatedBy\": \"최근 수정한 사람\",\n  \"PropertyType.UpdatedTime\": \"최근 수정 시간\",\n  \"PropertyType.Url\": \"URL 주소\",\n  \"PropertyValueElement.empty\": \"비어있음\",\n  \"RegistrationLink.confirmRegenerateToken\": \"이전에 공유된 링크가 무효화됩니다. 계속하시겠습니까?\",\n  \"RegistrationLink.copiedLink\": \"복사되었습니다!\",\n  \"RegistrationLink.copyLink\": \"링크 복사\",\n  \"RegistrationLink.description\": \"다른 구성원이 계정을 만들 수 있도록 이 링크를 공유하세요:\",\n  \"RegistrationLink.regenerateToken\": \"토큰 재성성\",\n  \"RegistrationLink.tokenRegenerated\": \"등록 링크가 재생성되었음\",\n  \"ShareBoard.PublishDescription\": \"웹 상의 모든 사용자와 읽기 전용 링크를 게시하고 공유합니다.\",\n  \"ShareBoard.PublishTitle\": \"웹에 게시하다\",\n  \"ShareBoard.ShareInternal\": \"내부공유하기\",\n  \"ShareBoard.ShareInternalDescription\": \"권한이 있는 사용자는 이 링크를 사용할 수 있습니다.\",\n  \"ShareBoard.Title\": \"보드 공유\",\n  \"ShareBoard.confirmRegenerateToken\": \"이전에 공유된 링크가 무효화됩니다. 계속하시겠습니까?\",\n  \"ShareBoard.copiedLink\": \"복사되었습니다!\",\n  \"ShareBoard.copyLink\": \"링크 복사\",\n  \"ShareBoard.regenerate\": \"토큰 재생성하기\",\n  \"ShareBoard.searchPlaceholder\": \"사용자 및 채널 검색\",\n  \"ShareBoard.teamPermissionsText\": \"{teamName}팀의 모든 사용자\",\n  \"ShareBoard.tokenRegenrated\": \"토큰이 재성생되었음\",\n  \"ShareBoard.userPermissionsRemoveMemberText\": \"멤버 제외하기\",\n  \"ShareBoard.userPermissionsYouText\": \"당신\",\n  \"ShareTemplate.Title\": \"템플릿 공유\",\n  \"ShareTemplate.searchPlaceholder\": \"사용자 검색\",\n  \"Sidebar.about\": \"Focalboard에 대하여\",\n  \"Sidebar.add-board\": \"+ 보드 추가\",\n  \"Sidebar.changePassword\": \"패스워드 변경\",\n  \"Sidebar.delete-board\": \"보드 삭제\",\n  \"Sidebar.duplicate-board\": \"보드 복제\",\n  \"Sidebar.export-archive\": \"아카이브 내보내기\",\n  \"Sidebar.import\": \"사이드바 가져요기\",\n  \"Sidebar.import-archive\": \"아카이브 들여오기\",\n  \"Sidebar.invite-users\": \"사용자 초대\",\n  \"Sidebar.logout\": \"로그아웃\",\n  \"Sidebar.new-category.badge\": \"신규\",\n  \"Sidebar.new-category.drag-boards-cta\": \"보드를 여기에 드래그하세요...\",\n  \"Sidebar.no-boards-in-category\": \"해당 카테고리에 보드가 존재하지 않음\",\n  \"Sidebar.product-tour\": \"상품 둘러보기\",\n  \"Sidebar.random-icons\": \"임의 아이콘\",\n  \"Sidebar.set-language\": \"언어 설정\",\n  \"Sidebar.set-theme\": \"테마 설정\",\n  \"Sidebar.settings\": \"설정\",\n  \"Sidebar.template-from-board\": \"보드의 새 템플릿 추가\",\n  \"Sidebar.untitled-board\": \"(제목 없는 보드)\",\n  \"Sidebar.untitled-view\": \"(제목 없는 뷰)\",\n  \"SidebarCategories.BlocksMenu.Move\": \"이동 ...\",\n  \"SidebarCategories.CategoryMenu.CreateNew\": \"새 카테고리 만들기\",\n  \"SidebarCategories.CategoryMenu.Delete\": \"카테고리 삭제하기\",\n  \"SidebarCategories.CategoryMenu.DeleteModal.Body\": \"<b>{categoryName}</b>의 다시 보드 카테고리로 이동합니다. 당신은 어떤 보드에서도 제거되지 않았습니다.\",\n  \"SidebarCategories.CategoryMenu.DeleteModal.Title\": \"해당 카테고리를 삭제하시겠습니까?\",\n  \"SidebarCategories.CategoryMenu.Update\": \"카테고리 이름 수정하기\",\n  \"SidebarTour.ManageCategories.Body\": \"사용자 정의 카테고리를 만들고 관리합니다. 카테고리는 사용자별로 다르므로 보드를 사용자의 카레고리로 이동해도 동일한 보드를 사용하는 다른 구성원에게는 영향을 주지 않습니다.\",\n  \"SidebarTour.ManageCategories.Title\": \"카테고리 관리하기\",\n  \"SidebarTour.SearchForBoards.Body\": \"(Cmd/Ctrl + K)를 열어 보드를 빠르게 검색하고 사이드바에 추가합니다.\",\n  \"SidebarTour.SearchForBoards.Title\": \"보드 검색하기\",\n  \"SidebarTour.SidebarCategories.Body\": \"이제 모든 보드가 새 사이드바 아래에 정리됩니다. 워크스페이스 간 전환은 더 이상 필요 없습니다. v7.2 업그레이드의 일부로 이전 작업 공간을 기반으로 한 일회성 사용자 지정 카테고리가 자동으로 생성될 수 있습니다. 해당 기능을 통해 원하는 대로 카테고리를 제거하거나 편집할 수 있습니다.\",\n  \"SidebarTour.SidebarCategories.Link\": \"더 배우기\",\n  \"SidebarTour.SidebarCategories.Title\": \"사이드바 카테고리\",\n  \"SiteStats.total_boards\": \"모든 보드\",\n  \"SiteStats.total_cards\": \"모든 카드\",\n  \"TableComponent.add-icon\": \"아이콘 추가\",\n  \"TableComponent.name\": \"이름\",\n  \"TableComponent.plus-new\": \"+ 생성\",\n  \"TableHeaderMenu.delete\": \"삭제\",\n  \"TableHeaderMenu.duplicate\": \"복제\",\n  \"TableHeaderMenu.hide\": \"숨김\",\n  \"TableHeaderMenu.insert-left\": \"왼쪽에 삽입\",\n  \"TableHeaderMenu.insert-right\": \"오른쪽에 삽입\",\n  \"TableHeaderMenu.sort-ascending\": \"오름차순 정렬\",\n  \"TableHeaderMenu.sort-descending\": \"내림차순 정렬\",\n  \"TableRow.MoreOption\": \"더 많은 행동\",\n  \"TableRow.open\": \"열기\",\n  \"TopBar.give-feedback\": \"피드백 하기\",\n  \"URLProperty.copiedLink\": \"복사되었습니다!\",\n  \"URLProperty.copy\": \"복사\",\n  \"URLProperty.edit\": \"수정\",\n  \"UndoRedoHotKeys.canRedo\": \"다시 실행하기\",\n  \"UndoRedoHotKeys.canRedo-with-description\": \"{description} 다시 실행하기\",\n  \"UndoRedoHotKeys.canUndo\": \"실행 취소하기\",\n  \"UndoRedoHotKeys.canUndo-with-description\": \"{description} 실행 취소하기\",\n  \"UndoRedoHotKeys.cannotRedo\": \"다시 실행하지 않기\",\n  \"UndoRedoHotKeys.cannotUndo\": \"실행취소 하지 않기\",\n  \"ValueSelector.noOptions\": \"옵션이 없습니다. 새로 추가하려면 입력을 시작하세요!\",\n  \"ValueSelector.valueSelector\": \"값 선택\",\n  \"ValueSelectorLabel.openMenu\": \"메뉴 열기\",\n  \"VersionMessage.help\": \"이 버전의 새로운 기능을 확인하십시오.\",\n  \"View.AddView\": \"뷰 추가\",\n  \"View.Board\": \"보드\",\n  \"View.DeleteView\": \"뷰 삭제\",\n  \"View.DuplicateView\": \"뷰 복제\",\n  \"View.Gallery\": \"갤러리\",\n  \"View.NewBoardTitle\": \"보드 형태로 보기\",\n  \"View.NewCalendarTitle\": \"달력 형태로 보기\",\n  \"View.NewGalleryTitle\": \"갤리리 형태로 보기\",\n  \"View.NewTableTitle\": \"표 형태로 보기\",\n  \"View.NewTemplateDefaultTitle\": \"무제 템플릿\",\n  \"View.NewTemplateTitle\": \"제목 없음\",\n  \"View.Table\": \"표\",\n  \"ViewHeader.add-template\": \"새 템플릿\",\n  \"ViewHeader.delete-template\": \"삭제\",\n  \"ViewHeader.display-by\": \"표시 대상: {property}\",\n  \"ViewHeader.edit-template\": \"수정\",\n  \"ViewHeader.empty-card\": \"빈 카드\",\n  \"ViewHeader.export-board-archive\": \"보드 아카이브 내보내기\",\n  \"ViewHeader.export-complete\": \"내보내기가 완료되었습니다!\",\n  \"ViewHeader.export-csv\": \"CSV로 내보내기\",\n  \"ViewHeader.export-failed\": \"내보내기가 실패했습니다!\",\n  \"ViewHeader.filter\": \"필터\",\n  \"ViewHeader.group-by\": \"{property}로 그룹화\",\n  \"ViewHeader.new\": \"생성\",\n  \"ViewHeader.properties\": \"속성\",\n  \"ViewHeader.properties-menu\": \"속성 메뉴\",\n  \"ViewHeader.search-text\": \"카드 검색하기\",\n  \"ViewHeader.select-a-template\": \"템플릿 선택\",\n  \"ViewHeader.set-default-template\": \"기본으로 설정\",\n  \"ViewHeader.sort\": \"정렬\",\n  \"ViewHeader.untitled\": \"제목 없음\",\n  \"ViewHeader.view-header-menu\": \"머리글 메뉴 보기\",\n  \"ViewHeader.view-menu\": \"뷰 메뉴\",\n  \"ViewLimitDialog.Heading\": \"보드 당 조회 수 제한에 도달했습니다\",\n  \"ViewLimitDialog.PrimaryButton.Title.Admin\": \"업그레이드\",\n  \"ViewLimitDialog.PrimaryButton.Title.RegularUser\": \"관리자에게 알리기\",\n  \"ViewLimitDialog.Subtext.Admin\": \"Professional 또는 Enterprise 플랜으로 업그레이드하여 보드당 무제한 보기, 카드 무제한 등을 이용할 수 있습니다.\",\n  \"ViewLimitDialog.Subtext.Admin.PricingPageLink\": \"우리의 계획에 대해 더 알아보기.\",\n  \"ViewLimitDialog.Subtext.RegularUser\": \"관리자에게 통지하여 프로페셔널 또는 엔터프라이즈 플랜으로 업그레이드하여 보드당 무제한 보기, 카드 무제한 등을 사용할 수 있습니다.\",\n  \"ViewLimitDialog.UpgradeImg.AltText\": \"이미지 업그레이드\",\n  \"ViewLimitDialog.notifyAdmin.Success\": \"관리자에게 알림이 왔습니다\",\n  \"ViewTitle.hide-description\": \"설명 숨기기\",\n  \"ViewTitle.pick-icon\": \"아이콘 선택\",\n  \"ViewTitle.random-icon\": \"임의\",\n  \"ViewTitle.remove-icon\": \"아이콘 제거\",\n  \"ViewTitle.show-description\": \"설명 보기\",\n  \"ViewTitle.untitled-board\": \"제목 없는 보드\",\n  \"WelcomePage.Description\": \"보드는 친숙한 칸반 보드 형태를 사용하여 팀간의 업무를 정의, 구성 및 추적하고 관리하는 프로젝트 관리 도구입니다.\",\n  \"WelcomePage.Explore.Button\": \"탐색하기\",\n  \"WelcomePage.Heading\": \"보드에 오신 것을 환영합니다\",\n  \"WelcomePage.NoThanks.Text\": \"아뇨, 제가 알아서 해결하겠습니다\",\n  \"WelcomePage.StartUsingIt.Text\": \"사용 시작하기\",\n  \"Workspace.editing-board-template\": \"보드 템플릿을 수정하는 중입니다.\",\n  \"badge.guest\": \"게스트\",\n  \"boardSelector.confirm-link-board\": \"채널에 보드 연결하기\",\n  \"boardSelector.confirm-link-board-button\": \"예. 보드 연결하기\",\n  \"boardSelector.confirm-link-board-subtext\": \"{boardName}을(를) 채널에 연결하면 게스트를 제외한 채널의 모든 구성원(기존 및 새)이 해당 채널을 편집할 수 있습니다. 언제든지 채널에서 보드의 연결을 해제할 수 있습니다.\",\n  \"boardSelector.confirm-link-board-subtext-with-other-channel\": \"{boardName}을(를) 채널에 연결하면 게스트를 제외한 채널의 모든 구성원(기존 및 새)이 해당 채널을 편집할 수 있습니다.{lineBreak} 이 보드는 현재 다른 채널에 연결되어 있습니다. 여기에 연결을 선택하면 연결이 해제됩니다.\",\n  \"boardSelector.create-a-board\": \"보드 만들기\",\n  \"boardSelector.link\": \"연결하기\",\n  \"boardSelector.search-for-boards\": \"보드 검색하기\",\n  \"boardSelector.title\": \"보드 연결하기\",\n  \"boardSelector.unlink\": \"연결 해제하기\",\n  \"calendar.month\": \"월\",\n  \"calendar.today\": \"오늘\",\n  \"calendar.week\": \"주\",\n  \"cloudMessage.learn-more\": \"더 배우기\",\n  \"createImageBlock.failed\": \"파일을 업로드할 수 없습니다. 파일 크기 제한에 도달했습니다.\",\n  \"default-properties.badges\": \"댓글 및 설명\",\n  \"default-properties.title\": \"제목\",\n  \"error.back-to-home\": \"홈으로 돌아가기\",\n  \"error.back-to-team\": \"팀으로 돌아가기\",\n  \"error.board-not-found\": \"보드를 찾을 수 없습니다.\",\n  \"error.go-login\": \"로그인\",\n  \"error.invalid-read-only-board\": \"이 보드에 액세스할 수 없습니다. 보드에 액세스하려면 로그인하십시오.\",\n  \"error.not-logged-in\": \"세션이 만료되었거나 로그인하지 않았을 수 있습니다. 보드에 액세스하려면 다시 로그인하십시오.\",\n  \"error.page.title\": \"죄송해요, 뭔가 잘못되었어요\",\n  \"error.team-undefined\": \"유효한 팀이 아닙니다.\",\n  \"error.unknown\": \"오류가 발생했습니다.\",\n  \"generic.previous\": \"이전\",\n  \"guest-no-board.subtitle\": \"당신은 이 팀의 어느 보드에도 접속하고 있지 않습니다. 누군가 당신을 보드에 추가해 줄 때까지 기다려주세요.\",\n  \"guest-no-board.title\": \"보드에 참여중이 아님\",\n  \"imagePaste.upload-failed\": \"일부 파일이 업로드 되지 않았습니다. 파일 크기 제한에 도달했습니다\",\n  \"limitedCard.title\": \"숨겨진 카드\",\n  \"login.log-in-button\": \"로그인\",\n  \"login.log-in-title\": \"로그인\",\n  \"login.register-button\": \"계정이 없다면 계정을 만드세요\",\n  \"notification-box-card-limit-reached.close-tooltip\": \"10일 동안 잠자기\",\n  \"notification-box-card-limit-reached.contact-link\": \"관리자에게 알리기\",\n  \"notification-box-card-limit-reached.link\": \"유료 요금제로 업그레이드하기\",\n  \"notification-box-card-limit-reached.title\": \"{cards}개의 숨겨진 카드가 보드에 있습니다\",\n  \"notification-box-cards-hidden.title\": \"이 작업으로 인해 다른 카드가 숨겨졌습니다\",\n  \"notification-box.card-limit-reached.not-admin.text\": \"아카이브된 카드에 액세스하려면 {contactLink}을(를) 사용하여 유료 요금제로 업그레이드하십시오.\",\n  \"notification-box.card-limit-reached.text\": \"카드 제한에 도달했습니다,이전 카드를 보려면 {link}를 눌러주세요\",\n  \"person.add-user-to-board\": \"{username}을 보드에 추가\",\n  \"person.add-user-to-board-confirm-button\": \"보드에 추가\",\n  \"person.add-user-to-board-permissions\": \"권한\",\n  \"person.add-user-to-board-question\": \"{username}을 보드에 추가하시겠습니까?\",\n  \"person.add-user-to-board-warning\": \"{username}은 보드에 참여중이지 않으며, 관련될 알림을 수신하지 않습니다.\",\n  \"register.login-button\": \"이미 계정이 있다면 로그인하세요\",\n  \"register.signup-title\": \"계정 등록\",\n  \"rhs-board-non-admin-msg\": \"당신은 보드의 관리자가 아닙니다\",\n  \"rhs-boards.add\": \"추가하기\",\n  \"rhs-boards.dm\": \"다이렉트 메시지\",\n  \"rhs-boards.gm\": \"그룹 메시지\",\n  \"rhs-boards.header.dm\": \"이 개인 메시지\",\n  \"rhs-boards.header.gm\": \"이 그룹 메시지\",\n  \"rhs-boards.last-update-at\": \"마지막 업데이트 시간: {datetime}\",\n  \"rhs-boards.link-boards-to-channel\": \"{channelName}에 보드 연결하기\",\n  \"rhs-boards.linked-boards\": \"연결된 보드\",\n  \"rhs-boards.no-boards-linked-to-channel\": \"{channelName}에 아직 연결된 보드가 없음\",\n  \"rhs-boards.no-boards-linked-to-channel-description\": \"Boards는 익숙한 Kanban 보드 뷰를 사용하여 팀 전체의 작업을 정의, 구성, 추적 및 관리하는 데 도움이 되는 프로젝트 관리 도구입니다.\",\n  \"rhs-boards.unlink-board\": \"보드 연결해제하기\",\n  \"rhs-boards.unlink-board1\": \"연결되지 않은 보드\",\n  \"rhs-channel-boards-header.title\": \"보드\",\n  \"share-board.publish\": \"게재하기\",\n  \"share-board.share\": \"공유하기\",\n  \"shareBoard.channels-select-group\": \"채널\",\n  \"shareBoard.confirm-change-team-role.body\": \"이 보드 사용자 중 {role}보다 낮은 권한을 가진 사용자들은 <b>{role}권한으로 승급합니다.</b> 이 보드의 최소 권한을 변경하시겠습니까?\",\n  \"shareBoard.confirm-change-team-role.confirmBtnText\": \"최소 권한 수정\",\n  \"shareBoard.confirm-change-team-role.title\": \"최소 권한 수정\",\n  \"shareBoard.confirm-link-channel\": \"채널에 보드 연결하기\",\n  \"shareBoard.confirm-link-channel-button\": \"채널 연결하기\",\n  \"shareBoard.confirm-link-channel-button-with-other-channel\": \"연결 및 연결해제하기\",\n  \"shareBoard.confirm-link-channel-subtext\": \"채널을 보드에 연결하면 게스트를 제외한 채널의 모든 구성원(기존 및 신규)이 해당 채널을 편집할 수 있습니다.\",\n  \"shareBoard.confirm-link-channel-subtext-with-other-channel\": \"채널을 보드에 연결하면 게스트를 제외한 채널의 모든 구성원(기존 및 신규)이 해당 채널을 편집할 수 있습니다.{lineBreak}이 보드는 현재 다른 채널에 연결되어 있습니다. 여기에 연결을 선택하면 연결이 해제됩니다.\",\n  \"shareBoard.confirm-unlink.body\": \"보드에서 채널 연결을 해제하면 채널의 모든 구성원(기존 및 새)이 개별적으로 권한이 부여되지 않는 한 해당 채널에 대한 액세스 권한을 잃게 됩니다.\",\n  \"shareBoard.confirm-unlink.confirmBtnText\": \"채널 연결 해제하기\",\n  \"shareBoard.confirm-unlink.title\": \"보드에서 채널 연결 해제하기\",\n  \"shareBoard.lastAdmin\": \"보드에는 최소한 한명 이상의 관리자 있어야 합니다\",\n  \"shareBoard.members-select-group\": \"멤버\",\n  \"shareBoard.unknown-channel-display-name\": \"알수없는 채널\",\n  \"tutorial_tip.finish_tour\": \"완료\",\n  \"tutorial_tip.got_it\": \"알겠습니다\",\n  \"tutorial_tip.ok\": \"다음\",\n  \"tutorial_tip.out\": \"이 도움말을 선택 해제합니다.\",\n  \"tutorial_tip.seen\": \"전에 본적 있나요?\"\n}\n"
  },
  {
    "path": "webapp/i18n/lt.json",
    "content": "{}\n"
  },
  {
    "path": "webapp/i18n/ml.json",
    "content": "{\n  \"BoardComponent.add-a-group\": \"+ ഒരു ഗ്രൂപ്പ് ചേർക്കുക\",\n  \"BoardComponent.delete\": \"നീക്കം ചെയ്യുക\",\n  \"BoardComponent.hidden-columns\": \"മറഞ്ഞിരിക്കുന്ന നിരകൾ\",\n  \"BoardComponent.hide\": \"മറയ്ക്കുക\",\n  \"BoardComponent.new\": \"+ പുതിയത്\",\n  \"BoardComponent.no-property\": \"ഇല്ല {property}\",\n  \"BoardComponent.no-property-title\": \"ശൂന്യമായ {property} പ്രോപ്പർട്ടി ഉള്ള ഇനങ്ങൾ ഇവിടെ പോകും. ഈ കോളം നീക്കം ചെയ്യാൻ കഴിയില്ല.\",\n  \"BoardComponent.show\": \"കാണിക്കുക\",\n  \"BoardMember.schemeAdmin\": \"അഡ്മിൻ\",\n  \"BoardMember.schemeCommenter\": \"കമന്റേറ്റർ\",\n  \"BoardMember.schemeEditor\": \"എഡിറ്റർ\",\n  \"BoardMember.schemeNone\": \"ഒന്നുമില്ല\",\n  \"BoardMember.schemeViewer\": \"കാഴ്ചക്കാരൻ\",\n  \"BoardPage.newVersion\": \"ബോർഡുകളുടെ ഒരു പുതിയ പതിപ്പ് ലഭ്യമാണ്, റീലോഡ് ചെയ്യാൻ ഇവിടെ ക്ലിക്ക് ചെയ്യുക.\",\n  \"BoardPage.syncFailed\": \"ബോർഡ് ഇല്ലാതാക്കുകയോ ആക്സസ് റദ്ദാക്കുകയോ ചെയ്യാം.\",\n  \"BoardTemplateSelector.add-template\": \"പുതിയ ടെംപ്ലേറ്റ്\",\n  \"BoardTemplateSelector.create-empty-board\": \"ശൂന്യമായ ബോർഡ് സൃഷ്ടിക്കുക\",\n  \"BoardTemplateSelector.delete-template\": \"ഇല്ലാതാക്കുക\",\n  \"BoardTemplateSelector.description\": \"ആരംഭിക്കാൻ നിങ്ങളെ സഹായിക്കുന്നതിന് ഒരു ടെംപ്ലേറ്റ് തിരഞ്ഞെടുക്കുക. നിങ്ങളുടെ ആവശ്യങ്ങൾക്ക് അനുയോജ്യമായ രീതിയിൽ ടെംപ്ലേറ്റ് എളുപ്പത്തിൽ ഇച്ഛാനുസൃതമാക്കുക, അല്ലെങ്കിൽ ആദ്യം മുതൽ ആരംഭിക്കാൻ ഒരു ശൂന്യമായ ബോർഡ് സൃഷ്ടിക്കുക.\",\n  \"BoardTemplateSelector.edit-template\": \"എഡിറ്റ് ചെയ്യുക\",\n  \"BoardTemplateSelector.plugin.no-content-description\": \"ചുവടെ നിർവചിച്ചിരിക്കുന്ന ഏതെങ്കിലും ടെംപ്ലേറ്റുകൾ ഉപയോഗിച്ച് സൈഡ്ബാറിലേക്ക് ഒരു ബോർഡ് ചേർക്കുക അല്ലെങ്കിൽ ആദ്യം മുതൽ ആരംഭിക്കുക.\",\n  \"BoardTemplateSelector.plugin.no-content-title\": \"ഒരു ബോർഡ് ഉണ്ടാക്കുക\",\n  \"BoardTemplateSelector.title\": \"ഒരു ബോർഡ് ഉണ്ടാക്കുക\",\n  \"BoardTemplateSelector.use-this-template\": \"ഈ ടെംപ്ലേറ്റ് ഉപയോഗിക്കുക\",\n  \"BoardsSwitcher.Title\": \"ബോർഡുകൾ കണ്ടെത്തുക\",\n  \"BoardsUnfurl.Remainder\": \"+{remainder} കൂടുതൽ\",\n  \"BoardsUnfurl.Updated\": \"പുതുക്കിയത് {time}\",\n  \"Calculations.Options.average.displayName\": \"ശരാശരി\",\n  \"Calculations.Options.average.label\": \"ശരാശരി\",\n  \"Calculations.Options.count.displayName\": \"എണ്ണുക\",\n  \"Calculations.Options.count.label\": \"എണ്ണുക\",\n  \"Calculations.Options.countChecked.displayName\": \"പരിശോധിച്ചു\",\n  \"Calculations.Options.countChecked.label\": \"എണ്ണം പരിശോധിച്ചു\",\n  \"Calculations.Options.countUnchecked.displayName\": \"പരിശോധിക്കാത്തത്\",\n  \"Calculations.Options.countUnchecked.label\": \"പരിശോധിക്കാത്തതിന്റെ എണ്ണം\",\n  \"Calculations.Options.countUniqueValue.displayName\": \"അതുല്യമായ\",\n  \"Calculations.Options.countUniqueValue.label\": \"അദ്വിതീയ മൂല്യങ്ങൾ എണ്ണുക\",\n  \"Calculations.Options.countValue.displayName\": \"മൂല്യങ്ങൾ\",\n  \"Calculations.Options.countValue.label\": \"മൂല്യം എണ്ണുക\",\n  \"Calculations.Options.dateRange.displayName\": \"പരിധി\",\n  \"Calculations.Options.dateRange.label\": \"പരിധി\",\n  \"Calculations.Options.earliest.displayName\": \"നേരത്തെ\",\n  \"Calculations.Options.earliest.label\": \"നേരത്തെ\",\n  \"Calculations.Options.latest.displayName\": \"ഏറ്റവും പുതിയ\",\n  \"Calculations.Options.latest.label\": \"ഏറ്റവും പുതിയ\",\n  \"Calculations.Options.max.displayName\": \"പരമാവധി\",\n  \"Calculations.Options.max.label\": \"പരമാവധി\",\n  \"Calculations.Options.median.displayName\": \"മധ്യമം\",\n  \"Calculations.Options.median.label\": \"മധ്യമം\",\n  \"Calculations.Options.min.displayName\": \"കുറവ്\",\n  \"Calculations.Options.min.label\": \"കുറവ്\",\n  \"Calculations.Options.none.displayName\": \"കണക്കാക്കുക\",\n  \"Calculations.Options.none.label\": \"ഒന്നുമില്ല\",\n  \"Calculations.Options.percentChecked.displayName\": \"പരിശോധിച്ചു\",\n  \"Calculations.Options.percentChecked.label\": \"ശതമാനം പരിശോധിച്ചു\",\n  \"Calculations.Options.percentUnchecked.displayName\": \"പരിശോധിക്കാത്തത്\",\n  \"Calculations.Options.percentUnchecked.label\": \"ശതമാനം പരിശോധിക്കാത്തത്\",\n  \"Calculations.Options.range.displayName\": \"പരിധി\",\n  \"Calculations.Options.range.label\": \"പരിധി\",\n  \"Calculations.Options.sum.displayName\": \"തുക\",\n  \"Calculations.Options.sum.label\": \"തുക\",\n  \"CardActionsMenu.copiedLink\": \"പകർത്തി!\",\n  \"CardActionsMenu.copyLink\": \"ലിങ്ക് പകർത്തുക\",\n  \"CardActionsMenu.delete\": \"ഡിലീറ്റ് ചെയ്യുക\",\n  \"CardActionsMenu.duplicate\": \"പകർപ്പ്\",\n  \"CardBadges.title-checkboxes\": \"ചെക്ക്ബോക്സുകൾ\",\n  \"CardBadges.title-comments\": \"അഭിപ്രായങ്ങൾ\",\n  \"CardBadges.title-description\": \"ഈ കാർഡിന് ഒരു വിവരണമുണ്ട്\",\n  \"CardDetail.Follow\": \"പിന്തുടരുക\",\n  \"CardDetail.Following\": \"പിന്തുടരുന്നു\",\n  \"CardDetail.add-content\": \"ഉള്ളടക്കം ചേർക്കുക\",\n  \"CardDetail.add-icon\": \"ഐക്കൺ ചേർക്കുക\",\n  \"CardDetail.add-property\": \"+ ഒരു വിശേഷണം ചേർക്കുക\",\n  \"CardDetail.addCardText\": \"കാർഡിൽ വാക്യം ചേർക്കുക\",\n  \"CardDetail.limited-body\": \"ആർക്കൈവ് ചെയ്‌ത കാർഡുകൾ കാണുന്നതിനും ഓരോ ബോർഡുകൾക്കും പരിധിയില്ലാത്ത കാഴ്‌ചകൾ നേടുന്നതിനും പരിധിയില്ലാത്ത കാർഡുകൾക്കും മറ്റും ഞങ്ങളുടെ പ്രൊഫഷണൽ അല്ലെങ്കിൽ എന്റർപ്രൈസ് പ്ലാനിലേക്ക് അപ്‌ഗ്രേഡ് ചെയ്യുക.\",\n  \"CardDetail.limited-button\": \"അപ്ഗ്രേഡ്\",\n  \"CardDetail.moveContent\": \"കാർഡ് ഉള്ളടക്കം നീക്കുക\",\n  \"CardDetail.new-comment-placeholder\": \"ഒരു അഭിപ്രായം ചേർക്കുക...\",\n  \"CardDetailProperty.confirm-delete-heading\": \"പ്രോപ്പർട്ടി ഇല്ലാതാക്കുന്നത് സ്ഥിരീകരിക്കുക\",\n  \"CardDetailProperty.confirm-delete-subtext\": \"പ്രോപ്പർട്ടി ഇല്ലാതാക്കണമെന്ന് തീർച്ചയാണോ \\\"{propertyName}\\\"? ഇത് ഇല്ലാതാക്കുന്നത് ഈ ബോർഡിലെ എല്ലാ കാർഡുകളിൽ നിന്നും പ്രോപ്പർട്ടി ഇല്ലാതാക്കും.\",\n  \"CardDetailProperty.confirm-property-name-change-subtext\": \"\\\"{propertyName}\\\" {customText} പ്രോപ്പർട്ടി മാറ്റണമെന്ന് തീർച്ചയാണോ? ഇത് ഈ ബോർഡിലെ {numOfCards} കാർഡുകളിലുടനീളമുള്ള മൂല്യങ്ങളെ(കളെ) ബാധിക്കുകയും ഡാറ്റ നഷ്‌ടത്തിന് കാരണമാവുകയും ചെയ്യും.\",\n  \"CardDetailProperty.confirm-property-type-change\": \"പ്രോപ്പർട്ടി തരം മാറ്റം സ്ഥിരീകരിക്കുക!\",\n  \"CardDetailProperty.delete-action-button\": \"നീക്കം ചെയ്യുക\",\n  \"CardDetailProperty.property-change-action-button\": \"പ്രോപ്പർട്ടി മാറ്റുക\",\n  \"CardDetailProperty.property-changed\": \"പ്രോപ്പർട്ടി വിജയകരമായി മാറ്റി!\",\n  \"CardDetailProperty.property-deleted\": \"വിജയകരമായി {propertyName} ഇല്ലാതാക്കിയിരിക്കുന്നു!\",\n  \"CardDetailProperty.property-name-change-subtext\": \"\\\"{oldPropType}\\\" മുതൽ \\\"{newPropType}\\\" വരെ ടൈപ്പ് ചെയ്യുക\",\n  \"CardDialog.delete-confirmation-dialog-button-text\": \"ഇല്ലാതാക്കുക\",\n  \"CardDialog.delete-confirmation-dialog-heading\": \"കാർഡ് ഇല്ലാതാക്കൽ സ്ഥിരീകരിക്കുക!\",\n  \"CardDialog.editing-template\": \"നിങ്ങൾ ഒരു ടെംപ്ലേറ്റ് എഡിറ്റ് ചെയ്യുകയാണ്.\",\n  \"CardDialog.nocard\": \"ഈ കാർഡ് നിലവിലില്ല അല്ലെങ്കിൽ ആക്സസ് ചെയ്യാനാകുന്നില്ല.\",\n  \"Categories.CreateCategoryDialog.CancelText\": \"റദ്ദാക്കുക\",\n  \"Categories.CreateCategoryDialog.CreateText\": \"സൃഷ്ടിക്കുക\",\n  \"Categories.CreateCategoryDialog.Placeholder\": \"നിങ്ങളുടെ വിഭാഗത്തിന് പേര് നൽകുക\",\n  \"Categories.CreateCategoryDialog.UpdateText\": \"അപ്ഡേറ്റ് ചെയ്യുക\",\n  \"CenterPanel.Login\": \"ലോഗിൻ\",\n  \"CenterPanel.Share\": \"പങ്കിടുക\",\n  \"ColorOption.selectColor\": \"നിറം {color} തിരഞ്ഞെടുക്കുക\",\n  \"Comment.delete\": \"ഇല്ലാതാക്കുക\",\n  \"CommentsList.send\": \"അയക്കുക\",\n  \"ConfirmationDialog.cancel-action\": \"റദ്ദാക്കുക\",\n  \"ConfirmationDialog.confirm-action\": \"സ്ഥിരീകരിക്കുക\",\n  \"ContentBlock.Delete\": \"ഇല്ലാതാക്കുക\",\n  \"ContentBlock.DeleteAction\": \"ഇല്ലാതാക്കുക\",\n  \"ContentBlock.addElement\": \"ചേർക്കുക {type}\",\n  \"ContentBlock.checkbox\": \"ചെക്ക്ബോക്സ്\",\n  \"ContentBlock.divider\": \"ഡിവൈഡർ\",\n  \"ContentBlock.editCardCheckbox\": \"ടുഗേൾഡ് -ചെക്ക്ബോക്സ്\",\n  \"ContentBlock.editCardCheckboxText\": \"കാർഡിലെ വാചകം തിരുത്തുക\",\n  \"ContentBlock.editCardText\": \"കാർഡിലെ വാചകം തിരുത്തുക\",\n  \"ContentBlock.editText\": \"വാചകം തിരുത്തുക...\",\n  \"ContentBlock.image\": \"ചിത്രം\",\n  \"ContentBlock.insertAbove\": \"മുകളിൽ തിരുകുക\",\n  \"ContentBlock.moveDown\": \"താഴേക്ക് നീക്കുക\",\n  \"ContentBlock.moveUp\": \"മുകളിലേക്കു നീക്കുക\",\n  \"ContentBlock.text\": \"വാചകം\",\n  \"DateRange.endDate\": \"അവസാന തീയതി\",\n  \"DateRange.today\": \"ഇന്ന്\",\n  \"DeleteBoardDialog.confirm-cancel\": \"നിര്‍ത്തലാക്കുക\",\n  \"DeleteBoardDialog.confirm-delete\": \"നീക്കം ചെയ്യുക\",\n  \"DeleteBoardDialog.confirm-info\": \"\\\"{boardTitle}\\\" എന്ന ബോർഡ് ഇല്ലാതാക്കണമെന്ന് തീർച്ചയാണോ? ഇത് ഇല്ലാതാക്കുന്നത് ബോർഡിലെ എല്ലാ കാർഡുകളും ഇല്ലാതാക്കും.\",\n  \"DeleteBoardDialog.confirm-tite\": \"ബോർഡ് നീക്കം ചെയ്യുന്നത് സ്ഥിരീകരിക്കുക\",\n  \"DeleteBoardDialog.confirm-tite-template\": \"ബോർഡ് ടെംപ്ലേറ്റ് ഇല്ലാതാക്കുന്നത് സ്ഥിരീകരിക്കുക\",\n  \"Dialog.closeDialog\": \"ഡയലോഗ് അവസാനിപ്പിക്കുക\",\n  \"EditableDayPicker.today\": \"ഇന്ന്\",\n  \"Error.mobileweb\": \"മൊബൈൽ വെബ് പിന്തുണ നിലവിൽ ആദ്യകാല ബീറ്റയിലാണ്. എല്ലാ പ്രവർത്തനങ്ങളും ഉണ്ടായിരിക്കണമെന്നില്ല.\",\n  \"Error.websocket-closed\": \"വെബ്‌സോക്കറ്റ് കണക്ഷൻ അടച്ചു, കണക്ഷൻ തടസ്സപ്പെട്ടു. ഇത് നിലനിൽക്കുകയാണെങ്കിൽ, നിങ്ങളുടെ സെർവർ അല്ലെങ്കിൽ വെബ് പ്രോക്സി കോൺഫിഗറേഷൻ പരിശോധിക്കുക.\",\n  \"Filter.includes\": \"ഉൾപ്പെടുന്നു\",\n  \"Filter.is-empty\": \"ശൂന്യമാണ്\",\n  \"Filter.is-not-empty\": \"ശൂന്യമല്ല\",\n  \"Filter.not-includes\": \"ഉൾപ്പെടുന്നില്ല\",\n  \"FilterComponent.add-filter\": \"+ ഫിൽട്ടർ ചേർക്കുക\",\n  \"FilterComponent.delete\": \"ഇല്ലാതാക്കുക\",\n  \"FindBoardsDialog.NoResultsFor\": \"\\\"{searchQuery}\\\" എന്നതിന് ഫലങ്ങളൊന്നുമില്ല\",\n  \"FindBoardsDialog.NoResultsSubtext\": \"അക്ഷരവിന്യാസം പരിശോധിക്കുക അല്ലെങ്കിൽ മറ്റൊരു തിരയൽ പരീക്ഷിക്കുക.\",\n  \"FindBoardsDialog.SubTitle\": \"ഒരു ബോർഡ് കണ്ടെത്താൻ ടൈപ്പ് ചെയ്യുക. ബ്രൗസ് ചെയ്യാൻ <b>UP/DOWN</b> ഉപയോഗിക്കുക. തിരഞ്ഞെടുക്കാൻ <b>ENTER</b>, ഡിസ്മിസ് ചെയ്യാൻ <b>ESC</b>\",\n  \"FindBoardsDialog.Title\": \"ബോർഡുകൾ കണ്ടെത്തുക\",\n  \"GroupBy.hideEmptyGroups\": \"{count} ശൂന്യമായ ഗ്രൂപ്പുകൾ മറയ്ക്കുക\",\n  \"GroupBy.showHiddenGroups\": \"മറഞ്ഞിരിക്കുന്ന {count} ഗ്രൂപ്പുകൾ കാണിക്കുക\",\n  \"GroupBy.ungroup\": \"ഗ്രൂപ്പിൽ നിന്നും മാറ്റുക\",\n  \"KanbanCard.untitled\": \"ശീർഷകമില്ലാത്തത്\",\n  \"Mutator.new-card-from-template\": \"ടെംപ്ലേറ്റിൽ നിന്നുള്ള പുതിയ കാർഡ്\",\n  \"Mutator.new-template-from-card\": \"കാർഡിൽ നിന്നുള്ള പുതിയ ടെംപ്ലേറ്റ്\",\n  \"OnboardingTour.AddComments.Body\": \"നിങ്ങൾക്ക് പ്രശ്‌നങ്ങളിൽ അഭിപ്രായമിടാം, ഒപ്പം നിങ്ങളുടെ സഹ മാറ്റർമോസ് ഉപയോക്താക്കളെ അവരുടെ ശ്രദ്ധ ആകർഷിക്കാൻ അവരെ @പരാമർശിക്കുകയും ചെയ്യാം.\",\n  \"OnboardingTour.AddComments.Title\": \"അഭിപ്രായങ്ങൾ ചേർക്കുക\",\n  \"OnboardingTour.AddDescription.Body\": \"നിങ്ങളുടെ കാർഡിലേക്ക് ഒരു വിവരണം ചേർക്കുക, അതുവഴി കാർഡ് എന്താണെന്ന് നിങ്ങളുടെ ടീമംഗങ്ങൾക്ക് അറിയാം.\",\n  \"OnboardingTour.AddDescription.Title\": \"വിവരണം ചേർക്കുക\",\n  \"OnboardingTour.AddProperties.Body\": \"കാർഡുകളെ കൂടുതൽ ശക്തമാക്കുന്നതിന് അവയിൽ വിവിധ പ്രോപ്പർട്ടികൾ ചേർക്കുക!\",\n  \"OnboardingTour.AddProperties.Title\": \"പ്രോപ്പർട്ടികൾ ചേർക്കുക\",\n  \"OnboardingTour.AddView.Body\": \"വ്യത്യസ്‌ത ലേഔട്ടുകൾ ഉപയോഗിച്ച് നിങ്ങളുടെ ബോർഡ് ഓർഗനൈസുചെയ്യുന്നതിന് ഒരു പുതിയ കാഴ്‌ച സൃഷ്‌ടിക്കുന്നതിന് ഇവിടെ പോകുക.\",\n  \"OnboardingTour.AddView.Title\": \"ഒരു പുതിയ കാഴ്ച ചേർക്കുക\",\n  \"OnboardingTour.CopyLink.Body\": \"ലിങ്ക് പകർത്തി ഒരു ചാനലിലോ ഡയറക്ട് മെസേജിലോ ഗ്രൂപ്പ് മെസേജിലോ പകർത്തികൊണ്ട് നിങ്ങൾക്ക് ടീമംഗങ്ങളുമായി കാർഡുകൾ പങ്കിടാം.\",\n  \"OnboardingTour.CopyLink.Title\": \"ലിങ്ക് പകർത്തുക\",\n  \"OnboardingTour.OpenACard.Body\": \"നിങ്ങളുടെ ജോലി ഓർഗനൈസുചെയ്യാൻ ബോർഡുകൾക്ക് നിങ്ങളെ സഹായിക്കുന്ന ശക്തമായ വഴികൾ പര്യവേക്ഷണം ചെയ്യാൻ ഒരു കാർഡ് തുറക്കുക.\",\n  \"OnboardingTour.OpenACard.Title\": \"ഒരു കാർഡ് തുറക്കുക\",\n  \"OnboardingTour.ShareBoard.Body\": \"നിങ്ങളുടെ ബോർഡ് ടീമിനുള്ളിലും പങ്കിടാം അല്ലെങ്കിൽ നിങ്ങളുടെ സ്ഥാപനത്തിന് പുറത്തുള്ള ദൃശ്യപരതയ്ക്കായി അത് പരസ്യമായി പ്രസിദ്ധീകരിക്കാം.\",\n  \"OnboardingTour.ShareBoard.Title\": \"ഷെയർ ബോർഡ്\",\n  \"PropertyMenu.Delete\": \"ഇല്ലാതാക്കുക\",\n  \"PropertyMenu.changeType\": \"പ്രോപ്പർട്ടി തരം മാറ്റുക\",\n  \"PropertyMenu.selectType\": \"പ്രോപ്പർട്ടി തരം തിരഞ്ഞെടുക്കുക\",\n  \"PropertyMenu.typeTitle\": \"തരം\",\n  \"PropertyType.Checkbox\": \"ചെക്ക്ബോക്സ്\",\n  \"PropertyType.CreatedBy\": \"ഉണ്ടാക്കിയത്\",\n  \"PropertyType.CreatedTime\": \"സൃഷ്ടിച്ച സമയം\",\n  \"PropertyType.Date\": \"തീയതി\",\n  \"PropertyType.Email\": \"ഇമെയിൽ\",\n  \"PropertyType.MultiSelect\": \"മൾട്ടി സെലക്ട്\",\n  \"PropertyType.Number\": \"നമ്പർ\",\n  \"PropertyType.Person\": \"വ്യക്തി\",\n  \"PropertyType.Phone\": \"ഫോൺ\",\n  \"PropertyType.Select\": \"തിരഞ്ഞെടുക്കുക\",\n  \"PropertyType.Text\": \"വാചകം\",\n  \"PropertyType.UpdatedBy\": \"അവസാനം അപ്ഡേറ്റ് ചെയ്തത്\",\n  \"PropertyType.UpdatedTime\": \"അവസാനം പുതുക്കിയ സമയം\",\n  \"PropertyValueElement.empty\": \"ശൂന്യം\",\n  \"RegistrationLink.confirmRegenerateToken\": \"ഇത് മുമ്പ് പങ്കിട്ട ലിങ്കുകളെ അസാധുവാക്കും. തുടരുക?\",\n  \"RegistrationLink.copiedLink\": \"പകർത്തി!\",\n  \"RegistrationLink.copyLink\": \"ലിങ്ക് പകർത്തുക\",\n  \"RegistrationLink.description\": \"മറ്റുള്ളവർക്ക് അക്കൗണ്ടുകൾ സൃഷ്ടിക്കാൻ ഈ ലിങ്ക് പങ്കിടുക:\",\n  \"RegistrationLink.regenerateToken\": \"ടോക്കൺ പുനർനിർമ്മിക്കുക\",\n  \"RegistrationLink.tokenRegenerated\": \"രജിസ്ട്രേഷൻ ലിങ്ക് പുനഃസൃഷ്ടിച്ചു\",\n  \"ShareBoard.PublishDescription\": \"വെബിലെ എല്ലാവരുമായും \\\"വായന മാത്രം\\\" എന്ന ലിങ്ക് പ്രസിദ്ധീകരിക്കുകയും പങ്കിടുകയും ചെയ്യുക\",\n  \"ShareBoard.PublishTitle\": \"വെബിൽ പ്രസിദ്ധീകരിക്കുക\",\n  \"ShareBoard.ShareInternalDescription\": \"അനുമതിയുള്ള ഉപയോക്താക്കൾക്ക് ഈ ലിങ്ക് ഉപയോഗിക്കാനാകും\",\n  \"ShareBoard.Title\": \"ഷെയർ ബോർഡ്\",\n  \"ShareBoard.confirmRegenerateToken\": \"ഇത് മുമ്പ് പങ്കിട്ട ലിങ്കുകളെ അസാധുവാക്കും. തുടരുക?\",\n  \"ShareBoard.copiedLink\": \"പകർത്തി!\",\n  \"ShareBoard.copyLink\": \"ലിങ്ക് പകർത്തുക\",\n  \"ShareBoard.regenerate\": \"ടോക്കൺ പുനർനിർമ്മിക്കുക\",\n  \"ShareBoard.teamPermissionsText\": \"{teamName} ടീമിലെ എല്ലാവരും\",\n  \"ShareBoard.tokenRegenrated\": \"ടോക്കൺ പുനർനിർമ്മിച്ചു\",\n  \"ShareBoard.userPermissionsRemoveMemberText\": \"അംഗത്തെ നീക്കം ചെയ്യുക\",\n  \"ShareBoard.userPermissionsYouText\": \"(നിങ്ങൾ)\",\n  \"ShareTemplate.Title\": \"ടെംപ്ലേറ്റ് പങ്കിടുക\",\n  \"Sidebar.about\": \"ഫോക്കൽബോർഡിനെക്കുറിച്ച്\",\n  \"Sidebar.add-board\": \"+ ബോർഡ് ചേർക്കുക\",\n  \"Sidebar.changePassword\": \"പാസ്സ്‌വേഡ്‌ മാറ്റുക\",\n  \"Sidebar.delete-board\": \"ബോർഡ് നീക്കം ചെയ്യുക\",\n  \"Sidebar.duplicate-board\": \"ഡ്യൂപ്ലിക്കേറ്റ് ബോർഡ്\",\n  \"Sidebar.export-archive\": \"ആർക്കൈവ് എക്സ്പോർട്ട് ചെയ്യുക\",\n  \"Sidebar.import\": \"ഇറക്കുമതി ചെയ്യുക\",\n  \"Sidebar.import-archive\": \"ആർക്കൈവ് ഇമ്പോർട്ട് ചെയ്യുക\",\n  \"Sidebar.invite-users\": \"ഉപയോക്താക്കളെ ക്ഷണിക്കുക\",\n  \"Sidebar.logout\": \"ലോഗ്ഔട്ട്\",\n  \"Sidebar.no-boards-in-category\": \"അകത്ത് ബോർഡുകളില്ല\",\n  \"Sidebar.random-icons\": \"ക്രമരഹിതമായ ഐക്കണുകൾ\",\n  \"Sidebar.set-language\": \"ഭാഷ സജ്ജമാക്കുക\",\n  \"Sidebar.set-theme\": \"തീം സജ്ജമാക്കുക\",\n  \"Sidebar.settings\": \"ക്രമീകരണങ്ങൾ\",\n  \"Sidebar.template-from-board\": \"ബോർഡിൽ നിന്നുള്ള പുതിയ ടെംപ്ലേറ്റ്\",\n  \"Sidebar.untitled-board\": \"(പേരില്ലാത്ത ബോർഡ്)\",\n  \"SidebarCategories.BlocksMenu.Move\": \"ഇതിലേക്ക് നീക്കുക...\",\n  \"SidebarCategories.CategoryMenu.CreateNew\": \"പുതിയ വിഭാഗം സൃഷ്ടിക്കുക\",\n  \"SidebarCategories.CategoryMenu.Delete\": \"വിഭാഗം ഇല്ലാതാക്കുക\",\n  \"SidebarCategories.CategoryMenu.DeleteModal.Body\": \"<b>{categoryName}</b> എന്നതിലെ ബോർഡുകൾ ബോർഡുകളുടെ വിഭാഗങ്ങളിലേക്ക് തിരികെ നീങ്ങും. നിങ്ങളെ ഒരു ബോർഡിൽ നിന്നും നീക്കം ചെയ്‌തിട്ടില്ല.\",\n  \"SidebarCategories.CategoryMenu.DeleteModal.Title\": \"ഈ കാറ്റഗറി ഇല്ലാതാക്കണോ?\",\n  \"SidebarCategories.CategoryMenu.Update\": \"കാറ്റഗറിയുടെ പേര് മാറ്റുക\",\n  \"TableComponent.add-icon\": \"ഐക്കൺ ചേർക്കുക\",\n  \"TableComponent.name\": \"പേര്\",\n  \"TableComponent.plus-new\": \"+ പുതിയത്\",\n  \"TableHeaderMenu.delete\": \"ഇല്ലാതാക്കുക\",\n  \"TableHeaderMenu.duplicate\": \"തനിപ്പകർപ്പ്\",\n  \"TableHeaderMenu.hide\": \"മറയ്ക്കുക\",\n  \"TableHeaderMenu.insert-left\": \"ഇടത്തേക്ക് തിരുകുക\",\n  \"TableHeaderMenu.insert-right\": \"വലത്തേക്ക് തിരുകുക\",\n  \"TableHeaderMenu.sort-ascending\": \"ആരോഹണക്രമത്തിൽ അടുക്കുക\",\n  \"TableHeaderMenu.sort-descending\": \"അവരോഹണക്രമം അടുക്കുക\",\n  \"TableRow.open\": \"തുറക്കുക\",\n  \"TopBar.give-feedback\": \"അഭിപ്രായം അറിയിക്കുക\",\n  \"URLProperty.copiedLink\": \"പകർത്തി!\",\n  \"URLProperty.copy\": \"പകർത്തുക\",\n  \"ValueSelector.noOptions\": \"ഓപ്ഷനുകളൊന്നുമില്ല. ആദ്യത്തേത് ചേർക്കാൻ ടൈപ്പുചെയ്യാൻ ആരംഭിക്കുക!\",\n  \"ValueSelector.valueSelector\": \"മൂല്യ0 തിരഞ്ഞെടുക്കുക\",\n  \"ValueSelectorLabel.openMenu\": \"മെനു തുറക്കുക\",\n  \"View.AddView\": \"വ്യൂ ചേർക്കുക\",\n  \"View.Board\": \"ബോർഡ്\",\n  \"View.DeleteView\": \"വ്യൂ നീക്കം ചെയ്യുക\",\n  \"View.DuplicateView\": \"വ്യൂവിന്റെ തനിപ്പകര്‍പ്പ്\",\n  \"View.Gallery\": \"ചിത്രശാല\",\n  \"View.NewBoardTitle\": \"ബോർഡ് വ്യൂ\",\n  \"View.NewCalendarTitle\": \"കലണ്ടർ വ്യൂ\",\n  \"View.NewGalleryTitle\": \"ചിത്രശാല വ്യൂ\",\n  \"View.NewTableTitle\": \"പട്ടിക വ്യൂ\",\n  \"View.Table\": \"പട്ടിക\",\n  \"ViewHeader.add-template\": \"പുതിയ ടെംപ്ലേറ്റ്\",\n  \"ViewHeader.delete-template\": \"നീക്കം ചെയ്യുക\",\n  \"ViewHeader.display-by\": \"പ്രദർശിപ്പിക്കുന്നത്: {property}\",\n  \"ViewHeader.edit-template\": \"തിരുത്തുക\",\n  \"ViewHeader.empty-card\": \"ശൂന്യമായ കാർഡ്\",\n  \"ViewHeader.export-board-archive\": \"ബോർഡ് ആർക്കൈവ് എക്സ്പോർട്ട് ചെയ്യുക\",\n  \"ViewHeader.export-complete\": \"എക്സ്പോർട്ട് പൂർത്തിയായി!\",\n  \"ViewHeader.export-csv\": \"CSV-ലേക്ക് എക്സ്പോർട്ട് ചെയ്യുക\",\n  \"ViewHeader.export-failed\": \"എക്സ്പോർട്ട് പരാജയപ്പെട്ടു!\",\n  \"ViewHeader.filter\": \"ഫിൽട്ടർ\",\n  \"ViewHeader.group-by\": \"ഗ്രൂപ്പ്:{property}\",\n  \"ViewHeader.new\": \"പുതിയത്\",\n  \"ViewHeader.properties\": \"സവിശേഷതകള്‍\",\n  \"ViewHeader.properties-menu\": \"പ്രോപ്പർട്ടീസ് മെനു\",\n  \"ViewHeader.search-text\": \"കാർഡുകൾ തിരയുക\",\n  \"ViewHeader.select-a-template\": \"ഒരു ടെംപ്ലേറ്റ് തിരഞ്ഞെടുക്കുക\",\n  \"ViewHeader.set-default-template\": \"സ്ഥിരസ്ഥിതിയായി സജ്ജമാക്കാൻ\",\n  \"ViewHeader.sort\": \"അടുക്കുക\",\n  \"ViewHeader.untitled\": \"ശീർഷകമില്ലാത്തത്\",\n  \"ViewHeader.view-header-menu\": \"തലക്കെട്ട് മെനു കാണുക\",\n  \"ViewHeader.view-menu\": \"മെനു കാണുക\",\n  \"ViewTitle.hide-description\": \"വിവരണം മറയ്ക്കുക\",\n  \"ViewTitle.pick-icon\": \"ഐക്കൺ തിരഞ്ഞെടുക്കുക\",\n  \"ViewTitle.random-icon\": \"ക്രമരഹിതം\",\n  \"ViewTitle.remove-icon\": \"ഐക്കൺ നീക്കം ചെയ്യുക\",\n  \"ViewTitle.show-description\": \"വിവരണം കാണിക്കുക\",\n  \"ViewTitle.untitled-board\": \"ശീർഷകമില്ലാത്ത ബോർഡ്\",\n  \"WelcomePage.Description\": \"പരിചിതമായ കാൻബൻ ബോർഡ് വ്യൂ ഉപയോഗിച്ച് ടീമുകളിലുടനീളമുള്ള ജോലി നിർവചിക്കാനും ഓർഗനൈസുചെയ്യാനും ട്രാക്കുചെയ്യാനും നിയന്ത്രിക്കാനും സഹായിക്കുന്ന ഒരു പ്രോജക്റ്റ് മാനേജ്‌മെന്റ് ടൂളാണ് ബോർഡുകൾ\",\n  \"WelcomePage.Explore.Button\": \"ഒരു ടൂർ നടത്തുക\",\n  \"WelcomePage.Heading\": \"ബോർഡുകളിലേക്ക് സ്വാഗതം\",\n  \"WelcomePage.NoThanks.Text\": \"വേണ്ട നന്ദി, ഞാനത് സ്വയം കണ്ടുപിടിക്കാം\",\n  \"Workspace.editing-board-template\": \"നിങ്ങൾ ഒരു ബോർഡ് ടെംപ്ലേറ്റ് എഡിറ്റ് ചെയ്യുകയാണ്.\",\n  \"calendar.month\": \"മാസം\",\n  \"calendar.today\": \"ഇന്ന്\",\n  \"calendar.week\": \"ആഴ്ച\",\n  \"createImageBlock.failed\": \"ഫയൽ അപ്‌ലോഡ് ചെയ്യാൻ കഴിയുന്നില്ല. ഫയൽ വലുപ്പ പരിധി എത്തി.\",\n  \"default-properties.badges\": \"അഭിപ്രായങ്ങളും വിവരണവും\",\n  \"default-properties.title\": \"തലക്കെട്ട്\",\n  \"error.page.title\": \"ക്ഷമിക്കണം, എന്തോ കുഴപ്പം സംഭവിച്ചു\",\n  \"generic.previous\": \"മുൻപിലേക്ക്\",\n  \"imagePaste.upload-failed\": \"ചില ഫയലുകൾ അപ്‌ലോഡ് ചെയ്തിട്ടില്ല. ഫയൽ വലുപ്പ പരിധി എത്തി\",\n  \"login.log-in-button\": \"ലോഗിൻ\",\n  \"login.log-in-title\": \"ലോഗിൻ\",\n  \"login.register-button\": \"അല്ലെങ്കിൽ നിങ്ങൾക്ക് അക്കൗണ്ട് ഇല്ലെങ്കിൽ ഒരു അക്കൗണ്ട് സൃഷ്ടിക്കുക\",\n  \"register.login-button\": \"അല്ലെങ്കിൽ നിങ്ങൾക്ക് ഇതിനകം ഒരു അക്കൗണ്ട് ഉണ്ടെങ്കിൽ ലോഗിൻ ചെയ്യുക\",\n  \"register.signup-title\": \"നിങ്ങളുടെ അക്കൗണ്ടിനായി സൈൻ അപ്പ് ചെയ്യുക\",\n  \"share-board.publish\": \"പ്രസിദ്ധീകരിക്കുക\",\n  \"share-board.share\": \"പങ്കിടുക\",\n  \"shareBoard.lastAdmin\": \"ബോർഡുകളിൽ കുറഞ്ഞത് ഒരു അഡ്മിനിസ്ട്രേറ്റർ ഉണ്ടായിരിക്കണം\",\n  \"tutorial_tip.finish_tour\": \"ചെയ്തു\",\n  \"tutorial_tip.got_it\": \"മനസ്സിലായി\",\n  \"tutorial_tip.ok\": \"അടുത്തത്\",\n  \"tutorial_tip.out\": \"ഈ നുറുങ്ങുകളിൽ നിന്നും തിരഞ്ഞെടുക്കുക.\",\n  \"tutorial_tip.seen\": \"ഇത് മുമ്പ് കണ്ടിട്ടുണ്ടോ?\"\n}\n"
  },
  {
    "path": "webapp/i18n/nb_NO.json",
    "content": "{\n  \"AppBar.Tooltip\": \"Veksle lenkede tavler\",\n  \"Attachment.Attachment-title\": \"Vedlegg\",\n  \"AttachmentBlock.DeleteAction\": \"slett\",\n  \"AttachmentBlock.addElement\": \"legg til {type}\",\n  \"AttachmentBlock.delete\": \"Vedlegg slettet.\",\n  \"AttachmentBlock.failed\": \"Denne filen kunne ikke lastes opp fordi størrelsesgrensen er nådd.\",\n  \"AttachmentBlock.upload\": \"Vedlegg lastes opp.\",\n  \"AttachmentBlock.uploadSuccess\": \"Vedlegg lastet opp.\",\n  \"AttachmentElement.delete-confirmation-dialog-button-text\": \"Slett\",\n  \"AttachmentElement.download\": \"Last ned\",\n  \"AttachmentElement.upload-percentage\": \"Laster opp ...({uploadPercent}%)\",\n  \"BoardComponent.add-a-group\": \"+ Legg til gruppe\",\n  \"BoardComponent.delete\": \"Slett\",\n  \"BoardComponent.hidden-columns\": \"Skjulte kolonner\",\n  \"BoardComponent.hide\": \"Skjul\",\n  \"BoardComponent.new\": \"+ Ny\",\n  \"BoardComponent.no-property\": \"Ingen {property}\",\n  \"BoardComponent.no-property-title\": \"Elementer med tom {property} atributt legges her. Denne kolonnen kan ikke fjernes.\",\n  \"BoardComponent.show\": \"Vis\",\n  \"BoardMember.schemeAdmin\": \"Admin\",\n  \"BoardMember.schemeCommenter\": \"Kommentator\",\n  \"BoardMember.schemeEditor\": \"Redaktør\",\n  \"BoardMember.schemeNone\": \"Ingen\",\n  \"BoardMember.schemeViewer\": \"Viser\",\n  \"BoardMember.unlinkChannel\": \"Fjern lenke\",\n  \"BoardPage.newVersion\": \"En ny versjon av Boards er tilgjengelig, klikk her for å laste inn på nytt.\",\n  \"BoardPage.syncFailed\": \"Tavle kan slettes eller adgangen trekkes tilbake.\",\n  \"BoardTemplateSelector.add-template\": \"Lag ny mal\",\n  \"BoardTemplateSelector.create-empty-board\": \"Opprett tom tavle\",\n  \"BoardTemplateSelector.delete-template\": \"Slett\",\n  \"BoardTemplateSelector.description\": \"Legg til en tavle til sidestolpen med hvilken mal du vil fra listen under, eller start med en helt tom tavle.\",\n  \"BoardTemplateSelector.edit-template\": \"Rediger\",\n  \"BoardTemplateSelector.plugin.no-content-description\": \"Legg til en tavle i sidestolpen med hvilken mal du vil, eller start med en tom tavle.\",\n  \"BoardTemplateSelector.plugin.no-content-title\": \"Lag ny tavle\",\n  \"BoardTemplateSelector.title\": \"Lag ny tavle\",\n  \"BoardTemplateSelector.use-this-template\": \"Bruk denne malen\",\n  \"BoardsSwitcher.Title\": \"Finn tavle\",\n  \"BoardsUnfurl.Limited\": \"Flere detaljer er skjult fordi kortet er arkivert\",\n  \"BoardsUnfurl.Remainder\": \"+{remainder} mer\",\n  \"BoardsUnfurl.Updated\": \"Oppdatert {time}\",\n  \"Calculations.Options.average.displayName\": \"Gjennomsnitt\",\n  \"Calculations.Options.average.label\": \"Average\",\n  \"Calculations.Options.count.displayName\": \"Antall\",\n  \"Calculations.Options.count.label\": \"Antall\",\n  \"Calculations.Options.countChecked.displayName\": \"Avkrysset\",\n  \"Calculations.Options.countChecked.label\": \"Antall valgt\",\n  \"Calculations.Options.countUnchecked.displayName\": \"Ikke avmerket\",\n  \"Calculations.Options.countUnchecked.label\": \"Antall ikke valgt\",\n  \"Calculations.Options.countUniqueValue.displayName\": \"Unik\",\n  \"Calculations.Options.countUniqueValue.label\": \"Antall unike verdier\",\n  \"Calculations.Options.countValue.displayName\": \"Verdier\",\n  \"Calculations.Options.countValue.label\": \"Antall verdier\",\n  \"Calculations.Options.dateRange.displayName\": \"Tidsrom\",\n  \"Calculations.Options.dateRange.label\": \"Tidsrom\",\n  \"Calculations.Options.earliest.displayName\": \"Tiligst\",\n  \"Calculations.Options.earliest.label\": \"Tiligst\",\n  \"Calculations.Options.latest.displayName\": \"Senest\",\n  \"Calculations.Options.latest.label\": \"Senest\",\n  \"Calculations.Options.max.displayName\": \"Maks\",\n  \"Calculations.Options.max.label\": \"Maks\",\n  \"Calculations.Options.median.displayName\": \"Median\",\n  \"Calculations.Options.median.label\": \"Median\",\n  \"Calculations.Options.min.displayName\": \"Min\",\n  \"Calculations.Options.min.label\": \"Min\",\n  \"Calculations.Options.none.displayName\": \"Kalkulèr\",\n  \"Calculations.Options.none.label\": \"Ingen\",\n  \"Calculations.Options.percentChecked.displayName\": \"Valgt\",\n  \"Calculations.Options.percentChecked.label\": \"Prosent valgt\",\n  \"Calculations.Options.percentUnchecked.displayName\": \"Ikke valgt\",\n  \"Calculations.Options.percentUnchecked.label\": \"Prosent ikke valgt\",\n  \"Calculations.Options.range.displayName\": \"Tidsrom\",\n  \"Calculations.Options.range.label\": \"Tidsrom\",\n  \"Calculations.Options.sum.displayName\": \"Sum\",\n  \"Calculations.Options.sum.label\": \"Sum\",\n  \"CalendarCard.untitled\": \"Uten navn\",\n  \"CardActionsMenu.copiedLink\": \"Kopiert!\",\n  \"CardActionsMenu.copyLink\": \"Kopier lenke\",\n  \"CardActionsMenu.delete\": \"Slett\",\n  \"CardActionsMenu.duplicate\": \"Dupliser\",\n  \"CardBadges.title-checkboxes\": \"Avkrysningsbokser\",\n  \"CardBadges.title-comments\": \"Kommentarer\",\n  \"CardBadges.title-description\": \"Dette kortet har en beskrivelsestekst\",\n  \"CardDetail.Attach\": \"Legg ved\",\n  \"CardDetail.Follow\": \"Følg\",\n  \"CardDetail.Following\": \"Følger\",\n  \"CardDetail.add-content\": \"Legg til innhold\",\n  \"CardDetail.add-icon\": \"Legg til ikon\",\n  \"CardDetail.add-property\": \"+ Legg til en verdi\",\n  \"CardDetail.addCardText\": \"legg inn tekst i kortet\",\n  \"CardDetail.limited-body\": \"Oppgrader til vår profesjonelle eller bedriftsplan.\",\n  \"CardDetail.limited-button\": \"Oppgrader\",\n  \"CardDetail.limited-title\": \"Dette kortet er skjult\",\n  \"CardDetail.moveContent\": \"Flytt innholdet\",\n  \"CardDetail.new-comment-placeholder\": \"Legg til kommentar ...\",\n  \"CardDetailProperty.confirm-delete-heading\": \"Bekreft sletting av verdi\",\n  \"CardDetailProperty.confirm-delete-subtext\": \"Er du sikker på at du vil slette verdien \\\"{propertyName}\\\"? Dette vil fjerne verdien fra alle kortene på denne tavlen.\",\n  \"CardDetailProperty.confirm-property-name-change-subtext\": \"Er du sikker på at du vil endre verdien \\\"{propertyName}\\\" {customText}? Dette vil påvirke verdien på {numOfCards} kort på denne tavlen, og kan forårsake at du mister informasjon.\",\n  \"CardDetailProperty.confirm-property-type-change\": \"Bekreft endring av verditype\",\n  \"CardDetailProperty.delete-action-button\": \"Slett\",\n  \"CardDetailProperty.property-change-action-button\": \"Endre verdi\",\n  \"CardDetailProperty.property-changed\": \"Verdi endret!\",\n  \"CardDetailProperty.property-deleted\": \"Fjernet {propertyName}!\",\n  \"CardDetailProperty.property-name-change-subtext\": \"type fra \\\"{oldPropType}\\\" til \\\"{newPropType}\\\"\",\n  \"CardDetial.limited-link\": \"Lær mer om våre planer.\",\n  \"CardDialog.delete-confirmation-dialog-attachment\": \"Bekreft sletting av vedlegg\",\n  \"CardDialog.delete-confirmation-dialog-button-text\": \"Slett\",\n  \"CardDialog.delete-confirmation-dialog-heading\": \"Bekreft sletting av kort\",\n  \"CardDialog.editing-template\": \"Du redigerer en mal.\",\n  \"CardDialog.nocard\": \"Dette kortet eksisterer ikke eller du har ikke tilgang.\",\n  \"Categories.CreateCategoryDialog.CancelText\": \"Avbryt\",\n  \"Categories.CreateCategoryDialog.CreateText\": \"Opprett\",\n  \"Categories.CreateCategoryDialog.Placeholder\": \"Navngi kategorien\",\n  \"Categories.CreateCategoryDialog.UpdateText\": \"Oppdater\",\n  \"CenterPanel.Login\": \"Logg inn\",\n  \"CenterPanel.Share\": \"Del\",\n  \"ChannelIntro.CreateBoard\": \"Opprett tavle\",\n  \"ColorOption.selectColor\": \"Velg {color} farge\",\n  \"Comment.delete\": \"Slett\",\n  \"CommentsList.send\": \"Send\",\n  \"ConfirmPerson.empty\": \"Tom\",\n  \"ConfirmPerson.search\": \"Søk ...\",\n  \"ConfirmationDialog.cancel-action\": \"Avbryt\",\n  \"ConfirmationDialog.confirm-action\": \"Bekreft\",\n  \"ContentBlock.Delete\": \"Slett\",\n  \"ContentBlock.DeleteAction\": \"slett\",\n  \"ContentBlock.addElement\": \"legg til {type}\",\n  \"ContentBlock.checkbox\": \"avkrysningsboks\",\n  \"ContentBlock.divider\": \"avdeler\",\n  \"ContentBlock.editCardCheckbox\": \"krysset-boks\",\n  \"ContentBlock.editCardCheckboxText\": \"rediger kort tekst\",\n  \"ContentBlock.editCardText\": \"rediger kort tekst\",\n  \"ContentBlock.editText\": \"Rediger tekst ...\",\n  \"ContentBlock.image\": \"bilde\",\n  \"ContentBlock.insertAbove\": \"Sett inn over\",\n  \"ContentBlock.moveBlock\": \"flytt kort innhold\",\n  \"ContentBlock.moveDown\": \"Flytt ned\",\n  \"ContentBlock.moveUp\": \"Flytt opp\",\n  \"ContentBlock.text\": \"tekst\",\n  \"DateRange.clear\": \"Tøm\",\n  \"DateRange.empty\": \"Tom\",\n  \"DateRange.endDate\": \"Sluttdato\",\n  \"DateRange.today\": \"I dag\",\n  \"DeleteBoardDialog.confirm-cancel\": \"Avbryt\",\n  \"DeleteBoardDialog.confirm-delete\": \"Slett\",\n  \"DeleteBoardDialog.confirm-info\": \"Er du sikker på at du vil slette tavlen \\\"{boardTitle}\\\"? Dette vil slette alle kortene på tavlen.\",\n  \"DeleteBoardDialog.confirm-info-template\": \"Er du sikker på at du vil slette tavlemalen \\\"{boardTitle}\\\"?\",\n  \"DeleteBoardDialog.confirm-tite\": \"Bekreft sletting av tavle\",\n  \"DeleteBoardDialog.confirm-tite-template\": \"Bekreft sletting av tavlemal\",\n  \"Dialog.closeDialog\": \"Lukk\",\n  \"EditableDayPicker.today\": \"I dag\",\n  \"Error.mobileweb\": \"Støtte for bruk i nettleser på mobil er i tidlig beta. Alt vil ikke fungere.\",\n  \"Error.websocket-closed\": \"Problemer med kobling til tjeneren. Sjekk konfigurasjonen hvis problemet vedvarer.\",\n  \"Filter.contains\": \"inneholder\",\n  \"Filter.ends-with\": \"ender med\",\n  \"Filter.includes\": \"inkluderer\",\n  \"Filter.is\": \"er\",\n  \"Filter.is-empty\": \"er tom\",\n  \"Filter.is-not-empty\": \"er ikke tom\",\n  \"Filter.is-not-set\": \"er ikke satt\",\n  \"Filter.is-set\": \"er satt\",\n  \"Filter.not-contains\": \"inkluderer ikke\",\n  \"Filter.not-ends-with\": \"ender ikke med\",\n  \"Filter.not-includes\": \"inkluderer ikke\",\n  \"Filter.not-starts-with\": \"starter ikke med\",\n  \"Filter.starts-with\": \"starter med\",\n  \"FilterByText.placeholder\": \"filtrer tekst\",\n  \"FilterComponent.add-filter\": \"+ Nytt filter\",\n  \"FilterComponent.delete\": \"Slett\",\n  \"FilterValue.empty\": \"(tom)\",\n  \"FindBoardsDialog.IntroText\": \"Søk etter tavle\",\n  \"FindBoardsDialog.NoResultsFor\": \"Ingen resultat for \\\"{searchQuery}\\\"\",\n  \"FindBoardsDialog.NoResultsSubtext\": \"Sjekk stavingen eller søk på noe annet.\",\n  \"FindBoardsDialog.SubTitle\": \"Skriv for å finne en tavle. Bruk <b>opp/ned</b> for å navigere. <b>Enter</b> for å velge, eller <b>Esc</b> for å avbryte\",\n  \"FindBoardsDialog.Title\": \"Finn tavle\",\n  \"GroupBy.hideEmptyGroups\": \"Skjul {count} tomme grupper\",\n  \"GroupBy.showHiddenGroups\": \"Vis {count} tomme grupper\",\n  \"GroupBy.ungroup\": \"Fjern fra gruppe\",\n  \"HideBoard.MenuOption\": \"Skjul tavlen\",\n  \"KanbanCard.untitled\": \"Uten navn\",\n  \"Mutator.new-board-from-template\": \"ny tavle fra mal\",\n  \"Mutator.new-card-from-template\": \"nytt kort fra mal\",\n  \"Mutator.new-template-from-card\": \"ny mal fra kort\"\n}\n"
  },
  {
    "path": "webapp/i18n/nl.json",
    "content": "{\n  \"AppBar.Tooltip\": \"Gekoppelde borden weergeven\",\n  \"Attachment.Attachment-title\": \"Bijlage\",\n  \"AttachmentBlock.DeleteAction\": \"verwijderen\",\n  \"AttachmentBlock.addElement\": \"voeg {type} toe\",\n  \"AttachmentBlock.delete\": \"Bijlage verwijderd.\",\n  \"AttachmentBlock.failed\": \"Dit bestand kon niet worden geüpload omdat de bestandslimiet wordt overschreden.\",\n  \"AttachmentBlock.upload\": \"Bijlage aan het uploaden.\",\n  \"AttachmentBlock.uploadSuccess\": \"Bijlage geüpload.\",\n  \"AttachmentElement.delete-confirmation-dialog-button-text\": \"Verwijderen\",\n  \"AttachmentElement.download\": \"Downloaden\",\n  \"AttachmentElement.upload-percentage\": \"Uploaden...({uploadPercent}%)\",\n  \"BoardComponent.add-a-group\": \"+ Een groep toevoegen\",\n  \"BoardComponent.delete\": \"Verwijderen\",\n  \"BoardComponent.hidden-columns\": \"Verborgen kolommen\",\n  \"BoardComponent.hide\": \"Verberg\",\n  \"BoardComponent.new\": \"+ Nieuw\",\n  \"BoardComponent.no-property\": \"Geen {property}\",\n  \"BoardComponent.no-property-title\": \"Items met een lege {property} eigenschap komen hier te staan. Deze kolom kan niet worden verwijderd.\",\n  \"BoardComponent.show\": \"Toon\",\n  \"BoardMember.schemeAdmin\": \"Beheerder\",\n  \"BoardMember.schemeCommenter\": \"Commentator\",\n  \"BoardMember.schemeEditor\": \"Bewerker\",\n  \"BoardMember.schemeNone\": \"Geen\",\n  \"BoardMember.schemeViewer\": \"Toeschouwer\",\n  \"BoardMember.unlinkChannel\": \"Losmaken\",\n  \"BoardPage.newVersion\": \"Er is een nieuwe versie van Boards, klik hier om te herladen.\",\n  \"BoardPage.syncFailed\": \"Het bord kan worden verwijderd of de toegang kan worden ingetrokken.\",\n  \"BoardTemplateSelector.add-template\": \"Nieuwe sjabloon aanmaken\",\n  \"BoardTemplateSelector.create-empty-board\": \"Maak een leeg bord\",\n  \"BoardTemplateSelector.delete-template\": \"Verwijderen\",\n  \"BoardTemplateSelector.description\": \"Voeg een bord aan de zijbalk door één van onderstaande sjabloon te gebruiken of start helemaal vanaf nul.\",\n  \"BoardTemplateSelector.edit-template\": \"Bewerken\",\n  \"BoardTemplateSelector.plugin.no-content-description\": \"Voeg een bord aan de zijbalk door één van onderstaande sjabloon te gebruiken of start helemaal vanaf nul.\",\n  \"BoardTemplateSelector.plugin.no-content-title\": \"Een bord aanmaken\",\n  \"BoardTemplateSelector.title\": \"Maak een board\",\n  \"BoardTemplateSelector.use-this-template\": \"Gebruik dit sjabloon\",\n  \"BoardsSwitcher.Title\": \"Boards vinden\",\n  \"BoardsUnfurl.Limited\": \"Extra details zijn verborgen omdat de kaart gearchiveerd is\",\n  \"BoardsUnfurl.Remainder\": \"+{remainder} meer\",\n  \"BoardsUnfurl.Updated\": \"Bijgewerkt {time}\",\n  \"Calculations.Options.average.displayName\": \"Gemiddeld\",\n  \"Calculations.Options.average.label\": \"Gemiddeld\",\n  \"Calculations.Options.count.displayName\": \"Aantal\",\n  \"Calculations.Options.count.label\": \"Aantal\",\n  \"Calculations.Options.countChecked.displayName\": \"Aangevinkt\",\n  \"Calculations.Options.countChecked.label\": \"Aantal aangevinkt\",\n  \"Calculations.Options.countUnchecked.displayName\": \"Niet aangevinkt\",\n  \"Calculations.Options.countUnchecked.label\": \"Aantal niet aangevinkt\",\n  \"Calculations.Options.countUniqueValue.displayName\": \"Uniek\",\n  \"Calculations.Options.countUniqueValue.label\": \"Tel unieke waarden\",\n  \"Calculations.Options.countValue.displayName\": \"Waarden\",\n  \"Calculations.Options.countValue.label\": \"Aantal waarden\",\n  \"Calculations.Options.dateRange.displayName\": \"Bereik\",\n  \"Calculations.Options.dateRange.label\": \"Bereik\",\n  \"Calculations.Options.earliest.displayName\": \"Vroegste\",\n  \"Calculations.Options.earliest.label\": \"Vroegste\",\n  \"Calculations.Options.latest.displayName\": \"Laatste\",\n  \"Calculations.Options.latest.label\": \"Laatste\",\n  \"Calculations.Options.max.displayName\": \"Maximum\",\n  \"Calculations.Options.max.label\": \"Maximum\",\n  \"Calculations.Options.median.displayName\": \"Mediaan\",\n  \"Calculations.Options.median.label\": \"Mediaan\",\n  \"Calculations.Options.min.displayName\": \"Minimum\",\n  \"Calculations.Options.min.label\": \"Minimum\",\n  \"Calculations.Options.none.displayName\": \"Bereken\",\n  \"Calculations.Options.none.label\": \"Geen\",\n  \"Calculations.Options.percentChecked.displayName\": \"Aangevinkt\",\n  \"Calculations.Options.percentChecked.label\": \"Percentage aangevinkt\",\n  \"Calculations.Options.percentUnchecked.displayName\": \"Niet aangevinkt\",\n  \"Calculations.Options.percentUnchecked.label\": \"Percentage niet aangevinkt\",\n  \"Calculations.Options.range.displayName\": \"Bereik\",\n  \"Calculations.Options.range.label\": \"Bereik\",\n  \"Calculations.Options.sum.displayName\": \"Som\",\n  \"Calculations.Options.sum.label\": \"Som\",\n  \"CalendarCard.untitled\": \"Titelloos\",\n  \"CardActionsMenu.copiedLink\": \"Gekopieerd!\",\n  \"CardActionsMenu.copyLink\": \"Kopieer link\",\n  \"CardActionsMenu.delete\": \"Verwijderen\",\n  \"CardActionsMenu.duplicate\": \"Dupliceren\",\n  \"CardBadges.title-checkboxes\": \"Selectievakjes\",\n  \"CardBadges.title-comments\": \"Opmerkingen\",\n  \"CardBadges.title-description\": \"Deze kaart heeft een beschrijving\",\n  \"CardDetail.Attach\": \"Toevoegen\",\n  \"CardDetail.Follow\": \"Volgen\",\n  \"CardDetail.Following\": \"Volgend\",\n  \"CardDetail.add-content\": \"Inhoud toevoegen\",\n  \"CardDetail.add-icon\": \"Pictogram toevoegen\",\n  \"CardDetail.add-property\": \"+ Een eigenschap toevoegen\",\n  \"CardDetail.addCardText\": \"kaarttekst toevoegen\",\n  \"CardDetail.limited-body\": \"Upgrade naar ons Professional- of Enterprise-plan.\",\n  \"CardDetail.limited-button\": \"Upgraden\",\n  \"CardDetail.limited-title\": \"Deze kaart is verborgen\",\n  \"CardDetail.moveContent\": \"Inhoud van de kaart verplaatsen\",\n  \"CardDetail.new-comment-placeholder\": \"Voeg commentaar toe...\",\n  \"CardDetailProperty.confirm-delete-heading\": \"Bevestig verwijderen eigenschap\",\n  \"CardDetailProperty.confirm-delete-subtext\": \"Weet je zeker dat je de eigenschap \\\"{propertyName}\\\" wilt verwijderen? Dit verwijderen zal de eigenschap van alle kaarten in dit bord verwijderen.\",\n  \"CardDetailProperty.confirm-property-name-change-subtext\": \"Weet je zeker dat je de eigenschap \\\"{propertyName}\\\" {customText} wilt wijzigen? Dit zal invloed hebben op de waarde(n) op de {numOfCards} kaart(en) in dit bord, en kan resulteren in data verlies.\",\n  \"CardDetailProperty.confirm-property-type-change\": \"Bevestig wijziging type eigenschap\",\n  \"CardDetailProperty.delete-action-button\": \"Verwijderen\",\n  \"CardDetailProperty.property-change-action-button\": \"Wijzig eigenschap\",\n  \"CardDetailProperty.property-changed\": \"Eigenschap succesvol gewijzigd!\",\n  \"CardDetailProperty.property-deleted\": \"{propertyName} werd succesvol verwijderd!\",\n  \"CardDetailProperty.property-name-change-subtext\": \"type van \\\"{oldPropType}\\\" naar \\\"{newPropType}\\\"\",\n  \"CardDetial.limited-link\": \"Meer informatie over onze plannen.\",\n  \"CardDialog.delete-confirmation-dialog-attachment\": \"Bevestig het verwijderen van de bijlage\",\n  \"CardDialog.delete-confirmation-dialog-button-text\": \"Verwijderen\",\n  \"CardDialog.delete-confirmation-dialog-heading\": \"Bevestig verwijderen kaart\",\n  \"CardDialog.editing-template\": \"Je bent een sjabloon aan het bewerken.\",\n  \"CardDialog.nocard\": \"Deze kaart bestaat niet of is ontoegankelijk.\",\n  \"Categories.CreateCategoryDialog.CancelText\": \"Annuleren\",\n  \"Categories.CreateCategoryDialog.CreateText\": \"Aanmaken\",\n  \"Categories.CreateCategoryDialog.Placeholder\": \"Geef je categorie een naam\",\n  \"Categories.CreateCategoryDialog.UpdateText\": \"Bijwerken\",\n  \"CenterPanel.Login\": \"Aanmelden\",\n  \"CenterPanel.Share\": \"Delen\",\n  \"ChannelIntro.CreateBoard\": \"Een bord aanmaken\",\n  \"ColorOption.selectColor\": \"Selecteer {color} Kleur\",\n  \"Comment.delete\": \"Verwijderen\",\n  \"CommentsList.send\": \"Verzenden\",\n  \"ConfirmPerson.empty\": \"Leeg\",\n  \"ConfirmPerson.search\": \"Zoeken...\",\n  \"ConfirmationDialog.cancel-action\": \"Annuleren\",\n  \"ConfirmationDialog.confirm-action\": \"Bevestigen\",\n  \"ContentBlock.Delete\": \"Verwijderen\",\n  \"ContentBlock.DeleteAction\": \"verwijderen\",\n  \"ContentBlock.addElement\": \"voeg {type} toe\",\n  \"ContentBlock.checkbox\": \"selectievakje\",\n  \"ContentBlock.divider\": \"verdeler\",\n  \"ContentBlock.editCardCheckbox\": \"Aangevinkt selectievakje\",\n  \"ContentBlock.editCardCheckboxText\": \"kaarttekst bewerken\",\n  \"ContentBlock.editCardText\": \"kaarttekst bewerken\",\n  \"ContentBlock.editText\": \"Tekst bewerken...\",\n  \"ContentBlock.image\": \"afbeelding\",\n  \"ContentBlock.insertAbove\": \"Hierboven invoegen\",\n  \"ContentBlock.moveBlock\": \"inhoud van de kaart verplaatsen\",\n  \"ContentBlock.moveDown\": \"Naar beneden verplaatsen\",\n  \"ContentBlock.moveUp\": \"Naar boven verplaatsen\",\n  \"ContentBlock.text\": \"tekst\",\n  \"DateRange.clear\": \"Wissen\",\n  \"DateRange.empty\": \"Leeg\",\n  \"DateRange.endDate\": \"Einddatum\",\n  \"DateRange.today\": \"Vandaag\",\n  \"DeleteBoardDialog.confirm-cancel\": \"Annuleren\",\n  \"DeleteBoardDialog.confirm-delete\": \"Verwijderen\",\n  \"DeleteBoardDialog.confirm-info\": \"Weet je zeker dat u het bord \\\"{boardTitle}\\\" wil verwijderen? Het verwijderen van het bord zal alle kaarten in het bord verwijderen.\",\n  \"DeleteBoardDialog.confirm-info-template\": \"Weet je zeker dat je het boardsjabloon \\\"{boardTitle}\\\" wilt verwijderen?\",\n  \"DeleteBoardDialog.confirm-tite\": \"Bevestig verwijderen board\",\n  \"DeleteBoardDialog.confirm-tite-template\": \"Bevestig verwijderen Board-sjabloon\",\n  \"Dialog.closeDialog\": \"Dialoogvenster sluiten\",\n  \"EditableDayPicker.today\": \"Vandaag\",\n  \"Error.mobileweb\": \"Mobiele webondersteuning is momenteel in vroege beta. Het is mogelijk dat niet alle functionaliteit aanwezig is.\",\n  \"Error.websocket-closed\": \"Websocketverbinding gesloten, verbinding onderbroken. Als dit aanhoudt, controleer dan jouw server of web proxy configuratie.\",\n  \"Filter.contains\": \"bevat\",\n  \"Filter.ends-with\": \"eindigt met\",\n  \"Filter.includes\": \"bevat\",\n  \"Filter.is\": \"is\",\n  \"Filter.is-empty\": \"is leeg\",\n  \"Filter.is-not-empty\": \"is niet leeg\",\n  \"Filter.is-not-set\": \"is niet ingesteld\",\n  \"Filter.is-set\": \"is ingesteld\",\n  \"Filter.not-contains\": \"bevat niet\",\n  \"Filter.not-ends-with\": \"eindigt niet met\",\n  \"Filter.not-includes\": \"bevat niet\",\n  \"Filter.not-starts-with\": \"begint niet met\",\n  \"Filter.starts-with\": \"begint met\",\n  \"FilterByText.placeholder\": \"filtertekst\",\n  \"FilterComponent.add-filter\": \"+ Filter toevoegen\",\n  \"FilterComponent.delete\": \"Verwijderen\",\n  \"FilterValue.empty\": \"(leeg)\",\n  \"FindBoardsDialog.IntroText\": \"Zoeken naar borden\",\n  \"FindBoardsDialog.NoResultsFor\": \"Geen resultaten voor \\\"{searchQuery}\\\"\",\n  \"FindBoardsDialog.NoResultsSubtext\": \"Controleer de spelling of probeer een andere zoekopdracht.\",\n  \"FindBoardsDialog.SubTitle\": \"Typ om een bord te vinden. Gebruik <b>UP/DOWN</b> om te bladeren. <b>ENTER</b> om te selecteren, <b>ESC</b> om te annuleren\",\n  \"FindBoardsDialog.Title\": \"Boards vinden\",\n  \"GroupBy.hideEmptyGroups\": \"Verberg {count} lege groepen\",\n  \"GroupBy.showHiddenGroups\": \"Toon {count} verborgen groepen\",\n  \"GroupBy.ungroup\": \"Groeperen stoppen\",\n  \"HideBoard.MenuOption\": \"Bord verbergen\",\n  \"KanbanCard.untitled\": \"Titelloos\",\n  \"MentionSuggestion.is-not-board-member\": \"Geen deelnemer van het bord\",\n  \"Mutator.new-board-from-template\": \"nieuw board van sjabloon\",\n  \"Mutator.new-card-from-template\": \"nieuwe kaart van sjabloon\",\n  \"Mutator.new-template-from-card\": \"nieuw sjabloon van kaart\",\n  \"OnboardingTour.AddComments.Body\": \"Je kunt commentaar geven op onderwerpen, en zelfs je medeMattermostgebruikers @vermelden om hun aandacht te trekken.\",\n  \"OnboardingTour.AddComments.Title\": \"Opmerkingen toevoegen\",\n  \"OnboardingTour.AddDescription.Body\": \"Voeg een beschrijving toe aan je kaart, zodat je teamgenoten weten waar de kaart over gaat.\",\n  \"OnboardingTour.AddDescription.Title\": \"Beschrijving toevoegen\",\n  \"OnboardingTour.AddProperties.Body\": \"Voeg verschillende eigenschappen toe aan kaarten om ze effectiever te maken.\",\n  \"OnboardingTour.AddProperties.Title\": \"Eigenschappen toevoegen\",\n  \"OnboardingTour.AddView.Body\": \"Kom hier naartoe om een nieuwe weergave te maken om uw bord te organiseren met verschillende lay-outs.\",\n  \"OnboardingTour.AddView.Title\": \"Een nieuwe weergave toevoegen\",\n  \"OnboardingTour.CopyLink.Body\": \"Je kunt je kaarten delen met teamgenoten door de link te kopiëren en in een kanaal, direct bericht of groepsbericht te plakken.\",\n  \"OnboardingTour.CopyLink.Title\": \"Link kopiëren\",\n  \"OnboardingTour.OpenACard.Body\": \"Open een kaart om de krachtige manieren te ontdekken waarop Boards je kunnen helpen je werk te organiseren.\",\n  \"OnboardingTour.OpenACard.Title\": \"Open een kaart\",\n  \"OnboardingTour.ShareBoard.Body\": \"Je kan jouw bord intern delen, binnen jouw team, of het publiek publiceren voor zichtbaarheid buiten jouw organisatie.\",\n  \"OnboardingTour.ShareBoard.Title\": \"Bord delen\",\n  \"PersonProperty.board-members\": \"Deelnemers aan het bord\",\n  \"PersonProperty.me\": \"Ik\",\n  \"PersonProperty.non-board-members\": \"Niet-deelnemers aan het bord\",\n  \"PropertyMenu.Delete\": \"Verwijderen\",\n  \"PropertyMenu.changeType\": \"Type eigenschap wijzigen\",\n  \"PropertyMenu.selectType\": \"Selecteer type eigenschap\",\n  \"PropertyMenu.typeTitle\": \"Type\",\n  \"PropertyType.Checkbox\": \"Selectievakje\",\n  \"PropertyType.CreatedBy\": \"Gemaakt door\",\n  \"PropertyType.CreatedTime\": \"Aangemaakt op\",\n  \"PropertyType.Date\": \"Datum\",\n  \"PropertyType.Email\": \"E-mail\",\n  \"PropertyType.MultiPerson\": \"Meerdere personen\",\n  \"PropertyType.MultiSelect\": \"Multiselect\",\n  \"PropertyType.Number\": \"Nummer\",\n  \"PropertyType.Person\": \"Persoon\",\n  \"PropertyType.Phone\": \"Telefoon\",\n  \"PropertyType.Select\": \"Selecteer\",\n  \"PropertyType.Text\": \"Tekst\",\n  \"PropertyType.Unknown\": \"Onbekend\",\n  \"PropertyType.UpdatedBy\": \"Laatst aangepast door\",\n  \"PropertyType.UpdatedTime\": \"Laatst bijgewerkte tijd\",\n  \"PropertyType.Url\": \"URL\",\n  \"PropertyValueElement.empty\": \"Leeg\",\n  \"RegistrationLink.confirmRegenerateToken\": \"Dit zal eerder gedeelde links ongeldig maken. Doorgaan?\",\n  \"RegistrationLink.copiedLink\": \"Gekopieerd!\",\n  \"RegistrationLink.copyLink\": \"Kopieer link\",\n  \"RegistrationLink.description\": \"Deel deze link zodat anderen een account kunnen aanmaken:\",\n  \"RegistrationLink.regenerateToken\": \"Token opnieuw genereren\",\n  \"RegistrationLink.tokenRegenerated\": \"Registratielink heraangemaakt\",\n  \"ShareBoard.PublishDescription\": \"Publiceer en deel een \\\"alleen-lezen\\\" link met iedereen op het web.\",\n  \"ShareBoard.PublishTitle\": \"Publiceren op het web\",\n  \"ShareBoard.ShareInternal\": \"Intern delen\",\n  \"ShareBoard.ShareInternalDescription\": \"Gebruikers die toegangsrechten hebben, kunnen deze link gebruiken.\",\n  \"ShareBoard.Title\": \"Bord delen\",\n  \"ShareBoard.confirmRegenerateToken\": \"Dit zal eerder gedeelde links ongeldig maken. Doorgaan?\",\n  \"ShareBoard.copiedLink\": \"Gekopieerd!\",\n  \"ShareBoard.copyLink\": \"Link kopiëren\",\n  \"ShareBoard.regenerate\": \"Token opnieuw genereren\",\n  \"ShareBoard.searchPlaceholder\": \"Mensen en kanalen zoeken\",\n  \"ShareBoard.teamPermissionsText\": \"Iedereen van team {teamName}\",\n  \"ShareBoard.tokenRegenrated\": \"Token opnieuw gegenereerd\",\n  \"ShareBoard.userPermissionsRemoveMemberText\": \"Lid verwijderen\",\n  \"ShareBoard.userPermissionsYouText\": \"(jij)\",\n  \"ShareTemplate.Title\": \"Sjabloon delen\",\n  \"ShareTemplate.searchPlaceholder\": \"Mensen zoeken\",\n  \"Sidebar.about\": \"Over Focalboard\",\n  \"Sidebar.add-board\": \"+ Bord toevoegen\",\n  \"Sidebar.changePassword\": \"Wachtwoord wijzigen\",\n  \"Sidebar.delete-board\": \"Verwijder bord\",\n  \"Sidebar.duplicate-board\": \"Board dupliceren\",\n  \"Sidebar.export-archive\": \"Archief exporteren\",\n  \"Sidebar.import\": \"Importeren\",\n  \"Sidebar.import-archive\": \"Archief importeren\",\n  \"Sidebar.invite-users\": \"Gebruikers uitnodigen\",\n  \"Sidebar.logout\": \"Afmelden\",\n  \"Sidebar.new-category.badge\": \"Nieuw\",\n  \"Sidebar.new-category.drag-boards-cta\": \"Sleep borden naar hier...\",\n  \"Sidebar.no-boards-in-category\": \"Geen boards hier\",\n  \"Sidebar.product-tour\": \"Product-rondleiding\",\n  \"Sidebar.random-icons\": \"Willekeurige iconen\",\n  \"Sidebar.set-language\": \"Taal instellen\",\n  \"Sidebar.set-theme\": \"Thema instellen\",\n  \"Sidebar.settings\": \"Instellingen\",\n  \"Sidebar.template-from-board\": \"Nieuw sjabloon van board\",\n  \"Sidebar.untitled-board\": \"(Titelloze bord )\",\n  \"Sidebar.untitled-view\": \"(Naamloze weergave)\",\n  \"SidebarCategories.BlocksMenu.Move\": \"Verplaatsen naar...\",\n  \"SidebarCategories.CategoryMenu.CreateNew\": \"Maak een nieuwe categorie\",\n  \"SidebarCategories.CategoryMenu.Delete\": \"Categorie verwijderen\",\n  \"SidebarCategories.CategoryMenu.DeleteModal.Body\": \"Borden in <b>{categoryName}</b> zullen terug verhuizen naar de Boards categorieën. Je zal niet verwijderd worden uit enig board.\",\n  \"SidebarCategories.CategoryMenu.DeleteModal.Title\": \"Deze categorie verwijderen?\",\n  \"SidebarCategories.CategoryMenu.Update\": \"Categorie hernoemen\",\n  \"SidebarTour.ManageCategories.Body\": \"Maak en beheer aangepaste categorieën. Categorieën zijn gebruikersspecifiek, dus het verplaatsen van een bord naar jouw categorie heeft geen invloed op andere leden die hetzelfde bord gebruiken.\",\n  \"SidebarTour.ManageCategories.Title\": \"Categorieën beheren\",\n  \"SidebarTour.SearchForBoards.Body\": \"Open de bordenswitcher (Cmd/Ctrl + K) om snel borden te zoeken en toe te voegen aan je zijbalk.\",\n  \"SidebarTour.SearchForBoards.Title\": \"Borden zoeken\",\n  \"SidebarTour.SidebarCategories.Body\": \"Al je borden zijn nu georganiseerd in je nieuwe zijbalk. Niet meer schakelen tussen werkruimten. Eenmalige zelfgemaakte categorieën gebaseerd op jouw vorige workspaces kunnen automatisch gemaakt zijn voor jou als onderdeel van jouw v7.2 upgrade. Deze kunnen worden verwijderd of aangepast aan jouw voorkeur.\",\n  \"SidebarTour.SidebarCategories.Link\": \"Meer info\",\n  \"SidebarTour.SidebarCategories.Title\": \"Zijbalk categorieën\",\n  \"SiteStats.total_boards\": \"Totaal aantal borden\",\n  \"SiteStats.total_cards\": \"Totaal aantal kaarten\",\n  \"TableComponent.add-icon\": \"Pictogram toevoegen\",\n  \"TableComponent.name\": \"Naam\",\n  \"TableComponent.plus-new\": \"+ Nieuw\",\n  \"TableHeaderMenu.delete\": \"Verwijderen\",\n  \"TableHeaderMenu.duplicate\": \"Kopiëren\",\n  \"TableHeaderMenu.hide\": \"Verberg\",\n  \"TableHeaderMenu.insert-left\": \"Links invoegen\",\n  \"TableHeaderMenu.insert-right\": \"Rechts invoegen\",\n  \"TableHeaderMenu.sort-ascending\": \"Sorteer oplopend\",\n  \"TableHeaderMenu.sort-descending\": \"Aflopend sorteren\",\n  \"TableRow.DuplicateCard\": \"dupliceren kaart\",\n  \"TableRow.MoreOption\": \"Meer acties\",\n  \"TableRow.open\": \"Openen\",\n  \"TopBar.give-feedback\": \"Geef feedback\",\n  \"URLProperty.copiedLink\": \"Gekopieerd!\",\n  \"URLProperty.copy\": \"Kopiëren\",\n  \"URLProperty.edit\": \"Bewerken\",\n  \"UndoRedoHotKeys.canRedo\": \"Herhaal\",\n  \"UndoRedoHotKeys.canRedo-with-description\": \"Herhaal {description}\",\n  \"UndoRedoHotKeys.canUndo\": \"Ongedaan maken\",\n  \"UndoRedoHotKeys.canUndo-with-description\": \"Ongedaan maken van {description}\",\n  \"UndoRedoHotKeys.cannotRedo\": \"Niets om te herhalen\",\n  \"UndoRedoHotKeys.cannotUndo\": \"Niets om ongedaan te maken\",\n  \"ValueSelector.noOptions\": \"Geen opties. Begin te typen om de eerste toe te voegen!\",\n  \"ValueSelector.valueSelector\": \"Waardekiezer\",\n  \"ValueSelectorLabel.openMenu\": \"Menu openen\",\n  \"VersionMessage.help\": \"Bekijk eens wat nieuw is in deze versie.\",\n  \"View.AddView\": \"Weergave toevoegen\",\n  \"View.Board\": \"Bord\",\n  \"View.DeleteView\": \"Weergave verwijderen\",\n  \"View.DuplicateView\": \"Weergave kopiëren\",\n  \"View.Gallery\": \"Galerij\",\n  \"View.NewBoardTitle\": \"Bordweergave\",\n  \"View.NewCalendarTitle\": \"Kalenderweergave\",\n  \"View.NewGalleryTitle\": \"Galerie bekijken\",\n  \"View.NewTableTitle\": \"Tabelweergave\",\n  \"View.NewTemplateDefaultTitle\": \"Naamloos sjabloon\",\n  \"View.NewTemplateTitle\": \"Naamloos\",\n  \"View.Table\": \"Tabel\",\n  \"ViewHeader.add-template\": \"Nieuw sjabloon\",\n  \"ViewHeader.delete-template\": \"Verwijderen\",\n  \"ViewHeader.display-by\": \"Weergegeven op: {property}\",\n  \"ViewHeader.edit-template\": \"Bewerken\",\n  \"ViewHeader.empty-card\": \"Lege kaart\",\n  \"ViewHeader.export-board-archive\": \"Archief bord exporteren\",\n  \"ViewHeader.export-complete\": \"Exporteren gelukt!\",\n  \"ViewHeader.export-csv\": \"Exporteren naar CSV\",\n  \"ViewHeader.export-failed\": \"Export mislukt!\",\n  \"ViewHeader.filter\": \"Filter\",\n  \"ViewHeader.group-by\": \"Groepeer op: {property}\",\n  \"ViewHeader.new\": \"Nieuw\",\n  \"ViewHeader.properties\": \"Eigenschappen\",\n  \"ViewHeader.properties-menu\": \"Eigenschappen-menu\",\n  \"ViewHeader.search-text\": \"Kaarten zoeken\",\n  \"ViewHeader.select-a-template\": \"Kies een sjabloon\",\n  \"ViewHeader.set-default-template\": \"Instellen als standaard\",\n  \"ViewHeader.sort\": \"Sorteer\",\n  \"ViewHeader.untitled\": \"Titelloos\",\n  \"ViewHeader.view-header-menu\": \"Menu hoofding weergeven\",\n  \"ViewHeader.view-menu\": \"Menuweergave\",\n  \"ViewLimitDialog.Heading\": \"Limiet aantal views per board bereikt\",\n  \"ViewLimitDialog.PrimaryButton.Title.Admin\": \"Upgraden\",\n  \"ViewLimitDialog.PrimaryButton.Title.RegularUser\": \"Verwittig Admin\",\n  \"ViewLimitDialog.Subtext.Admin\": \"Upgrade naar ons Professional- of Enterprise-plan.\",\n  \"ViewLimitDialog.Subtext.Admin.PricingPageLink\": \"Meer informatie over onze plannen.\",\n  \"ViewLimitDialog.Subtext.RegularUser\": \"Verwittig jouw Admin om te upgraden naar ons Professioneel of Enterprise plan.\",\n  \"ViewLimitDialog.UpgradeImg.AltText\": \"upgrade afbeelding\",\n  \"ViewLimitDialog.notifyAdmin.Success\": \"Jouw beheerder is op de hoogte gebracht\",\n  \"ViewTitle.hide-description\": \"beschrijving verbergen\",\n  \"ViewTitle.pick-icon\": \"Pictogram kiezen\",\n  \"ViewTitle.random-icon\": \"Willekeurig\",\n  \"ViewTitle.remove-icon\": \"Verwijder pictogram\",\n  \"ViewTitle.show-description\": \"beschrijving tonen\",\n  \"ViewTitle.untitled-board\": \"Titelloos board\",\n  \"WelcomePage.Description\": \"Boards is een projectmanagementtool die helpt bij het definiëren, organiseren, volgen en beheren van werk door teams heen, met behulp van een bekende Kanban-bordweergave.\",\n  \"WelcomePage.Explore.Button\": \"Start een rondleiding\",\n  \"WelcomePage.Heading\": \"Welkom bij Boards\",\n  \"WelcomePage.NoThanks.Text\": \"Nee bedankt, ik zoek het zelf wel uit\",\n  \"WelcomePage.StartUsingIt.Text\": \"Ga het gebruiken\",\n  \"Workspace.editing-board-template\": \"Je bent een bordsjabloon aan het bewerken.\",\n  \"badge.guest\": \"Gast\",\n  \"boardSelector.confirm-link-board\": \"Koppel bord aan kanaal\",\n  \"boardSelector.confirm-link-board-button\": \"Ja, koppel het bord\",\n  \"boardSelector.confirm-link-board-subtext\": \"Wanneer je \\\"{boardName}\\\" aan het kanaal koppelt, kunnen alle leden van het kanaal (bestaande en nieuwe) het bewerken. Dit sluit leden die gast zijn uit. Je kan de koppeling van een bord naar een kanaal op elk moment ongedaan maken.\",\n  \"boardSelector.confirm-link-board-subtext-with-other-channel\": \"Wanneer je \\\"{boardName}\\\" aan het kanaal koppelt zullen alle leden van het kanaal (bestaande en nieuwe) het kunnen bewerken. Dit sluit leden die gast zijn uit. {lineBreak} Dit board is momenteel gekoppeld aan een ander kanaal. Het zal worden ontkoppeld als je ervoor kiest om het hier te koppelen.\",\n  \"boardSelector.create-a-board\": \"Maak een bord\",\n  \"boardSelector.link\": \"Link\",\n  \"boardSelector.search-for-boards\": \"Zoeken naar borden\",\n  \"boardSelector.title\": \"Link borden\",\n  \"boardSelector.unlink\": \"Link ongedaan maken\",\n  \"calendar.month\": \"Maand\",\n  \"calendar.today\": \"VANDAAG\",\n  \"calendar.week\": \"Week\",\n  \"centerPanel.undefined\": \"Geen {propertyName}\",\n  \"centerPanel.unknown-user\": \"Onbekende gebruiker\",\n  \"cloudMessage.learn-more\": \"Meer info\",\n  \"createImageBlock.failed\": \"Dit bestand kon niet worden geüpload omdat de bestandslimiet wordt overschreden.\",\n  \"default-properties.badges\": \"Opmerkingen en beschrijving\",\n  \"default-properties.title\": \"Titel\",\n  \"error.back-to-home\": \"Terug naar startpagina\",\n  \"error.back-to-team\": \"Terug naar team\",\n  \"error.board-not-found\": \"Board niet gevonden.\",\n  \"error.go-login\": \"Aanmelden\",\n  \"error.invalid-read-only-board\": \"Je hebt geen toegang tot dit board. Meld je aan om toegang te krijgen tot Boards.\",\n  \"error.not-logged-in\": \"Jouw sessie is misschien verlopen of je bent niet ingelogd. Meldt je opnieuw aan om toegang te krijgen tot Boards.\",\n  \"error.page.title\": \"Sorry, er ging iets mis\",\n  \"error.team-undefined\": \"Geen geldig team.\",\n  \"error.unknown\": \"Er trad een fout op.\",\n  \"generic.previous\": \"Vorige\",\n  \"guest-no-board.subtitle\": \"Je hebt nog geen toegang tot een board in dit team, wacht tot iemand je toevoegt aan een board.\",\n  \"guest-no-board.title\": \"Nog geen borden\",\n  \"imagePaste.upload-failed\": \"Sommige bestanden zijn niet geüpload omdat de limiet voor de bestandsgrootte is bereikt.\",\n  \"limitedCard.title\": \"Verborgen kaarten\",\n  \"login.log-in-button\": \"Aanmelden\",\n  \"login.log-in-title\": \"Aanmelden\",\n  \"login.register-button\": \"of maak een account aan als je er nog geen hebt\",\n  \"new_channel_modal.create_board.empty_board_description\": \"Maak een nieuw leeg bord\",\n  \"new_channel_modal.create_board.empty_board_title\": \"Leeg bord\",\n  \"new_channel_modal.create_board.select_template_placeholder\": \"Kies een sjabloon\",\n  \"new_channel_modal.create_board.title\": \"Maak een bord voor dit kanaal\",\n  \"notification-box-card-limit-reached.close-tooltip\": \"Snooze voor 10 dagen\",\n  \"notification-box-card-limit-reached.contact-link\": \"breng je beheerder op de hoogte\",\n  \"notification-box-card-limit-reached.link\": \"Upgrade naar een betaald plan\",\n  \"notification-box-card-limit-reached.title\": \"{cards} kaarten verborgen van board\",\n  \"notification-box-cards-hidden.title\": \"Deze actie heeft een andere kaart verborgen\",\n  \"notification-box.card-limit-reached.not-admin.text\": \"Om toegang te krijgen tot gearchiveerde kaarten, neem contact op met {contactLink} om te upgraden naar een betaald plan.\",\n  \"notification-box.card-limit-reached.text\": \"Limiet van aantal kaarten bereikt, om oudere kaarten te bekijken, {link}\",\n  \"person.add-user-to-board\": \"{username} toevoegen aan bord\",\n  \"person.add-user-to-board-confirm-button\": \"Toevoegen aan bord\",\n  \"person.add-user-to-board-permissions\": \"Machtigingen\",\n  \"person.add-user-to-board-question\": \"Wil je {username} toevoegen aan het bord?\",\n  \"person.add-user-to-board-warning\": \"{username} is geen lid van het bord en zal er geen meldingen over ontvangen.\",\n  \"register.login-button\": \"of meldt je aan als je al een account hebt\",\n  \"register.signup-title\": \"Maak een nieuw account\",\n  \"rhs-board-non-admin-msg\": \"Je bent geen beheerder van het bord\",\n  \"rhs-boards.add\": \"Toevoegen\",\n  \"rhs-boards.dm\": \"DM\",\n  \"rhs-boards.gm\": \"GM\",\n  \"rhs-boards.header.dm\": \"dit directe bericht\",\n  \"rhs-boards.header.gm\": \"dit groepsbericht\",\n  \"rhs-boards.last-update-at\": \"Laatste wijziging op: {datetime}\",\n  \"rhs-boards.link-boards-to-channel\": \"Koppel borden aan {channelName}\",\n  \"rhs-boards.linked-boards\": \"Gekoppelde borden\",\n  \"rhs-boards.no-boards-linked-to-channel\": \"Er zijn nog geen borden gekoppeld aan {channelName}\",\n  \"rhs-boards.no-boards-linked-to-channel-description\": \"Boards is een projectmanagementtool die helpt bij het definiëren, organiseren, volgen en beheren van werk door teams heen, met behulp van een bekende kanban-bordweergave.\",\n  \"rhs-boards.unlink-board\": \"Bord loskoppelen\",\n  \"rhs-boards.unlink-board1\": \"Bord loskoppelen\",\n  \"rhs-channel-boards-header.title\": \"Boards\",\n  \"share-board.publish\": \"Publiceren\",\n  \"share-board.share\": \"Delen\",\n  \"shareBoard.channels-select-group\": \"Kanalen\",\n  \"shareBoard.confirm-change-team-role.body\": \"Iedereen op dit board met een lagere machtiging dan de \\\"{role}\\\" rol zal <b>nu opwaarderen naar {role}</b>. Weet je zeker dat je de minimale rol voor het board wilt veranderen?\",\n  \"shareBoard.confirm-change-team-role.confirmBtnText\": \"Minimale rol van het bord wijzigen\",\n  \"shareBoard.confirm-change-team-role.title\": \"Minimale rol van het bord wijzigen\",\n  \"shareBoard.confirm-link-channel\": \"Bord koppelen aan kanaal\",\n  \"shareBoard.confirm-link-channel-button\": \"Kanaal koppelen\",\n  \"shareBoard.confirm-link-channel-button-with-other-channel\": \"Koppel en ontkoppel hier\",\n  \"shareBoard.confirm-link-channel-subtext\": \"Wanneer je het bord aan het kanaal koppelt zullen alle leden van het kanaal (bestaande en nieuwe) het kunnen bewerken. Dit sluit leden die gast zijn uit.\",\n  \"shareBoard.confirm-link-channel-subtext-with-other-channel\": \"Wanneer je een kanaal aan een bord koppelt zullen alle leden van het kanaal (bestaande en nieuwe) het kunnen bewerken.Dit sluit leden die gast zijn uit. {lineBreak} Dit board is momenteel gekoppeld aan een ander kanaal. Het zal worden ontkoppeld als je ervoor kiest om het hier te koppelen.\",\n  \"shareBoard.confirm-unlink.body\": \"Wanneer een kanaal afkoppelt van een bord zullen alle leden van het kanaal (bestaande en nieuwe) geen toegang meer hebben tot ze apart toegang gegeven worden.\",\n  \"shareBoard.confirm-unlink.confirmBtnText\": \"Kanaal ontkoppelen\",\n  \"shareBoard.confirm-unlink.title\": \"Kanaal loskoppelen van bord\",\n  \"shareBoard.lastAdmin\": \"Besturen moeten ten minste één beheerder hebben\",\n  \"shareBoard.members-select-group\": \"Leden\",\n  \"shareBoard.unknown-channel-display-name\": \"Onbekend kanaal\",\n  \"tutorial_tip.finish_tour\": \"Klaar\",\n  \"tutorial_tip.got_it\": \"Begrepen\",\n  \"tutorial_tip.ok\": \"Volgende\",\n  \"tutorial_tip.out\": \"Schakel deze tips uit.\",\n  \"tutorial_tip.seen\": \"Heb je dit al gezien?\"\n}\n"
  },
  {
    "path": "webapp/i18n/oc.json",
    "content": "{\n  \"BoardComponent.add-a-group\": \"+ Apondre un grop\",\n  \"BoardComponent.delete\": \"Suprimir\",\n  \"BoardComponent.hidden-columns\": \"Colomnas rescondudas\",\n  \"BoardComponent.hide\": \"Rescondre\",\n  \"BoardComponent.new\": \"+ Nòu\",\n  \"BoardComponent.no-property\": \"Cap de {property}\",\n  \"BoardComponent.no-property-title\": \"Los elements sens proprietats {property} seràn plaçats aquí. Se pòt pas suprimir aquesta colomna.\",\n  \"BoardComponent.show\": \"Mostrar\",\n  \"BoardPage.syncFailed\": \"Lo tablèu es benlèu suprimit o l’accès es revocat.\",\n  \"BoardsUnfurl.Remainder\": \"+{remainder} de mai\",\n  \"BoardsUnfurl.Updated\": \"Actualizat {time}\",\n  \"CardDetail.add-content\": \"Apondre contengut\",\n  \"CardDetail.add-icon\": \"Apondre una icòna\",\n  \"CardDetail.add-property\": \"+ Apondre una proprietat\",\n  \"CardDetail.addCardText\": \"apondre una zòna de tèxt\",\n  \"CardDetail.moveContent\": \"desplaçar contengut de la carta\",\n  \"CardDetail.new-comment-placeholder\": \"Apondre un comentari...\",\n  \"CardDetailProperty.confirm-delete-subtext\": \"Volètz vertadièrament suprimir la proprietat « {propertyName} » ? La supression levarà la proprietat de totas las cartas d’aquesta tablèu.\",\n  \"CardDetailProperty.property-deleted\": \"Supression de {propertyName} reüssida !\",\n  \"CardDialog.editing-template\": \"Sètz a modificar un modèl.\",\n  \"CardDialog.nocard\": \"Aquesta zòna existís pas o es pas accessibla.\",\n  \"ColorOption.selectColor\": \"Seleccionar la color {color}\",\n  \"Comment.delete\": \"Suprimir\",\n  \"CommentsList.send\": \"Enviar\",\n  \"ConfirmationDialog.cancel-action\": \"Anullar\",\n  \"ContentBlock.Delete\": \"Suprimir\",\n  \"ContentBlock.DeleteAction\": \"suprimir\",\n  \"ContentBlock.addElement\": \"apondre {type}\",\n  \"ContentBlock.checkbox\": \"cassa de marcar\",\n  \"ContentBlock.divider\": \"separador\",\n  \"ContentBlock.editCardCheckbox\": \"alternar-cassa\",\n  \"ContentBlock.editCardCheckboxText\": \"modificar lo tèxt de la carta\",\n  \"ContentBlock.editCardText\": \"modificar lo tèxt de la carta\",\n  \"ContentBlock.editText\": \"Modificar lo tèxt...\",\n  \"ContentBlock.image\": \"imatge\",\n  \"ContentBlock.insertAbove\": \"Inserir al dessús\",\n  \"ContentBlock.moveDown\": \"Desplaçar al dejós\",\n  \"ContentBlock.moveUp\": \"Desplaçar al dessús\",\n  \"ContentBlock.text\": \"tèxt\",\n  \"Dialog.closeDialog\": \"Tampar la fenèstra de dialòg\",\n  \"EditableDayPicker.today\": \"Uèi\",\n  \"Error.websocket-closed\": \"Connexion al websocket tampada, connexion interrompuda. S’aquò ten de se produire, verificatz la configuracion del servidor o del servidor mandatari.\",\n  \"Filter.includes\": \"inclutz\",\n  \"Filter.is-empty\": \"es void\",\n  \"Filter.is-not-empty\": \"es pas void\",\n  \"Filter.not-includes\": \"inclutz pas\",\n  \"FilterComponent.add-filter\": \"+ Apondre un filtre\",\n  \"FilterComponent.delete\": \"Suprimir\",\n  \"GroupBy.ungroup\": \"Desgropar\",\n  \"KanbanCard.untitled\": \"Sens títol\",\n  \"Mutator.new-card-from-template\": \"zòna novèla a partir d’un modèl\",\n  \"Mutator.new-template-from-card\": \"modèl novèl a partir d’una zòna\",\n  \"PropertyMenu.Delete\": \"Suprimir\",\n  \"PropertyMenu.changeType\": \"Modificar lo tipe de proprietat\",\n  \"PropertyMenu.selectType\": \"Seleccionar tipe de proprietat\",\n  \"PropertyMenu.typeTitle\": \"Tipe\",\n  \"PropertyType.Checkbox\": \"Casa de marcar\",\n  \"PropertyType.CreatedBy\": \"Creat per\",\n  \"PropertyType.CreatedTime\": \"Data de creacion\",\n  \"PropertyType.Date\": \"Data\",\n  \"PropertyType.Email\": \"Adreça e-mail\",\n  \"PropertyType.MultiSelect\": \"Seleccion multipla\",\n  \"PropertyType.Number\": \"Nombre\",\n  \"PropertyType.Person\": \"Persona\",\n  \"PropertyType.Phone\": \"Telefòn\",\n  \"PropertyType.Select\": \"Lista\",\n  \"PropertyType.Text\": \"Tèxt\",\n  \"PropertyType.UpdatedBy\": \"Darrièra actualizacion per\",\n  \"PropertyType.UpdatedTime\": \"Data de darrièra actualizacion\",\n  \"PropertyValueElement.empty\": \"Void\",\n  \"RegistrationLink.confirmRegenerateToken\": \"Aquò desactivarà los ligams de partiment existents. Contunhar ?\",\n  \"RegistrationLink.copiedLink\": \"Copiat !\",\n  \"RegistrationLink.copyLink\": \"Copiar lo ligam\",\n  \"RegistrationLink.description\": \"Partejatz aqueste ligam per que d’autres pòscan crear un compte :\",\n  \"RegistrationLink.regenerateToken\": \"Generar un geton novèl\",\n  \"RegistrationLink.tokenRegenerated\": \"Un ligam novèl d’inscripcion es estat creat\",\n  \"ShareBoard.confirmRegenerateToken\": \"Aquò desactivarà los ligams de partiment existents. Contunhar ?\",\n  \"ShareBoard.copiedLink\": \"Copiat !\",\n  \"ShareBoard.copyLink\": \"Copiar lo ligam\",\n  \"ShareBoard.tokenRegenrated\": \"Geton regenerat\",\n  \"Sidebar.about\": \"A prepaus de Focalboard\",\n  \"Sidebar.add-board\": \"+ Apondre un tablèu\",\n  \"Sidebar.changePassword\": \"Modificar lo senhal\",\n  \"Sidebar.delete-board\": \"Suprimir lo tablèu\",\n  \"Sidebar.export-archive\": \"Exportar un archiu\",\n  \"Sidebar.import-archive\": \"Importar un archiu\",\n  \"Sidebar.invite-users\": \"Convidar utilizaires\",\n  \"Sidebar.logout\": \"Se desconnectar\",\n  \"Sidebar.random-icons\": \"Icònas aleatòrias\",\n  \"Sidebar.set-language\": \"Definir la lenga\",\n  \"Sidebar.set-theme\": \"Causir lo tèma\",\n  \"Sidebar.settings\": \"Paramètres\",\n  \"Sidebar.untitled-board\": \"(Tablèu sens títol)\",\n  \"TableComponent.add-icon\": \"Apondre una icòna\",\n  \"TableComponent.name\": \"Nom\",\n  \"TableComponent.plus-new\": \"+ Novèl\",\n  \"TableHeaderMenu.delete\": \"Suprimir\",\n  \"TableHeaderMenu.duplicate\": \"Duplicar\",\n  \"TableHeaderMenu.hide\": \"Rescondre\",\n  \"TableHeaderMenu.insert-left\": \"Inserir a esquèrra\",\n  \"TableHeaderMenu.insert-right\": \"Inserir a drecha\",\n  \"TableHeaderMenu.sort-ascending\": \"Tria ascendenta\",\n  \"TableHeaderMenu.sort-descending\": \"Tria descendenta\",\n  \"TableRow.open\": \"Dobrir\",\n  \"TopBar.give-feedback\": \"Far un retorn\",\n  \"ValueSelector.valueSelector\": \"Selector de valor\",\n  \"ValueSelectorLabel.openMenu\": \"Dobrir lo menú\",\n  \"View.AddView\": \"Apondre una vista\",\n  \"View.Board\": \"Tablèu\",\n  \"View.DeleteView\": \"Suprimir la vista\",\n  \"View.DuplicateView\": \"Duplicar la vista\",\n  \"View.Gallery\": \"Galariá\",\n  \"View.NewBoardTitle\": \"Vista en tablèu\",\n  \"View.NewGalleryTitle\": \"Vista galariá\",\n  \"View.NewTableTitle\": \"Vista en taula\",\n  \"View.Table\": \"Tablèu\",\n  \"ViewHeader.add-template\": \"Modèl novèl\",\n  \"ViewHeader.delete-template\": \"Suprimir\",\n  \"ViewHeader.edit-template\": \"Modificar\",\n  \"ViewHeader.empty-card\": \"Zòna voida\",\n  \"ViewHeader.export-board-archive\": \"Exportar l’archiu del tablèu\",\n  \"ViewHeader.export-complete\": \"Export acabat !\",\n  \"ViewHeader.export-csv\": \"Exportar al format CSV\",\n  \"ViewHeader.export-failed\": \"Export fracassat !\",\n  \"ViewHeader.filter\": \"Filtre\",\n  \"ViewHeader.group-by\": \"Agropar per : {property}\",\n  \"ViewHeader.new\": \"Novèl\",\n  \"ViewHeader.properties\": \"Proprietats\",\n  \"ViewHeader.search-text\": \"Recercar de tèxt\",\n  \"ViewHeader.select-a-template\": \"Seleccionar un modèl\",\n  \"ViewHeader.set-default-template\": \"Definir per defaut\",\n  \"ViewHeader.sort\": \"Triar\",\n  \"ViewHeader.untitled\": \"Sens títol\",\n  \"ViewTitle.hide-description\": \"rescondre la descripcion\",\n  \"ViewTitle.pick-icon\": \"Causir una icòna\",\n  \"ViewTitle.random-icon\": \"Aleatòria\",\n  \"ViewTitle.remove-icon\": \"Suprimir l'icòna\",\n  \"ViewTitle.show-description\": \"mostrar la descripcion\",\n  \"ViewTitle.untitled-board\": \"Tablèu sens títol\",\n  \"WelcomePage.Explore.Button\": \"Explorar\",\n  \"WelcomePage.Heading\": \"La benvengudas als tablèus\",\n  \"Workspace.editing-board-template\": \"Modificatz un modèl de tablèu.\",\n  \"default-properties.title\": \"Títol\",\n  \"login.log-in-button\": \"Connexion\",\n  \"login.log-in-title\": \"Connexion\",\n  \"login.register-button\": \"o creatz un compte se n’avètz pas un\",\n  \"register.login-button\": \"o connectatz-vos s’avètz un compte\",\n  \"register.signup-title\": \"Vos inscriure per aver un compte\"\n}\n"
  },
  {
    "path": "webapp/i18n/pl.json",
    "content": "{\n  \"AdminBadge.SystemAdmin\": \"Administrator\",\n  \"AdminBadge.TeamAdmin\": \"Administrator Zespołu\",\n  \"AppBar.Tooltip\": \"Przełączanie Podlinkowanych Tablic\",\n  \"Attachment.Attachment-title\": \"Załącznik\",\n  \"AttachmentBlock.DeleteAction\": \"usuń\",\n  \"AttachmentBlock.addElement\": \"dodaj {type}\",\n  \"AttachmentBlock.delete\": \"Załącznik usunięty.\",\n  \"AttachmentBlock.failed\": \"Ten plik nie mógł zostać przesłany, ponieważ został osiągnięty limit rozmiaru pliku.\",\n  \"AttachmentBlock.upload\": \"Przesyłanie załączników.\",\n  \"AttachmentBlock.uploadSuccess\": \"Załącznik przesłany.\",\n  \"AttachmentElement.delete-confirmation-dialog-button-text\": \"Usuń\",\n  \"AttachmentElement.download\": \"Pobierz\",\n  \"AttachmentElement.upload-percentage\": \"Przesyłanie...({uploadPercent}%)\",\n  \"BoardComponent.add-a-group\": \"+ Dodaj grupę\",\n  \"BoardComponent.delete\": \"Usuń\",\n  \"BoardComponent.hidden-columns\": \"Ukryte kolumny\",\n  \"BoardComponent.hide\": \"Ukryj\",\n  \"BoardComponent.new\": \"+ Nowy\",\n  \"BoardComponent.no-property\": \"Brak {property}\",\n  \"BoardComponent.no-property-title\": \"Elementy z pustą właściwością {property} trafią tutaj. Tej kolumny nie można usunąć.\",\n  \"BoardComponent.show\": \"Pokaż\",\n  \"BoardMember.schemeAdmin\": \"Administrator\",\n  \"BoardMember.schemeCommenter\": \"Komentujący\",\n  \"BoardMember.schemeEditor\": \"Redaktor\",\n  \"BoardMember.schemeNone\": \"Brak\",\n  \"BoardMember.schemeViewer\": \"Obserwator\",\n  \"BoardMember.unlinkChannel\": \"Odłącz\",\n  \"BoardPage.newVersion\": \"Dostępna jest nowa wersja tablic. Naciśnij tutaj, aby przeładować.\",\n  \"BoardPage.syncFailed\": \"Tablica mogła zostać usunięta lub dostęp do niej cofnięty.\",\n  \"BoardTemplateSelector.add-template\": \"Utwórz nowy szablon\",\n  \"BoardTemplateSelector.create-empty-board\": \"Utwórz pustą tablicę\",\n  \"BoardTemplateSelector.delete-template\": \"Usuń\",\n  \"BoardTemplateSelector.description\": \"Dodaj tablicę do paska bocznego używając dowolnego z poniższych szablonów lub zacznij od nowa.\",\n  \"BoardTemplateSelector.edit-template\": \"Edytuj\",\n  \"BoardTemplateSelector.plugin.no-content-description\": \"Dodaj tablicę do paska bocznego używając jednego z poniższych szablonów lub zacznij od nowa.\",\n  \"BoardTemplateSelector.plugin.no-content-title\": \"Utwórz tablicę\",\n  \"BoardTemplateSelector.title\": \"Utwórz tablicę\",\n  \"BoardTemplateSelector.use-this-template\": \"Użyj tego szablonu\",\n  \"BoardsSwitcher.Title\": \"Wyszukiwanie tablic\",\n  \"BoardsUnfurl.Limited\": \"Dodatkowe szczegóły są ukryte ze względu na archiwizację karty\",\n  \"BoardsUnfurl.Remainder\": \"+{remainder} więcej\",\n  \"BoardsUnfurl.Updated\": \"Zaktualizowano {time}\",\n  \"Calculations.Options.average.displayName\": \"Średnia\",\n  \"Calculations.Options.average.label\": \"Średnia\",\n  \"Calculations.Options.count.displayName\": \"Liczba\",\n  \"Calculations.Options.count.label\": \"Liczba\",\n  \"Calculations.Options.countChecked.displayName\": \"Zaznaczone\",\n  \"Calculations.Options.countChecked.label\": \"Zaznaczono licznik\",\n  \"Calculations.Options.countUnchecked.displayName\": \"Niezaznaczony\",\n  \"Calculations.Options.countUnchecked.label\": \"Licznik niezaznaczony\",\n  \"Calculations.Options.countUniqueValue.displayName\": \"Unikatowe\",\n  \"Calculations.Options.countUniqueValue.label\": \"Policz wartości unikatowe\",\n  \"Calculations.Options.countValue.displayName\": \"Wartości\",\n  \"Calculations.Options.countValue.label\": \"Licznik wartości\",\n  \"Calculations.Options.dateRange.displayName\": \"Zakres\",\n  \"Calculations.Options.dateRange.label\": \"Zakres\",\n  \"Calculations.Options.earliest.displayName\": \"Wcześniejszy\",\n  \"Calculations.Options.earliest.label\": \"Wcześniejszy\",\n  \"Calculations.Options.latest.displayName\": \"Ostatni\",\n  \"Calculations.Options.latest.label\": \"Ostatni\",\n  \"Calculations.Options.max.displayName\": \"Maks.\",\n  \"Calculations.Options.max.label\": \"Maks.\",\n  \"Calculations.Options.median.displayName\": \"Mediana\",\n  \"Calculations.Options.median.label\": \"Mediana\",\n  \"Calculations.Options.min.displayName\": \"Min.\",\n  \"Calculations.Options.min.label\": \"Min.\",\n  \"Calculations.Options.none.displayName\": \"Policz\",\n  \"Calculations.Options.none.label\": \"Brak\",\n  \"Calculations.Options.percentChecked.displayName\": \"Zaznaczony\",\n  \"Calculations.Options.percentChecked.label\": \"Procent sprawdzonych\",\n  \"Calculations.Options.percentUnchecked.displayName\": \"Niezaznaczony\",\n  \"Calculations.Options.percentUnchecked.label\": \"Procent nie zaznaczonych\",\n  \"Calculations.Options.range.displayName\": \"Zakres\",\n  \"Calculations.Options.range.label\": \"Zakres\",\n  \"Calculations.Options.sum.displayName\": \"Suma\",\n  \"Calculations.Options.sum.label\": \"Suma\",\n  \"CalendarCard.untitled\": \"Bez tytułu\",\n  \"CardActionsMenu.copiedLink\": \"Skopiowano!\",\n  \"CardActionsMenu.copyLink\": \"Kopiuj odnośnik\",\n  \"CardActionsMenu.delete\": \"Usuń\",\n  \"CardActionsMenu.duplicate\": \"Duplikuj\",\n  \"CardBadges.title-checkboxes\": \"Pola wyboru\",\n  \"CardBadges.title-comments\": \"Komentarze\",\n  \"CardBadges.title-description\": \"Ta karta ma opis\",\n  \"CardDetail.Attach\": \"Załącz\",\n  \"CardDetail.Follow\": \"Obserwuj\",\n  \"CardDetail.Following\": \"Obserwowane\",\n  \"CardDetail.add-content\": \"Dodaj treść\",\n  \"CardDetail.add-icon\": \"Dodaj ikonę\",\n  \"CardDetail.add-property\": \"+ Dodaj właściwość\",\n  \"CardDetail.addCardText\": \"dodaj tekst karty\",\n  \"CardDetail.limited-body\": \"Uaktualnij do naszego planu Professional lub Enterprise.\",\n  \"CardDetail.limited-button\": \"Zmień plan\",\n  \"CardDetail.limited-title\": \"Ta karta jest ukryta\",\n  \"CardDetail.moveContent\": \"Przenieś zawartość karty\",\n  \"CardDetail.new-comment-placeholder\": \"Dodaj komentarz…\",\n  \"CardDetailProperty.confirm-delete-heading\": \"Potwierdzanie usunięcia właściwości\",\n  \"CardDetailProperty.confirm-delete-subtext\": \"Na pewno chcesz usunąć właściwość „{propertyName}”? Usunięcie tej właściwości spowoduje usunięcie jej z wszystkich kart na tej tablicy.\",\n  \"CardDetailProperty.confirm-property-name-change-subtext\": \"Na pewno chcesz zmienić właściwość „{propertyName}” {customText}? Wpłynie to na wartości na {numOfCards} kartach na tej tablicy i może spowodować utratę danych.\",\n  \"CardDetailProperty.confirm-property-type-change\": \"Potwierdzenie zmiany typu właściwości\",\n  \"CardDetailProperty.delete-action-button\": \"Usuń\",\n  \"CardDetailProperty.property-change-action-button\": \"Zmień właściwość\",\n  \"CardDetailProperty.property-changed\": \"Zmieniono właściwość pomyślnie!\",\n  \"CardDetailProperty.property-deleted\": \"Usunięto pomyślnie {propertyName}!\",\n  \"CardDetailProperty.property-name-change-subtext\": \"typ z \\\"{oldPropType}\\\" do \\\"{newPropType}\\\"\",\n  \"CardDetial.limited-link\": \"Dowiedz się więcej o naszych planach.\",\n  \"CardDialog.delete-confirmation-dialog-attachment\": \"Potwierdź usunięcie załącznika\",\n  \"CardDialog.delete-confirmation-dialog-button-text\": \"Usuń\",\n  \"CardDialog.delete-confirmation-dialog-heading\": \"Potwierdź usunięcie karty\",\n  \"CardDialog.editing-template\": \"Edytujesz szablon.\",\n  \"CardDialog.nocard\": \"Ta karta nie istnieje lub jest niedostępna.\",\n  \"Categories.CreateCategoryDialog.CancelText\": \"Anuluj\",\n  \"Categories.CreateCategoryDialog.CreateText\": \"Utwórz\",\n  \"Categories.CreateCategoryDialog.Placeholder\": \"Nazwij kategorię\",\n  \"Categories.CreateCategoryDialog.UpdateText\": \"Zmień\",\n  \"CenterPanel.Login\": \"Logowanie\",\n  \"CenterPanel.Share\": \"Udostępnij\",\n  \"ChannelIntro.CreateBoard\": \"Utwórz tablicę\",\n  \"ColorOption.selectColor\": \"Wybierz Kolor {color}\",\n  \"Comment.delete\": \"Usuń\",\n  \"CommentsList.send\": \"Wyślij\",\n  \"ConfirmPerson.empty\": \"Puste\",\n  \"ConfirmPerson.search\": \"Szukaj...\",\n  \"ConfirmationDialog.cancel-action\": \"Anuluj\",\n  \"ConfirmationDialog.confirm-action\": \"Potwierdź\",\n  \"ContentBlock.Delete\": \"Usuń\",\n  \"ContentBlock.DeleteAction\": \"usuń\",\n  \"ContentBlock.addElement\": \"dodaj {type}\",\n  \"ContentBlock.checkbox\": \"pole wyboru\",\n  \"ContentBlock.divider\": \"dzielnik\",\n  \"ContentBlock.editCardCheckbox\": \"pole wyboru\",\n  \"ContentBlock.editCardCheckboxText\": \"edytuj tekst karty\",\n  \"ContentBlock.editCardText\": \"edytuj tekst karty\",\n  \"ContentBlock.editText\": \"Edytuj tekst...\",\n  \"ContentBlock.image\": \"obraz\",\n  \"ContentBlock.insertAbove\": \"Wstaw powyżej\",\n  \"ContentBlock.moveBlock\": \"przenieś zawartość karty\",\n  \"ContentBlock.moveDown\": \"Przenieś w dół\",\n  \"ContentBlock.moveUp\": \"Przenieś w górę\",\n  \"ContentBlock.text\": \"tekst\",\n  \"DateFilter.empty\": \"Puste\",\n  \"DateRange.clear\": \"Wyczyść\",\n  \"DateRange.empty\": \"Puste\",\n  \"DateRange.endDate\": \"Data końcowa\",\n  \"DateRange.today\": \"Dzisiaj\",\n  \"DeleteBoardDialog.confirm-cancel\": \"Anuluj\",\n  \"DeleteBoardDialog.confirm-delete\": \"Usuń\",\n  \"DeleteBoardDialog.confirm-info\": \"Na pewno chcesz usunąć tablicę „{boardTitle}”? Usunięcie tej tablicy spowoduje usunięcie z niej wszystkich kart.\",\n  \"DeleteBoardDialog.confirm-info-template\": \"Na pewno chcesz usunąć szablon tablicy „{boardTitle}”?\",\n  \"DeleteBoardDialog.confirm-tite\": \"Potwierdzenie usunięcia tablicy\",\n  \"DeleteBoardDialog.confirm-tite-template\": \"Potwierdzenie usunięcia szablonu tablicy\",\n  \"Dialog.closeDialog\": \"Zamknij okno dialogowe\",\n  \"EditableDayPicker.today\": \"Dzisiaj\",\n  \"Error.mobileweb\": \"Strona internetowa dla urządzeń mobilnych jest obecnie we wczesnej fazie testów. Nie wszystkie funkcje mogą być dostępne.\",\n  \"Error.websocket-closed\": \"Połączenie WebSocket zostało zamknięte – połączenie przerwane. Jeśli problem się powtarza, sprawdź konfigurację swojego serwera lub serwera pośredniczącego Web.\",\n  \"Filter.contains\": \"zawiera\",\n  \"Filter.ends-with\": \"kończy się na\",\n  \"Filter.includes\": \"zawiera\",\n  \"Filter.is\": \"jest\",\n  \"Filter.is-after\": \"jest po\",\n  \"Filter.is-before\": \"jest przed\",\n  \"Filter.is-empty\": \"jest pusty\",\n  \"Filter.is-not-empty\": \"nie jest pusty\",\n  \"Filter.is-not-set\": \"nie jest ustawiony\",\n  \"Filter.is-set\": \"jest ustawiony\",\n  \"Filter.isafter\": \"jest po\",\n  \"Filter.isbefore\": \"jest przed\",\n  \"Filter.not-contains\": \"nie zawiera\",\n  \"Filter.not-ends-with\": \"nie kończy się na\",\n  \"Filter.not-includes\": \"nie zawiera\",\n  \"Filter.not-starts-with\": \"nie zaczyna się od\",\n  \"Filter.starts-with\": \"zaczyna się od\",\n  \"FilterByText.placeholder\": \"tekst filtra\",\n  \"FilterComponent.add-filter\": \"+ Dodaj filtr\",\n  \"FilterComponent.delete\": \"Usuń\",\n  \"FilterValue.empty\": \"(pusty)\",\n  \"FindBoardsDialog.IntroText\": \"Wyszukiwanie tablic\",\n  \"FindBoardsDialog.NoResultsFor\": \"Brak wyników dla „{searchQuery}”\",\n  \"FindBoardsDialog.NoResultsSubtext\": \"Sprawdź pisownię lub spróbuj innego wyszukiwania.\",\n  \"FindBoardsDialog.SubTitle\": \"Wpisz, aby znaleźć tablicę. Użyj <b>GÓRA/DÓŁ</b>, aby przeglądać. <b>ENTER</b>, aby wybrać, <b>ESC</b>, aby odrzucić\",\n  \"FindBoardsDialog.Title\": \"Znajdź tablice\",\n  \"GroupBy.hideEmptyGroups\": \"Ukryj {count} pustych grup\",\n  \"GroupBy.showHiddenGroups\": \"Pokaż {count} ukrytych grup\",\n  \"GroupBy.ungroup\": \"Rozgrupuj\",\n  \"HideBoard.MenuOption\": \"Ukryj tablicę\",\n  \"KanbanCard.untitled\": \"Bez tytułu\",\n  \"MentionSuggestion.is-not-board-member\": \"(nie jest członkiem tablicy)\",\n  \"Mutator.new-board-from-template\": \"nowa tablica z szablonu\",\n  \"Mutator.new-card-from-template\": \"nowa karta z szablonu\",\n  \"Mutator.new-template-from-card\": \"nowy szablon z karty\",\n  \"OnboardingTour.AddComments.Body\": \"Możesz komentować zagadnienia, a nawet @wspominać innych użytkowników Mattermost, aby uzyskać ich uwagę.\",\n  \"OnboardingTour.AddComments.Title\": \"Dodawanie komentarzy\",\n  \"OnboardingTour.AddDescription.Body\": \"Dodaj opis do karty, aby członkowie zespołu wiedzieli, czego ona dotyczy .\",\n  \"OnboardingTour.AddDescription.Title\": \"Dodaj opis\",\n  \"OnboardingTour.AddProperties.Body\": \"Dodawaj różne właściwości do kart, aby zwiększyć ich moc.\",\n  \"OnboardingTour.AddProperties.Title\": \"Dodawanie właściwości\",\n  \"OnboardingTour.AddView.Body\": \"Tutaj utworzysz nowy widok, którym uporządkujesz tablicę za pomocą różnych układów.\",\n  \"OnboardingTour.AddView.Title\": \"Dodawanie nowego widoku\",\n  \"OnboardingTour.CopyLink.Body\": \"Karty można udostępniać członkom zespołu kopiując łącze i wklejając je w kanale, wiadomości prywatnej lub grupowej.\",\n  \"OnboardingTour.CopyLink.Title\": \"Kopiowanie odnośnika\",\n  \"OnboardingTour.OpenACard.Body\": \"Otwórz kartę i poznaj różne sposoby, dzięki którym zorganizujesz swoją pracę za pomocą tablic.\",\n  \"OnboardingTour.OpenACard.Title\": \"Otwieranie karty\",\n  \"OnboardingTour.ShareBoard.Body\": \"Możesz udostępniać swoją tablicę wewnętrznie, w ramach zespołu albo opublikować ją, aby była widoczna poza organizacją.\",\n  \"OnboardingTour.ShareBoard.Title\": \"Udostępnianie tablicy\",\n  \"PersonProperty.board-members\": \"Członkowie tablicy\",\n  \"PersonProperty.me\": \"Ja\",\n  \"PersonProperty.non-board-members\": \"Nie-członkowie tablicy\",\n  \"PropertyMenu.Delete\": \"Usuń\",\n  \"PropertyMenu.changeType\": \"Zmień typ właściwości\",\n  \"PropertyMenu.selectType\": \"Wybierz typ właściwości\",\n  \"PropertyMenu.typeTitle\": \"Typ\",\n  \"PropertyType.Checkbox\": \"Pole wyboru\",\n  \"PropertyType.CreatedBy\": \"Twórca\",\n  \"PropertyType.CreatedTime\": \"Czas utworzenia\",\n  \"PropertyType.Date\": \"Data\",\n  \"PropertyType.Email\": \"Email\",\n  \"PropertyType.MultiPerson\": \"Wiele osób\",\n  \"PropertyType.MultiSelect\": \"Pole wielokrotnego wyboru\",\n  \"PropertyType.Number\": \"Liczba\",\n  \"PropertyType.Person\": \"Osoba\",\n  \"PropertyType.Phone\": \"Telefon\",\n  \"PropertyType.Select\": \"Wybór\",\n  \"PropertyType.Text\": \"Tekst\",\n  \"PropertyType.Unknown\": \"Nieznany\",\n  \"PropertyType.UpdatedBy\": \"Ostatni aktualizujący\",\n  \"PropertyType.UpdatedTime\": \"Czas ostatniej aktualizacji\",\n  \"PropertyType.Url\": \"URL\",\n  \"PropertyValueElement.empty\": \"Puste\",\n  \"RegistrationLink.confirmRegenerateToken\": \"Unieważni to wcześniej udostępnione odnośniki. Kontynuować?\",\n  \"RegistrationLink.copiedLink\": \"Skopiowano!\",\n  \"RegistrationLink.copyLink\": \"Kopiuj odnośnik\",\n  \"RegistrationLink.description\": \"Udostępnij ten odnośnik innym, aby mogli utworzyć konta:\",\n  \"RegistrationLink.regenerateToken\": \"Wygeneruj ponownie poświadczenie\",\n  \"RegistrationLink.tokenRegenerated\": \"Wygenerowano ponownie odnośnik rejestracyjny\",\n  \"ShareBoard.PublishDescription\": \"Publikowanie i udostępnianie linku tylko-do-odczyt\\\" wszystkim w sieci.\",\n  \"ShareBoard.PublishTitle\": \"Opublikuj w sieci\",\n  \"ShareBoard.ShareInternal\": \"Udostępnij wewnętrznie\",\n  \"ShareBoard.ShareInternalDescription\": \"Użytkownicy z odpowiednimi uprawnieniami będą mogli korzystać z tego łącza.\",\n  \"ShareBoard.Title\": \"Udostępnij Tablicę\",\n  \"ShareBoard.confirmRegenerateToken\": \"Spowoduje to unieważnienie wcześniej udostępnionych linków. Kontynuować?\",\n  \"ShareBoard.copiedLink\": \"Skopiowane!\",\n  \"ShareBoard.copyLink\": \"Kopiuj odnośnik\",\n  \"ShareBoard.regenerate\": \"Wygeneruj ponownie token\",\n  \"ShareBoard.searchPlaceholder\": \"Wyszukiwanie osób\",\n  \"ShareBoard.teamPermissionsText\": \"Wszyscy w zespole {teamName}\",\n  \"ShareBoard.tokenRegenrated\": \"Token wygenerowany\",\n  \"ShareBoard.userPermissionsRemoveMemberText\": \"Usuń użytkownika\",\n  \"ShareBoard.userPermissionsYouText\": \"(Ty)\",\n  \"ShareTemplate.Title\": \"Udostępnij szablon\",\n  \"ShareTemplate.searchPlaceholder\": \"Wyszukiwanie osób\",\n  \"Sidebar.about\": \"O Focalboard\",\n  \"Sidebar.add-board\": \"+ Dodaj tablicę\",\n  \"Sidebar.changePassword\": \"Zmień hasło\",\n  \"Sidebar.delete-board\": \"Usuń tablicę\",\n  \"Sidebar.duplicate-board\": \"Duplikuj tablicę\",\n  \"Sidebar.export-archive\": \"Eksportuj archiwum\",\n  \"Sidebar.import\": \"Importuj\",\n  \"Sidebar.import-archive\": \"Importuj archiwum\",\n  \"Sidebar.invite-users\": \"Zaproś użytkowników\",\n  \"Sidebar.logout\": \"Wyloguj się\",\n  \"Sidebar.new-category.badge\": \"Nowy\",\n  \"Sidebar.new-category.drag-boards-cta\": \"Przenieś tutaj tablice...\",\n  \"Sidebar.no-boards-in-category\": \"Brak tablic wewnątrz\",\n  \"Sidebar.product-tour\": \"Przegląd\",\n  \"Sidebar.random-icons\": \"Losowe ikony\",\n  \"Sidebar.set-language\": \"Ustaw język\",\n  \"Sidebar.set-theme\": \"Ustaw motyw\",\n  \"Sidebar.settings\": \"Ustawienia\",\n  \"Sidebar.template-from-board\": \"Nowy szablon z tablicy\",\n  \"Sidebar.untitled-board\": \"(Tablica bez tytułu)\",\n  \"Sidebar.untitled-view\": \"(Widok bez Tytułu)\",\n  \"SidebarCategories.BlocksMenu.Move\": \"Przenieś Do...\",\n  \"SidebarCategories.CategoryMenu.CreateNew\": \"Utwórz Nową Kategorię\",\n  \"SidebarCategories.CategoryMenu.Delete\": \"Usuń Kategorię\",\n  \"SidebarCategories.CategoryMenu.DeleteModal.Body\": \"Tablice w <b>{categoryName}</b> zostaną przeniesione z powrotem do kategorii Tablice. Nie zostaniesz usunięty z żadnej tablicy.\",\n  \"SidebarCategories.CategoryMenu.DeleteModal.Title\": \"Usunąć tą kategorię?\",\n  \"SidebarCategories.CategoryMenu.Update\": \"Zmień nazwę Kategorii\",\n  \"SidebarTour.ManageCategories.Body\": \"Twórz i zarządzaj własnymi kategoriami. Kategorie są zależne od użytkownika, więc przeniesienie tablicy do twojej kategorii nie będzie miało wpływu na innych członków korzystających z tej samej tablicy.\",\n  \"SidebarTour.ManageCategories.Title\": \"Zarządzaj kategoriami\",\n  \"SidebarTour.SearchForBoards.Body\": \"Otwórz przełącznik tablic (Cmd/Ctrl + K), aby szybko wyszukać i dodać tablice do swojego paska bocznego.\",\n  \"SidebarTour.SearchForBoards.Title\": \"Wyszukiwanie tablic\",\n  \"SidebarTour.SidebarCategories.Body\": \"Wszystkie Twoje tablice są teraz uporządkowane w nowym pasku bocznym. Nie musisz już przełączać się między obszarami roboczymi. Jednorazowe niestandardowe kategorie oparte na Twoich poprzednich obszarach roboczych mogły zostać automatycznie utworzone dla Ciebie w ramach aktualizacji do wersji 7.2. Można je usunąć lub edytować według własnych preferencji.\",\n  \"SidebarTour.SidebarCategories.Link\": \"Dowiedź się więcej\",\n  \"SidebarTour.SidebarCategories.Title\": \"Kategorie paska bocznego\",\n  \"SiteStats.total_boards\": \"Tablice ogółem\",\n  \"SiteStats.total_cards\": \"Karty ogółem\",\n  \"TableComponent.add-icon\": \"Dodaj Ikonę\",\n  \"TableComponent.name\": \"Nazwa\",\n  \"TableComponent.plus-new\": \"+ Nowy\",\n  \"TableHeaderMenu.delete\": \"Usuń\",\n  \"TableHeaderMenu.duplicate\": \"Duplikuj\",\n  \"TableHeaderMenu.hide\": \"Ukryj\",\n  \"TableHeaderMenu.insert-left\": \"Wstaw z lewej\",\n  \"TableHeaderMenu.insert-right\": \"Wstaw z prawej\",\n  \"TableHeaderMenu.sort-ascending\": \"Sortuj rosnąco\",\n  \"TableHeaderMenu.sort-descending\": \"Sortuj malejąco\",\n  \"TableRow.DuplicateCard\": \"duplikuj kartę\",\n  \"TableRow.MoreOption\": \"Więcej działań\",\n  \"TableRow.open\": \"Otwórz\",\n  \"TopBar.give-feedback\": \"Przekaż informację zwrotną\",\n  \"URLProperty.copiedLink\": \"Skopiowane!\",\n  \"URLProperty.copy\": \"Kopia\",\n  \"URLProperty.edit\": \"Edycja\",\n  \"UndoRedoHotKeys.canRedo\": \"Powtórz\",\n  \"UndoRedoHotKeys.canRedo-with-description\": \"Powtórz {description}\",\n  \"UndoRedoHotKeys.canUndo\": \"Cofnij\",\n  \"UndoRedoHotKeys.canUndo-with-description\": \"Cofnij {description}\",\n  \"UndoRedoHotKeys.cannotRedo\": \"Nic do powtórzenia\",\n  \"UndoRedoHotKeys.cannotUndo\": \"Nic do cofnięcia\",\n  \"ValueSelector.noOptions\": \"Brak opcji. Zacznij wpisywać, aby dodać pierwszą z nich!\",\n  \"ValueSelector.valueSelector\": \"Selektor wartości\",\n  \"ValueSelectorLabel.openMenu\": \"Otwórz menu\",\n  \"VersionMessage.help\": \"Sprawdź co nowego w tej wersji.\",\n  \"VersionMessage.learn-more\": \"Dowiedź się więcej\",\n  \"View.AddView\": \"Dodaj widok\",\n  \"View.Board\": \"Tablica\",\n  \"View.DeleteView\": \"Usuń widok\",\n  \"View.DuplicateView\": \"Duplikuj widok\",\n  \"View.Gallery\": \"Galeria\",\n  \"View.NewBoardTitle\": \"Widok Tablicy\",\n  \"View.NewCalendarTitle\": \"Widok Kalendarza\",\n  \"View.NewGalleryTitle\": \"Widok galerii\",\n  \"View.NewTableTitle\": \"Widok tabeli\",\n  \"View.NewTemplateDefaultTitle\": \"Szablon bez tytułu\",\n  \"View.NewTemplateTitle\": \"Bez tytułu\",\n  \"View.Table\": \"Tabela\",\n  \"ViewHeader.add-template\": \"Nowy szablon\",\n  \"ViewHeader.delete-template\": \"Usuń\",\n  \"ViewHeader.display-by\": \"Wyświetl według: {property}\",\n  \"ViewHeader.edit-template\": \"Edytuj\",\n  \"ViewHeader.empty-card\": \"Wyczyść kartę\",\n  \"ViewHeader.export-board-archive\": \"Eksportuj archiwum tablicy\",\n  \"ViewHeader.export-complete\": \"Eksport zakończony!\",\n  \"ViewHeader.export-csv\": \"Eksportuj do CSV\",\n  \"ViewHeader.export-failed\": \"Eksport nie powiódł się!\",\n  \"ViewHeader.filter\": \"Filtr\",\n  \"ViewHeader.group-by\": \"Grupuj wg: {property}\",\n  \"ViewHeader.new\": \"Nowy\",\n  \"ViewHeader.properties\": \"Właściwości\",\n  \"ViewHeader.properties-menu\": \"Menu właściwości\",\n  \"ViewHeader.search-text\": \"Przeszukaj karty\",\n  \"ViewHeader.select-a-template\": \"Wybierz szablon\",\n  \"ViewHeader.set-default-template\": \"Ustaw jako domyślne\",\n  \"ViewHeader.sort\": \"Sortuj\",\n  \"ViewHeader.untitled\": \"Bez tytułu\",\n  \"ViewHeader.view-header-menu\": \"Wyświetl menu nagłówka\",\n  \"ViewHeader.view-menu\": \"Wyświetl menu\",\n  \"ViewLimitDialog.Heading\": \"Osiągnięty limit odsłon na tablicę\",\n  \"ViewLimitDialog.PrimaryButton.Title.Admin\": \"Aktualizuj\",\n  \"ViewLimitDialog.PrimaryButton.Title.RegularUser\": \"Powiadom Administratora\",\n  \"ViewLimitDialog.Subtext.Admin\": \"Uaktualnij do naszego planu Professional lub Enterprise.\",\n  \"ViewLimitDialog.Subtext.Admin.PricingPageLink\": \"Dowiedz się więcej o naszych planach.\",\n  \"ViewLimitDialog.Subtext.RegularUser\": \"Powiadom swojego Administratora, aby uaktualnić do naszego planu Professional lub Enterprise.\",\n  \"ViewLimitDialog.UpgradeImg.AltText\": \"aktualizuj obraz\",\n  \"ViewLimitDialog.notifyAdmin.Success\": \"Twój administrator został powiadomiony\",\n  \"ViewTitle.hide-description\": \"ukryj opis\",\n  \"ViewTitle.pick-icon\": \"Wybierz ikonę\",\n  \"ViewTitle.random-icon\": \"Losowy\",\n  \"ViewTitle.remove-icon\": \"Usuń ikonę\",\n  \"ViewTitle.show-description\": \"pokaż opis\",\n  \"ViewTitle.untitled-board\": \"Tablica bez tytułu\",\n  \"WelcomePage.Description\": \"Tablice to narzędzie do zarządzania projektami, które pomaga definiować, organizować, śledzić i zarządzać pracą w zespołach wykorzystując widok znanych tablic Kanban.\",\n  \"WelcomePage.Explore.Button\": \"Wybierz się na wycieczkę\",\n  \"WelcomePage.Heading\": \"Witamy w Tablicach\",\n  \"WelcomePage.NoThanks.Text\": \"Nie, dzięki, sam sobie z tym poradzę\",\n  \"WelcomePage.StartUsingIt.Text\": \"Zacznij używać\",\n  \"Workspace.editing-board-template\": \"Edytujesz szablon tablicy.\",\n  \"badge.guest\": \"Gość\",\n  \"boardPage.confirm-join-button\": \"Dołącz\",\n  \"boardPage.confirm-join-text\": \"Zamierzasz dołączyć do prywatnej tablicy bez wyraźnego dodania przez administratora forum. Czy na pewno chcesz dołączyć do tego prywatnego forum?\",\n  \"boardPage.confirm-join-title\": \"Dołącz do prywatnej tablicy\",\n  \"boardSelector.confirm-link-board\": \"Połączenie tablicy z kanałem\",\n  \"boardSelector.confirm-link-board-button\": \"Tak, podlinkuj tablicę\",\n  \"boardSelector.confirm-link-board-subtext\": \"Kiedy połączysz \\\"{boardName}\\\" z kanałem, wszyscy członkowie kanału (istniejący i nowi) będą mogli go edytować. Nie dotyczy to członków, którzy są gośćmi. W każdej chwili możesz odłączyć tablicę od kanału.\",\n  \"boardSelector.confirm-link-board-subtext-with-other-channel\": \"Kiedy połączysz \\\"{boardName}\\\" z kanałem, wszyscy członkowie kanału (istniejący i nowi) będą mogli go edytować. Wyklucza to członków, którzy są gośćmi.{lineBreak} Ta tablica jest obecnie połączona z innym kanałem. Zostanie ona odłączona, jeśli zdecydujesz się połączyć ją tutaj.\",\n  \"boardSelector.create-a-board\": \"Utwórz tablicę\",\n  \"boardSelector.link\": \"Link\",\n  \"boardSelector.search-for-boards\": \"Wyszukiwanie tablic\",\n  \"boardSelector.title\": \"Linki tablic\",\n  \"boardSelector.unlink\": \"Odłącz\",\n  \"calendar.month\": \"Miesiąc\",\n  \"calendar.today\": \"DZIŚ\",\n  \"calendar.week\": \"Tydzień\",\n  \"centerPanel.undefined\": \"Brak {propertyName}\",\n  \"centerPanel.unknown-user\": \"Nieznany użytkownik\",\n  \"cloudMessage.learn-more\": \"Dowiedź się więcej\",\n  \"createImageBlock.failed\": \"Ten plik nie mógł zostać przesłany, ponieważ został osiągnięty limit rozmiaru pliku.\",\n  \"default-properties.badges\": \"Uwagi i opis\",\n  \"default-properties.title\": \"Tytuł\",\n  \"error.back-to-home\": \"Powrót na stronę główną\",\n  \"error.back-to-team\": \"Powrót do zespołu\",\n  \"error.board-not-found\": \"Nie znaleziono tablicy.\",\n  \"error.go-login\": \"Zaloguj się\",\n  \"error.invalid-read-only-board\": \"Nie masz dostępu do tej tablicy. Zaloguj się, aby uzyskać dostęp do Tablic.\",\n  \"error.not-logged-in\": \"Twoja sesja mogła wygasnąć lub nie jesteś zalogowany. Zaloguj się ponownie, aby uzyskać dostęp do Tablic.\",\n  \"error.page.title\": \"Przepraszam, coś poszło nie tak\",\n  \"error.team-undefined\": \"Nieprawidłowy zespół.\",\n  \"error.unknown\": \"Wystąpił błąd.\",\n  \"generic.previous\": \"Wstecz\",\n  \"guest-no-board.subtitle\": \"Nie masz jeszcze dostępu do żadnej tablicy w tym zespole, poczekaj aż ktoś doda Cię do jakiejkolwiek tablicy.\",\n  \"guest-no-board.title\": \"Nie ma jeszcze tablic\",\n  \"imagePaste.upload-failed\": \"Niektóre pliki nie zostały przesłane, ponieważ został osiągnięty limit rozmiaru pliku.\",\n  \"limitedCard.title\": \"Ukryte karty\",\n  \"login.log-in-button\": \"Zaloguj się\",\n  \"login.log-in-title\": \"Zaloguj się\",\n  \"login.register-button\": \"lub załóż konto, jeśli jeszcze go nie masz\",\n  \"new_channel_modal.create_board.empty_board_description\": \"Utwórz nową pustą tablicę\",\n  \"new_channel_modal.create_board.empty_board_title\": \"Wyczyść tablicę\",\n  \"new_channel_modal.create_board.select_template_placeholder\": \"Wybierz szablon\",\n  \"new_channel_modal.create_board.title\": \"Utwórz tablicę dla tego kanału\",\n  \"notification-box-card-limit-reached.close-tooltip\": \"Uśpij na 10 dni\",\n  \"notification-box-card-limit-reached.contact-link\": \"powiadom swojego administratora\",\n  \"notification-box-card-limit-reached.link\": \"Uaktualnienie do planu płatnego\",\n  \"notification-box-card-limit-reached.title\": \"{cards} karty ukryte z tablicy\",\n  \"notification-box-cards-hidden.title\": \"Ta akcja zakryła inną kartę\",\n  \"notification-box.card-limit-reached.not-admin.text\": \"Aby uzyskać dostęp do zarchiwizowanych kart, możesz {contactLink} uaktualnić do płatnego planu.\",\n  \"notification-box.card-limit-reached.text\": \"Osiągnięto limit kart, aby wyświetlić starsze karty, {link}\",\n  \"person.add-user-to-board\": \"Dodaj {username} do tablicy\",\n  \"person.add-user-to-board-confirm-button\": \"Dodaj do tablicy\",\n  \"person.add-user-to-board-permissions\": \"Uprawnienia\",\n  \"person.add-user-to-board-question\": \"Czy chcesz dodać do tablicy {username}?\",\n  \"person.add-user-to-board-warning\": \"{username} nie jest członkiem tablicy i nie będzie otrzymywał żadnych powiadomień na ten temat.\",\n  \"register.login-button\": \"lub zaloguj się, jeśli masz już konto\",\n  \"register.signup-title\": \"Zarejestruj się na swoim koncie\",\n  \"rhs-board-non-admin-msg\": \"Nie jesteś administratorem tablicy\",\n  \"rhs-boards.add\": \"Dodaj\",\n  \"rhs-boards.dm\": \"DM\",\n  \"rhs-boards.gm\": \"GM\",\n  \"rhs-boards.header.dm\": \"ta bezpośrednia wiadomość\",\n  \"rhs-boards.header.gm\": \"ta wiadomość grupowa\",\n  \"rhs-boards.last-update-at\": \"Ostatnia aktualizacja o: {datetime}\",\n  \"rhs-boards.link-boards-to-channel\": \"Połączenie tablic z {channelName}\",\n  \"rhs-boards.linked-boards\": \"Połączone tablice\",\n  \"rhs-boards.no-boards-linked-to-channel\": \"Żadna z tablic nie jest jeszcze połączona z {channelName}\",\n  \"rhs-boards.no-boards-linked-to-channel-description\": \"Tablice to narzędzie do zarządzania projektami, które pomagają definiować, organizować, śledzić i zarządzać pracą w zespołach, wykorzystując widok znanych tablic kanban.\",\n  \"rhs-boards.unlink-board\": \"Odłączenie tablicy\",\n  \"rhs-boards.unlink-board1\": \"Odłączenie tablicy\",\n  \"rhs-channel-boards-header.title\": \"Tablice\",\n  \"share-board.publish\": \"Opublikuj\",\n  \"share-board.share\": \"Udostępnij\",\n  \"shareBoard.channels-select-group\": \"Kanały\",\n  \"shareBoard.confirm-change-team-role.body\": \"Każdy na tej tablicy z niższymi uprawnieniami niż rola \\\"{role}\\\" <b>teraz zostanie awansowany do {role}</b>. Czy na pewno chcesz zmienić minimalną rolę dla tablicy?\",\n  \"shareBoard.confirm-change-team-role.confirmBtnText\": \"Zmiana minimalnej roli tablicy\",\n  \"shareBoard.confirm-change-team-role.title\": \"Zmiana minimalnej roli tablicy\",\n  \"shareBoard.confirm-link-channel\": \"Podlinku tablicę do kanału\",\n  \"shareBoard.confirm-link-channel-button\": \"Połączenie kanału\",\n  \"shareBoard.confirm-link-channel-button-with-other-channel\": \"Odłącz i podłącz tutaj\",\n  \"shareBoard.confirm-link-channel-subtext\": \"Kiedy połączysz kanał z tablicą, wszyscy członkowie kanału (istniejący i nowi) będą mogli go edytować. Nie dotyczy to członków, którzy są gośćmi.\",\n  \"shareBoard.confirm-link-channel-subtext-with-other-channel\": \"Kiedy połączysz kanał z tablicą, wszyscy członkowie kanału (istniejący i nowi) będą mogli go edytować. Nie dotyczy to członków, którzy są gośćmi.{lineBreak}Ta tablica jest obecnie połączona z innym kanałem. Zostanie ona usunięta, jeśli zdecydujesz się połączyć ją tutaj.\",\n  \"shareBoard.confirm-unlink.body\": \"Kiedy odłączysz kanał od tablicy, wszyscy członkowie kanału (istniejący i nowi) stracą do niego dostęp, chyba że otrzymają osobne pozwolenie.\",\n  \"shareBoard.confirm-unlink.confirmBtnText\": \"Tak, odłącz\",\n  \"shareBoard.confirm-unlink.title\": \"Odłączenie kanału od tablicy\",\n  \"shareBoard.lastAdmin\": \"Tablice muszą mieć co najmniej jednego Administratora\",\n  \"shareBoard.members-select-group\": \"Członkowie\",\n  \"shareBoard.unknown-channel-display-name\": \"Nieznany kanał\",\n  \"tutorial_tip.finish_tour\": \"Gotowe\",\n  \"tutorial_tip.got_it\": \"Jasne\",\n  \"tutorial_tip.ok\": \"Dalej\",\n  \"tutorial_tip.out\": \"Zrezygnuj z tych porad.\",\n  \"tutorial_tip.seen\": \"Widziałeś to wcześniej?\"\n}\n"
  },
  {
    "path": "webapp/i18n/pt.json",
    "content": "{\n  \"AdminBadge.SystemAdmin\": \"Administrador\",\n  \"AppBar.Tooltip\": \"Alternar quadros vinculados\",\n  \"Attachment.Attachment-title\": \"Anexo\",\n  \"AttachmentBlock.DeleteAction\": \"Apagar\",\n  \"AttachmentBlock.addElement\": \"Adicionar {type}\",\n  \"AttachmentBlock.delete\": \"Anexo apagado.\",\n  \"AttachmentBlock.failed\": \"Este arquivo não pôde ser carregado pois ultrapassou o tamanho limite.\",\n  \"AttachmentBlock.upload\": \"Carregando anexo.\",\n  \"AttachmentBlock.uploadSuccess\": \"Anexo carregado.\",\n  \"AttachmentElement.delete-confirmation-dialog-button-text\": \"Apagar\",\n  \"AttachmentElement.download\": \"Baixar\",\n  \"BoardComponent.add-a-group\": \"+ Adicionar um grupo\",\n  \"BoardComponent.delete\": \"Apagar\",\n  \"BoardComponent.hidden-columns\": \"Colunas escondidas\",\n  \"BoardComponent.hide\": \"Esconder\",\n  \"BoardComponent.new\": \"+ Novo\",\n  \"BoardComponent.no-property\": \"Não {property}\",\n  \"BoardComponent.show\": \"Mostrar\",\n  \"BoardMember.schemeAdmin\": \"Admin\",\n  \"BoardMember.schemeCommenter\": \"Comentador\",\n  \"BoardMember.schemeEditor\": \"Editor\",\n  \"BoardMember.schemeNone\": \"Nenhum\",\n  \"BoardMember.unlinkChannel\": \"Desvincular\",\n  \"BoardPage.newVersion\": \"Está disponível uma nova versão do Boards, clique aqui para recarregar.\",\n  \"BoardPage.syncFailed\": \"O Board pode ter sido apagado ou o acesso revogado.\",\n  \"BoardTemplateSelector.add-template\": \"Criar novo modelo\",\n  \"BoardTemplateSelector.create-empty-board\": \"Criar um board vazio\",\n  \"BoardTemplateSelector.delete-template\": \"Apagar\",\n  \"BoardTemplateSelector.edit-template\": \"Editar\",\n  \"BoardTemplateSelector.plugin.no-content-title\": \"Criar um board\",\n  \"BoardTemplateSelector.title\": \"Criar um board\",\n  \"BoardTemplateSelector.use-this-template\": \"Usar este modelo\",\n  \"BoardsSwitcher.Title\": \"Encontrar boards\",\n  \"Calculations.Options.average.displayName\": \"Média\",\n  \"Calculations.Options.average.label\": \"Média\",\n  \"Calculations.Options.countUniqueValue.displayName\": \"Único\",\n  \"Calculations.Options.countValue.displayName\": \"Valores\",\n  \"Calculations.Options.dateRange.displayName\": \"Intervalo\",\n  \"Calculations.Options.dateRange.label\": \"Intervalo\",\n  \"Calculations.Options.latest.label\": \"Mais recente\",\n  \"Calculations.Options.max.displayName\": \"Máx\",\n  \"Calculations.Options.max.label\": \"Máximo\",\n  \"Calculations.Options.min.displayName\": \"Mínimo\",\n  \"Calculations.Options.min.label\": \"Mínimo\",\n  \"Calculations.Options.none.label\": \"Nenhum\",\n  \"Calculations.Options.range.displayName\": \"Intervalo\",\n  \"Calculations.Options.range.label\": \"Intervalo\",\n  \"Calculations.Options.sum.displayName\": \"Soma\",\n  \"Calculations.Options.sum.label\": \"Soma\",\n  \"CalendarCard.untitled\": \"Sem título\",\n  \"CardActionsMenu.copiedLink\": \"Copiado!\",\n  \"CardActionsMenu.copyLink\": \"Copiar link\",\n  \"CardActionsMenu.delete\": \"Apagar\",\n  \"CardActionsMenu.duplicate\": \"Duplicar\",\n  \"CardBadges.title-comments\": \"Comentários\",\n  \"CardDetail.Attach\": \"Anexar\",\n  \"CardDetail.Follow\": \"Seguir\",\n  \"CardDetail.Following\": \"Seguindo\",\n  \"CardDetail.add-content\": \"Adicionar conteúdo\",\n  \"CardDetail.add-icon\": \"Adicionar ícone\",\n  \"CardDetail.add-property\": \"+ Adicionar uma propriedade\",\n  \"CardDetail.limited-button\": \"Atualizar\",\n  \"CardDetail.new-comment-placeholder\": \"Adicionar um comentário...\",\n  \"CardDetailProperty.delete-action-button\": \"Apagar\",\n  \"CardDialog.delete-confirmation-dialog-button-text\": \"Apagar\",\n  \"Categories.CreateCategoryDialog.CancelText\": \"Cancelar\",\n  \"Categories.CreateCategoryDialog.CreateText\": \"Criar\",\n  \"Categories.CreateCategoryDialog.UpdateText\": \"Atualizar\",\n  \"CenterPanel.Login\": \"Entrar\",\n  \"CenterPanel.Share\": \"Compartilhar\",\n  \"Comment.delete\": \"Apagar\",\n  \"CommentsList.send\": \"Enviar\",\n  \"ConfirmPerson.empty\": \"Vazio\",\n  \"ConfirmPerson.search\": \"Procurar...\",\n  \"ConfirmationDialog.cancel-action\": \"Cancelar\",\n  \"ConfirmationDialog.confirm-action\": \"Confirmar\",\n  \"ContentBlock.Delete\": \"Apagar\",\n  \"ContentBlock.DeleteAction\": \"apagar\",\n  \"ContentBlock.addElement\": \"adicionar {type}\",\n  \"ContentBlock.editText\": \"Editar texto...\",\n  \"ContentBlock.image\": \"imagem\",\n  \"ContentBlock.moveDown\": \"Mover pra baixo\",\n  \"ContentBlock.moveUp\": \"Mover pra cima\",\n  \"ContentBlock.text\": \"texto\",\n  \"DateFilter.empty\": \"Vazio\",\n  \"DateRange.clear\": \"Limpar\",\n  \"DateRange.empty\": \"Vazio\",\n  \"DateRange.today\": \"Hoje\",\n  \"DeleteBoardDialog.confirm-cancel\": \"Cancelar\",\n  \"DeleteBoardDialog.confirm-delete\": \"Apagar\",\n  \"EditableDayPicker.today\": \"Hoje\",\n  \"shareBoard.members-select-group\": \"Membros\",\n  \"shareBoard.unknown-channel-display-name\": \"Canal desconhecido\",\n  \"tutorial_tip.ok\": \"Próximo\"\n}\n"
  },
  {
    "path": "webapp/i18n/pt_BR.json",
    "content": "{\n  \"AdminBadge.SystemAdmin\": \"Administrador\",\n  \"AdminBadge.TeamAdmin\": \"Administrador de equipe\",\n  \"AppBar.Tooltip\": \"Ativar Boards Vinculados\",\n  \"Attachment.Attachment-title\": \"Anexo\",\n  \"AttachmentBlock.DeleteAction\": \"excluir\",\n  \"AttachmentBlock.addElement\": \"adicionar {type}\",\n  \"AttachmentBlock.delete\": \"Anexo apagado.\",\n  \"AttachmentBlock.failed\": \"Este arquivo não pôde ser carregado pois ultrapassou o tamanho limite.\",\n  \"AttachmentBlock.upload\": \"Carregando anexo.\",\n  \"AttachmentBlock.uploadSuccess\": \"Anexo enviado.\",\n  \"AttachmentElement.delete-confirmation-dialog-button-text\": \"Excluir\",\n  \"AttachmentElement.download\": \"Baixar\",\n  \"AttachmentElement.upload-percentage\": \"Enviando...({uploadPercent}%)\",\n  \"BoardComponent.add-a-group\": \"+ Adicione um grupo\",\n  \"BoardComponent.delete\": \"Excluir\",\n  \"BoardComponent.hidden-columns\": \"Colunas ocultas\",\n  \"BoardComponent.hide\": \"Ocultar\",\n  \"BoardComponent.new\": \"Novo\",\n  \"BoardComponent.no-property\": \"Sem {property}\",\n  \"BoardComponent.no-property-title\": \"Itens com um valor {property} vazio aparecerão aqui. Esta coluna não pode ser excluída.\",\n  \"BoardComponent.show\": \"Exibir\",\n  \"BoardMember.schemeAdmin\": \"Administrador\",\n  \"BoardMember.schemeCommenter\": \"Comentarista\",\n  \"BoardMember.schemeEditor\": \"Editor\",\n  \"BoardMember.schemeNone\": \"Nenhum\",\n  \"BoardMember.schemeViewer\": \"Visualizador\",\n  \"BoardMember.unlinkChannel\": \"Desvincular\",\n  \"BoardPage.newVersion\": \"Uma nova versão do Boards está disponível, clique aqui para recarregar.\",\n  \"BoardPage.syncFailed\": \"O Board pode ter sido excluído ou o acesso revogado.\",\n  \"BoardTemplateSelector.add-template\": \"Criar novo modelo\",\n  \"BoardTemplateSelector.create-empty-board\": \"Criar um board vazio\",\n  \"BoardTemplateSelector.delete-template\": \"Excluir\",\n  \"BoardTemplateSelector.description\": \"Adicione um quadro à barra lateral usando qualquer um dos modelos definidos abaixo ou comece do zero.\",\n  \"BoardTemplateSelector.edit-template\": \"Editar\",\n  \"BoardTemplateSelector.plugin.no-content-description\": \"Adicione um board à barra lateral usando um dos templates disponíveis abaixo ou comece do zero.\",\n  \"BoardTemplateSelector.plugin.no-content-title\": \"Criar um board\",\n  \"BoardTemplateSelector.title\": \"Criar um board\",\n  \"BoardTemplateSelector.use-this-template\": \"Use este template\",\n  \"BoardsSwitcher.Title\": \"Encontrar boards\",\n  \"BoardsUnfurl.Limited\": \"Detalhes adicionais estão ocultos devido ao cartão ter sido arquivado\",\n  \"BoardsUnfurl.Remainder\": \"+{remainder} mais\",\n  \"BoardsUnfurl.Updated\": \"Atualizado {time}\",\n  \"Calculations.Options.average.displayName\": \"Média\",\n  \"Calculations.Options.average.label\": \"Média\",\n  \"Calculations.Options.count.displayName\": \"Total\",\n  \"Calculations.Options.count.label\": \"Total\",\n  \"Calculations.Options.countChecked.displayName\": \"Confirmado\",\n  \"Calculations.Options.countChecked.label\": \"Total de itens confirmados\",\n  \"Calculations.Options.countUnchecked.displayName\": \"Não confirmado\",\n  \"Calculations.Options.countUnchecked.label\": \"Total de itens não confirmados\",\n  \"Calculations.Options.countUniqueValue.displayName\": \"Único\",\n  \"Calculations.Options.countUniqueValue.label\": \"Total de valores únicos\",\n  \"Calculations.Options.countValue.displayName\": \"Valores\",\n  \"Calculations.Options.countValue.label\": \"Valor total\",\n  \"Calculations.Options.dateRange.displayName\": \"Período\",\n  \"Calculations.Options.dateRange.label\": \"Alcance\",\n  \"Calculations.Options.earliest.displayName\": \"Mais antigo\",\n  \"Calculations.Options.earliest.label\": \"Mais antigo\",\n  \"Calculations.Options.latest.displayName\": \"Mais recente\",\n  \"Calculations.Options.latest.label\": \"Mais recente\",\n  \"Calculations.Options.max.displayName\": \"Máx\",\n  \"Calculations.Options.max.label\": \"Máx\",\n  \"Calculations.Options.median.displayName\": \"Mediana\",\n  \"Calculations.Options.median.label\": \"Mediana\",\n  \"Calculations.Options.min.displayName\": \"Min\",\n  \"Calculations.Options.min.label\": \"Min\",\n  \"Calculations.Options.none.displayName\": \"Calcular\",\n  \"Calculations.Options.none.label\": \"Nenhum\",\n  \"Calculations.Options.percentChecked.displayName\": \"Verificado\",\n  \"Calculations.Options.percentChecked.label\": \"Porcentagem verificada\",\n  \"Calculations.Options.percentUnchecked.displayName\": \"Não verificado\",\n  \"Calculations.Options.percentUnchecked.label\": \"Porcentagem não verificada\",\n  \"Calculations.Options.range.displayName\": \"Alcance\",\n  \"Calculations.Options.range.label\": \"Período\",\n  \"Calculations.Options.sum.displayName\": \"Soma\",\n  \"Calculations.Options.sum.label\": \"Soma\",\n  \"CalendarCard.untitled\": \"Sem título\",\n  \"CardActionsMenu.copiedLink\": \"Copiado!\",\n  \"CardActionsMenu.copyLink\": \"Copiar link\",\n  \"CardActionsMenu.delete\": \"Excluir\",\n  \"CardActionsMenu.duplicate\": \"Duplicar\",\n  \"CardBadges.title-checkboxes\": \"Caixa de seleção\",\n  \"CardBadges.title-comments\": \"Comentários\",\n  \"CardBadges.title-description\": \"Este cartão tem uma descrição\",\n  \"CardDetail.Attach\": \"Anexar\",\n  \"CardDetail.Follow\": \"Seguir\",\n  \"CardDetail.Following\": \"Seguindo\",\n  \"CardDetail.add-content\": \"Adicionar conteúdo\",\n  \"CardDetail.add-icon\": \"Adicionar ícone\",\n  \"CardDetail.add-property\": \"+ Adicionar propriedade\",\n  \"CardDetail.addCardText\": \"adicionar texto ao card\",\n  \"CardDetail.limited-body\": \"Atualize para nosso plano Professional ou Enterprise.\",\n  \"CardDetail.limited-button\": \"Upgrade\",\n  \"CardDetail.limited-title\": \"Este cartão está oculto\",\n  \"CardDetail.moveContent\": \"Mover conteúdo do cartão\",\n  \"CardDetail.new-comment-placeholder\": \"Adicionar um comentário...\",\n  \"CardDetailProperty.confirm-delete-heading\": \"Confirmar exclusão da propriedade\",\n  \"CardDetailProperty.confirm-delete-subtext\": \"Tem certeza que quer excluir a propriedade \\\"{propertyName}\\\"? Deletando-a excluirá a propriedade de todos os cards nessa board.\",\n  \"CardDetailProperty.confirm-property-name-change-subtext\": \"Tem certeza que deseja alterar propriedade \\\"{propertyName}\\\" {customText}? Isto afetará valor(es) em {numOfCards} cartão(ões) neste quadro, podendo resultar em perda de dados.\",\n  \"CardDetailProperty.confirm-property-type-change\": \"Confirmar alteração de tipo de propriedade\",\n  \"CardDetailProperty.delete-action-button\": \"Excluir\",\n  \"CardDetailProperty.property-change-action-button\": \"Alterar propriedade\",\n  \"CardDetailProperty.property-changed\": \"Propriedade alterada com sucesso!\",\n  \"CardDetailProperty.property-deleted\": \"{propertyName} excluído com êxito!\",\n  \"CardDetailProperty.property-name-change-subtext\": \"digite de \\\"{oldPropType}\\\" para \\\"{newPropType}\\\"\",\n  \"CardDetial.limited-link\": \"Saiba mais sobre nossos planos.\",\n  \"CardDialog.delete-confirmation-dialog-attachment\": \"Confirmar exclusão de anexo\",\n  \"CardDialog.delete-confirmation-dialog-button-text\": \"Excluir\",\n  \"CardDialog.delete-confirmation-dialog-heading\": \"Confirmar exclusão do cartão\",\n  \"CardDialog.editing-template\": \"Você está editando um template.\",\n  \"CardDialog.nocard\": \"Esse cartão não existe ou não está acessível.\",\n  \"Categories.CreateCategoryDialog.CancelText\": \"Cancelar\",\n  \"Categories.CreateCategoryDialog.CreateText\": \"Criar\",\n  \"Categories.CreateCategoryDialog.Placeholder\": \"Nomeie sua categoria\",\n  \"Categories.CreateCategoryDialog.UpdateText\": \"Atualizar\",\n  \"CenterPanel.Login\": \"Login\",\n  \"CenterPanel.Share\": \"Compartilhar\",\n  \"ChannelIntro.CreateBoard\": \"Criar um board\",\n  \"ColorOption.selectColor\": \"Selecione {color} Cor\",\n  \"Comment.delete\": \"Excluir\",\n  \"CommentsList.send\": \"Enviar\",\n  \"ConfirmPerson.empty\": \"Vazio\",\n  \"ConfirmPerson.search\": \"Buscar...\",\n  \"ConfirmationDialog.cancel-action\": \"Cancelar\",\n  \"ConfirmationDialog.confirm-action\": \"Confirmar\",\n  \"ContentBlock.Delete\": \"Excluir\",\n  \"ContentBlock.DeleteAction\": \"Excluir\",\n  \"ContentBlock.addElement\": \"adicionar {type}\",\n  \"ContentBlock.checkbox\": \"caixa de seleção\",\n  \"ContentBlock.divider\": \"Divisor\",\n  \"ContentBlock.editCardCheckbox\": \"Caixa de seleção marcada\",\n  \"ContentBlock.editCardCheckboxText\": \"editar texto do cartão\",\n  \"ContentBlock.editCardText\": \"editar texto do cartão\",\n  \"ContentBlock.editText\": \"Editar texto...\",\n  \"ContentBlock.image\": \"imagem\",\n  \"ContentBlock.insertAbove\": \"Inserir acima\",\n  \"ContentBlock.moveBlock\": \"mover conteúdo do cartão\",\n  \"ContentBlock.moveDown\": \"Mover para baixo\",\n  \"ContentBlock.moveUp\": \"Mover para cima\",\n  \"ContentBlock.text\": \"texto\",\n  \"DateFilter.empty\": \"Vazio\",\n  \"DateRange.clear\": \"Limpar\",\n  \"DateRange.empty\": \"Vazio\",\n  \"DateRange.endDate\": \"data de término\",\n  \"DateRange.today\": \"Hoje\",\n  \"DeleteBoardDialog.confirm-cancel\": \"Cancelar\",\n  \"DeleteBoardDialog.confirm-delete\": \"Excluir\",\n  \"DeleteBoardDialog.confirm-info\": \"Tem certeza que quer excluir o quadro \\\"{boardTitle}\\\"? Excluí-lo irá apagar todos os cartões no quadro.\",\n  \"DeleteBoardDialog.confirm-info-template\": \"Tem certeza que deseja excluir o board template “{boardTitle}”?\",\n  \"DeleteBoardDialog.confirm-tite\": \"Confirmar exclusão do board\",\n  \"DeleteBoardDialog.confirm-tite-template\": \"Confirmar exclusão do template de board\",\n  \"Dialog.closeDialog\": \"Fechar diálogo\",\n  \"EditableDayPicker.today\": \"Hoje\",\n  \"Error.mobileweb\": \"Suporte Web móvel está em estágio beta. Algumas funcionalidades podem estar presentes.\",\n  \"Error.websocket-closed\": \"Conexão Websocket fechada, conexão interrompida. Se persistir, verifique a configuração do seu servidor ou proxy de web.\",\n  \"Filter.contains\": \"contém\",\n  \"Filter.ends-with\": \"termina com\",\n  \"Filter.includes\": \"Inclui\",\n  \"Filter.is\": \"é\",\n  \"Filter.is-after\": \"está depois\",\n  \"Filter.is-before\": \"está antes\",\n  \"Filter.is-empty\": \"está vazio\",\n  \"Filter.is-not-empty\": \"Não está vazio\",\n  \"Filter.is-not-set\": \"não está definido\",\n  \"Filter.is-set\": \"está definido\",\n  \"Filter.isafter\": \"está depois\",\n  \"Filter.isbefore\": \"está antes\",\n  \"Filter.not-contains\": \"não contém\",\n  \"Filter.not-ends-with\": \"não termina com\",\n  \"Filter.not-includes\": \"Não inclui\",\n  \"Filter.not-starts-with\": \"não começa com\",\n  \"Filter.starts-with\": \"começa com\",\n  \"FilterByText.placeholder\": \"filtrar texto\",\n  \"FilterComponent.add-filter\": \"+ Adicionar filtro\",\n  \"FilterComponent.delete\": \"Excluir\",\n  \"FilterValue.empty\": \"(vazio)\",\n  \"FindBoardsDialog.IntroText\": \"Procurar por quadros\",\n  \"FindBoardsDialog.NoResultsFor\": \"Sem resultado para \\\"{searchQuery}\\\"\",\n  \"FindBoardsDialog.NoResultsSubtext\": \"Verifique a digitação ou tente outra busca.\",\n  \"FindBoardsDialog.SubTitle\": \"Digite para localizar um board. <b>Use CIMA/BAIXO</b> para explorar <b>ENTER</b> para selecionar, <b>ESC</b> para dispensar\",\n  \"FindBoardsDialog.Title\": \"Encontrar quadros\",\n  \"GroupBy.hideEmptyGroups\": \"Ocultar {count} grupos vazios\",\n  \"GroupBy.showHiddenGroups\": \"Mostrar {count} grupos ocultos\",\n  \"GroupBy.ungroup\": \"Desagrupar\",\n  \"HideBoard.MenuOption\": \"Ocultar quadro\",\n  \"KanbanCard.untitled\": \"Sem nome\",\n  \"MentionSuggestion.is-not-board-member\": \"(não membro do board)\",\n  \"Mutator.new-board-from-template\": \"novo board do template\",\n  \"Mutator.new-card-from-template\": \"novo cartão à partir de um template\",\n  \"Mutator.new-template-from-card\": \"novo template à partir de um cartão\",\n  \"OnboardingTour.AddComments.Body\": \"Você pode comentar questões, e até mesmo @mencionar seus usuários companheiros de Mattermost para conseguir suas atenções.\",\n  \"OnboardingTour.AddComments.Title\": \"Adicionar comentários\",\n  \"OnboardingTour.AddDescription.Body\": \"Adicione uma descrição para que seus companheiros de time saibam sobre o que é o cartão.\",\n  \"OnboardingTour.AddDescription.Title\": \"Adicionar descrição\",\n  \"OnboardingTour.AddProperties.Body\": \"Adicione várias propriedades aos cartões para torná-los mais poderosos.\",\n  \"OnboardingTour.AddProperties.Title\": \"Adicionar propriedades\",\n  \"OnboardingTour.AddView.Body\": \"Crie uma nova view aquei para organizar seu board usando diferentes layouts.\",\n  \"OnboardingTour.AddView.Title\": \"Adicionar nova visualização\",\n  \"OnboardingTour.CopyLink.Body\": \"Você pode compartilhar seus cartões com companheiros de times copiando e colando o link em um canal, mensagem direta, ou mensagem de grupo.\",\n  \"OnboardingTour.CopyLink.Title\": \"Copiar link\",\n  \"OnboardingTour.OpenACard.Body\": \"Abra um cartão para explorar formas poderosas que os Boards podem ajudar a organizar seu trabalho.\",\n  \"OnboardingTour.OpenACard.Title\": \"Abrir um cartão\",\n  \"OnboardingTour.ShareBoard.Body\": \"Você pode compartilhar seu board internament, com seu time, ou public para permitir visibilidade fora da sua organização.\",\n  \"OnboardingTour.ShareBoard.Title\": \"Compartilhar quadro\",\n  \"PersonProperty.board-members\": \"Membros do Board\",\n  \"PersonProperty.me\": \"Eu\",\n  \"PersonProperty.non-board-members\": \"Não membros do board\",\n  \"PropertyMenu.Delete\": \"Excluir\",\n  \"PropertyMenu.changeType\": \"Alterar tipo da propriedade\",\n  \"PropertyMenu.selectType\": \"Selecione o tipo de propriedade\",\n  \"PropertyMenu.typeTitle\": \"Tipo\",\n  \"PropertyType.Checkbox\": \"Caixa de seleção\",\n  \"PropertyType.CreatedBy\": \"Criado por\",\n  \"PropertyType.CreatedTime\": \"Horário de criação\",\n  \"PropertyType.Date\": \"Data\",\n  \"PropertyType.Email\": \"Email\",\n  \"PropertyType.MultiPerson\": \"Múltiplas pessoas\",\n  \"PropertyType.MultiSelect\": \"Seleção múltipla\",\n  \"PropertyType.Number\": \"Número\",\n  \"PropertyType.Person\": \"Pessoa\",\n  \"PropertyType.Phone\": \"Telefone\",\n  \"PropertyType.Select\": \"Selcionar\",\n  \"PropertyType.Text\": \"Texto\",\n  \"PropertyType.Unknown\": \"Desconhecido\",\n  \"PropertyType.UpdatedBy\": \"Atualizado pela última vez por\",\n  \"PropertyType.UpdatedTime\": \"Atualizado pela última vez em\",\n  \"PropertyType.Url\": \"URL\",\n  \"PropertyValueElement.empty\": \"Vazio\",\n  \"RegistrationLink.confirmRegenerateToken\": \"Isso vai invalidar os links compartilhados anteriormente. Continuar?\",\n  \"RegistrationLink.copiedLink\": \"Copiado!\",\n  \"RegistrationLink.copyLink\": \"Copiar link\",\n  \"RegistrationLink.description\": \"Compartilhe esse link para que outras pessoas criarem contas:\",\n  \"RegistrationLink.regenerateToken\": \"Gerar o token novamente\",\n  \"RegistrationLink.tokenRegenerated\": \"Link para registro gerado novamente\",\n  \"ShareBoard.PublishDescription\": \"Publique e compartilhe um link de somente leitura com todos na web.\",\n  \"ShareBoard.PublishTitle\": \"Publicar para a web\",\n  \"ShareBoard.ShareInternal\": \"Compartilhar internamente\",\n  \"ShareBoard.ShareInternalDescription\": \"Usuários que terão permissão para utilizar este link.\",\n  \"ShareBoard.Title\": \"Compartilhar Quadro\",\n  \"ShareBoard.confirmRegenerateToken\": \"Isso vai invalidar links compartilhados anteriormente. Continuar?\",\n  \"ShareBoard.copiedLink\": \"Copiado!\",\n  \"ShareBoard.copyLink\": \"Copiar link\",\n  \"ShareBoard.regenerate\": \"Gerar token novamente\",\n  \"ShareBoard.searchPlaceholder\": \"Procurar por pessoas e canais\",\n  \"ShareBoard.teamPermissionsText\": \"Todos no time {teamName}\",\n  \"ShareBoard.tokenRegenrated\": \"Token gerado novamente\",\n  \"ShareBoard.userPermissionsRemoveMemberText\": \"Remover membro\",\n  \"ShareBoard.userPermissionsYouText\": \"(Você)\",\n  \"ShareTemplate.Title\": \"Compartilhar template\",\n  \"ShareTemplate.searchPlaceholder\": \"Busca por pessoas\",\n  \"Sidebar.about\": \"Sobre o Focalboard\",\n  \"Sidebar.add-board\": \"+ Adicionar quadro\",\n  \"Sidebar.changePassword\": \"Mudar senha\",\n  \"Sidebar.delete-board\": \"Excluir quadro\",\n  \"Sidebar.duplicate-board\": \"Duplicar quadro\",\n  \"Sidebar.export-archive\": \"Exportar arquivo\",\n  \"Sidebar.import\": \"Importar\",\n  \"Sidebar.import-archive\": \"Importar arquivo\",\n  \"Sidebar.invite-users\": \"Convidar usuários\",\n  \"Sidebar.logout\": \"Sair\",\n  \"Sidebar.new-category.badge\": \"Novo\",\n  \"Sidebar.new-category.drag-boards-cta\": \"Solte quadros aqui...\",\n  \"Sidebar.no-boards-in-category\": \"Nenhum board\",\n  \"Sidebar.product-tour\": \"Tour pelo produto\",\n  \"Sidebar.random-icons\": \"Ícones aleatórios\",\n  \"Sidebar.set-language\": \"Definir linguagem\",\n  \"Sidebar.set-theme\": \"Definir tema\",\n  \"Sidebar.settings\": \"Configurações\",\n  \"Sidebar.template-from-board\": \"Novo template vindo do board\",\n  \"Sidebar.untitled-board\": \"(Quadro sem nome)\",\n  \"Sidebar.untitled-view\": \"(View sem título)\",\n  \"SidebarCategories.BlocksMenu.Move\": \"Mover Para...\",\n  \"SidebarCategories.CategoryMenu.CreateNew\": \"Criar Nova Categoria\",\n  \"SidebarCategories.CategoryMenu.Delete\": \"Excluir Categoria\",\n  \"SidebarCategories.CategoryMenu.DeleteModal.Body\": \"Boards em <b>{categoryName}</b> serão movidos de volta para categoria de Boards. Você não será removido de nenhum board.\",\n  \"SidebarCategories.CategoryMenu.DeleteModal.Title\": \"Excluir esta categoria?\",\n  \"SidebarCategories.CategoryMenu.Update\": \"Renomear Categoria\",\n  \"SidebarTour.ManageCategories.Body\": \"Criar e gerenciar categorias personalizadas. Categorias são específicas para usuário, então mover um board para sua categoria não impactará outros membros usando o mesmo board.\",\n  \"SidebarTour.ManageCategories.Title\": \"Gerenciar categorias\",\n  \"SidebarTour.SearchForBoards.Body\": \"Abrir o alternador de board (Cmd/Ctrl + K) para buscar rapidamente e adicionar boards a sua barra lateral.\",\n  \"SidebarTour.SearchForBoards.Title\": \"Buscar por boards\",\n  \"SidebarTour.SidebarCategories.Body\": \"Todos seus boards agora são organizados sob sua nova barra lateral. Não é mais necessárioa alternar entre espaços de trabalho. Categorias personalizadas em suas estações prévias de trabalho foram automaticamente criadas para você como parte do seu upgrade para v7.2. Estas podem ser removidas ou editadas de acordo com a sua preferência.\",\n  \"SidebarTour.SidebarCategories.Link\": \"Saiba mais\",\n  \"SidebarTour.SidebarCategories.Title\": \"Categorias de barra lateral\",\n  \"SiteStats.total_boards\": \"Total de boards\",\n  \"SiteStats.total_cards\": \"Total de cartões\",\n  \"TableComponent.add-icon\": \"Adicionar Ícone\",\n  \"TableComponent.name\": \"Nome\",\n  \"TableComponent.plus-new\": \"+ Novo\",\n  \"TableHeaderMenu.delete\": \"Excluir\",\n  \"TableHeaderMenu.duplicate\": \"Duplicar\",\n  \"TableHeaderMenu.hide\": \"Esconder\",\n  \"TableHeaderMenu.insert-left\": \"Inserir à esquerda\",\n  \"TableHeaderMenu.insert-right\": \"Inserir à direita\",\n  \"TableHeaderMenu.sort-ascending\": \"Ordem ascendente\",\n  \"TableHeaderMenu.sort-descending\": \"Ordem descendente\",\n  \"TableRow.DuplicateCard\": \"duplicar cartão\",\n  \"TableRow.MoreOption\": \"Mais ações\",\n  \"TableRow.open\": \"Abrir\",\n  \"TopBar.give-feedback\": \"Dar feedback\",\n  \"URLProperty.copiedLink\": \"Copiado!\",\n  \"URLProperty.copy\": \"Copiar\",\n  \"URLProperty.edit\": \"Editar\",\n  \"UndoRedoHotKeys.canRedo\": \"Refazer\",\n  \"UndoRedoHotKeys.canRedo-with-description\": \"Refazer {description}\",\n  \"UndoRedoHotKeys.canUndo\": \"Desfazer\",\n  \"UndoRedoHotKeys.canUndo-with-description\": \"Desfazer {description}\",\n  \"UndoRedoHotKeys.cannotRedo\": \"Nada para Refazer\",\n  \"UndoRedoHotKeys.cannotUndo\": \"Nada para Desfazer\",\n  \"ValueSelector.noOptions\": \"Sem opções. Comece adicionando a primeira!\",\n  \"ValueSelector.valueSelector\": \"Selecionador de valor\",\n  \"ValueSelectorLabel.openMenu\": \"Abrir menu\",\n  \"VersionMessage.help\": \"Verifique o que é novo nesta versão.\",\n  \"VersionMessage.learn-more\": \"Saiba mais\",\n  \"View.AddView\": \"Adicionar visualização\",\n  \"View.Board\": \"Quadro\",\n  \"View.DeleteView\": \"Excluir visualização\",\n  \"View.DuplicateView\": \"Duplicar visualização\",\n  \"View.Gallery\": \"Galeria\",\n  \"View.NewBoardTitle\": \"Visualização de Quadro\",\n  \"View.NewCalendarTitle\": \"Visualização de calendário\",\n  \"View.NewGalleryTitle\": \"Visualização de Galeria\",\n  \"View.NewTableTitle\": \"Visualização de Tabela\",\n  \"View.NewTemplateDefaultTitle\": \"Template sem título\",\n  \"View.NewTemplateTitle\": \"Sem título\",\n  \"View.Table\": \"Tabela\",\n  \"ViewHeader.add-template\": \"+ Novo modelo\",\n  \"ViewHeader.delete-template\": \"Excluir\",\n  \"ViewHeader.display-by\": \"Exibir por: {property}\",\n  \"ViewHeader.edit-template\": \"Editar\",\n  \"ViewHeader.empty-card\": \"Cartão vazio\",\n  \"ViewHeader.export-board-archive\": \"Exportar arquivo do painel\",\n  \"ViewHeader.export-complete\": \"Exportação completa!\",\n  \"ViewHeader.export-csv\": \"Exportar para CSV\",\n  \"ViewHeader.export-failed\": \"Falha ao exportar!\",\n  \"ViewHeader.filter\": \"Filtrar\",\n  \"ViewHeader.group-by\": \"Agrupar por: {property}\",\n  \"ViewHeader.new\": \"Novo\",\n  \"ViewHeader.properties\": \"Propriedades\",\n  \"ViewHeader.properties-menu\": \"Menu de propriedades\",\n  \"ViewHeader.search-text\": \"Pesquisar cartões\",\n  \"ViewHeader.select-a-template\": \"Selecionar um modelo\",\n  \"ViewHeader.set-default-template\": \"Definir como padrão\",\n  \"ViewHeader.sort\": \"Ordenar\",\n  \"ViewHeader.untitled\": \"Sem nome\",\n  \"ViewHeader.view-header-menu\": \"Visualizar menu de cabeçalho\",\n  \"ViewHeader.view-menu\": \"Visualizar menu\",\n  \"ViewLimitDialog.Heading\": \"Limite de views por board alcaçado\",\n  \"ViewLimitDialog.PrimaryButton.Title.Admin\": \"Upgrade\",\n  \"ViewLimitDialog.PrimaryButton.Title.RegularUser\": \"Notificar Admin\",\n  \"ViewLimitDialog.Subtext.Admin\": \"Atualize para nosso plano Profissional ou Enterprise.\",\n  \"ViewLimitDialog.Subtext.Admin.PricingPageLink\": \"Saiba mais sobre nossos planos.\",\n  \"ViewLimitDialog.Subtext.RegularUser\": \"Notifique seu administrador para atualizar para nosso plano Professional ou Enterprise.\",\n  \"ViewLimitDialog.UpgradeImg.AltText\": \"Atualizar imagem\",\n  \"ViewLimitDialog.notifyAdmin.Success\": \"Seu administrador foi notificado\",\n  \"ViewTitle.hide-description\": \"esconder descrição\",\n  \"ViewTitle.pick-icon\": \"Escolher ícone\",\n  \"ViewTitle.random-icon\": \"Aleatório\",\n  \"ViewTitle.remove-icon\": \"Remover ícone\",\n  \"ViewTitle.show-description\": \"mostrar descrição\",\n  \"ViewTitle.untitled-board\": \"Quadro sem título\",\n  \"WelcomePage.Description\": \"Boards é uma ferramenta de gerenciamento de projeto que ajuda a definir, organizar, rastrear, e gerenciar traabalho de vários times utilizando uma estrturtura familiar de quadro de Kanban.\",\n  \"WelcomePage.Explore.Button\": \"Faça um tour\",\n  \"WelcomePage.Heading\": \"Bem vindo ao Boards\",\n  \"WelcomePage.NoThanks.Text\": \"Não obrigado, eu descubrirei sozinho\",\n  \"WelcomePage.StartUsingIt.Text\": \"Começar a usar\",\n  \"Workspace.editing-board-template\": \"Você está editando um modelo de quadro.\",\n  \"badge.guest\": \"Convidado\",\n  \"boardPage.confirm-join-button\": \"Ingressar\",\n  \"boardPage.confirm-join-text\": \"Você está prestes a ingressar em um board privado sem ter sido explicitamente adicionado pelo administrador do quadro. Você tem certeza de que deseja ingressar neste board privado?\",\n  \"boardPage.confirm-join-title\": \"Ingressar board privado\",\n  \"boardSelector.confirm-link-board\": \"Linkar board para canal\",\n  \"boardSelector.confirm-link-board-button\": \"Sim, linkar board\",\n  \"boardSelector.confirm-link-board-subtext\": \"Quando você vincula \\\"{boardName}\\\" a um canal, todos os membros daquele canal (existentes e novos) poderão editá-lo. Isto excluirá os membros que são convidados. Você pode desvincular um board de um canal a qualquer hora.\",\n  \"boardSelector.confirm-link-board-subtext-with-other-channel\": \"Quando você vincula \\\"{boardName}\\\" a um canal, todos membros do canal (existentes e novas) poderão editar. Isto excluirá os membros que forem convidados. {lineBreak} Este board está atualmente vinculado a outro canal. Será desvinculado se você optar por vincular aqui.\",\n  \"boardSelector.create-a-board\": \"Criar um board\",\n  \"boardSelector.link\": \"Link\",\n  \"boardSelector.search-for-boards\": \"Procurar por boards\",\n  \"boardSelector.title\": \"Vincular boards\",\n  \"boardSelector.unlink\": \"Desvincular\",\n  \"calendar.month\": \"Mês\",\n  \"calendar.today\": \"HOJE\",\n  \"calendar.week\": \"Semana\",\n  \"centerPanel.undefined\": \"Sem {propertyName}\",\n  \"centerPanel.unknown-user\": \"Usuário desconhecido\",\n  \"cloudMessage.learn-more\": \"Saiba mais\",\n  \"createImageBlock.failed\": \"Não foi possível enviar o arquivo, pois o tamanho ultrapassou o limite permitido.\",\n  \"default-properties.badges\": \"Comentários e descrição\",\n  \"default-properties.title\": \"Título\",\n  \"error.back-to-home\": \"Volta para o início\",\n  \"error.back-to-team\": \"Volta para o time\",\n  \"error.board-not-found\": \"Quadro não encontrado.\",\n  \"error.go-login\": \"Log in\",\n  \"error.invalid-read-only-board\": \"Você não possui acesso a este board. Faça log in para acessar Boards.\",\n  \"error.not-logged-in\": \"Sua sessão pode ter expirado ou você não está logado. Faça Log in para obter acesso ao Boards.\",\n  \"error.page.title\": \"Desculpe, algo deu errado\",\n  \"error.team-undefined\": \"Não é um time válido.\",\n  \"error.unknown\": \"Um erro ocorreu.\",\n  \"generic.previous\": \"Anterior\",\n  \"guest-no-board.subtitle\": \"Você não tem acesso a nenhum board neste time ainda, por favor aguarde até alguém adicionar você a algum board.\",\n  \"guest-no-board.title\": \"Nenhum board ainda\",\n  \"imagePaste.upload-failed\": \"Alguns arquivos não foram enviados, pois o tamanho ultrapassou o limite permitido.\",\n  \"limitedCard.title\": \"Cartões ocultos\",\n  \"login.log-in-button\": \"Entrar\",\n  \"login.log-in-title\": \"Entrar\",\n  \"login.register-button\": \"ou criar uma conta se você ainda não tiver uma\",\n  \"new_channel_modal.create_board.empty_board_description\": \"Criar um novo quadro vazio\",\n  \"new_channel_modal.create_board.empty_board_title\": \"Quadro vazio\",\n  \"new_channel_modal.create_board.select_template_placeholder\": \"Selecionar um modelo\",\n  \"new_channel_modal.create_board.title\": \"Criar um quadro para este canal\",\n  \"notification-box-card-limit-reached.close-tooltip\": \"Soneca por 10 dias\",\n  \"notification-box-card-limit-reached.contact-link\": \"notificar seu admin\",\n  \"notification-box-card-limit-reached.link\": \"Atualizar para um plano pago\",\n  \"notification-box-card-limit-reached.title\": \"{cards} cartões ocultos do board\",\n  \"notification-box-cards-hidden.title\": \"Esta ação ocultou outro cartão\",\n  \"notification-box.card-limit-reached.not-admin.text\": \"Para acessar cartões arquivados, você pode {contactLink} para atualizar para um plano pago.\",\n  \"notification-box.card-limit-reached.text\": \"Limite de cartão alcançado, para visualizar cartões mais antigos, {link}\",\n  \"person.add-user-to-board\": \"Adicionar {username} ao board\",\n  \"person.add-user-to-board-confirm-button\": \"Adicionar ao board\",\n  \"person.add-user-to-board-permissions\": \"Permissões\",\n  \"person.add-user-to-board-question\": \"Você quer adicionar {username} ao board?\",\n  \"person.add-user-to-board-warning\": \"{username} não é um membro do quadro e não receberá nenhuma notificação sobre ele.\",\n  \"register.login-button\": \"ou entre se você já tem uma conta\",\n  \"register.signup-title\": \"Registrar uma conta\",\n  \"rhs-board-non-admin-msg\": \"Você não é um adminstrador do quadro\",\n  \"rhs-boards.add\": \"Adicionar\",\n  \"rhs-boards.dm\": \"DM\",\n  \"rhs-boards.gm\": \"GM\",\n  \"rhs-boards.header.dm\": \"esta mensagem direta\",\n  \"rhs-boards.header.gm\": \"esta mensagem de grupo\",\n  \"rhs-boards.last-update-at\": \"Última atualização em: {datetime}\",\n  \"rhs-boards.link-boards-to-channel\": \"Vincular boards para {channelName}\",\n  \"rhs-boards.linked-boards\": \"Boards vinculados\",\n  \"rhs-boards.no-boards-linked-to-channel\": \"Nenhum board está vinculado a {channelName} ainda\",\n  \"rhs-boards.no-boards-linked-to-channel-description\": \"Boards é uma ferramenta de gerenciamento de projeto que ajuda a definir, organizar, rastrear e gerenciar o trabalho entre times, usando uma visualização de quadro estilo Kaban familiar.\",\n  \"rhs-boards.unlink-board\": \"Desvincular board\",\n  \"rhs-boards.unlink-board1\": \"Desvincular board\",\n  \"rhs-channel-boards-header.title\": \"Boards\",\n  \"share-board.publish\": \"Publicar\",\n  \"share-board.share\": \"Compartilhar\",\n  \"shareBoard.channels-select-group\": \"Canais\",\n  \"shareBoard.confirm-change-team-role.body\": \"Todos neste quadro com permissão mais baixa que papel \\\"{role}\\\" serão <b>serão promovidos para {role}</b>. Tem certeza que deseja alterar o papel mínimo para esse board?\",\n  \"shareBoard.confirm-change-team-role.confirmBtnText\": \"Alterar papel mínimo do board\",\n  \"shareBoard.confirm-change-team-role.title\": \"Alterar papel mínimo do board\",\n  \"shareBoard.confirm-link-channel\": \"Vincular ao canal\",\n  \"shareBoard.confirm-link-channel-button\": \"Vincular canal\",\n  \"shareBoard.confirm-link-channel-button-with-other-channel\": \"Desvincular e vincular aqui\",\n  \"shareBoard.confirm-link-channel-subtext\": \"Quando você vincula um canal a um quadro, todos os membros do canal (existentes e novos) poderão edita-lo. Isto excluirá os membros que forem convidados.\",\n  \"shareBoard.confirm-link-channel-subtext-with-other-channel\": \"Quando você vincula um canal a um board, todos os membros de um canal (existente e novos) poderão edita-lo. Isto excluirá os membros que forem convidados {lineBreak}. Este board está vinculado a outro canal. Será desvinculado se você optar por vincula-lo aqui.\",\n  \"shareBoard.confirm-unlink.body\": \"QUando você desvincula um canal de um board, todos os membros do canal (existentes e novos) irão perder acesso ao menos que tenham recebido permissão separadamente.\",\n  \"shareBoard.confirm-unlink.confirmBtnText\": \"Desvincular canal\",\n  \"shareBoard.confirm-unlink.title\": \"Desvincular canal do board\",\n  \"shareBoard.lastAdmin\": \"Boards devem ter pelo menos um Administrador\",\n  \"shareBoard.members-select-group\": \"Membros\",\n  \"shareBoard.unknown-channel-display-name\": \"Canal desconhecido\",\n  \"tutorial_tip.finish_tour\": \"Feito\",\n  \"tutorial_tip.got_it\": \"Entendi\",\n  \"tutorial_tip.ok\": \"Próximo\",\n  \"tutorial_tip.out\": \"Desativar estas dicas.\",\n  \"tutorial_tip.seen\": \"Já viu isto antes?\"\n}\n"
  },
  {
    "path": "webapp/i18n/ru.json",
    "content": "{\n  \"AppBar.Tooltip\": \"Переключить связанные доски\",\n  \"Attachment.Attachment-title\": \"Вложение\",\n  \"AttachmentBlock.DeleteAction\": \"Удалить\",\n  \"AttachmentBlock.addElement\": \"добавить {type}\",\n  \"AttachmentBlock.delete\": \"Вложение удалено.\",\n  \"AttachmentBlock.failed\": \"Не удалось загрузить файл, так как превышена квота на размер файла.\",\n  \"AttachmentBlock.upload\": \"Загрузка вложения.\",\n  \"AttachmentBlock.uploadSuccess\": \"Вложение загружено.\",\n  \"AttachmentElement.delete-confirmation-dialog-button-text\": \"Удалить\",\n  \"AttachmentElement.download\": \"Скачать\",\n  \"AttachmentElement.upload-percentage\": \"Загрузка...({uploadPercent}%)\",\n  \"BoardComponent.add-a-group\": \"+ Добавить группу\",\n  \"BoardComponent.delete\": \"Удалить\",\n  \"BoardComponent.hidden-columns\": \"Скрытые столбцы\",\n  \"BoardComponent.hide\": \"Скрыть\",\n  \"BoardComponent.new\": \"+ Создать\",\n  \"BoardComponent.no-property\": \"{property} пусто\",\n  \"BoardComponent.no-property-title\": \"Здесь будут элементы с пустым свойством {property}. Этот столбец не может быть удален.\",\n  \"BoardComponent.show\": \"Показать\",\n  \"BoardMember.schemeAdmin\": \"Администратор\",\n  \"BoardMember.schemeCommenter\": \"Комментатор\",\n  \"BoardMember.schemeEditor\": \"Редактор\",\n  \"BoardMember.schemeNone\": \"Никто\",\n  \"BoardMember.schemeViewer\": \"Наблюдатель\",\n  \"BoardMember.unlinkChannel\": \"Отключить\",\n  \"BoardPage.newVersion\": \"Доступна новая версия Доски. Нажмите здесь, чтобы перезагрузить.\",\n  \"BoardPage.syncFailed\": \"Доска может быть удалена или доступ аннулирован.\",\n  \"BoardTemplateSelector.add-template\": \"Новый шаблон\",\n  \"BoardTemplateSelector.create-empty-board\": \"Создать пустую доску\",\n  \"BoardTemplateSelector.delete-template\": \"Удалить\",\n  \"BoardTemplateSelector.description\": \"Добавьте доску на боковую панель, используя любой из шаблонов, описанных ниже, или начните с нуля.\",\n  \"BoardTemplateSelector.edit-template\": \"Изменить\",\n  \"BoardTemplateSelector.plugin.no-content-description\": \"Добавьте доску на боковую панель, используя любой из указанных ниже шаблонов, или начните с нуля.\",\n  \"BoardTemplateSelector.plugin.no-content-title\": \"Создать доску\",\n  \"BoardTemplateSelector.title\": \"Создать доску\",\n  \"BoardTemplateSelector.use-this-template\": \"Использовать этот шаблон\",\n  \"BoardsSwitcher.Title\": \"Найти доски\",\n  \"BoardsUnfurl.Limited\": \"Информация скрыта, потому что карточка находится в архиве\",\n  \"BoardsUnfurl.Remainder\": \"+{remainder} ещё\",\n  \"BoardsUnfurl.Updated\": \"Обновлено {time}\",\n  \"Calculations.Options.average.displayName\": \"Среднее\",\n  \"Calculations.Options.average.label\": \"Среднее\",\n  \"Calculations.Options.count.displayName\": \"Итого\",\n  \"Calculations.Options.count.label\": \"Итого\",\n  \"Calculations.Options.countChecked.displayName\": \"Проверено\",\n  \"Calculations.Options.countChecked.label\": \"Итог проверен\",\n  \"Calculations.Options.countUnchecked.displayName\": \"Не проверен\",\n  \"Calculations.Options.countUnchecked.label\": \"Итог не проверен\",\n  \"Calculations.Options.countUniqueValue.displayName\": \"Уникальный\",\n  \"Calculations.Options.countUniqueValue.label\": \"Итого уникальных значений\",\n  \"Calculations.Options.countValue.displayName\": \"Значения\",\n  \"Calculations.Options.countValue.label\": \"Итоговое значение\",\n  \"Calculations.Options.dateRange.displayName\": \"Диапазон\",\n  \"Calculations.Options.dateRange.label\": \"Диапазон\",\n  \"Calculations.Options.earliest.displayName\": \"Ранний\",\n  \"Calculations.Options.earliest.label\": \"Ранний\",\n  \"Calculations.Options.latest.displayName\": \"Последний\",\n  \"Calculations.Options.latest.label\": \"Последний\",\n  \"Calculations.Options.max.displayName\": \"Максимальный\",\n  \"Calculations.Options.max.label\": \"Максимальный\",\n  \"Calculations.Options.median.displayName\": \"Медиана\",\n  \"Calculations.Options.median.label\": \"Медиана\",\n  \"Calculations.Options.min.displayName\": \"Минимальный\",\n  \"Calculations.Options.min.label\": \"Минимальный\",\n  \"Calculations.Options.none.displayName\": \"Вычислить\",\n  \"Calculations.Options.none.label\": \"Ничто\",\n  \"Calculations.Options.percentChecked.displayName\": \"Проверено\",\n  \"Calculations.Options.percentChecked.label\": \"Процент проверенных\",\n  \"Calculations.Options.percentUnchecked.displayName\": \"Непроверенный\",\n  \"Calculations.Options.percentUnchecked.label\": \"Процент непроверенных\",\n  \"Calculations.Options.range.displayName\": \"Диапазон\",\n  \"Calculations.Options.range.label\": \"Диапазон\",\n  \"Calculations.Options.sum.displayName\": \"Сумма\",\n  \"Calculations.Options.sum.label\": \"Сумма\",\n  \"CalendarCard.untitled\": \"Без названия\",\n  \"CardActionsMenu.copiedLink\": \"Скопировано!\",\n  \"CardActionsMenu.copyLink\": \"Копировать ссылку\",\n  \"CardActionsMenu.delete\": \"Удалить\",\n  \"CardActionsMenu.duplicate\": \"Дублировать\",\n  \"CardBadges.title-checkboxes\": \"Флажки\",\n  \"CardBadges.title-comments\": \"Комментарии\",\n  \"CardBadges.title-description\": \"Эта карточка имеет описание\",\n  \"CardDetail.Attach\": \"Прикреплять\",\n  \"CardDetail.Follow\": \"Отслеживать\",\n  \"CardDetail.Following\": \"Отслеживание\",\n  \"CardDetail.add-content\": \"Добавить контент\",\n  \"CardDetail.add-icon\": \"Добавить иконку\",\n  \"CardDetail.add-property\": \"+ Добавить свойство\",\n  \"CardDetail.addCardText\": \"добавить текст карточки\",\n  \"CardDetail.limited-body\": \"Перейдите на наш тарифный план Professional или Enterprise, чтобы просматривать архивные карточки, иметь неограниченное количество просмотров для каждой доски, неограниченное количество карточек и многое другое.\",\n  \"CardDetail.limited-button\": \"Обновление\",\n  \"CardDetail.limited-title\": \"Эта карточка скрыта\",\n  \"CardDetail.moveContent\": \"переместить содержимое карты\",\n  \"CardDetail.new-comment-placeholder\": \"Добавить комментарий...\",\n  \"CardDetailProperty.confirm-delete-heading\": \"Подтвердить удаление свойства\",\n  \"CardDetailProperty.confirm-delete-subtext\": \"Вы действительно хотите удалить свойство \\\"{propertyName}\\\"? При его удалении свойство будет удалено со всех карточек на этой доске.\",\n  \"CardDetailProperty.confirm-property-name-change-subtext\": \"Вы действительно хотите изменить свойство \\\"{propertyName}\\\" {customText}? Это повлияет на значение(-я) на {numOfCards} карточке(-ах) на этой доске и может привести к потере данных.\",\n  \"CardDetailProperty.confirm-property-type-change\": \"Подтвердите изменение типа свойства\",\n  \"CardDetailProperty.delete-action-button\": \"Удалить\",\n  \"CardDetailProperty.property-change-action-button\": \"Изменить свойство\",\n  \"CardDetailProperty.property-changed\": \"Свойство изменено успешно!\",\n  \"CardDetailProperty.property-deleted\": \"{propertyName} успешно удалено!\",\n  \"CardDetailProperty.property-name-change-subtext\": \"тип из \\\"{oldPropType}\\\" в \\\"{newPropType}\\\"\",\n  \"CardDetial.limited-link\": \"Узнайте больше о наших планах.\",\n  \"CardDialog.delete-confirmation-dialog-attachment\": \"Подтвердите удаление вложения\",\n  \"CardDialog.delete-confirmation-dialog-button-text\": \"Удалить\",\n  \"CardDialog.delete-confirmation-dialog-heading\": \"Подтвердите удаление карточки\",\n  \"CardDialog.editing-template\": \"Вы редактируете шаблон.\",\n  \"CardDialog.nocard\": \"Эта карточка не существует или недоступна.\",\n  \"Categories.CreateCategoryDialog.CancelText\": \"Отмена\",\n  \"Categories.CreateCategoryDialog.CreateText\": \"Создать\",\n  \"Categories.CreateCategoryDialog.Placeholder\": \"Назовите свою категорию\",\n  \"Categories.CreateCategoryDialog.UpdateText\": \"Обновить\",\n  \"CenterPanel.Login\": \"Логин\",\n  \"CenterPanel.Share\": \"Поделиться\",\n  \"ChannelIntro.CreateBoard\": \"Создать доску\",\n  \"ColorOption.selectColor\": \"Выберите цвет {color}\",\n  \"Comment.delete\": \"Удалить\",\n  \"CommentsList.send\": \"Отправить\",\n  \"ConfirmPerson.empty\": \"Пусто\",\n  \"ConfirmPerson.search\": \"Поиск...\",\n  \"ConfirmationDialog.cancel-action\": \"Отмена\",\n  \"ConfirmationDialog.confirm-action\": \"Подтвердить\",\n  \"ContentBlock.Delete\": \"Удалить\",\n  \"ContentBlock.DeleteAction\": \"удалить\",\n  \"ContentBlock.addElement\": \"добавить {type}\",\n  \"ContentBlock.checkbox\": \"флажок\",\n  \"ContentBlock.divider\": \"разделитель\",\n  \"ContentBlock.editCardCheckbox\": \"помеченный флажок\",\n  \"ContentBlock.editCardCheckboxText\": \"редактировать текст карточки\",\n  \"ContentBlock.editCardText\": \"редактировать текст карточки\",\n  \"ContentBlock.editText\": \"Изменить текст...\",\n  \"ContentBlock.image\": \"изображение\",\n  \"ContentBlock.insertAbove\": \"Вставить выше\",\n  \"ContentBlock.moveBlock\": \"переместить содержимое карточки\",\n  \"ContentBlock.moveDown\": \"Опустить\",\n  \"ContentBlock.moveUp\": \"Поднять\",\n  \"ContentBlock.text\": \"текст\",\n  \"DateRange.clear\": \"Очистить\",\n  \"DateRange.empty\": \"Пусто\",\n  \"DateRange.endDate\": \"Дата окончания\",\n  \"DateRange.today\": \"Сегодня\",\n  \"DeleteBoardDialog.confirm-cancel\": \"Отмена\",\n  \"DeleteBoardDialog.confirm-delete\": \"Удалить\",\n  \"DeleteBoardDialog.confirm-info\": \"Вы уверены, что хотите удалить доску \\\"{boardTitle}\\\"? Ее удаление приведет к удалению всех карточек на доске.\",\n  \"DeleteBoardDialog.confirm-info-template\": \"Вы уверены, что хотите удалить шаблон доски “{boardTitle}”?\",\n  \"DeleteBoardDialog.confirm-tite\": \"Подтвердить удаление доски\",\n  \"DeleteBoardDialog.confirm-tite-template\": \"Подтвердите удаление шаблона Доски\",\n  \"Dialog.closeDialog\": \"Закрыть диалог\",\n  \"EditableDayPicker.today\": \"Сегодня\",\n  \"Error.mobileweb\": \"Мобильная веб-поддержка в настоящее время находится на ранней стадии бета-тестирования. Могут присутствовать не все функции.\",\n  \"Error.websocket-closed\": \"Соединение через веб-сокет закрыто, соединение прервано. Если это не устраняется, проверьте конфигурацию сервера или веб-прокси.\",\n  \"Filter.contains\": \"содержит\",\n  \"Filter.ends-with\": \"заканчивается\",\n  \"Filter.includes\": \"содержит\",\n  \"Filter.is\": \"является\",\n  \"Filter.is-empty\": \"пусто\",\n  \"Filter.is-not-empty\": \"не пусто\",\n  \"Filter.is-not-set\": \"не установлен\",\n  \"Filter.is-set\": \"установлен\",\n  \"Filter.not-contains\": \"не содержит\",\n  \"Filter.not-ends-with\": \"не заканчивается\",\n  \"Filter.not-includes\": \"не содержит\",\n  \"Filter.not-starts-with\": \"не начинается с\",\n  \"Filter.starts-with\": \"начинается с\",\n  \"FilterByText.placeholder\": \"фильтровать текст\",\n  \"FilterComponent.add-filter\": \"+ Добавить фильтр\",\n  \"FilterComponent.delete\": \"Удалить\",\n  \"FilterValue.empty\": \"(пусто)\",\n  \"FindBoardsDialog.IntroText\": \"Поиск досок\",\n  \"FindBoardsDialog.NoResultsFor\": \"Нет результатов для \\\"{searchQuery}\\\"\",\n  \"FindBoardsDialog.NoResultsSubtext\": \"Проверьте правильность написания или попробуйте другой запрос.\",\n  \"FindBoardsDialog.SubTitle\": \"Введите запрос, чтобы найти доску. Используйте <b>ВВЕРХ/ВНИЗ</b> для просмотра. <b>ENTER</b> для выбора, <b>ESC</b> для закрытия\",\n  \"FindBoardsDialog.Title\": \"Найти доски\",\n  \"GroupBy.hideEmptyGroups\": \"Скрыть {count} пустых групп\",\n  \"GroupBy.showHiddenGroups\": \"Показать {count} скрытых групп\",\n  \"GroupBy.ungroup\": \"Разгруппировать\",\n  \"HideBoard.MenuOption\": \"Скрыть доску\",\n  \"KanbanCard.untitled\": \"Без названия\",\n  \"MentionSuggestion.is-not-board-member\": \"(не член правления)\",\n  \"Mutator.new-board-from-template\": \"новая доска из шаблона\",\n  \"Mutator.new-card-from-template\": \"новая карточка из шаблона\",\n  \"Mutator.new-template-from-card\": \"новый шаблон из карточки\",\n  \"OnboardingTour.AddComments.Body\": \"Вы можете комментировать проблемы и даже @упоминать своих коллег-пользователей Mattermost, чтобы привлечь их внимание.\",\n  \"OnboardingTour.AddComments.Title\": \"Добавить комментарии\",\n  \"OnboardingTour.AddDescription.Body\": \"Добавьте описание к своей карточке, чтобы Ваши коллеги по команде знали, о чем эта карточка.\",\n  \"OnboardingTour.AddDescription.Title\": \"Добавить описание\",\n  \"OnboardingTour.AddProperties.Body\": \"Добавляйте различные свойства карточкам, чтобы сделать их более значительными.\",\n  \"OnboardingTour.AddProperties.Title\": \"Добавить свойства\",\n  \"OnboardingTour.AddView.Body\": \"Перейдите сюда, чтобы создать новый вид для организации доски с использованием различных макетов.\",\n  \"OnboardingTour.AddView.Title\": \"Добавить новый вид\",\n  \"OnboardingTour.CopyLink.Body\": \"Вы можете поделиться своими карточками с коллегами по команде, скопировав ссылку и вставив ее в канал, личное сообщение или групповое сообщение.\",\n  \"OnboardingTour.CopyLink.Title\": \"Копировать ссылку\",\n  \"OnboardingTour.OpenACard.Body\": \"Откройте карточку, чтобы изучить мощные способы, с помощью которых Доски могут помочь Вам организовать Вашу работу.\",\n  \"OnboardingTour.OpenACard.Title\": \"Открыть карточку\",\n  \"OnboardingTour.ShareBoard.Body\": \"Вы можете поделиться своей доской внутри своей команды или опубликовать ее для общего доступа за пределами Вашей организации.\",\n  \"OnboardingTour.ShareBoard.Title\": \"Поделиться доской\",\n  \"PersonProperty.board-members\": \"Совет директоров\",\n  \"PersonProperty.non-board-members\": \"Не члены правления\",\n  \"PropertyMenu.Delete\": \"Удалить\",\n  \"PropertyMenu.changeType\": \"Изменить тип свойства\",\n  \"PropertyMenu.selectType\": \"Выберите тип свойства\",\n  \"PropertyMenu.typeTitle\": \"Тип\",\n  \"PropertyType.Checkbox\": \"Флажок\",\n  \"PropertyType.CreatedBy\": \"Создано пользователем\",\n  \"PropertyType.CreatedTime\": \"Время создания\",\n  \"PropertyType.Date\": \"Дата\",\n  \"PropertyType.Email\": \"Email\",\n  \"PropertyType.MultiPerson\": \"Несколько человек\",\n  \"PropertyType.MultiSelect\": \"Многократный выбор\",\n  \"PropertyType.Number\": \"Номер\",\n  \"PropertyType.Person\": \"Персона\",\n  \"PropertyType.Phone\": \"Телефон\",\n  \"PropertyType.Select\": \"Выбрать\",\n  \"PropertyType.Text\": \"Текст\",\n  \"PropertyType.Unknown\": \"Неизвестный\",\n  \"PropertyType.UpdatedBy\": \"Обновлено пользователем\",\n  \"PropertyType.UpdatedTime\": \"Время обновления\",\n  \"PropertyType.Url\": \"URL\",\n  \"PropertyValueElement.empty\": \"Пустой\",\n  \"RegistrationLink.confirmRegenerateToken\": \"Это сделает недействительными ссылки, которые ранее были общими. Продолжить?\",\n  \"RegistrationLink.copiedLink\": \"Скопировано!\",\n  \"RegistrationLink.copyLink\": \"Скопировать ссылку\",\n  \"RegistrationLink.description\": \"Поделитесь этой ссылкой с другими для создания аккаунтов:\",\n  \"RegistrationLink.regenerateToken\": \"Пересоздать токен\",\n  \"RegistrationLink.tokenRegenerated\": \"Регистрационная ссылка пересоздана\",\n  \"ShareBoard.PublishDescription\": \"Публикуйте и делитесь ссылкой \\\"только для чтения\\\" со всеми пользователями сети.\",\n  \"ShareBoard.PublishTitle\": \"Опубликовать в Интернете\",\n  \"ShareBoard.ShareInternal\": \"Поделиться внутри организации\",\n  \"ShareBoard.ShareInternalDescription\": \"Пользователи, у которых есть разрешения, смогут использовать эту ссылку.\",\n  \"ShareBoard.Title\": \"Поделится Доской\",\n  \"ShareBoard.confirmRegenerateToken\": \"Это сделает недействительными ссылки, которые ранее были общими. Продолжить?\",\n  \"ShareBoard.copiedLink\": \"Скопировано!\",\n  \"ShareBoard.copyLink\": \"Скопировать ссылку\",\n  \"ShareBoard.regenerate\": \"Восстановить токен\",\n  \"ShareBoard.searchPlaceholder\": \"Поиск людей\",\n  \"ShareBoard.teamPermissionsText\": \"Все в команде {teamName}\",\n  \"ShareBoard.tokenRegenrated\": \"Токен пересоздан\",\n  \"ShareBoard.userPermissionsRemoveMemberText\": \"Удалить участника\",\n  \"ShareBoard.userPermissionsYouText\": \"(Вы)\",\n  \"ShareTemplate.Title\": \"Поделиться Шаблоном\",\n  \"ShareTemplate.searchPlaceholder\": \"Поиск людей\",\n  \"Sidebar.about\": \"О Focalboard\",\n  \"Sidebar.add-board\": \"+ Добавить доску\",\n  \"Sidebar.changePassword\": \"Изменить пароль\",\n  \"Sidebar.delete-board\": \"Удалить доску\",\n  \"Sidebar.duplicate-board\": \"Дублировать доску\",\n  \"Sidebar.export-archive\": \"Экспорт архива\",\n  \"Sidebar.import\": \"Импорт\",\n  \"Sidebar.import-archive\": \"Импорт архива\",\n  \"Sidebar.invite-users\": \"Пригласить пользователей\",\n  \"Sidebar.logout\": \"Выйти\",\n  \"Sidebar.new-category.badge\": \"Новый\",\n  \"Sidebar.new-category.drag-boards-cta\": \"Перетащите сюда доски...\",\n  \"Sidebar.no-boards-in-category\": \"Без досок внутри\",\n  \"Sidebar.product-tour\": \"Экскурсия по продукту\",\n  \"Sidebar.random-icons\": \"Случайные иконки\",\n  \"Sidebar.set-language\": \"Язык\",\n  \"Sidebar.set-theme\": \"Тема\",\n  \"Sidebar.settings\": \"Настройки\",\n  \"Sidebar.template-from-board\": \"Новый шаблон из доски\",\n  \"Sidebar.untitled-board\": \"(Доска без названия)\",\n  \"Sidebar.untitled-view\": \"(Безымянный вид)\",\n  \"SidebarCategories.BlocksMenu.Move\": \"Перейти к...\",\n  \"SidebarCategories.CategoryMenu.CreateNew\": \"Создать новую категорию\",\n  \"SidebarCategories.CategoryMenu.Delete\": \"Удалить категорию\",\n  \"SidebarCategories.CategoryMenu.DeleteModal.Body\": \"Доски в <b>{categoryName}</b> вернутся к категориям \\\"Доски\\\". Вы не удалены ни с одной доски.\",\n  \"SidebarCategories.CategoryMenu.DeleteModal.Title\": \"Удалить эту категорию?\",\n  \"SidebarCategories.CategoryMenu.Update\": \"Переименовать категорию\",\n  \"SidebarTour.ManageCategories.Body\": \"Создавайте и управляйте пользовательскими категориями. Категории зависят от пользователя, поэтому перемещение доски в вашу категорию не повлияет на других участников, использующих ту же доску.\",\n  \"SidebarTour.ManageCategories.Title\": \"Управление категориями\",\n  \"SidebarTour.SearchForBoards.Body\": \"Откройте переключатель досок (Cmd/Ctrl + K), чтобы быстро найти и добавить доски на боковую панель.\",\n  \"SidebarTour.SearchForBoards.Title\": \"Поиск досок\",\n  \"SidebarTour.SidebarCategories.Body\": \"Все ваши доски теперь организованы под новой боковой панелью. Больше не нужно переключаться между рабочими пространствами. Одноразовые настраиваемые категории, основанные на ваших предыдущих рабочих областях, могли быть автоматически созданы для вас в рамках обновления до версии 7.2. Их можно удалить или отредактировать по своему усмотрению.\",\n  \"SidebarTour.SidebarCategories.Link\": \"Учить больше\",\n  \"SidebarTour.SidebarCategories.Title\": \"Категории боковой панели\",\n  \"SiteStats.total_boards\": \"Всего досок\",\n  \"SiteStats.total_cards\": \"Всего карт\",\n  \"TableComponent.add-icon\": \"Добавить иконку\",\n  \"TableComponent.name\": \"Название\",\n  \"TableComponent.plus-new\": \"+ Создать\",\n  \"TableHeaderMenu.delete\": \"Удалить\",\n  \"TableHeaderMenu.duplicate\": \"Создать дубликат\",\n  \"TableHeaderMenu.hide\": \"Скрыть\",\n  \"TableHeaderMenu.insert-left\": \"Вставить слева\",\n  \"TableHeaderMenu.insert-right\": \"Вставить справа\",\n  \"TableHeaderMenu.sort-ascending\": \"Сортировать по возрастанию\",\n  \"TableHeaderMenu.sort-descending\": \"Сортировать по убыванию\",\n  \"TableRow.DuplicateCard\": \"дублировать карточку\",\n  \"TableRow.MoreOption\": \"Больше действий\",\n  \"TableRow.open\": \"Открыть\",\n  \"TopBar.give-feedback\": \"Дать обратную связь\",\n  \"URLProperty.copiedLink\": \"Скопировано!\",\n  \"URLProperty.copy\": \"Копировать\",\n  \"URLProperty.edit\": \"Изменить\",\n  \"UndoRedoHotKeys.canRedo\": \"Повторить\",\n  \"UndoRedoHotKeys.canRedo-with-description\": \"Повторить {description}\",\n  \"UndoRedoHotKeys.canUndo\": \"Отменить\",\n  \"UndoRedoHotKeys.canUndo-with-description\": \"Отменить {description}\",\n  \"UndoRedoHotKeys.cannotRedo\": \"Нечего переделывать\",\n  \"UndoRedoHotKeys.cannotUndo\": \"Нечего отменить\",\n  \"ValueSelector.noOptions\": \"Нет вариантов. Начните печатать, чтобы добавить первый!\",\n  \"ValueSelector.valueSelector\": \"Выбор значения\",\n  \"ValueSelectorLabel.openMenu\": \"Открыть меню\",\n  \"VersionMessage.help\": \"Узнайте, что нового в этой версии.\",\n  \"View.AddView\": \"Добавить вид\",\n  \"View.Board\": \"Доска\",\n  \"View.DeleteView\": \"Удалить вид\",\n  \"View.DuplicateView\": \"Создать дубликат вида\",\n  \"View.Gallery\": \"Галерея\",\n  \"View.NewBoardTitle\": \"Вид доски\",\n  \"View.NewCalendarTitle\": \"Просмотр календаря\",\n  \"View.NewGalleryTitle\": \"Представление \\\"галерея\\\"\",\n  \"View.NewTableTitle\": \"Вид таблицы\",\n  \"View.NewTemplateDefaultTitle\": \"Шаблон без названия\",\n  \"View.NewTemplateTitle\": \"Шаблон\",\n  \"View.Table\": \"Таблица\",\n  \"ViewHeader.add-template\": \"Новый шаблон\",\n  \"ViewHeader.delete-template\": \"Удалить\",\n  \"ViewHeader.display-by\": \"Показать по: {property}\",\n  \"ViewHeader.edit-template\": \"Изменить\",\n  \"ViewHeader.empty-card\": \"Очистить карточку\",\n  \"ViewHeader.export-board-archive\": \"Экспорт архива доски\",\n  \"ViewHeader.export-complete\": \"Экспорт завершен!\",\n  \"ViewHeader.export-csv\": \"Экспорт в CSV\",\n  \"ViewHeader.export-failed\": \"Ошибка экспорта!\",\n  \"ViewHeader.filter\": \"Фильтр\",\n  \"ViewHeader.group-by\": \"Сгруппировать по: {property}\",\n  \"ViewHeader.new\": \"Создать\",\n  \"ViewHeader.properties\": \"Свойства\",\n  \"ViewHeader.properties-menu\": \"Меню свойств\",\n  \"ViewHeader.search-text\": \"Карточки поиска\",\n  \"ViewHeader.select-a-template\": \"Выбрать шаблон\",\n  \"ViewHeader.set-default-template\": \"Установить по умолчанию\",\n  \"ViewHeader.sort\": \"Сортировать\",\n  \"ViewHeader.untitled\": \"Без названия\",\n  \"ViewHeader.view-header-menu\": \"Посмотреть меню заголовка\",\n  \"ViewHeader.view-menu\": \"Посмотреть меню\",\n  \"ViewLimitDialog.Heading\": \"Достигнут лимит просмотров на доске\",\n  \"ViewLimitDialog.PrimaryButton.Title.Admin\": \"Обновление\",\n  \"ViewLimitDialog.PrimaryButton.Title.RegularUser\": \"Сообщить администратору\",\n  \"ViewLimitDialog.Subtext.Admin\": \"Перейдите на наш план Professional или Enterprise, чтобы иметь неограниченное количество просмотров на досках, неограниченное количество карточек и многое другое.\",\n  \"ViewLimitDialog.Subtext.Admin.PricingPageLink\": \"Узнайте больше о наших тарифах.\",\n  \"ViewLimitDialog.Subtext.RegularUser\": \"Сообщите своему администратору, чтобы перейти на наш план Professional или Enterprise, чтобы иметь неограниченные количество просмотров на доске, карточек и многое другое.\",\n  \"ViewLimitDialog.UpgradeImg.AltText\": \"обновить изображение\",\n  \"ViewLimitDialog.notifyAdmin.Success\": \"Ваш администратор был уведомлен\",\n  \"ViewTitle.hide-description\": \"скрыть описание\",\n  \"ViewTitle.pick-icon\": \"Выбрать иконку\",\n  \"ViewTitle.random-icon\": \"Случайным образом\",\n  \"ViewTitle.remove-icon\": \"Убрать иконку\",\n  \"ViewTitle.show-description\": \"Показать описание\",\n  \"ViewTitle.untitled-board\": \"Доска без названия\",\n  \"WelcomePage.Description\": \"Доски — это инструмент управления проектами, который помогает определять, организовывать, отслеживать и управлять работой между командами, используя знакомое представление доски Kanban.\",\n  \"WelcomePage.Explore.Button\": \"Исследовать\",\n  \"WelcomePage.Heading\": \"Добро пожаловать на Доски\",\n  \"WelcomePage.NoThanks.Text\": \"Нет спасибо, сам разберусь\",\n  \"WelcomePage.StartUsingIt.Text\": \"Начать пользоваться\",\n  \"Workspace.editing-board-template\": \"Вы редактируете шаблон доски.\",\n  \"badge.guest\": \"Гость\",\n  \"boardSelector.confirm-link-board\": \"Привязать доску к каналу\",\n  \"boardSelector.confirm-link-board-button\": \"Да, ссылка доски\",\n  \"boardSelector.confirm-link-board-subtext\": \"Связывание доски \\\"{boardName}\\\" с каналом даст всем участникам канала доступ на редактирование доски. Вы можете в любое время отвязать доску о канала.\",\n  \"boardSelector.confirm-link-board-subtext-with-other-channel\": \"Привязка \\\"{boardName}\\\" с каналом приведет к возможности её редактирования всеми участниками канала (существующими и новыми). Кроме гостей канала.{lineBreak} Эта доска сейчас связана с другим каналом. Он будет отключен, если вы решите изменить привязку.\",\n  \"boardSelector.create-a-board\": \"Создать доску\",\n  \"boardSelector.link\": \"Ссылка\",\n  \"boardSelector.search-for-boards\": \"Поиск досок\",\n  \"boardSelector.title\": \"Ссылки на доски\",\n  \"boardSelector.unlink\": \"Отключить\",\n  \"calendar.month\": \"Месяц\",\n  \"calendar.today\": \"СЕГОДНЯ\",\n  \"calendar.week\": \"Неделя\",\n  \"centerPanel.undefined\": \"Отсутствует {propertyName}\",\n  \"centerPanel.unknown-user\": \"Неизвестный пользователь\",\n  \"cloudMessage.learn-more\": \"Учить больше\",\n  \"createImageBlock.failed\": \"Не удалось загрузить файл. Достигнут предел размера файла.\",\n  \"default-properties.badges\": \"Комментарии и описание\",\n  \"default-properties.title\": \"Заголовок\",\n  \"error.back-to-home\": \"Вернуться на Главную\",\n  \"error.back-to-team\": \"Вернуться в команду\",\n  \"error.board-not-found\": \"Доска не найдена.\",\n  \"error.go-login\": \"Логин\",\n  \"error.invalid-read-only-board\": \"У Вас нет доступа к этой доске. Войдите, чтобы получить доступ к Доскам.\",\n  \"error.not-logged-in\": \"Возможно, срок действия Вашего сеанса истек или Вы не вошли в систему. Войдите еще раз, чтобы получить доступ к Доскам.\",\n  \"error.page.title\": \"Извините, что-то пошло не так\",\n  \"error.team-undefined\": \"Не корректная команда.\",\n  \"error.unknown\": \"Произошла ошибка.\",\n  \"generic.previous\": \"Предыдущий\",\n  \"imagePaste.upload-failed\": \"Некоторые файлы не загружены из-за превышения квоты на размер файла.\",\n  \"limitedCard.title\": \"Карточки скрыты\",\n  \"login.log-in-button\": \"Вход в систему\",\n  \"login.log-in-title\": \"Вход в систему\",\n  \"login.register-button\": \"или создать аккаунт, если у Вас его нет\",\n  \"new_channel_modal.create_board.empty_board_description\": \"Создать новую пустую доску\",\n  \"new_channel_modal.create_board.empty_board_title\": \"Пустая доска\",\n  \"new_channel_modal.create_board.select_template_placeholder\": \"Выбрать шаблон\",\n  \"new_channel_modal.create_board.title\": \"Создать доску для этого канала\",\n  \"notification-box-card-limit-reached.close-tooltip\": \"Отложить на 10 дней\",\n  \"notification-box-card-limit-reached.contact-link\": \"уведомить Вашего администратора\",\n  \"notification-box-card-limit-reached.link\": \"Перейти на платный тариф\",\n  \"notification-box-card-limit-reached.title\": \"{cards} карты, скрытые на доске\",\n  \"notification-box-cards-hidden.title\": \"Это действие скрыло другую карточку\",\n  \"notification-box.card-limit-reached.not-admin.text\": \"Чтобы получить доступ к архивным карточкам, Вы можете {contactLink} перейти на платный тариф.\",\n  \"notification-box.card-limit-reached.text\": \"Достигнут лимит карточки, чтобы просмотреть старые карточки, {link}\",\n  \"person.add-user-to-board\": \"Добавить {username} на доску\",\n  \"person.add-user-to-board-confirm-button\": \"Добавить доску\",\n  \"person.add-user-to-board-permissions\": \"Разрешения\",\n  \"person.add-user-to-board-question\": \"Вы хотите добавить {username} на доску?\",\n  \"register.login-button\": \"или войти в систему, если у вас уже есть аккаунт\",\n  \"register.signup-title\": \"Зарегистрируйте свой аккаунт\",\n  \"rhs-board-non-admin-msg\": \"Вы не являетесь администратором этой доски\",\n  \"rhs-boards.add\": \"Добавить\",\n  \"rhs-boards.last-update-at\": \"Последнее обновление: {datetime}\",\n  \"rhs-boards.link-boards-to-channel\": \"Связать доски с {channelName}\",\n  \"rhs-boards.linked-boards\": \"Связанные доски\",\n  \"rhs-boards.no-boards-linked-to-channel\": \"К каналу {channelName} пока не подключены доски\",\n  \"rhs-boards.no-boards-linked-to-channel-description\": \"Доски — это инструмент управления проектами, который помогает определять, организовывать, отслеживать и управлять работой между командами, используя знакомое представление доски Канбан.\",\n  \"rhs-boards.unlink-board\": \"Отвязать доску\",\n  \"rhs-channel-boards-header.title\": \"Доски\",\n  \"share-board.publish\": \"Опубликовать\",\n  \"share-board.share\": \"Поделиться\",\n  \"shareBoard.channels-select-group\": \"Каналы\",\n  \"shareBoard.lastAdmin\": \"Доски должны иметь хотя бы одного администратора\",\n  \"shareBoard.members-select-group\": \"Участники\",\n  \"tutorial_tip.finish_tour\": \"Готово\",\n  \"tutorial_tip.got_it\": \"Понятно\",\n  \"tutorial_tip.ok\": \"Следующий\",\n  \"tutorial_tip.out\": \"Отказаться от этих советов.\",\n  \"tutorial_tip.seen\": \"Видели это раньше?\"\n}\n"
  },
  {
    "path": "webapp/i18n/sk.json",
    "content": "{\n  \"Attachment.Attachment-title\": \"Príloha\",\n  \"AttachmentBlock.DeleteAction\": \"odstrániť\",\n  \"AttachmentBlock.delete\": \"Príloha odstránená.\",\n  \"AttachmentBlock.failed\": \"Tento súbor nebol nahratý, pretože presiahol veľkostný limit.\",\n  \"AttachmentBlock.upload\": \"Príloha sa nahráva.\",\n  \"AttachmentBlock.uploadSuccess\": \"Príloha bola nahratá.\",\n  \"AttachmentElement.delete-confirmation-dialog-button-text\": \"Odstrániť\",\n  \"AttachmentElement.download\": \"Stiahnuť\",\n  \"AttachmentElement.upload-percentage\": \"Nahrávam... ({uploadPercent}%)\",\n  \"BoardComponent.add-a-group\": \"+ Pridaj skupinu\",\n  \"BoardComponent.delete\": \"Odstrániť\",\n  \"BoardComponent.hidden-columns\": \"Skryté stĺpce\",\n  \"BoardComponent.hide\": \"Skryť\",\n  \"BoardComponent.new\": \"+ Nový\",\n  \"BoardComponent.no-property\": \"žiadna {property}\",\n  \"BoardComponent.no-property-title\": \"Položky s prázdnou {property} pôjdu tu. Tento stĺpec nemožno vymazať.\",\n  \"BoardComponent.show\": \"Ukáž\",\n  \"BoardMember.schemeAdmin\": \"Administrátor\",\n  \"BoardMember.schemeCommenter\": \"Komentátor\",\n  \"BoardMember.schemeEditor\": \"Editor\",\n  \"BoardMember.schemeNone\": \"Žiadny\",\n  \"BoardMember.schemeViewer\": \"Sledovateľ\",\n  \"BoardMember.unlinkChannel\": \"Odpojiť\",\n  \"BoardPage.newVersion\": \"Nová verzia je dostupná, kliknite tu pre znovu načítanie.\",\n  \"BoardPage.syncFailed\": \"Nástenka môže byť vymazaná alebo prístup odobraný.\",\n  \"BoardTemplateSelector.add-template\": \"Vytvoriť novú šablónu\",\n  \"BoardTemplateSelector.create-empty-board\": \"Vytvoriť prázdnu nástenku\",\n  \"BoardTemplateSelector.delete-template\": \"Odstrániť\",\n  \"BoardTemplateSelector.description\": \"Pridajte nástenku do bočného panelu pomocou ktorýchkoľvek šablón definovaných dole alebo začnite od začiatku.\",\n  \"BoardTemplateSelector.edit-template\": \"Upraviť\",\n  \"BoardTemplateSelector.plugin.no-content-description\": \"Pridajte nástenku do bočného panelu pomocou ktorýchkoľvek šablón dole alebo začnite od začiatku.\",\n  \"BoardTemplateSelector.plugin.no-content-title\": \"Vytvoriť nástenku\",\n  \"BoardTemplateSelector.title\": \"Vytvoriť nástenku\",\n  \"BoardTemplateSelector.use-this-template\": \"Použiť túto šablónu\",\n  \"BoardsSwitcher.Title\": \"Hľadať nástenky\",\n  \"BoardsUnfurl.Limited\": \"Ďalšie detaily sú skryté, pretože je karta archivovaná\",\n  \"BoardsUnfurl.Remainder\": \"+{remainder} viac\",\n  \"BoardsUnfurl.Updated\": \"Upravené {time}\",\n  \"Calculations.Options.average.displayName\": \"Priemer\",\n  \"Calculations.Options.average.label\": \"Priemer\",\n  \"Calculations.Options.count.displayName\": \"Počet\",\n  \"Calculations.Options.count.label\": \"Počet\",\n  \"Calculations.Options.countChecked.displayName\": \"Označené\",\n  \"Calculations.Options.countChecked.label\": \"Počítať označené\",\n  \"Calculations.Options.countUnchecked.displayName\": \"Neoznačené\",\n  \"Calculations.Options.countUnchecked.label\": \"Počítať neoznačené\",\n  \"Calculations.Options.countUniqueValue.displayName\": \"Unikátne\",\n  \"Calculations.Options.countUniqueValue.label\": \"Počítať unikátne hodnoty\",\n  \"Calculations.Options.countValue.displayName\": \"Hodnoty\",\n  \"Calculations.Options.countValue.label\": \"Počítať hodnotu\",\n  \"Calculations.Options.dateRange.displayName\": \"Rozsah\",\n  \"Calculations.Options.dateRange.label\": \"Rozsah\",\n  \"Calculations.Options.earliest.displayName\": \"Prvý\",\n  \"Calculations.Options.earliest.label\": \"Prvý\",\n  \"Calculations.Options.latest.displayName\": \"Posledný\",\n  \"Calculations.Options.latest.label\": \"Posledný\",\n  \"Calculations.Options.max.displayName\": \"Max\",\n  \"Calculations.Options.max.label\": \"Max\",\n  \"Calculations.Options.median.displayName\": \"Medián\",\n  \"Calculations.Options.median.label\": \"Medián\",\n  \"Calculations.Options.min.displayName\": \"Min\",\n  \"Calculations.Options.min.label\": \"Min\",\n  \"Calculations.Options.none.displayName\": \"Vypočítať\",\n  \"Calculations.Options.none.label\": \"Nič\",\n  \"Calculations.Options.percentChecked.displayName\": \"Označené\",\n  \"Calculations.Options.percentChecked.label\": \"Percent skontrolovaných\",\n  \"Calculations.Options.percentUnchecked.displayName\": \"Neskontrolované\",\n  \"Calculations.Options.percentUnchecked.label\": \"Percent neskontrolovaných\",\n  \"Calculations.Options.range.displayName\": \"Rozsah\",\n  \"Calculations.Options.range.label\": \"Rozsah\",\n  \"Calculations.Options.sum.displayName\": \"Súčet\",\n  \"Calculations.Options.sum.label\": \"Súčet\",\n  \"CalendarCard.untitled\": \"Bez názvu\",\n  \"CardActionsMenu.copiedLink\": \"Skopírované!\",\n  \"CardActionsMenu.copyLink\": \"Skopírovať odkaz\",\n  \"CardActionsMenu.delete\": \"Odstrániť\",\n  \"CardActionsMenu.duplicate\": \"Duplikovať\",\n  \"CardBadges.title-checkboxes\": \"Začiarkávacie políčka\",\n  \"CardBadges.title-comments\": \"Komentáre\",\n  \"CardBadges.title-description\": \"Táto karta má popis\",\n  \"CardDetail.Attach\": \"Priložiť\",\n  \"CardDetail.Follow\": \"Sledovať\",\n  \"CardDetail.Following\": \"Sledujúci\",\n  \"CardDetail.add-content\": \"Pridať obsah\",\n  \"CardDetail.add-icon\": \"Pridať ikonu\",\n  \"CardDetail.add-property\": \"+ Pridať vlastnosť\",\n  \"CardDetail.addCardText\": \"pridať text karty\",\n  \"CardDetail.limited-body\": \"Vylepšiť na náš Professional alebo Enterprise plán.\",\n  \"CardDetail.limited-button\": \"Zmeniť plán\",\n  \"CardDetail.limited-title\": \"Táto karta je skrytá\",\n  \"CardDetail.moveContent\": \"Presunúť obsah karty\",\n  \"CardDetail.new-comment-placeholder\": \"Pridať komentár...\",\n  \"CardDetailProperty.confirm-delete-heading\": \"Potvrdiť vymazanie vlastnosti\",\n  \"CardDetailProperty.confirm-delete-subtext\": \"Skutočne chcete vymazať hodnotu \\\"{propertyName}\\\"? Bude odstránená zo všetkých kariet na tejto tabuli.\",\n  \"CardDetailProperty.confirm-property-name-change-subtext\": \"Skutočne chcete zmeniť hodnotu \\\"{propertyName}\\\" {customText}? Ovplyvní to {numOfCards} kariet na tabuli a môže viesť k strate dát.\",\n  \"CardDetailProperty.confirm-property-type-change\": \"Potvrdiť zmenu typu vlastnosti\",\n  \"CardDetailProperty.delete-action-button\": \"Odstrániť\",\n  \"CardDetailProperty.property-change-action-button\": \"Zmeniť vlastnosť\",\n  \"CardDetailProperty.property-changed\": \"Zmena vlastnosti úspešná!\",\n  \"CardDetailProperty.property-deleted\": \"Odstránenie {propertyName} úspešné!\",\n  \"CardDetailProperty.property-name-change-subtext\": \"typ z \\\"{oldPropType}\\\" na \\\"{newPropType}\\\"\",\n  \"CardDetial.limited-link\": \"Dozvedieť sa viac o našich plánoch.\",\n  \"CardDialog.delete-confirmation-dialog-attachment\": \"Potvrdiť odstránenie prílohy\",\n  \"CardDialog.delete-confirmation-dialog-button-text\": \"Odstrániť\",\n  \"CardDialog.delete-confirmation-dialog-heading\": \"Potvrdiť odstránenie karty\",\n  \"CardDialog.editing-template\": \"Upravujete šablónu.\",\n  \"CardDialog.nocard\": \"Táto karta neexistuje alebo nie je prístupná.\",\n  \"Categories.CreateCategoryDialog.CancelText\": \"Zrušiť\",\n  \"Categories.CreateCategoryDialog.CreateText\": \"Vytvoriť\",\n  \"Categories.CreateCategoryDialog.Placeholder\": \"Nazvite Vašu kategóriu\",\n  \"Categories.CreateCategoryDialog.UpdateText\": \"Zmeniť\",\n  \"CenterPanel.Login\": \"Prihlásiť sa\",\n  \"CenterPanel.Share\": \"Zdieľať\",\n  \"ChannelIntro.CreateBoard\": \"Vytvoriť nástenku\",\n  \"ColorOption.selectColor\": \"Vyberte {color} farbu\",\n  \"Comment.delete\": \"Odstrániť\",\n  \"CommentsList.send\": \"Odoslať\",\n  \"ConfirmPerson.empty\": \"Prázdne\",\n  \"ConfirmPerson.search\": \"Vyhľadať...\",\n  \"ConfirmationDialog.cancel-action\": \"Zrušiť\",\n  \"ConfirmationDialog.confirm-action\": \"Potvrdiť\",\n  \"ContentBlock.Delete\": \"Odstrániť\",\n  \"ContentBlock.DeleteAction\": \"odstrániť\",\n  \"ContentBlock.addElement\": \"pridať {type}\",\n  \"ContentBlock.checkbox\": \"začiarkávacie pole\",\n  \"ContentBlock.divider\": \"oddeľovač\",\n  \"ContentBlock.editCardCheckbox\": \"Začiarknuté pole\",\n  \"ContentBlock.editCardCheckboxText\": \"upraviť text karty\",\n  \"ContentBlock.editCardText\": \"upraviť text karty\",\n  \"ContentBlock.editText\": \"Upraviť text...\",\n  \"ContentBlock.image\": \"obrázok\",\n  \"ContentBlock.insertAbove\": \"Vložiť nad\",\n  \"ContentBlock.moveBlock\": \"presunúť obsah karty\",\n  \"ContentBlock.moveDown\": \"Presunúť dole\",\n  \"ContentBlock.moveUp\": \"Presunúť hore\",\n  \"ContentBlock.text\": \"text\",\n  \"DateRange.clear\": \"Vyčistiť\",\n  \"DateRange.empty\": \"Prázdny\",\n  \"DateRange.endDate\": \"Koncový dátum\",\n  \"DateRange.today\": \"Dnes\",\n  \"DeleteBoardDialog.confirm-cancel\": \"Zrušiť\",\n  \"DeleteBoardDialog.confirm-delete\": \"Odstrániť\",\n  \"DeleteBoardDialog.confirm-info\": \"Naozaj chcete odstrániť nástenku “{boardTitle}”? Odstránením vymažete všetky karty na tabuli.\",\n  \"DeleteBoardDialog.confirm-info-template\": \"Naozaj chcete odstrániť nástenkovú šablónu \\\"{boardTitle}\\\"?\",\n  \"DeleteBoardDialog.confirm-tite\": \"Potvrďte odstránenie nástenky\",\n  \"DeleteBoardDialog.confirm-tite-template\": \"Potvrdiť odstránenie šablóny nástenky\",\n  \"Dialog.closeDialog\": \"Zatvoriť dialógové okno\",\n  \"EditableDayPicker.today\": \"Dnes\",\n  \"Error.mobileweb\": \"Podpora pre mobilné prehliadače je v skorej bete. Niektoré funkcionality môžu chýbať.\",\n  \"Error.websocket-closed\": \"Websocket pripojenie zlyhalo - bolo prerušené. Pokiaľ problém pretrváva, skontrolujte konfiguráciu servera.\",\n  \"Filter.contains\": \"obsahuje\",\n  \"Filter.ends-with\": \"končí s\",\n  \"Filter.includes\": \"zahŕňa\",\n  \"Filter.is\": \"je\",\n  \"Filter.is-empty\": \"je prázdny\",\n  \"Filter.is-not-empty\": \"nie je prázdny\",\n  \"Filter.is-not-set\": \"nie je nastavený\",\n  \"Filter.is-set\": \"je nastavený\",\n  \"Filter.not-contains\": \"neobsahuje\",\n  \"Filter.not-ends-with\": \"nekončí s\",\n  \"Filter.not-includes\": \"nezahŕňa\",\n  \"Filter.not-starts-with\": \"nezačína s\",\n  \"Filter.starts-with\": \"začína s\",\n  \"FilterByText.placeholder\": \"text filtra\",\n  \"FilterComponent.add-filter\": \"+ Pridaj filter\",\n  \"FilterComponent.delete\": \"Odstrániť\",\n  \"FilterValue.empty\": \"(prázdny)\",\n  \"FindBoardsDialog.IntroText\": \"Vyhľadať nástenky\",\n  \"FindBoardsDialog.NoResultsFor\": \"Žiadne výsledky pre \\\"{searchQuery}\\\"\",\n  \"FindBoardsDialog.NoResultsSubtext\": \"Skontrolujte pravopis alebo vyskúšajte iný pojem.\",\n  \"FindBoardsDialog.SubTitle\": \"Nájdite nástenku písaním. Použite <b>HORE/DOLE</b> na prehliadanie, <b>ENTER</b> na vybratie a <b>ESC</b> na zrušenie\",\n  \"FindBoardsDialog.Title\": \"Nájsť nástenky\",\n  \"GroupBy.hideEmptyGroups\": \"Skryť {count} prázdnych skupín\",\n  \"GroupBy.showHiddenGroups\": \"Zobraziť {count} prázdnych skupín\",\n  \"GroupBy.ungroup\": \"Zrušiť zoskupenie\",\n  \"HideBoard.MenuOption\": \"Skryť nástenku\",\n  \"KanbanCard.untitled\": \"Bez názvu\",\n  \"MentionSuggestion.is-not-board-member\": \"(nie je členom nástenky)\",\n  \"Mutator.new-board-from-template\": \"nová nástenka zo šablóny\",\n  \"Mutator.new-card-from-template\": \"nová karta zo šablóny\",\n  \"Mutator.new-template-from-card\": \"nová šablóna z karty\",\n  \"PropertyMenu.Delete\": \"Odstrániť\",\n  \"PropertyMenu.changeType\": \"Zmeniť vlastnosť\",\n  \"PropertyMenu.selectType\": \"Vybrať vlastnosť\",\n  \"PropertyMenu.typeTitle\": \"Typ\",\n  \"PropertyType.Checkbox\": \"Checkbox\",\n  \"PropertyType.CreatedBy\": \"Vytvoril\",\n  \"PropertyType.CreatedTime\": \"Vytvorené\",\n  \"PropertyType.Date\": \"Dátum\",\n  \"PropertyType.Email\": \"Email\",\n  \"PropertyType.MultiSelect\": \"Viacnásobný výber\",\n  \"PropertyType.Number\": \"číslo\",\n  \"PropertyType.Person\": \"Osoba\",\n  \"PropertyType.Phone\": \"Telefón\",\n  \"PropertyType.Select\": \"Vyber\",\n  \"PropertyType.Text\": \"Text\",\n  \"PropertyType.UpdatedBy\": \"Naposledy upravil\",\n  \"PropertyType.UpdatedTime\": \"Posledná úprava\",\n  \"PropertyValueElement.empty\": \"Prázdny\",\n  \"RegistrationLink.confirmRegenerateToken\": \"Toto zruší platnosť predtým zdieľaných odkazov. Pokračovať?\",\n  \"RegistrationLink.copiedLink\": \"Skopírované!\",\n  \"RegistrationLink.copyLink\": \"Skopírovať odkaz\",\n  \"RegistrationLink.description\": \"Zdieľajte tento odkaz pre vytvorenie účtu:\",\n  \"RegistrationLink.regenerateToken\": \"Obnoviť token\",\n  \"RegistrationLink.tokenRegenerated\": \"Registračný odkaz obnovený\",\n  \"ShareBoard.confirmRegenerateToken\": \"Toto zruší platnosť predtým zdieľaných odkazov. Pokračovať?\",\n  \"ShareBoard.copiedLink\": \"Skopírované!\",\n  \"ShareBoard.copyLink\": \"Skopírovať odkaz\",\n  \"ShareBoard.tokenRegenrated\": \"Token obnovený\",\n  \"Sidebar.about\": \"O Focalboard\",\n  \"Sidebar.add-board\": \"+ Pridať nástenku\",\n  \"Sidebar.changePassword\": \"Zmeniť heslo\",\n  \"Sidebar.delete-board\": \"Odstrániť nástenku\",\n  \"Sidebar.export-archive\": \"Export archívu\",\n  \"Sidebar.import-archive\": \"Import archívu\",\n  \"Sidebar.invite-users\": \"Pozvať užívateľa\",\n  \"Sidebar.logout\": \"Odhlásiť sa\",\n  \"Sidebar.random-icons\": \"Náhodné ikony\",\n  \"Sidebar.set-language\": \"Nastaviť jazyk\",\n  \"Sidebar.set-theme\": \"Nastaviť tému\",\n  \"Sidebar.settings\": \"nastavenia\",\n  \"Sidebar.untitled-board\": \"(nástenka bez názvu)\",\n  \"TableComponent.add-icon\": \"Pridať ikonu\",\n  \"TableComponent.name\": \"názov\",\n  \"TableComponent.plus-new\": \"+ Nový\",\n  \"TableHeaderMenu.delete\": \"Odstrániť\",\n  \"TableHeaderMenu.duplicate\": \"Duplikuj\",\n  \"TableHeaderMenu.hide\": \"Skryť\",\n  \"TableHeaderMenu.insert-left\": \"Vložiť vľavo\",\n  \"TableHeaderMenu.insert-right\": \"Vložiť vpravo\",\n  \"TableHeaderMenu.sort-ascending\": \"vzostupne\",\n  \"TableHeaderMenu.sort-descending\": \"zostupne\",\n  \"TableRow.open\": \"Otvoriť\",\n  \"TopBar.give-feedback\": \"Spätná väzba\",\n  \"ValueSelector.noOptions\": \"Žiadne možnosti. Pridajte prvú!\",\n  \"ValueSelector.valueSelector\": \"Výber hodnoty\",\n  \"ValueSelectorLabel.openMenu\": \"Otvor menu\",\n  \"View.AddView\": \"Pridaj pohľad\",\n  \"View.Board\": \"nástenka\",\n  \"View.DeleteView\": \"Odstrániť pohľad\",\n  \"View.DuplicateView\": \"Duplikuj pohľad\",\n  \"View.Gallery\": \"Galéria\",\n  \"View.NewBoardTitle\": \"Náhľad nástenky\",\n  \"View.NewCalendarTitle\": \"Kalendár\",\n  \"View.NewGalleryTitle\": \"Galéria\",\n  \"View.NewTableTitle\": \"Tabuľka\",\n  \"View.Table\": \"Tabuľka\",\n  \"ViewHeader.add-template\": \"Nový template\",\n  \"ViewHeader.delete-template\": \"Odstrániť\",\n  \"ViewHeader.display-by\": \"Zobraziť podľa: {property}\",\n  \"ViewHeader.edit-template\": \"Upraviť\",\n  \"ViewHeader.empty-card\": \"Prázdna karta\",\n  \"ViewHeader.export-board-archive\": \"Export archívu nástenky\",\n  \"ViewHeader.export-complete\": \"Export hotový!\",\n  \"ViewHeader.export-csv\": \"Export do CSV\",\n  \"ViewHeader.export-failed\": \"Export zlyhal!\",\n  \"ViewHeader.filter\": \"Filter\",\n  \"ViewHeader.group-by\": \"Zoskupiť podľa: {property}\",\n  \"ViewHeader.new\": \"Nový\",\n  \"ViewHeader.properties\": \"Vlastnosti\",\n  \"ViewHeader.search-text\": \"Hľadať text\",\n  \"ViewHeader.select-a-template\": \"vyber template\",\n  \"ViewHeader.set-default-template\": \"Nastaviť ako predvolenú\",\n  \"ViewHeader.sort\": \"Triediť\",\n  \"ViewHeader.untitled\": \"Bez názvu\",\n  \"ViewTitle.hide-description\": \"Skryť popis\",\n  \"ViewTitle.pick-icon\": \"Vybrať ikonu\",\n  \"ViewTitle.random-icon\": \"Náhodne\",\n  \"ViewTitle.remove-icon\": \"Odstrániť ikonu\",\n  \"ViewTitle.show-description\": \"zobraziť popis\",\n  \"ViewTitle.untitled-board\": \"nástenka bez názvu\",\n  \"WelcomePage.Description\": \"Nástenky sú nástroj na riadenie projektov, ktorý pomáha definovať, organizovať, sledovať a riadiť prácu medzi tímami pomocou zobrazenia kanban\",\n  \"WelcomePage.Explore.Button\": \"Preskúmať\",\n  \"WelcomePage.Heading\": \"Vitajte\",\n  \"Workspace.editing-board-template\": \"Upravujete template nástenky.\",\n  \"calendar.month\": \"Mesiac\",\n  \"calendar.today\": \"Dnes\",\n  \"calendar.week\": \"Týždeň\",\n  \"default-properties.title\": \"Názov\",\n  \"login.log-in-button\": \"Prihlásiť\",\n  \"login.log-in-title\": \"Prihlásiť\",\n  \"login.register-button\": \"alebo vytvoriť účet, ak žiadny nemáte\",\n  \"register.login-button\": \"alebo sa prihláste ak už máte účet\",\n  \"register.signup-title\": \"Zaregistrujte si účet\"\n}\n"
  },
  {
    "path": "webapp/i18n/sl.json",
    "content": "{\n  \"BoardComponent.add-a-group\": \"+ Dodaj skupino\",\n  \"BoardComponent.delete\": \"Izbriši\",\n  \"BoardComponent.hidden-columns\": \"Skriti stolpci\",\n  \"BoardComponent.hide\": \"Skrij\",\n  \"BoardComponent.new\": \"+ Novo\",\n  \"BoardComponent.no-property\": \"Ni {property}\",\n  \"BoardComponent.no-property-title\": \"Elementi s prazno lastnostjo {property} bodo šli sem. Tega stolpca ni mogoče odstraniti.\",\n  \"BoardComponent.show\": \"Pokaži\",\n  \"BoardPage.newVersion\": \"Na voljo je nova različica Boards, kliknite tukaj za ponovno nalaganje.\",\n  \"BoardPage.syncFailed\": \"Plošča se lahko izbriše ali dostop prekliče.\"\n}\n"
  },
  {
    "path": "webapp/i18n/sv.json",
    "content": "{\n  \"AppBar.Tooltip\": \"Växla länkade boards\",\n  \"Attachment.Attachment-title\": \"Bilaga\",\n  \"AttachmentBlock.DeleteAction\": \"radera\",\n  \"AttachmentBlock.addElement\": \"lägg till {type}\",\n  \"AttachmentBlock.delete\": \"Bilagan har tagits bort.\",\n  \"AttachmentBlock.failed\": \"Flen kunde inte laddas upp eftersom gränsen för filstorlek har nåtts.\",\n  \"AttachmentBlock.upload\": \"Bilagor laddas upp.\",\n  \"AttachmentBlock.uploadSuccess\": \"Bilagan är uppladdad.\",\n  \"AttachmentElement.delete-confirmation-dialog-button-text\": \"Radera\",\n  \"AttachmentElement.download\": \"Ladda ner\",\n  \"AttachmentElement.upload-percentage\": \"Laddar upp...({uploadPercent}%)\",\n  \"BoardComponent.add-a-group\": \"+ Lägg till grupp\",\n  \"BoardComponent.delete\": \"Radera\",\n  \"BoardComponent.hidden-columns\": \"Dolda kolumner\",\n  \"BoardComponent.hide\": \"Dölj\",\n  \"BoardComponent.new\": \"+ Ny\",\n  \"BoardComponent.no-property\": \"Ingen {property}\",\n  \"BoardComponent.no-property-title\": \"Objekt med en tom {property} egenskap grupperas här. Denna kolumn kan inte tas bort.\",\n  \"BoardComponent.show\": \"Visa\",\n  \"BoardMember.schemeAdmin\": \"Administratör\",\n  \"BoardMember.schemeCommenter\": \"Kommentator\",\n  \"BoardMember.schemeEditor\": \"Redaktör\",\n  \"BoardMember.schemeNone\": \"Inget\",\n  \"BoardMember.schemeViewer\": \"Granskare\",\n  \"BoardMember.unlinkChannel\": \"Koppla ifrån\",\n  \"BoardPage.newVersion\": \"En ny version av Boards finns tillgänglig. Klicka här för att uppdatera.\",\n  \"BoardPage.syncFailed\": \"Denna Board kan ha blivit raderad eller så har din behörighet tagits bort.\",\n  \"BoardTemplateSelector.add-template\": \"Skapa ny mall\",\n  \"BoardTemplateSelector.create-empty-board\": \"Skapa en tom board\",\n  \"BoardTemplateSelector.delete-template\": \"Ta bort\",\n  \"BoardTemplateSelector.description\": \"Lägg till ett Board till sidomenyn genom att välja någon av mallarna nedan eller börja med en tom.\",\n  \"BoardTemplateSelector.edit-template\": \"Ändra\",\n  \"BoardTemplateSelector.plugin.no-content-description\": \"Lägg till en Board till sidofältet genom att använda en av mallarna nedan eller starta med en tom.\",\n  \"BoardTemplateSelector.plugin.no-content-title\": \"Skapa en Board\",\n  \"BoardTemplateSelector.title\": \"Skapa en board\",\n  \"BoardTemplateSelector.use-this-template\": \"Använd den här mallen\",\n  \"BoardsSwitcher.Title\": \"Hitta board\",\n  \"BoardsUnfurl.Limited\": \"Ytterligare uppgifter är dolda eftersom kortet är arkiverat\",\n  \"BoardsUnfurl.Remainder\": \"+{remainder} mer\",\n  \"BoardsUnfurl.Updated\": \"Uppdaterad {time}\",\n  \"Calculations.Options.average.displayName\": \"Genomsnitt\",\n  \"Calculations.Options.average.label\": \"Genomsnitt\",\n  \"Calculations.Options.count.displayName\": \"Räkna\",\n  \"Calculations.Options.count.label\": \"Räkna\",\n  \"Calculations.Options.countChecked.displayName\": \"Vald\",\n  \"Calculations.Options.countChecked.label\": \"Räkna valda\",\n  \"Calculations.Options.countUnchecked.displayName\": \"Ej vald\",\n  \"Calculations.Options.countUnchecked.label\": \"Räkna ej valda\",\n  \"Calculations.Options.countUniqueValue.displayName\": \"Unika\",\n  \"Calculations.Options.countUniqueValue.label\": \"Räkna unika värden\",\n  \"Calculations.Options.countValue.displayName\": \"Värden\",\n  \"Calculations.Options.countValue.label\": \"Räkna värden\",\n  \"Calculations.Options.dateRange.displayName\": \"Intervall\",\n  \"Calculations.Options.dateRange.label\": \"Intervall\",\n  \"Calculations.Options.earliest.displayName\": \"Tidigast\",\n  \"Calculations.Options.earliest.label\": \"Tidigast\",\n  \"Calculations.Options.latest.displayName\": \"Senast\",\n  \"Calculations.Options.latest.label\": \"Senast\",\n  \"Calculations.Options.max.displayName\": \"Max\",\n  \"Calculations.Options.max.label\": \"Max\",\n  \"Calculations.Options.median.displayName\": \"Median\",\n  \"Calculations.Options.median.label\": \"Median\",\n  \"Calculations.Options.min.displayName\": \"Min\",\n  \"Calculations.Options.min.label\": \"Min\",\n  \"Calculations.Options.none.displayName\": \"Beräkna\",\n  \"Calculations.Options.none.label\": \"Ingen\",\n  \"Calculations.Options.percentChecked.displayName\": \"Avbockad\",\n  \"Calculations.Options.percentChecked.label\": \"Procent avbockade\",\n  \"Calculations.Options.percentUnchecked.displayName\": \"Ej avbockad\",\n  \"Calculations.Options.percentUnchecked.label\": \"Procent ej avbockade\",\n  \"Calculations.Options.range.displayName\": \"Intervall\",\n  \"Calculations.Options.range.label\": \"Intervall\",\n  \"Calculations.Options.sum.displayName\": \"Summa\",\n  \"Calculations.Options.sum.label\": \"Summa\",\n  \"CalendarCard.untitled\": \"Saknar titel\",\n  \"CardActionsMenu.copiedLink\": \"Kopierad!\",\n  \"CardActionsMenu.copyLink\": \"Kopiera länk\",\n  \"CardActionsMenu.delete\": \"Radera\",\n  \"CardActionsMenu.duplicate\": \"Duplicera\",\n  \"CardBadges.title-checkboxes\": \"Kryssrutor\",\n  \"CardBadges.title-comments\": \"Kommentarer\",\n  \"CardBadges.title-description\": \"Detta kort har en beskrivning\",\n  \"CardDetail.Attach\": \"Bifoga\",\n  \"CardDetail.Follow\": \"Följ\",\n  \"CardDetail.Following\": \"Följer\",\n  \"CardDetail.add-content\": \"Lägg till innehåll\",\n  \"CardDetail.add-icon\": \"Lägg till ikon\",\n  \"CardDetail.add-property\": \"+ Lägg till egenskap\",\n  \"CardDetail.addCardText\": \"lägg till korttext\",\n  \"CardDetail.limited-body\": \"Uppgradera till Professional- eller Enterprise-abonnemang.\",\n  \"CardDetail.limited-button\": \"Uppgradera\",\n  \"CardDetail.limited-title\": \"Detta kort är dolt\",\n  \"CardDetail.moveContent\": \"Flytta kortinnehåll\",\n  \"CardDetail.new-comment-placeholder\": \"Lägg till kommentar...\",\n  \"CardDetailProperty.confirm-delete-heading\": \"Bekräfta ta bort egenskap\",\n  \"CardDetailProperty.confirm-delete-subtext\": \"Är du säker på att du vill ta bort egenskapen \\\"{propertyName}\\\"? Om du raderar den kommer egenskapen tas bort från alla kort på tavlan.\",\n  \"CardDetailProperty.confirm-property-name-change-subtext\": \"Är du säker du vill ändra egenskapen \\\"{propertyName}\\\" {customText}? Detta kommer påverka alla värden på {numOfCards} kort på den här boarden och kan innebära att du förlorar information.\",\n  \"CardDetailProperty.confirm-property-type-change\": \"Bekräfta ändring av egenskapstyp\",\n  \"CardDetailProperty.delete-action-button\": \"Ta bort\",\n  \"CardDetailProperty.property-change-action-button\": \"Ändra egenskap\",\n  \"CardDetailProperty.property-changed\": \"Ändrade egenskap!\",\n  \"CardDetailProperty.property-deleted\": \"{propertyName} har raderats!\",\n  \"CardDetailProperty.property-name-change-subtext\": \"typ från \\\"{oldPropType}\\\" till \\\"{newPropType}\\\"\",\n  \"CardDetial.limited-link\": \"Läs mer om våra abonnemang.\",\n  \"CardDialog.delete-confirmation-dialog-attachment\": \"Bekräfta att bilagor ska raderas\",\n  \"CardDialog.delete-confirmation-dialog-button-text\": \"Radera\",\n  \"CardDialog.delete-confirmation-dialog-heading\": \"Bekräfta att kortet ska raderas\",\n  \"CardDialog.editing-template\": \"Du redigerar en mall.\",\n  \"CardDialog.nocard\": \"Detta kort existerar inte eller är oåtkomligt.\",\n  \"Categories.CreateCategoryDialog.CancelText\": \"Avbryt\",\n  \"Categories.CreateCategoryDialog.CreateText\": \"Skapa\",\n  \"Categories.CreateCategoryDialog.Placeholder\": \"Namnge din kategori\",\n  \"Categories.CreateCategoryDialog.UpdateText\": \"Uppdatera\",\n  \"CenterPanel.Login\": \"Logga in\",\n  \"CenterPanel.Share\": \"Dela\",\n  \"ChannelIntro.CreateBoard\": \"Skapa en board\",\n  \"ColorOption.selectColor\": \"Välj {color} färg\",\n  \"Comment.delete\": \"Radera\",\n  \"CommentsList.send\": \"Skicka\",\n  \"ConfirmPerson.empty\": \"Tom\",\n  \"ConfirmPerson.search\": \"Sök...\",\n  \"ConfirmationDialog.cancel-action\": \"Avbryt\",\n  \"ConfirmationDialog.confirm-action\": \"Godkänn\",\n  \"ContentBlock.Delete\": \"Radera\",\n  \"ContentBlock.DeleteAction\": \"radera\",\n  \"ContentBlock.addElement\": \"lägg till {type}\",\n  \"ContentBlock.checkbox\": \"kryssrutan\",\n  \"ContentBlock.divider\": \"avdelare\",\n  \"ContentBlock.editCardCheckbox\": \"markerad krysskruta\",\n  \"ContentBlock.editCardCheckboxText\": \"redigera korttext\",\n  \"ContentBlock.editCardText\": \"redigera korttext\",\n  \"ContentBlock.editText\": \"Redigera text...\",\n  \"ContentBlock.image\": \"bild\",\n  \"ContentBlock.insertAbove\": \"Lägg till ovanför\",\n  \"ContentBlock.moveBlock\": \"flytta kortets innehåll\",\n  \"ContentBlock.moveDown\": \"Flytta ned\",\n  \"ContentBlock.moveUp\": \"Flytta upp\",\n  \"ContentBlock.text\": \"text\",\n  \"DateRange.clear\": \"Rensa\",\n  \"DateRange.empty\": \"Tom\",\n  \"DateRange.endDate\": \"Slutdatum\",\n  \"DateRange.today\": \"Idag\",\n  \"DeleteBoardDialog.confirm-cancel\": \"Avbryt\",\n  \"DeleteBoardDialog.confirm-delete\": \"Radera\",\n  \"DeleteBoardDialog.confirm-info\": \"Är du säker på att du vill ta bort board “{boardTitle}”? När du tar bort den kommer du radera alla kort på board.\",\n  \"DeleteBoardDialog.confirm-info-template\": \"Är du säker på att du vill ta bort board-mallen \\\"{boardTitle}\\\"?\",\n  \"DeleteBoardDialog.confirm-tite\": \"Bekräfta att ta bort board\",\n  \"DeleteBoardDialog.confirm-tite-template\": \"Bekräfta att board-mallen ska raderas\",\n  \"Dialog.closeDialog\": \"Stäng dialog\",\n  \"EditableDayPicker.today\": \"Idag\",\n  \"Error.mobileweb\": \"Webbåtkomst via mobilen är i tidig betaversion. All funktionalitet kanske inte är tillgänglig.\",\n  \"Error.websocket-closed\": \"Websocketanslutningen stängdes då anslutningen avbröts. Om detta fortgår, kontrollera din server eller webproxykonfigurationen.\",\n  \"Filter.contains\": \"innehåller\",\n  \"Filter.ends-with\": \"slutar med\",\n  \"Filter.includes\": \"inkluderar\",\n  \"Filter.is\": \"är\",\n  \"Filter.is-empty\": \"är tomt\",\n  \"Filter.is-not-empty\": \"är inte tomt\",\n  \"Filter.is-not-set\": \"är inte inställd\",\n  \"Filter.is-set\": \"är inställd\",\n  \"Filter.not-contains\": \"innehåller inte\",\n  \"Filter.not-ends-with\": \"slutar inte med\",\n  \"Filter.not-includes\": \"inkluderar inte\",\n  \"Filter.not-starts-with\": \"börjar inte med\",\n  \"Filter.starts-with\": \"börjar med\",\n  \"FilterByText.placeholder\": \"filtrera text\",\n  \"FilterComponent.add-filter\": \"+ Lägg till filter\",\n  \"FilterComponent.delete\": \"Radera\",\n  \"FilterValue.empty\": \"(tom)\",\n  \"FindBoardsDialog.IntroText\": \"Sök efter boards\",\n  \"FindBoardsDialog.NoResultsFor\": \"Inga sökresultat för \\\"{searchQuery}\\\"\",\n  \"FindBoardsDialog.NoResultsSubtext\": \"Kontrollera stavningen eller sök igen.\",\n  \"FindBoardsDialog.SubTitle\": \"Skriv för att hitta en board. Använd <b>UPP/NER</b> för att bläddra. <b>RETUR</b> för att välja, <b>ESC</b> för att avbryta\",\n  \"FindBoardsDialog.Title\": \"Hitta board\",\n  \"GroupBy.hideEmptyGroups\": \"Dölj {count} tomma grupper\",\n  \"GroupBy.showHiddenGroups\": \"Visa {count} dolda grupper\",\n  \"GroupBy.ungroup\": \"Dela upp grupp\",\n  \"HideBoard.MenuOption\": \"Dölj board\",\n  \"KanbanCard.untitled\": \"Saknar titel\",\n  \"MentionSuggestion.is-not-board-member\": \"(inte board-medlem)\",\n  \"Mutator.new-board-from-template\": \"ny board från en mall\",\n  \"Mutator.new-card-from-template\": \"nytt kort från mall\",\n  \"Mutator.new-template-from-card\": \"ny mall från kort\",\n  \"OnboardingTour.AddComments.Body\": \"Du kan kommentera ämnen och till och med @omnämna andra Mattermost-användare för att få deras uppmärksamhet.\",\n  \"OnboardingTour.AddComments.Title\": \"Lägg till kommentarer\",\n  \"OnboardingTour.AddDescription.Body\": \"Lägg till en beskrivning till kortet så ditt team vet vad kortet handlar om.\",\n  \"OnboardingTour.AddDescription.Title\": \"Lägg till beskrivning\",\n  \"OnboardingTour.AddProperties.Body\": \"Lägg till egenskaper till korten för att göra dem mer informativa.\",\n  \"OnboardingTour.AddProperties.Title\": \"Lägg till egenskaper\",\n  \"OnboardingTour.AddView.Body\": \"Gå hit för att skapa en ny vy för att organisera ditt Board med hjälp av olika layouter.\",\n  \"OnboardingTour.AddView.Title\": \"Lägg till en ny vy\",\n  \"OnboardingTour.CopyLink.Body\": \"Du kan dela dina kort med ditt team genom att kopiera länken och klistra in den i en kanal, ett direktmeddelande eller ett gruppmeddelande.\",\n  \"OnboardingTour.CopyLink.Title\": \"Kopiera länken\",\n  \"OnboardingTour.OpenACard.Body\": \"Öppna ett kort för att utforska produktiva sätt som Boards kan hjälpa dig att organisera ditt arbete.\",\n  \"OnboardingTour.OpenACard.Title\": \"Öppna ett kort\",\n  \"OnboardingTour.ShareBoard.Body\": \"Du kan dela ditt Board internt, inom ditt team, eller publicera den publikt för att visa det utanför din organisation.\",\n  \"OnboardingTour.ShareBoard.Title\": \"Dela Board\",\n  \"PersonProperty.board-members\": \"Board-medlemmar\",\n  \"PersonProperty.me\": \"Jag\",\n  \"PersonProperty.non-board-members\": \"Inte board-medlemmar\",\n  \"PropertyMenu.Delete\": \"Radera\",\n  \"PropertyMenu.changeType\": \"Ändra egenskapstyp\",\n  \"PropertyMenu.selectType\": \"Välj typ av egenskap\",\n  \"PropertyMenu.typeTitle\": \"Typ\",\n  \"PropertyType.Checkbox\": \"Checkruta\",\n  \"PropertyType.CreatedBy\": \"Skapad av\",\n  \"PropertyType.CreatedTime\": \"Skapad tid\",\n  \"PropertyType.Date\": \"Datum\",\n  \"PropertyType.Email\": \"Email\",\n  \"PropertyType.MultiPerson\": \"Flera personer\",\n  \"PropertyType.MultiSelect\": \"Flervalsalternativ\",\n  \"PropertyType.Number\": \"Tal\",\n  \"PropertyType.Person\": \"Person\",\n  \"PropertyType.Phone\": \"Telefon\",\n  \"PropertyType.Select\": \"Alternativ\",\n  \"PropertyType.Text\": \"Text\",\n  \"PropertyType.Unknown\": \"Okänd\",\n  \"PropertyType.UpdatedBy\": \"Senast ändrad av\",\n  \"PropertyType.UpdatedTime\": \"Senast uppdaterad\",\n  \"PropertyType.Url\": \"URL\",\n  \"PropertyValueElement.empty\": \"Tom\",\n  \"RegistrationLink.confirmRegenerateToken\": \"Det här kommer att göra tidigare delade länkar ogiltiga. Vill du fortsätta?\",\n  \"RegistrationLink.copiedLink\": \"Kopierad!\",\n  \"RegistrationLink.copyLink\": \"Kopiera länk\",\n  \"RegistrationLink.description\": \"Dela denna länk med andra för att skapa konton:\",\n  \"RegistrationLink.regenerateToken\": \"Återskapa åtkomstnyckel\",\n  \"RegistrationLink.tokenRegenerated\": \"Registreringslänk återskapad\",\n  \"ShareBoard.PublishDescription\": \"Publicera och dela en skrivskyddad länk med alla på webben.\",\n  \"ShareBoard.PublishTitle\": \"Publicera på webben\",\n  \"ShareBoard.ShareInternal\": \"Dela internt\",\n  \"ShareBoard.ShareInternalDescription\": \"Användare som har behörighet kan använda denna länk.\",\n  \"ShareBoard.Title\": \"Dela Board\",\n  \"ShareBoard.confirmRegenerateToken\": \"Det här kommer att göra tidigare delade länkar ogiltiga. Vill du fortsätta?\",\n  \"ShareBoard.copiedLink\": \"Kopierad!\",\n  \"ShareBoard.copyLink\": \"Kopiera länk\",\n  \"ShareBoard.regenerate\": \"Generera nytt Token\",\n  \"ShareBoard.searchPlaceholder\": \"Sök efter personer och kanaler\",\n  \"ShareBoard.teamPermissionsText\": \"Alla i teamet {teamName}\",\n  \"ShareBoard.tokenRegenrated\": \"Åtkomstnyckel återskapad\",\n  \"ShareBoard.userPermissionsRemoveMemberText\": \"Ta bort användare\",\n  \"ShareBoard.userPermissionsYouText\": \"(du)\",\n  \"ShareTemplate.Title\": \"Dela mallen\",\n  \"ShareTemplate.searchPlaceholder\": \"Sök efter personer\",\n  \"Sidebar.about\": \"Om Focalboard\",\n  \"Sidebar.add-board\": \"+ Lägg till tavla\",\n  \"Sidebar.changePassword\": \"Ändra lösenord\",\n  \"Sidebar.delete-board\": \"Radera tavla\",\n  \"Sidebar.duplicate-board\": \"Duplicera Board\",\n  \"Sidebar.export-archive\": \"Exportera arkiv\",\n  \"Sidebar.import\": \"Importera\",\n  \"Sidebar.import-archive\": \"Importera arkiv\",\n  \"Sidebar.invite-users\": \"Bjud in användare\",\n  \"Sidebar.logout\": \"Logga ut\",\n  \"Sidebar.new-category.badge\": \"Ny\",\n  \"Sidebar.new-category.drag-boards-cta\": \"Släpp Boards här...\",\n  \"Sidebar.no-boards-in-category\": \"Inga Boards\",\n  \"Sidebar.product-tour\": \"Produktvisning\",\n  \"Sidebar.random-icons\": \"Slumpmässiga ikoner\",\n  \"Sidebar.set-language\": \"Välj språk\",\n  \"Sidebar.set-theme\": \"Välj tema\",\n  \"Sidebar.settings\": \"Inställningar\",\n  \"Sidebar.template-from-board\": \"Ny mall från Board\",\n  \"Sidebar.untitled-board\": \"(Tavla saknar titel)\",\n  \"Sidebar.untitled-view\": \"(vy utan titel)\",\n  \"SidebarCategories.BlocksMenu.Move\": \"Flytta till...\",\n  \"SidebarCategories.CategoryMenu.CreateNew\": \"Skapa ny kategori\",\n  \"SidebarCategories.CategoryMenu.Delete\": \"Ta bort kategori\",\n  \"SidebarCategories.CategoryMenu.DeleteModal.Body\": \"Boards i <b>{categoryName}</b> flyttas tillbaka till kategorierna Boards. Du har inte tagits bort från några Boards.\",\n  \"SidebarCategories.CategoryMenu.DeleteModal.Title\": \"Radera kategorin?\",\n  \"SidebarCategories.CategoryMenu.Update\": \"Byt namn på kategori\",\n  \"SidebarTour.ManageCategories.Body\": \"Skapa och hantera egna kategorier. Kategorier är användarspecifika, så om du flyttar en Board till din kategori påverkas inte andra medlemmar som använder samma Board.\",\n  \"SidebarTour.ManageCategories.Title\": \"Hantera kategorier\",\n  \"SidebarTour.SearchForBoards.Body\": \"Öppna Board-växlaren (Cmd/Ctrl + K) för att snabbt söka och lägga till boards i sidofältet.\",\n  \"SidebarTour.SearchForBoards.Title\": \"Sök efter boards\",\n  \"SidebarTour.SidebarCategories.Body\": \"Alla dina boards är nu organiserade i ditt nya sidofält. Du behöver inte längre växla mellan olika arbetsområden. Anpassade kategorier baserade på dina tidigare arbetsytor kan ha skapats automatiskt för dig som en del av din uppgradering av v7.2. Dessa kan tas bort eller redigeras enligt dina önskemål.\",\n  \"SidebarTour.SidebarCategories.Link\": \"Mer information\",\n  \"SidebarTour.SidebarCategories.Title\": \"Kategorier i sidoomenyn\",\n  \"SiteStats.total_boards\": \"Totalt antal tavlor\",\n  \"SiteStats.total_cards\": \"Totalt antal kort\",\n  \"TableComponent.add-icon\": \"Lägg till ikon\",\n  \"TableComponent.name\": \"Namn\",\n  \"TableComponent.plus-new\": \"+ Ny\",\n  \"TableHeaderMenu.delete\": \"Radera\",\n  \"TableHeaderMenu.duplicate\": \"Duplicera\",\n  \"TableHeaderMenu.hide\": \"Dölj\",\n  \"TableHeaderMenu.insert-left\": \"Infoga till vänster\",\n  \"TableHeaderMenu.insert-right\": \"Infoga till höger\",\n  \"TableHeaderMenu.sort-ascending\": \"Sortera stigande\",\n  \"TableHeaderMenu.sort-descending\": \"Sortera fallande\",\n  \"TableRow.DuplicateCard\": \"duplicera kort\",\n  \"TableRow.MoreOption\": \"Fler åtgärder\",\n  \"TableRow.open\": \"Öppna\",\n  \"TopBar.give-feedback\": \"Ge återkoppling\",\n  \"URLProperty.copiedLink\": \"Kopierad!\",\n  \"URLProperty.copy\": \"Kopiera\",\n  \"URLProperty.edit\": \"Ändra\",\n  \"UndoRedoHotKeys.canRedo\": \"Gör om\",\n  \"UndoRedoHotKeys.canRedo-with-description\": \"Gör om {description}\",\n  \"UndoRedoHotKeys.canUndo\": \"Ångra\",\n  \"UndoRedoHotKeys.canUndo-with-description\": \"Ångra {description}\",\n  \"UndoRedoHotKeys.cannotRedo\": \"Inget att göra om igen\",\n  \"UndoRedoHotKeys.cannotUndo\": \"Inget att ångra\",\n  \"ValueSelector.noOptions\": \"Inga alternativ. Börja skriva för att lägga till den första!\",\n  \"ValueSelector.valueSelector\": \"Värdeväljare\",\n  \"ValueSelectorLabel.openMenu\": \"Öppna meny\",\n  \"VersionMessage.help\": \"Kolla in vad som är nytt i den här versionen.\",\n  \"View.AddView\": \"Lägg till vy\",\n  \"View.Board\": \"Tavla\",\n  \"View.DeleteView\": \"Radera vy\",\n  \"View.DuplicateView\": \"Duplicera vy\",\n  \"View.Gallery\": \"Galleri\",\n  \"View.NewBoardTitle\": \"Tavelvy\",\n  \"View.NewCalendarTitle\": \"Kalendervy\",\n  \"View.NewGalleryTitle\": \"Galleri vy\",\n  \"View.NewTableTitle\": \"Tabellvy\",\n  \"View.NewTemplateDefaultTitle\": \"Namnlös mall\",\n  \"View.NewTemplateTitle\": \"Namnlös\",\n  \"View.Table\": \"Tabell\",\n  \"ViewHeader.add-template\": \"Ny mall\",\n  \"ViewHeader.delete-template\": \"Radera\",\n  \"ViewHeader.display-by\": \"Visa efter: {property}\",\n  \"ViewHeader.edit-template\": \"Redigera\",\n  \"ViewHeader.empty-card\": \"Blankt kort\",\n  \"ViewHeader.export-board-archive\": \"Exportera tavelarkivet\",\n  \"ViewHeader.export-complete\": \"Export slutförd!\",\n  \"ViewHeader.export-csv\": \"Exportera till CSV\",\n  \"ViewHeader.export-failed\": \"Export misslyckades!\",\n  \"ViewHeader.filter\": \"Filter\",\n  \"ViewHeader.group-by\": \"Gruppera på: {property}\",\n  \"ViewHeader.new\": \"Ny\",\n  \"ViewHeader.properties\": \"Egenskaper\",\n  \"ViewHeader.properties-menu\": \"Menyn Egenskaper\",\n  \"ViewHeader.search-text\": \"Sök efter kort\",\n  \"ViewHeader.select-a-template\": \"Välj en mall\",\n  \"ViewHeader.set-default-template\": \"Sätt som förvald\",\n  \"ViewHeader.sort\": \"Sortera\",\n  \"ViewHeader.untitled\": \"Saknar titel\",\n  \"ViewHeader.view-header-menu\": \"Visa huvudmenyn\",\n  \"ViewHeader.view-menu\": \"Visa menyn\",\n  \"ViewLimitDialog.Heading\": \"Gränsen för antalet visningar per board har uppnåtts\",\n  \"ViewLimitDialog.PrimaryButton.Title.Admin\": \"Uppgradera\",\n  \"ViewLimitDialog.PrimaryButton.Title.RegularUser\": \"Meddela administratör\",\n  \"ViewLimitDialog.Subtext.Admin\": \"Uppgradera till Professional- eller Enterprise-abonnemang.\",\n  \"ViewLimitDialog.Subtext.Admin.PricingPageLink\": \"Läs mer om våra abonnemang.\",\n  \"ViewLimitDialog.Subtext.RegularUser\": \"Meddela din administratör att uppgradera till Professional- eller Enterprise-abonnemang.\",\n  \"ViewLimitDialog.UpgradeImg.AltText\": \"bild som föreställer en uppgradering\",\n  \"ViewLimitDialog.notifyAdmin.Success\": \"Din systemadministratör har blivit notifierad\",\n  \"ViewTitle.hide-description\": \"dölj beskrivning\",\n  \"ViewTitle.pick-icon\": \"Välj ikon\",\n  \"ViewTitle.random-icon\": \"Slumpmässig\",\n  \"ViewTitle.remove-icon\": \"Ta bort ikon\",\n  \"ViewTitle.show-description\": \"visa beskrivning\",\n  \"ViewTitle.untitled-board\": \"Board utan titel\",\n  \"WelcomePage.Description\": \"Boards är ett projekthanteringsverktyg som hjälper till att definiera, organisera, spåra och hantera arbete mellan team med hjälp av en välbekant Kanban-vy.\",\n  \"WelcomePage.Explore.Button\": \"Starta en rundtur\",\n  \"WelcomePage.Heading\": \"Välkommen till Boards\",\n  \"WelcomePage.NoThanks.Text\": \"Nej tack, jag löser det själv\",\n  \"WelcomePage.StartUsingIt.Text\": \"Börja använda den\",\n  \"Workspace.editing-board-template\": \"Du redigerar en tavelmall.\",\n  \"badge.guest\": \"Gäst\",\n  \"boardSelector.confirm-link-board\": \"Koppla board till kanal\",\n  \"boardSelector.confirm-link-board-button\": \"Ja, koppla board\",\n  \"boardSelector.confirm-link-board-subtext\": \"När du kopplar \\\"{boardName}\\\" till kanalen kommer alla medlemmar i kanalen (befintliga och nya) att kunna redigera den, gäster exkluderade. Du kan när som helst koppla bort ett board från kanalen.\",\n  \"boardSelector.confirm-link-board-subtext-with-other-channel\": \"När du kopplar \\\"{boardName}\\\" till kanalen kommer alla medlemmar i kanalen (befintliga och nya) att kunna redigera den, exkluderat gäster.{lineBreak}Denna board är kopplad till en annan kanal. Den kommer kopplas bort om du väljer att koppla den hit.\",\n  \"boardSelector.create-a-board\": \"Skapa en board\",\n  \"boardSelector.link\": \"Länk\",\n  \"boardSelector.search-for-boards\": \"Sök efter boards\",\n  \"boardSelector.title\": \"Länkade Boards\",\n  \"boardSelector.unlink\": \"Koppla ifrån\",\n  \"calendar.month\": \"Månad\",\n  \"calendar.today\": \"IDAG\",\n  \"calendar.week\": \"Vecka\",\n  \"centerPanel.undefined\": \"Ingen {propertyName}\",\n  \"centerPanel.unknown-user\": \"Okänd användare\",\n  \"cloudMessage.learn-more\": \"Läs mer\",\n  \"createImageBlock.failed\": \"Filen kunde inte laddas upp eftersom gränsen för filstorlek har uppnåtts.\",\n  \"default-properties.badges\": \"Kommentarer och beskrivning\",\n  \"default-properties.title\": \"Titel\",\n  \"error.back-to-home\": \"Tillbaka till förstasidan\",\n  \"error.back-to-team\": \"Tillbaka till team\",\n  \"error.board-not-found\": \"Board finns inte.\",\n  \"error.go-login\": \"Logga in\",\n  \"error.invalid-read-only-board\": \"Du har inte tillgång till denna board. Logga in för att få tillgång till Boards.\",\n  \"error.not-logged-in\": \"Din session kan ha löpt ut eller så är du inte inloggad. Logga in igen för att få tillgång till Boards.\",\n  \"error.page.title\": \"Oops, något gick fel\",\n  \"error.team-undefined\": \"Inte ett giltigt team.\",\n  \"error.unknown\": \"Ett fel inträffade.\",\n  \"generic.previous\": \"Föregående\",\n  \"guest-no-board.subtitle\": \"Du har inte tillgång till något board i teamet ännu, vänta tills någon lägger till dig i ett board.\",\n  \"guest-no-board.title\": \"Inga board ännu\",\n  \"imagePaste.upload-failed\": \"Några filer kunde inte laddas upp eftersom gränsen för filstorlek har nåtts.\",\n  \"limitedCard.title\": \"Dolda kort\",\n  \"login.log-in-button\": \"Logga in\",\n  \"login.log-in-title\": \"Logga in\",\n  \"login.register-button\": \"eller skapa ett konto om du inte redan har ett\",\n  \"new_channel_modal.create_board.empty_board_description\": \"Skapa en ny tom tavla\",\n  \"new_channel_modal.create_board.empty_board_title\": \"Tom tavla\",\n  \"new_channel_modal.create_board.select_template_placeholder\": \"Välj en mall\",\n  \"new_channel_modal.create_board.title\": \"Skapa en tavla för den här kanalen\",\n  \"notification-box-card-limit-reached.close-tooltip\": \"Sov i 10 dagar\",\n  \"notification-box-card-limit-reached.contact-link\": \"notifiera din administratör\",\n  \"notification-box-card-limit-reached.link\": \"Uppgradera till ett betal-abonnemang\",\n  \"notification-box-card-limit-reached.title\": \"{cards} kort dolda från board\",\n  \"notification-box-cards-hidden.title\": \"Åtgärden dolde ett annat kort\",\n  \"notification-box.card-limit-reached.not-admin.text\": \"Om du vill komma åt arkiverade kort kan du {contactLink} för att uppgradera till ett betal-abonnemang.\",\n  \"notification-box.card-limit-reached.text\": \"Gränsen för kort har nåtts, för att visa äldre kort, {link}\",\n  \"person.add-user-to-board\": \"Lägg till {username} till board\",\n  \"person.add-user-to-board-confirm-button\": \"Lägg till i board\",\n  \"person.add-user-to-board-permissions\": \"Behörigheter\",\n  \"person.add-user-to-board-question\": \"Vill du lägga till {username} till board?\",\n  \"person.add-user-to-board-warning\": \"{username} är inte medlem i board och kommer inte att få några meddelanden om den.\",\n  \"register.login-button\": \"eller logga in om du redan har ett konto\",\n  \"register.signup-title\": \"Registrera dig för ett konto\",\n  \"rhs-board-non-admin-msg\": \"Du är inte administratör för board\",\n  \"rhs-boards.add\": \"Lägg till\",\n  \"rhs-boards.dm\": \"DM\",\n  \"rhs-boards.gm\": \"GM\",\n  \"rhs-boards.header.dm\": \"detta direktmeddelande\",\n  \"rhs-boards.header.gm\": \"detta gruppmeddelande\",\n  \"rhs-boards.last-update-at\": \"Senast uppdaterad: {datetime}\",\n  \"rhs-boards.link-boards-to-channel\": \"Länka boards till {channelName}\",\n  \"rhs-boards.linked-boards\": \"Länkade boards\",\n  \"rhs-boards.no-boards-linked-to-channel\": \"Inga boards är länkade till {channelName} ännu\",\n  \"rhs-boards.no-boards-linked-to-channel-description\": \"Boards är ett projekthanteringsverktyg som hjälper till att definiera, organisera, spåra och hantera arbete mellan olika team med hjälp av en välbekant Kanban-vy.\",\n  \"rhs-boards.unlink-board\": \"Ta bort länk till Board\",\n  \"rhs-boards.unlink-board1\": \"Koppla bort board\",\n  \"rhs-channel-boards-header.title\": \"Boards\",\n  \"share-board.publish\": \"Publicera\",\n  \"share-board.share\": \"Dela\",\n  \"shareBoard.channels-select-group\": \"Channels\",\n  \"shareBoard.confirm-change-team-role.body\": \"Alla i denna board som har lägre behörighet än rollen \\\"{role}\\\" kommer <b>nu att befordras till {role}</b>. Är du säker på att du vill ändra den minsta rollen för board?\",\n  \"shareBoard.confirm-change-team-role.confirmBtnText\": \"Ändra lägsta board-rollen\",\n  \"shareBoard.confirm-change-team-role.title\": \"Ändra lägsta board-rollen\",\n  \"shareBoard.confirm-link-channel\": \"Koppla board till kanal\",\n  \"shareBoard.confirm-link-channel-button\": \"Koppla kanal\",\n  \"shareBoard.confirm-link-channel-button-with-other-channel\": \"Koppla och koppla bort här\",\n  \"shareBoard.confirm-link-channel-subtext\": \"När du kopplar en kanal till en board kommer alla medlemmar (befintliga och nya) kunna redigera den, exklusive gäster.\",\n  \"shareBoard.confirm-link-channel-subtext-with-other-channel\": \"När du kopplar en kanal till en board kommer alla medlemmar (befintliga och nya) kunna redigera den, exklusive gäster.{lineBreak}Denna Board är just nu kopplad till en annan kanal. Den kommer kopplas bort om du väljer att koppla den till denna kanal.\",\n  \"shareBoard.confirm-unlink.body\": \"När du kopplar bort en kanal från en board kommer alla medlemmar (befintliga och nya) tappa behörigheten om de inte blir tilldelade en egen behörighet.\",\n  \"shareBoard.confirm-unlink.confirmBtnText\": \"Koppla bort kanal\",\n  \"shareBoard.confirm-unlink.title\": \"Koppla bort kanal från board\",\n  \"shareBoard.lastAdmin\": \"En Board måste ha minst en administratör\",\n  \"shareBoard.members-select-group\": \"Medlemmar\",\n  \"shareBoard.unknown-channel-display-name\": \"Okänd kanal\",\n  \"tutorial_tip.finish_tour\": \"Klar\",\n  \"tutorial_tip.got_it\": \"Då förstår jag\",\n  \"tutorial_tip.ok\": \"Nästa\",\n  \"tutorial_tip.out\": \"Välj bort att få tips.\",\n  \"tutorial_tip.seen\": \"Sett detta tidigare?\"\n}\n"
  },
  {
    "path": "webapp/i18n/tr.json",
    "content": "{\n  \"AdminBadge.SystemAdmin\": \"Yönetici\",\n  \"AdminBadge.TeamAdmin\": \"Takım yöneticisi\",\n  \"AppBar.Tooltip\": \"Bağlantılı panoları aç/kapat\",\n  \"Attachment.Attachment-title\": \"Ek dosya\",\n  \"AttachmentBlock.DeleteAction\": \"sil\",\n  \"AttachmentBlock.addElement\": \"{type} ekle\",\n  \"AttachmentBlock.delete\": \"Ek dosya silindi.\",\n  \"AttachmentBlock.failed\": \"Dosya boyutu sınırı aşıldığından bu dosya yüklenemedi.\",\n  \"AttachmentBlock.upload\": \"Ek dosya yükleniyor.\",\n  \"AttachmentBlock.uploadSuccess\": \"Ek dosya yüklendi.\",\n  \"AttachmentElement.delete-confirmation-dialog-button-text\": \"Sil\",\n  \"AttachmentElement.download\": \"İndir\",\n  \"AttachmentElement.upload-percentage\": \"Yükleniyor...(%{uploadPercent})\",\n  \"BoardComponent.add-a-group\": \"+ Grup ekle\",\n  \"BoardComponent.delete\": \"Sil\",\n  \"BoardComponent.hidden-columns\": \"Gizli sütunlar\",\n  \"BoardComponent.hide\": \"Gizle\",\n  \"BoardComponent.new\": \"+ Yeni\",\n  \"BoardComponent.no-property\": \"{property} yok\",\n  \"BoardComponent.no-property-title\": \"{property} alanı boş olan ögeler buraya atanır. Bu sütun silinemez.\",\n  \"BoardComponent.show\": \"Görüntüle\",\n  \"BoardMember.schemeAdmin\": \"Yönetici\",\n  \"BoardMember.schemeCommenter\": \"Yorumcu\",\n  \"BoardMember.schemeEditor\": \"Düzenleyici\",\n  \"BoardMember.schemeNone\": \"Yok\",\n  \"BoardMember.schemeViewer\": \"Görüntüleyici\",\n  \"BoardMember.unlinkChannel\": \"Bağlantıyı kaldır\",\n  \"BoardPage.newVersion\": \"Yeni bir pano sürümü yayınlanmış. Yeniden yüklemek için buraya tıklayın.\",\n  \"BoardPage.syncFailed\": \"Pano silinmiş ya da erişim izni geri alınmış olabilir.\",\n  \"BoardTemplateSelector.add-template\": \"Yeni kalıp ekle\",\n  \"BoardTemplateSelector.create-empty-board\": \"Boş bir pano ekle\",\n  \"BoardTemplateSelector.delete-template\": \"Sil\",\n  \"BoardTemplateSelector.description\": \"Kalıplardan birini kullanarak ya da sıfırdan başlayarak yan çubuğa bir pano ekleyin.\",\n  \"BoardTemplateSelector.edit-template\": \"Düzenle\",\n  \"BoardTemplateSelector.plugin.no-content-description\": \"Aşağıdaki kalıplardan birini kullanarak ya da sıfırdan başlayarak yan çubuğa bir pano ekleyin.\",\n  \"BoardTemplateSelector.plugin.no-content-title\": \"Bir pano ekleyin\",\n  \"BoardTemplateSelector.title\": \"Bir pano ekle\",\n  \"BoardTemplateSelector.use-this-template\": \"Bu kalıp kullanılsın\",\n  \"BoardsSwitcher.Title\": \"Pano arama\",\n  \"BoardsUnfurl.Limited\": \"Kart arşivlendiğinden ek bilgiler gizleniyor\",\n  \"BoardsUnfurl.Remainder\": \"+{remainder} diğer\",\n  \"BoardsUnfurl.Updated\": \"Güncellenme: {time}\",\n  \"Calculations.Options.average.displayName\": \"Ortalama\",\n  \"Calculations.Options.average.label\": \"Ortalama\",\n  \"Calculations.Options.count.displayName\": \"Sayı\",\n  \"Calculations.Options.count.label\": \"Sayı\",\n  \"Calculations.Options.countChecked.displayName\": \"İşaretlenmiş\",\n  \"Calculations.Options.countChecked.label\": \"İşaretlenmiş sayısı\",\n  \"Calculations.Options.countUnchecked.displayName\": \"İşaretlenmemiş\",\n  \"Calculations.Options.countUnchecked.label\": \"İşaretlenmemiş sayısı\",\n  \"Calculations.Options.countUniqueValue.displayName\": \"Eşsiz\",\n  \"Calculations.Options.countUniqueValue.label\": \"Eşsiz değer sayısı\",\n  \"Calculations.Options.countValue.displayName\": \"Değer\",\n  \"Calculations.Options.countValue.label\": \"Değer sayısı\",\n  \"Calculations.Options.dateRange.displayName\": \"Aralık\",\n  \"Calculations.Options.dateRange.label\": \"Aralık\",\n  \"Calculations.Options.earliest.displayName\": \"En erken\",\n  \"Calculations.Options.earliest.label\": \"En erken\",\n  \"Calculations.Options.latest.displayName\": \"En geç\",\n  \"Calculations.Options.latest.label\": \"En geç\",\n  \"Calculations.Options.max.displayName\": \"En fazla\",\n  \"Calculations.Options.max.label\": \"En fazla\",\n  \"Calculations.Options.median.displayName\": \"Orta değer\",\n  \"Calculations.Options.median.label\": \"Orta değer\",\n  \"Calculations.Options.min.displayName\": \"En az\",\n  \"Calculations.Options.min.label\": \"En az\",\n  \"Calculations.Options.none.displayName\": \"Hesapla\",\n  \"Calculations.Options.none.label\": \"Yok\",\n  \"Calculations.Options.percentChecked.displayName\": \"İşaretlenmiş\",\n  \"Calculations.Options.percentChecked.label\": \"İşaretlenmiş yüzdesi\",\n  \"Calculations.Options.percentUnchecked.displayName\": \"İşaretlenmemiş\",\n  \"Calculations.Options.percentUnchecked.label\": \"İşaretlenmemiş yüzdesi\",\n  \"Calculations.Options.range.displayName\": \"Aralık\",\n  \"Calculations.Options.range.label\": \"Aralık\",\n  \"Calculations.Options.sum.displayName\": \"Toplam\",\n  \"Calculations.Options.sum.label\": \"Toplam\",\n  \"CalendarCard.untitled\": \"Adlandırılmamış\",\n  \"CardActionsMenu.copiedLink\": \"Kopyalandı!\",\n  \"CardActionsMenu.copyLink\": \"Bağlantıyı kopyala\",\n  \"CardActionsMenu.delete\": \"Sil\",\n  \"CardActionsMenu.duplicate\": \"Kopyala\",\n  \"CardBadges.title-checkboxes\": \"İşaret kutuları\",\n  \"CardBadges.title-comments\": \"Yorumlar\",\n  \"CardBadges.title-description\": \"Bu kartın bir açıklaması var\",\n  \"CardDetail.Attach\": \"Dosya ekle\",\n  \"CardDetail.Follow\": \"İzle\",\n  \"CardDetail.Following\": \"İzleniyor\",\n  \"CardDetail.add-content\": \"İçerik ekle\",\n  \"CardDetail.add-icon\": \"Simge ekle\",\n  \"CardDetail.add-property\": \"+ Bir özellik ekle\",\n  \"CardDetail.addCardText\": \"kart metni ekle\",\n  \"CardDetail.limited-body\": \"Professional ya da Enterprise tarifesine geçin.\",\n  \"CardDetail.limited-button\": \"Üst tarifeye geç\",\n  \"CardDetail.limited-title\": \"Bu kart gizli\",\n  \"CardDetail.moveContent\": \"Kart içeriğini taşı\",\n  \"CardDetail.new-comment-placeholder\": \"Bir yorum ekle...\",\n  \"CardDetailProperty.confirm-delete-heading\": \"Özelliği silmeyi onaylayın\",\n  \"CardDetailProperty.confirm-delete-subtext\": \"\\\"{propertyName}\\\" özelliğini silmek istediğinize emin misiniz? Bu işlem özelliği panodaki tüm kartlardan siler.\",\n  \"CardDetailProperty.confirm-property-name-change-subtext\": \"\\\"{propertyName}\\\" {customText} özelliğini değiştirmek istediğinize emin misiniz? Bu işlem bu panodaki {numOfCards} kartı etkiler ve veri kaybına yol açabilir.\",\n  \"CardDetailProperty.confirm-property-type-change\": \"Özellik türü değişimini onaylayın\",\n  \"CardDetailProperty.delete-action-button\": \"Sil\",\n  \"CardDetailProperty.property-change-action-button\": \"Özelliği değiştir\",\n  \"CardDetailProperty.property-changed\": \"Özellik değiştirildi!\",\n  \"CardDetailProperty.property-deleted\": \"{propertyName} silindi!\",\n  \"CardDetailProperty.property-name-change-subtext\": \"\\\"{oldPropType}\\\" türünden \\\"{newPropType}\\\" türüne\",\n  \"CardDetial.limited-link\": \"Tarifelerimiz hakkında ayrıntılı bilgi alın.\",\n  \"CardDialog.delete-confirmation-dialog-attachment\": \"Ek dosyanın silinmesini onaylayın\",\n  \"CardDialog.delete-confirmation-dialog-button-text\": \"Sil\",\n  \"CardDialog.delete-confirmation-dialog-heading\": \"Kartı silmeyi onaylayın\",\n  \"CardDialog.editing-template\": \"Bir kalıbı düzenliyorsunuz.\",\n  \"CardDialog.nocard\": \"Bu kart bulunamadı ya da erişilebilir değil.\",\n  \"Categories.CreateCategoryDialog.CancelText\": \"İptal\",\n  \"Categories.CreateCategoryDialog.CreateText\": \"Ekle\",\n  \"Categories.CreateCategoryDialog.Placeholder\": \"Kategorinize bir ad verin\",\n  \"Categories.CreateCategoryDialog.UpdateText\": \"Güncelle\",\n  \"CenterPanel.Login\": \"Oturum aç\",\n  \"CenterPanel.Share\": \"Paylaş\",\n  \"ChannelIntro.CreateBoard\": \"Bir pano ekle\",\n  \"ColorOption.selectColor\": \"{color} rengi seçin\",\n  \"Comment.delete\": \"Sil\",\n  \"CommentsList.send\": \"Gönder\",\n  \"ConfirmPerson.empty\": \"Boş\",\n  \"ConfirmPerson.search\": \"Arama...\",\n  \"ConfirmationDialog.cancel-action\": \"İptal\",\n  \"ConfirmationDialog.confirm-action\": \"Onayla\",\n  \"ContentBlock.Delete\": \"Sil\",\n  \"ContentBlock.DeleteAction\": \"sil\",\n  \"ContentBlock.addElement\": \"{type} ekle\",\n  \"ContentBlock.checkbox\": \"işaret kutusu\",\n  \"ContentBlock.divider\": \"ayıraç\",\n  \"ContentBlock.editCardCheckbox\": \"değiştirilmiş işaret kutusu\",\n  \"ContentBlock.editCardCheckboxText\": \"kart metnini düzenle\",\n  \"ContentBlock.editCardText\": \"kart metnini düzenle\",\n  \"ContentBlock.editText\": \"Metni düzenle...\",\n  \"ContentBlock.image\": \"görsel\",\n  \"ContentBlock.insertAbove\": \"Üste ekle\",\n  \"ContentBlock.moveBlock\": \"kart içeriğini taşı\",\n  \"ContentBlock.moveDown\": \"Alta taşı\",\n  \"ContentBlock.moveUp\": \"Üste taşı\",\n  \"ContentBlock.text\": \"metin\",\n  \"DateFilter.empty\": \"Boş\",\n  \"DateRange.clear\": \"Temizle\",\n  \"DateRange.empty\": \"Boş\",\n  \"DateRange.endDate\": \"Bitiş tarihi\",\n  \"DateRange.today\": \"Bugün\",\n  \"DeleteBoardDialog.confirm-cancel\": \"İptal\",\n  \"DeleteBoardDialog.confirm-delete\": \"Sil\",\n  \"DeleteBoardDialog.confirm-info\": \"“{boardTitle}” panosunu silmek istediğinize emin misiniz? Silme işlemi bu panodaki tüm kartları siler.\",\n  \"DeleteBoardDialog.confirm-info-template\": \"“{boardTitle}” pano kalıbını silmek istediğinize emin misiniz?\",\n  \"DeleteBoardDialog.confirm-tite\": \"Panoyu silmeyi onayla\",\n  \"DeleteBoardDialog.confirm-tite-template\": \"Pano kalıbını silmeyi onayla\",\n  \"Dialog.closeDialog\": \"Pencereyi kapat\",\n  \"EditableDayPicker.today\": \"Bugün\",\n  \"Error.mobileweb\": \"Mobil web desteği şu anda erken beta aşamasındadır. Tüm işlevler kullanılamıyor olabilir.\",\n  \"Error.websocket-closed\": \"Websoket bağlantısı kesildi. Bu sorun sürerse, sunucu ya da web vekil sunucu yapılandırmanızı denetleyin.\",\n  \"Filter.contains\": \"şunu içeren\",\n  \"Filter.ends-with\": \"şununla biten\",\n  \"Filter.includes\": \"şunu içeren\",\n  \"Filter.is\": \"şu olan\",\n  \"Filter.is-after\": \"şundan sonra\",\n  \"Filter.is-before\": \"şundan önce\",\n  \"Filter.is-empty\": \"boş olan\",\n  \"Filter.is-not-empty\": \"boş olmayan\",\n  \"Filter.is-not-set\": \"şuna ayarlanmamış olan\",\n  \"Filter.is-set\": \"şuna ayarlanmış olan\",\n  \"Filter.isafter\": \"şundan sonra\",\n  \"Filter.isbefore\": \"şundan önce\",\n  \"Filter.not-contains\": \"şunu içermeyen\",\n  \"Filter.not-ends-with\": \"şununla bitmeyen\",\n  \"Filter.not-includes\": \"şunu içermeyen\",\n  \"Filter.not-starts-with\": \"şununla başlamayan\",\n  \"Filter.starts-with\": \"şununla başlayan\",\n  \"FilterByText.placeholder\": \"metni süz\",\n  \"FilterComponent.add-filter\": \"+ Süzgeç ekle\",\n  \"FilterComponent.delete\": \"Sil\",\n  \"FilterValue.empty\": \"(boş)\",\n  \"FindBoardsDialog.IntroText\": \"Pano arama\",\n  \"FindBoardsDialog.NoResultsFor\": \"\\\"{searchQuery}\\\" için bir sonuç bulunamadı\",\n  \"FindBoardsDialog.NoResultsSubtext\": \"Yazımı denetleyin ya da başka bir arama yapmayı deneyin.\",\n  \"FindBoardsDialog.SubTitle\": \"Bulmak istediğiniz pano adını yazmaya başlayın. Gezinmek için <b>YUKAR/AŞAĞI</b>, seçmek için <b>ENTER</b>, vazgeçmek için <b>ESC</b> tuşlarını kullanın\",\n  \"FindBoardsDialog.Title\": \"Pano arama\",\n  \"GroupBy.hideEmptyGroups\": \"{count} boş grubu gizle\",\n  \"GroupBy.showHiddenGroups\": \"{count} gizli grubu görüntüle\",\n  \"GroupBy.ungroup\": \"Gruplamayı kaldır\",\n  \"HideBoard.MenuOption\": \"Panoyu gizle\",\n  \"KanbanCard.untitled\": \"Adlandırılmamış\",\n  \"MentionSuggestion.is-not-board-member\": \"(pano üyesi değil)\",\n  \"Mutator.new-board-from-template\": \"kalıptan yeni pano\",\n  \"Mutator.new-card-from-template\": \"kalıptan yeni kart oluştur\",\n  \"Mutator.new-template-from-card\": \"karttan yeni kalıp oluştur\",\n  \"OnboardingTour.AddComments.Body\": \"Sorunlar hakkında yorum yapabilir ve Mattermost kullanıcılarının dikkatini çekmek için @anabilirsiniz.\",\n  \"OnboardingTour.AddComments.Title\": \"Yorum yap\",\n  \"OnboardingTour.AddDescription.Body\": \"Takım arkadaşlarınızın kartın ne ile ilgili olduğunu anlaması için kartınıza bir açıklama ekleyin.\",\n  \"OnboardingTour.AddDescription.Title\": \"Açıklama ekle\",\n  \"OnboardingTour.AddProperties.Body\": \"Daha güçlü kılmak için kartlara çeşitli özellikler ekleyin.\",\n  \"OnboardingTour.AddProperties.Title\": \"Özellikler ekle\",\n  \"OnboardingTour.AddView.Body\": \"Farklı görünümler kullanarak panonuzu düzenleyecek yeni bir görünüm oluşturmak için buraya gidin.\",\n  \"OnboardingTour.AddView.Title\": \"Yeni bir görünüm ekle\",\n  \"OnboardingTour.CopyLink.Body\": \"Kartlarınızı takım arkadaşlarınızla paylaşmak için bağlantıyı kopyalayıp bir kanala, doğrudan iletiye veya grup iletisine yapıştırın.\",\n  \"OnboardingTour.CopyLink.Title\": \"Bağlantıyı kopyala\",\n  \"OnboardingTour.OpenACard.Body\": \"Panoların işinizi düzenlemenize yardımcı olabileceği güçlü yolları keşfetmek için bir kart açın.\",\n  \"OnboardingTour.OpenACard.Title\": \"Bir kart açın\",\n  \"OnboardingTour.ShareBoard.Body\": \"Panonuzu içeride, ekibiniz ile paylaşabilir ya da kuruluşunuzun dışında herkese açık olarak yayınlayabilirsiniz.\",\n  \"OnboardingTour.ShareBoard.Title\": \"Panoyu paylaş\",\n  \"PersonProperty.board-members\": \"Pano üyeleri\",\n  \"PersonProperty.me\": \"Benim\",\n  \"PersonProperty.non-board-members\": \"Pano üyesi olmayanlar\",\n  \"PropertyMenu.Delete\": \"Sil\",\n  \"PropertyMenu.changeType\": \"Özellik türünü değiştir\",\n  \"PropertyMenu.selectType\": \"Özellik türünü seçin\",\n  \"PropertyMenu.typeTitle\": \"Tür\",\n  \"PropertyType.Checkbox\": \"İşaret kutusu\",\n  \"PropertyType.CreatedBy\": \"Oluşturan\",\n  \"PropertyType.CreatedTime\": \"Oluşturulma zamanı\",\n  \"PropertyType.Date\": \"Tarih\",\n  \"PropertyType.Email\": \"E-posta\",\n  \"PropertyType.MultiPerson\": \"Çok kişi\",\n  \"PropertyType.MultiSelect\": \"Çoklu seçim\",\n  \"PropertyType.Number\": \"Sayı\",\n  \"PropertyType.Person\": \"Kişi\",\n  \"PropertyType.Phone\": \"Telefon\",\n  \"PropertyType.Select\": \"Seçin\",\n  \"PropertyType.Text\": \"Metin\",\n  \"PropertyType.Unknown\": \"Bilinmiyor\",\n  \"PropertyType.UpdatedBy\": \"Son güncelleyen\",\n  \"PropertyType.UpdatedTime\": \"Son güncelleme zamanı\",\n  \"PropertyType.Url\": \"Adres\",\n  \"PropertyValueElement.empty\": \"Boş\",\n  \"RegistrationLink.confirmRegenerateToken\": \"Bu işlem daha önce paylaşılmış bağlantıları geçersiz kılacak. İlerlemek istiyor musunuz?\",\n  \"RegistrationLink.copiedLink\": \"Kopyalandı!\",\n  \"RegistrationLink.copyLink\": \"Bağlantıyı kopyala\",\n  \"RegistrationLink.description\": \"Başkalarının hesap ekleyebilmesi için bu bağlantıyı paylaş:\",\n  \"RegistrationLink.regenerateToken\": \"Kodu yeniden oluştur\",\n  \"RegistrationLink.tokenRegenerated\": \"Kayıt bağlantısı yeniden oluşturuldu\",\n  \"ShareBoard.PublishDescription\": \"Web üzerinde herkese açık olarak \\\"salt okunur\\\" bir bağlantı yayınlayın ve paylaşın.\",\n  \"ShareBoard.PublishTitle\": \"Web üzerinde yayınla\",\n  \"ShareBoard.ShareInternal\": \"İçeride paylaş\",\n  \"ShareBoard.ShareInternalDescription\": \"İzni olan kullanıcılar bu bağlantıyı kullanabilecek.\",\n  \"ShareBoard.Title\": \"Panoyu paylaş\",\n  \"ShareBoard.confirmRegenerateToken\": \"Bu işlem daha önce paylaşılmış bağlantıları geçersiz kılacak. İlerlemek istiyor musunuz?\",\n  \"ShareBoard.copiedLink\": \"Kopyalandı!\",\n  \"ShareBoard.copyLink\": \"Bağlantıyı kopyala\",\n  \"ShareBoard.regenerate\": \"Kodu yeniden oluştur\",\n  \"ShareBoard.searchPlaceholder\": \"Kişi ve kanal arama\",\n  \"ShareBoard.teamPermissionsText\": \"{teamName} takımındaki herkes\",\n  \"ShareBoard.tokenRegenrated\": \"Kod yeniden oluşturuldu\",\n  \"ShareBoard.userPermissionsRemoveMemberText\": \"Üyelikten çıkar\",\n  \"ShareBoard.userPermissionsYouText\": \"(Siz)\",\n  \"ShareTemplate.Title\": \"Kalıbı paylaş\",\n  \"ShareTemplate.searchPlaceholder\": \"Kişi arama\",\n  \"Sidebar.about\": \"Focalboard hakkında\",\n  \"Sidebar.add-board\": \"+ Pano ekle\",\n  \"Sidebar.changePassword\": \"Parola değiştir\",\n  \"Sidebar.delete-board\": \"Panoyu sil\",\n  \"Sidebar.duplicate-board\": \"Panoyu kopyala\",\n  \"Sidebar.export-archive\": \"Arşivi dışa aktar\",\n  \"Sidebar.import\": \"İçe aktar\",\n  \"Sidebar.import-archive\": \"Arşivi içe aktar\",\n  \"Sidebar.invite-users\": \"Kullanıcıları çağır\",\n  \"Sidebar.logout\": \"Oturumu kapat\",\n  \"Sidebar.new-category.badge\": \"Yeni\",\n  \"Sidebar.new-category.drag-boards-cta\": \"Panoları sürükleyip buraya bırakın...\",\n  \"Sidebar.no-boards-in-category\": \"İçeride bir pano yok\",\n  \"Sidebar.product-tour\": \"Tanıtım turu\",\n  \"Sidebar.random-icons\": \"Rastgele simgeler\",\n  \"Sidebar.set-language\": \"Dili ayarla\",\n  \"Sidebar.set-theme\": \"Temayı ayarla\",\n  \"Sidebar.settings\": \"Ayarlar\",\n  \"Sidebar.template-from-board\": \"Panodan yeni kalıp\",\n  \"Sidebar.untitled-board\": \"(Adlandırılmamış pano)\",\n  \"Sidebar.untitled-view\": \"(Adlandırılmamış görünüm)\",\n  \"SidebarCategories.BlocksMenu.Move\": \"Şuraya taşı...\",\n  \"SidebarCategories.CategoryMenu.CreateNew\": \"Yeni kategori ekle\",\n  \"SidebarCategories.CategoryMenu.Delete\": \"Kategoriyi sił\",\n  \"SidebarCategories.CategoryMenu.DeleteModal.Body\": \"<b>{categoryName}</b> içindeki panolar Panolar kategorisine taşınacak. Herhangi bir panodan çıkarılmayacaksınız.\",\n  \"SidebarCategories.CategoryMenu.DeleteModal.Title\": \"Bu kategori silinsin mi?\",\n  \"SidebarCategories.CategoryMenu.Update\": \"Kategoriyi yeniden adlandır\",\n  \"SidebarTour.ManageCategories.Body\": \"Özel kategoriler oluşturun ve yönetin. Kategoriler kullanıcıya özeldir, bu nedenle bir panoyu kendi kategorinize taşımanız aynı panoyu kullanan diğer üyeleri etkilemez.\",\n  \"SidebarTour.ManageCategories.Title\": \"Kategori yönetimi\",\n  \"SidebarTour.SearchForBoards.Body\": \"Panoları hızlıca aramak ve yan çubuğunuza eklemek için pano değiştiriciyi (Cmd/Ctrl + K) açın.\",\n  \"SidebarTour.SearchForBoards.Title\": \"Pano arama\",\n  \"SidebarTour.SidebarCategories.Body\": \"Tüm panolarınızı artık yeni yan çubuğunuz altında bulabilirsiniz. Artık çalışma alanları arasında geçiş yapmanıza gerek yok. Önceki çalışma alanlarınıza göre eklenmiş tek seferlik özel kategoriler, 7.2 sürümüne güncellemenizin bir parçası olarak otomatik şekilde eklenmiş olabilir. Bunları isteğinize göre kaldırabilir ya da düzenleyebilirsiniz.\",\n  \"SidebarTour.SidebarCategories.Link\": \"Ayrıntılı bilgi alın\",\n  \"SidebarTour.SidebarCategories.Title\": \"Yan çubuk kategorileri\",\n  \"SiteStats.total_boards\": \"Toplam pano\",\n  \"SiteStats.total_cards\": \"Toplam kart\",\n  \"TableComponent.add-icon\": \"Simge ekle\",\n  \"TableComponent.name\": \"Ad\",\n  \"TableComponent.plus-new\": \"+ Yeni\",\n  \"TableHeaderMenu.delete\": \"Sil\",\n  \"TableHeaderMenu.duplicate\": \"Kopya oluştur\",\n  \"TableHeaderMenu.hide\": \"Gizle\",\n  \"TableHeaderMenu.insert-left\": \"Sola ekle\",\n  \"TableHeaderMenu.insert-right\": \"Sağa ekle\",\n  \"TableHeaderMenu.sort-ascending\": \"Artan sıralama\",\n  \"TableHeaderMenu.sort-descending\": \"Azalan sıralama\",\n  \"TableRow.DuplicateCard\": \"kartı kopyala\",\n  \"TableRow.MoreOption\": \"Diğer işlemler\",\n  \"TableRow.open\": \"Aç\",\n  \"TopBar.give-feedback\": \"Geri bildirimde bulunun\",\n  \"URLProperty.copiedLink\": \"Kopyalandı!\",\n  \"URLProperty.copy\": \"Kopyala\",\n  \"URLProperty.edit\": \"Düzenle\",\n  \"UndoRedoHotKeys.canRedo\": \"Yinele\",\n  \"UndoRedoHotKeys.canRedo-with-description\": \"{description} yinele\",\n  \"UndoRedoHotKeys.canUndo\": \"Geri al\",\n  \"UndoRedoHotKeys.canUndo-with-description\": \"{description} geri al\",\n  \"UndoRedoHotKeys.cannotRedo\": \"Yinelenecek bir işlem yok\",\n  \"UndoRedoHotKeys.cannotUndo\": \"Geri alınacak bir işlem yok\",\n  \"ValueSelector.noOptions\": \"Herhangi bir seçenek yok. İlk seçeneği eklemek için yazmaya başlayın!\",\n  \"ValueSelector.valueSelector\": \"Değer seçici\",\n  \"ValueSelectorLabel.openMenu\": \"Menüyü aç\",\n  \"VersionMessage.help\": \"Bu sürümdeki yeniliklere bakın.\",\n  \"VersionMessage.learn-more\": \"Ayrıntılı bilgi alın\",\n  \"View.AddView\": \"Görünüm ekle\",\n  \"View.Board\": \"Pano\",\n  \"View.DeleteView\": \"Görünümü sil\",\n  \"View.DuplicateView\": \"Görünümü kopyala\",\n  \"View.Gallery\": \"Galeri\",\n  \"View.NewBoardTitle\": \"Pano görünümü\",\n  \"View.NewCalendarTitle\": \"Takvim görünümü\",\n  \"View.NewGalleryTitle\": \"Galeri görünümü\",\n  \"View.NewTableTitle\": \"Tablo görünümü\",\n  \"View.NewTemplateDefaultTitle\": \"Adlandırılmamış kalıp\",\n  \"View.NewTemplateTitle\": \"Adlandırılmamış\",\n  \"View.Table\": \"Tablo\",\n  \"ViewHeader.add-template\": \"Yeni kalıp\",\n  \"ViewHeader.delete-template\": \"Sil\",\n  \"ViewHeader.display-by\": \"Görünüm: {property}\",\n  \"ViewHeader.edit-template\": \"Düzenle\",\n  \"ViewHeader.empty-card\": \"Boş kart\",\n  \"ViewHeader.export-board-archive\": \"Pano arşivini dışa aktar\",\n  \"ViewHeader.export-complete\": \"Dışa aktarıldı!\",\n  \"ViewHeader.export-csv\": \"CSV olarak dışa aktar\",\n  \"ViewHeader.export-failed\": \"Dışa aktarılamadı!\",\n  \"ViewHeader.filter\": \"Süz\",\n  \"ViewHeader.group-by\": \"Grupla: {property}\",\n  \"ViewHeader.new\": \"Yeni\",\n  \"ViewHeader.properties\": \"Özellikler\",\n  \"ViewHeader.properties-menu\": \"Özellikler menüsü\",\n  \"ViewHeader.search-text\": \"Kart arama\",\n  \"ViewHeader.select-a-template\": \"Bir kalıp seçin\",\n  \"ViewHeader.set-default-template\": \"Varsayılan olarak ata\",\n  \"ViewHeader.sort\": \"Sırala\",\n  \"ViewHeader.untitled\": \"Adlandırılmamış\",\n  \"ViewHeader.view-header-menu\": \"Başlık menüsünü görüntüle\",\n  \"ViewHeader.view-menu\": \"Menüyü görüntüle\",\n  \"ViewLimitDialog.Heading\": \"Bir panoyu görüntüleme sınırına ulaşıldı\",\n  \"ViewLimitDialog.PrimaryButton.Title.Admin\": \"Üst tarifeye geç\",\n  \"ViewLimitDialog.PrimaryButton.Title.RegularUser\": \"Yöneticiyi bilgilendir\",\n  \"ViewLimitDialog.Subtext.Admin\": \"Professional ya da Enterprise tarifemize geçin.\",\n  \"ViewLimitDialog.Subtext.Admin.PricingPageLink\": \"Tarifelerimiz hakkında ayrıntılı bilgi alın.\",\n  \"ViewLimitDialog.Subtext.RegularUser\": \"Yöneticinizi Professional ya da Enterprise tarifesine geçmesi hakkında bilgilendirin.\",\n  \"ViewLimitDialog.UpgradeImg.AltText\": \"üst tarifeye geçiş görseli\",\n  \"ViewLimitDialog.notifyAdmin.Success\": \"Yöneticiniz bilgilendirildi\",\n  \"ViewTitle.hide-description\": \"açıklamayı gizle\",\n  \"ViewTitle.pick-icon\": \"Simge seçin\",\n  \"ViewTitle.random-icon\": \"Rastgele\",\n  \"ViewTitle.remove-icon\": \"Simgeyi kaldır\",\n  \"ViewTitle.show-description\": \"açıklamayı görüntüle\",\n  \"ViewTitle.untitled-board\": \"Adlandırılmamış pano\",\n  \"WelcomePage.Description\": \"Pano, alışılmış Kanban panosu görünümünde takımların işleri tanımlamasını, düzenlemesini, izlemesi ve yönetmesini sağlayan bir proje yönetimi aracıdır.\",\n  \"WelcomePage.Explore.Button\": \"Tura çıkın\",\n  \"WelcomePage.Heading\": \"Panolara hoş geldiniz\",\n  \"WelcomePage.NoThanks.Text\": \"Hayır teşekkürler, kendim anlayacağım\",\n  \"WelcomePage.StartUsingIt.Text\": \"Kullanmaya başlayın\",\n  \"Workspace.editing-board-template\": \"Bir pano kalıbını düzenliyorsunuz.\",\n  \"badge.guest\": \"Konuk\",\n  \"boardPage.confirm-join-button\": \"Katıl\",\n  \"boardPage.confirm-join-text\": \"Bir özel kanala, pano yöneticisi tarafından açıkça eklenmeden katılmak üzeresiniz. Bu özel kanala katılmak istediğinize emin misiniz?\",\n  \"boardPage.confirm-join-title\": \"Özel kanala katıl\",\n  \"boardSelector.confirm-link-board\": \"Panoyu kanala bağla\",\n  \"boardSelector.confirm-link-board-button\": \"Evet, panoyu bağla\",\n  \"boardSelector.confirm-link-board-subtext\": \"\\\"{boardName}\\\" panosunu kanala bağladığınızda, kanalın tüm üyeleri (var olan ve yeni) panoyu düzenleyebilir. Bu işlem konuk üyeleri kaldırır. Bir pano ile bir kanalın bağlantısını istediğiniz zaman kaldırabilirsiniz.\",\n  \"boardSelector.confirm-link-board-subtext-with-other-channel\": \"\\\"{boardName}\\\" panosunu bir kanala bağladığınızda, kanalın tüm üyeleri (var olan ve yeni) panoyu düzenleyebilir. Bu işlem konukl üyeleri kaldırır.{lineBreak}Bu pano şu anda başka bir kanal ile bağlantılı. Bu kanala bağlamayı seçerseniz diğer kanal ile bağlantısı kesilecek.\",\n  \"boardSelector.create-a-board\": \"Bir pano ekle\",\n  \"boardSelector.link\": \"Bağlantı\",\n  \"boardSelector.search-for-boards\": \"Pano arama\",\n  \"boardSelector.title\": \"Panoları bağla\",\n  \"boardSelector.unlink\": \"Bağlantıyı kaldır\",\n  \"calendar.month\": \"Ay\",\n  \"calendar.today\": \"Bugün\",\n  \"calendar.week\": \"Hafta\",\n  \"centerPanel.undefined\": \"{propertyName} yok\",\n  \"centerPanel.unknown-user\": \"Kullanıcı bilinmiyor\",\n  \"cloudMessage.learn-more\": \"Ayrıntılı bilgi alın\",\n  \"createImageBlock.failed\": \"Dosya boyutu sınırı aşıldığından bu dosya yüklenemedi.\",\n  \"default-properties.badges\": \"Yorumlar ve açıklama\",\n  \"default-properties.title\": \"Başlık\",\n  \"error.back-to-home\": \"Girişe dön\",\n  \"error.back-to-team\": \"Takıma dön\",\n  \"error.board-not-found\": \"Pano bulunamadı.\",\n  \"error.go-login\": \"Oturum aç\",\n  \"error.invalid-read-only-board\": \"Bu panoya erişme izniniz yok. Panolara erişmek için oturum açın.\",\n  \"error.not-logged-in\": \"Oturumunuzun süresi dolmuş ya da oturum açmamışsınız. Panolara erişmek için yeniden oturum açın.\",\n  \"error.page.title\": \"Bir şeyler ters gitti\",\n  \"error.team-undefined\": \"Geçerli bir takım değil.\",\n  \"error.unknown\": \"Bir sorun çıktı.\",\n  \"generic.previous\": \"Önceki\",\n  \"guest-no-board.subtitle\": \"Henüz bu takımdaki herhangi bir panoya erişme izniniz yok. Lütfen biri sizi bir panoya ekleyene kadar bekleyin.\",\n  \"guest-no-board.title\": \"Henüz bir pano yok\",\n  \"imagePaste.upload-failed\": \"Dosya boyutu sınırı aşıldığından bazı dosyalar yüklenemedi.\",\n  \"limitedCard.title\": \"Kartlar gizli\",\n  \"login.log-in-button\": \"Oturum aç\",\n  \"login.log-in-title\": \"Oturum açın\",\n  \"login.register-button\": \"ya da hesabınız yoksa bir hesap açın\",\n  \"new_channel_modal.create_board.empty_board_description\": \"Yeni boş bir pano oluştur\",\n  \"new_channel_modal.create_board.empty_board_title\": \"Boş pano\",\n  \"new_channel_modal.create_board.select_template_placeholder\": \"Bir kalıp seçin\",\n  \"new_channel_modal.create_board.title\": \"Bu kanal için bir pano oluştur\",\n  \"notification-box-card-limit-reached.close-tooltip\": \"10 gün için sustur\",\n  \"notification-box-card-limit-reached.contact-link\": \"yöneticinizi bilgilendirin\",\n  \"notification-box-card-limit-reached.link\": \"Ücretli bir tarifeye geçin\",\n  \"notification-box-card-limit-reached.title\": \"panoda {cards} kart gizli\",\n  \"notification-box-cards-hidden.title\": \"Bu işlem başka bir kartı gizledi\",\n  \"notification-box.card-limit-reached.not-admin.text\": \"Arşivlenmiş kartlara erişmek için {contactLink} ile görüşerek ücretli bir tarifeye geçmesini isteyin.\",\n  \"notification-box.card-limit-reached.text\": \"Kart sınırına ulaşıldı. Eski kartları görüntülemek için {link}\",\n  \"person.add-user-to-board\": \"{username} kullanıcısını panoya ekle\",\n  \"person.add-user-to-board-confirm-button\": \"Panoya ekle\",\n  \"person.add-user-to-board-permissions\": \"İzinler\",\n  \"person.add-user-to-board-question\": \"{username} kullanıcısını panoya eklemek ister misiniz?\",\n  \"person.add-user-to-board-warning\": \"{username} panonun bir üyesi değil ve pano ile ilgili herhangi bir bildirim almayacak.\",\n  \"register.login-button\": \"ya da bir hesabınız varsa oturum açın\",\n  \"register.signup-title\": \"Hesap açın\",\n  \"rhs-board-non-admin-msg\": \"Panonun yöneticilerinden değilsiniz\",\n  \"rhs-boards.add\": \"Ekle\",\n  \"rhs-boards.dm\": \"Dİ\",\n  \"rhs-boards.gm\": \"Gİ\",\n  \"rhs-boards.header.dm\": \"bu doğrudan ileti\",\n  \"rhs-boards.header.gm\": \"bu grup iletisi\",\n  \"rhs-boards.last-update-at\": \"Son güncelleme: {datetime}\",\n  \"rhs-boards.link-boards-to-channel\": \"Panoları {channelName} kanalına bağla\",\n  \"rhs-boards.linked-boards\": \"Bağlı panolar\",\n  \"rhs-boards.no-boards-linked-to-channel\": \"Henüz {channelName} kanalına bağlanmış bir pano yok\",\n  \"rhs-boards.no-boards-linked-to-channel-description\": \"Panolar, takımlar arasındaki çalışmaları tanımlamak, organize etmek, izlemek ve yönetmek için kullanılabilen kandan panosuna benzer bir proje yönetimi aracıdır.\",\n  \"rhs-boards.unlink-board\": \"Panonun bağlantısını kaldır\",\n  \"rhs-boards.unlink-board1\": \"Pano bağlantısını kaldır\",\n  \"rhs-channel-boards-header.title\": \"Panolar\",\n  \"share-board.publish\": \"Yayınla\",\n  \"share-board.share\": \"Paylaş\",\n  \"shareBoard.channels-select-group\": \"Kanallar\",\n  \"shareBoard.confirm-change-team-role.body\": \"Bu panoda izinleri \\\"{role}\\\" rolünden daha aşağıda olan herkes <b>{role} rolüne yükseltilecek</b>. Panonunen düşük rolünü değiştirmek istediğinize emin misiniz?\",\n  \"shareBoard.confirm-change-team-role.confirmBtnText\": \"Panonun en düşük rolünü değiştir\",\n  \"shareBoard.confirm-change-team-role.title\": \"Panonun en düşük rolünü değiştir\",\n  \"shareBoard.confirm-link-channel\": \"Panoyu kanala bağla\",\n  \"shareBoard.confirm-link-channel-button\": \"Kanalı bağla\",\n  \"shareBoard.confirm-link-channel-button-with-other-channel\": \"Eski bağlantıyı kes ve bu kanala bağla\",\n  \"shareBoard.confirm-link-channel-subtext\": \"Bir kanalı bir panoya bağladığınızda, kanalın tüm üyeleri (var olan ve yeni) panoyu düzenleyebilir. Bu işlem konuk üyeleri kaldırır.\",\n  \"shareBoard.confirm-link-channel-subtext-with-other-channel\": \"Bir kanalı bir panoya bağladığınızda, kanalın tüm üyeleri (var olan ve yeni) panoyu düzenleyebilir. Bu işlem konuk üyeleri kaldırır.{lineBreak}Bu pano şu anda başka bir kanal ile bağlantılı. Bu kanala bağlamayı seçerseniz diğer kanal ile bağlantısı kesilecek.\",\n  \"shareBoard.confirm-unlink.body\": \"Bir kanalın bir pano ile bağlantısını kaldırdığınızda, kanalın tüm üyeleri (var olan ve yeni), kendilerine özel olarak izin verilmedikçe, panoya erişimi kaybeder.\",\n  \"shareBoard.confirm-unlink.confirmBtnText\": \"Kanalın bağlantısını kaldır\",\n  \"shareBoard.confirm-unlink.title\": \"Kanalın pano ile bağlantısı kaldır\",\n  \"shareBoard.lastAdmin\": \"Panoların en az bir yöneticisi olmalıdır\",\n  \"shareBoard.members-select-group\": \"Üyeler\",\n  \"shareBoard.unknown-channel-display-name\": \"Kanal bilinmiyor\",\n  \"tutorial_tip.finish_tour\": \"Tamam\",\n  \"tutorial_tip.got_it\": \"Anladım\",\n  \"tutorial_tip.ok\": \"Sonraki\",\n  \"tutorial_tip.out\": \"Bu ipuçları görüntülenmesin.\",\n  \"tutorial_tip.seen\": \"Daha önce gördünüz mü?\"\n}\n"
  },
  {
    "path": "webapp/i18n/uk.json",
    "content": "{\n  \"AppBar.Tooltip\": \"Перемкнути пов’язані дошки\",\n  \"Attachment.Attachment-title\": \"Прикріплення\",\n  \"AttachmentBlock.DeleteAction\": \"видалити\",\n  \"AttachmentBlock.addElement\": \"додати {type}\",\n  \"AttachmentBlock.delete\": \"Прикріплення успішно видалено.\",\n  \"AttachmentBlock.failed\": \"Не вдалося завантажити цей файл, оскільки досягнуто обмеження розміру файлу.\",\n  \"AttachmentBlock.upload\": \"Прикріплення завантажуються.\",\n  \"AttachmentBlock.uploadSuccess\": \"Вкладення завантажено.\",\n  \"AttachmentElement.delete-confirmation-dialog-button-text\": \"Видалити\",\n  \"AttachmentElement.download\": \"Завантажити\",\n  \"AttachmentElement.upload-percentage\": \"Завантаження...({uploadPercent}%)\",\n  \"BoardComponent.add-a-group\": \"+ Додати групу\",\n  \"BoardComponent.delete\": \"Видалити\",\n  \"BoardComponent.hidden-columns\": \"Приховані стовпці\",\n  \"BoardComponent.hide\": \"Приховати\",\n  \"BoardComponent.new\": \"+ Створити\",\n  \"BoardComponent.no-property\": \"Немає {property}\",\n  \"BoardComponent.no-property-title\": \"Елементи з порожнім полем {property} потраплять сюди. Цей стовпець неможливо видалити.\",\n  \"BoardComponent.show\": \"Показати\",\n  \"BoardMember.schemeAdmin\": \"Адміністратор\",\n  \"BoardMember.schemeCommenter\": \"Коментатор\",\n  \"BoardMember.schemeEditor\": \"Редактор\",\n  \"BoardMember.schemeNone\": \"Жоден\",\n  \"BoardMember.schemeViewer\": \"Спостерігач\",\n  \"BoardMember.unlinkChannel\": \"Від’єднати\",\n  \"BoardPage.newVersion\": \"Доступна оновлена версія Панелі, тицьни тут щоб оновити.\",\n  \"BoardPage.syncFailed\": \"Можливо Панель видалено або права анульовано.\",\n  \"BoardTemplateSelector.add-template\": \"Створити новий шаблон\",\n  \"BoardTemplateSelector.create-empty-board\": \"Створити порожню доску\",\n  \"BoardTemplateSelector.delete-template\": \"Видалити\",\n  \"BoardTemplateSelector.description\": \"Додайте дошку на бічній панелі, використовуючи будь-який із наведених нижче шаблонів, або почніть з нуля.\",\n  \"BoardTemplateSelector.edit-template\": \"Редагувати\",\n  \"BoardTemplateSelector.plugin.no-content-description\": \"Додайте дошку на бічній панелі, використовуючи будь-який із наведених нижче шаблонів, або почніть з нуля.\",\n  \"BoardTemplateSelector.plugin.no-content-title\": \"Створити дошку\",\n  \"BoardTemplateSelector.title\": \"Створити доску\",\n  \"BoardTemplateSelector.use-this-template\": \"Використати цей шаблон\",\n  \"BoardsSwitcher.Title\": \"Знайти дошки\",\n  \"BoardsUnfurl.Limited\": \"Додаткові деталі приховані бо картку архівовано\",\n  \"BoardsUnfurl.Remainder\": \"+{remainder} більше\",\n  \"BoardsUnfurl.Updated\": \"Оновлено {time}\",\n  \"Calculations.Options.average.displayName\": \"Середній\",\n  \"Calculations.Options.average.label\": \"Середній\",\n  \"Calculations.Options.count.displayName\": \"Кількість\",\n  \"Calculations.Options.count.label\": \"Кількість\",\n  \"Calculations.Options.countChecked.displayName\": \"Перевірено\",\n  \"Calculations.Options.countChecked.label\": \"Кількість перевірено\",\n  \"Calculations.Options.countUnchecked.displayName\": \"Не перевірено\",\n  \"Calculations.Options.countUnchecked.label\": \"Підрахунок не перевірено\",\n  \"Calculations.Options.countUniqueValue.displayName\": \"Унікальний\",\n  \"Calculations.Options.countUniqueValue.label\": \"Підрахувати унікальні значення\",\n  \"Calculations.Options.countValue.displayName\": \"Значення\",\n  \"Calculations.Options.countValue.label\": \"Розрахунок значення\",\n  \"Calculations.Options.dateRange.displayName\": \"Діапазон\",\n  \"Calculations.Options.dateRange.label\": \"Діапазон\",\n  \"Calculations.Options.earliest.displayName\": \"Найраніший\",\n  \"Calculations.Options.earliest.label\": \"Найраніший\",\n  \"Calculations.Options.latest.displayName\": \"Останній\",\n  \"Calculations.Options.latest.label\": \"Останній\",\n  \"Calculations.Options.max.displayName\": \"Макс\",\n  \"Calculations.Options.max.label\": \"Макс\",\n  \"Calculations.Options.median.displayName\": \"Медіана\",\n  \"Calculations.Options.median.label\": \"Медіана\",\n  \"Calculations.Options.min.displayName\": \"Мін\",\n  \"Calculations.Options.min.label\": \"Мін\",\n  \"Calculations.Options.none.displayName\": \"Обчислити\",\n  \"Calculations.Options.none.label\": \"Жодного\",\n  \"Calculations.Options.percentChecked.displayName\": \"Перевірено\",\n  \"Calculations.Options.percentChecked.label\": \"Відсоток перевірено\",\n  \"Calculations.Options.percentUnchecked.displayName\": \"Не перевірено\",\n  \"Calculations.Options.percentUnchecked.label\": \"Відсоток не перевірено\",\n  \"Calculations.Options.range.displayName\": \"Діапазон\",\n  \"Calculations.Options.range.label\": \"Діапазон\",\n  \"Calculations.Options.sum.displayName\": \"Сума\",\n  \"Calculations.Options.sum.label\": \"Сума\",\n  \"CalendarCard.untitled\": \"Без назви\",\n  \"CardActionsMenu.copiedLink\": \"Скопійовано!\",\n  \"CardActionsMenu.copyLink\": \"Копіювати посилання\",\n  \"CardActionsMenu.delete\": \"Видалити\",\n  \"CardActionsMenu.duplicate\": \"Дублювати\",\n  \"CardBadges.title-checkboxes\": \"Прапорці\",\n  \"CardBadges.title-comments\": \"Коментарі\",\n  \"CardBadges.title-description\": \"Ця картка має опис\",\n  \"CardDetail.Attach\": \"Прикріпити\",\n  \"CardDetail.Follow\": \"Слідкувати\",\n  \"CardDetail.Following\": \"Відслідковувати\",\n  \"CardDetail.add-content\": \"Додайте вміст\",\n  \"CardDetail.add-icon\": \"Додати значок\",\n  \"CardDetail.add-property\": \"+ Додати властивість\",\n  \"CardDetail.addCardText\": \"додати текст картки\",\n  \"CardDetail.limited-body\": \"Перейдіть на наш план Professional або Enterprise.\",\n  \"CardDetail.limited-button\": \"Оновлення\",\n  \"CardDetail.limited-title\": \"Ця прихована картка\",\n  \"CardDetail.moveContent\": \"Перемістити вміст картки\",\n  \"CardDetail.new-comment-placeholder\": \"Додати коментар...\",\n  \"CardDetailProperty.confirm-delete-heading\": \"Підтвердьте видалення властивості\",\n  \"CardDetailProperty.confirm-delete-subtext\": \"Ви впевнені, що хочете видалити властивість \\\"{propertyName}\\\"? При видаленні властивість буде видалено з усіх карток на цій дошці.\",\n  \"CardDetailProperty.confirm-property-name-change-subtext\": \"Ви дійсно хочете змінити властивість \\\"{propertyName}\\\" {customText}? Це вплине на значення(-я) на {numOfCards} картці(-ах) на цій дошці і може призвести до втрати даних.\",\n  \"CardDetailProperty.confirm-property-type-change\": \"Підтвердити зміну типу власності\",\n  \"CardDetailProperty.delete-action-button\": \"Видалити\",\n  \"CardDetailProperty.property-change-action-button\": \"Змінити властивість\",\n  \"CardDetailProperty.property-changed\": \"Властивість змінена успішно!\",\n  \"CardDetailProperty.property-deleted\": \"{propertyName} успішно видалено!\",\n  \"CardDetailProperty.property-name-change-subtext\": \"тип з \\\"{oldPropType}\\\" в \\\"{newPropType}\\\"\",\n  \"CardDetial.limited-link\": \"Дізнайтеся більше про наші плани.\",\n  \"CardDialog.delete-confirmation-dialog-attachment\": \"Підтвердити видалення вкладення\",\n  \"CardDialog.delete-confirmation-dialog-button-text\": \"Видалити\",\n  \"CardDialog.delete-confirmation-dialog-heading\": \"Підтвердити видалення картки\",\n  \"CardDialog.editing-template\": \"Ви редагуєте шаблон.\",\n  \"CardDialog.nocard\": \"Ця картка не існує або недоступна.\",\n  \"Categories.CreateCategoryDialog.CancelText\": \"Скасувати\",\n  \"Categories.CreateCategoryDialog.CreateText\": \"Створити\",\n  \"Categories.CreateCategoryDialog.Placeholder\": \"Назвіть свою категорію\",\n  \"Categories.CreateCategoryDialog.UpdateText\": \"Оновити\",\n  \"CenterPanel.Login\": \"Логін\",\n  \"CenterPanel.Share\": \"Поділитися\",\n  \"ChannelIntro.CreateBoard\": \"Створити дошку\",\n  \"ColorOption.selectColor\": \"Виберіть колір {color}\",\n  \"Comment.delete\": \"Видалити\",\n  \"CommentsList.send\": \"Надіслати\",\n  \"ConfirmPerson.empty\": \"Порожній\",\n  \"ConfirmPerson.search\": \"Пошук...\",\n  \"ConfirmationDialog.cancel-action\": \"Скасувати\",\n  \"ConfirmationDialog.confirm-action\": \"Підтвердити\",\n  \"ContentBlock.Delete\": \"Видалити\",\n  \"ContentBlock.DeleteAction\": \"видалити\",\n  \"ContentBlock.addElement\": \"додати {type}\",\n  \"ContentBlock.checkbox\": \"прапорець\",\n  \"ContentBlock.divider\": \"роздільник\",\n  \"ContentBlock.editCardCheckbox\": \"позначений прапорець\",\n  \"ContentBlock.editCardCheckboxText\": \"редагувати текст картки\",\n  \"ContentBlock.editCardText\": \"редагувати текст картки\",\n  \"ContentBlock.editText\": \"Редагувати текст...\",\n  \"ContentBlock.image\": \"зображення\",\n  \"ContentBlock.insertAbove\": \"Вставте вище\",\n  \"ContentBlock.moveBlock\": \"перемістити вміст картки\",\n  \"ContentBlock.moveDown\": \"Опустити\",\n  \"ContentBlock.moveUp\": \"Підняти\",\n  \"ContentBlock.text\": \"текст\",\n  \"DateRange.clear\": \"Очистити\",\n  \"DateRange.empty\": \"Пусто\",\n  \"DateRange.endDate\": \"Дата закінчення\",\n  \"DateRange.today\": \"Сьогодні\",\n  \"DeleteBoardDialog.confirm-cancel\": \"Скасувати\",\n  \"DeleteBoardDialog.confirm-delete\": \"Видалити\",\n  \"DeleteBoardDialog.confirm-info\": \"Ви впевнені, що хочете видалити дошку “{boardTitle}”? Видалення призведе до видалення всіх карток на дошці.\",\n  \"DeleteBoardDialog.confirm-info-template\": \"Ви впевнені, що хочете видалити шаблон дошки «{boardTitle}»?\",\n  \"DeleteBoardDialog.confirm-tite\": \"Підтвердьте видалення дошки\",\n  \"DeleteBoardDialog.confirm-tite-template\": \"Підтвердьте видалення шаблону дошки\",\n  \"Dialog.closeDialog\": \"Закрити діалогове вікно\",\n  \"EditableDayPicker.today\": \"Сьогодні\",\n  \"Error.mobileweb\": \"Мобільна веб-підтримка зараз знаходиться на ранній стадії бета-тестування. Не всі функції можуть бути присутніми.\",\n  \"Error.websocket-closed\": \"З'єднання через веб-сокет закрито, з'єднання перервано. Якщо це продовжується й далі, перевірте конфігурацію сервера або веб-проксі.\",\n  \"Filter.contains\": \"містить\",\n  \"Filter.ends-with\": \"закінчується на\",\n  \"Filter.includes\": \"включає в себе\",\n  \"Filter.is\": \"є\",\n  \"Filter.is-empty\": \"пусто\",\n  \"Filter.is-not-empty\": \"не порожній\",\n  \"Filter.is-not-set\": \"не встановлено\",\n  \"Filter.is-set\": \"встановлено\",\n  \"Filter.not-contains\": \"не містить\",\n  \"Filter.not-ends-with\": \"не закінчується\",\n  \"Filter.not-includes\": \"не включає\",\n  \"Filter.not-starts-with\": \"не починається з\",\n  \"Filter.starts-with\": \"починається з\",\n  \"FilterByText.placeholder\": \"фільтрувати текст\",\n  \"FilterComponent.add-filter\": \"+ Додати фільтр\",\n  \"FilterComponent.delete\": \"Видалити\",\n  \"FilterValue.empty\": \"(порожній)\",\n  \"FindBoardsDialog.IntroText\": \"Пошук дощок\",\n  \"FindBoardsDialog.NoResultsFor\": \"Немає результатів для \\\"{searchQuery}\\\"\",\n  \"FindBoardsDialog.NoResultsSubtext\": \"Перевірте правильність написання або спробуйте інший запит.\",\n  \"FindBoardsDialog.SubTitle\": \"Введіть, щоб знайти дошку. Використовуйте <b>ВГОРУ/ВНИЗ</b> для перегляду. <b>ENTER</b>, щоб вибрати, <b>ESC</b>, щоб закрити\",\n  \"FindBoardsDialog.Title\": \"Знайти дошки\",\n  \"GroupBy.hideEmptyGroups\": \"Сховати {count} порожні групи\",\n  \"GroupBy.showHiddenGroups\": \"Показати {count} прихованих груп\",\n  \"GroupBy.ungroup\": \"Розгрупувати\",\n  \"HideBoard.MenuOption\": \"Сховати дошку\",\n  \"KanbanCard.untitled\": \"Без назви\",\n  \"MentionSuggestion.is-not-board-member\": \"(не член правління)\",\n  \"Mutator.new-board-from-template\": \"нова дошка з шаблону\",\n  \"Mutator.new-card-from-template\": \"нова картка із шаблону\",\n  \"Mutator.new-template-from-card\": \"новий шаблон із картки\",\n  \"OnboardingTour.AddComments.Body\": \"Ви можете коментувати проблеми та навіть @згадувати інших користувачів Mattermost, щоб привернути їх увагу.\",\n  \"OnboardingTour.AddComments.Title\": \"Додати коментарі\",\n  \"OnboardingTour.AddDescription.Body\": \"Додайте опис до картки, щоб ваші товариші по команді знали, про що йде мова.\",\n  \"OnboardingTour.AddDescription.Title\": \"Додайте опис\",\n  \"OnboardingTour.AddProperties.Body\": \"Додайте карткам різні властивості, щоб зробити їх потужнішими.\",\n  \"OnboardingTour.AddProperties.Title\": \"Додайте властивості\",\n  \"OnboardingTour.AddView.Body\": \"Перейдіть сюди, щоб створити новий вид для організації дошки за допомогою різних макетів.\",\n  \"OnboardingTour.AddView.Title\": \"Додайте новий вид\",\n  \"OnboardingTour.CopyLink.Body\": \"Ви можете поділитися своїми картками з товаришами по команді, скопіювавши посилання та вставивши його в канал, пряме або групове повідомлення.\",\n  \"OnboardingTour.CopyLink.Title\": \"Копіювати посилання\",\n  \"OnboardingTour.OpenACard.Body\": \"Відкрийте картку, щоб дослідити потужні способи, за допомогою яких дошки можуть допомогти вам організувати вашу роботу.\",\n  \"OnboardingTour.OpenACard.Title\": \"Відкрити картку\",\n  \"OnboardingTour.ShareBoard.Body\": \"Ви можете поділитися своєю дошкою всередині, у своїй команді або опублікувати її публічно для видимості за межами вашої організації.\",\n  \"OnboardingTour.ShareBoard.Title\": \"Поділитися дошкою\",\n  \"PersonProperty.board-members\": \"Члени команди\",\n  \"PersonProperty.me\": \"Я\",\n  \"PersonProperty.non-board-members\": \"Не учасник команди\",\n  \"PropertyMenu.Delete\": \"Видалити\",\n  \"PropertyMenu.changeType\": \"Змінити тип власності\",\n  \"PropertyMenu.selectType\": \"Виберіть тип властивості\",\n  \"PropertyMenu.typeTitle\": \"Тип\",\n  \"PropertyType.Checkbox\": \"Прапорець\",\n  \"PropertyType.CreatedBy\": \"Створений\",\n  \"PropertyType.CreatedTime\": \"Час створення\",\n  \"PropertyType.Date\": \"Дата\",\n  \"PropertyType.Email\": \"Email\",\n  \"PropertyType.MultiPerson\": \"Кілька осіб\",\n  \"PropertyType.MultiSelect\": \"Множинний вибір\",\n  \"PropertyType.Number\": \"Номер\",\n  \"PropertyType.Person\": \"Особа\",\n  \"PropertyType.Phone\": \"Телефон\",\n  \"PropertyType.Select\": \"Обрати\",\n  \"PropertyType.Text\": \"Текст\",\n  \"PropertyType.Unknown\": \"Невідомий\",\n  \"PropertyType.UpdatedBy\": \"Оновлено користувачем\",\n  \"PropertyType.UpdatedTime\": \"Час останнього оновлення\",\n  \"PropertyType.Url\": \"URL\",\n  \"PropertyValueElement.empty\": \"Пусто\",\n  \"RegistrationLink.confirmRegenerateToken\": \"Це призведе до скасування попередніх спільних посилань. Продовжити?\",\n  \"RegistrationLink.copiedLink\": \"Скопійовано!\",\n  \"RegistrationLink.copyLink\": \"Копіювати посилання\",\n  \"RegistrationLink.description\": \"Поділіться цим посиланням, щоб інші могли створити облікові записи:\",\n  \"RegistrationLink.regenerateToken\": \"Згенерувати новий токен\",\n  \"RegistrationLink.tokenRegenerated\": \"Реєстраційне посилання відновлено\",\n  \"ShareBoard.PublishDescription\": \"Опублікуйте та поділіться посиланням лише для читання з усіма в Інтернеті.\",\n  \"ShareBoard.PublishTitle\": \"Опублікувати в Інтернеті\",\n  \"ShareBoard.ShareInternal\": \"Поділитися всередині організації\",\n  \"ShareBoard.ShareInternalDescription\": \"Користувачі, які мають дозволи, зможуть використовувати це посилання.\",\n  \"ShareBoard.Title\": \"Поділиться Дошкою\",\n  \"Sidebar.delete-board\": \"Видалити дошку\",\n  \"SidebarCategories.CategoryMenu.Delete\": \"Видалити категорію\",\n  \"SidebarCategories.CategoryMenu.DeleteModal.Title\": \"Видалити дану категорію?\",\n  \"TableHeaderMenu.delete\": \"Видалити\",\n  \"View.DeleteView\": \"Видалити вид\",\n  \"ViewHeader.delete-template\": \"Видалити\",\n  \"generic.previous\": \"Попередній\",\n  \"shareBoard.unknown-channel-display-name\": \"Невідомий канал\",\n  \"tutorial_tip.finish_tour\": \"Готово\",\n  \"tutorial_tip.got_it\": \"Зрозуміло\",\n  \"tutorial_tip.ok\": \"Далі\",\n  \"tutorial_tip.out\": \"Відмовтеся від цих порад.\",\n  \"tutorial_tip.seen\": \"Ви бачили це раніше?\"\n}\n"
  },
  {
    "path": "webapp/i18n/vi.json",
    "content": "{\n  \"AppBar.Tooltip\": \"Chuyển sang các bảng đã liên kết\",\n  \"Attachment.Attachment-title\": \"Đính kèm\",\n  \"AttachmentBlock.DeleteAction\": \"xóa\",\n  \"BoardComponent.add-a-group\": \"+ Thêm nhóm\",\n  \"BoardComponent.delete\": \"Xóa\",\n  \"BoardComponent.hidden-columns\": \"Cột ẩn\",\n  \"BoardComponent.hide\": \"Ẩn\",\n  \"BoardComponent.new\": \"+ Thêm\",\n  \"BoardComponent.no-property\": \"Không {property}\",\n  \"BoardComponent.show\": \"Hiện\",\n  \"BoardMember.schemeAdmin\": \"Quản trị\",\n  \"BoardMember.schemeCommenter\": \"Người bình luận\",\n  \"BoardMember.schemeEditor\": \"Người soạn thảo\",\n  \"BoardMember.schemeNone\": \"Không\",\n  \"BoardMember.schemeViewer\": \"Người xem\",\n  \"BoardMember.unlinkChannel\": \"Gỡ liên kết\",\n  \"BoardPage.newVersion\": \"Có một phiên bản mới của bảng, click vào đây để nạp lại.\",\n  \"Calculations.Options.average.displayName\": \"Trung bình\",\n  \"Calculations.Options.average.label\": \"Trung bình\",\n  \"TableComponent.add-icon\": \"Thêm icon\",\n  \"TableComponent.name\": \"Tên\",\n  \"TableComponent.plus-new\": \"+ Mới\",\n  \"TableHeaderMenu.delete\": \"Xóa\",\n  \"share-board.publish\": \"Công khai\",\n  \"share-board.share\": \"Chia sẻ\",\n  \"shareBoard.channels-select-group\": \"Kênh\",\n  \"shareBoard.members-select-group\": \"Thành viên\",\n  \"tutorial_tip.finish_tour\": \"Xong\",\n  \"tutorial_tip.got_it\": \"Đã hiểu\",\n  \"tutorial_tip.ok\": \"Tiếp theo\"\n}\n"
  },
  {
    "path": "webapp/i18n/zh_Hans.json",
    "content": "{\n  \"AppBar.Tooltip\": \"切换链接的板块\",\n  \"Attachment.Attachment-title\": \"附件\",\n  \"AttachmentBlock.DeleteAction\": \"删除\",\n  \"AttachmentBlock.addElement\": \"添加 {type}\",\n  \"AttachmentBlock.delete\": \"附件已删除。\",\n  \"AttachmentBlock.failed\": \"该文件无法上传，因为已经达到了文件大小的限制。\",\n  \"AttachmentBlock.upload\": \"附件正在上传。\",\n  \"AttachmentBlock.uploadSuccess\": \"附件已上传。\",\n  \"AttachmentElement.delete-confirmation-dialog-button-text\": \"删除\",\n  \"AttachmentElement.download\": \"下载\",\n  \"AttachmentElement.upload-percentage\": \"上传中…({uploadPercent}%)\",\n  \"BoardComponent.add-a-group\": \"+ 新增群组\",\n  \"BoardComponent.delete\": \"删除\",\n  \"BoardComponent.hidden-columns\": \"隐藏列\",\n  \"BoardComponent.hide\": \"隐藏\",\n  \"BoardComponent.new\": \"+ 新增\",\n  \"BoardComponent.no-property\": \"无 {property}\",\n  \"BoardComponent.no-property-title\": \"{property} 属性为空的项目将转到此处，该列无法删除。\",\n  \"BoardComponent.show\": \"显示\",\n  \"BoardMember.schemeAdmin\": \"管理\",\n  \"BoardMember.schemeCommenter\": \"评论者\",\n  \"BoardMember.schemeEditor\": \"编辑器\",\n  \"BoardMember.schemeNone\": \"无\",\n  \"BoardMember.schemeViewer\": \"视图\",\n  \"BoardMember.unlinkChannel\": \"断开\",\n  \"BoardPage.newVersion\": \"Boards 的新版本已可用，点击这里重新加载。\",\n  \"BoardPage.syncFailed\": \"板块或许已被删除或访问授权已被撤销。\",\n  \"BoardTemplateSelector.add-template\": \"创建新模板\",\n  \"BoardTemplateSelector.create-empty-board\": \"创建空白板块\",\n  \"BoardTemplateSelector.delete-template\": \"删除\",\n  \"BoardTemplateSelector.description\": \"选择一个模板助你开始。或者创建一个空白板块，从零开始。\",\n  \"BoardTemplateSelector.edit-template\": \"编辑\",\n  \"BoardTemplateSelector.plugin.no-content-description\": \"使用下面定义好的任意模板给侧栏添加一个看板，或者从头开始。\",\n  \"BoardTemplateSelector.plugin.no-content-title\": \"创建一个看板\",\n  \"BoardTemplateSelector.title\": \"创建一个看板\",\n  \"BoardTemplateSelector.use-this-template\": \"使用该模板\",\n  \"BoardsSwitcher.Title\": \"查找板块\",\n  \"BoardsUnfurl.Limited\": \"由于卡片被存档，其他细节被隐藏\",\n  \"BoardsUnfurl.Remainder\": \"+{remainder} 更多\",\n  \"BoardsUnfurl.Updated\": \"于 {time} 更新\",\n  \"Calculations.Options.average.displayName\": \"平均\",\n  \"Calculations.Options.average.label\": \"平均\",\n  \"Calculations.Options.count.displayName\": \"计数\",\n  \"Calculations.Options.count.label\": \"计数\",\n  \"Calculations.Options.countChecked.displayName\": \"选中\",\n  \"Calculations.Options.countChecked.label\": \"选中计数\",\n  \"Calculations.Options.countUnchecked.displayName\": \"未选中\",\n  \"Calculations.Options.countUnchecked.label\": \"未选中计数\",\n  \"Calculations.Options.countUniqueValue.displayName\": \"唯一\",\n  \"Calculations.Options.countUniqueValue.label\": \"唯一值计数\",\n  \"Calculations.Options.countValue.displayName\": \"值\",\n  \"Calculations.Options.countValue.label\": \"值计数\",\n  \"Calculations.Options.dateRange.displayName\": \"范围\",\n  \"Calculations.Options.dateRange.label\": \"范围\",\n  \"Calculations.Options.earliest.displayName\": \"最早的\",\n  \"Calculations.Options.earliest.label\": \"最早的\",\n  \"Calculations.Options.latest.displayName\": \"最新的\",\n  \"Calculations.Options.latest.label\": \"最新的\",\n  \"Calculations.Options.max.displayName\": \"最大的\",\n  \"Calculations.Options.max.label\": \"最大的\",\n  \"Calculations.Options.median.displayName\": \"中位数\",\n  \"Calculations.Options.median.label\": \"中位数\",\n  \"Calculations.Options.min.displayName\": \"最小的\",\n  \"Calculations.Options.min.label\": \"最小值\",\n  \"Calculations.Options.none.displayName\": \"计算\",\n  \"Calculations.Options.none.label\": \"无\",\n  \"Calculations.Options.percentChecked.displayName\": \"已选中\",\n  \"Calculations.Options.percentChecked.label\": \"已选中的百分比\",\n  \"Calculations.Options.percentUnchecked.displayName\": \"未选中\",\n  \"Calculations.Options.percentUnchecked.label\": \"未选中的百分比\",\n  \"Calculations.Options.range.displayName\": \"范围\",\n  \"Calculations.Options.range.label\": \"范围\",\n  \"Calculations.Options.sum.displayName\": \"总计\",\n  \"Calculations.Options.sum.label\": \"总计\",\n  \"CalendarCard.untitled\": \"无内容\",\n  \"CardActionsMenu.copiedLink\": \"复制成功！\",\n  \"CardActionsMenu.copyLink\": \"复制链接\",\n  \"CardActionsMenu.delete\": \"删除\",\n  \"CardActionsMenu.duplicate\": \"重复\",\n  \"CardBadges.title-checkboxes\": \"Checkbox\",\n  \"CardBadges.title-comments\": \"评论\",\n  \"CardBadges.title-description\": \"此卡片有描述内容\",\n  \"CardDetail.Attach\": \"附加\",\n  \"CardDetail.Follow\": \"关注\",\n  \"CardDetail.Following\": \"关注中\",\n  \"CardDetail.add-content\": \"新增内容\",\n  \"CardDetail.add-icon\": \"新增图标\",\n  \"CardDetail.add-property\": \"+ 新增属性\",\n  \"CardDetail.addCardText\": \"新增卡片文本\",\n  \"CardDetail.limited-body\": \"升级到我们的专业或企业计划。\",\n  \"CardDetail.limited-button\": \"升级\",\n  \"CardDetail.limited-title\": \"这张卡片是隐藏的\",\n  \"CardDetail.moveContent\": \"移动卡片内容\",\n  \"CardDetail.new-comment-placeholder\": \"新增评论...\",\n  \"CardDetailProperty.confirm-delete-heading\": \"确认删除此属性\",\n  \"CardDetailProperty.confirm-delete-subtext\": \"确定要删除属性：{propertyName}？也会同时删除这个版面中所有其他卡片的属性。\",\n  \"CardDetailProperty.confirm-property-name-change-subtext\": \"你确定要改变属性名称\\\"{propertyName}\\\" {customText}？这将影响此板块中的{numOfCards}个卡片，并可能导致数据丢失。\",\n  \"CardDetailProperty.confirm-property-type-change\": \"确认修改此属性的类型\",\n  \"CardDetailProperty.delete-action-button\": \"删除\",\n  \"CardDetailProperty.property-change-action-button\": \"修改属性\",\n  \"CardDetailProperty.property-changed\": \"已成功修改属性！\",\n  \"CardDetailProperty.property-deleted\": \"成功删除 {propertyName}！\",\n  \"CardDetailProperty.property-name-change-subtext\": \"属性的类型从\\\"{oldPropType}\\\" 更改为\\\"{newPropType}\\\"\",\n  \"CardDetial.limited-link\": \"了解更多关于我们的计划。\",\n  \"CardDialog.delete-confirmation-dialog-attachment\": \"确认删除附件\",\n  \"CardDialog.delete-confirmation-dialog-button-text\": \"删除\",\n  \"CardDialog.delete-confirmation-dialog-heading\": \"确认删除卡片\",\n  \"CardDialog.editing-template\": \"您正在编辑模板。\",\n  \"CardDialog.nocard\": \"卡片不存在或者无法被存取。\",\n  \"Categories.CreateCategoryDialog.CancelText\": \"取消\",\n  \"Categories.CreateCategoryDialog.CreateText\": \"新增\",\n  \"Categories.CreateCategoryDialog.Placeholder\": \"命名你的类别\",\n  \"Categories.CreateCategoryDialog.UpdateText\": \"更新\",\n  \"CenterPanel.Login\": \"登录\",\n  \"CenterPanel.Share\": \"分享\",\n  \"ChannelIntro.CreateBoard\": \"创建一个板块\",\n  \"ColorOption.selectColor\": \"选择{color}\",\n  \"Comment.delete\": \"删除\",\n  \"CommentsList.send\": \"发送\",\n  \"ConfirmPerson.empty\": \"空\",\n  \"ConfirmPerson.search\": \"搜索...\",\n  \"ConfirmationDialog.cancel-action\": \"取消\",\n  \"ConfirmationDialog.confirm-action\": \"确认\",\n  \"ContentBlock.Delete\": \"删除\",\n  \"ContentBlock.DeleteAction\": \"删除\",\n  \"ContentBlock.addElement\": \"新增 {type}\",\n  \"ContentBlock.checkbox\": \"复选框\",\n  \"ContentBlock.divider\": \"分割线\",\n  \"ContentBlock.editCardCheckbox\": \"切换复选框\",\n  \"ContentBlock.editCardCheckboxText\": \"编辑卡片文字\",\n  \"ContentBlock.editCardText\": \"编辑卡片文字\",\n  \"ContentBlock.editText\": \"编辑文字...\",\n  \"ContentBlock.image\": \"图片\",\n  \"ContentBlock.insertAbove\": \"在上方插入\",\n  \"ContentBlock.moveBlock\": \"移动卡片内容\",\n  \"ContentBlock.moveDown\": \"下移\",\n  \"ContentBlock.moveUp\": \"上移\",\n  \"ContentBlock.text\": \"文字\",\n  \"DateRange.clear\": \"清除\",\n  \"DateRange.empty\": \"空的\",\n  \"DateRange.endDate\": \"结束日期\",\n  \"DateRange.today\": \"今天\",\n  \"DeleteBoardDialog.confirm-cancel\": \"取消\",\n  \"DeleteBoardDialog.confirm-delete\": \"删除\",\n  \"DeleteBoardDialog.confirm-info\": \"确定要删除版块\\\"{boardTitle}\\\"？删除后，也将删除此版块中的所有卡片。\",\n  \"DeleteBoardDialog.confirm-info-template\": \"你确定要删除板块模板\\\"{boardTitle}\\\"吗？\",\n  \"DeleteBoardDialog.confirm-tite\": \"确认删除板块\",\n  \"DeleteBoardDialog.confirm-tite-template\": \"确认删除板块模板\",\n  \"Dialog.closeDialog\": \"关闭对话框\",\n  \"EditableDayPicker.today\": \"今天\",\n  \"Error.mobileweb\": \"移动端页面的支持目前处于早期测试阶段。不是所有的功能都已实现。\",\n  \"Error.websocket-closed\": \"Websocket 连接关闭，连接中断。如果这种情况仍然存在，请检查您的服务器或网页代理配置。\",\n  \"Filter.contains\": \"包含\",\n  \"Filter.ends-with\": \"结束于\",\n  \"Filter.includes\": \"含有\",\n  \"Filter.is\": \"是\",\n  \"Filter.is-empty\": \"为空\",\n  \"Filter.is-not-empty\": \"不为空\",\n  \"Filter.is-not-set\": \"未设置\",\n  \"Filter.is-set\": \"被设定为\",\n  \"Filter.not-contains\": \"不包含\",\n  \"Filter.not-ends-with\": \"不结束于\",\n  \"Filter.not-includes\": \"不含有\",\n  \"Filter.not-starts-with\": \"不开始于\",\n  \"Filter.starts-with\": \"开始于\",\n  \"FilterByText.placeholder\": \"过滤文本\",\n  \"FilterComponent.add-filter\": \"+ 增加过滤条件\",\n  \"FilterComponent.delete\": \"删除\",\n  \"FilterValue.empty\": \"（空）\",\n  \"FindBoardsDialog.IntroText\": \"搜索板块\",\n  \"FindBoardsDialog.NoResultsFor\": \"没有\\\"{searchQuery}\\\"相关的结果\",\n  \"FindBoardsDialog.NoResultsSubtext\": \"请检查拼写或者查找其他内容。\",\n  \"FindBoardsDialog.SubTitle\": \"输入内容来查找板块。使用<b>上/下</b>浏览。<b>ENTER</b>选择，<b>ESC</b>取消\",\n  \"FindBoardsDialog.Title\": \"查找板块\",\n  \"GroupBy.hideEmptyGroups\": \"隐藏{count}个空组\",\n  \"GroupBy.showHiddenGroups\": \"显示已隐藏的{count}个组\",\n  \"GroupBy.ungroup\": \"未分组\",\n  \"HideBoard.MenuOption\": \"隐藏板块\",\n  \"KanbanCard.untitled\": \"无标题\",\n  \"MentionSuggestion.is-not-board-member\": \"（非板块成员）\",\n  \"Mutator.new-board-from-template\": \"从模板创建板块\",\n  \"Mutator.new-card-from-template\": \"使用模板新增卡片\",\n  \"Mutator.new-template-from-card\": \"从卡片新增模板\",\n  \"OnboardingTour.AddComments.Body\": \"你可以对问题进行评论，甚至可以@提及你的Mattermost同伴，以引起他们的注意。\",\n  \"OnboardingTour.AddComments.Title\": \"添加评论\",\n  \"OnboardingTour.AddDescription.Body\": \"在你的卡片上添加描述，以便其他人了解卡片的内容。\",\n  \"OnboardingTour.AddDescription.Title\": \"添加描述\",\n  \"OnboardingTour.AddProperties.Body\": \"为卡片添加各种属性，使其更加强大。\",\n  \"OnboardingTour.AddProperties.Title\": \"添加属性\",\n  \"OnboardingTour.AddView.Body\": \"在这里创建一个新的视图，用不同的布局来组织你的板块。\",\n  \"OnboardingTour.AddView.Title\": \"添加一个新的视图\",\n  \"OnboardingTour.CopyLink.Body\": \"你可以通过频道，私信和群聊分享链接来和成员们一起共享卡片。\",\n  \"OnboardingTour.CopyLink.Title\": \"复制链接\",\n  \"OnboardingTour.OpenACard.Body\": \"打开卡片来探索板块的高效使用方法，从而助力你的整理项目。\",\n  \"OnboardingTour.OpenACard.Title\": \"打开一个卡片\",\n  \"OnboardingTour.ShareBoard.Body\": \"你可以分享板块，不管是与内部成员，还是公开发布到外部的机构。\",\n  \"OnboardingTour.ShareBoard.Title\": \"分享板块\",\n  \"PersonProperty.board-members\": \"板块成员\",\n  \"PersonProperty.me\": \"我\",\n  \"PersonProperty.non-board-members\": \"非板块成员\",\n  \"PropertyMenu.Delete\": \"删除\",\n  \"PropertyMenu.changeType\": \"修改属性类型\",\n  \"PropertyMenu.selectType\": \"选择属性类型\",\n  \"PropertyMenu.typeTitle\": \"类型\",\n  \"PropertyType.Checkbox\": \"复选框\",\n  \"PropertyType.CreatedBy\": \"创建者\",\n  \"PropertyType.CreatedTime\": \"创建时间\",\n  \"PropertyType.Date\": \"日期\",\n  \"PropertyType.Email\": \"Email\",\n  \"PropertyType.MultiPerson\": \"多人\",\n  \"PropertyType.MultiSelect\": \"多选\",\n  \"PropertyType.Number\": \"数字\",\n  \"PropertyType.Person\": \"个人\",\n  \"PropertyType.Phone\": \"电话号码\",\n  \"PropertyType.Select\": \"选取\",\n  \"PropertyType.Text\": \"文字框\",\n  \"PropertyType.Unknown\": \"未知\",\n  \"PropertyType.UpdatedBy\": \"最后更新者\",\n  \"PropertyType.UpdatedTime\": \"上次更新时间\",\n  \"PropertyType.Url\": \"URL\",\n  \"PropertyValueElement.empty\": \"空的\",\n  \"RegistrationLink.confirmRegenerateToken\": \"此动作将使先前分享的链接无效。确定要进行吗？\",\n  \"RegistrationLink.copiedLink\": \"已复制！\",\n  \"RegistrationLink.copyLink\": \"复制链接\",\n  \"RegistrationLink.description\": \"将此链接分享给他人以建立帐号：\",\n  \"RegistrationLink.regenerateToken\": \"重新生成令牌\",\n  \"RegistrationLink.tokenRegenerated\": \"已重新生成注册链接\",\n  \"ShareBoard.PublishDescription\": \"发布并与所有人分享 \\\"只读 \\\"链接。\",\n  \"ShareBoard.PublishTitle\": \"发布到网上\",\n  \"ShareBoard.ShareInternal\": \"内部分享\",\n  \"ShareBoard.ShareInternalDescription\": \"有权限的用户将能够使用这个链接。\",\n  \"ShareBoard.Title\": \"分享板块\",\n  \"ShareBoard.confirmRegenerateToken\": \"此动作将使先前分享的链接无效。确定要进行吗？\",\n  \"ShareBoard.copiedLink\": \"已复制！\",\n  \"ShareBoard.copyLink\": \"复制链接\",\n  \"ShareBoard.regenerate\": \"重新生成令牌\",\n  \"ShareBoard.searchPlaceholder\": \"搜索成员和频道\",\n  \"ShareBoard.teamPermissionsText\": \"在{teamName}团队的每个人\",\n  \"ShareBoard.tokenRegenrated\": \"已重新产生令牌\",\n  \"ShareBoard.userPermissionsRemoveMemberText\": \"移除成员\",\n  \"ShareBoard.userPermissionsYouText\": \"(你)\",\n  \"ShareTemplate.Title\": \"分享模板\",\n  \"ShareTemplate.searchPlaceholder\": \"搜索成员\",\n  \"Sidebar.about\": \"关于 Focalboard\",\n  \"Sidebar.add-board\": \"+ 新增版面\",\n  \"Sidebar.changePassword\": \"变更密码\",\n  \"Sidebar.delete-board\": \"删除版面\",\n  \"Sidebar.duplicate-board\": \"复制板块\",\n  \"Sidebar.export-archive\": \"导出档案\",\n  \"Sidebar.import\": \"导入\",\n  \"Sidebar.import-archive\": \"导入档案\",\n  \"Sidebar.invite-users\": \"邀请使用者\",\n  \"Sidebar.logout\": \"登出\",\n  \"Sidebar.new-category.badge\": \"新建\",\n  \"Sidebar.new-category.drag-boards-cta\": \"拖动板块到这里...\",\n  \"Sidebar.no-boards-in-category\": \"里面没有板块\",\n  \"Sidebar.product-tour\": \"产品导览\",\n  \"Sidebar.random-icons\": \"随机图标\",\n  \"Sidebar.set-language\": \"设定语言\",\n  \"Sidebar.set-theme\": \"设置主题\",\n  \"Sidebar.settings\": \"设定\",\n  \"Sidebar.template-from-board\": \"从板块新增一个模板\",\n  \"Sidebar.untitled-board\": \"(无标题版面)\",\n  \"Sidebar.untitled-view\": \"（未命名视图）\",\n  \"SidebarCategories.BlocksMenu.Move\": \"移动到...\",\n  \"SidebarCategories.CategoryMenu.CreateNew\": \"创建新类别\",\n  \"SidebarCategories.CategoryMenu.Delete\": \"删除类别\",\n  \"SidebarCategories.CategoryMenu.DeleteModal.Body\": \"在于<b>{categoryName}</b>的板块会被移回板块类别。这并不会移除任何板块。\",\n  \"SidebarCategories.CategoryMenu.DeleteModal.Title\": \"删除此类别？\",\n  \"SidebarCategories.CategoryMenu.Update\": \"重命名类别\",\n  \"SidebarTour.ManageCategories.Body\": \"新建并管理自定义的类别。类别是用户专属的，所以移动板块到你的类别不会影响到使用同个板块的其他成员。\",\n  \"SidebarTour.ManageCategories.Title\": \"管理类别\",\n  \"SidebarTour.SearchForBoards.Body\": \"打开类别切换器（Cmd/Ctrl+K）来快速查找并添加板块到你的侧边栏。\",\n  \"SidebarTour.SearchForBoards.Title\": \"搜索板块\",\n  \"SidebarTour.SidebarCategories.Body\": \"你所有的板块现会在侧边栏下被管理。无需在不同工作区中进行切换。基于你之前工作区的一次性自定义板块，将会作为v7.2版本更新自动创建。这个特性可以在设置里更改会或移除。\",\n  \"SidebarTour.SidebarCategories.Link\": \"了解更多\",\n  \"SidebarTour.SidebarCategories.Title\": \"侧边栏类别\",\n  \"SiteStats.total_boards\": \"所有板块\",\n  \"SiteStats.total_cards\": \"所有卡片\",\n  \"TableComponent.add-icon\": \"加入图标\",\n  \"TableComponent.name\": \"姓名\",\n  \"TableComponent.plus-new\": \"+ 新增\",\n  \"TableHeaderMenu.delete\": \"删除\",\n  \"TableHeaderMenu.duplicate\": \"制作副本\",\n  \"TableHeaderMenu.hide\": \"隐藏\",\n  \"TableHeaderMenu.insert-left\": \"在左侧插入\",\n  \"TableHeaderMenu.insert-right\": \"在右侧插入\",\n  \"TableHeaderMenu.sort-ascending\": \"升序排列\",\n  \"TableHeaderMenu.sort-descending\": \"降序排列\",\n  \"TableRow.DuplicateCard\": \"复制卡片\",\n  \"TableRow.MoreOption\": \"更多操作\",\n  \"TableRow.open\": \"开启\",\n  \"TopBar.give-feedback\": \"反馈问题\",\n  \"URLProperty.copiedLink\": \"已复制！\",\n  \"URLProperty.copy\": \"复制\",\n  \"URLProperty.edit\": \"编辑\",\n  \"UndoRedoHotKeys.canRedo\": \"撤回\",\n  \"UndoRedoHotKeys.canRedo-with-description\": \"撤回 {description}\",\n  \"UndoRedoHotKeys.canUndo\": \"撤销\",\n  \"UndoRedoHotKeys.canUndo-with-description\": \"撤销 {description}\",\n  \"UndoRedoHotKeys.cannotRedo\": \"已没有操作可撤回\",\n  \"UndoRedoHotKeys.cannotUndo\": \"已没有操作可撤销\",\n  \"ValueSelector.noOptions\": \"没有选项。现在添加一个!\",\n  \"ValueSelector.valueSelector\": \"值选择器\",\n  \"ValueSelectorLabel.openMenu\": \"打开菜单\",\n  \"VersionMessage.help\": \"了解查看新版本有什么新特性。\",\n  \"View.AddView\": \"添加视图\",\n  \"View.Board\": \"板块\",\n  \"View.DeleteView\": \"删除视图\",\n  \"View.DuplicateView\": \"复制视图\",\n  \"View.Gallery\": \"画廊\",\n  \"View.NewBoardTitle\": \"版面视图\",\n  \"View.NewCalendarTitle\": \"日历视图\",\n  \"View.NewGalleryTitle\": \"画廊视图\",\n  \"View.NewTableTitle\": \"图表视图\",\n  \"View.NewTemplateDefaultTitle\": \"未命名模板\",\n  \"View.NewTemplateTitle\": \"未命名\",\n  \"View.Table\": \"图表\",\n  \"ViewHeader.add-template\": \"+ 新模板\",\n  \"ViewHeader.delete-template\": \"删除\",\n  \"ViewHeader.display-by\": \"以{property}显示\",\n  \"ViewHeader.edit-template\": \"编辑\",\n  \"ViewHeader.empty-card\": \"空白卡片\",\n  \"ViewHeader.export-board-archive\": \"导出版面归档\",\n  \"ViewHeader.export-complete\": \"导出完成!\",\n  \"ViewHeader.export-csv\": \"导出为 CSV\",\n  \"ViewHeader.export-failed\": \"导出失败!\",\n  \"ViewHeader.filter\": \"筛选\",\n  \"ViewHeader.group-by\": \"以{property}分组\",\n  \"ViewHeader.new\": \"新\",\n  \"ViewHeader.properties\": \"属性\",\n  \"ViewHeader.properties-menu\": \"属性菜单\",\n  \"ViewHeader.search-text\": \"搜索卡片\",\n  \"ViewHeader.select-a-template\": \"选择范本\",\n  \"ViewHeader.set-default-template\": \"设为默认范本\",\n  \"ViewHeader.sort\": \"排序\",\n  \"ViewHeader.untitled\": \"无标题\",\n  \"ViewHeader.view-header-menu\": \"查看标题菜单\",\n  \"ViewHeader.view-menu\": \"查看菜单\",\n  \"ViewLimitDialog.Heading\": \"已达到板块观看的限制\",\n  \"ViewLimitDialog.PrimaryButton.Title.Admin\": \"升级\",\n  \"ViewLimitDialog.PrimaryButton.Title.RegularUser\": \"通知管理员\",\n  \"ViewLimitDialog.Subtext.Admin\": \"升级到专业版或企业版。\",\n  \"ViewLimitDialog.Subtext.Admin.PricingPageLink\": \"了解更多关于我们的付费套装。\",\n  \"ViewLimitDialog.Subtext.RegularUser\": \"通知你的管理员来升级到专业版和企业版。\",\n  \"ViewLimitDialog.UpgradeImg.AltText\": \"升级图片\",\n  \"ViewLimitDialog.notifyAdmin.Success\": \"已通知管理员\",\n  \"ViewTitle.hide-description\": \"隐藏描述\",\n  \"ViewTitle.pick-icon\": \"挑选图标\",\n  \"ViewTitle.random-icon\": \"随机\",\n  \"ViewTitle.remove-icon\": \"移除图标\",\n  \"ViewTitle.show-description\": \"显示描述\",\n  \"ViewTitle.untitled-board\": \"无标题板块\",\n  \"WelcomePage.Description\": \"板块是一个项目管理工具，使用熟悉的看板视图，帮助你的团队策划、组织、跟踪和管理跨团队的工作。\",\n  \"WelcomePage.Explore.Button\": \"探索\",\n  \"WelcomePage.Heading\": \"欢迎来到板块\",\n  \"WelcomePage.NoThanks.Text\": \"不了，请让我自己设置\",\n  \"WelcomePage.StartUsingIt.Text\": \"开始使用\",\n  \"Workspace.editing-board-template\": \"您正在编辑版面模板。\",\n  \"badge.guest\": \"访客\",\n  \"boardSelector.confirm-link-board\": \"连接板块到频道\",\n  \"boardSelector.confirm-link-board-button\": \"是的，连接板块\",\n  \"boardSelector.confirm-link-board-subtext\": \"当你连接“{boardName}”到频道时，所有频道的成员（现有的或新的）都可以编辑。这并不包括访客。你随时都可以取消板块与频道的连接。\",\n  \"boardSelector.confirm-link-board-subtext-with-other-channel\": \"当你连接\\\"{boardName}\\\"到频道时，此频道的所有成员（现有的和新的）将可以进行编辑，这并不包括访客。{lineBreak} 此板块目前与另一个频道已有连接，如果在此进行新的连接，那么将会自动取消之前连接的频道。\",\n  \"boardSelector.create-a-board\": \"创建板块\",\n  \"boardSelector.link\": \"连接\",\n  \"boardSelector.search-for-boards\": \"搜索板块\",\n  \"boardSelector.title\": \"连接板块\",\n  \"boardSelector.unlink\": \"取消连接\",\n  \"calendar.month\": \"月\",\n  \"calendar.today\": \"今天\",\n  \"calendar.week\": \"周\",\n  \"centerPanel.undefined\": \"不{propertyName}\",\n  \"centerPanel.unknown-user\": \"陌生用户\",\n  \"cloudMessage.learn-more\": \"了解更多\",\n  \"createImageBlock.failed\": \"图片上传失败，超过大小限制。\",\n  \"default-properties.badges\": \"评论和描述\",\n  \"default-properties.title\": \"标题\",\n  \"error.back-to-home\": \"回到主页\",\n  \"error.back-to-team\": \"回到团队\",\n  \"error.board-not-found\": \"未找到板块。\",\n  \"error.go-login\": \"登陆\",\n  \"error.invalid-read-only-board\": \"你没有权限访问此板块，请登陆后再进行访问板块。\",\n  \"error.not-logged-in\": \"尚未登陆或会话超时，请登陆后再进行访问板块。\",\n  \"error.page.title\": \"抱歉，出现了一些错误\",\n  \"error.team-undefined\": \"不是有效的团队。\",\n  \"error.unknown\": \"发生了一些错误。\",\n  \"generic.previous\": \"上一个\",\n  \"guest-no-board.subtitle\": \"你尚未有权限访问此团队的任何一个板块，请等待某人把你添加到某个板块。\",\n  \"guest-no-board.title\": \"尚未有板块\",\n  \"imagePaste.upload-failed\": \"图片上传失败，超过大小限制。\",\n  \"limitedCard.title\": \"卡片已隐藏\",\n  \"login.log-in-button\": \"登录\",\n  \"login.log-in-title\": \"登录\",\n  \"login.register-button\": \"或创建一个帐户（如果您没有帐户）\",\n  \"new_channel_modal.create_board.empty_board_description\": \"创建一个空白的板块\",\n  \"new_channel_modal.create_board.empty_board_title\": \"空白板块\",\n  \"new_channel_modal.create_board.select_template_placeholder\": \"选择模板\",\n  \"new_channel_modal.create_board.title\": \"为此频道创建一个新板块\",\n  \"notification-box-card-limit-reached.close-tooltip\": \"小睡十天\",\n  \"notification-box-card-limit-reached.contact-link\": \"通知你的管理员\",\n  \"notification-box-card-limit-reached.link\": \"升级到付费版\",\n  \"notification-box-card-limit-reached.title\": \"板块上的{cards}卡片已隐藏\",\n  \"notification-box-cards-hidden.title\": \"此行动已隐藏其他卡片\",\n  \"notification-box.card-limit-reached.not-admin.text\": \"要访问存档的卡片，你需要通过 {contactLink} 来升级到付费版。\",\n  \"notification-box.card-limit-reached.text\": \"已达到卡片上限，如需查看旧卡片请点{link}\",\n  \"person.add-user-to-board\": \"将 {username} 加入板块\",\n  \"person.add-user-to-board-confirm-button\": \"添加到板块\",\n  \"person.add-user-to-board-permissions\": \"权限\",\n  \"person.add-user-to-board-question\": \"你想将 {username} 加入板块吗？\",\n  \"person.add-user-to-board-warning\": \"{username} 不是此板块的成员，因此不会受到任何关于此板块的通知。\",\n  \"register.login-button\": \"或登录（如果您已拥有帐户）\",\n  \"register.signup-title\": \"注册您的帐户\",\n  \"rhs-board-non-admin-msg\": \"你不是板块的管理员\",\n  \"rhs-boards.add\": \"添加\",\n  \"rhs-boards.dm\": \"私信\",\n  \"rhs-boards.gm\": \"群聊\",\n  \"rhs-boards.header.dm\": \"此私信\",\n  \"rhs-boards.header.gm\": \"此群聊信息\",\n  \"rhs-boards.last-update-at\": \"最后更新日为：{datetime}\",\n  \"rhs-boards.link-boards-to-channel\": \"把板块连接到 {channelName}\",\n  \"rhs-boards.linked-boards\": \"连接板块\",\n  \"rhs-boards.no-boards-linked-to-channel\": \"尚未有板块与{channelName} 连接\",\n  \"rhs-boards.no-boards-linked-to-channel-description\": \"板块是一个能帮助我们定义，组织，追踪和管理团队工作的一个专业管理工具，可通过使用熟悉的看板视图。\",\n  \"rhs-boards.unlink-board\": \"取消连接板块\",\n  \"rhs-boards.unlink-board1\": \"取消连接板块\",\n  \"rhs-channel-boards-header.title\": \"板块\",\n  \"share-board.publish\": \"发布\",\n  \"share-board.share\": \"分享\",\n  \"shareBoard.channels-select-group\": \"频道\",\n  \"shareBoard.confirm-change-team-role.body\": \"此板块低于“{role}”的所有人都将<b>于现在被提升到{role}</b>。你确认要更改此板块的最低职责？\",\n  \"shareBoard.confirm-change-team-role.confirmBtnText\": \"更改板块的最低职责\",\n  \"shareBoard.confirm-change-team-role.title\": \"更改板块的最低职责\",\n  \"shareBoard.confirm-link-channel\": \"连接板块到频道\",\n  \"shareBoard.confirm-link-channel-button\": \"连接频道\",\n  \"shareBoard.confirm-link-channel-button-with-other-channel\": \"再此取消或进行连接\",\n  \"shareBoard.confirm-link-channel-subtext\": \"当你把频道连接到一个板块，此频道里的所有成员（现有的或新的）都可以进行编辑，这并不包括访客。\",\n  \"shareBoard.confirm-link-channel-subtext-with-other-channel\": \"当你把频道连接到一个板块，此频道里的所有成员（现有的或新的）都可以进行编辑，这并不包括访客。{lineBreak}此板块目前与另一个频道已有连接，如果在此进行新的连接，那么将会自动取消之前连接的频道。\",\n  \"shareBoard.confirm-unlink.body\": \"当你取消频道与板块的连接，频道的所有成员（现有的或新的）将会失去板块的访问权限，除非单独给予许可。\",\n  \"shareBoard.confirm-unlink.confirmBtnText\": \"取消连接频道\",\n  \"shareBoard.confirm-unlink.title\": \"取消连接此板块的频道\",\n  \"shareBoard.lastAdmin\": \"板块至少得有一位管理员\",\n  \"shareBoard.members-select-group\": \"成员\",\n  \"shareBoard.unknown-channel-display-name\": \"未知频道\",\n  \"tutorial_tip.finish_tour\": \"完成\",\n  \"tutorial_tip.got_it\": \"了解\",\n  \"tutorial_tip.ok\": \"下一个\",\n  \"tutorial_tip.out\": \"选择不使用这些提示。\",\n  \"tutorial_tip.seen\": \"之前有见到过吗？\"\n}\n"
  },
  {
    "path": "webapp/i18n/zh_Hant.json",
    "content": "{\n  \"AppBar.Tooltip\": \"切換看板\",\n  \"Attachment.Attachment-title\": \"附件\",\n  \"AttachmentBlock.DeleteAction\": \"刪除\",\n  \"AttachmentBlock.addElement\": \"添加 {type}\",\n  \"AttachmentBlock.delete\": \"已刪除附件\",\n  \"AttachmentBlock.failed\": \"無法上傳文件。 附件大小已達到限制。\",\n  \"AttachmentBlock.upload\": \"附件正在上傳。\",\n  \"AttachmentBlock.uploadSuccess\": \"附件已上傳\",\n  \"AttachmentElement.delete-confirmation-dialog-button-text\": \"刪除\",\n  \"AttachmentElement.download\": \"下載\",\n  \"AttachmentElement.upload-percentage\": \"正在上傳...({uploadPercent}%)\",\n  \"BoardComponent.add-a-group\": \"+ 新增群組\",\n  \"BoardComponent.delete\": \"刪除\",\n  \"BoardComponent.hidden-columns\": \"隱藏列\",\n  \"BoardComponent.hide\": \"隱藏\",\n  \"BoardComponent.new\": \"+ 新增\",\n  \"BoardComponent.no-property\": \"無 {property}\",\n  \"BoardComponent.no-property-title\": \"{property} 屬性為空的項目將轉到此處。該列無法刪除。\",\n  \"BoardComponent.show\": \"顯示\",\n  \"BoardMember.schemeAdmin\": \"管理員\",\n  \"BoardMember.schemeCommenter\": \"評論者\",\n  \"BoardMember.schemeEditor\": \"編輯者\",\n  \"BoardMember.schemeNone\": \"無\",\n  \"BoardMember.schemeViewer\": \"閱覽者\",\n  \"BoardMember.unlinkChannel\": \"取消連結\",\n  \"BoardPage.newVersion\": \"新版本的版面已可用，點此處重新載入。\",\n  \"BoardPage.syncFailed\": \"版面可能已被刪除或已被撤銷存取。\",\n  \"BoardTemplateSelector.add-template\": \"創建新模板\",\n  \"BoardTemplateSelector.create-empty-board\": \"建立空看板\",\n  \"BoardTemplateSelector.delete-template\": \"刪除\",\n  \"BoardTemplateSelector.description\": \"使用下方定義的模板或從頭開始，從側邊欄新增一個區塊。\",\n  \"BoardTemplateSelector.edit-template\": \"編輯\",\n  \"BoardTemplateSelector.plugin.no-content-description\": \"在側邊欄新增一個板塊，可以使用下方定義的任意範本或從新開始。\",\n  \"BoardTemplateSelector.plugin.no-content-title\": \"建立看板\",\n  \"BoardTemplateSelector.title\": \"建立看板\",\n  \"BoardTemplateSelector.use-this-template\": \"使用此範本\",\n  \"BoardsSwitcher.Title\": \"尋找看板\",\n  \"BoardsUnfurl.Limited\": \"由於該卡片被封存，其他細節都被影藏\",\n  \"BoardsUnfurl.Remainder\": \"+{remainder} 更多\",\n  \"BoardsUnfurl.Updated\": \"更新時間 {time}\",\n  \"Calculations.Options.average.displayName\": \"平均\",\n  \"Calculations.Options.average.label\": \"平均\",\n  \"Calculations.Options.count.displayName\": \"數量\",\n  \"Calculations.Options.count.label\": \"數量\",\n  \"Calculations.Options.countChecked.displayName\": \"已選取\",\n  \"Calculations.Options.countChecked.label\": \"選取數量\",\n  \"Calculations.Options.countUnchecked.displayName\": \"未選取\",\n  \"Calculations.Options.countUnchecked.label\": \"未選取數量\",\n  \"Calculations.Options.countUniqueValue.displayName\": \"唯一值\",\n  \"Calculations.Options.countUniqueValue.label\": \"唯一值數量\",\n  \"Calculations.Options.countValue.displayName\": \"值\",\n  \"Calculations.Options.countValue.label\": \"總計\",\n  \"Calculations.Options.dateRange.displayName\": \"區間\",\n  \"Calculations.Options.dateRange.label\": \"區間\",\n  \"Calculations.Options.earliest.displayName\": \"最前的\",\n  \"Calculations.Options.earliest.label\": \"最前的\",\n  \"Calculations.Options.latest.displayName\": \"最後的\",\n  \"Calculations.Options.latest.label\": \"最後的\",\n  \"Calculations.Options.max.displayName\": \"最大的\",\n  \"Calculations.Options.max.label\": \"最大的\",\n  \"Calculations.Options.median.displayName\": \"中位數\",\n  \"Calculations.Options.median.label\": \"中位數\",\n  \"Calculations.Options.min.displayName\": \"最小的\",\n  \"Calculations.Options.min.label\": \"最小的\",\n  \"Calculations.Options.none.displayName\": \"計算\",\n  \"Calculations.Options.none.label\": \"無\",\n  \"Calculations.Options.percentChecked.displayName\": \"已選取\",\n  \"Calculations.Options.percentChecked.label\": \"已選取百分比\",\n  \"Calculations.Options.percentUnchecked.displayName\": \"未選取\",\n  \"Calculations.Options.percentUnchecked.label\": \"未選取百分比\",\n  \"Calculations.Options.range.displayName\": \"範圍\",\n  \"Calculations.Options.range.label\": \"範圍\",\n  \"Calculations.Options.sum.displayName\": \"總和\",\n  \"Calculations.Options.sum.label\": \"總和\",\n  \"CalendarCard.untitled\": \"無標題\",\n  \"CardActionsMenu.copiedLink\": \"複製!\",\n  \"CardActionsMenu.copyLink\": \"複製連結\",\n  \"CardActionsMenu.delete\": \"刪除\",\n  \"CardActionsMenu.duplicate\": \"重複\",\n  \"CardBadges.title-checkboxes\": \"選取框\",\n  \"CardBadges.title-comments\": \"評論\",\n  \"CardBadges.title-description\": \"此卡片有說明\",\n  \"CardDetail.Attach\": \"附加\",\n  \"CardDetail.Follow\": \"追蹤\",\n  \"CardDetail.Following\": \"追蹤中\",\n  \"CardDetail.add-content\": \"新增內容\",\n  \"CardDetail.add-icon\": \"新增圖示\",\n  \"CardDetail.add-property\": \"+ 新增屬性\",\n  \"CardDetail.addCardText\": \"新增卡片文本\",\n  \"CardDetail.limited-body\": \"升級到專業版或是企業版\",\n  \"CardDetail.limited-button\": \"升級\",\n  \"CardDetail.limited-title\": \"此卡片被影藏\",\n  \"CardDetail.moveContent\": \"移動卡片內容\",\n  \"CardDetail.new-comment-placeholder\": \"新增評論…\",\n  \"CardDetailProperty.confirm-delete-heading\": \"確認刪除屬性\",\n  \"CardDetailProperty.confirm-delete-subtext\": \"您確定要刪除屬性“{propertyName}”嗎？ 刪除它會從該板的所有卡中刪除該屬性。\",\n  \"CardDetailProperty.confirm-property-name-change-subtext\": \"您確定要更改屬性“{propertyName}”{customText} 嗎？ 這將影響此板中 {numOfCards} 卡的值，並可能導致數據丟失。\",\n  \"CardDetailProperty.confirm-property-type-change\": \"確認屬性變更\",\n  \"CardDetailProperty.delete-action-button\": \"刪除\",\n  \"CardDetailProperty.property-change-action-button\": \"變更屬性\",\n  \"CardDetailProperty.property-changed\": \"已成功變更屬性！\",\n  \"CardDetailProperty.property-deleted\": \"成功刪除 {propertyName}！\",\n  \"CardDetailProperty.property-name-change-subtext\": \"類型從 \\\"{oldPropType}\\\" 變更為 \\\"{newPropType}\\\"\",\n  \"CardDetial.limited-link\": \"了解更多我們的計畫.\",\n  \"CardDialog.delete-confirmation-dialog-attachment\": \"確認刪除附件\",\n  \"CardDialog.delete-confirmation-dialog-button-text\": \"刪除\",\n  \"CardDialog.delete-confirmation-dialog-heading\": \"確認刪除卡片\",\n  \"CardDialog.editing-template\": \"您正在編輯範本。\",\n  \"CardDialog.nocard\": \"卡片不存在或者無法被存取。\",\n  \"Categories.CreateCategoryDialog.CancelText\": \"取消\",\n  \"Categories.CreateCategoryDialog.CreateText\": \"新增\",\n  \"Categories.CreateCategoryDialog.Placeholder\": \"命名你的類別\",\n  \"Categories.CreateCategoryDialog.UpdateText\": \"更新\",\n  \"CenterPanel.Login\": \"登入\",\n  \"CenterPanel.Share\": \"分享\",\n  \"ChannelIntro.CreateBoard\": \"建立看板\",\n  \"ColorOption.selectColor\": \"{color} 選擇顏色\",\n  \"Comment.delete\": \"刪除\",\n  \"CommentsList.send\": \"發送\",\n  \"ConfirmPerson.empty\": \"空白\",\n  \"ConfirmPerson.search\": \"查詢...\",\n  \"ConfirmationDialog.cancel-action\": \"取消\",\n  \"ConfirmationDialog.confirm-action\": \"確認\",\n  \"ContentBlock.Delete\": \"刪除\",\n  \"ContentBlock.DeleteAction\": \"刪除\",\n  \"ContentBlock.addElement\": \"新增 {type}\",\n  \"ContentBlock.checkbox\": \"復選框\",\n  \"ContentBlock.divider\": \"分隔線\",\n  \"ContentBlock.editCardCheckbox\": \"切換復選框\",\n  \"ContentBlock.editCardCheckboxText\": \"編輯卡片文字\",\n  \"ContentBlock.editCardText\": \"編輯卡片文字\",\n  \"ContentBlock.editText\": \"編輯文字...\",\n  \"ContentBlock.image\": \"圖片\",\n  \"ContentBlock.insertAbove\": \"在上方插入\",\n  \"ContentBlock.moveBlock\": \"移動卡片內容\",\n  \"ContentBlock.moveDown\": \"下移\",\n  \"ContentBlock.moveUp\": \"上移\",\n  \"ContentBlock.text\": \"文字\",\n  \"DateRange.clear\": \"清除\",\n  \"DateRange.empty\": \"空白\",\n  \"DateRange.endDate\": \"結束日期\",\n  \"DateRange.today\": \"今日\",\n  \"DeleteBoardDialog.confirm-cancel\": \"取消\",\n  \"DeleteBoardDialog.confirm-delete\": \"刪除\",\n  \"DeleteBoardDialog.confirm-info\": \"您確定要刪除圖板“{boardTitle}”嗎？ 刪除它會刪除棋盤中的所有卡片。\",\n  \"DeleteBoardDialog.confirm-info-template\": \"你確定要刪除此板塊名稱{boardTitle}範例?\",\n  \"DeleteBoardDialog.confirm-tite\": \"確認刪除看板\",\n  \"DeleteBoardDialog.confirm-tite-template\": \"確認刪除看板範本\",\n  \"Dialog.closeDialog\": \"關閉對話框\",\n  \"EditableDayPicker.today\": \"今天\",\n  \"Error.mobileweb\": \"手機板目前處於測試階段,不會呈現所有功能.\",\n  \"Error.websocket-closed\": \"Websocket 連線中斷,如果此問題持續發生，請檢查網路。\",\n  \"Filter.contains\": \"包含\",\n  \"Filter.ends-with\": \"結尾是\",\n  \"Filter.includes\": \"含有\",\n  \"Filter.is\": \"是\",\n  \"Filter.is-empty\": \"為空\",\n  \"Filter.is-not-empty\": \"不為空\",\n  \"Filter.is-not-set\": \"尚未設定\",\n  \"Filter.is-set\": \"已設定\",\n  \"Filter.not-contains\": \"不包含\",\n  \"Filter.not-ends-with\": \"不以結束\",\n  \"Filter.not-includes\": \"不包含\",\n  \"Filter.not-starts-with\": \"不以開始\",\n  \"Filter.starts-with\": \"起始於\",\n  \"FilterByText.placeholder\": \"過濾文字\",\n  \"FilterComponent.add-filter\": \"+ 增加過濾條件\",\n  \"FilterComponent.delete\": \"刪除\",\n  \"FilterValue.empty\": \"(空白)\",\n  \"FindBoardsDialog.IntroText\": \"查詢看板\",\n  \"FindBoardsDialog.NoResultsFor\": \"「{searchQuery}」搜尋未果\",\n  \"FindBoardsDialog.NoResultsSubtext\": \"檢查錯字或嘗試其他搜尋.\",\n  \"FindBoardsDialog.SubTitle\": \"輸入已找到面板.使用 <b>UP/DOWN</b>來瀏覽.<b>ENTER</b>來搜尋, <b>ESC</b> 來取消\",\n  \"FindBoardsDialog.Title\": \"尋找看板\",\n  \"GroupBy.hideEmptyGroups\": \"隱藏 {count}個空群組\",\n  \"GroupBy.showHiddenGroups\": \"顯示{count}個被隱藏的空群組\",\n  \"GroupBy.ungroup\": \"未分组\",\n  \"HideBoard.MenuOption\": \"隱藏面板\",\n  \"KanbanCard.untitled\": \"無標題\",\n  \"MentionSuggestion.is-not-board-member\": \"(非面板管理者)\",\n  \"Mutator.new-board-from-template\": \"新的面板模組\",\n  \"Mutator.new-card-from-template\": \"使用範本新增卡片\",\n  \"Mutator.new-template-from-card\": \"從卡片新增範本\",\n  \"OnboardingTour.AddComments.Body\": \"你可以對問題進行評論,甚至標記提到你的Mattermost夥伴,引起他們的注意。\",\n  \"OnboardingTour.AddComments.Title\": \"新增評論\",\n  \"OnboardingTour.AddDescription.Body\": \"在卡片上新增描述讓其他成員知道此卡片內容.\",\n  \"OnboardingTour.AddDescription.Title\": \"新增敘述\",\n  \"OnboardingTour.AddProperties.Body\": \"為卡片新增各式屬性使其更加強大\",\n  \"OnboardingTour.AddProperties.Title\": \"新增屬性\",\n  \"OnboardingTour.AddView.Body\": \"轉到此處創建一個新視圖以使用不同的佈局組織您的看板。\",\n  \"OnboardingTour.AddView.Title\": \"新增視圖\",\n  \"OnboardingTour.CopyLink.Body\": \"您可以通過複製鏈接並將其粘貼到頻道、直接消息或群組消息中來與隊友分享您的名片。\",\n  \"OnboardingTour.CopyLink.Title\": \"複製連結\",\n  \"OnboardingTour.OpenACard.Body\": \"打開卡片查看看板可以幫助你組織工作的優秀方法。\",\n  \"OnboardingTour.OpenACard.Title\": \"瀏覽卡片\",\n  \"OnboardingTour.ShareBoard.Body\": \"您可以在內部、團隊內部分享看板，或公開發布讓組織外部查看。\",\n  \"OnboardingTour.ShareBoard.Title\": \"分享看板\",\n  \"PersonProperty.board-members\": \"看板成員\",\n  \"PersonProperty.me\": \"我\",\n  \"PersonProperty.non-board-members\": \"不是看板成員\",\n  \"PropertyMenu.Delete\": \"刪除\",\n  \"PropertyMenu.changeType\": \"修改屬性類型\",\n  \"PropertyMenu.selectType\": \"選擇屬性類型\",\n  \"PropertyMenu.typeTitle\": \"類型\",\n  \"PropertyType.Checkbox\": \"勾選方塊\",\n  \"PropertyType.CreatedBy\": \"建立者\",\n  \"PropertyType.CreatedTime\": \"建立時間\",\n  \"PropertyType.Date\": \"日期\",\n  \"PropertyType.Email\": \"Email\",\n  \"PropertyType.MultiPerson\": \"多人\",\n  \"PropertyType.MultiSelect\": \"多選\",\n  \"PropertyType.Number\": \"數字\",\n  \"PropertyType.Person\": \"個人\",\n  \"PropertyType.Phone\": \"電話號碼\",\n  \"PropertyType.Select\": \"選取\",\n  \"PropertyType.Text\": \"文字框\",\n  \"PropertyType.Unknown\": \"未知\",\n  \"PropertyType.UpdatedBy\": \"最後更新者\",\n  \"PropertyType.UpdatedTime\": \"最後更新時間\",\n  \"PropertyType.Url\": \"網址\",\n  \"PropertyValueElement.empty\": \"空白\",\n  \"RegistrationLink.confirmRegenerateToken\": \"此動作將使先前分享的連結無效。確定要進行嗎？\",\n  \"RegistrationLink.copiedLink\": \"已複製！\",\n  \"RegistrationLink.copyLink\": \"複製連結\",\n  \"RegistrationLink.description\": \"將此連結分享給他人以建立帳號：\",\n  \"RegistrationLink.regenerateToken\": \"重新產生 token\",\n  \"RegistrationLink.tokenRegenerated\": \"已重新產生註冊鏈結\",\n  \"ShareBoard.PublishDescription\": \"發布只能讀取的連結。\",\n  \"ShareBoard.PublishTitle\": \"發布至網路\",\n  \"ShareBoard.ShareInternal\": \"內部分享\",\n  \"ShareBoard.ShareInternalDescription\": \"擁有權限的使用者才能使用此連結。\",\n  \"ShareBoard.Title\": \"分享看板\",\n  \"ShareBoard.confirmRegenerateToken\": \"此動作將使先前分享的鏈結無效。確定要進行嗎？\",\n  \"ShareBoard.copiedLink\": \"已複製！\",\n  \"ShareBoard.copyLink\": \"複製連結\",\n  \"ShareBoard.regenerate\": \"重新產生權杖\",\n  \"ShareBoard.searchPlaceholder\": \"查詢人和頻道\",\n  \"ShareBoard.teamPermissionsText\": \"在{teamName}的所有人\",\n  \"ShareBoard.tokenRegenrated\": \"已重新產生權杖\",\n  \"ShareBoard.userPermissionsRemoveMemberText\": \"移除成員\",\n  \"ShareBoard.userPermissionsYouText\": \"（你）\",\n  \"ShareTemplate.Title\": \"分享範本\",\n  \"ShareTemplate.searchPlaceholder\": \"查詢人\",\n  \"Sidebar.about\": \"關於 Focalboard\",\n  \"Sidebar.add-board\": \"+ 新增看板\",\n  \"Sidebar.changePassword\": \"變更密碼\",\n  \"Sidebar.delete-board\": \"刪除版面\",\n  \"Sidebar.duplicate-board\": \"複製看板\",\n  \"Sidebar.export-archive\": \"匯出打包檔\",\n  \"Sidebar.import\": \"匯入\",\n  \"Sidebar.import-archive\": \"匯入打包檔\",\n  \"Sidebar.invite-users\": \"邀請使用者\",\n  \"Sidebar.logout\": \"登出\",\n  \"Sidebar.new-category.badge\": \"新的\",\n  \"Sidebar.new-category.drag-boards-cta\": \"拖板到這裡...\",\n  \"Sidebar.no-boards-in-category\": \"沒有看板在裡面\",\n  \"Sidebar.product-tour\": \"產品導覽\",\n  \"Sidebar.random-icons\": \"隨機圖示\",\n  \"Sidebar.set-language\": \"設定語言\",\n  \"Sidebar.set-theme\": \"設定佈景主題\",\n  \"Sidebar.settings\": \"設定\",\n  \"Sidebar.template-from-board\": \"新的看板模板\",\n  \"Sidebar.untitled-board\": \"(無標題版面)\",\n  \"Sidebar.untitled-view\": \"（無題視圖）\",\n  \"SidebarCategories.BlocksMenu.Move\": \"移動至…\",\n  \"SidebarCategories.CategoryMenu.CreateNew\": \"新增分類\",\n  \"SidebarCategories.CategoryMenu.Delete\": \"刪除分類\",\n  \"SidebarCategories.CategoryMenu.DeleteModal.Body\": \"<b>{categoryName}</b> 中的看板將移回看板類別。 您不會從任何板上刪除。\",\n  \"SidebarCategories.CategoryMenu.DeleteModal.Title\": \"刪除這個分類?\",\n  \"SidebarCategories.CategoryMenu.Update\": \"重新命名分類\",\n  \"SidebarTour.ManageCategories.Body\": \"創建和管理自定義類別。 類別是特定於用戶的，因此將圖板移至您的類別不會影響使用同一圖板的其他成員。\",\n  \"SidebarTour.ManageCategories.Title\": \"管理分類\",\n  \"SidebarTour.SearchForBoards.Body\": \"打開板切換器 (Cmd/Ctrl + K) 以快速搜索板並將其添加到側邊欄。\",\n  \"SidebarTour.SearchForBoards.Title\": \"查詢看板\",\n  \"SidebarTour.SidebarCategories.Body\": \"您所有的看板現在都在您的新側邊欄下進行了組織。 不再在工作區之間切換。 作為 v7.2 升級的一部分，可能會自動為您創建基於您之前工作區的一次性自定義類別。 這些可以根據您的喜好刪除或編輯。\",\n  \"SidebarTour.SidebarCategories.Link\": \"更多\",\n  \"SidebarTour.SidebarCategories.Title\": \"邊欄類別\",\n  \"SiteStats.total_boards\": \"所有看板\",\n  \"SiteStats.total_cards\": \"總卡片數\",\n  \"TableComponent.add-icon\": \"加入圖示\",\n  \"TableComponent.name\": \"姓名\",\n  \"TableComponent.plus-new\": \"+ 新增\",\n  \"TableHeaderMenu.delete\": \"刪除\",\n  \"TableHeaderMenu.duplicate\": \"制作副本\",\n  \"TableHeaderMenu.hide\": \"隱藏\",\n  \"TableHeaderMenu.insert-left\": \"在左側插入\",\n  \"TableHeaderMenu.insert-right\": \"在右側插入\",\n  \"TableHeaderMenu.sort-ascending\": \"升序排列\",\n  \"TableHeaderMenu.sort-descending\": \"降序排列\",\n  \"TableRow.DuplicateCard\": \"複製卡片\",\n  \"TableRow.MoreOption\": \"更多操作\",\n  \"TableRow.open\": \"開啟\",\n  \"TopBar.give-feedback\": \"提供回饋\",\n  \"URLProperty.copiedLink\": \"已複製！\",\n  \"URLProperty.copy\": \"複製\",\n  \"URLProperty.edit\": \"編輯\",\n  \"UndoRedoHotKeys.canRedo\": \"重新執行\",\n  \"UndoRedoHotKeys.canRedo-with-description\": \"撤銷{description}\",\n  \"UndoRedoHotKeys.canUndo\": \"撤銷\",\n  \"UndoRedoHotKeys.canUndo-with-description\": \"重新執行 {description}\",\n  \"UndoRedoHotKeys.cannotRedo\": \"沒有可以重寫的\",\n  \"UndoRedoHotKeys.cannotUndo\": \"沒有可以取消的\",\n  \"ValueSelector.noOptions\": \"沒有選項.開始輸入第一個字!\",\n  \"ValueSelector.valueSelector\": \"值選擇器\",\n  \"ValueSelectorLabel.openMenu\": \"開啟選單\",\n  \"VersionMessage.help\": \"查看這個版本有什麼新功能.\",\n  \"View.AddView\": \"新增視圖\",\n  \"View.Board\": \"版面\",\n  \"View.DeleteView\": \"刪除視圖\",\n  \"View.DuplicateView\": \"建立視圖副本\",\n  \"View.Gallery\": \"畫廊\",\n  \"View.NewBoardTitle\": \"版面視圖\",\n  \"View.NewCalendarTitle\": \"行事曆檢視\",\n  \"View.NewGalleryTitle\": \"畫廊視圖\",\n  \"View.NewTableTitle\": \"圖表視圖\",\n  \"View.NewTemplateDefaultTitle\": \"沒有標題的模板\",\n  \"View.NewTemplateTitle\": \"沒有標題\",\n  \"View.Table\": \"圖表\",\n  \"ViewHeader.add-template\": \"新範本\",\n  \"ViewHeader.delete-template\": \"刪除\",\n  \"ViewHeader.display-by\": \"依據{property}顯示\",\n  \"ViewHeader.edit-template\": \"編輯\",\n  \"ViewHeader.empty-card\": \"清空卡片\",\n  \"ViewHeader.export-board-archive\": \"匯出版面打包檔\",\n  \"ViewHeader.export-complete\": \"匯出完成!\",\n  \"ViewHeader.export-csv\": \"匯出為 CSV\",\n  \"ViewHeader.export-failed\": \"匯出失敗!\",\n  \"ViewHeader.filter\": \"篩選\",\n  \"ViewHeader.group-by\": \"以 {property} 分組\",\n  \"ViewHeader.new\": \"新\",\n  \"ViewHeader.properties\": \"屬性\",\n  \"ViewHeader.properties-menu\": \"屬性菜單\",\n  \"ViewHeader.search-text\": \"搜尋文字\",\n  \"ViewHeader.select-a-template\": \"選擇範本\",\n  \"ViewHeader.set-default-template\": \"設為預設\",\n  \"ViewHeader.sort\": \"排序\",\n  \"ViewHeader.untitled\": \"無標題\",\n  \"ViewHeader.view-header-menu\": \"查看標題菜單\",\n  \"ViewHeader.view-menu\": \"查看菜單\",\n  \"ViewLimitDialog.Heading\": \"已達到每個看板觀看限制\",\n  \"ViewLimitDialog.PrimaryButton.Title.Admin\": \"升級\",\n  \"ViewLimitDialog.PrimaryButton.Title.RegularUser\": \"通知管理者\",\n  \"ViewLimitDialog.Subtext.Admin\": \"升級到專業版或企業版\",\n  \"ViewLimitDialog.Subtext.Admin.PricingPageLink\": \"了解更多我們的計畫。\",\n  \"ViewLimitDialog.Subtext.RegularUser\": \"通知你的管理員升級到專業版或是企業版\",\n  \"ViewLimitDialog.UpgradeImg.AltText\": \"升級圖片\",\n  \"ViewLimitDialog.notifyAdmin.Success\": \"已經通知管理者\",\n  \"ViewTitle.hide-description\": \"隱藏敘述\",\n  \"ViewTitle.pick-icon\": \"挑選圖示\",\n  \"ViewTitle.random-icon\": \"隨機\",\n  \"ViewTitle.remove-icon\": \"移除圖示\",\n  \"ViewTitle.show-description\": \"顯示敘述\",\n  \"ViewTitle.untitled-board\": \"未命名版面\",\n  \"WelcomePage.Description\": \"看板是一個專案管理工具，可以使用熟悉的圖表幫助我們定義、組織、追蹤和管理跨團隊工作。\",\n  \"WelcomePage.Explore.Button\": \"探索\",\n  \"WelcomePage.Heading\": \"歡迎來到看板\",\n  \"WelcomePage.NoThanks.Text\": \"不需要,自己想辦法\",\n  \"WelcomePage.StartUsingIt.Text\": \"開始使用\",\n  \"Workspace.editing-board-template\": \"您正在編輯版面範本。\",\n  \"badge.guest\": \"訪客\",\n  \"boardSelector.confirm-link-board\": \"連結看板與頻道\",\n  \"boardSelector.confirm-link-board-button\": \"是,連結看板\",\n  \"boardSelector.confirm-link-board-subtext\": \"當你將\\\"{boardName}\\\"連接到頻道時，該頻道的所有成員（現有的和新的）都可以編輯。並不包含訪客身分。你可以在任何時候從一個頻道上取消看板的連接。\",\n  \"boardSelector.confirm-link-board-subtext-with-other-channel\": \"當你將\\\"{boardName}\\\"連接到頻道時，該頻道的所有成員（現有的和新的）都可以編輯。並不包含訪客身分。{lineBreak} 看板目前正連接到另一個頻道。如果选择在這裡連接它，將取消另一個連接。\",\n  \"boardSelector.create-a-board\": \"建立看板\",\n  \"boardSelector.link\": \"連結\",\n  \"boardSelector.search-for-boards\": \"搜尋看板\",\n  \"boardSelector.title\": \"連結看板\",\n  \"boardSelector.unlink\": \"未連結\",\n  \"calendar.month\": \"月份\",\n  \"calendar.today\": \"今日\",\n  \"calendar.week\": \"週別\",\n  \"centerPanel.undefined\": \"沒有 {propertyName}\",\n  \"centerPanel.unknown-user\": \"未知使用者\",\n  \"cloudMessage.learn-more\": \"學習更多\",\n  \"createImageBlock.failed\": \"無法上傳檔案，檔案大小超過限制。\",\n  \"default-properties.badges\": \"評論和描述\",\n  \"default-properties.title\": \"標題\",\n  \"error.back-to-home\": \"返回首頁\",\n  \"error.back-to-team\": \"回到團隊\",\n  \"error.board-not-found\": \"沒有找到看板.\",\n  \"error.go-login\": \"登入\",\n  \"error.invalid-read-only-board\": \"沒有權限進入此看板.登入後才能訪問.\",\n  \"error.not-logged-in\": \"已被登出，請再次登入使用看板。\",\n  \"error.page.title\": \"很抱歉，發生了些錯誤\",\n  \"error.team-undefined\": \"不是有效的團隊。\",\n  \"error.unknown\": \"發生一個錯誤。\",\n  \"generic.previous\": \"上一篇\",\n  \"guest-no-board.subtitle\": \"你尚未有權限進入此看板，請等人把你加入任何看板。\",\n  \"guest-no-board.title\": \"尚未有看板\",\n  \"imagePaste.upload-failed\": \"有些檔案無法上傳.檔案大小達上限\",\n  \"limitedCard.title\": \"影藏卡片\",\n  \"login.log-in-button\": \"登錄\",\n  \"login.log-in-title\": \"登錄\",\n  \"login.register-button\": \"或創建一個帳戶（如果您沒有帳戶）\",\n  \"new_channel_modal.create_board.empty_board_description\": \"建立新的空白看板\",\n  \"new_channel_modal.create_board.empty_board_title\": \"空白看板\",\n  \"new_channel_modal.create_board.select_template_placeholder\": \"選擇一個範本\",\n  \"new_channel_modal.create_board.title\": \"在這個頻道新建一個看板\",\n  \"notification-box-card-limit-reached.close-tooltip\": \"小睡十天\",\n  \"notification-box-card-limit-reached.contact-link\": \"通知管理員\",\n  \"notification-box-card-limit-reached.link\": \"升級到付費版\",\n  \"notification-box-card-limit-reached.title\": \"將看板上{cards}卡片隱藏\",\n  \"notification-box-cards-hidden.title\": \"此行為隱藏了其他卡片\",\n  \"notification-box.card-limit-reached.not-admin.text\": \"要存取已封存的卡片，你可以點擊{contactLink}升級到付費版。\",\n  \"notification-box.card-limit-reached.text\": \"已達卡片上限，觀看舊卡片請點{link}\",\n  \"person.add-user-to-board\": \"將{username} 加入看板\",\n  \"person.add-user-to-board-confirm-button\": \"新增看板\",\n  \"person.add-user-to-board-permissions\": \"權限\",\n  \"person.add-user-to-board-question\": \"你想將{username} 加入到看板嗎?\",\n  \"person.add-user-to-board-warning\": \"{username}不是看板的成員,也不會收到任何有關的通知.\",\n  \"register.login-button\": \"或登錄（如果您已擁有帳戶）\",\n  \"register.signup-title\": \"註冊您的帳戶\",\n  \"rhs-board-non-admin-msg\": \"你不是看板的管理者\",\n  \"rhs-boards.add\": \"新增\",\n  \"rhs-boards.dm\": \"私人訊息\",\n  \"rhs-boards.gm\": \"群組訊息\",\n  \"rhs-boards.header.dm\": \"此私人訊息\",\n  \"rhs-boards.header.gm\": \"此群組訊息\",\n  \"rhs-boards.last-update-at\": \"最後更新日: {datetime}\",\n  \"rhs-boards.link-boards-to-channel\": \"將看板連接到{channelName}\",\n  \"rhs-boards.linked-boards\": \"連結看板\",\n  \"rhs-boards.no-boards-linked-to-channel\": \"還沒有看板與{channelName}連接\",\n  \"rhs-boards.no-boards-linked-to-channel-description\": \"看板是一個專案管理工具，可以使用熟悉的圖表幫助我們定義、組織、追蹤和管理跨團隊工作。\",\n  \"rhs-boards.unlink-board\": \"未連結看板\",\n  \"rhs-boards.unlink-board1\": \"未連結看板\",\n  \"rhs-channel-boards-header.title\": \"板塊\",\n  \"share-board.publish\": \"發布\",\n  \"share-board.share\": \"分享\",\n  \"shareBoard.channels-select-group\": \"頻道\",\n  \"shareBoard.confirm-change-team-role.body\": \"此看板上所有低於\\\"{role}\\\"的人都將<b>被提升到{role}</b>。你確定要改變這個看板最低角色?\",\n  \"shareBoard.confirm-change-team-role.confirmBtnText\": \"改變最小的看板規則\",\n  \"shareBoard.confirm-change-team-role.title\": \"改變最小的看板規則\",\n  \"shareBoard.confirm-link-channel\": \"連接看板到頻道\",\n  \"shareBoard.confirm-link-channel-button\": \"連接頻道\",\n  \"shareBoard.confirm-link-channel-button-with-other-channel\": \"解除連接或連接這\",\n  \"shareBoard.confirm-link-channel-subtext\": \"當你連接頻道到看板，該頻道所有成員(包含新的與現有的)都可以編輯，不包括訪客。\",\n  \"shareBoard.confirm-link-channel-subtext-with-other-channel\": \"當你將一個頻道連接到看板時，該頻道的所有成員（現有的和新的）都可以編輯。並不包含訪客身分{lineBreak}看板目前正連接到另一個頻道。如果选择在這裡連接它，將取消另一個連接。\",\n  \"shareBoard.confirm-unlink.body\": \"當你取消頻道與看板連接，所有頻道成員(現在和新的)都將無法失去查看權限，除非單獨獲得許可。\",\n  \"shareBoard.confirm-unlink.confirmBtnText\": \"解除連結頻道\",\n  \"shareBoard.confirm-unlink.title\": \"從看板上取消頻道連接\",\n  \"shareBoard.lastAdmin\": \"看板必須有一位管理者\",\n  \"shareBoard.members-select-group\": \"會員\",\n  \"shareBoard.unknown-channel-display-name\": \"未知管道\",\n  \"tutorial_tip.finish_tour\": \"完成\",\n  \"tutorial_tip.got_it\": \"了解\",\n  \"tutorial_tip.ok\": \"下一步\",\n  \"tutorial_tip.out\": \"不接受這個提示.\",\n  \"tutorial_tip.seen\": \"以前有見過嗎?\"\n}\n"
  },
  {
    "path": "webapp/package.json",
    "content": "{\n\t\"name\": \"focalboard\",\n\t\"version\": \"8.0.0\",\n\t\"private\": true,\n\t\"description\": \"\",\n\t\"scripts\": {\n\t\t\"pack\": \"cross-env NODE_ENV=production webpack --config webpack.prod.js\",\n\t\t\"packdev\": \"cross-env NODE_ENV=dev webpack --config webpack.dev.js\",\n\t\t\"watchdev\": \"cross-env NODE_ENV=dev webpack --watch --progress --config webpack.dev.js\",\n\t\t\"deveditor\": \"cross-env NODE_ENV=dev webpack server --config webpack.editor.js\",\n\t\t\"test\": \"jest\",\n\t\t\"updatesnapshot\": \"jest --updateSnapshot\",\n\t\t\"check\": \"eslint --ext .tsx,.ts . --quiet --cache && stylelint **/*.scss\",\n\t\t\"fix\": \"eslint --ext .tsx,.ts . --quiet --fix --cache && stylelint --fix **/*.scss\",\n\t\t\"fix:scss\": \"prettier --write './src/**/*.scss'\",\n\t\t\"i18n-extract\": \"formatjs extract \\\"src/**/*.{ts,tsx}\\\" --ignore \\\"**/*.d.ts\\\" \\\"../**/*.d.ts\\\" --out-file i18n/tmp.json && formatjs compile i18n/tmp.json --out-file i18n/en.json && npx rimraf i18n/tmp.json\",\n\t\t\"runserver-test\": \"cd cypress && \\\"../../bin/focalboard-server\\\"\",\n\t\t\"cypress:ci\": \"start-server-and-test runserver-test http://localhost:8088 cypress:run\",\n\t\t\"cypress:debug\": \"start-server-and-test runserver-test http://localhost:8088 cypress:open\",\n\t\t\"cypress:run\": \"cypress run\",\n\t\t\"cypress:run:chrome\": \"cypress run --browser chrome\",\n\t\t\"cypress:run:firefox\": \"cypress run --browser firefox\",\n\t\t\"cypress:run:edge\": \"cypress run --browser edge\",\n\t\t\"cypress:run:electron\": \"cypress run --browser electron\",\n\t\t\"cypress:open\": \"cypress open\"\n\t},\n\t\"dependencies\": {\n\t\t\"@draft-js-plugins/editor\": \"^4.1.2\",\n\t\t\"@draft-js-plugins/emoji\": \"^4.6.0\",\n\t\t\"@draft-js-plugins/mention\": \"^5.1.2\",\n\t\t\"@fullcalendar/core\": \"^5.10.1\",\n\t\t\"@fullcalendar/daygrid\": \"^5.10.1\",\n\t\t\"@fullcalendar/interaction\": \"^5.10.1\",\n\t\t\"@fullcalendar/react\": \"^5.10.1\",\n\t\t\"@mattermost/compass-icons\": \"^0.1.39\",\n\t\t\"@reduxjs/toolkit\": \"^1.8.0\",\n\t\t\"@tippyjs/react\": \"4.2.6\",\n\t\t\"classnames\": \"^2.5.1\",\n\t\t\"color\": \"^4.2.1\",\n\t\t\"draft-js\": \"^0.11.7\",\n\t\t\"emoji-mart\": \"^3.0.1\",\n\t\t\"fstream\": \"^1.0.12\",\n\t\t\"fullcalendar\": \"^5.10.2\",\n\t\t\"imagemin-gifsicle\": \"^7.0.0\",\n\t\t\"imagemin-mozjpeg\": \"^10.0.0\",\n\t\t\"imagemin-optipng\": \"^8.0.0\",\n\t\t\"imagemin-pngquant\": \"^9.0.2\",\n\t\t\"imagemin-svgo\": \"^10.0.1\",\n\t\t\"imagemin-webp\": \"^7.0.0\",\n\t\t\"lodash\": \"^4.17.21\",\n\t\t\"marked\": \"^4.0.12\",\n\t\t\"mini-create-react-context\": \"^0.4.1\",\n\t\t\"moment\": \"^2.29.1\",\n\t\t\"nanoevents\": \"^5.1.13\",\n\t\t\"react\": \"17.0.2\",\n\t\t\"react-beautiful-dnd\": \"^13.1.1\",\n\t\t\"react-day-picker\": \"^7.4.10\",\n\t\t\"react-dnd\": \"^14.0.2\",\n\t\t\"react-dnd-html5-backend\": \"^14.0.0\",\n\t\t\"react-dnd-scrolling\": \"^1.2.1\",\n\t\t\"react-dnd-touch-backend\": \"^14.0.0\",\n\t\t\"react-dom\": \"17.0.2\",\n\t\t\"react-hot-keys\": \"^2.7.1\",\n\t\t\"react-hotkeys-hook\": \"^3.4.4\",\n\t\t\"react-intl\": \"^5.20.0\",\n\t\t\"react-redux\": \"7.2.4\",\n\t\t\"react-router-dom\": \"^5.2.0\",\n\t\t\"react-select\": \"^5.2.2\",\n\t\t\"trim-newlines\": \"^4.0.2\"\n\t},\n\t\"jest\": {\n\t\t\"moduleNameMapper\": {\n\t\t\t\"\\\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$\": \"<rootDir>/__mocks__/fileMock.js\",\n\t\t\t\"\\\\.(scss|css)$\": \"<rootDir>/__mocks__/styleMock.js\"\n\t\t},\n\t\t\"globals\": {\n\t\t\t\"ts-jest\": {\n\t\t\t\t\"tsconfig\": \"./src/tsconfig.json\"\n\t\t\t}\n\t\t},\n\t\t\"transform\": {\n\t\t\t\"^.+\\\\.tsx?$\": \"@swc/jest\"\n\t\t},\n\t\t\"transformIgnorePatterns\": [\n\t\t\t\"/nanoevents/\"\n\t\t],\n\t\t\"maxWorkers\": \"50%\",\n\t\t\"testEnvironment\": \"jsdom\",\n\t\t\"collectCoverage\": true,\n\t\t\"collectCoverageFrom\": [\n\t\t\t\"src/**/*.{ts,tsx,js,jsx}\",\n\t\t\t\"!src/test/**\"\n\t\t]\n\t},\n\t\"devDependencies\": {\n\t\t\"@formatjs/cli\": \"^4.8.2\",\n\t\t\"@formatjs/ts-transformer\": \"^3.9.2\",\n\t\t\"@swc/jest\": \"^0.2.20\",\n\t\t\"@testing-library/cypress\": \"^8.0.2\",\n\t\t\"@testing-library/dom\": \"^8.11.4\",\n\t\t\"@testing-library/jest-dom\": \"^5.16.3\",\n\t\t\"@testing-library/react\": \"^11.2.5\",\n\t\t\"@testing-library/user-event\": \"^13.5.0\",\n\t\t\"@types/color\": \"^3.0.3\",\n\t\t\"@types/draft-js\": \"^0.11.9\",\n\t\t\"@types/emoji-mart\": \"^3.0.9\",\n\t\t\"@types/jest\": \"^27.4.1\",\n\t\t\"@types/marked\": \"^4.0.3\",\n\t\t\"@types/nanoevents\": \"^1.0.0\",\n\t\t\"@types/react\": \"^17.0.43\",\n\t\t\"@types/react-beautiful-dnd\": \"^13.1.2\",\n\t\t\"@types/react-dom\": \"^17.0.14\",\n\t\t\"@types/react-intl\": \"^3.0.0\",\n\t\t\"@types/react-redux\": \"^7.1.23\",\n\t\t\"@types/react-router-dom\": \"^5.3.3\",\n\t\t\"@types/react-select\": \"^5.0.0\",\n\t\t\"@types/redux-mock-store\": \"^1.0.3\",\n\t\t\"@typescript-eslint/eslint-plugin\": \"^5.16.0\",\n\t\t\"@typescript-eslint/parser\": \"^5.16.0\",\n\t\t\"copy-webpack-plugin\": \"^10.2.4\",\n\t\t\"cross-env\": \"^7.0.3\",\n\t\t\"css-loader\": \"^6.7.1\",\n\t\t\"cypress\": \"^9.5.2\",\n\t\t\"cypress-failed-log\": \"^2.9.5\",\n\t\t\"cypress-real-events\": \"^1.7.0\",\n\t\t\"eslint\": \"^8.11.0\",\n\t\t\"eslint-import-resolver-webpack\": \"0.13.2\",\n\t\t\"eslint-plugin-babel\": \"^5.3.1\",\n\t\t\"eslint-plugin-cypress\": \"2.12.1\",\n\t\t\"eslint-plugin-header\": \"3.1.1\",\n\t\t\"eslint-plugin-import\": \"2.25.4\",\n\t\t\"eslint-plugin-jquery\": \"1.5.1\",\n\t\t\"eslint-plugin-mattermost\": \"github:mattermost/eslint-plugin-mattermost#23abcf9988f7fa00d26929f11841aab7ccb16b2b\",\n\t\t\"eslint-plugin-no-only-tests\": \"2.6.0\",\n\t\t\"eslint-plugin-react\": \"7.29.4\",\n\t\t\"fetch-mock-jest\": \"^1.5.1\",\n\t\t\"file-loader\": \"^6.2.0\",\n\t\t\"html-webpack-plugin\": \"^5.5.0\",\n\t\t\"image-webpack-loader\": \"^8.1.0\",\n\t\t\"isomorphic-fetch\": \"^3.0.0\",\n\t\t\"jest\": \"27.5.1\",\n\t\t\"jest-mock\": \"27.5.1\",\n\t\t\"prettier\": \"^2.6.1\",\n\t\t\"redux-mock-store\": \"^1.5.4\",\n\t\t\"sass\": \"^1.49.9\",\n\t\t\"sass-loader\": \"^12.6.0\",\n\t\t\"start-server-and-test\": \"^1.14.0\",\n\t\t\"style-loader\": \"^3.3.1\",\n\t\t\"stylelint\": \"^14.6.1\",\n\t\t\"stylelint-config-sass-guidelines\": \"^9.0.1\",\n\t\t\"terser-webpack-plugin\": \"^5.3.1\",\n\t\t\"ts-jest\": \"^27.1.4\",\n\t\t\"ts-loader\": \"^9.2.8\",\n\t\t\"typescript\": \"^4.6.3\",\n\t\t\"webpack\": \"^5.70.0\",\n\t\t\"webpack-cli\": \"^4.9.2\",\n\t\t\"webpack-dev-server\": \"^4.11.1\",\n\t\t\"webpack-merge\": \"^5.8.0\"\n\t},\n\t\"optionalDependencies\": {\n\t\t\"cypress\": \"^9.5.2\"\n\t}\n}\n"
  },
  {
    "path": "webapp/src/app.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useEffect} from 'react'\nimport {IntlProvider} from 'react-intl'\nimport {DndProvider} from 'react-dnd'\nimport {HTML5Backend} from 'react-dnd-html5-backend'\nimport {TouchBackend} from 'react-dnd-touch-backend'\nimport {History} from 'history'\n\nimport TelemetryClient from './telemetry/telemetryClient'\n\nimport {getMessages} from './i18n'\nimport {FlashMessages} from './components/flashMessages'\nimport NewVersionBanner from './components/newVersionBanner'\nimport {Utils} from './utils'\nimport {fetchMe, getMe} from './store/users'\nimport {getLanguage, fetchLanguage} from './store/language'\nimport {useAppSelector, useAppDispatch} from './store/hooks'\nimport {fetchClientConfig} from './store/clientConfig'\nimport FocalboardRouter from './router'\n\nimport {IUser} from './user'\n\ntype Props = {\n    history?: History<unknown>\n}\n\nconst App = (props: Props): JSX.Element => {\n    const language = useAppSelector<string>(getLanguage)\n    const me = useAppSelector<IUser|null>(getMe)\n    const dispatch = useAppDispatch()\n\n    useEffect(() => {\n        dispatch(fetchLanguage())\n        dispatch(fetchMe())\n        dispatch(fetchClientConfig())\n    }, [])\n\n    useEffect(() => {\n        if (me) {\n            TelemetryClient.setUser(me)\n        }\n    }, [me])\n\n    return (\n        <IntlProvider\n            locale={language.split(/[_]/)[0]}\n            messages={getMessages(language)}\n        >\n            <DndProvider backend={Utils.isMobile() ? TouchBackend : HTML5Backend}>\n                <FlashMessages milliseconds={2000}/>\n                <div id='frame'>\n                    <div id='main'>\n                        <NewVersionBanner/>\n                        <FocalboardRouter history={props.history}/>\n                    </div>\n                </div>\n            </DndProvider>\n        </IntlProvider>\n    )\n}\n\nexport default React.memo(App)\n"
  },
  {
    "path": "webapp/src/archiver.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {IAppWindow} from './types'\nimport {Block} from './blocks/block'\nimport {Board} from './blocks/board'\nimport mutator from './mutator'\nimport {Utils} from './utils'\n\ndeclare let window: IAppWindow\n\nclass Archiver {\n    static async exportBoardArchive(board: Board): Promise<void> {\n        this.exportArchive(mutator.exportBoardArchive(board.id))\n    }\n\n    static async exportFullArchive(teamID: string): Promise<void> {\n        this.exportArchive(mutator.exportFullArchive(teamID))\n    }\n\n    private static exportArchive(prom: Promise<Response>): void {\n        // TODO:  don't download whole archive before presenting SaveAs dialog.\n        prom.then((response) => {\n            response.blob().\n                then((blob) => {\n                    const link = document.createElement('a')\n                    link.style.display = 'none'\n\n                    const date = new Date()\n                    const filename = `archive-${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}.boardarchive`\n\n                    const file = new Blob([blob], {type: 'application/octet-stream'})\n                    link.href = URL.createObjectURL(file)\n                    link.download = filename\n                    document.body.appendChild(link)\t\t// FireFox support\n\n                    link.click()\n\n                    // TODO: Review if this is needed in the future, this is to fix the problem with linux webview links\n                    if (window.openInNewBrowser) {\n                        window.openInNewBrowser(link.href)\n                    }\n\n                    // TODO: Remove or reuse link and revolkObjectURL to avoid memory leak\n                })\n        })\n    }\n\n    private static async importArchiveFromFile(file: File): Promise<void> {\n        const response = await mutator.importFullArchive(file)\n        if (response.status !== 200) {\n            Utils.log('ERROR importing archive: ' + response.text())\n        }\n    }\n\n    static isValidBlock(block: Block): boolean {\n        if (!block.id || !block.boardId) {\n            return false\n        }\n\n        return true\n    }\n\n    static importFullArchive(onComplete?: () => void): void {\n        const input = document.createElement('input')\n        input.type = 'file'\n        input.accept = '.boardarchive'\n        input.onchange = async () => {\n            const file = input.files && input.files[0]\n            if (file) {\n                await Archiver.importArchiveFromFile(file)\n            }\n\n            onComplete?.()\n        }\n\n        input.style.display = 'none'\n        document.body.appendChild(input)\n        input.click()\n\n        // TODO: Remove or reuse input\n    }\n}\n\nexport {Archiver}\n"
  },
  {
    "path": "webapp/src/blockIcons.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {randomEmojiList} from './emojiList'\n\nclass BlockIcons {\n    static readonly shared = new BlockIcons()\n\n    randomIcon(): string {\n        const index = Math.floor(Math.random() * randomEmojiList.length)\n        const icon = randomEmojiList[index]\n        return icon\n    }\n}\n\nexport {BlockIcons}\n"
  },
  {
    "path": "webapp/src/blocks/__snapshots__/block.test.ts.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`block tests correctly generate patches from two blocks should add fields on the new fields added and remove it in the undo 1`] = `\nArray [\n  Object {\n    \"deletedFields\": Array [],\n    \"updatedFields\": Object {\n      \"newField\": \"new field\",\n    },\n  },\n  Object {\n    \"deletedFields\": Array [\n      \"newField\",\n    ],\n    \"updatedFields\": Object {},\n  },\n]\n`;\n\nexports[`block tests correctly generate patches from two blocks should generate two empty patches for the same block 1`] = `\nArray [\n  Object {\n    \"deletedFields\": Array [],\n    \"updatedFields\": Object {},\n  },\n  Object {\n    \"deletedFields\": Array [],\n    \"updatedFields\": Object {},\n  },\n]\n`;\n\nexports[`block tests correctly generate patches from two blocks should remove field on the new block added and add it again in the undo 1`] = `\nArray [\n  Object {\n    \"deletedFields\": Array [\n      \"test\",\n    ],\n    \"updatedFields\": Object {},\n  },\n  Object {\n    \"deletedFields\": Array [],\n    \"updatedFields\": Object {\n      \"test\": \"test\",\n    },\n  },\n]\n`;\n\nexports[`block tests correctly generate patches from two blocks should update propertie on the main object and revert it back on the undo 1`] = `\nArray [\n  Object {\n    \"deletedFields\": Array [],\n    \"parentId\": \"new-parent-id\",\n    \"updatedFields\": Object {},\n  },\n  Object {\n    \"deletedFields\": Array [],\n    \"parentId\": \"old-parent-id\",\n    \"updatedFields\": Object {},\n  },\n]\n`;\n"
  },
  {
    "path": "webapp/src/blocks/__snapshots__/board.test.ts.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`board tests correctly generate patches for boards and blocks should add fields on update and remove it in the undo 1`] = `\nArray [\n  Object {\n    \"blockIDs\": Array [\n      \"test-old-block-id\",\n    ],\n    \"blockPatches\": Array [\n      Object {\n        \"deletedFields\": Array [],\n        \"updatedFields\": Object {\n          \"newField\": \"new field\",\n        },\n      },\n    ],\n    \"boardIDs\": Array [\n      \"test-board-id\",\n    ],\n    \"boardPatches\": Array [\n      Object {\n        \"deletedCardProperties\": Array [],\n        \"deletedProperties\": Array [],\n        \"updatedCardProperties\": Array [],\n        \"updatedProperties\": Object {},\n      },\n    ],\n  },\n  Object {\n    \"blockIDs\": Array [\n      \"test-old-block-id\",\n    ],\n    \"blockPatches\": Array [\n      Object {\n        \"deletedFields\": Array [\n          \"newField\",\n        ],\n        \"updatedFields\": Object {},\n      },\n    ],\n    \"boardIDs\": Array [\n      \"test-board-id\",\n    ],\n    \"boardPatches\": Array [\n      Object {\n        \"deletedCardProperties\": Array [],\n        \"deletedProperties\": Array [],\n        \"updatedCardProperties\": Array [],\n        \"updatedProperties\": Object {},\n      },\n    ],\n  },\n]\n`;\n\nexports[`board tests correctly generate patches for boards and blocks should generate two empty patches for the same board and block 1`] = `\nArray [\n  Object {\n    \"blockIDs\": Array [\n      \"test-card-id\",\n    ],\n    \"blockPatches\": Array [\n      Object {\n        \"deletedFields\": Array [],\n        \"updatedFields\": Object {},\n      },\n    ],\n    \"boardIDs\": Array [\n      \"test-board-id\",\n    ],\n    \"boardPatches\": Array [\n      Object {\n        \"deletedCardProperties\": Array [],\n        \"deletedProperties\": Array [],\n        \"updatedCardProperties\": Array [],\n        \"updatedProperties\": Object {},\n      },\n    ],\n  },\n  Object {\n    \"blockIDs\": Array [\n      \"test-card-id\",\n    ],\n    \"blockPatches\": Array [\n      Object {\n        \"deletedFields\": Array [],\n        \"updatedFields\": Object {},\n      },\n    ],\n    \"boardIDs\": Array [\n      \"test-board-id\",\n    ],\n    \"boardPatches\": Array [\n      Object {\n        \"deletedCardProperties\": Array [],\n        \"deletedProperties\": Array [],\n        \"updatedCardProperties\": Array [],\n        \"updatedProperties\": Object {},\n      },\n    ],\n  },\n]\n`;\n\nexports[`board tests correctly generate patches from two boards should add card properties on the redo and remove them on the undo 1`] = `\nArray [\n  Object {\n    \"deletedCardProperties\": Array [],\n    \"deletedProperties\": Array [],\n    \"updatedCardProperties\": Array [\n      Object {\n        \"id\": \"new-property-id\",\n        \"name\": \"property-name\",\n        \"options\": Array [\n          Object {\n            \"color\": \"propColorYellow\",\n            \"id\": \"opt\",\n            \"value\": \"val\",\n          },\n        ],\n        \"type\": \"select\",\n      },\n    ],\n    \"updatedProperties\": Object {},\n  },\n  Object {\n    \"deletedCardProperties\": Array [\n      \"new-property-id\",\n    ],\n    \"deletedProperties\": Array [],\n    \"updatedCardProperties\": Array [],\n    \"updatedProperties\": Object {},\n  },\n]\n`;\n\nexports[`board tests correctly generate patches from two boards should add card properties on the redo and undo if they exists in both, but differ 1`] = `\nArray [\n  Object {\n    \"deletedCardProperties\": Array [],\n    \"deletedProperties\": Array [],\n    \"updatedCardProperties\": Array [\n      Object {\n        \"id\": \"new-property-id\",\n        \"name\": \"property-name\",\n        \"options\": Array [\n          Object {\n            \"color\": \"propColorYellow\",\n            \"id\": \"opt\",\n            \"value\": \"val\",\n          },\n        ],\n        \"type\": \"select\",\n      },\n    ],\n    \"updatedProperties\": Object {},\n  },\n  Object {\n    \"deletedCardProperties\": Array [],\n    \"deletedProperties\": Array [],\n    \"updatedCardProperties\": Array [\n      Object {\n        \"id\": \"new-property-id\",\n        \"name\": \"a-different-name\",\n        \"options\": Array [\n          Object {\n            \"color\": \"propColorYellow\",\n            \"id\": \"opt\",\n            \"value\": \"val\",\n          },\n        ],\n        \"type\": \"select\",\n      },\n    ],\n    \"updatedProperties\": Object {},\n  },\n]\n`;\n\nexports[`board tests correctly generate patches from two boards should add card properties on the redo and undo if they exists in both, but their options are different 1`] = `\nArray [\n  Object {\n    \"deletedCardProperties\": Array [],\n    \"deletedProperties\": Array [],\n    \"updatedCardProperties\": Array [\n      Object {\n        \"id\": \"new-property-id\",\n        \"name\": \"property-name\",\n        \"options\": Array [\n          Object {\n            \"color\": \"propColorYellow\",\n            \"id\": \"opt\",\n            \"value\": \"val\",\n          },\n        ],\n        \"type\": \"select\",\n      },\n    ],\n    \"updatedProperties\": Object {},\n  },\n  Object {\n    \"deletedCardProperties\": Array [],\n    \"deletedProperties\": Array [],\n    \"updatedCardProperties\": Array [\n      Object {\n        \"id\": \"new-property-id\",\n        \"name\": \"property-name\",\n        \"options\": Array [\n          Object {\n            \"color\": \"propColorBrown\",\n            \"id\": \"another-opt\",\n            \"value\": \"val\",\n          },\n        ],\n        \"type\": \"select\",\n      },\n    ],\n    \"updatedProperties\": Object {},\n  },\n]\n`;\n\nexports[`board tests correctly generate patches from two boards should add properties on the update patch and remove them on the undo 1`] = `\nArray [\n  Object {\n    \"deletedCardProperties\": Array [],\n    \"deletedProperties\": Array [],\n    \"updatedCardProperties\": Array [],\n    \"updatedProperties\": Object {\n      \"prop1\": \"val1\",\n    },\n  },\n  Object {\n    \"deletedCardProperties\": Array [],\n    \"deletedProperties\": Array [\n      \"prop1\",\n    ],\n    \"updatedCardProperties\": Array [],\n    \"updatedProperties\": Object {},\n  },\n]\n`;\n\nexports[`board tests correctly generate patches from two boards should generate two empty patches for the same board 1`] = `\nArray [\n  Object {\n    \"deletedCardProperties\": Array [],\n    \"deletedProperties\": Array [],\n    \"updatedCardProperties\": Array [],\n    \"updatedProperties\": Object {},\n  },\n  Object {\n    \"deletedCardProperties\": Array [],\n    \"deletedProperties\": Array [],\n    \"updatedCardProperties\": Array [],\n    \"updatedProperties\": Object {},\n  },\n]\n`;\n"
  },
  {
    "path": "webapp/src/blocks/attachmentBlock.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {Block, createBlock} from './block'\n\ntype AttachmentBlockFields = {\n    fileId: string\n}\n\ntype AttachmentBlock = Block & {\n    type: 'attachment'\n    fields: AttachmentBlockFields\n    isUploading: boolean\n    uploadingPercent: number\n}\n\nfunction createAttachmentBlock(block?: Block): AttachmentBlock {\n    return {\n        ...createBlock(block),\n        type: 'attachment',\n        fields: {\n            fileId: block?.fields.attachmentId || block?.fields.fileId || '',\n        },\n        isUploading: false,\n        uploadingPercent: 0,\n    }\n}\n\nexport {AttachmentBlock, createAttachmentBlock}\n"
  },
  {
    "path": "webapp/src/blocks/block.test.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {TestBlockFactory} from '../test/testBlockFactory'\n\nimport {createPatchesFromBlocks, createBlock} from './block'\n\ndescribe('block tests', () => {\n    const board = TestBlockFactory.createBoard()\n    const card = TestBlockFactory.createCard(board)\n\n    describe('correctly generate patches from two blocks', () => {\n        it('should generate two empty patches for the same block', () => {\n            const textBlock = TestBlockFactory.createText(card)\n            const result = createPatchesFromBlocks(textBlock, textBlock)\n            expect(result).toMatchSnapshot()\n        })\n\n        it('should add fields on the new fields added and remove it in the undo', () => {\n            const oldBlock = TestBlockFactory.createText(card)\n            const newBlock = createBlock(oldBlock)\n            newBlock.fields.newField = 'new field'\n            const result = createPatchesFromBlocks(newBlock, oldBlock)\n            expect(result).toMatchSnapshot()\n        })\n\n        it('should remove field on the new block added and add it again in the undo', () => {\n            const oldBlock = TestBlockFactory.createText(card)\n            const newBlock = createBlock(oldBlock)\n            oldBlock.fields.test = 'test'\n            const result = createPatchesFromBlocks(newBlock, oldBlock)\n            expect(result).toMatchSnapshot()\n        })\n\n        it('should update propertie on the main object and revert it back on the undo', () => {\n            const oldBlock = TestBlockFactory.createText(card)\n            const newBlock = createBlock(oldBlock)\n            oldBlock.parentId = 'old-parent-id'\n            newBlock.parentId = 'new-parent-id'\n            const result = createPatchesFromBlocks(newBlock, oldBlock)\n            expect(result).toMatchSnapshot()\n        })\n    })\n})\n"
  },
  {
    "path": "webapp/src/blocks/block.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport difference from 'lodash/difference'\n\nimport {Utils} from '../utils'\n\nconst contentBlockTypes = ['text', 'image', 'divider', 'checkbox', 'h1', 'h2', 'h3', 'list-item', 'attachment', 'quote', 'video'] as const\n\n// ToDo: remove type board\nconst blockTypes = [...contentBlockTypes, 'board', 'view', 'card', 'comment', 'attachment', 'unknown'] as const\ntype ContentBlockTypes = typeof contentBlockTypes[number]\ntype BlockTypes = typeof blockTypes[number]\n\ninterface BlockPatch {\n    parentId?: string\n    schema?: number\n    type?: BlockTypes\n    title?: string\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    updatedFields?: Record<string, any>\n    deletedFields?: string[]\n    deleteAt?: number\n}\n\ninterface Block {\n    id: string\n    boardId: string\n    parentId: string\n    createdBy: string\n    modifiedBy: string\n\n    schema: number\n    type: BlockTypes\n    title: string\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    fields: Record<string, any>\n\n    createAt: number\n    updateAt: number\n    deleteAt: number\n\n    limited?: boolean\n}\n\ninterface FileInfo {\n    url?: string\n    archived?: boolean\n    extension?: string\n    name?: string\n    size?: number\n}\n\nfunction createBlock(block?: Block): Block {\n    const now = Date.now()\n    return {\n        id: block?.id || Utils.createGuid(Utils.blockTypeToIDType(block?.type)),\n        schema: 1,\n        boardId: block?.boardId || '',\n        parentId: block?.parentId || '',\n        createdBy: block?.createdBy || '',\n        modifiedBy: block?.modifiedBy || '',\n        type: block?.type || 'unknown',\n        fields: block?.fields ? {...block?.fields} : {},\n        title: block?.title || '',\n        createAt: block?.createAt || now,\n        updateAt: block?.updateAt || now,\n        deleteAt: block?.deleteAt || 0,\n        limited: Boolean(block?.limited),\n    }\n}\n\n// createPatchesFromBlocks creates two BlockPatch instances, one that\n// contains the delta to update the block and another one for the undo\n// action, in case it happens\nfunction createPatchesFromBlocks(newBlock: Block, oldBlock: Block): BlockPatch[] {\n    const newDeletedFields = difference(Object.keys(newBlock.fields), Object.keys(oldBlock.fields))\n    const newUpdatedFields: Record<string, any> = {}\n    const newUpdatedData: Record<string, any> = {}\n    Object.keys(newBlock.fields).forEach((val) => {\n        if (oldBlock.fields[val] !== newBlock.fields[val]) {\n            newUpdatedFields[val] = newBlock.fields[val]\n        }\n    })\n    Object.keys(newBlock).forEach((val) => {\n        if (val !== 'fields' && (oldBlock as any)[val] !== (newBlock as any)[val]) {\n            newUpdatedData[val] = (newBlock as any)[val]\n        }\n    })\n\n    const oldDeletedFields = difference(Object.keys(oldBlock.fields), Object.keys(newBlock.fields))\n    const oldUpdatedFields: Record<string, any> = {}\n    const oldUpdatedData: Record<string, any> = {}\n    Object.keys(oldBlock.fields).forEach((val) => {\n        if (oldBlock.fields[val] !== newBlock.fields[val]) {\n            oldUpdatedFields[val] = oldBlock.fields[val]\n        }\n    })\n    Object.keys(oldBlock).forEach((val) => {\n        if (val !== 'fields' && (oldBlock as any)[val] !== (newBlock as any)[val]) {\n            oldUpdatedData[val] = (oldBlock as any)[val]\n        }\n    })\n\n    return [\n        {\n            ...newUpdatedData,\n            updatedFields: newUpdatedFields,\n            deletedFields: oldDeletedFields,\n        },\n        {\n            ...oldUpdatedData,\n            updatedFields: oldUpdatedFields,\n            deletedFields: newDeletedFields,\n        },\n    ]\n}\n\nexport type {ContentBlockTypes, BlockTypes, FileInfo}\nexport {blockTypes, contentBlockTypes, Block, BlockPatch, createBlock, createPatchesFromBlocks}\n"
  },
  {
    "path": "webapp/src/blocks/board.test.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {TestBlockFactory} from '../test/testBlockFactory'\n\nimport {createPatchesFromBoards, createBoard, IPropertyTemplate, createPatchesFromBoardsAndBlocks} from './board'\nimport {createBlock} from './block'\n\ndescribe('board tests', () => {\n    describe('correctly generate patches from two boards', () => {\n        it('should generate two empty patches for the same board', () => {\n            const board = TestBlockFactory.createBoard()\n            const result = createPatchesFromBoards(board, board)\n            expect(result).toMatchSnapshot()\n        })\n\n        it('should add properties on the update patch and remove them on the undo', () => {\n            const board = TestBlockFactory.createBoard()\n            board.properties = {\n                prop1: 'val1',\n                prop2: 'val2',\n            }\n            const oldBoard = createBoard(board)\n            oldBoard.properties = {\n                prop2: 'val2',\n            }\n\n            const result = createPatchesFromBoards(board, oldBoard)\n            expect(result).toMatchSnapshot()\n        })\n\n        it('should add card properties on the redo and remove them on the undo', () => {\n            const board = TestBlockFactory.createBoard()\n            const oldBoard = createBoard(board)\n            board.cardProperties.push({\n                id: 'new-property-id',\n                name: 'property-name',\n                type: 'select',\n                options: [{\n                    id: 'opt',\n                    value: 'val',\n                    color: 'propColorYellow',\n                }],\n            })\n\n            const result = createPatchesFromBoards(board, oldBoard)\n            expect(result).toMatchSnapshot()\n        })\n\n        it('should add card properties on the redo and undo if they exists in both, but differ', () => {\n            const cardProperty = {\n                id: 'new-property-id',\n                name: 'property-name',\n                type: 'select',\n                options: [{\n                    id: 'opt',\n                    value: 'val',\n                    color: 'propColorYellow',\n                }],\n            } as IPropertyTemplate\n\n            const board = TestBlockFactory.createBoard()\n            const oldBoard = createBoard(board)\n            board.cardProperties = [cardProperty]\n            oldBoard.cardProperties = [{...cardProperty, name: 'a-different-name'}]\n\n            const result = createPatchesFromBoards(board, oldBoard)\n            expect(result).toMatchSnapshot()\n        })\n\n        it('should add card properties on the redo and undo if they exists in both, but their options are different', () => {\n            const cardProperty = {\n                id: 'new-property-id',\n                name: 'property-name',\n                type: 'select',\n                options: [{\n                    id: 'opt',\n                    value: 'val',\n                    color: 'propColorYellow',\n                }],\n            } as IPropertyTemplate\n\n            const board = TestBlockFactory.createBoard()\n            const oldBoard = createBoard(board)\n            board.cardProperties = [cardProperty]\n            oldBoard.cardProperties = [{\n                ...cardProperty,\n                options: [{\n                    id: 'another-opt',\n                    value: 'val',\n                    color: 'propColorBrown',\n                }],\n            }]\n\n            const result = createPatchesFromBoards(board, oldBoard)\n            expect(result).toMatchSnapshot()\n        })\n    })\n\n    describe('correctly generate patches for boards and blocks', () => {\n        const board = TestBlockFactory.createBoard()\n        board.id = 'test-board-id'\n        const card = TestBlockFactory.createCard()\n        card.id = 'test-card-id'\n\n        it('should generate two empty patches for the same board and block', () => {\n            const result = createPatchesFromBoardsAndBlocks(board, board, [card.id], [card], [card])\n            expect(result).toMatchSnapshot()\n        })\n\n        it('should add fields on update and remove it in the undo', () => {\n            const oldBlock = TestBlockFactory.createText(card)\n            oldBlock.id = 'test-old-block-id'\n            const newBlock = createBlock(oldBlock)\n            newBlock.fields.newField = 'new field'\n\n            const result = createPatchesFromBoardsAndBlocks(board, board, [newBlock.id], [newBlock], [oldBlock])\n            expect(result).toMatchSnapshot()\n        })\n    })\n})\n"
  },
  {
    "path": "webapp/src/blocks/board.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport difference from 'lodash/difference'\n\nimport {Utils, IDType} from '../utils'\n\nimport {Block, BlockPatch, createPatchesFromBlocks} from './block'\nimport {Card} from './card'\n\nconst BoardTypeOpen = 'O'\nconst BoardTypePrivate = 'P'\nconst boardTypes = [BoardTypeOpen, BoardTypePrivate]\ntype BoardTypes = typeof boardTypes[number]\n\nenum MemberRole {\n    Viewer = 'viewer',\n    Commenter = 'commenter',\n    Editor = 'editor',\n    Admin = 'admin',\n    None = '',\n}\n\ntype Board = {\n    id: string\n    teamId: string\n    channelId?: string\n    createdBy: string\n    modifiedBy: string\n    type: BoardTypes\n    minimumRole: MemberRole\n\n    title: string\n    description: string\n    icon?: string\n    showDescription: boolean\n    isTemplate: boolean\n    templateVersion: number\n    properties: Record<string, string | string[]>\n    cardProperties: IPropertyTemplate[]\n\n    createAt: number\n    updateAt: number\n    deleteAt: number\n}\n\ntype BoardPatch = {\n    type?: BoardTypes\n    minimumRole?: MemberRole\n    title?: string\n    description?: string\n    icon?: string\n    showDescription?: boolean\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    updatedProperties?: Record<string, any>\n    deletedProperties?: string[]\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    updatedCardProperties?: IPropertyTemplate[]\n    deletedCardProperties?: string[]\n}\n\ntype BoardMember = {\n    boardId: string\n    userId: string\n    roles?: string\n    minimumRole: MemberRole\n    schemeAdmin: boolean\n    schemeEditor: boolean\n    schemeCommenter: boolean\n    schemeViewer: boolean\n    synthetic: boolean\n}\n\ntype BoardsAndBlocks = {\n    boards: Board[]\n    blocks: Block[]\n}\n\ntype BoardsAndBlocksPatch = {\n    boardIDs: string[]\n    boardPatches: BoardPatch[]\n    blockIDs: string[]\n    blockPatches: BlockPatch[]\n}\n\ntype PropertyTypeEnum = 'text' | 'number' | 'select' | 'multiSelect' | 'date' | 'person' | 'multiPerson' | 'file' | 'checkbox' | 'url' | 'email' | 'phone' | 'createdTime' | 'createdBy' | 'updatedTime' | 'updatedBy' | 'unknown'\n\ninterface IPropertyOption {\n    id: string\n    value: string\n    color: string\n}\n\n// A template for card properties attached to a board\ninterface IPropertyTemplate {\n    id: string\n    name: string\n    type: PropertyTypeEnum\n    options: IPropertyOption[]\n}\n\nfunction createBoard(board?: Board): Board {\n    const now = Date.now()\n    let cardProperties: IPropertyTemplate[] = []\n    const selectProperties = cardProperties.find((o) => o.type === 'select')\n    if (!selectProperties) {\n        const property: IPropertyTemplate = {\n            id: Utils.createGuid(IDType.BlockID),\n            name: 'Status',\n            type: 'select',\n            options: [],\n        }\n        cardProperties.push(property)\n    }\n\n    if (board?.cardProperties) {\n        // Deep clone of card properties and their options\n        cardProperties = board?.cardProperties.map((o: IPropertyTemplate) => {\n            return {\n                id: o.id,\n                name: o.name,\n                type: o.type,\n                options: o.options ? o.options.map((option) => ({...option})) : [],\n            }\n        })\n    }\n\n    return {\n        id: board?.id || Utils.createGuid(IDType.Board),\n        teamId: board?.teamId || '',\n        channelId: board?.channelId || '',\n        createdBy: board?.createdBy || '',\n        modifiedBy: board?.modifiedBy || '',\n        type: board?.type || BoardTypePrivate,\n        minimumRole: board?.minimumRole || MemberRole.None,\n        title: board?.title || '',\n        description: board?.description || '',\n        icon: board?.icon || '',\n        showDescription: board?.showDescription || false,\n        isTemplate: board?.isTemplate || false,\n        templateVersion: board?.templateVersion || 0,\n        properties: board?.properties || {},\n        cardProperties,\n        createAt: board?.createAt || now,\n        updateAt: board?.updateAt || now,\n        deleteAt: board?.deleteAt || 0,\n    }\n}\n\ntype BoardGroup = {\n    option: IPropertyOption\n    cards: Card[]\n}\n\n// getPropertiesDifference returns a list of the property IDs that are\n// contained in propsA but are not contained in propsB\nfunction getPropertiesDifference(propsA: IPropertyTemplate[], propsB: IPropertyTemplate[]): string[] {\n    const diff: string[] = []\n    propsA.forEach((val) => {\n        if (!propsB.find((p) => p.id === val.id)) {\n            diff.push(val.id)\n        }\n    })\n\n    return diff\n}\n\n// isPropertyEqual checks that both the contents of the property and\n// its options are equal\nfunction isPropertyEqual(propA: IPropertyTemplate, propB: IPropertyTemplate): boolean {\n    for (const val of Object.keys(propA)) {\n        if (val !== 'options' && (propA as any)[val] !== (propB as any)[val]) {\n            return false\n        }\n    }\n\n    if (propA.options.length !== propB.options.length) {\n        return false\n    }\n\n    for (const opt of propA.options) {\n        const optionB = propB.options.find((o) => o.id === opt.id)\n        if (!optionB) {\n            return false\n        }\n\n        for (const val of Object.keys(opt)) {\n            if ((opt as any)[val] !== (optionB as any)[val]) {\n                return false\n            }\n        }\n    }\n\n    return true\n}\n\n// createCardPropertiesPatches creates two BoardPatch instances, one that\n// contains the delta to update the board cardProperties and another one for\n// the undo action, in case it happens\nfunction createCardPropertiesPatches(newCardProperties: IPropertyTemplate[], oldCardProperties: IPropertyTemplate[]): BoardPatch[] {\n    const newDeletedCardProperties = getPropertiesDifference(newCardProperties, oldCardProperties)\n    const oldDeletedCardProperties = getPropertiesDifference(oldCardProperties, newCardProperties)\n    const newUpdatedCardProperties: IPropertyTemplate[] = []\n    newCardProperties.forEach((val) => {\n        const oldCardProperty = oldCardProperties.find((o) => o.id === val.id)\n        if (!oldCardProperty || !isPropertyEqual(val, oldCardProperty)) {\n            newUpdatedCardProperties.push(val)\n        }\n    })\n    const oldUpdatedCardProperties: IPropertyTemplate[] = []\n    oldCardProperties.forEach((val) => {\n        const newCardProperty = newCardProperties.find((o) => o.id === val.id)\n        if (!newCardProperty || !isPropertyEqual(val, newCardProperty)) {\n            oldUpdatedCardProperties.push(val)\n        }\n    })\n\n    return [\n        {\n            updatedCardProperties: newUpdatedCardProperties,\n            deletedCardProperties: oldDeletedCardProperties,\n        },\n        {\n            updatedCardProperties: oldUpdatedCardProperties,\n            deletedCardProperties: newDeletedCardProperties,\n        },\n    ]\n}\n\n// createPatchesFromBoards creates two BoardPatch instances, one that\n// contains the delta to update the board and another one for the undo\n// action, in case it happens\nfunction createPatchesFromBoards(newBoard: Board, oldBoard: Board): BoardPatch[] {\n    const newDeletedProperties = difference(Object.keys(newBoard.properties || {}), Object.keys(oldBoard.properties || {}))\n\n    const newUpdatedProperties: Record<string, any> = {}\n    Object.keys(newBoard.properties || {}).forEach((val) => {\n        if (oldBoard.properties[val] !== newBoard.properties[val]) {\n            newUpdatedProperties[val] = newBoard.properties[val]\n        }\n    })\n\n    const newData: Record<string, any> = {}\n    Object.keys(newBoard).forEach((val) => {\n        if (val !== 'properties' &&\n            val !== 'cardProperties' &&\n            (oldBoard as any)[val] !== (newBoard as any)[val]) {\n            newData[val] = (newBoard as any)[val]\n        }\n    })\n\n    const oldDeletedProperties = difference(Object.keys(oldBoard.properties || {}), Object.keys(newBoard.properties || {}))\n\n    const oldUpdatedProperties: Record<string, any> = {}\n    Object.keys(oldBoard.properties || {}).forEach((val) => {\n        if (newBoard.properties[val] !== oldBoard.properties[val]) {\n            oldUpdatedProperties[val] = oldBoard.properties[val]\n        }\n    })\n\n    const oldData: Record<string, any> = {}\n    Object.keys(oldBoard).forEach((val) => {\n        if (val !== 'properties' &&\n            val !== 'cardProperties' &&\n            (newBoard as any)[val] !== (oldBoard as any)[val]) {\n            oldData[val] = (oldBoard as any)[val]\n        }\n    })\n\n    const [cardPropertiesPatch, cardPropertiesUndoPatch] = createCardPropertiesPatches(newBoard.cardProperties, oldBoard.cardProperties)\n\n    return [\n        {\n            ...newData,\n            ...cardPropertiesPatch,\n            updatedProperties: newUpdatedProperties,\n            deletedProperties: oldDeletedProperties,\n        },\n        {\n            ...oldData,\n            ...cardPropertiesUndoPatch,\n            updatedProperties: oldUpdatedProperties,\n            deletedProperties: newDeletedProperties,\n        },\n    ]\n}\n\nfunction createPatchesFromBoardsAndBlocks(updatedBoard: Board, oldBoard: Board, updatedBlockIDs: string[], updatedBlocks: Block[], oldBlocks: Block[]): BoardsAndBlocksPatch[] {\n    const blockUpdatePatches = [] as BlockPatch[]\n    const blockUndoPatches = [] as BlockPatch[]\n    updatedBlocks.forEach((newBlock, i) => {\n        const [updatePatch, undoPatch] = createPatchesFromBlocks(newBlock, oldBlocks[i])\n        blockUpdatePatches.push(updatePatch)\n        blockUndoPatches.push(undoPatch)\n    })\n\n    const [boardUpdatePatch, boardUndoPatch] = createPatchesFromBoards(updatedBoard, oldBoard)\n\n    const updatePatch: BoardsAndBlocksPatch = {\n        blockIDs: updatedBlockIDs,\n        blockPatches: blockUpdatePatches,\n        boardIDs: [updatedBoard.id],\n        boardPatches: [boardUpdatePatch],\n    }\n\n    const undoPatch: BoardsAndBlocksPatch = {\n        blockIDs: updatedBlockIDs,\n        blockPatches: blockUndoPatches,\n        boardIDs: [updatedBoard.id],\n        boardPatches: [boardUndoPatch],\n    }\n\n    return [updatePatch, undoPatch]\n}\n\nexport {\n    Board,\n    BoardPatch,\n    BoardMember,\n    BoardsAndBlocks,\n    BoardsAndBlocksPatch,\n    PropertyTypeEnum,\n    IPropertyOption,\n    IPropertyTemplate,\n    BoardGroup,\n    createBoard,\n    BoardTypes,\n    BoardTypeOpen,\n    BoardTypePrivate,\n    MemberRole,\n    createPatchesFromBoards,\n    createPatchesFromBoardsAndBlocks,\n    createCardPropertiesPatches,\n}\n"
  },
  {
    "path": "webapp/src/blocks/boardView.test.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {TestBlockFactory} from '../test/testBlockFactory'\n\nimport {sortBoardViewsAlphabetically} from './boardView'\n\ntest('boardView: sort with ASCII', async () => {\n    const view1 = TestBlockFactory.createBoardView()\n    view1.title = 'Maybe'\n    const view2 = TestBlockFactory.createBoardView()\n    view2.title = 'Active'\n\n    const views = [view1, view2]\n    const sorted = sortBoardViewsAlphabetically(views)\n    expect(sorted).toEqual([view2, view1])\n})\n\ntest('boardView: sort with leading emoji', async () => {\n    const view1 = TestBlockFactory.createBoardView()\n    view1.title = '🤔 Maybe'\n    const view2 = TestBlockFactory.createBoardView()\n    view2.title = '🚀 Active'\n\n    const views = [view1, view2]\n    const sorted = sortBoardViewsAlphabetically(views)\n    expect(sorted).toEqual([view2, view1])\n})\n\ntest('boardView: sort with non-latin characters', async () => {\n    const view1 = TestBlockFactory.createBoardView()\n    view1.title = 'zebra'\n    const view2 = TestBlockFactory.createBoardView()\n    view2.title = 'ñu'\n\n    const views = [view1, view2]\n    const sorted = sortBoardViewsAlphabetically(views)\n    expect(sorted).toEqual([view2, view1])\n})\n"
  },
  {
    "path": "webapp/src/blocks/boardView.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {Block, createBlock} from './block'\nimport {FilterGroup, createFilterGroup} from './filterGroup'\n\ntype IViewType = 'board' | 'table' | 'gallery' | 'calendar'\ntype ISortOption = { propertyId: '__title' | string, reversed: boolean }\n\ntype KanbanCalculationFields = {\n    calculation: string\n    propertyId: string\n}\n\ntype BoardViewFields = {\n    viewType: IViewType\n    groupById?: string\n    dateDisplayPropertyId?: string\n    sortOptions: ISortOption[]\n    visiblePropertyIds: string[]\n    visibleOptionIds: string[]\n    hiddenOptionIds: string[]\n    collapsedOptionIds: string[]\n    filter: FilterGroup\n    cardOrder: string[]\n    columnWidths: Record<string, number>\n    columnCalculations: Record<string, string>\n    kanbanCalculations: Record<string, KanbanCalculationFields>\n    defaultTemplateId: string\n}\n\ntype BoardView = Block & {\n    fields: BoardViewFields\n}\n\nfunction createBoardView(block?: Block): BoardView {\n    return {\n        ...createBlock(block),\n        type: 'view',\n        fields: {\n            viewType: block?.fields.viewType || 'board',\n            groupById: block?.fields.groupById,\n            dateDisplayPropertyId: block?.fields.dateDisplayPropertyId,\n            sortOptions: block?.fields.sortOptions?.map((o: ISortOption) => ({...o})) || [],\n            visiblePropertyIds: block?.fields.visiblePropertyIds?.slice() || [],\n            visibleOptionIds: block?.fields.visibleOptionIds?.slice() || [],\n            hiddenOptionIds: block?.fields.hiddenOptionIds?.slice() || [],\n            collapsedOptionIds: block?.fields.collapsedOptionIds?.slice() || [],\n            filter: createFilterGroup(block?.fields.filter),\n            cardOrder: block?.fields.cardOrder?.slice() || [],\n            columnWidths: {...(block?.fields.columnWidths || {})},\n            columnCalculations: {...(block?.fields.columnCalculations) || {}},\n            kanbanCalculations: {...(block?.fields.kanbanCalculations) || {}},\n            defaultTemplateId: block?.fields.defaultTemplateId || '',\n        },\n    }\n}\n\nfunction sortBoardViewsAlphabetically(views: BoardView[]): BoardView[] {\n    // Strip leading emoji to prevent unintuitive results\n    return views.map((v) => {\n        return {view: v, title: v.title.replace(/^\\p{Emoji}*\\s*/u, '')}\n    }).sort((v1, v2) => v1.title.localeCompare(v2.title)).map((v) => v.view)\n}\n\nexport {BoardView, IViewType, ISortOption, sortBoardViewsAlphabetically, createBoardView, KanbanCalculationFields}\n"
  },
  {
    "path": "webapp/src/blocks/card.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {Block, createBlock} from './block'\n\ntype CardFields = {\n    icon?: string\n    isTemplate?: boolean\n    properties: Record<string, string | string[]>\n    contentOrder: Array<string | string[]>\n}\n\ntype Card = Block & {\n    fields: CardFields\n}\n\nfunction createCard(block?: Block): Card {\n    const contentOrder: Array<string|string[]> = []\n    const contentIds = block?.fields?.contentOrder?.filter((id: any) => id !== null)\n\n    if (contentIds?.length > 0) {\n        for (const contentId of contentIds) {\n            if (typeof contentId === 'string') {\n                contentOrder.push(contentId)\n            } else {\n                contentOrder.push(contentId.slice())\n            }\n        }\n    }\n    return {\n        ...createBlock(block),\n        type: 'card',\n        fields: {\n            icon: block?.fields.icon || '',\n            properties: {...(block?.fields.properties || {})},\n            contentOrder,\n            isTemplate: block?.fields.isTemplate || false,\n        },\n    }\n}\n\nexport {Card, createCard}\n"
  },
  {
    "path": "webapp/src/blocks/checkboxBlock.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {ContentBlock} from './contentBlock'\nimport {Block, createBlock} from './block'\n\ntype CheckboxBlock = ContentBlock & {\n    type: 'checkbox'\n}\n\nfunction createCheckboxBlock(block?: Block): CheckboxBlock {\n    return {\n        ...createBlock(block),\n        type: 'checkbox',\n    }\n}\n\nexport {CheckboxBlock, createCheckboxBlock}\n"
  },
  {
    "path": "webapp/src/blocks/commentBlock.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {Block, createBlock} from './block'\n\ntype CommentBlock = Block & {\n    type: 'comment'\n}\n\nfunction createCommentBlock(block?: Block): CommentBlock {\n    return {\n        ...createBlock(block),\n        type: 'comment',\n    }\n}\n\nexport {CommentBlock, createCommentBlock}\n"
  },
  {
    "path": "webapp/src/blocks/contentBlock.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {Block, createBlock} from './block'\n\ntype IContentBlockWithCords = {\n    block: Block\n    cords: {x: number, y?: number, z?: number}\n}\n\ntype ContentBlock = Block\n\nconst createContentBlock = createBlock\n\nexport {ContentBlock, IContentBlockWithCords, createContentBlock}\n"
  },
  {
    "path": "webapp/src/blocks/dividerBlock.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {Block, createBlock} from './block'\nimport {ContentBlock} from './contentBlock'\n\ntype DividerBlock = ContentBlock & {\n    type: 'divider'\n}\n\nfunction createDividerBlock(block?: Block): DividerBlock {\n    return {\n        ...createBlock(block),\n        type: 'divider',\n    }\n}\n\nexport {DividerBlock, createDividerBlock}\n"
  },
  {
    "path": "webapp/src/blocks/filterClause.test.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {areEqual, createFilterClause} from './filterClause'\n\ndescribe('filterClause tests', () => {\n    it('create filter clause', () => {\n        const clause = createFilterClause({\n            propertyId: 'myPropertyId',\n            condition: 'contains',\n            values: [],\n        })\n\n        expect(clause).toEqual({\n            propertyId: 'myPropertyId',\n            condition: 'contains',\n            values: [],\n        })\n    })\n\n    it('test filter clauses are equal', () => {\n        const clause = createFilterClause({\n            propertyId: 'myPropertyId',\n            condition: 'contains',\n            values: ['abc', 'def'],\n        })\n        const newClause = createFilterClause(clause)\n        const testEqual = areEqual(clause, newClause)\n        expect(testEqual).toBeTruthy()\n    })\n\n    it('test filter clauses are Not equal property ID', () => {\n        const clause = createFilterClause({\n            propertyId: 'myPropertyId',\n            condition: 'contains',\n            values: ['abc', 'def'],\n        })\n        const newClause = createFilterClause(clause)\n        newClause.propertyId = 'DifferentID'\n        const testEqual = areEqual(clause, newClause)\n        expect(testEqual).toBeFalsy()\n    })\n    it('test filter clauses are Not equal condition', () => {\n        const clause = createFilterClause({\n            propertyId: 'myPropertyId',\n            condition: 'contains',\n            values: ['abc', 'def'],\n        })\n        const newClause = createFilterClause(clause)\n        newClause.condition = 'notContains'\n        const testEqual = areEqual(clause, newClause)\n        expect(testEqual).toBeFalsy()\n    })\n    it('test filter clauses are Not equal values', () => {\n        const clause = createFilterClause({\n            propertyId: 'myPropertyId',\n            condition: 'contains',\n            values: ['abc', 'def'],\n        })\n        const newClause = createFilterClause(clause)\n        newClause.values = ['abc, def']\n        const testEqual = areEqual(clause, newClause)\n        expect(testEqual).toBeFalsy()\n    })\n})\n"
  },
  {
    "path": "webapp/src/blocks/filterClause.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {Utils} from '../utils'\n\ntype FilterCondition =\n    'includes' | 'notIncludes' |\n    'isEmpty' | 'isNotEmpty' |\n    'isSet' | 'isNotSet' |\n    'is' |\n    'contains' | 'notContains' |\n    'startsWith' | 'notStartsWith' |\n    'endsWith' | 'notEndsWith' |\n    'isBefore' | 'isAfter'\n\ntype FilterClause = {\n    propertyId: string\n    condition: FilterCondition\n    values: string[]\n}\n\nfunction createFilterClause(o?: FilterClause): FilterClause {\n    return {\n        propertyId: o?.propertyId || '',\n        condition: o?.condition || 'includes',\n        values: o?.values?.slice() || [],\n    }\n}\n\nfunction areEqual(a: FilterClause, b: FilterClause): boolean {\n    return (\n        a.propertyId === b.propertyId &&\n        a.condition === b.condition &&\n        Utils.arraysEqual(a.values, b.values)\n    )\n}\n\nexport {FilterClause, FilterCondition, createFilterClause, areEqual}\n"
  },
  {
    "path": "webapp/src/blocks/filterGroup.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {FilterClause, createFilterClause} from './filterClause'\n\ntype FilterGroupOperation = 'and' | 'or'\n\n// A FilterGroup has 2 forms: (A or B or C) OR (A and B and C)\ntype FilterGroup = {\n    operation: FilterGroupOperation\n    filters: Array<FilterClause | FilterGroup>\n}\n\nfunction isAFilterGroupInstance(object: (FilterClause | FilterGroup)): object is FilterGroup {\n    return 'operation' in object && 'filters' in object\n}\n\nfunction createFilterGroup(o?: FilterGroup): FilterGroup {\n    let filters: Array<FilterClause | FilterGroup> = []\n    if (o?.filters) {\n        filters = o.filters.map((p: (FilterClause | FilterGroup)) => {\n            if (isAFilterGroupInstance(p)) {\n                return createFilterGroup(p)\n            }\n            return createFilterClause(p)\n        })\n    }\n    return {\n        operation: o?.operation || 'and',\n        filters,\n    }\n}\n\nexport {FilterGroup, FilterGroupOperation, createFilterGroup, isAFilterGroupInstance}\n"
  },
  {
    "path": "webapp/src/blocks/h1Block.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {ContentBlock} from './contentBlock'\nimport {Block, createBlock} from './block'\n\ntype H1Block = ContentBlock & {\n    type: 'h1'\n}\n\nfunction createH1Block(block?: Block): H1Block {\n    return {\n        ...createBlock(block),\n        type: 'h1',\n    }\n}\n\nexport {H1Block, createH1Block}\n\n"
  },
  {
    "path": "webapp/src/blocks/h2Block.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {ContentBlock} from './contentBlock'\nimport {Block, createBlock} from './block'\n\ntype H2Block = ContentBlock & {\n    type: 'h2'\n}\n\nfunction createH2Block(block?: Block): H2Block {\n    return {\n        ...createBlock(block),\n        type: 'h2',\n    }\n}\n\nexport {H2Block, createH2Block}\n\n"
  },
  {
    "path": "webapp/src/blocks/h3Block.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {ContentBlock} from './contentBlock'\nimport {Block, createBlock} from './block'\n\ntype H3Block = ContentBlock & {\n    type: 'h3'\n}\n\nfunction createH3Block(block?: Block): H3Block {\n    return {\n        ...createBlock(block),\n        type: 'h3',\n    }\n}\n\nexport {H3Block, createH3Block}\n\n"
  },
  {
    "path": "webapp/src/blocks/imageBlock.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {Block, createBlock} from './block'\nimport {ContentBlock} from './contentBlock'\n\ntype ImageBlockFields = {\n    fileId: string\n}\n\ntype ImageBlock = ContentBlock & {\n    type: 'image'\n    fields: ImageBlockFields\n}\n\nfunction createImageBlock(block?: Block): ImageBlock {\n    return {\n        ...createBlock(block),\n        type: 'image',\n        fields: {\n            fileId: block?.fields.fileId || '',\n        },\n    }\n}\n\nexport {ImageBlock, createImageBlock}\n"
  },
  {
    "path": "webapp/src/blocks/sharing.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\ninterface ISharing {\n    id: string\n    enabled: boolean\n    token: string\n    modifiedBy?: string\n    updateAt?: number\n}\n\nexport {ISharing}\n"
  },
  {
    "path": "webapp/src/blocks/team.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\ninterface ITeam {\n    readonly id: string\n    readonly title: string\n    readonly signupToken: string\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    readonly settings: Readonly<Record<string, any>>\n    readonly modifiedBy?: string\n    readonly updateAt?: number\n}\n\nexport {ITeam}\n"
  },
  {
    "path": "webapp/src/blocks/textBlock.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {ContentBlock} from './contentBlock'\nimport {Block, createBlock} from './block'\n\ntype TextBlock = ContentBlock & {\n    type: 'text'\n}\n\nfunction createTextBlock(block?: Block): TextBlock {\n    return {\n        ...createBlock(block),\n        type: 'text',\n    }\n}\n\nexport {TextBlock, createTextBlock}\n"
  },
  {
    "path": "webapp/src/blocks/workspace.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\ninterface IWorkspace {\n    readonly id: string\n    readonly title: string\n    readonly signupToken: string\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    readonly settings: Readonly<Record<string, any>>\n    readonly modifiedBy?: string\n    readonly updateAt?: number\n}\n\nexport {IWorkspace}\n"
  },
  {
    "path": "webapp/src/boardCloudLimits/index.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nexport const LimitUnlimited = 0\n\nexport interface BoardsCloudLimits {\n    cards: number\n    used_cards: number\n    card_limit_timestamp: number\n    views: number\n}\n"
  },
  {
    "path": "webapp/src/boardUtils.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {Card} from './blocks/card'\nimport {IPropertyTemplate, IPropertyOption, BoardGroup} from './blocks/board'\n\nfunction groupCardsByOptions(cards: Card[], optionIds: string[], groupByProperty?: IPropertyTemplate): BoardGroup[] {\n    const groups = []\n    for (const optionId of optionIds) {\n        if (optionId) {\n            const option = groupByProperty?.options.find((o) => o.id === optionId)\n            if (option) {\n                const c = cards.filter((o) => optionId === o.fields?.properties[groupByProperty!.id])\n                const group: BoardGroup = {\n                    option,\n                    cards: c,\n                }\n                groups.push(group)\n            } else {\n                // if optionId not found, its an old (deleted) option that can be ignored\n            }\n        } else {\n            // Empty group\n            const emptyGroupCards = cards.filter((card) => {\n                const groupByOptionId = card.fields.properties[groupByProperty?.id || '']\n                return !groupByOptionId || !groupByProperty?.options.find((option) => option.id === groupByOptionId)\n            })\n            const group: BoardGroup = {\n                option: {id: '', value: `No ${groupByProperty?.name}`, color: ''},\n                cards: emptyGroupCards,\n            }\n            groups.push(group)\n        }\n    }\n    return groups\n}\n\nfunction getOptionGroups(cards: Card[], visibleOptionIds: string[], hiddenOptionIds: string[], groupByProperty?: IPropertyTemplate): {visible: BoardGroup[], hidden: BoardGroup[]} {\n    let unassignedOptionIds: string[] = []\n    if (groupByProperty) {\n        unassignedOptionIds = groupByProperty.options.\n            filter((o: IPropertyOption) => !visibleOptionIds.includes(o.id) && !hiddenOptionIds.includes(o.id)).\n            map((o: IPropertyOption) => o.id)\n    }\n    const allVisibleOptionIds = [...visibleOptionIds, ...unassignedOptionIds]\n\n    // If the empty group positon is not explicitly specified, make it the first visible column\n    if (!allVisibleOptionIds.includes('') && !hiddenOptionIds.includes('')) {\n        allVisibleOptionIds.unshift('')\n    }\n\n    const visibleGroups = groupCardsByOptions(cards, allVisibleOptionIds, groupByProperty)\n    const hiddenGroups = groupCardsByOptions(cards, hiddenOptionIds, groupByProperty)\n    return {visible: visibleGroups, hidden: hiddenGroups}\n}\nexport function getVisibleAndHiddenGroups(cards: Card[], visibleOptionIds: string[], hiddenOptionIds: string[], groupByProperty?: IPropertyTemplate): {visible: BoardGroup[], hidden: BoardGroup[]} {\n    if (groupByProperty?.type === 'createdBy' || groupByProperty?.type === 'updatedBy' || groupByProperty?.type === 'person') {\n        return getPersonGroups(cards, groupByProperty, hiddenOptionIds)\n    }\n\n    return getOptionGroups(cards, visibleOptionIds, hiddenOptionIds, groupByProperty)\n}\n\nfunction getPersonGroups(cards: Card[], groupByProperty: IPropertyTemplate, hiddenOptionIds: string[]): {visible: BoardGroup[], hidden: BoardGroup[]} {\n    const groups = cards.reduce((unique: {[key: string]: Card[]}, item: Card): {[key: string]: Card[]} => {\n        let key = item.fields.properties[groupByProperty.id] as string\n        if (groupByProperty?.type === 'createdBy') {\n            key = item.createdBy\n        } else if (groupByProperty?.type === 'updatedBy') {\n            key = item.modifiedBy\n        }\n\n        const curGroup = unique[key] ?? []\n        return {...unique, [key]: [...curGroup, item]}\n    }, {})\n\n    const hiddenGroups: BoardGroup[] = []\n    const visibleGroups: BoardGroup[] = []\n    Object.entries(groups).forEach(([key, value]) => {\n        const propertyOption = {id: key, value: key, color: ''} as IPropertyOption\n        if (hiddenOptionIds.find((e) => e === key)) {\n            hiddenGroups.push({option: propertyOption, cards: value})\n        } else {\n            visibleGroups.push({option: propertyOption, cards: value})\n        }\n    })\n\n    return {visible: visibleGroups, hidden: hiddenGroups}\n}\n"
  },
  {
    "path": "webapp/src/boardsCloudLimits/index.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nexport const LimitUnlimited = 0\n\nexport interface BoardsCloudLimits {\n    cards: number\n    used_cards: number\n    card_limit_timestamp: number\n    views: number\n}\n"
  },
  {
    "path": "webapp/src/cardFilter.test.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {mocked} from 'jest-mock'\n\nimport {createFilterClause} from './blocks/filterClause'\n\nimport {createFilterGroup} from './blocks/filterGroup'\nimport {CardFilter} from './cardFilter'\nimport {TestBlockFactory} from './test/testBlockFactory'\nimport {Utils} from './utils'\n\nimport {IPropertyTemplate} from './blocks/board'\n\njest.mock('./utils')\nconst mockedUtils = mocked(Utils, true)\n\nconst dayMillis = 24 * 60 * 60 * 1000\n\ndescribe('src/cardFilter', () => {\n    const board = TestBlockFactory.createBoard()\n    board.id = '1'\n\n    const card1 = TestBlockFactory.createCard(board)\n    card1.id = '1'\n    card1.title = 'card1'\n    card1.fields.properties.propertyId = 'Status'\n\n    card1.createAt = new Date('December 7, 2023').getTime()\n    card1.updateAt = new Date('December 7, 2023').getTime()\n    card1.deleteAt = new Date('December 7, 2023').getTime()\n    const filterClause = createFilterClause({propertyId: 'propertyId', condition: 'isNotEmpty', values: ['Status']})\n\n    describe('verify isClauseMet method', () => {\n        test('should be true with isNotEmpty clause', () => {\n            const filterClauseIsNotEmpty = createFilterClause({propertyId: 'propertyId', condition: 'isNotEmpty', values: ['Status']})\n            const result = CardFilter.isClauseMet(filterClauseIsNotEmpty, [], card1)\n            expect(result).toBeTruthy()\n        })\n        test('should be false with isEmpty clause', () => {\n            const filterClauseIsEmpty = createFilterClause({propertyId: 'propertyId', condition: 'isEmpty', values: ['Status']})\n            const result = CardFilter.isClauseMet(filterClauseIsEmpty, [], card1)\n            expect(result).toBeFalsy()\n        })\n        test('should be true with includes clause', () => {\n            const filterClauseIncludes = createFilterClause({propertyId: 'propertyId', condition: 'includes', values: ['Status']})\n            const result = CardFilter.isClauseMet(filterClauseIncludes, [], card1)\n            expect(result).toBeTruthy()\n        })\n        test('should be true with includes and no values clauses', () => {\n            const filterClauseIncludes = createFilterClause({propertyId: 'propertyId', condition: 'includes', values: []})\n            const result = CardFilter.isClauseMet(filterClauseIncludes, [], card1)\n            expect(result).toBeTruthy()\n        })\n        test('should be false with notIncludes clause', () => {\n            const filterClauseNotIncludes = createFilterClause({propertyId: 'propertyId', condition: 'notIncludes', values: ['Status']})\n            const result = CardFilter.isClauseMet(filterClauseNotIncludes, [], card1)\n            expect(result).toBeFalsy()\n        })\n        test('should be true with notIncludes and no values clauses', () => {\n            const filterClauseNotIncludes = createFilterClause({propertyId: 'propertyId', condition: 'notIncludes', values: []})\n            const result = CardFilter.isClauseMet(filterClauseNotIncludes, [], card1)\n            expect(result).toBeTruthy()\n        })\n    })\n\n    describe('verify isClauseMet method - person property', () => {\n        const personCard = TestBlockFactory.createCard(board)\n        personCard.id = '1'\n        personCard.title = 'card1'\n        personCard.fields.properties.personPropertyID = 'personid1'\n\n        const template: IPropertyTemplate = {\n            id: 'personPropertyID',\n            name: 'myPerson',\n            type: 'person',\n            options: [],\n        }\n\n        test('should be true with isNotEmpty clause', () => {\n            const filterClauseIsNotEmpty = createFilterClause({propertyId: 'personPropertyID', condition: 'isNotEmpty', values: []})\n            const result = CardFilter.isClauseMet(filterClauseIsNotEmpty, [template], personCard)\n            expect(result).toBeTruthy()\n        })\n        test('should be false with isEmpty clause', () => {\n            const filterClauseIsEmpty = createFilterClause({propertyId: 'personPropertyID', condition: 'isEmpty', values: []})\n            const result = CardFilter.isClauseMet(filterClauseIsEmpty, [template], personCard)\n            expect(result).toBeFalsy()\n        })\n        test('verify empty includes clause', () => {\n            const filterClauseIncludes = createFilterClause({propertyId: 'personPropertyID', condition: 'includes', values: []})\n            const result = CardFilter.isClauseMet(filterClauseIncludes, [template], personCard)\n            expect(result).toBeTruthy()\n        })\n        test('verify includes clause', () => {\n            const filterClauseIncludes = createFilterClause({propertyId: 'personPropertyID', condition: 'includes', values: ['personid1']})\n            const result = CardFilter.isClauseMet(filterClauseIncludes, [template], personCard)\n            expect(result).toBeTruthy()\n        })\n        test('verify includes clause multiple values', () => {\n            const filterClauseIncludes = createFilterClause({propertyId: 'personPropertyID', condition: 'includes', values: ['personid2', 'personid1']})\n            const result = CardFilter.isClauseMet(filterClauseIncludes, [template], personCard)\n            expect(result).toBeTruthy()\n        })\n        test('verify not includes clause', () => {\n            const filterClauseIncludes = createFilterClause({propertyId: 'personPropertyID', condition: 'notIncludes', values: ['personid2']})\n            const result = CardFilter.isClauseMet(filterClauseIncludes, [template], personCard)\n            expect(result).toBeTruthy()\n        })\n    })\n\n    describe('verify isClauseMet method - multi-person property', () => {\n        const personCard = TestBlockFactory.createCard(board)\n        personCard.id = '1'\n        personCard.title = 'card1'\n        personCard.fields.properties.personPropertyID = ['personid1', 'personid2']\n\n        const template: IPropertyTemplate = {\n            id: 'personPropertyID',\n            name: 'myPerson',\n            type: 'multiPerson',\n            options: [],\n        }\n\n        test('should be true with isNotEmpty clause', () => {\n            const filterClauseIsNotEmpty = createFilterClause({propertyId: 'personPropertyID', condition: 'isNotEmpty', values: []})\n            const result = CardFilter.isClauseMet(filterClauseIsNotEmpty, [template], personCard)\n            expect(result).toBeTruthy()\n        })\n        test('should be false with isEmpty clause', () => {\n            const filterClauseIsEmpty = createFilterClause({propertyId: 'personPropertyID', condition: 'isEmpty', values: []})\n            const result = CardFilter.isClauseMet(filterClauseIsEmpty, [template], personCard)\n            expect(result).toBeFalsy()\n        })\n        test('verify empty includes clause', () => {\n            const filterClauseIncludes = createFilterClause({propertyId: 'personPropertyID', condition: 'includes', values: []})\n            const result = CardFilter.isClauseMet(filterClauseIncludes, [template], personCard)\n            expect(result).toBeTruthy()\n        })\n        test('verify includes clause', () => {\n            const filterClauseIncludes = createFilterClause({propertyId: 'personPropertyID', condition: 'includes', values: ['personid1']})\n            const result = CardFilter.isClauseMet(filterClauseIncludes, [template], personCard)\n            expect(result).toBeTruthy()\n        })\n        test('verify includes clause 2', () => {\n            const filterClauseIncludes = createFilterClause({propertyId: 'personPropertyID', condition: 'includes', values: ['personid2']})\n            const result = CardFilter.isClauseMet(filterClauseIncludes, [template], personCard)\n            expect(result).toBeTruthy()\n        })\n        test('verify includes clause multiple values', () => {\n            const filterClauseIncludes = createFilterClause({propertyId: 'personPropertyID', condition: 'includes', values: ['personid3', 'personid1']})\n            const result = CardFilter.isClauseMet(filterClauseIncludes, [template], personCard)\n            expect(result).toBeTruthy()\n        })\n        test('verify includes clause multiple values 2', () => {\n            const filterClauseIncludes = createFilterClause({propertyId: 'personPropertyID', condition: 'includes', values: ['personid3', 'personid2']})\n            const result = CardFilter.isClauseMet(filterClauseIncludes, [template], personCard)\n            expect(result).toBeTruthy()\n        })\n        test('verify not includes clause', () => {\n            const filterClauseIncludes = createFilterClause({propertyId: 'personPropertyID', condition: 'notIncludes', values: ['personid3']})\n            const result = CardFilter.isClauseMet(filterClauseIncludes, [template], personCard)\n            expect(result).toBeTruthy()\n        })\n        test('verify not includes clause, multiple values', () => {\n            const filterClauseIncludes = createFilterClause({propertyId: 'personPropertyID', condition: 'notIncludes', values: ['personid3', 'personid4']})\n            const result = CardFilter.isClauseMet(filterClauseIncludes, [template], personCard)\n            expect(result).toBeTruthy()\n        })\n    })\n\n    describe('verify isClauseMet method - (createdBy) person property', () => {\n        const personCard = TestBlockFactory.createCard(board)\n        personCard.id = '1'\n        personCard.title = 'card1'\n        personCard.createdBy = 'personid1'\n\n        const template: IPropertyTemplate = {\n            id: 'personPropertyID',\n            name: 'myPerson',\n            type: 'createdBy',\n            options: [],\n        }\n\n        test('verify empty includes clause', () => {\n            const filterClauseIncludes = createFilterClause({propertyId: 'personPropertyID', condition: 'includes', values: []})\n            const result = CardFilter.isClauseMet(filterClauseIncludes, [template], personCard)\n            expect(result).toBeTruthy()\n        })\n        test('verify includes clause', () => {\n            const filterClauseIncludes = createFilterClause({propertyId: 'personPropertyID', condition: 'includes', values: ['personid1']})\n            const result = CardFilter.isClauseMet(filterClauseIncludes, [template], personCard)\n            expect(result).toBeTruthy()\n        })\n        test('verify includes clause multiple values', () => {\n            const filterClauseIncludes = createFilterClause({propertyId: 'personPropertyID', condition: 'includes', values: ['personid3', 'personid1']})\n            const result = CardFilter.isClauseMet(filterClauseIncludes, [template], personCard)\n            expect(result).toBeTruthy()\n        })\n        test('verify not includes clause', () => {\n            const filterClauseIncludes = createFilterClause({propertyId: 'personPropertyID', condition: 'notIncludes', values: ['personid2']})\n            const result = CardFilter.isClauseMet(filterClauseIncludes, [template], personCard)\n            expect(result).toBeTruthy()\n        })\n    })\n\n    describe('verify isClauseMet method - single date property', () => {\n        // Date Properties are stored as 12PM UTC.\n        const now = new Date(Date.now())\n        const propertyDate = Date.UTC(now.getFullYear(), now.getMonth(), now.getDate(), 12)\n\n        const dateCard = TestBlockFactory.createCard(board)\n        dateCard.id = '1'\n        dateCard.title = 'card1'\n        dateCard.fields.properties.datePropertyID = '{ \"from\": ' + propertyDate.toString() + ' }'\n\n        const checkDayBefore = propertyDate - dayMillis\n        const checkDayAfter = propertyDate + dayMillis\n\n        const template: IPropertyTemplate = {\n            id: 'datePropertyID',\n            name: 'myDate',\n            type: 'date',\n            options: [],\n        }\n\n        test('should be true with isSet clause', () => {\n            const filterClauseIsSet = createFilterClause({propertyId: 'datePropertyID', condition: 'isSet', values: []})\n            const result = CardFilter.isClauseMet(filterClauseIsSet, [template], dateCard)\n            expect(result).toBeTruthy()\n        })\n        test('should be false with notSet clause', () => {\n            const filterClauseIsNotSet = createFilterClause({propertyId: 'datePropertyID', condition: 'isNotSet', values: []})\n            const result = CardFilter.isClauseMet(filterClauseIsNotSet, [template], dateCard)\n            expect(result).toBeFalsy()\n        })\n        test('verify isBefore clause', () => {\n            const filterClauseIsBefore = createFilterClause({propertyId: 'datePropertyID', condition: 'isBefore', values: [checkDayAfter.toString()]})\n            const result = CardFilter.isClauseMet(filterClauseIsBefore, [template], dateCard)\n            expect(result).toBeTruthy()\n\n            const filterClauseIsNotBefore = createFilterClause({propertyId: 'datePropertyID', condition: 'isBefore', values: [checkDayBefore.toString()]})\n            const result2 = CardFilter.isClauseMet(filterClauseIsNotBefore, [template], dateCard)\n            expect(result2).toBeFalsy()\n        })\n        test('verify isAfter clauses', () => {\n            const filterClauseisAfter = createFilterClause({propertyId: 'datePropertyID', condition: 'isAfter', values: [checkDayBefore.toString()]})\n            const result = CardFilter.isClauseMet(filterClauseisAfter, [template], dateCard)\n            expect(result).toBeTruthy()\n\n            const filterClauseisNotAfter = createFilterClause({propertyId: 'datePropertyID', condition: 'isAfter', values: [checkDayAfter.toString()]})\n            const result2 = CardFilter.isClauseMet(filterClauseisNotAfter, [template], dateCard)\n            expect(result2).toBeFalsy()\n        })\n        test('verify is clause', () => {\n            const filterClauseIs = createFilterClause({propertyId: 'datePropertyID', condition: 'is', values: [propertyDate.toString()]})\n            const result = CardFilter.isClauseMet(filterClauseIs, [template], dateCard)\n            expect(result).toBeTruthy()\n\n            const filterClauseIsNot = createFilterClause({propertyId: 'datePropertyID', condition: 'is', values: [checkDayBefore.toString()]})\n            const result2 = CardFilter.isClauseMet(filterClauseIsNot, [template], dateCard)\n            expect(result2).toBeFalsy()\n        })\n    })\n\n    describe('verify isClauseMet method - date range property', () => {\n        // Date Properties are stored as 12PM UTC.\n        const now = new Date(Date.now())\n        const fromDate = Date.UTC(now.getFullYear(), now.getMonth(), now.getDate(), 12)\n        const toDate = fromDate + (2 * dayMillis)\n        const dateCard = TestBlockFactory.createCard(board)\n        dateCard.id = '1'\n        dateCard.title = 'card1'\n        dateCard.fields.properties.datePropertyID = '{ \"from\": ' + fromDate.toString() + ', \"to\": ' + toDate.toString() + ' }'\n\n        const beforeRange = fromDate - dayMillis\n        const afterRange = toDate + dayMillis\n        const inRange = fromDate + dayMillis\n\n        const template: IPropertyTemplate = {\n            id: 'datePropertyID',\n            name: 'myDate',\n            type: 'date',\n            options: [],\n        }\n\n        test('verify isBefore clause', () => {\n            const filterClauseIsBeforeEmpty = createFilterClause({propertyId: 'datePropertyID', condition: 'isBefore', values: []})\n            const resulta = CardFilter.isClauseMet(filterClauseIsBeforeEmpty, [template], dateCard)\n            expect(resulta).toBeTruthy()\n\n            const filterClauseIsBefore = createFilterClause({propertyId: 'datePropertyID', condition: 'isBefore', values: [beforeRange.toString()]})\n            const result = CardFilter.isClauseMet(filterClauseIsBefore, [template], dateCard)\n            expect(result).toBeFalsy()\n\n            const filterClauseIsInRange = createFilterClause({propertyId: 'datePropertyID', condition: 'isBefore', values: [inRange.toString()]})\n            const result2 = CardFilter.isClauseMet(filterClauseIsInRange, [template], dateCard)\n            expect(result2).toBeTruthy()\n\n            const filterClauseIsAfter = createFilterClause({propertyId: 'datePropertyID', condition: 'isBefore', values: [afterRange.toString()]})\n            const result3 = CardFilter.isClauseMet(filterClauseIsAfter, [template], dateCard)\n            expect(result3).toBeTruthy()\n        })\n\n        test('verify isAfter clauses', () => {\n            const filterClauseIsAfterEmpty = createFilterClause({propertyId: 'datePropertyID', condition: 'isBefore', values: []})\n            const resulta = CardFilter.isClauseMet(filterClauseIsAfterEmpty, [template], dateCard)\n            expect(resulta).toBeTruthy()\n\n            const filterClauseIsAfter = createFilterClause({propertyId: 'datePropertyID', condition: 'isAfter', values: [afterRange.toString()]})\n            const result = CardFilter.isClauseMet(filterClauseIsAfter, [template], dateCard)\n            expect(result).toBeFalsy()\n\n            const filterClauseIsInRange = createFilterClause({propertyId: 'datePropertyID', condition: 'isAfter', values: [inRange.toString()]})\n            const result2 = CardFilter.isClauseMet(filterClauseIsInRange, [template], dateCard)\n            expect(result2).toBeTruthy()\n\n            const filterClauseIsBefore = createFilterClause({propertyId: 'datePropertyID', condition: 'isAfter', values: [beforeRange.toString()]})\n            const result3 = CardFilter.isClauseMet(filterClauseIsBefore, [template], dateCard)\n            expect(result3).toBeTruthy()\n        })\n\n        test('verify is clause', () => {\n            const filterClauseIsEmpty = createFilterClause({propertyId: 'datePropertyID', condition: 'isBefore', values: []})\n            const resulta = CardFilter.isClauseMet(filterClauseIsEmpty, [template], dateCard)\n            expect(resulta).toBeTruthy()\n\n            const filterClauseIsBefore = createFilterClause({propertyId: 'datePropertyID', condition: 'is', values: [beforeRange.toString()]})\n            const result = CardFilter.isClauseMet(filterClauseIsBefore, [template], dateCard)\n            expect(result).toBeFalsy()\n\n            const filterClauseIsInRange = createFilterClause({propertyId: 'datePropertyID', condition: 'is', values: [inRange.toString()]})\n            const result2 = CardFilter.isClauseMet(filterClauseIsInRange, [template], dateCard)\n            expect(result2).toBeTruthy()\n\n            const filterClauseIsAfter = createFilterClause({propertyId: 'datePropertyID', condition: 'is', values: [afterRange.toString()]})\n            const result3 = CardFilter.isClauseMet(filterClauseIsAfter, [template], dateCard)\n            expect(result3).toBeFalsy()\n        })\n    })\n\n    describe('verify isClauseMet method - (createdTime) date property', () => {\n        const createDate = new Date(card1.createAt)\n        const checkDate = Date.UTC(createDate.getFullYear(), createDate.getMonth(), createDate.getDate(), createDate.getHours(), createDate.getMinutes(), createDate.getSeconds(), createDate.getMilliseconds())\n        const checkDayBefore = checkDate - dayMillis\n        const checkDayAfter = checkDate + dayMillis\n\n        const template: IPropertyTemplate = {\n            id: 'datePropertyID',\n            name: 'myDate',\n            type: 'createdTime',\n            options: [],\n        }\n\n        test('should be true with isSet clause', () => {\n            const filterClauseIsSet = createFilterClause({propertyId: 'datePropertyID', condition: 'isSet', values: []})\n            const result = CardFilter.isClauseMet(filterClauseIsSet, [template], card1)\n            expect(result).toBeTruthy()\n        })\n        test('should be false with notSet clause', () => {\n            const filterClauseIsNotSet = createFilterClause({propertyId: 'datePropertyID', condition: 'isNotSet', values: []})\n            const result = CardFilter.isClauseMet(filterClauseIsNotSet, [template], card1)\n            expect(result).toBeFalsy()\n        })\n        test('verify isBefore clause', () => {\n            const filterClauseIsBefore = createFilterClause({propertyId: 'datePropertyID', condition: 'isBefore', values: [checkDayAfter.toString()]})\n            const result = CardFilter.isClauseMet(filterClauseIsBefore, [template], card1)\n            expect(result).toBeTruthy()\n\n            const filterClauseIsNotBefore = createFilterClause({propertyId: 'datePropertyID', condition: 'isBefore', values: [checkDate.toString()]})\n            const result2 = CardFilter.isClauseMet(filterClauseIsNotBefore, [template], card1)\n            expect(result2).toBeFalsy()\n        })\n        test('verify isAfter clauses', () => {\n            const filterClauseisAfter = createFilterClause({propertyId: 'datePropertyID', condition: 'isAfter', values: [checkDayBefore.toString()]})\n            const result = CardFilter.isClauseMet(filterClauseisAfter, [template], card1)\n            expect(result).toBeTruthy()\n\n            const filterClauseisNotAfter = createFilterClause({propertyId: 'datePropertyID', condition: 'isAfter', values: [checkDate.toString()]})\n            const result2 = CardFilter.isClauseMet(filterClauseisNotAfter, [template], card1)\n            expect(result2).toBeFalsy()\n        })\n        test('verify is clause', () => {\n            // Is should find on that date regardless of time.\n            const filterClauseIs = createFilterClause({propertyId: 'datePropertyID', condition: 'is', values: [checkDate.toString()]})\n            const result = CardFilter.isClauseMet(filterClauseIs, [template], card1)\n            expect(result).toBeTruthy()\n\n            const filterClauseIsNot = createFilterClause({propertyId: 'datePropertyID', condition: 'is', values: [checkDayBefore.toString()]})\n            const result2 = CardFilter.isClauseMet(filterClauseIsNot, [template], card1)\n            expect(result2).toBeFalsy()\n\n            const filterClauseIsNot2 = createFilterClause({propertyId: 'datePropertyID', condition: 'is', values: [checkDayAfter.toString()]})\n            const result3 = CardFilter.isClauseMet(filterClauseIsNot2, [template], card1)\n            expect(result3).toBeFalsy()\n        })\n    })\n\n    describe('verify isFilterGroupMet method', () => {\n        test('should return true with no filter', () => {\n            const filterGroup = createFilterGroup({\n                operation: 'and',\n                filters: [],\n            })\n            const result = CardFilter.isFilterGroupMet(filterGroup, [], card1)\n            expect(result).toBeTruthy()\n        })\n        test('should return true with or operation and 2 filterCause, one is false ', () => {\n            const filterClauseNotIncludes = createFilterClause({propertyId: 'propertyId', condition: 'notIncludes', values: ['Status']})\n            const filterGroup = createFilterGroup({\n                operation: 'or',\n                filters: [\n                    filterClauseNotIncludes,\n                    filterClause,\n                ],\n            })\n            const result = CardFilter.isFilterGroupMet(filterGroup, [], card1)\n            expect(result).toBeTruthy()\n        })\n        test('should return true with or operation and 2 filterCause, 1 filtergroup in filtergroup, one filterClause is false ', () => {\n            const filterClauseNotIncludes = createFilterClause({propertyId: 'propertyId', condition: 'notIncludes', values: ['Status']})\n            const filterGroupInFilterGroup = createFilterGroup({\n                operation: 'or',\n                filters: [\n                    filterClauseNotIncludes,\n                    filterClause,\n                ],\n            })\n            const filterGroup = createFilterGroup({\n                operation: 'or',\n                filters: [],\n            })\n            filterGroup.filters.push(filterGroupInFilterGroup)\n            const result = CardFilter.isFilterGroupMet(filterGroup, [], card1)\n            expect(result).toBeTruthy()\n        })\n        test('should return false with or operation and two filterCause, two are false ', () => {\n            const filterClauseNotIncludes = createFilterClause({propertyId: 'propertyId', condition: 'notIncludes', values: ['Status']})\n            const filterClauseEmpty = createFilterClause({propertyId: 'propertyId', condition: 'isEmpty', values: ['Status']})\n            const filterGroup = createFilterGroup({\n                operation: 'or',\n                filters: [\n                    filterClauseNotIncludes,\n                    filterClauseEmpty,\n                ],\n            })\n            const result = CardFilter.isFilterGroupMet(filterGroup, [], card1)\n            expect(result).toBeFalsy()\n        })\n        test('should return false with and operation and 2 filterCause, one is false ', () => {\n            const filterClauseNotIncludes = createFilterClause({propertyId: 'propertyId', condition: 'notIncludes', values: ['Status']})\n            const filterGroup = createFilterGroup({\n                operation: 'and',\n                filters: [\n                    filterClauseNotIncludes,\n                    filterClause,\n                ],\n            })\n            const result = CardFilter.isFilterGroupMet(filterGroup, [], card1)\n            expect(result).toBeFalsy()\n        })\n        test('should return true with and operation and 2 filterCause, two are true ', () => {\n            const filterClauseIncludes = createFilterClause({propertyId: 'propertyId', condition: 'includes', values: ['Status']})\n            const filterGroup = createFilterGroup({\n                operation: 'and',\n                filters: [\n                    filterClauseIncludes,\n                    filterClause,\n                ],\n            })\n            const result = CardFilter.isFilterGroupMet(filterGroup, [], card1)\n            expect(result).toBeTruthy()\n        })\n        test('should return true with or operation and 2 filterCause, 1 filtergroup in filtergroup, one filterClause is false ', () => {\n            const filterClauseNotIncludes = createFilterClause({propertyId: 'propertyId', condition: 'notIncludes', values: ['Status']})\n            const filterGroupInFilterGroup = createFilterGroup({\n                operation: 'and',\n                filters: [\n                    filterClauseNotIncludes,\n                    filterClause,\n                ],\n            })\n            const filterGroup = createFilterGroup({\n                operation: 'and',\n                filters: [],\n            })\n            filterGroup.filters.push(filterGroupInFilterGroup)\n            const result = CardFilter.isFilterGroupMet(filterGroup, [], card1)\n            expect(result).toBeFalsy()\n        })\n    })\n    describe('verify propertyThatMeetsFilterClause method', () => {\n        test('should return Utils.assertFailure and filterClause propertyId ', () => {\n            const filterClauseIsNotEmpty = createFilterClause({propertyId: 'propertyId', condition: 'isNotEmpty', values: ['Status']})\n            const result = CardFilter.propertyThatMeetsFilterClause(filterClauseIsNotEmpty, [])\n            expect(mockedUtils.assertFailure).toBeCalledTimes(1)\n            expect(result.id).toEqual(filterClauseIsNotEmpty.propertyId)\n        })\n        test('should return filterClause propertyId with non-select template and isNotEmpty clause ', () => {\n            const filterClauseIsNotEmpty = createFilterClause({propertyId: 'propertyId', condition: 'isNotEmpty', values: ['Status']})\n            const templateFilter: IPropertyTemplate = {\n                id: filterClauseIsNotEmpty.propertyId,\n                name: 'template',\n                type: 'text',\n                options: [],\n            }\n            const result = CardFilter.propertyThatMeetsFilterClause(filterClauseIsNotEmpty, [templateFilter])\n            expect(result.id).toEqual(filterClauseIsNotEmpty.propertyId)\n            expect(result.value).toBeFalsy()\n        })\n        test('should return filterClause propertyId with select template , an option and isNotEmpty clause ', () => {\n            const filterClauseIsNotEmpty = createFilterClause({propertyId: 'propertyId', condition: 'isNotEmpty', values: ['Status']})\n            const templateFilter: IPropertyTemplate = {\n                id: filterClauseIsNotEmpty.propertyId,\n                name: 'template',\n                type: 'select',\n                options: [{\n                    id: 'idOption',\n                    value: '',\n                    color: '',\n                }],\n            }\n            const result = CardFilter.propertyThatMeetsFilterClause(filterClauseIsNotEmpty, [templateFilter])\n            expect(result.id).toEqual(filterClauseIsNotEmpty.propertyId)\n            expect(result.value).toEqual('idOption')\n        })\n        test('should return filterClause propertyId with select template , no option and isNotEmpty clause ', () => {\n            const filterClauseIsNotEmpty = createFilterClause({propertyId: 'propertyId', condition: 'isNotEmpty', values: ['Status']})\n            const templateFilter: IPropertyTemplate = {\n                id: filterClauseIsNotEmpty.propertyId,\n                name: 'template',\n                type: 'select',\n                options: [],\n            }\n            const result = CardFilter.propertyThatMeetsFilterClause(filterClauseIsNotEmpty, [templateFilter])\n            expect(result.id).toEqual(filterClauseIsNotEmpty.propertyId)\n            expect(result.value).toBeFalsy()\n        })\n\n        test('should return filterClause propertyId with template, and includes clause with values', () => {\n            const filterClauseIncludes = createFilterClause({propertyId: 'propertyId', condition: 'includes', values: ['Status']})\n            const templateFilter: IPropertyTemplate = {\n                id: filterClauseIncludes.propertyId,\n                name: 'template',\n                type: 'text',\n                options: [],\n            }\n            const result = CardFilter.propertyThatMeetsFilterClause(filterClauseIncludes, [templateFilter])\n            expect(result.id).toEqual(filterClauseIncludes.propertyId)\n            expect(result.value).toEqual(filterClauseIncludes.values[0])\n        })\n        test('should return filterClause propertyId with template, and includes clause with no values', () => {\n            const filterClauseIncludes = createFilterClause({propertyId: 'propertyId', condition: 'includes', values: []})\n            const templateFilter: IPropertyTemplate = {\n                id: filterClauseIncludes.propertyId,\n                name: 'template',\n                type: 'text',\n                options: [],\n            }\n            const result = CardFilter.propertyThatMeetsFilterClause(filterClauseIncludes, [templateFilter])\n            expect(result.id).toEqual(filterClauseIncludes.propertyId)\n            expect(result.value).toBeFalsy()\n        })\n        test('should return filterClause propertyId with template, and notIncludes clause', () => {\n            const filterClauseNotIncludes = createFilterClause({propertyId: 'propertyId', condition: 'notIncludes', values: []})\n            const templateFilter: IPropertyTemplate = {\n                id: filterClauseNotIncludes.propertyId,\n                name: 'template',\n                type: 'text',\n                options: [],\n            }\n            const result = CardFilter.propertyThatMeetsFilterClause(filterClauseNotIncludes, [templateFilter])\n            expect(result.id).toEqual(filterClauseNotIncludes.propertyId)\n            expect(result.value).toBeFalsy()\n        })\n        test('should return filterClause propertyId with template, and isEmpty clause', () => {\n            const filterClauseIsEmpty = createFilterClause({propertyId: 'propertyId', condition: 'isEmpty', values: []})\n            const templateFilter: IPropertyTemplate = {\n                id: filterClauseIsEmpty.propertyId,\n                name: 'template',\n                type: 'text',\n                options: [],\n            }\n            const result = CardFilter.propertyThatMeetsFilterClause(filterClauseIsEmpty, [templateFilter])\n            expect(result.id).toEqual(filterClauseIsEmpty.propertyId)\n            expect(result.value).toBeFalsy()\n        })\n    })\n    describe('verify propertyThatMeetsFilterClause method - Person properties', () => {\n        test('should return filterClause propertyId with template, and isEmpty clause', () => {\n            const filterClauseIsEmpty = createFilterClause({propertyId: 'propertyId', condition: 'is', values: []})\n            const templateFilter: IPropertyTemplate = {\n                id: filterClauseIsEmpty.propertyId,\n                name: 'template',\n                type: 'createdBy',\n                options: [],\n            }\n            const result = CardFilter.propertyThatMeetsFilterClause(filterClauseIsEmpty, [templateFilter])\n            expect(result.id).toEqual(filterClauseIsEmpty.propertyId)\n            expect(result.value).toBeFalsy()\n        })\n        test('should return filterClause propertyId with template, and isEmpty clause', () => {\n            const filterClauseIsEmpty = createFilterClause({propertyId: 'propertyId', condition: 'is', values: []})\n            const templateFilter: IPropertyTemplate = {\n                id: filterClauseIsEmpty.propertyId,\n                name: 'template',\n                type: 'createdBy',\n                options: [],\n            }\n            const result = CardFilter.propertyThatMeetsFilterClause(filterClauseIsEmpty, [templateFilter])\n            expect(result.id).toEqual(filterClauseIsEmpty.propertyId)\n            expect(result.value).toBeFalsy()\n        })\n    })\n    describe('verify propertiesThatMeetFilterGroup method', () => {\n        test('should return {} with undefined filterGroup', () => {\n            const result = CardFilter.propertiesThatMeetFilterGroup(undefined, [])\n            expect(result).toEqual({})\n        })\n        test('should return {} with filterGroup without filter', () => {\n            const filterGroup = createFilterGroup({\n                operation: 'and',\n                filters: [],\n            })\n            const result = CardFilter.propertiesThatMeetFilterGroup(filterGroup, [])\n            expect(result).toEqual({})\n        })\n        test('should return {} with filterGroup, or operation and no template', () => {\n            const filterClauseIncludes = createFilterClause({propertyId: 'propertyId', condition: 'includes', values: ['Status']})\n            const filterGroup = createFilterGroup({\n                operation: 'or',\n                filters: [\n                    filterClauseIncludes,\n                    filterClause,\n                ],\n            })\n            const result = CardFilter.propertiesThatMeetFilterGroup(filterGroup, [])\n            expect(result).toEqual({})\n        })\n        test('should return a result with filterGroup, or operation and template', () => {\n            const filterClauseIncludes = createFilterClause({propertyId: 'propertyId', condition: 'includes', values: ['Status']})\n            const filterGroup = createFilterGroup({\n                operation: 'or',\n                filters: [\n                    filterClauseIncludes,\n                    filterClause,\n                ],\n            })\n            const templateFilter: IPropertyTemplate = {\n                id: filterClauseIncludes.propertyId,\n                name: 'template',\n                type: 'text',\n                options: [],\n            }\n            const result = CardFilter.propertiesThatMeetFilterGroup(filterGroup, [templateFilter])\n            expect(result).toBeDefined()\n            expect(result.propertyId).toEqual(filterClauseIncludes.values[0])\n        })\n        test('should return {} with filterGroup, and operation and no template', () => {\n            const filterClauseIncludes = createFilterClause({propertyId: 'propertyId', condition: 'includes', values: ['Status']})\n            const filterGroup = createFilterGroup({\n                operation: 'and',\n                filters: [\n                    filterClauseIncludes,\n                    filterClause,\n                ],\n            })\n            const result = CardFilter.propertiesThatMeetFilterGroup(filterGroup, [])\n            expect(result).toEqual({})\n        })\n\n        test('should return a result with filterGroup, and operation and template', () => {\n            const filterClauseIncludes = createFilterClause({propertyId: 'propertyId', condition: 'includes', values: ['Status']})\n            const filterGroup = createFilterGroup({\n                operation: 'and',\n                filters: [\n                    filterClauseIncludes,\n                    filterClause,\n                ],\n            })\n            const templateFilter: IPropertyTemplate = {\n                id: filterClauseIncludes.propertyId,\n                name: 'template',\n                type: 'text',\n                options: [],\n            }\n            const result = CardFilter.propertiesThatMeetFilterGroup(filterGroup, [templateFilter])\n            expect(result).toBeDefined()\n            expect(result.propertyId).toEqual(filterClauseIncludes.values[0])\n        })\n    })\n    describe('verify applyFilterGroup method', () => {\n        test('should return array with card1', () => {\n            const filterClauseNotIncludes = createFilterClause({propertyId: 'propertyId', condition: 'notIncludes', values: ['Status']})\n            const filterGroup = createFilterGroup({\n                operation: 'or',\n                filters: [\n                    filterClauseNotIncludes,\n                    filterClause,\n                ],\n            })\n            const result = CardFilter.applyFilterGroup(filterGroup, [], [card1])\n            expect(result).toBeDefined()\n            expect(result[0]).toEqual(card1)\n        })\n    })\n    describe('verfiy applyFilterGroup method for case-sensitive search', () => {\n        test('should return array with card1 when search by test as Card1', () => {\n            const filterClauseNotContains = createFilterClause({propertyId: 'title', condition: 'contains', values: ['Card1']})\n            const filterGroup = createFilterGroup({\n                operation: 'and',\n                filters: [\n                    filterClauseNotContains,\n                ],\n            })\n            const result = CardFilter.applyFilterGroup(filterGroup, [], [card1])\n            expect(result.length).toEqual(1)\n        })\n    })\n    describe('verify applyFilter for title', () => {\n        test('should not return array with card1', () => {\n            const filterClauseNotContains = createFilterClause({propertyId: 'title', condition: 'notContains', values: ['card1']})\n            const filterGroup = createFilterGroup({\n                operation: 'and',\n                filters: [\n                    filterClauseNotContains,\n                ],\n            })\n            const result = CardFilter.applyFilterGroup(filterGroup, [], [card1])\n            expect(result.length).toEqual(0)\n        })\n    })\n})\n"
  },
  {
    "path": "webapp/src/cardFilter.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {DateUtils} from 'react-day-picker'\n\nimport {DateProperty} from './properties/date/date'\n\nimport {IPropertyTemplate} from './blocks/board'\nimport {Card} from './blocks/card'\nimport {FilterClause} from './blocks/filterClause'\nimport {FilterGroup, isAFilterGroupInstance} from './blocks/filterGroup'\nimport {Utils} from './utils'\n\nconst halfDay = 12 * 60 * 60 * 1000\n\nclass CardFilter {\n    static createDatePropertyFromString(initialValue: string): DateProperty {\n        let dateProperty: DateProperty = {}\n        if (initialValue) {\n            const singleDate = new Date(Number(initialValue))\n            if (singleDate && DateUtils.isDate(singleDate)) {\n                dateProperty.from = singleDate.getTime()\n            } else {\n                try {\n                    dateProperty = JSON.parse(initialValue)\n                } catch {\n                    //Don't do anything, return empty dateProperty\n                }\n            }\n        }\n        return dateProperty\n    }\n\n    static applyFilterGroup(filterGroup: FilterGroup, templates: readonly IPropertyTemplate[], cards: Card[]): Card[] {\n        return cards.filter((card) => this.isFilterGroupMet(filterGroup, templates, card))\n    }\n\n    static isFilterGroupMet(filterGroup: FilterGroup, templates: readonly IPropertyTemplate[], card: Card): boolean {\n        const {filters} = filterGroup\n\n        if (filterGroup.filters.length < 1) {\n            return true\t// No filters = always met\n        }\n\n        if (filterGroup.operation === 'or') {\n            for (const filter of filters) {\n                if (isAFilterGroupInstance(filter)) {\n                    if (this.isFilterGroupMet(filter, templates, card)) {\n                        return true\n                    }\n                } else if (this.isClauseMet(filter, templates, card)) {\n                    return true\n                }\n            }\n            return false\n        }\n        Utils.assert(filterGroup.operation === 'and')\n        for (const filter of filters) {\n            if (isAFilterGroupInstance(filter)) {\n                if (!this.isFilterGroupMet(filter, templates, card)) {\n                    return false\n                }\n            } else if (!this.isClauseMet(filter, templates, card)) {\n                return false\n            }\n        }\n        return true\n    }\n\n    static isClauseMet(filter: FilterClause, templates: readonly IPropertyTemplate[], card: Card): boolean {\n        let value = card.fields.properties[filter.propertyId]\n        if (filter.propertyId === 'title') {\n            value = card.title.toLowerCase()\n        }\n        const template = templates.find((o) => o.id === filter.propertyId)\n        let dateValue: DateProperty | undefined\n        if (template?.type === 'date') {\n            dateValue = this.createDatePropertyFromString(value as string)\n        }\n        if (!value && template) {\n            if (template.type === 'createdBy') {\n                value = card.createdBy\n            } else if (template.type === 'updatedBy') {\n                value = card.modifiedBy\n            } else if (template && template.type === 'createdTime') {\n                value = card.createAt.toString()\n                dateValue = this.createDatePropertyFromString(value as string)\n            } else if (template && template.type === 'updatedTime') {\n                value = card.updateAt.toString()\n                dateValue = this.createDatePropertyFromString(value as string)\n            }\n        }\n\n        switch (filter.condition) {\n        case 'includes': {\n            if (filter.values?.length < 1) {\n                break\n            }\t\t// No values = ignore clause (always met)\n            return (filter.values.find((cValue) => (Array.isArray(value) ? value.includes(cValue) : cValue === value)) !== undefined)\n        }\n        case 'notIncludes': {\n            if (filter.values?.length < 1) {\n                break\n            }\t\t// No values = ignore clause (always met)\n            return (filter.values.find((cValue) => (Array.isArray(value) ? value.includes(cValue) : cValue === value)) === undefined)\n        }\n        case 'isEmpty': {\n            return (value || '').length <= 0\n        }\n        case 'isNotEmpty': {\n            return (value || '').length > 0\n        }\n        case 'isSet': {\n            return Boolean(value)\n        }\n        case 'isNotSet': {\n            return !value\n        }\n        case 'is': {\n            if (filter.values.length === 0) {\n                return true\n            }\n            if (dateValue !== undefined) {\n                const numericFilter = parseInt(filter.values[0], 10)\n                if (template && (template.type === 'createdTime' || template.type === 'updatedTime')) {\n                    // createdTime and updatedTime include the time\n                    // So to check if create and/or updated \"is\" date.\n                    // Need to add and subtract 12 hours and check range\n                    if (dateValue.from) {\n                        return dateValue.from > (numericFilter - halfDay) && dateValue.from < (numericFilter + halfDay)\n                    }\n                    return false\n                }\n\n                if (dateValue.from && dateValue.to) {\n                    return dateValue.from <= numericFilter && dateValue.to >= numericFilter\n                }\n                return dateValue.from === numericFilter\n            }\n            return filter.values[0]?.toLowerCase() === value\n        }\n        case 'contains': {\n            if (filter.values.length === 0) {\n                return true\n            }\n            return (value as string || '').includes(filter.values[0]?.toLowerCase())\n        }\n        case 'notContains': {\n            if (filter.values.length === 0) {\n                return true\n            }\n            return !(value as string || '').includes(filter.values[0]?.toLowerCase())\n        }\n        case 'startsWith': {\n            if (filter.values.length === 0) {\n                return true\n            }\n            return (value as string || '').startsWith(filter.values[0]?.toLowerCase())\n        }\n        case 'notStartsWith': {\n            if (filter.values.length === 0) {\n                return true\n            }\n            return !(value as string || '').startsWith(filter.values[0]?.toLowerCase())\n        }\n        case 'endsWith': {\n            if (filter.values.length === 0) {\n                return true\n            }\n            return (value as string || '').endsWith(filter.values[0]?.toLowerCase())\n        }\n        case 'notEndsWith': {\n            if (filter.values.length === 0) {\n                return true\n            }\n            return !(value as string || '').endsWith(filter.values[0]?.toLowerCase())\n        }\n        case 'isBefore': {\n            if (filter.values.length === 0) {\n                return true\n            }\n            if (dateValue !== undefined) {\n                const numericFilter = parseInt(filter.values[0], 10)\n                if (template && (template.type === 'createdTime' || template.type === 'updatedTime')) {\n                    // createdTime and updatedTime include the time\n                    // So to check if create and/or updated \"isBefore\" date.\n                    // Need to subtract 12 hours to filter\n                    if (dateValue.from) {\n                        return dateValue.from < (numericFilter - halfDay)\n                    }\n                    return false\n                }\n\n                return dateValue.from ? dateValue.from < numericFilter : false\n            }\n            return false\n        }\n        case 'isAfter': {\n            if (filter.values.length === 0) {\n                return true\n            }\n            if (dateValue !== undefined) {\n                const numericFilter = parseInt(filter.values[0], 10)\n                if (template && (template.type === 'createdTime' || template.type === 'updatedTime')) {\n                    // createdTime and updatedTime include the time\n                    // So to check if create and/or updated \"isAfter\" date.\n                    // Need to add 12 hours to filter\n                    if (dateValue.from) {\n                        return dateValue.from > (numericFilter + halfDay)\n                    }\n                    return false\n                }\n\n                if (dateValue.to) {\n                    return dateValue.to > numericFilter\n                }\n                return dateValue.from ? dateValue.from > numericFilter : false\n            }\n            return false\n        }\n\n        default: {\n            Utils.assertFailure(`Invalid filter condition ${filter.condition}`)\n        }\n        }\n        return true\n    }\n\n    static propertiesThatMeetFilterGroup(filterGroup: FilterGroup | undefined, templates: readonly IPropertyTemplate[]): Record<string, string> {\n        // TODO: Handle filter groups\n        if (!filterGroup) {\n            return {}\n        }\n\n        const filters = filterGroup.filters.filter((o) => !isAFilterGroupInstance(o))\n        if (filters.length < 1) {\n            return {}\n        }\n\n        if (filterGroup.operation === 'or') {\n            // Just need to meet the first clause\n            const property = this.propertyThatMeetsFilterClause(filters[0] as FilterClause, templates)\n            const result: Record<string, string> = {}\n            if (property.value) {\n                result[property.id] = property.value\n            }\n            return result\n        }\n\n        // And: Need to meet all clauses\n        const result: Record<string, string> = {}\n        filters.forEach((filterClause) => {\n            const property = this.propertyThatMeetsFilterClause(filterClause as FilterClause, templates)\n            if (property.value) {\n                result[property.id] = property.value\n            }\n        })\n        return result\n    }\n\n    static propertyThatMeetsFilterClause(filterClause: FilterClause, templates: readonly IPropertyTemplate[]): { id: string, value?: string } {\n        const template = templates.find((o) => o.id === filterClause.propertyId)\n        if (!template) {\n            Utils.assertFailure(`propertyThatMeetsFilterClause. Cannot find template: ${filterClause.propertyId}`)\n            return {id: filterClause.propertyId}\n        }\n\n        if (template.type === 'createdBy' || template.type === 'updatedBy') {\n            return {id: filterClause.propertyId}\n        }\n\n        switch (filterClause.condition) {\n        case 'includes': {\n            if (filterClause.values.length < 1) {\n                return {id: filterClause.propertyId}\n            }\n            return {id: filterClause.propertyId, value: filterClause.values[0]}\n        }\n        case 'notIncludes': {\n            return {id: filterClause.propertyId}\n        }\n        case 'isEmpty': {\n            return {id: filterClause.propertyId}\n        }\n        case 'isNotEmpty': {\n            if (template.type === 'select') {\n                if (template.options.length > 0) {\n                    const option = template.options[0]\n                    return {id: filterClause.propertyId, value: option.id}\n                }\n                return {id: filterClause.propertyId}\n            }\n\n            // TODO: Handle non-select types\n            return {id: filterClause.propertyId}\n        }\n        default: {\n            // Handle filter clause that cannot be set\n            return {id: filterClause.propertyId}\n        }\n        }\n    }\n}\n\nexport {CardFilter}\n"
  },
  {
    "path": "webapp/src/components/__snapshots__/addContentMenuItem.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/addContentMenuItem return a checkbox menu item 1`] = `\n<div>\n  <div\n    aria-label=\"checkbox\"\n    class=\"MenuOption TextOption menu-option\"\n    role=\"button\"\n  >\n    <div\n      class=\"d-flex\"\n    >\n      <div\n        class=\"menu-option__icon\"\n      >\n        <svg\n          class=\"CheckIcon Icon\"\n          viewBox=\"0 0 100 100\"\n          xmlns=\"http://www.w3.org/2000/svg\"\n        >\n          <polyline\n            points=\"20,60 40,80 80,40\"\n          />\n        </svg>\n      </div>\n    </div>\n    <div\n      class=\"menu-option__content\"\n    >\n      <div\n        class=\"menu-name\"\n      >\n        checkbox\n      </div>\n    </div>\n    <div\n      class=\"noicon\"\n    />\n  </div>\n</div>\n`;\n\nexports[`components/addContentMenuItem return a divider menu item 1`] = `\n<div>\n  <div\n    aria-label=\"divider\"\n    class=\"MenuOption TextOption menu-option\"\n    role=\"button\"\n  >\n    <div\n      class=\"d-flex\"\n    >\n      <div\n        class=\"menu-option__icon\"\n      >\n        <svg\n          class=\"DividerIcon Icon\"\n          viewBox=\"0 0 448 512\"\n          xmlns=\"http://www.w3.org/2000/svg\"\n        >\n          <path\n            d=\"M 432,224 H 16 c -8.836556,0 -16,7.16344 -16,16 v 32 c 0,8.83656 7.163444,16 16,16 h 416 c 8.83656,0 16,-7.16344 16,-16 v -32 c 0,-8.83656 -7.16344,-16 -16,-16 z\"\n          />\n        </svg>\n      </div>\n    </div>\n    <div\n      class=\"menu-option__content\"\n    >\n      <div\n        class=\"menu-name\"\n      >\n        divider\n      </div>\n    </div>\n    <div\n      class=\"noicon\"\n    />\n  </div>\n</div>\n`;\n\nexports[`components/addContentMenuItem return a text menu item 1`] = `\n<div>\n  <div\n    aria-label=\"text\"\n    class=\"MenuOption TextOption menu-option\"\n    role=\"button\"\n  >\n    <div\n      class=\"d-flex\"\n    >\n      <div\n        class=\"menu-option__icon\"\n      >\n        <svg\n          class=\"TextIcon Icon\"\n          viewBox=\"0 0 448 512\"\n          xmlns=\"http://www.w3.org/2000/svg\"\n        >\n          <path\n            d=\"M432 416H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-128H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-128H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-128H16A16 16 0 0 0 0 48v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16V48a16 16 0 0 0-16-16z\"\n          />\n        </svg>\n      </div>\n    </div>\n    <div\n      class=\"menu-option__content\"\n    >\n      <div\n        class=\"menu-name\"\n      >\n        text\n      </div>\n    </div>\n    <div\n      class=\"noicon\"\n    />\n  </div>\n</div>\n`;\n\nexports[`components/addContentMenuItem return an error and empty element from unknown type 1`] = `<div />`;\n\nexports[`components/addContentMenuItem return an image menu item 1`] = `\n<div>\n  <div\n    aria-label=\"image\"\n    class=\"MenuOption TextOption menu-option\"\n    role=\"button\"\n  >\n    <div\n      class=\"d-flex\"\n    >\n      <div\n        class=\"menu-option__icon\"\n      >\n        <svg\n          class=\"ImageIcon Icon\"\n          viewBox=\"0 0 512 512\"\n          xmlns=\"http://www.w3.org/2000/svg\"\n        >\n          <path\n            d=\"M464 64H48C21.49 64 0 85.49 0 112v288c0 26.51 21.49 48 48 48h416c26.51 0 48-21.49 48-48V112c0-26.51-21.49-48-48-48zm-6 336H54a6 6 0 0 1-6-6V118a6 6 0 0 1 6-6h404a6 6 0 0 1 6 6v276a6 6 0 0 1-6 6zM128 152c-22.091 0-40 17.909-40 40s17.909 40 40 40 40-17.909 40-40-17.909-40-40-40zM96 352h320v-80l-87.515-87.515c-4.686-4.686-12.284-4.686-16.971 0L192 304l-39.515-39.515c-4.686-4.686-12.284-4.686-16.971 0L96 304v48z\"\n          />\n        </svg>\n      </div>\n    </div>\n    <div\n      class=\"menu-option__content\"\n    >\n      <div\n        class=\"menu-name\"\n      >\n        image\n      </div>\n    </div>\n    <div\n      class=\"noicon\"\n    />\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/__snapshots__/blockIconSelector.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/blockIconSelector return an icon correctly 1`] = `\n<div>\n  <div\n    class=\"IconSelector\"\n  >\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper\"\n      role=\"button\"\n    >\n      <div\n        class=\"octo-icon size-l\"\n      >\n        <span>\n          👍\n        </span>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/blockIconSelector return menu on click 1`] = `\n<div>\n  <div\n    class=\"IconSelector\"\n  >\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper override menuOpened\"\n      role=\"button\"\n    >\n      <div\n        class=\"octo-icon size-l\"\n      >\n        <span>\n          👍\n        </span>\n      </div>\n      <div\n        class=\"Menu noselect bottom \"\n      >\n        <div\n          class=\"menu-contents\"\n        >\n          <div\n            class=\"menu-options\"\n          >\n            <div>\n              <div\n                aria-label=\"Random\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-shuffle-variant\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Random\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                class=\"MenuOption SubMenuOption menu-option\"\n                id=\"pick\"\n              >\n                <div\n                  class=\"menu-option__icon\"\n                >\n                  <svg\n                    class=\"EmojiIcon Icon\"\n                    viewBox=\"0 0 496 512\"\n                    xmlns=\"http://www.w3.org/2000/svg\"\n                  >\n                    <path\n                      d=\"M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 448c-110.3 0-200-89.7-200-200S137.7 56 248 56s200 89.7 200 200-89.7 200-200 200zm-80-216c17.7 0 32-14.3 32-32s-14.3-32-32-32-32 14.3-32 32 14.3 32 32 32zm160 0c17.7 0 32-14.3 32-32s-14.3-32-32-32-32 14.3-32 32 14.3 32 32 32zm4 72.6c-20.8 25-51.5 39.4-84 39.4s-63.2-14.3-84-39.4c-8.5-10.2-23.7-11.5-33.8-3.1-10.2 8.5-11.5 23.6-3.1 33.8 30 36 74.1 56.6 120.9 56.6s90.9-20.6 120.9-56.6c8.5-10.2 7.1-25.3-3.1-33.8-10.1-8.4-25.3-7.1-33.8 3.1z\"\n                    />\n                  </svg>\n                </div>\n                <div\n                  class=\"menu-name\"\n                >\n                  Pick icon\n                </div>\n                <i\n                  class=\"CompassIcon icon-chevron-right\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Remove icon\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-trash-can-outline DeleteIcon trash-can-outline\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Remove icon\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"menu-spacer hideOnWidescreen\"\n          />\n          <div\n            class=\"menu-options hideOnWidescreen\"\n          >\n            <div\n              aria-label=\"Cancel\"\n              class=\"MenuOption TextOption menu-option menu-cancel\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Cancel\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/blockIconSelector return no element with no icon 1`] = `<div />`;\n\nexports[`components/blockIconSelector return no icon after click on remove menu 1`] = `<div />`;\n\nexports[`components/blockIconSelector return no menu in readonly 1`] = `\n<div>\n  <div\n    class=\"IconSelector\"\n  >\n    <div\n      class=\"octo-icon size-m readonly\"\n    >\n      <span>\n        👍\n      </span>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/__snapshots__/cardBadges.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/cardBadges should match snapshot 1`] = `\n<div>\n  <div\n    class=\"CardBadges \"\n  >\n    <span\n      title=\"This card has a description\"\n    >\n      <svg\n        class=\"TextIcon Icon\"\n        viewBox=\"0 0 448 512\"\n        xmlns=\"http://www.w3.org/2000/svg\"\n      >\n        <path\n          d=\"M432 416H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-128H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-128H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-128H16A16 16 0 0 0 0 48v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16V48a16 16 0 0 0-16-16z\"\n        />\n      </svg>\n    </span>\n    <span\n      title=\"Comments\"\n    >\n      <i\n        class=\"CompassIcon icon-message-text-outline MessageIcon\"\n      />\n      3\n    </span>\n    <span\n      title=\"Checkboxes\"\n    >\n      <svg\n        class=\"CheckIcon Icon\"\n        viewBox=\"0 0 100 100\"\n        xmlns=\"http://www.w3.org/2000/svg\"\n      >\n        <polyline\n          points=\"20,60 40,80 80,40\"\n        />\n      </svg>\n      3/7\n    </span>\n  </div>\n</div>\n`;\n\nexports[`components/cardBadges should match snapshot for empty card 1`] = `<div />`;\n"
  },
  {
    "path": "webapp/src/components/__snapshots__/cardDialog.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/cardDialog already following card 1`] = `\n<div>\n  <div\n    class=\"Dialog dialog-back cardDialog size--medium\"\n  >\n    <div\n      class=\"backdrop\"\n    />\n    <div\n      class=\"wrapper\"\n    >\n      <div\n        class=\"dialog\"\n        role=\"dialog\"\n      >\n        <div\n          class=\"toolbar\"\n        >\n          <div>\n            <h1\n              class=\"dialog-title\"\n            >\n              <div />\n            </h1>\n          </div>\n          <div\n            class=\"toolbar--right\"\n          >\n            <div\n              class=\"d-flex\"\n            >\n              <button\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-paperclip\"\n                />\n                <span>\n                  Attach\n                </span>\n              </button>\n            </div>\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <button\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                />\n              </button>\n            </div>\n            <button\n              aria-label=\"Close dialog\"\n              title=\"Close dialog\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-close CloseIcon\"\n              />\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"CardDetail \"\n        >\n          <div\n            class=\"IconSelector\"\n          >\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <div\n                class=\"octo-icon size-l\"\n              >\n                <span>\n                  i\n                </span>\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"EditableAreaWrap\"\n          >\n            <textarea\n              class=\"EditableArea Editable title\"\n              height=\"0\"\n              placeholder=\"Untitled\"\n              rows=\"1\"\n              spellcheck=\"true\"\n              title=\"title\"\n            >\n              title\n            </textarea>\n            <div\n              class=\"EditableAreaContainer\"\n            >\n              <textarea\n                aria-hidden=\"true\"\n                class=\"EditableAreaReference Editable title\"\n                dir=\"auto\"\n                disabled=\"\"\n                rows=\"1\"\n              >\n                title\n              </textarea>\n            </div>\n          </div>\n          <div\n            class=\"octo-propertylist CardDetailProperties\"\n          >\n            <div\n              class=\"octo-propertyname add-property\"\n            >\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  type=\"button\"\n                >\n                  <span>\n                    + Add a property\n                  </span>\n                </button>\n              </div>\n            </div>\n          </div>\n          <hr />\n          <div\n            class=\"CommentsList\"\n          >\n            <div\n              class=\"CommentsList__new\"\n            >\n              <img\n                class=\"comment-avatar\"\n              />\n              <div\n                class=\"MarkdownEditor octo-editor newcomment \"\n              >\n                <div\n                  class=\"octo-editor-preview octo-placeholder\"\n                  data-testid=\"preview-element\"\n                />\n              </div>\n              \n            </div>\n            <hr\n              class=\"CommentsList__divider\"\n            />\n          </div>\n        </div>\n        <div\n          class=\"CardDetail CardDetail--fullwidth content-blocks\"\n        >\n          <div\n            class=\"octo-content CardDetailContents\"\n          >\n            <div\n              class=\"octo-block\"\n            >\n              <div\n                class=\"octo-block-margin\"\n              />\n              <div\n                class=\"MarkdownEditor octo-editor  \"\n              >\n                <div\n                  class=\"octo-editor-preview octo-placeholder\"\n                  data-testid=\"preview-element\"\n                />\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"CardDetailContentsMenu content add-content\"\n          >\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <button\n                type=\"button\"\n              >\n                <span>\n                  Add content\n                </span>\n              </button>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/cardDialog limited card shows hidden view (no toolbar) 1`] = `\n<div>\n  <div\n    class=\"Dialog dialog-back cardDialog size--medium\"\n  >\n    <div\n      class=\"backdrop\"\n    />\n    <div\n      class=\"wrapper\"\n    >\n      <div\n        class=\"dialog\"\n        role=\"dialog\"\n      >\n        <div\n          class=\"toolbar\"\n        >\n          <div>\n            <h1\n              class=\"dialog-title\"\n            >\n              <div />\n            </h1>\n          </div>\n          <div\n            class=\"toolbar--right\"\n          >\n            <div\n              class=\"d-flex\"\n            >\n              <button\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-paperclip\"\n                />\n                <span>\n                  Attach\n                </span>\n              </button>\n            </div>\n            <button\n              aria-label=\"Close dialog\"\n              title=\"Close dialog\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-close CloseIcon\"\n              />\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"CardDetail  CardDetail--is-limited\"\n        >\n          <div\n            class=\"IconSelector\"\n          >\n            <div\n              class=\"octo-icon size-l readonly\"\n            >\n              <span>\n                i\n              </span>\n            </div>\n          </div>\n          <div\n            class=\"EditableAreaWrap\"\n          >\n            <textarea\n              class=\"EditableArea Editable readonly title\"\n              height=\"0\"\n              placeholder=\"Untitled\"\n              readonly=\"\"\n              rows=\"1\"\n              spellcheck=\"true\"\n              title=\"title\"\n            >\n              title\n            </textarea>\n            <div\n              class=\"EditableAreaContainer\"\n            >\n              <textarea\n                aria-hidden=\"true\"\n                class=\"EditableAreaReference Editable readonly title\"\n                dir=\"auto\"\n                disabled=\"\"\n                rows=\"1\"\n              >\n                title\n              </textarea>\n            </div>\n          </div>\n          <div\n            class=\"CardDetail__limited-wrapper\"\n          >\n            <span\n              class=\"CardDetail__limited-bg\"\n            >\n              <svg\n                fill=\"none\"\n                height=\"521\"\n                viewBox=\"0 0 468 521\"\n                width=\"468\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n              >\n                <rect\n                  fill=\"#3F4350\"\n                  fill-opacity=\"0.08\"\n                  height=\"32\"\n                  rx=\"4\"\n                  width=\"156\"\n                />\n                <rect\n                  fill=\"#3F4350\"\n                  fill-opacity=\"0.08\"\n                  height=\"32\"\n                  rx=\"4\"\n                  width=\"296\"\n                  x=\"172\"\n                />\n                <rect\n                  fill=\"#3F4350\"\n                  fill-opacity=\"0.08\"\n                  height=\"32\"\n                  rx=\"4\"\n                  width=\"156\"\n                  y=\"48\"\n                />\n                <rect\n                  fill=\"#3F4350\"\n                  fill-opacity=\"0.08\"\n                  height=\"32\"\n                  rx=\"4\"\n                  width=\"296\"\n                  x=\"172\"\n                  y=\"48\"\n                />\n                <rect\n                  fill=\"#3F4350\"\n                  fill-opacity=\"0.08\"\n                  height=\"32\"\n                  rx=\"4\"\n                  width=\"156\"\n                  y=\"96\"\n                />\n                <rect\n                  fill=\"#3F4350\"\n                  fill-opacity=\"0.08\"\n                  height=\"32\"\n                  rx=\"4\"\n                  width=\"296\"\n                  x=\"172\"\n                  y=\"96\"\n                />\n                <rect\n                  fill=\"#3F4350\"\n                  fill-opacity=\"0.08\"\n                  height=\"32\"\n                  rx=\"4\"\n                  width=\"156\"\n                  y=\"144\"\n                />\n                <rect\n                  fill=\"#3F4350\"\n                  fill-opacity=\"0.08\"\n                  height=\"32\"\n                  rx=\"4\"\n                  width=\"296\"\n                  x=\"172\"\n                  y=\"144\"\n                />\n                <rect\n                  fill=\"#3D3C40\"\n                  fill-opacity=\"0.16\"\n                  height=\"1\"\n                  width=\"468\"\n                  y=\"192\"\n                />\n                <rect\n                  fill=\"#3F4350\"\n                  fill-opacity=\"0.08\"\n                  height=\"32\"\n                  rx=\"4\"\n                  width=\"468\"\n                  y=\"209\"\n                />\n                <rect\n                  fill=\"#3F4350\"\n                  fill-opacity=\"0.08\"\n                  height=\"32\"\n                  rx=\"4\"\n                  width=\"468\"\n                  y=\"257\"\n                />\n                <rect\n                  fill=\"#3D3C40\"\n                  fill-opacity=\"0.16\"\n                  height=\"1\"\n                  width=\"468\"\n                  y=\"305\"\n                />\n                <rect\n                  fill=\"#3F4350\"\n                  fill-opacity=\"0.08\"\n                  height=\"199\"\n                  rx=\"4\"\n                  width=\"468\"\n                  y=\"322\"\n                />\n              </svg>\n            </span>\n            <p\n              class=\"CardDetail__limited-title\"\n            >\n              This card is hidden\n            </p>\n            <p\n              class=\"CardDetail__limited-body\"\n            >\n              Upgrade to our Professional or Enterprise plan to view archived cards, have unlimited views per boards, unlimited cards and more.\n              <br />\n              <a\n                class=\"CardDetail__limited-link\"\n                role=\"button\"\n              >\n                Learn more about our plans.\n              </a>\n            </p>\n            <button\n              type=\"button\"\n            >\n              <span>\n                Upgrade\n              </span>\n            </button>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/cardDialog return a cardDialog readonly 1`] = `\n<div>\n  <div\n    class=\"Dialog dialog-back cardDialog size--medium\"\n  >\n    <div\n      class=\"backdrop\"\n    />\n    <div\n      class=\"wrapper\"\n    >\n      <div\n        class=\"dialog\"\n        role=\"dialog\"\n      >\n        <div\n          class=\"toolbar\"\n        >\n          <div>\n            <h1\n              class=\"dialog-title\"\n            >\n              <div />\n            </h1>\n          </div>\n          <div\n            class=\"toolbar--right\"\n          >\n            <div\n              class=\"d-flex\"\n            >\n              <button\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-paperclip\"\n                />\n                <span>\n                  Attach\n                </span>\n              </button>\n            </div>\n            <button\n              aria-label=\"Close dialog\"\n              title=\"Close dialog\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-close CloseIcon\"\n              />\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"CardDetail \"\n        >\n          <div\n            class=\"IconSelector\"\n          >\n            <div\n              class=\"octo-icon size-l readonly\"\n            >\n              <span>\n                i\n              </span>\n            </div>\n          </div>\n          <div\n            class=\"EditableAreaWrap\"\n          >\n            <textarea\n              class=\"EditableArea Editable readonly title\"\n              height=\"0\"\n              placeholder=\"Untitled\"\n              readonly=\"\"\n              rows=\"1\"\n              spellcheck=\"true\"\n              title=\"title\"\n            >\n              title\n            </textarea>\n            <div\n              class=\"EditableAreaContainer\"\n            >\n              <textarea\n                aria-hidden=\"true\"\n                class=\"EditableAreaReference Editable readonly title\"\n                dir=\"auto\"\n                disabled=\"\"\n                rows=\"1\"\n              >\n                title\n              </textarea>\n            </div>\n          </div>\n          <div\n            class=\"octo-propertylist CardDetailProperties\"\n          />\n          <hr />\n          <div\n            class=\"CommentsList\"\n          />\n        </div>\n        <div\n          class=\"CardDetail CardDetail--fullwidth content-blocks\"\n        >\n          <div\n            class=\"octo-content CardDetailContents\"\n          >\n            <div\n              class=\"octo-block\"\n            >\n              <div\n                class=\"octo-block-margin\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/cardDialog return cardDialog menu content 1`] = `\n<div>\n  <div\n    class=\"Dialog dialog-back cardDialog size--medium\"\n  >\n    <div\n      class=\"backdrop\"\n    />\n    <div\n      class=\"wrapper\"\n    >\n      <div\n        class=\"dialog\"\n        role=\"dialog\"\n      >\n        <div\n          class=\"toolbar\"\n        >\n          <div>\n            <h1\n              class=\"dialog-title\"\n            >\n              <div />\n            </h1>\n          </div>\n          <div\n            class=\"toolbar--right\"\n          >\n            <div\n              class=\"d-flex\"\n            >\n              <button\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-paperclip\"\n                />\n                <span>\n                  Attach\n                </span>\n              </button>\n            </div>\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper override menuOpened\"\n              role=\"button\"\n            >\n              <button\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                />\n              </button>\n              <div\n                class=\"Menu noselect left \"\n              >\n                <div\n                  class=\"menu-contents\"\n                >\n                  <div\n                    class=\"menu-options\"\n                  >\n                    <div>\n                      <div\n                        aria-label=\"Delete\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"menu-option__icon\"\n                          >\n                            <i\n                              class=\"CompassIcon icon-trash-can-outline DeleteIcon trash-can-outline\"\n                            />\n                          </div>\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            Delete\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                    </div>\n                    <div>\n                      <div\n                        aria-label=\"Copy link\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"menu-option__icon\"\n                          >\n                            <i\n                              class=\"CompassIcon icon-link-variant LinkIcon\"\n                            />\n                          </div>\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            Copy link\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                    </div>\n                    <div>\n                      <div\n                        aria-label=\"New template from card\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"menu-option__icon\"\n                          >\n                            <i\n                              class=\"CompassIcon icon-plus\"\n                            />\n                          </div>\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            New template from card\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                    </div>\n                  </div>\n                  <div\n                    class=\"menu-spacer hideOnWidescreen\"\n                  />\n                  <div\n                    class=\"menu-options hideOnWidescreen\"\n                  >\n                    <div\n                      aria-label=\"Cancel\"\n                      class=\"MenuOption TextOption menu-option menu-cancel\"\n                      role=\"button\"\n                    >\n                      <div\n                        class=\"d-flex\"\n                      >\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                      <div\n                        class=\"menu-option__content\"\n                      >\n                        <div\n                          class=\"menu-name\"\n                        >\n                          Cancel\n                        </div>\n                      </div>\n                      <div\n                        class=\"noicon\"\n                      />\n                    </div>\n                  </div>\n                </div>\n              </div>\n            </div>\n            <button\n              aria-label=\"Close dialog\"\n              title=\"Close dialog\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-close CloseIcon\"\n              />\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"CardDetail \"\n        >\n          <div\n            class=\"IconSelector\"\n          >\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <div\n                class=\"octo-icon size-l\"\n              >\n                <span>\n                  i\n                </span>\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"EditableAreaWrap\"\n          >\n            <textarea\n              class=\"EditableArea Editable title\"\n              height=\"0\"\n              placeholder=\"Untitled\"\n              rows=\"1\"\n              spellcheck=\"true\"\n              title=\"title\"\n            >\n              title\n            </textarea>\n            <div\n              class=\"EditableAreaContainer\"\n            >\n              <textarea\n                aria-hidden=\"true\"\n                class=\"EditableAreaReference Editable title\"\n                dir=\"auto\"\n                disabled=\"\"\n                rows=\"1\"\n              >\n                title\n              </textarea>\n            </div>\n          </div>\n          <div\n            class=\"octo-propertylist CardDetailProperties\"\n          >\n            <div\n              class=\"octo-propertyname add-property\"\n            >\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  type=\"button\"\n                >\n                  <span>\n                    + Add a property\n                  </span>\n                </button>\n              </div>\n            </div>\n          </div>\n          <hr />\n          <div\n            class=\"CommentsList\"\n          >\n            <div\n              class=\"CommentsList__new\"\n            >\n              <img\n                class=\"comment-avatar\"\n              />\n              <div\n                class=\"MarkdownEditor octo-editor newcomment \"\n              >\n                <div\n                  class=\"octo-editor-preview octo-placeholder\"\n                  data-testid=\"preview-element\"\n                />\n              </div>\n              \n            </div>\n            <hr\n              class=\"CommentsList__divider\"\n            />\n          </div>\n        </div>\n        <div\n          class=\"CardDetail CardDetail--fullwidth content-blocks\"\n        >\n          <div\n            class=\"octo-content CardDetailContents\"\n          >\n            <div\n              class=\"octo-block\"\n            >\n              <div\n                class=\"octo-block-margin\"\n              />\n              <div\n                class=\"MarkdownEditor octo-editor  \"\n              >\n                <div\n                  class=\"octo-editor-preview octo-placeholder\"\n                  data-testid=\"preview-element\"\n                />\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"CardDetailContentsMenu content add-content\"\n          >\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <button\n                type=\"button\"\n              >\n                <span>\n                  Add content\n                </span>\n              </button>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/cardDialog return cardDialog menu content and cancel delete confirmation do nothing 1`] = `\n<div>\n  <div\n    class=\"Dialog dialog-back cardDialog size--medium\"\n  >\n    <div\n      class=\"backdrop\"\n    />\n    <div\n      class=\"wrapper\"\n    >\n      <div\n        class=\"dialog\"\n        role=\"dialog\"\n      >\n        <div\n          class=\"toolbar\"\n        >\n          <div>\n            <h1\n              class=\"dialog-title\"\n            >\n              <div />\n            </h1>\n          </div>\n          <div\n            class=\"toolbar--right\"\n          >\n            <div\n              class=\"d-flex\"\n            >\n              <button\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-paperclip\"\n                />\n                <span>\n                  Attach\n                </span>\n              </button>\n            </div>\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <button\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                />\n              </button>\n            </div>\n            <button\n              aria-label=\"Close dialog\"\n              title=\"Close dialog\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-close CloseIcon\"\n              />\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"CardDetail \"\n        >\n          <div\n            class=\"IconSelector\"\n          >\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <div\n                class=\"octo-icon size-l\"\n              >\n                <span>\n                  i\n                </span>\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"EditableAreaWrap\"\n          >\n            <textarea\n              class=\"EditableArea Editable title\"\n              height=\"0\"\n              placeholder=\"Untitled\"\n              rows=\"1\"\n              spellcheck=\"true\"\n              title=\"title\"\n            >\n              title\n            </textarea>\n            <div\n              class=\"EditableAreaContainer\"\n            >\n              <textarea\n                aria-hidden=\"true\"\n                class=\"EditableAreaReference Editable title\"\n                dir=\"auto\"\n                disabled=\"\"\n                rows=\"1\"\n              >\n                title\n              </textarea>\n            </div>\n          </div>\n          <div\n            class=\"octo-propertylist CardDetailProperties\"\n          >\n            <div\n              class=\"octo-propertyname add-property\"\n            >\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  type=\"button\"\n                >\n                  <span>\n                    + Add a property\n                  </span>\n                </button>\n              </div>\n            </div>\n          </div>\n          <hr />\n          <div\n            class=\"CommentsList\"\n          >\n            <div\n              class=\"CommentsList__new\"\n            >\n              <img\n                class=\"comment-avatar\"\n              />\n              <div\n                class=\"MarkdownEditor octo-editor newcomment \"\n              >\n                <div\n                  class=\"octo-editor-preview octo-placeholder\"\n                  data-testid=\"preview-element\"\n                />\n              </div>\n              \n            </div>\n            <hr\n              class=\"CommentsList__divider\"\n            />\n          </div>\n        </div>\n        <div\n          class=\"CardDetail CardDetail--fullwidth content-blocks\"\n        >\n          <div\n            class=\"octo-content CardDetailContents\"\n          >\n            <div\n              class=\"octo-block\"\n            >\n              <div\n                class=\"octo-block-margin\"\n              />\n              <div\n                class=\"MarkdownEditor octo-editor  \"\n              >\n                <div\n                  class=\"octo-editor-preview octo-placeholder\"\n                  data-testid=\"preview-element\"\n                />\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"CardDetailContentsMenu content add-content\"\n          >\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <button\n                type=\"button\"\n              >\n                <span>\n                  Add content\n                </span>\n              </button>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/cardDialog should match snapshot 1`] = `\n<div>\n  <div\n    class=\"Dialog dialog-back cardDialog size--medium\"\n  >\n    <div\n      class=\"backdrop\"\n    />\n    <div\n      class=\"wrapper\"\n    >\n      <div\n        class=\"dialog\"\n        role=\"dialog\"\n      >\n        <div\n          class=\"toolbar\"\n        >\n          <div>\n            <h1\n              class=\"dialog-title\"\n            >\n              <div />\n            </h1>\n          </div>\n          <div\n            class=\"toolbar--right\"\n          >\n            <div\n              class=\"d-flex\"\n            >\n              <button\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-paperclip\"\n                />\n                <span>\n                  Attach\n                </span>\n              </button>\n            </div>\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <button\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                />\n              </button>\n            </div>\n            <button\n              aria-label=\"Close dialog\"\n              title=\"Close dialog\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-close CloseIcon\"\n              />\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"CardDetail \"\n        >\n          <div\n            class=\"IconSelector\"\n          >\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <div\n                class=\"octo-icon size-l\"\n              >\n                <span>\n                  i\n                </span>\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"EditableAreaWrap\"\n          >\n            <textarea\n              class=\"EditableArea Editable title\"\n              height=\"0\"\n              placeholder=\"Untitled\"\n              rows=\"1\"\n              spellcheck=\"true\"\n              title=\"title\"\n            >\n              title\n            </textarea>\n            <div\n              class=\"EditableAreaContainer\"\n            >\n              <textarea\n                aria-hidden=\"true\"\n                class=\"EditableAreaReference Editable title\"\n                dir=\"auto\"\n                disabled=\"\"\n                rows=\"1\"\n              >\n                title\n              </textarea>\n            </div>\n          </div>\n          <div\n            class=\"octo-propertylist CardDetailProperties\"\n          >\n            <div\n              class=\"octo-propertyname add-property\"\n            >\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  type=\"button\"\n                >\n                  <span>\n                    + Add a property\n                  </span>\n                </button>\n              </div>\n            </div>\n          </div>\n          <hr />\n          <div\n            class=\"CommentsList\"\n          >\n            <div\n              class=\"CommentsList__new\"\n            >\n              <img\n                class=\"comment-avatar\"\n              />\n              <div\n                class=\"MarkdownEditor octo-editor newcomment \"\n              >\n                <div\n                  class=\"octo-editor-preview octo-placeholder\"\n                  data-testid=\"preview-element\"\n                />\n              </div>\n              \n            </div>\n            <hr\n              class=\"CommentsList__divider\"\n            />\n          </div>\n        </div>\n        <div\n          class=\"CardDetail CardDetail--fullwidth content-blocks\"\n        >\n          <div\n            class=\"octo-content CardDetailContents\"\n          >\n            <div\n              class=\"octo-block\"\n            >\n              <div\n                class=\"octo-block-margin\"\n              />\n              <div\n                class=\"MarkdownEditor octo-editor  \"\n              >\n                <div\n                  class=\"octo-editor-preview octo-placeholder\"\n                  data-testid=\"preview-element\"\n                />\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"CardDetailContentsMenu content add-content\"\n          >\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <button\n                type=\"button\"\n              >\n                <span>\n                  Add content\n                </span>\n              </button>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/cardDialog should match snapshot without permissions 1`] = `\n<div>\n  <div\n    class=\"Dialog dialog-back cardDialog size--medium\"\n  >\n    <div\n      class=\"backdrop\"\n    />\n    <div\n      class=\"wrapper\"\n    >\n      <div\n        class=\"dialog\"\n        role=\"dialog\"\n      >\n        <div\n          class=\"toolbar\"\n        >\n          <div>\n            <h1\n              class=\"dialog-title\"\n            >\n              <div />\n            </h1>\n          </div>\n          <div\n            class=\"toolbar--right\"\n          >\n            <div\n              class=\"d-flex\"\n            />\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <button\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                />\n              </button>\n            </div>\n            <button\n              aria-label=\"Close dialog\"\n              title=\"Close dialog\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-close CloseIcon\"\n              />\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"CardDetail \"\n        >\n          <div\n            class=\"IconSelector\"\n          >\n            <div\n              class=\"octo-icon size-l readonly\"\n            >\n              <span>\n                i\n              </span>\n            </div>\n          </div>\n          <div\n            class=\"EditableAreaWrap\"\n          >\n            <textarea\n              class=\"EditableArea Editable readonly title\"\n              height=\"0\"\n              placeholder=\"Untitled\"\n              readonly=\"\"\n              rows=\"1\"\n              spellcheck=\"true\"\n              title=\"title\"\n            >\n              title\n            </textarea>\n            <div\n              class=\"EditableAreaContainer\"\n            >\n              <textarea\n                aria-hidden=\"true\"\n                class=\"EditableAreaReference Editable readonly title\"\n                dir=\"auto\"\n                disabled=\"\"\n                rows=\"1\"\n              >\n                title\n              </textarea>\n            </div>\n          </div>\n          <div\n            class=\"octo-propertylist CardDetailProperties\"\n          />\n          <hr />\n          <div\n            class=\"CommentsList\"\n          />\n        </div>\n        <div\n          class=\"CardDetail CardDetail--fullwidth content-blocks\"\n        >\n          <div\n            class=\"octo-content CardDetailContents\"\n          >\n            <div\n              class=\"octo-block\"\n            >\n              <div\n                class=\"octo-block-margin\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/__snapshots__/centerPanel.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/centerPanel Clicking on the Hidden card count should open a dailog 1`] = `\n<div>\n  <div\n    class=\"BoardComponent\"\n  >\n    <div\n      class=\"top-head\"\n    >\n      <div\n        class=\"TopBar\"\n      >\n        <a\n          class=\"link\"\n          href=\"https://www.focalboard.com/fwlink/feedback-focalboard.html?v=1.0.0\"\n          rel=\"noreferrer\"\n          target=\"_blank\"\n        >\n          Give feedback\n        </a>\n        <a\n          href=\"https://www.focalboard.com/guide/user?utm_source=webapp\"\n          rel=\"noreferrer\"\n          target=\"_blank\"\n        >\n          <i\n            class=\"CompassIcon icon-help-circle-outline HelpIcon\"\n          />\n        </a>\n      </div>\n      <div\n        class=\"mid-head\"\n      >\n        <div\n          class=\"ViewTitle\"\n        >\n          <div\n            class=\"add-buttons add-visible\"\n          >\n            <button\n              class=\"Button emphasis--default size--xsmall\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-eye-off-outline\"\n              />\n              <span>\n                hide description\n              </span>\n            </button>\n          </div>\n          <div\n            class=\"title\"\n          >\n            <div\n              class=\"IconSelector\"\n            >\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <div\n                  class=\"octo-icon size-m\"\n                >\n                  <span>\n                    i\n                  </span>\n                </div>\n              </div>\n            </div>\n            <input\n              class=\"Editable title\"\n              placeholder=\"Untitled board\"\n              spellcheck=\"true\"\n              title=\"board title\"\n              value=\"board title\"\n            />\n          </div>\n          <div\n            class=\"description\"\n          >\n            <div\n              class=\"MarkdownEditor octo-editor  \"\n            >\n              <div\n                class=\"octo-editor-preview\"\n                data-testid=\"preview-element\"\n              />\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"shareButtonWrapper\"\n        >\n          <div\n            class=\"ShareBoardButton\"\n          >\n            <button\n              class=\"Button emphasis--primary size--medium\"\n              title=\"Share board\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-lock-outline LockOutlineIcon\"\n              />\n              <span>\n                Share\n              </span>\n            </button>\n          </div>\n        </div>\n      </div>\n      <div\n        class=\"ViewHeader\"\n      >\n        <div\n          class=\"viewSelector\"\n        >\n          <input\n            class=\"Editable \"\n            placeholder=\"Untitled View\"\n            spellcheck=\"true\"\n            title=\"view title\"\n            value=\"view title\"\n          />\n          <div>\n            <div\n              aria-label=\"View menu\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-chevron-down DropdownIcon\"\n                />\n              </button>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"octo-spacer\"\n        />\n        <div\n          aria-label=\"Properties menu\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              Properties\n            </span>\n          </button>\n        </div>\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              Group by: \n              <span\n                id=\"groupByLabel\"\n              >\n                name\n              </span>\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"ModalWrapper\"\n        >\n          <button\n            class=\"Button active\"\n            type=\"button\"\n          >\n            <span>\n              Filter\n            </span>\n          </button>\n        </div>\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button active\"\n            type=\"button\"\n          >\n            <span>\n              Sort\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"board-search-field\"\n        >\n          <i\n            class=\"CompassIcon icon-magnify board-search-icon\"\n          />\n          <input\n            class=\"Editable \"\n            placeholder=\"Search cards\"\n            value=\"\"\n          />\n        </div>\n        <div\n          class=\"ModalWrapper\"\n        >\n          <div\n            aria-label=\"View header menu\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <button\n              class=\"IconButton\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n              />\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"ButtonWithMenu\"\n        >\n          <div\n            class=\"button-text\"\n          >\n            New\n          </div>\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <div\n              class=\"button-dropdown\"\n            >\n              <i\n                class=\"CompassIcon icon-chevron-down DropdownIcon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"Table\"\n    >\n      <div\n        class=\"octo-table-body\"\n      >\n        <div\n          class=\"octo-table-header TableHeaders\"\n          id=\"mainBoardHeader\"\n        >\n          <div\n            class=\"octo-table-cell header-cell\"\n            style=\"overflow: unset; opacity: 1; width: 100px;\"\n          >\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <span\n                class=\"Label empty \"\n              >\n                Name\n              </span>\n            </div>\n            <div\n              class=\"octo-spacer\"\n            />\n            <div\n              class=\"HorizontalGrip\"\n            />\n          </div>\n        </div>\n        <div\n          class=\"table-row-container\"\n        >\n          <div\n            class=\"octo-table-group\"\n          >\n            <div\n              class=\"octo-group-header-cell expanded\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"octo-table-cell\"\n                style=\"width: 100px;\"\n              >\n                <button\n                  class=\"IconButton octo-table-cell__expand \"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-menu-right\"\n                  />\n                </button>\n                <span\n                  class=\"Label empty \"\n                  title=\"Items with an empty name property will go here. This column cannot be removed.\"\n                >\n                  No name\n                </span>\n                \n              </div>\n              <button\n                class=\"Button\"\n                type=\"button\"\n              >\n                <span>\n                  0\n                </span>\n              </button>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-plus AddIcon\"\n                />\n              </button>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-group\"\n          >\n            <div\n              class=\"octo-group-header-cell expanded\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"octo-table-cell\"\n                style=\"width: 100px;\"\n              >\n                <button\n                  class=\"IconButton octo-table-cell__expand \"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-menu-right\"\n                  />\n                </button>\n                <span\n                  class=\"Label propColorOrange \"\n                >\n                  <input\n                    class=\"Editable \"\n                    placeholder=\"New Select\"\n                    spellcheck=\"true\"\n                    title=\"Q1\"\n                    value=\"Q1\"\n                  />\n                </span>\n              </div>\n              <button\n                class=\"Button\"\n                type=\"button\"\n              >\n                <span>\n                  2\n                </span>\n              </button>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-plus AddIcon\"\n                />\n              </button>\n            </div>\n            <div\n              class=\"TableRow octo-table-row\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"action-cell octo-table-cell-btn\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-drag-vertical\"\n                  />\n                </button>\n              </div>\n              <div\n                class=\"octo-table-cell title-cell\"\n                id=\"mainBoardHeader\"\n                style=\"width: 100px;\"\n              >\n                <div\n                  class=\"octo-icontitle\"\n                >\n                  <div\n                    class=\"octo-icon\"\n                  >\n                    i\n                  </div>\n                  <input\n                    class=\"Editable \"\n                    placeholder=\"Untitled\"\n                    spellcheck=\"true\"\n                    title=\"card1\"\n                    value=\"card1\"\n                  />\n                </div>\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper optionsMenu ml-2 mr-2\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"octo-tooltip tooltip-top\"\n                    data-tooltip=\"More actions\"\n                  >\n                    <button\n                      aria-label=\"MenuBtn\"\n                      class=\"IconButton\"\n                      title=\"MenuBtn\"\n                      type=\"button\"\n                    >\n                      <i\n                        class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                      />\n                    </button>\n                  </div>\n                </div>\n                <div\n                  class=\"open-button\"\n                >\n                  <button\n                    class=\"Button\"\n                    type=\"button\"\n                  >\n                    <span>\n                      Open\n                    </span>\n                  </button>\n                </div>\n              </div>\n            </div>\n            <div\n              class=\"TableRow octo-table-row\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"action-cell octo-table-cell-btn\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-drag-vertical\"\n                  />\n                </button>\n              </div>\n              <div\n                class=\"octo-table-cell title-cell\"\n                id=\"mainBoardHeader\"\n                style=\"width: 100px;\"\n              >\n                <div\n                  class=\"octo-icontitle\"\n                >\n                  <div\n                    class=\"octo-icon\"\n                  >\n                    i\n                  </div>\n                  <input\n                    class=\"Editable \"\n                    placeholder=\"Untitled\"\n                    spellcheck=\"true\"\n                    title=\"card2\"\n                    value=\"card2\"\n                  />\n                </div>\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper optionsMenu ml-2 mr-2\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"octo-tooltip tooltip-top\"\n                    data-tooltip=\"More actions\"\n                  >\n                    <button\n                      aria-label=\"MenuBtn\"\n                      class=\"IconButton\"\n                      title=\"MenuBtn\"\n                      type=\"button\"\n                    >\n                      <i\n                        class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                      />\n                    </button>\n                  </div>\n                </div>\n                <div\n                  class=\"open-button\"\n                >\n                  <button\n                    class=\"Button\"\n                    type=\"button\"\n                  >\n                    <span>\n                      Open\n                    </span>\n                  </button>\n                </div>\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-group\"\n          >\n            <div\n              class=\"octo-group-header-cell expanded\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"octo-table-cell\"\n                style=\"width: 100px;\"\n              >\n                <button\n                  class=\"IconButton octo-table-cell__expand \"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-menu-right\"\n                  />\n                </button>\n                <span\n                  class=\"Label propColorBlue \"\n                >\n                  <input\n                    class=\"Editable \"\n                    placeholder=\"New Select\"\n                    spellcheck=\"true\"\n                    title=\"Q2\"\n                    value=\"Q2\"\n                  />\n                </span>\n              </div>\n              <button\n                class=\"Button\"\n                type=\"button\"\n              >\n                <span>\n                  0\n                </span>\n              </button>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-plus AddIcon\"\n                />\n              </button>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"octo-table-footer\"\n        />\n        <div\n          class=\"CalculationRow octo-table-row\"\n        >\n          <div\n            class=\"Calculation count octo-table-cell   \"\n            style=\"width: 100px;\"\n            tabindex=\"0\"\n          >\n            <span\n              class=\"calculationLabel\"\n            >\n              Count\n            </span>\n            <span\n              class=\"calculationValue\"\n            >\n              2\n            </span>\n          </div>\n        </div>\n      </div>\n      <div\n        class=\"HiddenCardCount\"\n      >\n        <div\n          class=\"hidden-card-title\"\n        >\n          Cards hidden\n        </div>\n        <button\n          class=\"Button\"\n          title=\"hidden-card-count\"\n          type=\"button\"\n        >\n          <span>\n            2\n          </span>\n        </button>\n      </div>\n    </div>\n    <div\n      class=\"NotificationBox\"\n    >\n      <div\n        class=\"NotificationBox__icon\"\n      >\n        <i\n          class=\"CompassIcon icon-alert-outline AlertIcon\"\n        />\n      </div>\n      <div\n        class=\"content\"\n      >\n        <p\n          class=\"title\"\n        >\n          2 cards hidden from board\n        </p>\n        Card limit reached, to view older cards, \n        <a>\n          Upgrade to a paid plan\n        </a>\n      </div>\n      <button\n        class=\"IconButton\"\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-close CloseIcon\"\n        />\n      </button>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/centerPanel return centerPanel and click on card to show card 1`] = `\n<div>\n  <div\n    class=\"BoardComponent\"\n  >\n    <div\n      class=\"top-head\"\n    >\n      <div\n        class=\"TopBar\"\n      >\n        <a\n          class=\"link\"\n          href=\"https://www.focalboard.com/fwlink/feedback-focalboard.html?v=1.0.0\"\n          rel=\"noreferrer\"\n          target=\"_blank\"\n        >\n          Give feedback\n        </a>\n        <a\n          href=\"https://www.focalboard.com/guide/user?utm_source=webapp\"\n          rel=\"noreferrer\"\n          target=\"_blank\"\n        >\n          <i\n            class=\"CompassIcon icon-help-circle-outline HelpIcon\"\n          />\n        </a>\n      </div>\n      <div\n        class=\"mid-head\"\n      >\n        <div\n          class=\"ViewTitle\"\n        >\n          <div\n            class=\"add-buttons add-visible\"\n          >\n            <button\n              class=\"Button emphasis--default size--xsmall\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-eye-off-outline\"\n              />\n              <span>\n                hide description\n              </span>\n            </button>\n          </div>\n          <div\n            class=\"title\"\n          >\n            <div\n              class=\"IconSelector\"\n            >\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <div\n                  class=\"octo-icon size-m\"\n                >\n                  <span>\n                    i\n                  </span>\n                </div>\n              </div>\n            </div>\n            <input\n              class=\"Editable title\"\n              placeholder=\"Untitled board\"\n              spellcheck=\"true\"\n              title=\"board title\"\n              value=\"board title\"\n            />\n          </div>\n          <div\n            class=\"description\"\n          >\n            <div\n              class=\"MarkdownEditor octo-editor  \"\n            >\n              <div\n                class=\"octo-editor-preview\"\n                data-testid=\"preview-element\"\n              />\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"shareButtonWrapper\"\n        >\n          <div\n            class=\"ShareBoardButton\"\n          >\n            <button\n              class=\"Button emphasis--primary size--medium\"\n              title=\"Share board\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-lock-outline LockOutlineIcon\"\n              />\n              <span>\n                Share\n              </span>\n            </button>\n          </div>\n        </div>\n      </div>\n      <div\n        class=\"ViewHeader\"\n      >\n        <div\n          class=\"viewSelector\"\n        >\n          <input\n            class=\"Editable \"\n            placeholder=\"Untitled View\"\n            spellcheck=\"true\"\n            title=\"view title\"\n            value=\"view title\"\n          />\n          <div>\n            <div\n              aria-label=\"View menu\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-chevron-down DropdownIcon\"\n                />\n              </button>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"octo-spacer\"\n        />\n        <div\n          aria-label=\"Properties menu\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              Properties\n            </span>\n          </button>\n        </div>\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              Group by: \n              <span\n                id=\"groupByLabel\"\n              >\n                name\n              </span>\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"ModalWrapper\"\n        >\n          <button\n            class=\"Button active\"\n            type=\"button\"\n          >\n            <span>\n              Filter\n            </span>\n          </button>\n        </div>\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button active\"\n            type=\"button\"\n          >\n            <span>\n              Sort\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"board-search-field\"\n        >\n          <i\n            class=\"CompassIcon icon-magnify board-search-icon\"\n          />\n          <input\n            class=\"Editable \"\n            placeholder=\"Search cards\"\n            value=\"\"\n          />\n        </div>\n        <div\n          class=\"ModalWrapper\"\n        >\n          <div\n            aria-label=\"View header menu\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <button\n              class=\"IconButton\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n              />\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"ButtonWithMenu\"\n        >\n          <div\n            class=\"button-text\"\n          >\n            New\n          </div>\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <div\n              class=\"button-dropdown\"\n            >\n              <i\n                class=\"CompassIcon icon-chevron-down DropdownIcon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"Kanban\"\n    >\n      <div\n        class=\"octo-board-header\"\n        id=\"mainBoardHeader\"\n      >\n        <div\n          class=\"octo-board-header-cell KanbanColumnHeader\"\n          draggable=\"true\"\n          style=\"opacity: 1;\"\n        >\n          <span\n            class=\"Label empty \"\n            title=\"Items with an empty name property will go here. This column cannot be removed.\"\n          >\n            No name\n          </span>\n          \n          <div\n            class=\"KanbanCalculation\"\n          >\n            <button\n              class=\"Button\"\n              title=\"0\"\n              type=\"button\"\n            >\n              <span>\n                0\n              </span>\n            </button>\n          </div>\n          <div\n            class=\"octo-spacer\"\n          />\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <button\n              class=\"IconButton\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n              />\n            </button>\n          </div>\n          <button\n            class=\"IconButton\"\n            type=\"button\"\n          >\n            <i\n              class=\"CompassIcon icon-plus AddIcon\"\n            />\n          </button>\n        </div>\n        <div\n          class=\"octo-board-header-cell KanbanColumnHeader\"\n          draggable=\"true\"\n          style=\"opacity: 1;\"\n        >\n          <span\n            class=\"Label propColorOrange \"\n          >\n            <input\n              class=\"Editable \"\n              placeholder=\"New Select\"\n              spellcheck=\"true\"\n              title=\"Q1\"\n              value=\"Q1\"\n            />\n          </span>\n          <div\n            class=\"KanbanCalculation\"\n          >\n            <button\n              class=\"Button\"\n              title=\"2\"\n              type=\"button\"\n            >\n              <span>\n                2\n              </span>\n            </button>\n          </div>\n          <div\n            class=\"octo-spacer\"\n          />\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <button\n              class=\"IconButton\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n              />\n            </button>\n          </div>\n          <button\n            class=\"IconButton\"\n            type=\"button\"\n          >\n            <i\n              class=\"CompassIcon icon-plus AddIcon\"\n            />\n          </button>\n        </div>\n        <div\n          class=\"octo-board-header-cell KanbanColumnHeader\"\n          draggable=\"true\"\n          style=\"opacity: 1;\"\n        >\n          <span\n            class=\"Label propColorBlue \"\n          >\n            <input\n              class=\"Editable \"\n              placeholder=\"New Select\"\n              spellcheck=\"true\"\n              title=\"Q2\"\n              value=\"Q2\"\n            />\n          </span>\n          <div\n            class=\"KanbanCalculation\"\n          >\n            <button\n              class=\"Button\"\n              title=\"0\"\n              type=\"button\"\n            >\n              <span>\n                0\n              </span>\n            </button>\n          </div>\n          <div\n            class=\"octo-spacer\"\n          />\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <button\n              class=\"IconButton\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n              />\n            </button>\n          </div>\n          <button\n            class=\"IconButton\"\n            type=\"button\"\n          >\n            <i\n              class=\"CompassIcon icon-plus AddIcon\"\n            />\n          </button>\n        </div>\n        <div\n          class=\"octo-board-header-cell narrow\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              + Add a group\n            </span>\n          </button>\n        </div>\n      </div>\n      <div\n        class=\"octo-board-body\"\n        id=\"mainBoardBody\"\n      >\n        <div\n          class=\"octo-board-column\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              + New\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"octo-board-column\"\n        >\n          <div\n            class=\"KanbanCard\"\n            draggable=\"true\"\n            style=\"opacity: 1;\"\n          >\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper optionsMenu\"\n              role=\"button\"\n            >\n              <button\n                class=\"IconButton CardActionsMenuIcon\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                />\n              </button>\n            </div>\n            <div\n              class=\"octo-icontitle\"\n            >\n              <div\n                class=\"octo-icon\"\n              >\n                i\n              </div>\n              <div\n                class=\"octo-titletext\"\n              >\n                card1\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"KanbanCard\"\n            draggable=\"true\"\n            style=\"opacity: 1;\"\n          >\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper optionsMenu\"\n              role=\"button\"\n            >\n              <button\n                class=\"IconButton CardActionsMenuIcon\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                />\n              </button>\n            </div>\n            <div\n              class=\"octo-icontitle\"\n            >\n              <div\n                class=\"octo-icon\"\n              >\n                i\n              </div>\n              <div\n                class=\"octo-titletext\"\n              >\n                card2\n              </div>\n            </div>\n          </div>\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              + New\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"octo-board-column\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              + New\n            </span>\n          </button>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/centerPanel return centerPanel and click on new card to edit template 1`] = `\n<div>\n  <div\n    class=\"BoardComponent\"\n  >\n    <div\n      class=\"top-head\"\n    >\n      <div\n        class=\"TopBar\"\n      >\n        <a\n          class=\"link\"\n          href=\"https://www.focalboard.com/fwlink/feedback-focalboard.html?v=1.0.0\"\n          rel=\"noreferrer\"\n          target=\"_blank\"\n        >\n          Give feedback\n        </a>\n        <a\n          href=\"https://www.focalboard.com/guide/user?utm_source=webapp\"\n          rel=\"noreferrer\"\n          target=\"_blank\"\n        >\n          <i\n            class=\"CompassIcon icon-help-circle-outline HelpIcon\"\n          />\n        </a>\n      </div>\n      <div\n        class=\"mid-head\"\n      >\n        <div\n          class=\"ViewTitle\"\n        >\n          <div\n            class=\"add-buttons add-visible\"\n          >\n            <button\n              class=\"Button emphasis--default size--xsmall\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-eye-off-outline\"\n              />\n              <span>\n                hide description\n              </span>\n            </button>\n          </div>\n          <div\n            class=\"title\"\n          >\n            <div\n              class=\"IconSelector\"\n            >\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <div\n                  class=\"octo-icon size-m\"\n                >\n                  <span>\n                    i\n                  </span>\n                </div>\n              </div>\n            </div>\n            <input\n              class=\"Editable title\"\n              placeholder=\"Untitled board\"\n              spellcheck=\"true\"\n              title=\"board title\"\n              value=\"board title\"\n            />\n          </div>\n          <div\n            class=\"description\"\n          >\n            <div\n              class=\"MarkdownEditor octo-editor  \"\n            >\n              <div\n                class=\"octo-editor-preview\"\n                data-testid=\"preview-element\"\n              />\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"shareButtonWrapper\"\n        >\n          <div\n            class=\"ShareBoardButton\"\n          >\n            <button\n              class=\"Button emphasis--primary size--medium\"\n              title=\"Share board\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-lock-outline LockOutlineIcon\"\n              />\n              <span>\n                Share\n              </span>\n            </button>\n          </div>\n        </div>\n      </div>\n      <div\n        class=\"ViewHeader\"\n      >\n        <div\n          class=\"viewSelector\"\n        >\n          <input\n            class=\"Editable \"\n            placeholder=\"Untitled View\"\n            spellcheck=\"true\"\n            title=\"view title\"\n            value=\"view title\"\n          />\n          <div>\n            <div\n              aria-label=\"View menu\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-chevron-down DropdownIcon\"\n                />\n              </button>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"octo-spacer\"\n        />\n        <div\n          aria-label=\"Properties menu\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              Properties\n            </span>\n          </button>\n        </div>\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              Group by: \n              <span\n                id=\"groupByLabel\"\n              >\n                name\n              </span>\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"ModalWrapper\"\n        >\n          <button\n            class=\"Button active\"\n            type=\"button\"\n          >\n            <span>\n              Filter\n            </span>\n          </button>\n        </div>\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button active\"\n            type=\"button\"\n          >\n            <span>\n              Sort\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"board-search-field\"\n        >\n          <i\n            class=\"CompassIcon icon-magnify board-search-icon\"\n          />\n          <input\n            class=\"Editable \"\n            placeholder=\"Search cards\"\n            value=\"\"\n          />\n        </div>\n        <div\n          class=\"ModalWrapper\"\n        >\n          <div\n            aria-label=\"View header menu\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <button\n              class=\"IconButton\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n              />\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"ButtonWithMenu\"\n        >\n          <div\n            class=\"button-text\"\n          >\n            New\n          </div>\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <div\n              class=\"button-dropdown\"\n            >\n              <i\n                class=\"CompassIcon icon-chevron-down DropdownIcon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"Table\"\n    >\n      <div\n        class=\"octo-table-body\"\n      >\n        <div\n          class=\"octo-table-header TableHeaders\"\n          id=\"mainBoardHeader\"\n        >\n          <div\n            class=\"octo-table-cell header-cell\"\n            style=\"overflow: unset; opacity: 1; width: 100px;\"\n          >\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <span\n                class=\"Label empty \"\n              >\n                Name\n              </span>\n            </div>\n            <div\n              class=\"octo-spacer\"\n            />\n            <div\n              class=\"HorizontalGrip\"\n            />\n          </div>\n        </div>\n        <div\n          class=\"table-row-container\"\n        >\n          <div\n            class=\"octo-table-group\"\n          >\n            <div\n              class=\"octo-group-header-cell expanded\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"octo-table-cell\"\n                style=\"width: 100px;\"\n              >\n                <button\n                  class=\"IconButton octo-table-cell__expand \"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-menu-right\"\n                  />\n                </button>\n                <span\n                  class=\"Label empty \"\n                  title=\"Items with an empty name property will go here. This column cannot be removed.\"\n                >\n                  No name\n                </span>\n                \n              </div>\n              <button\n                class=\"Button\"\n                type=\"button\"\n              >\n                <span>\n                  0\n                </span>\n              </button>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-plus AddIcon\"\n                />\n              </button>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-group\"\n          >\n            <div\n              class=\"octo-group-header-cell expanded\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"octo-table-cell\"\n                style=\"width: 100px;\"\n              >\n                <button\n                  class=\"IconButton octo-table-cell__expand \"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-menu-right\"\n                  />\n                </button>\n                <span\n                  class=\"Label propColorOrange \"\n                >\n                  <input\n                    class=\"Editable \"\n                    placeholder=\"New Select\"\n                    spellcheck=\"true\"\n                    title=\"Q1\"\n                    value=\"Q1\"\n                  />\n                </span>\n              </div>\n              <button\n                class=\"Button\"\n                type=\"button\"\n              >\n                <span>\n                  2\n                </span>\n              </button>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-plus AddIcon\"\n                />\n              </button>\n            </div>\n            <div\n              class=\"TableRow octo-table-row\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"action-cell octo-table-cell-btn\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-drag-vertical\"\n                  />\n                </button>\n              </div>\n              <div\n                class=\"octo-table-cell title-cell\"\n                id=\"mainBoardHeader\"\n                style=\"width: 100px;\"\n              >\n                <div\n                  class=\"octo-icontitle\"\n                >\n                  <div\n                    class=\"octo-icon\"\n                  >\n                    i\n                  </div>\n                  <input\n                    class=\"Editable \"\n                    placeholder=\"Untitled\"\n                    spellcheck=\"true\"\n                    title=\"card1\"\n                    value=\"card1\"\n                  />\n                </div>\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper optionsMenu ml-2 mr-2\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"octo-tooltip tooltip-top\"\n                    data-tooltip=\"More actions\"\n                  >\n                    <button\n                      aria-label=\"MenuBtn\"\n                      class=\"IconButton\"\n                      title=\"MenuBtn\"\n                      type=\"button\"\n                    >\n                      <i\n                        class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                      />\n                    </button>\n                  </div>\n                </div>\n                <div\n                  class=\"open-button\"\n                >\n                  <button\n                    class=\"Button\"\n                    type=\"button\"\n                  >\n                    <span>\n                      Open\n                    </span>\n                  </button>\n                </div>\n              </div>\n            </div>\n            <div\n              class=\"TableRow octo-table-row\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"action-cell octo-table-cell-btn\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-drag-vertical\"\n                  />\n                </button>\n              </div>\n              <div\n                class=\"octo-table-cell title-cell\"\n                id=\"mainBoardHeader\"\n                style=\"width: 100px;\"\n              >\n                <div\n                  class=\"octo-icontitle\"\n                >\n                  <div\n                    class=\"octo-icon\"\n                  >\n                    i\n                  </div>\n                  <input\n                    class=\"Editable \"\n                    placeholder=\"Untitled\"\n                    spellcheck=\"true\"\n                    title=\"card2\"\n                    value=\"card2\"\n                  />\n                </div>\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper optionsMenu ml-2 mr-2\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"octo-tooltip tooltip-top\"\n                    data-tooltip=\"More actions\"\n                  >\n                    <button\n                      aria-label=\"MenuBtn\"\n                      class=\"IconButton\"\n                      title=\"MenuBtn\"\n                      type=\"button\"\n                    >\n                      <i\n                        class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                      />\n                    </button>\n                  </div>\n                </div>\n                <div\n                  class=\"open-button\"\n                >\n                  <button\n                    class=\"Button\"\n                    type=\"button\"\n                  >\n                    <span>\n                      Open\n                    </span>\n                  </button>\n                </div>\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-group\"\n          >\n            <div\n              class=\"octo-group-header-cell expanded\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"octo-table-cell\"\n                style=\"width: 100px;\"\n              >\n                <button\n                  class=\"IconButton octo-table-cell__expand \"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-menu-right\"\n                  />\n                </button>\n                <span\n                  class=\"Label propColorBlue \"\n                >\n                  <input\n                    class=\"Editable \"\n                    placeholder=\"New Select\"\n                    spellcheck=\"true\"\n                    title=\"Q2\"\n                    value=\"Q2\"\n                  />\n                </span>\n              </div>\n              <button\n                class=\"Button\"\n                type=\"button\"\n              >\n                <span>\n                  0\n                </span>\n              </button>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-plus AddIcon\"\n                />\n              </button>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"octo-table-footer\"\n        />\n        <div\n          class=\"CalculationRow octo-table-row\"\n        >\n          <div\n            class=\"Calculation count octo-table-cell   \"\n            style=\"width: 100px;\"\n            tabindex=\"0\"\n          >\n            <span\n              class=\"calculationLabel\"\n            >\n              Count\n            </span>\n            <span\n              class=\"calculationValue\"\n            >\n              2\n            </span>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/centerPanel return centerPanel and press touch 1 with readonly 1`] = `\n<div>\n  <div\n    class=\"BoardComponent\"\n  >\n    <div\n      class=\"top-head\"\n    >\n      <div\n        class=\"TopBar\"\n      >\n        <a\n          class=\"link\"\n          href=\"https://www.focalboard.com/fwlink/feedback-focalboard.html?v=1.0.0\"\n          rel=\"noreferrer\"\n          target=\"_blank\"\n        >\n          Give feedback\n        </a>\n        <a\n          href=\"https://www.focalboard.com/guide/user?utm_source=webapp\"\n          rel=\"noreferrer\"\n          target=\"_blank\"\n        >\n          <i\n            class=\"CompassIcon icon-help-circle-outline HelpIcon\"\n          />\n        </a>\n      </div>\n      <div\n        class=\"mid-head\"\n      >\n        <div\n          class=\"ViewTitle\"\n        >\n          <div\n            class=\"add-buttons add-visible\"\n          />\n          <div\n            class=\"title\"\n          >\n            <div\n              class=\"IconSelector\"\n            >\n              <div\n                class=\"octo-icon size-m readonly\"\n              >\n                <span>\n                  i\n                </span>\n              </div>\n            </div>\n            <input\n              class=\"Editable readonly title\"\n              placeholder=\"Untitled board\"\n              readonly=\"\"\n              spellcheck=\"true\"\n              title=\"board title\"\n              value=\"board title\"\n            />\n          </div>\n          <div\n            class=\"description\"\n          >\n            <div\n              class=\"MarkdownEditor octo-editor  \"\n            >\n              <div\n                class=\"octo-editor-preview\"\n                data-testid=\"preview-element\"\n              />\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"shareButtonWrapper\"\n        >\n          <div\n            class=\"ShareBoardLoginButton\"\n          >\n            <button\n              class=\"Button emphasis--primary size--medium\"\n              title=\"Login\"\n              type=\"button\"\n            >\n              <span>\n                Login\n              </span>\n            </button>\n          </div>\n        </div>\n      </div>\n      <div\n        class=\"ViewHeader\"\n      >\n        <div\n          class=\"viewSelector\"\n        >\n          <input\n            class=\"Editable readonly \"\n            placeholder=\"Untitled View\"\n            readonly=\"\"\n            spellcheck=\"true\"\n            title=\"view title\"\n            value=\"view title\"\n          />\n        </div>\n        <div\n          class=\"octo-spacer\"\n        />\n        <div\n          class=\"board-search-field\"\n        >\n          <i\n            class=\"CompassIcon icon-magnify board-search-icon\"\n          />\n          <input\n            class=\"Editable \"\n            placeholder=\"Search cards\"\n            value=\"\"\n          />\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"Table\"\n    >\n      <div\n        class=\"octo-table-body\"\n      >\n        <div\n          class=\"octo-table-header TableHeaders\"\n          id=\"mainBoardHeader\"\n        >\n          <div\n            class=\"octo-table-cell header-cell\"\n            style=\"overflow: unset; opacity: 1; width: 100px;\"\n          >\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper disabled\"\n              role=\"button\"\n            >\n              <span\n                class=\"Label empty \"\n              >\n                Name\n              </span>\n            </div>\n            <div\n              class=\"octo-spacer\"\n            />\n          </div>\n        </div>\n        <div\n          class=\"table-row-container\"\n        >\n          <div\n            class=\"octo-table-group\"\n          >\n            <div\n              class=\"octo-group-header-cell expanded\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"octo-table-cell\"\n                style=\"width: 100px;\"\n              >\n                <button\n                  class=\"IconButton octo-table-cell__expand readonly\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-menu-right\"\n                  />\n                </button>\n                <span\n                  class=\"Label empty \"\n                  title=\"Items with an empty name property will go here. This column cannot be removed.\"\n                >\n                  No name\n                </span>\n                \n              </div>\n              <button\n                class=\"Button\"\n                type=\"button\"\n              >\n                <span>\n                  0\n                </span>\n              </button>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-group\"\n          >\n            <div\n              class=\"octo-group-header-cell expanded\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"octo-table-cell\"\n                style=\"width: 100px;\"\n              >\n                <button\n                  class=\"IconButton octo-table-cell__expand readonly\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-menu-right\"\n                  />\n                </button>\n                <span\n                  class=\"Label propColorOrange \"\n                >\n                  <input\n                    class=\"Editable readonly \"\n                    placeholder=\"New Select\"\n                    readonly=\"\"\n                    spellcheck=\"true\"\n                    title=\"Q1\"\n                    value=\"Q1\"\n                  />\n                </span>\n              </div>\n              <button\n                class=\"Button\"\n                type=\"button\"\n              >\n                <span>\n                  2\n                </span>\n              </button>\n            </div>\n            <div\n              class=\"TableRow octo-table-row readonly\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"action-cell octo-table-cell-btn\"\n              />\n              <div\n                class=\"octo-table-cell title-cell\"\n                id=\"mainBoardHeader\"\n                style=\"width: 100px;\"\n              >\n                <div\n                  class=\"octo-icontitle\"\n                >\n                  <div\n                    class=\"octo-icon\"\n                  >\n                    i\n                  </div>\n                  <input\n                    class=\"Editable readonly \"\n                    placeholder=\"Untitled\"\n                    readonly=\"\"\n                    spellcheck=\"true\"\n                    title=\"card1\"\n                    value=\"card1\"\n                  />\n                </div>\n                <div\n                  class=\"open-button\"\n                >\n                  <button\n                    class=\"Button\"\n                    type=\"button\"\n                  >\n                    <span>\n                      Open\n                    </span>\n                  </button>\n                </div>\n              </div>\n            </div>\n            <div\n              class=\"TableRow octo-table-row readonly\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"action-cell octo-table-cell-btn\"\n              />\n              <div\n                class=\"octo-table-cell title-cell\"\n                id=\"mainBoardHeader\"\n                style=\"width: 100px;\"\n              >\n                <div\n                  class=\"octo-icontitle\"\n                >\n                  <div\n                    class=\"octo-icon\"\n                  >\n                    i\n                  </div>\n                  <input\n                    class=\"Editable readonly \"\n                    placeholder=\"Untitled\"\n                    readonly=\"\"\n                    spellcheck=\"true\"\n                    title=\"card2\"\n                    value=\"card2\"\n                  />\n                </div>\n                <div\n                  class=\"open-button\"\n                >\n                  <button\n                    class=\"Button\"\n                    type=\"button\"\n                  >\n                    <span>\n                      Open\n                    </span>\n                  </button>\n                </div>\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-group\"\n          >\n            <div\n              class=\"octo-group-header-cell expanded\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"octo-table-cell\"\n                style=\"width: 100px;\"\n              >\n                <button\n                  class=\"IconButton octo-table-cell__expand readonly\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-menu-right\"\n                  />\n                </button>\n                <span\n                  class=\"Label propColorBlue \"\n                >\n                  <input\n                    class=\"Editable readonly \"\n                    placeholder=\"New Select\"\n                    readonly=\"\"\n                    spellcheck=\"true\"\n                    title=\"Q2\"\n                    value=\"Q2\"\n                  />\n                </span>\n              </div>\n              <button\n                class=\"Button\"\n                type=\"button\"\n              >\n                <span>\n                  0\n                </span>\n              </button>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"octo-table-footer\"\n        />\n        <div\n          class=\"CalculationRow octo-table-row\"\n        >\n          <div\n            class=\"Calculation count octo-table-cell disabled  \"\n            style=\"width: 100px;\"\n            tabindex=\"0\"\n          >\n            <span\n              class=\"calculationLabel\"\n            >\n              Count\n            </span>\n            <span\n              class=\"calculationValue\"\n            >\n              2\n            </span>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/centerPanel return centerPanel and press touch ctrl+d for one card selected 1`] = `\n<div>\n  <div\n    class=\"BoardComponent\"\n  >\n    <div\n      class=\"top-head\"\n    >\n      <div\n        class=\"TopBar\"\n      >\n        <a\n          class=\"link\"\n          href=\"https://www.focalboard.com/fwlink/feedback-focalboard.html?v=1.0.0\"\n          rel=\"noreferrer\"\n          target=\"_blank\"\n        >\n          Give feedback\n        </a>\n        <a\n          href=\"https://www.focalboard.com/guide/user?utm_source=webapp\"\n          rel=\"noreferrer\"\n          target=\"_blank\"\n        >\n          <i\n            class=\"CompassIcon icon-help-circle-outline HelpIcon\"\n          />\n        </a>\n      </div>\n      <div\n        class=\"mid-head\"\n      >\n        <div\n          class=\"ViewTitle\"\n        >\n          <div\n            class=\"add-buttons add-visible\"\n          >\n            <button\n              class=\"Button emphasis--default size--xsmall\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-eye-off-outline\"\n              />\n              <span>\n                hide description\n              </span>\n            </button>\n          </div>\n          <div\n            class=\"title\"\n          >\n            <div\n              class=\"IconSelector\"\n            >\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <div\n                  class=\"octo-icon size-m\"\n                >\n                  <span>\n                    i\n                  </span>\n                </div>\n              </div>\n            </div>\n            <input\n              class=\"Editable title\"\n              placeholder=\"Untitled board\"\n              spellcheck=\"true\"\n              title=\"board title\"\n              value=\"board title\"\n            />\n          </div>\n          <div\n            class=\"description\"\n          >\n            <div\n              class=\"MarkdownEditor octo-editor  \"\n            >\n              <div\n                class=\"octo-editor-preview\"\n                data-testid=\"preview-element\"\n              />\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"shareButtonWrapper\"\n        >\n          <div\n            class=\"ShareBoardButton\"\n          >\n            <button\n              class=\"Button emphasis--primary size--medium\"\n              title=\"Share board\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-lock-outline LockOutlineIcon\"\n              />\n              <span>\n                Share\n              </span>\n            </button>\n          </div>\n        </div>\n      </div>\n      <div\n        class=\"ViewHeader\"\n      >\n        <div\n          class=\"viewSelector\"\n        >\n          <input\n            class=\"Editable \"\n            placeholder=\"Untitled View\"\n            spellcheck=\"true\"\n            title=\"view title\"\n            value=\"view title\"\n          />\n          <div>\n            <div\n              aria-label=\"View menu\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-chevron-down DropdownIcon\"\n                />\n              </button>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"octo-spacer\"\n        />\n        <div\n          aria-label=\"Properties menu\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              Properties\n            </span>\n          </button>\n        </div>\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              Group by: \n              <span\n                id=\"groupByLabel\"\n              >\n                name\n              </span>\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"ModalWrapper\"\n        >\n          <button\n            class=\"Button active\"\n            type=\"button\"\n          >\n            <span>\n              Filter\n            </span>\n          </button>\n        </div>\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button active\"\n            type=\"button\"\n          >\n            <span>\n              Sort\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"board-search-field\"\n        >\n          <i\n            class=\"CompassIcon icon-magnify board-search-icon\"\n          />\n          <input\n            class=\"Editable \"\n            placeholder=\"Search cards\"\n            value=\"\"\n          />\n        </div>\n        <div\n          class=\"ModalWrapper\"\n        >\n          <div\n            aria-label=\"View header menu\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <button\n              class=\"IconButton\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n              />\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"ButtonWithMenu\"\n        >\n          <div\n            class=\"button-text\"\n          >\n            New\n          </div>\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <div\n              class=\"button-dropdown\"\n            >\n              <i\n                class=\"CompassIcon icon-chevron-down DropdownIcon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"Table\"\n    >\n      <div\n        class=\"octo-table-body\"\n      >\n        <div\n          class=\"octo-table-header TableHeaders\"\n          id=\"mainBoardHeader\"\n        >\n          <div\n            class=\"octo-table-cell header-cell\"\n            style=\"overflow: unset; opacity: 1; width: 100px;\"\n          >\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <span\n                class=\"Label empty \"\n              >\n                Name\n              </span>\n            </div>\n            <div\n              class=\"octo-spacer\"\n            />\n            <div\n              class=\"HorizontalGrip\"\n            />\n          </div>\n        </div>\n        <div\n          class=\"table-row-container\"\n        >\n          <div\n            class=\"octo-table-group\"\n          >\n            <div\n              class=\"octo-group-header-cell expanded\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"octo-table-cell\"\n                style=\"width: 100px;\"\n              >\n                <button\n                  class=\"IconButton octo-table-cell__expand \"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-menu-right\"\n                  />\n                </button>\n                <span\n                  class=\"Label empty \"\n                  title=\"Items with an empty name property will go here. This column cannot be removed.\"\n                >\n                  No name\n                </span>\n                \n              </div>\n              <button\n                class=\"Button\"\n                type=\"button\"\n              >\n                <span>\n                  0\n                </span>\n              </button>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-plus AddIcon\"\n                />\n              </button>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-group\"\n          >\n            <div\n              class=\"octo-group-header-cell expanded\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"octo-table-cell\"\n                style=\"width: 100px;\"\n              >\n                <button\n                  class=\"IconButton octo-table-cell__expand \"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-menu-right\"\n                  />\n                </button>\n                <span\n                  class=\"Label propColorOrange \"\n                >\n                  <input\n                    class=\"Editable \"\n                    placeholder=\"New Select\"\n                    spellcheck=\"true\"\n                    title=\"Q1\"\n                    value=\"Q1\"\n                  />\n                </span>\n              </div>\n              <button\n                class=\"Button\"\n                type=\"button\"\n              >\n                <span>\n                  2\n                </span>\n              </button>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-plus AddIcon\"\n                />\n              </button>\n            </div>\n            <div\n              class=\"TableRow octo-table-row selected\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"action-cell octo-table-cell-btn\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-drag-vertical\"\n                  />\n                </button>\n              </div>\n              <div\n                class=\"octo-table-cell title-cell\"\n                id=\"mainBoardHeader\"\n                style=\"width: 100px;\"\n              >\n                <div\n                  class=\"octo-icontitle\"\n                >\n                  <div\n                    class=\"octo-icon\"\n                  >\n                    i\n                  </div>\n                  <input\n                    class=\"Editable \"\n                    placeholder=\"Untitled\"\n                    spellcheck=\"true\"\n                    title=\"card1\"\n                    value=\"card1\"\n                  />\n                </div>\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper optionsMenu ml-2 mr-2\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"octo-tooltip tooltip-top\"\n                    data-tooltip=\"More actions\"\n                  >\n                    <button\n                      aria-label=\"MenuBtn\"\n                      class=\"IconButton\"\n                      title=\"MenuBtn\"\n                      type=\"button\"\n                    >\n                      <i\n                        class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                      />\n                    </button>\n                  </div>\n                </div>\n                <div\n                  class=\"open-button\"\n                >\n                  <button\n                    class=\"Button\"\n                    type=\"button\"\n                  >\n                    <span>\n                      Open\n                    </span>\n                  </button>\n                </div>\n              </div>\n            </div>\n            <div\n              class=\"TableRow octo-table-row\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"action-cell octo-table-cell-btn\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-drag-vertical\"\n                  />\n                </button>\n              </div>\n              <div\n                class=\"octo-table-cell title-cell\"\n                id=\"mainBoardHeader\"\n                style=\"width: 100px;\"\n              >\n                <div\n                  class=\"octo-icontitle\"\n                >\n                  <div\n                    class=\"octo-icon\"\n                  >\n                    i\n                  </div>\n                  <input\n                    class=\"Editable \"\n                    placeholder=\"Untitled\"\n                    spellcheck=\"true\"\n                    title=\"card2\"\n                    value=\"card2\"\n                  />\n                </div>\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper optionsMenu ml-2 mr-2\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"octo-tooltip tooltip-top\"\n                    data-tooltip=\"More actions\"\n                  >\n                    <button\n                      aria-label=\"MenuBtn\"\n                      class=\"IconButton\"\n                      title=\"MenuBtn\"\n                      type=\"button\"\n                    >\n                      <i\n                        class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                      />\n                    </button>\n                  </div>\n                </div>\n                <div\n                  class=\"open-button\"\n                >\n                  <button\n                    class=\"Button\"\n                    type=\"button\"\n                  >\n                    <span>\n                      Open\n                    </span>\n                  </button>\n                </div>\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-group\"\n          >\n            <div\n              class=\"octo-group-header-cell expanded\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"octo-table-cell\"\n                style=\"width: 100px;\"\n              >\n                <button\n                  class=\"IconButton octo-table-cell__expand \"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-menu-right\"\n                  />\n                </button>\n                <span\n                  class=\"Label propColorBlue \"\n                >\n                  <input\n                    class=\"Editable \"\n                    placeholder=\"New Select\"\n                    spellcheck=\"true\"\n                    title=\"Q2\"\n                    value=\"Q2\"\n                  />\n                </span>\n              </div>\n              <button\n                class=\"Button\"\n                type=\"button\"\n              >\n                <span>\n                  0\n                </span>\n              </button>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-plus AddIcon\"\n                />\n              </button>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"octo-table-footer\"\n        />\n        <div\n          class=\"CalculationRow octo-table-row\"\n        >\n          <div\n            class=\"Calculation count octo-table-cell   \"\n            style=\"width: 100px;\"\n            tabindex=\"0\"\n          >\n            <span\n              class=\"calculationLabel\"\n            >\n              Count\n            </span>\n            <span\n              class=\"calculationValue\"\n            >\n              2\n            </span>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/centerPanel return centerPanel and press touch del for one card selected 1`] = `\n<div>\n  <div\n    class=\"BoardComponent\"\n  >\n    <div\n      class=\"top-head\"\n    >\n      <div\n        class=\"TopBar\"\n      >\n        <a\n          class=\"link\"\n          href=\"https://www.focalboard.com/fwlink/feedback-focalboard.html?v=1.0.0\"\n          rel=\"noreferrer\"\n          target=\"_blank\"\n        >\n          Give feedback\n        </a>\n        <a\n          href=\"https://www.focalboard.com/guide/user?utm_source=webapp\"\n          rel=\"noreferrer\"\n          target=\"_blank\"\n        >\n          <i\n            class=\"CompassIcon icon-help-circle-outline HelpIcon\"\n          />\n        </a>\n      </div>\n      <div\n        class=\"mid-head\"\n      >\n        <div\n          class=\"ViewTitle\"\n        >\n          <div\n            class=\"add-buttons add-visible\"\n          >\n            <button\n              class=\"Button emphasis--default size--xsmall\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-eye-off-outline\"\n              />\n              <span>\n                hide description\n              </span>\n            </button>\n          </div>\n          <div\n            class=\"title\"\n          >\n            <div\n              class=\"IconSelector\"\n            >\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <div\n                  class=\"octo-icon size-m\"\n                >\n                  <span>\n                    i\n                  </span>\n                </div>\n              </div>\n            </div>\n            <input\n              class=\"Editable title\"\n              placeholder=\"Untitled board\"\n              spellcheck=\"true\"\n              title=\"board title\"\n              value=\"board title\"\n            />\n          </div>\n          <div\n            class=\"description\"\n          >\n            <div\n              class=\"MarkdownEditor octo-editor  \"\n            >\n              <div\n                class=\"octo-editor-preview\"\n                data-testid=\"preview-element\"\n              />\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"shareButtonWrapper\"\n        >\n          <div\n            class=\"ShareBoardButton\"\n          >\n            <button\n              class=\"Button emphasis--primary size--medium\"\n              title=\"Share board\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-lock-outline LockOutlineIcon\"\n              />\n              <span>\n                Share\n              </span>\n            </button>\n          </div>\n        </div>\n      </div>\n      <div\n        class=\"ViewHeader\"\n      >\n        <div\n          class=\"viewSelector\"\n        >\n          <input\n            class=\"Editable \"\n            placeholder=\"Untitled View\"\n            spellcheck=\"true\"\n            title=\"view title\"\n            value=\"view title\"\n          />\n          <div>\n            <div\n              aria-label=\"View menu\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-chevron-down DropdownIcon\"\n                />\n              </button>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"octo-spacer\"\n        />\n        <div\n          aria-label=\"Properties menu\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              Properties\n            </span>\n          </button>\n        </div>\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              Group by: \n              <span\n                id=\"groupByLabel\"\n              >\n                name\n              </span>\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"ModalWrapper\"\n        >\n          <button\n            class=\"Button active\"\n            type=\"button\"\n          >\n            <span>\n              Filter\n            </span>\n          </button>\n        </div>\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button active\"\n            type=\"button\"\n          >\n            <span>\n              Sort\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"board-search-field\"\n        >\n          <i\n            class=\"CompassIcon icon-magnify board-search-icon\"\n          />\n          <input\n            class=\"Editable \"\n            placeholder=\"Search cards\"\n            value=\"\"\n          />\n        </div>\n        <div\n          class=\"ModalWrapper\"\n        >\n          <div\n            aria-label=\"View header menu\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <button\n              class=\"IconButton\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n              />\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"ButtonWithMenu\"\n        >\n          <div\n            class=\"button-text\"\n          >\n            New\n          </div>\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <div\n              class=\"button-dropdown\"\n            >\n              <i\n                class=\"CompassIcon icon-chevron-down DropdownIcon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"Table\"\n    >\n      <div\n        class=\"octo-table-body\"\n      >\n        <div\n          class=\"octo-table-header TableHeaders\"\n          id=\"mainBoardHeader\"\n        >\n          <div\n            class=\"octo-table-cell header-cell\"\n            style=\"overflow: unset; opacity: 1; width: 100px;\"\n          >\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <span\n                class=\"Label empty \"\n              >\n                Name\n              </span>\n            </div>\n            <div\n              class=\"octo-spacer\"\n            />\n            <div\n              class=\"HorizontalGrip\"\n            />\n          </div>\n        </div>\n        <div\n          class=\"table-row-container\"\n        >\n          <div\n            class=\"octo-table-group\"\n          >\n            <div\n              class=\"octo-group-header-cell expanded\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"octo-table-cell\"\n                style=\"width: 100px;\"\n              >\n                <button\n                  class=\"IconButton octo-table-cell__expand \"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-menu-right\"\n                  />\n                </button>\n                <span\n                  class=\"Label empty \"\n                  title=\"Items with an empty name property will go here. This column cannot be removed.\"\n                >\n                  No name\n                </span>\n                \n              </div>\n              <button\n                class=\"Button\"\n                type=\"button\"\n              >\n                <span>\n                  0\n                </span>\n              </button>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-plus AddIcon\"\n                />\n              </button>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-group\"\n          >\n            <div\n              class=\"octo-group-header-cell expanded\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"octo-table-cell\"\n                style=\"width: 100px;\"\n              >\n                <button\n                  class=\"IconButton octo-table-cell__expand \"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-menu-right\"\n                  />\n                </button>\n                <span\n                  class=\"Label propColorOrange \"\n                >\n                  <input\n                    class=\"Editable \"\n                    placeholder=\"New Select\"\n                    spellcheck=\"true\"\n                    title=\"Q1\"\n                    value=\"Q1\"\n                  />\n                </span>\n              </div>\n              <button\n                class=\"Button\"\n                type=\"button\"\n              >\n                <span>\n                  2\n                </span>\n              </button>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-plus AddIcon\"\n                />\n              </button>\n            </div>\n            <div\n              class=\"TableRow octo-table-row selected\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"action-cell octo-table-cell-btn\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-drag-vertical\"\n                  />\n                </button>\n              </div>\n              <div\n                class=\"octo-table-cell title-cell\"\n                id=\"mainBoardHeader\"\n                style=\"width: 100px;\"\n              >\n                <div\n                  class=\"octo-icontitle\"\n                >\n                  <div\n                    class=\"octo-icon\"\n                  >\n                    i\n                  </div>\n                  <input\n                    class=\"Editable \"\n                    placeholder=\"Untitled\"\n                    spellcheck=\"true\"\n                    title=\"card1\"\n                    value=\"card1\"\n                  />\n                </div>\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper optionsMenu ml-2 mr-2\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"octo-tooltip tooltip-top\"\n                    data-tooltip=\"More actions\"\n                  >\n                    <button\n                      aria-label=\"MenuBtn\"\n                      class=\"IconButton\"\n                      title=\"MenuBtn\"\n                      type=\"button\"\n                    >\n                      <i\n                        class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                      />\n                    </button>\n                  </div>\n                </div>\n                <div\n                  class=\"open-button\"\n                >\n                  <button\n                    class=\"Button\"\n                    type=\"button\"\n                  >\n                    <span>\n                      Open\n                    </span>\n                  </button>\n                </div>\n              </div>\n            </div>\n            <div\n              class=\"TableRow octo-table-row\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"action-cell octo-table-cell-btn\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-drag-vertical\"\n                  />\n                </button>\n              </div>\n              <div\n                class=\"octo-table-cell title-cell\"\n                id=\"mainBoardHeader\"\n                style=\"width: 100px;\"\n              >\n                <div\n                  class=\"octo-icontitle\"\n                >\n                  <div\n                    class=\"octo-icon\"\n                  >\n                    i\n                  </div>\n                  <input\n                    class=\"Editable \"\n                    placeholder=\"Untitled\"\n                    spellcheck=\"true\"\n                    title=\"card2\"\n                    value=\"card2\"\n                  />\n                </div>\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper optionsMenu ml-2 mr-2\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"octo-tooltip tooltip-top\"\n                    data-tooltip=\"More actions\"\n                  >\n                    <button\n                      aria-label=\"MenuBtn\"\n                      class=\"IconButton\"\n                      title=\"MenuBtn\"\n                      type=\"button\"\n                    >\n                      <i\n                        class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                      />\n                    </button>\n                  </div>\n                </div>\n                <div\n                  class=\"open-button\"\n                >\n                  <button\n                    class=\"Button\"\n                    type=\"button\"\n                  >\n                    <span>\n                      Open\n                    </span>\n                  </button>\n                </div>\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-group\"\n          >\n            <div\n              class=\"octo-group-header-cell expanded\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"octo-table-cell\"\n                style=\"width: 100px;\"\n              >\n                <button\n                  class=\"IconButton octo-table-cell__expand \"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-menu-right\"\n                  />\n                </button>\n                <span\n                  class=\"Label propColorBlue \"\n                >\n                  <input\n                    class=\"Editable \"\n                    placeholder=\"New Select\"\n                    spellcheck=\"true\"\n                    title=\"Q2\"\n                    value=\"Q2\"\n                  />\n                </span>\n              </div>\n              <button\n                class=\"Button\"\n                type=\"button\"\n              >\n                <span>\n                  0\n                </span>\n              </button>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-plus AddIcon\"\n                />\n              </button>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"octo-table-footer\"\n        />\n        <div\n          class=\"CalculationRow octo-table-row\"\n        >\n          <div\n            class=\"Calculation count octo-table-cell   \"\n            style=\"width: 100px;\"\n            tabindex=\"0\"\n          >\n            <span\n              class=\"calculationLabel\"\n            >\n              Count\n            </span>\n            <span\n              class=\"calculationValue\"\n            >\n              2\n            </span>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/centerPanel return centerPanel and press touch esc for one card selected 1`] = `\n<div>\n  <div\n    class=\"BoardComponent\"\n  >\n    <div\n      class=\"top-head\"\n    >\n      <div\n        class=\"TopBar\"\n      >\n        <a\n          class=\"link\"\n          href=\"https://www.focalboard.com/fwlink/feedback-focalboard.html?v=1.0.0\"\n          rel=\"noreferrer\"\n          target=\"_blank\"\n        >\n          Give feedback\n        </a>\n        <a\n          href=\"https://www.focalboard.com/guide/user?utm_source=webapp\"\n          rel=\"noreferrer\"\n          target=\"_blank\"\n        >\n          <i\n            class=\"CompassIcon icon-help-circle-outline HelpIcon\"\n          />\n        </a>\n      </div>\n      <div\n        class=\"mid-head\"\n      >\n        <div\n          class=\"ViewTitle\"\n        >\n          <div\n            class=\"add-buttons add-visible\"\n          >\n            <button\n              class=\"Button emphasis--default size--xsmall\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-eye-off-outline\"\n              />\n              <span>\n                hide description\n              </span>\n            </button>\n          </div>\n          <div\n            class=\"title\"\n          >\n            <div\n              class=\"IconSelector\"\n            >\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <div\n                  class=\"octo-icon size-m\"\n                >\n                  <span>\n                    i\n                  </span>\n                </div>\n              </div>\n            </div>\n            <input\n              class=\"Editable title\"\n              placeholder=\"Untitled board\"\n              spellcheck=\"true\"\n              title=\"board title\"\n              value=\"board title\"\n            />\n          </div>\n          <div\n            class=\"description\"\n          >\n            <div\n              class=\"MarkdownEditor octo-editor  \"\n            >\n              <div\n                class=\"octo-editor-preview\"\n                data-testid=\"preview-element\"\n              />\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"shareButtonWrapper\"\n        >\n          <div\n            class=\"ShareBoardButton\"\n          >\n            <button\n              class=\"Button emphasis--primary size--medium\"\n              title=\"Share board\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-lock-outline LockOutlineIcon\"\n              />\n              <span>\n                Share\n              </span>\n            </button>\n          </div>\n        </div>\n      </div>\n      <div\n        class=\"ViewHeader\"\n      >\n        <div\n          class=\"viewSelector\"\n        >\n          <input\n            class=\"Editable \"\n            placeholder=\"Untitled View\"\n            spellcheck=\"true\"\n            title=\"view title\"\n            value=\"view title\"\n          />\n          <div>\n            <div\n              aria-label=\"View menu\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-chevron-down DropdownIcon\"\n                />\n              </button>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"octo-spacer\"\n        />\n        <div\n          aria-label=\"Properties menu\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              Properties\n            </span>\n          </button>\n        </div>\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              Group by: \n              <span\n                id=\"groupByLabel\"\n              >\n                name\n              </span>\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"ModalWrapper\"\n        >\n          <button\n            class=\"Button active\"\n            type=\"button\"\n          >\n            <span>\n              Filter\n            </span>\n          </button>\n        </div>\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button active\"\n            type=\"button\"\n          >\n            <span>\n              Sort\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"board-search-field\"\n        >\n          <i\n            class=\"CompassIcon icon-magnify board-search-icon\"\n          />\n          <input\n            class=\"Editable \"\n            placeholder=\"Search cards\"\n            value=\"\"\n          />\n        </div>\n        <div\n          class=\"ModalWrapper\"\n        >\n          <div\n            aria-label=\"View header menu\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <button\n              class=\"IconButton\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n              />\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"ButtonWithMenu\"\n        >\n          <div\n            class=\"button-text\"\n          >\n            New\n          </div>\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <div\n              class=\"button-dropdown\"\n            >\n              <i\n                class=\"CompassIcon icon-chevron-down DropdownIcon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"Table\"\n    >\n      <div\n        class=\"octo-table-body\"\n      >\n        <div\n          class=\"octo-table-header TableHeaders\"\n          id=\"mainBoardHeader\"\n        >\n          <div\n            class=\"octo-table-cell header-cell\"\n            style=\"overflow: unset; opacity: 1; width: 100px;\"\n          >\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <span\n                class=\"Label empty \"\n              >\n                Name\n              </span>\n            </div>\n            <div\n              class=\"octo-spacer\"\n            />\n            <div\n              class=\"HorizontalGrip\"\n            />\n          </div>\n        </div>\n        <div\n          class=\"table-row-container\"\n        >\n          <div\n            class=\"octo-table-group\"\n          >\n            <div\n              class=\"octo-group-header-cell expanded\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"octo-table-cell\"\n                style=\"width: 100px;\"\n              >\n                <button\n                  class=\"IconButton octo-table-cell__expand \"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-menu-right\"\n                  />\n                </button>\n                <span\n                  class=\"Label empty \"\n                  title=\"Items with an empty name property will go here. This column cannot be removed.\"\n                >\n                  No name\n                </span>\n                \n              </div>\n              <button\n                class=\"Button\"\n                type=\"button\"\n              >\n                <span>\n                  0\n                </span>\n              </button>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-plus AddIcon\"\n                />\n              </button>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-group\"\n          >\n            <div\n              class=\"octo-group-header-cell expanded\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"octo-table-cell\"\n                style=\"width: 100px;\"\n              >\n                <button\n                  class=\"IconButton octo-table-cell__expand \"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-menu-right\"\n                  />\n                </button>\n                <span\n                  class=\"Label propColorOrange \"\n                >\n                  <input\n                    class=\"Editable \"\n                    placeholder=\"New Select\"\n                    spellcheck=\"true\"\n                    title=\"Q1\"\n                    value=\"Q1\"\n                  />\n                </span>\n              </div>\n              <button\n                class=\"Button\"\n                type=\"button\"\n              >\n                <span>\n                  2\n                </span>\n              </button>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-plus AddIcon\"\n                />\n              </button>\n            </div>\n            <div\n              class=\"TableRow octo-table-row selected\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"action-cell octo-table-cell-btn\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-drag-vertical\"\n                  />\n                </button>\n              </div>\n              <div\n                class=\"octo-table-cell title-cell\"\n                id=\"mainBoardHeader\"\n                style=\"width: 100px;\"\n              >\n                <div\n                  class=\"octo-icontitle\"\n                >\n                  <div\n                    class=\"octo-icon\"\n                  >\n                    i\n                  </div>\n                  <input\n                    class=\"Editable \"\n                    placeholder=\"Untitled\"\n                    spellcheck=\"true\"\n                    title=\"card1\"\n                    value=\"card1\"\n                  />\n                </div>\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper optionsMenu ml-2 mr-2\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"octo-tooltip tooltip-top\"\n                    data-tooltip=\"More actions\"\n                  >\n                    <button\n                      aria-label=\"MenuBtn\"\n                      class=\"IconButton\"\n                      title=\"MenuBtn\"\n                      type=\"button\"\n                    >\n                      <i\n                        class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                      />\n                    </button>\n                  </div>\n                </div>\n                <div\n                  class=\"open-button\"\n                >\n                  <button\n                    class=\"Button\"\n                    type=\"button\"\n                  >\n                    <span>\n                      Open\n                    </span>\n                  </button>\n                </div>\n              </div>\n            </div>\n            <div\n              class=\"TableRow octo-table-row\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"action-cell octo-table-cell-btn\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-drag-vertical\"\n                  />\n                </button>\n              </div>\n              <div\n                class=\"octo-table-cell title-cell\"\n                id=\"mainBoardHeader\"\n                style=\"width: 100px;\"\n              >\n                <div\n                  class=\"octo-icontitle\"\n                >\n                  <div\n                    class=\"octo-icon\"\n                  >\n                    i\n                  </div>\n                  <input\n                    class=\"Editable \"\n                    placeholder=\"Untitled\"\n                    spellcheck=\"true\"\n                    title=\"card2\"\n                    value=\"card2\"\n                  />\n                </div>\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper optionsMenu ml-2 mr-2\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"octo-tooltip tooltip-top\"\n                    data-tooltip=\"More actions\"\n                  >\n                    <button\n                      aria-label=\"MenuBtn\"\n                      class=\"IconButton\"\n                      title=\"MenuBtn\"\n                      type=\"button\"\n                    >\n                      <i\n                        class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                      />\n                    </button>\n                  </div>\n                </div>\n                <div\n                  class=\"open-button\"\n                >\n                  <button\n                    class=\"Button\"\n                    type=\"button\"\n                  >\n                    <span>\n                      Open\n                    </span>\n                  </button>\n                </div>\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-group\"\n          >\n            <div\n              class=\"octo-group-header-cell expanded\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"octo-table-cell\"\n                style=\"width: 100px;\"\n              >\n                <button\n                  class=\"IconButton octo-table-cell__expand \"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-menu-right\"\n                  />\n                </button>\n                <span\n                  class=\"Label propColorBlue \"\n                >\n                  <input\n                    class=\"Editable \"\n                    placeholder=\"New Select\"\n                    spellcheck=\"true\"\n                    title=\"Q2\"\n                    value=\"Q2\"\n                  />\n                </span>\n              </div>\n              <button\n                class=\"Button\"\n                type=\"button\"\n              >\n                <span>\n                  0\n                </span>\n              </button>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-plus AddIcon\"\n                />\n              </button>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"octo-table-footer\"\n        />\n        <div\n          class=\"CalculationRow octo-table-row\"\n        >\n          <div\n            class=\"Calculation count octo-table-cell   \"\n            style=\"width: 100px;\"\n            tabindex=\"0\"\n          >\n            <span\n              class=\"calculationLabel\"\n            >\n              Count\n            </span>\n            <span\n              class=\"calculationValue\"\n            >\n              2\n            </span>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/centerPanel return centerPanel and press touch esc for one card selected 2`] = `\n<div>\n  <div\n    class=\"BoardComponent\"\n  >\n    <div\n      class=\"top-head\"\n    >\n      <div\n        class=\"TopBar\"\n      >\n        <a\n          class=\"link\"\n          href=\"https://www.focalboard.com/fwlink/feedback-focalboard.html?v=1.0.0\"\n          rel=\"noreferrer\"\n          target=\"_blank\"\n        >\n          Give feedback\n        </a>\n        <a\n          href=\"https://www.focalboard.com/guide/user?utm_source=webapp\"\n          rel=\"noreferrer\"\n          target=\"_blank\"\n        >\n          <i\n            class=\"CompassIcon icon-help-circle-outline HelpIcon\"\n          />\n        </a>\n      </div>\n      <div\n        class=\"mid-head\"\n      >\n        <div\n          class=\"ViewTitle\"\n        >\n          <div\n            class=\"add-buttons add-visible\"\n          >\n            <button\n              class=\"Button emphasis--default size--xsmall\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-eye-off-outline\"\n              />\n              <span>\n                hide description\n              </span>\n            </button>\n          </div>\n          <div\n            class=\"title\"\n          >\n            <div\n              class=\"IconSelector\"\n            >\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <div\n                  class=\"octo-icon size-m\"\n                >\n                  <span>\n                    i\n                  </span>\n                </div>\n              </div>\n            </div>\n            <input\n              class=\"Editable title\"\n              placeholder=\"Untitled board\"\n              spellcheck=\"true\"\n              title=\"board title\"\n              value=\"board title\"\n            />\n          </div>\n          <div\n            class=\"description\"\n          >\n            <div\n              class=\"MarkdownEditor octo-editor  \"\n            >\n              <div\n                class=\"octo-editor-preview\"\n                data-testid=\"preview-element\"\n              />\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"shareButtonWrapper\"\n        >\n          <div\n            class=\"ShareBoardButton\"\n          >\n            <button\n              class=\"Button emphasis--primary size--medium\"\n              title=\"Share board\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-lock-outline LockOutlineIcon\"\n              />\n              <span>\n                Share\n              </span>\n            </button>\n          </div>\n        </div>\n      </div>\n      <div\n        class=\"ViewHeader\"\n      >\n        <div\n          class=\"viewSelector\"\n        >\n          <input\n            class=\"Editable \"\n            placeholder=\"Untitled View\"\n            spellcheck=\"true\"\n            title=\"view title\"\n            value=\"view title\"\n          />\n          <div>\n            <div\n              aria-label=\"View menu\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-chevron-down DropdownIcon\"\n                />\n              </button>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"octo-spacer\"\n        />\n        <div\n          aria-label=\"Properties menu\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              Properties\n            </span>\n          </button>\n        </div>\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              Group by: \n              <span\n                id=\"groupByLabel\"\n              >\n                name\n              </span>\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"ModalWrapper\"\n        >\n          <button\n            class=\"Button active\"\n            type=\"button\"\n          >\n            <span>\n              Filter\n            </span>\n          </button>\n        </div>\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button active\"\n            type=\"button\"\n          >\n            <span>\n              Sort\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"board-search-field\"\n        >\n          <i\n            class=\"CompassIcon icon-magnify board-search-icon\"\n          />\n          <input\n            class=\"Editable \"\n            placeholder=\"Search cards\"\n            value=\"\"\n          />\n        </div>\n        <div\n          class=\"ModalWrapper\"\n        >\n          <div\n            aria-label=\"View header menu\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <button\n              class=\"IconButton\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n              />\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"ButtonWithMenu\"\n        >\n          <div\n            class=\"button-text\"\n          >\n            New\n          </div>\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <div\n              class=\"button-dropdown\"\n            >\n              <i\n                class=\"CompassIcon icon-chevron-down DropdownIcon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"Table\"\n    >\n      <div\n        class=\"octo-table-body\"\n      >\n        <div\n          class=\"octo-table-header TableHeaders\"\n          id=\"mainBoardHeader\"\n        >\n          <div\n            class=\"octo-table-cell header-cell\"\n            style=\"overflow: unset; opacity: 1; width: 100px;\"\n          >\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <span\n                class=\"Label empty \"\n              >\n                Name\n              </span>\n            </div>\n            <div\n              class=\"octo-spacer\"\n            />\n            <div\n              class=\"HorizontalGrip\"\n            />\n          </div>\n        </div>\n        <div\n          class=\"table-row-container\"\n        >\n          <div\n            class=\"octo-table-group\"\n          >\n            <div\n              class=\"octo-group-header-cell expanded\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"octo-table-cell\"\n                style=\"width: 100px;\"\n              >\n                <button\n                  class=\"IconButton octo-table-cell__expand \"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-menu-right\"\n                  />\n                </button>\n                <span\n                  class=\"Label empty \"\n                  title=\"Items with an empty name property will go here. This column cannot be removed.\"\n                >\n                  No name\n                </span>\n                \n              </div>\n              <button\n                class=\"Button\"\n                type=\"button\"\n              >\n                <span>\n                  0\n                </span>\n              </button>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-plus AddIcon\"\n                />\n              </button>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-group\"\n          >\n            <div\n              class=\"octo-group-header-cell expanded\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"octo-table-cell\"\n                style=\"width: 100px;\"\n              >\n                <button\n                  class=\"IconButton octo-table-cell__expand \"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-menu-right\"\n                  />\n                </button>\n                <span\n                  class=\"Label propColorOrange \"\n                >\n                  <input\n                    class=\"Editable \"\n                    placeholder=\"New Select\"\n                    spellcheck=\"true\"\n                    title=\"Q1\"\n                    value=\"Q1\"\n                  />\n                </span>\n              </div>\n              <button\n                class=\"Button\"\n                type=\"button\"\n              >\n                <span>\n                  2\n                </span>\n              </button>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-plus AddIcon\"\n                />\n              </button>\n            </div>\n            <div\n              class=\"TableRow octo-table-row\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"action-cell octo-table-cell-btn\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-drag-vertical\"\n                  />\n                </button>\n              </div>\n              <div\n                class=\"octo-table-cell title-cell\"\n                id=\"mainBoardHeader\"\n                style=\"width: 100px;\"\n              >\n                <div\n                  class=\"octo-icontitle\"\n                >\n                  <div\n                    class=\"octo-icon\"\n                  >\n                    i\n                  </div>\n                  <input\n                    class=\"Editable \"\n                    placeholder=\"Untitled\"\n                    spellcheck=\"true\"\n                    title=\"card1\"\n                    value=\"card1\"\n                  />\n                </div>\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper optionsMenu ml-2 mr-2\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"octo-tooltip tooltip-top\"\n                    data-tooltip=\"More actions\"\n                  >\n                    <button\n                      aria-label=\"MenuBtn\"\n                      class=\"IconButton\"\n                      title=\"MenuBtn\"\n                      type=\"button\"\n                    >\n                      <i\n                        class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                      />\n                    </button>\n                  </div>\n                </div>\n                <div\n                  class=\"open-button\"\n                >\n                  <button\n                    class=\"Button\"\n                    type=\"button\"\n                  >\n                    <span>\n                      Open\n                    </span>\n                  </button>\n                </div>\n              </div>\n            </div>\n            <div\n              class=\"TableRow octo-table-row\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"action-cell octo-table-cell-btn\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-drag-vertical\"\n                  />\n                </button>\n              </div>\n              <div\n                class=\"octo-table-cell title-cell\"\n                id=\"mainBoardHeader\"\n                style=\"width: 100px;\"\n              >\n                <div\n                  class=\"octo-icontitle\"\n                >\n                  <div\n                    class=\"octo-icon\"\n                  >\n                    i\n                  </div>\n                  <input\n                    class=\"Editable \"\n                    placeholder=\"Untitled\"\n                    spellcheck=\"true\"\n                    title=\"card2\"\n                    value=\"card2\"\n                  />\n                </div>\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper optionsMenu ml-2 mr-2\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"octo-tooltip tooltip-top\"\n                    data-tooltip=\"More actions\"\n                  >\n                    <button\n                      aria-label=\"MenuBtn\"\n                      class=\"IconButton\"\n                      title=\"MenuBtn\"\n                      type=\"button\"\n                    >\n                      <i\n                        class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                      />\n                    </button>\n                  </div>\n                </div>\n                <div\n                  class=\"open-button\"\n                >\n                  <button\n                    class=\"Button\"\n                    type=\"button\"\n                  >\n                    <span>\n                      Open\n                    </span>\n                  </button>\n                </div>\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-group\"\n          >\n            <div\n              class=\"octo-group-header-cell expanded\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"octo-table-cell\"\n                style=\"width: 100px;\"\n              >\n                <button\n                  class=\"IconButton octo-table-cell__expand \"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-menu-right\"\n                  />\n                </button>\n                <span\n                  class=\"Label propColorBlue \"\n                >\n                  <input\n                    class=\"Editable \"\n                    placeholder=\"New Select\"\n                    spellcheck=\"true\"\n                    title=\"Q2\"\n                    value=\"Q2\"\n                  />\n                </span>\n              </div>\n              <button\n                class=\"Button\"\n                type=\"button\"\n              >\n                <span>\n                  0\n                </span>\n              </button>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-plus AddIcon\"\n                />\n              </button>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"octo-table-footer\"\n        />\n        <div\n          class=\"CalculationRow octo-table-row\"\n        >\n          <div\n            class=\"Calculation count octo-table-cell   \"\n            style=\"width: 100px;\"\n            tabindex=\"0\"\n          >\n            <span\n              class=\"calculationLabel\"\n            >\n              Count\n            </span>\n            <span\n              class=\"calculationValue\"\n            >\n              2\n            </span>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/centerPanel return centerPanel and press touch esc for two cards selected 1`] = `\n<div>\n  <div\n    class=\"BoardComponent\"\n  >\n    <div\n      class=\"top-head\"\n    >\n      <div\n        class=\"TopBar\"\n      >\n        <a\n          class=\"link\"\n          href=\"https://www.focalboard.com/fwlink/feedback-focalboard.html?v=1.0.0\"\n          rel=\"noreferrer\"\n          target=\"_blank\"\n        >\n          Give feedback\n        </a>\n        <a\n          href=\"https://www.focalboard.com/guide/user?utm_source=webapp\"\n          rel=\"noreferrer\"\n          target=\"_blank\"\n        >\n          <i\n            class=\"CompassIcon icon-help-circle-outline HelpIcon\"\n          />\n        </a>\n      </div>\n      <div\n        class=\"mid-head\"\n      >\n        <div\n          class=\"ViewTitle\"\n        >\n          <div\n            class=\"add-buttons add-visible\"\n          >\n            <button\n              class=\"Button emphasis--default size--xsmall\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-eye-off-outline\"\n              />\n              <span>\n                hide description\n              </span>\n            </button>\n          </div>\n          <div\n            class=\"title\"\n          >\n            <div\n              class=\"IconSelector\"\n            >\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <div\n                  class=\"octo-icon size-m\"\n                >\n                  <span>\n                    i\n                  </span>\n                </div>\n              </div>\n            </div>\n            <input\n              class=\"Editable title\"\n              placeholder=\"Untitled board\"\n              spellcheck=\"true\"\n              title=\"board title\"\n              value=\"board title\"\n            />\n          </div>\n          <div\n            class=\"description\"\n          >\n            <div\n              class=\"MarkdownEditor octo-editor  \"\n            >\n              <div\n                class=\"octo-editor-preview\"\n                data-testid=\"preview-element\"\n              />\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"shareButtonWrapper\"\n        >\n          <div\n            class=\"ShareBoardButton\"\n          >\n            <button\n              class=\"Button emphasis--primary size--medium\"\n              title=\"Share board\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-lock-outline LockOutlineIcon\"\n              />\n              <span>\n                Share\n              </span>\n            </button>\n          </div>\n        </div>\n      </div>\n      <div\n        class=\"ViewHeader\"\n      >\n        <div\n          class=\"viewSelector\"\n        >\n          <input\n            class=\"Editable \"\n            placeholder=\"Untitled View\"\n            spellcheck=\"true\"\n            title=\"view title\"\n            value=\"view title\"\n          />\n          <div>\n            <div\n              aria-label=\"View menu\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-chevron-down DropdownIcon\"\n                />\n              </button>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"octo-spacer\"\n        />\n        <div\n          aria-label=\"Properties menu\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              Properties\n            </span>\n          </button>\n        </div>\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              Group by: \n              <span\n                id=\"groupByLabel\"\n              >\n                name\n              </span>\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"ModalWrapper\"\n        >\n          <button\n            class=\"Button active\"\n            type=\"button\"\n          >\n            <span>\n              Filter\n            </span>\n          </button>\n        </div>\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button active\"\n            type=\"button\"\n          >\n            <span>\n              Sort\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"board-search-field\"\n        >\n          <i\n            class=\"CompassIcon icon-magnify board-search-icon\"\n          />\n          <input\n            class=\"Editable \"\n            placeholder=\"Search cards\"\n            value=\"\"\n          />\n        </div>\n        <div\n          class=\"ModalWrapper\"\n        >\n          <div\n            aria-label=\"View header menu\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <button\n              class=\"IconButton\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n              />\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"ButtonWithMenu\"\n        >\n          <div\n            class=\"button-text\"\n          >\n            New\n          </div>\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <div\n              class=\"button-dropdown\"\n            >\n              <i\n                class=\"CompassIcon icon-chevron-down DropdownIcon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"Table\"\n    >\n      <div\n        class=\"octo-table-body\"\n      >\n        <div\n          class=\"octo-table-header TableHeaders\"\n          id=\"mainBoardHeader\"\n        >\n          <div\n            class=\"octo-table-cell header-cell\"\n            style=\"overflow: unset; opacity: 1; width: 100px;\"\n          >\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <span\n                class=\"Label empty \"\n              >\n                Name\n              </span>\n            </div>\n            <div\n              class=\"octo-spacer\"\n            />\n            <div\n              class=\"HorizontalGrip\"\n            />\n          </div>\n        </div>\n        <div\n          class=\"table-row-container\"\n        >\n          <div\n            class=\"octo-table-group\"\n          >\n            <div\n              class=\"octo-group-header-cell expanded\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"octo-table-cell\"\n                style=\"width: 100px;\"\n              >\n                <button\n                  class=\"IconButton octo-table-cell__expand \"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-menu-right\"\n                  />\n                </button>\n                <span\n                  class=\"Label empty \"\n                  title=\"Items with an empty name property will go here. This column cannot be removed.\"\n                >\n                  No name\n                </span>\n                \n              </div>\n              <button\n                class=\"Button\"\n                type=\"button\"\n              >\n                <span>\n                  0\n                </span>\n              </button>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-plus AddIcon\"\n                />\n              </button>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-group\"\n          >\n            <div\n              class=\"octo-group-header-cell expanded\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"octo-table-cell\"\n                style=\"width: 100px;\"\n              >\n                <button\n                  class=\"IconButton octo-table-cell__expand \"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-menu-right\"\n                  />\n                </button>\n                <span\n                  class=\"Label propColorOrange \"\n                >\n                  <input\n                    class=\"Editable \"\n                    placeholder=\"New Select\"\n                    spellcheck=\"true\"\n                    title=\"Q1\"\n                    value=\"Q1\"\n                  />\n                </span>\n              </div>\n              <button\n                class=\"Button\"\n                type=\"button\"\n              >\n                <span>\n                  2\n                </span>\n              </button>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-plus AddIcon\"\n                />\n              </button>\n            </div>\n            <div\n              class=\"TableRow octo-table-row selected\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"action-cell octo-table-cell-btn\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-drag-vertical\"\n                  />\n                </button>\n              </div>\n              <div\n                class=\"octo-table-cell title-cell\"\n                id=\"mainBoardHeader\"\n                style=\"width: 100px;\"\n              >\n                <div\n                  class=\"octo-icontitle\"\n                >\n                  <div\n                    class=\"octo-icon\"\n                  >\n                    i\n                  </div>\n                  <input\n                    class=\"Editable \"\n                    placeholder=\"Untitled\"\n                    spellcheck=\"true\"\n                    title=\"card1\"\n                    value=\"card1\"\n                  />\n                </div>\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper optionsMenu ml-2 mr-2\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"octo-tooltip tooltip-top\"\n                    data-tooltip=\"More actions\"\n                  >\n                    <button\n                      aria-label=\"MenuBtn\"\n                      class=\"IconButton\"\n                      title=\"MenuBtn\"\n                      type=\"button\"\n                    >\n                      <i\n                        class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                      />\n                    </button>\n                  </div>\n                </div>\n                <div\n                  class=\"open-button\"\n                >\n                  <button\n                    class=\"Button\"\n                    type=\"button\"\n                  >\n                    <span>\n                      Open\n                    </span>\n                  </button>\n                </div>\n              </div>\n            </div>\n            <div\n              class=\"TableRow octo-table-row\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"action-cell octo-table-cell-btn\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-drag-vertical\"\n                  />\n                </button>\n              </div>\n              <div\n                class=\"octo-table-cell title-cell\"\n                id=\"mainBoardHeader\"\n                style=\"width: 100px;\"\n              >\n                <div\n                  class=\"octo-icontitle\"\n                >\n                  <div\n                    class=\"octo-icon\"\n                  >\n                    i\n                  </div>\n                  <input\n                    class=\"Editable \"\n                    placeholder=\"Untitled\"\n                    spellcheck=\"true\"\n                    title=\"card2\"\n                    value=\"card2\"\n                  />\n                </div>\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper optionsMenu ml-2 mr-2\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"octo-tooltip tooltip-top\"\n                    data-tooltip=\"More actions\"\n                  >\n                    <button\n                      aria-label=\"MenuBtn\"\n                      class=\"IconButton\"\n                      title=\"MenuBtn\"\n                      type=\"button\"\n                    >\n                      <i\n                        class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                      />\n                    </button>\n                  </div>\n                </div>\n                <div\n                  class=\"open-button\"\n                >\n                  <button\n                    class=\"Button\"\n                    type=\"button\"\n                  >\n                    <span>\n                      Open\n                    </span>\n                  </button>\n                </div>\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-group\"\n          >\n            <div\n              class=\"octo-group-header-cell expanded\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"octo-table-cell\"\n                style=\"width: 100px;\"\n              >\n                <button\n                  class=\"IconButton octo-table-cell__expand \"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-menu-right\"\n                  />\n                </button>\n                <span\n                  class=\"Label propColorBlue \"\n                >\n                  <input\n                    class=\"Editable \"\n                    placeholder=\"New Select\"\n                    spellcheck=\"true\"\n                    title=\"Q2\"\n                    value=\"Q2\"\n                  />\n                </span>\n              </div>\n              <button\n                class=\"Button\"\n                type=\"button\"\n              >\n                <span>\n                  0\n                </span>\n              </button>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-plus AddIcon\"\n                />\n              </button>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"octo-table-footer\"\n        />\n        <div\n          class=\"CalculationRow octo-table-row\"\n        >\n          <div\n            class=\"Calculation count octo-table-cell   \"\n            style=\"width: 100px;\"\n            tabindex=\"0\"\n          >\n            <span\n              class=\"calculationLabel\"\n            >\n              Count\n            </span>\n            <span\n              class=\"calculationValue\"\n            >\n              2\n            </span>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/centerPanel return centerPanel and press touch esc for two cards selected 2`] = `\n<div>\n  <div\n    class=\"BoardComponent\"\n  >\n    <div\n      class=\"top-head\"\n    >\n      <div\n        class=\"TopBar\"\n      >\n        <a\n          class=\"link\"\n          href=\"https://www.focalboard.com/fwlink/feedback-focalboard.html?v=1.0.0\"\n          rel=\"noreferrer\"\n          target=\"_blank\"\n        >\n          Give feedback\n        </a>\n        <a\n          href=\"https://www.focalboard.com/guide/user?utm_source=webapp\"\n          rel=\"noreferrer\"\n          target=\"_blank\"\n        >\n          <i\n            class=\"CompassIcon icon-help-circle-outline HelpIcon\"\n          />\n        </a>\n      </div>\n      <div\n        class=\"mid-head\"\n      >\n        <div\n          class=\"ViewTitle\"\n        >\n          <div\n            class=\"add-buttons add-visible\"\n          >\n            <button\n              class=\"Button emphasis--default size--xsmall\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-eye-off-outline\"\n              />\n              <span>\n                hide description\n              </span>\n            </button>\n          </div>\n          <div\n            class=\"title\"\n          >\n            <div\n              class=\"IconSelector\"\n            >\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <div\n                  class=\"octo-icon size-m\"\n                >\n                  <span>\n                    i\n                  </span>\n                </div>\n              </div>\n            </div>\n            <input\n              class=\"Editable title\"\n              placeholder=\"Untitled board\"\n              spellcheck=\"true\"\n              title=\"board title\"\n              value=\"board title\"\n            />\n          </div>\n          <div\n            class=\"description\"\n          >\n            <div\n              class=\"MarkdownEditor octo-editor  \"\n            >\n              <div\n                class=\"octo-editor-preview\"\n                data-testid=\"preview-element\"\n              />\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"shareButtonWrapper\"\n        >\n          <div\n            class=\"ShareBoardButton\"\n          >\n            <button\n              class=\"Button emphasis--primary size--medium\"\n              title=\"Share board\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-lock-outline LockOutlineIcon\"\n              />\n              <span>\n                Share\n              </span>\n            </button>\n          </div>\n        </div>\n      </div>\n      <div\n        class=\"ViewHeader\"\n      >\n        <div\n          class=\"viewSelector\"\n        >\n          <input\n            class=\"Editable \"\n            placeholder=\"Untitled View\"\n            spellcheck=\"true\"\n            title=\"view title\"\n            value=\"view title\"\n          />\n          <div>\n            <div\n              aria-label=\"View menu\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-chevron-down DropdownIcon\"\n                />\n              </button>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"octo-spacer\"\n        />\n        <div\n          aria-label=\"Properties menu\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              Properties\n            </span>\n          </button>\n        </div>\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              Group by: \n              <span\n                id=\"groupByLabel\"\n              >\n                name\n              </span>\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"ModalWrapper\"\n        >\n          <button\n            class=\"Button active\"\n            type=\"button\"\n          >\n            <span>\n              Filter\n            </span>\n          </button>\n        </div>\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button active\"\n            type=\"button\"\n          >\n            <span>\n              Sort\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"board-search-field\"\n        >\n          <i\n            class=\"CompassIcon icon-magnify board-search-icon\"\n          />\n          <input\n            class=\"Editable \"\n            placeholder=\"Search cards\"\n            value=\"\"\n          />\n        </div>\n        <div\n          class=\"ModalWrapper\"\n        >\n          <div\n            aria-label=\"View header menu\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <button\n              class=\"IconButton\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n              />\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"ButtonWithMenu\"\n        >\n          <div\n            class=\"button-text\"\n          >\n            New\n          </div>\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <div\n              class=\"button-dropdown\"\n            >\n              <i\n                class=\"CompassIcon icon-chevron-down DropdownIcon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"Table\"\n    >\n      <div\n        class=\"octo-table-body\"\n      >\n        <div\n          class=\"octo-table-header TableHeaders\"\n          id=\"mainBoardHeader\"\n        >\n          <div\n            class=\"octo-table-cell header-cell\"\n            style=\"overflow: unset; opacity: 1; width: 100px;\"\n          >\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <span\n                class=\"Label empty \"\n              >\n                Name\n              </span>\n            </div>\n            <div\n              class=\"octo-spacer\"\n            />\n            <div\n              class=\"HorizontalGrip\"\n            />\n          </div>\n        </div>\n        <div\n          class=\"table-row-container\"\n        >\n          <div\n            class=\"octo-table-group\"\n          >\n            <div\n              class=\"octo-group-header-cell expanded\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"octo-table-cell\"\n                style=\"width: 100px;\"\n              >\n                <button\n                  class=\"IconButton octo-table-cell__expand \"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-menu-right\"\n                  />\n                </button>\n                <span\n                  class=\"Label empty \"\n                  title=\"Items with an empty name property will go here. This column cannot be removed.\"\n                >\n                  No name\n                </span>\n                \n              </div>\n              <button\n                class=\"Button\"\n                type=\"button\"\n              >\n                <span>\n                  0\n                </span>\n              </button>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-plus AddIcon\"\n                />\n              </button>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-group\"\n          >\n            <div\n              class=\"octo-group-header-cell expanded\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"octo-table-cell\"\n                style=\"width: 100px;\"\n              >\n                <button\n                  class=\"IconButton octo-table-cell__expand \"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-menu-right\"\n                  />\n                </button>\n                <span\n                  class=\"Label propColorOrange \"\n                >\n                  <input\n                    class=\"Editable \"\n                    placeholder=\"New Select\"\n                    spellcheck=\"true\"\n                    title=\"Q1\"\n                    value=\"Q1\"\n                  />\n                </span>\n              </div>\n              <button\n                class=\"Button\"\n                type=\"button\"\n              >\n                <span>\n                  2\n                </span>\n              </button>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-plus AddIcon\"\n                />\n              </button>\n            </div>\n            <div\n              class=\"TableRow octo-table-row selected\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"action-cell octo-table-cell-btn\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-drag-vertical\"\n                  />\n                </button>\n              </div>\n              <div\n                class=\"octo-table-cell title-cell\"\n                id=\"mainBoardHeader\"\n                style=\"width: 100px;\"\n              >\n                <div\n                  class=\"octo-icontitle\"\n                >\n                  <div\n                    class=\"octo-icon\"\n                  >\n                    i\n                  </div>\n                  <input\n                    class=\"Editable \"\n                    placeholder=\"Untitled\"\n                    spellcheck=\"true\"\n                    title=\"card1\"\n                    value=\"card1\"\n                  />\n                </div>\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper optionsMenu ml-2 mr-2\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"octo-tooltip tooltip-top\"\n                    data-tooltip=\"More actions\"\n                  >\n                    <button\n                      aria-label=\"MenuBtn\"\n                      class=\"IconButton\"\n                      title=\"MenuBtn\"\n                      type=\"button\"\n                    >\n                      <i\n                        class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                      />\n                    </button>\n                  </div>\n                </div>\n                <div\n                  class=\"open-button\"\n                >\n                  <button\n                    class=\"Button\"\n                    type=\"button\"\n                  >\n                    <span>\n                      Open\n                    </span>\n                  </button>\n                </div>\n              </div>\n            </div>\n            <div\n              class=\"TableRow octo-table-row selected\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"action-cell octo-table-cell-btn\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-drag-vertical\"\n                  />\n                </button>\n              </div>\n              <div\n                class=\"octo-table-cell title-cell\"\n                id=\"mainBoardHeader\"\n                style=\"width: 100px;\"\n              >\n                <div\n                  class=\"octo-icontitle\"\n                >\n                  <div\n                    class=\"octo-icon\"\n                  >\n                    i\n                  </div>\n                  <input\n                    class=\"Editable \"\n                    placeholder=\"Untitled\"\n                    spellcheck=\"true\"\n                    title=\"card2\"\n                    value=\"card2\"\n                  />\n                </div>\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper optionsMenu ml-2 mr-2\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"octo-tooltip tooltip-top\"\n                    data-tooltip=\"More actions\"\n                  >\n                    <button\n                      aria-label=\"MenuBtn\"\n                      class=\"IconButton\"\n                      title=\"MenuBtn\"\n                      type=\"button\"\n                    >\n                      <i\n                        class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                      />\n                    </button>\n                  </div>\n                </div>\n                <div\n                  class=\"open-button\"\n                >\n                  <button\n                    class=\"Button\"\n                    type=\"button\"\n                  >\n                    <span>\n                      Open\n                    </span>\n                  </button>\n                </div>\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-group\"\n          >\n            <div\n              class=\"octo-group-header-cell expanded\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"octo-table-cell\"\n                style=\"width: 100px;\"\n              >\n                <button\n                  class=\"IconButton octo-table-cell__expand \"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-menu-right\"\n                  />\n                </button>\n                <span\n                  class=\"Label propColorBlue \"\n                >\n                  <input\n                    class=\"Editable \"\n                    placeholder=\"New Select\"\n                    spellcheck=\"true\"\n                    title=\"Q2\"\n                    value=\"Q2\"\n                  />\n                </span>\n              </div>\n              <button\n                class=\"Button\"\n                type=\"button\"\n              >\n                <span>\n                  0\n                </span>\n              </button>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-plus AddIcon\"\n                />\n              </button>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"octo-table-footer\"\n        />\n        <div\n          class=\"CalculationRow octo-table-row\"\n        >\n          <div\n            class=\"Calculation count octo-table-cell   \"\n            style=\"width: 100px;\"\n            tabindex=\"0\"\n          >\n            <span\n              class=\"calculationLabel\"\n            >\n              Count\n            </span>\n            <span\n              class=\"calculationValue\"\n            >\n              2\n            </span>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/centerPanel return centerPanel and press touch esc for two cards selected 3`] = `\n<div>\n  <div\n    class=\"BoardComponent\"\n  >\n    <div\n      class=\"top-head\"\n    >\n      <div\n        class=\"TopBar\"\n      >\n        <a\n          class=\"link\"\n          href=\"https://www.focalboard.com/fwlink/feedback-focalboard.html?v=1.0.0\"\n          rel=\"noreferrer\"\n          target=\"_blank\"\n        >\n          Give feedback\n        </a>\n        <a\n          href=\"https://www.focalboard.com/guide/user?utm_source=webapp\"\n          rel=\"noreferrer\"\n          target=\"_blank\"\n        >\n          <i\n            class=\"CompassIcon icon-help-circle-outline HelpIcon\"\n          />\n        </a>\n      </div>\n      <div\n        class=\"mid-head\"\n      >\n        <div\n          class=\"ViewTitle\"\n        >\n          <div\n            class=\"add-buttons add-visible\"\n          >\n            <button\n              class=\"Button emphasis--default size--xsmall\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-eye-off-outline\"\n              />\n              <span>\n                hide description\n              </span>\n            </button>\n          </div>\n          <div\n            class=\"title\"\n          >\n            <div\n              class=\"IconSelector\"\n            >\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <div\n                  class=\"octo-icon size-m\"\n                >\n                  <span>\n                    i\n                  </span>\n                </div>\n              </div>\n            </div>\n            <input\n              class=\"Editable title\"\n              placeholder=\"Untitled board\"\n              spellcheck=\"true\"\n              title=\"board title\"\n              value=\"board title\"\n            />\n          </div>\n          <div\n            class=\"description\"\n          >\n            <div\n              class=\"MarkdownEditor octo-editor  \"\n            >\n              <div\n                class=\"octo-editor-preview\"\n                data-testid=\"preview-element\"\n              />\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"shareButtonWrapper\"\n        >\n          <div\n            class=\"ShareBoardButton\"\n          >\n            <button\n              class=\"Button emphasis--primary size--medium\"\n              title=\"Share board\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-lock-outline LockOutlineIcon\"\n              />\n              <span>\n                Share\n              </span>\n            </button>\n          </div>\n        </div>\n      </div>\n      <div\n        class=\"ViewHeader\"\n      >\n        <div\n          class=\"viewSelector\"\n        >\n          <input\n            class=\"Editable \"\n            placeholder=\"Untitled View\"\n            spellcheck=\"true\"\n            title=\"view title\"\n            value=\"view title\"\n          />\n          <div>\n            <div\n              aria-label=\"View menu\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-chevron-down DropdownIcon\"\n                />\n              </button>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"octo-spacer\"\n        />\n        <div\n          aria-label=\"Properties menu\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              Properties\n            </span>\n          </button>\n        </div>\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              Group by: \n              <span\n                id=\"groupByLabel\"\n              >\n                name\n              </span>\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"ModalWrapper\"\n        >\n          <button\n            class=\"Button active\"\n            type=\"button\"\n          >\n            <span>\n              Filter\n            </span>\n          </button>\n        </div>\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button active\"\n            type=\"button\"\n          >\n            <span>\n              Sort\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"board-search-field\"\n        >\n          <i\n            class=\"CompassIcon icon-magnify board-search-icon\"\n          />\n          <input\n            class=\"Editable \"\n            placeholder=\"Search cards\"\n            value=\"\"\n          />\n        </div>\n        <div\n          class=\"ModalWrapper\"\n        >\n          <div\n            aria-label=\"View header menu\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <button\n              class=\"IconButton\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n              />\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"ButtonWithMenu\"\n        >\n          <div\n            class=\"button-text\"\n          >\n            New\n          </div>\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <div\n              class=\"button-dropdown\"\n            >\n              <i\n                class=\"CompassIcon icon-chevron-down DropdownIcon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"Table\"\n    >\n      <div\n        class=\"octo-table-body\"\n      >\n        <div\n          class=\"octo-table-header TableHeaders\"\n          id=\"mainBoardHeader\"\n        >\n          <div\n            class=\"octo-table-cell header-cell\"\n            style=\"overflow: unset; opacity: 1; width: 100px;\"\n          >\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <span\n                class=\"Label empty \"\n              >\n                Name\n              </span>\n            </div>\n            <div\n              class=\"octo-spacer\"\n            />\n            <div\n              class=\"HorizontalGrip\"\n            />\n          </div>\n        </div>\n        <div\n          class=\"table-row-container\"\n        >\n          <div\n            class=\"octo-table-group\"\n          >\n            <div\n              class=\"octo-group-header-cell expanded\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"octo-table-cell\"\n                style=\"width: 100px;\"\n              >\n                <button\n                  class=\"IconButton octo-table-cell__expand \"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-menu-right\"\n                  />\n                </button>\n                <span\n                  class=\"Label empty \"\n                  title=\"Items with an empty name property will go here. This column cannot be removed.\"\n                >\n                  No name\n                </span>\n                \n              </div>\n              <button\n                class=\"Button\"\n                type=\"button\"\n              >\n                <span>\n                  0\n                </span>\n              </button>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-plus AddIcon\"\n                />\n              </button>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-group\"\n          >\n            <div\n              class=\"octo-group-header-cell expanded\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"octo-table-cell\"\n                style=\"width: 100px;\"\n              >\n                <button\n                  class=\"IconButton octo-table-cell__expand \"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-menu-right\"\n                  />\n                </button>\n                <span\n                  class=\"Label propColorOrange \"\n                >\n                  <input\n                    class=\"Editable \"\n                    placeholder=\"New Select\"\n                    spellcheck=\"true\"\n                    title=\"Q1\"\n                    value=\"Q1\"\n                  />\n                </span>\n              </div>\n              <button\n                class=\"Button\"\n                type=\"button\"\n              >\n                <span>\n                  2\n                </span>\n              </button>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-plus AddIcon\"\n                />\n              </button>\n            </div>\n            <div\n              class=\"TableRow octo-table-row\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"action-cell octo-table-cell-btn\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-drag-vertical\"\n                  />\n                </button>\n              </div>\n              <div\n                class=\"octo-table-cell title-cell\"\n                id=\"mainBoardHeader\"\n                style=\"width: 100px;\"\n              >\n                <div\n                  class=\"octo-icontitle\"\n                >\n                  <div\n                    class=\"octo-icon\"\n                  >\n                    i\n                  </div>\n                  <input\n                    class=\"Editable \"\n                    placeholder=\"Untitled\"\n                    spellcheck=\"true\"\n                    title=\"card1\"\n                    value=\"card1\"\n                  />\n                </div>\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper optionsMenu ml-2 mr-2\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"octo-tooltip tooltip-top\"\n                    data-tooltip=\"More actions\"\n                  >\n                    <button\n                      aria-label=\"MenuBtn\"\n                      class=\"IconButton\"\n                      title=\"MenuBtn\"\n                      type=\"button\"\n                    >\n                      <i\n                        class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                      />\n                    </button>\n                  </div>\n                </div>\n                <div\n                  class=\"open-button\"\n                >\n                  <button\n                    class=\"Button\"\n                    type=\"button\"\n                  >\n                    <span>\n                      Open\n                    </span>\n                  </button>\n                </div>\n              </div>\n            </div>\n            <div\n              class=\"TableRow octo-table-row\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"action-cell octo-table-cell-btn\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-drag-vertical\"\n                  />\n                </button>\n              </div>\n              <div\n                class=\"octo-table-cell title-cell\"\n                id=\"mainBoardHeader\"\n                style=\"width: 100px;\"\n              >\n                <div\n                  class=\"octo-icontitle\"\n                >\n                  <div\n                    class=\"octo-icon\"\n                  >\n                    i\n                  </div>\n                  <input\n                    class=\"Editable \"\n                    placeholder=\"Untitled\"\n                    spellcheck=\"true\"\n                    title=\"card2\"\n                    value=\"card2\"\n                  />\n                </div>\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper optionsMenu ml-2 mr-2\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"octo-tooltip tooltip-top\"\n                    data-tooltip=\"More actions\"\n                  >\n                    <button\n                      aria-label=\"MenuBtn\"\n                      class=\"IconButton\"\n                      title=\"MenuBtn\"\n                      type=\"button\"\n                    >\n                      <i\n                        class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                      />\n                    </button>\n                  </div>\n                </div>\n                <div\n                  class=\"open-button\"\n                >\n                  <button\n                    class=\"Button\"\n                    type=\"button\"\n                  >\n                    <span>\n                      Open\n                    </span>\n                  </button>\n                </div>\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-group\"\n          >\n            <div\n              class=\"octo-group-header-cell expanded\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"octo-table-cell\"\n                style=\"width: 100px;\"\n              >\n                <button\n                  class=\"IconButton octo-table-cell__expand \"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-menu-right\"\n                  />\n                </button>\n                <span\n                  class=\"Label propColorBlue \"\n                >\n                  <input\n                    class=\"Editable \"\n                    placeholder=\"New Select\"\n                    spellcheck=\"true\"\n                    title=\"Q2\"\n                    value=\"Q2\"\n                  />\n                </span>\n              </div>\n              <button\n                class=\"Button\"\n                type=\"button\"\n              >\n                <span>\n                  0\n                </span>\n              </button>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-plus AddIcon\"\n                />\n              </button>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"octo-table-footer\"\n        />\n        <div\n          class=\"CalculationRow octo-table-row\"\n        >\n          <div\n            class=\"Calculation count octo-table-cell   \"\n            style=\"width: 100px;\"\n            tabindex=\"0\"\n          >\n            <span\n              class=\"calculationLabel\"\n            >\n              Count\n            </span>\n            <span\n              class=\"calculationValue\"\n            >\n              2\n            </span>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/centerPanel return centerPanel and select one card and click background 1`] = `\n<div>\n  <div\n    class=\"BoardComponent\"\n  >\n    <div\n      class=\"top-head\"\n    >\n      <div\n        class=\"TopBar\"\n      >\n        <a\n          class=\"link\"\n          href=\"https://www.focalboard.com/fwlink/feedback-focalboard.html?v=1.0.0\"\n          rel=\"noreferrer\"\n          target=\"_blank\"\n        >\n          Give feedback\n        </a>\n        <a\n          href=\"https://www.focalboard.com/guide/user?utm_source=webapp\"\n          rel=\"noreferrer\"\n          target=\"_blank\"\n        >\n          <i\n            class=\"CompassIcon icon-help-circle-outline HelpIcon\"\n          />\n        </a>\n      </div>\n      <div\n        class=\"mid-head\"\n      >\n        <div\n          class=\"ViewTitle\"\n        >\n          <div\n            class=\"add-buttons add-visible\"\n          >\n            <button\n              class=\"Button emphasis--default size--xsmall\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-eye-off-outline\"\n              />\n              <span>\n                hide description\n              </span>\n            </button>\n          </div>\n          <div\n            class=\"title\"\n          >\n            <div\n              class=\"IconSelector\"\n            >\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <div\n                  class=\"octo-icon size-m\"\n                >\n                  <span>\n                    i\n                  </span>\n                </div>\n              </div>\n            </div>\n            <input\n              class=\"Editable title\"\n              placeholder=\"Untitled board\"\n              spellcheck=\"true\"\n              title=\"board title\"\n              value=\"board title\"\n            />\n          </div>\n          <div\n            class=\"description\"\n          >\n            <div\n              class=\"MarkdownEditor octo-editor  \"\n            >\n              <div\n                class=\"octo-editor-preview\"\n                data-testid=\"preview-element\"\n              />\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"shareButtonWrapper\"\n        >\n          <div\n            class=\"ShareBoardButton\"\n          >\n            <button\n              class=\"Button emphasis--primary size--medium\"\n              title=\"Share board\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-lock-outline LockOutlineIcon\"\n              />\n              <span>\n                Share\n              </span>\n            </button>\n          </div>\n        </div>\n      </div>\n      <div\n        class=\"ViewHeader\"\n      >\n        <div\n          class=\"viewSelector\"\n        >\n          <input\n            class=\"Editable \"\n            placeholder=\"Untitled View\"\n            spellcheck=\"true\"\n            title=\"view title\"\n            value=\"view title\"\n          />\n          <div>\n            <div\n              aria-label=\"View menu\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-chevron-down DropdownIcon\"\n                />\n              </button>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"octo-spacer\"\n        />\n        <div\n          aria-label=\"Properties menu\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              Properties\n            </span>\n          </button>\n        </div>\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              Group by: \n              <span\n                id=\"groupByLabel\"\n              >\n                name\n              </span>\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"ModalWrapper\"\n        >\n          <button\n            class=\"Button active\"\n            type=\"button\"\n          >\n            <span>\n              Filter\n            </span>\n          </button>\n        </div>\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button active\"\n            type=\"button\"\n          >\n            <span>\n              Sort\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"board-search-field\"\n        >\n          <i\n            class=\"CompassIcon icon-magnify board-search-icon\"\n          />\n          <input\n            class=\"Editable \"\n            placeholder=\"Search cards\"\n            value=\"\"\n          />\n        </div>\n        <div\n          class=\"ModalWrapper\"\n        >\n          <div\n            aria-label=\"View header menu\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <button\n              class=\"IconButton\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n              />\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"ButtonWithMenu\"\n        >\n          <div\n            class=\"button-text\"\n          >\n            New\n          </div>\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <div\n              class=\"button-dropdown\"\n            >\n              <i\n                class=\"CompassIcon icon-chevron-down DropdownIcon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"Table\"\n    >\n      <div\n        class=\"octo-table-body\"\n      >\n        <div\n          class=\"octo-table-header TableHeaders\"\n          id=\"mainBoardHeader\"\n        >\n          <div\n            class=\"octo-table-cell header-cell\"\n            style=\"overflow: unset; opacity: 1; width: 100px;\"\n          >\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <span\n                class=\"Label empty \"\n              >\n                Name\n              </span>\n            </div>\n            <div\n              class=\"octo-spacer\"\n            />\n            <div\n              class=\"HorizontalGrip\"\n            />\n          </div>\n        </div>\n        <div\n          class=\"table-row-container\"\n        >\n          <div\n            class=\"octo-table-group\"\n          >\n            <div\n              class=\"octo-group-header-cell expanded\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"octo-table-cell\"\n                style=\"width: 100px;\"\n              >\n                <button\n                  class=\"IconButton octo-table-cell__expand \"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-menu-right\"\n                  />\n                </button>\n                <span\n                  class=\"Label empty \"\n                  title=\"Items with an empty name property will go here. This column cannot be removed.\"\n                >\n                  No name\n                </span>\n                \n              </div>\n              <button\n                class=\"Button\"\n                type=\"button\"\n              >\n                <span>\n                  0\n                </span>\n              </button>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-plus AddIcon\"\n                />\n              </button>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-group\"\n          >\n            <div\n              class=\"octo-group-header-cell expanded\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"octo-table-cell\"\n                style=\"width: 100px;\"\n              >\n                <button\n                  class=\"IconButton octo-table-cell__expand \"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-menu-right\"\n                  />\n                </button>\n                <span\n                  class=\"Label propColorOrange \"\n                >\n                  <input\n                    class=\"Editable \"\n                    placeholder=\"New Select\"\n                    spellcheck=\"true\"\n                    title=\"Q1\"\n                    value=\"Q1\"\n                  />\n                </span>\n              </div>\n              <button\n                class=\"Button\"\n                type=\"button\"\n              >\n                <span>\n                  2\n                </span>\n              </button>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-plus AddIcon\"\n                />\n              </button>\n            </div>\n            <div\n              class=\"TableRow octo-table-row selected\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"action-cell octo-table-cell-btn\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-drag-vertical\"\n                  />\n                </button>\n              </div>\n              <div\n                class=\"octo-table-cell title-cell\"\n                id=\"mainBoardHeader\"\n                style=\"width: 100px;\"\n              >\n                <div\n                  class=\"octo-icontitle\"\n                >\n                  <div\n                    class=\"octo-icon\"\n                  >\n                    i\n                  </div>\n                  <input\n                    class=\"Editable \"\n                    placeholder=\"Untitled\"\n                    spellcheck=\"true\"\n                    title=\"card1\"\n                    value=\"card1\"\n                  />\n                </div>\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper optionsMenu ml-2 mr-2\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"octo-tooltip tooltip-top\"\n                    data-tooltip=\"More actions\"\n                  >\n                    <button\n                      aria-label=\"MenuBtn\"\n                      class=\"IconButton\"\n                      title=\"MenuBtn\"\n                      type=\"button\"\n                    >\n                      <i\n                        class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                      />\n                    </button>\n                  </div>\n                </div>\n                <div\n                  class=\"open-button\"\n                >\n                  <button\n                    class=\"Button\"\n                    type=\"button\"\n                  >\n                    <span>\n                      Open\n                    </span>\n                  </button>\n                </div>\n              </div>\n            </div>\n            <div\n              class=\"TableRow octo-table-row\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"action-cell octo-table-cell-btn\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-drag-vertical\"\n                  />\n                </button>\n              </div>\n              <div\n                class=\"octo-table-cell title-cell\"\n                id=\"mainBoardHeader\"\n                style=\"width: 100px;\"\n              >\n                <div\n                  class=\"octo-icontitle\"\n                >\n                  <div\n                    class=\"octo-icon\"\n                  >\n                    i\n                  </div>\n                  <input\n                    class=\"Editable \"\n                    placeholder=\"Untitled\"\n                    spellcheck=\"true\"\n                    title=\"card2\"\n                    value=\"card2\"\n                  />\n                </div>\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper optionsMenu ml-2 mr-2\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"octo-tooltip tooltip-top\"\n                    data-tooltip=\"More actions\"\n                  >\n                    <button\n                      aria-label=\"MenuBtn\"\n                      class=\"IconButton\"\n                      title=\"MenuBtn\"\n                      type=\"button\"\n                    >\n                      <i\n                        class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                      />\n                    </button>\n                  </div>\n                </div>\n                <div\n                  class=\"open-button\"\n                >\n                  <button\n                    class=\"Button\"\n                    type=\"button\"\n                  >\n                    <span>\n                      Open\n                    </span>\n                  </button>\n                </div>\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-group\"\n          >\n            <div\n              class=\"octo-group-header-cell expanded\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"octo-table-cell\"\n                style=\"width: 100px;\"\n              >\n                <button\n                  class=\"IconButton octo-table-cell__expand \"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-menu-right\"\n                  />\n                </button>\n                <span\n                  class=\"Label propColorBlue \"\n                >\n                  <input\n                    class=\"Editable \"\n                    placeholder=\"New Select\"\n                    spellcheck=\"true\"\n                    title=\"Q2\"\n                    value=\"Q2\"\n                  />\n                </span>\n              </div>\n              <button\n                class=\"Button\"\n                type=\"button\"\n              >\n                <span>\n                  0\n                </span>\n              </button>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-plus AddIcon\"\n                />\n              </button>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"octo-table-footer\"\n        />\n        <div\n          class=\"CalculationRow octo-table-row\"\n        >\n          <div\n            class=\"Calculation count octo-table-cell   \"\n            style=\"width: 100px;\"\n            tabindex=\"0\"\n          >\n            <span\n              class=\"calculationLabel\"\n            >\n              Count\n            </span>\n            <span\n              class=\"calculationValue\"\n            >\n              2\n            </span>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/centerPanel return centerPanel and select one card and click background 2`] = `\n<div>\n  <div\n    class=\"BoardComponent\"\n  >\n    <div\n      class=\"top-head\"\n    >\n      <div\n        class=\"TopBar\"\n      >\n        <a\n          class=\"link\"\n          href=\"https://www.focalboard.com/fwlink/feedback-focalboard.html?v=1.0.0\"\n          rel=\"noreferrer\"\n          target=\"_blank\"\n        >\n          Give feedback\n        </a>\n        <a\n          href=\"https://www.focalboard.com/guide/user?utm_source=webapp\"\n          rel=\"noreferrer\"\n          target=\"_blank\"\n        >\n          <i\n            class=\"CompassIcon icon-help-circle-outline HelpIcon\"\n          />\n        </a>\n      </div>\n      <div\n        class=\"mid-head\"\n      >\n        <div\n          class=\"ViewTitle\"\n        >\n          <div\n            class=\"add-buttons add-visible\"\n          >\n            <button\n              class=\"Button emphasis--default size--xsmall\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-eye-off-outline\"\n              />\n              <span>\n                hide description\n              </span>\n            </button>\n          </div>\n          <div\n            class=\"title\"\n          >\n            <div\n              class=\"IconSelector\"\n            >\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <div\n                  class=\"octo-icon size-m\"\n                >\n                  <span>\n                    i\n                  </span>\n                </div>\n              </div>\n            </div>\n            <input\n              class=\"Editable title\"\n              placeholder=\"Untitled board\"\n              spellcheck=\"true\"\n              title=\"board title\"\n              value=\"board title\"\n            />\n          </div>\n          <div\n            class=\"description\"\n          >\n            <div\n              class=\"MarkdownEditor octo-editor  \"\n            >\n              <div\n                class=\"octo-editor-preview\"\n                data-testid=\"preview-element\"\n              />\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"shareButtonWrapper\"\n        >\n          <div\n            class=\"ShareBoardButton\"\n          >\n            <button\n              class=\"Button emphasis--primary size--medium\"\n              title=\"Share board\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-lock-outline LockOutlineIcon\"\n              />\n              <span>\n                Share\n              </span>\n            </button>\n          </div>\n        </div>\n      </div>\n      <div\n        class=\"ViewHeader\"\n      >\n        <div\n          class=\"viewSelector\"\n        >\n          <input\n            class=\"Editable \"\n            placeholder=\"Untitled View\"\n            spellcheck=\"true\"\n            title=\"view title\"\n            value=\"view title\"\n          />\n          <div>\n            <div\n              aria-label=\"View menu\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-chevron-down DropdownIcon\"\n                />\n              </button>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"octo-spacer\"\n        />\n        <div\n          aria-label=\"Properties menu\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              Properties\n            </span>\n          </button>\n        </div>\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              Group by: \n              <span\n                id=\"groupByLabel\"\n              >\n                name\n              </span>\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"ModalWrapper\"\n        >\n          <button\n            class=\"Button active\"\n            type=\"button\"\n          >\n            <span>\n              Filter\n            </span>\n          </button>\n        </div>\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button active\"\n            type=\"button\"\n          >\n            <span>\n              Sort\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"board-search-field\"\n        >\n          <i\n            class=\"CompassIcon icon-magnify board-search-icon\"\n          />\n          <input\n            class=\"Editable \"\n            placeholder=\"Search cards\"\n            value=\"\"\n          />\n        </div>\n        <div\n          class=\"ModalWrapper\"\n        >\n          <div\n            aria-label=\"View header menu\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <button\n              class=\"IconButton\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n              />\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"ButtonWithMenu\"\n        >\n          <div\n            class=\"button-text\"\n          >\n            New\n          </div>\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <div\n              class=\"button-dropdown\"\n            >\n              <i\n                class=\"CompassIcon icon-chevron-down DropdownIcon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"Table\"\n    >\n      <div\n        class=\"octo-table-body\"\n      >\n        <div\n          class=\"octo-table-header TableHeaders\"\n          id=\"mainBoardHeader\"\n        >\n          <div\n            class=\"octo-table-cell header-cell\"\n            style=\"overflow: unset; opacity: 1; width: 100px;\"\n          >\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <span\n                class=\"Label empty \"\n              >\n                Name\n              </span>\n            </div>\n            <div\n              class=\"octo-spacer\"\n            />\n            <div\n              class=\"HorizontalGrip\"\n            />\n          </div>\n        </div>\n        <div\n          class=\"table-row-container\"\n        >\n          <div\n            class=\"octo-table-group\"\n          >\n            <div\n              class=\"octo-group-header-cell expanded\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"octo-table-cell\"\n                style=\"width: 100px;\"\n              >\n                <button\n                  class=\"IconButton octo-table-cell__expand \"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-menu-right\"\n                  />\n                </button>\n                <span\n                  class=\"Label empty \"\n                  title=\"Items with an empty name property will go here. This column cannot be removed.\"\n                >\n                  No name\n                </span>\n                \n              </div>\n              <button\n                class=\"Button\"\n                type=\"button\"\n              >\n                <span>\n                  0\n                </span>\n              </button>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-plus AddIcon\"\n                />\n              </button>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-group\"\n          >\n            <div\n              class=\"octo-group-header-cell expanded\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"octo-table-cell\"\n                style=\"width: 100px;\"\n              >\n                <button\n                  class=\"IconButton octo-table-cell__expand \"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-menu-right\"\n                  />\n                </button>\n                <span\n                  class=\"Label propColorOrange \"\n                >\n                  <input\n                    class=\"Editable \"\n                    placeholder=\"New Select\"\n                    spellcheck=\"true\"\n                    title=\"Q1\"\n                    value=\"Q1\"\n                  />\n                </span>\n              </div>\n              <button\n                class=\"Button\"\n                type=\"button\"\n              >\n                <span>\n                  2\n                </span>\n              </button>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-plus AddIcon\"\n                />\n              </button>\n            </div>\n            <div\n              class=\"TableRow octo-table-row\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"action-cell octo-table-cell-btn\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-drag-vertical\"\n                  />\n                </button>\n              </div>\n              <div\n                class=\"octo-table-cell title-cell\"\n                id=\"mainBoardHeader\"\n                style=\"width: 100px;\"\n              >\n                <div\n                  class=\"octo-icontitle\"\n                >\n                  <div\n                    class=\"octo-icon\"\n                  >\n                    i\n                  </div>\n                  <input\n                    class=\"Editable \"\n                    placeholder=\"Untitled\"\n                    spellcheck=\"true\"\n                    title=\"card1\"\n                    value=\"card1\"\n                  />\n                </div>\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper optionsMenu ml-2 mr-2\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"octo-tooltip tooltip-top\"\n                    data-tooltip=\"More actions\"\n                  >\n                    <button\n                      aria-label=\"MenuBtn\"\n                      class=\"IconButton\"\n                      title=\"MenuBtn\"\n                      type=\"button\"\n                    >\n                      <i\n                        class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                      />\n                    </button>\n                  </div>\n                </div>\n                <div\n                  class=\"open-button\"\n                >\n                  <button\n                    class=\"Button\"\n                    type=\"button\"\n                  >\n                    <span>\n                      Open\n                    </span>\n                  </button>\n                </div>\n              </div>\n            </div>\n            <div\n              class=\"TableRow octo-table-row\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"action-cell octo-table-cell-btn\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-drag-vertical\"\n                  />\n                </button>\n              </div>\n              <div\n                class=\"octo-table-cell title-cell\"\n                id=\"mainBoardHeader\"\n                style=\"width: 100px;\"\n              >\n                <div\n                  class=\"octo-icontitle\"\n                >\n                  <div\n                    class=\"octo-icon\"\n                  >\n                    i\n                  </div>\n                  <input\n                    class=\"Editable \"\n                    placeholder=\"Untitled\"\n                    spellcheck=\"true\"\n                    title=\"card2\"\n                    value=\"card2\"\n                  />\n                </div>\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper optionsMenu ml-2 mr-2\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"octo-tooltip tooltip-top\"\n                    data-tooltip=\"More actions\"\n                  >\n                    <button\n                      aria-label=\"MenuBtn\"\n                      class=\"IconButton\"\n                      title=\"MenuBtn\"\n                      type=\"button\"\n                    >\n                      <i\n                        class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                      />\n                    </button>\n                  </div>\n                </div>\n                <div\n                  class=\"open-button\"\n                >\n                  <button\n                    class=\"Button\"\n                    type=\"button\"\n                  >\n                    <span>\n                      Open\n                    </span>\n                  </button>\n                </div>\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-group\"\n          >\n            <div\n              class=\"octo-group-header-cell expanded\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"octo-table-cell\"\n                style=\"width: 100px;\"\n              >\n                <button\n                  class=\"IconButton octo-table-cell__expand \"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-menu-right\"\n                  />\n                </button>\n                <span\n                  class=\"Label propColorBlue \"\n                >\n                  <input\n                    class=\"Editable \"\n                    placeholder=\"New Select\"\n                    spellcheck=\"true\"\n                    title=\"Q2\"\n                    value=\"Q2\"\n                  />\n                </span>\n              </div>\n              <button\n                class=\"Button\"\n                type=\"button\"\n              >\n                <span>\n                  0\n                </span>\n              </button>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-plus AddIcon\"\n                />\n              </button>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"octo-table-footer\"\n        />\n        <div\n          class=\"CalculationRow octo-table-row\"\n        >\n          <div\n            class=\"Calculation count octo-table-cell   \"\n            style=\"width: 100px;\"\n            tabindex=\"0\"\n          >\n            <span\n              class=\"calculationLabel\"\n            >\n              Count\n            </span>\n            <span\n              class=\"calculationValue\"\n            >\n              2\n            </span>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/centerPanel should match snapshot for Gallery 1`] = `\n<div>\n  <div\n    class=\"BoardComponent\"\n  >\n    <div\n      class=\"top-head\"\n    >\n      <div\n        class=\"TopBar\"\n      >\n        <a\n          class=\"link\"\n          href=\"https://www.focalboard.com/fwlink/feedback-focalboard.html?v=1.0.0\"\n          rel=\"noreferrer\"\n          target=\"_blank\"\n        >\n          Give feedback\n        </a>\n        <a\n          href=\"https://www.focalboard.com/guide/user?utm_source=webapp\"\n          rel=\"noreferrer\"\n          target=\"_blank\"\n        >\n          <i\n            class=\"CompassIcon icon-help-circle-outline HelpIcon\"\n          />\n        </a>\n      </div>\n      <div\n        class=\"mid-head\"\n      >\n        <div\n          class=\"ViewTitle\"\n        >\n          <div\n            class=\"add-buttons add-visible\"\n          >\n            <button\n              class=\"Button emphasis--default size--xsmall\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-eye-off-outline\"\n              />\n              <span>\n                hide description\n              </span>\n            </button>\n          </div>\n          <div\n            class=\"title\"\n          >\n            <div\n              class=\"IconSelector\"\n            >\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <div\n                  class=\"octo-icon size-m\"\n                >\n                  <span>\n                    i\n                  </span>\n                </div>\n              </div>\n            </div>\n            <input\n              class=\"Editable title\"\n              placeholder=\"Untitled board\"\n              spellcheck=\"true\"\n              title=\"board title\"\n              value=\"board title\"\n            />\n          </div>\n          <div\n            class=\"description\"\n          >\n            <div\n              class=\"MarkdownEditor octo-editor  \"\n            >\n              <div\n                class=\"octo-editor-preview\"\n                data-testid=\"preview-element\"\n              />\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"shareButtonWrapper\"\n        >\n          <div\n            class=\"ShareBoardButton\"\n          >\n            <button\n              class=\"Button emphasis--primary size--medium\"\n              title=\"Share board\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-lock-outline LockOutlineIcon\"\n              />\n              <span>\n                Share\n              </span>\n            </button>\n          </div>\n        </div>\n      </div>\n      <div\n        class=\"ViewHeader\"\n      >\n        <div\n          class=\"viewSelector\"\n        >\n          <input\n            class=\"Editable \"\n            placeholder=\"Untitled View\"\n            spellcheck=\"true\"\n            title=\"view title\"\n            value=\"view title\"\n          />\n          <div>\n            <div\n              aria-label=\"View menu\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-chevron-down DropdownIcon\"\n                />\n              </button>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"octo-spacer\"\n        />\n        <div\n          aria-label=\"Properties menu\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              Properties\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"ModalWrapper\"\n        >\n          <button\n            class=\"Button active\"\n            type=\"button\"\n          >\n            <span>\n              Filter\n            </span>\n          </button>\n        </div>\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button active\"\n            type=\"button\"\n          >\n            <span>\n              Sort\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"board-search-field\"\n        >\n          <i\n            class=\"CompassIcon icon-magnify board-search-icon\"\n          />\n          <input\n            class=\"Editable \"\n            placeholder=\"Search cards\"\n            value=\"\"\n          />\n        </div>\n        <div\n          class=\"ModalWrapper\"\n        >\n          <div\n            aria-label=\"View header menu\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <button\n              class=\"IconButton\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n              />\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"ButtonWithMenu\"\n        >\n          <div\n            class=\"button-text\"\n          >\n            New\n          </div>\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <div\n              class=\"button-dropdown\"\n            >\n              <i\n                class=\"CompassIcon icon-chevron-down DropdownIcon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"Gallery\"\n    >\n      <div\n        class=\"GalleryCard\"\n        draggable=\"true\"\n        style=\"opacity: 1;\"\n      >\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper optionsMenu\"\n          role=\"button\"\n        >\n          <button\n            class=\"IconButton CardActionsMenuIcon\"\n            type=\"button\"\n          >\n            <i\n              class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n            />\n          </button>\n        </div>\n        <div\n          class=\"gallery-item\"\n        />\n      </div>\n      <div\n        class=\"octo-gallery-new\"\n      >\n        + New\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/centerPanel should match snapshot for Kanban 1`] = `\n<div>\n  <div\n    class=\"BoardComponent\"\n  >\n    <div\n      class=\"top-head\"\n    >\n      <div\n        class=\"TopBar\"\n      >\n        <a\n          class=\"link\"\n          href=\"https://www.focalboard.com/fwlink/feedback-focalboard.html?v=1.0.0\"\n          rel=\"noreferrer\"\n          target=\"_blank\"\n        >\n          Give feedback\n        </a>\n        <a\n          href=\"https://www.focalboard.com/guide/user?utm_source=webapp\"\n          rel=\"noreferrer\"\n          target=\"_blank\"\n        >\n          <i\n            class=\"CompassIcon icon-help-circle-outline HelpIcon\"\n          />\n        </a>\n      </div>\n      <div\n        class=\"mid-head\"\n      >\n        <div\n          class=\"ViewTitle\"\n        >\n          <div\n            class=\"add-buttons add-visible\"\n          >\n            <button\n              class=\"Button emphasis--default size--xsmall\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-eye-off-outline\"\n              />\n              <span>\n                hide description\n              </span>\n            </button>\n          </div>\n          <div\n            class=\"title\"\n          >\n            <div\n              class=\"IconSelector\"\n            >\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <div\n                  class=\"octo-icon size-m\"\n                >\n                  <span>\n                    i\n                  </span>\n                </div>\n              </div>\n            </div>\n            <input\n              class=\"Editable title\"\n              placeholder=\"Untitled board\"\n              spellcheck=\"true\"\n              title=\"board title\"\n              value=\"board title\"\n            />\n          </div>\n          <div\n            class=\"description\"\n          >\n            <div\n              class=\"MarkdownEditor octo-editor  \"\n            >\n              <div\n                class=\"octo-editor-preview\"\n                data-testid=\"preview-element\"\n              />\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"shareButtonWrapper\"\n        >\n          <div\n            class=\"ShareBoardButton\"\n          >\n            <button\n              class=\"Button emphasis--primary size--medium\"\n              title=\"Share board\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-lock-outline LockOutlineIcon\"\n              />\n              <span>\n                Share\n              </span>\n            </button>\n          </div>\n        </div>\n      </div>\n      <div\n        class=\"ViewHeader\"\n      >\n        <div\n          class=\"viewSelector\"\n        >\n          <input\n            class=\"Editable \"\n            placeholder=\"Untitled View\"\n            spellcheck=\"true\"\n            title=\"view title\"\n            value=\"view title\"\n          />\n          <div>\n            <div\n              aria-label=\"View menu\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-chevron-down DropdownIcon\"\n                />\n              </button>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"octo-spacer\"\n        />\n        <div\n          aria-label=\"Properties menu\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              Properties\n            </span>\n          </button>\n        </div>\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              Group by: \n              <span\n                id=\"groupByLabel\"\n              >\n                name\n              </span>\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"ModalWrapper\"\n        >\n          <button\n            class=\"Button active\"\n            type=\"button\"\n          >\n            <span>\n              Filter\n            </span>\n          </button>\n        </div>\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button active\"\n            type=\"button\"\n          >\n            <span>\n              Sort\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"board-search-field\"\n        >\n          <i\n            class=\"CompassIcon icon-magnify board-search-icon\"\n          />\n          <input\n            class=\"Editable \"\n            placeholder=\"Search cards\"\n            value=\"\"\n          />\n        </div>\n        <div\n          class=\"ModalWrapper\"\n        >\n          <div\n            aria-label=\"View header menu\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <button\n              class=\"IconButton\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n              />\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"ButtonWithMenu\"\n        >\n          <div\n            class=\"button-text\"\n          >\n            New\n          </div>\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <div\n              class=\"button-dropdown\"\n            >\n              <i\n                class=\"CompassIcon icon-chevron-down DropdownIcon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"Kanban\"\n    >\n      <div\n        class=\"octo-board-header\"\n        id=\"mainBoardHeader\"\n      >\n        <div\n          class=\"octo-board-header-cell KanbanColumnHeader\"\n          draggable=\"true\"\n          style=\"opacity: 1;\"\n        >\n          <span\n            class=\"Label empty \"\n            title=\"Items with an empty name property will go here. This column cannot be removed.\"\n          >\n            No name\n          </span>\n          \n          <div\n            class=\"KanbanCalculation\"\n          >\n            <button\n              class=\"Button\"\n              title=\"0\"\n              type=\"button\"\n            >\n              <span>\n                0\n              </span>\n            </button>\n          </div>\n          <div\n            class=\"octo-spacer\"\n          />\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <button\n              class=\"IconButton\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n              />\n            </button>\n          </div>\n          <button\n            class=\"IconButton\"\n            type=\"button\"\n          >\n            <i\n              class=\"CompassIcon icon-plus AddIcon\"\n            />\n          </button>\n        </div>\n        <div\n          class=\"octo-board-header-cell KanbanColumnHeader\"\n          draggable=\"true\"\n          style=\"opacity: 1;\"\n        >\n          <span\n            class=\"Label propColorOrange \"\n          >\n            <input\n              class=\"Editable \"\n              placeholder=\"New Select\"\n              spellcheck=\"true\"\n              title=\"Q1\"\n              value=\"Q1\"\n            />\n          </span>\n          <div\n            class=\"KanbanCalculation\"\n          >\n            <button\n              class=\"Button\"\n              title=\"1\"\n              type=\"button\"\n            >\n              <span>\n                1\n              </span>\n            </button>\n          </div>\n          <div\n            class=\"octo-spacer\"\n          />\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <button\n              class=\"IconButton\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n              />\n            </button>\n          </div>\n          <button\n            class=\"IconButton\"\n            type=\"button\"\n          >\n            <i\n              class=\"CompassIcon icon-plus AddIcon\"\n            />\n          </button>\n        </div>\n        <div\n          class=\"octo-board-header-cell KanbanColumnHeader\"\n          draggable=\"true\"\n          style=\"opacity: 1;\"\n        >\n          <span\n            class=\"Label propColorBlue \"\n          >\n            <input\n              class=\"Editable \"\n              placeholder=\"New Select\"\n              spellcheck=\"true\"\n              title=\"Q2\"\n              value=\"Q2\"\n            />\n          </span>\n          <div\n            class=\"KanbanCalculation\"\n          >\n            <button\n              class=\"Button\"\n              title=\"0\"\n              type=\"button\"\n            >\n              <span>\n                0\n              </span>\n            </button>\n          </div>\n          <div\n            class=\"octo-spacer\"\n          />\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <button\n              class=\"IconButton\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n              />\n            </button>\n          </div>\n          <button\n            class=\"IconButton\"\n            type=\"button\"\n          >\n            <i\n              class=\"CompassIcon icon-plus AddIcon\"\n            />\n          </button>\n        </div>\n        <div\n          class=\"octo-board-header-cell narrow\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              + Add a group\n            </span>\n          </button>\n        </div>\n      </div>\n      <div\n        class=\"octo-board-body\"\n        id=\"mainBoardBody\"\n      >\n        <div\n          class=\"octo-board-column\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              + New\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"octo-board-column\"\n        >\n          <div\n            class=\"KanbanCard\"\n            draggable=\"true\"\n            style=\"opacity: 1;\"\n          >\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper optionsMenu\"\n              role=\"button\"\n            >\n              <button\n                class=\"IconButton CardActionsMenuIcon\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                />\n              </button>\n            </div>\n            <div\n              class=\"octo-icontitle\"\n            >\n              <div\n                class=\"octo-icon\"\n              >\n                i\n              </div>\n              <div\n                class=\"octo-titletext\"\n              >\n                card1\n              </div>\n            </div>\n          </div>\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              + New\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"octo-board-column\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              + New\n            </span>\n          </button>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/centerPanel should match snapshot for Kanban, not shared 1`] = `\n<div>\n  <div\n    class=\"BoardComponent\"\n  >\n    <div\n      class=\"top-head\"\n    >\n      <div\n        class=\"TopBar\"\n      >\n        <a\n          class=\"link\"\n          href=\"https://www.focalboard.com/fwlink/feedback-focalboard.html?v=1.0.0\"\n          rel=\"noreferrer\"\n          target=\"_blank\"\n        >\n          Give feedback\n        </a>\n        <a\n          href=\"https://www.focalboard.com/guide/user?utm_source=webapp\"\n          rel=\"noreferrer\"\n          target=\"_blank\"\n        >\n          <i\n            class=\"CompassIcon icon-help-circle-outline HelpIcon\"\n          />\n        </a>\n      </div>\n      <div\n        class=\"mid-head\"\n      >\n        <div\n          class=\"ViewTitle\"\n        >\n          <div\n            class=\"add-buttons add-visible\"\n          >\n            <button\n              class=\"Button emphasis--default size--xsmall\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-eye-off-outline\"\n              />\n              <span>\n                hide description\n              </span>\n            </button>\n          </div>\n          <div\n            class=\"title\"\n          >\n            <div\n              class=\"IconSelector\"\n            >\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <div\n                  class=\"octo-icon size-m\"\n                >\n                  <span>\n                    i\n                  </span>\n                </div>\n              </div>\n            </div>\n            <input\n              class=\"Editable title\"\n              placeholder=\"Untitled board\"\n              spellcheck=\"true\"\n              title=\"board title\"\n              value=\"board title\"\n            />\n          </div>\n          <div\n            class=\"description\"\n          >\n            <div\n              class=\"MarkdownEditor octo-editor  \"\n            >\n              <div\n                class=\"octo-editor-preview\"\n                data-testid=\"preview-element\"\n              />\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"shareButtonWrapper\"\n        >\n          <div\n            class=\"ShareBoardButton\"\n          >\n            <button\n              class=\"Button emphasis--primary size--medium\"\n              title=\"Share board\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-lock-outline LockOutlineIcon\"\n              />\n              <span>\n                Share\n              </span>\n            </button>\n          </div>\n        </div>\n      </div>\n      <div\n        class=\"ViewHeader\"\n      >\n        <div\n          class=\"viewSelector\"\n        >\n          <input\n            class=\"Editable \"\n            placeholder=\"Untitled View\"\n            spellcheck=\"true\"\n            title=\"view title\"\n            value=\"view title\"\n          />\n          <div>\n            <div\n              aria-label=\"View menu\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-chevron-down DropdownIcon\"\n                />\n              </button>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"octo-spacer\"\n        />\n        <div\n          aria-label=\"Properties menu\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              Properties\n            </span>\n          </button>\n        </div>\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              Group by: \n              <span\n                id=\"groupByLabel\"\n              >\n                name\n              </span>\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"ModalWrapper\"\n        >\n          <button\n            class=\"Button active\"\n            type=\"button\"\n          >\n            <span>\n              Filter\n            </span>\n          </button>\n        </div>\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button active\"\n            type=\"button\"\n          >\n            <span>\n              Sort\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"board-search-field\"\n        >\n          <i\n            class=\"CompassIcon icon-magnify board-search-icon\"\n          />\n          <input\n            class=\"Editable \"\n            placeholder=\"Search cards\"\n            value=\"\"\n          />\n        </div>\n        <div\n          class=\"ModalWrapper\"\n        >\n          <div\n            aria-label=\"View header menu\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <button\n              class=\"IconButton\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n              />\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"ButtonWithMenu\"\n        >\n          <div\n            class=\"button-text\"\n          >\n            New\n          </div>\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <div\n              class=\"button-dropdown\"\n            >\n              <i\n                class=\"CompassIcon icon-chevron-down DropdownIcon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"Kanban\"\n    >\n      <div\n        class=\"octo-board-header\"\n        id=\"mainBoardHeader\"\n      >\n        <div\n          class=\"octo-board-header-cell KanbanColumnHeader\"\n          draggable=\"true\"\n          style=\"opacity: 1;\"\n        >\n          <span\n            class=\"Label empty \"\n            title=\"Items with an empty name property will go here. This column cannot be removed.\"\n          >\n            No name\n          </span>\n          \n          <div\n            class=\"KanbanCalculation\"\n          >\n            <button\n              class=\"Button\"\n              title=\"0\"\n              type=\"button\"\n            >\n              <span>\n                0\n              </span>\n            </button>\n          </div>\n          <div\n            class=\"octo-spacer\"\n          />\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <button\n              class=\"IconButton\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n              />\n            </button>\n          </div>\n          <button\n            class=\"IconButton\"\n            type=\"button\"\n          >\n            <i\n              class=\"CompassIcon icon-plus AddIcon\"\n            />\n          </button>\n        </div>\n        <div\n          class=\"octo-board-header-cell KanbanColumnHeader\"\n          draggable=\"true\"\n          style=\"opacity: 1;\"\n        >\n          <span\n            class=\"Label propColorOrange \"\n          >\n            <input\n              class=\"Editable \"\n              placeholder=\"New Select\"\n              spellcheck=\"true\"\n              title=\"Q1\"\n              value=\"Q1\"\n            />\n          </span>\n          <div\n            class=\"KanbanCalculation\"\n          >\n            <button\n              class=\"Button\"\n              title=\"1\"\n              type=\"button\"\n            >\n              <span>\n                1\n              </span>\n            </button>\n          </div>\n          <div\n            class=\"octo-spacer\"\n          />\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <button\n              class=\"IconButton\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n              />\n            </button>\n          </div>\n          <button\n            class=\"IconButton\"\n            type=\"button\"\n          >\n            <i\n              class=\"CompassIcon icon-plus AddIcon\"\n            />\n          </button>\n        </div>\n        <div\n          class=\"octo-board-header-cell KanbanColumnHeader\"\n          draggable=\"true\"\n          style=\"opacity: 1;\"\n        >\n          <span\n            class=\"Label propColorBlue \"\n          >\n            <input\n              class=\"Editable \"\n              placeholder=\"New Select\"\n              spellcheck=\"true\"\n              title=\"Q2\"\n              value=\"Q2\"\n            />\n          </span>\n          <div\n            class=\"KanbanCalculation\"\n          >\n            <button\n              class=\"Button\"\n              title=\"0\"\n              type=\"button\"\n            >\n              <span>\n                0\n              </span>\n            </button>\n          </div>\n          <div\n            class=\"octo-spacer\"\n          />\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <button\n              class=\"IconButton\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n              />\n            </button>\n          </div>\n          <button\n            class=\"IconButton\"\n            type=\"button\"\n          >\n            <i\n              class=\"CompassIcon icon-plus AddIcon\"\n            />\n          </button>\n        </div>\n        <div\n          class=\"octo-board-header-cell narrow\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              + Add a group\n            </span>\n          </button>\n        </div>\n      </div>\n      <div\n        class=\"octo-board-body\"\n        id=\"mainBoardBody\"\n      >\n        <div\n          class=\"octo-board-column\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              + New\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"octo-board-column\"\n        >\n          <div\n            class=\"KanbanCard\"\n            draggable=\"true\"\n            style=\"opacity: 1;\"\n          >\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper optionsMenu\"\n              role=\"button\"\n            >\n              <button\n                class=\"IconButton CardActionsMenuIcon\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                />\n              </button>\n            </div>\n            <div\n              class=\"octo-icontitle\"\n            >\n              <div\n                class=\"octo-icon\"\n              >\n                i\n              </div>\n              <div\n                class=\"octo-titletext\"\n              >\n                card1\n              </div>\n            </div>\n          </div>\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              + New\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"octo-board-column\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              + New\n            </span>\n          </button>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/centerPanel should match snapshot for Table 1`] = `\n<div>\n  <div\n    class=\"BoardComponent\"\n  >\n    <div\n      class=\"top-head\"\n    >\n      <div\n        class=\"TopBar\"\n      >\n        <a\n          class=\"link\"\n          href=\"https://www.focalboard.com/fwlink/feedback-focalboard.html?v=1.0.0\"\n          rel=\"noreferrer\"\n          target=\"_blank\"\n        >\n          Give feedback\n        </a>\n        <a\n          href=\"https://www.focalboard.com/guide/user?utm_source=webapp\"\n          rel=\"noreferrer\"\n          target=\"_blank\"\n        >\n          <i\n            class=\"CompassIcon icon-help-circle-outline HelpIcon\"\n          />\n        </a>\n      </div>\n      <div\n        class=\"mid-head\"\n      >\n        <div\n          class=\"ViewTitle\"\n        >\n          <div\n            class=\"add-buttons add-visible\"\n          >\n            <button\n              class=\"Button emphasis--default size--xsmall\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-eye-off-outline\"\n              />\n              <span>\n                hide description\n              </span>\n            </button>\n          </div>\n          <div\n            class=\"title\"\n          >\n            <div\n              class=\"IconSelector\"\n            >\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <div\n                  class=\"octo-icon size-m\"\n                >\n                  <span>\n                    i\n                  </span>\n                </div>\n              </div>\n            </div>\n            <input\n              class=\"Editable title\"\n              placeholder=\"Untitled board\"\n              spellcheck=\"true\"\n              title=\"board title\"\n              value=\"board title\"\n            />\n          </div>\n          <div\n            class=\"description\"\n          >\n            <div\n              class=\"MarkdownEditor octo-editor  \"\n            >\n              <div\n                class=\"octo-editor-preview\"\n                data-testid=\"preview-element\"\n              />\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"shareButtonWrapper\"\n        >\n          <div\n            class=\"ShareBoardButton\"\n          >\n            <button\n              class=\"Button emphasis--primary size--medium\"\n              title=\"Share board\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-lock-outline LockOutlineIcon\"\n              />\n              <span>\n                Share\n              </span>\n            </button>\n          </div>\n        </div>\n      </div>\n      <div\n        class=\"ViewHeader\"\n      >\n        <div\n          class=\"viewSelector\"\n        >\n          <input\n            class=\"Editable \"\n            placeholder=\"Untitled View\"\n            spellcheck=\"true\"\n            title=\"view title\"\n            value=\"view title\"\n          />\n          <div>\n            <div\n              aria-label=\"View menu\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-chevron-down DropdownIcon\"\n                />\n              </button>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"octo-spacer\"\n        />\n        <div\n          aria-label=\"Properties menu\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              Properties\n            </span>\n          </button>\n        </div>\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              Group by: \n              <span\n                id=\"groupByLabel\"\n              >\n                name\n              </span>\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"ModalWrapper\"\n        >\n          <button\n            class=\"Button active\"\n            type=\"button\"\n          >\n            <span>\n              Filter\n            </span>\n          </button>\n        </div>\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button active\"\n            type=\"button\"\n          >\n            <span>\n              Sort\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"board-search-field\"\n        >\n          <i\n            class=\"CompassIcon icon-magnify board-search-icon\"\n          />\n          <input\n            class=\"Editable \"\n            placeholder=\"Search cards\"\n            value=\"\"\n          />\n        </div>\n        <div\n          class=\"ModalWrapper\"\n        >\n          <div\n            aria-label=\"View header menu\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <button\n              class=\"IconButton\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n              />\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"ButtonWithMenu\"\n        >\n          <div\n            class=\"button-text\"\n          >\n            New\n          </div>\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <div\n              class=\"button-dropdown\"\n            >\n              <i\n                class=\"CompassIcon icon-chevron-down DropdownIcon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"Table\"\n    >\n      <div\n        class=\"octo-table-body\"\n      >\n        <div\n          class=\"octo-table-header TableHeaders\"\n          id=\"mainBoardHeader\"\n        >\n          <div\n            class=\"octo-table-cell header-cell\"\n            style=\"overflow: unset; opacity: 1; width: 100px;\"\n          >\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <span\n                class=\"Label empty \"\n              >\n                Name\n              </span>\n            </div>\n            <div\n              class=\"octo-spacer\"\n            />\n            <div\n              class=\"HorizontalGrip\"\n            />\n          </div>\n        </div>\n        <div\n          class=\"table-row-container\"\n        >\n          <div\n            class=\"octo-table-group\"\n          >\n            <div\n              class=\"octo-group-header-cell expanded\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"octo-table-cell\"\n                style=\"width: 100px;\"\n              >\n                <button\n                  class=\"IconButton octo-table-cell__expand \"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-menu-right\"\n                  />\n                </button>\n                <span\n                  class=\"Label empty \"\n                  title=\"Items with an empty name property will go here. This column cannot be removed.\"\n                >\n                  No name\n                </span>\n                \n              </div>\n              <button\n                class=\"Button\"\n                type=\"button\"\n              >\n                <span>\n                  0\n                </span>\n              </button>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-plus AddIcon\"\n                />\n              </button>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-group\"\n          >\n            <div\n              class=\"octo-group-header-cell expanded\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"octo-table-cell\"\n                style=\"width: 100px;\"\n              >\n                <button\n                  class=\"IconButton octo-table-cell__expand \"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-menu-right\"\n                  />\n                </button>\n                <span\n                  class=\"Label propColorOrange \"\n                >\n                  <input\n                    class=\"Editable \"\n                    placeholder=\"New Select\"\n                    spellcheck=\"true\"\n                    title=\"Q1\"\n                    value=\"Q1\"\n                  />\n                </span>\n              </div>\n              <button\n                class=\"Button\"\n                type=\"button\"\n              >\n                <span>\n                  1\n                </span>\n              </button>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-plus AddIcon\"\n                />\n              </button>\n            </div>\n            <div\n              class=\"TableRow octo-table-row\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"action-cell octo-table-cell-btn\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-drag-vertical\"\n                  />\n                </button>\n              </div>\n              <div\n                class=\"octo-table-cell title-cell\"\n                id=\"mainBoardHeader\"\n                style=\"width: 100px;\"\n              >\n                <div\n                  class=\"octo-icontitle\"\n                >\n                  <div\n                    class=\"octo-icon\"\n                  >\n                    i\n                  </div>\n                  <input\n                    class=\"Editable \"\n                    placeholder=\"Untitled\"\n                    spellcheck=\"true\"\n                    title=\"card1\"\n                    value=\"card1\"\n                  />\n                </div>\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper optionsMenu ml-2 mr-2\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"octo-tooltip tooltip-top\"\n                    data-tooltip=\"More actions\"\n                  >\n                    <button\n                      aria-label=\"MenuBtn\"\n                      class=\"IconButton\"\n                      title=\"MenuBtn\"\n                      type=\"button\"\n                    >\n                      <i\n                        class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                      />\n                    </button>\n                  </div>\n                </div>\n                <div\n                  class=\"open-button\"\n                >\n                  <button\n                    class=\"Button\"\n                    type=\"button\"\n                  >\n                    <span>\n                      Open\n                    </span>\n                  </button>\n                </div>\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-group\"\n          >\n            <div\n              class=\"octo-group-header-cell expanded\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <div\n                class=\"octo-table-cell\"\n                style=\"width: 100px;\"\n              >\n                <button\n                  class=\"IconButton octo-table-cell__expand \"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-menu-right\"\n                  />\n                </button>\n                <span\n                  class=\"Label propColorBlue \"\n                >\n                  <input\n                    class=\"Editable \"\n                    placeholder=\"New Select\"\n                    spellcheck=\"true\"\n                    title=\"Q2\"\n                    value=\"Q2\"\n                  />\n                </span>\n              </div>\n              <button\n                class=\"Button\"\n                type=\"button\"\n              >\n                <span>\n                  0\n                </span>\n              </button>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-plus AddIcon\"\n                />\n              </button>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"octo-table-footer\"\n        />\n        <div\n          class=\"CalculationRow octo-table-row\"\n        >\n          <div\n            class=\"Calculation count octo-table-cell   \"\n            style=\"width: 100px;\"\n            tabindex=\"0\"\n          >\n            <span\n              class=\"calculationLabel\"\n            >\n              Count\n            </span>\n            <span\n              class=\"calculationValue\"\n            >\n              1\n            </span>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/__snapshots__/confirmAddUserForNotifications.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`/components/confirmAddUserForNotifications should match snapshot 1`] = `\n<div>\n  <div\n    class=\"Dialog dialog-back confirmation-dialog-box size--small\"\n  >\n    <div\n      class=\"backdrop\"\n    />\n    <div\n      class=\"wrapper\"\n    >\n      <div\n        class=\"dialog\"\n        role=\"dialog\"\n      >\n        <div\n          class=\"toolbar\"\n        >\n          <div>\n            <h1\n              class=\"dialog-title\"\n            />\n          </div>\n          <div\n            class=\"toolbar--right\"\n          >\n            <button\n              aria-label=\"Close dialog\"\n              class=\"IconButton dialog__close size--medium\"\n              title=\"Close dialog\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-close CloseIcon\"\n              />\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"box-area\"\n          title=\"Confirmation Dialog Box\"\n        >\n          <h3\n            class=\"text-heading5\"\n          >\n            Add fake-username to board\n          </h3>\n          <div\n            class=\"sub-text\"\n          >\n            <div\n              class=\"ConfirmAddUserForNotifications\"\n            >\n              <p>\n                fake-username is not a member of the board, and will not receive any notifications about it.\n              </p>\n              <p>\n                Do you want to add fake-username to the board?\n              </p>\n              <div\n                class=\"permissions-title\"\n              >\n                <label>\n                  Permissions\n                </label>\n              </div>\n              <div\n                class=\"select css-b62m3t-container\"\n              >\n                <span\n                  class=\"css-1f43avz-a11yText-A11yText\"\n                  id=\"react-select-2-live-region\"\n                />\n                <span\n                  aria-atomic=\"false\"\n                  aria-live=\"polite\"\n                  aria-relevant=\"additions text\"\n                  class=\"css-1f43avz-a11yText-A11yText\"\n                  role=\"log\"\n                />\n                <div\n                  class=\" css-13cymwt-control\"\n                >\n                  <div\n                    class=\" css-1fdsijx-ValueContainer\"\n                  >\n                    <div\n                      class=\" css-1dimb5e-singleValue\"\n                    >\n                      Editor\n                    </div>\n                    <div\n                      class=\" css-qbdosj-Input\"\n                      data-value=\"\"\n                    >\n                      <input\n                        aria-activedescendant=\"\"\n                        aria-autocomplete=\"list\"\n                        aria-expanded=\"false\"\n                        aria-haspopup=\"true\"\n                        autocapitalize=\"none\"\n                        autocomplete=\"off\"\n                        autocorrect=\"off\"\n                        class=\"\"\n                        id=\"react-select-2-input\"\n                        role=\"combobox\"\n                        spellcheck=\"false\"\n                        style=\"opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;\"\n                        tabindex=\"0\"\n                        type=\"text\"\n                        value=\"\"\n                      />\n                    </div>\n                  </div>\n                  <div\n                    class=\" css-1hb7zxy-IndicatorsContainer\"\n                  >\n                    <span\n                      class=\" css-1u9des2-indicatorSeparator\"\n                    />\n                    <div\n                      aria-hidden=\"true\"\n                      class=\" css-1xc3v61-indicatorContainer\"\n                    >\n                      <svg\n                        aria-hidden=\"true\"\n                        class=\"css-tj5bde-Svg\"\n                        focusable=\"false\"\n                        height=\"20\"\n                        viewBox=\"0 0 20 20\"\n                        width=\"20\"\n                      >\n                        <path\n                          d=\"M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z\"\n                        />\n                      </svg>\n                    </div>\n                  </div>\n                </div>\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"action-buttons\"\n          >\n            <button\n              class=\"Button emphasis--tertiary size--medium\"\n              title=\"Cancel\"\n              type=\"button\"\n            >\n              <span>\n                Cancel\n              </span>\n            </button>\n            <button\n              class=\"Button filled size--medium\"\n              title=\"Add to board\"\n              type=\"submit\"\n            >\n              <span>\n                Add to board\n              </span>\n            </button>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/__snapshots__/confirmationDialogBox.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`/components/confirmationDialogBox confirmDialog should match snapshot 1`] = `\n<div>\n  <div\n    class=\"Dialog dialog-back confirmation-dialog-box size--small\"\n  >\n    <div\n      class=\"backdrop\"\n    />\n    <div\n      class=\"wrapper\"\n    >\n      <div\n        class=\"dialog\"\n        role=\"dialog\"\n      >\n        <div\n          class=\"toolbar\"\n        >\n          <div>\n            <h1\n              class=\"dialog-title\"\n            />\n          </div>\n          <div\n            class=\"toolbar--right\"\n          >\n            <button\n              aria-label=\"Close dialog\"\n              class=\"IconButton dialog__close size--medium\"\n              title=\"Close dialog\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-close CloseIcon\"\n              />\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"box-area\"\n          title=\"Confirmation Dialog Box\"\n        >\n          <h3\n            class=\"text-heading5\"\n          >\n            test-heading\n          </h3>\n          <div\n            class=\"sub-text\"\n          >\n            test-sub-text\n          </div>\n          <div\n            class=\"action-buttons\"\n          >\n            <button\n              class=\"Button emphasis--tertiary size--medium\"\n              title=\"Cancel\"\n              type=\"button\"\n            >\n              <span>\n                Cancel\n              </span>\n            </button>\n            <button\n              class=\"Button filled size--medium\"\n              title=\"test-btn-text\"\n              type=\"submit\"\n            >\n              <span>\n                test-btn-text\n              </span>\n            </button>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`/components/confirmationDialogBox confirmDialog with Confirm Button Text should match snapshot 1`] = `\n<div>\n  <div\n    class=\"Dialog dialog-back confirmation-dialog-box size--small\"\n  >\n    <div\n      class=\"backdrop\"\n    />\n    <div\n      class=\"wrapper\"\n    >\n      <div\n        class=\"dialog\"\n        role=\"dialog\"\n      >\n        <div\n          class=\"toolbar\"\n        >\n          <div>\n            <h1\n              class=\"dialog-title\"\n            />\n          </div>\n          <div\n            class=\"toolbar--right\"\n          >\n            <button\n              aria-label=\"Close dialog\"\n              class=\"IconButton dialog__close size--medium\"\n              title=\"Close dialog\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-close CloseIcon\"\n              />\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"box-area\"\n          title=\"Confirmation Dialog Box\"\n        >\n          <h3\n            class=\"text-heading5\"\n          >\n            test-heading\n          </h3>\n          <div\n            class=\"sub-text\"\n          >\n            test-sub-text\n          </div>\n          <div\n            class=\"action-buttons\"\n          >\n            <button\n              class=\"Button emphasis--tertiary size--medium\"\n              title=\"Cancel\"\n              type=\"button\"\n            >\n              <span>\n                Cancel\n              </span>\n            </button>\n            <button\n              class=\"Button filled size--medium\"\n              title=\"test-btn-text\"\n              type=\"submit\"\n            >\n              <span>\n                test-btn-text\n              </span>\n            </button>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/__snapshots__/contentBlock.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/contentBlock return commentBlock and click on menuwrapper 1`] = `\n<div>\n  <div\n    class=\"rowContents\"\n  >\n    <div\n      class=\"ContentBlock octo-block menuOpened\"\n    >\n      <div\n        class=\"octo-block-margin\"\n      >\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper override menuOpened\"\n          role=\"button\"\n        >\n          <button\n            type=\"button\"\n          >\n            <i\n              class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n            />\n          </button>\n          <div\n            class=\"Menu noselect bottom \"\n          >\n            <div\n              class=\"menu-contents\"\n            >\n              <div\n                class=\"menu-options\"\n              >\n                <div>\n                  <div\n                    aria-label=\"Move up\"\n                    class=\"MenuOption TextOption menu-option\"\n                    role=\"button\"\n                  >\n                    <div\n                      class=\"d-flex\"\n                    >\n                      <div\n                        class=\"menu-option__icon\"\n                      >\n                        <svg\n                          class=\"SortUpIcon Icon\"\n                          viewBox=\"0 0 100 100\"\n                          xmlns=\"http://www.w3.org/2000/svg\"\n                        >\n                          <polyline\n                            points=\"50,20 50,80\"\n                          />\n                          <polyline\n                            points=\"30,40 50,20 70,40\"\n                          />\n                        </svg>\n                      </div>\n                    </div>\n                    <div\n                      class=\"menu-option__content\"\n                    >\n                      <div\n                        class=\"menu-name\"\n                      >\n                        Move up\n                      </div>\n                    </div>\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                </div>\n                <div>\n                  <div\n                    aria-label=\"Move down\"\n                    class=\"MenuOption TextOption menu-option\"\n                    role=\"button\"\n                  >\n                    <div\n                      class=\"d-flex\"\n                    >\n                      <div\n                        class=\"menu-option__icon\"\n                      >\n                        <svg\n                          class=\"SortDownIcon Icon\"\n                          viewBox=\"0 0 100 100\"\n                          xmlns=\"http://www.w3.org/2000/svg\"\n                        >\n                          <polyline\n                            points=\"50,20 50,80\"\n                          />\n                          <polyline\n                            points=\"30,60 50,80 70,60\"\n                          />\n                        </svg>\n                      </div>\n                    </div>\n                    <div\n                      class=\"menu-option__content\"\n                    >\n                      <div\n                        class=\"menu-name\"\n                      >\n                        Move down\n                      </div>\n                    </div>\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                </div>\n                <div>\n                  <div\n                    class=\"MenuOption SubMenuOption menu-option\"\n                    id=\"insertAbove\"\n                  >\n                    <div\n                      class=\"menu-option__icon\"\n                    >\n                      <i\n                        class=\"CompassIcon icon-plus AddIcon\"\n                      />\n                    </div>\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Insert above\n                    </div>\n                    <i\n                      class=\"CompassIcon icon-chevron-right\"\n                    />\n                  </div>\n                </div>\n                <div>\n                  <div\n                    aria-label=\"Delete\"\n                    class=\"MenuOption TextOption menu-option\"\n                    role=\"button\"\n                  >\n                    <div\n                      class=\"d-flex\"\n                    >\n                      <div\n                        class=\"menu-option__icon\"\n                      >\n                        <i\n                          class=\"CompassIcon icon-trash-can-outline DeleteIcon trash-can-outline\"\n                        />\n                      </div>\n                    </div>\n                    <div\n                      class=\"menu-option__content\"\n                    >\n                      <div\n                        class=\"menu-name\"\n                      >\n                        Delete\n                      </div>\n                    </div>\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                </div>\n              </div>\n              <div\n                class=\"menu-spacer hideOnWidescreen\"\n              />\n              <div\n                class=\"menu-options hideOnWidescreen\"\n              >\n                <div\n                  aria-label=\"Cancel\"\n                  class=\"MenuOption TextOption menu-option menu-cancel\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex\"\n                  >\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Cancel\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"dnd-handle\"\n          draggable=\"true\"\n        >\n          <svg\n            class=\"GripIcon Icon\"\n            viewBox=\"0 0 24 24\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n          >\n            <path\n              d=\"M0 0h24v24H0V0z\"\n              fill=\"none\"\n            />\n            <path\n              d=\"M11 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm-2-8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z\"\n            />\n          </svg>\n        </div>\n      </div>\n      <div\n        class=\"addToRow \"\n        style=\"flex: 0 0 auto; height: 100%;\"\n      />\n    </div>\n    <div\n      class=\"addToRow \"\n    />\n  </div>\n</div>\n`;\n\nexports[`components/contentBlock should match snapshot with commentBlock 1`] = `\n<div>\n  <div\n    class=\"rowContents\"\n  >\n    <div\n      class=\"ContentBlock octo-block\"\n    >\n      <div\n        class=\"octo-block-margin\"\n      >\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            type=\"button\"\n          >\n            <i\n              class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n            />\n          </button>\n        </div>\n        <div\n          class=\"dnd-handle\"\n          draggable=\"true\"\n        >\n          <svg\n            class=\"GripIcon Icon\"\n            viewBox=\"0 0 24 24\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n          >\n            <path\n              d=\"M0 0h24v24H0V0z\"\n              fill=\"none\"\n            />\n            <path\n              d=\"M11 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm-2-8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z\"\n            />\n          </svg>\n        </div>\n      </div>\n      <div\n        class=\"addToRow \"\n        style=\"flex: 0 0 auto; height: 100%;\"\n      />\n    </div>\n    <div\n      class=\"addToRow \"\n    />\n  </div>\n</div>\n`;\n\nexports[`components/contentBlock should match snapshot with commentBlock readonly 1`] = `\n<div>\n  <div\n    class=\"rowContents\"\n  >\n    <div\n      class=\"ContentBlock octo-block\"\n    >\n      <div\n        class=\"octo-block-margin\"\n      />\n      <div\n        class=\"addToRow \"\n        style=\"flex: 0 0 auto; height: 100%;\"\n      />\n    </div>\n    <div\n      class=\"addToRow \"\n    />\n  </div>\n</div>\n`;\n\nexports[`components/contentBlock should match snapshot with dividerBlock 1`] = `\n<div>\n  <div\n    class=\"rowContents\"\n  >\n    <div\n      class=\"ContentBlock octo-block\"\n    >\n      <div\n        class=\"octo-block-margin\"\n      >\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            type=\"button\"\n          >\n            <i\n              class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n            />\n          </button>\n        </div>\n        <div\n          class=\"dnd-handle\"\n          draggable=\"true\"\n        >\n          <svg\n            class=\"GripIcon Icon\"\n            viewBox=\"0 0 24 24\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n          >\n            <path\n              d=\"M0 0h24v24H0V0z\"\n              fill=\"none\"\n            />\n            <path\n              d=\"M11 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm-2-8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z\"\n            />\n          </svg>\n        </div>\n      </div>\n      <div\n        class=\"addToRow \"\n        style=\"flex: 0 0 auto; height: 100%;\"\n      />\n      <div\n        class=\"DividerElement\"\n      />\n    </div>\n    <div\n      class=\"addToRow \"\n    />\n  </div>\n</div>\n`;\n\nexports[`components/contentBlock should match snapshot with imageBlock 1`] = `\n<div>\n  <div\n    class=\"rowContents\"\n  >\n    <div\n      class=\"ContentBlock octo-block\"\n    >\n      <div\n        class=\"octo-block-margin\"\n      >\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            type=\"button\"\n          >\n            <i\n              class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n            />\n          </button>\n        </div>\n        <div\n          class=\"dnd-handle\"\n          draggable=\"true\"\n        >\n          <svg\n            class=\"GripIcon Icon\"\n            viewBox=\"0 0 24 24\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n          >\n            <path\n              d=\"M0 0h24v24H0V0z\"\n              fill=\"none\"\n            />\n            <path\n              d=\"M11 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm-2-8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z\"\n            />\n          </svg>\n        </div>\n      </div>\n      <div\n        class=\"addToRow \"\n        style=\"flex: 0 0 auto; height: 100%;\"\n      />\n      <img\n        alt=\"\"\n        class=\"ImageElement\"\n        src=\"test.jpg\"\n      />\n    </div>\n    <div\n      class=\"addToRow \"\n    />\n  </div>\n</div>\n`;\n\nexports[`components/contentBlock should match snapshot with textBlock 1`] = `\n<div>\n  <div\n    class=\"rowContents\"\n  >\n    <div\n      class=\"ContentBlock octo-block\"\n    >\n      <div\n        class=\"octo-block-margin\"\n      >\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            type=\"button\"\n          >\n            <i\n              class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n            />\n          </button>\n        </div>\n        <div\n          class=\"dnd-handle\"\n          draggable=\"true\"\n        >\n          <svg\n            class=\"GripIcon Icon\"\n            viewBox=\"0 0 24 24\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n          >\n            <path\n              d=\"M0 0h24v24H0V0z\"\n              fill=\"none\"\n            />\n            <path\n              d=\"M11 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm-2-8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z\"\n            />\n          </svg>\n        </div>\n      </div>\n      <div\n        class=\"addToRow \"\n        style=\"flex: 0 0 auto; height: 100%;\"\n      />\n      <div\n        class=\"TextElement\"\n      >\n        <div\n          class=\"MarkdownEditor octo-editor  \"\n        >\n          <div\n            class=\"octo-editor-preview\"\n            data-testid=\"preview-element\"\n          />\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"addToRow \"\n    />\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/__snapshots__/dialog.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/dialog should match snapshot 1`] = `\n<div>\n  <div\n    class=\"Dialog dialog-back undefined size--medium\"\n  >\n    <div\n      class=\"backdrop\"\n    />\n    <div\n      class=\"wrapper\"\n    >\n      <div\n        class=\"dialog\"\n        role=\"dialog\"\n      >\n        <div\n          class=\"toolbar\"\n        >\n          <div>\n            <h1\n              class=\"dialog-title\"\n            />\n          </div>\n          <div\n            class=\"toolbar--right\"\n          >\n            <button\n              aria-label=\"Close dialog\"\n              class=\"IconButton dialog__close size--medium\"\n              title=\"Close dialog\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-close CloseIcon\"\n              />\n            </button>\n          </div>\n        </div>\n        <div\n          id=\"test\"\n        />\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/dialog should return dialog and click on cancel button 1`] = `\n<div>\n  <div\n    class=\"Dialog dialog-back undefined size--medium\"\n  >\n    <div\n      class=\"backdrop\"\n    />\n    <div\n      class=\"wrapper\"\n    >\n      <div\n        class=\"dialog\"\n        role=\"dialog\"\n      >\n        <div\n          class=\"toolbar\"\n        >\n          <div>\n            <h1\n              class=\"dialog-title\"\n            />\n          </div>\n          <div\n            class=\"toolbar--right\"\n          >\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <button\n                class=\"IconButton size--medium\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                />\n              </button>\n            </div>\n            <button\n              aria-label=\"Close dialog\"\n              class=\"IconButton dialog__close size--medium\"\n              title=\"Close dialog\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-close CloseIcon\"\n              />\n            </button>\n          </div>\n        </div>\n        <div\n          id=\"test\"\n        />\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/__snapshots__/flashMessages.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/flashMessages renders a flash message with high severity 1`] = `\n<div>\n  <div\n    class=\"FlashMessages high flashIn\"\n  >\n    Mock Content\n  </div>\n</div>\n`;\n\nexports[`components/flashMessages renders a flash message with low severity 1`] = `\n<div>\n  <div\n    class=\"FlashMessages low flashIn\"\n  >\n    Mock Content\n  </div>\n</div>\n`;\n\nexports[`components/flashMessages renders a flash message with low severity and check onClick on flash works 1`] = `\n<div>\n  <div\n    class=\"FlashMessages low flashOut\"\n  >\n    Mock Content\n  </div>\n</div>\n`;\n\nexports[`components/flashMessages renders a flash message with low severity and custom HTML in flash message 1`] = `\n<div>\n  <div\n    class=\"FlashMessages low flashIn\"\n  >\n    <div\n      data-testid=\"mock-test-id\"\n    >\n      Mock Content\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/flashMessages renders a flash message with normal severity 1`] = `\n<div>\n  <div\n    class=\"FlashMessages normal flashIn\"\n  >\n    Mock Content\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/__snapshots__/markdownEditor.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/markdownEditor should match snapshot 1`] = `\n<div>\n  <div\n    class=\"MarkdownEditor octo-editor classname-test \"\n  >\n    <div\n      class=\"octo-editor-preview octo-placeholder\"\n      data-testid=\"preview-element\"\n    />\n  </div>\n</div>\n`;\n\nexports[`components/markdownEditor should match snapshot with initial text 1`] = `\n<div>\n  <div\n    class=\"MarkdownEditor octo-editor classname-test \"\n  >\n    <div\n      class=\"octo-editor-preview\"\n      data-testid=\"preview-element\"\n    />\n  </div>\n</div>\n`;\n\nexports[`components/markdownEditor should match snapshot with on click on preview element 1`] = `\n<div>\n  <div\n    class=\"MarkdownEditor octo-editor classname-test active\"\n  />\n</div>\n`;\n\nexports[`components/markdownEditor should match snapshot with on click on preview element and then click out of it 1`] = `\n<div>\n  <div\n    class=\"MarkdownEditor octo-editor classname-test active\"\n  >\n    <div\n      class=\"MarkdownEditorInput\"\n    >\n      <div\n        class=\"DraftEditor-root\"\n      >\n        <div\n          class=\"DraftEditor-editorContainer\"\n        >\n          <div\n            aria-autocomplete=\"list\"\n            aria-expanded=\"false\"\n            class=\"notranslate public-DraftEditor-content\"\n            contenteditable=\"true\"\n            role=\"combobox\"\n            spellcheck=\"false\"\n            style=\"outline: none; user-select: text; white-space: pre-wrap; word-wrap: break-word;\"\n          >\n            <div\n              data-contents=\"true\"\n            >\n              <div\n                class=\"\"\n                data-block=\"true\"\n                data-editor=\"test-id\"\n                data-offset-key=\"123-0-0\"\n              >\n                <div\n                  class=\"public-DraftStyleDefault-block public-DraftStyleDefault-ltr\"\n                  data-offset-key=\"123-0-0\"\n                >\n                  <span\n                    data-offset-key=\"123-0-0\"\n                  >\n                    <span\n                      data-text=\"true\"\n                    >\n                      some initial text already set\n                    </span>\n                  </span>\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/__snapshots__/modal.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/modal return Modal on position bottom 1`] = `\n<div>\n  <div\n    class=\"Modal bottom\"\n  >\n    <div\n      class=\"toolbar hideOnWidescreen\"\n    >\n      <button\n        aria-label=\"Close\"\n        class=\"IconButton\"\n        title=\"Close\"\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-close CloseIcon\"\n        />\n      </button>\n    </div>\n    <div\n      id=\"test\"\n    />\n  </div>\n</div>\n`;\n\nexports[`components/modal return Modal on position bottom-right 1`] = `\n<div>\n  <div\n    class=\"Modal bottom-right\"\n  >\n    <div\n      class=\"toolbar hideOnWidescreen\"\n    >\n      <button\n        aria-label=\"Close\"\n        class=\"IconButton\"\n        title=\"Close\"\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-close CloseIcon\"\n        />\n      </button>\n    </div>\n    <div\n      id=\"test\"\n    />\n  </div>\n</div>\n`;\n\nexports[`components/modal return Modal on position top 1`] = `\n<div>\n  <div\n    class=\"Modal top\"\n  >\n    <div\n      class=\"toolbar hideOnWidescreen\"\n    >\n      <button\n        aria-label=\"Close\"\n        class=\"IconButton\"\n        title=\"Close\"\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-close CloseIcon\"\n        />\n      </button>\n    </div>\n    <div\n      id=\"test\"\n    />\n  </div>\n</div>\n`;\n\nexports[`components/modal should match snapshot 1`] = `\n<div>\n  <div\n    class=\"Modal bottom\"\n  >\n    <div\n      class=\"toolbar hideOnWidescreen\"\n    >\n      <button\n        aria-label=\"Close\"\n        class=\"IconButton\"\n        title=\"Close\"\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-close CloseIcon\"\n        />\n      </button>\n    </div>\n    <div\n      id=\"test\"\n    />\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/__snapshots__/personSelector.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`properties/person not readOnly, show firstname 1`] = `\n<div>\n  <div\n    class=\"Person octo-propertyvalue css-b62m3t-container\"\n  >\n    <span\n      class=\"css-1f43avz-a11yText-A11yText\"\n      id=\"react-select-3-live-region\"\n    />\n    <span\n      aria-atomic=\"false\"\n      aria-live=\"polite\"\n      aria-relevant=\"additions text\"\n      class=\"css-1f43avz-a11yText-A11yText\"\n      role=\"log\"\n    />\n    <div\n      class=\"react-select__control css-18140j1-Control\"\n    >\n      <div\n        class=\"react-select__value-container react-select__value-container--has-value css-1gbdvdc-ValueContainer\"\n      >\n        <div\n          class=\"react-select__single-value css-qosd1h-singleValue\"\n        >\n          <div\n            class=\"Person-item\"\n          >\n            test user\n          </div>\n        </div>\n        <div\n          class=\"react-select__input-container css-1p5v8kp-Input\"\n          data-value=\"\"\n        >\n          <input\n            aria-activedescendant=\"\"\n            aria-autocomplete=\"list\"\n            aria-expanded=\"false\"\n            aria-haspopup=\"true\"\n            autocapitalize=\"none\"\n            autocomplete=\"off\"\n            autocorrect=\"off\"\n            class=\"react-select__input\"\n            id=\"react-select-3-input\"\n            role=\"combobox\"\n            spellcheck=\"false\"\n            style=\"opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;\"\n            tabindex=\"0\"\n            type=\"text\"\n            value=\"\"\n          />\n        </div>\n      </div>\n      <div\n        class=\"react-select__indicators css-1hb7zxy-IndicatorsContainer\"\n      >\n        <div\n          aria-hidden=\"true\"\n          class=\"react-select__indicator react-select__clear-indicator css-31haax-indicatorContainer\"\n        >\n          <svg\n            aria-hidden=\"true\"\n            class=\"css-tj5bde-Svg\"\n            focusable=\"false\"\n            height=\"20\"\n            viewBox=\"0 0 20 20\"\n            width=\"20\"\n          >\n            <path\n              d=\"M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z\"\n            />\n          </svg>\n        </div>\n        <span\n          class=\"react-select__indicator-separator css-1uei4ir-indicatorSeparator\"\n        />\n        <div\n          aria-hidden=\"true\"\n          class=\"react-select__indicator react-select__dropdown-indicator css-uycnsi-indicatorContainer\"\n        >\n          <svg\n            aria-hidden=\"true\"\n            class=\"css-tj5bde-Svg\"\n            focusable=\"false\"\n            height=\"20\"\n            viewBox=\"0 0 20 20\"\n            width=\"20\"\n          >\n            <path\n              d=\"M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z\"\n            />\n          </svg>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`properties/person not readOnly, show me 1`] = `\n<div>\n  <div\n    class=\"Person octo-propertyvalue css-b62m3t-container\"\n  >\n    <span\n      class=\"css-1f43avz-a11yText-A11yText\"\n      id=\"react-select-7-live-region\"\n    />\n    <span\n      aria-atomic=\"false\"\n      aria-live=\"polite\"\n      aria-relevant=\"additions text\"\n      class=\"css-1f43avz-a11yText-A11yText\"\n      role=\"log\"\n    >\n      <span\n        id=\"aria-selection\"\n      />\n      <span\n        id=\"aria-focused\"\n      />\n      <span\n        id=\"aria-results\"\n      >\n        4 results available.\n      </span>\n      <span\n        id=\"aria-guidance\"\n      >\n        Use Up and Down to choose options, press Enter to select the currently focused option, press Escape to exit the menu, press Tab to select the option and exit the menu.\n      </span>\n    </span>\n    <div\n      class=\"react-select__control react-select__control--is-focused react-select__control--menu-is-open css-18140j1-Control\"\n    >\n      <div\n        class=\"react-select__value-container css-1gbdvdc-ValueContainer\"\n      >\n        <div\n          class=\"react-select__placeholder css-1jqq78o-placeholder\"\n          id=\"react-select-7-placeholder\"\n        >\n          Empty\n        </div>\n        <div\n          class=\"react-select__input-container css-1p5v8kp-Input\"\n          data-value=\"\"\n        >\n          <input\n            aria-activedescendant=\"react-select-7-option-0\"\n            aria-autocomplete=\"list\"\n            aria-controls=\"react-select-7-listbox\"\n            aria-describedby=\"react-select-7-placeholder\"\n            aria-expanded=\"true\"\n            aria-haspopup=\"true\"\n            autocapitalize=\"none\"\n            autocomplete=\"off\"\n            autocorrect=\"off\"\n            class=\"react-select__input\"\n            id=\"react-select-7-input\"\n            role=\"combobox\"\n            spellcheck=\"false\"\n            style=\"opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;\"\n            tabindex=\"0\"\n            type=\"text\"\n            value=\"\"\n          />\n        </div>\n      </div>\n      <div\n        class=\"react-select__indicators css-1hb7zxy-IndicatorsContainer\"\n      >\n        <span\n          class=\"react-select__indicator-separator css-1uei4ir-indicatorSeparator\"\n        />\n        <div\n          aria-hidden=\"true\"\n          class=\"react-select__indicator react-select__dropdown-indicator css-zngtjc-indicatorContainer\"\n        >\n          <svg\n            aria-hidden=\"true\"\n            class=\"css-tj5bde-Svg\"\n            focusable=\"false\"\n            height=\"20\"\n            viewBox=\"0 0 20 20\"\n            width=\"20\"\n          >\n            <path\n              d=\"M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z\"\n            />\n          </svg>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"react-select__menu css-1slvcut-menu\"\n    >\n      <div\n        aria-multiselectable=\"false\"\n        class=\"react-select__menu-list css-1d1qzc4-MenuList\"\n        id=\"react-select-7-listbox\"\n        role=\"listbox\"\n      >\n        <div\n          aria-disabled=\"false\"\n          aria-selected=\"false\"\n          class=\"react-select__option react-select__option--is-focused css-63bi6m-option\"\n          id=\"react-select-7-option-0\"\n          role=\"option\"\n          tabindex=\"-1\"\n        >\n          <div\n            class=\"Person-item\"\n          >\n            Me\n          </div>\n        </div>\n        <div\n          aria-disabled=\"false\"\n          aria-selected=\"false\"\n          class=\"react-select__option css-1uk8033-option\"\n          id=\"react-select-7-option-1\"\n          role=\"option\"\n          tabindex=\"-1\"\n        >\n          <div\n            class=\"Person-item\"\n          >\n            username-1\n          </div>\n        </div>\n        <div\n          aria-disabled=\"false\"\n          aria-selected=\"false\"\n          class=\"react-select__option css-1uk8033-option\"\n          id=\"react-select-7-option-2\"\n          role=\"option\"\n          tabindex=\"-1\"\n        >\n          <div\n            class=\"Person-item\"\n          >\n            username-2\n          </div>\n        </div>\n        <div\n          aria-disabled=\"false\"\n          aria-selected=\"false\"\n          class=\"react-select__option css-1uk8033-option\"\n          id=\"react-select-7-option-3\"\n          role=\"option\"\n          tabindex=\"-1\"\n        >\n          <div\n            class=\"Person-item\"\n          >\n            username-3\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`properties/person not readOnly, show me 2`] = `\n<div>\n  <div\n    class=\"Person octo-propertyvalue css-b62m3t-container\"\n  >\n    <span\n      class=\"css-1f43avz-a11yText-A11yText\"\n      id=\"react-select-7-live-region\"\n    />\n    <span\n      aria-atomic=\"false\"\n      aria-live=\"polite\"\n      aria-relevant=\"additions text\"\n      class=\"css-1f43avz-a11yText-A11yText\"\n      role=\"log\"\n    >\n      <span\n        id=\"aria-selection\"\n      />\n      <span\n        id=\"aria-focused\"\n      />\n      <span\n        id=\"aria-results\"\n      >\n        4 results available.\n      </span>\n      <span\n        id=\"aria-guidance\"\n      >\n        Use Up and Down to choose options, press Enter to select the currently focused option, press Escape to exit the menu, press Tab to select the option and exit the menu.\n      </span>\n    </span>\n    <div\n      class=\"react-select__control react-select__control--is-focused react-select__control--menu-is-open css-18140j1-Control\"\n    >\n      <div\n        class=\"react-select__value-container css-1gbdvdc-ValueContainer\"\n      >\n        <div\n          class=\"react-select__placeholder css-1jqq78o-placeholder\"\n          id=\"react-select-7-placeholder\"\n        >\n          Empty\n        </div>\n        <div\n          class=\"react-select__input-container css-1p5v8kp-Input\"\n          data-value=\"\"\n        >\n          <input\n            aria-activedescendant=\"react-select-7-option-0\"\n            aria-autocomplete=\"list\"\n            aria-controls=\"react-select-7-listbox\"\n            aria-describedby=\"react-select-7-placeholder\"\n            aria-expanded=\"true\"\n            aria-haspopup=\"true\"\n            autocapitalize=\"none\"\n            autocomplete=\"off\"\n            autocorrect=\"off\"\n            class=\"react-select__input\"\n            id=\"react-select-7-input\"\n            role=\"combobox\"\n            spellcheck=\"false\"\n            style=\"opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;\"\n            tabindex=\"0\"\n            type=\"text\"\n            value=\"\"\n          />\n        </div>\n      </div>\n      <div\n        class=\"react-select__indicators css-1hb7zxy-IndicatorsContainer\"\n      >\n        <span\n          class=\"react-select__indicator-separator css-1uei4ir-indicatorSeparator\"\n        />\n        <div\n          aria-hidden=\"true\"\n          class=\"react-select__indicator react-select__dropdown-indicator css-zngtjc-indicatorContainer\"\n        >\n          <svg\n            aria-hidden=\"true\"\n            class=\"css-tj5bde-Svg\"\n            focusable=\"false\"\n            height=\"20\"\n            viewBox=\"0 0 20 20\"\n            width=\"20\"\n          >\n            <path\n              d=\"M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z\"\n            />\n          </svg>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"react-select__menu css-1slvcut-menu\"\n    >\n      <div\n        aria-multiselectable=\"false\"\n        class=\"react-select__menu-list css-1d1qzc4-MenuList\"\n        id=\"react-select-7-listbox\"\n        role=\"listbox\"\n      >\n        <div\n          aria-disabled=\"false\"\n          aria-selected=\"false\"\n          class=\"react-select__option react-select__option--is-focused css-63bi6m-option\"\n          id=\"react-select-7-option-0\"\n          role=\"option\"\n          tabindex=\"-1\"\n        >\n          <div\n            class=\"Person-item\"\n          >\n            Me\n          </div>\n        </div>\n        <div\n          aria-disabled=\"false\"\n          aria-selected=\"false\"\n          class=\"react-select__option css-1uk8033-option\"\n          id=\"react-select-7-option-1\"\n          role=\"option\"\n          tabindex=\"-1\"\n        >\n          <div\n            class=\"Person-item\"\n          >\n            username-1\n          </div>\n        </div>\n        <div\n          aria-disabled=\"false\"\n          aria-selected=\"false\"\n          class=\"react-select__option css-1uk8033-option\"\n          id=\"react-select-7-option-2\"\n          role=\"option\"\n          tabindex=\"-1\"\n        >\n          <div\n            class=\"Person-item\"\n          >\n            username-2\n          </div>\n        </div>\n        <div\n          aria-disabled=\"false\"\n          aria-selected=\"false\"\n          class=\"react-select__option css-1uk8033-option\"\n          id=\"react-select-7-option-3\"\n          role=\"option\"\n          tabindex=\"-1\"\n        >\n          <div\n            class=\"Person-item\"\n          >\n            username-3\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`properties/person not readOnly, show modal 1`] = `\n<div>\n  <div\n    class=\"Person octo-propertyvalue css-b62m3t-container\"\n  >\n    <span\n      class=\"css-1f43avz-a11yText-A11yText\"\n      id=\"react-select-4-live-region\"\n    />\n    <span\n      aria-atomic=\"false\"\n      aria-live=\"polite\"\n      aria-relevant=\"additions text\"\n      class=\"css-1f43avz-a11yText-A11yText\"\n      role=\"log\"\n    />\n    <div\n      class=\"react-select__control css-18140j1-Control\"\n    >\n      <div\n        class=\"react-select__value-container css-1gbdvdc-ValueContainer\"\n      >\n        <div\n          class=\"react-select__placeholder css-1jqq78o-placeholder\"\n          id=\"react-select-4-placeholder\"\n        >\n          Empty\n        </div>\n        <div\n          class=\"react-select__input-container css-1p5v8kp-Input\"\n          data-value=\"\"\n        >\n          <input\n            aria-activedescendant=\"\"\n            aria-autocomplete=\"list\"\n            aria-describedby=\"react-select-4-placeholder\"\n            aria-expanded=\"false\"\n            aria-haspopup=\"true\"\n            autocapitalize=\"none\"\n            autocomplete=\"off\"\n            autocorrect=\"off\"\n            class=\"react-select__input\"\n            id=\"react-select-4-input\"\n            role=\"combobox\"\n            spellcheck=\"false\"\n            style=\"opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;\"\n            tabindex=\"0\"\n            type=\"text\"\n            value=\"\"\n          />\n        </div>\n      </div>\n      <div\n        class=\"react-select__indicators css-1hb7zxy-IndicatorsContainer\"\n      >\n        <span\n          class=\"react-select__indicator-separator css-1uei4ir-indicatorSeparator\"\n        />\n        <div\n          aria-hidden=\"true\"\n          class=\"react-select__indicator react-select__dropdown-indicator css-uycnsi-indicatorContainer\"\n        >\n          <svg\n            aria-hidden=\"true\"\n            class=\"css-tj5bde-Svg\"\n            focusable=\"false\"\n            height=\"20\"\n            viewBox=\"0 0 20 20\"\n            width=\"20\"\n          >\n            <path\n              d=\"M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z\"\n            />\n          </svg>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`properties/person not readOnly, show modal 2`] = `\n<div>\n  <div\n    class=\"Person octo-propertyvalue css-b62m3t-container\"\n  >\n    <span\n      class=\"css-1f43avz-a11yText-A11yText\"\n      id=\"react-select-4-live-region\"\n    />\n    <span\n      aria-atomic=\"false\"\n      aria-live=\"polite\"\n      aria-relevant=\"additions text\"\n      class=\"css-1f43avz-a11yText-A11yText\"\n      role=\"log\"\n    >\n      <span\n        id=\"aria-selection\"\n      />\n      <span\n        id=\"aria-focused\"\n      />\n      <span\n        id=\"aria-results\"\n      >\n        3 results available.\n      </span>\n      <span\n        id=\"aria-guidance\"\n      >\n        Use Up and Down to choose options, press Enter to select the currently focused option, press Escape to exit the menu, press Tab to select the option and exit the menu.\n      </span>\n    </span>\n    <div\n      class=\"react-select__control react-select__control--is-focused react-select__control--menu-is-open css-18140j1-Control\"\n    >\n      <div\n        class=\"react-select__value-container css-1gbdvdc-ValueContainer\"\n      >\n        <div\n          class=\"react-select__placeholder css-1jqq78o-placeholder\"\n          id=\"react-select-4-placeholder\"\n        >\n          Empty\n        </div>\n        <div\n          class=\"react-select__input-container css-1p5v8kp-Input\"\n          data-value=\"\"\n        >\n          <input\n            aria-activedescendant=\"react-select-4-option-0\"\n            aria-autocomplete=\"list\"\n            aria-controls=\"react-select-4-listbox\"\n            aria-describedby=\"react-select-4-placeholder\"\n            aria-expanded=\"true\"\n            aria-haspopup=\"true\"\n            autocapitalize=\"none\"\n            autocomplete=\"off\"\n            autocorrect=\"off\"\n            class=\"react-select__input\"\n            id=\"react-select-4-input\"\n            role=\"combobox\"\n            spellcheck=\"false\"\n            style=\"opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;\"\n            tabindex=\"0\"\n            type=\"text\"\n            value=\"\"\n          />\n        </div>\n      </div>\n      <div\n        class=\"react-select__indicators css-1hb7zxy-IndicatorsContainer\"\n      >\n        <span\n          class=\"react-select__indicator-separator css-1uei4ir-indicatorSeparator\"\n        />\n        <div\n          aria-hidden=\"true\"\n          class=\"react-select__indicator react-select__dropdown-indicator css-zngtjc-indicatorContainer\"\n        >\n          <svg\n            aria-hidden=\"true\"\n            class=\"css-tj5bde-Svg\"\n            focusable=\"false\"\n            height=\"20\"\n            viewBox=\"0 0 20 20\"\n            width=\"20\"\n          >\n            <path\n              d=\"M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z\"\n            />\n          </svg>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"react-select__menu css-1slvcut-menu\"\n    >\n      <div\n        aria-multiselectable=\"false\"\n        class=\"react-select__menu-list css-1d1qzc4-MenuList\"\n        id=\"react-select-4-listbox\"\n        role=\"listbox\"\n      >\n        <div\n          aria-disabled=\"false\"\n          aria-selected=\"false\"\n          class=\"react-select__option react-select__option--is-focused css-63bi6m-option\"\n          id=\"react-select-4-option-0\"\n          role=\"option\"\n          tabindex=\"-1\"\n        >\n          <div\n            class=\"Person-item\"\n          >\n            username-1\n          </div>\n        </div>\n        <div\n          aria-disabled=\"false\"\n          aria-selected=\"false\"\n          class=\"react-select__option css-1uk8033-option\"\n          id=\"react-select-4-option-1\"\n          role=\"option\"\n          tabindex=\"-1\"\n        >\n          <div\n            class=\"Person-item\"\n          >\n            username-2\n          </div>\n        </div>\n        <div\n          aria-disabled=\"false\"\n          aria-selected=\"false\"\n          class=\"react-select__option css-1uk8033-option\"\n          id=\"react-select-4-option-2\"\n          role=\"option\"\n          tabindex=\"-1\"\n        >\n          <div\n            class=\"Person-item\"\n          >\n            username-3\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`properties/person not readOnly, show username 1`] = `\n<div>\n  <div\n    class=\"Person octo-propertyvalue css-b62m3t-container\"\n  >\n    <span\n      class=\"css-1f43avz-a11yText-A11yText\"\n      id=\"react-select-2-live-region\"\n    />\n    <span\n      aria-atomic=\"false\"\n      aria-live=\"polite\"\n      aria-relevant=\"additions text\"\n      class=\"css-1f43avz-a11yText-A11yText\"\n      role=\"log\"\n    />\n    <div\n      class=\"react-select__control css-18140j1-Control\"\n    >\n      <div\n        class=\"react-select__value-container react-select__value-container--has-value css-1gbdvdc-ValueContainer\"\n      >\n        <div\n          class=\"react-select__single-value css-qosd1h-singleValue\"\n        >\n          <div\n            class=\"Person-item\"\n          >\n            username-1\n          </div>\n        </div>\n        <div\n          class=\"react-select__input-container css-1p5v8kp-Input\"\n          data-value=\"\"\n        >\n          <input\n            aria-activedescendant=\"\"\n            aria-autocomplete=\"list\"\n            aria-expanded=\"false\"\n            aria-haspopup=\"true\"\n            autocapitalize=\"none\"\n            autocomplete=\"off\"\n            autocorrect=\"off\"\n            class=\"react-select__input\"\n            id=\"react-select-2-input\"\n            role=\"combobox\"\n            spellcheck=\"false\"\n            style=\"opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;\"\n            tabindex=\"0\"\n            type=\"text\"\n            value=\"\"\n          />\n        </div>\n      </div>\n      <div\n        class=\"react-select__indicators css-1hb7zxy-IndicatorsContainer\"\n      >\n        <div\n          aria-hidden=\"true\"\n          class=\"react-select__indicator react-select__clear-indicator css-31haax-indicatorContainer\"\n        >\n          <svg\n            aria-hidden=\"true\"\n            class=\"css-tj5bde-Svg\"\n            focusable=\"false\"\n            height=\"20\"\n            viewBox=\"0 0 20 20\"\n            width=\"20\"\n          >\n            <path\n              d=\"M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z\"\n            />\n          </svg>\n        </div>\n        <span\n          class=\"react-select__indicator-separator css-1uei4ir-indicatorSeparator\"\n        />\n        <div\n          aria-hidden=\"true\"\n          class=\"react-select__indicator react-select__dropdown-indicator css-uycnsi-indicatorContainer\"\n        >\n          <svg\n            aria-hidden=\"true\"\n            class=\"css-tj5bde-Svg\"\n            focusable=\"false\"\n            height=\"20\"\n            viewBox=\"0 0 20 20\"\n            width=\"20\"\n          >\n            <path\n              d=\"M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z\"\n            />\n          </svg>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`properties/person readOnly view 1`] = `\n<div>\n  <div\n    class=\"Person octo-propertyvalue octo-propertyvalue--readonly\"\n  >\n    <div\n      class=\"Person-item\"\n    >\n      username-1\n    </div>\n  </div>\n</div>\n`;\n\nexports[`properties/person show multiple 1`] = `\n<div>\n  <div\n    class=\"MultiPerson octo-propertyvalue css-b62m3t-container\"\n  >\n    <span\n      class=\"css-1f43avz-a11yText-A11yText\"\n      id=\"react-select-5-live-region\"\n    />\n    <span\n      aria-atomic=\"false\"\n      aria-live=\"polite\"\n      aria-relevant=\"additions text\"\n      class=\"css-1f43avz-a11yText-A11yText\"\n      role=\"log\"\n    />\n    <div\n      class=\"react-select__control css-18140j1-Control\"\n    >\n      <div\n        class=\"react-select__value-container react-select__value-container--is-multi react-select__value-container--has-value css-pcwdi-ValueContainer\"\n      >\n        <div\n          class=\"react-select__multi-value css-1p3m7a8-multiValue\"\n        >\n          <div\n            class=\"react-select__multi-value__label css-wsp0cs-MultiValueGeneric\"\n          >\n            <div\n              class=\"MultiPerson-item\"\n            >\n              username-1\n            </div>\n          </div>\n          <div\n            aria-label=\"Remove [object Object]\"\n            class=\"react-select__multi-value__remove css-12a83d4-MultiValueRemove\"\n            role=\"button\"\n          >\n            <svg\n              aria-hidden=\"true\"\n              class=\"css-tj5bde-Svg\"\n              focusable=\"false\"\n              height=\"14\"\n              viewBox=\"0 0 20 20\"\n              width=\"14\"\n            >\n              <path\n                d=\"M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z\"\n              />\n            </svg>\n          </div>\n        </div>\n        <div\n          class=\"react-select__multi-value css-1p3m7a8-multiValue\"\n        >\n          <div\n            class=\"react-select__multi-value__label css-wsp0cs-MultiValueGeneric\"\n          >\n            <div\n              class=\"MultiPerson-item\"\n            >\n              username-2\n            </div>\n          </div>\n          <div\n            aria-label=\"Remove [object Object]\"\n            class=\"react-select__multi-value__remove css-12a83d4-MultiValueRemove\"\n            role=\"button\"\n          >\n            <svg\n              aria-hidden=\"true\"\n              class=\"css-tj5bde-Svg\"\n              focusable=\"false\"\n              height=\"14\"\n              viewBox=\"0 0 20 20\"\n              width=\"14\"\n            >\n              <path\n                d=\"M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z\"\n              />\n            </svg>\n          </div>\n        </div>\n        <div\n          class=\"react-select__input-container css-1p5v8kp-Input\"\n          data-value=\"\"\n        >\n          <input\n            aria-activedescendant=\"\"\n            aria-autocomplete=\"list\"\n            aria-expanded=\"false\"\n            aria-haspopup=\"true\"\n            autocapitalize=\"none\"\n            autocomplete=\"off\"\n            autocorrect=\"off\"\n            class=\"react-select__input\"\n            id=\"react-select-5-input\"\n            role=\"combobox\"\n            spellcheck=\"false\"\n            style=\"opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;\"\n            tabindex=\"0\"\n            type=\"text\"\n            value=\"\"\n          />\n        </div>\n      </div>\n      <div\n        class=\"react-select__indicators css-1hb7zxy-IndicatorsContainer\"\n      >\n        <div\n          aria-hidden=\"true\"\n          class=\"react-select__indicator react-select__clear-indicator css-31haax-indicatorContainer\"\n        >\n          <svg\n            aria-hidden=\"true\"\n            class=\"css-tj5bde-Svg\"\n            focusable=\"false\"\n            height=\"20\"\n            viewBox=\"0 0 20 20\"\n            width=\"20\"\n          >\n            <path\n              d=\"M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z\"\n            />\n          </svg>\n        </div>\n        <span\n          class=\"react-select__indicator-separator css-1uei4ir-indicatorSeparator\"\n        />\n        <div\n          aria-hidden=\"true\"\n          class=\"react-select__indicator react-select__dropdown-indicator css-uycnsi-indicatorContainer\"\n        >\n          <svg\n            aria-hidden=\"true\"\n            class=\"css-tj5bde-Svg\"\n            focusable=\"false\"\n            height=\"20\"\n            viewBox=\"0 0 20 20\"\n            width=\"20\"\n          >\n            <path\n              d=\"M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z\"\n            />\n          </svg>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`properties/person show multiple, display modal 1`] = `\n<div>\n  <div\n    class=\"MultiPerson octo-propertyvalue css-b62m3t-container\"\n  >\n    <span\n      class=\"css-1f43avz-a11yText-A11yText\"\n      id=\"react-select-6-live-region\"\n    />\n    <span\n      aria-atomic=\"false\"\n      aria-live=\"polite\"\n      aria-relevant=\"additions text\"\n      class=\"css-1f43avz-a11yText-A11yText\"\n      role=\"log\"\n    />\n    <div\n      class=\"react-select__control css-18140j1-Control\"\n    >\n      <div\n        class=\"react-select__value-container react-select__value-container--is-multi react-select__value-container--has-value css-pcwdi-ValueContainer\"\n      >\n        <div\n          class=\"react-select__multi-value css-1p3m7a8-multiValue\"\n        >\n          <div\n            class=\"react-select__multi-value__label css-wsp0cs-MultiValueGeneric\"\n          >\n            <div\n              class=\"MultiPerson-item\"\n            >\n              username-1\n            </div>\n          </div>\n          <div\n            aria-label=\"Remove [object Object]\"\n            class=\"react-select__multi-value__remove css-12a83d4-MultiValueRemove\"\n            role=\"button\"\n          >\n            <svg\n              aria-hidden=\"true\"\n              class=\"css-tj5bde-Svg\"\n              focusable=\"false\"\n              height=\"14\"\n              viewBox=\"0 0 20 20\"\n              width=\"14\"\n            >\n              <path\n                d=\"M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z\"\n              />\n            </svg>\n          </div>\n        </div>\n        <div\n          class=\"react-select__multi-value css-1p3m7a8-multiValue\"\n        >\n          <div\n            class=\"react-select__multi-value__label css-wsp0cs-MultiValueGeneric\"\n          >\n            <div\n              class=\"MultiPerson-item\"\n            >\n              username-2\n            </div>\n          </div>\n          <div\n            aria-label=\"Remove [object Object]\"\n            class=\"react-select__multi-value__remove css-12a83d4-MultiValueRemove\"\n            role=\"button\"\n          >\n            <svg\n              aria-hidden=\"true\"\n              class=\"css-tj5bde-Svg\"\n              focusable=\"false\"\n              height=\"14\"\n              viewBox=\"0 0 20 20\"\n              width=\"14\"\n            >\n              <path\n                d=\"M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z\"\n              />\n            </svg>\n          </div>\n        </div>\n        <div\n          class=\"react-select__input-container css-1p5v8kp-Input\"\n          data-value=\"\"\n        >\n          <input\n            aria-activedescendant=\"\"\n            aria-autocomplete=\"list\"\n            aria-expanded=\"false\"\n            aria-haspopup=\"true\"\n            autocapitalize=\"none\"\n            autocomplete=\"off\"\n            autocorrect=\"off\"\n            class=\"react-select__input\"\n            id=\"react-select-6-input\"\n            role=\"combobox\"\n            spellcheck=\"false\"\n            style=\"opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;\"\n            tabindex=\"0\"\n            type=\"text\"\n            value=\"\"\n          />\n        </div>\n      </div>\n      <div\n        class=\"react-select__indicators css-1hb7zxy-IndicatorsContainer\"\n      >\n        <div\n          aria-hidden=\"true\"\n          class=\"react-select__indicator react-select__clear-indicator css-31haax-indicatorContainer\"\n        >\n          <svg\n            aria-hidden=\"true\"\n            class=\"css-tj5bde-Svg\"\n            focusable=\"false\"\n            height=\"20\"\n            viewBox=\"0 0 20 20\"\n            width=\"20\"\n          >\n            <path\n              d=\"M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z\"\n            />\n          </svg>\n        </div>\n        <span\n          class=\"react-select__indicator-separator css-1uei4ir-indicatorSeparator\"\n        />\n        <div\n          aria-hidden=\"true\"\n          class=\"react-select__indicator react-select__dropdown-indicator css-uycnsi-indicatorContainer\"\n        >\n          <svg\n            aria-hidden=\"true\"\n            class=\"css-tj5bde-Svg\"\n            focusable=\"false\"\n            height=\"20\"\n            viewBox=\"0 0 20 20\"\n            width=\"20\"\n          >\n            <path\n              d=\"M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z\"\n            />\n          </svg>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`properties/person show multiple, display modal 2`] = `\n<div>\n  <div\n    class=\"MultiPerson octo-propertyvalue css-b62m3t-container\"\n  >\n    <span\n      class=\"css-1f43avz-a11yText-A11yText\"\n      id=\"react-select-6-live-region\"\n    />\n    <span\n      aria-atomic=\"false\"\n      aria-live=\"polite\"\n      aria-relevant=\"additions text\"\n      class=\"css-1f43avz-a11yText-A11yText\"\n      role=\"log\"\n    >\n      <span\n        id=\"aria-selection\"\n      />\n      <span\n        id=\"aria-focused\"\n      />\n      <span\n        id=\"aria-results\"\n      >\n        1 result available.\n      </span>\n      <span\n        id=\"aria-guidance\"\n      >\n        Use Up and Down to choose options, press Enter to select the currently focused option, press Escape to exit the menu, press Tab to select the option and exit the menu.\n      </span>\n    </span>\n    <div\n      class=\"react-select__control react-select__control--is-focused react-select__control--menu-is-open css-18140j1-Control\"\n    >\n      <div\n        class=\"react-select__value-container react-select__value-container--is-multi react-select__value-container--has-value css-pcwdi-ValueContainer\"\n      >\n        <div\n          class=\"react-select__multi-value css-1p3m7a8-multiValue\"\n        >\n          <div\n            class=\"react-select__multi-value__label css-wsp0cs-MultiValueGeneric\"\n          >\n            <div\n              class=\"MultiPerson-item\"\n            >\n              username-1\n            </div>\n          </div>\n          <div\n            aria-label=\"Remove [object Object]\"\n            class=\"react-select__multi-value__remove css-12a83d4-MultiValueRemove\"\n            role=\"button\"\n          >\n            <svg\n              aria-hidden=\"true\"\n              class=\"css-tj5bde-Svg\"\n              focusable=\"false\"\n              height=\"14\"\n              viewBox=\"0 0 20 20\"\n              width=\"14\"\n            >\n              <path\n                d=\"M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z\"\n              />\n            </svg>\n          </div>\n        </div>\n        <div\n          class=\"react-select__multi-value css-1p3m7a8-multiValue\"\n        >\n          <div\n            class=\"react-select__multi-value__label css-wsp0cs-MultiValueGeneric\"\n          >\n            <div\n              class=\"MultiPerson-item\"\n            >\n              username-2\n            </div>\n          </div>\n          <div\n            aria-label=\"Remove [object Object]\"\n            class=\"react-select__multi-value__remove css-12a83d4-MultiValueRemove\"\n            role=\"button\"\n          >\n            <svg\n              aria-hidden=\"true\"\n              class=\"css-tj5bde-Svg\"\n              focusable=\"false\"\n              height=\"14\"\n              viewBox=\"0 0 20 20\"\n              width=\"14\"\n            >\n              <path\n                d=\"M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z\"\n              />\n            </svg>\n          </div>\n        </div>\n        <div\n          class=\"react-select__input-container css-1p5v8kp-Input\"\n          data-value=\"\"\n        >\n          <input\n            aria-activedescendant=\"react-select-6-option-2\"\n            aria-autocomplete=\"list\"\n            aria-controls=\"react-select-6-listbox\"\n            aria-expanded=\"true\"\n            aria-haspopup=\"true\"\n            autocapitalize=\"none\"\n            autocomplete=\"off\"\n            autocorrect=\"off\"\n            class=\"react-select__input\"\n            id=\"react-select-6-input\"\n            role=\"combobox\"\n            spellcheck=\"false\"\n            style=\"opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;\"\n            tabindex=\"0\"\n            type=\"text\"\n            value=\"\"\n          />\n        </div>\n      </div>\n      <div\n        class=\"react-select__indicators css-1hb7zxy-IndicatorsContainer\"\n      >\n        <div\n          aria-hidden=\"true\"\n          class=\"react-select__indicator react-select__clear-indicator css-3pqe01-indicatorContainer\"\n        >\n          <svg\n            aria-hidden=\"true\"\n            class=\"css-tj5bde-Svg\"\n            focusable=\"false\"\n            height=\"20\"\n            viewBox=\"0 0 20 20\"\n            width=\"20\"\n          >\n            <path\n              d=\"M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z\"\n            />\n          </svg>\n        </div>\n        <span\n          class=\"react-select__indicator-separator css-1uei4ir-indicatorSeparator\"\n        />\n        <div\n          aria-hidden=\"true\"\n          class=\"react-select__indicator react-select__dropdown-indicator css-zngtjc-indicatorContainer\"\n        >\n          <svg\n            aria-hidden=\"true\"\n            class=\"css-tj5bde-Svg\"\n            focusable=\"false\"\n            height=\"20\"\n            viewBox=\"0 0 20 20\"\n            width=\"20\"\n          >\n            <path\n              d=\"M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z\"\n            />\n          </svg>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"react-select__menu css-1slvcut-menu\"\n    >\n      <div\n        aria-multiselectable=\"true\"\n        class=\"react-select__menu-list react-select__menu-list--is-multi css-1d1qzc4-MenuList\"\n        id=\"react-select-6-listbox\"\n        role=\"listbox\"\n      >\n        <div\n          aria-disabled=\"false\"\n          aria-selected=\"false\"\n          class=\"react-select__option react-select__option--is-focused css-63bi6m-option\"\n          id=\"react-select-6-option-2\"\n          role=\"option\"\n          tabindex=\"-1\"\n        >\n          <div\n            class=\"MultiPerson-item\"\n          >\n            username-3\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/__snapshots__/propertyValueElement.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/propertyValueElement Generic fields should allow cancel 1`] = `\n<div>\n  <input\n    class=\"Editable octo-propertyvalue\"\n    placeholder=\"Empty\"\n    spellcheck=\"true\"\n    style=\"width: 100%;\"\n    title=\"\"\n    value=\"\"\n  />\n</div>\n`;\n\nexports[`components/propertyValueElement URL fields should allow cancel 1`] = `\n<div>\n  <div\n    class=\"URLProperty octo-propertyvalue\"\n  >\n    <a\n      class=\"link\"\n      href=\"http://localhost\"\n      rel=\"noreferrer\"\n      target=\"_blank\"\n    >\n      http://localhost\n    </a>\n    <button\n      aria-label=\"Edit\"\n      class=\"IconButton Button_Edit\"\n      title=\"Edit\"\n      type=\"button\"\n    >\n      <i\n        class=\"CompassIcon icon-pencil-outline EditIcon\"\n      />\n    </button>\n    <button\n      aria-label=\"Copy\"\n      class=\"IconButton Button_Copy\"\n      title=\"Copy\"\n      type=\"button\"\n    >\n      <i\n        class=\"CompassIcon icon-content-copy content-copy\"\n      />\n    </button>\n  </div>\n</div>\n`;\n\nexports[`components/propertyValueElement should match snapshot, date, array value 1`] = `\n<div>\n  <div\n    class=\"DateRange empty octo-propertyvalue\"\n  >\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        Empty\n      </span>\n    </button>\n  </div>\n</div>\n`;\n\nexports[`components/propertyValueElement should match snapshot, multi-select 1`] = `\n<div>\n  <div\n    class=\"octo-propertyvalue\"\n    data-testid=\"multiselect-non-editable\"\n    tabindex=\"0\"\n  >\n    <span\n      class=\"Label propColorBrown \"\n    >\n      value 1\n    </span>\n    <span\n      class=\"Label propColorBrown \"\n    >\n      value 2\n    </span>\n  </div>\n</div>\n`;\n\nexports[`components/propertyValueElement should match snapshot, person, array value 1`] = `\n<div>\n  <input\n    class=\"Editable octo-propertyvalue\"\n    placeholder=\"Empty\"\n    spellcheck=\"true\"\n    style=\"width: 100%;\"\n    title=\"\"\n    value=\"\"\n  />\n</div>\n`;\n\nexports[`components/propertyValueElement should match snapshot, select 1`] = `\n<div>\n  <div\n    class=\"octo-propertyvalue\"\n    data-testid=\"select-non-editable\"\n    tabindex=\"0\"\n  >\n    <span\n      class=\"Label propColorBrown \"\n    >\n      <span\n        class=\"Label-text\"\n      >\n        value 1\n      </span>\n    </span>\n  </div>\n</div>\n`;\n\nexports[`components/propertyValueElement should match snapshot, select, read-only 1`] = `\n<div>\n  <div\n    class=\"octo-propertyvalue octo-propertyvalue--readonly\"\n    data-testid=\"select-non-editable\"\n    tabindex=\"0\"\n  >\n    <span\n      class=\"Label propColorBrown \"\n    >\n      <span\n        class=\"Label-text\"\n      >\n        value 1\n      </span>\n    </span>\n  </div>\n</div>\n`;\n\nexports[`components/propertyValueElement should match snapshot, url, array value 1`] = `\n<div>\n  <div\n    class=\"URLProperty octo-propertyvalue\"\n  >\n    <a\n      class=\"link\"\n      href=\"http://localhost\"\n      rel=\"noreferrer\"\n      target=\"_blank\"\n    >\n      http://localhost\n    </a>\n    <button\n      aria-label=\"Edit\"\n      class=\"IconButton Button_Edit\"\n      title=\"Edit\"\n      type=\"button\"\n    >\n      <i\n        class=\"CompassIcon icon-pencil-outline EditIcon\"\n      />\n    </button>\n    <button\n      aria-label=\"Copy\"\n      class=\"IconButton Button_Copy\"\n      title=\"Copy\"\n      type=\"button\"\n    >\n      <i\n        class=\"CompassIcon icon-content-copy content-copy\"\n      />\n    </button>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/__snapshots__/rootPortal.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/RootPortal should match snapshot 1`] = `\n<div\n  id=\"focalboard-root-portal\"\n>\n  <div>\n    <div>\n      Testing Portal\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/__snapshots__/topBar.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`src/components/topBar should match snapshot for focalboardPlugin 1`] = `\n<div>\n  <div\n    class=\"TopBar\"\n  >\n    <a\n      class=\"link\"\n      href=\"https://www.focalboard.com/fwlink/feedback-focalboard.html?v=1.0.0\"\n      rel=\"noreferrer\"\n      target=\"_blank\"\n    >\n      Give feedback\n    </a>\n    <a\n      href=\"https://www.focalboard.com/guide/user?utm_source=webapp\"\n      rel=\"noreferrer\"\n      target=\"_blank\"\n    >\n      <i\n        class=\"CompassIcon icon-help-circle-outline HelpIcon\"\n      />\n    </a>\n  </div>\n</div>\n`;\n\nexports[`src/components/topBar should match snapshot for none focalboardPlugin 1`] = `\n<div>\n  <div\n    class=\"TopBar\"\n  >\n    <a\n      class=\"link\"\n      href=\"https://www.focalboard.com/fwlink/feedback-focalboard.html?v=1.0.0\"\n      rel=\"noreferrer\"\n      target=\"_blank\"\n    >\n      Give feedback\n    </a>\n    <a\n      href=\"https://www.focalboard.com/guide/user?utm_source=webapp\"\n      rel=\"noreferrer\"\n      target=\"_blank\"\n    >\n      <i\n        class=\"CompassIcon icon-help-circle-outline HelpIcon\"\n      />\n    </a>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/__snapshots__/viewMenu.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`/components/viewMenu should match snapshot 1`] = `\nObject {\n  \"asFragment\": [Function],\n  \"baseElement\": <body>\n    <div>\n      <div\n        class=\"ViewMenu\"\n      >\n        <div\n          class=\"Menu noselect bottom \"\n        >\n          <div\n            class=\"menu-contents\"\n          >\n            <div\n              class=\"menu-options\"\n            >\n              <div>\n                <div\n                  class=\"view-list\"\n                >\n                  <div\n                    aria-label=\"view title\"\n                    class=\"MenuOption TextOption menu-option\"\n                    role=\"button\"\n                  >\n                    <div\n                      class=\"d-flex\"\n                    >\n                      <div\n                        class=\"menu-option__icon\"\n                      >\n                        <svg\n                          class=\"TableIcon Icon\"\n                          fill=\"currentColor\"\n                          height=\"24\"\n                          viewBox=\"0 0 24 24\"\n                          width=\"24\"\n                          xmlns=\"http://www.w3.org/2000/svg\"\n                        >\n                          <g\n                            opacity=\"0.8\"\n                          >\n                            <path\n                              clip-rule=\"evenodd\"\n                              d=\"M20 4H10V8L20 8V4ZM8 4V8H4V4H8ZM4 14L4 10H8V14H4ZM4 16L4 20H8V16H4ZM10 16V20H20V16L10 16ZM20 14V10L10 10V14L20 14ZM4 2C2.89543 2 2 2.89543 2 4V20C2 21.1046 2.89543 22 4 22H20C21.1046 22 22 21.1046 22 20V4C22 2.89543 21.1046 2 20 2H4Z\"\n                              fill=\"currentColor\"\n                              fill-rule=\"evenodd\"\n                            />\n                          </g>\n                        </svg>\n                      </div>\n                    </div>\n                    <div\n                      class=\"menu-option__content\"\n                    >\n                      <div\n                        class=\"menu-name\"\n                      >\n                        view title\n                      </div>\n                    </div>\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                  <div\n                    aria-label=\"view title\"\n                    class=\"MenuOption TextOption menu-option\"\n                    role=\"button\"\n                  >\n                    <div\n                      class=\"d-flex\"\n                    >\n                      <div\n                        class=\"menu-option__icon\"\n                      >\n                        <svg\n                          class=\"TableIcon Icon\"\n                          fill=\"currentColor\"\n                          height=\"24\"\n                          viewBox=\"0 0 24 24\"\n                          width=\"24\"\n                          xmlns=\"http://www.w3.org/2000/svg\"\n                        >\n                          <g\n                            opacity=\"0.8\"\n                          >\n                            <path\n                              clip-rule=\"evenodd\"\n                              d=\"M20 4H10V8L20 8V4ZM8 4V8H4V4H8ZM4 14L4 10H8V14H4ZM4 16L4 20H8V16H4ZM10 16V20H20V16L10 16ZM20 14V10L10 10V14L20 14ZM4 2C2.89543 2 2 2.89543 2 4V20C2 21.1046 2.89543 22 4 22H20C21.1046 22 22 21.1046 22 20V4C22 2.89543 21.1046 2 20 2H4Z\"\n                              fill=\"currentColor\"\n                              fill-rule=\"evenodd\"\n                            />\n                          </g>\n                        </svg>\n                      </div>\n                    </div>\n                    <div\n                      class=\"menu-option__content\"\n                    >\n                      <div\n                        class=\"menu-name\"\n                      >\n                        view title\n                      </div>\n                    </div>\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                </div>\n              </div>\n              <div>\n                <div\n                  class=\"MenuOption MenuSeparator menu-separator\"\n                />\n              </div>\n              <div>\n                <div\n                  aria-label=\"Duplicate view\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex\"\n                  >\n                    <div\n                      class=\"menu-option__icon\"\n                    >\n                      <i\n                        class=\"CompassIcon icon-content-copy content-copy\"\n                      />\n                    </div>\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Duplicate view\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n              </div>\n              <div>\n                <div\n                  aria-label=\"Delete view\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex\"\n                  >\n                    <div\n                      class=\"menu-option__icon\"\n                    >\n                      <i\n                        class=\"CompassIcon icon-trash-can-outline DeleteIcon trash-can-outline\"\n                      />\n                    </div>\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Delete view\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n              </div>\n              <div>\n                <div\n                  class=\"MenuOption SubMenuOption menu-option\"\n                  id=\"__addView\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-plus AddIcon\"\n                    />\n                  </div>\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Add view\n                  </div>\n                  <i\n                    class=\"CompassIcon icon-chevron-right\"\n                  />\n                </div>\n              </div>\n            </div>\n            <div\n              class=\"menu-spacer hideOnWidescreen\"\n            />\n            <div\n              class=\"menu-options hideOnWidescreen\"\n            >\n              <div\n                aria-label=\"Cancel\"\n                class=\"MenuOption TextOption menu-option menu-cancel\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Cancel\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </body>,\n  \"container\": <div>\n    <div\n      class=\"ViewMenu\"\n    >\n      <div\n        class=\"Menu noselect bottom \"\n      >\n        <div\n          class=\"menu-contents\"\n        >\n          <div\n            class=\"menu-options\"\n          >\n            <div>\n              <div\n                class=\"view-list\"\n              >\n                <div\n                  aria-label=\"view title\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex\"\n                  >\n                    <div\n                      class=\"menu-option__icon\"\n                    >\n                      <svg\n                        class=\"TableIcon Icon\"\n                        fill=\"currentColor\"\n                        height=\"24\"\n                        viewBox=\"0 0 24 24\"\n                        width=\"24\"\n                        xmlns=\"http://www.w3.org/2000/svg\"\n                      >\n                        <g\n                          opacity=\"0.8\"\n                        >\n                          <path\n                            clip-rule=\"evenodd\"\n                            d=\"M20 4H10V8L20 8V4ZM8 4V8H4V4H8ZM4 14L4 10H8V14H4ZM4 16L4 20H8V16H4ZM10 16V20H20V16L10 16ZM20 14V10L10 10V14L20 14ZM4 2C2.89543 2 2 2.89543 2 4V20C2 21.1046 2.89543 22 4 22H20C21.1046 22 22 21.1046 22 20V4C22 2.89543 21.1046 2 20 2H4Z\"\n                            fill=\"currentColor\"\n                            fill-rule=\"evenodd\"\n                          />\n                        </g>\n                      </svg>\n                    </div>\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      view title\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  aria-label=\"view title\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex\"\n                  >\n                    <div\n                      class=\"menu-option__icon\"\n                    >\n                      <svg\n                        class=\"TableIcon Icon\"\n                        fill=\"currentColor\"\n                        height=\"24\"\n                        viewBox=\"0 0 24 24\"\n                        width=\"24\"\n                        xmlns=\"http://www.w3.org/2000/svg\"\n                      >\n                        <g\n                          opacity=\"0.8\"\n                        >\n                          <path\n                            clip-rule=\"evenodd\"\n                            d=\"M20 4H10V8L20 8V4ZM8 4V8H4V4H8ZM4 14L4 10H8V14H4ZM4 16L4 20H8V16H4ZM10 16V20H20V16L10 16ZM20 14V10L10 10V14L20 14ZM4 2C2.89543 2 2 2.89543 2 4V20C2 21.1046 2.89543 22 4 22H20C21.1046 22 22 21.1046 22 20V4C22 2.89543 21.1046 2 20 2H4Z\"\n                            fill=\"currentColor\"\n                            fill-rule=\"evenodd\"\n                          />\n                        </g>\n                      </svg>\n                    </div>\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      view title\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n              </div>\n            </div>\n            <div>\n              <div\n                class=\"MenuOption MenuSeparator menu-separator\"\n              />\n            </div>\n            <div>\n              <div\n                aria-label=\"Duplicate view\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-content-copy content-copy\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Duplicate view\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Delete view\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-trash-can-outline DeleteIcon trash-can-outline\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Delete view\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                class=\"MenuOption SubMenuOption menu-option\"\n                id=\"__addView\"\n              >\n                <div\n                  class=\"menu-option__icon\"\n                >\n                  <i\n                    class=\"CompassIcon icon-plus AddIcon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-name\"\n                >\n                  Add view\n                </div>\n                <i\n                  class=\"CompassIcon icon-chevron-right\"\n                />\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"menu-spacer hideOnWidescreen\"\n          />\n          <div\n            class=\"menu-options hideOnWidescreen\"\n          >\n            <div\n              aria-label=\"Cancel\"\n              class=\"MenuOption TextOption menu-option menu-cancel\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Cancel\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>,\n  \"debug\": [Function],\n  \"findAllByAltText\": [Function],\n  \"findAllByDisplayValue\": [Function],\n  \"findAllByLabelText\": [Function],\n  \"findAllByPlaceholderText\": [Function],\n  \"findAllByRole\": [Function],\n  \"findAllByTestId\": [Function],\n  \"findAllByText\": [Function],\n  \"findAllByTitle\": [Function],\n  \"findByAltText\": [Function],\n  \"findByDisplayValue\": [Function],\n  \"findByLabelText\": [Function],\n  \"findByPlaceholderText\": [Function],\n  \"findByRole\": [Function],\n  \"findByTestId\": [Function],\n  \"findByText\": [Function],\n  \"findByTitle\": [Function],\n  \"getAllByAltText\": [Function],\n  \"getAllByDisplayValue\": [Function],\n  \"getAllByLabelText\": [Function],\n  \"getAllByPlaceholderText\": [Function],\n  \"getAllByRole\": [Function],\n  \"getAllByTestId\": [Function],\n  \"getAllByText\": [Function],\n  \"getAllByTitle\": [Function],\n  \"getByAltText\": [Function],\n  \"getByDisplayValue\": [Function],\n  \"getByLabelText\": [Function],\n  \"getByPlaceholderText\": [Function],\n  \"getByRole\": [Function],\n  \"getByTestId\": [Function],\n  \"getByText\": [Function],\n  \"getByTitle\": [Function],\n  \"queryAllByAltText\": [Function],\n  \"queryAllByDisplayValue\": [Function],\n  \"queryAllByLabelText\": [Function],\n  \"queryAllByPlaceholderText\": [Function],\n  \"queryAllByRole\": [Function],\n  \"queryAllByTestId\": [Function],\n  \"queryAllByText\": [Function],\n  \"queryAllByTitle\": [Function],\n  \"queryByAltText\": [Function],\n  \"queryByDisplayValue\": [Function],\n  \"queryByLabelText\": [Function],\n  \"queryByPlaceholderText\": [Function],\n  \"queryByRole\": [Function],\n  \"queryByTestId\": [Function],\n  \"queryByText\": [Function],\n  \"queryByTitle\": [Function],\n  \"rerender\": [Function],\n  \"unmount\": [Function],\n}\n`;\n\nexports[`/components/viewMenu should match snapshot, read only 1`] = `\nObject {\n  \"asFragment\": [Function],\n  \"baseElement\": <body>\n    <div>\n      <div\n        class=\"ViewMenu\"\n      >\n        <div\n          class=\"Menu noselect bottom \"\n        >\n          <div\n            class=\"menu-contents\"\n          >\n            <div\n              class=\"menu-options\"\n            >\n              <div>\n                <div\n                  class=\"view-list\"\n                >\n                  <div\n                    aria-label=\"view title\"\n                    class=\"MenuOption TextOption menu-option\"\n                    role=\"button\"\n                  >\n                    <div\n                      class=\"d-flex\"\n                    >\n                      <div\n                        class=\"menu-option__icon\"\n                      >\n                        <svg\n                          class=\"TableIcon Icon\"\n                          fill=\"currentColor\"\n                          height=\"24\"\n                          viewBox=\"0 0 24 24\"\n                          width=\"24\"\n                          xmlns=\"http://www.w3.org/2000/svg\"\n                        >\n                          <g\n                            opacity=\"0.8\"\n                          >\n                            <path\n                              clip-rule=\"evenodd\"\n                              d=\"M20 4H10V8L20 8V4ZM8 4V8H4V4H8ZM4 14L4 10H8V14H4ZM4 16L4 20H8V16H4ZM10 16V20H20V16L10 16ZM20 14V10L10 10V14L20 14ZM4 2C2.89543 2 2 2.89543 2 4V20C2 21.1046 2.89543 22 4 22H20C21.1046 22 22 21.1046 22 20V4C22 2.89543 21.1046 2 20 2H4Z\"\n                              fill=\"currentColor\"\n                              fill-rule=\"evenodd\"\n                            />\n                          </g>\n                        </svg>\n                      </div>\n                    </div>\n                    <div\n                      class=\"menu-option__content\"\n                    >\n                      <div\n                        class=\"menu-name\"\n                      >\n                        view title\n                      </div>\n                    </div>\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                  <div\n                    aria-label=\"view title\"\n                    class=\"MenuOption TextOption menu-option\"\n                    role=\"button\"\n                  >\n                    <div\n                      class=\"d-flex\"\n                    >\n                      <div\n                        class=\"menu-option__icon\"\n                      >\n                        <svg\n                          class=\"TableIcon Icon\"\n                          fill=\"currentColor\"\n                          height=\"24\"\n                          viewBox=\"0 0 24 24\"\n                          width=\"24\"\n                          xmlns=\"http://www.w3.org/2000/svg\"\n                        >\n                          <g\n                            opacity=\"0.8\"\n                          >\n                            <path\n                              clip-rule=\"evenodd\"\n                              d=\"M20 4H10V8L20 8V4ZM8 4V8H4V4H8ZM4 14L4 10H8V14H4ZM4 16L4 20H8V16H4ZM10 16V20H20V16L10 16ZM20 14V10L10 10V14L20 14ZM4 2C2.89543 2 2 2.89543 2 4V20C2 21.1046 2.89543 22 4 22H20C21.1046 22 22 21.1046 22 20V4C22 2.89543 21.1046 2 20 2H4Z\"\n                              fill=\"currentColor\"\n                              fill-rule=\"evenodd\"\n                            />\n                          </g>\n                        </svg>\n                      </div>\n                    </div>\n                    <div\n                      class=\"menu-option__content\"\n                    >\n                      <div\n                        class=\"menu-name\"\n                      >\n                        view title\n                      </div>\n                    </div>\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                </div>\n              </div>\n              <div>\n                <div\n                  class=\"MenuOption MenuSeparator menu-separator\"\n                />\n              </div>\n              <div />\n              <div />\n              <div />\n            </div>\n            <div\n              class=\"menu-spacer hideOnWidescreen\"\n            />\n            <div\n              class=\"menu-options hideOnWidescreen\"\n            >\n              <div\n                aria-label=\"Cancel\"\n                class=\"MenuOption TextOption menu-option menu-cancel\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Cancel\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </body>,\n  \"container\": <div>\n    <div\n      class=\"ViewMenu\"\n    >\n      <div\n        class=\"Menu noselect bottom \"\n      >\n        <div\n          class=\"menu-contents\"\n        >\n          <div\n            class=\"menu-options\"\n          >\n            <div>\n              <div\n                class=\"view-list\"\n              >\n                <div\n                  aria-label=\"view title\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex\"\n                  >\n                    <div\n                      class=\"menu-option__icon\"\n                    >\n                      <svg\n                        class=\"TableIcon Icon\"\n                        fill=\"currentColor\"\n                        height=\"24\"\n                        viewBox=\"0 0 24 24\"\n                        width=\"24\"\n                        xmlns=\"http://www.w3.org/2000/svg\"\n                      >\n                        <g\n                          opacity=\"0.8\"\n                        >\n                          <path\n                            clip-rule=\"evenodd\"\n                            d=\"M20 4H10V8L20 8V4ZM8 4V8H4V4H8ZM4 14L4 10H8V14H4ZM4 16L4 20H8V16H4ZM10 16V20H20V16L10 16ZM20 14V10L10 10V14L20 14ZM4 2C2.89543 2 2 2.89543 2 4V20C2 21.1046 2.89543 22 4 22H20C21.1046 22 22 21.1046 22 20V4C22 2.89543 21.1046 2 20 2H4Z\"\n                            fill=\"currentColor\"\n                            fill-rule=\"evenodd\"\n                          />\n                        </g>\n                      </svg>\n                    </div>\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      view title\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  aria-label=\"view title\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex\"\n                  >\n                    <div\n                      class=\"menu-option__icon\"\n                    >\n                      <svg\n                        class=\"TableIcon Icon\"\n                        fill=\"currentColor\"\n                        height=\"24\"\n                        viewBox=\"0 0 24 24\"\n                        width=\"24\"\n                        xmlns=\"http://www.w3.org/2000/svg\"\n                      >\n                        <g\n                          opacity=\"0.8\"\n                        >\n                          <path\n                            clip-rule=\"evenodd\"\n                            d=\"M20 4H10V8L20 8V4ZM8 4V8H4V4H8ZM4 14L4 10H8V14H4ZM4 16L4 20H8V16H4ZM10 16V20H20V16L10 16ZM20 14V10L10 10V14L20 14ZM4 2C2.89543 2 2 2.89543 2 4V20C2 21.1046 2.89543 22 4 22H20C21.1046 22 22 21.1046 22 20V4C22 2.89543 21.1046 2 20 2H4Z\"\n                            fill=\"currentColor\"\n                            fill-rule=\"evenodd\"\n                          />\n                        </g>\n                      </svg>\n                    </div>\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      view title\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n              </div>\n            </div>\n            <div>\n              <div\n                class=\"MenuOption MenuSeparator menu-separator\"\n              />\n            </div>\n            <div />\n            <div />\n            <div />\n          </div>\n          <div\n            class=\"menu-spacer hideOnWidescreen\"\n          />\n          <div\n            class=\"menu-options hideOnWidescreen\"\n          >\n            <div\n              aria-label=\"Cancel\"\n              class=\"MenuOption TextOption menu-option menu-cancel\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Cancel\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>,\n  \"debug\": [Function],\n  \"findAllByAltText\": [Function],\n  \"findAllByDisplayValue\": [Function],\n  \"findAllByLabelText\": [Function],\n  \"findAllByPlaceholderText\": [Function],\n  \"findAllByRole\": [Function],\n  \"findAllByTestId\": [Function],\n  \"findAllByText\": [Function],\n  \"findAllByTitle\": [Function],\n  \"findByAltText\": [Function],\n  \"findByDisplayValue\": [Function],\n  \"findByLabelText\": [Function],\n  \"findByPlaceholderText\": [Function],\n  \"findByRole\": [Function],\n  \"findByTestId\": [Function],\n  \"findByText\": [Function],\n  \"findByTitle\": [Function],\n  \"getAllByAltText\": [Function],\n  \"getAllByDisplayValue\": [Function],\n  \"getAllByLabelText\": [Function],\n  \"getAllByPlaceholderText\": [Function],\n  \"getAllByRole\": [Function],\n  \"getAllByTestId\": [Function],\n  \"getAllByText\": [Function],\n  \"getAllByTitle\": [Function],\n  \"getByAltText\": [Function],\n  \"getByDisplayValue\": [Function],\n  \"getByLabelText\": [Function],\n  \"getByPlaceholderText\": [Function],\n  \"getByRole\": [Function],\n  \"getByTestId\": [Function],\n  \"getByText\": [Function],\n  \"getByTitle\": [Function],\n  \"queryAllByAltText\": [Function],\n  \"queryAllByDisplayValue\": [Function],\n  \"queryAllByLabelText\": [Function],\n  \"queryAllByPlaceholderText\": [Function],\n  \"queryAllByRole\": [Function],\n  \"queryAllByTestId\": [Function],\n  \"queryAllByText\": [Function],\n  \"queryAllByTitle\": [Function],\n  \"queryByAltText\": [Function],\n  \"queryByDisplayValue\": [Function],\n  \"queryByLabelText\": [Function],\n  \"queryByPlaceholderText\": [Function],\n  \"queryByRole\": [Function],\n  \"queryByTestId\": [Function],\n  \"queryByText\": [Function],\n  \"queryByTitle\": [Function],\n  \"rerender\": [Function],\n  \"unmount\": [Function],\n}\n`;\n"
  },
  {
    "path": "webapp/src/components/__snapshots__/viewTitle.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/viewTitle add random icon 1`] = `\n<div>\n  <div\n    class=\"ViewTitle\"\n  >\n    <div\n      class=\"add-buttons add-visible\"\n    >\n      <button\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-emoticon-outline\"\n        />\n        <span>\n          Add icon\n        </span>\n      </button>\n      <button\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-eye-outline\"\n        />\n        <span>\n          show description\n        </span>\n      </button>\n    </div>\n    <div\n      class=\"title\"\n    >\n      <input\n        class=\"Editable title\"\n        placeholder=\"Untitled board\"\n        spellcheck=\"true\"\n        title=\"board title\"\n        value=\"board title\"\n      />\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/viewTitle hide description 1`] = `\n<div>\n  <div\n    class=\"ViewTitle\"\n  >\n    <div\n      class=\"add-buttons add-visible\"\n    >\n      <button\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-eye-outline\"\n        />\n        <span>\n          show description\n        </span>\n      </button>\n    </div>\n    <div\n      class=\"title\"\n    >\n      <div\n        class=\"IconSelector\"\n      >\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <div\n            class=\"octo-icon size-m\"\n          >\n            <span>\n              i\n            </span>\n          </div>\n        </div>\n      </div>\n      <input\n        class=\"Editable title\"\n        placeholder=\"Untitled board\"\n        spellcheck=\"true\"\n        title=\"board title\"\n        value=\"board title\"\n      />\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/viewTitle should match snapshot 1`] = `\n<div>\n  <div\n    class=\"ViewTitle\"\n  >\n    <div\n      class=\"add-buttons add-visible\"\n    >\n      <button\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-eye-off-outline\"\n        />\n        <span>\n          hide description\n        </span>\n      </button>\n    </div>\n    <div\n      class=\"title\"\n    >\n      <div\n        class=\"IconSelector\"\n      >\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <div\n            class=\"octo-icon size-m\"\n          >\n            <span>\n              i\n            </span>\n          </div>\n        </div>\n      </div>\n      <input\n        class=\"Editable title\"\n        placeholder=\"Untitled board\"\n        spellcheck=\"true\"\n        title=\"board title\"\n        value=\"board title\"\n      />\n    </div>\n    <div\n      class=\"description\"\n    >\n      <div\n        class=\"MarkdownEditor octo-editor  \"\n      >\n        <div\n          class=\"octo-editor-preview\"\n          data-testid=\"preview-element\"\n        />\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/viewTitle should match snapshot readonly 1`] = `\n<div>\n  <div\n    class=\"ViewTitle\"\n  >\n    <div\n      class=\"add-buttons add-visible\"\n    />\n    <div\n      class=\"title\"\n    >\n      <div\n        class=\"IconSelector\"\n      >\n        <div\n          class=\"octo-icon size-m readonly\"\n        >\n          <span>\n            i\n          </span>\n        </div>\n      </div>\n      <input\n        class=\"Editable readonly title\"\n        placeholder=\"Untitled board\"\n        readonly=\"\"\n        spellcheck=\"true\"\n        title=\"board title\"\n        value=\"board title\"\n      />\n    </div>\n    <div\n      class=\"description\"\n    >\n      <div\n        class=\"MarkdownEditor octo-editor  \"\n      >\n        <div\n          class=\"octo-editor-preview\"\n          data-testid=\"preview-element\"\n        />\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/viewTitle show description 1`] = `\n<div>\n  <div\n    class=\"ViewTitle\"\n  >\n    <div\n      class=\"add-buttons add-visible\"\n    >\n      <button\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-eye-off-outline\"\n        />\n        <span>\n          hide description\n        </span>\n      </button>\n    </div>\n    <div\n      class=\"title\"\n    >\n      <div\n        class=\"IconSelector\"\n      >\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <div\n            class=\"octo-icon size-m\"\n          >\n            <span>\n              i\n            </span>\n          </div>\n        </div>\n      </div>\n      <input\n        class=\"Editable title\"\n        placeholder=\"Untitled board\"\n        spellcheck=\"true\"\n        title=\"board title\"\n        value=\"board title\"\n      />\n    </div>\n    <div\n      class=\"description\"\n    >\n      <div\n        class=\"MarkdownEditor octo-editor  \"\n      >\n        <div\n          class=\"octo-editor-preview\"\n          data-testid=\"preview-element\"\n        />\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/__snapshots__/workspace.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`src/components/workspace return workspace and showcard 1`] = `\n<div>\n  <div\n    class=\"Workspace\"\n  >\n    <div\n      class=\"Sidebar octo-sidebar\"\n    >\n      <div\n        class=\"octo-sidebar-header\"\n      >\n        <div\n          class=\"heading\"\n        >\n          <div\n            class=\"SidebarUserMenu\"\n          >\n            <div\n              class=\"ModalWrapper\"\n            >\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <div\n                  class=\"logo\"\n                >\n                  <div\n                    class=\"logo-title\"\n                  >\n                    <svg\n                      class=\"FocalboardLogoIcon Icon\"\n                      version=\"1.1\"\n                      viewBox=\"0 0 52.589677 64\"\n                      x=\"0px\"\n                      y=\"0px\"\n                    >\n                      <path\n                        d=\"m 33.071077,12.069805 c -12.663,-3.4670001 -27.0530002,3.289 -31.6760002,16.943 -4.655,13.75 2.719,28.67 16.4690002,33.325 13.75,4.655 28.67,-2.719 33.326,-16.469 3.804,-11.235 -0.462,-22.701 -8.976,-29.249 l -0.46,4.871 h -0.001 c 4.631,4.896 6.709,11.941 4.325,18.985 -3.362,9.931 -14.447,15.151 -24.76,11.66 -10.313,-3.49 -15.9480002,-14.37 -12.5870002,-24.301 2.9750002,-8.788 11.9980002,-13.715 20.7430002,-12.625 v -10e-4 z m -6.175,16.488 c 3.456,-0.665 6.986,2.754 5.762,6.37 -0.854,2.522 -3.67,3.85 -6.291,2.962 -2.62,-0.887 -4.052,-3.651 -3.197,-6.174 0.573,-1.697 2.034,-2.852 3.726,-3.158 z m -1.285,-4.944 c -1.786,0.323 -3.45,1.104 -4.812,2.258 -1.299,1.101 -2.319,2.545 -2.898,4.258 -0.879,2.597 -0.579,5.323 0.617,7.632 1.206,2.329 3.325,4.234 6.07,5.164 2.744,0.929 5.584,0.701 7.959,-0.417 2.352,-1.107 4.246,-3.091 5.125,-5.688 0.555,-1.639 0.633,-3.254 0.344,-4.761 -0.21,-1.093 -0.615,-2.134 -1.174,-3.091 l 1.019,-5.107 c 0.189,0.187 0.374,0.378 0.552,0.574 1.75,1.919 3.008,4.283 3.508,6.877 0.415,2.154 0.304,4.457 -0.484,6.784 -1.239,3.661 -3.898,6.453 -7.193,8.005 -3.273,1.541 -7.175,1.858 -10.93,0.588 -3.754,-1.271 -6.661,-3.895 -8.326,-7.108 -1.674,-3.233 -2.09,-7.065 -0.851,-10.728 0.819,-2.419 2.26,-4.46 4.097,-6.016 1.88,-1.593 4.181,-2.673 6.656,-3.125 l -0.001,-0.004 c 1.759,-0.339 3.522,-0.313 5.213,0.016 l -3.583,3.761 c -0.294,0.028 -0.588,0.071 -0.883,0.127 h -0.025 z\"\n                      />\n                      <polygon\n                        points=\"26.057,32.594 37.495,11.658 36.79,8.44 41.066,0.207 43.683,4.611 48.803,4.434 44.185,12.48 40.902,13.697 29.542,34.491 \"\n                        transform=\"translate(7.6780426e-5,-0.21919512)\"\n                      />\n                    </svg>\n                    <span>\n                      Focalboard\n                    </span>\n                    <div\n                      class=\"versionFrame\"\n                    >\n                      <div\n                        class=\"version\"\n                        title=\"v1.0.0\"\n                      >\n                        v1.0.0\n                      </div>\n                    </div>\n                  </div>\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"octo-spacer\"\n        />\n        <div\n          class=\"sidebarSwitcher\"\n        >\n          <button\n            type=\"button\"\n          >\n            <svg\n              class=\"HideSidebarIcon Icon\"\n              viewBox=\"0 0 100 100\"\n              xmlns=\"http://www.w3.org/2000/svg\"\n            >\n              <polyline\n                points=\"80,20 50,50 80,80\"\n              />\n              <polyline\n                points=\"50,20 20,50, 50,80\"\n              />\n            </svg>\n          </button>\n        </div>\n      </div>\n      <div\n        class=\"WorkspaceTitle\"\n      />\n      <div\n        class=\"BoardsSwitcherWrapper\"\n      >\n        <div\n          class=\"BoardsSwitcher\"\n        >\n          <i\n            class=\"CompassIcon icon-magnify MagnifyIcon\"\n          />\n          <div>\n            <span>\n              Find Boards\n            </span>\n          </div>\n        </div>\n      </div>\n      <div\n        class=\"octo-sidebar-list\"\n        data-rbd-droppable-context-id=\"1\"\n        data-rbd-droppable-id=\"lhs-categories\"\n      >\n        <div\n          data-rbd-draggable-context-id=\"1\"\n        >\n          <div\n            class=\"SidebarCategory\"\n          >\n            <div\n              class=\"categoryBoardsDroppableArea\"\n              data-rbd-droppable-context-id=\"1\"\n            >\n              <div\n                class=\"octo-sidebar-item category expanded active\"\n              >\n                <div\n                  aria-describedby=\"rbd-hidden-text-1-hidden-text-5\"\n                  class=\"octo-sidebar-title category-title\"\n                  data-rbd-drag-handle-context-id=\"1\"\n                  draggable=\"false\"\n                  role=\"button\"\n                  tabindex=\"0\"\n                  title=\"Category 1\"\n                >\n                  <i\n                    class=\"CompassIcon icon-chevron-down ChevronDownIcon\"\n                  />\n                  Category 1\n                  <div\n                    class=\"sidebarCategoriesTour\"\n                  />\n                </div>\n                <div\n                  class=\"\"\n                >\n                  <div\n                    aria-label=\"menuwrapper\"\n                    class=\"MenuWrapper\"\n                    role=\"button\"\n                  >\n                    <button\n                      type=\"button\"\n                    >\n                      <i\n                        class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                      />\n                    </button>\n                  </div>\n                </div>\n              </div>\n              <div\n                data-rbd-draggable-context-id=\"1\"\n                data-rbd-draggable-id=\"board1\"\n              >\n                <div\n                  aria-describedby=\"rbd-hidden-text-1-hidden-text-5\"\n                  class=\"SidebarBoardItem subitem active\"\n                  data-rbd-drag-handle-context-id=\"1\"\n                  data-rbd-drag-handle-draggable-id=\"board1\"\n                  draggable=\"false\"\n                  role=\"button\"\n                  tabindex=\"0\"\n                >\n                  <div\n                    class=\"octo-sidebar-icon\"\n                  >\n                    i\n                  </div>\n                  <div\n                    class=\"octo-sidebar-title\"\n                    title=\"board title\"\n                  >\n                    board title\n                  </div>\n                  <div>\n                    <div\n                      aria-label=\"menuwrapper\"\n                      class=\"MenuWrapper x\"\n                      role=\"button\"\n                    >\n                      <button\n                        type=\"button\"\n                      >\n                        <i\n                          class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                        />\n                      </button>\n                    </div>\n                  </div>\n                </div>\n                <div\n                  class=\"SidebarBoardItem sidebar-view-item active\"\n                >\n                  <svg\n                    class=\"BoardIcon Icon\"\n                    fill=\"currentColor\"\n                    height=\"24\"\n                    viewBox=\"0 0 24 24\"\n                    width=\"24\"\n                    xmlns=\"http://www.w3.org/2000/svg\"\n                  >\n                    <g\n                      opacity=\"0.8\"\n                    >\n                      <path\n                        clip-rule=\"evenodd\"\n                        d=\"M4 4H20V20H4V4ZM2 4C2 2.89543 2.89543 2 4 2H20C21.1046 2 22 2.89543 22 4V20C22 21.1046 21.1046 22 20 22H4C2.89543 22 2 21.1046 2 20V4ZM8 6H6V12H8V6ZM11 6H13V16H11V6ZM18 6H16V9H18V6Z\"\n                        fill=\"currentColor\"\n                        fill-rule=\"evenodd\"\n                      />\n                    </g>\n                  </svg>\n                  <div\n                    class=\"octo-sidebar-title\"\n                    title=\"view title\"\n                  >\n                    view title\n                  </div>\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n      <div\n        class=\"octo-spacer\"\n      />\n      <div\n        class=\"add-board\"\n      >\n        + Add board\n      </div>\n      <div\n        class=\"SidebarSettingsMenu\"\n      >\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <div\n            class=\"menu-entry\"\n          >\n            Settings\n          </div>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"mainFrame\"\n    >\n      <div\n        class=\"BoardComponent\"\n      >\n        <div\n          class=\"top-head\"\n        >\n          <div\n            class=\"TopBar\"\n          >\n            <a\n              class=\"link\"\n              href=\"https://www.focalboard.com/fwlink/feedback-focalboard.html?v=1.0.0\"\n              rel=\"noreferrer\"\n              target=\"_blank\"\n            >\n              Give feedback\n            </a>\n            <a\n              href=\"https://www.focalboard.com/guide/user?utm_source=webapp\"\n              rel=\"noreferrer\"\n              target=\"_blank\"\n            >\n              <i\n                class=\"CompassIcon icon-help-circle-outline HelpIcon\"\n              />\n            </a>\n          </div>\n          <div\n            class=\"mid-head\"\n          >\n            <div\n              class=\"ViewTitle\"\n            >\n              <div\n                class=\"add-buttons add-visible\"\n              >\n                <button\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-eye-off-outline\"\n                  />\n                  <span>\n                    hide description\n                  </span>\n                </button>\n              </div>\n              <div\n                class=\"title\"\n              >\n                <div\n                  class=\"IconSelector\"\n                >\n                  <div\n                    aria-label=\"menuwrapper\"\n                    class=\"MenuWrapper\"\n                    role=\"button\"\n                  >\n                    <div\n                      class=\"octo-icon size-m\"\n                    >\n                      <span>\n                        i\n                      </span>\n                    </div>\n                  </div>\n                </div>\n                <input\n                  class=\"Editable title\"\n                  placeholder=\"Untitled board\"\n                  spellcheck=\"true\"\n                  title=\"board title\"\n                  value=\"board title\"\n                />\n              </div>\n              <div\n                class=\"description\"\n              >\n                <div\n                  class=\"MarkdownEditor octo-editor  \"\n                >\n                  <div\n                    class=\"octo-editor-preview\"\n                    data-testid=\"preview-element\"\n                  />\n                </div>\n              </div>\n            </div>\n            <div\n              class=\"shareButtonWrapper\"\n            >\n              <div\n                class=\"ShareBoardButton\"\n              >\n                <button\n                  title=\"Share board\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-lock-outline LockOutlineIcon\"\n                  />\n                  <span>\n                    Share\n                  </span>\n                </button>\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"ViewHeader\"\n          >\n            <div\n              class=\"viewSelector\"\n            >\n              <input\n                class=\"Editable \"\n                placeholder=\"Untitled View\"\n                spellcheck=\"true\"\n                title=\"view title\"\n                value=\"view title\"\n              />\n              <div>\n                <div\n                  aria-label=\"View menu\"\n                  class=\"MenuWrapper\"\n                  role=\"button\"\n                >\n                  <button\n                    type=\"button\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-chevron-down DropdownIcon\"\n                    />\n                  </button>\n                </div>\n              </div>\n            </div>\n            <div\n              class=\"octo-spacer\"\n            />\n            <div\n              aria-label=\"Properties menu\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <button\n                type=\"button\"\n              >\n                <span>\n                  Properties\n                </span>\n              </button>\n            </div>\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <button\n                type=\"button\"\n              >\n                <span>\n                  Group by: \n                  <span\n                    id=\"groupByLabel\"\n                  >\n                    Property 2\n                  </span>\n                </span>\n              </button>\n            </div>\n            <div\n              class=\"ModalWrapper\"\n            >\n              <button\n                type=\"button\"\n              >\n                <span>\n                  Filter\n                </span>\n              </button>\n            </div>\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <button\n                type=\"button\"\n              >\n                <span>\n                  Sort\n                </span>\n              </button>\n            </div>\n            <div\n              class=\"board-search-field\"\n            >\n              <i\n                class=\"CompassIcon icon-magnify board-search-icon\"\n              />\n              <input\n                class=\"Editable \"\n                placeholder=\"Search cards\"\n                value=\"\"\n              />\n            </div>\n            <div\n              class=\"ModalWrapper\"\n            >\n              <div\n                aria-label=\"View header menu\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n            </div>\n            <div\n              class=\"ButtonWithMenu\"\n            >\n              <div\n                class=\"button-text\"\n              >\n                New\n              </div>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <div\n                  class=\"button-dropdown\"\n                >\n                  <i\n                    class=\"CompassIcon icon-chevron-down DropdownIcon\"\n                  />\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"Kanban\"\n        >\n          <div\n            class=\"octo-board-header\"\n            id=\"mainBoardHeader\"\n          >\n            <div\n              class=\"octo-board-header-cell KanbanColumnHeader\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <span\n                class=\"Label empty \"\n                title=\"Items with an empty Property 2 property will go here. This column cannot be removed.\"\n              >\n                No Property 2\n              </span>\n              \n              <div\n                class=\"KanbanCalculation\"\n              >\n                <button\n                  title=\"3\"\n                  type=\"button\"\n                >\n                  <span>\n                    3\n                  </span>\n                </button>\n              </div>\n              <div\n                class=\"octo-spacer\"\n              />\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n              <button\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-plus AddIcon\"\n                />\n              </button>\n            </div>\n            <div\n              class=\"octo-board-header-cell KanbanColumnHeader\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <span\n                class=\"Label propColorBlue \"\n              >\n                <input\n                  class=\"Editable \"\n                  placeholder=\"New Select\"\n                  spellcheck=\"true\"\n                  title=\"value 2\"\n                  value=\"value 2\"\n                />\n              </span>\n              <div\n                class=\"KanbanCalculation\"\n              >\n                <button\n                  title=\"0\"\n                  type=\"button\"\n                >\n                  <span>\n                    0\n                  </span>\n                </button>\n              </div>\n              <div\n                class=\"octo-spacer\"\n              />\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n              <button\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-plus AddIcon\"\n                />\n              </button>\n            </div>\n            <div\n              class=\"octo-board-header-cell narrow\"\n            >\n              <button\n                type=\"button\"\n              >\n                <span>\n                  + Add a group\n                </span>\n              </button>\n            </div>\n          </div>\n          <div\n            class=\"octo-board-body\"\n            id=\"mainBoardBody\"\n          >\n            <div\n              class=\"octo-board-column\"\n            >\n              <div\n                class=\"KanbanCard\"\n                draggable=\"true\"\n                style=\"opacity: 1;\"\n              >\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper optionsMenu\"\n                  role=\"button\"\n                >\n                  <button\n                    type=\"button\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                    />\n                  </button>\n                </div>\n                <div\n                  class=\"octo-icontitle\"\n                >\n                  <div\n                    class=\"octo-icon\"\n                  >\n                    i\n                  </div>\n                  <div\n                    class=\"octo-titletext\"\n                  >\n                    card-1\n                  </div>\n                </div>\n                <div\n                  class=\"octo-tooltip tooltip-top\"\n                  data-tooltip=\"Property 1\"\n                >\n                  <div\n                    class=\"octo-propertyvalue octo-propertyvalue--readonly\"\n                  >\n                    value1\n                  </div>\n                </div>\n              </div>\n              <div\n                class=\"KanbanCard\"\n                draggable=\"true\"\n                style=\"opacity: 1;\"\n              >\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper optionsMenu\"\n                  role=\"button\"\n                >\n                  <button\n                    type=\"button\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                    />\n                  </button>\n                </div>\n                <div\n                  class=\"octo-icontitle\"\n                >\n                  <div\n                    class=\"octo-icon\"\n                  >\n                    i\n                  </div>\n                  <div\n                    class=\"octo-titletext\"\n                  >\n                    card-2\n                  </div>\n                </div>\n                <div\n                  class=\"octo-tooltip tooltip-top\"\n                  data-tooltip=\"Property 1\"\n                >\n                  <div\n                    class=\"octo-propertyvalue octo-propertyvalue--readonly\"\n                  >\n                    value1\n                  </div>\n                </div>\n              </div>\n              <div\n                class=\"KanbanCard\"\n                draggable=\"true\"\n                style=\"opacity: 1;\"\n              >\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper optionsMenu\"\n                  role=\"button\"\n                >\n                  <button\n                    type=\"button\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                    />\n                  </button>\n                </div>\n                <div\n                  class=\"octo-icontitle\"\n                >\n                  <div\n                    class=\"octo-icon\"\n                  >\n                    i\n                  </div>\n                  <div\n                    class=\"octo-titletext\"\n                  >\n                    card-3\n                  </div>\n                </div>\n                <div\n                  class=\"octo-tooltip tooltip-top\"\n                  data-tooltip=\"Property 1\"\n                >\n                  <div\n                    class=\"octo-propertyvalue octo-propertyvalue--readonly\"\n                  >\n                    value1\n                  </div>\n                </div>\n              </div>\n              <button\n                type=\"button\"\n              >\n                <span>\n                  + New\n                </span>\n              </button>\n            </div>\n            <div\n              class=\"octo-board-column\"\n            >\n              <button\n                type=\"button\"\n              >\n                <span>\n                  + New\n                </span>\n              </button>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`src/components/workspace return workspace readonly and showcard 1`] = `\n<div>\n  <div\n    class=\"Workspace\"\n  >\n    <div\n      class=\"mainFrame\"\n    >\n      <div\n        class=\"BoardComponent\"\n      >\n        <div\n          class=\"top-head\"\n        >\n          <div\n            class=\"TopBar\"\n          >\n            <a\n              class=\"link\"\n              href=\"https://www.focalboard.com/fwlink/feedback-focalboard.html?v=1.0.0\"\n              rel=\"noreferrer\"\n              target=\"_blank\"\n            >\n              Give feedback\n            </a>\n            <a\n              href=\"https://www.focalboard.com/guide/user?utm_source=webapp\"\n              rel=\"noreferrer\"\n              target=\"_blank\"\n            >\n              <i\n                class=\"CompassIcon icon-help-circle-outline HelpIcon\"\n              />\n            </a>\n          </div>\n          <div\n            class=\"mid-head\"\n          >\n            <div\n              class=\"ViewTitle\"\n            >\n              <div\n                class=\"add-buttons add-visible\"\n              />\n              <div\n                class=\"title\"\n              >\n                <div\n                  class=\"IconSelector\"\n                >\n                  <div\n                    class=\"octo-icon size-m readonly\"\n                  >\n                    <span>\n                      i\n                    </span>\n                  </div>\n                </div>\n                <input\n                  class=\"Editable readonly title\"\n                  placeholder=\"Untitled board\"\n                  readonly=\"\"\n                  spellcheck=\"true\"\n                  title=\"board title\"\n                  value=\"board title\"\n                />\n              </div>\n              <div\n                class=\"description\"\n              >\n                <div\n                  class=\"MarkdownEditor octo-editor  \"\n                >\n                  <div\n                    class=\"octo-editor-preview\"\n                    data-testid=\"preview-element\"\n                  />\n                </div>\n              </div>\n            </div>\n            <div\n              class=\"shareButtonWrapper\"\n            >\n              <div\n                class=\"ShareBoardLoginButton\"\n              >\n                <button\n                  title=\"Login\"\n                  type=\"button\"\n                >\n                  <span>\n                    Login\n                  </span>\n                </button>\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"ViewHeader\"\n          >\n            <div\n              class=\"viewSelector\"\n            >\n              <input\n                class=\"Editable readonly \"\n                placeholder=\"Untitled View\"\n                readonly=\"\"\n                spellcheck=\"true\"\n                title=\"view title\"\n                value=\"view title\"\n              />\n            </div>\n            <div\n              class=\"octo-spacer\"\n            />\n            <div\n              class=\"board-search-field\"\n            >\n              <i\n                class=\"CompassIcon icon-magnify board-search-icon\"\n              />\n              <input\n                class=\"Editable \"\n                placeholder=\"Search cards\"\n                value=\"\"\n              />\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"Kanban\"\n        >\n          <div\n            class=\"octo-board-header\"\n            id=\"mainBoardHeader\"\n          >\n            <div\n              class=\"octo-board-header-cell KanbanColumnHeader\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <span\n                class=\"Label empty \"\n                title=\"Items with an empty Property 2 property will go here. This column cannot be removed.\"\n              >\n                No Property 2\n              </span>\n              \n              <div\n                class=\"KanbanCalculation\"\n              >\n                <button\n                  title=\"3\"\n                  type=\"button\"\n                >\n                  <span>\n                    3\n                  </span>\n                </button>\n              </div>\n              <div\n                class=\"octo-spacer\"\n              />\n            </div>\n            <div\n              class=\"octo-board-header-cell KanbanColumnHeader\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <span\n                class=\"Label propColorBlue \"\n              >\n                <input\n                  class=\"Editable readonly \"\n                  placeholder=\"New Select\"\n                  readonly=\"\"\n                  spellcheck=\"true\"\n                  title=\"value 2\"\n                  value=\"value 2\"\n                />\n              </span>\n              <div\n                class=\"KanbanCalculation\"\n              >\n                <button\n                  title=\"0\"\n                  type=\"button\"\n                >\n                  <span>\n                    0\n                  </span>\n                </button>\n              </div>\n              <div\n                class=\"octo-spacer\"\n              />\n            </div>\n          </div>\n          <div\n            class=\"octo-board-body\"\n            id=\"mainBoardBody\"\n          >\n            <div\n              class=\"octo-board-column\"\n            >\n              <div\n                class=\"KanbanCard\"\n                draggable=\"false\"\n                style=\"opacity: 1;\"\n              >\n                <div\n                  class=\"octo-icontitle\"\n                >\n                  <div\n                    class=\"octo-icon\"\n                  >\n                    i\n                  </div>\n                  <div\n                    class=\"octo-titletext\"\n                  >\n                    card-1\n                  </div>\n                </div>\n                <div\n                  class=\"octo-tooltip tooltip-top\"\n                  data-tooltip=\"Property 1\"\n                >\n                  <div\n                    class=\"octo-propertyvalue octo-propertyvalue--readonly\"\n                  >\n                    value1\n                  </div>\n                </div>\n              </div>\n              <div\n                class=\"KanbanCard\"\n                draggable=\"false\"\n                style=\"opacity: 1;\"\n              >\n                <div\n                  class=\"octo-icontitle\"\n                >\n                  <div\n                    class=\"octo-icon\"\n                  >\n                    i\n                  </div>\n                  <div\n                    class=\"octo-titletext\"\n                  >\n                    card-2\n                  </div>\n                </div>\n                <div\n                  class=\"octo-tooltip tooltip-top\"\n                  data-tooltip=\"Property 1\"\n                >\n                  <div\n                    class=\"octo-propertyvalue octo-propertyvalue--readonly\"\n                  >\n                    value1\n                  </div>\n                </div>\n              </div>\n              <div\n                class=\"KanbanCard\"\n                draggable=\"false\"\n                style=\"opacity: 1;\"\n              >\n                <div\n                  class=\"octo-icontitle\"\n                >\n                  <div\n                    class=\"octo-icon\"\n                  >\n                    i\n                  </div>\n                  <div\n                    class=\"octo-titletext\"\n                  >\n                    card-3\n                  </div>\n                </div>\n                <div\n                  class=\"octo-tooltip tooltip-top\"\n                  data-tooltip=\"Property 1\"\n                >\n                  <div\n                    class=\"octo-propertyvalue octo-propertyvalue--readonly\"\n                  >\n                    value1\n                  </div>\n                </div>\n              </div>\n            </div>\n            <div\n              class=\"octo-board-column\"\n            />\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`src/components/workspace return workspace with BoardTemplateSelector component 1`] = `\n<div>\n  <div\n    class=\"Workspace\"\n  >\n    <div\n      class=\"mainFrame\"\n    />\n  </div>\n</div>\n`;\n\nexports[`src/components/workspace should match snapshot 1`] = `\n<div>\n  <div\n    class=\"Workspace\"\n  >\n    <div\n      class=\"Sidebar octo-sidebar\"\n    >\n      <div\n        class=\"octo-sidebar-header\"\n      >\n        <div\n          class=\"heading\"\n        >\n          <div\n            class=\"SidebarUserMenu\"\n          >\n            <div\n              class=\"ModalWrapper\"\n            >\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <div\n                  class=\"logo\"\n                >\n                  <div\n                    class=\"logo-title\"\n                  >\n                    <svg\n                      class=\"FocalboardLogoIcon Icon\"\n                      version=\"1.1\"\n                      viewBox=\"0 0 52.589677 64\"\n                      x=\"0px\"\n                      y=\"0px\"\n                    >\n                      <path\n                        d=\"m 33.071077,12.069805 c -12.663,-3.4670001 -27.0530002,3.289 -31.6760002,16.943 -4.655,13.75 2.719,28.67 16.4690002,33.325 13.75,4.655 28.67,-2.719 33.326,-16.469 3.804,-11.235 -0.462,-22.701 -8.976,-29.249 l -0.46,4.871 h -0.001 c 4.631,4.896 6.709,11.941 4.325,18.985 -3.362,9.931 -14.447,15.151 -24.76,11.66 -10.313,-3.49 -15.9480002,-14.37 -12.5870002,-24.301 2.9750002,-8.788 11.9980002,-13.715 20.7430002,-12.625 v -10e-4 z m -6.175,16.488 c 3.456,-0.665 6.986,2.754 5.762,6.37 -0.854,2.522 -3.67,3.85 -6.291,2.962 -2.62,-0.887 -4.052,-3.651 -3.197,-6.174 0.573,-1.697 2.034,-2.852 3.726,-3.158 z m -1.285,-4.944 c -1.786,0.323 -3.45,1.104 -4.812,2.258 -1.299,1.101 -2.319,2.545 -2.898,4.258 -0.879,2.597 -0.579,5.323 0.617,7.632 1.206,2.329 3.325,4.234 6.07,5.164 2.744,0.929 5.584,0.701 7.959,-0.417 2.352,-1.107 4.246,-3.091 5.125,-5.688 0.555,-1.639 0.633,-3.254 0.344,-4.761 -0.21,-1.093 -0.615,-2.134 -1.174,-3.091 l 1.019,-5.107 c 0.189,0.187 0.374,0.378 0.552,0.574 1.75,1.919 3.008,4.283 3.508,6.877 0.415,2.154 0.304,4.457 -0.484,6.784 -1.239,3.661 -3.898,6.453 -7.193,8.005 -3.273,1.541 -7.175,1.858 -10.93,0.588 -3.754,-1.271 -6.661,-3.895 -8.326,-7.108 -1.674,-3.233 -2.09,-7.065 -0.851,-10.728 0.819,-2.419 2.26,-4.46 4.097,-6.016 1.88,-1.593 4.181,-2.673 6.656,-3.125 l -0.001,-0.004 c 1.759,-0.339 3.522,-0.313 5.213,0.016 l -3.583,3.761 c -0.294,0.028 -0.588,0.071 -0.883,0.127 h -0.025 z\"\n                      />\n                      <polygon\n                        points=\"26.057,32.594 37.495,11.658 36.79,8.44 41.066,0.207 43.683,4.611 48.803,4.434 44.185,12.48 40.902,13.697 29.542,34.491 \"\n                        transform=\"translate(7.6780426e-5,-0.21919512)\"\n                      />\n                    </svg>\n                    <span>\n                      Focalboard\n                    </span>\n                    <div\n                      class=\"versionFrame\"\n                    >\n                      <div\n                        class=\"version\"\n                        title=\"v1.0.0\"\n                      >\n                        v1.0.0\n                      </div>\n                    </div>\n                  </div>\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"octo-spacer\"\n        />\n        <div\n          class=\"sidebarSwitcher\"\n        >\n          <button\n            type=\"button\"\n          >\n            <svg\n              class=\"HideSidebarIcon Icon\"\n              viewBox=\"0 0 100 100\"\n              xmlns=\"http://www.w3.org/2000/svg\"\n            >\n              <polyline\n                points=\"80,20 50,50 80,80\"\n              />\n              <polyline\n                points=\"50,20 20,50, 50,80\"\n              />\n            </svg>\n          </button>\n        </div>\n      </div>\n      <div\n        class=\"WorkspaceTitle\"\n      />\n      <div\n        class=\"BoardsSwitcherWrapper\"\n      >\n        <div\n          class=\"BoardsSwitcher\"\n        >\n          <i\n            class=\"CompassIcon icon-magnify MagnifyIcon\"\n          />\n          <div>\n            <span>\n              Find Boards\n            </span>\n          </div>\n        </div>\n      </div>\n      <div\n        class=\"octo-sidebar-list\"\n        data-rbd-droppable-context-id=\"0\"\n        data-rbd-droppable-id=\"lhs-categories\"\n      >\n        <div\n          data-rbd-draggable-context-id=\"0\"\n        >\n          <div\n            class=\"SidebarCategory\"\n          >\n            <div\n              class=\"categoryBoardsDroppableArea\"\n              data-rbd-droppable-context-id=\"0\"\n            >\n              <div\n                class=\"octo-sidebar-item category expanded active\"\n              >\n                <div\n                  aria-describedby=\"rbd-hidden-text-0-hidden-text-0\"\n                  class=\"octo-sidebar-title category-title\"\n                  data-rbd-drag-handle-context-id=\"0\"\n                  draggable=\"false\"\n                  role=\"button\"\n                  tabindex=\"0\"\n                  title=\"Category 1\"\n                >\n                  <i\n                    class=\"CompassIcon icon-chevron-down ChevronDownIcon\"\n                  />\n                  Category 1\n                  <div\n                    class=\"sidebarCategoriesTour\"\n                  />\n                </div>\n                <div\n                  class=\"\"\n                >\n                  <div\n                    aria-label=\"menuwrapper\"\n                    class=\"MenuWrapper\"\n                    role=\"button\"\n                  >\n                    <button\n                      type=\"button\"\n                    >\n                      <i\n                        class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                      />\n                    </button>\n                  </div>\n                </div>\n              </div>\n              <div\n                data-rbd-draggable-context-id=\"0\"\n                data-rbd-draggable-id=\"board1\"\n              >\n                <div\n                  aria-describedby=\"rbd-hidden-text-0-hidden-text-0\"\n                  class=\"SidebarBoardItem subitem active\"\n                  data-rbd-drag-handle-context-id=\"0\"\n                  data-rbd-drag-handle-draggable-id=\"board1\"\n                  draggable=\"false\"\n                  role=\"button\"\n                  tabindex=\"0\"\n                >\n                  <div\n                    class=\"octo-sidebar-icon\"\n                  >\n                    i\n                  </div>\n                  <div\n                    class=\"octo-sidebar-title\"\n                    title=\"board title\"\n                  >\n                    board title\n                  </div>\n                  <div>\n                    <div\n                      aria-label=\"menuwrapper\"\n                      class=\"MenuWrapper x\"\n                      role=\"button\"\n                    >\n                      <button\n                        type=\"button\"\n                      >\n                        <i\n                          class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                        />\n                      </button>\n                    </div>\n                  </div>\n                </div>\n                <div\n                  class=\"SidebarBoardItem sidebar-view-item active\"\n                >\n                  <svg\n                    class=\"BoardIcon Icon\"\n                    fill=\"currentColor\"\n                    height=\"24\"\n                    viewBox=\"0 0 24 24\"\n                    width=\"24\"\n                    xmlns=\"http://www.w3.org/2000/svg\"\n                  >\n                    <g\n                      opacity=\"0.8\"\n                    >\n                      <path\n                        clip-rule=\"evenodd\"\n                        d=\"M4 4H20V20H4V4ZM2 4C2 2.89543 2.89543 2 4 2H20C21.1046 2 22 2.89543 22 4V20C22 21.1046 21.1046 22 20 22H4C2.89543 22 2 21.1046 2 20V4ZM8 6H6V12H8V6ZM11 6H13V16H11V6ZM18 6H16V9H18V6Z\"\n                        fill=\"currentColor\"\n                        fill-rule=\"evenodd\"\n                      />\n                    </g>\n                  </svg>\n                  <div\n                    class=\"octo-sidebar-title\"\n                    title=\"view title\"\n                  >\n                    view title\n                  </div>\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n      <div\n        class=\"octo-spacer\"\n      />\n      <div\n        class=\"add-board\"\n      >\n        + Add board\n      </div>\n      <div\n        class=\"SidebarSettingsMenu\"\n      >\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <div\n            class=\"menu-entry\"\n          >\n            Settings\n          </div>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"mainFrame\"\n    >\n      <div\n        class=\"BoardComponent\"\n      >\n        <div\n          class=\"top-head\"\n        >\n          <div\n            class=\"TopBar\"\n          >\n            <a\n              class=\"link\"\n              href=\"https://www.focalboard.com/fwlink/feedback-focalboard.html?v=1.0.0\"\n              rel=\"noreferrer\"\n              target=\"_blank\"\n            >\n              Give feedback\n            </a>\n            <a\n              href=\"https://www.focalboard.com/guide/user?utm_source=webapp\"\n              rel=\"noreferrer\"\n              target=\"_blank\"\n            >\n              <i\n                class=\"CompassIcon icon-help-circle-outline HelpIcon\"\n              />\n            </a>\n          </div>\n          <div\n            class=\"mid-head\"\n          >\n            <div\n              class=\"ViewTitle\"\n            >\n              <div\n                class=\"add-buttons add-visible\"\n              >\n                <button\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-eye-off-outline\"\n                  />\n                  <span>\n                    hide description\n                  </span>\n                </button>\n              </div>\n              <div\n                class=\"title\"\n              >\n                <div\n                  class=\"IconSelector\"\n                >\n                  <div\n                    aria-label=\"menuwrapper\"\n                    class=\"MenuWrapper\"\n                    role=\"button\"\n                  >\n                    <div\n                      class=\"octo-icon size-m\"\n                    >\n                      <span>\n                        i\n                      </span>\n                    </div>\n                  </div>\n                </div>\n                <input\n                  class=\"Editable title\"\n                  placeholder=\"Untitled board\"\n                  spellcheck=\"true\"\n                  title=\"board title\"\n                  value=\"board title\"\n                />\n              </div>\n              <div\n                class=\"description\"\n              >\n                <div\n                  class=\"MarkdownEditor octo-editor  \"\n                >\n                  <div\n                    class=\"octo-editor-preview\"\n                    data-testid=\"preview-element\"\n                  />\n                </div>\n              </div>\n            </div>\n            <div\n              class=\"shareButtonWrapper\"\n            >\n              <div\n                class=\"ShareBoardButton\"\n              >\n                <button\n                  title=\"Share board\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-lock-outline LockOutlineIcon\"\n                  />\n                  <span>\n                    Share\n                  </span>\n                </button>\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"ViewHeader\"\n          >\n            <div\n              class=\"viewSelector\"\n            >\n              <input\n                class=\"Editable \"\n                placeholder=\"Untitled View\"\n                spellcheck=\"true\"\n                title=\"view title\"\n                value=\"view title\"\n              />\n              <div>\n                <div\n                  aria-label=\"View menu\"\n                  class=\"MenuWrapper\"\n                  role=\"button\"\n                >\n                  <button\n                    type=\"button\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-chevron-down DropdownIcon\"\n                    />\n                  </button>\n                </div>\n              </div>\n            </div>\n            <div\n              class=\"octo-spacer\"\n            />\n            <div\n              aria-label=\"Properties menu\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <button\n                type=\"button\"\n              >\n                <span>\n                  Properties\n                </span>\n              </button>\n            </div>\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <button\n                type=\"button\"\n              >\n                <span>\n                  Group by: \n                  <span\n                    id=\"groupByLabel\"\n                  >\n                    Property 2\n                  </span>\n                </span>\n              </button>\n            </div>\n            <div\n              class=\"ModalWrapper\"\n            >\n              <button\n                type=\"button\"\n              >\n                <span>\n                  Filter\n                </span>\n              </button>\n            </div>\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <button\n                type=\"button\"\n              >\n                <span>\n                  Sort\n                </span>\n              </button>\n            </div>\n            <div\n              class=\"board-search-field\"\n            >\n              <i\n                class=\"CompassIcon icon-magnify board-search-icon\"\n              />\n              <input\n                class=\"Editable \"\n                placeholder=\"Search cards\"\n                value=\"\"\n              />\n            </div>\n            <div\n              class=\"ModalWrapper\"\n            >\n              <div\n                aria-label=\"View header menu\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n            </div>\n            <div\n              class=\"ButtonWithMenu\"\n            >\n              <div\n                class=\"button-text\"\n              >\n                New\n              </div>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <div\n                  class=\"button-dropdown\"\n                >\n                  <i\n                    class=\"CompassIcon icon-chevron-down DropdownIcon\"\n                  />\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"Kanban\"\n        >\n          <div\n            class=\"octo-board-header\"\n            id=\"mainBoardHeader\"\n          >\n            <div\n              class=\"octo-board-header-cell KanbanColumnHeader\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <span\n                class=\"Label empty \"\n                title=\"Items with an empty Property 2 property will go here. This column cannot be removed.\"\n              >\n                No Property 2\n              </span>\n              \n              <div\n                class=\"KanbanCalculation\"\n              >\n                <button\n                  title=\"3\"\n                  type=\"button\"\n                >\n                  <span>\n                    3\n                  </span>\n                </button>\n              </div>\n              <div\n                class=\"octo-spacer\"\n              />\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n              <button\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-plus AddIcon\"\n                />\n              </button>\n            </div>\n            <div\n              class=\"octo-board-header-cell KanbanColumnHeader\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <span\n                class=\"Label propColorBlue \"\n              >\n                <input\n                  class=\"Editable \"\n                  placeholder=\"New Select\"\n                  spellcheck=\"true\"\n                  title=\"value 2\"\n                  value=\"value 2\"\n                />\n              </span>\n              <div\n                class=\"KanbanCalculation\"\n              >\n                <button\n                  title=\"0\"\n                  type=\"button\"\n                >\n                  <span>\n                    0\n                  </span>\n                </button>\n              </div>\n              <div\n                class=\"octo-spacer\"\n              />\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n              <button\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-plus AddIcon\"\n                />\n              </button>\n            </div>\n            <div\n              class=\"octo-board-header-cell narrow\"\n            >\n              <button\n                type=\"button\"\n              >\n                <span>\n                  + Add a group\n                </span>\n              </button>\n            </div>\n          </div>\n          <div\n            class=\"octo-board-body\"\n            id=\"mainBoardBody\"\n          >\n            <div\n              class=\"octo-board-column\"\n            >\n              <div\n                class=\"KanbanCard\"\n                draggable=\"true\"\n                style=\"opacity: 1;\"\n              >\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper optionsMenu\"\n                  role=\"button\"\n                >\n                  <button\n                    type=\"button\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                    />\n                  </button>\n                </div>\n                <div\n                  class=\"octo-icontitle\"\n                >\n                  <div\n                    class=\"octo-icon\"\n                  >\n                    i\n                  </div>\n                  <div\n                    class=\"octo-titletext\"\n                  >\n                    card-1\n                  </div>\n                </div>\n                <div\n                  class=\"octo-tooltip tooltip-top\"\n                  data-tooltip=\"Property 1\"\n                >\n                  <div\n                    class=\"octo-propertyvalue octo-propertyvalue--readonly\"\n                  >\n                    value1\n                  </div>\n                </div>\n              </div>\n              <div\n                class=\"KanbanCard\"\n                draggable=\"true\"\n                style=\"opacity: 1;\"\n              >\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper optionsMenu\"\n                  role=\"button\"\n                >\n                  <button\n                    type=\"button\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                    />\n                  </button>\n                </div>\n                <div\n                  class=\"octo-icontitle\"\n                >\n                  <div\n                    class=\"octo-icon\"\n                  >\n                    i\n                  </div>\n                  <div\n                    class=\"octo-titletext\"\n                  >\n                    card-2\n                  </div>\n                </div>\n                <div\n                  class=\"octo-tooltip tooltip-top\"\n                  data-tooltip=\"Property 1\"\n                >\n                  <div\n                    class=\"octo-propertyvalue octo-propertyvalue--readonly\"\n                  >\n                    value1\n                  </div>\n                </div>\n              </div>\n              <div\n                class=\"KanbanCard\"\n                draggable=\"true\"\n                style=\"opacity: 1;\"\n              >\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper optionsMenu\"\n                  role=\"button\"\n                >\n                  <button\n                    type=\"button\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                    />\n                  </button>\n                </div>\n                <div\n                  class=\"octo-icontitle\"\n                >\n                  <div\n                    class=\"octo-icon\"\n                  >\n                    i\n                  </div>\n                  <div\n                    class=\"octo-titletext\"\n                  >\n                    card-3\n                  </div>\n                </div>\n                <div\n                  class=\"octo-tooltip tooltip-top\"\n                  data-tooltip=\"Property 1\"\n                >\n                  <div\n                    class=\"octo-propertyvalue octo-propertyvalue--readonly\"\n                  >\n                    value1\n                  </div>\n                </div>\n              </div>\n              <button\n                type=\"button\"\n              >\n                <span>\n                  + New\n                </span>\n              </button>\n            </div>\n            <div\n              class=\"octo-board-column\"\n            >\n              <button\n                type=\"button\"\n              >\n                <span>\n                  + New\n                </span>\n              </button>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`src/components/workspace should match snapshot with readonly 1`] = `\n<div>\n  <div\n    class=\"Workspace\"\n  >\n    <div\n      class=\"mainFrame\"\n    >\n      <div\n        class=\"BoardComponent\"\n      >\n        <div\n          class=\"top-head\"\n        >\n          <div\n            class=\"TopBar\"\n          >\n            <a\n              class=\"link\"\n              href=\"https://www.focalboard.com/fwlink/feedback-focalboard.html?v=1.0.0\"\n              rel=\"noreferrer\"\n              target=\"_blank\"\n            >\n              Give feedback\n            </a>\n            <a\n              href=\"https://www.focalboard.com/guide/user?utm_source=webapp\"\n              rel=\"noreferrer\"\n              target=\"_blank\"\n            >\n              <i\n                class=\"CompassIcon icon-help-circle-outline HelpIcon\"\n              />\n            </a>\n          </div>\n          <div\n            class=\"mid-head\"\n          >\n            <div\n              class=\"ViewTitle\"\n            >\n              <div\n                class=\"add-buttons add-visible\"\n              />\n              <div\n                class=\"title\"\n              >\n                <div\n                  class=\"IconSelector\"\n                >\n                  <div\n                    class=\"octo-icon size-m readonly\"\n                  >\n                    <span>\n                      i\n                    </span>\n                  </div>\n                </div>\n                <input\n                  class=\"Editable readonly title\"\n                  placeholder=\"Untitled board\"\n                  readonly=\"\"\n                  spellcheck=\"true\"\n                  title=\"board title\"\n                  value=\"board title\"\n                />\n              </div>\n              <div\n                class=\"description\"\n              >\n                <div\n                  class=\"MarkdownEditor octo-editor  \"\n                >\n                  <div\n                    class=\"octo-editor-preview\"\n                    data-testid=\"preview-element\"\n                  />\n                </div>\n              </div>\n            </div>\n            <div\n              class=\"shareButtonWrapper\"\n            >\n              <div\n                class=\"ShareBoardLoginButton\"\n              >\n                <button\n                  title=\"Login\"\n                  type=\"button\"\n                >\n                  <span>\n                    Login\n                  </span>\n                </button>\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"ViewHeader\"\n          >\n            <div\n              class=\"viewSelector\"\n            >\n              <input\n                class=\"Editable readonly \"\n                placeholder=\"Untitled View\"\n                readonly=\"\"\n                spellcheck=\"true\"\n                title=\"view title\"\n                value=\"view title\"\n              />\n            </div>\n            <div\n              class=\"octo-spacer\"\n            />\n            <div\n              class=\"board-search-field\"\n            >\n              <i\n                class=\"CompassIcon icon-magnify board-search-icon\"\n              />\n              <input\n                class=\"Editable \"\n                placeholder=\"Search cards\"\n                value=\"\"\n              />\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"Kanban\"\n        >\n          <div\n            class=\"octo-board-header\"\n            id=\"mainBoardHeader\"\n          >\n            <div\n              class=\"octo-board-header-cell KanbanColumnHeader\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <span\n                class=\"Label empty \"\n                title=\"Items with an empty Property 2 property will go here. This column cannot be removed.\"\n              >\n                No Property 2\n              </span>\n              \n              <div\n                class=\"KanbanCalculation\"\n              >\n                <button\n                  title=\"3\"\n                  type=\"button\"\n                >\n                  <span>\n                    3\n                  </span>\n                </button>\n              </div>\n              <div\n                class=\"octo-spacer\"\n              />\n            </div>\n            <div\n              class=\"octo-board-header-cell KanbanColumnHeader\"\n              draggable=\"true\"\n              style=\"opacity: 1;\"\n            >\n              <span\n                class=\"Label propColorBlue \"\n              >\n                <input\n                  class=\"Editable readonly \"\n                  placeholder=\"New Select\"\n                  readonly=\"\"\n                  spellcheck=\"true\"\n                  title=\"value 2\"\n                  value=\"value 2\"\n                />\n              </span>\n              <div\n                class=\"KanbanCalculation\"\n              >\n                <button\n                  title=\"0\"\n                  type=\"button\"\n                >\n                  <span>\n                    0\n                  </span>\n                </button>\n              </div>\n              <div\n                class=\"octo-spacer\"\n              />\n            </div>\n          </div>\n          <div\n            class=\"octo-board-body\"\n            id=\"mainBoardBody\"\n          >\n            <div\n              class=\"octo-board-column\"\n            >\n              <div\n                class=\"KanbanCard\"\n                draggable=\"false\"\n                style=\"opacity: 1;\"\n              >\n                <div\n                  class=\"octo-icontitle\"\n                >\n                  <div\n                    class=\"octo-icon\"\n                  >\n                    i\n                  </div>\n                  <div\n                    class=\"octo-titletext\"\n                  >\n                    card-1\n                  </div>\n                </div>\n                <div\n                  class=\"octo-tooltip tooltip-top\"\n                  data-tooltip=\"Property 1\"\n                >\n                  <div\n                    class=\"octo-propertyvalue octo-propertyvalue--readonly\"\n                  >\n                    value1\n                  </div>\n                </div>\n              </div>\n              <div\n                class=\"KanbanCard\"\n                draggable=\"false\"\n                style=\"opacity: 1;\"\n              >\n                <div\n                  class=\"octo-icontitle\"\n                >\n                  <div\n                    class=\"octo-icon\"\n                  >\n                    i\n                  </div>\n                  <div\n                    class=\"octo-titletext\"\n                  >\n                    card-2\n                  </div>\n                </div>\n                <div\n                  class=\"octo-tooltip tooltip-top\"\n                  data-tooltip=\"Property 1\"\n                >\n                  <div\n                    class=\"octo-propertyvalue octo-propertyvalue--readonly\"\n                  >\n                    value1\n                  </div>\n                </div>\n              </div>\n              <div\n                class=\"KanbanCard\"\n                draggable=\"false\"\n                style=\"opacity: 1;\"\n              >\n                <div\n                  class=\"octo-icontitle\"\n                >\n                  <div\n                    class=\"octo-icon\"\n                  >\n                    i\n                  </div>\n                  <div\n                    class=\"octo-titletext\"\n                  >\n                    card-3\n                  </div>\n                </div>\n                <div\n                  class=\"octo-tooltip tooltip-top\"\n                  data-tooltip=\"Property 1\"\n                >\n                  <div\n                    class=\"octo-propertyvalue octo-propertyvalue--readonly\"\n                  >\n                    value1\n                  </div>\n                </div>\n              </div>\n            </div>\n            <div\n              class=\"octo-board-column\"\n            />\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`src/components/workspace show add new view tooltip 1`] = `\n<div\n  class=\"tippy-box tutorial-tour-tip__box AddViewTourStep\"\n  data-animation=\"scale-subtle\"\n  data-escaped=\"\"\n  data-placement=\"bottom-start\"\n  data-reference-hidden=\"\"\n  data-state=\"visible\"\n  role=\"tooltip\"\n  style=\"max-width: 320px; transition-duration: 250ms;\"\n  tabindex=\"-1\"\n>\n  <div\n    class=\"tippy-content\"\n    data-state=\"visible\"\n    style=\"transition-duration: 250ms;\"\n  >\n    <div>\n      <div>\n        <div\n          class=\"tutorial-tour-tip__header\"\n        >\n          <h4\n            class=\"tutorial-tour-tip__header__title\"\n          >\n            Add a new view\n          </h4>\n          <button\n            type=\"button\"\n          >\n            <i\n              class=\"CompassIcon icon-close CloseIcon\"\n            />\n          </button>\n        </div>\n        <div\n          class=\"tutorial-tour-tip__body\"\n        >\n          Go here to create a new view to organise your board using different layouts.\n        </div>\n        <div\n          class=\"tutorial-tour-tip__footer\"\n        >\n          <div\n            class=\"tutorial-tour-tip__footer-buttons\"\n          >\n            <div\n              class=\"tutorial-tour-tip__circles-ctr\"\n            >\n              <div\n                class=\"tutorial-tour-tip__circular-ring tutorial-tour-tip__circular-ring-active\"\n              >\n                <a\n                  class=\"tutorial-tour-tip__circle active\"\n                  data-screen=\"0\"\n                  href=\"#\"\n                />\n              </div>\n              <div\n                class=\"tutorial-tour-tip__circular-ring\"\n              >\n                <a\n                  class=\"tutorial-tour-tip__circle\"\n                  data-screen=\"1\"\n                  href=\"#\"\n                />\n              </div>\n              <div\n                class=\"tutorial-tour-tip__circular-ring\"\n              >\n                <a\n                  class=\"tutorial-tour-tip__circle\"\n                  data-screen=\"2\"\n                  href=\"#\"\n                />\n              </div>\n            </div>\n            <div\n              class=\"tutorial-tour-tip__btn-ctr\"\n            >\n              <button\n                type=\"button\"\n              >\n                <span>\n                  Next\n                </span>\n                <i\n                  class=\"CompassIcon icon-chevron-right icon\"\n                />\n              </button>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n  <div\n    class=\"tippy-arrow\"\n    style=\"position: absolute; left: 0px; transform: translate(3px, 0px);\"\n  />\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/addContentMenuItem.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React, {ReactElement, ReactNode} from 'react'\nimport {render, screen, waitFor} from '@testing-library/react'\n\nimport '@testing-library/jest-dom'\n\nimport {mocked} from 'jest-mock'\n\nimport userEvent from '@testing-library/user-event'\n\nimport mutator from '../mutator'\n\nimport {TestBlockFactory} from '../test/testBlockFactory'\n\nimport {wrapIntl} from '../testUtils'\n\nimport AddContentMenuItem from './addContentMenuItem'\n\nimport './content/textElement'\nimport './content/imageElement'\nimport './content/dividerElement'\nimport './content/checkboxElement'\nimport {CardDetailProvider} from './cardDetail/cardDetailContext'\n\nconst board = TestBlockFactory.createBoard()\nconst card = TestBlockFactory.createCard(board)\nconst wrap = (child: ReactNode): ReactElement => (\n    wrapIntl(\n        <CardDetailProvider card={card} >\n            {child}\n        </CardDetailProvider>,\n    )\n)\n\njest.mock('../mutator')\nconst mockedMutator = mocked(mutator, true)\n\ndescribe('components/addContentMenuItem', () => {\n    beforeEach(() => {\n        jest.clearAllMocks()\n    })\n    test('return an image menu item', () => {\n        const {container} = render(\n            wrap(\n                <AddContentMenuItem\n                    type={'image'}\n                    card={card}\n                    cords={{x: 0}}\n                />,\n            ),\n        )\n        expect(container).toMatchSnapshot()\n    })\n\n    test('return a text menu item', async () => {\n        const {container} = render(\n            wrap(\n                <AddContentMenuItem\n                    type={'text'}\n                    card={card}\n                    cords={{x: 0}}\n                />,\n            ),\n        )\n        expect(container).toMatchSnapshot()\n        const buttonElement = screen.getByRole('button', {name: 'text'})\n        userEvent.click(buttonElement)\n        await waitFor(() => expect(mockedMutator.insertBlock).toBeCalled())\n    })\n\n    test('return a checkbox menu item', async () => {\n        const {container} = render(\n            wrap(\n                <AddContentMenuItem\n                    type={'checkbox'}\n                    card={card}\n                    cords={{x: 0}}\n                />,\n            ),\n        )\n        expect(container).toMatchSnapshot()\n        const buttonElement = screen.getByRole('button', {name: 'checkbox'})\n        userEvent.click(buttonElement)\n        await waitFor(() => expect(mockedMutator.insertBlock).toBeCalled())\n    })\n\n    test('return a divider menu item', async () => {\n        const {container} = render(\n            wrap(\n                <AddContentMenuItem\n                    type={'divider'}\n                    card={card}\n                    cords={{x: 0}}\n                />,\n            ),\n        )\n        expect(container).toMatchSnapshot()\n        const buttonElement = screen.getByRole('button', {name: 'divider'})\n        userEvent.click(buttonElement)\n        await waitFor(() => expect(mockedMutator.insertBlock).toBeCalled())\n    })\n\n    test('return an error and empty element from unknown type', () => {\n        const {container} = render(\n            wrap(\n                <AddContentMenuItem\n                    type={'unknown'}\n                    card={card}\n                    cords={{x: 0}}\n                />,\n            ),\n        )\n        expect(container).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/addContentMenuItem.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {useIntl} from 'react-intl'\n\nimport {BlockTypes, Block} from '../blocks/block'\nimport {Card} from '../blocks/card'\nimport mutator from '../mutator'\nimport octoClient from '../octoClient'\nimport {Utils} from '../utils'\nimport Menu from '../widgets/menu'\n\nimport {contentRegistry} from './content/contentRegistry'\n\ntype Props = {\n    type: BlockTypes\n    card: Card\n    cords: {x: number, y?: number, z?: number}\n}\n\nconst AddContentMenuItem = (props: Props): JSX.Element => {\n    const {card, type, cords} = props\n    const index = cords.x\n    const intl = useIntl()\n\n    const handler = contentRegistry.getHandler(type)\n    if (!handler) {\n        Utils.logError(`addContentMenu, unknown content type: ${type}`)\n        return <></>\n    }\n\n    return (\n        <Menu.Text\n            key={type}\n            id={type}\n            name={handler.getDisplayText(intl)}\n            icon={handler.getIcon()}\n            onClick={async () => {\n                const newBlock = await handler.createBlock(card.boardId, intl)\n                newBlock.parentId = card.id\n                newBlock.boardId = card.boardId\n\n                const typeName = handler.getDisplayText(intl)\n                const description = intl.formatMessage({id: 'ContentBlock.addElement', defaultMessage: 'add {type}'}, {type: typeName})\n\n                const afterRedo = async (nb: Block) => {\n                    const contentOrder = card.fields.contentOrder.slice()\n                    contentOrder.splice(index, 0, nb.id)\n                    await octoClient.patchBlock(card.boardId, card.id, {updatedFields: {contentOrder}})\n                }\n\n                const beforeUndo = async () => {\n                    const contentOrder = card.fields.contentOrder.slice()\n                    await octoClient.patchBlock(card.boardId, card.id, {updatedFields: {contentOrder}})\n                }\n\n                await mutator.insertBlock(newBlock.boardId, newBlock, description, afterRedo, beforeUndo)\n            }}\n        />\n    )\n}\n\nexport default React.memo(AddContentMenuItem)\n"
  },
  {
    "path": "webapp/src/components/blockIconSelector.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {fireEvent, render, screen, act} from '@testing-library/react'\n\nimport userEvent from '@testing-library/user-event'\n\nimport '@testing-library/jest-dom'\n\nimport {mocked} from 'jest-mock'\n\nimport mutator from '../mutator'\n\nimport {wrapIntl} from '../testUtils'\n\nimport {TestBlockFactory} from '../test/testBlockFactory'\n\nimport BlockIconSelector from './blockIconSelector'\n\nconst card = TestBlockFactory.createCard()\nconst icon = '👍'\n\njest.mock('../mutator')\nconst mockedMutator = mocked(mutator, true)\n\ndescribe('components/blockIconSelector', () => {\n    beforeEach(() => {\n        card.fields.icon = icon\n        jest.clearAllMocks()\n    })\n    test('return an icon correctly', () => {\n        const {container} = render(wrapIntl(\n            <BlockIconSelector\n                block={card}\n                size='l'\n            />,\n        ))\n        expect(container).toMatchSnapshot()\n    })\n    test('return no element with no icon', () => {\n        card.fields.icon = ''\n        const {container} = render(wrapIntl(\n            <BlockIconSelector\n                block={card}\n                size='l'\n            />,\n        ))\n        expect(container).toMatchSnapshot()\n    })\n    test('return menu on click', () => {\n        const {container} = render(wrapIntl(\n            <BlockIconSelector\n                block={card}\n                size='l'\n            />,\n        ))\n        userEvent.click(screen.getByRole('button', {name: 'menuwrapper'}))\n        expect(container).toMatchSnapshot()\n    })\n    test('return no menu in readonly', () => {\n        const {container} = render(wrapIntl(\n            <BlockIconSelector\n                block={card}\n                readonly={true}\n            />,\n        ))\n        expect(container).toMatchSnapshot()\n    })\n\n    test('return a new icon after click on random menu', () => {\n        render(wrapIntl(\n            <BlockIconSelector\n                block={card}\n                size='l'\n            />,\n        ))\n        userEvent.click(screen.getByRole('button', {name: 'menuwrapper'}))\n        const buttonRandom = screen.queryByRole('button', {name: 'Random'})\n        expect(buttonRandom).not.toBeNull()\n        userEvent.click(buttonRandom!)\n        expect(mockedMutator.changeBlockIcon).toBeCalledTimes(1)\n    })\n\n    test('return a new icon after click on EmojiPicker', () => {\n        const {container, getByRole, getAllByRole} = render(wrapIntl(\n            <BlockIconSelector\n                block={card}\n                size='l'\n            />,\n        ))\n        act(() => {\n            userEvent.click(getByRole('button', {name: 'menuwrapper'}))\n        })\n        const menuPicker = container.querySelector('div#pick')\n        expect(menuPicker).not.toBeNull()\n\n        act(() => {\n            fireEvent.mouseEnter(menuPicker!)\n        })\n\n        const allButtonThumbUp = getAllByRole('button', {name: /thumbsup/i})\n        userEvent.click(allButtonThumbUp[0])\n        expect(mockedMutator.changeBlockIcon).toBeCalledTimes(1)\n        expect(mockedMutator.changeBlockIcon).toBeCalledWith(card.boardId, card.id, card.fields.icon, '👍')\n    })\n\n    test('return no icon after click on remove menu', () => {\n        const {container, rerender} = render(wrapIntl(\n            <BlockIconSelector\n                block={card}\n                size='l'\n            />,\n        ))\n        userEvent.click(screen.getByRole('button', {name: 'menuwrapper'}))\n        const buttonRemove = screen.queryByRole('button', {name: 'Remove icon'})\n        expect(buttonRemove).not.toBeNull()\n        userEvent.click(buttonRemove!)\n        expect(mockedMutator.changeBlockIcon).toBeCalledTimes(1)\n        expect(mockedMutator.changeBlockIcon).toBeCalledWith(card.boardId, card.id, card.fields.icon, '', 'remove icon')\n\n        //simulate reset icon\n        card.fields.icon = ''\n\n        rerender(wrapIntl(\n            <BlockIconSelector\n                block={card}\n            />),\n        )\n        expect(container).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/blockIconSelector.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useCallback} from 'react'\n\nimport {BlockIcons} from '../blockIcons'\nimport {Card} from '../blocks/card'\nimport mutator from '../mutator'\n\nimport IconSelector from './iconSelector'\n\ntype Props = {\n    block: Card\n    size?: 's' | 'm' | 'l'\n    readonly?: boolean\n}\n\nconst BlockIconSelector = (props: Props) => {\n    const {block, size} = props\n\n    const onSelectEmoji = useCallback((emoji: string) => {\n        mutator.changeBlockIcon(block.boardId, block.id, block.fields.icon, emoji)\n        document.body.click()\n    }, [block.id, block.fields.icon])\n    const onAddRandomIcon = useCallback(() => mutator.changeBlockIcon(block.boardId, block.id, block.fields.icon, BlockIcons.shared.randomIcon()), [block.id, block.fields.icon])\n    const onRemoveIcon = useCallback(() => mutator.changeBlockIcon(block.boardId, block.id, block.fields.icon, '', 'remove icon'), [block.id, block.fields.icon])\n\n    if (!block.fields.icon) {\n        return null\n    }\n\n    let className = `octo-icon size-${size || 'm'}`\n    if (props.readonly) {\n        className += ' readonly'\n    }\n    const iconElement = <div className={className}><span>{block.fields.icon}</span></div>\n\n    return (\n        <IconSelector\n            readonly={props.readonly}\n            iconElement={iconElement}\n            onAddRandomIcon={onAddRandomIcon}\n            onSelectEmoji={onSelectEmoji}\n            onRemoveIcon={onRemoveIcon}\n        />\n    )\n}\n\nexport default React.memo(BlockIconSelector)\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/__snapshots__/blockContent.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/blocksEditor/blockContent should match snapshot 1`] = `\n<div>\n  <div\n    class=\"BlockContent   \"\n    data-testid=\"block-content\"\n    style=\"opacity: 1;\"\n  >\n    <span\n      class=\"action\"\n      data-testid=\"add-action\"\n    >\n      <i\n        class=\"CompassIcon icon-plus AddIcon\"\n      />\n    </span>\n    <span\n      class=\"action\"\n      draggable=\"true\"\n    >\n      <svg\n        class=\"GripIcon Icon\"\n        viewBox=\"0 0 24 24\"\n        xmlns=\"http://www.w3.org/2000/svg\"\n      >\n        <path\n          d=\"M0 0h24v24H0V0z\"\n          fill=\"none\"\n        />\n        <path\n          d=\"M11 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm-2-8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z\"\n        />\n      </svg>\n    </span>\n    <div\n      class=\"content\"\n    >\n      <div>\n        <h1\n          id=\"title\"\n        >\n          Title\n        </h1>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/blocksEditor/blockContent should match snapshot editing 1`] = `\n<div>\n  <div\n    class=\"Editor\"\n  >\n    <input\n      class=\"H1\"\n      data-testid=\"h1\"\n      value=\"Title\"\n    />\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/__snapshots__/blocksEditor.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/blocksEditor/blocksEditor should match snapshot on empty 1`] = `\n<div>\n  <div\n    class=\"BlocksEditor\"\n  >\n    <div\n      class=\"Editor\"\n    >\n      <div\n        class=\"RootInput css-b62m3t-container\"\n      >\n        <span\n          class=\"css-1f43avz-a11yText-A11yText\"\n          id=\"react-select-2-live-region\"\n        />\n        <span\n          aria-atomic=\"false\"\n          aria-live=\"polite\"\n          aria-relevant=\"additions text\"\n          class=\"css-1f43avz-a11yText-A11yText\"\n          role=\"log\"\n        />\n        <div\n          class=\" css-1haocjs-control\"\n        >\n          <div\n            class=\" css-b2z5qd-ValueContainer\"\n          >\n            <div\n              class=\" css-1jqq78o-placeholder\"\n              id=\"react-select-2-placeholder\"\n            >\n              Introduce your text or your slash command\n            </div>\n            <div\n              class=\" css-26cneq-Input\"\n              data-value=\"\"\n            >\n              <input\n                aria-activedescendant=\"\"\n                aria-autocomplete=\"list\"\n                aria-describedby=\"react-select-2-placeholder\"\n                aria-expanded=\"false\"\n                aria-haspopup=\"true\"\n                autocapitalize=\"none\"\n                autocomplete=\"off\"\n                autocorrect=\"off\"\n                class=\"\"\n                id=\"react-select-2-input\"\n                role=\"combobox\"\n                spellcheck=\"false\"\n                style=\"opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;\"\n                tabindex=\"0\"\n                type=\"text\"\n                value=\"\"\n              />\n            </div>\n          </div>\n          <div\n            class=\" css-1hb7zxy-IndicatorsContainer\"\n          />\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/blocksEditor/blocksEditor should match snapshot with blocks 1`] = `\n<div>\n  <div\n    class=\"BlocksEditor\"\n  >\n    <div>\n      <div\n        class=\"BlockContent   \"\n        data-testid=\"block-content\"\n        style=\"opacity: 1;\"\n      >\n        <span\n          class=\"action\"\n          data-testid=\"add-action\"\n        >\n          <i\n            class=\"CompassIcon icon-plus AddIcon\"\n          />\n        </span>\n        <span\n          class=\"action\"\n          draggable=\"true\"\n        >\n          <svg\n            class=\"GripIcon Icon\"\n            viewBox=\"0 0 24 24\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n          >\n            <path\n              d=\"M0 0h24v24H0V0z\"\n              fill=\"none\"\n            />\n            <path\n              d=\"M11 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm-2-8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z\"\n            />\n          </svg>\n        </span>\n        <div\n          class=\"content\"\n        >\n          <div>\n            <h1\n              id=\"title\"\n            >\n              Title\n            </h1>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div>\n      <div\n        class=\"BlockContent   \"\n        data-testid=\"block-content\"\n        style=\"opacity: 1;\"\n      >\n        <span\n          class=\"action\"\n          data-testid=\"add-action\"\n        >\n          <i\n            class=\"CompassIcon icon-plus AddIcon\"\n          />\n        </span>\n        <span\n          class=\"action\"\n          draggable=\"true\"\n        >\n          <svg\n            class=\"GripIcon Icon\"\n            viewBox=\"0 0 24 24\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n          >\n            <path\n              d=\"M0 0h24v24H0V0z\"\n              fill=\"none\"\n            />\n            <path\n              d=\"M11 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm-2-8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z\"\n            />\n          </svg>\n        </span>\n        <div\n          class=\"content\"\n        >\n          <div>\n            <h2\n              id=\"sub-title\"\n            >\n              Sub title\n            </h2>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div>\n      <div\n        class=\"BlockContent   \"\n        data-testid=\"block-content\"\n        style=\"opacity: 1;\"\n      >\n        <span\n          class=\"action\"\n          data-testid=\"add-action\"\n        >\n          <i\n            class=\"CompassIcon icon-plus AddIcon\"\n          />\n        </span>\n        <span\n          class=\"action\"\n          draggable=\"true\"\n        >\n          <svg\n            class=\"GripIcon Icon\"\n            viewBox=\"0 0 24 24\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n          >\n            <path\n              d=\"M0 0h24v24H0V0z\"\n              fill=\"none\"\n            />\n            <path\n              d=\"M11 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm-2-8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z\"\n            />\n          </svg>\n        </span>\n        <div\n          class=\"content\"\n        >\n          <div>\n            <h3\n              id=\"sub-sub-title\"\n            >\n              Sub sub title\n            </h3>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div>\n      <div\n        class=\"BlockContent   \"\n        data-testid=\"block-content\"\n        style=\"opacity: 1;\"\n      >\n        <span\n          class=\"action\"\n          data-testid=\"add-action\"\n        >\n          <i\n            class=\"CompassIcon icon-plus AddIcon\"\n          />\n        </span>\n        <span\n          class=\"action\"\n          draggable=\"true\"\n        >\n          <svg\n            class=\"GripIcon Icon\"\n            viewBox=\"0 0 24 24\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n          >\n            <path\n              d=\"M0 0h24v24H0V0z\"\n              fill=\"none\"\n            />\n            <path\n              d=\"M11 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm-2-8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z\"\n            />\n          </svg>\n        </span>\n        <div\n          class=\"content\"\n        >\n          <div\n            class=\"octo-editor-preview\"\n          >\n            <p>\n              Some \n              <strong>\n                markdown\n              </strong>\n               text\n            </p>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div>\n      <div\n        class=\"BlockContent   \"\n        data-testid=\"block-content\"\n        style=\"opacity: 1;\"\n      >\n        <span\n          class=\"action\"\n          data-testid=\"add-action\"\n        >\n          <i\n            class=\"CompassIcon icon-plus AddIcon\"\n          />\n        </span>\n        <span\n          class=\"action\"\n          draggable=\"true\"\n        >\n          <svg\n            class=\"GripIcon Icon\"\n            viewBox=\"0 0 24 24\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n          >\n            <path\n              d=\"M0 0h24v24H0V0z\"\n              fill=\"none\"\n            />\n            <path\n              d=\"M11 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm-2-8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z\"\n            />\n          </svg>\n        </span>\n        <div\n          class=\"content\"\n        >\n          <div\n            class=\"octo-editor-preview\"\n          >\n            <p>\n              Some multiline\n              <br />\n              <strong>\n                markdown\n              </strong>\n               text\n            </p>\n            \n\n            <h3\n              id=\"with-items\"\n            >\n              With Items\n            </h3>\n            \n\n            <ul>\n              \n\n              <li>\n                Item 1\n              </li>\n              \n\n              <li>\n                Item2\n              </li>\n              \n\n              <li>\n                Item3\n              </li>\n              \n\n            </ul>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div>\n      <div\n        class=\"BlockContent   \"\n        data-testid=\"block-content\"\n        style=\"opacity: 1;\"\n      >\n        <span\n          class=\"action\"\n          data-testid=\"add-action\"\n        >\n          <i\n            class=\"CompassIcon icon-plus AddIcon\"\n          />\n        </span>\n        <span\n          class=\"action\"\n          draggable=\"true\"\n        >\n          <svg\n            class=\"GripIcon Icon\"\n            viewBox=\"0 0 24 24\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n          >\n            <path\n              d=\"M0 0h24v24H0V0z\"\n              fill=\"none\"\n            />\n            <path\n              d=\"M11 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm-2-8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z\"\n            />\n          </svg>\n        </span>\n        <div\n          class=\"content\"\n        >\n          <div\n            class=\"CheckboxView\"\n          >\n            <input\n              checked=\"\"\n              data-testid=\"checkbox-check\"\n              type=\"checkbox\"\n            />\n            <div>\n              <p>\n                Checkbox\n              </p>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"Editor\"\n    >\n      <div\n        class=\"RootInput css-b62m3t-container\"\n      >\n        <span\n          class=\"css-1f43avz-a11yText-A11yText\"\n          id=\"react-select-3-live-region\"\n        />\n        <span\n          aria-atomic=\"false\"\n          aria-live=\"polite\"\n          aria-relevant=\"additions text\"\n          class=\"css-1f43avz-a11yText-A11yText\"\n          role=\"log\"\n        />\n        <div\n          class=\" css-1haocjs-control\"\n        >\n          <div\n            class=\" css-b2z5qd-ValueContainer\"\n          >\n            <div\n              class=\" css-1jqq78o-placeholder\"\n              id=\"react-select-3-placeholder\"\n            >\n              Introduce your text or your slash command\n            </div>\n            <div\n              class=\" css-26cneq-Input\"\n              data-value=\"\"\n            >\n              <input\n                aria-activedescendant=\"\"\n                aria-autocomplete=\"list\"\n                aria-describedby=\"react-select-3-placeholder\"\n                aria-expanded=\"false\"\n                aria-haspopup=\"true\"\n                autocapitalize=\"none\"\n                autocomplete=\"off\"\n                autocorrect=\"off\"\n                class=\"\"\n                id=\"react-select-3-input\"\n                role=\"combobox\"\n                spellcheck=\"false\"\n                style=\"opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;\"\n                tabindex=\"0\"\n                type=\"text\"\n                value=\"\"\n              />\n            </div>\n          </div>\n          <div\n            class=\" css-1hb7zxy-IndicatorsContainer\"\n          />\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/__snapshots__/editor.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/blocksEditor/editor should match snapshot 1`] = `\n<div>\n  <div\n    class=\"Editor\"\n  >\n    <div\n      class=\"TextContent\"\n      data-testid=\"text\"\n    >\n      <div\n        class=\"MarkdownEditor octo-editor  active\"\n      >\n        <div\n          class=\"MarkdownEditorInput\"\n        >\n          <div\n            class=\"DraftEditor-root\"\n          >\n            <div\n              class=\"DraftEditor-editorContainer\"\n            >\n              <div\n                aria-autocomplete=\"list\"\n                aria-expanded=\"false\"\n                class=\"notranslate public-DraftEditor-content\"\n                contenteditable=\"true\"\n                role=\"combobox\"\n                spellcheck=\"false\"\n                style=\"outline: none; user-select: text; white-space: pre-wrap; word-wrap: break-word;\"\n              >\n                <div\n                  data-contents=\"true\"\n                >\n                  <div\n                    class=\"\"\n                    data-block=\"true\"\n                    data-editor=\"123\"\n                    data-offset-key=\"123-0-0\"\n                  >\n                    <div\n                      class=\"public-DraftStyleDefault-block public-DraftStyleDefault-ltr\"\n                      data-offset-key=\"123-0-0\"\n                    >\n                      <span\n                        data-offset-key=\"123-0-0\"\n                      >\n                        <span\n                          data-text=\"true\"\n                        >\n                          test-value\n                        </span>\n                      </span>\n                    </div>\n                  </div>\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/blocksEditor/editor should match snapshot on empty 1`] = `\n<div>\n  <div\n    class=\"Editor\"\n  >\n    <div\n      class=\"RootInput css-b62m3t-container\"\n    >\n      <span\n        class=\"css-1f43avz-a11yText-A11yText\"\n        id=\"react-select-2-live-region\"\n      />\n      <span\n        aria-atomic=\"false\"\n        aria-live=\"polite\"\n        aria-relevant=\"additions text\"\n        class=\"css-1f43avz-a11yText-A11yText\"\n        role=\"log\"\n      />\n      <div\n        class=\" css-1haocjs-control\"\n      >\n        <div\n          class=\" css-b2z5qd-ValueContainer\"\n        >\n          <div\n            class=\" css-1jqq78o-placeholder\"\n            id=\"react-select-2-placeholder\"\n          >\n            Introduce your text or your slash command\n          </div>\n          <div\n            class=\" css-26cneq-Input\"\n            data-value=\"\"\n          >\n            <input\n              aria-activedescendant=\"\"\n              aria-autocomplete=\"list\"\n              aria-describedby=\"react-select-2-placeholder\"\n              aria-expanded=\"false\"\n              aria-haspopup=\"true\"\n              autocapitalize=\"none\"\n              autocomplete=\"off\"\n              autocorrect=\"off\"\n              class=\"\"\n              id=\"react-select-2-input\"\n              role=\"combobox\"\n              spellcheck=\"false\"\n              style=\"opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;\"\n              tabindex=\"0\"\n              type=\"text\"\n              value=\"\"\n            />\n          </div>\n        </div>\n        <div\n          class=\" css-1hb7zxy-IndicatorsContainer\"\n        />\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/__snapshots__/rootInput.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/blocksEditor/rootInput should match Display snapshot 1`] = `\n<div>\n  <div\n    class=\"RootInput css-b62m3t-container\"\n  >\n    <span\n      class=\"css-1f43avz-a11yText-A11yText\"\n      id=\"react-select-2-live-region\"\n    />\n    <span\n      aria-atomic=\"false\"\n      aria-live=\"polite\"\n      aria-relevant=\"additions text\"\n      class=\"css-1f43avz-a11yText-A11yText\"\n      role=\"log\"\n    />\n    <div\n      class=\" css-1haocjs-control\"\n    >\n      <div\n        class=\" css-b2z5qd-ValueContainer\"\n      >\n        <div\n          class=\" css-1qgh1u0-Input\"\n          data-value=\"test-value\"\n        >\n          <input\n            aria-activedescendant=\"\"\n            aria-autocomplete=\"list\"\n            aria-describedby=\"react-select-2-placeholder\"\n            aria-expanded=\"false\"\n            aria-haspopup=\"true\"\n            autocapitalize=\"none\"\n            autocomplete=\"off\"\n            autocorrect=\"off\"\n            class=\"\"\n            id=\"react-select-2-input\"\n            role=\"combobox\"\n            spellcheck=\"false\"\n            style=\"opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;\"\n            tabindex=\"0\"\n            type=\"text\"\n            value=\"test-value\"\n          />\n        </div>\n      </div>\n      <div\n        class=\" css-1hb7zxy-IndicatorsContainer\"\n      />\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/blocksEditor/rootInput should match Input snapshot 1`] = `\n<div>\n  <div\n    class=\"RootInput css-b62m3t-container\"\n  >\n    <span\n      class=\"css-1f43avz-a11yText-A11yText\"\n      id=\"react-select-3-live-region\"\n    />\n    <span\n      aria-atomic=\"false\"\n      aria-live=\"polite\"\n      aria-relevant=\"additions text\"\n      class=\"css-1f43avz-a11yText-A11yText\"\n      role=\"log\"\n    />\n    <div\n      class=\" css-1haocjs-control\"\n    >\n      <div\n        class=\" css-b2z5qd-ValueContainer\"\n      >\n        <div\n          class=\" css-1qgh1u0-Input\"\n          data-value=\"test-value\"\n        >\n          <input\n            aria-activedescendant=\"\"\n            aria-autocomplete=\"list\"\n            aria-describedby=\"react-select-3-placeholder\"\n            aria-expanded=\"false\"\n            aria-haspopup=\"true\"\n            autocapitalize=\"none\"\n            autocomplete=\"off\"\n            autocorrect=\"off\"\n            class=\"\"\n            id=\"react-select-3-input\"\n            role=\"combobox\"\n            spellcheck=\"false\"\n            style=\"opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;\"\n            tabindex=\"0\"\n            type=\"text\"\n            value=\"test-value\"\n          />\n        </div>\n      </div>\n      <div\n        class=\" css-1hb7zxy-IndicatorsContainer\"\n      />\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/blocksEditor/rootInput should match Input snapshot with menu open 1`] = `\n<div>\n  <div\n    class=\"RootInput css-b62m3t-container\"\n  >\n    <span\n      class=\"css-1f43avz-a11yText-A11yText\"\n      id=\"react-select-4-live-region\"\n    />\n    <span\n      aria-atomic=\"false\"\n      aria-live=\"polite\"\n      aria-relevant=\"additions text\"\n      class=\"css-1f43avz-a11yText-A11yText\"\n      role=\"log\"\n    />\n    <div\n      class=\" css-1haocjs-control\"\n    >\n      <div\n        class=\" css-b2z5qd-ValueContainer\"\n      >\n        <div\n          class=\" css-1jqq78o-placeholder\"\n          id=\"react-select-4-placeholder\"\n        >\n          Introduce your text or your slash command\n        </div>\n        <div\n          class=\" css-26cneq-Input\"\n          data-value=\"\"\n        >\n          <input\n            aria-activedescendant=\"react-select-4-option-0\"\n            aria-autocomplete=\"list\"\n            aria-controls=\"react-select-4-listbox\"\n            aria-describedby=\"react-select-4-placeholder\"\n            aria-expanded=\"true\"\n            aria-haspopup=\"true\"\n            autocapitalize=\"none\"\n            autocomplete=\"off\"\n            autocorrect=\"off\"\n            class=\"\"\n            id=\"react-select-4-input\"\n            role=\"combobox\"\n            spellcheck=\"false\"\n            style=\"opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;\"\n            tabindex=\"0\"\n            type=\"text\"\n            value=\"\"\n          />\n        </div>\n      </div>\n      <div\n        class=\" css-1hb7zxy-IndicatorsContainer\"\n      />\n    </div>\n    <div\n      class=\" css-u0i6pk-MenuPortal\"\n    >\n      <div\n        class=\" css-45h7mv-menu\"\n      >\n        <div\n          aria-multiselectable=\"false\"\n          class=\" css-1d1qzc4-MenuList\"\n          id=\"react-select-4-listbox\"\n          role=\"listbox\"\n        >\n          <div\n            aria-disabled=\"false\"\n            aria-selected=\"false\"\n            class=\" css-8e5kjb-option\"\n            id=\"react-select-4-option-0\"\n            role=\"option\"\n            tabindex=\"-1\"\n          >\n            /title Creates a new Title block.\n          </div>\n          <div\n            aria-disabled=\"false\"\n            aria-selected=\"false\"\n            class=\" css-x3yilo-option\"\n            id=\"react-select-4-option-1\"\n            role=\"option\"\n            tabindex=\"-1\"\n          >\n            /subtitle Creates a new Sub title block.\n          </div>\n          <div\n            aria-disabled=\"false\"\n            aria-selected=\"false\"\n            class=\" css-x3yilo-option\"\n            id=\"react-select-4-option-2\"\n            role=\"option\"\n            tabindex=\"-1\"\n          >\n            /subsubtitle Creates a new Sub Sub title block.\n          </div>\n          <div\n            aria-disabled=\"false\"\n            aria-selected=\"false\"\n            class=\" css-x3yilo-option\"\n            id=\"react-select-4-option-3\"\n            role=\"option\"\n            tabindex=\"-1\"\n          >\n            /image Creates a new Image block.\n          </div>\n          <div\n            aria-disabled=\"false\"\n            aria-selected=\"false\"\n            class=\" css-x3yilo-option\"\n            id=\"react-select-4-option-4\"\n            role=\"option\"\n            tabindex=\"-1\"\n          >\n            /text Creates a new Text block.\n          </div>\n          <div\n            aria-disabled=\"false\"\n            aria-selected=\"false\"\n            class=\" css-x3yilo-option\"\n            id=\"react-select-4-option-5\"\n            role=\"option\"\n            tabindex=\"-1\"\n          >\n            /divider Creates a new Divider block.\n          </div>\n          <div\n            aria-disabled=\"false\"\n            aria-selected=\"false\"\n            class=\" css-x3yilo-option\"\n            id=\"react-select-4-option-6\"\n            role=\"option\"\n            tabindex=\"-1\"\n          >\n            /list-item Creates a new List item block.\n          </div>\n          <div\n            aria-disabled=\"false\"\n            aria-selected=\"false\"\n            class=\" css-x3yilo-option\"\n            id=\"react-select-4-option-7\"\n            role=\"option\"\n            tabindex=\"-1\"\n          >\n            /attachment Creates a new Attachment block.\n          </div>\n          <div\n            aria-disabled=\"false\"\n            aria-selected=\"false\"\n            class=\" css-x3yilo-option\"\n            id=\"react-select-4-option-8\"\n            role=\"option\"\n            tabindex=\"-1\"\n          >\n            /quote Creates a new Quote block.\n          </div>\n          <div\n            aria-disabled=\"false\"\n            aria-selected=\"false\"\n            class=\" css-x3yilo-option\"\n            id=\"react-select-4-option-9\"\n            role=\"option\"\n            tabindex=\"-1\"\n          >\n            /video Creates a new Video block.\n          </div>\n          <div\n            aria-disabled=\"false\"\n            aria-selected=\"false\"\n            class=\" css-x3yilo-option\"\n            id=\"react-select-4-option-10\"\n            role=\"option\"\n            tabindex=\"-1\"\n          >\n            /checkbox Creates a new Checkbox block.\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blockContent.scss",
    "content": ".BlockContent {\n    display: flex;\n\n    &.over-up {\n        border-top: 1px solid rgba(128, 192, 255, 0.4);\n    }\n\n    &.over-down {\n        border-bottom: 1px solid rgba(128, 192, 255, 0.4);\n    }\n\n    &:hover {\n        .action {\n            opacity: 1;\n\n            .AddIcon {\n                opacity: 0.5;\n            }\n        }\n    }\n\n    .action {\n        transition: opacity 0.3s;\n        opacity: 0;\n        margin: 5px;\n    }\n\n    .content {\n        flex-grow: 1;\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blockContent.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {Provider as ReduxProvider} from 'react-redux'\nimport {render, screen, fireEvent, act} from '@testing-library/react'\n\nimport {mockDOM, wrapDNDIntl, mockStateStore} from '../../testUtils'\nimport {TestBlockFactory} from '../../test/testBlockFactory'\n\nimport BlockContent from './blockContent'\n\njest.mock('draft-js/lib/generateRandomKey', () => () => '123')\n\ndescribe('components/blocksEditor/blockContent', () => {\n    beforeEach(mockDOM)\n\n    const block = {id: '1', value: 'Title', contentType: 'h1'}\n\n    const board1 = TestBlockFactory.createBoard()\n    board1.id = 'board-id-1'\n\n    const state = {\n        users: {\n            boardUsers: {\n                1: {username: 'abc'},\n                2: {username: 'd'},\n                3: {username: 'e'},\n                4: {username: 'f'},\n                5: {username: 'g'},\n            },\n        },\n        boards: {\n            current: 'board-id-1',\n            boards: {\n                [board1.id]: board1,\n            },\n        },\n        clientConfig: {\n            value: {},\n        },\n    }\n    const store = mockStateStore([], state)\n\n    test('should match snapshot', async () => {\n        let container\n        await act(async () => {\n            const result = render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <BlockContent\n                        boardId='fake-board-id'\n                        block={block}\n                        contentOrder={[block.id]}\n                        editing={null}\n                        setEditing={jest.fn()}\n                        setAfterBlock={jest.fn()}\n                        onSave={jest.fn()}\n                        onMove={jest.fn()}\n                    />\n                </ReduxProvider>,\n            ))\n            container = result.container\n        })\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot editing', async () => {\n        let container\n        await act(async () => {\n            const result = render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <BlockContent\n                        boardId='fake-board-id'\n                        block={block}\n                        contentOrder={[block.id]}\n                        editing={block}\n                        setEditing={jest.fn()}\n                        setAfterBlock={jest.fn()}\n                        onSave={jest.fn()}\n                        onMove={jest.fn()}\n                    />\n                </ReduxProvider>,\n            ))\n            container = result.container\n        })\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should call setEditing on click the content', async () => {\n        const setEditing = jest.fn()\n        await act(async () => {\n            render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <BlockContent\n                        boardId='fake-board-id'\n                        block={block}\n                        contentOrder={[block.id]}\n                        editing={null}\n                        setEditing={setEditing}\n                        setAfterBlock={jest.fn()}\n                        onSave={jest.fn()}\n                        onMove={jest.fn()}\n                    />\n                </ReduxProvider>,\n            ))\n        })\n        const item = screen.getByTestId('block-content')\n        expect(setEditing).not.toBeCalled()\n        fireEvent.click(item)\n        expect(setEditing).toBeCalledWith(block)\n    })\n\n    test('should call setEditing on click the content', async () => {\n        const setAfterBlock = jest.fn()\n        await act(async () => {\n            render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <BlockContent\n                        boardId='fake-board-id'\n                        block={block}\n                        contentOrder={[block.id]}\n                        editing={null}\n                        setEditing={jest.fn()}\n                        setAfterBlock={setAfterBlock}\n                        onSave={jest.fn()}\n                        onMove={jest.fn()}\n                    />\n                </ReduxProvider>,\n            ))\n        })\n        const item = screen.getByTestId('add-action')\n        expect(setAfterBlock).not.toBeCalled()\n        fireEvent.click(item)\n        expect(setAfterBlock).toBeCalledWith(block)\n    })\n\n    test('should call onSave on hit enter in the input', async () => {\n        const onSave = jest.fn()\n        await act(async () => {\n            render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <BlockContent\n                        boardId='fake-board-id'\n                        block={block}\n                        contentOrder={[block.id]}\n                        editing={block}\n                        setEditing={jest.fn()}\n                        setAfterBlock={jest.fn()}\n                        onSave={onSave}\n                        onMove={jest.fn()}\n                    />\n                </ReduxProvider>,\n            ))\n        })\n        const input = screen.getByDisplayValue('Title')\n        expect(onSave).not.toBeCalled()\n        fireEvent.change(input, {target: {value: 'test'}})\n        fireEvent.keyDown(input, {key: 'Enter'})\n\n        expect(onSave).toBeCalledWith(expect.objectContaining({value: 'test'}))\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blockContent.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\nimport {useDrag, useDrop} from 'react-dnd'\n\nimport GripIcon from '../../widgets/icons/grip'\n\nimport AddIcon from '../../widgets/icons/add'\n\nimport Editor from './editor'\nimport * as registry from './blocks'\nimport {BlockData} from './blocks/types'\n\nimport './blockContent.scss'\n\ntype Props = {\n    boardId?: string\n    block: BlockData\n    contentOrder: string[]\n    editing: BlockData|null\n    setEditing: (block: BlockData|null) => void\n    setAfterBlock: (block: BlockData|null) => void\n    onSave: (block: BlockData) => Promise<BlockData|null>\n    onMove: (block: BlockData, beforeBlock: BlockData|null, afterBlock: BlockData|null) => Promise<void>\n}\n\nfunction BlockContent(props: Props) {\n    const {block, editing, setEditing, onSave, contentOrder, boardId} = props\n\n    const [{isDragging}, drag, preview] = useDrag(() => ({\n        type: 'block',\n        item: block,\n        collect: (monitor) => ({\n            isDragging: Boolean(monitor.isDragging()),\n        }),\n    }), [block, contentOrder])\n    const [{isOver, draggingUp}, drop] = useDrop(\n        () => ({\n            accept: 'block',\n            drop: (item: BlockData) => {\n                if (item.id !== block.id) {\n                    if (contentOrder.indexOf(item.id || '') > contentOrder.indexOf(block.id || '')) {\n                        props.onMove(item, block, null)\n                    } else {\n                        props.onMove(item, null, block)\n                    }\n                }\n            },\n            collect: (monitor) => ({\n                isOver: Boolean(monitor.isOver()) && (monitor.getItem() as BlockData).id! !== block.id,\n                draggingUp: (monitor.getItem() as BlockData)?.id && contentOrder.indexOf((monitor.getItem() as BlockData).id!) > contentOrder.indexOf(block.id || ''),\n            }),\n        }),\n        [block, props.onMove, contentOrder],\n    )\n\n    if (editing && editing.id === block.id) {\n        return (\n            <Editor\n                onSave={async (b) => {\n                    const updatedBlock = await onSave(b)\n                    props.setEditing(null)\n                    props.setAfterBlock(updatedBlock)\n                    return updatedBlock\n                }}\n                id={block.id}\n                initialValue={block.value}\n                initialContentType={block.contentType}\n            />\n        )\n    }\n\n    const contentType = registry.get(block.contentType)\n    if (contentType && contentType.Display) {\n        const DisplayContent = contentType.Display\n        return (\n            <div\n                ref={drop}\n                data-testid='block-content'\n                className={`BlockContent ${isOver && draggingUp ? 'over-up' : ''}  ${isOver && !draggingUp ? 'over-down' : ''}`}\n                key={block.id}\n                style={{\n                    opacity: isDragging ? 0.5 : 1,\n                }}\n                onClick={() => {\n                    setEditing(block)\n                }}\n            >\n                <span\n                    className='action'\n                    data-testid='add-action'\n                    onClick={(e) => {\n                        e.preventDefault()\n                        e.stopPropagation()\n                        props.setAfterBlock(block)\n                    }}\n                >\n                    <AddIcon/>\n                </span>\n                <span\n                    className='action'\n                    ref={drag}\n                >\n                    <GripIcon/>\n                </span>\n                <div\n                    className='content'\n                    ref={preview}\n                >\n                    <DisplayContent\n                        value={block.value}\n                        onChange={() => null}\n                        onCancel={() => null}\n                        onSave={(value) => onSave({...block, value})}\n                        currentBoardId={boardId}\n                    />\n                </div>\n            </div>\n        )\n    }\n    return null\n}\n\nexport default BlockContent\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blocks/attachment/__snapshots__/attachment.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/blocksEditor/blocks/attachment should match Display snapshot 1`] = `\n<div>\n  <div\n    class=\"AttachmentView\"\n    data-testid=\"attachment\"\n  >\n    <a\n      download=\"test-filename\"\n      href=\"test.jpg\"\n    >\n      📎\n       \n      test-filename\n    </a>\n  </div>\n</div>\n`;\n\nexports[`components/blocksEditor/blocks/attachment should match Display snapshot with empty value 1`] = `\n<div>\n  <div\n    class=\"AttachmentView\"\n    data-testid=\"attachment\"\n  >\n    <a\n      download=\"\"\n      href=\"#\"\n    >\n      📎\n       \n      \n    </a>\n  </div>\n</div>\n`;\n\nexports[`components/blocksEditor/blocks/attachment should match Input snapshot 1`] = `\n<div>\n  <input\n    class=\"Attachment\"\n    data-testid=\"attachment-input\"\n    type=\"file\"\n  />\n</div>\n`;\n\nexports[`components/blocksEditor/blocks/attachment should match Input snapshot with empty input 1`] = `\n<div>\n  <input\n    class=\"Attachment\"\n    data-testid=\"attachment-input\"\n    type=\"file\"\n  />\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blocks/attachment/attachment.scss",
    "content": ".Attachment {\n    display: none;\n}\n\n.AttachmentView {\n    border: 1px solid #ccc;\n    border-radius: 5px;\n    padding: 10px;\n    margin-bottom: 10px;\n}\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blocks/attachment/attachment.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {render, screen, fireEvent} from '@testing-library/react'\nimport {mocked} from 'jest-mock'\n\nimport octoClient from '../../../../octoClient'\n\nimport AttachmentBlock from '.'\n\njest.mock('../../../../octoClient')\n\ndescribe('components/blocksEditor/blocks/attachment', () => {\n    test('should match Display snapshot', async () => {\n        const mockedOcto = mocked(octoClient, true)\n        mockedOcto.getFileAsDataUrl.mockResolvedValue({url: 'test.jpg'})\n        const Component = AttachmentBlock.Display\n        const {container} = render(\n            <Component\n                onChange={jest.fn()}\n                value={{file: 'test', filename: 'test-filename'}}\n                onCancel={jest.fn()}\n                onSave={jest.fn()}\n            />,\n        )\n        await screen.findByTestId('attachment')\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match Display snapshot with empty value', async () => {\n        const Component = AttachmentBlock.Display\n        const {container} = render(\n            <Component\n                onChange={jest.fn()}\n                value={{file: '', filename: ''}}\n                onCancel={jest.fn()}\n                onSave={jest.fn()}\n                currentBoardId=''\n            />,\n        )\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match Input snapshot', async () => {\n        const Component = AttachmentBlock.Input\n        const {container} = render(\n            <Component\n                onChange={jest.fn()}\n                value={{file: 'test', filename: 'test-filename'}}\n                onCancel={jest.fn()}\n                onSave={jest.fn()}\n            />,\n        )\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match Input snapshot with empty input', async () => {\n        const Component = AttachmentBlock.Input\n        const {container} = render(\n            <Component\n                onChange={jest.fn()}\n                value={{file: '', filename: ''}}\n                onCancel={jest.fn()}\n                onSave={jest.fn()}\n            />,\n        )\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should emit onSave on change', async () => {\n        const onSave = jest.fn()\n        const Component = AttachmentBlock.Input\n        render(\n            <Component\n                onChange={jest.fn()}\n                value={{file: 'test', filename: 'test-filename'}}\n                onCancel={jest.fn()}\n                onSave={onSave}\n            />,\n        )\n\n        expect(onSave).not.toBeCalled()\n        const input = screen.getByTestId('attachment-input')\n        fireEvent.change(input, {target: {files: {length: 1, item: () => new File([], 'test-file', {type: 'text/plain'})}}})\n        expect(onSave).toBeCalledWith({file: new File([], 'test-file', {type: 'text/plain'}), filename: 'test-file'})\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blocks/attachment/index.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useRef, useEffect, useState} from 'react'\n\nimport {BlockInputProps, ContentType} from '../types'\nimport octoClient from '../../../../octoClient'\n\nimport './attachment.scss'\n\ntype FileInfo = {\n    file: string|File\n    filename: string\n}\n\nconst Attachment: ContentType<FileInfo> = {\n    name: 'attachment',\n    displayName: 'Attachment',\n    slashCommand: '/attachment',\n    prefix: '',\n    runSlashCommand: (): void => {},\n    editable: false,\n    Display: (props: BlockInputProps<FileInfo>) => {\n        const [fileDataUrl, setFileDataUrl] = useState<string|null>(null)\n\n        useEffect(() => {\n            if (!fileDataUrl) {\n                const loadFile = async () => {\n                    if (props.value && props.value.file && typeof props.value.file === 'string') {\n                        const fileURL = await octoClient.getFileAsDataUrl(props.currentBoardId || '', props.value.file)\n                        setFileDataUrl(fileURL.url || '')\n                    }\n                }\n                loadFile()\n            }\n        }, [props.value, props.value.file, props.currentBoardId])\n\n        return (\n            <div\n                className='AttachmentView'\n                data-testid='attachment'\n            >\n                <a\n                    href={fileDataUrl || '#'}\n                    onClick={(e) => e.stopPropagation()}\n                    download={props.value.filename}\n                >\n                    {'📎'} {props.value.filename}\n                </a>\n            </div>\n        )\n    },\n    Input: (props: BlockInputProps<FileInfo>) => {\n        const ref = useRef<HTMLInputElement|null>(null)\n        useEffect(() => {\n            ref.current?.click()\n        }, [])\n\n        return (\n            <input\n                ref={ref}\n                className='Attachment'\n                data-testid='attachment-input'\n                type='file'\n                onChange={(e) => {\n                    const files = e.currentTarget?.files\n                    if (files) {\n                        for (let i = 0; i < files.length; i++) {\n                            const file = files.item(i)\n                            if (file) {\n                                props.onSave({file, filename: file.name})\n                            }\n                        }\n                    }\n                }}\n            />\n        )\n    },\n}\n\nAttachment.runSlashCommand = (changeType: (contentType: ContentType<FileInfo>) => void, changeValue: (value: FileInfo) => void): void => {\n    changeType(Attachment)\n    changeValue({} as any)\n}\n\nexport default Attachment\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blocks/checkbox/__snapshots__/checkbox.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/blocksEditor/blocks/checkbox should match Display snapshot 1`] = `\n<div>\n  <div\n    class=\"CheckboxView\"\n  >\n    <input\n      checked=\"\"\n      data-testid=\"checkbox-check\"\n      type=\"checkbox\"\n    />\n    <div>\n      <p>\n        test-value\n      </p>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/blocksEditor/blocks/checkbox should match Display snapshot not checked 1`] = `\n<div>\n  <div\n    class=\"CheckboxView\"\n  >\n    <input\n      data-testid=\"checkbox-check\"\n      type=\"checkbox\"\n    />\n    <div>\n      <p>\n        test-value\n      </p>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/blocksEditor/blocks/checkbox should match Input snapshot 1`] = `\n<div>\n  <div\n    class=\"Checkbox\"\n  >\n    <input\n      checked=\"\"\n      class=\"inputCheck\"\n      data-testid=\"checkbox-check\"\n      type=\"checkbox\"\n    />\n    <input\n      class=\"inputText\"\n      data-testid=\"checkbox-input\"\n      value=\"test-value\"\n    />\n  </div>\n</div>\n`;\n\nexports[`components/blocksEditor/blocks/checkbox should match Input snapshot not checked 1`] = `\n<div>\n  <div\n    class=\"Checkbox\"\n  >\n    <input\n      class=\"inputCheck\"\n      data-testid=\"checkbox-check\"\n      type=\"checkbox\"\n    />\n    <input\n      class=\"inputText\"\n      data-testid=\"checkbox-input\"\n      value=\"test-value\"\n    />\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blocks/checkbox/checkbox.scss",
    "content": ".Checkbox {\n    display: flex;\n\n    .inputCheck {\n        width: auto;\n    }\n\n    .inputText {\n        outline: 0;\n        flex-grow: 1;\n    }\n\n}\n\n.CheckboxView {\n    display: flex;\n}\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blocks/checkbox/checkbox.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {render, screen, fireEvent} from '@testing-library/react'\n\nimport CheckboxBlock from '.'\n\ndescribe('components/blocksEditor/blocks/checkbox', () => {\n    test('should match Display snapshot', async () => {\n        const Component = CheckboxBlock.Display\n        const {container} = render(\n            <Component\n                onChange={jest.fn()}\n                value={{value: 'test-value', checked: true}}\n                onCancel={jest.fn()}\n                onSave={jest.fn()}\n            />,\n        )\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match Display snapshot not checked', async () => {\n        const Component = CheckboxBlock.Display\n        const {container} = render(\n            <Component\n                onChange={jest.fn()}\n                value={{value: 'test-value', checked: false}}\n                onCancel={jest.fn()}\n                onSave={jest.fn()}\n            />,\n        )\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match Input snapshot', async () => {\n        const Component = CheckboxBlock.Input\n        const {container} = render(\n            <Component\n                onChange={jest.fn()}\n                value={{value: 'test-value', checked: true}}\n                onCancel={jest.fn()}\n                onSave={jest.fn()}\n            />,\n        )\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match Input snapshot not checked', async () => {\n        const Component = CheckboxBlock.Input\n        const {container} = render(\n            <Component\n                onChange={jest.fn()}\n                value={{value: 'test-value', checked: false}}\n                onCancel={jest.fn()}\n                onSave={jest.fn()}\n            />,\n        )\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should emit onSave event on Display checkbox clicked', async () => {\n        const onSave = jest.fn()\n        const Component = CheckboxBlock.Display\n        render(\n            <Component\n                onChange={jest.fn()}\n                value={{value: 'test-value', checked: true}}\n                onCancel={jest.fn()}\n                onSave={onSave}\n            />,\n        )\n        expect(onSave).not.toBeCalled()\n\n        const input = screen.getByTestId('checkbox-check')\n        fireEvent.click(input)\n        expect(onSave).toBeCalledWith({value: 'test-value', checked: false})\n    })\n\n    test('should emit onChange event on input change', async () => {\n        const onChange = jest.fn()\n        const Component = CheckboxBlock.Input\n        render(\n            <Component\n                onChange={onChange}\n                value={{value: 'test-value', checked: true}}\n                onCancel={jest.fn()}\n                onSave={jest.fn()}\n            />,\n        )\n\n        expect(onChange).not.toBeCalled()\n\n        const input = screen.getByTestId('checkbox-input')\n        fireEvent.change(input, {target: {value: 'test-value-'}})\n        expect(onChange).toBeCalledWith({value: 'test-value-', checked: true})\n    })\n\n    test('should emit onChange event on checkbox click', async () => {\n        const onChange = jest.fn()\n        const Component = CheckboxBlock.Input\n        render(\n            <Component\n                onChange={onChange}\n                value={{value: 'test-value', checked: true}}\n                onCancel={jest.fn()}\n                onSave={jest.fn()}\n            />,\n        )\n\n        expect(onChange).not.toBeCalled()\n\n        const input = screen.getByTestId('checkbox-check')\n        fireEvent.click(input)\n        expect(onChange).toBeCalledWith({value: 'test-value', checked: false})\n    })\n\n    test('should not emit onCancel event when value is not empty and hit backspace', async () => {\n        const onCancel = jest.fn()\n        const Component = CheckboxBlock.Input\n        render(\n            <Component\n                onChange={jest.fn()}\n                value={{value: 'test-value', checked: true}}\n                onCancel={onCancel}\n                onSave={jest.fn()}\n            />,\n        )\n\n        expect(onCancel).not.toBeCalled()\n        const input = screen.getByTestId('checkbox-input')\n        fireEvent.keyDown(input, {key: 'Backspace'})\n        expect(onCancel).not.toBeCalled()\n    })\n\n    test('should emit onCancel event when value is empty and hit backspace', async () => {\n        const onCancel = jest.fn()\n        const Component = CheckboxBlock.Input\n        render(\n            <Component\n                onChange={jest.fn()}\n                value={{value: '', checked: false}}\n                onCancel={onCancel}\n                onSave={jest.fn()}\n            />,\n        )\n\n        expect(onCancel).not.toBeCalled()\n\n        const input = screen.getByTestId('checkbox-input')\n        fireEvent.keyDown(input, {key: 'Backspace'})\n        expect(onCancel).toBeCalled()\n    })\n\n    test('should emit onSave event hit enter', async () => {\n        const onSave = jest.fn()\n        const Component = CheckboxBlock.Input\n        render(\n            <Component\n                onChange={jest.fn()}\n                value={{value: 'test-value', checked: true}}\n                onCancel={jest.fn()}\n                onSave={onSave}\n            />,\n        )\n\n        expect(onSave).not.toBeCalled()\n        const input = screen.getByTestId('checkbox-input')\n        fireEvent.keyDown(input, {key: 'Enter'})\n        expect(onSave).toBeCalledWith({value: 'test-value', checked: true})\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blocks/checkbox/index.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useRef, useEffect} from 'react'\nimport {marked} from 'marked'\n\nimport {BlockInputProps, ContentType} from '../types'\n\nimport './checkbox.scss'\n\ntype ValueType = {\n    value: string\n    checked: boolean\n}\n\nconst Checkbox: ContentType<ValueType> = {\n    name: 'checkbox',\n    displayName: 'Checkbox',\n    slashCommand: '/checkbox',\n    prefix: '[ ] ',\n    nextType: 'checkbox',\n    runSlashCommand: (): void => {},\n    editable: true,\n    Display: (props: BlockInputProps<ValueType>) => {\n        const renderer = new marked.Renderer()\n        const html = marked(props.value.value || '', {renderer, breaks: true})\n        return (\n            <div className='CheckboxView'>\n                <input\n                    data-testid='checkbox-check'\n                    type='checkbox'\n                    onChange={(e) => {\n                        const newValue = {checked: Boolean(e.target.checked), value: props.value.value || ''}\n                        props.onSave(newValue)\n                    }}\n                    checked={props.value.checked || false}\n                    onClick={(e) => e.stopPropagation()}\n                />\n                <div\n                    dangerouslySetInnerHTML={{__html: html.trim()}}\n                />\n            </div>\n        )\n    },\n    Input: (props: BlockInputProps<ValueType>) => {\n        const ref = useRef<HTMLInputElement|null>(null)\n        useEffect(() => {\n            ref.current?.focus()\n        }, [])\n        return (\n            <div className='Checkbox'>\n                <input\n                    type='checkbox'\n                    data-testid='checkbox-check'\n                    className='inputCheck'\n                    onChange={(e) => {\n                        let newValue = {checked: false, value: props.value.value || ''}\n                        if (e.target.checked) {\n                            newValue = {checked: true, value: props.value.value || ''}\n                        }\n                        props.onChange(newValue)\n                        ref.current?.focus()\n                    }}\n                    checked={props.value.checked || false}\n                />\n                <input\n                    ref={ref}\n                    data-testid='checkbox-input'\n                    className='inputText'\n                    onChange={(e) => {\n                        props.onChange({checked: Boolean(props.value.checked), value: e.currentTarget.value})\n                    }}\n                    onKeyDown={(e) => {\n                        if ((props.value.value || '') === '' && e.key === 'Backspace') {\n                            props.onCancel()\n                        }\n                        if (e.key === 'Enter') {\n                            props.onSave(props.value || {checked: false, value: ''})\n                        }\n                    }}\n                    value={props.value.value || ''}\n                />\n            </div>\n        )\n    },\n}\n\nCheckbox.runSlashCommand = (changeType: (contentType: ContentType<ValueType>) => void, changeValue: (value: ValueType) => void, ...args: string[]): void => {\n    changeType(Checkbox)\n    changeValue({checked: false, value: args.join(' ')})\n}\n\nexport default Checkbox\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blocks/divider/__snapshots__/divider.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/blocksEditor/blocks/divider should match Display snapshot 1`] = `\n<div>\n  <hr\n    class=\"Divider\"\n  />\n</div>\n`;\n\nexports[`components/blocksEditor/blocks/divider should match Input snapshot 1`] = `<div />`;\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blocks/divider/divider.scss",
    "content": ".Divider {\n    width: 100%;\n    margin: 5px 0;\n}\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blocks/divider/divider.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {render} from '@testing-library/react'\n\nimport DividerBlock from '.'\n\ndescribe('components/blocksEditor/blocks/divider', () => {\n    test('should match Display snapshot', async () => {\n        const Component = DividerBlock.Display\n        const {container} = render(\n            <Component\n                onChange={jest.fn()}\n                value='test-value'\n                onCancel={jest.fn()}\n                onSave={jest.fn()}\n            />,\n        )\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match Input snapshot', async () => {\n        const Component = DividerBlock.Input\n        const {container} = render(\n            <Component\n                onChange={jest.fn()}\n                value='test-value'\n                onCancel={jest.fn()}\n                onSave={jest.fn()}\n            />,\n        )\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should emit onSave event on mount', async () => {\n        const onSave = jest.fn()\n        const Component = DividerBlock.Input\n        render(\n            <Component\n                onChange={jest.fn()}\n                value='test-value'\n                onCancel={jest.fn()}\n                onSave={onSave}\n            />,\n        )\n        expect(onSave).toBeCalled()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blocks/divider/index.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useEffect} from 'react'\n\nimport {BlockInputProps, ContentType} from '../types'\n\nimport './divider.scss'\n\nconst Divider: ContentType = {\n    name: 'divider',\n    displayName: 'Divider',\n    slashCommand: '/divider',\n    prefix: '--- ',\n    runSlashCommand: (): void => {},\n    editable: false,\n    Display: () => <hr className='Divider'/>,\n    Input: (props: BlockInputProps) => {\n        useEffect(() => {\n            props.onSave(props.value)\n        }, [])\n        return null\n    },\n}\n\nDivider.runSlashCommand = (changeType: (contentType: ContentType) => void, changeValue: (value: string) => void, ...args: string[]): void => {\n    changeType(Divider)\n    changeValue(args.join(' '))\n}\n\nexport default Divider\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blocks/h1/__snapshots__/h1.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/blocksEditor/blocks/h1 should match Display snapshot 1`] = `\n<div>\n  <div>\n    <h1\n      id=\"test-value\"\n    >\n      test-value\n    </h1>\n  </div>\n</div>\n`;\n\nexports[`components/blocksEditor/blocks/h1 should match Input snapshot 1`] = `\n<div>\n  <input\n    class=\"H1\"\n    data-testid=\"h1\"\n    value=\"test-value\"\n  />\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blocks/h1/h1.scss",
    "content": ".H1 {\n    font-size: 32px;\n    font-weight: 700;\n}\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blocks/h1/h1.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {render, screen, fireEvent} from '@testing-library/react'\n\nimport H1Block from '.'\n\ndescribe('components/blocksEditor/blocks/h1', () => {\n    test('should match Display snapshot', async () => {\n        const Component = H1Block.Display\n        const {container} = render(\n            <Component\n                onChange={jest.fn()}\n                value='test-value'\n                onCancel={jest.fn()}\n                onSave={jest.fn()}\n            />,\n        )\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match Input snapshot', async () => {\n        const Component = H1Block.Input\n        const {container} = render(\n            <Component\n                onChange={jest.fn()}\n                value='test-value'\n                onCancel={jest.fn()}\n                onSave={jest.fn()}\n            />,\n        )\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should emit onChange event', async () => {\n        const onChange = jest.fn()\n        const Component = H1Block.Input\n        render(\n            <Component\n                onChange={onChange}\n                value='test-value'\n                onCancel={jest.fn()}\n                onSave={jest.fn()}\n            />,\n        )\n\n        expect(onChange).not.toBeCalled()\n\n        const input = screen.getByTestId('h1')\n        fireEvent.change(input, {target: {value: 'test-value-'}})\n        expect(onChange).toBeCalled()\n    })\n\n    test('should not emit onCancel event when value is not empty and hit backspace', async () => {\n        const onCancel = jest.fn()\n        const Component = H1Block.Input\n        render(\n            <Component\n                onChange={jest.fn()}\n                value='test-value'\n                onCancel={onCancel}\n                onSave={jest.fn()}\n            />,\n        )\n\n        expect(onCancel).not.toBeCalled()\n        const input = screen.getByTestId('h1')\n        fireEvent.keyDown(input, {key: 'Backspace'})\n        expect(onCancel).not.toBeCalled()\n    })\n\n    test('should emit onCancel event when value is empty and hit backspace', async () => {\n        const onCancel = jest.fn()\n        const Component = H1Block.Input\n        render(\n            <Component\n                onChange={jest.fn()}\n                value=''\n                onCancel={onCancel}\n                onSave={jest.fn()}\n            />,\n        )\n\n        expect(onCancel).not.toBeCalled()\n\n        const input = screen.getByTestId('h1')\n        fireEvent.keyDown(input, {key: 'Backspace'})\n        expect(onCancel).toBeCalled()\n    })\n\n    test('should emit onSave event hit enter', async () => {\n        const onSave = jest.fn()\n        const Component = H1Block.Input\n        render(\n            <Component\n                onChange={jest.fn()}\n                value='test-value'\n                onCancel={jest.fn()}\n                onSave={onSave}\n            />,\n        )\n\n        expect(onSave).not.toBeCalled()\n        const input = screen.getByTestId('h1')\n        fireEvent.keyDown(input, {key: 'Enter'})\n        expect(onSave).toBeCalled()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blocks/h1/index.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useRef, useEffect} from 'react'\nimport {marked} from 'marked'\n\nimport {BlockInputProps, ContentType} from '../types'\n\nimport './h1.scss'\n\nconst H1: ContentType = {\n    name: 'h1',\n    displayName: 'Title',\n    slashCommand: '/title',\n    prefix: '# ',\n    runSlashCommand: (): void => {},\n    editable: true,\n    Display: (props: BlockInputProps) => {\n        const renderer = new marked.Renderer()\n        const html = marked('# ' + props.value, {renderer, breaks: true})\n        return (\n            <div\n                dangerouslySetInnerHTML={{__html: html.trim()}}\n            />\n        )\n    },\n    Input: (props: BlockInputProps) => {\n        const ref = useRef<HTMLInputElement|null>(null)\n        useEffect(() => {\n            ref.current?.focus()\n        }, [])\n        return (\n            <input\n                ref={ref}\n                className='H1'\n                data-testid='h1'\n                onChange={(e) => props.onChange(e.currentTarget.value)}\n                onKeyDown={(e) => {\n                    if (props.value === '' && e.key === 'Backspace') {\n                        props.onCancel()\n                    }\n                    if (e.key === 'Enter') {\n                        props.onSave(props.value)\n                    }\n                }}\n                value={props.value}\n            />\n        )\n    },\n}\n\nH1.runSlashCommand = (changeType: (contentType: ContentType) => void, changeValue: (value: string) => void, ...args: string[]): void => {\n    changeType(H1)\n    changeValue(args.join(' '))\n}\n\nexport default H1\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blocks/h2/__snapshots__/h2.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/blocksEditor/blocks/h2 should match Display snapshot 1`] = `\n<div>\n  <div>\n    <h2\n      id=\"test-value\"\n    >\n      test-value\n    </h2>\n  </div>\n</div>\n`;\n\nexports[`components/blocksEditor/blocks/h2 should match Input snapshot 1`] = `\n<div>\n  <input\n    class=\"H2\"\n    data-testid=\"h2\"\n    value=\"test-value\"\n  />\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blocks/h2/h2.scss",
    "content": ".H2 {\n    font-size: 24px;\n    font-weight: 700;\n}\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blocks/h2/h2.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {render, screen, fireEvent} from '@testing-library/react'\n\nimport H2Block from '.'\n\ndescribe('components/blocksEditor/blocks/h2', () => {\n    test('should match Display snapshot', async () => {\n        const Component = H2Block.Display\n        const {container} = render(\n            <Component\n                onChange={jest.fn()}\n                value='test-value'\n                onCancel={jest.fn()}\n                onSave={jest.fn()}\n            />,\n        )\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match Input snapshot', async () => {\n        const Component = H2Block.Input\n        const {container} = render(\n            <Component\n                onChange={jest.fn()}\n                value='test-value'\n                onCancel={jest.fn()}\n                onSave={jest.fn()}\n            />,\n        )\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should emit onChange event', async () => {\n        const onChange = jest.fn()\n        const Component = H2Block.Input\n        render(\n            <Component\n                onChange={onChange}\n                value='test-value'\n                onCancel={jest.fn()}\n                onSave={jest.fn()}\n            />,\n        )\n\n        expect(onChange).not.toBeCalled()\n\n        const input = screen.getByTestId('h2')\n        fireEvent.change(input, {target: {value: 'test-value-'}})\n        expect(onChange).toBeCalled()\n    })\n\n    test('should not emit onCancel event when value is not empty and hit backspace', async () => {\n        const onCancel = jest.fn()\n        const Component = H2Block.Input\n        render(\n            <Component\n                onChange={jest.fn()}\n                value='test-value'\n                onCancel={onCancel}\n                onSave={jest.fn()}\n            />,\n        )\n\n        expect(onCancel).not.toBeCalled()\n        const input = screen.getByTestId('h2')\n        fireEvent.keyDown(input, {key: 'Backspace'})\n        expect(onCancel).not.toBeCalled()\n    })\n\n    test('should emit onCancel event when value is empty and hit backspace', async () => {\n        const onCancel = jest.fn()\n        const Component = H2Block.Input\n        render(\n            <Component\n                onChange={jest.fn()}\n                value=''\n                onCancel={onCancel}\n                onSave={jest.fn()}\n            />,\n        )\n\n        expect(onCancel).not.toBeCalled()\n\n        const input = screen.getByTestId('h2')\n        fireEvent.keyDown(input, {key: 'Backspace'})\n        expect(onCancel).toBeCalled()\n    })\n\n    test('should emit onSave event hit enter', async () => {\n        const onSave = jest.fn()\n        const Component = H2Block.Input\n        render(\n            <Component\n                onChange={jest.fn()}\n                value='test-value'\n                onCancel={jest.fn()}\n                onSave={onSave}\n            />,\n        )\n\n        expect(onSave).not.toBeCalled()\n        const input = screen.getByTestId('h2')\n        fireEvent.keyDown(input, {key: 'Enter'})\n        expect(onSave).toBeCalled()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blocks/h2/index.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useRef, useEffect} from 'react'\nimport {marked} from 'marked'\n\nimport {BlockInputProps, ContentType} from '../types'\n\nimport './h2.scss'\n\nconst H2: ContentType = {\n    name: 'h2',\n    displayName: 'Sub title',\n    slashCommand: '/subtitle',\n    prefix: '## ',\n    runSlashCommand: (): void => {},\n    editable: true,\n    Display: (props: BlockInputProps) => {\n        const renderer = new marked.Renderer()\n        const html = marked('## ' + props.value, {renderer, breaks: true})\n        return (\n            <div\n                dangerouslySetInnerHTML={{__html: html.trim()}}\n            />\n        )\n    },\n    Input: (props: BlockInputProps) => {\n        const ref = useRef<HTMLInputElement|null>(null)\n        useEffect(() => {\n            ref.current?.focus()\n        }, [])\n        return (\n            <input\n                ref={ref}\n                className='H2'\n                data-testid='h2'\n                onChange={(e) => props.onChange(e.currentTarget.value)}\n                onKeyDown={(e) => {\n                    if (props.value === '' && e.key === 'Backspace') {\n                        props.onCancel()\n                    }\n                    if (e.key === 'Enter') {\n                        props.onSave(props.value)\n                    }\n                }}\n                value={props.value}\n            />\n        )\n    },\n}\n\nH2.runSlashCommand = (changeType: (contentType: ContentType) => void, changeValue: (value: string) => void, ...args: string[]): void => {\n    changeType(H2)\n    changeValue(args.join(' '))\n}\n\nexport default H2\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blocks/h3/__snapshots__/h3.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/blocksEditor/blocks/h3 should match Display snapshot 1`] = `\n<div>\n  <div>\n    <h3\n      id=\"test-value\"\n    >\n      test-value\n    </h3>\n  </div>\n</div>\n`;\n\nexports[`components/blocksEditor/blocks/h3 should match Input snapshot 1`] = `\n<div>\n  <input\n    class=\"H3\"\n    data-testid=\"h3\"\n    value=\"test-value\"\n  />\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blocks/h3/h3.scss",
    "content": ".H3 {\n    font-size: 18px;\n    font-weight: 700;\n}\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blocks/h3/h3.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {render, screen, fireEvent} from '@testing-library/react'\n\nimport H3Block from '.'\n\ndescribe('components/blocksEditor/blocks/h3', () => {\n    test('should match Display snapshot', async () => {\n        const Component = H3Block.Display\n        const {container} = render(\n            <Component\n                onChange={jest.fn()}\n                value='test-value'\n                onCancel={jest.fn()}\n                onSave={jest.fn()}\n            />,\n        )\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match Input snapshot', async () => {\n        const Component = H3Block.Input\n        const {container} = render(\n            <Component\n                onChange={jest.fn()}\n                value='test-value'\n                onCancel={jest.fn()}\n                onSave={jest.fn()}\n            />,\n        )\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should emit onChange event', async () => {\n        const onChange = jest.fn()\n        const Component = H3Block.Input\n        render(\n            <Component\n                onChange={onChange}\n                value='test-value'\n                onCancel={jest.fn()}\n                onSave={jest.fn()}\n            />,\n        )\n\n        expect(onChange).not.toBeCalled()\n\n        const input = screen.getByTestId('h3')\n        fireEvent.change(input, {target: {value: 'test-value-'}})\n        expect(onChange).toBeCalled()\n    })\n\n    test('should not emit onCancel event when value is not empty and hit backspace', async () => {\n        const onCancel = jest.fn()\n        const Component = H3Block.Input\n        render(\n            <Component\n                onChange={jest.fn()}\n                value='test-value'\n                onCancel={onCancel}\n                onSave={jest.fn()}\n            />,\n        )\n\n        expect(onCancel).not.toBeCalled()\n        const input = screen.getByTestId('h3')\n        fireEvent.keyDown(input, {key: 'Backspace'})\n        expect(onCancel).not.toBeCalled()\n    })\n\n    test('should emit onCancel event when value is empty and hit backspace', async () => {\n        const onCancel = jest.fn()\n        const Component = H3Block.Input\n        render(\n            <Component\n                onChange={jest.fn()}\n                value=''\n                onCancel={onCancel}\n                onSave={jest.fn()}\n            />,\n        )\n\n        expect(onCancel).not.toBeCalled()\n\n        const input = screen.getByTestId('h3')\n        fireEvent.keyDown(input, {key: 'Backspace'})\n        expect(onCancel).toBeCalled()\n    })\n\n    test('should emit onSave event hit enter', async () => {\n        const onSave = jest.fn()\n        const Component = H3Block.Input\n        render(\n            <Component\n                onChange={jest.fn()}\n                value='test-value'\n                onCancel={jest.fn()}\n                onSave={onSave}\n            />,\n        )\n\n        expect(onSave).not.toBeCalled()\n        const input = screen.getByTestId('h3')\n        fireEvent.keyDown(input, {key: 'Enter'})\n        expect(onSave).toBeCalled()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blocks/h3/index.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useRef, useEffect} from 'react'\nimport {marked} from 'marked'\n\nimport {BlockInputProps, ContentType} from '../types'\n\nimport './h3.scss'\n\nconst H3: ContentType = {\n    name: 'h3',\n    displayName: 'Sub Sub title',\n    slashCommand: '/subsubtitle',\n    prefix: '### ',\n    runSlashCommand: (): void => {},\n    editable: true,\n    Display: (props: BlockInputProps) => {\n        const renderer = new marked.Renderer()\n        const html = marked('### ' + props.value, {renderer, breaks: true})\n        return (\n            <div\n                dangerouslySetInnerHTML={{__html: html.trim()}}\n            />\n        )\n    },\n    Input: (props: BlockInputProps) => {\n        const ref = useRef<HTMLInputElement|null>(null)\n        useEffect(() => {\n            ref.current?.focus()\n        }, [])\n        return (\n            <input\n                ref={ref}\n                className='H3'\n                data-testid='h3'\n                onChange={(e) => props.onChange(e.currentTarget.value)}\n                onKeyDown={(e) => {\n                    if (props.value === '' && e.key === 'Backspace') {\n                        props.onCancel()\n                    }\n                    if (e.key === 'Enter') {\n                        props.onSave(props.value)\n                    }\n                }}\n                value={props.value}\n            />\n        )\n    },\n}\n\nH3.runSlashCommand = (changeType: (contentType: ContentType) => void, changeValue: (value: string) => void, ...args: string[]): void => {\n    changeType(H3)\n    changeValue(args.join(' '))\n}\n\nexport default H3\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blocks/image/__snapshots__/image.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/blocksEditor/blocks/image should match Display snapshot 1`] = `\n<div>\n  <img\n    class=\"ImageView\"\n    data-testid=\"image\"\n    src=\"test.jpg\"\n  />\n</div>\n`;\n\nexports[`components/blocksEditor/blocks/image should match Display snapshot with empty value 1`] = `<div />`;\n\nexports[`components/blocksEditor/blocks/image should match Input snapshot 1`] = `\n<div>\n  <div>\n    <img\n      class=\"ImageView\"\n      src=\"test\"\n    />\n    <input\n      accept=\"image/*\"\n      class=\"Image\"\n      data-testid=\"image-input\"\n      type=\"file\"\n    />\n  </div>\n</div>\n`;\n\nexports[`components/blocksEditor/blocks/image should match Input snapshot with empty input 1`] = `\n<div>\n  <div>\n    \n    <input\n      accept=\"image/*\"\n      class=\"Image\"\n      data-testid=\"image-input\"\n      type=\"file\"\n    />\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blocks/image/image.scss",
    "content": ".Image {\n    display: none;\n}\n\n.ImageView {\n    max-width: 400px;\n}\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blocks/image/image.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {render, screen, fireEvent} from '@testing-library/react'\nimport {mocked} from 'jest-mock'\n\nimport octoClient from '../../../../octoClient'\n\nimport ImageBlock from '.'\n\njest.mock('../../../../octoClient')\n\ndescribe('components/blocksEditor/blocks/image', () => {\n    test('should match Display snapshot', async () => {\n        const mockedOcto = mocked(octoClient, true)\n        mockedOcto.getFileAsDataUrl.mockResolvedValue({url: 'test.jpg'})\n        const Component = ImageBlock.Display\n        const {container} = render(\n            <Component\n                onChange={jest.fn()}\n                value={{file: 'test'}}\n                onCancel={jest.fn()}\n                onSave={jest.fn()}\n            />,\n        )\n        await screen.findByTestId('image')\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match Display snapshot with empty value', async () => {\n        const Component = ImageBlock.Display\n        const {container} = render(\n            <Component\n                onChange={jest.fn()}\n                value={{file: ''}}\n                onCancel={jest.fn()}\n                onSave={jest.fn()}\n                currentBoardId=''\n            />,\n        )\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match Input snapshot', async () => {\n        const Component = ImageBlock.Input\n        const {container} = render(\n            <Component\n                onChange={jest.fn()}\n                value={{file: 'test'}}\n                onCancel={jest.fn()}\n                onSave={jest.fn()}\n            />,\n        )\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match Input snapshot with empty input', async () => {\n        const Component = ImageBlock.Input\n        const {container} = render(\n            <Component\n                onChange={jest.fn()}\n                value={{file: ''}}\n                onCancel={jest.fn()}\n                onSave={jest.fn()}\n            />,\n        )\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should emit onSave on change', async () => {\n        const onSave = jest.fn()\n        const Component = ImageBlock.Input\n        render(\n            <Component\n                onChange={jest.fn()}\n                value={{file: 'test'}}\n                onCancel={jest.fn()}\n                onSave={onSave}\n            />,\n        )\n\n        expect(onSave).not.toBeCalled()\n        const input = screen.getByTestId('image-input')\n        fireEvent.change(input, {target: {files: ['test-file']}})\n        expect(onSave).toBeCalledWith({file: 'test-file'})\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blocks/image/index.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useRef, useEffect, useState} from 'react'\n\nimport {BlockInputProps, ContentType} from '../types'\nimport octoClient from '../../../../octoClient'\n\nimport './image.scss'\n\ntype FileInfo = {\n    file: string|File\n    width?: number\n    align?: 'left'|'center'|'right'\n}\n\nconst Image: ContentType<FileInfo> = {\n    name: 'image',\n    displayName: 'Image',\n    slashCommand: '/image',\n    prefix: '',\n    runSlashCommand: (): void => {},\n    editable: false,\n    Display: (props: BlockInputProps<FileInfo>) => {\n        const [imageDataUrl, setImageDataUrl] = useState<string|null>(null)\n\n        useEffect(() => {\n            if (!imageDataUrl) {\n                const loadImage = async () => {\n                    if (props.value && props.value.file && typeof props.value.file === 'string') {\n                        const fileURL = await octoClient.getFileAsDataUrl(props.currentBoardId || '', props.value.file)\n                        setImageDataUrl(fileURL.url || '')\n                    }\n                }\n                loadImage()\n            }\n        }, [props.value, props.value.file, props.currentBoardId])\n\n        if (imageDataUrl) {\n            return (\n                <img\n                    data-testid='image'\n                    className='ImageView'\n                    src={imageDataUrl}\n                />\n            )\n        }\n        return null\n    },\n    Input: (props: BlockInputProps<FileInfo>) => {\n        const ref = useRef<HTMLInputElement|null>(null)\n        useEffect(() => {\n            ref.current?.click()\n        }, [])\n\n        return (\n            <div>\n                {props.value.file && (typeof props.value.file === 'string') && (\n                    <img\n                        className='ImageView'\n                        src={props.value.file}\n                        onClick={() => ref.current?.click()}\n                    />\n                )}\n                <input\n                    ref={ref}\n                    className='Image'\n                    data-testid='image-input'\n                    type='file'\n                    accept='image/*'\n                    onChange={(e) => {\n                        const file = (e.currentTarget?.files || [])[0]\n                        props.onSave({file})\n                    }}\n                />\n            </div>\n        )\n    },\n}\n\nImage.runSlashCommand = (changeType: (contentType: ContentType<FileInfo>) => void, changeValue: (value: FileInfo) => void): void => {\n    changeType(Image)\n    changeValue({file: ''})\n}\n\nexport default Image\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blocks/index.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {ContentType} from './types'\nimport H1 from './h1'\nimport H2 from './h2'\nimport H3 from './h3'\nimport Image from './image'\nimport Text from './text'\nimport Divider from './divider'\n\n// import Markdown from './markdown'\nimport ListItem from './list-item'\nimport Attachment from './attachment'\nimport Quote from './quote'\nimport Video from './video'\nimport Checkbox from './checkbox'\n\nconst blocks: {[key: string]: ContentType} = {}\nconst blocksByPrefix: {[key: string]: ContentType} = {}\nconst blocksBySlashCommand: {[key: string]: ContentType} = {}\n\nexport function register(contentType: ContentType<any>) {\n    blocks[contentType.name] = contentType\n    if (contentType.prefix !== '') {\n        blocksByPrefix[contentType.prefix] = contentType\n    }\n    blocksBySlashCommand[contentType.slashCommand] = contentType\n}\n\nexport function list() {\n    return Object.values(blocks)\n}\n\nexport function get(name: string): ContentType {\n    return blocks[name]\n}\n\nexport function getByPrefix(prefix: string): ContentType {\n    return blocksByPrefix[prefix]\n}\n\nexport function isSubPrefix(text: string): boolean {\n    for (const ct of list()) {\n        if (ct.prefix !== '' && ct.prefix.startsWith(text)) {\n            return true\n        }\n    }\n    return false\n}\n\nexport function getBySlashCommand(slashCommand: string): ContentType {\n    return blocksBySlashCommand[slashCommand]\n}\n\nexport function getBySlashCommandPrefix(slashCommandPrefix: string): ContentType|null {\n    for (const ct of list()) {\n        if (ct.slashCommand.startsWith(slashCommandPrefix)) {\n            return ct\n        }\n    }\n    return null\n}\n\nregister(H1)\nregister(H2)\nregister(H3)\nregister(Image)\nregister(Text)\nregister(Divider)\n\n// register(Markdown)\nregister(ListItem)\nregister(Attachment)\nregister(Quote)\nregister(Video)\nregister(Checkbox)\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blocks/list-item/__snapshots__/list-item.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/blocksEditor/blocks/list-item should match Display snapshot 1`] = `\n<div>\n  <ul>\n    <li>\n      test-value\n    </li>\n  </ul>\n</div>\n`;\n\nexports[`components/blocksEditor/blocks/list-item should match Input snapshot 1`] = `\n<div>\n  <ul>\n    <li>\n      <input\n        class=\"ListItem\"\n        data-testid=\"list-item\"\n        value=\"test-value\"\n      />\n    </li>\n  </ul>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blocks/list-item/index.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useRef, useEffect} from 'react'\n\nimport {BlockInputProps, ContentType} from '../types'\n\nimport './list-item.scss'\n\nconst ListItem: ContentType = {\n    name: 'list-item',\n    displayName: 'List item',\n    slashCommand: '/list-item',\n    prefix: '* ',\n    nextType: 'list-item',\n    runSlashCommand: (): void => {},\n    editable: true,\n    Display: (props: BlockInputProps) => <ul><li>{props.value}</li></ul>,\n    Input: (props: BlockInputProps) => {\n        const ref = useRef<HTMLInputElement|null>(null)\n        useEffect(() => {\n            ref.current?.focus()\n        }, [])\n        return (\n            <ul>\n                <li>\n                    <input\n                        ref={ref}\n                        className='ListItem'\n                        data-testid='list-item'\n                        onChange={(e) => props.onChange(e.currentTarget.value)}\n                        onKeyDown={(e) => {\n                            if (props.value === '' && e.key === 'Backspace') {\n                                props.onCancel()\n                            }\n                            if (e.key === 'Enter') {\n                                props.onSave(props.value)\n                            }\n                        }}\n                        value={props.value}\n                    />\n                </li>\n            </ul>\n        )\n    },\n}\n\nListItem.runSlashCommand = (changeType: (contentType: ContentType) => void, changeValue: (value: string) => void, ...args: string[]): void => {\n    changeType(ListItem)\n    changeValue(args.join(' '))\n}\n\nexport default ListItem\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blocks/list-item/list-item.scss",
    "content": ""
  },
  {
    "path": "webapp/src/components/blocksEditor/blocks/list-item/list-item.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {render, screen, fireEvent} from '@testing-library/react'\n\nimport ListItemBlock from '.'\n\ndescribe('components/blocksEditor/blocks/list-item', () => {\n    test('should match Display snapshot', async () => {\n        const Component = ListItemBlock.Display\n        const {container} = render(\n            <Component\n                onChange={jest.fn()}\n                value='test-value'\n                onCancel={jest.fn()}\n                onSave={jest.fn()}\n            />,\n        )\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match Input snapshot', async () => {\n        const Component = ListItemBlock.Input\n        const {container} = render(\n            <Component\n                onChange={jest.fn()}\n                value='test-value'\n                onCancel={jest.fn()}\n                onSave={jest.fn()}\n            />,\n        )\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should emit onChange event', async () => {\n        const onChange = jest.fn()\n        const Component = ListItemBlock.Input\n        render(\n            <Component\n                onChange={onChange}\n                value='test-value'\n                onCancel={jest.fn()}\n                onSave={jest.fn()}\n            />,\n        )\n\n        expect(onChange).not.toBeCalled()\n\n        const input = screen.getByTestId('list-item')\n        fireEvent.change(input, {target: {value: 'test-value-'}})\n        expect(onChange).toBeCalled()\n    })\n\n    test('should not emit onCancel event when value is not empty and hit backspace', async () => {\n        const onCancel = jest.fn()\n        const Component = ListItemBlock.Input\n        render(\n            <Component\n                onChange={jest.fn()}\n                value='test-value'\n                onCancel={onCancel}\n                onSave={jest.fn()}\n            />,\n        )\n\n        expect(onCancel).not.toBeCalled()\n        const input = screen.getByTestId('list-item')\n        fireEvent.keyDown(input, {key: 'Backspace'})\n        expect(onCancel).not.toBeCalled()\n    })\n\n    test('should emit onCancel event when value is empty and hit backspace', async () => {\n        const onCancel = jest.fn()\n        const Component = ListItemBlock.Input\n        render(\n            <Component\n                onChange={jest.fn()}\n                value=''\n                onCancel={onCancel}\n                onSave={jest.fn()}\n            />,\n        )\n\n        expect(onCancel).not.toBeCalled()\n\n        const input = screen.getByTestId('list-item')\n        fireEvent.keyDown(input, {key: 'Backspace'})\n        expect(onCancel).toBeCalled()\n    })\n\n    test('should emit onSave event hit enter', async () => {\n        const onSave = jest.fn()\n        const Component = ListItemBlock.Input\n        render(\n            <Component\n                onChange={jest.fn()}\n                value='test-value'\n                onCancel={jest.fn()}\n                onSave={onSave}\n            />,\n        )\n\n        expect(onSave).not.toBeCalled()\n        const input = screen.getByTestId('list-item')\n        fireEvent.keyDown(input, {key: 'Enter'})\n        expect(onSave).toBeCalled()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blocks/quote/__snapshots__/quote.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/blocksEditor/blocks/quote should match Display snapshot 1`] = `\n<div>\n  <div\n    class=\"Quote\"\n    data-testid=\"quote\"\n  >\n    <blockquote>\n      \n\n      <p>\n        test-value\n      </p>\n      \n\n    </blockquote>\n  </div>\n</div>\n`;\n\nexports[`components/blocksEditor/blocks/quote should match Input snapshot 1`] = `\n<div>\n  <blockquote\n    class=\"Quote\"\n  >\n    <input\n      data-testid=\"quote\"\n      value=\"test-value\"\n    />\n  </blockquote>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blocks/quote/index.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useRef, useEffect} from 'react'\nimport {marked} from 'marked'\n\nimport {BlockInputProps, ContentType} from '../types'\n\nimport './quote.scss'\n\nconst Quote: ContentType = {\n    name: 'quote',\n    displayName: 'Quote',\n    slashCommand: '/quote',\n    prefix: '> ',\n    Display: (props: BlockInputProps) => {\n        const renderer = new marked.Renderer()\n        const html = marked('> ' + props.value, {renderer, breaks: true})\n        return (\n            <div\n                className='Quote'\n                data-testid='quote'\n                dangerouslySetInnerHTML={{__html: html.trim()}}\n            />\n        )\n    },\n    runSlashCommand: (): void => {},\n    editable: true,\n    Input: (props: BlockInputProps) => {\n        const ref = useRef<HTMLInputElement|null>(null)\n        useEffect(() => {\n            ref.current?.focus()\n        }, [])\n        return (\n            <blockquote\n                className='Quote'\n            >\n                <input\n                    ref={ref}\n                    data-testid='quote'\n                    onChange={(e) => props.onChange(e.currentTarget.value)}\n                    onKeyDown={(e) => {\n                        if (props.value === '' && e.key === 'Backspace') {\n                            props.onCancel()\n                        }\n                        if (e.key === 'Enter') {\n                            props.onSave(props.value)\n                        }\n                    }}\n                    onBlur={() => props.onSave(props.value)}\n                    value={props.value}\n                />\n            </blockquote>\n        )\n    },\n}\n\nQuote.runSlashCommand = (changeType: (contentType: ContentType) => void, changeValue: (value: string) => void, ...args: string[]): void => {\n    changeType(Quote)\n    changeValue(args.join(' '))\n}\n\nexport default Quote\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blocks/quote/quote.scss",
    "content": ".Editor .Quote input {\n    width: calc(100% - 80px);\n}\n\n.Quote {\n    width: 100%;\n}\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blocks/quote/quote.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {render, screen, fireEvent} from '@testing-library/react'\n\nimport QuoteBlock from '.'\n\ndescribe('components/blocksEditor/blocks/quote', () => {\n    test('should match Display snapshot', async () => {\n        const Component = QuoteBlock.Display\n        const {container} = render(\n            <Component\n                onChange={jest.fn()}\n                value='test-value'\n                onCancel={jest.fn()}\n                onSave={jest.fn()}\n            />,\n        )\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match Input snapshot', async () => {\n        const Component = QuoteBlock.Input\n        const {container} = render(\n            <Component\n                onChange={jest.fn()}\n                value='test-value'\n                onCancel={jest.fn()}\n                onSave={jest.fn()}\n            />,\n        )\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should emit onChange event', async () => {\n        const onChange = jest.fn()\n        const Component = QuoteBlock.Input\n        render(\n            <Component\n                onChange={onChange}\n                value='test-value'\n                onCancel={jest.fn()}\n                onSave={jest.fn()}\n            />,\n        )\n\n        expect(onChange).not.toBeCalled()\n\n        const input = screen.getByTestId('quote')\n        fireEvent.change(input, {target: {value: 'test-value-'}})\n        expect(onChange).toBeCalled()\n    })\n\n    test('should not emit onCancel event when value is not empty and hit backspace', async () => {\n        const onCancel = jest.fn()\n        const Component = QuoteBlock.Input\n        render(\n            <Component\n                onChange={jest.fn()}\n                value='test-value'\n                onCancel={onCancel}\n                onSave={jest.fn()}\n            />,\n        )\n\n        expect(onCancel).not.toBeCalled()\n        const input = screen.getByTestId('quote')\n        fireEvent.keyDown(input, {key: 'Backspace'})\n        expect(onCancel).not.toBeCalled()\n    })\n\n    test('should emit onCancel event when value is empty and hit backspace', async () => {\n        const onCancel = jest.fn()\n        const Component = QuoteBlock.Input\n        render(\n            <Component\n                onChange={jest.fn()}\n                value=''\n                onCancel={onCancel}\n                onSave={jest.fn()}\n            />,\n        )\n\n        expect(onCancel).not.toBeCalled()\n\n        const input = screen.getByTestId('quote')\n        fireEvent.keyDown(input, {key: 'Backspace'})\n        expect(onCancel).toBeCalled()\n    })\n\n    test('should emit onSave event hit enter', async () => {\n        const onSave = jest.fn()\n        const Component = QuoteBlock.Input\n        render(\n            <Component\n                onChange={jest.fn()}\n                value='test-value'\n                onCancel={jest.fn()}\n                onSave={onSave}\n            />,\n        )\n\n        expect(onSave).not.toBeCalled()\n        const input = screen.getByTestId('quote')\n        fireEvent.keyDown(input, {key: 'Enter'})\n        expect(onSave).toBeCalled()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blocks/text/__snapshots__/text.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/blocksEditor/blocks/text should match Display snapshot 1`] = `\n<div>\n  <div\n    class=\"octo-editor-preview\"\n  >\n    <p>\n      test-value\n    </p>\n  </div>\n</div>\n`;\n\nexports[`components/blocksEditor/blocks/text should match Input snapshot 1`] = `\n<div>\n  <div\n    class=\"TextContent\"\n    data-testid=\"text\"\n  >\n    <div\n      class=\"MarkdownEditor octo-editor  active\"\n    >\n      <div\n        class=\"MarkdownEditorInput\"\n      >\n        <div\n          class=\"DraftEditor-root\"\n        >\n          <div\n            class=\"DraftEditor-editorContainer\"\n          >\n            <div\n              aria-autocomplete=\"list\"\n              aria-expanded=\"false\"\n              class=\"notranslate public-DraftEditor-content\"\n              contenteditable=\"true\"\n              role=\"combobox\"\n              spellcheck=\"false\"\n              style=\"outline: none; user-select: text; white-space: pre-wrap; word-wrap: break-word;\"\n            >\n              <div\n                data-contents=\"true\"\n              >\n                <div\n                  class=\"\"\n                  data-block=\"true\"\n                  data-editor=\"123\"\n                  data-offset-key=\"123-0-0\"\n                >\n                  <div\n                    class=\"public-DraftStyleDefault-block public-DraftStyleDefault-ltr\"\n                    data-offset-key=\"123-0-0\"\n                  >\n                    <span\n                      data-offset-key=\"123-0-0\"\n                    >\n                      <span\n                        data-text=\"true\"\n                      >\n                        test-value\n                      </span>\n                    </span>\n                  </div>\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blocks/text/index.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\n\nimport {MarkdownEditor} from '../../../markdownEditor'\nimport {Utils} from '../../../../utils'\n\nimport {BlockInputProps, ContentType} from '../types'\n\nimport './text.scss'\n\nconst TextContent: ContentType = {\n    name: 'text',\n    displayName: 'Text',\n    slashCommand: '/text',\n    prefix: '',\n    runSlashCommand: (): void => {},\n    editable: true,\n    Display: (props: BlockInputProps) => {\n        const html: string = Utils.htmlFromMarkdown(props.value || '')\n        return (\n            <div\n                dangerouslySetInnerHTML={{__html: html}}\n                className={props.value ? 'octo-editor-preview' : 'octo-editor-preview octo-placeholder'}\n            />\n        )\n    },\n    Input: (props: BlockInputProps) => {\n        return (\n            <div\n                className='TextContent'\n                data-testid='text'\n            >\n                <MarkdownEditor\n                    autofocus={true}\n                    onBlur={(val: string) => {\n                        props.onSave(val)\n                    }}\n                    text={props.value}\n                    saveOnEnter={true}\n                    onEditorCancel={() => {\n                        props.onCancel()\n                    }}\n                />\n            </div>\n        )\n    },\n}\n\nTextContent.runSlashCommand = (changeType: (contentType: ContentType) => void, changeValue: (value: string) => void, ...args: string[]): void => {\n    changeType(TextContent)\n    changeValue(args.join(' '))\n}\n\nexport default TextContent\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blocks/text/text.scss",
    "content": ".TextContent {\n    width: 100%;\n    border: 2px solid #2684ff;\n    border-radius: 4px;\n    padding: 10px;\n}\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blocks/text/text.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {Provider as ReduxProvider} from 'react-redux'\nimport {render, act} from '@testing-library/react'\n\nimport {mockDOM, wrapDNDIntl, mockStateStore} from '../../../../testUtils'\nimport {TestBlockFactory} from '../../../../test/testBlockFactory'\n\nimport TextBlock from '.'\n\njest.mock('draft-js/lib/generateRandomKey', () => () => '123')\n\ndescribe('components/blocksEditor/blocks/text', () => {\n    beforeEach(mockDOM)\n\n    const board1 = TestBlockFactory.createBoard()\n    board1.id = 'board-id-1'\n\n    const state = {\n        users: {\n            boardUsers: {\n                1: {username: 'abc'},\n                2: {username: 'd'},\n                3: {username: 'e'},\n                4: {username: 'f'},\n                5: {username: 'g'},\n            },\n        },\n        boards: {\n            current: 'board-id-1',\n            boards: {\n                [board1.id]: board1,\n            },\n        },\n        clientConfig: {\n            value: {},\n        },\n    }\n    const store = mockStateStore([], state)\n\n    test('should match Display snapshot', async () => {\n        const Component = TextBlock.Display\n        const {container} = render(wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <Component\n                    onChange={jest.fn()}\n                    value='test-value'\n                    onCancel={jest.fn()}\n                    onSave={jest.fn()}\n                />\n            </ReduxProvider>,\n        ))\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match Input snapshot', async () => {\n        let container\n        await act(async () => {\n            const Component = TextBlock.Input\n            const result = render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <Component\n                        onChange={jest.fn()}\n                        value='test-value'\n                        onCancel={jest.fn()}\n                        onSave={jest.fn()}\n                    />\n                </ReduxProvider>,\n            ))\n            container = result.container\n        })\n        expect(container).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blocks/text-dev/index.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useRef, useEffect} from 'react'\n\nimport {BlockInputProps, ContentType} from '../types'\nimport {Utils} from '../../../../utils'\n\nimport './text.scss'\n\nconst Text: ContentType = {\n    name: 'text',\n    displayName: 'Text',\n    slashCommand: '/text',\n    prefix: '',\n    runSlashCommand: (): void => {},\n    editable: true,\n    Display: (props: BlockInputProps) => {\n        const html: string = Utils.htmlFromMarkdown(props.value || '')\n        return (\n            <div\n                dangerouslySetInnerHTML={{__html: html}}\n                className={props.value ? 'octo-editor-preview' : 'octo-editor-preview octo-placeholder'}\n            />\n        )\n    },\n    Input: (props: BlockInputProps) => {\n        const ref = useRef<HTMLInputElement|null>(null)\n        useEffect(() => {\n            ref.current?.focus()\n        }, [])\n        return (\n            <input\n                ref={ref}\n                className='Text'\n                onChange={(e) => props.onChange(e.currentTarget.value)}\n                onKeyDown={(e) => {\n                    if (props.value === '' && e.key === 'Backspace') {\n                        props.onCancel()\n                    }\n                    if (e.key === 'Enter') {\n                        props.onSave(props.value)\n                    }\n                }}\n                value={props.value}\n            />\n        )\n    },\n}\n\nText.runSlashCommand = (changeType: (contentType: ContentType) => void, changeValue: (value: string) => void, ...args: string[]): void => {\n    changeType(Text)\n    changeValue(args.join(' '))\n}\n\nexport default Text\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blocks/text-dev/text.scss",
    "content": ""
  },
  {
    "path": "webapp/src/components/blocksEditor/blocks/types.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nexport type BlockInputProps<ValueType = string> = {\n    onChange: (value: ValueType) => void\n    value: ValueType\n    onCancel: () => void\n    onSave: (val: ValueType) => void\n    currentBoardId?: string\n}\n\nexport type ContentType<ValueType = string> = {\n    name: string\n    displayName: string\n    slashCommand: string\n    prefix: string\n    editable: boolean\n    Input: React.FunctionComponent<BlockInputProps<ValueType>>\n    Display: React.FunctionComponent<BlockInputProps<ValueType>>\n    runSlashCommand: (changeType: (contentType: ContentType<ValueType>) => void, changeValue: (value: ValueType) => void, ...args: string[]) => void\n    nextType?: string\n}\n\nexport type BlockData<ValueType = string> = {\n    id?: string\n    value: ValueType\n    contentType: string\n}\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blocks/video/__snapshots__/video.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/blocksEditor/blocks/video should match Display snapshot 1`] = `\n<div>\n  <video\n    class=\"VideoView\"\n    controls=\"\"\n    data-testid=\"video\"\n    height=\"240\"\n    width=\"320\"\n  >\n    <source\n      src=\"test.jpg\"\n    />\n  </video>\n</div>\n`;\n\nexports[`components/blocksEditor/blocks/video should match Display snapshot with empty value 1`] = `<div />`;\n\nexports[`components/blocksEditor/blocks/video should match Input snapshot 1`] = `\n<div>\n  <input\n    accept=\"video/*\"\n    class=\"Video\"\n    data-testid=\"video-input\"\n    type=\"file\"\n  />\n</div>\n`;\n\nexports[`components/blocksEditor/blocks/video should match Input snapshot with empty input 1`] = `\n<div>\n  <input\n    accept=\"video/*\"\n    class=\"Video\"\n    data-testid=\"video-input\"\n    type=\"file\"\n  />\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blocks/video/index.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useRef, useEffect, useState} from 'react'\n\nimport {BlockInputProps, ContentType} from '../types'\nimport octoClient from '../../../../octoClient'\n\nimport './video.scss'\n\ntype FileInfo = {\n    file: string|File\n    filename: string\n    width?: number\n    align?: 'left'|'center'|'right'\n}\n\nconst Video: ContentType<FileInfo> = {\n    name: 'video',\n    displayName: 'Video',\n    slashCommand: '/video',\n    prefix: '',\n    runSlashCommand: (): void => {},\n    editable: false,\n    Display: (props: BlockInputProps<FileInfo>) => {\n        const [videoDataUrl, setVideoDataUrl] = useState<string|null>(null)\n\n        useEffect(() => {\n            if (!videoDataUrl) {\n                const loadVideo = async () => {\n                    if (props.value && props.value.file && typeof props.value.file === 'string') {\n                        const fileURL = await octoClient.getFileAsDataUrl(props.currentBoardId || '', props.value.file)\n                        setVideoDataUrl(fileURL.url || '')\n                    }\n                }\n                loadVideo()\n            }\n        }, [props.value, props.value.file, props.currentBoardId])\n\n        if (videoDataUrl) {\n            return (\n                <video\n                    width='320'\n                    height='240'\n                    controls={true}\n                    className='VideoView'\n                    data-testid='video'\n                >\n                    <source src={videoDataUrl}/>\n                </video>\n            )\n        }\n        return null\n    },\n    Input: (props: BlockInputProps<FileInfo>) => {\n        const ref = useRef<HTMLInputElement|null>(null)\n        useEffect(() => {\n            ref.current?.click()\n        }, [])\n\n        return (\n            <input\n                ref={ref}\n                className='Video'\n                data-testid='video-input'\n                type='file'\n                accept='video/*'\n                onChange={(e) => {\n                    const file = (e.currentTarget?.files || [])[0]\n                    props.onSave({file, filename: file.name})\n                }}\n            />\n        )\n    },\n}\n\nVideo.runSlashCommand = (changeType: (contentType: ContentType<FileInfo>) => void, changeValue: (value: FileInfo) => void): void => {\n    changeType(Video)\n    changeValue({} as any)\n}\n\nexport default Video\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blocks/video/video.scss",
    "content": ".Video {\n    display: none;\n}\n\n.VideoView {\n    max-width: 400px;\n}\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blocks/video/video.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {render, screen, fireEvent} from '@testing-library/react'\nimport {mocked} from 'jest-mock'\n\nimport octoClient from '../../../../octoClient'\n\nimport VideoBlock from '.'\n\njest.mock('../../../../octoClient')\n\ndescribe('components/blocksEditor/blocks/video', () => {\n    test('should match Display snapshot', async () => {\n        const mockedOcto = mocked(octoClient, true)\n        mockedOcto.getFileAsDataUrl.mockResolvedValue({url: 'test.jpg'})\n        const Component = VideoBlock.Display\n        const {container} = render(\n            <Component\n                onChange={jest.fn()}\n                value={{file: 'test', filename: 'test-filename'}}\n                onCancel={jest.fn()}\n                onSave={jest.fn()}\n            />,\n        )\n        await screen.findByTestId('video')\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match Display snapshot with empty value', async () => {\n        const Component = VideoBlock.Display\n        const {container} = render(\n            <Component\n                onChange={jest.fn()}\n                value={{file: '', filename: ''}}\n                onCancel={jest.fn()}\n                onSave={jest.fn()}\n                currentBoardId=''\n            />,\n        )\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match Input snapshot', async () => {\n        const Component = VideoBlock.Input\n        const {container} = render(\n            <Component\n                onChange={jest.fn()}\n                value={{file: 'test', filename: 'test-filename'}}\n                onCancel={jest.fn()}\n                onSave={jest.fn()}\n            />,\n        )\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match Input snapshot with empty input', async () => {\n        const Component = VideoBlock.Input\n        const {container} = render(\n            <Component\n                onChange={jest.fn()}\n                value={{file: '', filename: ''}}\n                onCancel={jest.fn()}\n                onSave={jest.fn()}\n            />,\n        )\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should emit onSave on change', async () => {\n        const onSave = jest.fn()\n        const Component = VideoBlock.Input\n        render(\n            <Component\n                onChange={jest.fn()}\n                value={{file: 'test', filename: 'test-filename'}}\n                onCancel={jest.fn()}\n                onSave={onSave}\n            />,\n        )\n\n        expect(onSave).not.toBeCalled()\n        const input = screen.getByTestId('video-input')\n        fireEvent.change(input, {target: {files: ['test-file']}})\n        expect(onSave).toBeCalledWith({file: 'test-file'})\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blocksEditor.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {Provider as ReduxProvider} from 'react-redux'\nimport {render, screen, fireEvent, act} from '@testing-library/react'\n\nimport {mockDOM, wrapDNDIntl, mockStateStore} from '../../testUtils'\nimport {TestBlockFactory} from '../../test/testBlockFactory'\n\nimport {BlockData} from './blocks/types'\nimport BlocksEditor from './blocksEditor'\n\njest.mock('draft-js/lib/generateRandomKey', () => () => '123')\n\ndescribe('components/blocksEditor/blocksEditor', () => {\n    beforeEach(mockDOM)\n\n    const blocks: Array<BlockData<any>> = [\n        {id: '1', value: 'Title', contentType: 'h1'},\n        {id: '2', value: 'Sub title', contentType: 'h2'},\n        {id: '3', value: 'Sub sub title', contentType: 'h3'},\n        {id: '4', value: 'Some **markdown** text', contentType: 'text'},\n        {id: '5', value: 'Some multiline\\n**markdown** text\\n### With Items\\n- Item 1\\n- Item2\\n- Item3', contentType: 'text'},\n        {id: '6', value: {checked: true, value: 'Checkbox'}, contentType: 'checkbox'},\n    ]\n\n    const board1 = TestBlockFactory.createBoard()\n    board1.id = 'board-id-1'\n\n    const state = {\n        users: {\n            boardUsers: {\n                1: {username: 'abc'},\n                2: {username: 'd'},\n                3: {username: 'e'},\n                4: {username: 'f'},\n                5: {username: 'g'},\n            },\n        },\n        boards: {\n            current: 'board-id-1',\n            boards: {\n                [board1.id]: board1,\n            },\n        },\n        clientConfig: {\n            value: {},\n        },\n    }\n    const store = mockStateStore([], state)\n\n    test('should match snapshot on empty', async () => {\n        let container\n        await act(async () => {\n            const result = render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <BlocksEditor\n                        boardId='test-board'\n                        onBlockCreated={jest.fn()}\n                        onBlockModified={jest.fn()}\n                        onBlockMoved={jest.fn()}\n                        blocks={[]}\n                    />\n                </ReduxProvider>,\n            ))\n            container = result.container\n        })\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot with blocks', async () => {\n        let container\n        await act(async () => {\n            const result = render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <BlocksEditor\n                        boardId='test-board'\n                        onBlockCreated={jest.fn()}\n                        onBlockModified={jest.fn()}\n                        onBlockMoved={jest.fn()}\n                        blocks={blocks}\n                    />\n                </ReduxProvider>,\n            ))\n            container = result.container\n        })\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should call onBlockCreate after introduce text and hit enter', async () => {\n        const onBlockCreated = jest.fn()\n        await act(async () => {\n            render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <BlocksEditor\n                        boardId='test-board'\n                        onBlockCreated={onBlockCreated}\n                        onBlockModified={jest.fn()}\n                        onBlockMoved={jest.fn()}\n                        blocks={[]}\n                    />\n                </ReduxProvider>,\n            ))\n        })\n\n        let input = screen.getByDisplayValue('')\n        expect(onBlockCreated).not.toBeCalled()\n        fireEvent.change(input, {target: {value: '/title'}})\n        fireEvent.keyDown(input, {key: 'Enter'})\n\n        input = screen.getByDisplayValue('')\n        fireEvent.change(input, {target: {value: 'test'}})\n        fireEvent.keyDown(input, {key: 'Enter'})\n\n        expect(onBlockCreated).toBeCalledWith(expect.objectContaining({value: 'test'}))\n    })\n\n    test('should call onBlockModified after introduce text and hit enter', async () => {\n        const onBlockModified = jest.fn()\n        await act(async () => {\n            render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <BlocksEditor\n                        boardId='test-board'\n                        onBlockCreated={jest.fn()}\n                        onBlockModified={onBlockModified}\n                        onBlockMoved={jest.fn()}\n                        blocks={blocks}\n                    />\n                </ReduxProvider>,\n            ))\n            const input = screen.getByTestId('checkbox-check')\n            expect(onBlockModified).not.toBeCalled()\n            fireEvent.click(input)\n            expect(onBlockModified).toBeCalledWith(expect.objectContaining({value: {checked: false, value: 'Checkbox'}}))\n        })\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/blocksEditor.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useState, useMemo} from 'react'\nimport {DndProvider} from 'react-dnd'\nimport {HTML5Backend} from 'react-dnd-html5-backend'\n\nimport Editor from './editor'\nimport {BlockData} from './blocks/types'\nimport BlockContent from './blockContent'\nimport * as registry from './blocks'\n\ntype Props = {\n    boardId?: string\n    onBlockCreated: (block: BlockData, afterBlock?: BlockData) => Promise<BlockData|null>\n    onBlockModified: (block: BlockData) => Promise<BlockData|null>\n    onBlockMoved: (block: BlockData, beforeBlock: BlockData|null, afterBlock: BlockData|null) => Promise<void>\n    blocks: BlockData[]\n}\n\nfunction BlocksEditor(props: Props) {\n    const [nextType, setNextType] = useState<string>('')\n    const [editing, setEditing] = useState<BlockData|null>(null)\n    const [afterBlock, setAfterBlock] = useState<BlockData|null>(null)\n    const contentOrder = useMemo(() => props.blocks.filter((b) => b.id).map((b) => b.id!), [props.blocks])\n    return (\n        <div\n            className='BlocksEditor'\n            onKeyDown={(e: React.KeyboardEvent<HTMLDivElement>) => {\n                if (e.key === 'ArrowUp') {\n                    if (editing === null) {\n                        if (afterBlock === null) {\n                            setEditing(props.blocks[props.blocks.length - 1] || null)\n                        } else {\n                            setEditing(afterBlock)\n                        }\n                        setAfterBlock(null)\n                        return\n                    }\n                    let prevBlock = null\n                    for (const b of props.blocks) {\n                        if (editing?.id === b.id) {\n                            break\n                        }\n                        const blockType = registry.get(b.contentType)\n                        if (blockType.editable) {\n                            prevBlock = b\n                        }\n                    }\n                    if (prevBlock) {\n                        setEditing(prevBlock)\n                        setAfterBlock(null)\n                    }\n                } else if (e.key === 'ArrowDown') {\n                    let currentBlock = editing\n                    if (currentBlock === null) {\n                        currentBlock = afterBlock\n                    }\n                    if (currentBlock === null) {\n                        return\n                    }\n\n                    let nextBlock = null\n                    let breakNext = false\n                    for (const b of props.blocks) {\n                        if (breakNext) {\n                            const blockType = registry.get(b.contentType)\n                            if (blockType.editable) {\n                                nextBlock = b\n                                break\n                            }\n                        }\n                        if (currentBlock.id === b.id) {\n                            breakNext = true\n                        }\n                    }\n                    setEditing(nextBlock)\n                    setAfterBlock(null)\n                }\n            }}\n        >\n            <DndProvider backend={HTML5Backend}>\n                {Object.values(props.blocks).map((d) => (\n                    <div\n                        key={d.id}\n                    >\n                        <BlockContent\n                            key={d.id}\n                            block={d}\n                            editing={editing}\n                            setEditing={(block) => {\n                                setEditing(block)\n                                setAfterBlock(null)\n                            }}\n                            contentOrder={contentOrder}\n                            setAfterBlock={setAfterBlock}\n                            onSave={async (b) => {\n                                const newBlock = await props.onBlockModified(b)\n                                setNextType(registry.get(b.contentType).nextType || '')\n                                setAfterBlock(newBlock)\n                                return newBlock\n                            }}\n                            onMove={props.onBlockMoved}\n                        />\n                        {afterBlock && afterBlock.id === d.id && (\n                            <Editor\n                                initialValue=''\n                                initialContentType={nextType}\n                                onSave={async (b) => {\n                                    const newBlock = await props.onBlockCreated(b, afterBlock)\n                                    setNextType(registry.get(b.contentType).nextType || '')\n                                    setAfterBlock(newBlock)\n                                    return newBlock\n                                }}\n                            />)}\n                    </div>\n                ))}\n                {!editing && !afterBlock && <Editor onSave={props.onBlockCreated}/>}\n            </DndProvider>\n        </div>\n    )\n}\n\nexport default BlocksEditor\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/devmain.scss",
    "content": "h1,\nh2,\nh3,\np {\n    margin: 5px;\n}\n\n.App-header {\n    padding: 20px 100px;\n    min-height: 100vh;\n    display: flex;\n    flex-direction: column;\n}\n\nbody {\n    margin: 0;\n    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',\n        'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',\n        sans-serif;\n    -webkit-font-smoothing: antialiased;\n    -moz-osx-font-smoothing: grayscale;\n}\n\ncode {\n    font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',\n        monospace;\n}\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/devmain.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useState} from 'react'\nimport ReactDOM from 'react-dom'\n\nimport {BlockData} from './blocks/types'\nimport BlocksEditor from './blocksEditor'\nimport {register} from './blocks/'\nimport TextDev from './blocks/text-dev'\n\nimport '@mattermost/compass-icons/css/compass-icons.css'\n\nimport '../../styles/variables.scss'\nimport '../../styles/main.scss'\nimport '../../styles/labels.scss'\nimport '../../styles/_markdown.scss'\n\nimport './devmain.scss'\n\nconst newID = () => Math.random().toString(36).slice(2)\n\nregister(TextDev)\n\nconst fakeData = [\n    {id: '1', value: 'Title', contentType: 'h1'},\n    {id: '2', value: 'Sub title', contentType: 'h2'},\n    {id: '3', value: 'Sub sub title', contentType: 'h3'},\n    {id: '4', value: 'Some **markdown** text', contentType: 'text'},\n    {id: '5', value: 'Some multiline\\n**markdown** text\\n### With Items\\n- Item 1\\n- Item2\\n- Item3', contentType: 'text'},\n    {id: '6', value: {checked: true, value: 'Checkbox'}, contentType: 'checkbox'},\n]\n\nfunction App() {\n    //const [data, setData] = useState<BlockData[]>([])\n    const [data, setData] = useState<Array<BlockData<any>>>(fakeData)\n\n    return (\n        <div className='App'>\n            <header className='App-header'>\n                <BlocksEditor\n                    blocks={data}\n                    onBlockCreated={async (block: BlockData<any>, afterBlock?: BlockData<any>): Promise<BlockData|null> => {\n                        if (block.contentType === 'text' && block.value === '') {\n                            return null\n                        }\n                        const id = newID()\n                        let newData: BlockData[] = []\n                        const newBlock = {value: block.value, contentType: block.contentType, id}\n\n                        if (block.contentType === 'image' && (typeof block.value.file !== 'string')) {\n                            const base64String = btoa(String.fromCharCode.apply(null, (new Uint8Array(block.value.file)) as unknown as number[]))\n                            newBlock.value.file = `data:image/jpeg;base64,${base64String}`\n                        }\n\n                        if (afterBlock) {\n                            for (const b of data) {\n                                newData.push(b)\n                                if (b.id === afterBlock.id) {\n                                    newData.push(newBlock)\n                                }\n                            }\n                        } else {\n                            newData = [...data, newBlock]\n                        }\n                        setData(newData)\n                        return newBlock\n                    }}\n                    onBlockModified={async (block: BlockData): Promise<BlockData|null> => {\n                        const newData: BlockData[] = []\n                        if (block.contentType === 'text' && block.value === '') {\n                            for (const b of data) {\n                                if (b.id !== block.id) {\n                                    newData.push(b)\n                                }\n                            }\n                            setData(newData)\n                            return block\n                        }\n                        for (const b of data) {\n                            if (b.id === block.id) {\n                                newData.push(block)\n                            } else {\n                                newData.push(b)\n                            }\n                        }\n                        setData(newData)\n                        return block\n                    }}\n                    onBlockMoved={async (block: BlockData<any>, beforeBlock: BlockData|null, afterBlock: BlockData<any>|null): Promise<void> => {\n                        const newData: BlockData[] = []\n                        for (const b of data) {\n                            if (b.id !== block.id) {\n                                if (beforeBlock && b.id === beforeBlock.id) {\n                                    newData.push(block)\n                                }\n                                newData.push(b)\n                                if (afterBlock && b.id === afterBlock.id) {\n                                    newData.push(block)\n                                }\n                            }\n                        }\n                        setData(newData)\n                    }}\n                />\n            </header>\n        </div>\n    )\n}\n\nReactDOM.render(<App/>, document.getElementById('focalboard-app'))\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/editor.scss",
    "content": ".Editor {\n    margin-left: 50px;\n    color: rgb(var(--center-channel-color-rgb));\n    background-color: rgb(var(--center-channel-bg-rgb));\n\n    .RootInput {\n        display: block;\n    }\n\n    &.with-content-type {\n        .RootInput {\n            display: none;\n        }\n    }\n\n    input {\n        width: 100%;\n        border: 1px solid hsl(0, 0%, 80%);\n        border-radius: 4px;\n        padding: 3px 6px;\n        outline: 0;\n\n        &:focus {\n            border: 2px solid #2684ff;\n        }\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/editor.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {Provider as ReduxProvider} from 'react-redux'\nimport {render, screen, fireEvent, act} from '@testing-library/react'\n\nimport {mockDOM, wrapDNDIntl, mockStateStore} from '../../testUtils'\nimport {TestBlockFactory} from '../../test/testBlockFactory'\n\nimport Editor from './editor'\n\njest.mock('draft-js/lib/generateRandomKey', () => () => '123')\n\ndescribe('components/blocksEditor/editor', () => {\n    beforeEach(mockDOM)\n\n    const board1 = TestBlockFactory.createBoard()\n    board1.id = 'board-id-1'\n\n    const state = {\n        users: {\n            boardUsers: {\n                1: {username: 'abc'},\n                2: {username: 'd'},\n                3: {username: 'e'},\n                4: {username: 'f'},\n                5: {username: 'g'},\n            },\n        },\n        boards: {\n            current: 'board-id-1',\n            boards: {\n                [board1.id]: board1,\n            },\n        },\n        clientConfig: {\n            value: {},\n        },\n    }\n    const store = mockStateStore([], state)\n\n    test('should match snapshot', async () => {\n        let container\n        await act(async () => {\n            const result = render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <Editor\n                        id='block-id'\n                        boardId='fake-board-id'\n                        initialValue='test-value'\n                        initialContentType='text'\n                        onSave={jest.fn()}\n                    />\n                </ReduxProvider>,\n            ))\n            container = result.container\n        })\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot on empty', async () => {\n        let container\n        await act(async () => {\n            const result = render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <Editor\n                        boardId='fake-board-id'\n                        onSave={jest.fn()}\n                    />\n                </ReduxProvider>,\n            ))\n            container = result.container\n        })\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should call onSave after introduce text and hit enter', async () => {\n        const onSave = jest.fn()\n        await act(async () => {\n            render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <Editor\n                        boardId='fake-board-id'\n                        onSave={onSave}\n                    />\n                </ReduxProvider>,\n            ))\n        })\n        let input = screen.getByDisplayValue('')\n        expect(onSave).not.toBeCalled()\n        fireEvent.change(input, {target: {value: '/title'}})\n        fireEvent.keyDown(input, {key: 'Enter'})\n        expect(onSave).not.toBeCalled()\n\n        input = screen.getByDisplayValue('')\n        fireEvent.change(input, {target: {value: 'test'}})\n        fireEvent.keyDown(input, {key: 'Enter'})\n\n        expect(onSave).toBeCalledWith(expect.objectContaining({value: 'test'}))\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/editor.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useState, useEffect} from 'react'\n\nimport * as contentBlocks from './blocks/'\nimport {ContentType, BlockData} from './blocks/types'\nimport RootInput from './rootInput'\n\nimport './editor.scss'\n\ntype Props = {\n    boardId?: string\n    onSave: (block: BlockData) => Promise<BlockData|null>\n    id?: string\n    initialValue?: string\n    initialContentType?: string\n}\n\nexport default function Editor(props: Props) {\n    const [value, setValue] = useState(props.initialValue || '')\n    const [currentBlockType, setCurrentBlockType] = useState<ContentType|null>(contentBlocks.get(props.initialContentType || '') || null)\n\n    useEffect(() => {\n        if (!currentBlockType) {\n            const block = contentBlocks.getByPrefix(value)\n            if (block) {\n                setValue('')\n                setCurrentBlockType(block)\n            } else if (value !== '' && !contentBlocks.isSubPrefix(value) && !value.startsWith('/')) {\n                setCurrentBlockType(contentBlocks.get('text'))\n            }\n        }\n    }, [value, currentBlockType])\n\n    const CurrentBlockInput = currentBlockType?.Input\n\n    return (\n        <div className='Editor'>\n            {currentBlockType === null &&\n                <RootInput\n                    onChange={setValue}\n                    onChangeType={setCurrentBlockType}\n                    value={value}\n                    onSave={async (val: string, blockType: string) => {\n                        if (blockType === null && val === '') {\n                            return\n                        }\n                        await props.onSave({value: val, contentType: blockType, id: props.id})\n                        setValue('')\n                        setCurrentBlockType(null)\n                    }}\n                />}\n            {CurrentBlockInput &&\n                <CurrentBlockInput\n                    onChange={setValue}\n                    value={value}\n                    onCancel={() => {\n                        setValue('')\n                        setCurrentBlockType(null)\n                    }}\n                    onSave={async (val: string) => {\n                        const newBlock = await props.onSave({value: val, contentType: currentBlockType.name, id: props.id})\n                        setValue('')\n                        const createdContentType = contentBlocks.get(newBlock?.contentType || '')\n                        setCurrentBlockType(contentBlocks.get(createdContentType?.nextType || '') || null)\n                    }}\n                    currentBoardId={props.boardId}\n                />}\n        </div>\n    )\n}\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/rootInput.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {render, screen, fireEvent} from '@testing-library/react'\n\nimport RootInput from './rootInput'\n\ndescribe('components/blocksEditor/rootInput', () => {\n    test('should match Display snapshot', async () => {\n        const {container} = render(\n            <RootInput\n                onChange={jest.fn()}\n                value='test-value'\n                onChangeType={jest.fn()}\n                onSave={jest.fn()}\n            />,\n        )\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match Input snapshot', async () => {\n        const {container} = render(\n            <RootInput\n                onChange={jest.fn()}\n                value='test-value'\n                onChangeType={jest.fn()}\n                onSave={jest.fn()}\n            />,\n        )\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match Input snapshot with menu open', async () => {\n        const {container} = render(\n            <RootInput\n                onChange={jest.fn()}\n                value=''\n                onChangeType={jest.fn()}\n                onSave={jest.fn()}\n            />,\n        )\n        const input = screen.getByDisplayValue('')\n        fireEvent.change(input, {target: {value: '/'}})\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should emit onChange event', async () => {\n        const onChange = jest.fn()\n        render(\n            <RootInput\n                onChange={onChange}\n                value='test-value'\n                onChangeType={jest.fn()}\n                onSave={jest.fn()}\n            />,\n        )\n\n        expect(onChange).not.toBeCalled()\n\n        const input = screen.getByDisplayValue('test-value')\n        fireEvent.change(input, {target: {value: 'test-value-'}})\n        expect(onChange).toBeCalled()\n    })\n\n    test('should not emit onChangeType event when value is not empty and hit backspace', async () => {\n        const onChangeType = jest.fn()\n        render(\n            <RootInput\n                onChange={jest.fn()}\n                value='test-value'\n                onChangeType={onChangeType}\n                onSave={jest.fn()}\n            />,\n        )\n\n        expect(onChangeType).not.toBeCalled()\n        const input = screen.getByDisplayValue('test-value')\n        fireEvent.keyDown(input, {key: 'Backspace'})\n        expect(onChangeType).not.toBeCalled()\n    })\n\n    test('should emit onSave event hit enter', async () => {\n        const onSave = jest.fn()\n        render(\n            <RootInput\n                onChange={jest.fn()}\n                value='test-value'\n                onChangeType={jest.fn()}\n                onSave={onSave}\n            />,\n        )\n\n        expect(onSave).not.toBeCalled()\n        const input = screen.getByDisplayValue('test-value')\n        fireEvent.keyDown(input, {key: 'Enter'})\n        expect(onSave).toBeCalled()\n    })\n\n    test('should emit onChangeType event on menu option selected', async () => {\n        const onChangeType = jest.fn()\n        render(\n            <RootInput\n                onChange={jest.fn()}\n                value=''\n                onChangeType={onChangeType}\n                onSave={jest.fn()}\n            />,\n        )\n\n        const input = screen.getByDisplayValue('')\n        fireEvent.change(input, {target: {value: '/'}})\n\n        const option = screen.getByText('/title Creates a new Title block.')\n        fireEvent.click(option)\n\n        expect(onChangeType).toBeCalledWith(expect.objectContaining({name: 'h1'}))\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/blocksEditor/rootInput.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useState} from 'react'\nimport Select from 'react-select'\nimport {CSSObject} from '@emotion/serialize'\n\nimport {getSelectBaseStyle} from '../../theme'\n\nimport * as registry from './blocks/'\nimport {ContentType} from './blocks/types'\n\ntype Props = {\n    onChange: (value: string) => void\n    onChangeType: (blockType: ContentType) => void\n    onSave: (value: string, blockType: string) => void\n    value: string\n}\n\nconst baseStyles = getSelectBaseStyle()\n\nconst styles = {\n    ...baseStyles,\n    control: (provided: CSSObject): CSSObject => ({\n        ...provided,\n        width: '100%',\n        height: '100%',\n        display: 'flex',\n        background: 'rgb(var(--center-channel-bg-rgb))',\n        color: 'rgb(var(--center-channel-color-rgb))',\n        flexDirection: 'row',\n    }),\n    input: (provided: CSSObject): CSSObject => ({\n        ...provided,\n        background: 'rgb(var(--center-channel-bg-rgb))',\n        color: 'rgb(var(--center-channel-color-rgb))',\n    }),\n    menu: (provided: CSSObject): CSSObject => ({\n        ...provided,\n        minWidth: '100%',\n        width: 'max-content',\n        background: 'rgb(var(--center-channel-bg-rgb))',\n        left: '0',\n        marginBottom: '0',\n    }),\n    menuPortal: (provided: CSSObject): CSSObject => ({\n        ...provided,\n        zIndex: 999,\n    }),\n}\n\nexport default function RootInput(props: Props) {\n    const [showMenu, setShowMenu] = useState(false)\n\n    return (\n        <Select\n            styles={styles}\n            components={{DropdownIndicator: () => null, IndicatorSeparator: () => null}}\n            className='RootInput'\n            placeholder={'Introduce your text or your slash command'}\n            autoFocus={true}\n            menuIsOpen={showMenu}\n            menuPortalTarget={document.getElementById('focalboard-root-portal')}\n            menuPosition={'fixed'}\n            options={registry.list()}\n            getOptionValue={(ct: ContentType) => ct.slashCommand}\n            getOptionLabel={(ct: ContentType) => ct.slashCommand + ' Creates a new ' + ct.displayName + ' block.'}\n            filterOption={(option: any, inputValue: string): boolean => {\n                return inputValue.startsWith(option.value) || option.value.startsWith(inputValue)\n            }}\n            inputValue={props.value}\n            onInputChange={(inputValue: string) => {\n                props.onChange(inputValue)\n                if (inputValue.startsWith('/')) {\n                    setShowMenu(true)\n                } else {\n                    setShowMenu(false)\n                }\n            }}\n            onChange={(ct: ContentType|null) => {\n                if (ct) {\n                    const args = props.value.split(' ').slice(1)\n                    ct.runSlashCommand(props.onChangeType, props.onChange, ...args)\n                }\n            }}\n            onBlur={() => {\n                const command = props.value.trimStart().split(' ')[0]\n                const block = registry.getBySlashCommandPrefix(command)\n                if (command === '' || !block) {\n                    props.onSave(props.value, 'text')\n                    props.onChange('')\n                }\n            }}\n            onFocus={(e: React.FocusEvent) => {\n                const target = e.currentTarget\n                target.scrollIntoView({block: 'center'})\n            }}\n            onKeyDown={(e) => {\n                if (e.key === 'Escape') {\n                    props.onSave('', 'text')\n                    props.onChange('')\n                }\n                if (e.key === 'Enter') {\n                    const command = props.value.trimStart().split(' ')[0]\n                    const block = registry.getBySlashCommandPrefix(command)\n                    if (command === '' || !block) {\n                        e.preventDefault()\n                        e.stopPropagation()\n                        props.onSave(props.value, 'text')\n                        props.onChange('')\n                    }\n                }\n            }}\n        />\n    )\n}\n\n"
  },
  {
    "path": "webapp/src/components/boardIconSelector.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useCallback} from 'react'\n\nimport {BlockIcons} from '../blockIcons'\nimport {Board} from '../blocks/board'\n\nimport mutator from '../mutator'\n\nimport IconSelector from './iconSelector'\n\ntype Props = {\n    board: Board\n    size?: 's' | 'm' | 'l'\n    readonly?: boolean\n}\n\nconst BoardIconSelector = React.memo((props: Props) => {\n    const {board, size} = props\n\n    const onSelectEmoji = useCallback((emoji: string) => {\n        mutator.changeBoardIcon(board.id, board.icon, emoji)\n        document.body.click()\n    }, [board.id, board.icon])\n    const onAddRandomIcon = useCallback(() => mutator.changeBoardIcon(board.id, board.icon, BlockIcons.shared.randomIcon()), [board.id, board.icon])\n    const onRemoveIcon = useCallback(() => mutator.changeBoardIcon(board.id, board.icon, '', 'remove board icon'), [board.id, board.icon])\n\n    if (!board.icon) {\n        return null\n    }\n\n    let className = `octo-icon size-${size || 'm'}`\n    if (props.readonly) {\n        className += ' readonly'\n    }\n    const iconElement = <div className={className}><span>{board.icon}</span></div>\n\n    return (\n        <IconSelector\n            readonly={props.readonly}\n            iconElement={iconElement}\n            onAddRandomIcon={onAddRandomIcon}\n            onSelectEmoji={onSelectEmoji}\n            onRemoveIcon={onRemoveIcon}\n        />\n    )\n})\n\nexport default BoardIconSelector\n"
  },
  {
    "path": "webapp/src/components/boardTemplateSelector/__snapshots__/boardTemplateSelector.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/boardTemplateSelector/boardTemplateSelector a focalboard Plugin should match snapshot 1`] = `\n<div>\n  <div\n    class=\"BoardTemplateSelector__container \"\n  >\n    <div\n      class=\"BoardTemplateSelector__backdrop\"\n    />\n    <div\n      class=\"BoardTemplateSelector\"\n    >\n      <div\n        class=\"toolbar\"\n      >\n        <button\n          aria-label=\"Close\"\n          title=\"Close\"\n          type=\"button\"\n        >\n          <i\n            class=\"CompassIcon icon-close CloseIcon\"\n          />\n        </button>\n      </div>\n      <div\n        class=\"header\"\n      >\n        <h1\n          class=\"title\"\n        >\n          Create a board\n        </h1>\n        <p\n          class=\"description\"\n        >\n          Add a board to the sidebar using any of the templates defined below or start from scratch.\n        </p>\n      </div>\n      <div\n        class=\"templates\"\n      >\n        <div\n          class=\"templates-sidebar\"\n        >\n          <div\n            class=\"templates-list\"\n          >\n            <button\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-plus\"\n              />\n              <span>\n                Create new template\n              </span>\n            </button>\n            <div\n              class=\"BoardTemplateSelectorItem active\"\n            >\n              <span\n                class=\"template-icon\"\n              >\n                🚴🏻‍♂️\n              </span>\n              <span\n                class=\"template-name\"\n              >\n                Template Global\n              </span>\n            </div>\n            <div\n              class=\"BoardTemplateSelectorItem\"\n            >\n              <span\n                class=\"template-icon\"\n              >\n                🚴🏻‍♂️\n              </span>\n              <span\n                class=\"template-name\"\n              >\n                Template 1\n              </span>\n              <div\n                class=\"actions\"\n              >\n                <button\n                  aria-label=\"Delete\"\n                  title=\"Delete\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-trash-can-outline DeleteIcon trash-can-outline\"\n                  />\n                </button>\n                <button\n                  aria-label=\"Edit\"\n                  title=\"Edit\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-pencil-outline EditIcon\"\n                  />\n                </button>\n              </div>\n            </div>\n            <div\n              class=\"BoardTemplateSelectorItem\"\n            >\n              <span\n                class=\"template-icon\"\n              >\n                ❄️\n              </span>\n              <span\n                class=\"template-name\"\n              >\n                Welcome to Boards!\n              </span>\n            </div>\n          </div>\n          <div\n            class=\"templates-sidebar__footer\"\n          >\n            <button\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-kanban\"\n              />\n              <span>\n                Create empty board\n              </span>\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"templates-content\"\n        >\n          <div\n            class=\"template-preview-box\"\n          >\n            <div\n              class=\"BoardTemplateSelectorPreview\"\n            />\n          </div>\n          <div\n            class=\"buttons\"\n          >\n            <button\n              type=\"button\"\n            >\n              <span>\n                Use this template\n              </span>\n            </button>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/boardTemplateSelector/boardTemplateSelector a focalboard Plugin should match snapshot with custom title and description 1`] = `\n<div>\n  <div\n    class=\"BoardTemplateSelector__container BoardTemplateSelector__container--page\"\n  >\n    <div\n      class=\"BoardTemplateSelector\"\n    >\n      <div\n        class=\"toolbar\"\n      />\n      <div\n        class=\"header\"\n      >\n        <h1\n          class=\"title\"\n        >\n          test-title\n        </h1>\n        <p\n          class=\"description\"\n        >\n          test-description\n        </p>\n      </div>\n      <div\n        class=\"templates\"\n      >\n        <div\n          class=\"templates-sidebar\"\n        >\n          <div\n            class=\"templates-list\"\n          >\n            <button\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-plus\"\n              />\n              <span>\n                Create new template\n              </span>\n            </button>\n            <div\n              class=\"BoardTemplateSelectorItem active\"\n            >\n              <span\n                class=\"template-icon\"\n              >\n                🚴🏻‍♂️\n              </span>\n              <span\n                class=\"template-name\"\n              >\n                Template Global\n              </span>\n            </div>\n            <div\n              class=\"BoardTemplateSelectorItem\"\n            >\n              <span\n                class=\"template-icon\"\n              >\n                🚴🏻‍♂️\n              </span>\n              <span\n                class=\"template-name\"\n              >\n                Template 1\n              </span>\n              <div\n                class=\"actions\"\n              >\n                <button\n                  aria-label=\"Delete\"\n                  title=\"Delete\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-trash-can-outline DeleteIcon trash-can-outline\"\n                  />\n                </button>\n                <button\n                  aria-label=\"Edit\"\n                  title=\"Edit\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-pencil-outline EditIcon\"\n                  />\n                </button>\n              </div>\n            </div>\n            <div\n              class=\"BoardTemplateSelectorItem\"\n            >\n              <span\n                class=\"template-icon\"\n              >\n                ❄️\n              </span>\n              <span\n                class=\"template-name\"\n              >\n                Welcome to Boards!\n              </span>\n            </div>\n          </div>\n          <div\n            class=\"templates-sidebar__footer\"\n          >\n            <button\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-kanban\"\n              />\n              <span>\n                Create empty board\n              </span>\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"templates-content\"\n        >\n          <div\n            class=\"template-preview-box\"\n          >\n            <div\n              class=\"BoardTemplateSelectorPreview\"\n            />\n          </div>\n          <div\n            class=\"buttons\"\n          >\n            <button\n              type=\"button\"\n            >\n              <span>\n                Use this template\n              </span>\n            </button>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/boardTemplateSelector/boardTemplateSelector a focalboard Plugin should match snapshot without close 1`] = `\n<div>\n  <div\n    class=\"BoardTemplateSelector__container BoardTemplateSelector__container--page\"\n  >\n    <div\n      class=\"BoardTemplateSelector\"\n    >\n      <div\n        class=\"toolbar\"\n      />\n      <div\n        class=\"header\"\n      >\n        <h1\n          class=\"title\"\n        >\n          Create a board\n        </h1>\n        <p\n          class=\"description\"\n        >\n          Add a board to the sidebar using any of the templates defined below or start from scratch.\n        </p>\n      </div>\n      <div\n        class=\"templates\"\n      >\n        <div\n          class=\"templates-sidebar\"\n        >\n          <div\n            class=\"templates-list\"\n          >\n            <button\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-plus\"\n              />\n              <span>\n                Create new template\n              </span>\n            </button>\n            <div\n              class=\"BoardTemplateSelectorItem active\"\n            >\n              <span\n                class=\"template-icon\"\n              >\n                🚴🏻‍♂️\n              </span>\n              <span\n                class=\"template-name\"\n              >\n                Template Global\n              </span>\n            </div>\n            <div\n              class=\"BoardTemplateSelectorItem\"\n            >\n              <span\n                class=\"template-icon\"\n              >\n                🚴🏻‍♂️\n              </span>\n              <span\n                class=\"template-name\"\n              >\n                Template 1\n              </span>\n              <div\n                class=\"actions\"\n              >\n                <button\n                  aria-label=\"Delete\"\n                  title=\"Delete\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-trash-can-outline DeleteIcon trash-can-outline\"\n                  />\n                </button>\n                <button\n                  aria-label=\"Edit\"\n                  title=\"Edit\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-pencil-outline EditIcon\"\n                  />\n                </button>\n              </div>\n            </div>\n            <div\n              class=\"BoardTemplateSelectorItem\"\n            >\n              <span\n                class=\"template-icon\"\n              >\n                ❄️\n              </span>\n              <span\n                class=\"template-name\"\n              >\n                Welcome to Boards!\n              </span>\n            </div>\n          </div>\n          <div\n            class=\"templates-sidebar__footer\"\n          >\n            <button\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-kanban\"\n              />\n              <span>\n                Create empty board\n              </span>\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"templates-content\"\n        >\n          <div\n            class=\"template-preview-box\"\n          >\n            <div\n              class=\"BoardTemplateSelectorPreview\"\n            />\n          </div>\n          <div\n            class=\"buttons\"\n          >\n            <button\n              type=\"button\"\n            >\n              <span>\n                Use this template\n              </span>\n            </button>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/boardTemplateSelector/boardTemplateSelector not a focalboard Plugin should match snapshot 1`] = `\n<div>\n  <div\n    class=\"BoardTemplateSelector__container \"\n  >\n    <div\n      class=\"BoardTemplateSelector__backdrop\"\n    />\n    <div\n      class=\"BoardTemplateSelector\"\n    >\n      <div\n        class=\"toolbar\"\n      >\n        <button\n          aria-label=\"Close\"\n          title=\"Close\"\n          type=\"button\"\n        >\n          <i\n            class=\"CompassIcon icon-close CloseIcon\"\n          />\n        </button>\n      </div>\n      <div\n        class=\"header\"\n      >\n        <h1\n          class=\"title\"\n        >\n          Create a board\n        </h1>\n        <p\n          class=\"description\"\n        >\n          Add a board to the sidebar using any of the templates defined below or start from scratch.\n        </p>\n      </div>\n      <div\n        class=\"templates\"\n      >\n        <div\n          class=\"templates-sidebar\"\n        >\n          <div\n            class=\"templates-list\"\n          >\n            <button\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-plus\"\n              />\n              <span>\n                Create new template\n              </span>\n            </button>\n            <div\n              class=\"BoardTemplateSelectorItem active\"\n            >\n              <span\n                class=\"template-icon\"\n              >\n                🚴🏻‍♂️\n              </span>\n              <span\n                class=\"template-name\"\n              >\n                Template Global\n              </span>\n            </div>\n            <div\n              class=\"BoardTemplateSelectorItem\"\n            >\n              <span\n                class=\"template-icon\"\n              >\n                🚴🏻‍♂️\n              </span>\n              <span\n                class=\"template-name\"\n              >\n                Template 1\n              </span>\n              <div\n                class=\"actions\"\n              >\n                <button\n                  aria-label=\"Delete\"\n                  title=\"Delete\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-trash-can-outline DeleteIcon trash-can-outline\"\n                  />\n                </button>\n                <button\n                  aria-label=\"Edit\"\n                  title=\"Edit\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-pencil-outline EditIcon\"\n                  />\n                </button>\n              </div>\n            </div>\n            <div\n              class=\"BoardTemplateSelectorItem\"\n            >\n              <span\n                class=\"template-icon\"\n              >\n                ❄️\n              </span>\n              <span\n                class=\"template-name\"\n              >\n                Welcome to Boards!\n              </span>\n            </div>\n          </div>\n          <div\n            class=\"templates-sidebar__footer\"\n          >\n            <button\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-kanban\"\n              />\n              <span>\n                Create empty board\n              </span>\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"templates-content\"\n        >\n          <div\n            class=\"template-preview-box\"\n          >\n            <div\n              class=\"BoardTemplateSelectorPreview\"\n            />\n          </div>\n          <div\n            class=\"buttons\"\n          >\n            <button\n              type=\"button\"\n            >\n              <span>\n                Use this template\n              </span>\n            </button>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/boardTemplateSelector/__snapshots__/boardTemplateSelectorItem.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/boardTemplateSelector/boardTemplateSelectorItem should match snapshot 1`] = `\n<div>\n  <div\n    class=\"BoardTemplateSelectorItem\"\n  >\n    <span\n      class=\"template-icon\"\n    >\n      🚴🏻‍♂️\n    </span>\n    <span\n      class=\"template-name\"\n    >\n      Template 1\n    </span>\n    <div\n      class=\"actions\"\n    >\n      <button\n        aria-label=\"Delete\"\n        title=\"Delete\"\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-trash-can-outline DeleteIcon trash-can-outline\"\n        />\n      </button>\n      <button\n        aria-label=\"Edit\"\n        title=\"Edit\"\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-pencil-outline EditIcon\"\n        />\n      </button>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/boardTemplateSelector/boardTemplateSelectorItem should match snapshot when active 1`] = `\n<div>\n  <div\n    class=\"BoardTemplateSelectorItem active\"\n  >\n    <span\n      class=\"template-icon\"\n    >\n      🚴🏻‍♂️\n    </span>\n    <span\n      class=\"template-name\"\n    >\n      Template 1\n    </span>\n    <div\n      class=\"actions\"\n    >\n      <button\n        aria-label=\"Delete\"\n        title=\"Delete\"\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-trash-can-outline DeleteIcon trash-can-outline\"\n        />\n      </button>\n      <button\n        aria-label=\"Edit\"\n        title=\"Edit\"\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-pencil-outline EditIcon\"\n        />\n      </button>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/boardTemplateSelector/boardTemplateSelectorItem should match snapshot with global template 1`] = `\n<div>\n  <div\n    class=\"BoardTemplateSelectorItem\"\n  >\n    <span\n      class=\"template-icon\"\n    >\n      🚴🏻‍♂️\n    </span>\n    <span\n      class=\"template-name\"\n    >\n      Template global\n    </span>\n  </div>\n</div>\n`;\n\nexports[`components/boardTemplateSelector/boardTemplateSelectorItem should trigger the onDelete (and not any other) when click the delete icon and confirm 1`] = `\n<div\n  id=\"focalboard-root-portal\"\n>\n  <div\n    class=\"BoardTemplateSelectorItem\"\n  >\n    <span\n      class=\"template-icon\"\n    >\n      🚴🏻‍♂️\n    </span>\n    <span\n      class=\"template-name\"\n    >\n      Template 1\n    </span>\n    <div\n      class=\"actions\"\n    >\n      <button\n        aria-label=\"Delete\"\n        title=\"Delete\"\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-trash-can-outline DeleteIcon trash-can-outline\"\n        />\n      </button>\n      <button\n        aria-label=\"Edit\"\n        title=\"Edit\"\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-pencil-outline EditIcon\"\n        />\n      </button>\n    </div>\n  </div>\n  <div>\n    <div\n      class=\"Dialog dialog-back DeleteBoardDialog size--medium\"\n    >\n      <div\n        class=\"backdrop\"\n      />\n      <div\n        class=\"wrapper\"\n      >\n        <div\n          class=\"dialog\"\n          role=\"dialog\"\n        >\n          <div\n            class=\"toolbar\"\n          >\n            <div>\n              <h1\n                class=\"dialog-title\"\n              />\n            </div>\n            <div\n              class=\"toolbar--right\"\n            >\n              <button\n                aria-label=\"Close dialog\"\n                title=\"Close dialog\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-close CloseIcon\"\n                />\n              </button>\n            </div>\n          </div>\n          <div\n            class=\"container\"\n          >\n            <h2\n              class=\"header text-heading5\"\n            >\n              Confirm delete board template\n            </h2>\n            <p\n              class=\"body\"\n            >\n              Are you sure you want to delete the board template “Template 1”?\n            </p>\n            <div\n              class=\"footer\"\n            >\n              <button\n                type=\"button\"\n              >\n                <span>\n                  Cancel\n                </span>\n              </button>\n              <button\n                type=\"button\"\n              >\n                <span>\n                  Delete\n                </span>\n              </button>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/boardTemplateSelector/__snapshots__/boardTemplateSelectorPreview.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/boardTemplateSelector/boardTemplateSelectorPreview should be null without activeTemplate 1`] = `<div />`;\n\nexports[`components/boardTemplateSelector/boardTemplateSelectorPreview should match snapshot 1`] = `\n<div>\n  <div\n    class=\"BoardTemplateSelectorPreview\"\n  >\n    <div\n      class=\"top-head\"\n    >\n      <div\n        class=\"ViewTitle\"\n      >\n        <div\n          class=\"add-buttons add-visible\"\n        />\n        <div\n          class=\"title\"\n        >\n          <div\n            class=\"IconSelector\"\n          >\n            <div\n              class=\"octo-icon size-m readonly\"\n            >\n              <span>\n                🚴🏻‍♂️\n              </span>\n            </div>\n          </div>\n          <input\n            class=\"Editable readonly title\"\n            placeholder=\"Untitled board\"\n            readonly=\"\"\n            spellcheck=\"true\"\n            title=\"Template 1\"\n            value=\"Template 1\"\n          />\n        </div>\n      </div>\n      <div\n        class=\"ViewHeader\"\n      >\n        <div\n          class=\"viewSelector\"\n        >\n          <input\n            class=\"Editable \"\n            placeholder=\"Untitled View\"\n            spellcheck=\"true\"\n            title=\"View\"\n            value=\"View\"\n          />\n          <div>\n            <div\n              aria-label=\"View menu\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <button\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-chevron-down DropdownIcon\"\n                />\n              </button>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"octo-spacer\"\n        />\n        <div\n          aria-label=\"Properties menu\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            type=\"button\"\n          >\n            <span>\n              Properties\n            </span>\n          </button>\n        </div>\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            type=\"button\"\n          >\n            <span>\n              Group by: \n              <span\n                id=\"groupByLabel\"\n              >\n                name\n              </span>\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"ModalWrapper\"\n        >\n          <button\n            type=\"button\"\n          >\n            <span>\n              Filter\n            </span>\n          </button>\n        </div>\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            type=\"button\"\n          >\n            <span>\n              Sort\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"board-search-field\"\n        >\n          <i\n            class=\"CompassIcon icon-magnify board-search-icon\"\n          />\n          <input\n            class=\"Editable \"\n            placeholder=\"Search cards\"\n            title=\"\"\n            value=\"\"\n          />\n        </div>\n        <div\n          class=\"ModalWrapper\"\n        >\n          <div\n            aria-label=\"View header menu\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <button\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n              />\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"ButtonWithMenu\"\n        >\n          <div\n            class=\"button-text\"\n          >\n            New\n          </div>\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <div\n              class=\"button-dropdown\"\n            >\n              <i\n                class=\"CompassIcon icon-chevron-down DropdownIcon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"Kanban\"\n    >\n      <div\n        class=\"octo-board-header\"\n        id=\"mainBoardHeader\"\n      >\n        <div\n          class=\"octo-board-header-cell KanbanColumnHeader\"\n          draggable=\"true\"\n          style=\"opacity: 1;\"\n        >\n          <span\n            class=\"Label empty \"\n            title=\"Items with an empty name property will go here. This column cannot be removed.\"\n          >\n            No name\n          </span>\n          \n          <div\n            class=\"KanbanCalculation\"\n          >\n            <button\n              title=\"1\"\n              type=\"button\"\n            >\n              <span>\n                1\n              </span>\n            </button>\n          </div>\n          <div\n            class=\"octo-spacer\"\n          />\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <button\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n              />\n            </button>\n          </div>\n          <button\n            type=\"button\"\n          >\n            <i\n              class=\"CompassIcon icon-plus AddIcon\"\n            />\n          </button>\n        </div>\n        <div\n          class=\"octo-board-header-cell KanbanColumnHeader\"\n          draggable=\"true\"\n          style=\"opacity: 1;\"\n        >\n          <span\n            class=\"Label propColorOrange \"\n          >\n            <input\n              class=\"Editable \"\n              placeholder=\"New Select\"\n              spellcheck=\"true\"\n              title=\"Q1\"\n              value=\"Q1\"\n            />\n          </span>\n          <div\n            class=\"KanbanCalculation\"\n          >\n            <button\n              title=\"0\"\n              type=\"button\"\n            >\n              <span>\n                0\n              </span>\n            </button>\n          </div>\n          <div\n            class=\"octo-spacer\"\n          />\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <button\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n              />\n            </button>\n          </div>\n          <button\n            type=\"button\"\n          >\n            <i\n              class=\"CompassIcon icon-plus AddIcon\"\n            />\n          </button>\n        </div>\n        <div\n          class=\"octo-board-header-cell KanbanColumnHeader\"\n          draggable=\"true\"\n          style=\"opacity: 1;\"\n        >\n          <span\n            class=\"Label propColorBlue \"\n          >\n            <input\n              class=\"Editable \"\n              placeholder=\"New Select\"\n              spellcheck=\"true\"\n              title=\"Q2\"\n              value=\"Q2\"\n            />\n          </span>\n          <div\n            class=\"KanbanCalculation\"\n          >\n            <button\n              title=\"0\"\n              type=\"button\"\n            >\n              <span>\n                0\n              </span>\n            </button>\n          </div>\n          <div\n            class=\"octo-spacer\"\n          />\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <button\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n              />\n            </button>\n          </div>\n          <button\n            type=\"button\"\n          >\n            <i\n              class=\"CompassIcon icon-plus AddIcon\"\n            />\n          </button>\n        </div>\n        <div\n          class=\"octo-board-header-cell narrow\"\n        >\n          <button\n            type=\"button\"\n          >\n            <span>\n              + Add a group\n            </span>\n          </button>\n        </div>\n      </div>\n      <div\n        class=\"octo-board-body\"\n        id=\"mainBoardBody\"\n      >\n        <div\n          class=\"octo-board-column\"\n        >\n          <div\n            class=\"KanbanCard\"\n            draggable=\"true\"\n            style=\"opacity: 1;\"\n          >\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper optionsMenu\"\n              role=\"button\"\n            >\n              <button\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                />\n              </button>\n            </div>\n            <div\n              class=\"octo-icontitle\"\n            >\n              <div\n                class=\"octo-icon\"\n              >\n                🚴🏻‍♂️\n              </div>\n              <div\n                class=\"octo-titletext\"\n              >\n                Card\n              </div>\n            </div>\n            <div\n              class=\"octo-tooltip tooltip-top\"\n              data-tooltip=\"name\"\n            >\n              <div\n                class=\"octo-propertyvalue octo-propertyvalue--readonly\"\n              >\n                test\n              </div>\n            </div>\n          </div>\n          <button\n            type=\"button\"\n          >\n            <span>\n              + New\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"octo-board-column\"\n        >\n          <button\n            type=\"button\"\n          >\n            <span>\n              + New\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"octo-board-column\"\n        >\n          <button\n            type=\"button\"\n          >\n            <span>\n              + New\n            </span>\n          </button>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/boardTemplateSelector/boardTemplateSelector.scss",
    "content": "@import '../../styles/z-index';\n\n.BoardTemplateSelector__container {\n    @include z-index(modal-permissions-label);\n    position: fixed;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    padding: 120px;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n\n    @media (max-height: 900) {\n        padding: 80px;\n    }\n}\n\n.BoardTemplateSelector__container--page {\n    position: absolute;\n    padding: 0;\n\n    .BoardTemplateSelector {\n        max-width: 100%;\n        max-height: 100%;\n    }\n}\n\n.BoardTemplateSelector__backdrop {\n    background-color: rgba(0, 0, 0, 0.56);\n    position: absolute;\n    left: 0;\n    top: 0;\n    width: 100%;\n    height: 100%;\n}\n\n.BoardTemplateSelector {\n    @include z-index(board-template-selector);\n    position: relative;\n    background-color: rgb(var(--center-channel-bg-rgb));\n    border: 1px solid rgba(var(--center-channel-color-rgb), 0.16);\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    overflow: auto;\n    display: flex;\n    flex-direction: column;\n    border-radius: 12px;\n    max-width: 1600px;\n    max-height: 1200px;\n\n    .toolbar {\n        display: flex;\n        justify-content: flex-end;\n        padding: 20px;\n        position: absolute;\n        right: 0;\n        top: 0;\n    }\n\n    .header {\n        width: 100%;\n        height: 80px;\n        display: flex;\n        align-items: center;\n        justify-content: flex-start;\n        padding: 32px;\n        border-bottom: 1px solid rgba(var(--center-channel-color-rgb), 0.16);\n\n        .title {\n            font-size: 22px;\n            margin: 0;\n        }\n\n        .description {\n            max-width: 640px;\n            text-align: center;\n            color: rgba(var(--center-channel-color-rgb), 0.56);\n            border-left: 1px solid rgba(var(--center-channel-color-rgb), 0.08);\n            padding-left: 8px;\n            margin: 0 0 0 8px;\n        }\n    }\n\n    .templates-sidebar {\n        flex: 0 0 294px;\n        display: flex;\n        flex-direction: column;\n        overflow: hidden;\n        background-color: rgba(var(--center-channel-color-rgb), 0.04);\n        border-right: 1px solid rgba(var(--center-channel-color-rgb), 0.08);\n    }\n\n    .templates-sidebar__footer {\n        display: flex;\n        align-items: center;\n        border-top: 1px solid rgba(var(--center-channel-color-rgb), 0.16);\n        padding: 24px;\n        height: 89px;\n\n        button {\n            width: 100%;\n        }\n    }\n\n    .templates__empty-board {\n        color: rgb(var(--button-bg-rgb));\n        padding: 0;\n        border: 0;\n        display: flex;\n        align-items: center;\n        background: transparent;\n        font-weight: 600;\n        gap: 10px;\n\n        i {\n            font-size: 16px;\n        }\n    }\n\n    .templates {\n        display: flex;\n        width: 100%;\n        justify-content: center;\n        flex: 1;\n        overflow: hidden;\n\n        .templates-list {\n            flex: 1;\n            overflow-y: auto;\n            padding: 16px;\n\n            .new-template {\n                width: 100%;\n                justify-content: flex-start;\n                padding-left: 14px;\n                margin-bottom: 16px;\n            }\n        }\n    }\n\n    .templates-content {\n        flex: 1;\n        display: flex;\n        flex-direction: column;\n        height: 100%;\n        overflow: hidden;\n    }\n\n    .buttons {\n        display: flex;\n        padding: 24px;\n        justify-content: flex-end;\n        height: 89px;\n        border-top: 1px solid rgba(var(--center-channel-color-rgb), 0.16);\n        background-color: rgb(var(--center-channel-bg-rgb));\n    }\n\n    .template-preview-box {\n        padding: 32px 32px 0;\n        position: relative;\n        background-color: rgb(var(--center-channel-bg-rgb));\n        width: 100%;\n        flex: 1;\n        overflow: hidden;\n        overflow-y: auto;\n\n        .empty-board {\n            background-color: rgb(var(--center-channel-bg-rgb));\n        }\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/boardTemplateSelector/boardTemplateSelector.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {render, screen, act, waitFor, within} from '@testing-library/react'\nimport userEvent from '@testing-library/user-event'\nimport React from 'react'\nimport {MockStoreEnhanced} from 'redux-mock-store'\nimport {createMemoryHistory} from 'history'\n\nimport {mocked} from 'jest-mock'\n\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport {MemoryRouter, Router} from 'react-router-dom'\n\nimport Mutator from '../../mutator'\nimport {Team} from '../../store/teams'\nimport {createBoard, Board} from '../../blocks/board'\nimport {IUser} from '../../user'\nimport {mockDOM, mockStateStore, wrapDNDIntl} from '../../testUtils'\n\nimport client from '../../octoClient'\n\nimport TelemetryClient from '../../telemetry/telemetryClient'\n\nimport BoardTemplateSelector from './boardTemplateSelector'\n\njest.mock('react-router-dom', () => {\n    const originalModule = jest.requireActual('react-router-dom')\n\n    return {\n        ...originalModule,\n        useRouteMatch: jest.fn(() => {\n            return {url: '/'}\n        }),\n    }\n})\njest.mock('../../octoClient', () => {\n    return {\n        getAllBlocks: jest.fn(() => Promise.resolve([])),\n        patchUserConfig: jest.fn(() => Promise.resolve({})),\n    }\n})\njest.mock('../../utils')\njest.mock('../../mutator')\n\njest.mock('../../telemetry/telemetryClient')\nconst mockedTelemetry = mocked(TelemetryClient, true)\n\ndescribe('components/boardTemplateSelector/boardTemplateSelector', () => {\n    const mockedMutator = mocked(Mutator, true)\n    const mockedOctoClient = mocked(client, true)\n    const team1: Team = {\n        id: 'team-1',\n        title: 'Team 1',\n        signupToken: '',\n        updateAt: 0,\n        modifiedBy: 'user-1',\n    }\n    const me: IUser = {\n        id: 'user-id-1',\n        username: 'username_1',\n        email: '',\n        nickname: '',\n        firstname: '',\n        lastname: '',\n        props: {},\n        create_at: 0,\n        update_at: 0,\n        is_bot: false,\n        is_guest: false,\n        roles: 'system_user',\n    }\n    const template1Title = 'Template 1'\n    const globalTemplateTitle = 'Template Global'\n    const boardTitle = 'Board 1'\n    let store: MockStoreEnhanced<unknown, unknown>\n    beforeAll(mockDOM)\n    beforeEach(() => {\n        jest.clearAllMocks()\n        const state = {\n            teams: {\n                current: team1,\n            },\n            users: {\n                me,\n                boardUsers: {[me.id]: me},\n            },\n            boards: {\n                boards: [\n                    {\n                        id: '2',\n                        title: boardTitle,\n                        teamId: team1.id,\n                        icon: '🚴🏻‍♂️',\n                        cardProperties: [\n                            {id: 'id-6'},\n                        ],\n                        dateDisplayPropertyId: 'id-6',\n                    },\n                ],\n                templates: [\n                    {\n                        id: '1',\n                        teamId: team1.id,\n                        title: template1Title,\n                        icon: '🚴🏻‍♂️',\n                        cardProperties: [\n                            {id: 'id-5'},\n                        ],\n                        dateDisplayPropertyId: 'id-5',\n                    },\n                    {\n                        id: '2',\n                        teamId: '0',\n                        title: 'Welcome to Boards!',\n                        icon: '❄️',\n                        cardProperties: [\n                            {id: 'id-5'},\n                        ],\n                        dateDisplayPropertyId: 'id-5',\n                        properties: {\n                            trackingTemplateId: 'template_id_2',\n                        },\n                        createdBy: 'system',\n                    },\n                ],\n                membersInBoards: {\n                    1: {userId: me.id, schemeAdmin: true},\n                    2: {userId: me.id, schemeAdmin: true},\n                },\n                myBoardMemberships: {\n                    1: {userId: me.id, schemeAdmin: true},\n                    2: {userId: me.id, schemeAdmin: true},\n                },\n                cards: [],\n                views: [],\n            },\n            globalTemplates: {\n                value: [{\n                    id: 'global-1',\n                    title: globalTemplateTitle,\n                    teamId: '0',\n                    icon: '🚴🏻‍♂️',\n                    cardProperties: [\n                        {id: 'global-id-5'},\n                    ],\n                    dateDisplayPropertyId: 'global-id-5',\n                    isTemplate: true,\n                    templateVersion: 2,\n                    properties: {\n                        trackingTemplateId: 'template_id_global',\n                    },\n                    createdBy: 'system',\n                }],\n            },\n        }\n        store = mockStateStore([], state)\n        jest.useRealTimers()\n    })\n    describe('not a focalboard Plugin', () => {\n        test('should match snapshot', () => {\n            const {container} = render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <BoardTemplateSelector onClose={jest.fn()}/>\n                </ReduxProvider>\n                ,\n            ), {wrapper: MemoryRouter})\n            expect(container).toMatchSnapshot()\n        })\n    })\n    describe('a focalboard Plugin', () => {\n        test('should match snapshot', () => {\n            const {container} = render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <BoardTemplateSelector onClose={jest.fn()}/>\n                </ReduxProvider>\n                ,\n            ), {wrapper: MemoryRouter})\n            expect(container).toMatchSnapshot()\n        })\n        test('should match snapshot without close', () => {\n            const {container} = render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <BoardTemplateSelector/>\n                </ReduxProvider>\n                ,\n            ), {wrapper: MemoryRouter})\n            expect(container).toMatchSnapshot()\n        })\n        test('should match snapshot with custom title and description', () => {\n            const {container} = render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <BoardTemplateSelector\n                        title='test-title'\n                        description='test-description'\n                    />\n                </ReduxProvider>\n                ,\n            ), {wrapper: MemoryRouter})\n            expect(container).toMatchSnapshot()\n        })\n        test('return BoardTemplateSelector and click close call the onClose callback', () => {\n            const onClose = jest.fn()\n            const {container} = render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <BoardTemplateSelector onClose={onClose}/>\n                </ReduxProvider>\n                ,\n            ), {wrapper: MemoryRouter})\n            const divCloseButton = container.querySelector('div.toolbar .CloseIcon')\n            expect(divCloseButton).not.toBeNull()\n            userEvent.click(divCloseButton!)\n            expect(onClose).toBeCalledTimes(1)\n        })\n        test('return BoardTemplateSelector and click new template', () => {\n            render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <BoardTemplateSelector onClose={jest.fn()}/>\n                </ReduxProvider>\n                ,\n            ), {wrapper: MemoryRouter})\n            const divNewTemplate = screen.getByText('Create new template').parentElement\n            expect(divNewTemplate).not.toBeNull()\n            userEvent.click(divNewTemplate!)\n            expect(mockedMutator.addEmptyBoardTemplate).toBeCalledTimes(1)\n        })\n        test('return BoardTemplateSelector and click empty board', async () => {\n            const newBoard = createBoard({id: 'new-board'} as Board)\n            mockedMutator.addEmptyBoard.mockResolvedValue({boards: [newBoard], blocks: []})\n\n            render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <BoardTemplateSelector onClose={jest.fn()}/>\n                </ReduxProvider>\n                ,\n            ), {wrapper: MemoryRouter})\n\n            const divEmptyboard = screen.getByText('Create empty board').parentElement\n            expect(divEmptyboard).not.toBeNull()\n            userEvent.click(divEmptyboard!)\n            expect(mockedMutator.addEmptyBoard).toBeCalledTimes(1)\n            await waitFor(() => expect(mockedMutator.updateBoard).toBeCalledWith(newBoard, newBoard, 'linked channel'))\n        })\n        test('return BoardTemplateSelector and click delete template icon', async () => {\n            const root = document.createElement('div')\n            root.setAttribute('id', 'focalboard-root-portal')\n            render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <BoardTemplateSelector onClose={jest.fn()}/>\n                </ReduxProvider>\n                ,\n            ), {wrapper: MemoryRouter, container: document.body.appendChild(root)})\n            const deleteIcon = screen.getByText(template1Title).parentElement?.querySelector('.DeleteIcon')\n            expect(deleteIcon).not.toBeNull()\n            act(() => {\n                userEvent.click(deleteIcon!)\n            })\n\n            const {getByText} = within(root)\n            const deleteConfirm = getByText('Delete')\n            expect(deleteConfirm).not.toBeNull()\n\n            await act(async () => {\n                await userEvent.click(deleteConfirm!)\n            })\n\n            expect(mockedMutator.deleteBoard).toBeCalledTimes(1)\n        })\n        test('return BoardTemplateSelector and click edit template icon', async () => {\n            const history = createMemoryHistory()\n            history.push = jest.fn()\n            render(wrapDNDIntl(\n                <Router history={history}>\n                    <ReduxProvider store={store}>\n                        <BoardTemplateSelector onClose={jest.fn()}/>\n                    </ReduxProvider>\n                </Router>,\n            ))\n            const editIcon = screen.getByText(template1Title).parentElement?.querySelector('.EditIcon')\n            expect(editIcon).not.toBeNull()\n            userEvent.click(editIcon!)\n        })\n        test('return BoardTemplateSelector and click to add board from template', async () => {\n            const newBoard = createBoard({id: 'new-board'} as Board)\n            mockedMutator.addBoardFromTemplate.mockResolvedValue({boards: [newBoard], blocks: []})\n\n            render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <BoardTemplateSelector onClose={jest.fn()}/>\n                </ReduxProvider>\n                ,\n            ), {wrapper: MemoryRouter})\n            const divBoardToSelect = screen.getByText(template1Title).parentElement\n            expect(divBoardToSelect).not.toBeNull()\n\n            act(() => {\n                userEvent.click(divBoardToSelect!)\n            })\n\n            const useTemplateButton = screen.getByText('Use this template').parentElement\n            expect(useTemplateButton).not.toBeNull()\n            act(() => {\n                userEvent.click(useTemplateButton!)\n            })\n\n            await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledTimes(1))\n            await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledWith(team1.id, expect.anything(), expect.anything(), expect.anything(), '1', team1.id))\n            await waitFor(() => expect(mockedMutator.updateBoard).toBeCalledWith(newBoard, newBoard, 'linked channel'))\n        })\n\n        test('return BoardTemplateSelector and click to add board from template with channelId', async () => {\n            const newBoard = createBoard({id: 'new-board'} as Board)\n            mockedMutator.addBoardFromTemplate.mockResolvedValue({boards: [newBoard], blocks: []})\n\n            render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <BoardTemplateSelector\n                        onClose={jest.fn()}\n                        channelId='test-channel'\n                    />\n                </ReduxProvider>\n                ,\n            ), {wrapper: MemoryRouter})\n            const divBoardToSelect = screen.getByText(template1Title).parentElement\n            expect(divBoardToSelect).not.toBeNull()\n\n            act(() => {\n                userEvent.click(divBoardToSelect!)\n            })\n\n            const useTemplateButton = screen.getByText('Use this template').parentElement\n            expect(useTemplateButton).not.toBeNull()\n            act(() => {\n                userEvent.click(useTemplateButton!)\n            })\n\n            await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledTimes(1))\n            await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledWith(team1.id, expect.anything(), expect.anything(), expect.anything(), '1', team1.id))\n            await waitFor(() => expect(mockedMutator.updateBoard).toBeCalledWith({...newBoard, channelId: 'test-channel'}, newBoard, 'linked channel'))\n        })\n\n        test('return BoardTemplateSelector and click to add board from global template', async () => {\n            const newBoard = createBoard({id: 'new-board'} as Board)\n            mockedMutator.addBoardFromTemplate.mockResolvedValue({boards: [newBoard], blocks: []})\n\n            render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <BoardTemplateSelector onClose={jest.fn()}/>\n                </ReduxProvider>\n                ,\n            ), {wrapper: MemoryRouter})\n            const divBoardToSelect = screen.getByText(globalTemplateTitle).parentElement\n            expect(divBoardToSelect).not.toBeNull()\n\n            act(() => {\n                userEvent.click(divBoardToSelect!)\n            })\n\n            const useTemplateButton = screen.getByText('Use this template').parentElement\n            expect(useTemplateButton).not.toBeNull()\n            act(() => {\n                userEvent.click(useTemplateButton!)\n            })\n            await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledTimes(1))\n            await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledWith(team1.id, expect.anything(), expect.anything(), expect.anything(), 'global-1', team1.id))\n            await waitFor(() => expect(mockedTelemetry.trackEvent).toBeCalledWith('boards', 'createBoardViaTemplate', {boardTemplateId: 'template_id_global'}))\n            await waitFor(() => expect(mockedMutator.updateBoard).toBeCalledWith(newBoard, newBoard, 'linked channel'))\n        })\n        test('should start product tour on choosing welcome template', async () => {\n            const newBoard = createBoard({id: 'new-board'} as Board)\n            mockedMutator.addBoardFromTemplate.mockResolvedValue({boards: [newBoard], blocks: []})\n\n            render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <BoardTemplateSelector onClose={jest.fn()}/>\n                </ReduxProvider>\n                ,\n            ), {wrapper: MemoryRouter})\n            const divBoardToSelect = screen.getByText('Welcome to Boards!').parentElement\n            expect(divBoardToSelect).not.toBeNull()\n\n            act(() => {\n                userEvent.click(divBoardToSelect!)\n            })\n\n            const useTemplateButton = screen.getByText('Use this template').parentElement\n            expect(useTemplateButton).not.toBeNull()\n            act(() => {\n                userEvent.click(useTemplateButton!)\n            })\n\n            await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledTimes(1))\n            await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledWith(team1.id, expect.anything(), expect.anything(), expect.anything(), '2', team1.id))\n            await waitFor(() => expect(mockedTelemetry.trackEvent).toBeCalledWith('boards', 'createBoardViaTemplate', {boardTemplateId: 'template_id_2'}))\n            await waitFor(() => expect(mockedMutator.updateBoard).toBeCalledWith(newBoard, newBoard, 'linked channel'))\n            expect(mockedOctoClient.patchUserConfig).toBeCalledWith('user-id-1', {\n                updatedFields: {\n                    onboardingTourStarted: '1',\n                    onboardingTourStep: '0',\n                    tourCategory: 'onboarding',\n                },\n            })\n        })\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/boardTemplateSelector/boardTemplateSelector.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useEffect, useState, useCallback, useMemo} from 'react'\nimport {FormattedMessage, useIntl} from 'react-intl'\nimport {useHistory, useRouteMatch} from 'react-router-dom'\nimport {useHotkeys} from 'react-hotkeys-hook'\n\nimport CompassIcon from '../../widgets/icons/compassIcon'\n\nimport {Board} from '../../blocks/board'\nimport IconButton from '../../widgets/buttons/iconButton'\nimport CloseIcon from '../../widgets/icons/close'\nimport Button from '../../widgets/buttons/button'\nimport octoClient from '../../octoClient'\nimport mutator from '../../mutator'\nimport {getTemplates, getCurrentBoardId} from '../../store/boards'\nimport {getCurrentTeam, Team} from '../../store/teams'\nimport {fetchGlobalTemplates, getGlobalTemplates} from '../../store/globalTemplates'\nimport {useAppDispatch, useAppSelector} from '../../store/hooks'\nimport TelemetryClient, {TelemetryActions, TelemetryCategory} from '../../telemetry/telemetryClient'\n\nimport './boardTemplateSelector.scss'\nimport {OnboardingBoardTitle} from '../cardDetail/cardDetail'\nimport {IUser, UserConfigPatch} from '../../user'\nimport {getMe, patchProps} from '../../store/users'\nimport {BaseTourSteps, TOUR_BASE} from '../onboardingTour'\n\nimport {Utils} from '../../utils'\n\nimport {Constants} from '../../constants'\n\nimport BoardTemplateSelectorPreview from './boardTemplateSelectorPreview'\nimport BoardTemplateSelectorItem from './boardTemplateSelectorItem'\n\ntype Props = {\n    title?: React.ReactNode\n    description?: React.ReactNode\n    onClose?: () => void\n    channelId?: string\n}\n\nconst BoardTemplateSelector = (props: Props) => {\n    const globalTemplates = useAppSelector<Board[]>(getGlobalTemplates) || []\n    const currentBoardId = useAppSelector<string>(getCurrentBoardId) || null\n    const currentTeam = useAppSelector<Team|null>(getCurrentTeam)\n    const {title, description, onClose} = props\n    const dispatch = useAppDispatch()\n    const intl = useIntl()\n    const history = useHistory()\n    const match = useRouteMatch<{boardId: string, viewId?: string}>()\n    const me = useAppSelector<IUser|null>(getMe)\n\n    useHotkeys('esc', () => props.onClose?.())\n\n    const showBoard = useCallback(async (boardId) => {\n        Utils.showBoard(boardId, match, history)\n        if (onClose) {\n            onClose()\n        }\n    }, [match, history, onClose])\n\n    useEffect(() => {\n        if (octoClient.teamId !== Constants.globalTeamId && globalTemplates.length === 0) {\n            dispatch(fetchGlobalTemplates())\n        }\n    }, [octoClient.teamId])\n\n    const onBoardTemplateDelete = useCallback((template: Board) => {\n        TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.DeleteBoardTemplate, {board: template.id})\n        mutator.deleteBoard(\n            template,\n            intl.formatMessage({id: 'BoardTemplateSelector.delete-template', defaultMessage: 'Delete'}),\n            async () => {},\n            async () => {\n                showBoard(template.id)\n            },\n        )\n    }, [showBoard])\n\n    const unsortedTemplates = useAppSelector(getTemplates)\n    const templates = useMemo(() => Object.values(unsortedTemplates).sort((a: Board, b: Board) => a.createAt - b.createAt), [unsortedTemplates])\n    const allTemplates = globalTemplates.concat(templates)\n\n    const resetTour = async () => {\n        TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.StartTour)\n\n        if (!me) {\n            return\n        }\n\n        const patch: UserConfigPatch = {\n            updatedFields: {\n                onboardingTourStarted: '1',\n                onboardingTourStep: BaseTourSteps.OPEN_A_CARD.toString(),\n                tourCategory: TOUR_BASE,\n            },\n        }\n\n        const patchedProps = await octoClient.patchUserConfig(me.id, patch)\n        if (patchedProps) {\n            await dispatch(patchProps(patchedProps))\n        }\n    }\n\n    const handleUseTemplate = async () => {\n        if (activeTemplate.teamId === '0') {\n            TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.CreateBoardViaTemplate, {boardTemplateId: activeTemplate.properties.trackingTemplateId as string, channelID: props.channelId})\n        }\n\n        const boardsAndBlocks = await mutator.addBoardFromTemplate(currentTeam?.id || Constants.globalTeamId, intl, showBoard, () => showBoard(currentBoardId), activeTemplate.id, currentTeam?.id)\n        const board = boardsAndBlocks.boards[0]\n        await mutator.updateBoard({...board, channelId: props.channelId || ''}, board, 'linked channel')\n        if (activeTemplate.title === OnboardingBoardTitle) {\n            resetTour()\n        }\n    }\n\n    const [activeTemplate, setActiveTemplate] = useState<Board>(allTemplates[0])\n\n    useEffect(() => {\n        if (!activeTemplate) {\n            setActiveTemplate(templates.concat(globalTemplates)[0])\n        }\n    }, [templates, globalTemplates])\n\n    if (!allTemplates) {\n        return <div/>\n    }\n\n    return (\n        <div className={`BoardTemplateSelector__container ${onClose ? '' : 'BoardTemplateSelector__container--page'}`}>\n            {onClose &&\n                <div\n                    onClick={onClose}\n                    className='BoardTemplateSelector__backdrop'\n                />}\n            <div className='BoardTemplateSelector'>\n                <div className='toolbar'>\n                    {onClose &&\n                        <IconButton\n                            size='medium'\n                            onClick={onClose}\n                            icon={<CloseIcon/>}\n                            title={'Close'}\n                        />}\n                </div>\n                <div className='header'>\n                    <h1 className='title'>\n                        {title || (\n                            <FormattedMessage\n                                id='BoardTemplateSelector.title'\n                                defaultMessage='Create a board'\n                            />\n                        )}\n                    </h1>\n                    <p className='description'>\n                        {description || (\n                            <FormattedMessage\n                                id='BoardTemplateSelector.description'\n                                defaultMessage='Add a board to the sidebar using any of the templates defined below or start from scratch.'\n                            />\n                        )}\n                    </p>\n                </div>\n                <div className='templates'>\n                    <div className='templates-sidebar'>\n                        <div className='templates-list'>\n                            <Button\n                                emphasis='link'\n                                size='medium'\n                                icon={<CompassIcon icon='plus'/>}\n                                className='new-template'\n                                onClick={() => mutator.addEmptyBoardTemplate(currentTeam?.id || '', intl, showBoard, () => showBoard(currentBoardId))}\n                            >\n                                <FormattedMessage\n                                    id='BoardTemplateSelector.add-template'\n                                    defaultMessage='Create new template'\n                                />\n                            </Button>\n                            {allTemplates.map((boardTemplate) => (\n                                <BoardTemplateSelectorItem\n                                    key={boardTemplate.id}\n                                    isActive={activeTemplate?.id === boardTemplate.id}\n                                    template={boardTemplate}\n                                    onSelect={setActiveTemplate}\n                                    onDelete={onBoardTemplateDelete}\n                                    onEdit={showBoard}\n                                />\n                            ))}\n                        </div>\n                        <div className='templates-sidebar__footer'>\n                            <Button\n                                emphasis='secondary'\n                                size={'medium'}\n                                icon={<CompassIcon icon='kanban'/>}\n                                onClick={async () => {\n                                    const boardsAndBlocks = await mutator.addEmptyBoard(currentTeam?.id || '', intl, showBoard, () => showBoard(currentBoardId))\n                                    const board = boardsAndBlocks.boards[0]\n                                    await mutator.updateBoard({...board, channelId: props.channelId || ''}, board, 'linked channel')\n                                }}\n                            >\n                                <FormattedMessage\n                                    id='BoardTemplateSelector.create-empty-board'\n                                    defaultMessage='Create empty board'\n                                />\n                            </Button>\n                        </div>\n                    </div>\n                    <div className='templates-content'>\n                        <div className='template-preview-box'>\n                            <BoardTemplateSelectorPreview activeTemplate={activeTemplate}/>\n                        </div>\n                        <div className='buttons'>\n                            <Button\n                                filled={true}\n                                size={'medium'}\n                                onClick={handleUseTemplate}\n                            >\n                                <FormattedMessage\n                                    id='BoardTemplateSelector.use-this-template'\n                                    defaultMessage='Use this template'\n                                />\n                            </Button>\n                        </div>\n                    </div>\n                </div>\n            </div>\n        </div>\n    )\n}\n\nexport default React.memo(BoardTemplateSelector)\n"
  },
  {
    "path": "webapp/src/components/boardTemplateSelector/boardTemplateSelectorItem.scss",
    "content": ".BoardTemplateSelectorItem {\n    cursor: pointer;\n    transition: all 100ms ease-out 0s;\n    font-weight: 600;\n    position: relative;\n    display: flex;\n    align-items: center;\n    padding: 8px 16px;\n    margin-bottom: 4px;\n    border-radius: var(--default-rad);\n    color: rgba(var(--center-channel-color-rgb), 0.64);\n    height: 40px;\n\n    .template-name {\n        white-space: nowrap;\n        overflow: hidden;\n        text-overflow: ellipsis;\n        width: 100%;\n    }\n\n    .template-icon {\n        margin-right: 10px;\n\n        i {\n            font-size: 16px;\n        }\n    }\n\n    &:hover {\n        background-color: rgba(var(--center-channel-color-rgb), 0.08);\n        color: rgba(var(--center-channel-color-rgb), 1);\n\n        .actions {\n            display: flex;\n        }\n    }\n\n    &.active {\n        background-color: rgba(var(--button-bg-rgb), 0.08);\n        color: rgba(var(--button-bg-rgb), 1);\n    }\n\n    .actions {\n        position: relative;\n        display: none;\n        right: -8px;\n    }\n}\n\n"
  },
  {
    "path": "webapp/src/components/boardTemplateSelector/boardTemplateSelectorItem.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {render, within, act, waitFor} from '@testing-library/react'\nimport userEvent from '@testing-library/user-event'\nimport React from 'react'\nimport {MockStoreEnhanced} from 'redux-mock-store'\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport {Board, MemberRole, IPropertyTemplate} from '../../blocks/board'\nimport {mockStateStore, wrapDNDIntl} from '../../testUtils'\n\nimport {IUser} from '../../user'\nimport {Team} from '../../store/teams'\n\nimport BoardTemplateSelectorItem from './boardTemplateSelectorItem'\n\njest.mock('react-router-dom', () => {\n    const originalModule = jest.requireActual('react-router-dom')\n\n    return {\n        ...originalModule,\n        useRouteMatch: jest.fn(() => {\n            return {url: '/'}\n        }),\n    }\n})\n\nconst groupProperty: IPropertyTemplate = {\n    id: 'group-prop-id',\n    name: 'name',\n    type: 'text',\n    options: [\n        {\n            color: 'propColorOrange',\n            id: 'property_value_id_1',\n            value: 'Q1',\n        },\n        {\n            color: 'propColorBlue',\n            id: 'property_value_id_2',\n            value: 'Q2',\n        },\n    ],\n}\n\njest.mock('../../utils')\njest.mock('../../mutator')\n\ndescribe('components/boardTemplateSelector/boardTemplateSelectorItem', () => {\n    const team1: Team = {\n        id: 'team-1',\n        title: 'Team 1',\n        signupToken: '',\n        updateAt: 0,\n        modifiedBy: 'user-1',\n    }\n\n    const template: Board = {\n        id: '1',\n        teamId: 'team-1',\n        title: 'Template 1',\n        createdBy: 'user-1',\n        modifiedBy: 'user-1',\n        createAt: 10,\n        updateAt: 20,\n        deleteAt: 0,\n        description: 'test',\n        showDescription: false,\n        type: 'board',\n        minimumRole: MemberRole.Editor,\n        isTemplate: true,\n        templateVersion: 0,\n        icon: '🚴🏻‍♂️',\n        cardProperties: [groupProperty],\n        properties: {},\n    }\n\n    const globalTemplate: Board = {\n        id: 'global-1',\n        title: 'Template global',\n        teamId: '0',\n        createdBy: 'system',\n        modifiedBy: 'system',\n        createAt: 10,\n        updateAt: 20,\n        deleteAt: 0,\n        type: 'board',\n        minimumRole: MemberRole.Editor,\n        icon: '🚴🏻‍♂️',\n        description: 'test',\n        showDescription: false,\n        cardProperties: [groupProperty],\n        isTemplate: true,\n        templateVersion: 2,\n        properties: {},\n    }\n\n    const me: IUser = {\n        id: 'user-id-1',\n        username: 'username_1',\n        nickname: '',\n        firstname: '',\n        lastname: '',\n        email: '',\n        props: {},\n        create_at: 0,\n        update_at: 0,\n        is_bot: false,\n        is_guest: false,\n        roles: 'system_user',\n    }\n\n    let store: MockStoreEnhanced<unknown, unknown>\n    beforeEach(() => {\n        jest.clearAllMocks()\n        const state = {\n            teams: {\n                current: team1,\n            },\n            boards: {\n                current: '1',\n                myBoardMemberships: {\n                    1: {userId: me.id, schemeAdmin: true},\n                },\n                templates: {\n                    [template.id]: template,\n                },\n            },\n        }\n        store = mockStateStore([], state)\n    })\n\n    test('should match snapshot', async () => {\n        const {container} = render(wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <BoardTemplateSelectorItem\n                    isActive={false}\n                    template={template}\n                    onSelect={jest.fn()}\n                    onDelete={jest.fn()}\n                    onEdit={jest.fn()}\n                />\n            </ReduxProvider>\n            ,\n        ))\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot when active', async () => {\n        const {container} = render(wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <BoardTemplateSelectorItem\n                    isActive={true}\n                    template={template}\n                    onSelect={jest.fn()}\n                    onDelete={jest.fn()}\n                    onEdit={jest.fn()}\n                />\n            </ReduxProvider>\n            ,\n        ))\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot with global template', async () => {\n        const {container} = render(wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <BoardTemplateSelectorItem\n                    isActive={false}\n                    template={globalTemplate}\n                    onSelect={jest.fn()}\n                    onDelete={jest.fn()}\n                    onEdit={jest.fn()}\n                />\n            </ReduxProvider>\n            ,\n        ))\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should trigger the onSelect (and not any other) when click the element', async () => {\n        const onSelect = jest.fn()\n        const onDelete = jest.fn()\n        const onEdit = jest.fn()\n        const {container} = render(wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <BoardTemplateSelectorItem\n                    isActive={false}\n                    template={template}\n                    onSelect={onSelect}\n                    onDelete={onDelete}\n                    onEdit={onEdit}\n                />\n            </ReduxProvider>\n            ,\n        ))\n        userEvent.click(container.querySelector('.BoardTemplateSelectorItem')!)\n        expect(onSelect).toBeCalledTimes(1)\n        expect(onSelect).toBeCalledWith(template)\n        expect(onDelete).not.toBeCalled()\n        expect(onEdit).not.toBeCalled()\n    })\n\n    test('should trigger the onDelete (and not any other) when click the delete icon', async () => {\n        const onSelect = jest.fn()\n        const onDelete = jest.fn()\n        const onEdit = jest.fn()\n        const {container} = render(wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <BoardTemplateSelectorItem\n                    isActive={false}\n                    template={template}\n                    onSelect={onSelect}\n                    onDelete={onDelete}\n                    onEdit={onEdit}\n                />\n            </ReduxProvider>\n            ,\n        ))\n        userEvent.click(container.querySelector('.BoardTemplateSelectorItem .EditIcon')!)\n        expect(onEdit).toBeCalledTimes(1)\n        expect(onEdit).toBeCalledWith(template.id)\n        expect(onSelect).not.toBeCalled()\n        expect(onDelete).not.toBeCalled()\n    })\n\n    test('should trigger the onDelete (and not any other) when click the delete icon and confirm', async () => {\n        const onSelect = jest.fn()\n        const onDelete = jest.fn()\n        const onEdit = jest.fn()\n\n        const root = document.createElement('div')\n        root.setAttribute('id', 'focalboard-root-portal')\n        render(wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <BoardTemplateSelectorItem\n                    isActive={false}\n                    template={template}\n                    onSelect={onSelect}\n                    onDelete={onDelete}\n                    onEdit={onEdit}\n                />\n            </ReduxProvider>\n            ,\n        ), {container: document.body.appendChild(root)})\n        act(() => {\n            userEvent.click(root.querySelector('.BoardTemplateSelectorItem .DeleteIcon')!)\n        })\n\n        expect(root).toMatchSnapshot()\n\n        const {getByText} = within(root)\n        act(() => {\n            userEvent.click(getByText('Delete')!)\n        })\n\n        await waitFor(async () => expect(onDelete).toBeCalledTimes(1))\n        await waitFor(async () => expect(onDelete).toBeCalledWith(template))\n        await waitFor(async () => expect(onSelect).not.toBeCalled())\n        await waitFor(async () => expect(onEdit).not.toBeCalled())\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/boardTemplateSelector/boardTemplateSelectorItem.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useCallback, useState} from 'react'\nimport {useIntl} from 'react-intl'\n\nimport {Board} from '../../blocks/board'\nimport CompassIcon from '../../widgets/icons/compassIcon'\nimport IconButton from '../../widgets/buttons/iconButton'\nimport DeleteIcon from '../../widgets/icons/delete'\nimport EditIcon from '../../widgets/icons/edit'\nimport DeleteBoardDialog from '../sidebar/deleteBoardDialog'\n\nimport BoardPermissionGate from '../permissions/boardPermissionGate'\n\nimport './boardTemplateSelectorItem.scss'\nimport {Constants, Permission} from '../../constants'\n\ntype Props = {\n    isActive: boolean\n    template: Board\n    onSelect: (template: Board) => void\n    onDelete: (template: Board) => void\n    onEdit: (templateId: string) => void\n}\n\nconst BoardTemplateSelectorItem = (props: Props) => {\n    const {isActive, template, onEdit, onDelete, onSelect} = props\n    const intl = useIntl()\n    const [deleteOpen, setDeleteOpen] = useState<boolean>(false)\n    const onClickHandler = useCallback(() => {\n        onSelect(template)\n    }, [onSelect, template])\n    const onEditHandler = useCallback((e: React.MouseEvent) => {\n        e.stopPropagation()\n        onEdit(template.id)\n    }, [onEdit, template])\n\n    return (\n        <div\n            className={isActive ? 'BoardTemplateSelectorItem active' : 'BoardTemplateSelectorItem'}\n            onClick={onClickHandler}\n        >\n            <span className='template-icon'>{template.icon || <CompassIcon icon='product-boards'/>}</span>\n            <span className='template-name'>{template.title || intl.formatMessage({id: 'View.NewTemplateTitle', defaultMessage: 'Untitled'})}</span>\n\n            {/* don't show template menu options for default templates */}\n            {template.createdBy !== Constants.SystemUserID &&\n                <div className='actions'>\n                    <BoardPermissionGate\n                        boardId={template.id}\n                        teamId={template.teamId}\n                        permissions={[Permission.DeleteBoard]}\n                    >\n                        <IconButton\n                            icon={<DeleteIcon/>}\n                            title={intl.formatMessage({id: 'BoardTemplateSelector.delete-template', defaultMessage: 'Delete'})}\n                            onClick={(e: React.MouseEvent) => {\n                                e.stopPropagation()\n                                setDeleteOpen(true)\n                            }}\n                        />\n                    </BoardPermissionGate>\n                    <BoardPermissionGate\n                        boardId={template.id}\n                        teamId={template.teamId}\n                        permissions={[Permission.ManageBoardCards, Permission.ManageBoardProperties]}\n                    >\n                        <IconButton\n                            icon={<EditIcon/>}\n                            title={intl.formatMessage({id: 'BoardTemplateSelector.edit-template', defaultMessage: 'Edit'})}\n                            onClick={onEditHandler}\n                        />\n                    </BoardPermissionGate>\n                </div>}\n            {deleteOpen &&\n            <DeleteBoardDialog\n                boardTitle={template.title}\n                onClose={() => setDeleteOpen(false)}\n                isTemplate={true}\n                onDelete={async () => {\n                    onDelete(template)\n                }}\n            />}\n        </div>\n    )\n}\n\nexport default React.memo(BoardTemplateSelectorItem)\n"
  },
  {
    "path": "webapp/src/components/boardTemplateSelector/boardTemplateSelectorPreview.scss",
    "content": ".BoardTemplateSelectorPreview {\n    position: relative;\n    transform-origin: top left;\n    transform: scale(0.8);\n    width: 126%;\n    height: 480px;\n    border-radius: var(--modal-rad);\n    pointer-events: none;\n\n    .ButtonWithMenu {\n        display: none;\n    }\n\n    .Kanban {\n        overflow: hidden;\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/boardTemplateSelector/boardTemplateSelectorPreview.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {render, waitFor} from '@testing-library/react'\nimport React from 'react'\nimport {MockStoreEnhanced} from 'redux-mock-store'\n\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport {IPropertyTemplate} from '../../blocks/board'\nimport {mockDOM, mockStateStore, wrapDNDIntl} from '../../testUtils'\n\nimport {TestBlockFactory} from '../../test/testBlockFactory'\n\nimport BoardTemplateSelectorPreview from './boardTemplateSelectorPreview'\n\njest.mock('react-router-dom', () => {\n    const originalModule = jest.requireActual('react-router-dom')\n\n    return {\n        ...originalModule,\n        useRouteMatch: jest.fn(() => {\n            return {url: '/'}\n        }),\n    }\n})\n\nconst groupProperty: IPropertyTemplate = {\n    id: 'group-prop-id',\n    name: 'name',\n    type: 'text',\n    options: [\n        {\n            color: 'propColorOrange',\n            id: 'property_value_id_1',\n            value: 'Q1',\n        },\n        {\n            color: 'propColorBlue',\n            id: 'property_value_id_2',\n            value: 'Q2',\n        },\n    ],\n}\n\njest.mock('../../octoClient', () => {\n    return {\n        getAllBlocks: jest.fn(() => Promise.resolve([\n            {\n                id: '1',\n                teamId: 'team',\n                title: 'Template',\n                type: 'board',\n                icon: '🚴🏻‍♂️',\n                cardProperties: [groupProperty],\n                dateDisplayPropertyId: 'id-5',\n            },\n            {\n                id: '2',\n                workspaceId: 'workspace',\n                title: 'View',\n                type: 'view',\n                fields: {\n                    groupById: 'group-prop-id',\n                    viewType: 'board',\n                    visibleOptionIds: ['group-prop-id'],\n                    hiddenOptionIds: [],\n                    visiblePropertyIds: ['group-prop-id'],\n                    sortOptions: [],\n                    kanbanCalculations: {},\n                },\n            },\n            {\n                id: '3',\n                workspaceId: 'workspace',\n                title: 'Card',\n                type: 'card',\n                fields: {\n                    icon: '🚴🏻‍♂️',\n                    properties: {\n                        'group-prop-id': 'test',\n                    },\n                },\n                limited: false,\n            },\n        ])),\n    }\n})\njest.mock('../../utils')\njest.mock('../../mutator')\n\ndescribe('components/boardTemplateSelector/boardTemplateSelectorPreview', () => {\n    const template1Title = 'Template 1'\n    const globalTemplateTitle = 'Template Global'\n    const boardTitle = 'Board 1'\n    let store: MockStoreEnhanced<unknown, unknown>\n    beforeAll(mockDOM)\n    beforeEach(() => {\n        jest.clearAllMocks()\n\n        const board = TestBlockFactory.createBoard()\n        board.id = '2'\n        board.title = boardTitle\n        board.teamId = 'team-id'\n        board.icon = '🚴🏻‍♂️'\n        board.cardProperties = [groupProperty]\n        const activeView = TestBlockFactory.createBoardView(board)\n        activeView.fields.defaultTemplateId = 'defaultTemplateId'\n\n        const state = {\n            searchText: {value: ''},\n            users: {\n                me: {\n                    id: 'user-id',\n                },\n                myConfig: {\n                    onboardingTourStarted: {value: false},\n                },\n            },\n            cards: {\n                templates: [],\n                cards: {\n                    card_id_1: {title: 'Create a new card'},\n                },\n                current: 'card_id_1',\n            },\n            views: {\n                views: {\n                    boardView: activeView,\n                },\n                current: 'boardView',\n            },\n            contents: {contents: []},\n            comments: {comments: []},\n            teams: {\n                current: {id: 'team-id'},\n            },\n            boards: {\n                current: board.id,\n                boards: {\n                    [board.id]: board,\n                },\n                templates: [\n                    {\n                        id: '1',\n                        teamId: 'team-id',\n                        title: template1Title,\n                        icon: '🚴🏻‍♂️',\n                        cardProperties: [groupProperty],\n                        dateDisplayPropertyId: 'id-5',\n                    },\n                ],\n                cards: [],\n                views: [],\n                myBoardMemberships: {\n                    [board.id]: {userId: 'user-id', schemeAdmin: true},\n                },\n            },\n            globalTemplates: {\n                value: [{\n                    id: 'global-1',\n                    title: globalTemplateTitle,\n                    teamId: '0',\n                    icon: '🚴🏻‍♂️',\n                    cardProperties: [\n                        {id: 'global-id-5'},\n                    ],\n                    dateDisplayPropertyId: 'global-id-5',\n                }],\n            },\n            limits: {\n                limits: {\n                    views: 0,\n                },\n            },\n        }\n        store = mockStateStore([], state)\n    })\n\n    test('should match snapshot', async () => {\n        const {container} = render(wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <BoardTemplateSelectorPreview activeTemplate={(store.getState() as any).boards.templates[0]}/>\n            </ReduxProvider>\n            ,\n        ))\n        await waitFor(() => expect(container.querySelector('.top-head')).not.toBeNull())\n        expect(container).toMatchSnapshot()\n    })\n    test('should be null without activeTemplate', () => {\n        const {container} = render(wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <BoardTemplateSelectorPreview activeTemplate={null}/>\n            </ReduxProvider>\n            ,\n        ))\n        expect(container).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/boardTemplateSelector/boardTemplateSelectorPreview.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useEffect, useState, useMemo} from 'react'\n\nimport {Board} from '../../blocks/board'\nimport {Card} from '../../blocks/card'\nimport {BoardView} from '../../blocks/boardView'\nimport octoClient from '../../octoClient'\nimport {getVisibleAndHiddenGroups} from '../../boardUtils'\n\nimport ViewHeader from '../viewHeader/viewHeader'\nimport ViewTitle from '../viewTitle'\nimport Kanban from '../kanban/kanban'\nimport Table from '../table/table'\nimport CalendarFullView from '../calendar/fullCalendar'\nimport Gallery from '../gallery/gallery'\n\nimport './boardTemplateSelectorPreview.scss'\n\ntype Props = {\n    activeTemplate: Board|null\n}\n\nconst BoardTemplateSelectorPreview = (props: Props) => {\n    const {activeTemplate} = props\n    const [activeView, setActiveView] = useState<BoardView|null>(null)\n    const [activeTemplateCards, setActiveTemplateCards] = useState<Card[]>([])\n\n    useEffect(() => {\n        let isSubscribed = true\n        if (activeTemplate) {\n            setActiveTemplateCards([])\n            setActiveView(null)\n            setActiveTemplateCards([])\n            octoClient.getAllBlocks(activeTemplate.id).then((blocks) => {\n                if (isSubscribed) {\n                    const cards = blocks.filter((b) => b.type === 'card')\n                    const views = blocks.filter((b) => b.type === 'view').sort((a, b) => a.title.localeCompare(b.title))\n                    if (views.length > 0) {\n                        setActiveView(views[0] as BoardView)\n                    }\n                    if (cards.length > 0) {\n                        setActiveTemplateCards(cards as Card[])\n                    }\n                }\n            })\n        }\n        return () => {\n            isSubscribed = false\n        }\n    }, [activeTemplate])\n\n    const dateDisplayProperty = useMemo(() => {\n        return activeTemplate?.cardProperties.find((o) => o.id === activeView?.fields.dateDisplayPropertyId)\n    }, [activeView, activeTemplate])\n\n    const groupByProperty = useMemo(() => {\n        return activeTemplate?.cardProperties.find((o) => o.id === activeView?.fields.groupById) || activeTemplate?.cardProperties[0]\n    }, [activeView, activeTemplate])\n\n    const {visible: visibleGroups, hidden: hiddenGroups} = useMemo(() => {\n        if (!activeView) {\n            return {visible: [], hidden: []}\n        }\n        return getVisibleAndHiddenGroups(activeTemplateCards, activeView.fields.visibleOptionIds, activeView?.fields.hiddenOptionIds, groupByProperty)\n    }, [activeTemplateCards, activeView, groupByProperty])\n\n    if (!activeTemplate) {\n        return null\n    }\n\n    return (\n        <div className='BoardTemplateSelectorPreview'>\n            {activeView &&\n            <div className='top-head'>\n                <ViewTitle\n                    key={activeTemplate?.id + activeTemplate?.title}\n                    board={activeTemplate}\n                    readonly={true}\n                />\n                <ViewHeader\n                    board={activeTemplate}\n                    activeView={activeView}\n                    cards={activeTemplateCards}\n                    views={[activeView]}\n                    groupByProperty={groupByProperty}\n                    addCard={() => null}\n                    addCardFromTemplate={() => null}\n                    addCardTemplate={() => null}\n                    editCardTemplate={() => null}\n                    readonly={false}\n                />\n            </div>}\n\n            {activeView?.fields.viewType === 'board' &&\n            <Kanban\n                board={activeTemplate}\n                activeView={activeView}\n                cards={activeTemplateCards}\n                groupByProperty={groupByProperty}\n                visibleGroups={visibleGroups}\n                hiddenGroups={hiddenGroups}\n                selectedCardIds={[]}\n                readonly={false}\n                onCardClicked={() => null}\n                addCard={() => Promise.resolve()}\n                addCardFromTemplate={() => Promise.resolve()}\n                showCard={() => null}\n                hiddenCardsCount={0}\n                showHiddenCardCountNotification={() => null}\n            />}\n            {activeView?.fields.viewType === 'table' &&\n            <Table\n                board={activeTemplate}\n                activeView={activeView}\n                cards={activeTemplateCards}\n                groupByProperty={groupByProperty}\n                views={[activeView]}\n                visibleGroups={visibleGroups}\n                selectedCardIds={[]}\n                readonly={false}\n                cardIdToFocusOnRender={''}\n                onCardClicked={() => null}\n                addCard={() => Promise.resolve()}\n                showCard={() => null}\n                hiddenCardsCount={0}\n                showHiddenCardCountNotification={() => null}\n            />}\n            {activeView?.fields.viewType === 'gallery' &&\n            <Gallery\n                board={activeTemplate}\n                cards={activeTemplateCards}\n                activeView={activeView}\n                readonly={false}\n                selectedCardIds={[]}\n                onCardClicked={() => null}\n                addCard={() => Promise.resolve()}\n                hiddenCardsCount={0}\n                showHiddenCardCountNotification={() => null}\n            />}\n            {activeView?.fields.viewType === 'calendar' &&\n            <CalendarFullView\n                board={activeTemplate}\n                cards={activeTemplateCards}\n                activeView={activeView}\n                readonly={false}\n                dateDisplayProperty={dateDisplayProperty}\n                showCard={() => null}\n                addCard={() => Promise.resolve()}\n            />}\n        </div>\n    )\n}\n\nexport default React.memo(BoardTemplateSelectorPreview)\n\n"
  },
  {
    "path": "webapp/src/components/boardsSwitcher/boardsSwitcher.scss",
    "content": ".BoardsSwitcherWrapper {\n    display: flex;\n    flex-direction: row;\n    padding: 0 16px;\n\n    .BoardsSwitcher {\n        display: flex;\n        flex-direction: row;\n        background-color: rgba(var(--sidebar-text-rgb), 0.08);\n        color: rgba(var(--sidebar-text-rgb), 0.56);\n        flex: 1;\n        padding: 6px 8px 6px 4px;\n        gap: 4px;\n        border-radius: 4px;\n        cursor: pointer;\n        height: 28px;\n        align-items: center;\n        font-size: 12px;\n\n        &:hover {\n            background: rgba(var(--sidebar-text-rgb), 0.16);\n            color: var(--sidebar-text);\n        }\n    }\n\n    .CompassIcon.icon-magnify.CompassIcon {\n        font-size: 16px;\n    }\n\n    .add-board-icon {\n        border-radius: 28px;\n        margin-left: 8px;\n        width: 28px;\n        height: 28px;\n        flex: 0 0 28px;\n        background-color: rgba(var(--sidebar-text-rgb), 0.08);\n        color: rgba(var(--sidebar-text-rgb), 0.72);\n\n        &:hover {\n            background-color: rgba(var(--sidebar-text-rgb), 0.16);\n            color: var(--sidebar-text);\n        }\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/boardsSwitcher/boardsSwitcher.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useEffect, useState} from 'react'\n\nimport {FormattedMessage, useIntl} from 'react-intl'\n\nimport Search from '../../widgets/icons/search'\nimport CreateCategory from '../createCategory/createCategory'\nimport {useAppSelector} from '../../store/hooks'\n\nimport {\n    getOnboardingTourCategory,\n    getOnboardingTourStep,\n} from '../../store/users'\nimport {getCurrentCard} from '../../store/cards'\n\nimport './boardsSwitcher.scss'\nimport BoardSwitcherDialog from '../boardsSwitcherDialog/boardSwitcherDialog'\nimport {Utils} from '../../utils'\nimport {Constants} from '../../constants'\nimport {TOUR_SIDEBAR, SidebarTourSteps} from '../../components/onboardingTour'\n\nimport SearchForBoardsTourStep from '../../components/onboardingTour/searchForBoards/searchForBoards'\n\nconst BoardsSwitcher = (): JSX.Element => {\n    const intl = useIntl()\n\n    const [showSwitcher, setShowSwitcher] = useState<boolean>(false)\n    const onboardingTourCategory = useAppSelector(getOnboardingTourCategory)\n    const [showCreateCategoryModal, setShowCreateCategoryModal] = useState(false)\n    const onboardingTourStep = useAppSelector(getOnboardingTourStep)\n    const currentCard = useAppSelector(getCurrentCard)\n    const noCardOpen = !currentCard\n\n    const shouldViewSearchForBoardsTour = noCardOpen &&\n                                       onboardingTourCategory === TOUR_SIDEBAR &&\n                                       onboardingTourStep === SidebarTourSteps.SEARCH_FOR_BOARDS.toString()\n\n    // We need this keyboard handling (copied from Mattermost webapp) instead of\n    // using react-hotkeys-hook as react-hotkeys-hook is unable to handle keyboard shortcuts that\n    // the browser uses when the user is focused in an input field.\n    //\n    // For example, you press Cmd + k, then type something in the search input field. Pressing Cmd + k again\n    // is expected to close the board switcher, however, with react-hotkeys-hook it doesn't.\n    // This is because Cmd + k is a Firefox shortcut and react-hotkeys-hook is\n    // unable to override it if the user is focused on any input field.\n    const handleQuickSwitchKeyPress = (e: KeyboardEvent) => {\n        if (Utils.cmdOrCtrlPressed(e) && !e.shiftKey && Utils.isKeyPressed(e, Constants.keyCodes.K)) {\n            if (!e.altKey) {\n                e.preventDefault()\n                setShowSwitcher((show) => !show)\n            }\n        }\n    }\n\n    const handleEscKeyPress = (e: KeyboardEvent) => {\n        if (Utils.isKeyPressed(e, Constants.keyCodes.ESC)) {\n            e.preventDefault()\n            setShowSwitcher(false)\n        }\n    }\n\n    useEffect(() => {\n        document.addEventListener('keydown', handleQuickSwitchKeyPress)\n        document.addEventListener('keydown', handleEscKeyPress)\n\n        // cleanup function\n        return () => {\n            document.removeEventListener('keydown', handleQuickSwitchKeyPress)\n            document.removeEventListener('keydown', handleEscKeyPress)\n        }\n    }, [])\n\n    return (\n        <div className='BoardsSwitcherWrapper'>\n            <div\n                className='BoardsSwitcher'\n                onClick={() => setShowSwitcher(true)}\n            >\n                <Search/>\n                <div>\n                    <span>\n                        {intl.formatMessage({id: 'BoardsSwitcher.Title', defaultMessage: 'Find Boards'})}\n                    </span>\n                </div>\n            </div>\n            {shouldViewSearchForBoardsTour && <div><SearchForBoardsTourStep/></div>}\n            {\n                showSwitcher &&\n                <BoardSwitcherDialog onClose={() => setShowSwitcher(false)}/>\n            }\n\n            {\n                showCreateCategoryModal && (\n                    <CreateCategory\n                        onClose={() => setShowCreateCategoryModal(false)}\n                        title={(\n                            <FormattedMessage\n                                id='SidebarCategories.CategoryMenu.CreateNew'\n                                defaultMessage='Create New Category'\n                            />\n                        )}\n                    />\n                )\n            }\n        </div>\n    )\n}\n\nexport default BoardsSwitcher\n"
  },
  {
    "path": "webapp/src/components/boardsSwitcherDialog/__snapshots__/boardSwitcherDialog.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`component/BoardSwitcherDialog base case 1`] = `\n<div>\n  <div\n    class=\"Dialog dialog-back BoardSwitcherDialog size--medium\"\n  >\n    <div\n      class=\"backdrop\"\n    />\n    <div\n      class=\"wrapper\"\n    >\n      <div\n        class=\"dialog\"\n        role=\"dialog\"\n      >\n        <div\n          class=\"toolbar\"\n        >\n          <div>\n            <h1\n              class=\"dialog-title\"\n            >\n              <div>\n                Find Boards\n              </div>\n            </h1>\n            <h5\n              class=\"dialog-subtitle\"\n            >\n              <div>\n                Type to find a board. Use \n                <b>\n                  UP/DOWN\n                </b>\n                 to browse. \n                <b>\n                  ENTER\n                </b>\n                 to select, \n                <b>\n                  ESC\n                </b>\n                 to dismiss\n              </div>\n            </h5>\n          </div>\n          <div\n            class=\"toolbar--right\"\n          >\n            <button\n              aria-label=\"Close dialog\"\n              class=\"IconButton dialog__close size--medium\"\n              title=\"Close dialog\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-close CloseIcon\"\n              />\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"BoardSwitcherDialogBody\"\n        >\n          <div\n            class=\"head\"\n          >\n            <div\n              class=\"queryWrapper\"\n            >\n              <i\n                class=\"CompassIcon icon-magnify MagnifyIcon\"\n              />\n              <input\n                class=\"searchQuery\"\n                maxlength=\"100\"\n                placeholder=\"Search for boards\"\n                type=\"text\"\n              />\n            </div>\n          </div>\n          <div\n            class=\"searchResults\"\n          >\n            \n            \n            <div\n              class=\"noResults introScreen\"\n            >\n              <div\n                class=\"iconWrapper\"\n              >\n                <i\n                  class=\"CompassIcon icon-magnify MagnifyIcon\"\n                />\n              </div>\n              <h4\n                class=\"text-heading4\"\n              >\n                Search for boards\n              </h4>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/boardsSwitcherDialog/boardSwitcherDialog.scss",
    "content": ".blockSearchResult {\n    display: flex;\n    gap: 12px;\n    overflow: hidden;\n    flex-direction: row;\n    width: 100%;\n\n    .CompassIcon {\n        font-size: 18px;\n        color: rgba(var(--center-channel-color-rgb), 0.56);\n    }\n\n    span {\n        display: inline-block;\n        height: 100%;\n        overflow: hidden;\n        text-overflow: ellipsis;\n        white-space: nowrap;\n    }\n\n    .resultTitle {\n        max-width: 60%;\n    }\n\n    .teamTitle {\n        right: auto;\n        margin-left: auto;\n        color: rgba(var(--center-channel-color-rgb), 0.56);\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/boardsSwitcherDialog/boardSwitcherDialog.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport {MockStoreEnhanced} from 'redux-mock-store'\n\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport {render} from '@testing-library/react'\n\nimport {createMemoryHistory, History} from 'history'\n\nimport {Router} from 'react-router-dom'\n\nimport {Team} from '../../store/teams'\nimport {TestBlockFactory} from '../../test/testBlockFactory'\n\nimport {mockStateStore, wrapDNDIntl} from '../../testUtils'\n\nimport BoardSwitcherDialog from './boardSwitcherDialog'\n\ndescribe('component/BoardSwitcherDialog', () => {\n    const team1: Team = {\n        id: 'team-id-1',\n        title: 'Dunder Mifflin',\n        signupToken: '',\n        updateAt: 0,\n        modifiedBy: 'michael-scott',\n    }\n\n    const team2: Team = {\n        id: 'team-id-2',\n        title: 'Michael Scott Paper Company',\n        signupToken: '',\n        updateAt: 0,\n        modifiedBy: 'michael-scott',\n    }\n\n    const me = TestBlockFactory.createUser()\n\n    const state = {\n        users: {\n            me,\n        },\n        teams: {\n            allTeams: [team1, team2],\n            current: team1,\n        },\n    }\n\n    let store: MockStoreEnhanced<unknown, unknown>\n    let history: History\n\n    beforeEach(() => {\n        store = mockStateStore([], state)\n        history = createMemoryHistory()\n    })\n\n    test('base case', () => {\n        const onCloseHandler = jest.fn()\n        const component = wrapDNDIntl(\n            <Router history={history}>\n                <ReduxProvider store={store}>\n                    <BoardSwitcherDialog onClose={onCloseHandler}/>\n                </ReduxProvider>\n            </Router>,\n        )\n\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/boardsSwitcherDialog/boardSwitcherDialog.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {ReactNode, useRef, createRef, useState, useEffect, MutableRefObject} from 'react'\n\nimport './boardSwitcherDialog.scss'\nimport {useIntl} from 'react-intl'\n\nimport {generatePath, useHistory, useRouteMatch} from 'react-router-dom'\n\nimport octoClient from '../../octoClient'\nimport SearchDialog from '../searchDialog/searchDialog'\nimport Globe from '../../widgets/icons/globe'\nimport LockOutline from '../../widgets/icons/lockOutline'\nimport {useAppSelector} from '../../store/hooks'\nimport {getAllTeams, getCurrentTeam, Team} from '../../store/teams'\nimport {getMe} from '../../store/users'\nimport {Utils} from '../../utils'\nimport {BoardTypeOpen, BoardTypePrivate} from '../../blocks/board'\nimport {Constants} from '../../constants'\n\ntype Props = {\n    onClose: () => void\n}\n\nconst BoardSwitcherDialog = (props: Props): JSX.Element => {\n    const [selected, setSelected] = useState<number>(-1)\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    const [refs, setRefs] = useState<MutableRefObject<any>>(useRef([]))\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    const [IDs, setIDs] = useState<any>({})\n    const intl = useIntl()\n    const team = useAppSelector(getCurrentTeam)\n    const me = useAppSelector(getMe)\n    const title = intl.formatMessage({id: 'FindBoardsDialog.Title', defaultMessage: 'Find Boards'})\n    const subTitle = intl.formatMessage(\n        {\n            id: 'FindBoardsDialog.SubTitle',\n            defaultMessage: 'Type to find a board. Use <b>UP/DOWN</b> to browse. <b>ENTER</b> to select, <b>ESC</b> to dismiss',\n        },\n        {\n            b: (...chunks) => <b>{chunks}</b>,\n        },\n    )\n\n    const match = useRouteMatch<{boardId: string, viewId: string, cardId?: string}>()\n    const history = useHistory()\n\n    const selectBoard = async (teamId: string, boardId: string): Promise<void> => {\n        if (!me) {\n            return\n        }\n        const newPath = generatePath(Utils.getBoardPagePath(match.path), {...match.params, teamId, boardId, viewId: undefined})\n        history.push(newPath)\n        props.onClose()\n    }\n\n    const teamsById: Record<string, Team> = {}\n    useAppSelector(getAllTeams).forEach((t) => {\n        teamsById[t.id] = t\n    })\n\n    const searchHandler = async (query: string): Promise<ReactNode[]> => {\n        if (query.trim().length === 0 || !team) {\n            return []\n        }\n\n        const items = await octoClient.searchAll(query)\n        const untitledBoardTitle = intl.formatMessage({id: 'ViewTitle.untitled-board', defaultMessage: 'Untitled board'})\n        refs.current = items.map((_, i) => refs.current[i] ?? createRef())\n        setRefs(refs)\n        return items.map((item, i) => {\n            const resultTitle = item.title || untitledBoardTitle\n            const teamTitle = teamsById[item.teamId].title\n            // eslint-disable-next-line @typescript-eslint/no-explicit-any\n            setIDs((prevIDs: any) => ({\n                ...prevIDs,\n                [i]: [item.teamId, item.id],\n            }))\n            return (\n                <div\n                    key={item.id}\n                    className='blockSearchResult'\n                    onClick={() => selectBoard(item.teamId, item.id)}\n                    ref={refs.current[i]}\n                >\n                    {item.type === BoardTypeOpen && <Globe/>}\n                    {item.type === BoardTypePrivate && <LockOutline/>}\n                    <span className='resultTitle'>{resultTitle}</span>\n                    <span className='teamTitle'>{teamTitle}</span>\n                </div>\n            )\n        })\n    }\n\n    const handleEnterKeyPress = (e: KeyboardEvent) => {\n        if (Utils.isKeyPressed(e, Constants.keyCodes.ENTER) && selected > -1) {\n            e.preventDefault()\n            const [teamId, id] = IDs[selected]\n            selectBoard(teamId, id)\n        }\n    }\n\n    useEffect(() => {\n        if (selected >= 0) {\n            refs.current[selected].current.parentElement.focus()\n        }\n\n        document.addEventListener('keydown', handleEnterKeyPress)\n\n        // cleanup function\n        return () => {\n            document.removeEventListener('keydown', handleEnterKeyPress)\n        }\n    }, [selected, refs, IDs])\n\n    return (\n        <SearchDialog\n            onClose={props.onClose}\n            title={title}\n            subTitle={subTitle}\n            searchHandler={searchHandler}\n            selected={selected}\n            setSelected={(n: number) => setSelected(n)}\n        />\n    )\n}\n\nexport default BoardSwitcherDialog\n"
  },
  {
    "path": "webapp/src/components/calculations/__snapshots__/calculation.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/calculations/Calculation should match snapshot - count 1`] = `\n<div>\n  <div\n    class=\"Calculation count fooClass  hovered\"\n    style=\"width: 100px;\"\n    tabindex=\"0\"\n  >\n    <span\n      class=\"calculationLabel\"\n    >\n      Count\n    </span>\n    <span\n      class=\"calculationValue\"\n    >\n      2\n    </span>\n  </div>\n</div>\n`;\n\nexports[`components/calculations/Calculation should match snapshot - countUniqueValue 1`] = `\n<div>\n  <div\n    class=\"Calculation countUniqueValue fooClass  hovered\"\n    style=\"width: 100px;\"\n    tabindex=\"0\"\n  >\n    <span\n      class=\"calculationLabel\"\n    >\n      Unique\n    </span>\n    <span\n      class=\"calculationValue\"\n    >\n      1\n    </span>\n  </div>\n</div>\n`;\n\nexports[`components/calculations/Calculation should match snapshot - countValue 1`] = `\n<div>\n  <div\n    class=\"Calculation countValue fooClass  hovered\"\n    style=\"width: 100px;\"\n    tabindex=\"0\"\n  >\n    <span\n      class=\"calculationLabel\"\n    >\n      Values\n    </span>\n    <span\n      class=\"calculationValue\"\n    >\n      1\n    </span>\n  </div>\n</div>\n`;\n\nexports[`components/calculations/Calculation should match snapshot - none 1`] = `\n<div>\n  <div\n    class=\"Calculation none fooClass  hovered\"\n    style=\"width: 100px;\"\n    tabindex=\"0\"\n  >\n    <span\n      class=\"calculationLabel\"\n    >\n      Calculate\n    </span>\n    <i\n      class=\"CompassIcon icon-chevron-up ChevronUpIcon\"\n    />\n  </div>\n</div>\n`;\n\nexports[`components/calculations/Calculation should match snapshot - option change 1`] = `\n<div>\n  <div\n    class=\"Calculation none fooClass menuOpen hovered\"\n    style=\"width: 100px;\"\n    tabindex=\"0\"\n  >\n    <div>\n      <div\n        class=\"CalculationOptions css-b62m3t-container\"\n      >\n        <span\n          class=\"css-1f43avz-a11yText-A11yText\"\n          id=\"react-select-2-live-region\"\n        />\n        <span\n          aria-atomic=\"false\"\n          aria-live=\"polite\"\n          aria-relevant=\"additions text\"\n          class=\"css-1f43avz-a11yText-A11yText\"\n          role=\"log\"\n        />\n        <div\n          class=\"CalculationOptions__control css-1s59geg-Control\"\n        >\n          <div\n            class=\"CalculationOptions__value-container CalculationOptions__value-container--has-value css-1kliayw-ValueContainer\"\n          >\n            <div\n              class=\"CalculationOptions__single-value css-3hkq9s-singleValue\"\n            >\n              Calculate\n            </div>\n            <input\n              aria-activedescendant=\"\"\n              aria-autocomplete=\"list\"\n              aria-expanded=\"false\"\n              aria-haspopup=\"true\"\n              aria-readonly=\"true\"\n              class=\"css-mohuvp-dummyInput-DummyInput\"\n              id=\"react-select-2-input\"\n              inputmode=\"none\"\n              role=\"combobox\"\n              tabindex=\"0\"\n              value=\"\"\n            />\n          </div>\n          <div\n            class=\"CalculationOptions__indicators css-1hb7zxy-IndicatorsContainer\"\n          >\n            <div\n              aria-hidden=\"true\"\n              class=\"CalculationOptions__indicator CalculationOptions__clear-indicator css-31haax-indicatorContainer\"\n            >\n              <svg\n                aria-hidden=\"true\"\n                class=\"css-tj5bde-Svg\"\n                focusable=\"false\"\n                height=\"20\"\n                viewBox=\"0 0 20 20\"\n                width=\"20\"\n              >\n                <path\n                  d=\"M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z\"\n                />\n              </svg>\n            </div>\n            <span\n              class=\"CalculationOptions__indicator-separator css-1uei4ir-indicatorSeparator\"\n            />\n            <div\n              aria-hidden=\"true\"\n              class=\"CalculationOptions__indicator CalculationOptions__dropdown-indicator css-y45573-indicatorContainer\"\n            >\n              <i\n                class=\"CompassIcon icon-chevron-up ChevronUpIcon\"\n              />\n            </div>\n          </div>\n        </div>\n        <input\n          name=\"calculation_options\"\n          type=\"hidden\"\n          value=\"none\"\n        />\n      </div>\n    </div>\n    <span\n      class=\"calculationLabel\"\n    >\n      Calculate\n    </span>\n    <i\n      class=\"CompassIcon icon-chevron-up ChevronUpIcon\"\n    />\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/calculations/__snapshots__/options.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/calculations/Options should match snapshot 1`] = `\n<div>\n  <div\n    class=\"CalculationOptions css-b62m3t-container\"\n  >\n    <span\n      class=\"css-1f43avz-a11yText-A11yText\"\n      id=\"react-select-2-live-region\"\n    />\n    <span\n      aria-atomic=\"false\"\n      aria-live=\"polite\"\n      aria-relevant=\"additions text\"\n      class=\"css-1f43avz-a11yText-A11yText\"\n      role=\"log\"\n    />\n    <div\n      class=\"CalculationOptions__control css-1s59geg-Control\"\n    >\n      <div\n        class=\"CalculationOptions__value-container CalculationOptions__value-container--has-value css-1kliayw-ValueContainer\"\n      >\n        <div\n          class=\"CalculationOptions__single-value css-3hkq9s-singleValue\"\n        >\n          Calculate\n        </div>\n        <input\n          aria-activedescendant=\"\"\n          aria-autocomplete=\"list\"\n          aria-expanded=\"false\"\n          aria-haspopup=\"true\"\n          aria-readonly=\"true\"\n          class=\"css-mohuvp-dummyInput-DummyInput\"\n          id=\"react-select-2-input\"\n          inputmode=\"none\"\n          role=\"combobox\"\n          tabindex=\"0\"\n          value=\"\"\n        />\n      </div>\n      <div\n        class=\"CalculationOptions__indicators css-1hb7zxy-IndicatorsContainer\"\n      >\n        <div\n          aria-hidden=\"true\"\n          class=\"CalculationOptions__indicator CalculationOptions__clear-indicator css-31haax-indicatorContainer\"\n        >\n          <svg\n            aria-hidden=\"true\"\n            class=\"css-tj5bde-Svg\"\n            focusable=\"false\"\n            height=\"20\"\n            viewBox=\"0 0 20 20\"\n            width=\"20\"\n          >\n            <path\n              d=\"M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z\"\n            />\n          </svg>\n        </div>\n        <span\n          class=\"CalculationOptions__indicator-separator css-1uei4ir-indicatorSeparator\"\n        />\n        <div\n          aria-hidden=\"true\"\n          class=\"CalculationOptions__indicator CalculationOptions__dropdown-indicator css-y45573-indicatorContainer\"\n        >\n          <i\n            class=\"CompassIcon icon-chevron-up ChevronUpIcon\"\n          />\n        </div>\n      </div>\n    </div>\n    <input\n      name=\"calculation_options\"\n      type=\"hidden\"\n      value=\"none\"\n    />\n  </div>\n</div>\n`;\n\nexports[`components/calculations/Options should match snapshot menu open 1`] = `\n<div>\n  <div\n    class=\"CalculationOptions css-b62m3t-container\"\n  >\n    <span\n      class=\"css-1f43avz-a11yText-A11yText\"\n      id=\"react-select-3-live-region\"\n    />\n    <span\n      aria-atomic=\"false\"\n      aria-live=\"polite\"\n      aria-relevant=\"additions text\"\n      class=\"css-1f43avz-a11yText-A11yText\"\n      role=\"log\"\n    />\n    <div\n      class=\"CalculationOptions__control CalculationOptions__control--menu-is-open css-1s59geg-Control\"\n    >\n      <div\n        class=\"CalculationOptions__value-container CalculationOptions__value-container--has-value css-1kliayw-ValueContainer\"\n      >\n        <div\n          class=\"CalculationOptions__single-value css-3hkq9s-singleValue\"\n        >\n          Calculate\n        </div>\n        <input\n          aria-activedescendant=\"\"\n          aria-autocomplete=\"list\"\n          aria-controls=\"react-select-3-listbox\"\n          aria-expanded=\"true\"\n          aria-haspopup=\"true\"\n          aria-readonly=\"true\"\n          class=\"css-mohuvp-dummyInput-DummyInput\"\n          id=\"react-select-3-input\"\n          inputmode=\"none\"\n          role=\"combobox\"\n          tabindex=\"0\"\n          value=\"\"\n        />\n      </div>\n      <div\n        class=\"CalculationOptions__indicators css-1hb7zxy-IndicatorsContainer\"\n      >\n        <div\n          aria-hidden=\"true\"\n          class=\"CalculationOptions__indicator CalculationOptions__clear-indicator css-31haax-indicatorContainer\"\n        >\n          <svg\n            aria-hidden=\"true\"\n            class=\"css-tj5bde-Svg\"\n            focusable=\"false\"\n            height=\"20\"\n            viewBox=\"0 0 20 20\"\n            width=\"20\"\n          >\n            <path\n              d=\"M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z\"\n            />\n          </svg>\n        </div>\n        <span\n          class=\"CalculationOptions__indicator-separator css-1uei4ir-indicatorSeparator\"\n        />\n        <div\n          aria-hidden=\"true\"\n          class=\"CalculationOptions__indicator CalculationOptions__dropdown-indicator css-y45573-indicatorContainer\"\n        >\n          <i\n            class=\"CompassIcon icon-chevron-up ChevronUpIcon\"\n          />\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"CalculationOptions__menu css-45h7mv-menu\"\n    >\n      <div\n        aria-multiselectable=\"false\"\n        class=\"CalculationOptions__menu-list css-1d1qzc4-MenuList\"\n        id=\"react-select-3-listbox\"\n        role=\"listbox\"\n      >\n        <div\n          aria-disabled=\"false\"\n          aria-selected=\"false\"\n          class=\"CalculationOptions__option css-x3yilo-option\"\n          id=\"react-select-3-option-0\"\n          role=\"option\"\n          tabindex=\"-1\"\n        >\n          Count\n        </div>\n        <div\n          aria-disabled=\"false\"\n          aria-selected=\"false\"\n          class=\"CalculationOptions__option css-x3yilo-option\"\n          id=\"react-select-3-option-1\"\n          role=\"option\"\n          tabindex=\"-1\"\n        >\n          Max\n        </div>\n      </div>\n    </div>\n    <input\n      name=\"calculation_options\"\n      type=\"hidden\"\n      value=\"none\"\n    />\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/calculations/calculation.scss",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\n.Calculation {\n    cursor: pointer;\n    transition: opacity 0.1s ease-in;\n\n    &.disabled {\n        cursor: unset;\n    }\n\n    &.none {\n        opacity: 0;\n\n        .calculationLabel {\n            text-transform: capitalize;\n            letter-spacing: 0;\n            font-size: 14px;\n            margin-right: 0;\n        }\n\n        &.hovered {\n            opacity: 0.64;\n        }\n\n        &.menuOpen {\n            opacity: 1;\n        }\n    }\n\n    .calculationLabel {\n        text-transform: uppercase;\n        font-size: 12px;\n        opacity: 0.8;\n        margin-right: 8px;\n        letter-spacing: 0.8px;\n    }\n\n    .calculationValue {\n        top: -1px;\n        position: relative;\n        font-weight: bold;\n        max-height: 100%;\n        overflow: hidden;\n        text-overflow: ellipsis;\n    }\n\n    .ChevronUpIcon {\n        font-size: 22px;\n        line-height: 14px;\n        padding: 0;\n    }\n\n    .CalculationOptions {\n        right: 0;\n        top: 0;\n        position: absolute;\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/calculations/calculation.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\n\nimport {render} from '@testing-library/react'\n\nimport userEvent from '@testing-library/user-event'\n\nimport {TestBlockFactory} from '../../test/testBlockFactory'\nimport {wrapIntl} from '../../testUtils'\n\nimport {TableCalculationOptions} from '../table/calculation/tableCalculationOptions'\n\nimport {ColumnResizeProvider} from '../table/tableColumnResizeContext'\n\nimport Calculation from './calculation'\n\ndescribe('components/calculations/Calculation', () => {\n    const board = TestBlockFactory.createBoard()\n\n    const card = TestBlockFactory.createCard(board)\n    card.fields.properties.property_2 = 'Foo'\n    card.fields.properties.property_3 = 'Bar'\n    card.fields.properties.property_4 = 'Baz'\n\n    const card2 = TestBlockFactory.createCard(board)\n    card2.fields.properties.property_2 = 'Lorem'\n    card2.fields.properties.property_3 = ''\n    card2.fields.properties.property_4 = 'Baz'\n\n    const Wrapper: React.FC = ({children}) => {\n        return wrapIntl(\n            <ColumnResizeProvider\n                columnWidths={{}}\n                onResizeColumn={jest.fn()}\n            >\n                {children}\n            </ColumnResizeProvider>,\n        )\n    }\n\n    test('should match snapshot - none', () => {\n        const {container} = render(\n            <Wrapper>\n                <Calculation\n                    class={'fooClass'}\n                    value={'none'}\n                    menuOpen={false}\n                    onMenuClose={() => {}}\n                    onMenuOpen={() => {}}\n                    onChange={() => {}}\n                    cards={[card, card2]}\n                    hovered={true}\n                    property={{\n                        id: 'property_2',\n                        name: '',\n                        type: 'text',\n                        options: [],\n                    }}\n                    optionsComponent={TableCalculationOptions}\n                />\n            </Wrapper>,\n        )\n\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot - count', () => {\n        const {container} = render(\n            <Wrapper>\n                <Calculation\n                    class={'fooClass'}\n                    value={'count'}\n                    menuOpen={false}\n                    onMenuClose={() => {}}\n                    onMenuOpen={() => {}}\n                    onChange={() => {}}\n                    cards={[card, card2]}\n                    hovered={true}\n                    property={{\n                        id: 'property_2',\n                        name: '',\n                        type: 'text',\n                        options: [],\n                    }}\n                    optionsComponent={TableCalculationOptions}\n                />\n            </Wrapper>,\n        )\n\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot - countValue', () => {\n        const {container} = render(\n            <Wrapper>\n                <Calculation\n                    class={'fooClass'}\n                    value={'countValue'}\n                    menuOpen={false}\n                    onMenuClose={() => {}}\n                    onMenuOpen={() => {}}\n                    onChange={() => {}}\n                    cards={[card, card2]}\n                    hovered={true}\n                    property={{\n                        id: 'property_3',\n                        name: '',\n                        type: 'text',\n                        options: [],\n                    }}\n                    optionsComponent={TableCalculationOptions}\n                />\n            </Wrapper>,\n        )\n\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot - countUniqueValue', () => {\n        const {container} = render(\n            <Wrapper>\n                <Calculation\n                    class={'fooClass'}\n                    value={'countUniqueValue'}\n                    menuOpen={false}\n                    onMenuClose={() => {}}\n                    onMenuOpen={() => {}}\n                    onChange={() => {}}\n                    cards={[card, card2]}\n                    hovered={true}\n                    property={{\n                        id: 'property_4',\n                        name: '',\n                        type: 'text',\n                        options: [],\n                    }}\n                    optionsComponent={TableCalculationOptions}\n                />\n            </Wrapper>,\n        )\n\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot - option change', () => {\n        const onMenuOpen = jest.fn()\n        const onMenuClose = jest.fn()\n        const onChange = jest.fn()\n\n        const {container} = render(\n            <Wrapper>\n                <Calculation\n                    class={'fooClass'}\n                    value={'none'}\n                    menuOpen={true}\n                    onMenuClose={onMenuClose}\n                    onMenuOpen={onMenuOpen}\n                    onChange={onChange}\n                    cards={[card, card2]}\n                    hovered={true}\n                    property={{\n                        id: 'property_2',\n                        name: '',\n                        type: 'text',\n                        options: [],\n                    }}\n                    optionsComponent={TableCalculationOptions}\n                />\n            </Wrapper>,\n        )\n\n        const countMenuOption = container.querySelector('#react-select-2-option-1')\n        userEvent.click(countMenuOption as Element)\n        expect(container).toMatchSnapshot()\n        expect(onMenuOpen).not.toBeCalled()\n        expect(onMenuClose).toBeCalled()\n        expect(onChange).toBeCalled()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/calculations/calculation.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\nimport {useIntl} from 'react-intl'\n\nimport {Card} from '../../blocks/card'\n\nimport {IPropertyTemplate} from '../../blocks/board'\n\nimport ChevronUp from '../../widgets/icons/chevronUp'\n\nimport {useColumnResize} from '../table/tableColumnResizeContext'\n\nimport {Constants} from '../../constants'\n\nimport {CommonCalculationOptionProps, Options, optionDisplayNameString} from './options'\n\nimport Calculations from './calculations'\n\nimport './calculation.scss'\n\ntype Props = {\n    class: string\n    value: string\n    menuOpen: boolean\n    onMenuClose: () => void\n    onMenuOpen: () => void\n    onChange: (value: string) => void\n    cards: readonly Card[]\n    property: IPropertyTemplate\n    hovered: boolean\n    optionsComponent: React.ComponentType<CommonCalculationOptionProps>\n}\n\nconst Calculation = (props: Props): JSX.Element => {\n    const value = props.value || Options.none.value\n    const valueOption = Options[value]\n    const intl = useIntl()\n    const columnResize = useColumnResize()\n\n    const option = (\n        <props.optionsComponent\n            value={value}\n            menuOpen={props.menuOpen}\n            onClose={props.onMenuClose}\n            onChange={props.onChange}\n            property={props.property}\n        />\n    )\n\n    return (\n\n        // tabindex is needed to make onBlur work on div.\n        // See this for more details-\n        // https://stackoverflow.com/questions/47308081/onblur-event-is-not-firing\n        <div\n            className={`Calculation ${value} ${props.class} ${props.menuOpen ? 'menuOpen' : ''} ${props.hovered ? 'hovered' : ''}`}\n            onClick={() => (props.menuOpen ? props.onMenuClose() : props.onMenuOpen())}\n            tabIndex={0}\n            onBlur={props.onMenuClose}\n            style={{width: columnResize.width(props.property.id)}}\n            ref={(ref) => columnResize.updateRef(Constants.tableCalculationId, props.property.id, ref)}\n        >\n            {\n                props.menuOpen && (\n                    <div>\n                        {option}\n                    </div>\n                )\n            }\n\n            <span className='calculationLabel'>\n                {optionDisplayNameString(valueOption!, intl)}\n            </span>\n\n            {\n                value === Options.none.value &&\n                <ChevronUp/>\n            }\n\n            {\n                value !== Options.none.value &&\n                <span className='calculationValue'>\n                    {Calculations[value] ? Calculations[value](props.cards, props.property, intl) : ''}\n                </span>\n            }\n\n        </div>\n    )\n}\n\nexport default Calculation\n"
  },
  {
    "path": "webapp/src/components/calculations/calculations.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {createIntl} from 'react-intl'\n\nimport {TestBlockFactory} from '../../test/testBlockFactory'\n\nimport {IPropertyTemplate} from '../../blocks/board'\n\nimport Calculations from './calculations'\n\ndescribe('components/calculations/calculation logic', () => {\n    const board = TestBlockFactory.createBoard()\n\n    const card1 = TestBlockFactory.createCard(board)\n    card1.fields.properties.property_text = 'lorem ipsum'\n    card1.fields.properties.property_number = '100'\n    card1.fields.properties.property_email = 'foobar@example.com'\n    card1.fields.properties.property_phone = '+1 1234567890'\n    card1.fields.properties.property_url = 'example.com'\n    card1.fields.properties.property_select = 'option_id_1'\n    card1.fields.properties.property_multiSelect = ['option_id_1', 'option_id_2', 'option_id_3']\n    card1.fields.properties.property_date = '1625553000000'\n    card1.fields.properties.property_person = 'user_id_1'\n    card1.fields.properties.property_checkbox = 'true'\n    card1.createdBy = 'user_id_1'\n    card1.createAt = 1625553000000\n    card1.modifiedBy = 'user_id_1'\n    card1.updateAt = 1625553000000\n\n    const card2 = TestBlockFactory.createCard(board)\n    card2.fields.properties.property_text = 'foo bar'\n    card2.fields.properties.property_number = '-30'\n    card2.fields.properties.property_email = 'loremipsum@example.com'\n    card2.fields.properties.property_phone = '+1 111'\n    card2.fields.properties.property_url = 'example.com/foobar'\n    card2.fields.properties.property_select = 'option_id_2'\n    card2.fields.properties.property_multiSelect = ['option_id_2', 'option_id_3']\n    card2.fields.properties.property_date = '1625639400000'\n    card2.fields.properties.property_person = 'user_id_2'\n    card2.fields.properties.property_checkbox = 'false'\n    card2.createAt = 1625639400000\n    card2.createdBy = 'user_id_2'\n    card2.updateAt = 1625639400000\n    card2.modifiedBy = 'user_id_2'\n\n    // card with all properties unset\n    const card3 = TestBlockFactory.createCard(board)\n    card3.createAt = 1625639400000\n    card3.createdBy = 'user_id_2'\n    card3.updateAt = 1625639400000\n    card3.modifiedBy = 'user_id_2'\n\n    // clone of card 1. All properties exactly same as that of card 1\n    const card4 = TestBlockFactory.createCard(board)\n    card4.fields.properties.property_text = 'lorem ipsum'\n    card4.fields.properties.property_number = '100'\n    card4.fields.properties.property_email = 'foobar@example.com'\n    card4.fields.properties.property_phone = '+1 1234567890'\n    card4.fields.properties.property_url = 'example.com'\n    card4.fields.properties.property_select = 'option_id_1'\n    card4.fields.properties.property_multiSelect = ['option_id_1', 'option_id_2', 'option_id_3']\n    card4.fields.properties.property_date = '1625553000000'\n    card4.fields.properties.property_person = 'user_id_1'\n    card4.fields.properties.property_checkbox = 'true'\n    card4.createAt = 1625553000000\n    card4.createdBy = 'user_id_1'\n    card4.updateAt = 1625553000000\n    card4.modifiedBy = 'user_id_1'\n\n    // card with all empty values\n    const card5 = TestBlockFactory.createCard(board)\n    card5.fields.properties.property_text = ''\n    card5.fields.properties.property_number = ''\n    card5.fields.properties.property_email = ''\n    card5.fields.properties.property_phone = ''\n    card5.fields.properties.property_url = ''\n    card5.fields.properties.property_select = ''\n    card5.fields.properties.property_multiSelect = []\n    card5.fields.properties.property_date = ''\n    card5.fields.properties.property_person = ''\n    card5.fields.properties.property_checkbox = ''\n\n    // clone of card 3 but created / updated 1 second later\n    const card6 = TestBlockFactory.createCard(board)\n    card6.createAt = 1625639401000\n    card6.createdBy = 'user_id_2'\n    card6.updateAt = 1625639401000\n    card6.modifiedBy = 'user_id_2'\n\n    // clone of card 3 but created / updated 1 minute later\n    const card7 = TestBlockFactory.createCard(board)\n    card7.createAt = 1625639460000\n    card7.createdBy = 'user_id_2'\n    card7.updateAt = 1625639460000\n    card7.modifiedBy = 'user_id_2'\n\n    const cards = [card1, card2, card3, card4]\n\n    const properties: Record<string, IPropertyTemplate> = {\n        text: {id: 'property_text', type: 'text', name: '', options: []},\n        number: {id: 'property_number', type: 'number', name: '', options: []},\n        email: {id: 'property_email', type: 'email', name: '', options: []},\n        phone: {id: 'property_phone', type: 'phone', name: '', options: []},\n        url: {id: 'property_url', type: 'url', name: '', options: []},\n        select: {\n            id: 'property_select',\n            type: 'select',\n            name: '',\n            options: [\n                {\n                    color: 'propColorYellow',\n                    id: 'option_id_1',\n                    value: 'Option 1',\n                },\n                {\n                    color: 'propColorBlue',\n                    id: 'option_id_2',\n                    value: 'Option 2',\n                },\n            ],\n        },\n        multiSelect: {\n            id: 'property_multiSelect',\n            type: 'multiSelect',\n            name: '',\n            options: [\n                {\n                    color: 'propColorYellow',\n                    id: 'option_id_1',\n                    value: 'Option 1',\n                },\n                {\n                    color: 'propColorBlue',\n                    id: 'option_id_2',\n                    value: 'Option 2',\n                },\n                {\n                    color: 'propColorBlue',\n                    id: 'option_id_3',\n                    value: 'Option 3',\n                },\n            ],\n        },\n        date: {id: 'property_date', type: 'date', name: '', options: []},\n        person: {id: 'property_person', type: 'person', name: '', options: []},\n        checkbox: {id: 'property_checkbox', type: 'checkbox', name: '', options: []},\n        createdTime: {id: 'property_createdTime', type: 'createdTime', name: '', options: []},\n        createdBy: {id: 'property_createdBy', type: 'createdBy', name: '', options: []},\n        updatedTime: {id: 'property_lastUpdatedTime', type: 'updatedTime', name: '', options: []},\n        updatedBy: {id: 'property_lastUpdatedBy', type: 'updatedBy', name: '', options: []},\n    }\n\n    const autofilledProperties = new Set([properties.createdBy, properties.createdTime, properties.updatedBy, properties.updatedTime])\n\n    const intl = createIntl({locale: 'en-us'})\n\n    // testing count\n    Object.values(properties).forEach((property) => {\n        it(`should correctly count for property type \"${property.type}\"`, function() {\n            expect(Calculations.count(cards, property, intl)).toBe('4')\n        })\n    })\n\n    // testing count empty\n    Object.values(properties).filter((p) => !autofilledProperties.has(p)).forEach((property) => {\n        it(`should correctly count empty for property type \"${property.type}\"`, function() {\n            expect(Calculations.countEmpty(cards, property, intl)).toBe('1')\n        })\n    })\n\n    // testing percent empty\n    Object.values(properties).filter((p) => !autofilledProperties.has(p)).forEach((property) => {\n        it(`should correctly compute empty percent for property type \"${property.type}\"`, function() {\n            expect(Calculations.percentEmpty(cards, property, intl)).toBe('25%')\n        })\n    })\n\n    // testing count not empty\n    Object.values(properties).filter((p) => !autofilledProperties.has(p)).forEach((property) => {\n        it(`should correctly count not empty for property type \"${property.type}\"`, function() {\n            expect(Calculations.countNotEmpty(cards, property, intl)).toBe('3')\n        })\n    })\n\n    // testing percent not empty\n    Object.values(properties).filter((p) => !autofilledProperties.has(p)).forEach((property) => {\n        it(`should correctly compute not empty percent for property type \"${property.type}\"`, function() {\n            expect(Calculations.percentNotEmpty(cards, property, intl)).toBe('75%')\n        })\n    })\n\n    // testing countValues\n    const countValueTests: Record<string, string> = {\n        text: '3',\n        number: '3',\n        email: '3',\n        phone: '3',\n        url: '3',\n        select: '3',\n        multiSelect: '8',\n        date: '3',\n        person: '3',\n        checkbox: '3',\n        createdTime: '4',\n        createdBy: '4',\n        updatedTime: '4',\n        updatedBy: '4',\n    }\n    Object.keys(countValueTests).forEach((propertyType) => {\n        it(`should correctly count values for property type ${propertyType}`, function() {\n            expect(Calculations.countValue(cards, properties[propertyType]!, intl)).toBe(countValueTests[propertyType]!)\n        })\n    })\n\n    // testing countUniqueValue\n    const countUniqueValueTests: Record<string, string> = {\n        text: '2',\n        number: '2',\n        email: '2',\n        phone: '2',\n        url: '2',\n        select: '2',\n        multiSelect: '3',\n        date: '2',\n        person: '2',\n        checkbox: '2',\n        createdTime: '2',\n        createdBy: '2',\n        updatedTime: '2',\n        updatedBy: '2',\n    }\n    Object.keys(countUniqueValueTests).forEach((propertyType) => {\n        it(`should correctly count unique values for property type ${propertyType}`, function() {\n            expect(Calculations.countUniqueValue(cards, properties[propertyType]!, intl)).toBe(countUniqueValueTests[propertyType]!)\n        })\n    })\n\n    test('countUniqueValue for cards created 1 second apart', () => {\n        const result = Calculations.countUniqueValue([card3, card6], properties.createdTime, intl)\n        expect(result).toBe('1')\n    })\n\n    test('countUniqueValue for cards updated 1 second apart', () => {\n        const result = Calculations.countUniqueValue([card3, card6], properties.updatedTime, intl)\n        expect(result).toBe('1')\n    })\n\n    test('countUniqueValue for cards created 1 minute apart', () => {\n        const result = Calculations.countUniqueValue([card3, card7], properties.createdTime, intl)\n        expect(result).toBe('2')\n    })\n\n    test('countUniqueValue for cards updated 1 minute apart', () => {\n        const result = Calculations.countUniqueValue([card3, card7], properties.updatedTime, intl)\n        expect(result).toBe('2')\n    })\n\n    test('countChecked for cards', () => {\n        const result = Calculations.countChecked(cards, properties.checkbox, intl)\n        expect(result).toBe('3')\n    })\n\n    test('countChecked for cards, one set, other unset', () => {\n        const result = Calculations.countChecked([card1, card5], properties.checkbox, intl)\n        expect(result).toBe('1')\n    })\n\n    test('countUnchecked for cards', () => {\n        const result = Calculations.countUnchecked(cards, properties.checkbox, intl)\n        expect(result).toBe('1')\n    })\n\n    test('countUnchecked for cards, two set, one unset', () => {\n        const result = Calculations.countUnchecked([card1, card1, card5], properties.checkbox, intl)\n        expect(result).toBe('1')\n    })\n\n    test('countUnchecked for cards, one set, other unset', () => {\n        const result = Calculations.countUnchecked([card1, card5], properties.checkbox, intl)\n        expect(result).toBe('1')\n    })\n\n    test('countUnchecked for cards, one set, two unset', () => {\n        const result = Calculations.countUnchecked([card1, card5, card5], properties.checkbox, intl)\n        expect(result).toBe('2')\n    })\n\n    test('percentChecked for cards', () => {\n        const result = Calculations.percentChecked(cards, properties.checkbox, intl)\n        expect(result).toBe('75%')\n    })\n\n    test('percentUnchecked for cards', () => {\n        const result = Calculations.percentUnchecked(cards, properties.checkbox, intl)\n        expect(result).toBe('25%')\n    })\n\n    test('sum', () => {\n        const result = Calculations.sum(cards, properties.number, intl)\n        expect(result).toBe('170')\n    })\n\n    test('average', () => {\n        const result = Calculations.average(cards, properties.number, intl)\n        expect(result).toBe('56.67')\n    })\n\n    test('median', () => {\n        const result = Calculations.median(cards, properties.number, intl)\n        expect(result).toBe('100')\n    })\n\n    test('min', () => {\n        const result = Calculations.min(cards, properties.number, intl)\n        expect(result).toBe('-30')\n    })\n\n    test('max', () => {\n        const result = Calculations.max(cards, properties.number, intl)\n        expect(result).toBe('100')\n    })\n\n    test('range', () => {\n        const result = Calculations.range(cards, properties.number, intl)\n        expect(result).toBe('-30 - 100')\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/calculations/calculations.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {IntlShape} from 'react-intl'\n\nimport moment from 'moment'\n\nimport {Card} from '../../blocks/card'\nimport {IPropertyTemplate} from '../../blocks/board'\nimport {Utils} from '../../utils'\nimport {Constants} from '../../constants'\nimport {DateProperty} from '../../properties/date/date'\n\nconst ROUNDED_DECIMAL_PLACES = 2\n\nfunction getCardProperty(card: Card, property: IPropertyTemplate): string | string[] | number {\n    if (property.id === Constants.titleColumnId) {\n        return card.title\n    }\n\n    switch (property.type) {\n    case ('createdBy'): {\n        return card.createdBy\n    }\n    case ('createdTime'): {\n        return fixTimestampToMinutesAccuracy(card.createAt)\n    }\n    case ('updatedBy'): {\n        return card.modifiedBy\n    }\n    case ('updatedTime'): {\n        return fixTimestampToMinutesAccuracy(card.updateAt)\n    }\n    default: {\n        return card.fields.properties[property.id]\n    }\n    }\n}\n\nfunction fixTimestampToMinutesAccuracy(timestamp: number) {\n    // For timestamps that are formatted as hour/minute strings on the UI, we throw away the (milli)seconds\n    // so that things like counting unique values work intuitively\n    return timestamp - (timestamp % 60000)\n}\n\nfunction cardsWithValue(cards: readonly Card[], property: IPropertyTemplate): Card[] {\n    return cards.\n        filter((card) => Boolean(getCardProperty(card, property)))\n}\n\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nfunction count(cards: readonly Card[], property: IPropertyTemplate): string {\n    return String(cards.length)\n}\n\nfunction countEmpty(cards: readonly Card[], property: IPropertyTemplate): string {\n    return String(cards.length - cardsWithValue(cards, property).length)\n}\n\n// return count of card which have this property value as not null \\\\ undefined \\\\ ''\nfunction countNotEmpty(cards: readonly Card[], property: IPropertyTemplate): string {\n    return String(cardsWithValue(cards, property).length)\n}\n\nfunction percentEmpty(cards: readonly Card[], property: IPropertyTemplate): string {\n    if (cards.length === 0) {\n        return ''\n    }\n    return String((((cards.length - cardsWithValue(cards, property).length) / cards.length) * 100).toFixed(0)) + '%'\n}\n\nfunction percentNotEmpty(cards: readonly Card[], property: IPropertyTemplate): string {\n    if (cards.length === 0) {\n        return ''\n    }\n    return String(((cardsWithValue(cards, property).length / cards.length) * 100).toFixed(0)) + '%'\n}\n\nfunction countValueHelper(cards: readonly Card[], property: IPropertyTemplate): number {\n    let values = 0\n\n    if (property.type === 'multiSelect') {\n        cardsWithValue(cards, property).\n            forEach((card) => {\n                values += (getCardProperty(card, property) as string[]).length\n            })\n    } else {\n        values = cardsWithValue(cards, property).length\n    }\n\n    return values\n}\n\nfunction countValue(cards: readonly Card[], property: IPropertyTemplate): string {\n    return String(countValueHelper(cards, property))\n}\n\nfunction countChecked(cards: readonly Card[], property: IPropertyTemplate): string {\n    return countValue(cards, property)\n}\n\nfunction countUnchecked(cards: readonly Card[], property: IPropertyTemplate): string {\n    return String(cards.length - countValueHelper(cards, property))\n}\n\nfunction percentChecked(cards: readonly Card[], property: IPropertyTemplate): string {\n    const total = cards.length\n    const checked = countValueHelper(cards, property)\n\n    return String(Math.round((checked * 100) / total)) + '%'\n}\n\nfunction percentUnchecked(cards: readonly Card[], property: IPropertyTemplate): string {\n    const total = cards.length\n    const checked = countValueHelper(cards, property)\n\n    return String(Math.round(((total - checked) * 100) / total)) + '%'\n}\n\nfunction countUniqueValue(cards: readonly Card[], property: IPropertyTemplate): string {\n    const valueMap: Map<string | string[], boolean> = new Map()\n\n    cards.forEach((card) => {\n        const value = getCardProperty(card, property)\n\n        if (!value) {\n            return\n        }\n\n        if (property.type === 'multiSelect') {\n            (value as string[]).forEach((v) => valueMap.set(v, true))\n        } else {\n            valueMap.set(String(value), true)\n        }\n    })\n\n    return String(valueMap.size)\n}\n\nfunction sum(cards: readonly Card[], property: IPropertyTemplate): string {\n    let result = 0\n\n    cardsWithValue(cards, property).\n        forEach((card) => {\n            result += parseFloat(getCardProperty(card, property) as string)\n        })\n\n    return String(Utils.roundTo(result, ROUNDED_DECIMAL_PLACES))\n}\n\nfunction average(cards: readonly Card[], property: IPropertyTemplate): string {\n    const numCards = cardsWithValue(cards, property).length\n    if (numCards === 0) {\n        return '0'\n    }\n\n    const result = parseFloat(sum(cards, property))\n    const avg = result / numCards\n    return String(Utils.roundTo(avg, ROUNDED_DECIMAL_PLACES))\n}\n\nfunction median(cards: readonly Card[], property: IPropertyTemplate): string {\n    const sorted = cardsWithValue(cards, property).\n        sort((a, b) => {\n            if (!getCardProperty(a, property)) {\n                return 1\n            }\n\n            if (!getCardProperty(b, property)) {\n                return -1\n            }\n\n            const aValue = parseFloat(getCardProperty(a, property) as string || '0')\n            const bValue = parseFloat(getCardProperty(b, property) as string || '0')\n\n            return aValue - bValue\n        })\n\n    if (sorted.length === 0) {\n        return '0'\n    }\n\n    let result: number\n\n    if (sorted.length % 2 === 0) {\n        const val1 = parseFloat(getCardProperty(sorted[sorted.length / 2], property) as string)\n        const val2 = parseFloat(getCardProperty(sorted[(sorted.length / 2) - 1], property) as string)\n        result = (val1 + val2) / 2\n    } else {\n        result = parseFloat(getCardProperty(sorted[Math.floor(sorted.length / 2)], property) as string)\n    }\n\n    return String(Utils.roundTo(result, ROUNDED_DECIMAL_PLACES))\n}\n\nfunction min(cards: readonly Card[], property: IPropertyTemplate): string {\n    let result = Number.POSITIVE_INFINITY\n    cards.forEach((card) => {\n        if (!getCardProperty(card, property)) {\n            return\n        }\n\n        const value = parseFloat(getCardProperty(card, property) as string)\n        result = Math.min(result, value)\n    })\n\n    return String(result === Number.POSITIVE_INFINITY ? '0' : String(Utils.roundTo(result, ROUNDED_DECIMAL_PLACES)))\n}\n\nfunction max(cards: readonly Card[], property: IPropertyTemplate): string {\n    let result = Number.NEGATIVE_INFINITY\n    cards.forEach((card) => {\n        if (!getCardProperty(card, property)) {\n            return\n        }\n\n        const value = parseFloat(getCardProperty(card, property) as string)\n        result = Math.max(result, value)\n    })\n\n    return String(result === Number.NEGATIVE_INFINITY ? '0' : String(Utils.roundTo(result, ROUNDED_DECIMAL_PLACES)))\n}\n\nfunction range(cards: readonly Card[], property: IPropertyTemplate): string {\n    return min(cards, property) + ' - ' + max(cards, property)\n}\n\nfunction earliest(cards: readonly Card[], property: IPropertyTemplate, intl: IntlShape): string {\n    const result = earliestEpoch(cards, property)\n    if (result === Number.POSITIVE_INFINITY) {\n        return ''\n    }\n    const date = new Date(result)\n    return property.type === 'date' ? Utils.displayDate(date, intl) : Utils.displayDateTime(date, intl)\n}\n\nfunction earliestEpoch(cards: readonly Card[], property: IPropertyTemplate): number {\n    let result = Number.POSITIVE_INFINITY\n    cards.forEach((card) => {\n        const timestamps = getTimestampsFromPropertyValue(getCardProperty(card, property))\n        for (const timestamp of timestamps) {\n            result = Math.min(result, timestamp)\n        }\n    })\n    return result\n}\n\nfunction latest(cards: readonly Card[], property: IPropertyTemplate, intl: IntlShape): string {\n    const result = latestEpoch(cards, property)\n    if (result === Number.NEGATIVE_INFINITY) {\n        return ''\n    }\n    const date = new Date(result)\n    return property.type === 'date' ? Utils.displayDate(date, intl) : Utils.displayDateTime(date, intl)\n}\n\nfunction latestEpoch(cards: readonly Card[], property: IPropertyTemplate): number {\n    let result = Number.NEGATIVE_INFINITY\n    cards.forEach((card) => {\n        const timestamps = getTimestampsFromPropertyValue(getCardProperty(card, property))\n        for (const timestamp of timestamps) {\n            result = Math.max(result, timestamp)\n        }\n    })\n    return result\n}\n\nfunction getTimestampsFromPropertyValue(value: number | string | string[]): number[] {\n    if (typeof value === 'number') {\n        return [value]\n    }\n    if (typeof value === 'string') {\n        let property: DateProperty\n        try {\n            property = JSON.parse(value)\n        } catch {\n            return []\n        }\n        return [property.from, property.to].flatMap((e) => {\n            return e ? [e] : []\n        })\n    }\n    return []\n}\n\nfunction dateRange(cards: readonly Card[], property: IPropertyTemplate, intl: IntlShape): string {\n    const resultEarliest = earliestEpoch(cards, property)\n    if (resultEarliest === Number.POSITIVE_INFINITY) {\n        return ''\n    }\n    const resultLatest = latestEpoch(cards, property)\n    if (resultLatest === Number.NEGATIVE_INFINITY) {\n        return ''\n    }\n    return moment.duration(resultLatest - resultEarliest, 'milliseconds').locale(intl.locale.toLowerCase()).humanize()\n}\n\nconst Calculations: Record<string, (cards: readonly Card[], property: IPropertyTemplate, intl: IntlShape) => string> = {\n    count,\n    countEmpty,\n    countNotEmpty,\n    percentEmpty,\n    percentNotEmpty,\n    countValue,\n    countUniqueValue,\n    countChecked,\n    countUnchecked,\n    percentChecked,\n    percentUnchecked,\n    sum,\n    average,\n    median,\n    min,\n    max,\n    range,\n    earliest,\n    latest,\n    dateRange,\n}\n\nexport default Calculations\n"
  },
  {
    "path": "webapp/src/components/calculations/options.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {render} from '@testing-library/react'\nimport React from 'react'\n\nimport {IPropertyTemplate} from '../../blocks/board'\n\nimport {wrapIntl} from '../../testUtils'\n\nimport {CalculationOptions} from './options'\n\ndescribe('components/calculations/Options', () => {\n    test('should match snapshot', () => {\n        const property = {\n            type: 'number',\n        } as IPropertyTemplate\n\n        const component = wrapIntl(\n            <CalculationOptions\n                value={'none'}\n                onChange={() => {}}\n                property={property}\n                menuOpen={false}\n                options={[{\n                    label: 'Count',\n                    value: 'count',\n                    displayName: 'Count',\n                }]}\n            />,\n        )\n\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot menu open', () => {\n        const property = {\n            type: 'number',\n        } as IPropertyTemplate\n\n        const component = wrapIntl(\n            <CalculationOptions\n                value={'none'}\n                menuOpen={true}\n                onChange={() => {}}\n                property={property}\n                options={[\n                    {\n                        label: 'Count',\n                        value: 'count',\n                        displayName: 'Count',\n                    },\n                    {\n                        label: 'Max',\n                        value: 'max',\n                        displayName: 'Max',\n                    },\n                ]}\n            />,\n        )\n\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/calculations/options.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\n\nimport Select, {components, DropdownIndicatorProps} from 'react-select'\n\nimport {CSSObject} from '@emotion/serialize'\n\nimport {useIntl, IntlShape} from 'react-intl'\n\nimport {getSelectBaseStyle} from '../../theme'\nimport ChevronUp from '../../widgets/icons/chevronUp'\nimport {IPropertyTemplate} from '../../blocks/board'\n\nexport type Option = {\n    label: string\n    value: string\n    displayName: string\n}\n\nexport const Options: Record<string, Option> = {\n    none: {value: 'none', label: 'None', displayName: 'Calculate'},\n    count: {value: 'count', label: 'Count', displayName: 'Count'},\n    countEmpty: {value: 'countEmpty', label: 'Count Empty', displayName: 'Empty'},\n    countNotEmpty: {value: 'countNotEmpty', label: 'Count Not Empty', displayName: 'Not Empty'},\n    percentEmpty: {value: 'percentEmpty', label: 'Percent Empty', displayName: 'Empty'},\n    percentNotEmpty: {value: 'percentNotEmpty', label: 'Percent Not Empty', displayName: 'Not Empty'},\n    countValue: {value: 'countValue', label: 'Count Value', displayName: 'Values'},\n    countChecked: {value: 'countChecked', label: 'Count Checked', displayName: 'Checked'},\n    percentChecked: {value: 'percentChecked', label: 'Percent Checked', displayName: 'Checked'},\n    percentUnchecked: {value: 'percentUnchecked', label: 'Percent Unchecked', displayName: 'Unchecked'},\n    countUnchecked: {value: 'countUnchecked', label: 'Count Unchecked', displayName: 'Unchecked'},\n    countUniqueValue: {value: 'countUniqueValue', label: 'Count Unique Values', displayName: 'Unique'},\n    sum: {value: 'sum', label: 'Sum', displayName: 'Sum'},\n    average: {value: 'average', label: 'Average', displayName: 'Average'},\n    median: {value: 'median', label: 'Median', displayName: 'Median'},\n    min: {value: 'min', label: 'Min', displayName: 'Min'},\n    max: {value: 'max', label: 'Max', displayName: 'Max'},\n    range: {value: 'range', label: 'Range', displayName: 'Range'},\n    earliest: {value: 'earliest', label: 'Earliest Date', displayName: 'Earliest'},\n    latest: {value: 'latest', label: 'Latest Date', displayName: 'Latest'},\n    dateRange: {value: 'dateRange', label: 'Date Range', displayName: 'Range'},\n}\n\nexport const optionLabelString = (option: Option, intl: IntlShape): string => {\n    switch (option.value) {\n    case 'none': return intl.formatMessage({id: 'Calculations.Options.none.label', defaultMessage: 'None'})\n    case 'count': return intl.formatMessage({id: 'Calculations.Options.count.label', defaultMessage: 'Count'})\n    case 'countValue': return intl.formatMessage({id: 'Calculations.Options.countValue.label', defaultMessage: 'Count value'})\n    case 'countChecked': return intl.formatMessage({id: 'Calculations.Options.countChecked.label', defaultMessage: 'Count checked'})\n    case 'percentChecked': return intl.formatMessage({id: 'Calculations.Options.percentChecked.label', defaultMessage: 'Percent checked'})\n    case 'percentUnchecked': return intl.formatMessage({id: 'Calculations.Options.percentUnchecked.label', defaultMessage: 'Percent unchecked'})\n    case 'countUnchecked': return intl.formatMessage({id: 'Calculations.Options.countUnchecked.label', defaultMessage: 'Count unchecked'})\n    case 'countUniqueValue': return intl.formatMessage({id: 'Calculations.Options.countUniqueValue.label', defaultMessage: 'Count unique values'})\n    case 'sum': return intl.formatMessage({id: 'Calculations.Options.sum.label', defaultMessage: 'Sum'})\n    case 'average': return intl.formatMessage({id: 'Calculations.Options.average.label', defaultMessage: 'Average'})\n    case 'median': return intl.formatMessage({id: 'Calculations.Options.median.label', defaultMessage: 'Median'})\n    case 'min': return intl.formatMessage({id: 'Calculations.Options.min.label', defaultMessage: 'Min'})\n    case 'max': return intl.formatMessage({id: 'Calculations.Options.max.label', defaultMessage: 'Max'})\n    case 'range': return intl.formatMessage({id: 'Calculations.Options.range.label', defaultMessage: 'Range'})\n    case 'earliest': return intl.formatMessage({id: 'Calculations.Options.earliest.label', defaultMessage: 'Earliest'})\n    case 'latest': return intl.formatMessage({id: 'Calculations.Options.latest.label', defaultMessage: 'Latest'})\n    case 'dateRange': return intl.formatMessage({id: 'Calculations.Options.dateRange.label', defaultMessage: 'Range'})\n    default: return option.label\n    }\n}\n\nexport const optionDisplayNameString = (option: Option, intl: IntlShape): string => {\n    switch (option.value) {\n    case 'none': return intl.formatMessage({id: 'Calculations.Options.none.displayName', defaultMessage: 'Calculate'})\n    case 'count': return intl.formatMessage({id: 'Calculations.Options.count.displayName', defaultMessage: 'Count'})\n    case 'countValue': return intl.formatMessage({id: 'Calculations.Options.countValue.displayName', defaultMessage: 'Values'})\n    case 'countChecked': return intl.formatMessage({id: 'Calculations.Options.countChecked.displayName', defaultMessage: 'Checked'})\n    case 'percentChecked': return intl.formatMessage({id: 'Calculations.Options.percentChecked.displayName', defaultMessage: 'Checked'})\n    case 'percentUnchecked': return intl.formatMessage({id: 'Calculations.Options.percentUnchecked.displayName', defaultMessage: 'Unchecked'})\n    case 'countUnchecked': return intl.formatMessage({id: 'Calculations.Options.countUnchecked.displayName', defaultMessage: 'Unchecked'})\n    case 'countUniqueValue': return intl.formatMessage({id: 'Calculations.Options.countUniqueValue.displayName', defaultMessage: 'Unique'})\n    case 'sum': return intl.formatMessage({id: 'Calculations.Options.sum.displayName', defaultMessage: 'Sum'})\n    case 'average': return intl.formatMessage({id: 'Calculations.Options.average.displayName', defaultMessage: 'Average'})\n    case 'median': return intl.formatMessage({id: 'Calculations.Options.median.displayName', defaultMessage: 'Median'})\n    case 'min': return intl.formatMessage({id: 'Calculations.Options.min.displayName', defaultMessage: 'Min'})\n    case 'max': return intl.formatMessage({id: 'Calculations.Options.max.displayName', defaultMessage: 'Max'})\n    case 'range': return intl.formatMessage({id: 'Calculations.Options.range.displayName', defaultMessage: 'Range'})\n    case 'earliest': return intl.formatMessage({id: 'Calculations.Options.earliest.displayName', defaultMessage: 'Earliest'})\n    case 'latest': return intl.formatMessage({id: 'Calculations.Options.latest.displayName', defaultMessage: 'Latest'})\n    case 'dateRange': return intl.formatMessage({id: 'Calculations.Options.dateRange.displayName', defaultMessage: 'Range'})\n    default: return option.displayName\n    }\n}\n\nexport const optionsByType: Map<string, Option[]> = new Map([\n    ['common', [Options.none, Options.count, Options.countEmpty, Options.countNotEmpty, Options.percentEmpty,\n        Options.percentNotEmpty, Options.countValue, Options.countUniqueValue]],\n    ['checkbox', [Options.countChecked, Options.countUnchecked, Options.percentChecked, Options.percentUnchecked]],\n    ['number', [Options.sum, Options.average, Options.median, Options.min, Options.max, Options.range]],\n    ['date', [Options.earliest, Options.latest, Options.dateRange]],\n    ['createdTime', [Options.earliest, Options.latest, Options.dateRange]],\n    ['updatedTime', [Options.earliest, Options.latest, Options.dateRange]],\n])\n\nexport const typesByOptions: Map<string, string[]> = generateTypesByOption()\n\nfunction generateTypesByOption(): Map<string, string[]> {\n    const mapping = new Map<string, string[]>()\n\n    optionsByType.forEach((options, type) => {\n        options.forEach((option) => {\n            const types = mapping.get(option.value) || []\n            types.push(type)\n            mapping.set(option.value, types)\n        })\n    })\n\n    return mapping\n}\n\nconst baseStyles = getSelectBaseStyle()\n\nconst styles = {\n    ...baseStyles,\n    dropdownIndicator: (provided: CSSObject): CSSObject => ({\n        ...baseStyles.dropdownIndicator(provided),\n        pointerEvents: 'none',\n    }),\n    control: (): CSSObject => ({\n        border: 0,\n        width: '100%',\n        margin: '0',\n        display: 'flex',\n        flexDirection: 'row',\n    }),\n    menu: (provided: CSSObject): CSSObject => ({\n        ...provided,\n        minWidth: '100%',\n        width: 'max-content',\n        background: 'rgb(var(--center-channel-bg-rgb))',\n        left: '0',\n        marginBottom: '0',\n    }),\n    singleValue: (provided: CSSObject): CSSObject => ({\n        ...baseStyles.singleValue(provided),\n        opacity: '0.8',\n        fontSize: '12px',\n        right: '0',\n        textTransform: 'uppercase',\n    }),\n    valueContainer: (provided: CSSObject): CSSObject => ({\n        ...baseStyles.valueContainer(provided),\n        display: 'none',\n        pointerEvents: 'none',\n    }),\n}\n\nconst DropdownIndicator = (props: DropdownIndicatorProps<Option, false>) => {\n    return (\n        <components.DropdownIndicator {...props}>\n            <ChevronUp/>\n        </components.DropdownIndicator>\n    )\n}\n\n// Calculation option props shared by all implementations of calculation options\nexport type CommonCalculationOptionProps = {\n    value: string\n    menuOpen: boolean\n    onClose?: () => void\n    components?: {[key: string]: (props: any) => JSX.Element}\n    onChange: (data: any) => void\n    property?: IPropertyTemplate\n}\n\n// Props used by the base calculation option component\ntype BaseCalculationOptionProps = CommonCalculationOptionProps & {\n    options: Option[]\n}\n\nexport const CalculationOptions = (props: BaseCalculationOptionProps): JSX.Element => {\n    const intl = useIntl()\n\n    return (\n        <Select\n            styles={styles}\n            value={Options[props.value]}\n            isMulti={false}\n            isClearable={true}\n            name={'calculation_options'}\n            className={'CalculationOptions'}\n            classNamePrefix={'CalculationOptions'}\n            options={props.options}\n            menuPlacement={'auto'}\n            isSearchable={false}\n            components={{DropdownIndicator, ...(props.components || {})}}\n            defaultMenuIsOpen={props.menuOpen}\n            autoFocus={false}\n            formatOptionLabel={(option: Option, meta) => {\n                return meta.context === 'menu' ? optionLabelString(option, intl) : optionDisplayNameString(option, intl)\n            }}\n            onMenuClose={() => {\n                if (props.onClose) {\n                    props.onClose()\n                }\n            }}\n            onChange={(item) => {\n                if (item?.value) {\n                    props.onChange(item.value)\n                }\n            }}\n        />\n    )\n}\n"
  },
  {
    "path": "webapp/src/components/calendar/__snapshots__/fullCalendar.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/calendar/toolbar return calendar, no date property 1`] = `\n<div>\n  <div\n    class=\"CalendarContainer\"\n  >\n    <div\n      class=\"fc fc-media-screen fc-direction-ltr fc-theme-standard fc-liquid-hack\"\n    >\n      <div\n        class=\"fc-header-toolbar fc-toolbar fc-toolbar-ltr\"\n      >\n        <div\n          class=\"fc-toolbar-chunk\"\n        >\n          <h2\n            class=\"fc-toolbar-title\"\n            id=\"fc-dom-1\"\n          >\n            October 2021\n          </h2>\n        </div>\n        <div\n          class=\"fc-toolbar-chunk\"\n        />\n        <div\n          class=\"fc-toolbar-chunk\"\n        >\n          <button\n            aria-pressed=\"false\"\n            class=\"fc-dayGridWeek-button fc-button fc-button-primary\"\n            title=\"Week view\"\n            type=\"button\"\n          >\n            Week\n          </button>\n          <button\n            aria-pressed=\"true\"\n            class=\"fc-dayGridMonth-button fc-button fc-button-primary fc-button-active\"\n            title=\"Month view\"\n            type=\"button\"\n          >\n            Month\n          </button>\n          <div\n            class=\"fc-button-group\"\n          >\n            <button\n              aria-pressed=\"false\"\n              class=\"fc-prev-button fc-button fc-button-primary\"\n              title=\"Previous Month\"\n              type=\"button\"\n            >\n              <span\n                class=\"fc-icon fc-icon-chevron-left\"\n              />\n            </button>\n            <button\n              aria-pressed=\"false\"\n              class=\"fc-today-button fc-button fc-button-primary\"\n              title=\"This Month\"\n              type=\"button\"\n            >\n              TODAY\n            </button>\n            <button\n              aria-pressed=\"false\"\n              class=\"fc-next-button fc-button fc-button-primary\"\n              title=\"Next Month\"\n              type=\"button\"\n            >\n              <span\n                class=\"fc-icon fc-icon-chevron-right\"\n              />\n            </button>\n          </div>\n        </div>\n      </div>\n      <div\n        aria-labelledby=\"fc-dom-1\"\n        class=\"fc-view-harness fc-view-harness-active\"\n        style=\"padding-bottom: 74.07407407407408%; height: 0px;\"\n      >\n        <div\n          class=\"fc-daygrid fc-dayGridMonth-view fc-view\"\n        >\n          <table\n            class=\"fc-scrollgrid  fc-scrollgrid-liquid\"\n            role=\"grid\"\n          >\n            <tbody\n              role=\"rowgroup\"\n            >\n              <tr\n                class=\"fc-scrollgrid-section fc-scrollgrid-section-header \"\n                role=\"presentation\"\n              >\n                <th\n                  role=\"presentation\"\n                >\n                  <div\n                    class=\"fc-scroller-harness\"\n                  >\n                    <div\n                      class=\"fc-scroller\"\n                      style=\"overflow-x: hidden; overflow-y: hidden;\"\n                    >\n                      <table\n                        class=\"fc-col-header \"\n                        role=\"presentation\"\n                        style=\"width: 0px;\"\n                      >\n                        <colgroup />\n                        <thead\n                          role=\"presentation\"\n                        >\n                          <tr\n                            role=\"row\"\n                          >\n                            <th\n                              class=\"fc-col-header-cell fc-day fc-day-sun\"\n                              role=\"columnheader\"\n                            >\n                              <div\n                                class=\"fc-scrollgrid-sync-inner\"\n                              >\n                                <a\n                                  aria-label=\"Sunday\"\n                                  class=\"fc-col-header-cell-cushion \"\n                                >\n                                  Sun\n                                </a>\n                              </div>\n                            </th>\n                            <th\n                              class=\"fc-col-header-cell fc-day fc-day-mon\"\n                              role=\"columnheader\"\n                            >\n                              <div\n                                class=\"fc-scrollgrid-sync-inner\"\n                              >\n                                <a\n                                  aria-label=\"Monday\"\n                                  class=\"fc-col-header-cell-cushion \"\n                                >\n                                  Mon\n                                </a>\n                              </div>\n                            </th>\n                            <th\n                              class=\"fc-col-header-cell fc-day fc-day-tue\"\n                              role=\"columnheader\"\n                            >\n                              <div\n                                class=\"fc-scrollgrid-sync-inner\"\n                              >\n                                <a\n                                  aria-label=\"Tuesday\"\n                                  class=\"fc-col-header-cell-cushion \"\n                                >\n                                  Tue\n                                </a>\n                              </div>\n                            </th>\n                            <th\n                              class=\"fc-col-header-cell fc-day fc-day-wed\"\n                              role=\"columnheader\"\n                            >\n                              <div\n                                class=\"fc-scrollgrid-sync-inner\"\n                              >\n                                <a\n                                  aria-label=\"Wednesday\"\n                                  class=\"fc-col-header-cell-cushion \"\n                                >\n                                  Wed\n                                </a>\n                              </div>\n                            </th>\n                            <th\n                              class=\"fc-col-header-cell fc-day fc-day-thu\"\n                              role=\"columnheader\"\n                            >\n                              <div\n                                class=\"fc-scrollgrid-sync-inner\"\n                              >\n                                <a\n                                  aria-label=\"Thursday\"\n                                  class=\"fc-col-header-cell-cushion \"\n                                >\n                                  Thu\n                                </a>\n                              </div>\n                            </th>\n                            <th\n                              class=\"fc-col-header-cell fc-day fc-day-fri\"\n                              role=\"columnheader\"\n                            >\n                              <div\n                                class=\"fc-scrollgrid-sync-inner\"\n                              >\n                                <a\n                                  aria-label=\"Friday\"\n                                  class=\"fc-col-header-cell-cushion \"\n                                >\n                                  Fri\n                                </a>\n                              </div>\n                            </th>\n                            <th\n                              class=\"fc-col-header-cell fc-day fc-day-sat\"\n                              role=\"columnheader\"\n                            >\n                              <div\n                                class=\"fc-scrollgrid-sync-inner\"\n                              >\n                                <a\n                                  aria-label=\"Saturday\"\n                                  class=\"fc-col-header-cell-cushion \"\n                                >\n                                  Sat\n                                </a>\n                              </div>\n                            </th>\n                          </tr>\n                        </thead>\n                      </table>\n                    </div>\n                  </div>\n                </th>\n              </tr>\n              <tr\n                class=\"fc-scrollgrid-section fc-scrollgrid-section-body  fc-scrollgrid-section-liquid\"\n                role=\"presentation\"\n              >\n                <td\n                  role=\"presentation\"\n                >\n                  <div\n                    class=\"fc-scroller-harness fc-scroller-harness-liquid\"\n                  >\n                    <div\n                      class=\"fc-scroller fc-scroller-liquid-absolute\"\n                      style=\"overflow-x: hidden; overflow-y: auto;\"\n                    >\n                      <div\n                        class=\"fc-daygrid-body fc-daygrid-body-unbalanced \"\n                        style=\"width: 0px;\"\n                      >\n                        <table\n                          class=\"fc-scrollgrid-sync-table\"\n                          role=\"presentation\"\n                          style=\"width: 0px; height: 0px;\"\n                        >\n                          <colgroup />\n                          <tbody\n                            role=\"presentation\"\n                          >\n                            <tr\n                              role=\"row\"\n                            >\n                              <td\n                                aria-labelledby=\"fc-dom-2\"\n                                class=\"fc-daygrid-day fc-day fc-day-sun fc-day-past fc-day-other\"\n                                data-date=\"2021-09-26\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"September 26, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-2\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          26\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-4\"\n                                class=\"fc-daygrid-day fc-day fc-day-mon fc-day-past fc-day-other\"\n                                data-date=\"2021-09-27\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"September 27, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-4\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          27\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-6\"\n                                class=\"fc-daygrid-day fc-day fc-day-tue fc-day-past fc-day-other\"\n                                data-date=\"2021-09-28\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"September 28, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-6\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          28\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-8\"\n                                class=\"fc-daygrid-day fc-day fc-day-wed fc-day-past fc-day-other\"\n                                data-date=\"2021-09-29\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"September 29, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-8\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          29\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-10\"\n                                class=\"fc-daygrid-day fc-day fc-day-thu fc-day-past fc-day-other\"\n                                data-date=\"2021-09-30\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"September 30, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-10\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          30\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-12\"\n                                class=\"fc-daygrid-day fc-day fc-day-fri fc-day-past\"\n                                data-date=\"2021-10-01\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 1, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-12\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          1\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-14\"\n                                class=\"fc-daygrid-day fc-day fc-day-sat fc-day-past\"\n                                data-date=\"2021-10-02\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 2, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-14\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          2\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                            </tr>\n                            <tr\n                              role=\"row\"\n                            >\n                              <td\n                                aria-labelledby=\"fc-dom-16\"\n                                class=\"fc-daygrid-day fc-day fc-day-sun fc-day-past\"\n                                data-date=\"2021-10-03\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 3, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-16\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          3\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-18\"\n                                class=\"fc-daygrid-day fc-day fc-day-mon fc-day-past\"\n                                data-date=\"2021-10-04\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 4, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-18\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          4\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-20\"\n                                class=\"fc-daygrid-day fc-day fc-day-tue fc-day-past\"\n                                data-date=\"2021-10-05\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 5, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-20\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          5\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-event-harness\"\n                                      style=\"margin-top: 0px;\"\n                                    >\n                                      <a\n                                        class=\"fc-daygrid-event fc-daygrid-block-event fc-h-event fc-event fc-event-start fc-event-end fc-event-past\"\n                                      >\n                                        <div\n                                          class=\"fc-event-main\"\n                                        >\n                                          <div\n                                            class=\"EventContent\"\n                                          >\n                                            <div\n                                              aria-label=\"menuwrapper\"\n                                              class=\"MenuWrapper optionsMenu\"\n                                              role=\"button\"\n                                            >\n                                              <button\n                                                class=\"IconButton CardActionsMenuIcon\"\n                                                type=\"button\"\n                                              >\n                                                <i\n                                                  class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                                                />\n                                              </button>\n                                            </div>\n                                            <div\n                                              class=\"octo-icontitle\"\n                                            >\n                                              <div\n                                                class=\"octo-icon\"\n                                              >\n                                                i\n                                              </div>\n                                              <div\n                                                class=\"fc-event-title\"\n                                              >\n                                                title\n                                              </div>\n                                            </div>\n                                          </div>\n                                        </div>\n                                      </a>\n                                    </div>\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-22\"\n                                class=\"fc-daygrid-day fc-day fc-day-wed fc-day-past\"\n                                data-date=\"2021-10-06\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 6, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-22\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          6\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-24\"\n                                class=\"fc-daygrid-day fc-day fc-day-thu fc-day-past\"\n                                data-date=\"2021-10-07\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 7, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-24\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          7\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-26\"\n                                class=\"fc-daygrid-day fc-day fc-day-fri fc-day-past\"\n                                data-date=\"2021-10-08\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 8, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-26\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          8\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-28\"\n                                class=\"fc-daygrid-day fc-day fc-day-sat fc-day-past\"\n                                data-date=\"2021-10-09\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 9, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-28\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          9\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                            </tr>\n                            <tr\n                              role=\"row\"\n                            >\n                              <td\n                                aria-labelledby=\"fc-dom-30\"\n                                class=\"fc-daygrid-day fc-day fc-day-sun fc-day-past\"\n                                data-date=\"2021-10-10\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 10, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-30\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          10\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-32\"\n                                class=\"fc-daygrid-day fc-day fc-day-mon fc-day-past\"\n                                data-date=\"2021-10-11\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 11, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-32\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          11\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-34\"\n                                class=\"fc-daygrid-day fc-day fc-day-tue fc-day-past\"\n                                data-date=\"2021-10-12\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 12, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-34\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          12\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-36\"\n                                class=\"fc-daygrid-day fc-day fc-day-wed fc-day-past\"\n                                data-date=\"2021-10-13\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 13, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-36\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          13\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-38\"\n                                class=\"fc-daygrid-day fc-day fc-day-thu fc-day-past\"\n                                data-date=\"2021-10-14\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 14, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-38\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          14\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-40\"\n                                class=\"fc-daygrid-day fc-day fc-day-fri fc-day-past\"\n                                data-date=\"2021-10-15\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 15, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-40\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          15\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-42\"\n                                class=\"fc-daygrid-day fc-day fc-day-sat fc-day-past\"\n                                data-date=\"2021-10-16\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 16, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-42\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          16\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                            </tr>\n                            <tr\n                              role=\"row\"\n                            >\n                              <td\n                                aria-labelledby=\"fc-dom-44\"\n                                class=\"fc-daygrid-day fc-day fc-day-sun fc-day-past\"\n                                data-date=\"2021-10-17\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 17, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-44\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          17\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-46\"\n                                class=\"fc-daygrid-day fc-day fc-day-mon fc-day-past\"\n                                data-date=\"2021-10-18\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 18, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-46\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          18\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-48\"\n                                class=\"fc-daygrid-day fc-day fc-day-tue fc-day-past\"\n                                data-date=\"2021-10-19\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 19, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-48\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          19\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-50\"\n                                class=\"fc-daygrid-day fc-day fc-day-wed fc-day-past\"\n                                data-date=\"2021-10-20\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 20, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-50\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          20\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-52\"\n                                class=\"fc-daygrid-day fc-day fc-day-thu fc-day-past\"\n                                data-date=\"2021-10-21\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 21, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-52\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          21\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-54\"\n                                class=\"fc-daygrid-day fc-day fc-day-fri fc-day-past\"\n                                data-date=\"2021-10-22\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 22, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-54\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          22\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-56\"\n                                class=\"fc-daygrid-day fc-day fc-day-sat fc-day-past\"\n                                data-date=\"2021-10-23\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 23, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-56\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          23\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                            </tr>\n                            <tr\n                              role=\"row\"\n                            >\n                              <td\n                                aria-labelledby=\"fc-dom-58\"\n                                class=\"fc-daygrid-day fc-day fc-day-sun fc-day-past\"\n                                data-date=\"2021-10-24\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 24, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-58\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          24\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-60\"\n                                class=\"fc-daygrid-day fc-day fc-day-mon fc-day-past\"\n                                data-date=\"2021-10-25\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 25, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-60\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          25\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-62\"\n                                class=\"fc-daygrid-day fc-day fc-day-tue fc-day-past\"\n                                data-date=\"2021-10-26\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 26, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-62\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          26\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-64\"\n                                class=\"fc-daygrid-day fc-day fc-day-wed fc-day-past\"\n                                data-date=\"2021-10-27\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 27, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-64\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          27\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-66\"\n                                class=\"fc-daygrid-day fc-day fc-day-thu fc-day-past\"\n                                data-date=\"2021-10-28\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 28, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-66\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          28\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-68\"\n                                class=\"fc-daygrid-day fc-day fc-day-fri fc-day-past\"\n                                data-date=\"2021-10-29\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 29, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-68\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          29\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-70\"\n                                class=\"fc-daygrid-day fc-day fc-day-sat fc-day-past\"\n                                data-date=\"2021-10-30\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 30, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-70\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          30\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                            </tr>\n                            <tr\n                              role=\"row\"\n                            >\n                              <td\n                                aria-labelledby=\"fc-dom-72\"\n                                class=\"fc-daygrid-day fc-day fc-day-sun fc-day-past\"\n                                data-date=\"2021-10-31\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 31, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-72\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          31\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-74\"\n                                class=\"fc-daygrid-day fc-day fc-day-mon fc-day-past fc-day-other\"\n                                data-date=\"2021-11-01\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"November 1, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-74\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          1\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-76\"\n                                class=\"fc-daygrid-day fc-day fc-day-tue fc-day-past fc-day-other\"\n                                data-date=\"2021-11-02\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"November 2, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-76\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          2\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-78\"\n                                class=\"fc-daygrid-day fc-day fc-day-wed fc-day-past fc-day-other\"\n                                data-date=\"2021-11-03\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"November 3, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-78\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          3\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-80\"\n                                class=\"fc-daygrid-day fc-day fc-day-thu fc-day-past fc-day-other\"\n                                data-date=\"2021-11-04\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"November 4, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-80\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          4\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-82\"\n                                class=\"fc-daygrid-day fc-day fc-day-fri fc-day-past fc-day-other\"\n                                data-date=\"2021-11-05\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"November 5, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-82\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          5\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-84\"\n                                class=\"fc-daygrid-day fc-day fc-day-sat fc-day-past fc-day-other\"\n                                data-date=\"2021-11-06\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"November 6, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-84\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          6\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                            </tr>\n                          </tbody>\n                        </table>\n                      </div>\n                    </div>\n                  </div>\n                </td>\n              </tr>\n            </tbody>\n          </table>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/calendar/toolbar return calendar, with date property not set 1`] = `\n<div>\n  <div\n    class=\"CalendarContainer\"\n  >\n    <div\n      class=\"fc fc-media-screen fc-direction-ltr fc-theme-standard fc-liquid-hack\"\n    >\n      <div\n        class=\"fc-header-toolbar fc-toolbar fc-toolbar-ltr\"\n      >\n        <div\n          class=\"fc-toolbar-chunk\"\n        >\n          <h2\n            class=\"fc-toolbar-title\"\n            id=\"fc-dom-86\"\n          >\n            October 2021\n          </h2>\n        </div>\n        <div\n          class=\"fc-toolbar-chunk\"\n        />\n        <div\n          class=\"fc-toolbar-chunk\"\n        >\n          <button\n            aria-pressed=\"false\"\n            class=\"fc-dayGridWeek-button fc-button fc-button-primary\"\n            title=\"Week view\"\n            type=\"button\"\n          >\n            Week\n          </button>\n          <button\n            aria-pressed=\"true\"\n            class=\"fc-dayGridMonth-button fc-button fc-button-primary fc-button-active\"\n            title=\"Month view\"\n            type=\"button\"\n          >\n            Month\n          </button>\n          <div\n            class=\"fc-button-group\"\n          >\n            <button\n              aria-pressed=\"false\"\n              class=\"fc-prev-button fc-button fc-button-primary\"\n              title=\"Previous Month\"\n              type=\"button\"\n            >\n              <span\n                class=\"fc-icon fc-icon-chevron-left\"\n              />\n            </button>\n            <button\n              aria-pressed=\"false\"\n              class=\"fc-today-button fc-button fc-button-primary\"\n              title=\"This Month\"\n              type=\"button\"\n            >\n              TODAY\n            </button>\n            <button\n              aria-pressed=\"false\"\n              class=\"fc-next-button fc-button fc-button-primary\"\n              title=\"Next Month\"\n              type=\"button\"\n            >\n              <span\n                class=\"fc-icon fc-icon-chevron-right\"\n              />\n            </button>\n          </div>\n        </div>\n      </div>\n      <div\n        aria-labelledby=\"fc-dom-86\"\n        class=\"fc-view-harness fc-view-harness-active\"\n        style=\"padding-bottom: 74.07407407407408%; height: 0px;\"\n      >\n        <div\n          class=\"fc-daygrid fc-dayGridMonth-view fc-view\"\n        >\n          <table\n            class=\"fc-scrollgrid  fc-scrollgrid-liquid\"\n            role=\"grid\"\n          >\n            <tbody\n              role=\"rowgroup\"\n            >\n              <tr\n                class=\"fc-scrollgrid-section fc-scrollgrid-section-header \"\n                role=\"presentation\"\n              >\n                <th\n                  role=\"presentation\"\n                >\n                  <div\n                    class=\"fc-scroller-harness\"\n                  >\n                    <div\n                      class=\"fc-scroller\"\n                      style=\"overflow-x: hidden; overflow-y: hidden;\"\n                    >\n                      <table\n                        class=\"fc-col-header \"\n                        role=\"presentation\"\n                        style=\"width: 0px;\"\n                      >\n                        <colgroup />\n                        <thead\n                          role=\"presentation\"\n                        >\n                          <tr\n                            role=\"row\"\n                          >\n                            <th\n                              class=\"fc-col-header-cell fc-day fc-day-sun\"\n                              role=\"columnheader\"\n                            >\n                              <div\n                                class=\"fc-scrollgrid-sync-inner\"\n                              >\n                                <a\n                                  aria-label=\"Sunday\"\n                                  class=\"fc-col-header-cell-cushion \"\n                                >\n                                  Sun\n                                </a>\n                              </div>\n                            </th>\n                            <th\n                              class=\"fc-col-header-cell fc-day fc-day-mon\"\n                              role=\"columnheader\"\n                            >\n                              <div\n                                class=\"fc-scrollgrid-sync-inner\"\n                              >\n                                <a\n                                  aria-label=\"Monday\"\n                                  class=\"fc-col-header-cell-cushion \"\n                                >\n                                  Mon\n                                </a>\n                              </div>\n                            </th>\n                            <th\n                              class=\"fc-col-header-cell fc-day fc-day-tue\"\n                              role=\"columnheader\"\n                            >\n                              <div\n                                class=\"fc-scrollgrid-sync-inner\"\n                              >\n                                <a\n                                  aria-label=\"Tuesday\"\n                                  class=\"fc-col-header-cell-cushion \"\n                                >\n                                  Tue\n                                </a>\n                              </div>\n                            </th>\n                            <th\n                              class=\"fc-col-header-cell fc-day fc-day-wed\"\n                              role=\"columnheader\"\n                            >\n                              <div\n                                class=\"fc-scrollgrid-sync-inner\"\n                              >\n                                <a\n                                  aria-label=\"Wednesday\"\n                                  class=\"fc-col-header-cell-cushion \"\n                                >\n                                  Wed\n                                </a>\n                              </div>\n                            </th>\n                            <th\n                              class=\"fc-col-header-cell fc-day fc-day-thu\"\n                              role=\"columnheader\"\n                            >\n                              <div\n                                class=\"fc-scrollgrid-sync-inner\"\n                              >\n                                <a\n                                  aria-label=\"Thursday\"\n                                  class=\"fc-col-header-cell-cushion \"\n                                >\n                                  Thu\n                                </a>\n                              </div>\n                            </th>\n                            <th\n                              class=\"fc-col-header-cell fc-day fc-day-fri\"\n                              role=\"columnheader\"\n                            >\n                              <div\n                                class=\"fc-scrollgrid-sync-inner\"\n                              >\n                                <a\n                                  aria-label=\"Friday\"\n                                  class=\"fc-col-header-cell-cushion \"\n                                >\n                                  Fri\n                                </a>\n                              </div>\n                            </th>\n                            <th\n                              class=\"fc-col-header-cell fc-day fc-day-sat\"\n                              role=\"columnheader\"\n                            >\n                              <div\n                                class=\"fc-scrollgrid-sync-inner\"\n                              >\n                                <a\n                                  aria-label=\"Saturday\"\n                                  class=\"fc-col-header-cell-cushion \"\n                                >\n                                  Sat\n                                </a>\n                              </div>\n                            </th>\n                          </tr>\n                        </thead>\n                      </table>\n                    </div>\n                  </div>\n                </th>\n              </tr>\n              <tr\n                class=\"fc-scrollgrid-section fc-scrollgrid-section-body  fc-scrollgrid-section-liquid\"\n                role=\"presentation\"\n              >\n                <td\n                  role=\"presentation\"\n                >\n                  <div\n                    class=\"fc-scroller-harness fc-scroller-harness-liquid\"\n                  >\n                    <div\n                      class=\"fc-scroller fc-scroller-liquid-absolute\"\n                      style=\"overflow-x: hidden; overflow-y: auto;\"\n                    >\n                      <div\n                        class=\"fc-daygrid-body fc-daygrid-body-unbalanced \"\n                        style=\"width: 0px;\"\n                      >\n                        <table\n                          class=\"fc-scrollgrid-sync-table\"\n                          role=\"presentation\"\n                          style=\"width: 0px; height: 0px;\"\n                        >\n                          <colgroup />\n                          <tbody\n                            role=\"presentation\"\n                          >\n                            <tr\n                              role=\"row\"\n                            >\n                              <td\n                                aria-labelledby=\"fc-dom-87\"\n                                class=\"fc-daygrid-day fc-day fc-day-sun fc-day-past fc-day-other\"\n                                data-date=\"2021-09-26\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"September 26, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-87\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          26\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-89\"\n                                class=\"fc-daygrid-day fc-day fc-day-mon fc-day-past fc-day-other\"\n                                data-date=\"2021-09-27\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"September 27, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-89\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          27\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-91\"\n                                class=\"fc-daygrid-day fc-day fc-day-tue fc-day-past fc-day-other\"\n                                data-date=\"2021-09-28\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"September 28, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-91\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          28\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-93\"\n                                class=\"fc-daygrid-day fc-day fc-day-wed fc-day-past fc-day-other\"\n                                data-date=\"2021-09-29\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"September 29, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-93\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          29\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-95\"\n                                class=\"fc-daygrid-day fc-day fc-day-thu fc-day-past fc-day-other\"\n                                data-date=\"2021-09-30\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"September 30, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-95\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          30\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-97\"\n                                class=\"fc-daygrid-day fc-day fc-day-fri fc-day-past\"\n                                data-date=\"2021-10-01\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 1, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-97\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          1\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-99\"\n                                class=\"fc-daygrid-day fc-day fc-day-sat fc-day-past\"\n                                data-date=\"2021-10-02\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 2, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-99\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          2\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                            </tr>\n                            <tr\n                              role=\"row\"\n                            >\n                              <td\n                                aria-labelledby=\"fc-dom-101\"\n                                class=\"fc-daygrid-day fc-day fc-day-sun fc-day-past\"\n                                data-date=\"2021-10-03\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 3, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-101\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          3\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-103\"\n                                class=\"fc-daygrid-day fc-day fc-day-mon fc-day-past\"\n                                data-date=\"2021-10-04\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 4, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-103\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          4\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-105\"\n                                class=\"fc-daygrid-day fc-day fc-day-tue fc-day-past\"\n                                data-date=\"2021-10-05\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 5, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-105\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          5\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-event-harness\"\n                                      style=\"margin-top: 0px;\"\n                                    >\n                                      <a\n                                        class=\"fc-daygrid-event fc-daygrid-block-event fc-h-event fc-event fc-event-start fc-event-end fc-event-past\"\n                                      >\n                                        <div\n                                          class=\"fc-event-main\"\n                                        >\n                                          <div\n                                            class=\"EventContent\"\n                                          >\n                                            <div\n                                              aria-label=\"menuwrapper\"\n                                              class=\"MenuWrapper optionsMenu\"\n                                              role=\"button\"\n                                            >\n                                              <button\n                                                class=\"IconButton CardActionsMenuIcon\"\n                                                type=\"button\"\n                                              >\n                                                <i\n                                                  class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                                                />\n                                              </button>\n                                            </div>\n                                            <div\n                                              class=\"octo-icontitle\"\n                                            >\n                                              <div\n                                                class=\"octo-icon\"\n                                              >\n                                                i\n                                              </div>\n                                              <div\n                                                class=\"fc-event-title\"\n                                              >\n                                                title\n                                              </div>\n                                            </div>\n                                          </div>\n                                        </div>\n                                      </a>\n                                    </div>\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-107\"\n                                class=\"fc-daygrid-day fc-day fc-day-wed fc-day-past\"\n                                data-date=\"2021-10-06\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 6, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-107\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          6\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-109\"\n                                class=\"fc-daygrid-day fc-day fc-day-thu fc-day-past\"\n                                data-date=\"2021-10-07\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 7, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-109\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          7\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-111\"\n                                class=\"fc-daygrid-day fc-day fc-day-fri fc-day-past\"\n                                data-date=\"2021-10-08\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 8, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-111\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          8\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-113\"\n                                class=\"fc-daygrid-day fc-day fc-day-sat fc-day-past\"\n                                data-date=\"2021-10-09\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 9, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-113\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          9\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                            </tr>\n                            <tr\n                              role=\"row\"\n                            >\n                              <td\n                                aria-labelledby=\"fc-dom-115\"\n                                class=\"fc-daygrid-day fc-day fc-day-sun fc-day-past\"\n                                data-date=\"2021-10-10\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 10, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-115\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          10\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-117\"\n                                class=\"fc-daygrid-day fc-day fc-day-mon fc-day-past\"\n                                data-date=\"2021-10-11\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 11, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-117\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          11\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-119\"\n                                class=\"fc-daygrid-day fc-day fc-day-tue fc-day-past\"\n                                data-date=\"2021-10-12\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 12, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-119\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          12\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-121\"\n                                class=\"fc-daygrid-day fc-day fc-day-wed fc-day-past\"\n                                data-date=\"2021-10-13\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 13, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-121\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          13\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-123\"\n                                class=\"fc-daygrid-day fc-day fc-day-thu fc-day-past\"\n                                data-date=\"2021-10-14\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 14, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-123\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          14\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-125\"\n                                class=\"fc-daygrid-day fc-day fc-day-fri fc-day-past\"\n                                data-date=\"2021-10-15\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 15, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-125\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          15\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-127\"\n                                class=\"fc-daygrid-day fc-day fc-day-sat fc-day-past\"\n                                data-date=\"2021-10-16\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 16, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-127\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          16\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                            </tr>\n                            <tr\n                              role=\"row\"\n                            >\n                              <td\n                                aria-labelledby=\"fc-dom-129\"\n                                class=\"fc-daygrid-day fc-day fc-day-sun fc-day-past\"\n                                data-date=\"2021-10-17\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 17, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-129\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          17\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-131\"\n                                class=\"fc-daygrid-day fc-day fc-day-mon fc-day-past\"\n                                data-date=\"2021-10-18\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 18, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-131\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          18\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-133\"\n                                class=\"fc-daygrid-day fc-day fc-day-tue fc-day-past\"\n                                data-date=\"2021-10-19\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 19, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-133\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          19\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-135\"\n                                class=\"fc-daygrid-day fc-day fc-day-wed fc-day-past\"\n                                data-date=\"2021-10-20\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 20, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-135\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          20\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-137\"\n                                class=\"fc-daygrid-day fc-day fc-day-thu fc-day-past\"\n                                data-date=\"2021-10-21\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 21, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-137\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          21\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-139\"\n                                class=\"fc-daygrid-day fc-day fc-day-fri fc-day-past\"\n                                data-date=\"2021-10-22\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 22, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-139\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          22\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-141\"\n                                class=\"fc-daygrid-day fc-day fc-day-sat fc-day-past\"\n                                data-date=\"2021-10-23\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 23, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-141\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          23\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                            </tr>\n                            <tr\n                              role=\"row\"\n                            >\n                              <td\n                                aria-labelledby=\"fc-dom-143\"\n                                class=\"fc-daygrid-day fc-day fc-day-sun fc-day-past\"\n                                data-date=\"2021-10-24\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 24, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-143\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          24\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-145\"\n                                class=\"fc-daygrid-day fc-day fc-day-mon fc-day-past\"\n                                data-date=\"2021-10-25\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 25, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-145\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          25\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-147\"\n                                class=\"fc-daygrid-day fc-day fc-day-tue fc-day-past\"\n                                data-date=\"2021-10-26\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 26, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-147\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          26\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-149\"\n                                class=\"fc-daygrid-day fc-day fc-day-wed fc-day-past\"\n                                data-date=\"2021-10-27\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 27, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-149\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          27\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-151\"\n                                class=\"fc-daygrid-day fc-day fc-day-thu fc-day-past\"\n                                data-date=\"2021-10-28\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 28, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-151\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          28\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-153\"\n                                class=\"fc-daygrid-day fc-day fc-day-fri fc-day-past\"\n                                data-date=\"2021-10-29\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 29, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-153\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          29\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-155\"\n                                class=\"fc-daygrid-day fc-day fc-day-sat fc-day-past\"\n                                data-date=\"2021-10-30\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 30, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-155\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          30\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                            </tr>\n                            <tr\n                              role=\"row\"\n                            >\n                              <td\n                                aria-labelledby=\"fc-dom-157\"\n                                class=\"fc-daygrid-day fc-day fc-day-sun fc-day-past\"\n                                data-date=\"2021-10-31\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 31, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-157\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          31\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-159\"\n                                class=\"fc-daygrid-day fc-day fc-day-mon fc-day-past fc-day-other\"\n                                data-date=\"2021-11-01\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"November 1, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-159\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          1\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-161\"\n                                class=\"fc-daygrid-day fc-day fc-day-tue fc-day-past fc-day-other\"\n                                data-date=\"2021-11-02\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"November 2, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-161\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          2\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-163\"\n                                class=\"fc-daygrid-day fc-day fc-day-wed fc-day-past fc-day-other\"\n                                data-date=\"2021-11-03\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"November 3, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-163\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          3\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-165\"\n                                class=\"fc-daygrid-day fc-day fc-day-thu fc-day-past fc-day-other\"\n                                data-date=\"2021-11-04\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"November 4, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-165\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          4\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-167\"\n                                class=\"fc-daygrid-day fc-day fc-day-fri fc-day-past fc-day-other\"\n                                data-date=\"2021-11-05\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"November 5, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-167\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          5\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-169\"\n                                class=\"fc-daygrid-day fc-day fc-day-sat fc-day-past fc-day-other\"\n                                data-date=\"2021-11-06\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"November 6, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-169\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          6\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                            </tr>\n                          </tbody>\n                        </table>\n                      </div>\n                    </div>\n                  </div>\n                </td>\n              </tr>\n            </tbody>\n          </table>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/calendar/toolbar return calendar, with date property set 1`] = `\n<div>\n  <div\n    class=\"CalendarContainer\"\n  >\n    <div\n      class=\"fc fc-media-screen fc-direction-ltr fc-theme-standard fc-liquid-hack\"\n    >\n      <div\n        class=\"fc-header-toolbar fc-toolbar fc-toolbar-ltr\"\n      >\n        <div\n          class=\"fc-toolbar-chunk\"\n        >\n          <h2\n            class=\"fc-toolbar-title\"\n            id=\"fc-dom-171\"\n          >\n            October 2021\n          </h2>\n        </div>\n        <div\n          class=\"fc-toolbar-chunk\"\n        />\n        <div\n          class=\"fc-toolbar-chunk\"\n        >\n          <button\n            aria-pressed=\"false\"\n            class=\"fc-dayGridWeek-button fc-button fc-button-primary\"\n            title=\"Week view\"\n            type=\"button\"\n          >\n            Week\n          </button>\n          <button\n            aria-pressed=\"true\"\n            class=\"fc-dayGridMonth-button fc-button fc-button-primary fc-button-active\"\n            title=\"Month view\"\n            type=\"button\"\n          >\n            Month\n          </button>\n          <div\n            class=\"fc-button-group\"\n          >\n            <button\n              aria-pressed=\"false\"\n              class=\"fc-prev-button fc-button fc-button-primary\"\n              title=\"Previous Month\"\n              type=\"button\"\n            >\n              <span\n                class=\"fc-icon fc-icon-chevron-left\"\n              />\n            </button>\n            <button\n              aria-pressed=\"false\"\n              class=\"fc-today-button fc-button fc-button-primary\"\n              title=\"This Month\"\n              type=\"button\"\n            >\n              TODAY\n            </button>\n            <button\n              aria-pressed=\"false\"\n              class=\"fc-next-button fc-button fc-button-primary\"\n              title=\"Next Month\"\n              type=\"button\"\n            >\n              <span\n                class=\"fc-icon fc-icon-chevron-right\"\n              />\n            </button>\n          </div>\n        </div>\n      </div>\n      <div\n        aria-labelledby=\"fc-dom-171\"\n        class=\"fc-view-harness fc-view-harness-active\"\n        style=\"padding-bottom: 74.07407407407408%; height: 0px;\"\n      >\n        <div\n          class=\"fc-daygrid fc-dayGridMonth-view fc-view\"\n        >\n          <table\n            class=\"fc-scrollgrid  fc-scrollgrid-liquid\"\n            role=\"grid\"\n          >\n            <tbody\n              role=\"rowgroup\"\n            >\n              <tr\n                class=\"fc-scrollgrid-section fc-scrollgrid-section-header \"\n                role=\"presentation\"\n              >\n                <th\n                  role=\"presentation\"\n                >\n                  <div\n                    class=\"fc-scroller-harness\"\n                  >\n                    <div\n                      class=\"fc-scroller\"\n                      style=\"overflow-x: hidden; overflow-y: hidden;\"\n                    >\n                      <table\n                        class=\"fc-col-header \"\n                        role=\"presentation\"\n                        style=\"width: 0px;\"\n                      >\n                        <colgroup />\n                        <thead\n                          role=\"presentation\"\n                        >\n                          <tr\n                            role=\"row\"\n                          >\n                            <th\n                              class=\"fc-col-header-cell fc-day fc-day-sun\"\n                              role=\"columnheader\"\n                            >\n                              <div\n                                class=\"fc-scrollgrid-sync-inner\"\n                              >\n                                <a\n                                  aria-label=\"Sunday\"\n                                  class=\"fc-col-header-cell-cushion \"\n                                >\n                                  Sun\n                                </a>\n                              </div>\n                            </th>\n                            <th\n                              class=\"fc-col-header-cell fc-day fc-day-mon\"\n                              role=\"columnheader\"\n                            >\n                              <div\n                                class=\"fc-scrollgrid-sync-inner\"\n                              >\n                                <a\n                                  aria-label=\"Monday\"\n                                  class=\"fc-col-header-cell-cushion \"\n                                >\n                                  Mon\n                                </a>\n                              </div>\n                            </th>\n                            <th\n                              class=\"fc-col-header-cell fc-day fc-day-tue\"\n                              role=\"columnheader\"\n                            >\n                              <div\n                                class=\"fc-scrollgrid-sync-inner\"\n                              >\n                                <a\n                                  aria-label=\"Tuesday\"\n                                  class=\"fc-col-header-cell-cushion \"\n                                >\n                                  Tue\n                                </a>\n                              </div>\n                            </th>\n                            <th\n                              class=\"fc-col-header-cell fc-day fc-day-wed\"\n                              role=\"columnheader\"\n                            >\n                              <div\n                                class=\"fc-scrollgrid-sync-inner\"\n                              >\n                                <a\n                                  aria-label=\"Wednesday\"\n                                  class=\"fc-col-header-cell-cushion \"\n                                >\n                                  Wed\n                                </a>\n                              </div>\n                            </th>\n                            <th\n                              class=\"fc-col-header-cell fc-day fc-day-thu\"\n                              role=\"columnheader\"\n                            >\n                              <div\n                                class=\"fc-scrollgrid-sync-inner\"\n                              >\n                                <a\n                                  aria-label=\"Thursday\"\n                                  class=\"fc-col-header-cell-cushion \"\n                                >\n                                  Thu\n                                </a>\n                              </div>\n                            </th>\n                            <th\n                              class=\"fc-col-header-cell fc-day fc-day-fri\"\n                              role=\"columnheader\"\n                            >\n                              <div\n                                class=\"fc-scrollgrid-sync-inner\"\n                              >\n                                <a\n                                  aria-label=\"Friday\"\n                                  class=\"fc-col-header-cell-cushion \"\n                                >\n                                  Fri\n                                </a>\n                              </div>\n                            </th>\n                            <th\n                              class=\"fc-col-header-cell fc-day fc-day-sat\"\n                              role=\"columnheader\"\n                            >\n                              <div\n                                class=\"fc-scrollgrid-sync-inner\"\n                              >\n                                <a\n                                  aria-label=\"Saturday\"\n                                  class=\"fc-col-header-cell-cushion \"\n                                >\n                                  Sat\n                                </a>\n                              </div>\n                            </th>\n                          </tr>\n                        </thead>\n                      </table>\n                    </div>\n                  </div>\n                </th>\n              </tr>\n              <tr\n                class=\"fc-scrollgrid-section fc-scrollgrid-section-body  fc-scrollgrid-section-liquid\"\n                role=\"presentation\"\n              >\n                <td\n                  role=\"presentation\"\n                >\n                  <div\n                    class=\"fc-scroller-harness fc-scroller-harness-liquid\"\n                  >\n                    <div\n                      class=\"fc-scroller fc-scroller-liquid-absolute\"\n                      style=\"overflow-x: hidden; overflow-y: auto;\"\n                    >\n                      <div\n                        class=\"fc-daygrid-body fc-daygrid-body-unbalanced \"\n                        style=\"width: 0px;\"\n                      >\n                        <table\n                          class=\"fc-scrollgrid-sync-table\"\n                          role=\"presentation\"\n                          style=\"width: 0px; height: 0px;\"\n                        >\n                          <colgroup />\n                          <tbody\n                            role=\"presentation\"\n                          >\n                            <tr\n                              role=\"row\"\n                            >\n                              <td\n                                aria-labelledby=\"fc-dom-172\"\n                                class=\"fc-daygrid-day fc-day fc-day-sun fc-day-past fc-day-other\"\n                                data-date=\"2021-09-26\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"September 26, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-172\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          26\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-174\"\n                                class=\"fc-daygrid-day fc-day fc-day-mon fc-day-past fc-day-other\"\n                                data-date=\"2021-09-27\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"September 27, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-174\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          27\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-176\"\n                                class=\"fc-daygrid-day fc-day fc-day-tue fc-day-past fc-day-other\"\n                                data-date=\"2021-09-28\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"September 28, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-176\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          28\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-178\"\n                                class=\"fc-daygrid-day fc-day fc-day-wed fc-day-past fc-day-other\"\n                                data-date=\"2021-09-29\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"September 29, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-178\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          29\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-180\"\n                                class=\"fc-daygrid-day fc-day fc-day-thu fc-day-past fc-day-other\"\n                                data-date=\"2021-09-30\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"September 30, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-180\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          30\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-182\"\n                                class=\"fc-daygrid-day fc-day fc-day-fri fc-day-past\"\n                                data-date=\"2021-10-01\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 1, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-182\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          1\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-184\"\n                                class=\"fc-daygrid-day fc-day fc-day-sat fc-day-past\"\n                                data-date=\"2021-10-02\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 2, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-184\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          2\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                            </tr>\n                            <tr\n                              role=\"row\"\n                            >\n                              <td\n                                aria-labelledby=\"fc-dom-186\"\n                                class=\"fc-daygrid-day fc-day fc-day-sun fc-day-past\"\n                                data-date=\"2021-10-03\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 3, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-186\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          3\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-188\"\n                                class=\"fc-daygrid-day fc-day fc-day-mon fc-day-past\"\n                                data-date=\"2021-10-04\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 4, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-188\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          4\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-190\"\n                                class=\"fc-daygrid-day fc-day fc-day-tue fc-day-past\"\n                                data-date=\"2021-10-05\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 5, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-190\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          5\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-192\"\n                                class=\"fc-daygrid-day fc-day fc-day-wed fc-day-past\"\n                                data-date=\"2021-10-06\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 6, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-192\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          6\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-194\"\n                                class=\"fc-daygrid-day fc-day fc-day-thu fc-day-past\"\n                                data-date=\"2021-10-07\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 7, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-194\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          7\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-196\"\n                                class=\"fc-daygrid-day fc-day fc-day-fri fc-day-past\"\n                                data-date=\"2021-10-08\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 8, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-196\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          8\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-198\"\n                                class=\"fc-daygrid-day fc-day fc-day-sat fc-day-past\"\n                                data-date=\"2021-10-09\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 9, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-198\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          9\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                            </tr>\n                            <tr\n                              role=\"row\"\n                            >\n                              <td\n                                aria-labelledby=\"fc-dom-200\"\n                                class=\"fc-daygrid-day fc-day fc-day-sun fc-day-past\"\n                                data-date=\"2021-10-10\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 10, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-200\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          10\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-202\"\n                                class=\"fc-daygrid-day fc-day fc-day-mon fc-day-past\"\n                                data-date=\"2021-10-11\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 11, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-202\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          11\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-204\"\n                                class=\"fc-daygrid-day fc-day fc-day-tue fc-day-past\"\n                                data-date=\"2021-10-12\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 12, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-204\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          12\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-206\"\n                                class=\"fc-daygrid-day fc-day fc-day-wed fc-day-past\"\n                                data-date=\"2021-10-13\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 13, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-206\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          13\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-208\"\n                                class=\"fc-daygrid-day fc-day fc-day-thu fc-day-past\"\n                                data-date=\"2021-10-14\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 14, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-208\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          14\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-210\"\n                                class=\"fc-daygrid-day fc-day fc-day-fri fc-day-past\"\n                                data-date=\"2021-10-15\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 15, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-210\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          15\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-212\"\n                                class=\"fc-daygrid-day fc-day fc-day-sat fc-day-past\"\n                                data-date=\"2021-10-16\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 16, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-212\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          16\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                            </tr>\n                            <tr\n                              role=\"row\"\n                            >\n                              <td\n                                aria-labelledby=\"fc-dom-214\"\n                                class=\"fc-daygrid-day fc-day fc-day-sun fc-day-past\"\n                                data-date=\"2021-10-17\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 17, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-214\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          17\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-216\"\n                                class=\"fc-daygrid-day fc-day fc-day-mon fc-day-past\"\n                                data-date=\"2021-10-18\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 18, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-216\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          18\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-218\"\n                                class=\"fc-daygrid-day fc-day fc-day-tue fc-day-past\"\n                                data-date=\"2021-10-19\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 19, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-218\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          19\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-220\"\n                                class=\"fc-daygrid-day fc-day fc-day-wed fc-day-past\"\n                                data-date=\"2021-10-20\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 20, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-220\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          20\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-event-harness\"\n                                      style=\"margin-top: 0px;\"\n                                    >\n                                      <a\n                                        class=\"fc-daygrid-event fc-daygrid-block-event fc-h-event fc-event fc-event-draggable fc-event-resizable fc-event-start fc-event-end fc-event-past\"\n                                      >\n                                        <div\n                                          class=\"fc-event-main\"\n                                        >\n                                          <div\n                                            class=\"EventContent\"\n                                          >\n                                            <div\n                                              aria-label=\"menuwrapper\"\n                                              class=\"MenuWrapper optionsMenu\"\n                                              role=\"button\"\n                                            >\n                                              <button\n                                                class=\"IconButton CardActionsMenuIcon\"\n                                                type=\"button\"\n                                              >\n                                                <i\n                                                  class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                                                />\n                                              </button>\n                                            </div>\n                                            <div\n                                              class=\"octo-icontitle\"\n                                            >\n                                              <div\n                                                class=\"octo-icon\"\n                                              >\n                                                i\n                                              </div>\n                                              <div\n                                                class=\"fc-event-title\"\n                                              >\n                                                title\n                                              </div>\n                                            </div>\n                                          </div>\n                                        </div>\n                                        <div\n                                          class=\"fc-event-resizer fc-event-resizer-start\"\n                                        />\n                                        <div\n                                          class=\"fc-event-resizer fc-event-resizer-end\"\n                                        />\n                                      </a>\n                                    </div>\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-222\"\n                                class=\"fc-daygrid-day fc-day fc-day-thu fc-day-past\"\n                                data-date=\"2021-10-21\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 21, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-222\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          21\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-224\"\n                                class=\"fc-daygrid-day fc-day fc-day-fri fc-day-past\"\n                                data-date=\"2021-10-22\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 22, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-224\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          22\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-226\"\n                                class=\"fc-daygrid-day fc-day fc-day-sat fc-day-past\"\n                                data-date=\"2021-10-23\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 23, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-226\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          23\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                            </tr>\n                            <tr\n                              role=\"row\"\n                            >\n                              <td\n                                aria-labelledby=\"fc-dom-228\"\n                                class=\"fc-daygrid-day fc-day fc-day-sun fc-day-past\"\n                                data-date=\"2021-10-24\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 24, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-228\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          24\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-230\"\n                                class=\"fc-daygrid-day fc-day fc-day-mon fc-day-past\"\n                                data-date=\"2021-10-25\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 25, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-230\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          25\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-232\"\n                                class=\"fc-daygrid-day fc-day fc-day-tue fc-day-past\"\n                                data-date=\"2021-10-26\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 26, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-232\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          26\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-234\"\n                                class=\"fc-daygrid-day fc-day fc-day-wed fc-day-past\"\n                                data-date=\"2021-10-27\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 27, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-234\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          27\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-236\"\n                                class=\"fc-daygrid-day fc-day fc-day-thu fc-day-past\"\n                                data-date=\"2021-10-28\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 28, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-236\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          28\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-238\"\n                                class=\"fc-daygrid-day fc-day fc-day-fri fc-day-past\"\n                                data-date=\"2021-10-29\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 29, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-238\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          29\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-240\"\n                                class=\"fc-daygrid-day fc-day fc-day-sat fc-day-past\"\n                                data-date=\"2021-10-30\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 30, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-240\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          30\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                            </tr>\n                            <tr\n                              role=\"row\"\n                            >\n                              <td\n                                aria-labelledby=\"fc-dom-242\"\n                                class=\"fc-daygrid-day fc-day fc-day-sun fc-day-past\"\n                                data-date=\"2021-10-31\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 31, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-242\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          31\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-244\"\n                                class=\"fc-daygrid-day fc-day fc-day-mon fc-day-past fc-day-other\"\n                                data-date=\"2021-11-01\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"November 1, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-244\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          1\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-246\"\n                                class=\"fc-daygrid-day fc-day fc-day-tue fc-day-past fc-day-other\"\n                                data-date=\"2021-11-02\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"November 2, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-246\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          2\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-248\"\n                                class=\"fc-daygrid-day fc-day fc-day-wed fc-day-past fc-day-other\"\n                                data-date=\"2021-11-03\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"November 3, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-248\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          3\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-250\"\n                                class=\"fc-daygrid-day fc-day fc-day-thu fc-day-past fc-day-other\"\n                                data-date=\"2021-11-04\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"November 4, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-250\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          4\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-252\"\n                                class=\"fc-daygrid-day fc-day fc-day-fri fc-day-past fc-day-other\"\n                                data-date=\"2021-11-05\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"November 5, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-252\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          5\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-254\"\n                                class=\"fc-daygrid-day fc-day fc-day-sat fc-day-past fc-day-other\"\n                                data-date=\"2021-11-06\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"November 6, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-254\"\n                                    >\n                                      <div\n                                        class=\"dateContainer with-plus\"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          6\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                            </tr>\n                          </tbody>\n                        </table>\n                      </div>\n                    </div>\n                  </div>\n                </td>\n              </tr>\n            </tbody>\n          </table>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/calendar/toolbar return calendar, without permissions 1`] = `\n<div>\n  <div\n    class=\"CalendarContainer\"\n  >\n    <div\n      class=\"fc fc-media-screen fc-direction-ltr fc-theme-standard fc-liquid-hack\"\n    >\n      <div\n        class=\"fc-header-toolbar fc-toolbar fc-toolbar-ltr\"\n      >\n        <div\n          class=\"fc-toolbar-chunk\"\n        >\n          <h2\n            class=\"fc-toolbar-title\"\n            id=\"fc-dom-256\"\n          >\n            October 2021\n          </h2>\n        </div>\n        <div\n          class=\"fc-toolbar-chunk\"\n        />\n        <div\n          class=\"fc-toolbar-chunk\"\n        >\n          <button\n            aria-pressed=\"false\"\n            class=\"fc-dayGridWeek-button fc-button fc-button-primary\"\n            title=\"Week view\"\n            type=\"button\"\n          >\n            Week\n          </button>\n          <button\n            aria-pressed=\"true\"\n            class=\"fc-dayGridMonth-button fc-button fc-button-primary fc-button-active\"\n            title=\"Month view\"\n            type=\"button\"\n          >\n            Month\n          </button>\n          <div\n            class=\"fc-button-group\"\n          >\n            <button\n              aria-pressed=\"false\"\n              class=\"fc-prev-button fc-button fc-button-primary\"\n              title=\"Previous Month\"\n              type=\"button\"\n            >\n              <span\n                class=\"fc-icon fc-icon-chevron-left\"\n              />\n            </button>\n            <button\n              aria-pressed=\"false\"\n              class=\"fc-today-button fc-button fc-button-primary\"\n              title=\"This Month\"\n              type=\"button\"\n            >\n              TODAY\n            </button>\n            <button\n              aria-pressed=\"false\"\n              class=\"fc-next-button fc-button fc-button-primary\"\n              title=\"Next Month\"\n              type=\"button\"\n            >\n              <span\n                class=\"fc-icon fc-icon-chevron-right\"\n              />\n            </button>\n          </div>\n        </div>\n      </div>\n      <div\n        aria-labelledby=\"fc-dom-256\"\n        class=\"fc-view-harness fc-view-harness-active\"\n        style=\"padding-bottom: 74.07407407407408%; height: 0px;\"\n      >\n        <div\n          class=\"fc-daygrid fc-dayGridMonth-view fc-view\"\n        >\n          <table\n            class=\"fc-scrollgrid  fc-scrollgrid-liquid\"\n            role=\"grid\"\n          >\n            <tbody\n              role=\"rowgroup\"\n            >\n              <tr\n                class=\"fc-scrollgrid-section fc-scrollgrid-section-header \"\n                role=\"presentation\"\n              >\n                <th\n                  role=\"presentation\"\n                >\n                  <div\n                    class=\"fc-scroller-harness\"\n                  >\n                    <div\n                      class=\"fc-scroller\"\n                      style=\"overflow-x: hidden; overflow-y: hidden;\"\n                    >\n                      <table\n                        class=\"fc-col-header \"\n                        role=\"presentation\"\n                        style=\"width: 0px;\"\n                      >\n                        <colgroup />\n                        <thead\n                          role=\"presentation\"\n                        >\n                          <tr\n                            role=\"row\"\n                          >\n                            <th\n                              class=\"fc-col-header-cell fc-day fc-day-sun\"\n                              role=\"columnheader\"\n                            >\n                              <div\n                                class=\"fc-scrollgrid-sync-inner\"\n                              >\n                                <a\n                                  aria-label=\"Sunday\"\n                                  class=\"fc-col-header-cell-cushion \"\n                                >\n                                  Sun\n                                </a>\n                              </div>\n                            </th>\n                            <th\n                              class=\"fc-col-header-cell fc-day fc-day-mon\"\n                              role=\"columnheader\"\n                            >\n                              <div\n                                class=\"fc-scrollgrid-sync-inner\"\n                              >\n                                <a\n                                  aria-label=\"Monday\"\n                                  class=\"fc-col-header-cell-cushion \"\n                                >\n                                  Mon\n                                </a>\n                              </div>\n                            </th>\n                            <th\n                              class=\"fc-col-header-cell fc-day fc-day-tue\"\n                              role=\"columnheader\"\n                            >\n                              <div\n                                class=\"fc-scrollgrid-sync-inner\"\n                              >\n                                <a\n                                  aria-label=\"Tuesday\"\n                                  class=\"fc-col-header-cell-cushion \"\n                                >\n                                  Tue\n                                </a>\n                              </div>\n                            </th>\n                            <th\n                              class=\"fc-col-header-cell fc-day fc-day-wed\"\n                              role=\"columnheader\"\n                            >\n                              <div\n                                class=\"fc-scrollgrid-sync-inner\"\n                              >\n                                <a\n                                  aria-label=\"Wednesday\"\n                                  class=\"fc-col-header-cell-cushion \"\n                                >\n                                  Wed\n                                </a>\n                              </div>\n                            </th>\n                            <th\n                              class=\"fc-col-header-cell fc-day fc-day-thu\"\n                              role=\"columnheader\"\n                            >\n                              <div\n                                class=\"fc-scrollgrid-sync-inner\"\n                              >\n                                <a\n                                  aria-label=\"Thursday\"\n                                  class=\"fc-col-header-cell-cushion \"\n                                >\n                                  Thu\n                                </a>\n                              </div>\n                            </th>\n                            <th\n                              class=\"fc-col-header-cell fc-day fc-day-fri\"\n                              role=\"columnheader\"\n                            >\n                              <div\n                                class=\"fc-scrollgrid-sync-inner\"\n                              >\n                                <a\n                                  aria-label=\"Friday\"\n                                  class=\"fc-col-header-cell-cushion \"\n                                >\n                                  Fri\n                                </a>\n                              </div>\n                            </th>\n                            <th\n                              class=\"fc-col-header-cell fc-day fc-day-sat\"\n                              role=\"columnheader\"\n                            >\n                              <div\n                                class=\"fc-scrollgrid-sync-inner\"\n                              >\n                                <a\n                                  aria-label=\"Saturday\"\n                                  class=\"fc-col-header-cell-cushion \"\n                                >\n                                  Sat\n                                </a>\n                              </div>\n                            </th>\n                          </tr>\n                        </thead>\n                      </table>\n                    </div>\n                  </div>\n                </th>\n              </tr>\n              <tr\n                class=\"fc-scrollgrid-section fc-scrollgrid-section-body  fc-scrollgrid-section-liquid\"\n                role=\"presentation\"\n              >\n                <td\n                  role=\"presentation\"\n                >\n                  <div\n                    class=\"fc-scroller-harness fc-scroller-harness-liquid\"\n                  >\n                    <div\n                      class=\"fc-scroller fc-scroller-liquid-absolute\"\n                      style=\"overflow-x: hidden; overflow-y: auto;\"\n                    >\n                      <div\n                        class=\"fc-daygrid-body fc-daygrid-body-unbalanced \"\n                        style=\"width: 0px;\"\n                      >\n                        <table\n                          class=\"fc-scrollgrid-sync-table\"\n                          role=\"presentation\"\n                          style=\"width: 0px; height: 0px;\"\n                        >\n                          <colgroup />\n                          <tbody\n                            role=\"presentation\"\n                          >\n                            <tr\n                              role=\"row\"\n                            >\n                              <td\n                                aria-labelledby=\"fc-dom-257\"\n                                class=\"fc-daygrid-day fc-day fc-day-sun fc-day-past fc-day-other\"\n                                data-date=\"2021-09-26\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"September 26, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-257\"\n                                    >\n                                      <div\n                                        class=\"dateContainer \"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          26\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-259\"\n                                class=\"fc-daygrid-day fc-day fc-day-mon fc-day-past fc-day-other\"\n                                data-date=\"2021-09-27\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"September 27, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-259\"\n                                    >\n                                      <div\n                                        class=\"dateContainer \"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          27\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-261\"\n                                class=\"fc-daygrid-day fc-day fc-day-tue fc-day-past fc-day-other\"\n                                data-date=\"2021-09-28\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"September 28, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-261\"\n                                    >\n                                      <div\n                                        class=\"dateContainer \"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          28\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-263\"\n                                class=\"fc-daygrid-day fc-day fc-day-wed fc-day-past fc-day-other\"\n                                data-date=\"2021-09-29\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"September 29, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-263\"\n                                    >\n                                      <div\n                                        class=\"dateContainer \"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          29\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-265\"\n                                class=\"fc-daygrid-day fc-day fc-day-thu fc-day-past fc-day-other\"\n                                data-date=\"2021-09-30\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"September 30, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-265\"\n                                    >\n                                      <div\n                                        class=\"dateContainer \"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          30\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-267\"\n                                class=\"fc-daygrid-day fc-day fc-day-fri fc-day-past\"\n                                data-date=\"2021-10-01\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 1, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-267\"\n                                    >\n                                      <div\n                                        class=\"dateContainer \"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          1\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-269\"\n                                class=\"fc-daygrid-day fc-day fc-day-sat fc-day-past\"\n                                data-date=\"2021-10-02\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 2, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-269\"\n                                    >\n                                      <div\n                                        class=\"dateContainer \"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          2\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                            </tr>\n                            <tr\n                              role=\"row\"\n                            >\n                              <td\n                                aria-labelledby=\"fc-dom-271\"\n                                class=\"fc-daygrid-day fc-day fc-day-sun fc-day-past\"\n                                data-date=\"2021-10-03\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 3, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-271\"\n                                    >\n                                      <div\n                                        class=\"dateContainer \"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          3\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-273\"\n                                class=\"fc-daygrid-day fc-day fc-day-mon fc-day-past\"\n                                data-date=\"2021-10-04\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 4, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-273\"\n                                    >\n                                      <div\n                                        class=\"dateContainer \"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          4\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-275\"\n                                class=\"fc-daygrid-day fc-day fc-day-tue fc-day-past\"\n                                data-date=\"2021-10-05\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 5, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-275\"\n                                    >\n                                      <div\n                                        class=\"dateContainer \"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          5\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-event-harness\"\n                                      style=\"margin-top: 0px;\"\n                                    >\n                                      <a\n                                        class=\"fc-daygrid-event fc-daygrid-block-event fc-h-event fc-event fc-event-start fc-event-end fc-event-past\"\n                                      >\n                                        <div\n                                          class=\"fc-event-main\"\n                                        >\n                                          <div\n                                            class=\"EventContent\"\n                                          >\n                                            <div\n                                              aria-label=\"menuwrapper\"\n                                              class=\"MenuWrapper optionsMenu\"\n                                              role=\"button\"\n                                            >\n                                              <button\n                                                class=\"IconButton CardActionsMenuIcon\"\n                                                type=\"button\"\n                                              >\n                                                <i\n                                                  class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                                                />\n                                              </button>\n                                            </div>\n                                            <div\n                                              class=\"octo-icontitle\"\n                                            >\n                                              <div\n                                                class=\"octo-icon\"\n                                              >\n                                                i\n                                              </div>\n                                              <div\n                                                class=\"fc-event-title\"\n                                              >\n                                                title\n                                              </div>\n                                            </div>\n                                          </div>\n                                        </div>\n                                      </a>\n                                    </div>\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-277\"\n                                class=\"fc-daygrid-day fc-day fc-day-wed fc-day-past\"\n                                data-date=\"2021-10-06\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 6, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-277\"\n                                    >\n                                      <div\n                                        class=\"dateContainer \"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          6\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-279\"\n                                class=\"fc-daygrid-day fc-day fc-day-thu fc-day-past\"\n                                data-date=\"2021-10-07\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 7, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-279\"\n                                    >\n                                      <div\n                                        class=\"dateContainer \"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          7\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-281\"\n                                class=\"fc-daygrid-day fc-day fc-day-fri fc-day-past\"\n                                data-date=\"2021-10-08\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 8, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-281\"\n                                    >\n                                      <div\n                                        class=\"dateContainer \"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          8\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-283\"\n                                class=\"fc-daygrid-day fc-day fc-day-sat fc-day-past\"\n                                data-date=\"2021-10-09\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 9, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-283\"\n                                    >\n                                      <div\n                                        class=\"dateContainer \"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          9\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                            </tr>\n                            <tr\n                              role=\"row\"\n                            >\n                              <td\n                                aria-labelledby=\"fc-dom-285\"\n                                class=\"fc-daygrid-day fc-day fc-day-sun fc-day-past\"\n                                data-date=\"2021-10-10\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 10, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-285\"\n                                    >\n                                      <div\n                                        class=\"dateContainer \"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          10\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-287\"\n                                class=\"fc-daygrid-day fc-day fc-day-mon fc-day-past\"\n                                data-date=\"2021-10-11\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 11, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-287\"\n                                    >\n                                      <div\n                                        class=\"dateContainer \"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          11\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-289\"\n                                class=\"fc-daygrid-day fc-day fc-day-tue fc-day-past\"\n                                data-date=\"2021-10-12\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 12, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-289\"\n                                    >\n                                      <div\n                                        class=\"dateContainer \"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          12\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-291\"\n                                class=\"fc-daygrid-day fc-day fc-day-wed fc-day-past\"\n                                data-date=\"2021-10-13\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 13, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-291\"\n                                    >\n                                      <div\n                                        class=\"dateContainer \"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          13\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-293\"\n                                class=\"fc-daygrid-day fc-day fc-day-thu fc-day-past\"\n                                data-date=\"2021-10-14\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 14, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-293\"\n                                    >\n                                      <div\n                                        class=\"dateContainer \"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          14\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-295\"\n                                class=\"fc-daygrid-day fc-day fc-day-fri fc-day-past\"\n                                data-date=\"2021-10-15\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 15, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-295\"\n                                    >\n                                      <div\n                                        class=\"dateContainer \"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          15\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-297\"\n                                class=\"fc-daygrid-day fc-day fc-day-sat fc-day-past\"\n                                data-date=\"2021-10-16\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 16, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-297\"\n                                    >\n                                      <div\n                                        class=\"dateContainer \"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          16\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                            </tr>\n                            <tr\n                              role=\"row\"\n                            >\n                              <td\n                                aria-labelledby=\"fc-dom-299\"\n                                class=\"fc-daygrid-day fc-day fc-day-sun fc-day-past\"\n                                data-date=\"2021-10-17\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 17, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-299\"\n                                    >\n                                      <div\n                                        class=\"dateContainer \"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          17\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-301\"\n                                class=\"fc-daygrid-day fc-day fc-day-mon fc-day-past\"\n                                data-date=\"2021-10-18\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 18, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-301\"\n                                    >\n                                      <div\n                                        class=\"dateContainer \"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          18\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-303\"\n                                class=\"fc-daygrid-day fc-day fc-day-tue fc-day-past\"\n                                data-date=\"2021-10-19\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 19, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-303\"\n                                    >\n                                      <div\n                                        class=\"dateContainer \"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          19\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-305\"\n                                class=\"fc-daygrid-day fc-day fc-day-wed fc-day-past\"\n                                data-date=\"2021-10-20\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 20, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-305\"\n                                    >\n                                      <div\n                                        class=\"dateContainer \"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          20\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-307\"\n                                class=\"fc-daygrid-day fc-day fc-day-thu fc-day-past\"\n                                data-date=\"2021-10-21\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 21, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-307\"\n                                    >\n                                      <div\n                                        class=\"dateContainer \"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          21\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-309\"\n                                class=\"fc-daygrid-day fc-day fc-day-fri fc-day-past\"\n                                data-date=\"2021-10-22\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 22, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-309\"\n                                    >\n                                      <div\n                                        class=\"dateContainer \"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          22\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-311\"\n                                class=\"fc-daygrid-day fc-day fc-day-sat fc-day-past\"\n                                data-date=\"2021-10-23\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 23, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-311\"\n                                    >\n                                      <div\n                                        class=\"dateContainer \"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          23\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                            </tr>\n                            <tr\n                              role=\"row\"\n                            >\n                              <td\n                                aria-labelledby=\"fc-dom-313\"\n                                class=\"fc-daygrid-day fc-day fc-day-sun fc-day-past\"\n                                data-date=\"2021-10-24\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 24, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-313\"\n                                    >\n                                      <div\n                                        class=\"dateContainer \"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          24\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-315\"\n                                class=\"fc-daygrid-day fc-day fc-day-mon fc-day-past\"\n                                data-date=\"2021-10-25\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 25, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-315\"\n                                    >\n                                      <div\n                                        class=\"dateContainer \"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          25\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-317\"\n                                class=\"fc-daygrid-day fc-day fc-day-tue fc-day-past\"\n                                data-date=\"2021-10-26\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 26, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-317\"\n                                    >\n                                      <div\n                                        class=\"dateContainer \"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          26\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-319\"\n                                class=\"fc-daygrid-day fc-day fc-day-wed fc-day-past\"\n                                data-date=\"2021-10-27\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 27, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-319\"\n                                    >\n                                      <div\n                                        class=\"dateContainer \"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          27\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-321\"\n                                class=\"fc-daygrid-day fc-day fc-day-thu fc-day-past\"\n                                data-date=\"2021-10-28\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 28, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-321\"\n                                    >\n                                      <div\n                                        class=\"dateContainer \"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          28\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-323\"\n                                class=\"fc-daygrid-day fc-day fc-day-fri fc-day-past\"\n                                data-date=\"2021-10-29\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 29, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-323\"\n                                    >\n                                      <div\n                                        class=\"dateContainer \"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          29\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-325\"\n                                class=\"fc-daygrid-day fc-day fc-day-sat fc-day-past\"\n                                data-date=\"2021-10-30\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 30, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-325\"\n                                    >\n                                      <div\n                                        class=\"dateContainer \"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          30\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                            </tr>\n                            <tr\n                              role=\"row\"\n                            >\n                              <td\n                                aria-labelledby=\"fc-dom-327\"\n                                class=\"fc-daygrid-day fc-day fc-day-sun fc-day-past\"\n                                data-date=\"2021-10-31\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"October 31, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-327\"\n                                    >\n                                      <div\n                                        class=\"dateContainer \"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          31\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-329\"\n                                class=\"fc-daygrid-day fc-day fc-day-mon fc-day-past fc-day-other\"\n                                data-date=\"2021-11-01\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"November 1, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-329\"\n                                    >\n                                      <div\n                                        class=\"dateContainer \"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          1\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-331\"\n                                class=\"fc-daygrid-day fc-day fc-day-tue fc-day-past fc-day-other\"\n                                data-date=\"2021-11-02\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"November 2, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-331\"\n                                    >\n                                      <div\n                                        class=\"dateContainer \"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          2\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-333\"\n                                class=\"fc-daygrid-day fc-day fc-day-wed fc-day-past fc-day-other\"\n                                data-date=\"2021-11-03\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"November 3, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-333\"\n                                    >\n                                      <div\n                                        class=\"dateContainer \"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          3\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-335\"\n                                class=\"fc-daygrid-day fc-day fc-day-thu fc-day-past fc-day-other\"\n                                data-date=\"2021-11-04\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"November 4, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-335\"\n                                    >\n                                      <div\n                                        class=\"dateContainer \"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          4\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-337\"\n                                class=\"fc-daygrid-day fc-day fc-day-fri fc-day-past fc-day-other\"\n                                data-date=\"2021-11-05\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"November 5, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-337\"\n                                    >\n                                      <div\n                                        class=\"dateContainer \"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          5\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                              <td\n                                aria-labelledby=\"fc-dom-339\"\n                                class=\"fc-daygrid-day fc-day fc-day-sat fc-day-past fc-day-other\"\n                                data-date=\"2021-11-06\"\n                                role=\"gridcell\"\n                              >\n                                <div\n                                  class=\"fc-daygrid-day-frame fc-scrollgrid-sync-inner\"\n                                >\n                                  <div\n                                    class=\"fc-daygrid-day-top\"\n                                  >\n                                    <a\n                                      aria-label=\"November 6, 2021\"\n                                      class=\"fc-daygrid-day-number\"\n                                      id=\"fc-dom-339\"\n                                    >\n                                      <div\n                                        class=\"dateContainer \"\n                                      >\n                                        <div\n                                          class=\"addEvent\"\n                                        >\n                                          +\n                                        </div>\n                                        <div\n                                          class=\"dateDisplay\"\n                                        >\n                                          6\n                                        </div>\n                                      </div>\n                                    </a>\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-events\"\n                                  >\n                                    <div\n                                      class=\"fc-daygrid-day-bottom\"\n                                      style=\"margin-top: 0px;\"\n                                    />\n                                  </div>\n                                  <div\n                                    class=\"fc-daygrid-day-bg\"\n                                  />\n                                </div>\n                              </td>\n                            </tr>\n                          </tbody>\n                        </table>\n                      </div>\n                    </div>\n                  </div>\n                </td>\n              </tr>\n            </tbody>\n          </table>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/calendar/fullCalendar.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\nimport {render} from '@testing-library/react'\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport {TestBlockFactory} from '../../test/testBlockFactory'\nimport '@testing-library/jest-dom'\nimport {wrapIntl, mockStateStore} from '../../testUtils'\nimport {IPropertyTemplate} from '../../blocks/board'\n\nimport CalendarView from './fullCalendar'\n\njest.mock('../../mutator')\n\ndescribe('components/calendar/toolbar', () => {\n    const mockShow = jest.fn()\n    const mockAdd = jest.fn()\n    const dateDisplayProperty = {\n        id: '12345',\n        name: 'DateProperty',\n        type: 'date',\n        options: [],\n    } as IPropertyTemplate\n    const board = TestBlockFactory.createBoard()\n    const view = TestBlockFactory.createBoardView(board)\n    view.fields.viewType = 'calendar'\n    view.fields.groupById = undefined\n    const card = TestBlockFactory.createCard(board)\n    const fifth = Date.UTC(2021, 9, 5, 12)\n    const twentieth = Date.UTC(2021, 9, 20, 12)\n    card.createAt = fifth\n    const rObject = {from: twentieth}\n\n    const state = {\n        teams: {\n            current: {id: 'team-id'},\n        },\n        boards: {\n            current: board.id,\n            boards: {\n                [board.id]: board,\n            },\n            myBoardMemberships: {\n                [board.id]: {userId: 'user_id_1', schemeAdmin: true},\n            },\n        },\n    }\n    const store = mockStateStore([], state)\n    beforeEach(() => {\n        jest.clearAllMocks()\n    })\n\n    test('return calendar, no date property', () => {\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <CalendarView\n                        board={board}\n                        activeView={view}\n                        cards={[card]}\n                        readonly={false}\n                        showCard={mockShow}\n                        addCard={mockAdd}\n                        initialDate={new Date(fifth)}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        expect(container).toMatchSnapshot()\n    })\n\n    test('return calendar, with date property not set', () => {\n        board.cardProperties.push(dateDisplayProperty)\n        card.fields.properties['12345'] = JSON.stringify(rObject)\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <CalendarView\n                        board={board}\n                        activeView={view}\n                        cards={[card]}\n                        readonly={false}\n                        showCard={mockShow}\n                        addCard={mockAdd}\n                        initialDate={new Date(fifth)}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        expect(container).toMatchSnapshot()\n    })\n\n    test('return calendar, with date property set', () => {\n        board.cardProperties.push(dateDisplayProperty)\n        card.fields.properties['12345'] = JSON.stringify(rObject)\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <CalendarView\n                        board={board}\n                        activeView={view}\n                        readonly={false}\n                        dateDisplayProperty={dateDisplayProperty}\n                        cards={[card]}\n                        showCard={mockShow}\n                        addCard={mockAdd}\n                        initialDate={new Date(fifth)}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        expect(container).toMatchSnapshot()\n    })\n\n    test('return calendar, without permissions', () => {\n        const localStore = mockStateStore([], {...state, teams: {current: undefined}})\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={localStore}>\n                    <CalendarView\n                        board={board}\n                        activeView={view}\n                        cards={[card]}\n                        readonly={false}\n                        showCard={mockShow}\n                        addCard={mockAdd}\n                        initialDate={new Date(fifth)}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        expect(container).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/calendar/fullCalendar.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React, {useCallback, useMemo, useState} from 'react'\nimport {useIntl} from 'react-intl'\n\nimport FullCalendar, {EventChangeArg, EventInput, EventContentArg, DayCellContentArg} from '@fullcalendar/react'\n\nimport interactionPlugin from '@fullcalendar/interaction'\nimport dayGridPlugin from '@fullcalendar/daygrid'\n\nimport {DatePropertyType} from '../../properties/types'\n\nimport mutator from '../../mutator'\n\nimport {Board, IPropertyTemplate} from '../../blocks/board'\nimport {BoardView} from '../../blocks/boardView'\nimport {Card} from '../../blocks/card'\nimport {DateProperty} from '../../properties/date/date'\nimport propsRegistry from '../../properties'\nimport Tooltip from '../../widgets/tooltip'\nimport PropertyValueElement from '../propertyValueElement'\nimport {Constants, Permission} from '../../constants'\nimport {useHasCurrentBoardPermissions} from '../../hooks/permissions'\nimport CardBadges from '../cardBadges'\nimport ConfirmationDialogBox, {ConfirmationDialogBoxProps} from '../confirmationDialogBox'\n\nimport './fullcalendar.scss'\nimport MenuWrapper from '../../widgets/menuWrapper'\nimport CardActionsMenu from '../cardActionsMenu/cardActionsMenu'\nimport TelemetryClient, {TelemetryActions, TelemetryCategory} from '../../telemetry/telemetryClient'\nimport CardActionsMenuIcon from '../cardActionsMenu/cardActionsMenuIcon'\n\nconst oneDay = 60 * 60 * 24 * 1000\n\ntype Props = {\n    board: Board\n    cards: Card[]\n    activeView: BoardView\n    readonly: boolean\n    initialDate?: Date\n    dateDisplayProperty?: IPropertyTemplate\n    showCard: (cardId: string) => void\n    addCard: (properties: Record<string, string>) => void\n}\n\nfunction createDatePropertyFromCalendarDates(start: Date, end: Date): DateProperty {\n    // save as noon local, expected from the date picker\n    start.setHours(12)\n    const dateFrom = start.getTime() - timeZoneOffset(start.getTime())\n    end.setHours(12)\n    const dateTo = end.getTime() - timeZoneOffset(end.getTime()) - oneDay // subtract one day. Calendar is date exclusive\n\n    const dateProperty: DateProperty = {from: dateFrom}\n    if (dateTo !== dateFrom) {\n        dateProperty.to = dateTo\n    }\n    return dateProperty\n}\n\nfunction createDatePropertyFromCalendarDate(start: Date): DateProperty {\n    // save as noon local, expected from the date picker\n    start.setHours(12)\n    const dateFrom = start.getTime() - timeZoneOffset(start.getTime())\n\n    const dateProperty: DateProperty = {from: dateFrom}\n    return dateProperty\n}\n\nconst timeZoneOffset = (date: number): number => {\n    return new Date(date).getTimezoneOffset() * 60 * 1000\n}\n\nconst CalendarFullView = (props: Props): JSX.Element|null => {\n    const intl = useIntl()\n    const {board, cards, activeView, dateDisplayProperty, readonly} = props\n    const isSelectable = !readonly\n    const canAddCards = useHasCurrentBoardPermissions([Permission.ManageBoardCards])\n    const [showConfirmationDialogBox, setShowConfirmationDialogBox] = useState<boolean>(false)\n    const [cardItem, setCardItem] = useState<Card>()\n\n    const visiblePropertyTemplates = useMemo(() => (\n        board.cardProperties.filter((template: IPropertyTemplate) => activeView.fields.visiblePropertyIds.includes(template.id))\n    ), [board.cardProperties, activeView.fields.visiblePropertyIds])\n\n    let {initialDate} = props\n    if (!initialDate) {\n        initialDate = new Date()\n    }\n\n    const isEditable = useCallback((): boolean => {\n        if (readonly || !dateDisplayProperty || propsRegistry.get(dateDisplayProperty.type).isReadOnly) {\n            return false\n        }\n        return true\n    }, [readonly, dateDisplayProperty])\n\n    const myEventsList = useMemo(() => (\n        cards.flatMap((card): EventInput[] => {\n            const property = propsRegistry.get(dateDisplayProperty?.type || 'unknown')\n\n            let dateFrom = new Date(card.createAt || 0)\n            let dateTo = new Date(card.createAt || 0)\n            if (property instanceof DatePropertyType) {\n                const dateFromValue = property.getDateFrom(card.fields.properties[dateDisplayProperty?.id || ''], card)\n                if (!dateFromValue) {\n                    return []\n                }\n                dateFrom = dateFromValue\n                const dateToValue = property.getDateTo(card.fields.properties[dateDisplayProperty?.id || ''], card)\n                dateTo = dateToValue || new Date(dateFrom)\n\n                //full calendar end date is exclusive, so increment by 1 day.\n                dateTo.setDate(dateTo.getDate() + 1)\n            }\n            return [{\n                id: card.id,\n                title: card.title,\n                extendedProps: {icon: card.fields.icon},\n                properties: card.fields.properties,\n                allDay: true,\n                start: dateFrom,\n                end: dateTo,\n            }]\n        })\n    ), [cards, dateDisplayProperty])\n\n    const visibleBadges = activeView.fields.visiblePropertyIds.includes(Constants.badgesColumnId)\n\n    const openConfirmationDialogBox = (card: Card) => {\n        setShowConfirmationDialogBox(true)\n        setCardItem(card)\n    }\n\n    const handleDeleteCard = useCallback(() => {\n        if (!cardItem) {\n            return\n        }\n        mutator.deleteBlock(cardItem, 'delete card')\n        setShowConfirmationDialogBox(false)\n    }, [cardItem, board.id])\n\n    const confirmDialogProps: ConfirmationDialogBoxProps = useMemo(() => {\n        return {\n            heading: intl.formatMessage({id: 'CardDialog.delete-confirmation-dialog-heading', defaultMessage: 'Confirm card delete!'}),\n            confirmButtonText: intl.formatMessage({id: 'CardDialog.delete-confirmation-dialog-button-text', defaultMessage: 'Delete'}),\n            onConfirm: handleDeleteCard,\n            onClose: () => {\n                setShowConfirmationDialogBox(false)\n            },\n        }\n    }, [handleDeleteCard])\n\n    const renderEventContent = (eventProps: EventContentArg): JSX.Element|null => {\n        const {event} = eventProps\n        const card = cards.find((o) => o.id === event.id) || cards[0]\n\n        return (\n            <>\n                <div\n                    className='EventContent'\n                    onClick={() => props.showCard(event.id)}\n                >\n                    {!props.readonly &&\n                    <MenuWrapper\n                        className='optionsMenu'\n                        stopPropagationOnToggle={true}\n                    >\n                        <CardActionsMenuIcon/>\n                        <CardActionsMenu\n                            cardId={card.id}\n                            boardId={card.boardId}\n                            onClickDelete={() => openConfirmationDialogBox(card)}\n                            onClickDuplicate={() => {\n                                TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.DuplicateCard, {board: board.id, card: card.id})\n                                mutator.duplicateCard(card.id, board.id)\n                            }}\n                        />\n                    </MenuWrapper>}\n                    <div className='octo-icontitle'>\n                        { event.extendedProps.icon ? <div className='octo-icon'>{event.extendedProps.icon}</div> : undefined }\n                        <div\n                            className='fc-event-title'\n                            key='__title'\n                        >{event.title || intl.formatMessage({id: 'CalendarCard.untitled', defaultMessage: 'Untitled'})}</div>\n                    </div>\n                    {visiblePropertyTemplates.map((template) => (\n                        <Tooltip\n                            key={template.id}\n                            title={template.name}\n                        >\n                            <PropertyValueElement\n                                board={board}\n                                readOnly={true}\n                                card={card}\n                                propertyTemplate={template}\n                                showEmptyPlaceholder={false}\n                            />\n                        </Tooltip>\n                    ))}\n                    {visibleBadges &&\n                    <CardBadges card={card}/> }\n                </div>\n            </>\n        )\n    }\n\n    const eventChange = useCallback((eventProps: EventChangeArg) => {\n        const {event} = eventProps\n        if (!event.start) {\n            return\n        }\n        if (!event.end) {\n            return\n        }\n\n        const startDate = new Date(event.start.getTime())\n        const endDate = new Date(event.end.getTime())\n        const dateProperty = createDatePropertyFromCalendarDates(startDate, endDate)\n        const card = cards.find((o) => o.id === event.id)\n        if (card && dateDisplayProperty) {\n            mutator.changePropertyValue(board.id, card, dateDisplayProperty.id, JSON.stringify(dateProperty))\n        }\n    }, [cards, dateDisplayProperty])\n\n    const onNewEvent = useCallback((args: {start: Date, end: Date}) => {\n        let dateProperty: DateProperty\n        if (args.start === args.end) {\n            dateProperty = createDatePropertyFromCalendarDate(args.start)\n        } else {\n            dateProperty = createDatePropertyFromCalendarDates(args.start, args.end)\n            if (dateProperty.to === undefined) {\n                return\n            }\n        }\n\n        const properties: Record<string, string> = {}\n        if (dateDisplayProperty) {\n            properties[dateDisplayProperty.id] = JSON.stringify(dateProperty)\n        }\n\n        props.addCard(properties)\n    }, [props.addCard, dateDisplayProperty])\n\n    const toolbar = useMemo(() => ({\n        left: 'title',\n        center: '',\n        right: 'dayGridWeek dayGridMonth prev,today,next',\n    }), [])\n\n    const buttonText = useMemo(() => ({\n        today: intl.formatMessage({id: 'calendar.today', defaultMessage: 'TODAY'}),\n        month: intl.formatMessage({id: 'calendar.month', defaultMessage: 'Month'}),\n        week: intl.formatMessage({id: 'calendar.week', defaultMessage: 'Week'}),\n    }), [])\n\n    const dayCellContent = useCallback((args: DayCellContentArg): JSX.Element|null => {\n        return (\n            <div className={'dateContainer ' + (canAddCards ? 'with-plus' : '')}>\n                <div\n                    className='addEvent'\n                    onClick={() => onNewEvent({start: args.date, end: args.date})}\n                >\n                    {'+'}\n                </div>\n                <div className='dateDisplay'>\n                    {args.dayNumberText}\n                </div>\n            </div>\n        )\n    }, [dateDisplayProperty, canAddCards])\n\n    return (\n        <div\n            className='CalendarContainer'\n        >\n            <FullCalendar\n                key={activeView.id}\n                dayCellContent={dayCellContent}\n                dayMaxEventRows={5}\n                initialDate={initialDate}\n                plugins={[dayGridPlugin, interactionPlugin]}\n                initialView='dayGridMonth'\n                events={myEventsList}\n                editable={isEditable()}\n                eventResizableFromStart={isEditable()}\n                headerToolbar={toolbar}\n                buttonText={buttonText}\n                eventContent={renderEventContent}\n                eventChange={eventChange}\n                selectable={isSelectable}\n                selectMirror={true}\n                select={onNewEvent}\n            />\n            {showConfirmationDialogBox && <ConfirmationDialogBox dialogBox={confirmDialogProps}/>}\n        </div>\n    )\n}\n\nexport default CalendarFullView\n"
  },
  {
    "path": "webapp/src/components/calendar/fullcalendar.scss",
    "content": "@import '../../styles/z-index';\n\n.CalendarContainer {\n    margin-right: 80px;\n    margin-bottom: 10px;\n    overflow: auto;\n\n    .fc-daygrid-event,\n    .fc-event-main {\n        @include z-index(unset);\n    }\n\n    .fc-daygrid-event,\n    .fc-daygrid-day-number {\n        text-decoration: none;\n        color: var(--link-color-rgb);\n\n        &:hover {\n            background-color: unset;\n\n            .optionsMenu {\n                display: block;\n            }\n        }\n    }\n\n    .EventContent {\n        display: flex;\n        flex-direction: column;\n        align-items: flex-start;\n    }\n\n    .optionsMenu {\n        @include z-index(calendar-options-menu);\n        background-color: rgb(var(--center-channel-bg-rgb));\n        border-radius: var(--default-rad);\n        display: none;\n        position: absolute;\n        right: 0;\n        top: 0;\n    }\n\n    .octo-tooltip {\n        display: flex;\n        max-width: 100%;\n    }\n\n    .octo-propertyvalue {\n        overflow: hidden;\n        white-space: nowrap;\n        text-overflow: ellipsis;\n\n        input[value=''] {\n            display: none;\n        }\n\n        .Label.empty {\n            display: block;\n            padding: 0;\n            margin: 0;\n        }\n    }\n\n    .octo-icontitle {\n        flex: 1 1 auto;\n        font-weight: 600;\n        line-height: 20px;\n        margin: 4px 0;\n\n        .octo-icon {\n            font-size: 16px;\n            margin-right: 2px;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            width: 20px;\n            height: 20px;\n        }\n    }\n\n    .fc {\n        height: 100%;\n    }\n\n    .fc-day {\n        .dateContainer {\n            display: flex;\n            flex-direction: row-reverse;\n            justify-content: right;\n            align-items: center;\n            width: 100%;\n\n            &.with-plus {\n                .addEvent {\n                    display: none;\n                }\n            }\n\n            .addEvent {\n                border-radius: 4px;\n                margin: 0 4px;\n                width: 24px;\n                height: 24px;\n                align-items: center;\n                justify-content: center;\n                display: none;\n                font-size: 20px;\n                box-shadow: var(--elevation-1);\n                background: rgb(var(--center-channel-bg-rgb), 1);\n                border: 1px solid rgb(var(--center-channel-color-rgb), 0.12);\n                color: rgb(var(--center-channel-color-rgb), 0.56);\n                cursor: pointer;\n\n                &:hover {\n                    background: rgb(var(--center-channel-color-rgb), 0.08);\n                    color: rgb(var(--center-channel-color-rgb), 0.72);\n                }\n\n                &:active {\n                    color: rgba(var(--button-bg-rgb), 1);\n                    background: rgba(var(--button-bg-rgb), 0.08);\n                }\n            }\n        }\n\n        &:hover {\n            .dateContainer.with-plus {\n                // display: flex;\n                flex-direction: row;\n                justify-content: space-between;\n                align-items: flex-end;\n\n                .addEvent {\n                    display: flex;\n                }\n            }\n        }\n    }\n\n    .fc .fc-toolbar.fc-header-toolbar {\n        margin-top: 0.75em;\n        margin-bottom: 0.75em;\n\n        .fc-button {\n            background: rgba(var(--button-bg-rgb), 0.08);\n            border-color: transparent;\n            border-radius: 4px;\n            color: rgb(var(--button-bg-rgb));\n            margin: 2px;\n            font-weight: 600;\n            font-size: 14px;\n            height: 32px;\n            display: inline-flex;\n            align-items: center;\n            justify-content: center;\n\n            &.fc-button-active {\n                background-color: rgb(var(--button-bg-rgb));\n                color: rgb(var(--button-color-rgb));\n            }\n        }\n\n        .fc-today-button,\n        .fc-prev-button,\n        .fc-next-button {\n            background-color: transparent;\n            border: 0;\n            color: rgb(var(--center-channel-color-rgb));\n\n            &:hover {\n                background-color: rgba(var(--center-channel-color-rgb), 0.1);\n            }\n        }\n\n        .fc-prev-button,\n        .fc-next-button {\n            width: 32px;\n            height: 32px;\n            color: rgb(var(--center-channel-color-rgb), 0.56);\n\n            &:hover {\n                background: rgb(var(--center-channel-color-rgb), 0.08);\n                color: rgba(var(--center-channel-color-rgb), 0.72);\n            }\n\n            &:active {\n                background-color: rgba(var(--button-bg-rgb), 0.08);\n                color: rgb(var(--button-bg-rgb), 1);\n            }\n\n            &:focus {\n                border: 0;\n            }\n        }\n    }\n\n    .fc-event {\n        border: 1px solid;\n        border-radius: 4px;\n        border-color: rgba(var(--center-channel-color-rgb), 0.16);\n        background-color: rgb(var(--center-channel-bg-rgb));\n        box-shadow: var(--elevation-1);\n        margin: 0 8px 10px;\n        padding: 4px 6px;\n\n        &:hover::before {\n            content: '';\n            position: absolute;\n            left: 0;\n            top: 0;\n            width: 100%;\n            height: 100%;\n            background-color: rgba(var(--center-channel-color-rgb), 0.1);\n        }\n    }\n\n    .Label {\n        font-size: 12px;\n        padding: 2px 6px;\n    }\n\n    .fc-event-main {\n        color: rgb(var(--center-channel-color-rgb));\n    }\n\n    .fc-event-title {\n        display: -webkit-box; // stylelint-disable-line\n        font-size: 14px;\n        white-space: normal;\n        overflow: hidden;\n        text-overflow: ellipsis;\n        -webkit-line-clamp: 5;\n        -webkit-box-orient: vertical; // stylelint-disable-line\n    }\n\n    .fc-day-sat,\n    .fc-day-sun {\n        &:not(.fc-popover) {\n            background-color: rgba(var(--center-channel-color-rgb), 0.04);\n        }\n    }\n\n    .dateDisplay {\n        font-weight: 600;\n        margin: 4px 4px 0 0;\n        min-width: 24px;\n        height: 24px;\n        border-radius: 50px;\n        align-items: center;\n        text-align: center;\n    }\n\n    .fc-daygrid-day.fc-day-today {\n        background-color: unset;\n\n        .fc-daygrid-day-number {\n            .dateDisplay {\n                color: rgba(var(--button-color-rgb));\n                background-color: rgba(var(--button-bg-rgb), 1);\n            }\n        }\n    }\n\n    .fc-daygrid-day-number {\n        align-items: center;\n        width: 100%;\n    }\n\n    .fc-more-link {\n        width: 100%;\n        display: flex;\n        align-items: center;\n        justify-content: center;\n        font-size: 11px;\n        border-radius: 4px;\n        color: rgba(var(--button-bg-rgb), 1);\n        background-color: rgba(var(--button-bg-rgb), 0.08);\n        font-weight: 600;\n        margin-bottom: 8px;\n        height: 24px;\n\n        &:hover {\n            background-color: rgba(var(--button-bg-rgb), 0.12);\n        }\n\n        &:active {\n            background-color: rgba(var(--button-bg-rgb), 0.16);\n        }\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/cardActionsMenu/__snapshots__/cardActionsMenu.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/cardActionsMenu should match snapshot 1`] = `\n<div>\n  <div\n    class=\"Menu noselect left \"\n  >\n    <div\n      class=\"menu-contents\"\n    >\n      <div\n        class=\"menu-options\"\n      >\n        <div>\n          <div\n            aria-label=\"Delete\"\n            class=\"MenuOption TextOption menu-option\"\n            role=\"button\"\n          >\n            <div\n              class=\"d-flex\"\n            >\n              <div\n                class=\"menu-option__icon\"\n              >\n                <i\n                  class=\"CompassIcon icon-trash-can-outline DeleteIcon trash-can-outline\"\n                />\n              </div>\n            </div>\n            <div\n              class=\"menu-option__content\"\n            >\n              <div\n                class=\"menu-name\"\n              >\n                Delete\n              </div>\n            </div>\n            <div\n              class=\"noicon\"\n            />\n          </div>\n        </div>\n        <div>\n          <div\n            aria-label=\"Copy link\"\n            class=\"MenuOption TextOption menu-option\"\n            role=\"button\"\n          >\n            <div\n              class=\"d-flex\"\n            >\n              <div\n                class=\"menu-option__icon\"\n              >\n                <i\n                  class=\"CompassIcon icon-link-variant LinkIcon\"\n                />\n              </div>\n            </div>\n            <div\n              class=\"menu-option__content\"\n            >\n              <div\n                class=\"menu-name\"\n              >\n                Copy link\n              </div>\n            </div>\n            <div\n              class=\"noicon\"\n            />\n          </div>\n        </div>\n        <div />\n      </div>\n      <div\n        class=\"menu-spacer hideOnWidescreen\"\n      />\n      <div\n        class=\"menu-options hideOnWidescreen\"\n      >\n        <div\n          aria-label=\"Cancel\"\n          class=\"MenuOption TextOption menu-option menu-cancel\"\n          role=\"button\"\n        >\n          <div\n            class=\"d-flex\"\n          >\n            <div\n              class=\"noicon\"\n            />\n          </div>\n          <div\n            class=\"menu-option__content\"\n          >\n            <div\n              class=\"menu-name\"\n            >\n              Cancel\n            </div>\n          </div>\n          <div\n            class=\"noicon\"\n          />\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/cardActionsMenu should match snapshot w/ children prop 1`] = `\n<div>\n  <div\n    class=\"Menu noselect left \"\n  >\n    <div\n      class=\"menu-contents\"\n    >\n      <div\n        class=\"menu-options\"\n      >\n        <div>\n          <div\n            aria-label=\"Delete\"\n            class=\"MenuOption TextOption menu-option\"\n            role=\"button\"\n          >\n            <div\n              class=\"d-flex\"\n            >\n              <div\n                class=\"menu-option__icon\"\n              >\n                <i\n                  class=\"CompassIcon icon-trash-can-outline DeleteIcon trash-can-outline\"\n                />\n              </div>\n            </div>\n            <div\n              class=\"menu-option__content\"\n            >\n              <div\n                class=\"menu-name\"\n              >\n                Delete\n              </div>\n            </div>\n            <div\n              class=\"noicon\"\n            />\n          </div>\n        </div>\n        <div>\n          <div\n            aria-label=\"Copy link\"\n            class=\"MenuOption TextOption menu-option\"\n            role=\"button\"\n          >\n            <div\n              class=\"d-flex\"\n            >\n              <div\n                class=\"menu-option__icon\"\n              >\n                <i\n                  class=\"CompassIcon icon-link-variant LinkIcon\"\n                />\n              </div>\n            </div>\n            <div\n              class=\"menu-option__content\"\n            >\n              <div\n                class=\"menu-name\"\n              >\n                Copy link\n              </div>\n            </div>\n            <div\n              class=\"noicon\"\n            />\n          </div>\n        </div>\n        <div>\n          Test.\n        </div>\n      </div>\n      <div\n        class=\"menu-spacer hideOnWidescreen\"\n      />\n      <div\n        class=\"menu-options hideOnWidescreen\"\n      >\n        <div\n          aria-label=\"Cancel\"\n          class=\"MenuOption TextOption menu-option menu-cancel\"\n          role=\"button\"\n        >\n          <div\n            class=\"d-flex\"\n          >\n            <div\n              class=\"noicon\"\n            />\n          </div>\n          <div\n            class=\"menu-option__content\"\n          >\n            <div\n              class=\"menu-name\"\n            >\n              Cancel\n            </div>\n          </div>\n          <div\n            class=\"noicon\"\n          />\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/cardActionsMenu should match snapshot w/ onClickDuplicate prop 1`] = `\n<div>\n  <div\n    class=\"Menu noselect left \"\n  >\n    <div\n      class=\"menu-contents\"\n    >\n      <div\n        class=\"menu-options\"\n      >\n        <div>\n          <div\n            aria-label=\"Delete\"\n            class=\"MenuOption TextOption menu-option\"\n            role=\"button\"\n          >\n            <div\n              class=\"d-flex\"\n            >\n              <div\n                class=\"menu-option__icon\"\n              >\n                <i\n                  class=\"CompassIcon icon-trash-can-outline DeleteIcon trash-can-outline\"\n                />\n              </div>\n            </div>\n            <div\n              class=\"menu-option__content\"\n            >\n              <div\n                class=\"menu-name\"\n              >\n                Delete\n              </div>\n            </div>\n            <div\n              class=\"noicon\"\n            />\n          </div>\n          <div\n            aria-label=\"Duplicate\"\n            class=\"MenuOption TextOption menu-option\"\n            role=\"button\"\n          >\n            <div\n              class=\"d-flex\"\n            >\n              <div\n                class=\"menu-option__icon\"\n              >\n                <i\n                  class=\"CompassIcon icon-content-copy content-copy\"\n                />\n              </div>\n            </div>\n            <div\n              class=\"menu-option__content\"\n            >\n              <div\n                class=\"menu-name\"\n              >\n                Duplicate\n              </div>\n            </div>\n            <div\n              class=\"noicon\"\n            />\n          </div>\n        </div>\n        <div>\n          <div\n            aria-label=\"Copy link\"\n            class=\"MenuOption TextOption menu-option\"\n            role=\"button\"\n          >\n            <div\n              class=\"d-flex\"\n            >\n              <div\n                class=\"menu-option__icon\"\n              >\n                <i\n                  class=\"CompassIcon icon-link-variant LinkIcon\"\n                />\n              </div>\n            </div>\n            <div\n              class=\"menu-option__content\"\n            >\n              <div\n                class=\"menu-name\"\n              >\n                Copy link\n              </div>\n            </div>\n            <div\n              class=\"noicon\"\n            />\n          </div>\n        </div>\n        <div />\n      </div>\n      <div\n        class=\"menu-spacer hideOnWidescreen\"\n      />\n      <div\n        class=\"menu-options hideOnWidescreen\"\n      >\n        <div\n          aria-label=\"Cancel\"\n          class=\"MenuOption TextOption menu-option menu-cancel\"\n          role=\"button\"\n        >\n          <div\n            class=\"d-flex\"\n          >\n            <div\n              class=\"noicon\"\n            />\n          </div>\n          <div\n            class=\"menu-option__content\"\n          >\n            <div\n              class=\"menu-name\"\n            >\n              Cancel\n            </div>\n          </div>\n          <div\n            class=\"noicon\"\n          />\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/cardActionsMenu/cardActionsMenu.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport '@testing-library/jest-dom'\nimport {act, render} from '@testing-library/react'\nimport React from 'react'\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport {TestBlockFactory} from '../../test/testBlockFactory'\nimport {mockDOM, mockStateStore, wrapIntl} from '../../testUtils'\n\nimport CardActionsMenu from './cardActionsMenu'\n\nbeforeAll(() => {\n    mockDOM()\n})\n\ndescribe('components/cardActionsMenu', () => {\n    const board = TestBlockFactory.createBoard()\n    board.id = 'boardId'\n\n    const state = {\n        boards: {\n            current: board.id,\n            boards: {\n                [board.id]: board,\n            },\n            templates: [],\n            myBoardMemberships: {\n                [board.id]: {userId: 'user_id_1', schemeAdmin: true},\n            },\n        },\n        teams: {\n            current: {id: 'team-id'},\n        },\n        users: {\n            me: {\n                id: 'user_id_1',\n            },\n        },\n    }\n    const store = mockStateStore([], state)\n\n    test('should match snapshot', async () => {\n        let container\n        await act(async () => {\n            const result = render(wrapIntl(\n                <ReduxProvider store={store}>\n                    <CardActionsMenu\n                        cardId='123'\n                        boardId='345'\n                        onClickDelete={jest.fn()}\n                    />\n                </ReduxProvider>,\n            ))\n            container = result.container\n        })\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot w/ onClickDuplicate prop', async () => {\n        let container\n        await act(async () => {\n            const result = render(wrapIntl(\n                <ReduxProvider store={store}>\n                    <CardActionsMenu\n                        cardId='123'\n                        boardId='345'\n                        onClickDelete={jest.fn()}\n                        onClickDuplicate={jest.fn()}\n                    />\n                </ReduxProvider>,\n            ))\n            container = result.container\n        })\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot w/ children prop', async () => {\n        let container\n        await act(async () => {\n            const result = render(wrapIntl(\n                <ReduxProvider store={store}>\n                    <CardActionsMenu\n                        cardId='123'\n                        boardId='345'\n                        onClickDelete={jest.fn()}\n                    >\n                        <React.Fragment>\n                            {'Test.'}\n                        </React.Fragment>\n                    </CardActionsMenu>\n                </ReduxProvider>,\n            ))\n            container = result.container\n        })\n        expect(container).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/cardActionsMenu/cardActionsMenu.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React, {ReactNode} from 'react'\nimport {useIntl} from 'react-intl'\n\nimport DeleteIcon from '../../widgets/icons/delete'\nimport Menu from '../../widgets/menu'\nimport BoardPermissionGate from '../permissions/boardPermissionGate'\nimport DuplicateIcon from '../../widgets/icons/duplicate'\nimport LinkIcon from '../../widgets/icons/Link'\nimport {Utils} from '../../utils'\nimport {Permission} from '../../constants'\nimport {sendFlashMessage} from '../flashMessages'\nimport {IUser} from '../../user'\nimport {getMe} from '../../store/users'\nimport {useAppSelector} from '../../store/hooks'\nimport TelemetryClient, {TelemetryActions, TelemetryCategory} from '../../telemetry/telemetryClient'\n\ntype Props = {\n    cardId: string\n    boardId: string\n    onClickDelete: () => void\n    onClickDuplicate?: () => void\n    children?: ReactNode\n}\n\nexport const CardActionsMenu = (props: Props): JSX.Element => {\n    const {cardId} = props\n\n    const me = useAppSelector<IUser|null>(getMe)\n    const intl = useIntl()\n\n    const handleDeleteCard = () => {\n        TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.DeleteCard, {board: props.boardId, card: props.cardId})\n        props.onClickDelete()\n    }\n\n    const handleDuplicateCard = () => {\n        if (props.onClickDuplicate) {\n            TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.DuplicateCard, {board: props.boardId, card: props.cardId})\n            props.onClickDuplicate()\n        }\n    }\n\n    return (\n        <Menu position='left'>\n            <BoardPermissionGate permissions={[Permission.ManageBoardCards]}>\n                <Menu.Text\n                    icon={<DeleteIcon/>}\n                    id='delete'\n                    name={intl.formatMessage({id: 'CardActionsMenu.delete', defaultMessage: 'Delete'})}\n                    onClick={handleDeleteCard}\n                />\n                {props.onClickDuplicate &&\n                <Menu.Text\n                    icon={<DuplicateIcon/>}\n                    id='duplicate'\n                    name={intl.formatMessage({id: 'CardActionsMenu.duplicate', defaultMessage: 'Duplicate'})}\n                    onClick={handleDuplicateCard}\n                />}\n            </BoardPermissionGate>\n            {me?.id !== 'single-user' &&\n                <Menu.Text\n                    icon={<LinkIcon/>}\n                    id='copy'\n                    name={intl.formatMessage({id: 'CardActionsMenu.copyLink', defaultMessage: 'Copy link'})}\n                    onClick={() => {\n                        let cardLink = window.location.href\n\n                        if (!cardLink.includes(cardId)) {\n                            cardLink += `/${cardId}`\n                        }\n\n                        Utils.copyTextToClipboard(cardLink)\n                        sendFlashMessage({content: intl.formatMessage({id: 'CardActionsMenu.copiedLink', defaultMessage: 'Copied!'}), severity: 'high'})\n                    }}\n                />\n            }\n            {props.children}\n        </Menu>\n    )\n}\n\nexport default CardActionsMenu\n"
  },
  {
    "path": "webapp/src/components/cardActionsMenu/cardActionsMenuIcon.scss",
    "content": ".CardActionsMenuIcon {\n    border-radius: 3px;\n    padding: 0;\n    box-shadow: rgba(var(--center-channel-color-rgb), 0.1) 0 0 0 1px,\n        rgba(var(--center-channel-color-rgb), 0.1) 0 2px 4px;\n    height: 24px;\n    width: 24px;\n\n    &:hover {\n        background: rgb(var(--center-channel-color-rgb), 0.1);\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/cardActionsMenu/cardActionsMenuIcon.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport OptionsIcon from '../../widgets/icons/options'\nimport IconButton from '../../widgets/buttons/iconButton'\n\nimport './cardActionsMenuIcon.scss'\n\nconst CardActionsMenuIcon = () => {\n    return (\n        <IconButton\n            className='CardActionsMenuIcon'\n            icon={<OptionsIcon/>}\n        />\n    )\n}\n\nexport default CardActionsMenuIcon\n"
  },
  {
    "path": "webapp/src/components/cardBadges.scss",
    "content": ".CardBadges {\n    display: flex;\n    align-items: center;\n    margin-top: 4px;\n    height: 24px;\n\n    span {\n        display: flex;\n        align-items: center;\n        margin-right: 4px;\n\n        .Icon,\n        .CompassIcon {\n            color: rgba(var(--center-channel-color-rgb), 0.64);\n            fill: currentColor;\n            margin-right: 4px;\n        }\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/cardBadges.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport {render, screen} from '@testing-library/react'\nimport '@testing-library/jest-dom'\n\nimport {TestBlockFactory} from '../test/testBlockFactory'\nimport {blocksById, mockStateStore, wrapDNDIntl} from '../testUtils'\n\nimport {RootState} from '../store'\n\nimport {CommentBlock} from '../blocks/commentBlock'\n\nimport {CheckboxBlock} from '../blocks/checkboxBlock'\n\nimport CardBadges from './cardBadges'\n\ndescribe('components/cardBadges', () => {\n    const board = TestBlockFactory.createBoard()\n    const card = TestBlockFactory.createCard(board)\n    const emptyCard = TestBlockFactory.createCard(board)\n    const text = TestBlockFactory.createText(card)\n    text.title = `\n                ## Header\n                - [x] one\n                - [ ] two\n                - [x] three\n   `.replace(/\\n\\s+/gm, '\\n')\n    const comments = Array.from(Array<CommentBlock>(3), () => TestBlockFactory.createComment(card))\n    const checkboxes = Array.from(Array<CheckboxBlock>(4), () => TestBlockFactory.createCheckbox(card))\n    checkboxes[2].fields.value = true\n\n    const state: Partial<RootState> = {\n        cards: {\n            current: '',\n            limitTimestamp: 0,\n            cards: blocksById([card, emptyCard]),\n            templates: {},\n            cardHiddenWarning: true,\n        },\n        comments: {\n            comments: blocksById(comments),\n            commentsByCard: {\n                [card.id]: comments,\n            },\n        },\n        contents: {\n            contents: {\n                ...blocksById([text]),\n                ...blocksById(checkboxes),\n            },\n            contentsByCard: {\n                [card.id]: [text, ...checkboxes],\n            },\n        },\n    }\n    const store = mockStateStore([], state)\n\n    it('should match snapshot', () => {\n        const {container} = render(wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <CardBadges card={card}/>\n            </ReduxProvider>,\n        ))\n        expect(container).toMatchSnapshot()\n    })\n\n    it('should match snapshot for empty card', () => {\n        const {container} = render(wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <CardBadges card={emptyCard}/>\n            </ReduxProvider>,\n        ))\n        expect(container).toMatchSnapshot()\n    })\n\n    it('should render correct values', () => {\n        render(wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <CardBadges card={card}/>\n            </ReduxProvider>,\n        ))\n        expect(screen.getByTitle(/card has a description/)).toBeInTheDocument()\n        expect(screen.getByTitle('Comments')).toHaveTextContent('3')\n        expect(screen.getByTitle('Checkboxes')).toHaveTextContent('3/7')\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/cardBadges.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useMemo} from 'react'\nimport {useIntl} from 'react-intl'\n\nimport {Card} from '../blocks/card'\nimport {useAppSelector} from '../store/hooks'\nimport {getCardContents} from '../store/contents'\nimport {getCardComments} from '../store/comments'\nimport {ContentBlock} from '../blocks/contentBlock'\nimport {CommentBlock} from '../blocks/commentBlock'\nimport TextIcon from '../widgets/icons/text'\nimport MessageIcon from '../widgets/icons/message'\nimport CheckIcon from '../widgets/icons/check'\nimport {Utils} from '../utils'\n\nimport './cardBadges.scss'\n\ntype Props = {\n    card: Card\n    className?: string\n}\n\ntype Checkboxes = {\n    total: number\n    checked: number\n}\n\ntype Badges = {\n    description: boolean\n    comments: number\n    checkboxes: Checkboxes\n}\n\nconst hasBadges = (badges: Badges): boolean => {\n    return badges.description || badges.comments > 0 || badges.checkboxes.total > 0\n}\n\ntype ContentsType = Array<ContentBlock | ContentBlock[]>\n\nconst calculateBadges = (contents: ContentsType, comments: CommentBlock[]): Badges => {\n    let text = 0\n    let total = 0\n    let checked = 0\n\n    const updateCounters = (block: ContentBlock) => {\n        if (block.type === 'text') {\n            text++\n            const checkboxes = Utils.countCheckboxesInMarkdown(block.title)\n            total += checkboxes.total\n            checked += checkboxes.checked\n        } else if (block.type === 'checkbox') {\n            total++\n            if (block.fields.value) {\n                checked++\n            }\n        }\n    }\n\n    for (const content of contents) {\n        if (Array.isArray(content)) {\n            content.forEach(updateCounters)\n        } else {\n            updateCounters(content)\n        }\n    }\n    return {\n        description: text > 0,\n        comments: comments.length,\n        checkboxes: {\n            total,\n            checked,\n        },\n    }\n}\n\nconst CardBadges = (props: Props) => {\n    const {card, className} = props\n    const contents = useAppSelector(getCardContents(card.id))\n    const comments = useAppSelector(getCardComments(card.id))\n    const badges = useMemo(() => calculateBadges(contents, comments), [contents, comments])\n    if (!hasBadges(badges)) {\n        return null\n    }\n    const intl = useIntl()\n    const {checkboxes} = badges\n    return (\n        <div className={`CardBadges ${className || ''}`}>\n            {badges.description &&\n                <span title={intl.formatMessage({id: 'CardBadges.title-description', defaultMessage: 'This card has a description'})}>\n                    <TextIcon/>\n                </span>}\n            {badges.comments > 0 &&\n                <span title={intl.formatMessage({id: 'CardBadges.title-comments', defaultMessage: 'Comments'})}>\n                    <MessageIcon/>\n                    {badges.comments}\n                </span>}\n            {checkboxes.total > 0 &&\n                <span title={intl.formatMessage({id: 'CardBadges.title-checkboxes', defaultMessage: 'Checkboxes'})}>\n                    <CheckIcon/>\n                    {`${checkboxes.checked}/${checkboxes.total}`}\n                </span>}\n        </div>\n    )\n}\n\nexport default React.memo(CardBadges)\n"
  },
  {
    "path": "webapp/src/components/cardDetail/__snapshots__/cardDetail.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/cardDetail/CardDetail should render hidden view if limited 1`] = `\n<div>\n  <div\n    class=\"CardDetail  CardDetail--is-limited\"\n  >\n    <div\n      class=\"IconSelector\"\n    >\n      <div\n        class=\"octo-icon size-l readonly\"\n      >\n        <span>\n          i\n        </span>\n      </div>\n    </div>\n    <div\n      class=\"EditableAreaWrap\"\n    >\n      <textarea\n        class=\"EditableArea Editable readonly title\"\n        height=\"0\"\n        placeholder=\"Untitled\"\n        readonly=\"\"\n        rows=\"1\"\n        spellcheck=\"true\"\n        title=\"title\"\n      >\n        title\n      </textarea>\n      <div\n        class=\"EditableAreaContainer\"\n      >\n        <textarea\n          aria-hidden=\"true\"\n          class=\"EditableAreaReference Editable readonly title\"\n          dir=\"auto\"\n          disabled=\"\"\n          rows=\"1\"\n        >\n          title\n        </textarea>\n      </div>\n    </div>\n    <div\n      class=\"CardDetail__limited-wrapper\"\n    >\n      <span\n        class=\"CardDetail__limited-bg\"\n      >\n        <svg\n          fill=\"none\"\n          height=\"521\"\n          viewBox=\"0 0 468 521\"\n          width=\"468\"\n          xmlns=\"http://www.w3.org/2000/svg\"\n        >\n          <rect\n            fill=\"#3F4350\"\n            fill-opacity=\"0.08\"\n            height=\"32\"\n            rx=\"4\"\n            width=\"156\"\n          />\n          <rect\n            fill=\"#3F4350\"\n            fill-opacity=\"0.08\"\n            height=\"32\"\n            rx=\"4\"\n            width=\"296\"\n            x=\"172\"\n          />\n          <rect\n            fill=\"#3F4350\"\n            fill-opacity=\"0.08\"\n            height=\"32\"\n            rx=\"4\"\n            width=\"156\"\n            y=\"48\"\n          />\n          <rect\n            fill=\"#3F4350\"\n            fill-opacity=\"0.08\"\n            height=\"32\"\n            rx=\"4\"\n            width=\"296\"\n            x=\"172\"\n            y=\"48\"\n          />\n          <rect\n            fill=\"#3F4350\"\n            fill-opacity=\"0.08\"\n            height=\"32\"\n            rx=\"4\"\n            width=\"156\"\n            y=\"96\"\n          />\n          <rect\n            fill=\"#3F4350\"\n            fill-opacity=\"0.08\"\n            height=\"32\"\n            rx=\"4\"\n            width=\"296\"\n            x=\"172\"\n            y=\"96\"\n          />\n          <rect\n            fill=\"#3F4350\"\n            fill-opacity=\"0.08\"\n            height=\"32\"\n            rx=\"4\"\n            width=\"156\"\n            y=\"144\"\n          />\n          <rect\n            fill=\"#3F4350\"\n            fill-opacity=\"0.08\"\n            height=\"32\"\n            rx=\"4\"\n            width=\"296\"\n            x=\"172\"\n            y=\"144\"\n          />\n          <rect\n            fill=\"#3D3C40\"\n            fill-opacity=\"0.16\"\n            height=\"1\"\n            width=\"468\"\n            y=\"192\"\n          />\n          <rect\n            fill=\"#3F4350\"\n            fill-opacity=\"0.08\"\n            height=\"32\"\n            rx=\"4\"\n            width=\"468\"\n            y=\"209\"\n          />\n          <rect\n            fill=\"#3F4350\"\n            fill-opacity=\"0.08\"\n            height=\"32\"\n            rx=\"4\"\n            width=\"468\"\n            y=\"257\"\n          />\n          <rect\n            fill=\"#3D3C40\"\n            fill-opacity=\"0.16\"\n            height=\"1\"\n            width=\"468\"\n            y=\"305\"\n          />\n          <rect\n            fill=\"#3F4350\"\n            fill-opacity=\"0.08\"\n            height=\"199\"\n            rx=\"4\"\n            width=\"468\"\n            y=\"322\"\n          />\n        </svg>\n      </span>\n      <p\n        class=\"CardDetail__limited-title\"\n      >\n        This card is hidden\n      </p>\n      <p\n        class=\"CardDetail__limited-body\"\n      >\n        Upgrade to our Professional or Enterprise plan to view archived cards, have unlimited views per boards, unlimited cards and more.\n        <br />\n        <a\n          class=\"CardDetail__limited-link\"\n          role=\"button\"\n        >\n          Learn more about our plans.\n        </a>\n      </p>\n      <button\n        class=\"Button emphasis--primary size--large CardDetail__limited-button\"\n        type=\"button\"\n      >\n        <span>\n          Upgrade\n        </span>\n      </button>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/cardDetail/CardDetail should show add comments tour tip 1`] = `\n<div\n  class=\"tippy-box tutorial-tour-tip__box AddCommentTourStep\"\n  data-animation=\"scale-subtle\"\n  data-escaped=\"\"\n  data-placement=\"right-end\"\n  data-reference-hidden=\"\"\n  data-state=\"visible\"\n  role=\"tooltip\"\n  style=\"max-width: 320px; transition-duration: 250ms;\"\n  tabindex=\"-1\"\n>\n  <div\n    class=\"tippy-content\"\n    data-state=\"visible\"\n    style=\"transition-duration: 250ms;\"\n  >\n    <div>\n      <div>\n        <div\n          class=\"tutorial-tour-tip__header\"\n        >\n          <h4\n            class=\"tutorial-tour-tip__header__title\"\n          >\n            Add comments\n          </h4>\n          <button\n            class=\"IconButton tutorial-tour-tip__header__close size--small\"\n            type=\"button\"\n          >\n            <i\n              class=\"CompassIcon icon-close CloseIcon\"\n            />\n          </button>\n        </div>\n        <div\n          class=\"tutorial-tour-tip__body\"\n        >\n          You can comment on issues, and even @mention your fellow Mattermost users to get their attention.\n        </div>\n        <div\n          class=\"tutorial-tour-tip__image\"\n        >\n          <img\n            alt=\"tutorial tour tip product image\"\n            src=\"http://localhost/test-file-stub\"\n          />\n        </div>\n        <div\n          class=\"tutorial-tour-tip__footer\"\n        >\n          <div\n            class=\"tutorial-tour-tip__footer-buttons\"\n          >\n            <div\n              class=\"tutorial-tour-tip__circles-ctr\"\n            >\n              <div\n                class=\"tutorial-tour-tip__circular-ring\"\n              >\n                <a\n                  class=\"tutorial-tour-tip__circle\"\n                  data-screen=\"0\"\n                  href=\"#\"\n                />\n              </div>\n              <div\n                class=\"tutorial-tour-tip__circular-ring tutorial-tour-tip__circular-ring-active\"\n              >\n                <a\n                  class=\"tutorial-tour-tip__circle active\"\n                  data-screen=\"1\"\n                  href=\"#\"\n                />\n              </div>\n              <div\n                class=\"tutorial-tour-tip__circular-ring\"\n              >\n                <a\n                  class=\"tutorial-tour-tip__circle\"\n                  data-screen=\"2\"\n                  href=\"#\"\n                />\n              </div>\n            </div>\n            <div\n              class=\"tutorial-tour-tip__btn-ctr\"\n            >\n              <button\n                class=\"Button emphasis--tertiary size--small\"\n                title=\"Previous\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-chevron-left icon\"\n                />\n                <span>\n                  Previous\n                </span>\n              </button>\n              <button\n                class=\"Button filled size--small tipNextButton\"\n                type=\"button\"\n              >\n                <span>\n                  Next\n                </span>\n                <i\n                  class=\"CompassIcon icon-chevron-right icon\"\n                />\n              </button>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n  <div\n    class=\"tippy-arrow\"\n    style=\"position: absolute; top: 0px; transform: translate(0px, 3px);\"\n  />\n</div>\n`;\n\nexports[`components/cardDetail/CardDetail should show add description tour tip 1`] = `\n<div\n  class=\"tippy-box tutorial-tour-tip__box AddDescriptionTourStep\"\n  data-animation=\"scale-subtle\"\n  data-escaped=\"\"\n  data-placement=\"top-start\"\n  data-reference-hidden=\"\"\n  data-state=\"visible\"\n  role=\"tooltip\"\n  style=\"max-width: 320px; transition-duration: 250ms;\"\n  tabindex=\"-1\"\n>\n  <div\n    class=\"tippy-content\"\n    data-state=\"visible\"\n    style=\"transition-duration: 250ms;\"\n  >\n    <div>\n      <div>\n        <div\n          class=\"tutorial-tour-tip__header\"\n        >\n          <h4\n            class=\"tutorial-tour-tip__header__title\"\n          >\n            Add description\n          </h4>\n          <button\n            class=\"IconButton tutorial-tour-tip__header__close size--small\"\n            type=\"button\"\n          >\n            <i\n              class=\"CompassIcon icon-close CloseIcon\"\n            />\n          </button>\n        </div>\n        <div\n          class=\"tutorial-tour-tip__body\"\n        >\n          Add a description to your card so your teammates know what the card is about.\n        </div>\n        <div\n          class=\"tutorial-tour-tip__image\"\n        >\n          <img\n            alt=\"tutorial tour tip product image\"\n            src=\"http://localhost/test-file-stub\"\n          />\n        </div>\n        <div\n          class=\"tutorial-tour-tip__footer\"\n        >\n          <div\n            class=\"tutorial-tour-tip__footer-buttons\"\n          >\n            <div\n              class=\"tutorial-tour-tip__circles-ctr\"\n            >\n              <div\n                class=\"tutorial-tour-tip__circular-ring\"\n              >\n                <a\n                  class=\"tutorial-tour-tip__circle\"\n                  data-screen=\"0\"\n                  href=\"#\"\n                />\n              </div>\n              <div\n                class=\"tutorial-tour-tip__circular-ring\"\n              >\n                <a\n                  class=\"tutorial-tour-tip__circle\"\n                  data-screen=\"1\"\n                  href=\"#\"\n                />\n              </div>\n              <div\n                class=\"tutorial-tour-tip__circular-ring tutorial-tour-tip__circular-ring-active\"\n              >\n                <a\n                  class=\"tutorial-tour-tip__circle active\"\n                  data-screen=\"2\"\n                  href=\"#\"\n                />\n              </div>\n            </div>\n            <div\n              class=\"tutorial-tour-tip__btn-ctr\"\n            >\n              <button\n                class=\"Button emphasis--tertiary size--small\"\n                title=\"Previous\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-chevron-left icon\"\n                />\n                <span>\n                  Previous\n                </span>\n              </button>\n              <button\n                class=\"Button filled size--small tipNextButton\"\n                type=\"button\"\n              >\n                <span>\n                  Done\n                </span>\n                \n              </button>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n  <div\n    class=\"tippy-arrow\"\n    style=\"position: absolute; left: 0px; transform: translate(3px, 0px);\"\n  />\n</div>\n`;\n\nexports[`components/cardDetail/CardDetail should show add properties tour tip 1`] = `\n<div\n  class=\"tippy-box tutorial-tour-tip__box AddPropertiesTourStep\"\n  data-animation=\"scale-subtle\"\n  data-escaped=\"\"\n  data-placement=\"right-end\"\n  data-reference-hidden=\"\"\n  data-state=\"visible\"\n  role=\"tooltip\"\n  style=\"max-width: 320px; transition-duration: 250ms;\"\n  tabindex=\"-1\"\n>\n  <div\n    class=\"tippy-content\"\n    data-state=\"visible\"\n    style=\"transition-duration: 250ms;\"\n  >\n    <div>\n      <div>\n        <div\n          class=\"tutorial-tour-tip__header\"\n        >\n          <h4\n            class=\"tutorial-tour-tip__header__title\"\n          >\n            Add properties\n          </h4>\n          <button\n            class=\"IconButton tutorial-tour-tip__header__close size--small\"\n            type=\"button\"\n          >\n            <i\n              class=\"CompassIcon icon-close CloseIcon\"\n            />\n          </button>\n        </div>\n        <div\n          class=\"tutorial-tour-tip__body\"\n        >\n          Add various properties to cards to make them more powerful!\n        </div>\n        <div\n          class=\"tutorial-tour-tip__image\"\n        >\n          <img\n            alt=\"tutorial tour tip product image\"\n            src=\"http://localhost/test-file-stub\"\n          />\n        </div>\n        <div\n          class=\"tutorial-tour-tip__footer\"\n        >\n          <div\n            class=\"tutorial-tour-tip__footer-buttons\"\n          >\n            <div\n              class=\"tutorial-tour-tip__circles-ctr\"\n            >\n              <div\n                class=\"tutorial-tour-tip__circular-ring tutorial-tour-tip__circular-ring-active\"\n              >\n                <a\n                  class=\"tutorial-tour-tip__circle active\"\n                  data-screen=\"0\"\n                  href=\"#\"\n                />\n              </div>\n              <div\n                class=\"tutorial-tour-tip__circular-ring\"\n              >\n                <a\n                  class=\"tutorial-tour-tip__circle\"\n                  data-screen=\"1\"\n                  href=\"#\"\n                />\n              </div>\n              <div\n                class=\"tutorial-tour-tip__circular-ring\"\n              >\n                <a\n                  class=\"tutorial-tour-tip__circle\"\n                  data-screen=\"2\"\n                  href=\"#\"\n                />\n              </div>\n            </div>\n            <div\n              class=\"tutorial-tour-tip__btn-ctr\"\n            >\n              <button\n                class=\"Button filled size--small tipNextButton\"\n                type=\"button\"\n              >\n                <span>\n                  Next\n                </span>\n                <i\n                  class=\"CompassIcon icon-chevron-right icon\"\n                />\n              </button>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n  <div\n    class=\"tippy-arrow\"\n    style=\"position: absolute; top: 0px; transform: translate(0px, 3px);\"\n  />\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/cardDetail/__snapshots__/cardDetailContents.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/cardDetail/cardDetailContents should match snapshot 1`] = `\n<div>\n  <div\n    class=\"octo-content CardDetailContents\"\n  >\n    <div\n      class=\"octo-block\"\n    >\n      <div\n        class=\"octo-block-margin\"\n      />\n      <div\n        class=\"MarkdownEditor octo-editor  \"\n      >\n        <div\n          class=\"octo-editor-preview octo-placeholder\"\n          data-testid=\"preview-element\"\n        >\n          <p>\n            Add a description...\n          </p>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/cardDetail/cardDetailContents should match snapshot after drag and drop event 1`] = `\n<div>\n  <div\n    class=\"octo-content\"\n  >\n    <div>\n      <div\n        class=\"addToRow \"\n        style=\"width: 94%; height: 10px; margin-left: 48px;\"\n      />\n      <div\n        class=\"rowContents\"\n      >\n        <div\n          class=\"ContentBlock octo-block\"\n        >\n          <div\n            class=\"octo-block-margin\"\n          >\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                />\n              </button>\n            </div>\n            <div\n              class=\"dnd-handle\"\n              draggable=\"true\"\n            >\n              <svg\n                class=\"GripIcon Icon\"\n                viewBox=\"0 0 24 24\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n              >\n                <path\n                  d=\"M0 0h24v24H0V0z\"\n                  fill=\"none\"\n                />\n                <path\n                  d=\"M11 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm-2-8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z\"\n                />\n              </svg>\n            </div>\n          </div>\n          <div\n            class=\"addToRow \"\n            style=\"flex: 0 0 auto; height: 100%;\"\n          />\n          <div\n            class=\"DividerElement\"\n          />\n        </div>\n        <div\n          class=\"addToRow \"\n        />\n      </div>\n    </div>\n    <div>\n      <div\n        class=\"addToRow \"\n        style=\"width: 94%; height: 10px; margin-left: 48px;\"\n      />\n      <div\n        style=\"display: flex;\"\n      >\n        <div\n          class=\"rowContents\"\n          style=\"width: 50%;\"\n        >\n          <div\n            class=\"ContentBlock octo-block\"\n          >\n            <div\n              class=\"octo-block-margin\"\n            >\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n              <div\n                class=\"dnd-handle\"\n                draggable=\"true\"\n              >\n                <svg\n                  class=\"GripIcon Icon\"\n                  viewBox=\"0 0 24 24\"\n                  xmlns=\"http://www.w3.org/2000/svg\"\n                >\n                  <path\n                    d=\"M0 0h24v24H0V0z\"\n                    fill=\"none\"\n                  />\n                  <path\n                    d=\"M11 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm-2-8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z\"\n                  />\n                </svg>\n              </div>\n            </div>\n            <div\n              class=\"addToRow \"\n              style=\"flex: 0 0 auto; height: 100%;\"\n            />\n            <div\n              class=\"DividerElement\"\n            />\n          </div>\n          <div\n            class=\"addToRow \"\n          />\n        </div>\n        <div\n          class=\"rowContents\"\n          style=\"width: 50%;\"\n        >\n          <div\n            class=\"ContentBlock octo-block\"\n          >\n            <div\n              class=\"octo-block-margin\"\n            >\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n              <div\n                class=\"dnd-handle\"\n                draggable=\"true\"\n              >\n                <svg\n                  class=\"GripIcon Icon\"\n                  viewBox=\"0 0 24 24\"\n                  xmlns=\"http://www.w3.org/2000/svg\"\n                >\n                  <path\n                    d=\"M0 0h24v24H0V0z\"\n                    fill=\"none\"\n                  />\n                  <path\n                    d=\"M11 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm-2-8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z\"\n                  />\n                </svg>\n              </div>\n            </div>\n            <div\n              class=\"DividerElement\"\n            />\n          </div>\n          <div\n            class=\"addToRow \"\n          />\n        </div>\n      </div>\n      <div\n        class=\"addToRow \"\n        style=\"width: 94%; height: 10px; margin-left: 48px;\"\n      />\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/cardDetail/cardDetailContents should match snapshot after drag and drop event 2 1`] = `\n<div>\n  <div\n    class=\"octo-content\"\n  >\n    <div>\n      <div\n        class=\"addToRow \"\n        style=\"width: 94%; height: 10px; margin-left: 48px;\"\n      />\n      <div\n        class=\"rowContents\"\n      >\n        <div\n          class=\"ContentBlock octo-block\"\n        >\n          <div\n            class=\"octo-block-margin\"\n          >\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                />\n              </button>\n            </div>\n            <div\n              class=\"dnd-handle\"\n              draggable=\"true\"\n            >\n              <svg\n                class=\"GripIcon Icon\"\n                viewBox=\"0 0 24 24\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n              >\n                <path\n                  d=\"M0 0h24v24H0V0z\"\n                  fill=\"none\"\n                />\n                <path\n                  d=\"M11 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm-2-8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z\"\n                />\n              </svg>\n            </div>\n          </div>\n          <div\n            class=\"addToRow \"\n            style=\"flex: 0 0 auto; height: 100%;\"\n          />\n          <div\n            class=\"DividerElement\"\n          />\n        </div>\n        <div\n          class=\"addToRow \"\n        />\n      </div>\n    </div>\n    <div>\n      <div\n        class=\"addToRow \"\n        style=\"width: 94%; height: 10px; margin-left: 48px;\"\n      />\n      <div\n        class=\"rowContents\"\n      >\n        <div\n          class=\"ContentBlock octo-block\"\n        >\n          <div\n            class=\"octo-block-margin\"\n          >\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                />\n              </button>\n            </div>\n            <div\n              class=\"dnd-handle\"\n              draggable=\"true\"\n            >\n              <svg\n                class=\"GripIcon Icon\"\n                viewBox=\"0 0 24 24\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n              >\n                <path\n                  d=\"M0 0h24v24H0V0z\"\n                  fill=\"none\"\n                />\n                <path\n                  d=\"M11 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm-2-8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z\"\n                />\n              </svg>\n            </div>\n          </div>\n          <div\n            class=\"addToRow \"\n            style=\"flex: 0 0 auto; height: 100%;\"\n          />\n          <div\n            class=\"DividerElement\"\n          />\n        </div>\n        <div\n          class=\"addToRow \"\n        />\n      </div>\n      <div\n        class=\"addToRow \"\n        style=\"width: 94%; height: 10px; margin-left: 48px;\"\n      />\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/cardDetail/cardDetailContents should match snapshot after drag and drop event 3 1`] = `\n<div>\n  <div\n    class=\"octo-content\"\n  >\n    <div>\n      <div\n        class=\"addToRow \"\n        style=\"width: 94%; height: 10px; margin-left: 48px;\"\n      />\n      <div\n        class=\"rowContents\"\n      >\n        <div\n          class=\"ContentBlock octo-block\"\n        >\n          <div\n            class=\"octo-block-margin\"\n          >\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                />\n              </button>\n            </div>\n            <div\n              class=\"dnd-handle\"\n              draggable=\"true\"\n            >\n              <svg\n                class=\"GripIcon Icon\"\n                viewBox=\"0 0 24 24\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n              >\n                <path\n                  d=\"M0 0h24v24H0V0z\"\n                  fill=\"none\"\n                />\n                <path\n                  d=\"M11 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm-2-8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z\"\n                />\n              </svg>\n            </div>\n          </div>\n          <div\n            class=\"addToRow \"\n            style=\"flex: 0 0 auto; height: 100%;\"\n          />\n          <div\n            class=\"DividerElement\"\n          />\n        </div>\n        <div\n          class=\"addToRow \"\n        />\n      </div>\n    </div>\n    <div>\n      <div\n        class=\"addToRow \"\n        style=\"width: 94%; height: 10px; margin-left: 48px;\"\n      />\n      <div\n        class=\"rowContents\"\n      >\n        <div\n          class=\"ContentBlock octo-block\"\n        >\n          <div\n            class=\"octo-block-margin\"\n          >\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                />\n              </button>\n            </div>\n            <div\n              class=\"dnd-handle\"\n              draggable=\"true\"\n            >\n              <svg\n                class=\"GripIcon Icon\"\n                viewBox=\"0 0 24 24\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n              >\n                <path\n                  d=\"M0 0h24v24H0V0z\"\n                  fill=\"none\"\n                />\n                <path\n                  d=\"M11 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm-2-8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z\"\n                />\n              </svg>\n            </div>\n          </div>\n          <div\n            class=\"addToRow \"\n            style=\"flex: 0 0 auto; height: 100%;\"\n          />\n          <div\n            class=\"DividerElement\"\n          />\n        </div>\n        <div\n          class=\"addToRow \"\n        />\n      </div>\n      <div\n        class=\"addToRow \"\n        style=\"width: 94%; height: 10px; margin-left: 48px;\"\n      />\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/cardDetail/cardDetailContents should match snapshot with contents array 1`] = `\n<div>\n  <div\n    class=\"octo-content\"\n  >\n    <div>\n      <div\n        class=\"addToRow \"\n        style=\"width: 94%; height: 10px; margin-left: 48px;\"\n      />\n      <div\n        class=\"rowContents\"\n      >\n        <div\n          class=\"ContentBlock octo-block\"\n        >\n          <div\n            class=\"octo-block-margin\"\n          >\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                />\n              </button>\n            </div>\n            <div\n              class=\"dnd-handle\"\n              draggable=\"true\"\n            >\n              <svg\n                class=\"GripIcon Icon\"\n                viewBox=\"0 0 24 24\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n              >\n                <path\n                  d=\"M0 0h24v24H0V0z\"\n                  fill=\"none\"\n                />\n                <path\n                  d=\"M11 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm-2-8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z\"\n                />\n              </svg>\n            </div>\n          </div>\n          <div\n            class=\"addToRow \"\n            style=\"flex: 0 0 auto; height: 100%;\"\n          />\n          <div\n            class=\"DividerElement\"\n          />\n        </div>\n        <div\n          class=\"addToRow \"\n        />\n      </div>\n      <div\n        class=\"addToRow \"\n        style=\"width: 94%; height: 10px; margin-left: 48px;\"\n      />\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/cardDetail/cardDetailContents should match snapshot with contents array that has array inside it 1`] = `\n<div>\n  <div\n    class=\"octo-content\"\n  >\n    <div>\n      <div\n        class=\"addToRow \"\n        style=\"width: 94%; height: 10px; margin-left: 48px;\"\n      />\n      <div\n        class=\"rowContents\"\n      >\n        <div\n          class=\"ContentBlock octo-block\"\n        >\n          <div\n            class=\"octo-block-margin\"\n          >\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <button\n                class=\"IconButton\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                />\n              </button>\n            </div>\n            <div\n              class=\"dnd-handle\"\n              draggable=\"true\"\n            >\n              <svg\n                class=\"GripIcon Icon\"\n                viewBox=\"0 0 24 24\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n              >\n                <path\n                  d=\"M0 0h24v24H0V0z\"\n                  fill=\"none\"\n                />\n                <path\n                  d=\"M11 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm-2-8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z\"\n                />\n              </svg>\n            </div>\n          </div>\n          <div\n            class=\"addToRow \"\n            style=\"flex: 0 0 auto; height: 100%;\"\n          />\n          <div\n            class=\"DividerElement\"\n          />\n        </div>\n        <div\n          class=\"addToRow \"\n        />\n      </div>\n    </div>\n    <div>\n      <div\n        class=\"addToRow \"\n        style=\"width: 94%; height: 10px; margin-left: 48px;\"\n      />\n      <div\n        style=\"display: flex;\"\n      >\n        <div\n          class=\"rowContents\"\n          style=\"width: 50%;\"\n        >\n          <div\n            class=\"ContentBlock octo-block\"\n          >\n            <div\n              class=\"octo-block-margin\"\n            >\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n              <div\n                class=\"dnd-handle\"\n                draggable=\"true\"\n              >\n                <svg\n                  class=\"GripIcon Icon\"\n                  viewBox=\"0 0 24 24\"\n                  xmlns=\"http://www.w3.org/2000/svg\"\n                >\n                  <path\n                    d=\"M0 0h24v24H0V0z\"\n                    fill=\"none\"\n                  />\n                  <path\n                    d=\"M11 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm-2-8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z\"\n                  />\n                </svg>\n              </div>\n            </div>\n            <div\n              class=\"addToRow \"\n              style=\"flex: 0 0 auto; height: 100%;\"\n            />\n            <div\n              class=\"DividerElement\"\n            />\n          </div>\n          <div\n            class=\"addToRow \"\n          />\n        </div>\n        <div\n          class=\"rowContents\"\n          style=\"width: 50%;\"\n        >\n          <div\n            class=\"ContentBlock octo-block\"\n          >\n            <div\n              class=\"octo-block-margin\"\n            >\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n              <div\n                class=\"dnd-handle\"\n                draggable=\"true\"\n              >\n                <svg\n                  class=\"GripIcon Icon\"\n                  viewBox=\"0 0 24 24\"\n                  xmlns=\"http://www.w3.org/2000/svg\"\n                >\n                  <path\n                    d=\"M0 0h24v24H0V0z\"\n                    fill=\"none\"\n                  />\n                  <path\n                    d=\"M11 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm-2-8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z\"\n                  />\n                </svg>\n              </div>\n            </div>\n            <div\n              class=\"DividerElement\"\n            />\n          </div>\n          <div\n            class=\"addToRow \"\n          />\n        </div>\n      </div>\n      <div\n        class=\"addToRow \"\n        style=\"width: 94%; height: 10px; margin-left: 48px;\"\n      />\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/cardDetail/__snapshots__/cardDetailContentsMenu.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/cardDetail/cardDetailContentsMenu return cardDetailContentsMenu 1`] = `\n<div>\n  <div\n    class=\"CardDetailContentsMenu content add-content\"\n  >\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper override menuOpened\"\n      role=\"button\"\n    >\n      <button\n        class=\"Button\"\n        type=\"button\"\n      >\n        <span>\n          Add content\n        </span>\n      </button>\n      <div\n        class=\"Menu noselect top \"\n      >\n        <div\n          class=\"menu-contents\"\n        >\n          <div\n            class=\"menu-options\"\n          >\n            <div>\n              <div\n                aria-label=\"text\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <svg\n                      class=\"TextIcon Icon\"\n                      viewBox=\"0 0 448 512\"\n                      xmlns=\"http://www.w3.org/2000/svg\"\n                    >\n                      <path\n                        d=\"M432 416H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-128H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-128H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-128H16A16 16 0 0 0 0 48v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16V48a16 16 0 0 0-16-16z\"\n                      />\n                    </svg>\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    text\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"image\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <svg\n                      class=\"ImageIcon Icon\"\n                      viewBox=\"0 0 512 512\"\n                      xmlns=\"http://www.w3.org/2000/svg\"\n                    >\n                      <path\n                        d=\"M464 64H48C21.49 64 0 85.49 0 112v288c0 26.51 21.49 48 48 48h416c26.51 0 48-21.49 48-48V112c0-26.51-21.49-48-48-48zm-6 336H54a6 6 0 0 1-6-6V118a6 6 0 0 1 6-6h404a6 6 0 0 1 6 6v276a6 6 0 0 1-6 6zM128 152c-22.091 0-40 17.909-40 40s17.909 40 40 40 40-17.909 40-40-17.909-40-40-40zM96 352h320v-80l-87.515-87.515c-4.686-4.686-12.284-4.686-16.971 0L192 304l-39.515-39.515c-4.686-4.686-12.284-4.686-16.971 0L96 304v48z\"\n                      />\n                    </svg>\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    image\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"divider\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <svg\n                      class=\"DividerIcon Icon\"\n                      viewBox=\"0 0 448 512\"\n                      xmlns=\"http://www.w3.org/2000/svg\"\n                    >\n                      <path\n                        d=\"M 432,224 H 16 c -8.836556,0 -16,7.16344 -16,16 v 32 c 0,8.83656 7.163444,16 16,16 h 416 c 8.83656,0 16,-7.16344 16,-16 v -32 c 0,-8.83656 -7.16344,-16 -16,-16 z\"\n                      />\n                    </svg>\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    divider\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"checkbox\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <svg\n                      class=\"CheckIcon Icon\"\n                      viewBox=\"0 0 100 100\"\n                      xmlns=\"http://www.w3.org/2000/svg\"\n                    >\n                      <polyline\n                        points=\"20,60 40,80 80,40\"\n                      />\n                    </svg>\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    checkbox\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"menu-spacer hideOnWidescreen\"\n          />\n          <div\n            class=\"menu-options hideOnWidescreen\"\n          >\n            <div\n              aria-label=\"Cancel\"\n              class=\"MenuOption TextOption menu-option menu-cancel\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Cancel\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/cardDetail/cardDetailContentsMenu return cardDetailContentsMenu and add Text content 1`] = `\n<div>\n  <div\n    class=\"CardDetailContentsMenu content add-content\"\n  >\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper override menuOpened\"\n      role=\"button\"\n    >\n      <button\n        class=\"Button\"\n        type=\"button\"\n      >\n        <span>\n          Add content\n        </span>\n      </button>\n      <div\n        class=\"Menu noselect top \"\n      >\n        <div\n          class=\"menu-contents\"\n        >\n          <div\n            class=\"menu-options\"\n          >\n            <div>\n              <div\n                aria-label=\"text\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <svg\n                      class=\"TextIcon Icon\"\n                      viewBox=\"0 0 448 512\"\n                      xmlns=\"http://www.w3.org/2000/svg\"\n                    >\n                      <path\n                        d=\"M432 416H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-128H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-128H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-128H16A16 16 0 0 0 0 48v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16V48a16 16 0 0 0-16-16z\"\n                      />\n                    </svg>\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    text\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"image\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <svg\n                      class=\"ImageIcon Icon\"\n                      viewBox=\"0 0 512 512\"\n                      xmlns=\"http://www.w3.org/2000/svg\"\n                    >\n                      <path\n                        d=\"M464 64H48C21.49 64 0 85.49 0 112v288c0 26.51 21.49 48 48 48h416c26.51 0 48-21.49 48-48V112c0-26.51-21.49-48-48-48zm-6 336H54a6 6 0 0 1-6-6V118a6 6 0 0 1 6-6h404a6 6 0 0 1 6 6v276a6 6 0 0 1-6 6zM128 152c-22.091 0-40 17.909-40 40s17.909 40 40 40 40-17.909 40-40-17.909-40-40-40zM96 352h320v-80l-87.515-87.515c-4.686-4.686-12.284-4.686-16.971 0L192 304l-39.515-39.515c-4.686-4.686-12.284-4.686-16.971 0L96 304v48z\"\n                      />\n                    </svg>\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    image\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"divider\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <svg\n                      class=\"DividerIcon Icon\"\n                      viewBox=\"0 0 448 512\"\n                      xmlns=\"http://www.w3.org/2000/svg\"\n                    >\n                      <path\n                        d=\"M 432,224 H 16 c -8.836556,0 -16,7.16344 -16,16 v 32 c 0,8.83656 7.163444,16 16,16 h 416 c 8.83656,0 16,-7.16344 16,-16 v -32 c 0,-8.83656 -7.16344,-16 -16,-16 z\"\n                      />\n                    </svg>\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    divider\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"checkbox\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <svg\n                      class=\"CheckIcon Icon\"\n                      viewBox=\"0 0 100 100\"\n                      xmlns=\"http://www.w3.org/2000/svg\"\n                    >\n                      <polyline\n                        points=\"20,60 40,80 80,40\"\n                      />\n                    </svg>\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    checkbox\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"menu-spacer hideOnWidescreen\"\n          />\n          <div\n            class=\"menu-options hideOnWidescreen\"\n          >\n            <div\n              aria-label=\"Cancel\"\n              class=\"MenuOption TextOption menu-option menu-cancel\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Cancel\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/cardDetail/cardDetailContentsMenu return cardDetailContentsMenu and add Text content 2`] = `\n<div>\n  <div\n    class=\"CardDetailContentsMenu content add-content\"\n  >\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper override menuOpened\"\n      role=\"button\"\n    >\n      <button\n        class=\"Button\"\n        type=\"button\"\n      >\n        <span>\n          Add content\n        </span>\n      </button>\n      <div\n        class=\"Menu noselect top \"\n      >\n        <div\n          class=\"menu-contents\"\n        >\n          <div\n            class=\"menu-options\"\n          >\n            <div>\n              <div\n                aria-label=\"text\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <svg\n                      class=\"TextIcon Icon\"\n                      viewBox=\"0 0 448 512\"\n                      xmlns=\"http://www.w3.org/2000/svg\"\n                    >\n                      <path\n                        d=\"M432 416H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-128H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-128H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-128H16A16 16 0 0 0 0 48v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16V48a16 16 0 0 0-16-16z\"\n                      />\n                    </svg>\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    text\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"image\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <svg\n                      class=\"ImageIcon Icon\"\n                      viewBox=\"0 0 512 512\"\n                      xmlns=\"http://www.w3.org/2000/svg\"\n                    >\n                      <path\n                        d=\"M464 64H48C21.49 64 0 85.49 0 112v288c0 26.51 21.49 48 48 48h416c26.51 0 48-21.49 48-48V112c0-26.51-21.49-48-48-48zm-6 336H54a6 6 0 0 1-6-6V118a6 6 0 0 1 6-6h404a6 6 0 0 1 6 6v276a6 6 0 0 1-6 6zM128 152c-22.091 0-40 17.909-40 40s17.909 40 40 40 40-17.909 40-40-17.909-40-40-40zM96 352h320v-80l-87.515-87.515c-4.686-4.686-12.284-4.686-16.971 0L192 304l-39.515-39.515c-4.686-4.686-12.284-4.686-16.971 0L96 304v48z\"\n                      />\n                    </svg>\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    image\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"divider\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <svg\n                      class=\"DividerIcon Icon\"\n                      viewBox=\"0 0 448 512\"\n                      xmlns=\"http://www.w3.org/2000/svg\"\n                    >\n                      <path\n                        d=\"M 432,224 H 16 c -8.836556,0 -16,7.16344 -16,16 v 32 c 0,8.83656 7.163444,16 16,16 h 416 c 8.83656,0 16,-7.16344 16,-16 v -32 c 0,-8.83656 -7.16344,-16 -16,-16 z\"\n                      />\n                    </svg>\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    divider\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"checkbox\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <svg\n                      class=\"CheckIcon Icon\"\n                      viewBox=\"0 0 100 100\"\n                      xmlns=\"http://www.w3.org/2000/svg\"\n                    >\n                      <polyline\n                        points=\"20,60 40,80 80,40\"\n                      />\n                    </svg>\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    checkbox\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"menu-spacer hideOnWidescreen\"\n          />\n          <div\n            class=\"menu-options hideOnWidescreen\"\n          >\n            <div\n              aria-label=\"Cancel\"\n              class=\"MenuOption TextOption menu-option menu-cancel\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Cancel\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/cardDetail/__snapshots__/cardDetailProperties.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/cardDetail/CardDetailProperties cancel on delete dialog should do nothing 1`] = `\n<div>\n  <div\n    class=\"octo-propertylist CardDetailProperties\"\n  >\n    <div\n      class=\"octo-propertyrow\"\n    >\n      <div\n        aria-label=\"menuwrapper\"\n        class=\"MenuWrapper\"\n        role=\"button\"\n      >\n        <div\n          class=\"octo-propertyname\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              Owner\n            </span>\n          </button>\n        </div>\n      </div>\n      <div\n        class=\"octo-propertyvalue\"\n        data-testid=\"select-non-editable\"\n        tabindex=\"0\"\n      >\n        <span\n          class=\"Label propColorDefault \"\n        >\n          <span\n            class=\"Label-text\"\n          >\n            Jean-Luc Picard\n          </span>\n        </span>\n      </div>\n    </div>\n    <div\n      class=\"octo-propertyrow\"\n    >\n      <div\n        aria-label=\"menuwrapper\"\n        class=\"MenuWrapper\"\n        role=\"button\"\n      >\n        <div\n          class=\"octo-propertyname\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              MockStatus\n            </span>\n          </button>\n        </div>\n      </div>\n      <input\n        class=\"Editable octo-propertyvalue\"\n        placeholder=\"Empty\"\n        style=\"width: 100%;\"\n        title=\"1234\"\n        value=\"1234\"\n      />\n    </div>\n    <div\n      class=\"octo-propertyname add-property\"\n    >\n      <div\n        aria-label=\"menuwrapper\"\n        class=\"MenuWrapper\"\n        role=\"button\"\n      >\n        <button\n          class=\"Button\"\n          type=\"button\"\n        >\n          <span>\n            + Add a property\n          </span>\n        </button>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/cardDetail/CardDetailProperties should match snapshot 1`] = `\n<div>\n  <div\n    class=\"octo-propertylist CardDetailProperties\"\n  >\n    <div\n      class=\"octo-propertyrow\"\n    >\n      <div\n        aria-label=\"menuwrapper\"\n        class=\"MenuWrapper\"\n        role=\"button\"\n      >\n        <div\n          class=\"octo-propertyname\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              Owner\n            </span>\n          </button>\n        </div>\n      </div>\n      <div\n        class=\"octo-propertyvalue\"\n        data-testid=\"select-non-editable\"\n        tabindex=\"0\"\n      >\n        <span\n          class=\"Label propColorDefault \"\n        >\n          <span\n            class=\"Label-text\"\n          >\n            Jean-Luc Picard\n          </span>\n        </span>\n      </div>\n    </div>\n    <div\n      class=\"octo-propertyrow\"\n    >\n      <div\n        aria-label=\"menuwrapper\"\n        class=\"MenuWrapper\"\n        role=\"button\"\n      >\n        <div\n          class=\"octo-propertyname\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              MockStatus\n            </span>\n          </button>\n        </div>\n      </div>\n      <input\n        class=\"Editable octo-propertyvalue\"\n        placeholder=\"Empty\"\n        style=\"width: 100%;\"\n        title=\"1234\"\n        value=\"1234\"\n      />\n    </div>\n    <div\n      class=\"octo-propertyname add-property\"\n    >\n      <div\n        aria-label=\"menuwrapper\"\n        class=\"MenuWrapper\"\n        role=\"button\"\n      >\n        <button\n          class=\"Button\"\n          type=\"button\"\n        >\n          <span>\n            + Add a property\n          </span>\n        </button>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/cardDetail/CardDetailProperties should show property types menu 1`] = `\n<div>\n  <div\n    class=\"octo-propertylist CardDetailProperties\"\n  >\n    <div\n      class=\"octo-propertyrow\"\n    >\n      <div\n        aria-label=\"menuwrapper\"\n        class=\"MenuWrapper\"\n        role=\"button\"\n      >\n        <div\n          class=\"octo-propertyname\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              Owner\n            </span>\n          </button>\n        </div>\n      </div>\n      <div\n        class=\"octo-propertyvalue\"\n        data-testid=\"select-non-editable\"\n        tabindex=\"0\"\n      >\n        <span\n          class=\"Label propColorDefault \"\n        >\n          <span\n            class=\"Label-text\"\n          >\n            Jean-Luc Picard\n          </span>\n        </span>\n      </div>\n    </div>\n    <div\n      class=\"octo-propertyrow\"\n    >\n      <div\n        aria-label=\"menuwrapper\"\n        class=\"MenuWrapper\"\n        role=\"button\"\n      >\n        <div\n          class=\"octo-propertyname\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              MockStatus\n            </span>\n          </button>\n        </div>\n      </div>\n      <input\n        class=\"Editable octo-propertyvalue\"\n        placeholder=\"Empty\"\n        style=\"width: 100%;\"\n        title=\"1234\"\n        value=\"1234\"\n      />\n    </div>\n    <div\n      class=\"octo-propertyname add-property\"\n    >\n      <div\n        aria-label=\"menuwrapper\"\n        class=\"MenuWrapper override menuOpened\"\n        role=\"button\"\n      >\n        <button\n          class=\"Button\"\n          type=\"button\"\n        >\n          <span>\n            + Add a property\n          </span>\n        </button>\n        <div\n          class=\"Menu noselect bottom \"\n        >\n          <div\n            class=\"menu-contents\"\n          >\n            <div\n              class=\"menu-options\"\n            >\n              <div>\n                <div\n                  class=\"MenuOption LabelOption menu-option\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                  <div\n                    class=\"menu-name\"\n                  >\n                    <b>\n                      Select property type\n                    </b>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"MenuOption MenuSeparator menu-separator\"\n                />\n                <div\n                  aria-label=\"Text\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex\"\n                  >\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Text\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  aria-label=\"Number\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex\"\n                  >\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Number\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  aria-label=\"Email\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex\"\n                  >\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Email\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  aria-label=\"Phone\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex\"\n                  >\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Phone\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  aria-label=\"URL\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex\"\n                  >\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      URL\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  aria-label=\"Select\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex\"\n                  >\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Select\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  aria-label=\"Multi select\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex\"\n                  >\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Multi select\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  aria-label=\"Date\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex\"\n                  >\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Date\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  aria-label=\"Person\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex\"\n                  >\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Person\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  aria-label=\"Multi person\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex\"\n                  >\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Multi person\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  aria-label=\"Checkbox\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex\"\n                  >\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Checkbox\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  aria-label=\"Created time\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex\"\n                  >\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Created time\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  aria-label=\"Created by\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex\"\n                  >\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Created by\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  aria-label=\"Last updated time\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex\"\n                  >\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Last updated time\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  aria-label=\"Last updated by\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex\"\n                  >\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Last updated by\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n              </div>\n            </div>\n            <div\n              class=\"menu-spacer hideOnWidescreen\"\n            />\n            <div\n              class=\"menu-options hideOnWidescreen\"\n            >\n              <div\n                aria-label=\"Cancel\"\n                class=\"MenuOption TextOption menu-option menu-cancel\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Cancel\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/cardDetail/__snapshots__/comment.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/cardDetail/comment return comment 1`] = `\n<div>\n  <div\n    class=\"Comment comment\"\n  >\n    <div\n      class=\"comment-header\"\n    >\n      <img\n        class=\"comment-avatar\"\n        src=\"data:image/svg+xml\"\n      />\n      <div\n        class=\"comment-username\"\n      >\n        username_1\n      </div>\n      <div\n        class=\"octo-tooltip tooltip-top\"\n        data-tooltip=\"October 01, 2020 at 12:00 AM\"\n      >\n        <div\n          class=\"comment-date\"\n        >\n          a day ago\n        </div>\n      </div>\n      <div\n        aria-label=\"menuwrapper\"\n        class=\"MenuWrapper override menuOpened\"\n        role=\"button\"\n      >\n        <button\n          class=\"IconButton\"\n          type=\"button\"\n        >\n          <i\n            class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n          />\n        </button>\n        <div\n          class=\"Menu noselect left \"\n        >\n          <div\n            class=\"menu-contents\"\n          >\n            <div\n              class=\"menu-options\"\n            >\n              <div>\n                <div\n                  aria-label=\"Delete\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex\"\n                  >\n                    <div\n                      class=\"menu-option__icon\"\n                    >\n                      <i\n                        class=\"CompassIcon icon-trash-can-outline DeleteIcon trash-can-outline\"\n                      />\n                    </div>\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Delete\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n              </div>\n            </div>\n            <div\n              class=\"menu-spacer hideOnWidescreen\"\n            />\n            <div\n              class=\"menu-options hideOnWidescreen\"\n            >\n              <div\n                aria-label=\"Cancel\"\n                class=\"MenuOption TextOption menu-option menu-cancel\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Cancel\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"comment-text\"\n    >\n      <p>\n        Test comment\n      </p>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/cardDetail/comment return comment and delete comment 1`] = `\n<div>\n  <div\n    class=\"Comment comment\"\n  >\n    <div\n      class=\"comment-header\"\n    >\n      <img\n        class=\"comment-avatar\"\n        src=\"data:image/svg+xml\"\n      />\n      <div\n        class=\"comment-username\"\n      >\n        username_1\n      </div>\n      <div\n        class=\"octo-tooltip tooltip-top\"\n        data-tooltip=\"October 01, 2020 at 12:00 AM\"\n      >\n        <div\n          class=\"comment-date\"\n        >\n          a day ago\n        </div>\n      </div>\n      <div\n        aria-label=\"menuwrapper\"\n        class=\"MenuWrapper override menuOpened\"\n        role=\"button\"\n      >\n        <button\n          class=\"IconButton\"\n          type=\"button\"\n        >\n          <i\n            class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n          />\n        </button>\n        <div\n          class=\"Menu noselect left \"\n        >\n          <div\n            class=\"menu-contents\"\n          >\n            <div\n              class=\"menu-options\"\n            >\n              <div>\n                <div\n                  aria-label=\"Delete\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex\"\n                  >\n                    <div\n                      class=\"menu-option__icon\"\n                    >\n                      <i\n                        class=\"CompassIcon icon-trash-can-outline DeleteIcon trash-can-outline\"\n                      />\n                    </div>\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Delete\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n              </div>\n            </div>\n            <div\n              class=\"menu-spacer hideOnWidescreen\"\n            />\n            <div\n              class=\"menu-options hideOnWidescreen\"\n            >\n              <div\n                aria-label=\"Cancel\"\n                class=\"MenuOption TextOption menu-option menu-cancel\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Cancel\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"comment-text\"\n    >\n      <p>\n        Test comment\n      </p>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/cardDetail/comment return comment readonly 1`] = `\n<div>\n  <div\n    class=\"Comment comment\"\n  >\n    <div\n      class=\"comment-header\"\n    >\n      <img\n        class=\"comment-avatar\"\n        src=\"data:image/svg+xml\"\n      />\n      <div\n        class=\"comment-username\"\n      >\n        username_1\n      </div>\n      <div\n        class=\"octo-tooltip tooltip-top\"\n        data-tooltip=\"October 01, 2020 at 12:00 AM\"\n      >\n        <div\n          class=\"comment-date\"\n        >\n          a day ago\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"comment-text\"\n    >\n      <p>\n        Test comment\n      </p>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/cardDetail/comment return guest comment 1`] = `\n<div>\n  <div\n    class=\"Comment comment\"\n  >\n    <div\n      class=\"comment-header\"\n    >\n      <img\n        class=\"comment-avatar\"\n        src=\"data:image/svg+xml\"\n      />\n      <div\n        class=\"comment-username\"\n      >\n        username_1\n      </div>\n      <div\n        class=\"GuestBadge\"\n      >\n        <div\n          class=\"GuestBadge__box\"\n        >\n          Guest\n        </div>\n      </div>\n      <div\n        class=\"octo-tooltip tooltip-top\"\n        data-tooltip=\"October 01, 2020 at 12:00 AM\"\n      >\n        <div\n          class=\"comment-date\"\n        >\n          a day ago\n        </div>\n      </div>\n      <div\n        aria-label=\"menuwrapper\"\n        class=\"MenuWrapper override menuOpened\"\n        role=\"button\"\n      >\n        <button\n          class=\"IconButton\"\n          type=\"button\"\n        >\n          <i\n            class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n          />\n        </button>\n        <div\n          class=\"Menu noselect left \"\n        >\n          <div\n            class=\"menu-contents\"\n          >\n            <div\n              class=\"menu-options\"\n            >\n              <div>\n                <div\n                  aria-label=\"Delete\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex\"\n                  >\n                    <div\n                      class=\"menu-option__icon\"\n                    >\n                      <i\n                        class=\"CompassIcon icon-trash-can-outline DeleteIcon trash-can-outline\"\n                      />\n                    </div>\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Delete\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n              </div>\n            </div>\n            <div\n              class=\"menu-spacer hideOnWidescreen\"\n            />\n            <div\n              class=\"menu-options hideOnWidescreen\"\n            >\n              <div\n                aria-label=\"Cancel\"\n                class=\"MenuOption TextOption menu-option menu-cancel\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Cancel\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"comment-text\"\n    >\n      <p>\n        Test comment\n      </p>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/cardDetail/comment return guest comment and delete comment 1`] = `\n<div>\n  <div\n    class=\"Comment comment\"\n  >\n    <div\n      class=\"comment-header\"\n    >\n      <img\n        class=\"comment-avatar\"\n        src=\"data:image/svg+xml\"\n      />\n      <div\n        class=\"comment-username\"\n      >\n        username_1\n      </div>\n      <div\n        class=\"GuestBadge\"\n      >\n        <div\n          class=\"GuestBadge__box\"\n        >\n          Guest\n        </div>\n      </div>\n      <div\n        class=\"octo-tooltip tooltip-top\"\n        data-tooltip=\"October 01, 2020 at 12:00 AM\"\n      >\n        <div\n          class=\"comment-date\"\n        >\n          a day ago\n        </div>\n      </div>\n      <div\n        aria-label=\"menuwrapper\"\n        class=\"MenuWrapper override menuOpened\"\n        role=\"button\"\n      >\n        <button\n          class=\"IconButton\"\n          type=\"button\"\n        >\n          <i\n            class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n          />\n        </button>\n        <div\n          class=\"Menu noselect left \"\n        >\n          <div\n            class=\"menu-contents\"\n          >\n            <div\n              class=\"menu-options\"\n            >\n              <div>\n                <div\n                  aria-label=\"Delete\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex\"\n                  >\n                    <div\n                      class=\"menu-option__icon\"\n                    >\n                      <i\n                        class=\"CompassIcon icon-trash-can-outline DeleteIcon trash-can-outline\"\n                      />\n                    </div>\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Delete\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n              </div>\n            </div>\n            <div\n              class=\"menu-spacer hideOnWidescreen\"\n            />\n            <div\n              class=\"menu-options hideOnWidescreen\"\n            >\n              <div\n                aria-label=\"Cancel\"\n                class=\"MenuOption TextOption menu-option menu-cancel\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Cancel\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"comment-text\"\n    >\n      <p>\n        Test comment\n      </p>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/cardDetail/comment return guest comment readonly 1`] = `\n<div>\n  <div\n    class=\"Comment comment\"\n  >\n    <div\n      class=\"comment-header\"\n    >\n      <img\n        class=\"comment-avatar\"\n        src=\"data:image/svg+xml\"\n      />\n      <div\n        class=\"comment-username\"\n      >\n        username_1\n      </div>\n      <div\n        class=\"GuestBadge\"\n      >\n        <div\n          class=\"GuestBadge__box\"\n        >\n          Guest\n        </div>\n      </div>\n      <div\n        class=\"octo-tooltip tooltip-top\"\n        data-tooltip=\"October 01, 2020 at 12:00 AM\"\n      >\n        <div\n          class=\"comment-date\"\n        >\n          a day ago\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"comment-text\"\n    >\n      <p>\n        Test comment\n      </p>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/cardDetail/__snapshots__/commentsList.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/cardDetail/CommentsList comments show up 1`] = `\n<div>\n  <div\n    class=\"CommentsList\"\n  >\n    <div\n      class=\"CommentsList__new\"\n    >\n      <img\n        class=\"comment-avatar\"\n        src=\"data:image/svg+xml,<svg xmlns=\\\\\"http://www.w3.org/2000/svg\\\\\" viewBox=\\\\\"0 0 100 100\\\\\" style=\\\\\"fill: rgb(192, 192, 192);\\\\\"><rect width=\\\\\"100\\\\\" height=\\\\\"100\\\\\" /></svg>\"\n      />\n      <div\n        class=\"MarkdownEditor octo-editor newcomment \"\n      >\n        <div\n          class=\"octo-editor-preview octo-placeholder\"\n          data-testid=\"preview-element\"\n        >\n          <p>\n            Add a comment...\n          </p>\n        </div>\n      </div>\n      \n    </div>\n    <div\n      class=\"Comment comment\"\n    >\n      <div\n        class=\"comment-header\"\n      >\n        <img\n          class=\"comment-avatar\"\n          src=\"data:image/svg+xml,<svg xmlns=\\\\\"http://www.w3.org/2000/svg\\\\\" viewBox=\\\\\"0 0 100 100\\\\\" style=\\\\\"fill: rgb(192, 192, 192);\\\\\"><rect width=\\\\\"100\\\\\" height=\\\\\"100\\\\\" /></svg>\"\n        />\n        <div\n          class=\"comment-username\"\n        />\n        <div\n          class=\"octo-tooltip tooltip-top\"\n          data-tooltip=\"a long time ago\"\n        >\n          <div\n            class=\"comment-date\"\n          >\n            a long time ago\n          </div>\n        </div>\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"IconButton\"\n            type=\"button\"\n          >\n            <i\n              class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n            />\n          </button>\n        </div>\n      </div>\n      <div\n        class=\"comment-text\"\n      >\n        <p>\n          Comment 2\n        </p>\n      </div>\n    </div>\n    <div\n      class=\"Comment comment\"\n    >\n      <div\n        class=\"comment-header\"\n      >\n        <img\n          class=\"comment-avatar\"\n          src=\"data:image/svg+xml,<svg xmlns=\\\\\"http://www.w3.org/2000/svg\\\\\" viewBox=\\\\\"0 0 100 100\\\\\" style=\\\\\"fill: rgb(192, 192, 192);\\\\\"><rect width=\\\\\"100\\\\\" height=\\\\\"100\\\\\" /></svg>\"\n        />\n        <div\n          class=\"comment-username\"\n        />\n        <div\n          class=\"octo-tooltip tooltip-top\"\n          data-tooltip=\"a long time ago\"\n        >\n          <div\n            class=\"comment-date\"\n          >\n            a long time ago\n          </div>\n        </div>\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"IconButton\"\n            type=\"button\"\n          >\n            <i\n              class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n            />\n          </button>\n        </div>\n      </div>\n      <div\n        class=\"comment-text\"\n      >\n        <p>\n          Comment 1\n        </p>\n      </div>\n    </div>\n    <hr\n      class=\"CommentsList__divider\"\n    />\n  </div>\n</div>\n`;\n\nexports[`components/cardDetail/CommentsList comments show up in readonly mode 1`] = `\n<div>\n  <div\n    class=\"CommentsList\"\n  >\n    <div\n      class=\"Comment comment\"\n    >\n      <div\n        class=\"comment-header\"\n      >\n        <img\n          class=\"comment-avatar\"\n          src=\"data:image/svg+xml,<svg xmlns=\\\\\"http://www.w3.org/2000/svg\\\\\" viewBox=\\\\\"0 0 100 100\\\\\" style=\\\\\"fill: rgb(192, 192, 192);\\\\\"><rect width=\\\\\"100\\\\\" height=\\\\\"100\\\\\" /></svg>\"\n        />\n        <div\n          class=\"comment-username\"\n        />\n        <div\n          class=\"octo-tooltip tooltip-top\"\n          data-tooltip=\"a long time ago\"\n        >\n          <div\n            class=\"comment-date\"\n          >\n            a long time ago\n          </div>\n        </div>\n      </div>\n      <div\n        class=\"comment-text\"\n      >\n        <p>\n          Comment 2\n        </p>\n      </div>\n    </div>\n    <div\n      class=\"Comment comment\"\n    >\n      <div\n        class=\"comment-header\"\n      >\n        <img\n          class=\"comment-avatar\"\n          src=\"data:image/svg+xml,<svg xmlns=\\\\\"http://www.w3.org/2000/svg\\\\\" viewBox=\\\\\"0 0 100 100\\\\\" style=\\\\\"fill: rgb(192, 192, 192);\\\\\"><rect width=\\\\\"100\\\\\" height=\\\\\"100\\\\\" /></svg>\"\n        />\n        <div\n          class=\"comment-username\"\n        />\n        <div\n          class=\"octo-tooltip tooltip-top\"\n          data-tooltip=\"a long time ago\"\n        >\n          <div\n            class=\"comment-date\"\n          >\n            a long time ago\n          </div>\n        </div>\n      </div>\n      <div\n        class=\"comment-text\"\n      >\n        <p>\n          Comment 1\n        </p>\n      </div>\n    </div>\n    <hr\n      class=\"CommentsList__divider\"\n    />\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/cardDetail/attachment.scss",
    "content": ".Attachment {\n    display: block;\n    width: 100%;\n\n    .attachment-header {\n        display: flex;\n    }\n\n    .attachment-plus-btn {\n        margin-left: auto;\n    }\n\n    .attachment-content {\n        padding-bottom: 20px;\n        display: flex;\n        overflow-x: auto;\n    }\n\n    .attachment-plus-icon {\n        color: rgba(var(--center-channel-color-rgb), 0.56);\n        cursor: pointer;\n    }\n\n    .attachment-title {\n        font-family: 'Open Sans';\n        font-style: normal;\n        font-weight: 600;\n        font-size: 14px;\n        line-height: 20px;\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/cardDetail/attachment.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\n\nimport {useIntl} from 'react-intl'\n\nimport AttachmentElement from '../../components/content/attachmentElement'\nimport {AttachmentBlock} from '../../blocks/attachmentBlock'\n\nimport './attachment.scss'\nimport {Block} from '../../blocks/block'\nimport CompassIcon from '../../widgets/icons/compassIcon'\nimport BoardPermissionGate from '../../components/permissions/boardPermissionGate'\nimport {Permission} from '../../constants'\n\ntype Props = {\n    attachments: AttachmentBlock[]\n    onDelete: (block: Block) => void\n    addAttachment: () => void\n}\n\nconst AttachmentList = (props: Props): JSX.Element => {\n    const {attachments, onDelete, addAttachment} = props\n    const intl = useIntl()\n\n    return (\n        <div className='Attachment'>\n            <div className='attachment-header'>\n                <div className='attachment-title mb-2'>{intl.formatMessage({id: 'Attachment.Attachment-title', defaultMessage: 'Attachment'})} {`(${attachments.length})`}</div>\n                <BoardPermissionGate permissions={[Permission.ManageBoardCards]}>\n                    <div\n                        className='attachment-plus-btn'\n                        onClick={addAttachment}\n                    >\n                        <CompassIcon\n                            icon='plus'\n                            className='attachment-plus-icon'\n                        />\n                    </div>\n                </BoardPermissionGate>\n            </div>\n            <div className='attachment-content'>\n                {attachments.map((block: AttachmentBlock) => {\n                    return (\n                        <div key={block.id}>\n                            <AttachmentElement\n                                block={block}\n                                onDelete={onDelete}\n                            />\n                        </div>)\n                })\n                }\n            </div>\n        </div>\n    )\n}\n\nexport default AttachmentList\n"
  },
  {
    "path": "webapp/src/components/cardDetail/cardDetail.scss",
    "content": "@import '../../styles/z-index';\n\n.CardDetail {\n    .title {\n        width: 100%;\n        font-size: 32px;\n        line-height: 40px;\n    }\n\n    .add-buttons {\n        display: flex;\n        flex-direction: column;\n        min-height: 32px;\n        color: rgba(var(--center-channel-color-rgb), 0.4);\n        width: 100%;\n        align-items: flex-start;\n\n        .Button {\n            display: none;\n        }\n\n        &:hover {\n            .Button {\n                display: flex;\n            }\n        }\n    }\n\n    .octo-propertylist {\n        display: flex;\n        flex-direction: column;\n        width: 100%;\n    }\n\n\n\n    .octo-propertyvalue {\n        font-size: 14px;\n        padding: 4px 8px;\n        min-height: 32px;\n        display: flex;\n\n        &:not(.URLProperty) {\n            flex-wrap: wrap;\n        }\n\n        &.octo-propertyvalue--readonly {\n            overflow: hidden;\n            text-overflow: ellipsis;\n            white-space: nowrap;\n        }\n\n        &:not(.octo-propertyvalue--readonly) {\n            min-width: 180px;\n            transition: background 100ms ease-out 0s;\n            cursor: pointer;\n            border-radius: 4px;\n\n            &:hover {\n                background-color: rgba(var(--center-channel-color-rgb), 0.08);\n            }\n        }\n\n        .MenuWrapper {\n            display: flex;\n            align-items: center;\n        }\n    }\n\n    .ValueSelector {\n        padding: 4px 0;\n        background-color: rgba(var(--center-channel-color-rgb), 0.08);\n        width: auto;\n    }\n\n    .ValueSelector__menu {\n        @include z-index(card-value-selector-menu);\n        max-width: 100%;\n\n        .value-menu-option {\n            justify-content: space-between;\n        }\n\n        .label-container {\n            max-width: 90%;\n        }\n    }\n\n    .octo-propertyrow {\n        max-width: 595px;\n        display: flex;\n        align-items: flex-start;\n        margin: 6px 0;\n        min-height: 32px;\n\n        .Switch {\n            margin: 4px 0 0 8px;\n            width: 40px;\n            height: 24px;\n            box-sizing: border-box;\n            padding: 2px;\n\n            .octo-switch-inner {\n                width: 20px;\n                height: 20px;\n            }\n\n            &.on {\n                .octo-switch-inner {\n                    transform: translateX(16px) translateY(0);\n                }\n            }\n        }\n\n        .Label {\n            margin: 0 4px 0 0;\n        }\n    }\n\n    .Person {\n        .react-select__value-container {\n            padding: 0;\n        }\n\n        .react-select__control {\n            margin-top: 0;\n        }\n    }\n\n    .octo-propertyrow {\n        .octo-propertyname {\n            flex: 0 0 150px;\n        }\n    }\n\n    .octo-propertyname {\n        width: 150px;\n        margin-right: 5px;\n        color: rgba(var(--center-channel-color-rgb), 0.6);\n\n        .Button {\n            width: 100%;\n            height: 100%;\n            padding: 4px 8px;\n            text-align: left;\n            justify-content: unset;\n        }\n\n        &.add-property {\n            margin: 8px 0 0;\n            color: rgba(var(--center-channel-color-rgb), 0.4);\n            position: relative;\n        }\n    }\n\n    &.content-blocks {\n        position: relative;\n\n        &:hover,\n        &:focus-within {\n            .CardDetailContentsMenu {\n                .Button {\n                    opacity: 1;\n                }\n            }\n        }\n\n        .CardDetailContentsMenu {\n            margin-left: 48px;\n\n            .Button {\n                opacity: 0;\n                color: rgba(var(--center-channel-color-rgb), 0.6);\n            }\n        }\n    }\n\n    &.CardDetail--is-limited {\n        flex: 1;\n    }\n\n    &__limited-wrapper {\n        flex: 1;\n        display: flex;\n        flex-direction: column;\n        align-items: center;\n        justify-content: center;\n        text-align: center;\n        font-size: 14px;\n        position: relative;\n\n        > :not(.CardDetail__limited-bg) {\n            position: relative;\n        }\n    }\n\n    &__limited-bg {\n        filter: blur(12px);\n        position: absolute;\n        top: 50%;\n        left: 50%;\n        transform: translate(-50%, -50%);\n        width: 100%;\n\n        > svg {\n            width: 100%;\n            height: auto;\n\n            > rect {\n                fill: rgba(var(--center-channel-color-rgb));\n            }\n        }\n    }\n\n    &__limited-title {\n        font-weight: bold;\n        font-size: 22px;\n        line-height: 28px;\n    }\n\n    &__limited-link {\n        color: var(--link-color);\n    }\n\n    &__limited-button {\n        margin-top: 24px;\n    }\n\n    .BlocksEditor {\n        width: 100%;\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/cardDetail/cardDetail.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\n\nimport 'isomorphic-fetch'\nimport {act, render} from '@testing-library/react'\n\nimport configureStore from 'redux-mock-store'\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport userEvent from '@testing-library/user-event'\n\nimport {mocked} from 'jest-mock'\n\nimport {FetchMock} from '../../test/fetchMock'\nimport {TestBlockFactory} from '../../test/testBlockFactory'\n\nimport {mockDOM, wrapDNDIntl, wrapIntl} from '../../testUtils'\n\nimport octoClient from '../../octoClient'\n\nimport {createTextBlock} from '../../blocks/textBlock'\n\nimport CardDetail from './cardDetail'\n\nglobal.fetch = FetchMock.fn\njest.mock('../../octoClient')\n\nconst mockedOctoClient = mocked(octoClient, true)\n\nbeforeEach(() => {\n    FetchMock.fn.mockReset()\n})\n\n// This is needed to run EasyMDE in tests.\n// It needs bounding rectangle box property\n// on HTML elements, but Jest's HTML engine jsdom\n// doesn't provide it.\n// So we mock it.\nbeforeAll(() => {\n    mockDOM()\n})\n\ndescribe('components/cardDetail/CardDetail', () => {\n    const board = TestBlockFactory.createBoard()\n\n    const view = TestBlockFactory.createBoardView(board)\n    view.fields.sortOptions = []\n    view.fields.groupById = undefined\n    view.fields.hiddenOptionIds = []\n\n    const card = TestBlockFactory.createCard(board)\n\n    const createdAt = Date.parse('01 Jan 2021 00:00:00 GMT')\n    const comment1 = TestBlockFactory.createComment(card)\n    comment1.type = 'comment'\n    comment1.title = 'Comment 1'\n    comment1.parentId = card.id\n    comment1.createAt = createdAt\n\n    const comment2 = TestBlockFactory.createComment(card)\n    comment2.type = 'comment'\n    comment2.title = 'Comment 2'\n    comment2.parentId = card.id\n    comment2.createAt = createdAt\n\n    test('should show comments', async () => {\n        const mockStore = configureStore([])\n        const store = mockStore({\n            users: {\n                boardUsers: {\n                    'user-id-1': {username: 'username_1'},\n                },\n            },\n            teams: {\n                current: {id: 'team-id'},\n            },\n            boards: {\n                boards: {\n                    [board.id]: board,\n                },\n                current: board.id,\n                myBoardMemberships: {\n                    [board.id]: {userId: 'user_id_1', schemeAdmin: true},\n                },\n            },\n            cards: {\n                cards: {\n                    [card.id]: card,\n                },\n                current: card.id,\n            },\n            clientConfig: {\n                value: {},\n            },\n        })\n\n        const component = (\n            <ReduxProvider store={store}>\n                {wrapIntl(\n                    <CardDetail\n                        board={board}\n                        activeView={view}\n                        views={[view]}\n                        cards={[card]}\n                        card={card}\n                        comments={[comment1, comment2]}\n                        contents={[]}\n                        attachments={[]}\n                        readonly={false}\n                        onClose={jest.fn()}\n                        onDelete={jest.fn()}\n                        addAttachment={jest.fn()}\n                    />,\n                )}\n            </ReduxProvider>\n        )\n\n        let container: Element | DocumentFragment | null = null\n\n        await act(async () => {\n            const result = render(component)\n            container = result.container\n        })\n\n        expect(container).toBeDefined()\n\n        // Comments show up\n        const comments = container!.querySelectorAll('.comment-text')\n        expect(comments.length).toBe(2)\n\n        // Add comment option visible when readonly mode is off\n        const newCommentSection = container!.querySelectorAll('.newcomment')\n        expect(newCommentSection.length).toBe(1)\n    })\n\n    test('should show comments in readonly view', async () => {\n        const mockStore = configureStore([])\n        const store = mockStore({\n            teams: {\n                current: {id: 'team-id'},\n            },\n            boards: {\n                boards: {\n                    [board.id]: board,\n                },\n                current: board.id,\n                myBoardMemberships: {\n                    [board.id]: {userId: 'user_id_1', schemeAdmin: true},\n                },\n            },\n            users: {\n                boardUsers: {\n                    'user-id-1': {username: 'username_1'},\n                },\n            },\n            clientConfig: {\n                value: {},\n            },\n        })\n\n        const component = (\n            <ReduxProvider store={store}>\n                {wrapIntl(\n                    <CardDetail\n                        board={board}\n                        activeView={view}\n                        views={[view]}\n                        cards={[card]}\n                        card={card}\n                        comments={[comment1, comment2]}\n                        contents={[]}\n                        attachments={[]}\n                        readonly={true}\n                        onClose={jest.fn()}\n                        onDelete={jest.fn()}\n                        addAttachment={jest.fn()}\n                    />,\n                )}\n            </ReduxProvider>\n        )\n\n        let container: Element | DocumentFragment | null = null\n\n        await act(async () => {\n            const result = render(component)\n            container = result.container\n        })\n\n        expect(container).toBeDefined()\n\n        // comments show up\n        const comments = container!.querySelectorAll('.comment-text')\n        expect(comments.length).toBe(2)\n\n        // Add comment option is not shown in readonly mode\n        const newCommentSection = container!.querySelectorAll('.newcomment')\n        expect(newCommentSection.length).toBe(0)\n    })\n\n    test('should show add properties tour tip', async () => {\n        const mockStore = configureStore([])\n\n        const welcomeBoard = TestBlockFactory.createBoard()\n        welcomeBoard.title = 'Welcome to Boards!'\n\n        const welcomeCard = TestBlockFactory.createCard(welcomeBoard)\n        welcomeCard.title = 'Create a new card'\n\n        const store = mockStore({\n            users: {\n                me: {\n                    id: 'user_id_1',\n                },\n                myConfig: {\n                    welcomePageViewed: {value: '1'},\n                    onboardingTourStarted: {value: true},\n                    tourCategory: {value: 'card'},\n                    onboardingTourStep: {value: '0'},\n                },\n                boardUsers: {\n                    'user-id-1': {username: 'username_1'},\n                },\n            },\n            teams: {\n                current: {id: 'team-id'},\n            },\n            boards: {\n                boards: {\n                    [welcomeBoard.id]: welcomeBoard,\n                },\n                current: welcomeBoard.id,\n                myBoardMemberships: {\n                    [welcomeBoard.id]: {userId: 'user_id_1', schemeAdmin: true},\n                },\n            },\n            cards: {\n                cards: {\n                    [welcomeCard.id]: welcomeCard,\n                },\n                current: welcomeCard.id,\n            },\n            clientConfig: {\n                value: {},\n            },\n        })\n\n        const onboardingBoard = TestBlockFactory.createBoard()\n        onboardingBoard.title = 'Welcome to Boards!'\n\n        const onboardingCard = TestBlockFactory.createCard(board)\n        onboardingCard.title = 'Create a new card'\n\n        const component = (\n            <ReduxProvider store={store}>\n                {wrapIntl(\n                    <CardDetail\n                        board={onboardingBoard}\n                        activeView={view}\n                        views={[view]}\n                        cards={[onboardingCard]}\n                        card={onboardingCard}\n                        comments={[comment1, comment2]}\n                        contents={[]}\n                        attachments={[]}\n                        readonly={false}\n                        onClose={jest.fn()}\n                        onDelete={jest.fn()}\n                        addAttachment={jest.fn()}\n                    />,\n                )}\n            </ReduxProvider>\n        )\n\n        let container: Element | DocumentFragment | null = null\n\n        await act(async () => {\n            const result = render(component)\n            container = result.container\n        })\n\n        expect(container).toBeDefined()\n        expect(container).not.toBeNull()\n\n        const tourTip = document.querySelectorAll('.AddPropertiesTourStep')\n        expect(tourTip.length).toBe(2)\n        expect(tourTip[1]).toMatchSnapshot()\n\n        // moving to next step\n        mockedOctoClient.patchUserConfig.mockResolvedValueOnce([])\n\n        const nextBtn = document!.querySelector('.tipNextButton')\n        expect(nextBtn).toBeDefined()\n        expect(nextBtn).not.toBeNull()\n        await act(async () => {\n            userEvent.click(nextBtn!)\n        })\n        expect(mockedOctoClient.patchUserConfig).toBeCalledWith(\n            'user_id_1',\n            {\n                updatedFields: {\n                    onboardingTourStep: '1',\n                },\n            },\n        )\n    })\n\n    test('should show add comments tour tip', async () => {\n        const mockStore = configureStore([])\n\n        const welcomeBoard = TestBlockFactory.createBoard()\n        welcomeBoard.title = 'Welcome to Boards!'\n\n        const welcomeCard = TestBlockFactory.createCard(welcomeBoard)\n        welcomeCard.title = 'Create a new card'\n\n        const store = mockStore({\n            users: {\n                me: {\n                    id: 'user_id_1',\n                },\n                myConfig: {\n                    welcomePageViewed: {value: '1'},\n                    onboardingTourStarted: {value: true},\n                    tourCategory: {value: 'card'},\n                    onboardingTourStep: {value: '1'},\n                },\n                boardUsers: {\n                    'user-id-1': {username: 'username_1'},\n                },\n            },\n            teams: {\n                current: {id: 'team-id'},\n            },\n            boards: {\n                boards: {\n                    [welcomeBoard.id]: welcomeBoard,\n                },\n                current: welcomeBoard.id,\n                myBoardMemberships: {\n                    [welcomeBoard.id]: {userId: 'user_id_1', schemeAdmin: true},\n                },\n            },\n            cards: {\n                cards: {\n                    [welcomeCard.id]: welcomeCard,\n                },\n                current: welcomeCard.id,\n            },\n            clientConfig: {\n                value: {},\n            },\n        })\n\n        const onboardingBoard = TestBlockFactory.createBoard()\n        onboardingBoard.title = 'Welcome to Boards!'\n\n        const onboardingCard = TestBlockFactory.createCard(board)\n        onboardingCard.title = 'Create a new card'\n\n        const component = (\n            <ReduxProvider store={store}>\n                {wrapIntl(\n                    <CardDetail\n                        board={onboardingBoard}\n                        activeView={view}\n                        views={[view]}\n                        cards={[onboardingCard]}\n                        card={onboardingCard}\n                        comments={[comment1, comment2]}\n                        contents={[]}\n                        attachments={[]}\n                        readonly={false}\n                        onClose={jest.fn()}\n                        onDelete={jest.fn()}\n                        addAttachment={jest.fn()}\n                    />,\n                )}\n            </ReduxProvider>\n        )\n\n        let container: Element | DocumentFragment | null = null\n\n        await act(async () => {\n            const result = render(component)\n            container = result.container\n        })\n\n        expect(container).toBeDefined()\n        expect(container).not.toBeNull()\n\n        const tourTip = document.querySelectorAll('.AddCommentTourStep')\n        expect(tourTip.length).toBe(2)\n        expect(tourTip[1]).toMatchSnapshot()\n\n        // moving to next step\n        mockedOctoClient.patchUserConfig.mockResolvedValueOnce([])\n\n        const nextBtn = document!.querySelector('.tipNextButton')\n        expect(nextBtn).toBeDefined()\n        expect(nextBtn).not.toBeNull()\n        await act(async () => {\n            userEvent.click(nextBtn!)\n        })\n        expect(mockedOctoClient.patchUserConfig).toBeCalledWith(\n            'user_id_1',\n            {\n                updatedFields: {\n                    onboardingTourStep: '2',\n                },\n            },\n        )\n    })\n\n    test('should show add description tour tip', async () => {\n        const mockStore = configureStore([])\n        const welcomeBoard = TestBlockFactory.createBoard()\n        welcomeBoard.title = 'Welcome to Boards!'\n\n        const welcomeCard = TestBlockFactory.createCard(welcomeBoard)\n        welcomeCard.title = 'Create a new card'\n        const state = {\n            users: {\n                me: {\n                    id: 'user_id_1',\n                },\n                myConfig: {\n                    welcomePageViewed: {value: '1'},\n                    onboardingTourStarted: {value: true},\n                    tourCategory: {value: 'card'},\n                    onboardingTourStep: {value: '2'},\n                },\n                boardUsers: {\n                    'user-id-1': {username: 'username_1'},\n                },\n            },\n            teams: {\n                current: {id: 'team-id'},\n            },\n            boards: {\n                boards: {\n                    [welcomeBoard.id]: welcomeBoard,\n                },\n                current: welcomeBoard.id,\n                myBoardMemberships: {\n                    [welcomeBoard.id]: {userId: 'user_id_1', schemeAdmin: true},\n                },\n            },\n            cards: {\n                cards: {\n                    [welcomeCard.id]: welcomeCard,\n                },\n                current: welcomeCard.id,\n            },\n            clientConfig: {\n                value: {},\n            },\n        }\n        const store = mockStore(state)\n\n        const onboardingBoard = TestBlockFactory.createBoard()\n        onboardingBoard.title = 'Welcome to Boards!'\n\n        const onboardingCard = TestBlockFactory.createCard(board)\n        onboardingCard.title = 'Create a new card'\n\n        const text = createTextBlock()\n        text.title = 'description'\n        text.parentId = onboardingCard.id\n        onboardingCard.fields.contentOrder = [text.id]\n\n        const component = (\n            <ReduxProvider store={store}>\n                {wrapDNDIntl(\n                    <CardDetail\n                        board={onboardingBoard}\n                        activeView={view}\n                        views={[view]}\n                        cards={[onboardingCard]}\n                        card={onboardingCard}\n                        comments={[comment1, comment2]}\n                        contents={[text]}\n                        attachments={[]}\n                        readonly={false}\n                        onClose={jest.fn()}\n                        onDelete={jest.fn()}\n                        addAttachment={jest.fn()}\n                    />,\n                )}\n            </ReduxProvider>\n        )\n\n        let container: Element | DocumentFragment | null = null\n\n        await act(async () => {\n            const result = render(component)\n            container = result.container\n        })\n\n        expect(container).toBeDefined()\n        expect(container).not.toBeNull()\n\n        const tourTip = document.querySelectorAll('.AddDescriptionTourStep')\n        expect(tourTip.length).toBe(2)\n        expect(tourTip[1]).toMatchSnapshot()\n\n        // moving to next step\n        mockedOctoClient.patchUserConfig.mockResolvedValueOnce([])\n\n        const nextBtn = document!.querySelector('.tipNextButton')\n        expect(nextBtn).toBeDefined()\n        expect(nextBtn).not.toBeNull()\n        await act(async () => {\n            userEvent.click(nextBtn!)\n        })\n        expect(mockedOctoClient.patchUserConfig).toBeCalledWith(\n            'user_id_1',\n            {\n                updatedFields: {\n                    onboardingTourStep: '999',\n                },\n            },\n        )\n    })\n\n    test('should render hidden view if limited', async () => {\n        const limitedCard = {...card, limited: true}\n        const mockStore = configureStore([])\n        const store = mockStore({\n            users: {\n                workspaceUsers: [\n                    {username: 'username_1'},\n                ],\n            },\n            teams: {\n                current: {id: 'team-id'},\n            },\n            boards: {\n                boards: {\n                    [board.id]: board,\n                },\n                current: board.id,\n                myBoardMemberships: {\n                    [board.id]: {userId: 'user_id_1', schemeAdmin: true},\n                },\n            },\n            cards: {\n                cards: {\n                    [limitedCard.id]: limitedCard,\n                },\n                current: limitedCard.id,\n            },\n            clientConfig: {\n                value: {},\n            },\n        })\n\n        const component = (\n            <ReduxProvider store={store}>\n                {wrapIntl(\n                    <CardDetail\n                        board={board}\n                        activeView={view}\n                        views={[view]}\n                        cards={[limitedCard]}\n                        card={limitedCard}\n                        comments={[comment1, comment2]}\n                        contents={[]}\n                        attachments={[]}\n                        readonly={false}\n                        onClose={jest.fn()}\n                        onDelete={jest.fn()}\n                        addAttachment={jest.fn()}\n                    />,\n                )}\n            </ReduxProvider>\n        )\n\n        let container: Element | DocumentFragment | null = null\n\n        await act(async () => {\n            const result = render(component)\n            container = result.container\n        })\n\n        expect(container).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/cardDetail/cardDetail.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useCallback, useEffect, useRef, useState, Fragment, useMemo} from 'react'\nimport {FormattedMessage, useIntl, IntlShape} from 'react-intl'\n\nimport {BlockIcons} from '../../blockIcons'\nimport {Card} from '../../blocks/card'\nimport {BoardView} from '../../blocks/boardView'\nimport {Board} from '../../blocks/board'\nimport {CommentBlock} from '../../blocks/commentBlock'\nimport {AttachmentBlock} from '../../blocks/attachmentBlock'\nimport {ContentBlock} from '../../blocks/contentBlock'\nimport {Block, ContentBlockTypes, createBlock} from '../../blocks/block'\nimport mutator from '../../mutator'\nimport octoClient from '../../octoClient'\nimport {Utils} from '../../utils'\nimport Button from '../../widgets/buttons/button'\nimport {Focusable} from '../../widgets/editable'\nimport EditableArea from '../../widgets/editableArea'\nimport CompassIcon from '../../widgets/icons/compassIcon'\nimport TelemetryClient, {TelemetryActions, TelemetryCategory} from '../../telemetry/telemetryClient'\n\nimport BlockIconSelector from '../blockIconSelector'\n\nimport {useAppDispatch, useAppSelector} from '../../store/hooks'\nimport {updateCards, setCurrent as setCurrentCard} from '../../store/cards'\nimport {updateContents} from '../../store/contents'\nimport {Permission} from '../../constants'\nimport {useHasCurrentBoardPermissions} from '../../hooks/permissions'\nimport BlocksEditor from '../blocksEditor/blocksEditor'\nimport {BlockData} from '../blocksEditor/blocks/types'\nimport {ClientConfig} from '../../config/clientConfig'\nimport {getClientConfig} from '../../store/clientConfig'\n\nimport CardSkeleton from '../../svg/card-skeleton'\n\nimport CommentsList from './commentsList'\nimport {CardDetailProvider} from './cardDetailContext'\nimport CardDetailContents from './cardDetailContents'\nimport CardDetailContentsMenu from './cardDetailContentsMenu'\nimport CardDetailProperties from './cardDetailProperties'\nimport useImagePaste from './imagePaste'\nimport AttachmentList from './attachment'\n\nimport './cardDetail.scss'\n\nexport const OnboardingBoardTitle = 'Welcome to Boards!'\nexport const OnboardingCardTitle = 'Create a new card'\n\ntype Props = {\n    board: Board\n    activeView: BoardView\n    views: BoardView[]\n    cards: Card[]\n    card: Card\n    comments: CommentBlock[]\n    attachments: AttachmentBlock[]\n    contents: Array<ContentBlock|ContentBlock[]>\n    readonly: boolean\n    onClose: () => void\n    onDelete: (block: Block) => void\n    addAttachment: () => void\n}\n\nasync function addBlockNewEditor(card: Card, intl: IntlShape, title: string, fields: any, contentType: ContentBlockTypes, afterBlockId: string, dispatch: any): Promise<Block> {\n    const block = createBlock()\n    block.parentId = card.id\n    block.boardId = card.boardId\n    block.title = title\n    block.type = contentType\n    block.fields = {...block.fields, ...fields}\n\n    const description = intl.formatMessage({id: 'CardDetail.addCardText', defaultMessage: 'add card text'})\n\n    const afterRedo = async (newBlock: Block) => {\n        const contentOrder = card.fields.contentOrder.slice()\n        if (afterBlockId) {\n            const idx = contentOrder.indexOf(afterBlockId)\n            if (idx === -1) {\n                contentOrder.push(newBlock.id)\n            } else {\n                contentOrder.splice(idx + 1, 0, newBlock.id)\n            }\n        } else {\n            contentOrder.push(newBlock.id)\n        }\n        await octoClient.patchBlock(card.boardId, card.id, {updatedFields: {contentOrder}})\n        dispatch(updateCards([{...card, fields: {...card.fields, contentOrder}}]))\n    }\n\n    const beforeUndo = async () => {\n        const contentOrder = card.fields.contentOrder.slice()\n        await octoClient.patchBlock(card.boardId, card.id, {updatedFields: {contentOrder}})\n    }\n\n    const newBlock = await mutator.insertBlock(block.boardId, block, description, afterRedo, beforeUndo)\n    dispatch(updateContents([newBlock]))\n    return newBlock\n}\n\nconst CardDetail = (props: Props): JSX.Element|null => {\n    const {card, comments, attachments, onDelete, addAttachment} = props\n    const {limited} = card\n    const [title, setTitle] = useState(card.title)\n    const [serverTitle, setServerTitle] = useState(card.title)\n    const titleRef = useRef<Focusable>(null)\n    const saveTitle = useCallback(() => {\n        if (title !== card.title) {\n            mutator.changeBlockTitle(props.board.id, card.id, card.title, title)\n        }\n    }, [card.title, title])\n    const canEditBoardCards = useHasCurrentBoardPermissions([Permission.ManageBoardCards])\n    const canCommentBoardCards = useHasCurrentBoardPermissions([Permission.CommentBoardCards])\n\n    const saveTitleRef = useRef<() => void>(saveTitle)\n    saveTitleRef.current = saveTitle\n    const intl = useIntl()\n\n    const clientConfig = useAppSelector<ClientConfig>(getClientConfig)\n    const newBoardsEditor = clientConfig?.featureFlags?.newBoardsEditor || false\n\n    useImagePaste(props.board.id, card.id, card.fields.contentOrder)\n\n    useEffect(() => {\n        if (!title) {\n            setTimeout(() => titleRef.current?.focus(), 300)\n        }\n        TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.ViewCard, {board: props.board.id, view: props.activeView.id, card: card.id})\n    }, [])\n\n    useEffect(() => {\n        if (serverTitle === title) {\n            setTitle(card.title)\n        }\n        setServerTitle(card.title)\n    }, [card.title, title])\n\n    useEffect(() => {\n        return () => {\n            saveTitleRef.current && saveTitleRef.current()\n        }\n    }, [])\n\n    const setRandomIcon = useCallback(() => {\n        const newIcon = BlockIcons.shared.randomIcon()\n        mutator.changeBlockIcon(props.board.id, card.id, card.fields.icon, newIcon)\n    }, [card.id, card.fields.icon])\n\n    const dispatch = useAppDispatch()\n    useEffect(() => {\n        dispatch(setCurrentCard(card.id))\n    }, [card.id])\n\n    if (!card) {\n        return null\n    }\n\n    const blocks = useMemo(() => props.contents.flatMap((value: Block | Block[]): BlockData<any> => {\n        const v: Block = Array.isArray(value) ? value[0] : value\n\n        let data: any = v?.title\n        if (v?.type === 'image') {\n            data = {\n                file: v?.fields.fileId,\n            }\n        }\n\n        if (v?.type === 'attachment') {\n            data = {\n                file: v?.fields.fileId,\n                filename: v?.fields.filename,\n            }\n        }\n\n        if (v?.type === 'video') {\n            data = {\n                file: v?.fields.fileId,\n                filename: v?.fields.filename,\n            }\n        }\n\n        if (v?.type === 'checkbox') {\n            data = {\n                value: v?.title,\n                checked: v?.fields.value,\n            }\n        }\n\n        return {\n            id: v?.id,\n            value: data,\n            contentType: v?.type,\n        }\n    }), [props.contents])\n\n    return (\n        <>\n            <div className={`CardDetail ${limited ? ' CardDetail--is-limited' : ''}`}>\n                <BlockIconSelector\n                    block={card}\n                    size='l'\n                    readonly={props.readonly || !canEditBoardCards || limited}\n                />\n                {!props.readonly && canEditBoardCards && !card.fields.icon &&\n                    <div className='add-buttons'>\n                        <Button\n                            emphasis='default'\n                            size='small'\n                            onClick={setRandomIcon}\n                            icon={\n                                <CompassIcon\n                                    icon='emoticon-outline'\n                                />}\n\n                        >\n                            <FormattedMessage\n                                id='CardDetail.add-icon'\n                                defaultMessage='Add icon'\n                            />\n                        </Button>\n                    </div>}\n\n                <EditableArea\n                    ref={titleRef}\n                    className='title'\n                    value={title}\n                    placeholderText='Untitled'\n                    onChange={(newTitle: string) => setTitle(newTitle)}\n                    saveOnEsc={true}\n                    onSave={saveTitle}\n                    onCancel={() => setTitle(props.card.title)}\n                    readonly={props.readonly || !canEditBoardCards || limited}\n                    spellCheck={true}\n                />\n\n                {/* Hidden (limited) card copy + CTA */}\n\n                {limited && <div className='CardDetail__limited-wrapper'>\n                    <CardSkeleton\n                        className='CardDetail__limited-bg'\n                    />\n                    <p className='CardDetail__limited-title'>\n                        <FormattedMessage\n                            id='CardDetail.limited-title'\n                            defaultMessage='This card is hidden'\n                        />\n                    </p>\n                    <p className='CardDetail__limited-body'>\n                        <FormattedMessage\n                            id='CardDetail.limited-body'\n                            defaultMessage='Upgrade to our Professional or Enterprise plan to view archived cards, have unlimited views per boards, unlimited cards and more.'\n                        />\n                        <br/>\n                        <a\n                            className='CardDetail__limited-link'\n                            role='button'\n                            onClick={() => {\n                                props.onClose();\n                                (window as any).openPricingModal()({trackingLocation: 'boards > learn_more_about_our_plans_click'})\n                            }}\n                        >\n                            <FormattedMessage\n                                id='CardDetial.limited-link'\n                                defaultMessage='Learn more about our plans.'\n                            />\n                        </a>\n                    </p>\n                    <Button\n                        className='CardDetail__limited-button'\n                        onClick={() => {\n                            props.onClose();\n                            (window as any).openPricingModal()({trackingLocation: 'boards > upgrade_click'})\n                        }}\n                        emphasis='primary'\n                        size='large'\n                    >\n                        {intl.formatMessage({id: 'CardDetail.limited-button', defaultMessage: 'Upgrade'})}\n                    </Button>\n                </div>}\n\n                {/* Property list */}\n\n                {!limited &&\n                <CardDetailProperties\n                    board={props.board}\n                    card={props.card}\n                    cards={props.cards}\n                    activeView={props.activeView}\n                    views={props.views}\n                    readonly={props.readonly}\n                />}\n\n                {attachments.length !== 0 && <Fragment>\n                    <hr/>\n                    <AttachmentList\n                        attachments={attachments}\n                        onDelete={onDelete}\n                        addAttachment={addAttachment}\n                    />\n                </Fragment>}\n\n                {/* Comments */}\n\n                {!limited && <Fragment>\n                    <hr/>\n                    <CommentsList\n                        comments={comments}\n                        boardId={card.boardId}\n                        cardId={card.id}\n                        readonly={props.readonly || !canCommentBoardCards}\n                    />\n                </Fragment>}\n            </div>\n\n            {/* Content blocks */}\n\n            {!limited && <div className='CardDetail CardDetail--fullwidth content-blocks'>\n                {newBoardsEditor && (\n                    <BlocksEditor\n                        boardId={card.boardId}\n                        blocks={blocks}\n                        onBlockCreated={async (block: any, afterBlock: any): Promise<BlockData|null> => {\n                            if (block.contentType === 'text' && block.value === '') {\n                                return null\n                            }\n                            let newBlock: Block\n                            if (block.contentType === 'checkbox') {\n                                newBlock = await addBlockNewEditor(card, intl, block.value.value, {value: block.value.checked}, block.contentType, afterBlock?.id, dispatch)\n                            } else if (block.contentType === 'image' || block.contentType === 'attachment' || block.contentType === 'video') {\n                                const newFileId = await octoClient.uploadFile(card.boardId, block.value.file)\n                                newBlock = await addBlockNewEditor(card, intl, '', {fileId: newFileId, filename: block.value.filename}, block.contentType, afterBlock?.id, dispatch)\n                            } else {\n                                newBlock = await addBlockNewEditor(card, intl, block.value, {}, block.contentType, afterBlock?.id, dispatch)\n                            }\n                            return {...block, id: newBlock.id}\n                        }}\n                        onBlockModified={async (block: any): Promise<BlockData<any>|null> => {\n                            const originalContentBlock = props.contents.flatMap((b) => b).find((b) => b.id === block.id)\n                            if (!originalContentBlock) {\n                                return null\n                            }\n\n                            if (block.contentType === 'text' && block.value === '') {\n                                const description = intl.formatMessage({id: 'ContentBlock.DeleteAction', defaultMessage: 'delete'})\n\n                                mutator.deleteBlock(originalContentBlock, description)\n                                return null\n                            }\n                            const newBlock = {\n                                ...originalContentBlock,\n                                title: block.value,\n                            }\n\n                            if (block.contentType === 'checkbox') {\n                                newBlock.title = block.value.value\n                                newBlock.fields = {...newBlock.fields, value: block.value.checked}\n                            }\n                            mutator.updateBlock(card.boardId, newBlock, originalContentBlock, intl.formatMessage({id: 'ContentBlock.editCardText', defaultMessage: 'edit card content'}))\n                            return block\n                        }}\n                        onBlockMoved={async (block: BlockData, beforeBlock: BlockData|null, afterBlock: BlockData|null): Promise<void> => {\n                            if (block.id) {\n                                const idx = card.fields.contentOrder.indexOf(block.id)\n                                let sourceBlockId: string\n                                let sourceWhere: 'after'|'before'\n                                if (idx === -1) {\n                                    Utils.logError('Unable to find the block id in the order of the current block')\n                                    return\n                                }\n                                if (idx === 0) {\n                                    sourceBlockId = card.fields.contentOrder[1] as string\n                                    sourceWhere = 'before'\n                                } else {\n                                    sourceBlockId = card.fields.contentOrder[idx - 1] as string\n                                    sourceWhere = 'after'\n                                }\n                                if (afterBlock && afterBlock.id) {\n                                    await mutator.moveContentBlock(block.id, afterBlock.id, 'after', sourceBlockId, sourceWhere, intl.formatMessage({id: 'ContentBlock.moveBlock', defaultMessage: 'move card content'}))\n                                    return\n                                }\n                                if (beforeBlock && beforeBlock.id) {\n                                    await mutator.moveContentBlock(block.id, beforeBlock.id, 'before', sourceBlockId, sourceWhere, intl.formatMessage({id: 'ContentBlock.moveBlock', defaultMessage: 'move card content'}))\n                                }\n                            }\n                        }}\n                    />)}\n                {!newBoardsEditor && (\n                    <CardDetailProvider card={card}>\n                        <CardDetailContents\n                            card={props.card}\n                            contents={props.contents}\n                            readonly={props.readonly || !canEditBoardCards}\n                        />\n                        {!props.readonly && canEditBoardCards && <CardDetailContentsMenu/>}\n                    </CardDetailProvider>)}\n            </div>}\n        </>\n    )\n}\n\nexport default CardDetail\n"
  },
  {
    "path": "webapp/src/components/cardDetail/cardDetailContents.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {ReactElement, ReactNode} from 'react'\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport {fireEvent, render} from '@testing-library/react'\n\nimport {act} from 'react-dom/test-utils'\n\nimport {TestBlockFactory} from '../../test/testBlockFactory'\n\nimport {mockDOM, wrapDNDIntl, mockStateStore} from '../../testUtils'\n\nimport CardDetailContents from './cardDetailContents'\nimport {CardDetailProvider} from './cardDetailContext'\n\nglobal.fetch = jest.fn()\njest.mock('draft-js/lib/generateRandomKey', () => () => '123')\n\nbeforeAll(() => {\n    mockDOM()\n})\n\ndescribe('components/cardDetail/cardDetailContents', () => {\n    const board = TestBlockFactory.createBoard()\n    board.cardProperties = [\n        {\n            id: 'property_id_1',\n            name: 'Owner',\n            type: 'select',\n            options: [\n                {\n                    color: 'propColorDefault',\n                    id: 'property_value_id_1',\n                    value: 'Jean-Luc Picard',\n                },\n                {\n                    color: 'propColorDefault',\n                    id: 'property_value_id_2',\n                    value: 'William Riker',\n                },\n                {\n                    color: 'propColorDefault',\n                    id: 'property_value_id_3',\n                    value: 'Deanna Troi',\n                },\n            ],\n        },\n    ]\n\n    const card = TestBlockFactory.createCard(board)\n\n    const state = {\n        users: {\n            boardUsers: {\n                1: {username: 'abc'},\n                2: {username: 'd'},\n                3: {username: 'e'},\n                4: {username: 'f'},\n                5: {username: 'g'},\n            },\n        },\n        boards: {\n            boards: {\n                [board.id]: board,\n            },\n            current: board.id,\n        },\n        cards: {\n            cards: {\n                [card.id]: card,\n            },\n            current: card.id,\n        },\n        clientConfig: {\n            value: {},\n        },\n    }\n    const store = mockStateStore([], state)\n    const wrap = (child: ReactNode): ReactElement => (\n        wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <CardDetailProvider card={card}>\n                    {child}\n                </CardDetailProvider>\n            </ReduxProvider>,\n        )\n    )\n\n    test('should match snapshot', async () => {\n        const component = wrap((\n            <CardDetailContents\n                id='test-id'\n                card={card}\n                contents={[]}\n                readonly={false}\n            />\n        ))\n\n        let container: Element | undefined\n        await act(async () => {\n            const result = render(component)\n            container = result.container\n        })\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot with contents array', async () => {\n        const contents = [TestBlockFactory.createDivider(card)]\n        const component = wrap((\n            <CardDetailContents\n                id='test-id'\n                card={card}\n                contents={contents}\n                readonly={false}\n            />\n        ))\n\n        let container: Element | undefined\n        await act(async () => {\n            const result = render(component)\n            container = result.container\n        })\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot with contents array that has array inside it', async () => {\n        const contents = [TestBlockFactory.createDivider(card), [TestBlockFactory.createDivider(card), TestBlockFactory.createDivider(card)]]\n        const component = wrap((\n            <CardDetailContents\n                id='test-id'\n                card={card}\n                contents={contents}\n                readonly={false}\n            />\n        ))\n        let container: Element | undefined\n        await act(async () => {\n            const result = render(component)\n            container = result.container\n        })\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot after drag and drop event', async () => {\n        const contents = [TestBlockFactory.createDivider(card), [TestBlockFactory.createDivider(card), TestBlockFactory.createDivider(card)]]\n        card.fields.contentOrder = contents.map((content) => (Array.isArray(content) ? content.map((c) => c.id) : (content as any).id))\n        const component = wrap((\n            <CardDetailContents\n                id='test-id'\n                card={card}\n                contents={contents}\n                readonly={false}\n            />\n        ))\n\n        let container: Element | undefined\n        await act(async () => {\n            const result = render(component)\n            container = result.container\n        })\n\n        const drag = container!.querySelectorAll('.dnd-handle')[0]\n        const drop = container!.querySelectorAll('.addToRow')[4]\n\n        fireEvent.dragStart(drag)\n        fireEvent.dragEnter(drop)\n        fireEvent.dragOver(drop)\n        fireEvent.drop(drop)\n\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot after drag and drop event 2', async () => {\n        const contents = [TestBlockFactory.createDivider(card), TestBlockFactory.createDivider(card)]\n        card.fields.contentOrder = contents.map((content) => (Array.isArray(content) ? content.map((c) => c.id) : (content as any).id))\n        const component = wrap((\n            <CardDetailContents\n                id='test-id'\n                card={card}\n                contents={contents}\n                readonly={false}\n            />\n        ))\n\n        let container: Element | undefined\n        await act(async () => {\n            const result = render(component)\n            container = result.container\n        })\n\n        const drag = container!.querySelectorAll('.dnd-handle')[0]\n        const drop = container!.querySelectorAll('.addToRow')[4]\n\n        fireEvent.dragStart(drag)\n        fireEvent.dragEnter(drop)\n        fireEvent.dragOver(drop)\n        fireEvent.drop(drop)\n\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot after drag and drop event 3', async () => {\n        const contents = [TestBlockFactory.createDivider(card), TestBlockFactory.createDivider(card)]\n        card.fields.contentOrder = contents.map((content) => (Array.isArray(content) ? content.map((c) => c.id) : (content as any).id))\n        const component = wrap((\n            <CardDetailContents\n                id='test-id'\n                card={card}\n                contents={contents}\n                readonly={false}\n            />\n        ))\n\n        let container: Element | undefined\n        await act(async () => {\n            const result = render(component)\n            container = result.container\n        })\n\n        const drag = container!.querySelectorAll('.dnd-handle')[1]\n        const drop = container!.querySelectorAll('.addToRow')[2]\n\n        fireEvent.dragStart(drag)\n        fireEvent.dragEnter(drop)\n        fireEvent.dragOver(drop)\n        fireEvent.drop(drop)\n\n        expect(container).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/cardDetail/cardDetailContents.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\nimport {useIntl, IntlShape} from 'react-intl'\n\nimport {IContentBlockWithCords, ContentBlock as ContentBlockType} from '../../blocks/contentBlock'\nimport {Card} from '../../blocks/card'\nimport {createTextBlock} from '../../blocks/textBlock'\nimport {Block} from '../../blocks/block'\nimport mutator from '../../mutator'\nimport octoClient from '../../octoClient'\nimport {useSortableWithGrip} from '../../hooks/sortable'\n\nimport ContentBlock from '../contentBlock'\nimport {MarkdownEditor} from '../markdownEditor'\n\nimport AddDescriptionTourStep from '../onboardingTour/addDescription/add_description'\n\nimport {dragAndDropRearrange} from './cardDetailContentsUtility'\n\nexport type Position = 'left' | 'right' | 'above' | 'below' | 'aboveRow' | 'belowRow'\n\ntype Props = {\n    id?: string\n    card: Card\n    contents: Array<ContentBlockType|ContentBlockType[]>\n    readonly: boolean\n}\n\nasync function addTextBlock(card: Card, intl: IntlShape, text: string): Promise<Block> {\n    const block = createTextBlock()\n    block.parentId = card.id\n    block.boardId = card.boardId\n    block.title = text\n\n    const description = intl.formatMessage({id: 'CardDetail.addCardText', defaultMessage: 'add card text'})\n\n    const afterRedo = async (newBlock: Block) => {\n        const contentOrder = card.fields.contentOrder.slice()\n        contentOrder.push(newBlock.id)\n        await octoClient.patchBlock(card.boardId, card.id, {updatedFields: {contentOrder}})\n    }\n\n    const beforeUndo = async () => {\n        const contentOrder = card.fields.contentOrder.slice()\n        await octoClient.patchBlock(card.boardId, card.id, {updatedFields: {contentOrder}})\n    }\n\n    return mutator.insertBlock(block.boardId, block, description, afterRedo, beforeUndo)\n}\n\nfunction moveBlock(card: Card, srcBlock: IContentBlockWithCords, dstBlock: IContentBlockWithCords, intl: IntlShape, moveTo: Position): void {\n    const contentOrder: Array<string|string[]> = []\n    if (card.fields.contentOrder) {\n        for (const contentId of card.fields.contentOrder) {\n            if (typeof contentId === 'string') {\n                contentOrder.push(contentId)\n            } else {\n                contentOrder.push(contentId.slice())\n            }\n        }\n    }\n\n    const srcBlockId = srcBlock.block.id\n    const dstBlockId = dstBlock.block.id\n\n    const srcBlockX = srcBlock.cords.x\n    const dstBlockX = dstBlock.cords.x\n\n    const srcBlockY = (srcBlock.cords.y || srcBlock.cords.y === 0) && (srcBlock.cords.y > -1) ? srcBlock.cords.y : -1\n    const dstBlockY = (dstBlock.cords.y || dstBlock.cords.y === 0) && (dstBlock.cords.y > -1) ? dstBlock.cords.y : -1\n\n    if (srcBlockId === dstBlockId) {\n        return\n    }\n\n    const newContentOrder = dragAndDropRearrange({contentOrder, srcBlockId, srcBlockX, srcBlockY, dstBlockId, dstBlockX, dstBlockY, moveTo})\n\n    mutator.performAsUndoGroup(async () => {\n        const description = intl.formatMessage({id: 'CardDetail.moveContent', defaultMessage: 'Move card content'})\n        await mutator.changeCardContentOrder(card.boardId, card.id, card.fields.contentOrder, newContentOrder, description)\n    })\n}\n\ntype ContentBlockWithDragAndDropProps = {\n    block: ContentBlockType | ContentBlockType[]\n    x: number\n    card: Card\n    contents: Array<ContentBlockType|ContentBlockType[]>\n    intl: IntlShape\n    readonly: boolean\n}\n\nconst ContentBlockWithDragAndDrop = (props: ContentBlockWithDragAndDropProps) => {\n    const [, isOver,, itemRef] = useSortableWithGrip('content', {block: props.block, cords: {x: props.x}}, true, (src, dst) => moveBlock(props.card, src, dst, props.intl, 'aboveRow'))\n    const [, isOver2,, itemRef2] = useSortableWithGrip('content', {block: props.block, cords: {x: props.x}}, true, (src, dst) => moveBlock(props.card, src, dst, props.intl, 'belowRow'))\n\n    if (Array.isArray(props.block)) {\n        return (\n            <div >\n                <div\n                    ref={itemRef}\n                    className={`addToRow ${isOver ? 'dragover' : ''}`}\n                    style={{width: '94%', height: '10px', marginLeft: '48px'}}\n                />\n                <div\n                    style={{display: 'flex'}}\n                >\n\n                    {props.block.map((b, y) => (\n                        <ContentBlock\n                            key={b.id}\n                            block={b}\n                            card={props.card}\n                            readonly={props.readonly}\n                            width={(1 / (props.block as ContentBlockType[]).length) * 100}\n                            onDrop={(src, dst, moveTo) => moveBlock(props.card, src, dst, props.intl, moveTo)}\n                            cords={{x: props.x, y}}\n                        />\n                    ))}\n                </div>\n                {props.x === props.contents.length - 1 && (\n                    <div\n                        ref={itemRef2}\n                        className={`addToRow ${isOver2 ? 'dragover' : ''}`}\n                        style={{width: '94%', height: '10px', marginLeft: '48px'}}\n                    />\n                )}\n            </div>\n\n        )\n    }\n\n    return (\n        <div>\n            <div\n                ref={itemRef}\n                className={`addToRow ${isOver ? 'dragover' : ''}`}\n                style={{width: '94%', height: '10px', marginLeft: '48px'}}\n            />\n            <ContentBlock\n                key={props.block.id}\n                block={props.block}\n                card={props.card}\n                readonly={props.readonly}\n                onDrop={(src, dst, moveTo) => moveBlock(props.card, src, dst, props.intl, moveTo)}\n                cords={{x: props.x}}\n            />\n            {props.x === props.contents.length - 1 && (\n                <div\n                    ref={itemRef2}\n                    className={`addToRow ${isOver2 ? 'dragover' : ''}`}\n                    style={{width: '94%', height: '10px', marginLeft: '48px'}}\n                />\n            )}\n        </div>\n\n    )\n}\n\nconst CardDetailContents = (props: Props) => {\n    const intl = useIntl()\n    const {contents, card, id} = props\n    if (contents.length) {\n        return (\n            <div className='octo-content'>\n                {contents.map((block, x) =>\n                    (\n                        <React.Fragment key={x}>\n                            <ContentBlockWithDragAndDrop\n                                block={block}\n                                x={x}\n                                card={card}\n                                contents={contents}\n                                intl={intl}\n                                readonly={props.readonly}\n                            />\n                            {x === 0 && <AddDescriptionTourStep/>}\n                        </React.Fragment>\n                    ),\n                )}\n            </div>\n        )\n    }\n    return (\n        <div className='octo-content CardDetailContents'>\n            <div className='octo-block'>\n                <div className='octo-block-margin'/>\n                {!props.readonly &&\n                    <MarkdownEditor\n                        id={id}\n                        text=''\n                        placeholderText='Add a description...'\n                        onBlur={(text) => {\n                            if (text) {\n                                addTextBlock(card, intl, text)\n                            }\n                        }}\n                    />\n                }\n            </div>\n        </div>\n    )\n}\n\nexport default React.memo(CardDetailContents)\n"
  },
  {
    "path": "webapp/src/components/cardDetail/cardDetailContentsMenu.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {act, render, screen} from '@testing-library/react'\nimport userEvent from '@testing-library/user-event'\nimport React, {ReactElement, ReactNode} from 'react'\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport {wrapIntl, mockStateStore} from '../../testUtils'\n\nimport {TestBlockFactory} from '../../test/testBlockFactory'\n\nimport CardDetailContentsMenu from './cardDetailContentsMenu'\n\n//for contentRegistry\nimport '../content/textElement'\nimport '../content/imageElement'\nimport '../content/dividerElement'\nimport '../content/checkboxElement'\nimport {CardDetailProvider} from './cardDetailContext'\n\njest.mock('../../mutator')\n\nconst board = TestBlockFactory.createBoard()\nconst card = TestBlockFactory.createCard(board)\ndescribe('components/cardDetail/cardDetailContentsMenu', () => {\n    const store = mockStateStore([], {})\n    const wrap = (child: ReactNode): ReactElement => (\n        wrapIntl(\n            <ReduxProvider store={store}>\n                <CardDetailProvider card={card}>\n                    {child}\n                </CardDetailProvider>\n            </ReduxProvider>,\n        )\n    )\n    beforeEach(() => {\n        jest.clearAllMocks()\n    })\n    test('return cardDetailContentsMenu', () => {\n        const {container} = render(wrap(<CardDetailContentsMenu/>))\n        const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})\n        userEvent.click(buttonElement)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('return cardDetailContentsMenu and add Text content', async () => {\n        const {container} = render(wrap(<CardDetailContentsMenu/>))\n        const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})\n        userEvent.click(buttonElement)\n        expect(container).toMatchSnapshot()\n        await act(async () => {\n            const buttonAddText = screen.getByRole('button', {name: 'text'})\n            userEvent.click(buttonAddText)\n        })\n        expect(container).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/cardDetail/cardDetailContentsMenu.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useCallback} from 'react'\nimport {FormattedMessage, IntlShape, useIntl} from 'react-intl'\n\nimport {BlockTypes} from '../../blocks/block'\nimport {Utils} from '../../utils'\nimport Button from '../../widgets/buttons/button'\nimport Menu from '../../widgets/menu'\nimport MenuWrapper from '../../widgets/menuWrapper'\n\nimport {contentRegistry} from '../content/contentRegistry'\n\nimport {useCardDetailContext} from './cardDetailContext'\n\nfunction addContentMenu(intl: IntlShape, type: BlockTypes): JSX.Element {\n    const handler = contentRegistry.getHandler(type)\n    if (!handler) {\n        Utils.logError(`addContentMenu, unknown content type: ${type}`)\n        return <></>\n    }\n    const cardDetail = useCardDetailContext()\n    const addElement = useCallback(async () => {\n        const {card} = cardDetail\n        const index = card.fields.contentOrder.length\n        cardDetail.addBlock(handler, index, false)\n    }, [cardDetail, handler])\n\n    return (\n        <Menu.Text\n            key={type}\n            id={type}\n            name={handler.getDisplayText(intl)}\n            icon={handler.getIcon()}\n            onClick={addElement}\n        />\n    )\n}\n\nconst CardDetailContentsMenu = () => {\n    const intl = useIntl()\n    return (\n        <div className='CardDetailContentsMenu content add-content'>\n            <MenuWrapper>\n                <Button>\n                    <FormattedMessage\n                        id='CardDetail.add-content'\n                        defaultMessage='Add content'\n                    />\n                </Button>\n                <Menu position='top'>\n                    {contentRegistry.contentTypes.map((type) => addContentMenu(intl, type))}\n                </Menu>\n            </MenuWrapper>\n        </div>\n    )\n}\n\nexport default React.memo(CardDetailContentsMenu)\n"
  },
  {
    "path": "webapp/src/components/cardDetail/cardDetailContentsUtility.test.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {dragAndDropRearrange} from './cardDetailContentsUtility'\nimport {Position} from './cardDetailContents'\n\ndescribe('components/cardDetail/cardDetailContentsUtility', () => {\n    test('Testing moving first item in the row to the end', () => {\n        const data = {\n            contentOrder: [\n                ['1', '2', '3'],\n            ],\n            srcBlockId: '1',\n            srcBlockX: 0,\n            srcBlockY: 0,\n            dstBlockX: 0,\n            dstBlockY: 2,\n            dstBlockId: '3',\n            moveTo: 'right' as Position,\n        }\n\n        const result = dragAndDropRearrange(data)\n        const expected = [\n            ['2', '3', '1'],\n        ]\n        expect(result).toEqual(expected)\n    })\n\n    test('Testing moving last item in the row to the beginning', () => {\n        const data = {\n            contentOrder: [\n                ['1', '2', '3'],\n            ],\n            srcBlockId: '3',\n            srcBlockX: 0,\n            srcBlockY: 2,\n            dstBlockX: 0,\n            dstBlockY: 0,\n            dstBlockId: '1',\n            moveTo: 'left' as Position,\n        }\n\n        const result = dragAndDropRearrange(data)\n        const expected = [\n            ['3', '1', '2'],\n        ]\n        expect(result).toEqual(expected)\n    })\n\n    test('Testing moving item from beginning of row to the middle of row', () => {\n        const data = {\n            contentOrder: [\n                ['1', '2', '3'],\n            ],\n            srcBlockId: '1',\n            srcBlockX: 0,\n            srcBlockY: 0,\n            dstBlockX: 0,\n            dstBlockY: 2,\n            dstBlockId: '3',\n            moveTo: 'left' as Position,\n        }\n\n        const result = dragAndDropRearrange(data)\n        const expected = [\n            ['2', '1', '3'],\n        ]\n        expect(result).toEqual(expected)\n    })\n\n    test('Testing swapping two items in the same row by moving second item to the left of the first item', () => {\n        const data = {\n            contentOrder: [\n                ['1', '2'],\n            ],\n            srcBlockId: '2',\n            srcBlockX: 0,\n            srcBlockY: 1,\n            dstBlockX: 0,\n            dstBlockY: 0,\n            dstBlockId: '1',\n            moveTo: 'left' as Position,\n        }\n\n        const result = dragAndDropRearrange(data)\n        const expected = [\n            ['2', '1'],\n        ]\n        expect(result).toEqual(expected)\n    })\n\n    test('Testing swapping two items in the same row by moving the first item to the right of the second item', () => {\n        const data = {\n            contentOrder: [\n                ['1', '2'],\n            ],\n            srcBlockId: '1',\n            srcBlockX: 0,\n            srcBlockY: 0,\n            dstBlockX: 0,\n            dstBlockY: 1,\n            dstBlockId: '2',\n            moveTo: 'right' as Position,\n        }\n\n        const result = dragAndDropRearrange(data)\n        const expected = [\n            ['2', '1'],\n        ]\n        expect(result).toEqual(expected)\n    })\n\n    test('Testing moving a single item in the first row into the middle of second row using left operation', () => {\n        const data = {\n            contentOrder: [\n                '4',\n                ['1', '3'],\n            ],\n            srcBlockId: '4',\n            srcBlockX: 0,\n            srcBlockY: -1,\n            dstBlockX: 1,\n            dstBlockY: 1,\n            dstBlockId: '3',\n            moveTo: 'left' as Position,\n        }\n\n        const result = dragAndDropRearrange(data)\n        const expected = [\n            ['1', '4', '3'],\n        ]\n        expect(result).toEqual(expected)\n    })\n\n    test('Testing moving a single item in the first row into the middle of second row using right operation', () => {\n        const data = {\n            contentOrder: [\n                '4',\n                ['1', '3'],\n            ],\n            srcBlockId: '4',\n            srcBlockX: 0,\n            srcBlockY: -1,\n            dstBlockX: 1,\n            dstBlockY: 0,\n            dstBlockId: '1',\n            moveTo: 'right' as Position,\n        }\n\n        const result = dragAndDropRearrange(data)\n        const expected = [\n            ['1', '4', '3'],\n        ]\n        expect(result).toEqual(expected)\n    })\n\n    test('Testing moving a single item in the last row into the middle of first row', () => {\n        const data = {\n            contentOrder: [\n                ['1', '3'],\n                '4',\n            ],\n            srcBlockId: '4',\n            srcBlockX: 1,\n            srcBlockY: -1,\n            dstBlockX: 0,\n            dstBlockY: 0,\n            dstBlockId: '1',\n            moveTo: 'right' as Position,\n        }\n\n        const result = dragAndDropRearrange(data)\n        const expected = [\n            ['1', '4', '3'],\n        ]\n        expect(result).toEqual(expected)\n    })\n\n    test('Testing moving a single item in the last row above the first row', () => {\n        const data = {\n            contentOrder: [\n                ['1', '3'],\n                '4',\n            ],\n            srcBlockId: '4',\n            srcBlockX: 1,\n            srcBlockY: -1,\n            dstBlockX: 0,\n            dstBlockY: 0,\n            dstBlockId: '1',\n            moveTo: 'aboveRow' as Position,\n        }\n\n        const result = dragAndDropRearrange(data)\n        const expected = [\n            '4',\n            ['1', '3'],\n        ]\n        expect(result).toEqual(expected)\n    })\n\n    test('Testing moving an item out of a row', () => {\n        const data = {\n            contentOrder: [\n                ['1', '3'],\n                '4',\n            ],\n            srcBlockId: '3',\n            srcBlockX: 0,\n            srcBlockY: 1,\n            dstBlockX: 1,\n            dstBlockY: -1,\n            dstBlockId: '4',\n            moveTo: 'belowRow' as Position,\n        }\n\n        const result = dragAndDropRearrange(data)\n        const expected = [\n            '1',\n            '4',\n            '3',\n        ]\n        expect(result).toEqual(expected)\n    })\n\n    test('Testing moving an item out of a row and creating a new row with a single item at the end', () => {\n        const data = {\n            contentOrder: [\n                ['1', '3'],\n                '4',\n            ],\n            srcBlockId: '3',\n            srcBlockX: 0,\n            srcBlockY: 1,\n            dstBlockX: 1,\n            dstBlockY: -1,\n            dstBlockId: '4',\n            moveTo: 'right' as Position,\n        }\n\n        const result = dragAndDropRearrange(data)\n        const expected = [\n            '1',\n            ['4', '3'],\n        ]\n        expect(result).toEqual(expected)\n    })\n\n    test('Testing moving an item out of a row and creating a new row with a single item at the beginning', () => {\n        const data = {\n            contentOrder: [\n                ['1', '3'],\n                '4',\n            ],\n            srcBlockId: '3',\n            srcBlockX: 0,\n            srcBlockY: 1,\n            dstBlockX: 1,\n            dstBlockY: -1,\n            dstBlockId: '4',\n            moveTo: 'left' as Position,\n        }\n\n        const result = dragAndDropRearrange(data)\n        const expected = [\n            '1',\n            ['3', '4'],\n        ]\n        expect(result).toEqual(expected)\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/cardDetail/cardDetailContentsUtility.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {Position} from './cardDetailContents'\n\ntype DragAndDropRearrangeData = {\n    contentOrder: Array<string|string[]>\n    srcBlockId: string\n    srcBlockX: number\n    srcBlockY: number\n    dstBlockX: number\n    dstBlockY: number\n    dstBlockId: string\n    moveTo: Position\n}\n\nexport const dragAndDropRearrange =\n    (data: DragAndDropRearrangeData): Array<string|string[]> => {\n        const {contentOrder, srcBlockX, srcBlockY, dstBlockX, dstBlockY, moveTo, srcBlockId, dstBlockId} = data\n        const newContentOrder: Array<string|string[]> = JSON.parse(JSON.stringify(contentOrder))\n\n        const copySrcBlockX = srcBlockX\n        const copySrcBlockY = srcBlockY\n\n        let copyDstBlockX = dstBlockX\n        let copyDstBlockY = dstBlockY\n\n        // Delete the block we are moving first then move it to the correct place\n\n        // Delete Src Block\n        if (copySrcBlockY > -1) {\n            (newContentOrder[copySrcBlockX] as string[]).splice(copySrcBlockY, 1)\n\n            if (newContentOrder[copySrcBlockX].length === 1 && copySrcBlockX !== copyDstBlockX) {\n                newContentOrder.splice(copySrcBlockX, 1, newContentOrder[copySrcBlockX][0])\n            }\n        } else {\n            newContentOrder.splice(copySrcBlockX, 1)\n\n            if (copyDstBlockX > copySrcBlockX) {\n                copyDstBlockX -= 1\n            }\n        }\n\n        if (moveTo === 'right') {\n            if (copyDstBlockY > -1) {\n                if (copyDstBlockX === copySrcBlockX && copyDstBlockY > copySrcBlockY && copySrcBlockY > -1) {\n                    copyDstBlockY -= 1\n                }\n\n                (newContentOrder[copyDstBlockX] as string[]).splice(copyDstBlockY + 1, 0, srcBlockId)\n            } else {\n                newContentOrder.splice(copyDstBlockX, 1, [dstBlockId, srcBlockId])\n            }\n        } else if (moveTo === 'left') {\n            if (copyDstBlockY > -1) {\n                if (copyDstBlockX === copySrcBlockX && copyDstBlockY > copySrcBlockY && copySrcBlockY > -1) {\n                    copyDstBlockY -= 1\n                }\n\n                (newContentOrder[copyDstBlockX] as string[]).splice(copyDstBlockY, 0, srcBlockId)\n            } else {\n                newContentOrder.splice(copyDstBlockX, 1, [srcBlockId, dstBlockId])\n            }\n        } else if (moveTo === 'aboveRow') {\n            newContentOrder.splice(copyDstBlockX, 0, srcBlockId)\n        } else if (moveTo === 'belowRow') {\n            newContentOrder.splice(copyDstBlockX + 1, 0, srcBlockId)\n        }\n\n        return newContentOrder\n    }\n"
  },
  {
    "path": "webapp/src/components/cardDetail/cardDetailContext.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {createContext, ReactElement, ReactNode, useContext, useMemo, useState, useCallback} from 'react'\n\nimport {useIntl} from 'react-intl'\n\nimport {Block} from '../../blocks/block'\nimport {Card} from '../../blocks/card'\nimport {ContentHandler} from '../content/contentRegistry'\nimport octoClient from '../../octoClient'\nimport mutator from '../../mutator'\n\nexport type AddedBlock = {\n    id: string\n    autoAdded: boolean\n}\n\nexport type CardDetailContextType = {\n    card: Card\n    lastAddedBlock: AddedBlock\n    addBlock: (handler: ContentHandler, index: number, auto: boolean) => void\n    deleteBlock: (block: Block, index: number) => void\n}\n\nexport const CardDetailContext = createContext<CardDetailContextType | null>(null)\n\nexport function useCardDetailContext(): CardDetailContextType {\n    const cardDetailContext = useContext(CardDetailContext)\n    if (!cardDetailContext) {\n        throw new Error('CardDetailContext is not available!')\n    }\n    return cardDetailContext\n}\n\ntype CardDetailProps = {\n    card: Card\n    children: ReactNode\n}\n\nexport const CardDetailProvider = (props: CardDetailProps): ReactElement => {\n    const intl = useIntl()\n    const [lastAddedBlock, setLastAddedBlock] = useState<AddedBlock>({\n        id: '',\n        autoAdded: false,\n    })\n    const {card} = props\n    const addBlock = useCallback(async (handler: ContentHandler, index: number, auto: boolean) => {\n        const block = await handler.createBlock(card.boardId, intl)\n        block.parentId = card.id\n        block.boardId = card.boardId\n        const typeName = handler.getDisplayText(intl)\n        const description = intl.formatMessage({id: 'ContentBlock.addElement', defaultMessage: 'add {type}'}, {type: typeName})\n        await mutator.performAsUndoGroup(async () => {\n            const afterRedo = async (newBlock: Block) => {\n                const contentOrder = card.fields.contentOrder.slice()\n                contentOrder.splice(index, 0, newBlock.id)\n                await octoClient.patchBlock(card.boardId, card.id, {updatedFields: {contentOrder}})\n            }\n\n            const beforeUndo = async () => {\n                const contentOrder = card.fields.contentOrder.slice()\n                await octoClient.patchBlock(card.boardId, card.id, {updatedFields: {contentOrder}})\n            }\n\n            const insertedBlock = await mutator.insertBlock(block.boardId, block, description, afterRedo, beforeUndo)\n            setLastAddedBlock({id: insertedBlock.id, autoAdded: auto})\n        })\n    }, [card.boardId, card.id, card.fields.contentOrder])\n\n    const deleteBlock = useCallback(async (block: Block, index: number) => {\n        const contentOrder = card.fields.contentOrder.slice()\n        contentOrder.splice(index, 1)\n        const description = intl.formatMessage({id: 'ContentBlock.DeleteAction', defaultMessage: 'delete'})\n        await mutator.performAsUndoGroup(async () => {\n            await mutator.deleteBlock(block, description)\n            await mutator.changeCardContentOrder(card.boardId, card.id, card.fields.contentOrder, contentOrder, description)\n        })\n    }, [card.boardId, card.id, card.fields.contentOrder])\n\n    const contextValue = useMemo(() => ({\n        card,\n        lastAddedBlock,\n        addBlock,\n        deleteBlock,\n    }), [card, lastAddedBlock, addBlock, deleteBlock])\n\n    return (\n        <CardDetailContext.Provider value={contextValue}>\n            {props.children}\n        </CardDetailContext.Provider>\n    )\n}\n"
  },
  {
    "path": "webapp/src/components/cardDetail/cardDetailProperties.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {render, screen, act, fireEvent} from '@testing-library/react'\nimport userEvent from '@testing-library/user-event'\nimport {mocked} from 'jest-mock'\nimport '@testing-library/jest-dom'\nimport {createIntl} from 'react-intl'\n\nimport configureStore from 'redux-mock-store'\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport {wrapIntl} from '../../testUtils'\nimport {TestBlockFactory} from '../../test/testBlockFactory'\nimport mutator from '../../mutator'\nimport propsRegistry from '../../properties'\nimport {PropertyType} from '../../properties/types'\n\nimport CardDetailProperties from './cardDetailProperties'\n\njest.mock('../../mutator')\nconst mockedMutator = mocked(mutator, true)\n\ndescribe('components/cardDetail/CardDetailProperties', () => {\n    const board = TestBlockFactory.createBoard()\n    board.cardProperties = [\n        {\n            id: 'property_id_1',\n            name: 'Owner',\n            type: 'select',\n            options: [\n                {\n                    color: 'propColorDefault',\n                    id: 'property_value_id_1',\n                    value: 'Jean-Luc Picard',\n                },\n                {\n                    color: 'propColorDefault',\n                    id: 'property_value_id_2',\n                    value: 'William Riker',\n                },\n                {\n                    color: 'propColorDefault',\n                    id: 'property_value_id_3',\n                    value: 'Deanna Troi',\n                },\n            ],\n        },\n        {\n            id: 'property_id_2',\n            name: 'MockStatus',\n            type: 'number',\n            options: [],\n        },\n    ]\n\n    const view = TestBlockFactory.createBoardView(board)\n    view.fields.sortOptions = []\n    view.fields.groupById = undefined\n    view.fields.hiddenOptionIds = []\n    const views = [view]\n\n    const card = TestBlockFactory.createCard(board)\n    card.fields.properties.property_id_1 = 'property_value_id_1'\n    card.fields.properties.property_id_2 = '1234'\n\n    const cardTemplate = TestBlockFactory.createCard(board)\n    cardTemplate.fields.isTemplate = true\n\n    const cards = [card]\n\n    const state = {\n        users: {\n            me: {\n                id: 'user_id_1',\n            },\n            myConfig: {\n                onboardingTourStarted: {value: true},\n                tourCategory: {value: 'card'},\n                onboardingTourStep: {value: '1'},\n            },\n        },\n        teams: {\n            current: {id: 'team-id'},\n        },\n        boards: {\n            boards: {\n                [board.id]: board,\n            },\n            current: board.id,\n            myBoardMemberships: {\n                [board.id]: {userId: 'user_id_1', schemeAdmin: true},\n            },\n        },\n        cards: {\n            cards: {\n                [card.id]: card,\n            },\n            current: card.id,\n        },\n        clientConfig: {\n            value: {},\n        },\n    }\n\n    const mockStore = configureStore([])\n    let store = mockStore(state)\n\n    beforeEach(() => {\n        store = mockStore(state)\n    })\n\n    function renderComponent() {\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <CardDetailProperties\n                    board={board!}\n                    card={card}\n                    cards={[card]}\n                    activeView={view}\n                    views={views}\n                    readonly={false}\n                />\n            </ReduxProvider>,\n        )\n\n        return render(component)\n    }\n\n    it('should match snapshot', async () => {\n        const {container} = renderComponent()\n        expect(container).toMatchSnapshot()\n    })\n\n    it('should show confirmation dialog when deleting existing select property', () => {\n        renderComponent()\n\n        const menuElement = screen.getByRole('button', {name: 'Owner'})\n        userEvent.click(menuElement)\n\n        const deleteButton = screen.getByRole('button', {name: /delete/i})\n        userEvent.click(deleteButton)\n\n        expect(screen.getByRole('heading', {name: 'Confirm delete property'})).toBeInTheDocument()\n        expect(screen.getByRole('button', {name: /delete/i})).toBeInTheDocument()\n    })\n\n    it('should show property types menu', () => {\n        const intl = createIntl({locale: 'en'})\n        const {container} = renderComponent()\n\n        const menuElement = screen.getByRole('button', {name: /add a property/i})\n        userEvent.click(menuElement)\n        expect(container).toMatchSnapshot()\n\n        const selectProperty = screen.getByText(/select property type/i)\n        expect(selectProperty).toBeInTheDocument()\n\n        propsRegistry.list().forEach((type: PropertyType) => {\n            const typeButton = screen.getByRole('button', {name: type.displayName(intl)})\n            expect(typeButton).toBeInTheDocument()\n        })\n    })\n\n    it('should allow change property types menu, confirm', () => {\n        renderComponent()\n\n        const menuElement = screen.getByRole('button', {name: 'Owner'})\n        userEvent.click(menuElement)\n\n        const typeProperty = screen.getByText(/Type: Select/i)\n        expect(typeProperty).toBeInTheDocument()\n\n        fireEvent.mouseOver(typeProperty)\n\n        const newTypeMenu = screen.getByRole('button', {name: 'Text'})\n        userEvent.click(newTypeMenu)\n\n        expect(screen.getByRole('heading', {name: 'Confirm property type change'})).toBeInTheDocument()\n        expect(screen.getByRole('button', {name: /Change property/i})).toBeInTheDocument()\n    })\n\n    test('rename select property and confirm button on dialog should rename property', async () => {\n        const result = renderComponent()\n\n        // rename to \"Owner-Renamed\"\n        onPropertyRenameNoConfirmationDialog(result.container)\n        const propertyTemplate = board.cardProperties[0]\n\n        // should be called once on confirming renaming the property\n        expect(mockedMutator.changePropertyTypeAndName).toBeCalledTimes(1)\n        expect(mockedMutator.changePropertyTypeAndName).toHaveBeenCalledWith(board, cards, propertyTemplate, 'select', 'Owner - Renamed')\n    })\n\n    it('should add new number property', async () => {\n        renderComponent()\n\n        const menuElement = screen.getByRole('button', {name: /add a property/i})\n        userEvent.click(menuElement)\n\n        await act(async () => {\n            const numberType = screen.getByRole('button', {name: /number/i})\n            userEvent.click(numberType)\n        })\n\n        expect(mockedMutator.insertPropertyTemplate).toHaveBeenCalledTimes(1)\n\n        const args = mockedMutator.insertPropertyTemplate.mock.calls[0]\n        const template = args[3]\n        expect(template).toBeTruthy()\n        expect(template!.name).toMatch(/number/i)\n        expect(template!.type).toBe('number')\n    })\n\n    it('confirmation on delete dialog should delete the property', () => {\n        const result = renderComponent()\n        const container = result.container\n\n        openDeleteConfirmationDialog(container)\n\n        const propertyTemplate = board.cardProperties[0]\n\n        const confirmButton = result.getByTitle('Delete')\n        expect(confirmButton).toBeDefined()\n\n        //click delete button\n        userEvent.click(confirmButton!)\n\n        // should be called once on confirming delete\n        expect(mockedMutator.deleteProperty).toBeCalledTimes(1)\n        expect(mockedMutator.deleteProperty).toBeCalledWith(board, views, cards, propertyTemplate.id)\n    })\n\n    it('cancel on delete dialog should do nothing', () => {\n        const result = renderComponent()\n        const container = result.container\n\n        openDeleteConfirmationDialog(container)\n\n        const cancelButton = result.getByTitle('Cancel')\n        expect(cancelButton).toBeDefined()\n\n        userEvent.click(cancelButton!)\n        expect(container).toMatchSnapshot()\n    })\n\n    function openDeleteConfirmationDialog(container: HTMLElement) {\n        const propertyLabel = container.querySelector('.MenuWrapper')\n        expect(propertyLabel).toBeDefined()\n        userEvent.click(propertyLabel!)\n\n        const deleteOption = container.querySelector('.MenuOption.TextOption')\n        expect(propertyLabel).toBeDefined()\n        userEvent.click(deleteOption!)\n\n        const confirmDialog = container.querySelector('.dialog.confirmation-dialog-box')\n        expect(confirmDialog).toBeDefined()\n    }\n\n    function onPropertyRenameNoConfirmationDialog(container: HTMLElement) {\n        const propertyLabel = container.querySelector('.MenuWrapper')\n        expect(propertyLabel).toBeDefined()\n        userEvent.click(propertyLabel!)\n\n        // write new name in the name text box\n        const propertyNameInput = container.querySelector('.PropertyMenu.menu-textbox')\n        expect(propertyNameInput).toBeDefined()\n        userEvent.type(propertyNameInput!, 'Owner - Renamed{enter}')\n        userEvent.click(propertyLabel!)\n    }\n})\n"
  },
  {
    "path": "webapp/src/components/cardDetail/cardDetailProperties.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useEffect, useState} from 'react'\nimport {FormattedMessage, useIntl} from 'react-intl'\n\nimport {Board, IPropertyTemplate} from '../../blocks/board'\nimport {Card} from '../../blocks/card'\nimport {BoardView} from '../../blocks/boardView'\n\nimport mutator from '../../mutator'\nimport Button from '../../widgets/buttons/button'\nimport MenuWrapper from '../../widgets/menuWrapper'\nimport PropertyMenu, {PropertyTypes} from '../../widgets/propertyMenu'\n\nimport Calculations from '../calculations/calculations'\nimport PropertyValueElement from '../propertyValueElement'\nimport ConfirmationDialogBox, {ConfirmationDialogBoxProps} from '../confirmationDialogBox'\nimport {sendFlashMessage} from '../flashMessages'\nimport Menu from '../../widgets/menu'\nimport {IDType, Utils} from '../../utils'\nimport AddPropertiesTourStep from '../onboardingTour/addProperties/add_properties'\nimport {Permission} from '../../constants'\nimport {useHasCurrentBoardPermissions} from '../../hooks/permissions'\nimport propRegistry from '../../properties'\nimport {PropertyType} from '../../properties/types'\n\ntype Props = {\n    board: Board\n    card: Card\n    cards: Card[]\n    activeView: BoardView\n    views: BoardView[]\n    readonly: boolean\n}\n\nconst CardDetailProperties = (props: Props) => {\n    const {board, card, cards, views, activeView} = props\n    const [newTemplateId, setNewTemplateId] = useState('')\n    const canEditBoardProperties = useHasCurrentBoardPermissions([Permission.ManageBoardProperties])\n    const canEditBoardCards = useHasCurrentBoardPermissions([Permission.ManageBoardCards])\n    const intl = useIntl()\n\n    useEffect(() => {\n        const newProperty = board.cardProperties.find((property) => property.id === newTemplateId)\n        if (newProperty) {\n            setNewTemplateId('')\n        }\n    }, [newTemplateId, board.cardProperties])\n\n    const [confirmationDialogBox, setConfirmationDialogBox] = useState<ConfirmationDialogBoxProps>({heading: '', onConfirm: () => {}, onClose: () => {}})\n    const [showConfirmationDialog, setShowConfirmationDialog] = useState<boolean>(false)\n\n    function onPropertyChangeSetAndOpenConfirmationDialog(newType: PropertyType, newName: string, propertyTemplate: IPropertyTemplate) {\n        const oldType = propRegistry.get(propertyTemplate.type)\n\n        // do nothing if no change\n        if (oldType === newType && propertyTemplate.name === newName) {\n            return\n        }\n\n        const affectsNumOfCards: string = Calculations.countNotEmpty(cards, propertyTemplate, intl)\n\n        // if only the name has changed, set the property without warning\n        if (affectsNumOfCards === '0' || oldType === newType) {\n            mutator.changePropertyTypeAndName(board, cards, propertyTemplate, newType.type, newName)\n            return\n        }\n\n        const subTextString = intl.formatMessage({\n            id: 'CardDetailProperty.property-name-change-subtext',\n            defaultMessage: 'type from \"{oldPropType}\" to \"{newPropType}\"',\n        }, {oldPropType: oldType.displayName(intl), newPropType: newType.displayName(intl)})\n\n        setConfirmationDialogBox({\n            heading: intl.formatMessage({id: 'CardDetailProperty.confirm-property-type-change', defaultMessage: 'Confirm property type change'}),\n            subText: intl.formatMessage({\n                id: 'CardDetailProperty.confirm-property-name-change-subtext',\n                defaultMessage: 'Are you sure you want to change property \"{propertyName}\" {customText}? This will affect value(s) across {numOfCards} card(s) in this board, and can result in data loss.',\n            },\n            {\n                propertyName: propertyTemplate.name,\n                customText: subTextString,\n                numOfCards: affectsNumOfCards,\n            }),\n\n            confirmButtonText: intl.formatMessage({id: 'CardDetailProperty.property-change-action-button', defaultMessage: 'Change property'}),\n            onConfirm: async () => {\n                setShowConfirmationDialog(false)\n                try {\n                    await mutator.changePropertyTypeAndName(board, cards, propertyTemplate, newType.type, newName)\n                } catch (err: any) {\n                    Utils.logError(`Error Changing Property And Name:${propertyTemplate.name}: ${err?.toString()}`)\n                }\n                sendFlashMessage({content: intl.formatMessage({id: 'CardDetailProperty.property-changed', defaultMessage: 'Changed property successfully!'}), severity: 'high'})\n            },\n            onClose: () => setShowConfirmationDialog(false),\n        })\n\n        // open confirmation dialog for property type change\n        setShowConfirmationDialog(true)\n    }\n\n    function onPropertyDeleteSetAndOpenConfirmationDialog(propertyTemplate: IPropertyTemplate) {\n        // set ConfirmationDialogBox Props\n        setConfirmationDialogBox({\n            heading: intl.formatMessage({id: 'CardDetailProperty.confirm-delete-heading', defaultMessage: 'Confirm delete property'}),\n            subText: intl.formatMessage({\n                id: 'CardDetailProperty.confirm-delete-subtext',\n                defaultMessage: 'Are you sure you want to delete the property \"{propertyName}\"? Deleting it will delete the property from all cards in this board.',\n            },\n            {propertyName: propertyTemplate.name}),\n            confirmButtonText: intl.formatMessage({id: 'CardDetailProperty.delete-action-button', defaultMessage: 'Delete'}),\n            onConfirm: async () => {\n                const deletingPropName = propertyTemplate.name\n                setShowConfirmationDialog(false)\n                try {\n                    await mutator.deleteProperty(board, views, cards, propertyTemplate.id)\n                    sendFlashMessage({content: intl.formatMessage({id: 'CardDetailProperty.property-deleted', defaultMessage: 'Deleted {propertyName} successfully!'}, {propertyName: deletingPropName}), severity: 'high'})\n                } catch (err: any) {\n                    Utils.logError(`Error Deleting Property!: Could Not delete Property -\" + ${deletingPropName} ${err?.toString()}`)\n                }\n            },\n\n            onClose: () => setShowConfirmationDialog(false),\n        })\n\n        // open confirmation dialog property delete\n        setShowConfirmationDialog(true)\n    }\n\n    return (\n        <div className='octo-propertylist CardDetailProperties'>\n            {board.cardProperties.map((propertyTemplate: IPropertyTemplate) => {\n                return (\n                    <div\n                        key={propertyTemplate.id + '-' + propertyTemplate.type}\n                        className='octo-propertyrow'\n                    >\n                        {(props.readonly || !canEditBoardProperties) && <div className='octo-propertyname octo-propertyname--readonly'>{propertyTemplate.name}</div>}\n                        {!props.readonly && canEditBoardProperties &&\n                            <MenuWrapper isOpen={propertyTemplate.id === newTemplateId}>\n                                <div className='octo-propertyname'><Button>{propertyTemplate.name}</Button></div>\n                                <PropertyMenu\n                                    propertyId={propertyTemplate.id}\n                                    propertyName={propertyTemplate.name}\n                                    propertyType={propRegistry.get(propertyTemplate.type)}\n                                    onTypeAndNameChanged={(newType: PropertyType, newName: string) => onPropertyChangeSetAndOpenConfirmationDialog(newType, newName, propertyTemplate)}\n                                    onDelete={() => onPropertyDeleteSetAndOpenConfirmationDialog(propertyTemplate)}\n                                />\n                            </MenuWrapper>\n                        }\n                        <PropertyValueElement\n                            readOnly={props.readonly || !canEditBoardCards}\n                            card={card}\n                            board={board}\n                            propertyTemplate={propertyTemplate}\n                            showEmptyPlaceholder={true}\n                        />\n                    </div>\n                )\n            })}\n\n            {showConfirmationDialog && (\n                <ConfirmationDialogBox\n                    dialogBox={confirmationDialogBox}\n                />\n            )}\n\n            {!props.readonly && canEditBoardProperties &&\n                <div className='octo-propertyname add-property'>\n                    <MenuWrapper>\n                        <Button>\n                            <FormattedMessage\n                                id='CardDetail.add-property'\n                                defaultMessage='+ Add a property'\n                            />\n                        </Button>\n                        <Menu>\n                            <PropertyTypes\n                                label={intl.formatMessage({id: 'PropertyMenu.selectType', defaultMessage: 'Select property type'})}\n                                onTypeSelected={async (type) => {\n                                    const template: IPropertyTemplate = {\n                                        id: Utils.createGuid(IDType.BlockID),\n                                        name: type.displayName(intl),\n                                        type: type.type,\n                                        options: [],\n                                    }\n                                    const templateId = await mutator.insertPropertyTemplate(board, activeView, -1, template)\n                                    setNewTemplateId(templateId)\n                                }}\n                            />\n                        </Menu>\n                    </MenuWrapper>\n\n                    <AddPropertiesTourStep/>\n                </div>\n            }\n        </div>\n    )\n}\n\nexport default React.memo(CardDetailProperties)\n"
  },
  {
    "path": "webapp/src/components/cardDetail/comment.scss",
    "content": ".Comment {\n    display: flex;\n    flex-direction: column;\n    margin: 5px 0;\n\n    &:hover {\n        .MenuWrapper {\n            display: block;\n        }\n    }\n\n    .MenuWrapper {\n        display: none;\n        position: absolute;\n        right: 0;\n    }\n\n    .comment-header {\n        align-items: center;\n        display: flex;\n        flex-direction: row;\n\n        position: relative;\n    }\n\n    .comment-avatar {\n        width: 20px;\n        height: 20px;\n        border-radius: 100%;\n        box-shadow: rgba(15, 15, 15, 0.1) 0 2px 4px;\n    }\n\n    .comment-username {\n        font-weight: bold;\n        margin: 0 6px 0 8px;\n    }\n\n    .comment-date {\n        color: #ccc;\n        font-size: 12px;\n    }\n\n    .comment-text {\n        color: rgb(var(--center-channel-color-rgb));\n        width: 100%;\n        padding-left: 32px;\n        overflow-wrap: anywhere;\n\n        a {\n            color: rgb(var(--link-color-rgb));\n\n            &:hover {\n                text-decoration: underline;\n            }\n        }\n\n        p {\n            &:first-child {\n                margin-top: 0;\n            }\n        }\n    }\n\n    .comment-text * {\n        user-select: text;\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/cardDetail/comment.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {render, screen} from '@testing-library/react'\nimport userEvent from '@testing-library/user-event'\nimport React from 'react'\nimport {Provider as ReduxProvider} from 'react-redux'\nimport moment from 'moment'\n\nimport {mocked} from 'jest-mock'\n\nimport {wrapIntl, mockStateStore} from '../../testUtils'\n\nimport {TestBlockFactory} from '../../test/testBlockFactory'\n\nimport mutator from '../../mutator'\n\nimport Comment from './comment'\n\njest.mock('../../mutator')\nconst mockedMutator = mocked(mutator, true)\n\nconst board = TestBlockFactory.createBoard()\nconst card = TestBlockFactory.createCard(board)\nconst comment = TestBlockFactory.createComment(card)\nconst dateFixed = Date.parse('01 Oct 2020')\ncomment.createAt = dateFixed\ncomment.updateAt = dateFixed\ncomment.title = 'Test comment'\n\nconst userImageUrl = 'data:image/svg+xml'\n\ndescribe('components/cardDetail/comment', () => {\n    const state = {\n        users: {\n            boardUsers: {[comment.modifiedBy]: {username: 'username_1'}},\n        },\n    }\n    const store = mockStateStore([], state)\n\n    beforeEach(() => {\n        jest.clearAllMocks()\n        moment.now = () => {\n            return dateFixed + (24 * 60 * 60 * 1000)\n        }\n    })\n\n    afterEach(() => {\n        moment.now = () => {\n            return Number(new Date())\n        }\n    })\n\n    test('return comment', () => {\n        const {container} = render(wrapIntl(\n            <ReduxProvider store={store}>\n                <Comment\n                    comment={comment}\n                    userId={comment.modifiedBy}\n                    userImageUrl={userImageUrl}\n                    readonly={false}\n                />\n            </ReduxProvider>,\n        ))\n        const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})\n        userEvent.click(buttonElement)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('return comment readonly', () => {\n        const {container} = render(wrapIntl(\n            <ReduxProvider store={store}>\n                <Comment\n                    comment={comment}\n                    userId={comment.modifiedBy}\n                    userImageUrl={userImageUrl}\n                    readonly={true}\n                />\n            </ReduxProvider>,\n        ))\n        expect(container).toMatchSnapshot()\n    })\n\n    test('return comment and delete comment', () => {\n        const {container} = render(wrapIntl(\n            <ReduxProvider store={store}>\n                <Comment\n                    comment={comment}\n                    userId={comment.modifiedBy}\n                    userImageUrl={userImageUrl}\n                    readonly={false}\n                />\n            </ReduxProvider>,\n        ))\n        const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})\n        userEvent.click(buttonElement)\n        expect(container).toMatchSnapshot()\n        const buttonDelete = screen.getByRole('button', {name: 'Delete'})\n        userEvent.click(buttonDelete)\n        expect(mockedMutator.deleteBlock).toBeCalledTimes(1)\n        expect(mockedMutator.deleteBlock).toBeCalledWith(comment)\n    })\n\n    test('return guest comment', () => {\n        const localStore = mockStateStore([], {users: {boardUsers: {[comment.modifiedBy]: {username: 'username_1', is_guest: true}}}})\n        const {container} = render(wrapIntl(\n            <ReduxProvider store={localStore}>\n                <Comment\n                    comment={comment}\n                    userId={comment.modifiedBy}\n                    userImageUrl={userImageUrl}\n                    readonly={false}\n                />\n            </ReduxProvider>,\n        ))\n        const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})\n        userEvent.click(buttonElement)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('return guest comment readonly', () => {\n        const localStore = mockStateStore([], {users: {boardUsers: {[comment.modifiedBy]: {username: 'username_1', is_guest: true}}}})\n        const {container} = render(wrapIntl(\n            <ReduxProvider store={localStore}>\n                <Comment\n                    comment={comment}\n                    userId={comment.modifiedBy}\n                    userImageUrl={userImageUrl}\n                    readonly={true}\n                />\n            </ReduxProvider>,\n        ))\n        expect(container).toMatchSnapshot()\n    })\n\n    test('return guest comment and delete comment', () => {\n        const localStore = mockStateStore([], {users: {boardUsers: {[comment.modifiedBy]: {username: 'username_1', is_guest: true}}}})\n        const {container} = render(wrapIntl(\n            <ReduxProvider store={localStore}>\n                <Comment\n                    comment={comment}\n                    userId={comment.modifiedBy}\n                    userImageUrl={userImageUrl}\n                    readonly={false}\n                />\n            </ReduxProvider>,\n        ))\n        const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})\n        userEvent.click(buttonElement)\n        expect(container).toMatchSnapshot()\n        const buttonDelete = screen.getByRole('button', {name: 'Delete'})\n        userEvent.click(buttonDelete)\n        expect(mockedMutator.deleteBlock).toBeCalledTimes(1)\n        expect(mockedMutator.deleteBlock).toBeCalledWith(comment)\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/cardDetail/comment.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {FC} from 'react'\nimport {useIntl} from 'react-intl'\n\nimport {Block} from '../../blocks/block'\nimport mutator from '../../mutator'\nimport {Utils} from '../../utils'\nimport IconButton from '../../widgets/buttons/iconButton'\nimport DeleteIcon from '../../widgets/icons/delete'\nimport OptionsIcon from '../../widgets/icons/options'\nimport Menu from '../../widgets/menu'\nimport MenuWrapper from '../../widgets/menuWrapper'\nimport {getUser} from '../../store/users'\nimport {useAppSelector} from '../../store/hooks'\nimport Tooltip from '../../widgets/tooltip'\nimport GuestBadge from '../../widgets/guestBadge'\n\nimport './comment.scss'\n\ntype Props = {\n    comment: Block\n    userId: string\n    userImageUrl: string\n    readonly: boolean\n}\n\nconst Comment: FC<Props> = (props: Props) => {\n    const {comment, userId, userImageUrl} = props\n    const intl = useIntl()\n    const html = Utils.htmlFromMarkdown(comment.title)\n    const user = useAppSelector(getUser(userId))\n    const date = new Date(comment.createAt)\n\n    return (\n        <div\n            key={comment.id}\n            className='Comment comment'\n        >\n            <div className='comment-header'>\n                <img\n                    className='comment-avatar'\n                    src={userImageUrl}\n                />\n                <div className='comment-username'>{user?.username}</div>\n                <GuestBadge show={user?.is_guest}/>\n\n                <Tooltip title={Utils.displayDateTime(date, intl)}>\n                    <div className='comment-date'>\n                        {Utils.relativeDisplayDateTime(date, intl)}\n                    </div>\n                </Tooltip>\n\n                {!props.readonly && (\n                    <MenuWrapper>\n                        <IconButton icon={<OptionsIcon/>}/>\n                        <Menu position='left'>\n                            <Menu.Text\n                                icon={<DeleteIcon/>}\n                                id='delete'\n                                name={intl.formatMessage({id: 'Comment.delete', defaultMessage: 'Delete'})}\n                                onClick={() => mutator.deleteBlock(comment)}\n                            />\n                        </Menu>\n                    </MenuWrapper>\n                )}\n            </div>\n            <div\n                className='comment-text'\n                dangerouslySetInnerHTML={{__html: html}}\n            />\n        </div>\n    )\n}\n\nexport default Comment\n"
  },
  {
    "path": "webapp/src/components/cardDetail/commentsList.scss",
    "content": ".CommentsList {\n    padding: 8px 0;\n    display: flex;\n    flex-direction: column;\n\n    width: 100%;\n\n    .comment-avatar {\n        width: 24px;\n        height: 24px;\n        border-radius: 100%;\n    }\n\n    .CommentsList__new {\n        position: relative;\n        display: flex;\n        flex-direction: row;\n        align-items: flex-start;\n        padding: 8px 0;\n        min-height: 48px;\n    }\n\n    .newcomment {\n        flex-grow: 1;\n        margin: 0 0 0 8px;\n        overflow: hidden;\n\n        + button {\n            margin: -4px 0 0 8px;\n        }\n    }\n}\n\n.CommentsList__divider {\n    margin-top: 8px;\n}\n"
  },
  {
    "path": "webapp/src/components/cardDetail/commentsList.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\nimport 'isomorphic-fetch'\n\nimport {render} from '@testing-library/react'\nimport {act} from 'react-dom/test-utils'\n\nimport {Provider as ReduxProvider} from 'react-redux'\nimport configureStore from 'redux-mock-store'\n\nimport {CommentBlock} from '../../blocks/commentBlock'\n\nimport {mockDOM, wrapIntl} from '../../testUtils'\nimport {Utils} from '../../utils'\n\nimport {FetchMock} from '../../test/fetchMock'\n\nimport CommentsList from './commentsList'\n\nglobal.fetch = FetchMock.fn\njest.spyOn(Utils, 'displayDateTime').mockReturnValue('a long time ago')\njest.spyOn(Utils, 'relativeDisplayDateTime').mockReturnValue('a long time ago')\n\nbeforeEach(() => {\n    FetchMock.fn.mockReset()\n})\n\nbeforeAll(() => {\n    mockDOM()\n})\n\ndescribe('components/cardDetail/CommentsList', () => {\n    const createdAt = Date.parse('01 Jan 2021 00:00:00 GMT')\n    const comment1: CommentBlock = {\n        id: 'comment_id_1',\n        title: 'Comment 1',\n        createAt: createdAt,\n        modifiedBy: 'user_id_1',\n    } as CommentBlock\n\n    const comment2: CommentBlock = {\n        id: 'comment_id_2',\n        title: 'Comment 2',\n        createAt: createdAt,\n        modifiedBy: 'user_id_2',\n    } as CommentBlock\n\n    test('comments show up', async () => {\n        const mockStore = configureStore([])\n        const store = mockStore({\n            users: {\n                boardUsers: {\n                    'user-id-1': {username: 'username_1'},\n                },\n            },\n            boards: {\n                boards: {\n                    board_id_1: {title: 'Board'},\n                },\n                current: 'board_id_1',\n                myBoardMemberships: {\n                    board_id_1: {userId: 'user_id_1', schemeAdmin: true},\n                },\n            },\n            cards: {\n                cards: {\n                    card_id_1: {title: 'Card'},\n                },\n                current: 'card_id_1',\n            },\n            clientConfig: {\n                value: {},\n            },\n            teams: {\n                current: {id: 'team_id_1'},\n            },\n        })\n\n        const component = (\n            <ReduxProvider store={store}>\n                {wrapIntl(\n                    <CommentsList\n                        comments={[comment1, comment2]}\n                        cardId={'card_id'}\n                        boardId={'board_id'}\n                        readonly={false}\n                    />,\n                )}\n            </ReduxProvider>)\n\n        let container: Element | DocumentFragment | null = null\n\n        await act(async () => {\n            const result = render(component)\n            container = result.container\n        })\n\n        expect(container).toBeDefined()\n        expect(container).toMatchSnapshot()\n\n        // Comments show up\n        const comments = container!.querySelectorAll('.comment-text')\n        expect(comments.length).toBe(2)\n\n        // Add comment option visible when readonly mode is off\n        const newCommentSection = container!.querySelectorAll('.newcomment')\n        expect(newCommentSection.length).toBe(1)\n    })\n\n    test('comments show up in readonly mode', async () => {\n        const mockStore = configureStore([])\n        const store = mockStore({\n            users: {\n                boardUsers: {\n                    'user-id-1': {username: 'username_1'},\n                },\n            },\n            boards: {\n                boards: {\n                    board_id_1: {title: 'Board'},\n                },\n                current: 'board_id_1',\n                myBoardMemberships: {\n                    board_id_1: {userId: 'user_id_1', schemeAdmin: true},\n                },\n            },\n            teams: {\n                current: {id: 'team_id_1'},\n            },\n        })\n\n        const component = (\n            <ReduxProvider store={store}>\n                {wrapIntl(\n                    <CommentsList\n                        comments={[comment1, comment2]}\n                        cardId={'card_id'}\n                        boardId={'board_id'}\n                        readonly={true}\n                    />,\n                )}\n            </ReduxProvider>)\n\n        let container: Element | DocumentFragment | null = null\n\n        await act(async () => {\n            const result = render(component)\n            container = result.container\n        })\n\n        expect(container).toBeDefined()\n        expect(container).toMatchSnapshot()\n\n        // Comments show up\n        const comments = container!.querySelectorAll('.comment-text')\n        expect(comments.length).toBe(2)\n\n        // Add comment option visible when readonly mode is off\n        const newCommentSection = container!.querySelectorAll('.newcomment')\n        expect(newCommentSection.length).toBe(0)\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/cardDetail/commentsList.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useState} from 'react'\nimport {FormattedMessage, useIntl} from 'react-intl'\n\nimport {CommentBlock, createCommentBlock} from '../../blocks/commentBlock'\nimport mutator from '../../mutator'\nimport {useAppSelector} from '../../store/hooks'\nimport {Utils} from '../../utils'\nimport Button from '../../widgets/buttons/button'\n\nimport {MarkdownEditor} from '../markdownEditor'\n\nimport {IUser} from '../../user'\nimport {getMe} from '../../store/users'\nimport {useHasCurrentBoardPermissions} from '../../hooks/permissions'\nimport {Permission} from '../../constants'\n\nimport AddCommentTourStep from '../onboardingTour/addComments/addComments'\n\nimport Comment from './comment'\n\nimport './commentsList.scss'\n\ntype Props = {\n    comments: readonly CommentBlock[]\n    boardId: string\n    cardId: string\n    readonly: boolean\n}\n\nconst CommentsList = (props: Props) => {\n    const [newComment, setNewComment] = useState('')\n    const me = useAppSelector<IUser|null>(getMe)\n    const canDeleteOthersComments = useHasCurrentBoardPermissions([Permission.DeleteOthersComments])\n\n    const onSendClicked = () => {\n        const commentText = newComment\n        if (commentText) {\n            const {cardId, boardId} = props\n            Utils.log(`Send comment: ${commentText}`)\n            Utils.assertValue(cardId)\n\n            const comment = createCommentBlock()\n            comment.parentId = cardId\n            comment.boardId = boardId\n            comment.title = commentText\n            mutator.insertBlock(boardId, comment, 'add comment')\n            setNewComment('')\n        }\n    }\n\n    const {comments} = props\n    const intl = useIntl()\n\n    const newCommentComponent = (\n        <div className='CommentsList__new'>\n            <img\n                className='comment-avatar'\n                src={Utils.getProfilePicture(me?.id)}\n            />\n            <MarkdownEditor\n                className='newcomment'\n                text={newComment}\n                placeholderText={intl.formatMessage({id: 'CardDetail.new-comment-placeholder', defaultMessage: 'Add a comment...'})}\n                onChange={(value: string) => {\n                    if (newComment !== value) {\n                        setNewComment(value)\n                    }\n                }}\n            />\n\n            {newComment &&\n            <Button\n                filled={true}\n                onClick={onSendClicked}\n            >\n                <FormattedMessage\n                    id='CommentsList.send'\n                    defaultMessage='Send'\n                />\n            </Button>\n            }\n\n            <AddCommentTourStep/>\n        </div>\n    )\n\n    return (\n        <div className='CommentsList'>\n            {/* New comment */}\n            {!props.readonly && newCommentComponent}\n\n            {comments.slice(0).reverse().map((comment) => {\n                // Only modify _own_ comments, EXCEPT for Admins, which can delete _any_ comment\n                // NOTE: editing comments will exist in the future (in addition to deleting)\n                const canDeleteComment: boolean = canDeleteOthersComments || me?.id === comment.modifiedBy\n                return (\n                    <Comment\n                        key={comment.id}\n                        comment={comment}\n                        userImageUrl={Utils.getProfilePicture(comment.modifiedBy)}\n                        userId={comment.modifiedBy}\n                        readonly={props.readonly || !canDeleteComment}\n                    />\n                )\n            })}\n\n            {/* horizontal divider below comments */}\n            {!(comments.length === 0 && props.readonly) && <hr className='CommentsList__divider'/>}\n        </div>\n    )\n}\n\nexport default React.memo(CommentsList)\n"
  },
  {
    "path": "webapp/src/components/cardDetail/imagePaste.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {useEffect, useCallback} from 'react'\nimport {useIntl} from 'react-intl'\n\nimport {ImageBlock, createImageBlock} from '../../blocks/imageBlock'\nimport {sendFlashMessage} from '../flashMessages'\nimport {Block} from '../../blocks/block'\nimport octoClient from '../../octoClient'\nimport mutator from '../../mutator'\n\nexport default function useImagePaste(boardId: string, cardId: string, contentOrder: Array<string | string[]>): void {\n    const intl = useIntl()\n    const uploadItems = useCallback(async (items: FileList) => {\n        let newImage: File|null = null\n        const uploads: Array<Promise<string|undefined>> = []\n\n        if (!items.length) {\n            return\n        }\n\n        for (const item of items) {\n            newImage = item\n            if (newImage?.type.indexOf('image/') === 0) {\n                uploads.push(octoClient.uploadFile(boardId, newImage))\n            }\n        }\n\n        const uploaded = await Promise.all(uploads)\n        const blocksToInsert: ImageBlock[] = []\n        let someFilesNotUploaded = false\n        for (const fileId of uploaded) {\n            if (!fileId) {\n                someFilesNotUploaded = true\n                continue\n            }\n            const block = createImageBlock()\n            block.parentId = cardId\n            block.boardId = boardId\n            block.fields.fileId = fileId || ''\n            blocksToInsert.push(block)\n        }\n\n        if (someFilesNotUploaded) {\n            sendFlashMessage({content: intl.formatMessage({id: 'imagePaste.upload-failed', defaultMessage: 'Some files not uploaded. File size limit reached'}), severity: 'normal'})\n        }\n\n        const afterRedo = async (newBlocks: Block[]) => {\n            const newContentOrder = JSON.parse(JSON.stringify(contentOrder))\n            newContentOrder.push(...newBlocks.map((b: Block) => b.id))\n            await octoClient.patchBlock(boardId, cardId, {updatedFields: {contentOrder: newContentOrder}})\n        }\n\n        const beforeUndo = async () => {\n            const newContentOrder = JSON.parse(JSON.stringify(contentOrder))\n            await octoClient.patchBlock(boardId, cardId, {updatedFields: {contentOrder: newContentOrder}})\n        }\n\n        await mutator.insertBlocks(boardId, blocksToInsert, 'pasted images', afterRedo, beforeUndo)\n    }, [cardId, contentOrder, boardId])\n\n    const onDrop = useCallback((event: DragEvent): void => {\n        if (event.dataTransfer) {\n            const items = event.dataTransfer.files\n            uploadItems(items)\n        }\n    }, [uploadItems])\n\n    const onPaste = useCallback((event: ClipboardEvent): void => {\n        if (event.clipboardData) {\n            const items = event.clipboardData.files\n            uploadItems(items)\n        }\n    }, [uploadItems])\n\n    useEffect(() => {\n        document.addEventListener('paste', onPaste)\n        document.addEventListener('drop', onDrop)\n        return () => {\n            document.removeEventListener('paste', onPaste)\n            document.removeEventListener('drop', onDrop)\n        }\n    }, [uploadItems, onPaste, onDrop])\n}\n"
  },
  {
    "path": "webapp/src/components/cardDialog.scss",
    "content": ".cardDialog {\n    .dialog {\n        width: 100%;\n        top: 0;\n        height: 100%;\n\n        @media (min-width: 975px) {\n            height: calc(100% - 144px);\n        }\n\n        >.CardDetail {\n            display: flex;\n            flex-direction: column;\n            align-items: flex-start;\n\n            @media not screen and (max-width: 975px) {\n                padding: 10px 126px;\n            }\n\n            @media screen and (max-width: 975px) {\n                padding: 16px 32px;\n            }\n        }\n\n        >.CardDetail--fullwidth {\n            padding-left: 78px;\n        }\n    }\n}\n\n.cardFollowBtn {\n    display: inline-flex;\n\n    &--attach {\n        margin-right: 8px;\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/cardDialog.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport '@testing-library/jest-dom'\nimport {act, render, screen} from '@testing-library/react'\nimport userEvent from '@testing-library/user-event'\n\nimport React from 'react'\nimport {Provider as ReduxProvider} from 'react-redux'\nimport {mocked} from 'jest-mock'\n\nimport mutator from '../mutator'\nimport {IUser} from '../user'\nimport {Utils} from '../utils'\nimport octoClient from '../octoClient'\nimport {TestBlockFactory} from '../test/testBlockFactory'\nimport {mockDOM, mockStateStore, wrapDNDIntl} from '../testUtils'\n\nimport CardDialog from './cardDialog'\n\njest.mock('../mutator')\njest.mock('../octoClient')\njest.mock('../utils')\njest.mock('draft-js/lib/generateRandomKey', () => () => '123')\n\nconst mockedUtils = mocked(Utils, true)\nconst mockedMutator = mocked(mutator, true)\nconst mockedOctoClient = mocked(octoClient, true)\nmockedUtils.createGuid.mockReturnValue('test-id')\n\nbeforeAll(() => {\n    mockDOM()\n})\ndescribe('components/cardDialog', () => {\n    const board = TestBlockFactory.createBoard()\n    board.cardProperties = []\n    board.id = 'test-id'\n    board.teamId = 'team-id'\n    const boardView = TestBlockFactory.createBoardView(board)\n    boardView.id = board.id\n    const card = TestBlockFactory.createCard(board)\n    card.id = board.id\n    card.createdBy = 'user-id-1'\n\n    const state = {\n        clientConfig: {\n            value: {},\n        },\n        comments: {\n            comments: {},\n            commentsByCard: {},\n        },\n        contents: {\n            contents: {},\n            contentsByCard: {},\n        },\n        cards: {\n            cards: {\n                [card.id]: card,\n            },\n            current: card.id,\n        },\n        teams: {\n            current: {id: 'team-id'},\n        },\n        boards: {\n            boards: {\n                [board.id]: board,\n            },\n            current: board.id,\n            myBoardMemberships: {\n                [board.id]: {userId: 'user_id_1', schemeAdmin: true},\n            },\n        },\n        users: {\n            boardUsers: {\n                1: {username: 'abc'},\n                2: {username: 'd'},\n                3: {username: 'e'},\n                4: {username: 'f'},\n                5: {username: 'g'},\n            },\n            blockSubscriptions: [],\n        },\n    }\n\n    mockedOctoClient.searchTeamUsers.mockResolvedValue(Object.values(state.users.boardUsers) as IUser[])\n    const store = mockStateStore([], state)\n    beforeEach(() => {\n        jest.clearAllMocks()\n    })\n    test('should match snapshot', async () => {\n        let container\n        await act(async () => {\n            const result = render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <CardDialog\n                        board={board}\n                        activeView={boardView}\n                        views={[boardView]}\n                        cards={[card]}\n                        cardId={card.id}\n                        onClose={jest.fn()}\n                        showCard={jest.fn()}\n                        readonly={false}\n                    />\n                </ReduxProvider>,\n            ))\n            container = result.container\n        })\n        expect(container).toMatchSnapshot()\n    })\n    test('should match snapshot without permissions', async () => {\n        let container\n        const localStore = mockStateStore([], {...state, teams: {current: undefined}})\n        await act(async () => {\n            const result = render(wrapDNDIntl(\n                <ReduxProvider store={localStore}>\n                    <CardDialog\n                        board={board}\n                        activeView={boardView}\n                        views={[boardView]}\n                        cards={[card]}\n                        cardId={card.id}\n                        onClose={jest.fn()}\n                        showCard={jest.fn()}\n                        readonly={false}\n                    />\n                </ReduxProvider>,\n            ))\n            container = result.container\n        })\n        expect(container).toMatchSnapshot()\n    })\n    test('return a cardDialog readonly', async () => {\n        let container\n        await act(async () => {\n            const result = render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <CardDialog\n                        board={board}\n                        activeView={boardView}\n                        views={[boardView]}\n                        cards={[card]}\n                        cardId={card.id}\n                        onClose={jest.fn()}\n                        showCard={jest.fn()}\n                        readonly={true}\n                    />\n                </ReduxProvider>,\n            ))\n            container = result.container\n        })\n        expect(container).toMatchSnapshot()\n    })\n    test('return cardDialog and do a close action', async () => {\n        const closeFn = jest.fn()\n        await act(async () => {\n            render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <CardDialog\n                        board={board}\n                        activeView={boardView}\n                        views={[boardView]}\n                        cards={[card]}\n                        cardId={card.id}\n                        onClose={closeFn}\n                        showCard={jest.fn()}\n                        readonly={false}\n                    />\n                </ReduxProvider>,\n            ))\n        })\n        const buttonElement = screen.getByRole('button', {name: 'Close dialog'})\n        userEvent.click(buttonElement)\n        expect(closeFn).toBeCalledTimes(1)\n    })\n    test('return cardDialog menu content', async () => {\n        let container\n        await act(async () => {\n            const result = render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <CardDialog\n                        board={board}\n                        activeView={boardView}\n                        views={[boardView]}\n                        cards={[card]}\n                        cardId={card.id}\n                        onClose={jest.fn()}\n                        showCard={jest.fn()}\n                        readonly={false}\n                    />\n                </ReduxProvider>,\n            ))\n            container = result.container\n        })\n        const buttonMenu = screen.getAllByRole('button', {name: 'menuwrapper'})[0]\n        userEvent.click(buttonMenu)\n        expect(container).toMatchSnapshot()\n    })\n    test('return cardDialog menu content and verify delete action', async () => {\n        await act(async () => {\n            render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <CardDialog\n                        board={board}\n                        activeView={boardView}\n                        views={[boardView]}\n                        cards={[card]}\n                        cardId={card.id}\n                        onClose={jest.fn()}\n                        showCard={jest.fn()}\n                        readonly={false}\n                    />\n                </ReduxProvider>,\n            ))\n        })\n        const buttonMenu = screen.getAllByRole('button', {name: 'menuwrapper'})[0]\n        userEvent.click(buttonMenu)\n        const buttonDelete = screen.getByRole('button', {name: 'Delete'})\n        userEvent.click(buttonDelete)\n\n        const confirmDialog = screen.getByTitle('Confirmation Dialog Box')\n        expect(confirmDialog).toBeDefined()\n\n        const confirmButton = screen.getByTitle('Delete')\n        expect(confirmButton).toBeDefined()\n\n        //click delete button\n        userEvent.click(confirmButton!)\n\n        // should be called once on confirming delete\n        expect(mockedMutator.deleteBlock).toBeCalledTimes(1)\n    })\n\n    test('return cardDialog menu content and cancel delete confirmation do nothing', async () => {\n        let container\n        await act(async () => {\n            const result = render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <CardDialog\n                        board={board}\n                        activeView={boardView}\n                        views={[boardView]}\n                        cards={[card]}\n                        cardId={card.id}\n                        onClose={jest.fn()}\n                        showCard={jest.fn()}\n                        readonly={false}\n                    />\n                </ReduxProvider>,\n            ))\n            container = result.container\n        })\n\n        const buttonMenu = screen.getAllByRole('button', {name: 'menuwrapper'})[0]\n        userEvent.click(buttonMenu)\n        const buttonDelete = screen.getByRole('button', {name: 'Delete'})\n        userEvent.click(buttonDelete)\n\n        const confirmDialog = screen.getByTitle('Confirmation Dialog Box')\n        expect(confirmDialog).toBeDefined()\n\n        const cancelButton = screen.getByTitle('Cancel')\n        expect(cancelButton).toBeDefined()\n\n        //click delete button\n        userEvent.click(cancelButton!)\n\n        // should do nothing  on cancel delete dialog\n        expect(container).toMatchSnapshot()\n    })\n\n    test('return cardDialog menu content and do a New template from card', async () => {\n        await act(async () => {\n            render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <CardDialog\n                        board={board}\n                        activeView={boardView}\n                        views={[boardView]}\n                        cards={[card]}\n                        cardId={card.id}\n                        onClose={jest.fn()}\n                        showCard={jest.fn()}\n                        readonly={false}\n                    />\n                </ReduxProvider>,\n            ))\n        })\n        const buttonMenu = screen.getAllByRole('button', {name: 'menuwrapper'})[0]\n        userEvent.click(buttonMenu)\n        const buttonTemplate = screen.getByRole('button', {name: 'New template from card'})\n        userEvent.click(buttonTemplate)\n        expect(mockedMutator.duplicateCard).toBeCalledTimes(1)\n    })\n\n    test('return cardDialog menu content and do a copy Link', async () => {\n        await act(async () => {\n            render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <CardDialog\n                        board={board}\n                        activeView={boardView}\n                        views={[boardView]}\n                        cards={[card]}\n                        cardId={card.id}\n                        onClose={jest.fn()}\n                        showCard={jest.fn()}\n                        readonly={false}\n                    />\n                </ReduxProvider>,\n            ))\n        })\n        const buttonMenu = screen.getAllByRole('button', {name: 'menuwrapper'})[0]\n        userEvent.click(buttonMenu)\n        const buttonCopy = screen.getByRole('button', {name: 'Copy link'})\n        userEvent.click(buttonCopy)\n        expect(mockedUtils.copyTextToClipboard).toBeCalledTimes(1)\n    })\n\n    test('already following card', async () => {\n        // simply doing {...state} gives a TypeScript error\n        // when you try updating it's values.\n        const newState = JSON.parse(JSON.stringify(state))\n        newState.users.blockSubscriptions = [{blockId: card.id}]\n        newState.clientConfig = {\n            value: {},\n        }\n\n        const newStore = mockStateStore([], newState)\n\n        let container\n        await act(async () => {\n            const result = render(wrapDNDIntl(\n                <ReduxProvider store={newStore}>\n                    <CardDialog\n                        board={board}\n                        activeView={boardView}\n                        views={[boardView]}\n                        cards={[card]}\n                        cardId={card.id}\n                        onClose={jest.fn()}\n                        showCard={jest.fn()}\n                        readonly={false}\n                    />\n                </ReduxProvider>,\n            ))\n            container = result.container\n        })\n        expect(container).toMatchSnapshot()\n    })\n\n    test('limited card shows hidden view (no toolbar)', async () => {\n        // simply doing {...state} gives a TypeScript error\n        // when you try updating it's values.\n        const newState = JSON.parse(JSON.stringify(state))\n        const limitedCard = {...card, limited: true}\n        newState.cards = {\n            cards: {\n                [limitedCard.id]: limitedCard,\n            },\n            current: limitedCard.id,\n        }\n\n        const newStore = mockStateStore([], newState)\n\n        let container\n        await act(async () => {\n            const result = render(wrapDNDIntl(\n                <ReduxProvider store={newStore}>\n                    <CardDialog\n                        board={board}\n                        activeView={boardView}\n                        views={[boardView]}\n                        cards={[limitedCard]}\n                        cardId={limitedCard.id}\n                        onClose={jest.fn()}\n                        showCard={jest.fn()}\n                        readonly={false}\n                    />\n                </ReduxProvider>,\n            ))\n            container = result.container\n        })\n        expect(container).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/cardDialog.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useCallback, useState} from 'react'\nimport {FormattedMessage, useIntl} from 'react-intl'\n\nimport {Board} from '../blocks/board'\nimport {BoardView} from '../blocks/boardView'\nimport {Card} from '../blocks/card'\nimport {sendFlashMessage} from '../components/flashMessages'\nimport mutator from '../mutator'\nimport octoClient from '../octoClient'\nimport {getCardAttachments, updateAttachments, updateUploadPrecent} from '../store/attachments'\nimport {getCard} from '../store/cards'\nimport {getCardComments} from '../store/comments'\nimport {getCardContents} from '../store/contents'\nimport {useAppDispatch, useAppSelector} from '../store/hooks'\nimport TelemetryClient, {TelemetryActions, TelemetryCategory} from '../telemetry/telemetryClient'\nimport {Utils} from '../utils'\nimport CompassIcon from '../widgets/icons/compassIcon'\nimport Menu from '../widgets/menu'\n\nimport ConfirmationDialogBox, {ConfirmationDialogBoxProps} from '../components/confirmationDialogBox'\n\nimport Button from '../widgets/buttons/button'\n\nimport {AttachmentBlock, createAttachmentBlock} from '../blocks/attachmentBlock'\nimport {Block, createBlock} from '../blocks/block'\nimport {Permission} from '../constants'\n\nimport BoardPermissionGate from './permissions/boardPermissionGate'\n\nimport CardDetail from './cardDetail/cardDetail'\nimport Dialog from './dialog'\n\nimport CardActionsMenu from './cardActionsMenu/cardActionsMenu'\nimport './cardDialog.scss'\n\ntype Props = {\n    board: Board\n    activeView: BoardView\n    views: BoardView[]\n    cards: Card[]\n    cardId: string\n    onClose: () => void\n    showCard: (cardId?: string) => void\n    readonly: boolean\n}\n\nconst CardDialog = (props: Props): JSX.Element => {\n    const {board, activeView, cards, views} = props\n    const card = useAppSelector(getCard(props.cardId))\n    const contents = useAppSelector(getCardContents(props.cardId))\n    const comments = useAppSelector(getCardComments(props.cardId))\n    const attachments = useAppSelector(getCardAttachments(props.cardId))\n    const intl = useIntl()\n    const dispatch = useAppDispatch()\n    const isTemplate = card && card.fields.isTemplate\n\n    const [showConfirmationDialogBox, setShowConfirmationDialogBox] = useState<boolean>(false)\n    const makeTemplateClicked = async () => {\n        if (!card) {\n            Utils.assertFailure('card')\n            return\n        }\n\n        TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.AddTemplateFromCard, {board: props.board.id, view: activeView.id, card: props.cardId})\n        await mutator.duplicateCard(\n            props.cardId,\n            board.id,\n            card.fields.isTemplate,\n            intl.formatMessage({id: 'Mutator.new-template-from-card', defaultMessage: 'new template from card'}),\n            true,\n            {},\n            async (newCardId) => {\n                props.showCard(newCardId)\n            },\n            async () => {\n                props.showCard(undefined)\n            },\n        )\n    }\n    const handleDeleteCard = async () => {\n        if (!card) {\n            Utils.assertFailure()\n            return\n        }\n        TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.DeleteCard, {board: props.board.id, view: props.activeView.id, card: card.id})\n        await mutator.deleteBlock(card, 'delete card')\n        props.onClose()\n    }\n\n    const confirmDialogProps: ConfirmationDialogBoxProps = {\n        heading: intl.formatMessage({id: 'CardDialog.delete-confirmation-dialog-heading', defaultMessage: 'Confirm card delete!'}),\n        confirmButtonText: intl.formatMessage({id: 'CardDialog.delete-confirmation-dialog-button-text', defaultMessage: 'Delete'}),\n        onConfirm: handleDeleteCard,\n        onClose: () => {\n            setShowConfirmationDialogBox(false)\n        },\n    }\n\n    const handleDeleteButtonOnClick = () => {\n        // use may be renaming a card title\n        // and accidently delete the card\n        // so adding des\n        if (card?.title === '' && card?.fields.contentOrder.length === 0) {\n            handleDeleteCard()\n            return\n        }\n\n        setShowConfirmationDialogBox(true)\n    }\n\n    const menu = (\n        <CardActionsMenu\n            cardId={props.cardId}\n            boardId={board.id}\n            onClickDelete={handleDeleteButtonOnClick}\n        >\n            {!isTemplate &&\n            <BoardPermissionGate permissions={[Permission.ManageBoardProperties]}>\n                <Menu.Text\n                    id='makeTemplate'\n                    icon={\n                        <CompassIcon\n                            icon='plus'\n                        />}\n                    name='New template from card'\n                    onClick={makeTemplateClicked}\n                />\n            </BoardPermissionGate>\n            }\n        </CardActionsMenu>\n    )\n\n    const removeUploadingAttachment = (uploadingBlock: Block) => {\n        uploadingBlock.deleteAt = 1\n        const removeUploadingAttachmentBlock = createAttachmentBlock(uploadingBlock)\n        dispatch(updateAttachments([removeUploadingAttachmentBlock]))\n    }\n\n    const selectAttachment = (boardId: string) => {\n        return new Promise<AttachmentBlock>(\n            (resolve) => {\n                Utils.selectLocalFile(async (attachment) => {\n                    const uploadingBlock = createBlock()\n                    uploadingBlock.title = attachment.name\n                    uploadingBlock.fields.fileId = attachment.name\n                    uploadingBlock.boardId = boardId\n                    if (card) {\n                        uploadingBlock.parentId = card.id\n                    }\n                    const attachmentBlock = createAttachmentBlock(uploadingBlock)\n                    attachmentBlock.isUploading = true\n                    dispatch(updateAttachments([attachmentBlock]))\n                    sendFlashMessage({content: intl.formatMessage({id: 'AttachmentBlock.upload', defaultMessage: 'Attachment uploading.'}), severity: 'normal'})\n                    const xhr = await octoClient.uploadAttachment(boardId, attachment)\n                    if (xhr) {\n                        xhr.upload.onprogress = (event) => {\n                            const percent = Math.floor((event.loaded / event.total) * 100)\n                            dispatch(updateUploadPrecent({\n                                blockId: attachmentBlock.id,\n                                uploadPercent: percent,\n                            }))\n                        }\n\n                        xhr.onload = () => {\n                            if (xhr.status === 200 && xhr.readyState === 4) {\n                                const json = JSON.parse(xhr.response)\n                                const fileId = json.fileId\n                                if (fileId) {\n                                    removeUploadingAttachment(uploadingBlock)\n                                    const block = createAttachmentBlock()\n                                    block.fields.fileId = fileId || ''\n                                    block.title = attachment.name\n                                    sendFlashMessage({content: intl.formatMessage({id: 'AttachmentBlock.uploadSuccess', defaultMessage: 'Attachment uploaded successfull.'}), severity: 'normal'})\n                                    resolve(block)\n                                } else {\n                                    removeUploadingAttachment(uploadingBlock)\n                                    sendFlashMessage({content: intl.formatMessage({id: 'AttachmentBlock.failed', defaultMessage: 'Unable to upload the file. Attachment size limit reached.'}), severity: 'normal'})\n                                }\n                            }\n                        }\n                    }\n                },\n                '')\n            },\n        )\n    }\n\n    const addElement = async () => {\n        if (!card) {\n            return\n        }\n        const block = await selectAttachment(board.id)\n        block.parentId = card.id\n        block.boardId = card.boardId\n        const typeName = block.type\n        const description = intl.formatMessage({id: 'AttachmentBlock.addElement', defaultMessage: 'add {type}'}, {type: typeName})\n        await mutator.insertBlock(block.boardId, block, description)\n    }\n\n    const deleteBlock = useCallback(async (block: Block) => {\n        if (!card) {\n            return\n        }\n        const description = intl.formatMessage({id: 'AttachmentBlock.DeleteAction', defaultMessage: 'delete'})\n        await mutator.deleteBlock(block, description)\n        sendFlashMessage({content: intl.formatMessage({id: 'AttachmentBlock.delete', defaultMessage: 'Attachment Deleted Successfully.'}), severity: 'normal'})\n    }, [card?.boardId, card?.id, card?.fields.contentOrder])\n\n    const attachBtn = (): React.ReactNode => {\n        return (\n            <BoardPermissionGate permissions={[Permission.ManageBoardCards]}>\n                <Button\n                    icon={<CompassIcon icon='paperclip'/>}\n                    className='cardFollowBtn cardFollowBtn--attach'\n                    emphasis='gray'\n                    size='medium'\n                    onClick={addElement}\n                >\n                    {intl.formatMessage({id: 'CardDetail.Attach', defaultMessage: 'Attach'})}\n                </Button>\n            </BoardPermissionGate>\n        )\n    }\n\n    return (\n        <>\n            <Dialog\n                title={<div/>}\n                className='cardDialog'\n                onClose={props.onClose}\n                toolsMenu={!props.readonly && !card?.limited && menu}\n                toolbar={attachBtn()}\n            >\n                {isTemplate &&\n                    <div className='banner'>\n                        <FormattedMessage\n                            id='CardDialog.editing-template'\n                            defaultMessage=\"You're editing a template.\"\n                        />\n                    </div>}\n\n                {card &&\n                    <CardDetail\n                        board={board}\n                        activeView={activeView}\n                        views={views}\n                        cards={cards}\n                        card={card}\n                        contents={contents}\n                        comments={comments}\n                        attachments={attachments}\n                        readonly={props.readonly}\n                        onClose={props.onClose}\n                        onDelete={deleteBlock}\n                        addAttachment={addElement}\n                    />}\n\n                {!card &&\n                    <div className='banner error'>\n                        <FormattedMessage\n                            id='CardDialog.nocard'\n                            defaultMessage=\"This card doesn't exist or is inaccessible.\"\n                        />\n                    </div>}\n            </Dialog>\n\n            {showConfirmationDialogBox && <ConfirmationDialogBox dialogBox={confirmDialogProps}/>}\n        </>\n    )\n}\n\nexport default CardDialog\n"
  },
  {
    "path": "webapp/src/components/cardLimitNotification.scss",
    "content": ".NotifyAdminSuccessNotify {\n    position: fixed;\n    bottom: 173px;\n    height: unset;\n\n    .NotificationBox__icon {\n        font-size: 24px;\n\n        svg {\n            stroke: var(--online-indicator);\n        }\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/cardLimitNotification.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useCallback, useEffect, useState} from 'react'\nimport {useIntl, FormattedMessage} from 'react-intl'\n\nimport AlertIcon from '../widgets/icons/alert'\n\nimport {useAppSelector, useAppDispatch} from '../store/hooks'\nimport {IUser, UserConfigPatch} from '../user'\nimport {getMe, patchProps, getCardLimitSnoozeUntil, getCardHiddenWarningSnoozeUntil} from '../store/users'\nimport {getCurrentBoardHiddenCardsCount, getCardHiddenWarning} from '../store/cards'\nimport TelemetryClient, {TelemetryActions, TelemetryCategory} from '../telemetry/telemetryClient'\nimport CheckIcon from '../widgets/icons/check'\nimport NotificationBox from '../widgets/notificationBox/notificationBox'\nimport octoClient from '../octoClient'\n\nimport './cardLimitNotification.scss'\n\ntype Props = {\n    showHiddenCardNotification: boolean\n    hiddenCardCountNotificationHandler: (show: boolean) => void\n}\n\nconst snoozeTime = 1000 * 60 * 60 * 24 * 10\nconst checkSnoozeInterval = 1000 * 60 * 5\n\nconst CardLimitNotification = (props: Props) => {\n    const intl = useIntl()\n    const [time, setTime] = useState(Date.now())\n    const [showNotifyAdminSuccess, setShowNotifyAdminSuccess] = useState<boolean>(false)\n\n    const hiddenCards = useAppSelector<number>(getCurrentBoardHiddenCardsCount)\n    const cardHiddenWarning = useAppSelector<boolean>(getCardHiddenWarning)\n    const me = useAppSelector<IUser|null>(getMe)\n    const snoozedUntil = useAppSelector<number>(getCardLimitSnoozeUntil)\n    const snoozedCardHiddenWarningUntil = useAppSelector<number>(getCardHiddenWarningSnoozeUntil)\n    const dispatch = useAppDispatch()\n\n    const onCloseHidden = useCallback(async () => {\n        if (me) {\n            const patch: UserConfigPatch = {\n                updatedFields: {\n                    cardLimitSnoozeUntil: `${Date.now() + snoozeTime}`,\n                },\n            }\n\n            const patchedProps = await octoClient.patchUserConfig(me.id, patch)\n            if (patchedProps) {\n                dispatch(patchProps(patchedProps))\n            }\n        }\n    }, [me])\n\n    const onCloseWarning = useCallback(async () => {\n        if (me) {\n            const patch: UserConfigPatch = {\n                updatedFields: {\n                    cardHiddenWarningSnoozeUntil: `${Date.now() + snoozeTime}`,\n                },\n            }\n\n            const patchedProps = await octoClient.patchUserConfig(me.id, patch)\n            if (patchedProps) {\n                dispatch(patchProps(patchedProps))\n            }\n        }\n    }, [me])\n\n    let show = false\n    let onClose = onCloseHidden\n    let title = intl.formatMessage(\n        {\n            id: 'notification-box-card-limit-reached.title',\n            defaultMessage: '{cards} cards hidden from board',\n        },\n        {cards: hiddenCards},\n    )\n\n    if (!show && props.showHiddenCardNotification) {\n        show = true\n    }\n\n    if (hiddenCards > 0 && time > snoozedUntil) {\n        show = true\n    }\n\n    if (!show && cardHiddenWarning) {\n        show = time > snoozedCardHiddenWarningUntil\n        onClose = onCloseWarning\n        title = intl.formatMessage(\n            {\n                id: 'notification-box-cards-hidden.title',\n                defaultMessage: 'This action has hidden another card',\n            },\n        )\n    }\n\n    useEffect(() => {\n        if (!show) {\n            const interval = setInterval(() => setTime(Date.now()), checkSnoozeInterval)\n            return () => {\n                clearInterval(interval)\n            }\n        }\n        return () => null\n    }, [show])\n\n    useEffect(() => {\n        if (show) {\n            TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.LimitCardLimitReached, {})\n        }\n    }, [show])\n\n    const handleContactAdminClicked = useCallback(async () => {\n        TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.LimitCardCTAPerformed)\n\n        await octoClient.notifyAdminUpgrade()\n        setShowNotifyAdminSuccess(true)\n    }, [me?.id])\n\n    const onClick = useCallback(() => {\n        (window as any).openPricingModal()({trackingLocation: 'boards > card_limit_notification_upgrade_to_a_paid_plan_click'})\n        TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.LimitCardLimitLinkOpen, {})\n    }, [])\n\n    const hasPermissionToUpgrade = me?.roles?.split(' ').indexOf('system_admin') !== -1\n\n    if (!show) {\n        return null\n    }\n\n    const hidHiddenCardNotification = () => {\n        show = false\n        props.hiddenCardCountNotificationHandler(false)\n    }\n\n    return (\n        <NotificationBox\n            icon={<AlertIcon/>}\n            title={title}\n            onClose={props.showHiddenCardNotification ? hidHiddenCardNotification : onClose}\n            closeTooltip={props.showHiddenCardNotification ? '' : intl.formatMessage({\n                id: 'notification-box-card-limit-reached.close-tooltip',\n                defaultMessage: 'Snooze for 10 days',\n            })}\n        >\n            {hasPermissionToUpgrade &&\n                <FormattedMessage\n                    id='notification-box.card-limit-reached.text'\n                    defaultMessage='Card limit reached, to view older cards, {link}'\n                    values={{\n                        link: (\n                            <a\n                                onClick={onClick}\n                            >\n                                <FormattedMessage\n                                    id='notification-box-card-limit-reached.link'\n                                    defaultMessage='Upgrade to a paid plan'\n                                />\n                            </a>),\n                    }}\n                />}\n            {!hasPermissionToUpgrade &&\n                <FormattedMessage\n                    id='notification-box.card-limit-reached.not-admin.text'\n                    defaultMessage='To access archived cards, you can {contactLink} to upgrade to a paid plan.'\n                    values={{\n                        contactLink: (\n                            <a\n                                onClick={handleContactAdminClicked}\n                            >\n                                <FormattedMessage\n                                    id='notification-box-card-limit-reached.contact-link'\n                                    defaultMessage='notify your admin'\n                                />\n                            </a>),\n                    }}\n                />}\n\n            {showNotifyAdminSuccess &&\n                <NotificationBox\n                    className='NotifyAdminSuccessNotify'\n                    icon={<CheckIcon/>}\n                    title={intl.formatMessage({id: 'ViewLimitDialog.notifyAdmin.Success', defaultMessage: 'Your admin has been notified'})}\n                    onClose={() => setShowNotifyAdminSuccess(false)}\n                />}\n        </NotificationBox>\n    )\n}\n\nexport default React.memo(CardLimitNotification)\n"
  },
  {
    "path": "webapp/src/components/centerPanel.scss",
    "content": "@import '../styles/z-index';\n\n.CenterPanel {\n    flex: 1 1 auto;\n    display: flex;\n    flex-direction: column;\n\n    .octo-board {\n        flex: 0 1 auto;\n\n        display: flex;\n        flex-direction: column;\n    }\n}\n\n\n.BoardComponent {\n    flex: 1 1 auto;\n    display: flex;\n    flex-direction: column;\n    overflow: hidden;\n\n    > * {\n        padding: 0 32px;\n\n        @media screen and (max-width: 768px) {\n            padding: 0 40px;\n        }\n\n        &:first-child {\n            padding-top: 8px;\n\n            @media screen and (max-width: 768px) {\n                padding: 0 8px;\n            }\n        }\n\n        &:last-child {\n            padding-bottom: 8px;\n        }\n    }\n\n    > .top-head {\n        @include z-index(center-panel);\n        position: sticky;\n        top: 0;\n        left: 0;\n        background: rgb(var(--center-channel-bg-rgb));\n\n        > .mid-head {\n            margin-top: 24px;\n            flex: 0 0 auto;\n            display: flex;\n            flex-direction: row;\n            justify-content: space-between;\n            align-items: center;\n        }\n\n        .ViewTitle {\n            flex: 1;\n            padding: 0 16px 0 0;\n            position: relative;\n            min-height: 40px;\n            justify-content: center;\n            display: flex;\n            flex-direction: column;\n        }\n    }\n\n\n    > div:nth-child(2) {\n        padding: 0 0 0 1px;\n        margin-left: 32px;\n\n        @media (max-width: 768px) {\n            margin-left: 0;\n            padding-left: 20px;\n            -webkit-overflow-scrolling: touch;\n        }\n    }\n\n    .shareButtonWrapper {\n        position: relative;\n        flex: 0 0 auto;\n    }\n\n    .NotificationBox {\n        .AlertIcon {\n            color: #ffbc1f;\n            font-size: 24px;\n        }\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/centerPanel.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {fireEvent, render, screen, within, act} from '@testing-library/react'\nimport userEvent from '@testing-library/user-event'\nimport React from 'react'\nimport {mocked} from 'jest-mock'\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport {mockDOM, mockStateStore, wrapDNDIntl} from '../testUtils'\nimport {TestBlockFactory} from '../test/testBlockFactory'\nimport {IPropertyTemplate} from '../blocks/board'\nimport {Utils} from '../utils'\nimport {IUser} from '../user'\nimport octoClient from '../octoClient'\nimport Mutator from '../mutator'\nimport {Constants} from '../constants'\n\nimport CenterPanel from './centerPanel'\nObject.defineProperty(Constants, 'versionString', {value: '1.0.0'})\njest.mock('react-router-dom', () => {\n    const originalModule = jest.requireActual('react-router-dom')\n\n    return {\n        ...originalModule,\n        useRouteMatch: jest.fn(() => {\n            return {url: '/board/view'}\n        }),\n    }\n})\njest.mock('../utils')\njest.mock('../octoClient')\njest.mock('../mutator')\njest.mock('../telemetry/telemetryClient')\njest.mock('draft-js/lib/generateRandomKey', () => () => '123')\nconst mockedUtils = mocked(Utils, true)\nconst mockedMutator = mocked(Mutator, true)\nconst mockedOctoClient = mocked(octoClient, true)\nmockedUtils.createGuid.mockReturnValue('test-id')\nmockedUtils.generateClassName = jest.requireActual('../utils').Utils.generateClassName\ndescribe('components/centerPanel', () => {\n    const board = TestBlockFactory.createBoard()\n    board.id = '1'\n    board.teamId = 'team-id'\n    const activeView = TestBlockFactory.createBoardView(board)\n    activeView.id = '1'\n    const card1 = TestBlockFactory.createCard(board)\n    card1.id = '1'\n    card1.title = 'card1'\n    card1.fields.isTemplate = true\n    card1.fields.properties = {id: 'property_value_id_1'}\n    const card2 = TestBlockFactory.createCard(board)\n    card2.id = '2'\n    card2.title = 'card2'\n    card2.fields.properties = {id: 'property_value_id_1'}\n    const comment1 = TestBlockFactory.createComment(card1)\n    comment1.id = '1'\n    const comment2 = TestBlockFactory.createComment(card2)\n    comment2.id = '2'\n    const groupProperty: IPropertyTemplate = {\n        id: 'id',\n        name: 'name',\n        type: 'text',\n        options: [\n            {\n                color: 'propColorOrange',\n                id: 'property_value_id_1',\n                value: 'Q1',\n            },\n            {\n                color: 'propColorBlue',\n                id: 'property_value_id_2',\n                value: 'Q2',\n            },\n        ],\n    }\n    const state = {\n        clientConfig: {\n            value: {},\n        },\n        searchText: '',\n        users: {\n            me: {\n                id: 'user_id_1',\n            },\n            myConfig: {\n                onboardingTourStarted: {value: false},\n            },\n            boardUsers: {\n                'user-id-1': {username: 'username_1'},\n            },\n            blockSubscriptions: [],\n        },\n        teams: {\n            current: {id: 'team-id'},\n        },\n        boards: {\n            current: board.id,\n            boards: {\n                [board.id]: board,\n            },\n            templates: [],\n            myBoardMemberships: {\n                [board.id]: {userId: 'user_id_1', schemeAdmin: true},\n            },\n        },\n        limits: {\n            limits: {\n                cards: 0,\n                used_cards: 0,\n                card_limit_timestamp: 0,\n                views: 0,\n            },\n        },\n        cards: {\n            templates: [card1, card2],\n            cards: [card1, card2],\n            current: card1.id,\n        },\n        views: {\n            views: {\n                boardView: activeView,\n            },\n            current: 'boardView',\n        },\n        contents: {\n            contents: [],\n            contentsByCard: {},\n        },\n        comments: {\n            comments: [comment1, comment2],\n            commentsByCard: {\n                [card1.id]: [comment1],\n                [card2.id]: [comment2],\n            },\n        },\n        imits: {\n            limits: {\n                views: 0,\n            },\n        },\n    }\n    mockedOctoClient.searchTeamUsers.mockResolvedValue(Object.values(state.users.boardUsers) as IUser[])\n    const store = mockStateStore([], state)\n    beforeAll(() => {\n        mockDOM()\n        console.error = jest.fn()\n    })\n    beforeEach(() => {\n        activeView.fields.viewType = 'board'\n        jest.clearAllMocks()\n    })\n    test('should match snapshot for Kanban, not shared', () => {\n        const {container} = render(wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <CenterPanel\n                    cards={[card1]}\n                    views={[activeView]}\n                    board={board}\n                    activeView={activeView}\n                    readonly={false}\n                    showCard={jest.fn()}\n                    groupByProperty={groupProperty}\n                    shownCardId={card1.id}\n                    hiddenCardsCount={0}\n                />\n            </ReduxProvider>,\n        ))\n        expect(container).toMatchSnapshot()\n    })\n    test('should match snapshot for Kanban', () => {\n        const {container} = render(wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <CenterPanel\n                    cards={[card1]}\n                    views={[activeView]}\n                    board={board}\n                    activeView={activeView}\n                    readonly={false}\n                    showCard={jest.fn()}\n                    groupByProperty={groupProperty}\n                    shownCardId={card1.id}\n                    hiddenCardsCount={0}\n                />\n            </ReduxProvider>,\n        ))\n        expect(container).toMatchSnapshot()\n    })\n    test('should match snapshot for Gallery', () => {\n        activeView.fields.viewType = 'gallery'\n        const {container} = render(wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <CenterPanel\n                    cards={[card1]}\n                    views={[activeView]}\n                    board={board}\n                    activeView={activeView}\n                    readonly={false}\n                    showCard={jest.fn()}\n                    groupByProperty={groupProperty}\n                    shownCardId={card1.id}\n                    hiddenCardsCount={0}\n                />\n            </ReduxProvider>,\n        ))\n        expect(container).toMatchSnapshot()\n    })\n    test('should match snapshot for Table', () => {\n        activeView.fields.viewType = 'table'\n        const {container} = render(wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <CenterPanel\n                    cards={[card1]}\n                    views={[activeView]}\n                    board={board}\n                    activeView={activeView}\n                    readonly={false}\n                    showCard={jest.fn()}\n                    groupByProperty={groupProperty}\n                    shownCardId={card1.id}\n                    hiddenCardsCount={0}\n                />\n            </ReduxProvider>,\n        ))\n        expect(container).toMatchSnapshot()\n    })\n    describe('return centerPanel and', () => {\n        test('select one card and click background', () => {\n            activeView.fields.viewType = 'table'\n            const {container} = render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <CenterPanel\n                        cards={[card1, card2]}\n                        views={[activeView]}\n                        board={board}\n                        activeView={activeView}\n                        readonly={false}\n                        showCard={jest.fn()}\n                        groupByProperty={groupProperty}\n                        shownCardId={card1.id}\n                        hiddenCardsCount={0}\n                    />\n                </ReduxProvider>,\n            ))\n\n            //select card\n            const cardElement = screen.getByRole('textbox', {name: 'card1'})\n            expect(cardElement).not.toBeNull()\n            userEvent.click(cardElement, {shiftKey: true})\n            expect(container).toMatchSnapshot()\n\n            //background\n            const boardElement = container.querySelector('.BoardComponent')\n            expect(boardElement).not.toBeNull()\n            userEvent.click(boardElement!)\n            expect(container).toMatchSnapshot()\n        })\n\n        test('press touch 1 with readonly', () => {\n            activeView.fields.viewType = 'table'\n            const {container, baseElement} = render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <CenterPanel\n                        cards={[card1, card2]}\n                        views={[activeView]}\n                        board={board}\n                        activeView={activeView}\n                        readonly={true}\n                        showCard={jest.fn()}\n                        groupByProperty={groupProperty}\n                        shownCardId={card1.id}\n                        hiddenCardsCount={0}\n                    />\n                </ReduxProvider>,\n            ))\n\n            //touch '1'\n            fireEvent.keyDown(baseElement, {keyCode: 49})\n            expect(container).toMatchSnapshot()\n        })\n\n        test('press touch esc for one card selected', () => {\n            activeView.fields.viewType = 'table'\n            const {container, baseElement} = render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <CenterPanel\n                        cards={[card1, card2]}\n                        views={[activeView]}\n                        board={board}\n                        activeView={activeView}\n                        readonly={false}\n                        showCard={jest.fn()}\n                        groupByProperty={groupProperty}\n                        shownCardId={card1.id}\n                        hiddenCardsCount={0}\n                    />\n                </ReduxProvider>,\n            ))\n\n            act(() => {\n                const cardElement = screen.getByRole('textbox', {name: 'card1'})\n                expect(cardElement.parentNode).not.toBeNull()\n                userEvent.click(cardElement as HTMLElement, {shiftKey: true})\n            })\n            expect(container).toMatchSnapshot()\n\n            //escape\n            fireEvent.keyDown(baseElement, {keyCode: 27})\n            expect(container).toMatchSnapshot()\n        })\n        test('press touch esc for two cards selected', async () => {\n            activeView.fields.viewType = 'table'\n            const {container, baseElement} = render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <CenterPanel\n                        cards={[card1, card2]}\n                        views={[activeView]}\n                        board={board}\n                        activeView={activeView}\n                        readonly={false}\n                        showCard={jest.fn()}\n                        groupByProperty={groupProperty}\n                        shownCardId={card1.id}\n                        hiddenCardsCount={0}\n                    />\n                </ReduxProvider>,\n            ))\n\n            act(() => {\n                //select card1\n                const card1Element = screen.getByRole('textbox', {name: 'card1'})\n                expect(card1Element).not.toBeNull()\n                userEvent.click(card1Element, {shiftKey: true})\n            })\n            expect(container).toMatchSnapshot()\n\n            act(() => {\n                //select card2\n                const card2Element = screen.getByRole('textbox', {name: 'card2'})\n                expect(card2Element).not.toBeNull()\n                userEvent.click(card2Element, {shiftKey: true, ctrlKey: true})\n            })\n            expect(container).toMatchSnapshot()\n\n            //escape\n            fireEvent.keyDown(baseElement, {keyCode: 27})\n            expect(container).toMatchSnapshot()\n        })\n        test('press touch del for one card selected', () => {\n            activeView.fields.viewType = 'table'\n            const {container, baseElement} = render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <CenterPanel\n                        cards={[card1, card2]}\n                        views={[activeView]}\n                        board={board}\n                        activeView={activeView}\n                        readonly={false}\n                        showCard={jest.fn()}\n                        groupByProperty={groupProperty}\n                        shownCardId={card1.id}\n                        hiddenCardsCount={0}\n                    />\n                </ReduxProvider>,\n            ))\n            act(() => {\n                const cardElement = screen.getByRole('textbox', {name: 'card1'})\n                expect(cardElement).not.toBeNull()\n                userEvent.click(cardElement, {shiftKey: true})\n            })\n            expect(container).toMatchSnapshot()\n\n            //delete\n            fireEvent.keyDown(baseElement, {keyCode: 8})\n            expect(mockedMutator.performAsUndoGroup).toBeCalledTimes(1)\n        })\n        test('press touch ctrl+d for one card selected', () => {\n            activeView.fields.viewType = 'table'\n            const {container, baseElement} = render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <CenterPanel\n                        cards={[card1, card2]}\n                        views={[activeView]}\n                        board={board}\n                        activeView={activeView}\n                        readonly={false}\n                        showCard={jest.fn()}\n                        groupByProperty={groupProperty}\n                        shownCardId={card1.id}\n                        hiddenCardsCount={0}\n                    />\n                </ReduxProvider>,\n            ))\n            act(() => {\n                const cardElement = screen.getByRole('textbox', {name: 'card1'})\n                expect(cardElement).not.toBeNull()\n                userEvent.click(cardElement, {shiftKey: true})\n            })\n            expect(container).toMatchSnapshot()\n\n            //ctrl+d\n            fireEvent.keyDown(baseElement, {ctrlKey: true, keyCode: 68})\n            expect(mockedMutator.performAsUndoGroup).toBeCalledTimes(1)\n        })\n        test('click on card to show card', () => {\n            activeView.fields.viewType = 'board'\n            const mockedShowCard = jest.fn()\n            const {container} = render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <CenterPanel\n                        cards={[card1, card2]}\n                        views={[activeView]}\n                        board={board}\n                        activeView={activeView}\n                        readonly={false}\n                        showCard={mockedShowCard}\n                        groupByProperty={groupProperty}\n                        shownCardId={card1.id}\n                        hiddenCardsCount={0}\n                    />\n                </ReduxProvider>,\n            ))\n\n            const kanbanCardElements = container.querySelectorAll('.KanbanCard')\n            expect(kanbanCardElements).not.toBeNull()\n            const kanbanCardElement = kanbanCardElements[0]\n            userEvent.click(kanbanCardElement)\n            expect(container).toMatchSnapshot()\n            expect(mockedShowCard).toBeCalledWith(card1.id)\n        })\n        test('click on new card to add card', () => {\n            activeView.fields.viewType = 'table'\n            const {container} = render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <CenterPanel\n                        cards={[card1, card2]}\n                        views={[activeView]}\n                        board={board}\n                        activeView={activeView}\n                        readonly={false}\n                        showCard={jest.fn()}\n                        groupByProperty={groupProperty}\n                        shownCardId={card1.id}\n                        hiddenCardsCount={0}\n                    />\n                </ReduxProvider>,\n            ))\n            const buttonWithMenuElement = container.querySelector('.ButtonWithMenu')\n            expect(buttonWithMenuElement).not.toBeNull()\n            userEvent.click(buttonWithMenuElement!)\n            expect(mockedMutator.performAsUndoGroup).toBeCalledTimes(1)\n        })\n        test('click on new card to add card template', () => {\n            activeView.fields.viewType = 'table'\n            const {container} = render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <CenterPanel\n                        cards={[card1, card2]}\n                        views={[activeView]}\n                        board={board}\n                        activeView={activeView}\n                        readonly={false}\n                        showCard={jest.fn()}\n                        groupByProperty={groupProperty}\n                        shownCardId={card1.id}\n                        hiddenCardsCount={0}\n                    />\n                </ReduxProvider>,\n            ))\n            const elementMenuWrapper = container.querySelector('.ButtonWithMenu > div.MenuWrapper')\n            expect(elementMenuWrapper).not.toBeNull()\n            userEvent.click(elementMenuWrapper!)\n            const buttonNewTemplate = within(elementMenuWrapper!.parentElement!).getByRole('button', {name: 'New template'})\n            userEvent.click(buttonNewTemplate)\n            expect(mockedMutator.insertBlock).toBeCalledTimes(1)\n        })\n\n        test('click on new card to add card from template', () => {\n            activeView.fields.viewType = 'table'\n            activeView.fields.defaultTemplateId = '1'\n            const {container} = render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <CenterPanel\n                        cards={[card1, card2]}\n                        views={[activeView]}\n                        board={board}\n                        activeView={activeView}\n                        readonly={false}\n                        showCard={jest.fn()}\n                        groupByProperty={groupProperty}\n                        shownCardId={card1.id}\n                        hiddenCardsCount={0}\n                    />\n                </ReduxProvider>,\n            ))\n            const elementMenuWrapper = container.querySelector('.ButtonWithMenu > div.MenuWrapper')\n            expect(elementMenuWrapper).not.toBeNull()\n            userEvent.click(elementMenuWrapper!)\n            const elementCard1 = within(elementMenuWrapper!.parentElement!).getByRole('button', {name: 'card1'})\n            expect(elementCard1).not.toBeNull()\n            userEvent.click(elementCard1)\n            expect(mockedMutator.performAsUndoGroup).toBeCalledTimes(1)\n        })\n\n        test('click on new card to edit template', () => {\n            activeView.fields.viewType = 'table'\n            activeView.fields.defaultTemplateId = '1'\n            const {container} = render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <CenterPanel\n                        cards={[card1, card2]}\n                        views={[activeView]}\n                        board={board}\n                        activeView={activeView}\n                        readonly={false}\n                        showCard={jest.fn()}\n                        groupByProperty={groupProperty}\n                        shownCardId={card1.id}\n                        hiddenCardsCount={0}\n                    />\n                </ReduxProvider>,\n            ))\n            const elementMenuWrapper = container.querySelector('.ButtonWithMenu > div.MenuWrapper')\n            expect(elementMenuWrapper).not.toBeNull()\n            userEvent.click(elementMenuWrapper!)\n            const elementCard1 = within(elementMenuWrapper!.parentElement!).getByRole('button', {name: 'card1'})\n            expect(elementCard1).not.toBeNull()\n            const elementMenuWrapperCard1 = within(elementCard1).getByRole('button', {name: 'menuwrapper'})\n            expect(elementMenuWrapperCard1).not.toBeNull()\n            userEvent.click(elementMenuWrapperCard1)\n            const elementEditMenuTemplate = within(elementMenuWrapperCard1).getByRole('button', {name: 'Edit'})\n            expect(elementMenuWrapperCard1).not.toBeNull()\n            userEvent.click(elementEditMenuTemplate)\n            expect(container).toMatchSnapshot()\n        })\n    })\n})\n\ndescribe('components/centerPanel', () => {\n    const board = TestBlockFactory.createBoard()\n    board.id = '1'\n    const activeView = TestBlockFactory.createBoardView(board)\n    activeView.id = '1'\n    const card1 = TestBlockFactory.createCard(board)\n    card1.id = '1'\n    card1.title = 'card1'\n    card1.fields.properties = {id: 'property_value_id_1'}\n    card1.limited = true\n    const card2 = TestBlockFactory.createCard(board)\n    card2.id = '2'\n    card2.title = 'card2'\n    card2.fields.properties = {id: 'property_value_id_1'}\n    card2.limited = true\n    const comment1 = TestBlockFactory.createComment(card1)\n    comment1.id = '1'\n    const comment2 = TestBlockFactory.createComment(card2)\n    comment2.id = '2'\n    const groupProperty: IPropertyTemplate = {\n        id: 'id',\n        name: 'name',\n        type: 'text',\n        options: [\n            {\n                color: 'propColorOrange',\n                id: 'property_value_id_1',\n                value: 'Q1',\n            },\n            {\n                color: 'propColorBlue',\n                id: 'property_value_id_2',\n                value: 'Q2',\n            },\n        ],\n    }\n    const state = {\n        clientConfig: {\n            value: {},\n        },\n        searchText: '',\n        users: {\n            me: {\n                id: 'user_id_1',\n            },\n            myConfig: {\n                onboardingTourStarted: {value: false},\n            },\n            workspaceUsers: [\n                {username: 'username_1'},\n            ],\n            boardUsers: [\n                {username: 'username_1'},\n            ],\n            blockSubscriptions: [],\n        },\n        teams: {\n            current: {id: 'team-id'},\n        },\n        boards: {\n            current: board.id,\n            boards: {\n                [board.id]: board,\n            },\n            templates: [],\n            myBoardMemberships: {\n                [board.id]: {userId: 'user_id_1', schemeAdmin: true},\n            },\n        },\n        cards: {\n            templates: [card1, card2],\n            cards: [card1, card2],\n            current: card1.id,\n        },\n        views: {\n            views: {\n                boardView: activeView,\n            },\n            current: 'boardView',\n        },\n        contents: {},\n        comments: {\n            comments: [comment1, comment2],\n        },\n        limits: {\n            limits: {\n                views: 0,\n            },\n        },\n    }\n    const store = mockStateStore([], state)\n    beforeAll(() => {\n        mockDOM()\n        console.error = jest.fn()\n    })\n    beforeEach(() => {\n        activeView.fields.viewType = 'board'\n        jest.clearAllMocks()\n    })\n\n    test('Clicking on the Hidden card count should open a dailog', () => {\n        activeView.fields.viewType = 'table'\n        activeView.fields.defaultTemplateId = '1'\n        const {container, getByTitle, getByText} = render(wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <CenterPanel\n                    cards={[card1, card2]}\n                    views={[activeView]}\n                    board={board}\n                    activeView={activeView}\n                    readonly={false}\n                    showCard={jest.fn()}\n                    groupByProperty={groupProperty}\n                    shownCardId={card1.id}\n                    hiddenCardsCount={2}\n                />\n            </ReduxProvider>,\n        ))\n        fireEvent.click(getByTitle('hidden-card-count'))\n        expect(getByText('2 cards hidden from board')).not.toBeNull()\n        expect(container).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/centerPanel.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n/* eslint-disable max-lines */\nimport React, {useState, useCallback, useEffect, useMemo} from 'react'\nimport {useIntl} from 'react-intl'\nimport {useHotkeys} from 'react-hotkeys-hook'\n\nimport {ClientConfig} from '../config/clientConfig'\n\nimport {Block} from '../blocks/block'\nimport {BlockIcons} from '../blockIcons'\nimport {Card, createCard} from '../blocks/card'\nimport {Board, IPropertyTemplate, BoardGroup} from '../blocks/board'\nimport {BoardView} from '../blocks/boardView'\nimport {CardFilter} from '../cardFilter'\nimport mutator from '../mutator'\nimport {Utils} from '../utils'\nimport {UserSettings} from '../userSettings'\nimport {getCurrentCard, addCard as addCardAction, addTemplate as addTemplateAction, showCardHiddenWarning} from '../store/cards'\nimport {getCardLimitTimestamp} from '../store/limits'\nimport {updateView} from '../store/views'\nimport {getVisibleAndHiddenGroups} from '../boardUtils'\nimport TelemetryClient, {TelemetryCategory, TelemetryActions} from '../../../webapp/src/telemetry/telemetryClient'\n\nimport {getClientConfig} from '../store/clientConfig'\n\nimport './centerPanel.scss'\n\nimport {useAppSelector, useAppDispatch} from '../store/hooks'\n\nimport {\n    getMe,\n    getBoardUsers,\n    getOnboardingTourCategory,\n    getOnboardingTourStarted,\n    getOnboardingTourStep,\n    patchProps,\n} from '../store/users'\n\nimport {UserConfigPatch} from '../user'\n\nimport octoClient from '../octoClient'\n\nimport ShareBoardButton from './shareBoard/shareBoardButton'\nimport ShareBoardLoginButton from './shareBoard/shareBoardLoginButton'\n\nimport CardDialog from './cardDialog'\nimport RootPortal from './rootPortal'\nimport TopBar from './topBar'\nimport ViewHeader from './viewHeader/viewHeader'\nimport ViewTitle from './viewTitle'\nimport Kanban from './kanban/kanban'\n\nimport Table from './table/table'\n\nimport CalendarFullView from './calendar/fullCalendar'\n\nimport CardLimitNotification from './cardLimitNotification'\n\nimport Gallery from './gallery/gallery'\nimport {BoardTourSteps, FINISHED, TOUR_BOARD, TOUR_CARD} from './onboardingTour'\nimport ShareBoardTourStep from './onboardingTour/shareBoard/shareBoard'\n\ntype Props = {\n    clientConfig?: ClientConfig\n    board: Board\n    cards: Card[]\n    activeView: BoardView\n    views: BoardView[]\n    groupByProperty?: IPropertyTemplate\n    dateDisplayProperty?: IPropertyTemplate\n    readonly: boolean\n    shownCardId?: string\n    showCard: (cardId?: string) => void\n    hiddenCardsCount: number\n}\n\nconst CenterPanel = (props: Props) => {\n    const intl = useIntl()\n    const [selectedCardIds, setSelectedCardIds] = useState<string[]>([])\n    const [cardIdToFocusOnRender, setCardIdToFocusOnRender] = useState('')\n    const [showHiddenCardCountNotification, setShowHiddenCardCountNotification] = useState(false)\n\n    const onboardingTourStarted = useAppSelector(getOnboardingTourStarted)\n    const onboardingTourCategory = useAppSelector(getOnboardingTourCategory)\n    const onboardingTourStep = useAppSelector(getOnboardingTourStep)\n    const cardLimitTimestamp = useAppSelector(getCardLimitTimestamp)\n    const me = useAppSelector(getMe)\n    const currentCard = useAppSelector(getCurrentCard)\n    const boardUsers = useAppSelector(getBoardUsers)\n    const dispatch = useAppDispatch()\n\n    const clientConfig = useAppSelector<ClientConfig>(getClientConfig)\n\n    // empty dependency array yields behavior like `componentDidMount`, it only runs _once_\n    // https://stackoverflow.com/a/58579462\n    useEffect(() => {\n        TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.ViewBoard, {board: props.board.id, view: props.activeView.id, viewType: props.activeView.fields.viewType})\n    }, [])\n\n    useHotkeys('esc', (e: KeyboardEvent) => {\n        if (e.target !== document.body || props.readonly) {\n            return\n        }\n        if (selectedCardIds.length > 0) {\n            setSelectedCardIds([])\n            e.stopPropagation()\n        }\n    }, [selectedCardIds, props.readonly])\n\n    useHotkeys('ctrl+d', (e: KeyboardEvent) => {\n        if (e.target !== document.body || props.readonly) {\n            return\n        }\n\n        if (selectedCardIds.length > 0) {\n            // CTRL+D: Duplicate selected cards\n            const {board} = props\n            if (selectedCardIds.length < 1) {\n                return\n            }\n\n            mutator.performAsUndoGroup(async () => {\n                for (const cardId of selectedCardIds) {\n                    const card = props.cards.find((o) => o.id === cardId)\n                    if (card) {\n                        mutator.duplicateCard(cardId, board.id)\n                    } else {\n                        Utils.assertFailure(`Selected card not found: ${cardId}`)\n                    }\n                }\n            })\n\n            setSelectedCardIds([])\n            e.stopPropagation()\n            e.preventDefault()\n        }\n    }, [selectedCardIds, props.readonly, props.cards, props.board.id])\n\n    useHotkeys('del,backspace', (e: KeyboardEvent) => {\n        if (e.target !== document.body || props.readonly) {\n            return\n        }\n\n        if (selectedCardIds.length > 0) {\n            // Backspace or Del: Delete selected cards\n            if (selectedCardIds.length < 1) {\n                return\n            }\n\n            mutator.performAsUndoGroup(async () => {\n                for (const cardId of selectedCardIds) {\n                    const card = props.cards.find((o) => o.id === cardId)\n                    if (card) {\n                        mutator.deleteBlock(card, selectedCardIds.length > 1 ? `delete ${selectedCardIds.length} cards` : 'delete card')\n                    } else {\n                        Utils.assertFailure(`Selected card not found: ${cardId}`)\n                    }\n                }\n            })\n\n            setSelectedCardIds([])\n            e.stopPropagation()\n        }\n    }, [selectedCardIds, props.readonly, props.cards])\n\n    const showCard = useCallback((cardId?: string) => {\n        if (selectedCardIds.length > 0) {\n            setSelectedCardIds([])\n        }\n        props.showCard(cardId)\n    }, [props.showCard, selectedCardIds])\n\n    const addCard = useCallback(async (groupByOptionId?: string, show = false, properties: Record<string, string> = {}): Promise<void> => {\n        const {activeView, board, groupByProperty} = props\n\n        const card = createCard()\n\n        TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.CreateCard, {board: board.id, view: activeView.id, card: card.id})\n\n        card.parentId = board.id\n        card.boardId = board.id\n        const propertiesThatMeetFilters = CardFilter.propertiesThatMeetFilterGroup(activeView.fields.filter, board.cardProperties)\n        if ((activeView.fields.viewType === 'board' || activeView.fields.viewType === 'table') && groupByProperty) {\n            if (groupByOptionId) {\n                propertiesThatMeetFilters[groupByProperty.id] = groupByOptionId\n            } else {\n                delete propertiesThatMeetFilters[groupByProperty.id]\n            }\n        }\n        card.fields.properties = {...card.fields.properties, ...properties, ...propertiesThatMeetFilters}\n        if (!card.fields.icon && UserSettings.prefillRandomIcons) {\n            card.fields.icon = BlockIcons.shared.randomIcon()\n        }\n        mutator.performAsUndoGroup(async () => {\n            const newCard = await mutator.insertBlock(\n                card.boardId,\n                card,\n                'add card',\n                async (block: Block) => {\n                    if (show) {\n                        dispatch(addCardAction(createCard(block)))\n                        dispatch(updateView({...activeView, fields: {...activeView.fields, cardOrder: [...activeView.fields.cardOrder, block.id]}}))\n                        showCard(block.id)\n                    } else {\n                        // Focus on this card's title inline on next render\n                        setCardIdToFocusOnRender(block.id)\n                        setTimeout(() => setCardIdToFocusOnRender(''), 300)\n                    }\n                },\n                async () => {\n                    showCard(undefined)\n                },\n            )\n            dispatch(showCardHiddenWarning(cardLimitTimestamp > 0))\n            await mutator.changeViewCardOrder(board.id, activeView.id, activeView.fields.cardOrder, [...activeView.fields.cardOrder, newCard.id], 'add-card')\n        })\n    }, [props.activeView, props.board.id, props.board.cardProperties, props.groupByProperty, showCard])\n\n    const addEmptyCardAndShow = useCallback(() => addCard('', true), [addCard])\n\n    const shouldStartBoardsTour = useCallback((): boolean => {\n        const isOnboardingBoard = props.board.title === 'Welcome to Boards!'\n        const isTourStarted = onboardingTourStarted\n        const completedCardsTour = onboardingTourCategory === TOUR_CARD && onboardingTourStep === FINISHED.toString()\n        const noCardOpen = !currentCard\n\n        return isOnboardingBoard && isTourStarted && completedCardsTour && noCardOpen\n    }, [currentCard, onboardingTourStarted, onboardingTourCategory, onboardingTourStep, props.board.title])\n\n    const prepareBoardsTour = useCallback(async () => {\n        if (!me?.id) {\n            return\n        }\n\n        const patch: UserConfigPatch = {\n            updatedFields: {\n                tourCategory: TOUR_BOARD,\n                onboardingTourStep: BoardTourSteps.ADD_VIEW.toString(),\n            },\n        }\n\n        const patchedProps = await octoClient.patchUserConfig(me.id, patch)\n        if (patchedProps) {\n            await dispatch(patchProps(patchedProps))\n        }\n    }, [me?.id])\n\n    const startBoardsTour = useCallback(async () => {\n        if (!shouldStartBoardsTour()) {\n            return\n        }\n\n        await prepareBoardsTour()\n    }, [prepareBoardsTour, shouldStartBoardsTour])\n\n    useEffect(() => {\n        startBoardsTour()\n    })\n\n    const backgroundClicked = useCallback((e: React.MouseEvent) => {\n        if (selectedCardIds.length > 0) {\n            setSelectedCardIds([])\n            e.stopPropagation()\n        }\n    }, [selectedCardIds])\n\n    const addCardFromTemplate = useCallback(async (cardTemplateId: string, groupByOptionId?: string) => {\n        const {activeView, board, groupByProperty} = props\n\n        const propertiesThatMeetFilters = CardFilter.propertiesThatMeetFilterGroup(activeView.fields.filter, board.cardProperties)\n        if ((activeView.fields.viewType === 'board' || activeView.fields.viewType === 'table') && groupByProperty) {\n            if (groupByOptionId) {\n                propertiesThatMeetFilters[groupByProperty.id] = groupByOptionId\n            } else {\n                delete propertiesThatMeetFilters[groupByProperty.id]\n            }\n        }\n\n        mutator.performAsUndoGroup(async () => {\n            const [, newCardId] = await mutator.duplicateCard(\n                cardTemplateId,\n                board.id,\n                true,\n                intl.formatMessage({id: 'Mutator.new-card-from-template', defaultMessage: 'new card from template'}),\n                false,\n                propertiesThatMeetFilters,\n                async (cardId) => {\n                    dispatch(updateView({...activeView, fields: {...activeView.fields, cardOrder: [...activeView.fields.cardOrder, cardId]}}))\n                    TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.CreateCardViaTemplate, {board: props.board.id, view: props.activeView.id, card: cardId, cardTemplateId})\n                    showCard(cardId)\n                },\n                async () => {\n                    showCard(undefined)\n                },\n            )\n            await mutator.changeViewCardOrder(props.board.id, activeView.id, activeView.fields.cardOrder, [...activeView.fields.cardOrder, newCardId], 'add-card')\n        })\n    }, [props.board, props.activeView, showCard])\n\n    const addCardTemplate = useCallback(async () => {\n        const {board, activeView} = props\n\n        const cardTemplate = createCard()\n        cardTemplate.fields.isTemplate = true\n        cardTemplate.parentId = board.id\n        cardTemplate.boardId = board.id\n\n        await mutator.insertBlock(\n            cardTemplate.boardId,\n            cardTemplate,\n            'add card template',\n            async (newBlock: Block) => {\n                const newTemplate = createCard(newBlock)\n                TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.CreateCardTemplate, {board: board.id, view: activeView.id, card: newTemplate.id})\n                dispatch(addTemplateAction(newTemplate))\n                showCard(newTemplate.id)\n            }, async () => {\n                showCard(undefined)\n            },\n        )\n    }, [props.board, props.activeView, showCard])\n\n    const editCardTemplate = useCallback((cardTemplateId: string) => {\n        showCard(cardTemplateId)\n    }, [showCard])\n\n    const cardClicked = useCallback((e: React.MouseEvent, card: Card): void => {\n        const {activeView, cards} = props\n\n        if (e.shiftKey) {\n            let newSelectedCardIds = [...selectedCardIds]\n            if (newSelectedCardIds.length > 0 && (e.metaKey || e.ctrlKey)) {\n                // Cmd+Shift+Click: Extend the selection\n                const orderedCardIds = cards.map((o) => o.id)\n                const lastCardId = newSelectedCardIds[newSelectedCardIds.length - 1]\n                const srcIndex = orderedCardIds.indexOf(lastCardId)\n                const destIndex = orderedCardIds.indexOf(card.id)\n                const newCardIds = (srcIndex < destIndex) ? orderedCardIds.slice(srcIndex, destIndex + 1) : orderedCardIds.slice(destIndex, srcIndex + 1)\n                for (const newCardId of newCardIds) {\n                    if (!newSelectedCardIds.includes(newCardId)) {\n                        newSelectedCardIds.push(newCardId)\n                    }\n                }\n                setSelectedCardIds(newSelectedCardIds)\n            } else {\n                // Shift+Click: add to selection\n                if (newSelectedCardIds.includes(card.id)) {\n                    newSelectedCardIds = selectedCardIds.filter((o) => o !== card.id)\n                } else {\n                    newSelectedCardIds.push(card.id)\n                }\n                setSelectedCardIds(newSelectedCardIds)\n            }\n        } else if (activeView.fields.viewType === 'board' || activeView.fields.viewType === 'gallery') {\n            showCard(card.id)\n        }\n\n        e.stopPropagation()\n    }, [selectedCardIds, props.activeView, props.cards, showCard])\n\n    const hiddenCardCountNotifyHandler = useCallback((show: boolean) => {\n        setShowHiddenCardCountNotification(show)\n    }, [showHiddenCardCountNotification])\n\n    const showShareButton = !props.readonly && me?.id !== 'single-user'\n    const showShareLoginButton = props.readonly && me?.id !== 'single-user'\n\n    const {groupByProperty, activeView, board, views, cards} = props\n\n    const getUserDisplayName = (boardGroup: BoardGroup) => {\n        const user = boardUsers[boardGroup.option.id]\n        if (user) {\n            return Utils.getUserDisplayName(user, clientConfig.teammateNameDisplay)\n        } else if (boardGroup.option.id === 'undefined') {\n            return intl.formatMessage({\n                id: 'centerPanel.undefined',\n                defaultMessage: 'No {propertyName}',\n            }, {propertyName: groupByProperty?.name})\n        }\n        return intl.formatMessage({id: 'centerPanel.unknown-user', defaultMessage: 'Unknown user'})\n    }\n\n    const {visible: visibleGroups, hidden: hiddenGroups} = useMemo(() => {\n        const {visible: vg, hidden: hg} = getVisibleAndHiddenGroups(cards, activeView.fields.visibleOptionIds, activeView.fields.hiddenOptionIds, groupByProperty)\n        if (groupByProperty?.type === 'createdBy' || groupByProperty?.type === 'updatedBy' || groupByProperty?.type === 'person') {\n            if (boardUsers) {\n                vg.forEach((value) => {\n                    value.option.value = getUserDisplayName(value)\n                })\n                hg.forEach((value) => {\n                    value.option.value = getUserDisplayName(value)\n                })\n            }\n        }\n        return {visible: vg, hidden: hg}\n    }, [cards, activeView.fields.visibleOptionIds, activeView.fields.hiddenOptionIds, groupByProperty, boardUsers])\n\n    return (\n        <div\n            className='BoardComponent'\n            onClick={backgroundClicked}\n        >\n            {props.shownCardId &&\n                <RootPortal>\n                    <CardDialog\n                        board={board}\n                        activeView={activeView}\n                        views={views}\n                        cards={cards}\n                        key={props.shownCardId}\n                        cardId={props.shownCardId}\n                        onClose={() => showCard(undefined)}\n                        showCard={(cardId) => showCard(cardId)}\n                        readonly={props.readonly}\n                    />\n                </RootPortal>}\n\n            <div className='top-head'>\n                <TopBar/>\n                <div className='mid-head'>\n                    <ViewTitle\n                        key={board.id + board.title}\n                        board={board}\n                        readonly={props.readonly}\n                    />\n                    <div className='shareButtonWrapper'>\n                        {showShareButton &&\n                        <ShareBoardButton\n                            enableSharedBoards={props.clientConfig?.enablePublicSharedBoards || false}\n                        />\n                        }\n                        {showShareLoginButton &&\n                            <ShareBoardLoginButton/>\n                        }\n                        <ShareBoardTourStep/>\n                    </div>\n                </div>\n                <ViewHeader\n                    board={props.board}\n                    activeView={props.activeView}\n                    cards={props.cards}\n                    views={props.views}\n                    groupByProperty={props.groupByProperty}\n                    dateDisplayProperty={props.dateDisplayProperty}\n                    addCard={addEmptyCardAndShow}\n                    addCardFromTemplate={addCardFromTemplate}\n                    addCardTemplate={addCardTemplate}\n                    editCardTemplate={editCardTemplate}\n                    readonly={props.readonly}\n                />\n            </div>\n\n            {activeView.fields.viewType === 'board' &&\n            <Kanban\n                board={props.board}\n                activeView={props.activeView}\n                cards={props.cards}\n                groupByProperty={props.groupByProperty}\n                visibleGroups={visibleGroups}\n                hiddenGroups={hiddenGroups}\n                selectedCardIds={selectedCardIds}\n                readonly={props.readonly}\n                onCardClicked={cardClicked}\n                addCard={addCard}\n                addCardFromTemplate={addCardFromTemplate}\n                showCard={showCard}\n                hiddenCardsCount={props.hiddenCardsCount}\n                showHiddenCardCountNotification={hiddenCardCountNotifyHandler}\n            />}\n            {activeView.fields.viewType === 'table' &&\n                <Table\n                    board={props.board}\n                    activeView={props.activeView}\n                    cards={props.cards}\n                    groupByProperty={props.groupByProperty}\n                    views={props.views}\n                    visibleGroups={visibleGroups}\n                    selectedCardIds={selectedCardIds}\n                    readonly={props.readonly}\n                    cardIdToFocusOnRender={cardIdToFocusOnRender}\n                    showCard={showCard}\n                    addCard={addCard}\n                    onCardClicked={cardClicked}\n                    hiddenCardsCount={props.hiddenCardsCount}\n                    showHiddenCardCountNotification={hiddenCardCountNotifyHandler}\n                />}\n            {activeView.fields.viewType === 'calendar' &&\n                <CalendarFullView\n                    board={props.board}\n                    cards={props.cards}\n                    activeView={props.activeView}\n                    readonly={props.readonly}\n                    dateDisplayProperty={props.dateDisplayProperty}\n                    showCard={showCard}\n                    addCard={(properties: Record<string, string>) => {\n                        addCard('', true, properties)\n                    }}\n                />}\n\n            {activeView.fields.viewType === 'gallery' &&\n                <Gallery\n                    board={props.board}\n                    cards={props.cards}\n                    activeView={props.activeView}\n                    readonly={props.readonly}\n                    onCardClicked={cardClicked}\n                    selectedCardIds={selectedCardIds}\n                    addCard={(show) => addCard('', show)}\n                    hiddenCardsCount={props.hiddenCardsCount}\n                    showHiddenCardCountNotification={hiddenCardCountNotifyHandler}\n                />}\n            <CardLimitNotification\n                showHiddenCardNotification={showHiddenCardCountNotification}\n                hiddenCardCountNotificationHandler={hiddenCardCountNotifyHandler}\n            />\n        </div>\n    )\n}\n\nexport default React.memo(CenterPanel)\n"
  },
  {
    "path": "webapp/src/components/confirmAddUserForNotifications.scss",
    "content": "@import '../styles/z-index';\n\n.ConfirmAddUserForNotifications {\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n\n    .select {\n        text-align: left;\n        width: 250px;\n        margin-top: 10px;\n    }\n\n    .permissions-title {\n        width: 250px;\n        position: relative;\n        top: -3px;\n        left: 6px;\n        height: 0;\n        text-align: left;\n        font-size: 12px;\n\n        label {\n            @include z-index(modal-permissions-label);\n            position: absolute;\n            padding: 0 5px;\n            margin: 0;\n            background-color: rgb(var(--center-channel-bg-rgb));\n            color: rgba(var(--center-channel-color-rgb), 0.8);\n        }\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/confirmAddUserForNotifications.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport '@testing-library/jest-dom'\nimport {render} from '@testing-library/react'\nimport userEvent from '@testing-library/user-event'\nimport React from 'react'\n\nimport {MemberRole} from '../blocks/board'\n\nimport {wrapDNDIntl} from '../testUtils'\nimport {IUser} from '../user'\n\nimport ConfirmAddUserForNotifications from './confirmAddUserForNotifications'\n\ndescribe('/components/confirmAddUserForNotifications', () => {\n    it('should match snapshot', async () => {\n        const result = render(\n            wrapDNDIntl(\n                <ConfirmAddUserForNotifications\n                    allowManageBoardRoles={true}\n                    minimumRole={MemberRole.Editor}\n                    user={{id: 'fake-user-id', username: 'fake-username'} as IUser}\n                    onConfirm={jest.fn()}\n                    onClose={jest.fn()}\n                />,\n            ),\n        )\n        expect(result.container).toMatchSnapshot()\n    })\n\n    it('confirm button click, run onConfirm Function once', () => {\n        const onConfirm = jest.fn()\n\n        const result = render(\n            wrapDNDIntl(\n                <ConfirmAddUserForNotifications\n                    allowManageBoardRoles={true}\n                    minimumRole={MemberRole.Editor}\n                    user={{id: 'fake-user-id', username: 'fake-username'} as IUser}\n                    onConfirm={onConfirm}\n                    onClose={jest.fn()}\n                />,\n            ),\n        )\n        userEvent.click(result.getByTitle('Add to board'))\n        expect(onConfirm).toBeCalledTimes(1)\n    })\n\n    it('cancel button click runs onClose function', () => {\n        const onClose = jest.fn()\n\n        const result = render(\n            wrapDNDIntl(\n                <ConfirmAddUserForNotifications\n                    allowManageBoardRoles={true}\n                    minimumRole={MemberRole.Editor}\n                    user={{id: 'fake-user-id', username: 'fake-username'} as IUser}\n                    onConfirm={jest.fn()}\n                    onClose={onClose}\n                />,\n            ),\n        )\n        userEvent.click(result.getByTitle('Cancel'))\n        expect(onClose).toBeCalledTimes(1)\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/confirmAddUserForNotifications.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React, {useState, useRef} from 'react'\nimport Select from 'react-select'\nimport {useIntl, FormattedMessage} from 'react-intl'\n\nimport {MemberRole} from '../blocks/board'\n\nimport {IUser} from '../user'\n\nimport ConfirmationDialog from './confirmationDialogBox'\n\nimport './confirmAddUserForNotifications.scss'\n\ntype Props = {\n    user: IUser\n    minimumRole: MemberRole\n    allowManageBoardRoles: boolean\n    onConfirm: (userId: string, role: string) => void\n    onClose: () => void\n}\n\nconst ConfirmAddUserForNotifications = (props: Props): JSX.Element => {\n    const {user, allowManageBoardRoles} = props\n    const [newUserRole, setNewUserRole] = useState<MemberRole>(props.minimumRole || MemberRole.Viewer)\n    const userRole = useRef<string>(newUserRole)\n\n    const intl = useIntl()\n\n    // if allowed to manage board roles, only display roles higher than minimum\n    const roleOptions = []\n    if (allowManageBoardRoles) {\n        if (props.minimumRole === MemberRole.Viewer || props.minimumRole === MemberRole.None) {\n            roleOptions.push(\n                {id: MemberRole.Viewer, label: intl.formatMessage({id: 'BoardMember.schemeViewer', defaultMessage: 'Viewer'})},\n            )\n        }\n        if (props.minimumRole === MemberRole.Viewer || props.minimumRole === MemberRole.None || props.minimumRole === MemberRole.Commenter) {\n            roleOptions.push(\n                {id: MemberRole.Commenter, label: intl.formatMessage({id: 'BoardMember.schemeCommenter', defaultMessage: 'Commenter'})},\n            )\n        }\n        roleOptions.push(\n            {id: MemberRole.Editor, label: intl.formatMessage({id: 'BoardMember.schemeEditor', defaultMessage: 'Editor'})},\n        )\n        if (!user.is_guest) {\n            roleOptions.push(\n                {id: MemberRole.Admin, label: intl.formatMessage({id: 'BoardMember.schemeAdmin', defaultMessage: 'Admin'})},\n            )\n        }\n    }\n\n    // if not admin, (ie. Editor/Commentor on Public board)\n    // set to minimum board role, only option, read only.\n    if (!allowManageBoardRoles) {\n        if (props.minimumRole === MemberRole.Viewer || props.minimumRole === MemberRole.None) {\n            roleOptions.push(\n                {id: MemberRole.Viewer, label: intl.formatMessage({id: 'BoardMember.schemeViewer', defaultMessage: 'Viewer'})},\n            )\n        }\n        if (props.minimumRole === MemberRole.Commenter) {\n            roleOptions.push(\n                {id: MemberRole.Commenter, label: intl.formatMessage({id: 'BoardMember.schemeCommenter', defaultMessage: 'Commenter'})},\n            )\n        }\n        if (props.minimumRole === MemberRole.Editor) {\n            roleOptions.push(\n                {id: MemberRole.Editor, label: intl.formatMessage({id: 'BoardMember.schemeEditor', defaultMessage: 'Editor'})},\n            )\n        }\n    }\n\n    const subText = (\n        <div className='ConfirmAddUserForNotifications'>\n            <p>\n                <FormattedMessage\n                    id='person.add-user-to-board-warning'\n                    defaultMessage='{username} is not a member of the board, and will not receive any notifications about it.'\n                    values={{username: props.user.username}}\n                />\n            </p>\n            <p>\n                <FormattedMessage\n                    id='person.add-user-to-board-question'\n                    defaultMessage='Do you want to add {username} to the board?'\n                    values={{username: props.user.username}}\n                />\n            </p>\n            <div className='permissions-title'>\n                <label>\n                    <FormattedMessage\n                        id='person.add-user-to-board-permissions'\n                        defaultMessage='Permissions'\n                    />\n                </label>\n            </div>\n            <Select\n                className='select'\n                getOptionLabel={(o: {id: MemberRole, label: string}) => o.label}\n                getOptionValue={(o: {id: MemberRole, label: string}) => o.id}\n                styles={{menuPortal: (base) => ({...base, zIndex: 9999})}}\n                menuPortalTarget={document.body}\n                isDisabled={!allowManageBoardRoles}\n                options={roleOptions}\n                onChange={(option) => {\n                    if (allowManageBoardRoles) {\n                        setNewUserRole(option?.id || props.minimumRole)\n                        userRole.current = option?.id || props.minimumRole\n                    }\n                }}\n                value={roleOptions.find((o) => o.id === newUserRole)}\n            />\n        </div>\n    )\n\n    return (\n        <ConfirmationDialog\n            dialogBox={{\n                heading: intl.formatMessage({id: 'person.add-user-to-board', defaultMessage: 'Add {username} to board'}, {username: props.user.username}),\n                subText,\n                confirmButtonText: intl.formatMessage({id: 'person.add-user-to-board-confirm-button', defaultMessage: 'Add to board'}),\n                onConfirm: () => props.onConfirm(user.id, userRole.current),\n                onClose: props.onClose,\n            }}\n        />\n    )\n}\n\nexport default ConfirmAddUserForNotifications\n"
  },
  {
    "path": "webapp/src/components/confirmationDialogBox.scss",
    "content": "@import '../styles/z-index';\n\n.confirmation-dialog-box {\n    .dialog {\n        @include z-index(confirmation-dialog-box);\n\n        > .toolbar {\n            position: absolute;\n            top: 0;\n            right: 0;\n        }\n    }\n}\n\n.box-area {\n    display: grid;\n    place-items: center;\n    padding: 48px 40px;\n\n\n    .text-heading5 {\n        margin: 0 0 8px;\n    }\n\n    .sub-text {\n        text-align: center;\n    }\n}\n\n.action-buttons {\n    display: flex;\n    grid-gap: 12px;\n    gap: 12px;\n    grid-template-columns: repeat(2, 1fr);\n    margin-top: 32px;\n}\n"
  },
  {
    "path": "webapp/src/components/confirmationDialogBox.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport '@testing-library/jest-dom'\nimport {act, render} from '@testing-library/react'\nimport userEvent from '@testing-library/user-event'\nimport React from 'react'\n\nimport {wrapDNDIntl} from '../testUtils'\n\nimport ConfirmationDialogBox from './confirmationDialogBox'\n\ndescribe('/components/confirmationDialogBox', () => {\n    const dialogPropsWithCnfrmBtnText = {\n        heading: 'test-heading',\n        subText: 'test-sub-text',\n        confirmButtonText: 'test-btn-text',\n        onConfirm: jest.fn(),\n        onClose: jest.fn(),\n    }\n\n    const dialogProps = {\n        heading: 'test-heading',\n        onConfirm: jest.fn(),\n        onClose: jest.fn(),\n    }\n\n    it('confirmDialog should match snapshot', async () => {\n        let container\n\n        await act(async () => {\n            const result = render(\n                wrapDNDIntl(\n                    <ConfirmationDialogBox\n                        dialogBox={dialogPropsWithCnfrmBtnText}\n                    />,\n                ),\n            )\n            container = result.container\n        })\n        expect(container).toMatchSnapshot()\n    })\n\n    it('confirmDialog with Confirm Button Text should match snapshot', async () => {\n        let containerWithCnfrmBtnText\n        await act(async () => {\n            const result = render(\n                wrapDNDIntl(\n                    <ConfirmationDialogBox\n                        dialogBox={dialogPropsWithCnfrmBtnText}\n                    />,\n                ),\n            )\n            containerWithCnfrmBtnText = result.container\n        })\n        expect(containerWithCnfrmBtnText).toMatchSnapshot()\n    })\n\n    it('confirm button click, run onConfirm Function once', () => {\n        const result = render(\n            wrapDNDIntl(<ConfirmationDialogBox dialogBox={dialogProps}/>),\n        )\n\n        userEvent.click(result.getByTitle('Confirm'))\n        expect(dialogProps.onConfirm).toBeCalledTimes(1)\n    })\n\n    it('confirm button (with passed prop text), run onConfirm Function once', () => {\n        const resultWithConfirmBtnText = render(\n            wrapDNDIntl(\n                <ConfirmationDialogBox\n                    dialogBox={dialogPropsWithCnfrmBtnText}\n                />,\n            ),\n        )\n\n        userEvent.click(\n            resultWithConfirmBtnText.getByTitle(dialogPropsWithCnfrmBtnText.confirmButtonText),\n        )\n\n        expect(dialogPropsWithCnfrmBtnText.onConfirm).toBeCalledTimes(1)\n    })\n\n    it('cancel button click runs onClose function', () => {\n        const result = render(wrapDNDIntl(\n            <ConfirmationDialogBox\n                dialogBox={dialogProps}\n            />,\n        ))\n\n        userEvent.click(result.getByTitle('Cancel'))\n        expect(dialogProps.onClose).toBeCalledTimes(1)\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/confirmationDialogBox.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React, {ReactNode, useCallback} from 'react'\nimport {FormattedMessage} from 'react-intl'\n\nimport Button from '../widgets/buttons/button'\n\nimport Dialog from './dialog'\nimport './confirmationDialogBox.scss'\n\ntype ConfirmationDialogBoxProps = {\n    heading: string\n    subText?: string | ReactNode\n    confirmButtonText?: string\n    destructive?: boolean\n    onConfirm: () => void\n    onClose: () => void\n}\n\ntype Props = {\n    dialogBox: ConfirmationDialogBoxProps\n}\n\nexport const ConfirmationDialogBox = (props: Props) => {\n    const handleOnClose = useCallback(props.dialogBox.onClose, [])\n    const handleOnConfirm = useCallback(props.dialogBox.onConfirm, [])\n\n    return (\n        <Dialog\n            size='small'\n            className='confirmation-dialog-box'\n            onClose={handleOnClose}\n        >\n            <div\n                className='box-area'\n                title='Confirmation Dialog Box'\n            >\n                <h3 className='text-heading5'>{props.dialogBox.heading}</h3>\n                <div className='sub-text'>{props.dialogBox.subText}</div>\n\n                <div className='action-buttons'>\n                    <Button\n                        title='Cancel'\n                        size='medium'\n                        emphasis='tertiary'\n                        onClick={handleOnClose}\n                    >\n                        <FormattedMessage\n                            id='ConfirmationDialog.cancel-action'\n                            defaultMessage='Cancel'\n                        />\n                    </Button>\n                    <Button\n                        title={props.dialogBox.confirmButtonText || 'Confirm'}\n                        size='medium'\n                        submit={true}\n                        danger={Boolean(props.dialogBox.destructive)}\n                        onClick={handleOnConfirm}\n                        filled={true}\n                    >\n                        { props.dialogBox.confirmButtonText ||\n                        <FormattedMessage\n                            id='ConfirmationDialog.confirm-action'\n                            defaultMessage='Confirm'\n                        />\n                        }\n                    </Button>\n                </div>\n            </div>\n        </Dialog>\n    )\n}\n\nexport default ConfirmationDialogBox\nexport {ConfirmationDialogBoxProps}\n"
  },
  {
    "path": "webapp/src/components/content/__snapshots__/attachmentElement.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`component/content/FileBlock archived file 1`] = `\n<div>\n  <div\n    class=\"FileElement mr-4\"\n  >\n    <div\n      class=\"fileElement-icon-division\"\n    >\n      <i\n        class=\"CompassIcon icon-file-text-outline-large fileElement-icon\"\n      />\n    </div>\n    <div\n      class=\"fileElement-file-details mt-3\"\n    >\n      <div\n        class=\"octo-tooltip tooltip-bottom\"\n        data-tooltip=\"test.txt\"\n      >\n        <div\n          class=\"fileElement-file-name\"\n        >\n          test.txt\n        </div>\n      </div>\n      <div\n        class=\"fileElement-file-ext-and-size\"\n      >\n        txt\n         \n        2.2 KiB\n      </div>\n    </div>\n    <div\n      class=\"fileElement-delete-download\"\n    >\n      <div\n        aria-label=\"menuwrapper\"\n        class=\"MenuWrapper mt-3 fileElement-menu-icon\"\n        role=\"button\"\n      >\n        <button\n          class=\"IconButton size--medium\"\n          type=\"button\"\n        >\n          <i\n            class=\"CompassIcon icon-dots-vertical\"\n          />\n        </button>\n      </div>\n      <div\n        class=\"octo-tooltip tooltip-bottom\"\n        data-tooltip=\"Download\"\n      >\n        <div\n          class=\"fileElement-download-btn mt-3 mr-2\"\n        >\n          <i\n            class=\"CompassIcon icon-download-outline\"\n          />\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`component/content/FileBlock should match snapshot 1`] = `\n<div>\n  <div\n    class=\"FileElement mr-4\"\n  >\n    <div\n      class=\"fileElement-icon-division\"\n    >\n      <i\n        class=\"CompassIcon icon-file-text-outline-large fileElement-icon\"\n      />\n    </div>\n    <div\n      class=\"fileElement-file-details mt-3\"\n    >\n      <div\n        class=\"octo-tooltip tooltip-bottom\"\n        data-tooltip=\"test.txt\"\n      >\n        <div\n          class=\"fileElement-file-name\"\n        >\n          test.txt\n        </div>\n      </div>\n      <div\n        class=\"fileElement-file-ext-and-size\"\n      >\n        txt\n         \n        2.2 KiB\n      </div>\n    </div>\n    <div\n      class=\"fileElement-delete-download\"\n    >\n      <div\n        aria-label=\"menuwrapper\"\n        class=\"MenuWrapper mt-3 fileElement-menu-icon\"\n        role=\"button\"\n      >\n        <button\n          class=\"IconButton size--medium\"\n          type=\"button\"\n        >\n          <i\n            class=\"CompassIcon icon-dots-vertical\"\n          />\n        </button>\n      </div>\n      <div\n        class=\"octo-tooltip tooltip-bottom\"\n        data-tooltip=\"Download\"\n      >\n        <div\n          class=\"fileElement-download-btn mt-3 mr-2\"\n        >\n          <i\n            class=\"CompassIcon icon-download-outline\"\n          />\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/content/__snapshots__/checkboxElement.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/content/checkboxElement should change title 1`] = `\n<div>\n  <div\n    class=\"CheckboxElement\"\n  >\n    <input\n      id=\"checkbox-test-id\"\n      type=\"checkbox\"\n      value=\"off\"\n    />\n    <input\n      class=\"Editable \"\n      placeholder=\"Edit text...\"\n      spellcheck=\"true\"\n      title=\"new title\"\n      type=\"text\"\n      value=\"new title\"\n    />\n  </div>\n</div>\n`;\n\nexports[`components/content/checkboxElement should match snapshot 1`] = `\n<div>\n  <div\n    class=\"CheckboxElement\"\n  >\n    <input\n      id=\"checkbox-test-id\"\n      type=\"checkbox\"\n      value=\"off\"\n    />\n    <input\n      class=\"Editable \"\n      placeholder=\"Edit text...\"\n      spellcheck=\"true\"\n      title=\"test-title\"\n      value=\"test-title\"\n    />\n  </div>\n</div>\n`;\n\nexports[`components/content/checkboxElement should match snapshot when read only 1`] = `\n<div>\n  <div\n    class=\"CheckboxElement\"\n  >\n    <input\n      disabled=\"\"\n      id=\"checkbox-test-id\"\n      type=\"checkbox\"\n      value=\"off\"\n    />\n    <input\n      class=\"Editable readonly \"\n      placeholder=\"Edit text...\"\n      readonly=\"\"\n      spellcheck=\"true\"\n      title=\"test-title\"\n      value=\"test-title\"\n    />\n  </div>\n</div>\n`;\n\nexports[`components/content/checkboxElement should toggle value 1`] = `\n<div>\n  <div\n    class=\"CheckboxElement\"\n  >\n    <input\n      id=\"checkbox-test-id\"\n      type=\"checkbox\"\n      value=\"on\"\n    />\n    <input\n      class=\"Editable \"\n      placeholder=\"Edit text...\"\n      spellcheck=\"true\"\n      title=\"test-title\"\n      value=\"test-title\"\n    />\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/content/__snapshots__/contentElement.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/content/contentElement should match snapshot for checkbox type 1`] = `\n<div>\n  <div\n    class=\"CheckboxElement\"\n  >\n    <input\n      id=\"checkbox-test-id\"\n      type=\"checkbox\"\n      value=\"off\"\n    />\n    <input\n      class=\"Editable \"\n      placeholder=\"Edit text...\"\n      spellcheck=\"true\"\n      title=\"test-title\"\n      value=\"test-title\"\n    />\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/content/__snapshots__/dividerElement.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/content/DividerElement should match snapshot 1`] = `\n<div>\n  <div\n    class=\"DividerElement\"\n  />\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/content/__snapshots__/imageElement.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/content/ImageElement archived file 1`] = `\n<div>\n  <div\n    class=\"ArchivedFile\"\n  >\n    <i\n      class=\"CompassIcon icon-file-image-broken-outline BrokenFile\"\n    />\n    <div\n      class=\"fileMetadata\"\n    >\n      <p\n        class=\"filename\"\n      >\n        Filename\n      </p>\n      <p>\n        TXT\n         \n        161.1 KiB\n      </p>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/content/ImageElement should match snapshot 1`] = `\n<div>\n  <img\n    alt=\"test-title\"\n    class=\"ImageElement\"\n    src=\"test.jpg\"\n  />\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/content/__snapshots__/textElement.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/content/TextElement return a textElement 1`] = `\n<div>\n  <div\n    class=\"TextElement\"\n  >\n    <div\n      class=\"MarkdownEditor octo-editor  \"\n    >\n      <div\n        class=\"octo-editor-preview octo-placeholder\"\n        data-testid=\"preview-element\"\n      />\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/content/archivedFile/__snapshots__/archivedFile.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/content/archivedFile should match snapshot 1`] = `\n<div>\n  <div\n    class=\"ArchivedFile\"\n  >\n    <i\n      class=\"CompassIcon icon-file-image-broken-outline BrokenFile\"\n    />\n    <div\n      class=\"fileMetadata\"\n    >\n      <p\n        class=\"filename\"\n      >\n        stuff to put in jell-o\n      </p>\n      <p>\n        TXT\n         \n        2.0 KiB\n      </p>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/content/archivedFile/archivedFile.scss",
    "content": ".ArchivedFile {\n    border: 2px solid rgba(var(--center-channel-color-rgb), 0.1);\n    border-radius: 4px;\n    box-shadow: 0 2px 3px rgba(var(--center-channel-color-rgb), 0.1);\n    padding: 12px;\n    display: flex;\n    flex-direction: row;\n    color: rgba(var(--center-channel-color-rgb), 0.4);\n\n\n    .CompassIcon.icon-file-image-broken-outline.BrokenFile {\n        font-size: 48px;\n    }\n\n    .fileMetadata {\n        display: flex;\n        flex-direction: column;\n    }\n\n    .fileMetadata > p {\n        margin: 0;\n    }\n\n    .filename {\n        font-weight: bold;\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/content/archivedFile/archivedFile.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport {render} from '@testing-library/react'\n\nimport {FileInfo} from '../../../blocks/block'\n\nimport ArchivedFile from './archivedFile'\n\ndescribe('components/content/archivedFile', () => {\n    it('should match snapshot', () => {\n        const fileInfo: FileInfo = {\n            archived: true,\n            extension: '.txt',\n            name: 'stuff to put in jell-o',\n            size: 2056,\n        }\n\n        const component = (<ArchivedFile fileInfo={fileInfo}/>)\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/content/archivedFile/archivedFile.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useCallback} from 'react'\n\nimport {FileInfo} from '../../../blocks/block'\nimport BrokenFile from '../../../widgets/icons/brokenFile'\nimport {Utils} from '../../../utils'\n\nimport './archivedFile.scss'\n\ntype Props = {\n    fileInfo: FileInfo\n}\n\nconst ArchivedFile = (props: Props): JSX.Element => {\n    const fileName = useCallback(() => props.fileInfo.name || 'untitled file', [props.fileInfo.name])\n\n    const fileExtension = useCallback(() => {\n        let extension = props.fileInfo.extension\n        extension = extension?.startsWith('.') ? extension?.substring(1) : extension\n        return extension?.toUpperCase()\n    }, [props.fileInfo.extension])\n\n    const fileSize = useCallback(() => Utils.humanFileSize(props.fileInfo.size || 0), [props.fileInfo.size])\n\n    return (\n        <div className='ArchivedFile'>\n            <BrokenFile/>\n            <div className='fileMetadata'>\n                <p className='filename'>{fileName()}</p>\n                <p>{fileExtension()} {fileSize()}</p>\n            </div>\n        </div>\n    )\n}\n\nexport default ArchivedFile\n"
  },
  {
    "path": "webapp/src/components/content/attachmentElement.scss",
    "content": ".FileElement {\n    background: rgb(var(--center-channel-bg-rgb));\n    border: 1px solid rgba(var(--center-channel-color-rgb), 0.16);\n    min-width: 300px;\n    width: max-content;\n    height: 64px;\n    box-shadow: var(--elevation-1);\n    display: flex;\n    position: relative;\n\n    .fileElement-file-name {\n        font-size: 14px;\n        font-weight: 600;\n    }\n\n    .fileElement-file-ext-and-size {\n        text-transform: uppercase;\n        font-weight: 400;\n        font-size: 12px;\n        line-height: 16px;\n        color: rgb(var(--center-channel-color-rgb));\n    }\n\n    .fileElement-file-uploading {\n        font-weight: 400;\n        font-size: 12px;\n        line-height: 16px;\n        color: rgb(var(--center-channel-color-rgb));\n    }\n\n    .fileElement-icon-division {\n        margin-top: 8px;\n    }\n\n    .fileElement-icon {\n        font-size: 48px;\n        color: rgba(237, 82, 42, 1);\n    }\n\n    .fileElement-download-btn {\n        display: none;\n        font-size: 20px;\n        color: rgba(var(--center-channel-color-rgb), 0.56);\n        padding: 8px;\n\n        &:hover {\n            background-color: rgba(var(--center-channel-color-rgb), 0.08);\n            border-radius: 5px;\n            cursor: pointer;\n        }\n    }\n\n    .fileElement-menu-icon {\n        display: none;\n        float: right;\n    }\n\n    .delete-menu {\n        display: none;\n        margin-top: -30px;\n    }\n\n    .fileElement-delete-download {\n        position: absolute;\n        display: flex;\n        right: 0;\n    }\n\n    &:hover {\n        .fileElement-download-btn {\n            display: block;\n        }\n\n        .fileElement-menu-icon {\n            display: block;\n        }\n\n        .delete-menu {\n            display: block;\n        }\n    }\n\n    .progress {\n        position: absolute;\n        bottom: 0;\n        width: 100%;\n        height: 7px;\n        margin-bottom: 0;\n        border-radius: 0;\n    }\n\n    .progress-bar {\n        float: left;\n        width: 0%;\n        height: 100%;\n        line-height: 20px;\n        color: #fff;\n        text-align: center;\n        background-color: #285ab9;\n    }\n\n    .dialog {\n        max-width: 550px !important;\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/content/attachmentElement.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {Provider as ReduxProvider} from 'react-redux'\nimport {render} from '@testing-library/react'\nimport {act} from 'react-dom/test-utils'\nimport {mocked} from 'jest-mock'\n\nimport {AttachmentBlock} from '../../blocks/attachmentBlock'\nimport {mockStateStore, wrapIntl} from '../../testUtils'\nimport octoClient from '../../octoClient'\nimport {TestBlockFactory} from '../../test/testBlockFactory'\nimport {IUser} from '../../user'\n\nimport AttachmentElement from './attachmentElement'\n\njest.mock('../../octoClient')\nconst mockedOcto = mocked(octoClient, true)\nmockedOcto.getFileAsDataUrl.mockResolvedValue({url: 'test.txt'})\nmockedOcto.getFileInfo.mockResolvedValue({\n    name: 'test.txt',\n    size: 2300,\n    extension: '.txt',\n})\n\nconst board = TestBlockFactory.createBoard()\nboard.id = '1'\nboard.teamId = 'team-id'\nboard.channelId = 'channel_1'\n\ndescribe('component/content/FileBlock', () => {\n    const defaultBlock: AttachmentBlock = {\n        id: 'test-id',\n        boardId: '1',\n        parentId: '',\n        modifiedBy: 'test-user-id',\n        schema: 0,\n        type: 'attachment',\n        title: 'test-title',\n        fields: {\n            fileId: 'test.txt',\n        },\n        createdBy: 'test-user-id',\n        createAt: 0,\n        updateAt: 0,\n        deleteAt: 0,\n        limited: false,\n        isUploading: false,\n        uploadingPercent: 0,\n    }\n\n    const me: IUser = {\n        id: 'user-id-1',\n        username: 'username_1',\n        email: '',\n        nickname: '',\n        firstname: '',\n        lastname: '',\n        props: {},\n        create_at: 0,\n        update_at: 0,\n        is_bot: false,\n        is_guest: false,\n        roles: 'system_user',\n    }\n\n    const state = {\n        teams: {\n            current: {id: 'team-id', title: 'Test Team'},\n        },\n        users: {\n            me,\n            boardUsers: [me],\n            blockSubscriptions: [],\n        },\n        boards: {\n            current: board.id,\n            boards: {\n                [board.id]: board,\n            },\n            templates: [],\n            membersInBoards: {\n                [board.id]: {},\n            },\n            myBoardMemberships: {\n                [board.id]: {userId: me.id, schemeAdmin: true},\n            },\n        },\n\n        attachments: {\n            attachments: {\n                'test-id': {\n                    uploadPercent: 0,\n                },\n            },\n        },\n    }\n\n    const store = mockStateStore([], state)\n\n    test('should match snapshot', async () => {\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <AttachmentElement\n                    block={defaultBlock}\n                />\n            </ReduxProvider>,\n        )\n        let fileContainer: Element | undefined\n        await act(async () => {\n            const {container} = render(component)\n            fileContainer = container\n        })\n        expect(fileContainer).toMatchSnapshot()\n    })\n\n    test('archived file', async () => {\n        mockedOcto.getFileAsDataUrl.mockResolvedValue({\n            archived: true,\n            name: 'FileName',\n            extension: '.txt',\n            size: 165002,\n        })\n\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <AttachmentElement\n                    block={defaultBlock}\n                />\n            </ReduxProvider>,\n        )\n        let fileContainer: Element | undefined\n        await act(async () => {\n            const {container} = render(component)\n            fileContainer = container\n        })\n        expect(fileContainer).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/content/attachmentElement.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useEffect, useState} from 'react'\nimport {useIntl} from 'react-intl'\n\nimport octoClient from '../../octoClient'\n\nimport {AttachmentBlock} from '../../blocks/attachmentBlock'\nimport {Block, FileInfo} from '../../blocks/block'\nimport Files from '../../file'\nimport FileIcons from '../../fileIcons'\n\nimport BoardPermissionGate from '../../components/permissions/boardPermissionGate'\nimport ConfirmationDialogBox, {ConfirmationDialogBoxProps} from '../../components/confirmationDialogBox'\nimport {Utils} from '../../utils'\nimport {getUploadPercent} from '../../store/attachments'\nimport {useAppSelector} from '../../store/hooks'\nimport {Permission} from '../../constants'\n\nimport ArchivedFile from './archivedFile/archivedFile'\n\nimport './attachmentElement.scss'\nimport CompassIcon from './../../widgets/icons/compassIcon'\nimport MenuWrapper from './../../widgets/menuWrapper'\nimport IconButton from './../../widgets/buttons/iconButton'\nimport Menu from './../../widgets/menu'\nimport Tooltip from './../../widgets/tooltip'\n\ntype Props = {\n    block: AttachmentBlock\n    onDelete?: (block: Block) => void\n}\n\nconst AttachmentElement = (props: Props): JSX.Element|null => {\n    const {block, onDelete} = props\n    const [fileInfo, setFileInfo] = useState<FileInfo>({})\n    const [fileSize, setFileSize] = useState<string>()\n    const [fileIcon, setFileIcon] = useState<string>('file-text-outline-larg')\n    const [fileName, setFileName] = useState<string>()\n    const [showConfirmationDialogBox, setShowConfirmationDialogBox] = useState<boolean>(false)\n    const uploadPercent = useAppSelector(getUploadPercent(block.id))\n    const intl = useIntl()\n\n    useEffect(() => {\n        const loadFile = async () => {\n            if (block.isUploading) {\n                setFileInfo({\n                    name: block.title,\n                    extension: block.title.split('.').slice(0, -1).join('.'),\n                })\n                return\n            }\n            const attachmentInfo = await octoClient.getFileInfo(block.boardId, block.fields.fileId)\n            setFileInfo(attachmentInfo)\n        }\n        loadFile()\n    }, [])\n\n    useEffect(() => {\n        if (fileInfo.size && !fileSize) {\n            setFileSize(Utils.humanFileSize(fileInfo.size))\n        }\n        if (fileInfo.name && !fileName) {\n            const generateFileName = (fName: string) => {\n                if (fName.length > 18) {\n                    let result = fName.slice(0, 15)\n                    result += '...'\n                    return result\n                }\n                return fName\n            }\n            setFileName(generateFileName(fileInfo.name))\n        }\n    }, [fileInfo.size, fileInfo.name])\n\n    useEffect(() => {\n        if (fileInfo.extension) {\n            const getFileIcon = (fileExt: string) => {\n                const extType = (Object.keys(Files) as string[]).find((key) => Files[key].find((ext) => ext === fileExt))\n                if (extType) {\n                    setFileIcon(FileIcons[extType])\n                } else {\n                    setFileIcon('file-generic-outline-large')\n                }\n            }\n            getFileIcon(fileInfo.extension.substring(1))\n        }\n    }, [fileInfo.extension])\n\n    const deleteAttachment = () => {\n        if (onDelete) {\n            onDelete(block)\n        }\n    }\n\n    const confirmDialogProps: ConfirmationDialogBoxProps = {\n        heading: intl.formatMessage({id: 'CardDialog.delete-confirmation-dialog-attachment', defaultMessage: 'Confirm Attachment delete!'}),\n        confirmButtonText: intl.formatMessage({id: 'AttachmentElement.delete-confirmation-dialog-button-text', defaultMessage: 'Delete'}),\n        onConfirm: deleteAttachment,\n        onClose: () => {\n            setShowConfirmationDialogBox(false)\n        },\n    }\n\n    const handleDeleteButtonClick = () => {\n        setShowConfirmationDialogBox(true)\n    }\n\n    if (fileInfo.archived) {\n        return (\n            <ArchivedFile fileInfo={fileInfo}/>\n        )\n    }\n\n    const attachmentDownloadHandler = async () => {\n        const attachment = await octoClient.getFileAsDataUrl(block.boardId, block.fields.fileId)\n        const anchor = document.createElement('a')\n        anchor.href = attachment.url || ''\n        anchor.download = fileInfo.name || ''\n        document.body.appendChild(anchor)\n        anchor.click()\n        document.body.removeChild(anchor)\n    }\n\n    return (\n        <div className='FileElement mr-4'>\n            {showConfirmationDialogBox && <ConfirmationDialogBox dialogBox={confirmDialogProps}/>}\n            <div className='fileElement-icon-division'>\n                <CompassIcon\n                    icon={fileIcon}\n                    className='fileElement-icon'\n                />\n            </div>\n            <div className='fileElement-file-details mt-3'>\n                <Tooltip\n                    title={fileInfo.name ? fileInfo.name : ''}\n                    placement='bottom'\n                >\n                    <div className='fileElement-file-name'>\n                        {fileName}\n                    </div>\n                </Tooltip>\n                {!block.isUploading && <div className='fileElement-file-ext-and-size'>\n                    {fileInfo.extension?.substring(1)} {fileSize}\n                </div> }\n                {block.isUploading && <div className='fileElement-file-uploading'>\n                    {intl.formatMessage({\n                        id: 'AttachmentElement.upload-percentage',\n                        defaultMessage: 'Uploading...({uploadPercent}%)',\n                    }, {\n                        uploadPercent,\n                    })}\n                </div>}\n            </div>\n            {block.isUploading &&\n                <div className='progress'>\n                    <span\n                        className='progress-bar'\n                        style={{width: uploadPercent + '%'}}\n                    >\n                        {''}\n                    </span>\n                </div>}\n            {!block.isUploading &&\n            <div className='fileElement-delete-download'>\n                <BoardPermissionGate permissions={[Permission.ManageBoardCards]}>\n                    <MenuWrapper className='mt-3 fileElement-menu-icon'>\n                        <IconButton\n                            size='medium'\n                            icon={<CompassIcon icon='dots-vertical'/>}\n                        />\n                        <div className='delete-menu'>\n                            <Menu position='left'>\n                                <Menu.Text\n                                    id='makeTemplate'\n                                    icon={\n                                        <CompassIcon\n                                            icon='trash-can-outline'\n                                        />}\n                                    name='Delete'\n                                    onClick={handleDeleteButtonClick}\n                                />\n                            </Menu>\n                        </div>\n                    </MenuWrapper>\n                </BoardPermissionGate>\n                <Tooltip\n                    title={intl.formatMessage({id: 'AttachmentElement.download', defaultMessage: 'Download'})}\n                    placement='bottom'\n                >\n                    <div\n                        className='fileElement-download-btn mt-3 mr-2'\n                        onClick={attachmentDownloadHandler}\n                    >\n                        <CompassIcon\n                            icon='download-outline'\n                        />\n                    </div>\n                </Tooltip>\n            </div> }\n        </div>\n    )\n}\n\nexport default React.memo(AttachmentElement)\n"
  },
  {
    "path": "webapp/src/components/content/checkboxElement.scss",
    "content": ".CheckboxElement {\n    display: flex;\n    align-items: center;\n    min-height: 34px;\n\n    input {\n        margin-right: 10px;\n    }\n\n    .Editable {\n        width: 100%;\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/content/checkboxElement.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React, {ReactElement, ReactNode} from 'react'\nimport {fireEvent, render, screen, waitFor} from '@testing-library/react'\nimport '@testing-library/jest-dom'\nimport {mocked} from 'jest-mock'\nimport userEvent from '@testing-library/user-event'\n\nimport {wrapIntl} from '../../testUtils'\nimport {ContentBlock, createContentBlock} from '../../blocks/contentBlock'\nimport {CardDetailContext, CardDetailContextType, CardDetailProvider} from '../cardDetail/cardDetailContext'\nimport {TestBlockFactory} from '../../test/testBlockFactory'\nimport mutator from '../../mutator'\n\nimport CheckboxElement from './checkboxElement'\n\njest.mock('../../mutator')\nconst mockedMutator = mocked(mutator, true)\n\nconst board = TestBlockFactory.createBoard()\nconst card = TestBlockFactory.createCard(board)\nconst checkboxBlock: ContentBlock = {\n    id: 'test-id',\n    boardId: board.id,\n    parentId: card.id,\n    modifiedBy: 'test-user-id',\n    schema: 1,\n    type: 'checkbox',\n    title: 'test-title',\n    fields: {value: false},\n    createdBy: 'test-user-id',\n    createAt: 0,\n    updateAt: 0,\n    deleteAt: 0,\n    limited: false,\n}\n\nconst cardDetailContextValue = (autoAdded: boolean): CardDetailContextType => ({\n    card,\n    lastAddedBlock: {\n        id: checkboxBlock.id,\n        autoAdded,\n    },\n    deleteBlock: jest.fn(),\n    addBlock: jest.fn(),\n})\n\nconst wrap = (child: ReactNode): ReactElement => (\n    wrapIntl(\n        <CardDetailProvider card={card}>\n            {child}\n        </CardDetailProvider>,\n    )\n)\n\ndescribe('components/content/checkboxElement', () => {\n    beforeEach(jest.clearAllMocks)\n\n    it('should match snapshot', () => {\n        const component = wrap(\n            <CheckboxElement\n                block={checkboxBlock}\n                readonly={false}\n            />,\n        )\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n\n    it('should match snapshot when read only', () => {\n        const component = wrap(\n            <CheckboxElement\n                block={checkboxBlock}\n                readonly={true}\n            />,\n        )\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n\n    it('should change title', () => {\n        const {container} = render(wrap(\n            <CheckboxElement\n                block={checkboxBlock}\n                readonly={false}\n            />,\n        ))\n        const newTitle = 'new title'\n        const input = screen.getByRole('textbox', {name: /test-title/i})\n        userEvent.clear(input)\n        userEvent.type(input, newTitle)\n        fireEvent.blur(input)\n        expect(container).toMatchSnapshot()\n        expect(mockedMutator.changeBlockTitle).toHaveBeenCalledTimes(1)\n        expect(mockedMutator.changeBlockTitle).toHaveBeenCalledWith(\n            checkboxBlock.boardId,\n            checkboxBlock.id,\n            checkboxBlock.title,\n            newTitle,\n            expect.anything())\n    })\n\n    it('should toggle value', () => {\n        const {container} = render(wrap(\n            <CheckboxElement\n                block={checkboxBlock}\n                readonly={false}\n            />,\n        ))\n        const input = screen.getByRole('checkbox')\n        userEvent.click(input)\n        expect(container).toMatchSnapshot()\n        expect(mockedMutator.updateBlock).toHaveBeenCalledTimes(1)\n        expect(mockedMutator.updateBlock).toHaveBeenCalledWith(\n            checkboxBlock.boardId,\n            expect.objectContaining({fields: {value: true}}),\n            checkboxBlock,\n            expect.anything())\n    })\n\n    it('should have focus when last added', () => {\n        render(wrapIntl(\n            <CardDetailContext.Provider value={cardDetailContextValue(false)}>\n                <CheckboxElement\n                    block={checkboxBlock}\n                    readonly={false}\n                />\n            </CardDetailContext.Provider>,\n        ))\n        const input = screen.getByRole('textbox', {name: /test-title/i})\n        expect(input).toHaveFocus()\n    })\n\n    it('should add new checkbox when enter pressed', async () => {\n        const addElement = jest.fn()\n        render(wrap(\n            <CheckboxElement\n                block={checkboxBlock}\n                readonly={false}\n                onAddElement={addElement}\n            />,\n        ))\n        const input = screen.getByRole('textbox', {name: /test-title/i})\n\n        // should not add new checkbox when current one has empty title\n        userEvent.clear(input)\n        userEvent.type(input, '{enter}')\n        expect(addElement).toHaveBeenCalledTimes(0)\n\n        // should add new checkbox when current one has non-empty title\n        userEvent.clear(input)\n        userEvent.type(input, 'new-title{enter}')\n        await waitFor(() => expect(addElement).toHaveBeenCalledTimes(1))\n    })\n\n    it('should delete automatically added checkbox with empty title on esc/enter pressed', () => {\n        const addedBlock = createContentBlock(checkboxBlock)\n        addedBlock.title = ''\n        const deleteElement = jest.fn()\n\n        render(wrapIntl(\n            <CardDetailContext.Provider value={cardDetailContextValue(true)}>\n                <CheckboxElement\n                    block={addedBlock}\n                    readonly={false}\n                    onDeleteElement={deleteElement}\n                />\n            </CardDetailContext.Provider>,\n        ))\n\n        const input = screen.getByRole('textbox')\n        userEvent.type(input, '{esc}')\n        expect(deleteElement).toHaveBeenCalledTimes(1)\n        userEvent.type(input, '{enter}')\n        expect(deleteElement).toHaveBeenCalledTimes(2)\n\n        // should not delete if title is not empty\n        userEvent.type(input, 'new-title{esc}')\n        expect(deleteElement).toHaveBeenCalledTimes(2)\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/content/checkboxElement.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useEffect, useRef, useState} from 'react'\nimport {useIntl} from 'react-intl'\n\nimport {createCheckboxBlock} from '../../blocks/checkboxBlock'\nimport {ContentBlock} from '../../blocks/contentBlock'\nimport CheckIcon from '../../widgets/icons/check'\nimport mutator from '../../mutator'\nimport Editable, {Focusable} from '../../widgets/editable'\nimport {useCardDetailContext} from '../cardDetail/cardDetailContext'\n\nimport './checkboxElement.scss'\n\nimport {contentRegistry} from './contentRegistry'\n\ntype Props = {\n    block: ContentBlock\n    readonly: boolean\n    onAddElement?: () => void\n    onDeleteElement?: () => void\n}\n\nconst CheckboxElement = (props: Props) => {\n    const {block, readonly} = props\n    const intl = useIntl()\n    const titleRef = useRef<Focusable>(null)\n    const cardDetail = useCardDetailContext()\n    const [addedBlockId, setAddedBlockId] = useState(cardDetail.lastAddedBlock.id)\n    const [active, setActive] = useState(Boolean(block.fields.value))\n    const [title, setTitle] = useState(block.title)\n\n    useEffect(() => {\n        if (block.id === addedBlockId) {\n            titleRef.current?.focus()\n            setAddedBlockId('')\n        }\n    }, [block, addedBlockId, titleRef])\n\n    useEffect(() => {\n        setActive(Boolean(block.fields.value))\n    }, [Boolean(block.fields.value)])\n\n    return (\n        <div className='CheckboxElement'>\n            <input\n                type='checkbox'\n                id={`checkbox-${block.id}`}\n                disabled={readonly}\n                checked={active}\n                value={active ? 'on' : 'off'}\n                onChange={(e) => {\n                    e.preventDefault()\n                    const newBlock = createCheckboxBlock(block)\n                    newBlock.fields.value = !active\n                    newBlock.title = title\n                    setActive(newBlock.fields.value)\n                    mutator.updateBlock(block.boardId, newBlock, block, intl.formatMessage({id: 'ContentBlock.editCardCheckbox', defaultMessage: 'toggled-checkbox'}))\n                }}\n            />\n            <Editable\n                ref={titleRef}\n                value={title}\n                placeholderText={intl.formatMessage({id: 'ContentBlock.editText', defaultMessage: 'Edit text...'})}\n                onChange={setTitle}\n                saveOnEsc={true}\n                onSave={async (saveType) => {\n                    const {lastAddedBlock} = cardDetail\n                    if (title === '' && block.id === lastAddedBlock.id && lastAddedBlock.autoAdded && props.onDeleteElement) {\n                        props.onDeleteElement()\n                        return\n                    }\n\n                    if (block.title !== title) {\n                        await mutator.changeBlockTitle(block.boardId, block.id, block.title, title, intl.formatMessage({id: 'ContentBlock.editCardCheckboxText', defaultMessage: 'edit card text'}))\n                        if (saveType === 'onEnter' && title !== '' && props.onAddElement) {\n                            // Wait for the change to happen\n                            setTimeout(props.onAddElement, 100)\n                        }\n                        return\n                    }\n\n                    if (saveType === 'onEnter' && title !== '' && props.onAddElement) {\n                        props.onAddElement()\n                    }\n                }}\n                readonly={readonly}\n                spellCheck={true}\n            />\n        </div>\n    )\n}\n\ncontentRegistry.registerContentType({\n    type: 'checkbox',\n    getDisplayText: (intl) => intl.formatMessage({id: 'ContentBlock.checkbox', defaultMessage: 'checkbox'}),\n    getIcon: () => <CheckIcon/>,\n    createBlock: async () => {\n        return createCheckboxBlock()\n    },\n    createComponent: (block, readonly, onAddElement, onDeleteElement) => {\n        return (\n            <CheckboxElement\n                block={block}\n                readonly={readonly}\n                onAddElement={onAddElement}\n                onDeleteElement={onDeleteElement}\n            />\n        )\n    },\n})\n\nexport default React.memo(CheckboxElement)\n"
  },
  {
    "path": "webapp/src/components/content/contentElement.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {ReactElement, ReactNode} from 'react'\n\nimport '@testing-library/jest-dom'\n\nimport {render} from '@testing-library/react'\n\nimport {wrapIntl} from '../../testUtils'\n\nimport {ContentBlock} from '../../blocks/contentBlock'\n\nimport {CardDetailProvider} from '../cardDetail/cardDetailContext'\nimport {TestBlockFactory} from '../../test/testBlockFactory'\n\nimport ContentElement from './contentElement'\n\nconst board = TestBlockFactory.createBoard()\nconst card = TestBlockFactory.createCard(board)\nconst contentBlock: ContentBlock = {\n    id: 'test-id',\n    boardId: card.boardId,\n    parentId: card.id,\n    modifiedBy: 'test-user-id',\n    schema: 0,\n    type: 'checkbox',\n    title: 'test-title',\n    fields: {},\n    createdBy: 'test-user-id',\n    createAt: 0,\n    updateAt: 0,\n    deleteAt: 0,\n    limited: false,\n}\n\nconst wrap = (child: ReactNode): ReactElement => (\n    wrapIntl(\n        <CardDetailProvider card={card}>\n            {child}\n        </CardDetailProvider>,\n    )\n)\n\ndescribe('components/content/contentElement', () => {\n    it('should match snapshot for checkbox type', () => {\n        const {container} = render(wrap(\n            <ContentElement\n                block={contentBlock}\n                readonly={false}\n                cords={{x: 0}}\n            />,\n        ))\n        expect(container).toMatchSnapshot()\n    })\n\n    it('should return null for unknown type', () => {\n        const block: ContentBlock = {...contentBlock, type: 'unknown'}\n        const {container} = render(wrap(\n            <ContentElement\n                block={block}\n                readonly={false}\n                cords={{x: 0}}\n            />,\n        ))\n        expect(container).toBeEmptyDOMElement()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/content/contentElement.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {useCallback} from 'react'\n\nimport {ContentBlock} from '../../blocks/contentBlock'\nimport {Utils} from '../../utils'\n\nimport {useCardDetailContext} from '../cardDetail/cardDetailContext'\n\nimport {contentRegistry} from './contentRegistry'\n\n// Need to require here to prevent webpack from tree-shaking these away\n// TODO: Update webpack to avoid this\nimport './textElement'\nimport './imageElement'\nimport './dividerElement'\nimport './checkboxElement'\n\ntype Props = {\n    block: ContentBlock\n    readonly: boolean\n    cords: {x: number, y?: number, z?: number}\n}\n\nexport default function ContentElement(props: Props): JSX.Element|null {\n    const {block, readonly, cords} = props\n    const cardDetail = useCardDetailContext()\n\n    const handler = contentRegistry.getHandler(block.type)\n    if (!handler) {\n        Utils.logError(`ContentElement, unknown content type: ${block.type}`)\n        return null\n    }\n\n    const addElement = useCallback(() => {\n        const index = cords.x + 1\n        cardDetail.addBlock(handler, index, true)\n    }, [cardDetail, cords, handler])\n\n    const deleteElement = useCallback(() => {\n        const index = cords.x\n        cardDetail.deleteBlock(block, index)\n    }, [block, cords, cardDetail])\n\n    return handler.createComponent(block, readonly, addElement, deleteElement)\n}\n"
  },
  {
    "path": "webapp/src/components/content/contentRegistry.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport '../content/textElement'\nimport '../content/imageElement'\nimport '../content/dividerElement'\nimport '../content/checkboxElement'\n\nimport {ContentBlockTypes} from '../../blocks/block'\n\nimport {contentRegistry} from './contentRegistry'\n\nconst contentBlockTypes = ['text', 'image', 'divider', 'checkbox'] as ContentBlockTypes[]\n\ndescribe('components/content/ContentRegistry', () => {\n    test('have all contentTypes', () => {\n        expect(contentRegistry.contentTypes).toEqual(contentBlockTypes)\n    })\n    test.each(contentBlockTypes)('have a contentType %s', (content) => {\n        expect(contentRegistry.isContentType(content)).toBeTruthy()\n    })\n    test.each(contentBlockTypes)('get a contentHandler for %s', (content) => {\n        expect(contentRegistry.getHandler(content)).toBeTruthy()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/content/contentRegistry.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n/* eslint-disable react/require-optimization */\nimport {IntlShape} from 'react-intl'\n\nimport {BlockTypes} from '../../blocks/block'\nimport {ContentBlock} from '../../blocks/contentBlock'\nimport {Utils} from '../../utils'\n\nexport type ContentHandler = {\n    type: BlockTypes\n    getDisplayText: (intl: IntlShape) => string\n    getIcon: () => JSX.Element\n    createBlock: (boardId: string, intl: IntlShape) => Promise<ContentBlock>\n    createComponent: (block: ContentBlock, readonly: boolean, onAddElement?: () => void, onDeleteElement?: () => void) => JSX.Element\n}\n\nclass ContentRegistry {\n    private registry: Map<BlockTypes, ContentHandler> = new Map()\n\n    get contentTypes(): BlockTypes[] {\n        return [...this.registry.keys()]\n    }\n\n    registerContentType(entry: ContentHandler) {\n        if (this.isContentType(entry.type)) {\n            Utils.logError(`registerContentType, already registered type: ${entry.type}`)\n            return\n        }\n        this.registry.set(entry.type, entry)\n    }\n\n    isContentType(type: BlockTypes): boolean {\n        return this.registry.has(type)\n    }\n\n    getHandler(type: BlockTypes): ContentHandler | undefined {\n        return this.registry.get(type)\n    }\n}\n\nconst contentRegistry = new ContentRegistry()\n\nexport {contentRegistry}\n"
  },
  {
    "path": "webapp/src/components/content/dividerElement.scss",
    "content": ".DividerElement {\n    padding-top: 16px;\n    border-bottom: 1px solid rgba(var(--center-channel-color-rgb), 0.09);\n    margin-bottom: 17px;\n    flex-grow: 1;\n}\n"
  },
  {
    "path": "webapp/src/components/content/dividerElement.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {render} from '@testing-library/react'\n\nimport {wrapIntl} from '../../testUtils'\n\nimport DividerElement from './dividerElement'\n\ndescribe('components/content/DividerElement', () => {\n    test('should match snapshot', async () => {\n        const component = wrapIntl(<DividerElement/>)\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/content/dividerElement.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\n\nimport {DividerBlock, createDividerBlock} from '../../blocks/dividerBlock'\nimport DividerIcon from '../../widgets/icons/divider'\n\nimport {contentRegistry} from './contentRegistry'\nimport './dividerElement.scss'\n\nconst DividerElement = (): JSX.Element => <div className='DividerElement'/>\n\ncontentRegistry.registerContentType({\n    type: 'divider',\n    getDisplayText: (intl) => intl.formatMessage({id: 'ContentBlock.divider', defaultMessage: 'divider'}),\n    getIcon: () => <DividerIcon/>,\n    createBlock: async (): Promise<DividerBlock> => {\n        return createDividerBlock()\n    },\n    createComponent: () => <DividerElement/>,\n})\n\nexport default React.memo(DividerElement)\n"
  },
  {
    "path": "webapp/src/components/content/imageElement.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {render} from '@testing-library/react'\n\nimport {act} from 'react-dom/test-utils'\n\nimport {mocked} from 'jest-mock'\n\nimport {ImageBlock} from '../../blocks/imageBlock'\n\nimport {wrapIntl} from '../../testUtils'\n\nimport octoClient from '../../octoClient'\n\nimport ImageElement from './imageElement'\n\njest.mock('../../octoClient')\nconst mockedOcto = mocked(octoClient, true)\nmockedOcto.getFileAsDataUrl.mockResolvedValue({url: 'test.jpg'})\n\ndescribe('components/content/ImageElement', () => {\n    const defaultBlock: ImageBlock = {\n        id: 'test-id',\n        boardId: '1',\n        parentId: '',\n        modifiedBy: 'test-user-id',\n        schema: 0,\n        type: 'image',\n        title: 'test-title',\n        fields: {\n            fileId: 'test.jpg',\n        },\n        createdBy: 'test-user-id',\n        createAt: 0,\n        updateAt: 0,\n        deleteAt: 0,\n        limited: false,\n    }\n\n    test('should match snapshot', async () => {\n        const component = wrapIntl(\n            <ImageElement\n                block={defaultBlock}\n            />,\n        )\n        let imageContainer: Element | undefined\n        await act(async () => {\n            const {container} = render(component)\n            imageContainer = container\n        })\n        expect(imageContainer).toMatchSnapshot()\n    })\n\n    test('archived file', async () => {\n        mockedOcto.getFileAsDataUrl.mockResolvedValue({\n            archived: true,\n            name: 'Filename',\n            extension: '.txt',\n            size: 165002,\n        })\n\n        const component = wrapIntl(\n            <ImageElement\n                block={defaultBlock}\n            />,\n        )\n        let imageContainer: Element | undefined\n        await act(async () => {\n            const {container} = render(component)\n            imageContainer = container\n        })\n        expect(imageContainer).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/content/imageElement.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useEffect, useState} from 'react'\nimport {IntlShape} from 'react-intl'\n\nimport {ContentBlock} from '../../blocks/contentBlock'\nimport {ImageBlock, createImageBlock} from '../../blocks/imageBlock'\nimport octoClient from '../../octoClient'\nimport {Utils} from '../../utils'\nimport ImageIcon from '../../widgets/icons/image'\nimport {sendFlashMessage} from '../../components/flashMessages'\n\nimport {FileInfo} from '../../blocks/block'\n\nimport {contentRegistry} from './contentRegistry'\nimport ArchivedFile from './archivedFile/archivedFile'\n\ntype Props = {\n    block: ContentBlock\n}\n\nconst ImageElement = (props: Props): JSX.Element|null => {\n    const [imageDataUrl, setImageDataUrl] = useState<string|null>(null)\n    const [fileInfo, setFileInfo] = useState<FileInfo>({})\n\n    const {block} = props\n\n    useEffect(() => {\n        if (!imageDataUrl) {\n            const loadImage = async () => {\n                const fileURL = await octoClient.getFileAsDataUrl(block.boardId, props.block.fields.fileId)\n                setImageDataUrl(fileURL.url || '')\n                setFileInfo(fileURL)\n            }\n            loadImage()\n        }\n    }, [])\n\n    if (fileInfo.archived) {\n        return (\n            <ArchivedFile fileInfo={fileInfo}/>\n        )\n    }\n\n    if (!imageDataUrl) {\n        return null\n    }\n\n    return (\n        <img\n            className='ImageElement'\n            src={imageDataUrl}\n            alt={block.title}\n        />\n    )\n}\n\ncontentRegistry.registerContentType({\n    type: 'image',\n    getDisplayText: (intl: IntlShape) => intl.formatMessage({id: 'ContentBlock.image', defaultMessage: 'image'}),\n    getIcon: () => <ImageIcon/>,\n    createBlock: async (boardId: string, intl: IntlShape) => {\n        return new Promise<ImageBlock>(\n            (resolve) => {\n                Utils.selectLocalFile(async (file) => {\n                    const fileId = await octoClient.uploadFile(boardId, file)\n\n                    if (fileId) {\n                        const block = createImageBlock()\n                        block.fields.fileId = fileId || ''\n                        resolve(block)\n                    } else {\n                        sendFlashMessage({content: intl.formatMessage({id: 'createImageBlock.failed', defaultMessage: 'Unable to upload the file. File size limit reached.'}), severity: 'normal'})\n                    }\n                },\n                '.jpg,.jpeg,.png,.gif')\n            },\n        )\n\n        // return new ImageBlock()\n    },\n    createComponent: (block) => <ImageElement block={block}/>,\n})\n\nexport default React.memo(ImageElement)\n"
  },
  {
    "path": "webapp/src/components/content/textElement.scss",
    "content": ".TextElement {\n    display: inline;\n\n    .markdown-editor-error {\n        background-color: rgba(210, 75, 78, 0.08);\n        border: 1px solid var(--error-text);\n    }\n\n    .error-message {\n        color: var(--dnd-indicator, rgba(210, 75, 78, 1));\n        margin: 16px 0;\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/content/textElement.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {render, act} from '@testing-library/react'\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport '@testing-library/jest-dom'\n\nimport {mocked} from 'jest-mock'\n\nimport {TextBlock} from '../../blocks/textBlock'\n\nimport {mockDOM, wrapDNDIntl, mockStateStore} from '../../testUtils'\n\nimport {Utils} from '../../utils'\n\nimport {TestBlockFactory} from '../../test/testBlockFactory'\n\nimport TextElement from './textElement'\n\njest.mock('../../utils')\njest.mock('../../mutator')\njest.mock('draft-js/lib/generateRandomKey', () => () => '123')\nconst mockedUtils = mocked(Utils, true)\nmockedUtils.createGuid.mockReturnValue('test-id')\nconst defaultBlock: TextBlock = {\n    id: 'test-id',\n    boardId: 'test-id',\n    parentId: 'test-id',\n    modifiedBy: 'test-user-id',\n    schema: 0,\n    type: 'text',\n    title: '',\n    fields: {},\n    createdBy: 'test-user-id',\n    createAt: 0,\n    updateAt: 0,\n    deleteAt: 0,\n    limited: false,\n}\ndescribe('components/content/TextElement', () => {\n    beforeAll(() => {\n        mockDOM()\n    })\n\n    const board1 = TestBlockFactory.createBoard()\n    board1.id = 'board-id-1'\n\n    const state = {\n        users: {\n            boardUsers: {\n                1: {username: 'abc'},\n                2: {username: 'd'},\n                3: {username: 'e'},\n                4: {username: 'f'},\n                5: {username: 'g'},\n            },\n        },\n        boards: {\n            current: 'board-id-1',\n            boards: {\n                [board1.id]: board1,\n            },\n        },\n        clientConfig: {\n            value: {},\n        },\n    }\n    const store = mockStateStore([], state)\n\n    test('return a textElement', async () => {\n        const component = wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <TextElement\n                    block={defaultBlock}\n                    readonly={false}\n                />\n            </ReduxProvider>,\n        )\n\n        let container: Element | undefined\n        await act(async () => {\n            const result = render(component)\n            container = result.container\n        })\n        expect(container).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/content/textElement.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useState} from 'react'\nimport {useIntl} from 'react-intl'\n\nimport cx from 'classnames'\n\nimport {ContentBlock} from '../../blocks/contentBlock'\nimport {createTextBlock} from '../../blocks/textBlock'\nimport mutator from '../../mutator'\nimport TextIcon from '../../widgets/icons/text'\nimport {MarkdownEditor} from '../markdownEditor'\n\nimport {contentRegistry} from './contentRegistry'\n\nimport './textElement.scss'\n\ntype Props = {\n    block: ContentBlock\n    readonly: boolean\n}\n\nconst BlockTitleMaxBytes = 65535 // Maximum size of a TEXT column in MySQL\nconst BlockTitleMaxRunes = BlockTitleMaxBytes / 4 // Assume a worst-case representation\n\nconst TextElement = ({block, readonly}: Props): JSX.Element => {\n    const intl = useIntl()\n\n    const [isError, setIsError] = useState<boolean>(false)\n    const [blockTitle, setBlockTitle] = useState(block.title)\n\n    const textChangedHandler = (text: string): void => {\n        setBlockTitle(text)\n        const textSize = text.length\n        setIsError(textSize > BlockTitleMaxRunes)\n    }\n\n    const handleBlur = (text: string): void => {\n        if (text !== block.title || blockTitle !== block.title) {\n            const textSize = new Blob([text]).size\n            if (textSize <= BlockTitleMaxRunes) {\n                mutator.changeBlockTitle(block.boardId, block.id, block.title, text, intl.formatMessage({id: 'ContentBlock.editCardText', defaultMessage: 'edit card text'})).\n                    finally(() => {\n                        setIsError(false)\n                    })\n            }\n        }\n    }\n\n    return (\n        <div className='TextElement'>\n            <MarkdownEditor\n                className={cx({'markdown-editor-error': isError})}\n                text={blockTitle}\n                placeholderText={intl.formatMessage({id: 'ContentBlock.editText', defaultMessage: 'Edit text...'})}\n                onChange={textChangedHandler}\n                onBlur={handleBlur}\n                readonly={readonly}\n            />\n            {isError && <div className='error-message'>{intl.formatMessage({id: 'ContentBlock.errorText', defaultMessage: 'You\\'ve exceeded the size limit for this content. Please shorten it to avoid losing data.'})}</div>}\n        </div>\n    )\n}\n\ncontentRegistry.registerContentType({\n    type: 'text',\n    getDisplayText: (intl) => intl.formatMessage({id: 'ContentBlock.text', defaultMessage: 'text'}),\n    getIcon: () => <TextIcon/>,\n    createBlock: async () => {\n        return createTextBlock()\n    },\n    createComponent: (block, readonly) => {\n        return (\n            <TextElement\n                block={block}\n                readonly={readonly}\n            />\n        )\n    },\n})\n\nexport default React.memo(TextElement)\n"
  },
  {
    "path": "webapp/src/components/contentBlock.scss",
    "content": "@import '../styles/z-index';\n\n.ContentBlock {\n    // HACK: Fixes Chrome drag and drop preview\n    transform: translate3d(0, 0, 0);\n\n    .MenuWrapper,\n    .dnd-handle {\n        display: none;\n    }\n\n    &:hover,\n    &.menuOpened {\n        @include z-index(menu);\n\n        .MenuWrapper {\n            display: flex;\n        }\n\n        .dnd-handle {\n            display: flex;\n            opacity: 0.8;\n            height: 18px;\n        }\n    }\n\n    > * {\n        flex: 1 1 auto;\n        max-width: 100%;\n        min-height: 32px;\n    }\n\n    > .octo-block-margin {\n        flex: 0 0 auto;\n    }\n\n    .ImageElement {\n        pointer-events: none;\n        width: 100%;\n    }\n}\n\n.rowContents {\n    display: flex;\n    width: 100%;\n}\n\n.addToRow {\n    width: 10px;\n}\n"
  },
  {
    "path": "webapp/src/components/contentBlock.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport '@testing-library/jest-dom'\nimport {act, render, screen} from '@testing-library/react'\n\nimport React, {ReactNode, ReactElement} from 'react'\nimport {mocked} from 'jest-mock'\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport userEvent from '@testing-library/user-event'\n\nimport {Utils} from '../utils'\nimport {TestBlockFactory} from '../test/testBlockFactory'\nimport {mockDOM, wrapDNDIntl, mockStateStore} from '../testUtils'\n\nimport mutator from '../mutator'\n\nimport octoClient from '../octoClient'\n\nimport ContentBlock from './contentBlock'\nimport {CardDetailContext, CardDetailContextType} from './cardDetail/cardDetailContext'\n\njest.mock('../mutator')\njest.mock('../utils')\njest.mock('../octoClient')\njest.mock('draft-js/lib/generateRandomKey', () => () => '123')\n\nbeforeAll(mockDOM)\n\ndescribe('components/contentBlock', () => {\n    const mockedMutator = mocked(mutator, true)\n    const mockedUtils = mocked(Utils, true)\n    const mockedOcto = mocked(octoClient, true)\n\n    mockedUtils.createGuid.mockReturnValue('test-id')\n    mockedOcto.getFileAsDataUrl.mockResolvedValue({url: 'test.jpg'})\n\n    const board = TestBlockFactory.createBoard()\n    board.cardProperties = []\n    board.id = 'board-id'\n    const boardView = TestBlockFactory.createBoardView(board)\n    boardView.id = board.id\n    const card = TestBlockFactory.createCard(board)\n    card.id = board.id\n    card.createdBy = 'user-id-1'\n    const textBlock = TestBlockFactory.createText(card)\n    textBlock.id = 'textBlock-id'\n    const dividerBlock = TestBlockFactory.createDivider(card)\n    dividerBlock.id = 'dividerBlock-id'\n    const imageBlock = TestBlockFactory.createImage(card)\n    imageBlock.fields.fileId = 'test.jpg'\n    imageBlock.id = 'imageBlock-id'\n    const commentBlock = TestBlockFactory.createComment(card)\n    commentBlock.id = 'commentBlock-id'\n\n    card.fields.contentOrder = [textBlock.id, dividerBlock.id, commentBlock.id]\n    const cardDetailContextValue = (autoAdded: boolean): CardDetailContextType => ({\n        card,\n        lastAddedBlock: {\n            id: textBlock.id,\n            autoAdded,\n        },\n        deleteBlock: jest.fn(),\n        addBlock: jest.fn(),\n    })\n\n    const board1 = TestBlockFactory.createBoard()\n    board1.id = 'board-id-1'\n\n    const state = {\n        users: {\n            boardUsers: {\n                1: {username: 'abc'},\n                2: {username: 'd'},\n                3: {username: 'e'},\n                4: {username: 'f'},\n                5: {username: 'g'},\n            },\n        },\n        boards: {\n            current: 'board-id-1',\n            boards: {\n                [board1.id]: board1,\n            },\n        },\n        clientConfig: {\n            value: {},\n        },\n    }\n    const store = mockStateStore([], state)\n\n    const wrap = (child: ReactNode): ReactElement => (\n        wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <CardDetailContext.Provider value={cardDetailContextValue(true)}>\n                    {child}\n                </CardDetailContext.Provider>\n            </ReduxProvider>,\n        )\n    )\n\n    beforeEach(jest.clearAllMocks)\n\n    test('should match snapshot with textBlock', async () => {\n        let container\n        await act(async () => {\n            const result = render(wrap(\n                <ContentBlock\n                    block={textBlock}\n                    card={card}\n                    readonly={false}\n                    onDrop={jest.fn()}\n                    width={undefined}\n                    cords={{x: 1, y: 0, z: 0}}\n                />,\n            ))\n            container = result.container\n        })\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot with dividerBlock', async () => {\n        let container\n        await act(async () => {\n            const result = render(wrap(\n                <ContentBlock\n                    block={dividerBlock}\n                    card={card}\n                    readonly={false}\n                    onDrop={jest.fn()}\n                    width={undefined}\n                    cords={{x: 1, y: 0, z: 0}}\n                />,\n            ))\n            container = result.container\n        })\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot with commentBlock', async () => {\n        let container\n        await act(async () => {\n            const result = render(wrap(\n                <ContentBlock\n                    block={commentBlock}\n                    card={card}\n                    readonly={false}\n                    onDrop={jest.fn()}\n                    width={undefined}\n                    cords={{x: 1, y: 0, z: 0}}\n                />,\n            ))\n            container = result.container\n        })\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot with imageBlock', async () => {\n        let container\n        await act(async () => {\n            const result = render(wrap(\n                <ContentBlock\n                    block={imageBlock}\n                    card={card}\n                    readonly={false}\n                    onDrop={jest.fn()}\n                    width={undefined}\n                    cords={{x: 1, y: 0, z: 0}}\n                />,\n            ))\n            container = result.container\n        })\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot with commentBlock readonly', async () => {\n        let container\n        await act(async () => {\n            const result = render(wrap(\n                <ContentBlock\n                    block={commentBlock}\n                    card={card}\n                    readonly={true}\n                    onDrop={jest.fn()}\n                    width={undefined}\n                    cords={{x: 1, y: 0, z: 0}}\n                />,\n            ))\n            container = result.container\n        })\n        expect(container).toMatchSnapshot()\n    })\n\n    test('return commentBlock and click on menuwrapper', async () => {\n        let container\n        await act(async () => {\n            const result = render(wrap(\n                <ContentBlock\n                    block={commentBlock}\n                    card={card}\n                    readonly={false}\n                    onDrop={jest.fn()}\n                    width={undefined}\n                    cords={{x: 1, y: 0, z: 0}}\n                />,\n            ))\n            container = result.container\n        })\n        const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})\n        userEvent.click(buttonElement)\n\n        expect(container).toMatchSnapshot()\n    })\n\n    test('return commentBlock and click move up', async () => {\n        await act(async () => {\n            render(wrap(\n                <ContentBlock\n                    block={commentBlock}\n                    card={card}\n                    readonly={false}\n                    onDrop={jest.fn()}\n                    width={undefined}\n                    cords={{x: 1, y: 0, z: 0}}\n                />,\n            ))\n        })\n        const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})\n        userEvent.click(buttonElement)\n        const buttonMoveUp = screen.getByRole('button', {name: 'Move up'})\n        userEvent.click(buttonMoveUp)\n        expect(mockedUtils.arrayMove).toBeCalledTimes(1)\n        expect(mockedMutator.changeCardContentOrder).toBeCalledTimes(1)\n    })\n\n    test('return commentBlock and click move down', async () => {\n        await act(async () => {\n            render(wrap(\n                <ContentBlock\n                    block={commentBlock}\n                    card={card}\n                    readonly={false}\n                    onDrop={jest.fn()}\n                    width={undefined}\n                    cords={{x: 1, y: 0, z: 0}}\n                />,\n            ))\n        })\n        const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})\n        userEvent.click(buttonElement)\n        const buttonMoveUp = screen.getByRole('button', {name: 'Move down'})\n        userEvent.click(buttonMoveUp)\n        expect(mockedUtils.arrayMove).toBeCalledTimes(1)\n        expect(mockedMutator.changeCardContentOrder).toBeCalledTimes(1)\n    })\n\n    test('return commentBlock and click delete', async () => {\n        await act(async () => {\n            render(wrap(\n                <ContentBlock\n                    block={commentBlock}\n                    card={card}\n                    readonly={false}\n                    onDrop={jest.fn()}\n                    width={undefined}\n                    cords={{x: 1, y: -1, z: 0}}\n                />,\n            ))\n        })\n        const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})\n        userEvent.click(buttonElement)\n        const buttonMoveUp = screen.getByRole('button', {name: 'Delete'})\n        userEvent.click(buttonMoveUp)\n        expect(mockedMutator.performAsUndoGroup).toBeCalledTimes(1)\n    })\n\n    test('return commentBlock and click delete with another contentOrder', async () => {\n        card.fields.contentOrder = [[textBlock.id], [dividerBlock.id], [commentBlock.id]]\n        await act(async () => {\n            render(wrap(\n                <ContentBlock\n                    block={commentBlock}\n                    card={card}\n                    readonly={false}\n                    onDrop={jest.fn()}\n                    width={undefined}\n                    cords={{x: 1, y: 0, z: 0}}\n                />,\n            ))\n        })\n        const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})\n        userEvent.click(buttonElement)\n        const buttonMoveUp = screen.getByRole('button', {name: 'Delete'})\n        userEvent.click(buttonMoveUp)\n        expect(mockedMutator.performAsUndoGroup).toBeCalledTimes(1)\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/contentBlock.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React, {useState} from 'react'\nimport {useIntl} from 'react-intl'\n\nimport {Card} from '../blocks/card'\nimport {ContentBlock as ContentBlockType, IContentBlockWithCords} from '../blocks/contentBlock'\nimport mutator from '../mutator'\nimport {Utils} from '../utils'\nimport IconButton from '../widgets/buttons/iconButton'\nimport AddIcon from '../widgets/icons/add'\nimport DeleteIcon from '../widgets/icons/delete'\nimport OptionsIcon from '../widgets/icons/options'\nimport SortDownIcon from '../widgets/icons/sortDown'\nimport SortUpIcon from '../widgets/icons/sortUp'\nimport GripIcon from '../widgets/icons/grip'\nimport Menu from '../widgets/menu'\nimport MenuWrapper from '../widgets/menuWrapper'\nimport {useSortableWithGrip} from '../hooks/sortable'\nimport {Position} from '../components/cardDetail/cardDetailContents'\n\nimport ContentElement from './content/contentElement'\nimport AddContentMenuItem from './addContentMenuItem'\nimport {contentRegistry} from './content/contentRegistry'\n\nimport './contentBlock.scss'\n\ntype Props = {\n    block: ContentBlockType\n    card: Card\n    readonly: boolean\n    onDrop: (srctBlock: IContentBlockWithCords, dstBlock: IContentBlockWithCords, position: Position) => void\n    width?: number\n    cords: {x: number, y?: number, z?: number}\n}\n\nconst ContentBlock = (props: Props): JSX.Element => {\n    const {card, block, readonly, cords} = props\n    const intl = useIntl()\n    const [, , gripRef, itemRef] = useSortableWithGrip('content', {block, cords}, true, () => {})\n    const [, isOver2,, itemRef2] = useSortableWithGrip('content', {block, cords}, true, (src, dst) => props.onDrop(src, dst, 'right'))\n    const [, isOver3,, itemRef3] = useSortableWithGrip('content', {block, cords}, true, (src, dst) => props.onDrop(src, dst, 'left'))\n    const [menuOpened, setMenuOpened] = useState(false)\n\n    const index = cords.x\n    const colIndex = (cords.y || cords.y === 0) && cords.y > -1 ? cords.y : -1\n    const contentOrder: Array<string|string[]> = []\n    if (card.fields.contentOrder) {\n        for (const contentId of card.fields.contentOrder) {\n            if (typeof contentId === 'string') {\n                contentOrder.push(contentId)\n            } else {\n                contentOrder.push(contentId.slice())\n            }\n        }\n    }\n\n    let className = 'ContentBlock octo-block'\n    if (menuOpened) {\n        className += ' menuOpened'\n    }\n\n    return (\n        <div\n            className='rowContents'\n            style={{width: props.width + '%'}}\n        >\n            <div\n                ref={itemRef}\n                className={className}\n            >\n                <div className='octo-block-margin'>\n                    {!props.readonly &&\n                    <MenuWrapper onToggle={setMenuOpened}>\n                        <IconButton icon={<OptionsIcon/>}/>\n                        <Menu>\n                            {index > 0 &&\n                                <Menu.Text\n                                    id='moveUp'\n                                    name={intl.formatMessage({id: 'ContentBlock.moveUp', defaultMessage: 'Move up'})}\n                                    icon={<SortUpIcon/>}\n                                    onClick={() => {\n                                        Utils.arrayMove(contentOrder, index, index - 1)\n                                        mutator.changeCardContentOrder(props.card.boardId, card.id, card.fields.contentOrder, contentOrder)\n                                    }}\n                                />}\n                            {index < (contentOrder.length - 1) &&\n                                <Menu.Text\n                                    id='moveDown'\n                                    name={intl.formatMessage({id: 'ContentBlock.moveDown', defaultMessage: 'Move down'})}\n                                    icon={<SortDownIcon/>}\n                                    onClick={() => {\n                                        Utils.arrayMove(contentOrder, index, index + 1)\n                                        mutator.changeCardContentOrder(props.card.boardId, card.id, card.fields.contentOrder, contentOrder)\n                                    }}\n                                />}\n                            <Menu.SubMenu\n                                id='insertAbove'\n                                name={intl.formatMessage({id: 'ContentBlock.insertAbove', defaultMessage: 'Insert above'})}\n                                icon={<AddIcon/>}\n                                position='top'\n                            >\n                                {contentRegistry.contentTypes.map((type) => (\n                                    <AddContentMenuItem\n                                        key={type}\n                                        type={type}\n                                        card={card}\n                                        cords={cords}\n                                    />\n                                ))}\n                            </Menu.SubMenu>\n                            <Menu.Text\n                                icon={<DeleteIcon/>}\n                                id='delete'\n                                name={intl.formatMessage({id: 'ContentBlock.Delete', defaultMessage: 'Delete'})}\n                                onClick={() => {\n                                    const description = intl.formatMessage({id: 'ContentBlock.DeleteAction', defaultMessage: 'delete'})\n\n                                    if (colIndex > -1) {\n                                        (contentOrder[index] as string[]).splice(colIndex, 1)\n                                    } else {\n                                        contentOrder.splice(index, 1)\n                                    }\n\n                                    // If only one item in the row, convert form an array item to normal item ( [item] => item )\n                                    if (Array.isArray(contentOrder[index]) && contentOrder[index].length === 1) {\n                                        contentOrder[index] = contentOrder[index][0]\n                                    }\n\n                                    mutator.performAsUndoGroup(async () => {\n                                        await mutator.deleteBlock(block, description)\n                                        await mutator.changeCardContentOrder(props.card.boardId, card.id, card.fields.contentOrder, contentOrder, description)\n                                    })\n                                }}\n                            />\n                        </Menu>\n                    </MenuWrapper>\n                    }\n                    {!props.readonly &&\n                        <div\n                            ref={gripRef}\n                            className='dnd-handle'\n                        >\n                            <GripIcon/>\n                        </div>\n                    }\n                </div>\n                {!cords.y /* That is to say if cords.y === 0 or cords.y === undefined */ &&\n                    <div\n                        ref={itemRef3}\n                        className={`addToRow ${isOver3 ? 'dragover' : ''}`}\n                        style={{flex: 'none', height: '100%'}}\n                    />\n                }\n                <ContentElement\n                    block={block}\n                    readonly={readonly}\n                    cords={cords}\n                />\n            </div>\n            <div\n                ref={itemRef2}\n                className={`addToRow ${isOver2 ? 'dragover' : ''}`}\n            />\n        </div>\n    )\n}\n\nexport default React.memo(ContentBlock)\n"
  },
  {
    "path": "webapp/src/components/createCategory/__snapshots__/createCategory.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/createCategory/CreateCategory base case should match snapshot 1`] = `\n<div>\n  <div\n    class=\"Dialog dialog-back CreateCategoryModal size--medium\"\n  >\n    <div\n      class=\"backdrop\"\n    />\n    <div\n      class=\"wrapper\"\n    >\n      <div\n        class=\"dialog\"\n        role=\"dialog\"\n      >\n        <div\n          class=\"toolbar\"\n        >\n          <div>\n            <h1\n              class=\"dialog-title\"\n            >\n              <span>\n                title\n              </span>\n            </h1>\n          </div>\n          <div\n            class=\"toolbar--right\"\n          >\n            <button\n              aria-label=\"Close dialog\"\n              class=\"IconButton dialog__close size--medium\"\n              title=\"Close dialog\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-close CloseIcon\"\n              />\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"CreateCategory\"\n        >\n          <div\n            class=\"inputWrapper\"\n          >\n            <input\n              class=\"categoryNameInput\"\n              maxlength=\"100\"\n              placeholder=\"Name your category\"\n              type=\"text\"\n              value=\"\"\n            />\n          </div>\n          <div\n            class=\"createCategoryActions\"\n          >\n            <button\n              class=\"Button danger size--medium\"\n              type=\"button\"\n            >\n              <span>\n                Cancel\n              </span>\n            </button>\n            <button\n              class=\"Button size--medium\"\n              disabled=\"\"\n              type=\"button\"\n            >\n              <span>\n                Create\n              </span>\n            </button>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/createCategory/createCategory.scss",
    "content": ".CreateCategoryModal {\n    color: rgba(var(--center-channel-color-rgb));\n\n    .wrapper {\n        .dialog {\n            width: 600px;\n            height: auto;\n        }\n    }\n\n    .CreateCategory {\n        display: flex;\n        flex-direction: column;\n        padding: 0 32px 24px;\n        gap: 24px;\n\n        .inputWrapper {\n            position: relative;\n\n            .inputWrapper__close-wrapper {\n                position: absolute;\n                height: 100%;\n                top: 0;\n                right: 0;\n                display: flex;\n                align-items: center;\n                padding-right: 12px;\n            }\n\n            .CloseCircle {\n                cursor: pointer;\n                font-size: 18px;\n                color: rgba(var(--center-channel-color-rgb), 0.64);\n\n                &:hover {\n                    color: rgba(var(--center-channel-color-rgb), 0.8);\n                }\n            }\n        }\n\n        .categoryNameInput {\n            width: 100%;\n        }\n\n        input {\n            height: 48px;\n            font-size: 16px;\n            border-radius: 4px;\n            border: 1px solid rgba(var(--center-channel-color-rgb), 0.16);\n            background: var(--center-channel-bg);\n            color: var(--center-channel-color);\n            padding: 0 16px;\n            flex: 1;\n            transition: border 0.15s ease-in;\n\n            &:focus {\n                border-color: var(--button-bg);\n                box-shadow: inset 0 0 0 1px var(--button-bg);\n            }\n        }\n\n        .footer {\n            display: flex;\n            flex-direction: row;\n            justify-content: flex-end;\n        }\n\n        .createCategoryActions {\n            display: flex;\n            flex-direction: row;\n            justify-content: flex-end;\n            margin-top: auto;\n            gap: 12px;\n        }\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/createCategory/createCategory.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport {render} from '@testing-library/react'\n\nimport userEvent from '@testing-library/user-event'\n\nimport thunk from 'redux-thunk'\n\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport {mocked} from 'jest-mock'\n\nimport {mockStateStore, wrapIntl} from '../../testUtils'\n\nimport {IUser} from '../../user'\n\nimport mutator from '../../mutator'\n\nimport CreateCategory from './createCategory'\n\njest.mock('../../mutator')\nconst mockedMutator = mocked(mutator, true)\n\ndescribe('components/createCategory/CreateCategory', () => {\n    const me: IUser = {\n        id: 'user-id-1',\n        username: 'username_1',\n        email: '',\n        nickname: '',\n        firstname: '',\n        lastname: '',\n        props: {},\n        create_at: 0,\n        update_at: 0,\n        is_bot: false,\n        roles: 'system_user',\n        is_guest: false,\n    }\n\n    const state = {\n        teams: {\n            current: {id: 'team-id', title: 'Test Team'},\n        },\n        users: {\n            me,\n        },\n    }\n    const store = mockStateStore([thunk], state)\n\n    it('base case should match snapshot', () => {\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <CreateCategory\n                    onClose={jest.fn()}\n                    title={\n                        <span>{'title'}</span>\n                    }\n                />\n            </ReduxProvider>,\n        )\n\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n\n    it('should call onClose on being closed', () => {\n        const onCloseHandler = jest.fn()\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <CreateCategory\n                    onClose={onCloseHandler}\n                    title={\n                        <span>{'title'}</span>\n                    }\n                />\n            </ReduxProvider>,\n        )\n\n        const {container} = render(component)\n        const cancelBtn = container.querySelector('.createCategoryActions > .Button.danger')\n        expect(cancelBtn).toBeTruthy()\n        userEvent.click(cancelBtn as Element)\n        expect(onCloseHandler).toBeCalledTimes(1)\n\n        const closeBtn = container.querySelector('.toolbar .dialog__close')\n        expect(closeBtn).toBeTruthy()\n        userEvent.click(closeBtn as Element)\n        expect(onCloseHandler).toBeCalledTimes(2)\n    })\n\n    it('should call onCreate on pressing enter', () => {\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <CreateCategory\n                    onClose={jest.fn()}\n                    title={\n                        <span>{'title'}</span>\n                    }\n                />\n            </ReduxProvider>,\n        )\n\n        const {container} = render(component)\n        const inputField = container.querySelector('.categoryNameInput')\n        expect(inputField).toBeTruthy()\n        userEvent.type(inputField as Element, 'category name{enter}')\n        expect(mockedMutator.createCategory).toBeCalledWith({\n            name: 'category name',\n            teamID: 'team-id',\n            userID: 'user-id-1',\n        })\n    })\n\n    it('should show initial value', () => {\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <CreateCategory\n                    initialValue='Dwight prank ideas'\n                    onClose={jest.fn()}\n                    title={\n                        <span>{'title'}</span>\n                    }\n                />\n            </ReduxProvider>,\n        )\n\n        const {container} = render(component)\n        const inputField = container.querySelector('.categoryNameInput')\n        expect(inputField).toBeTruthy()\n        expect((inputField as HTMLInputElement).value).toBe('Dwight prank ideas')\n    })\n\n    it('should clear input field on clicking clear icon', () => {\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <CreateCategory\n                    initialValue='Dunder Mifflin'\n                    onClose={jest.fn()}\n                    title={\n                        <span>{'title'}</span>\n                    }\n                />\n            </ReduxProvider>,\n        )\n\n        const {container} = render(component)\n        const inputField = container.querySelector('.categoryNameInput')\n        expect(inputField).toBeTruthy()\n        expect((inputField as HTMLInputElement).value).toBe('Dunder Mifflin')\n\n        const clearBtn = container.querySelector('.clearBtn')\n        expect(clearBtn).toBeTruthy()\n        userEvent.click(clearBtn as Element)\n        expect((inputField as HTMLInputElement).value).toBe('')\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/createCategory/createCategory.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React, {useState, KeyboardEvent} from 'react'\n\nimport {useIntl} from 'react-intl'\n\nimport {IUser} from '../../user'\nimport {Category} from '../../store/sidebar'\nimport {getCurrentTeam} from '../../store/teams'\nimport mutator from '../../mutator'\nimport {useAppSelector} from '../../store/hooks'\nimport {\n    getMe,\n} from '../../store/users'\n\nimport {Utils} from '../../utils'\n\nimport Dialog from '../dialog'\nimport Button from '../../widgets/buttons/button'\n\nimport './createCategory.scss'\nimport CloseCircle from '../../widgets/icons/closeCircle'\n\ntype Props = {\n    boardCategoryId?: string\n    renameModal?: boolean\n    initialValue?: string\n    onClose: () => void\n    title: JSX.Element\n}\n\nconst CreateCategory = (props: Props): JSX.Element => {\n    const intl = useIntl()\n    const me = useAppSelector<IUser|null>(getMe)\n    const team = useAppSelector(getCurrentTeam)\n    const teamID = team?.id || ''\n    const placeholder = intl.formatMessage({id: 'Categories.CreateCategoryDialog.Placeholder', defaultMessage: 'Name your category'})\n    const cancelText = intl.formatMessage({id: 'Categories.CreateCategoryDialog.CancelText', defaultMessage: 'Cancel'})\n    const createText = intl.formatMessage({id: 'Categories.CreateCategoryDialog.CreateText', defaultMessage: 'Create'})\n    const updateText = intl.formatMessage({id: 'Categories.CreateCategoryDialog.UpdateText', defaultMessage: 'Update'})\n\n    const [name, setName] = useState(props.initialValue || '')\n\n    const handleKeypress = (e: KeyboardEvent) => {\n        if (e.key === 'Enter') {\n            onCreate(name)\n        }\n    }\n\n    const onCreate = async (categoryName: string) => {\n        if (!me) {\n            Utils.logError('me not initialized')\n            return\n        }\n\n        if (props.renameModal) {\n            const category: Category = {\n                name: categoryName,\n                id: props.boardCategoryId,\n                userID: me.id,\n                teamID,\n            } as Category\n\n            await mutator.updateCategory(category)\n        } else {\n            const category: Category = {\n                name: categoryName,\n                userID: me.id,\n                teamID,\n            } as Category\n\n            await mutator.createCategory(category)\n        }\n\n        props.onClose()\n    }\n\n    return (\n        <Dialog\n            title={props.title}\n            className='CreateCategoryModal'\n            onClose={props.onClose}\n        >\n            <div className='CreateCategory'>\n                <div className='inputWrapper'>\n                    <input\n                        className='categoryNameInput'\n                        type='text'\n                        placeholder={placeholder}\n                        value={name}\n                        onChange={(e) => setName(e.target.value)}\n                        autoFocus={true}\n                        maxLength={100}\n                        onKeyUp={handleKeypress}\n                    />\n                    {\n                        Boolean(name) &&\n                        <div\n                            className='clearBtn inputWrapper__close-wrapper'\n                            onClick={() => setName('')}\n                        >\n                            <CloseCircle/>\n                        </div>\n                    }\n                </div>\n                <div className='createCategoryActions'>\n                    <Button\n                        size={'medium'}\n                        danger={true}\n                        onClick={props.onClose}\n                    >\n                        {cancelText}\n                    </Button>\n                    <Button\n                        size={'medium'}\n                        filled={Boolean(name.trim())}\n                        onClick={() => onCreate(name.trim())}\n                        disabled={!(name.trim())}\n                    >\n                        {props.initialValue ? updateText : createText}\n                    </Button>\n                </div>\n            </div>\n        </Dialog>\n    )\n}\n\nexport default CreateCategory\n"
  },
  {
    "path": "webapp/src/components/dialog.scss",
    "content": "@import '../styles/z-index';\n\n.Dialog {\n    &.dialog-back {\n        @include z-index(dialog-back);\n        position: fixed;\n        top: 0;\n        left: 0;\n        right: 0;\n        bottom: 0;\n    }\n\n    &.size--small {\n        .dialog {\n            max-width: 512px;\n            width: 100%;\n            height: max-content;\n        }\n    }\n\n    .dialog-title {\n        margin: 0;\n        font-weight: 600;\n        font-size: 22px;\n        line-height: 28px;\n    }\n\n    .dialog-subtitle {\n        font-size: 12px;\n        margin-top: 8px;\n    }\n\n    .backdrop {\n        @include z-index(dialog-backdrop);\n        position: fixed;\n        width: 100%;\n        height: 100%;\n        background-color: rgba(var(--center-channel-color-rgb), 0.5);\n    }\n\n    .wrapper {\n        width: 100%;\n        height: 100%;\n        display: flex;\n        align-items: center;\n        justify-content: center;\n    }\n\n    .dialog {\n        position: relative;\n        display: flex;\n        flex-direction: column;\n        background-color: rgb(var(--center-channel-bg-rgb));\n        box-shadow: rgba(var(--center-channel-color-rgb), 0.1) 0 0 0 1px,\n            rgba(var(--center-channel-color-rgb), 0.1) 0 2px 4px;\n        border-radius: var(--modal-rad);\n        padding: 0;\n        -webkit-overflow-scrolling: touch;\n        overflow-x: hidden;\n        overflow-y: auto;\n\n        @media not screen and (max-width: 975px) {\n            margin: 72px auto;\n            max-width: 975px;\n            height: calc(100% - 144px);\n\n            .hideOnWidescreen {\n                /* Hide elements on larger screens */\n                display: none !important;\n            }\n        }\n\n        > * {\n            flex-shrink: 0;\n        }\n\n        > .banner {\n            background-color: rgba(230, 220, 192, 0.9);\n            text-align: center;\n            padding: 10px;\n            color: #222;\n        }\n\n        > .banner.error {\n            background-color: rgba(230, 192, 192, 0.9);\n        }\n\n        .IconButton {\n            color: rgba(var(--center-channel-color-rgb), 0.56);\n\n            &:hover {\n                color: rgba(var(--center-channel-color-rgb), 0.72);\n                background-color: rgba(var(--center-channel-color-rgb), 0.08);\n            }\n\n            &:active {\n                background-color: rgba(var(--button-bg-rgb), 0.08);\n                color: rgba(var(--button-bg-rgb), 1);\n            }\n        }\n\n        .toolbar {\n            display: flex;\n            flex-direction: row;\n            padding: 24px 32px;\n            justify-content: space-between;\n            align-items: flex-start;\n        }\n\n        .toolbar--right {\n            display: flex;\n            gap: 8px;\n            align-items: center;\n            height: 28px;\n            margin-right: -14px;\n        }\n    }\n\n    .cardToolbar {\n        width: 100%;\n        margin: 0 16px;\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/dialog.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport '@testing-library/jest-dom'\nimport {render, screen} from '@testing-library/react'\n\nimport React from 'react'\n\nimport userEvent from '@testing-library/user-event'\n\nimport {wrapDNDIntl} from '../testUtils'\n\nimport Menu from '../widgets/menu'\n\nimport OptionsIcon from '../widgets/icons/options'\n\nimport Dialog from './dialog'\n\ndescribe('components/dialog', () => {\n    beforeEach(jest.clearAllMocks)\n    test('should match snapshot', () => {\n        const {container} = render(wrapDNDIntl(\n            <Dialog\n                onClose={jest.fn()}\n            >\n                <div id='test'/>\n            </Dialog>,\n        ))\n        expect(container).toMatchSnapshot()\n    })\n    test('should return dialog and click onClose button', () => {\n        const onCloseMethod = jest.fn()\n        render(wrapDNDIntl(\n            <Dialog\n                onClose={onCloseMethod}\n            >\n                <div id='test'/>\n            </Dialog>,\n        ))\n        const buttonClose = screen.getByRole('button', {name: 'Close dialog'})\n        userEvent.click(buttonClose)\n        expect(onCloseMethod).toBeCalledTimes(1)\n    })\n    test('should return dialog and click to close on wrapper', () => {\n        const onCloseMethod = jest.fn()\n        const {container} = render(wrapDNDIntl(\n            <Dialog\n                onClose={onCloseMethod}\n            >\n                <Menu position='left'>\n                    <Menu.Text\n                        id='test'\n                        icon={<OptionsIcon/>}\n                        name='Test'\n                        onClick={async () => {\n                            jest.fn()\n                        }}\n                    />\n                </Menu>\n            </Dialog>,\n        ))\n        const buttonClose = container.querySelector('.wrapper')!\n        userEvent.click(buttonClose)\n        expect(onCloseMethod).toBeCalledTimes(1)\n    })\n\n    test('should return dialog and click on test button', () => {\n        const onTest = jest.fn()\n        render(wrapDNDIntl(\n            <Dialog\n                onClose={jest.fn()}\n                toolsMenu={<Menu position='left'>\n                    <Menu.Text\n                        id='test'\n                        icon={<OptionsIcon/>}\n                        name='Test'\n                        onClick={async () => {\n                            onTest()\n                        }}\n                    />\n                </Menu>}\n            >\n                <div id='test'/>\n            </Dialog>,\n        ))\n        const buttonMenu = screen.getByRole('button', {name: 'menuwrapper'})\n        userEvent.click(buttonMenu)\n        const buttonTest = screen.getByRole('button', {name: 'Test'})\n        userEvent.click(buttonTest)\n        expect(onTest).toBeCalledTimes(1)\n    })\n    test('should return dialog and click on cancel button', () => {\n        const {container} = render(wrapDNDIntl(\n            <Dialog\n                onClose={jest.fn()}\n                toolsMenu={<Menu position='left'>\n                    <Menu.Text\n                        id='test'\n                        icon={<OptionsIcon/>}\n                        name='Test'\n                        onClick={async () => {\n                            jest.fn()\n                        }}\n                    />\n                </Menu>}\n            >\n                <div id='test'/>\n            </Dialog>,\n        ))\n        const buttonMenu = screen.getByRole('button', {name: 'menuwrapper'})\n        userEvent.click(buttonMenu)\n        const buttonTest = screen.getByRole('button', {name: 'Cancel'})\n        userEvent.click(buttonTest)\n        expect(container).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/dialog.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useRef} from 'react'\nimport {useIntl} from 'react-intl'\nimport {useHotkeys} from 'react-hotkeys-hook'\n\nimport IconButton from '../widgets/buttons/iconButton'\nimport CloseIcon from '../widgets/icons/close'\nimport OptionsIcon from '../widgets/icons/options'\nimport MenuWrapper from '../widgets/menuWrapper'\nimport './dialog.scss'\n\ntype Props = {\n    children: React.ReactNode\n    size?: string\n    toolsMenu?: React.ReactNode // some dialogs may not  require a toolmenu\n    toolbar?: React.ReactNode\n    hideCloseButton?: boolean\n    className?: string\n    title?: JSX.Element\n    subtitle?: JSX.Element\n    onClose: () => void\n}\n\nconst Dialog = (props: Props) => {\n    const {toolsMenu, toolbar, title, subtitle, size} = props\n    const intl = useIntl()\n\n    const closeDialogText = intl.formatMessage({\n        id: 'Dialog.closeDialog',\n        defaultMessage: 'Close dialog',\n    })\n\n    useHotkeys('esc', () => props.onClose())\n\n    const isBackdropClickedRef = useRef(false)\n\n    return (\n        <div className={`Dialog dialog-back ${props.className} size--${size || 'medium'}`}>\n            <div className='backdrop'/>\n            <div\n                className='wrapper'\n                onClick={(e) => {\n                    e.stopPropagation()\n                    if (!isBackdropClickedRef.current) {\n                        return\n                    }\n                    isBackdropClickedRef.current = false\n                    props.onClose()\n                }}\n                onMouseDown={(e) => {\n                    if (e.target === e.currentTarget) {\n                        isBackdropClickedRef.current = true\n                    }\n                }}\n            >\n                <div\n                    role='dialog'\n                    className='dialog'\n                >\n                    <div className='toolbar'>\n                        <div>\n                            {<h1 className='dialog-title'>{title || ''}</h1>}\n                            {subtitle && <h5 className='dialog-subtitle'>{subtitle}</h5>}\n                        </div>\n                        <div className='toolbar--right'>\n                            {toolbar && <div className='d-flex'>{toolbar}</div>}\n                            {toolsMenu && <MenuWrapper>\n                                <IconButton\n                                    size='medium'\n                                    icon={<OptionsIcon/>}\n                                />\n                                {toolsMenu}\n                            </MenuWrapper>\n                            }\n                            {\n                                !props.hideCloseButton &&\n                                <IconButton\n                                    className='dialog__close'\n                                    onClick={props.onClose}\n                                    icon={<CloseIcon/>}\n                                    title={closeDialogText}\n                                    size='medium'\n                                />\n                            }\n                        </div>\n                    </div>\n                    {props.children}\n                </div>\n            </div>\n        </div>\n    )\n}\n\nexport default React.memo(Dialog)\n"
  },
  {
    "path": "webapp/src/components/flashMessages.scss",
    "content": "@import '../styles/z-index';\n\n.FlashMessages {\n    @include z-index(flash-messages);\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    position: fixed;\n    bottom: 48px;\n    left: 50%;\n    margin-left: -160px;\n    padding: 10px 20px;\n    width: 320px;\n    min-height: 48px;\n    color: rgba(var(--center-channel-bg-rgb), 1);\n    background-color: rgba(var(--center-channel-color-rgb), 0.8);\n    font-size: 16px;\n    font-weight: 600;\n    vertical-align: middle;\n    border-radius: 4px;\n\n    &.flashIn {\n        visibility: visible;\n        opacity: 1;\n    }\n\n    &.flashOut {\n        visibility: hidden;\n        opacity: 0;\n        transition: visibility 0s linear 200ms, opacity ease-in 200ms;\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/flashMessages.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport {render, act, screen} from '@testing-library/react'\n\nimport '@testing-library/jest-dom'\n\nimport userEvent from '@testing-library/user-event'\n\nimport {wrapIntl} from '../testUtils'\n\nimport {FlashMessages, sendFlashMessage} from './flashMessages'\n\njest.mock('../mutator')\n\nbeforeEach(() => {\n    jest.useFakeTimers()\n})\n\nafterEach(() => {\n    jest.clearAllTimers()\n})\n\ndescribe('components/flashMessages', () => {\n    test('renders a flash message with high severity', () => {\n        const {container} = render(\n            wrapIntl(<FlashMessages milliseconds={200}/>),\n        )\n\n        /**\n         * Check for high severity\n         */\n\n        act(() => {\n            sendFlashMessage({content: 'Mock Content', severity: 'high'})\n        })\n\n        expect(container).toMatchSnapshot()\n\n        act(() => {\n            jest.advanceTimersByTime(200)\n        })\n\n        expect(screen.queryByText('Mock Content')).toBeNull()\n    })\n\n    test('renders a flash message with normal severity', () => {\n        const {container} = render(\n            wrapIntl(<FlashMessages milliseconds={200}/>),\n        )\n\n        act(() => {\n            sendFlashMessage({content: 'Mock Content', severity: 'normal'})\n        })\n\n        expect(screen.getByText('Mock Content')).toHaveClass('normal')\n\n        expect(container).toMatchSnapshot()\n\n        act(() => {\n            jest.advanceTimersByTime(200)\n        })\n\n        expect(screen.queryByText('Mock Content')).toBeNull()\n    })\n\n    test('renders a flash message with low severity', () => {\n        const {container} = render(\n            wrapIntl(<FlashMessages milliseconds={200}/>),\n        )\n\n        act(() => {\n            sendFlashMessage({content: 'Mock Content', severity: 'low'})\n        })\n\n        expect(screen.getByText('Mock Content')).toHaveClass('low')\n\n        expect(container).toMatchSnapshot()\n\n        act(() => {\n            jest.advanceTimersByTime(200)\n        })\n\n        expect(screen.queryByText('Mock Content')).toBeNull()\n    })\n\n    test('renders a flash message with low severity and custom HTML in flash message', () => {\n        const {container} = render(\n            wrapIntl(<FlashMessages milliseconds={200}/>),\n        )\n\n        act(() => {\n            sendFlashMessage({content: <div data-testid='mock-test-id'>{'Mock Content'}</div>, severity: 'low'})\n        })\n\n        expect(screen.getByTestId('mock-test-id')).toBeVisible()\n\n        expect(container).toMatchSnapshot()\n\n        act(() => {\n            jest.advanceTimersByTime(200)\n        })\n\n        expect(screen.queryByText('Mock Content')).toBeNull()\n    })\n\n    test('renders a flash message with low severity and check onClick on flash works', () => {\n        const {container} = render(\n            wrapIntl(<FlashMessages milliseconds={200}/>),\n        )\n\n        act(() => {\n            sendFlashMessage({content: 'Mock Content', severity: 'low'})\n        })\n\n        userEvent.click(screen.getByText('Mock Content'))\n\n        expect(container).toMatchSnapshot()\n\n        act(() => {\n            jest.advanceTimersByTime(200)\n        })\n\n        expect(screen.queryByText('Mock Content')).toBeNull()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/flashMessages.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useState, useEffect} from 'react'\nimport {createNanoEvents} from 'nanoevents'\n\nimport './flashMessages.scss'\n\nexport type FlashMessage = {\n    content: React.ReactNode\n    severity: 'low' | 'normal' | 'high'\n}\n\nconst emitter = createNanoEvents()\n\nexport function sendFlashMessage(message: FlashMessage): void {\n    emitter.emit('message', message)\n}\n\ntype Props = {\n    milliseconds: number\n}\n\nexport const FlashMessages = React.memo((props: Props) => {\n    const [message, setMessage] = useState<FlashMessage|null>(null)\n    const [fadeOut, setFadeOut] = useState(false)\n    const [timeoutId, setTimeoutId] = useState<ReturnType<typeof setTimeout>|null>(null)\n\n    useEffect(() => {\n        let isSubscribed = true\n        emitter.on('message', (newMessage: FlashMessage) => {\n            if (isSubscribed) {\n                if (timeoutId) {\n                    clearTimeout(timeoutId)\n                    setTimeoutId(null)\n                }\n                setTimeoutId(setTimeout(handleFadeOut, props.milliseconds - 200))\n                setMessage(newMessage)\n            }\n        })\n        return () => {\n            isSubscribed = false\n        }\n    }, [])\n\n    const handleFadeOut = (): void => {\n        setFadeOut(true)\n        setTimeoutId(setTimeout(handleTimeout, 200))\n    }\n\n    const handleTimeout = (): void => {\n        setMessage(null)\n        setFadeOut(false)\n    }\n\n    const handleClick = (): void => {\n        if (timeoutId) {\n            clearTimeout(timeoutId)\n            setTimeoutId(null)\n        }\n        handleFadeOut()\n    }\n\n    if (!message) {\n        return null\n    }\n\n    return (\n        <div\n            className={'FlashMessages ' + message.severity + (fadeOut ? ' flashOut' : ' flashIn')}\n            onClick={handleClick}\n        >\n            {message.content}\n        </div>\n    )\n})\n"
  },
  {
    "path": "webapp/src/components/gallery/__snapshots__/gallery.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`src/components/gallery/Gallery limited card count check 1`] = `\n<div>\n  <div\n    class=\"Gallery\"\n  >\n    <div\n      class=\"GalleryCard selected\"\n      draggable=\"true\"\n      style=\"opacity: 1;\"\n    >\n      <div\n        aria-label=\"menuwrapper\"\n        class=\"MenuWrapper optionsMenu\"\n        role=\"button\"\n      >\n        <button\n          class=\"IconButton CardActionsMenuIcon\"\n          type=\"button\"\n        >\n          <i\n            class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n          />\n        </button>\n      </div>\n      <div\n        class=\"gallery-item\"\n      />\n    </div>\n    <div\n      class=\"GalleryCard\"\n      draggable=\"true\"\n      style=\"opacity: 1;\"\n    >\n      <div\n        aria-label=\"menuwrapper\"\n        class=\"MenuWrapper optionsMenu\"\n        role=\"button\"\n      >\n        <button\n          class=\"IconButton CardActionsMenuIcon\"\n          type=\"button\"\n        >\n          <i\n            class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n          />\n        </button>\n      </div>\n      <div\n        class=\"gallery-item\"\n      />\n    </div>\n    <div\n      class=\"octo-gallery-new\"\n    >\n      + New\n    </div>\n    <div\n      class=\"gallery-hidden-cards\"\n    >\n      <div\n        class=\"HiddenCardCount\"\n      >\n        <div\n          class=\"hidden-card-title\"\n        >\n          Cards hidden\n        </div>\n        <button\n          class=\"Button\"\n          title=\"hidden-card-count\"\n          type=\"button\"\n        >\n          <span>\n            2\n          </span>\n        </button>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`src/components/gallery/Gallery return Gallery and click new 1`] = `\n<div>\n  <div\n    class=\"Gallery\"\n  >\n    <div\n      class=\"GalleryCard selected\"\n      draggable=\"true\"\n      style=\"opacity: 1;\"\n    >\n      <div\n        aria-label=\"menuwrapper\"\n        class=\"MenuWrapper optionsMenu\"\n        role=\"button\"\n      >\n        <button\n          class=\"IconButton CardActionsMenuIcon\"\n          type=\"button\"\n        >\n          <i\n            class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n          />\n        </button>\n      </div>\n      <div\n        class=\"gallery-item\"\n      >\n        <div\n          class=\"DividerElement\"\n        />\n        <div\n          class=\"DividerElement\"\n        />\n      </div>\n    </div>\n    <div\n      class=\"GalleryCard\"\n      draggable=\"true\"\n      style=\"opacity: 1;\"\n    >\n      <div\n        aria-label=\"menuwrapper\"\n        class=\"MenuWrapper optionsMenu\"\n        role=\"button\"\n      >\n        <button\n          class=\"IconButton CardActionsMenuIcon\"\n          type=\"button\"\n        >\n          <i\n            class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n          />\n        </button>\n      </div>\n      <div\n        class=\"gallery-item\"\n      />\n    </div>\n    <div\n      class=\"octo-gallery-new\"\n    >\n      + New\n    </div>\n  </div>\n</div>\n`;\n\nexports[`src/components/gallery/Gallery return Gallery readonly 1`] = `\n<div>\n  <div\n    class=\"Gallery\"\n  >\n    <div\n      class=\"GalleryCard selected\"\n      draggable=\"true\"\n      style=\"opacity: 1;\"\n    >\n      <div\n        class=\"gallery-item\"\n      >\n        <div\n          class=\"DividerElement\"\n        />\n        <div\n          class=\"DividerElement\"\n        />\n      </div>\n    </div>\n    <div\n      class=\"GalleryCard\"\n      draggable=\"true\"\n      style=\"opacity: 1;\"\n    >\n      <div\n        class=\"gallery-item\"\n      />\n    </div>\n  </div>\n</div>\n`;\n\nexports[`src/components/gallery/Gallery should match snapshot 1`] = `\n<div>\n  <div\n    class=\"Gallery\"\n  >\n    <div\n      class=\"GalleryCard selected\"\n      draggable=\"true\"\n      style=\"opacity: 1;\"\n    >\n      <div\n        aria-label=\"menuwrapper\"\n        class=\"MenuWrapper override menuOpened optionsMenu\"\n        role=\"button\"\n      >\n        <button\n          class=\"IconButton CardActionsMenuIcon\"\n          type=\"button\"\n        >\n          <i\n            class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n          />\n        </button>\n        <div\n          class=\"Menu noselect left \"\n        >\n          <div\n            class=\"menu-contents\"\n          >\n            <div\n              class=\"menu-options\"\n            >\n              <div>\n                <div\n                  aria-label=\"Delete\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex\"\n                  >\n                    <div\n                      class=\"menu-option__icon\"\n                    >\n                      <i\n                        class=\"CompassIcon icon-trash-can-outline DeleteIcon trash-can-outline\"\n                      />\n                    </div>\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Delete\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  aria-label=\"Duplicate\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex\"\n                  >\n                    <div\n                      class=\"menu-option__icon\"\n                    >\n                      <i\n                        class=\"CompassIcon icon-content-copy content-copy\"\n                      />\n                    </div>\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Duplicate\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n              </div>\n              <div>\n                <div\n                  aria-label=\"Copy link\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex\"\n                  >\n                    <div\n                      class=\"menu-option__icon\"\n                    >\n                      <i\n                        class=\"CompassIcon icon-link-variant LinkIcon\"\n                      />\n                    </div>\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Copy link\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n              </div>\n              <div />\n            </div>\n            <div\n              class=\"menu-spacer hideOnWidescreen\"\n            />\n            <div\n              class=\"menu-options hideOnWidescreen\"\n            >\n              <div\n                aria-label=\"Cancel\"\n                class=\"MenuOption TextOption menu-option menu-cancel\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Cancel\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n      <div\n        class=\"gallery-item\"\n      >\n        <div\n          class=\"DividerElement\"\n        />\n        <div\n          class=\"DividerElement\"\n        />\n      </div>\n    </div>\n    <div\n      class=\"GalleryCard\"\n      draggable=\"true\"\n      style=\"opacity: 1;\"\n    >\n      <div\n        aria-label=\"menuwrapper\"\n        class=\"MenuWrapper optionsMenu\"\n        role=\"button\"\n      >\n        <button\n          class=\"IconButton CardActionsMenuIcon\"\n          type=\"button\"\n        >\n          <i\n            class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n          />\n        </button>\n      </div>\n      <div\n        class=\"gallery-item\"\n      />\n    </div>\n    <div\n      class=\"octo-gallery-new\"\n    >\n      + New\n    </div>\n  </div>\n</div>\n`;\n\nexports[`src/components/gallery/Gallery should match snapshot without permissions 1`] = `\n<div>\n  <div\n    class=\"Gallery\"\n  >\n    <div\n      class=\"GalleryCard selected\"\n      draggable=\"true\"\n      style=\"opacity: 1;\"\n    >\n      <div\n        aria-label=\"menuwrapper\"\n        class=\"MenuWrapper override menuOpened optionsMenu\"\n        role=\"button\"\n      >\n        <button\n          class=\"IconButton CardActionsMenuIcon\"\n          type=\"button\"\n        >\n          <i\n            class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n          />\n        </button>\n        <div\n          class=\"Menu noselect left \"\n        >\n          <div\n            class=\"menu-contents\"\n          >\n            <div\n              class=\"menu-options\"\n            >\n              <div />\n              <div>\n                <div\n                  aria-label=\"Copy link\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex\"\n                  >\n                    <div\n                      class=\"menu-option__icon\"\n                    >\n                      <i\n                        class=\"CompassIcon icon-link-variant LinkIcon\"\n                      />\n                    </div>\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Copy link\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n              </div>\n              <div />\n            </div>\n            <div\n              class=\"menu-spacer hideOnWidescreen\"\n            />\n            <div\n              class=\"menu-options hideOnWidescreen\"\n            >\n              <div\n                aria-label=\"Cancel\"\n                class=\"MenuOption TextOption menu-option menu-cancel\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Cancel\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n      <div\n        class=\"gallery-item\"\n      >\n        <div\n          class=\"DividerElement\"\n        />\n        <div\n          class=\"DividerElement\"\n        />\n      </div>\n    </div>\n    <div\n      class=\"GalleryCard\"\n      draggable=\"true\"\n      style=\"opacity: 1;\"\n    >\n      <div\n        aria-label=\"menuwrapper\"\n        class=\"MenuWrapper optionsMenu\"\n        role=\"button\"\n      >\n        <button\n          class=\"IconButton CardActionsMenuIcon\"\n          type=\"button\"\n        >\n          <i\n            class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n          />\n        </button>\n      </div>\n      <div\n        class=\"gallery-item\"\n      />\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/gallery/__snapshots__/galleryCard.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`src/components/gallery/GalleryCard with a comment content return GalleryCard with content readonly 1`] = `\n<div>\n  <div\n    class=\"GalleryCard selected\"\n    draggable=\"true\"\n    style=\"opacity: 1;\"\n  >\n    <div\n      class=\"gallery-item\"\n    />\n    <div\n      class=\"gallery-title\"\n    >\n      <div\n        class=\"octo-icon\"\n      >\n        i\n      </div>\n      <div\n        class=\"octo-titletext\"\n      >\n        title\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`src/components/gallery/GalleryCard with a comment content should match snapshot 1`] = `\n<div>\n  <div\n    class=\"GalleryCard selected\"\n    draggable=\"true\"\n    style=\"opacity: 1;\"\n  >\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper override menuOpened optionsMenu\"\n      role=\"button\"\n    >\n      <button\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n        />\n      </button>\n      <div\n        class=\"Menu noselect left \"\n      >\n        <div\n          class=\"menu-contents\"\n        >\n          <div\n            class=\"menu-options\"\n          >\n            <div>\n              <div\n                aria-label=\"Delete\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-trash-can-outline DeleteIcon trash-can-outline\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Delete\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                aria-label=\"Duplicate\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-content-copy content-copy\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Duplicate\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Copy link\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-link-variant LinkIcon\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Copy link\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div />\n          </div>\n          <div\n            class=\"menu-spacer hideOnWidescreen\"\n          />\n          <div\n            class=\"menu-options hideOnWidescreen\"\n          >\n            <div\n              aria-label=\"Cancel\"\n              class=\"MenuOption TextOption menu-option menu-cancel\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Cancel\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"gallery-item\"\n    />\n    <div\n      class=\"gallery-title\"\n    >\n      <div\n        class=\"octo-icon\"\n      >\n        i\n      </div>\n      <div\n        class=\"octo-titletext\"\n      >\n        title\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`src/components/gallery/GalleryCard with an image content should match snapshot 1`] = `\n<div>\n  <div\n    class=\"GalleryCard selected\"\n    draggable=\"true\"\n    style=\"opacity: 1;\"\n  >\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper override menuOpened optionsMenu\"\n      role=\"button\"\n    >\n      <button\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n        />\n      </button>\n      <div\n        class=\"Menu noselect left \"\n      >\n        <div\n          class=\"menu-contents\"\n        >\n          <div\n            class=\"menu-options\"\n          >\n            <div>\n              <div\n                aria-label=\"Delete\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-trash-can-outline DeleteIcon trash-can-outline\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Delete\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                aria-label=\"Duplicate\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-content-copy content-copy\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Duplicate\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Copy link\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-link-variant LinkIcon\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Copy link\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div />\n          </div>\n          <div\n            class=\"menu-spacer hideOnWidescreen\"\n          />\n          <div\n            class=\"menu-options hideOnWidescreen\"\n          >\n            <div\n              aria-label=\"Cancel\"\n              class=\"MenuOption TextOption menu-option menu-cancel\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Cancel\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"gallery-image\"\n    >\n      <img\n        alt=\"\"\n        class=\"ImageElement\"\n        src=\"test.jpg\"\n      />\n    </div>\n    <div\n      class=\"gallery-title\"\n    >\n      <div\n        class=\"octo-icon\"\n      >\n        i\n      </div>\n      <div\n        class=\"octo-titletext\"\n      >\n        title\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`src/components/gallery/GalleryCard with many contents return GalleryCard with contents readonly 1`] = `\n<div>\n  <div\n    class=\"GalleryCard selected\"\n    draggable=\"true\"\n    style=\"opacity: 1;\"\n  >\n    <div\n      class=\"gallery-item\"\n    >\n      <div\n        class=\"DividerElement\"\n      />\n    </div>\n    <div\n      class=\"gallery-title\"\n    >\n      <div\n        class=\"octo-icon\"\n      >\n        i\n      </div>\n      <div\n        class=\"octo-titletext\"\n      >\n        title\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`src/components/gallery/GalleryCard with many contents should match snapshot 1`] = `\n<div>\n  <div\n    class=\"GalleryCard selected\"\n    draggable=\"true\"\n    style=\"opacity: 1;\"\n  >\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper override menuOpened optionsMenu\"\n      role=\"button\"\n    >\n      <button\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n        />\n      </button>\n      <div\n        class=\"Menu noselect left \"\n      >\n        <div\n          class=\"menu-contents\"\n        >\n          <div\n            class=\"menu-options\"\n          >\n            <div>\n              <div\n                aria-label=\"Delete\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-trash-can-outline DeleteIcon trash-can-outline\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Delete\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                aria-label=\"Duplicate\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-content-copy content-copy\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Duplicate\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Copy link\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-link-variant LinkIcon\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Copy link\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div />\n          </div>\n          <div\n            class=\"menu-spacer hideOnWidescreen\"\n          />\n          <div\n            class=\"menu-options hideOnWidescreen\"\n          >\n            <div\n              aria-label=\"Cancel\"\n              class=\"MenuOption TextOption menu-option menu-cancel\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Cancel\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"gallery-item\"\n    >\n      <div\n        class=\"DividerElement\"\n      />\n    </div>\n    <div\n      class=\"gallery-title\"\n    >\n      <div\n        class=\"octo-icon\"\n      >\n        i\n      </div>\n      <div\n        class=\"octo-titletext\"\n      >\n        title\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`src/components/gallery/GalleryCard with many images content should match snapshot with only first image 1`] = `\n<div>\n  <div\n    class=\"GalleryCard selected\"\n    draggable=\"true\"\n    style=\"opacity: 1;\"\n  >\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper override menuOpened optionsMenu\"\n      role=\"button\"\n    >\n      <button\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n        />\n      </button>\n      <div\n        class=\"Menu noselect left \"\n      >\n        <div\n          class=\"menu-contents\"\n        >\n          <div\n            class=\"menu-options\"\n          >\n            <div>\n              <div\n                aria-label=\"Delete\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-trash-can-outline DeleteIcon trash-can-outline\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Delete\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                aria-label=\"Duplicate\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-content-copy content-copy\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Duplicate\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Copy link\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-link-variant LinkIcon\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Copy link\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div />\n          </div>\n          <div\n            class=\"menu-spacer hideOnWidescreen\"\n          />\n          <div\n            class=\"menu-options hideOnWidescreen\"\n          >\n            <div\n              aria-label=\"Cancel\"\n              class=\"MenuOption TextOption menu-option menu-cancel\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Cancel\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"gallery-image\"\n    >\n      <img\n        alt=\"\"\n        class=\"ImageElement\"\n        src=\"test.jpg\"\n      />\n    </div>\n    <div\n      class=\"gallery-title\"\n    >\n      <div\n        class=\"octo-icon\"\n      >\n        i\n      </div>\n      <div\n        class=\"octo-titletext\"\n      >\n        title\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`src/components/gallery/GalleryCard without block content return GalleryCard and cancel 1`] = `\n<div>\n  <div\n    class=\"GalleryCard selected\"\n    draggable=\"true\"\n    style=\"opacity: 1;\"\n  >\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper optionsMenu\"\n      role=\"button\"\n    >\n      <button\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n        />\n      </button>\n    </div>\n    <div\n      class=\"gallery-item\"\n    />\n    <div\n      class=\"gallery-title\"\n    >\n      <div\n        class=\"octo-icon\"\n      >\n        i\n      </div>\n      <div\n        class=\"octo-titletext\"\n      >\n        title\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`src/components/gallery/GalleryCard without block content return GalleryCard and copy link 1`] = `\n<div>\n  <div\n    class=\"GalleryCard selected\"\n    draggable=\"true\"\n    style=\"opacity: 1;\"\n  >\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper optionsMenu\"\n      role=\"button\"\n    >\n      <button\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n        />\n      </button>\n    </div>\n    <div\n      class=\"gallery-item\"\n    />\n    <div\n      class=\"gallery-title\"\n    >\n      <div\n        class=\"octo-icon\"\n      >\n        i\n      </div>\n      <div\n        class=\"octo-titletext\"\n      >\n        title\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`src/components/gallery/GalleryCard without block content return GalleryCard and delete card 1`] = `\n<div>\n  <div\n    class=\"GalleryCard selected\"\n    draggable=\"true\"\n    style=\"opacity: 1;\"\n  >\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper optionsMenu\"\n      role=\"button\"\n    >\n      <button\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n        />\n      </button>\n    </div>\n    <div\n      class=\"gallery-item\"\n    />\n    <div\n      class=\"gallery-title\"\n    >\n      <div\n        class=\"octo-icon\"\n      >\n        i\n      </div>\n      <div\n        class=\"octo-titletext\"\n      >\n        title\n      </div>\n    </div>\n  </div>\n  <div\n    class=\"Dialog dialog-back confirmation-dialog-box size--small\"\n  >\n    <div\n      class=\"backdrop\"\n    />\n    <div\n      class=\"wrapper\"\n    >\n      <div\n        class=\"dialog\"\n        role=\"dialog\"\n      >\n        <div\n          class=\"toolbar\"\n        >\n          <div>\n            <h1\n              class=\"dialog-title\"\n            />\n          </div>\n          <div\n            class=\"toolbar--right\"\n          >\n            <button\n              aria-label=\"Close dialog\"\n              title=\"Close dialog\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-close CloseIcon\"\n              />\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"box-area\"\n          title=\"Confirmation Dialog Box\"\n        >\n          <h3\n            class=\"text-heading5\"\n          >\n            Confirm card delete!\n          </h3>\n          <div\n            class=\"sub-text\"\n          />\n          <div\n            class=\"action-buttons\"\n          >\n            <button\n              title=\"Cancel\"\n              type=\"button\"\n            >\n              <span>\n                Cancel\n              </span>\n            </button>\n            <button\n              title=\"Delete\"\n              type=\"submit\"\n            >\n              <span>\n                Delete\n              </span>\n            </button>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`src/components/gallery/GalleryCard without block content return GalleryCard and duplicate card 1`] = `\n<div>\n  <div\n    class=\"GalleryCard selected\"\n    draggable=\"true\"\n    style=\"opacity: 1;\"\n  >\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper optionsMenu\"\n      role=\"button\"\n    >\n      <button\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n        />\n      </button>\n    </div>\n    <div\n      class=\"gallery-item\"\n    />\n    <div\n      class=\"gallery-title\"\n    >\n      <div\n        class=\"octo-icon\"\n      >\n        i\n      </div>\n      <div\n        class=\"octo-titletext\"\n      >\n        title\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`src/components/gallery/GalleryCard without block content should match snapshot 1`] = `\n<div>\n  <div\n    class=\"GalleryCard selected\"\n    draggable=\"true\"\n    style=\"opacity: 1;\"\n  >\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper override menuOpened optionsMenu\"\n      role=\"button\"\n    >\n      <button\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n        />\n      </button>\n      <div\n        class=\"Menu noselect left \"\n      >\n        <div\n          class=\"menu-contents\"\n        >\n          <div\n            class=\"menu-options\"\n          >\n            <div>\n              <div\n                aria-label=\"Delete\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-trash-can-outline DeleteIcon trash-can-outline\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Delete\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                aria-label=\"Duplicate\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-content-copy content-copy\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Duplicate\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Copy link\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-link-variant LinkIcon\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Copy link\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div />\n          </div>\n          <div\n            class=\"menu-spacer hideOnWidescreen\"\n          />\n          <div\n            class=\"menu-options hideOnWidescreen\"\n          >\n            <div\n              aria-label=\"Cancel\"\n              class=\"MenuOption TextOption menu-option menu-cancel\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Cancel\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"gallery-item\"\n    />\n    <div\n      class=\"gallery-title\"\n    >\n      <div\n        class=\"octo-icon\"\n      >\n        i\n      </div>\n      <div\n        class=\"octo-titletext\"\n      >\n        title\n      </div>\n    </div>\n    <div\n      class=\"gallery-props\"\n    >\n      <div\n        class=\"octo-tooltip tooltip-top\"\n        data-tooltip=\"testTemplateProperty\"\n      >\n        <div\n          class=\"octo-propertyvalue octo-propertyvalue--readonly\"\n        />\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/gallery/gallery.scss",
    "content": ".Gallery {\n    display: flex;\n    flex-wrap: wrap;\n    overflow: auto;\n    margin-top: 10px;\n\n    .octo-gallery-new {\n        border: 1px solid rgba(var(--center-channel-color-rgb), 0.09);\n        border-radius: var(--default-rad);\n        color: rgba(var(--center-channel-color-rgb), 0.3);\n        cursor: pointer;\n        width: 280px;\n        min-height: 160px;\n        display: flex;\n        flex-direction: column;\n        align-items: center;\n        justify-content: center;\n        margin-bottom: 10px;\n\n        &.selected {\n            background-color: rgba(90, 200, 255, 0.2);\n        }\n\n        &:hover {\n            background-color: rgba(var(--center-channel-color-rgb), 0.05);\n        }\n    }\n\n    .gallery-hidden-cards {\n        margin-left: 6px;\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/gallery/gallery.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {render, screen, fireEvent} from '@testing-library/react'\n\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport userEvent from '@testing-library/user-event'\n\nimport {mocked} from 'jest-mock'\n\nimport {wrapDNDIntl, mockStateStore, blocksById} from '../../testUtils'\n\nimport {TestBlockFactory} from '../../test/testBlockFactory'\n\nimport mutator from '../../mutator'\n\nimport Gallery from './gallery'\n\njest.mock('../../mutator')\nconst mockedMutator = mocked(mutator, true)\n\ndescribe('src/components/gallery/Gallery', () => {\n    const board = TestBlockFactory.createBoard()\n    const activeView = TestBlockFactory.createBoardView(board)\n    activeView.fields.sortOptions = []\n    const card = TestBlockFactory.createCard(board)\n    const card2 = TestBlockFactory.createCard(board)\n    const contents = [TestBlockFactory.createDivider(card), TestBlockFactory.createDivider(card), TestBlockFactory.createDivider(card2)]\n    const state = {\n        contents: {\n            contents: blocksById(contents),\n            contentsByCard: {\n                [card.id]: [contents[0], contents[1]],\n                [card2.id]: [contents[2]],\n            },\n        },\n        cards: {\n            current: '',\n            limitTimestamp: 0,\n            cards: {\n                [card.id]: card,\n            },\n            templates: {},\n            cardHiddenWarning: true,\n        },\n        teams: {\n            current: {id: 'team-id'},\n        },\n        boards: {\n            current: board.id,\n            boards: {\n                [board.id]: board,\n            },\n            myBoardMemberships: {\n                [board.id]: {userId: 'user_id_1', schemeAdmin: true},\n            },\n        },\n        comments: {\n            comments: {},\n        },\n        users: {\n            me: {\n                id: 'user_id_1',\n                props: {},\n            },\n        },\n    }\n    const store = mockStateStore([], state)\n    beforeEach(() => {\n        jest.clearAllMocks()\n    })\n    test('should match snapshot', () => {\n        const {container} = render(wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <Gallery\n                    board={board}\n                    cards={[card, card2]}\n                    activeView={activeView}\n                    readonly={false}\n                    addCard={jest.fn()}\n                    selectedCardIds={[card.id]}\n                    onCardClicked={jest.fn()}\n                    hiddenCardsCount={0}\n                    showHiddenCardCountNotification={jest.fn()}\n                />\n            </ReduxProvider>,\n        ))\n        const buttonElement = screen.getAllByRole('button', {name: 'menuwrapper'})[0]\n        userEvent.click(buttonElement)\n        expect(container).toMatchSnapshot()\n    })\n    test('should match snapshot without permissions', () => {\n        const localStore = mockStateStore([], {...state, teams: {current: undefined}})\n        const {container} = render(wrapDNDIntl(\n            <ReduxProvider store={localStore}>\n                <Gallery\n                    board={board}\n                    cards={[card, card2]}\n                    activeView={activeView}\n                    readonly={false}\n                    addCard={jest.fn()}\n                    selectedCardIds={[card.id]}\n                    onCardClicked={jest.fn()}\n                    hiddenCardsCount={0}\n                    showHiddenCardCountNotification={jest.fn()}\n                />\n            </ReduxProvider>,\n        ))\n        const buttonElement = screen.getAllByRole('button', {name: 'menuwrapper'})[0]\n        userEvent.click(buttonElement)\n        expect(container).toMatchSnapshot()\n    })\n    test('return Gallery and click new', () => {\n        const mockAddCard = jest.fn()\n        const {container} = render(wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <Gallery\n                    board={board}\n                    cards={[card, card2]}\n                    activeView={activeView}\n                    readonly={false}\n                    addCard={mockAddCard}\n                    selectedCardIds={[card.id]}\n                    onCardClicked={jest.fn()}\n                    hiddenCardsCount={0}\n                    showHiddenCardCountNotification={jest.fn()}\n                />\n            </ReduxProvider>,\n        ))\n        expect(container).toMatchSnapshot()\n\n        const elementNew = container.querySelector('.octo-gallery-new')!\n        expect(elementNew).toBeDefined()\n        userEvent.click(elementNew)\n        expect(mockAddCard).toBeCalledTimes(1)\n    })\n\n    test('return Gallery readonly', () => {\n        const {container} = render(wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <Gallery\n                    board={board}\n                    cards={[card, card2]}\n                    activeView={activeView}\n                    readonly={true}\n                    addCard={jest.fn()}\n                    selectedCardIds={[card.id]}\n                    onCardClicked={jest.fn()}\n                    hiddenCardsCount={0}\n                    showHiddenCardCountNotification={jest.fn()}\n                />\n            </ReduxProvider>,\n        ))\n        expect(container).toMatchSnapshot()\n    })\n    test('return Gallery and drag and drop card', async () => {\n        const {container} = render(wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <Gallery\n                    board={board}\n                    cards={[card, card2]}\n                    activeView={activeView}\n                    readonly={false}\n                    addCard={jest.fn()}\n                    selectedCardIds={[]}\n                    onCardClicked={jest.fn()}\n                    hiddenCardsCount={0}\n                    showHiddenCardCountNotification={jest.fn()}\n                />\n            </ReduxProvider>,\n        ))\n        const allGalleryCard = container.querySelectorAll('.GalleryCard')\n        const drag = allGalleryCard[0]\n        const drop = allGalleryCard[1]\n        fireEvent.dragStart(drag)\n        fireEvent.dragEnter(drop)\n        fireEvent.dragOver(drop)\n        fireEvent.drop(drop)\n        expect(mockedMutator.performAsUndoGroup).toBeCalledTimes(1)\n    })\n\n    test('limited card count check', () => {\n        const boardTest = TestBlockFactory.createBoard()\n        const card1 = TestBlockFactory.createCard(boardTest)\n        const card3 = TestBlockFactory.createCard(boardTest)\n        const stateTest = {\n            contents: {\n                contents: blocksById(contents),\n                contentsByCard: {\n                    [card.id]: [contents[0], contents[1]],\n                    [card2.id]: [contents[2]],\n                },\n            },\n            cards: {\n                current: '',\n                cards: {\n                    [card1.id]: card1,\n                    [card3.id]: card3,\n                },\n                templates: {},\n                cardHiddenWarning: true,\n                limitTimestamp: 2,\n            },\n            users: {\n                me: {\n                    id: 'user_id_1',\n                    props: {},\n                },\n            },\n            teams: {\n                current: {id: 'team-id'},\n            },\n            comments: {\n                comments: {},\n            },\n            boards: {\n                current: board.id,\n                boards: {\n                    [board.id]: board,\n                },\n                myBoardMemberships: {\n                    [board.id]: {userId: 'user_id_1', schemeAdmin: true},\n                },\n            },\n        }\n        const storeTest = mockStateStore([], stateTest)\n        const {container, getByTitle} = render(wrapDNDIntl(\n            <ReduxProvider store={storeTest}>\n                <Gallery\n                    board={boardTest}\n                    cards={[card1, card3]}\n                    activeView={activeView}\n                    readonly={false}\n                    addCard={jest.fn()}\n                    selectedCardIds={[card1.id]}\n                    onCardClicked={jest.fn()}\n                    hiddenCardsCount={2}\n                    showHiddenCardCountNotification={jest.fn()}\n                />\n            </ReduxProvider>,\n        ))\n        expect(getByTitle('hidden-card-count').innerHTML).toBe('<span>2</span>')\n        expect(container).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/gallery/gallery.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useMemo, useCallback} from 'react'\nimport {FormattedMessage} from 'react-intl'\n\nimport {Constants, Permission} from '../../constants'\nimport HiddenCardCount from '../../components/hiddenCardCount/hiddenCardCount'\n\nimport {Card} from '../../blocks/card'\nimport {Board, IPropertyTemplate} from '../../blocks/board'\nimport {BoardView} from '../../blocks/boardView'\nimport mutator from '../../mutator'\nimport {Utils} from '../../utils'\n\nimport BoardPermissionGate from '../permissions/boardPermissionGate'\n\nimport './gallery.scss'\nimport GalleryCard from './galleryCard'\n\ntype Props = {\n    board: Board\n    cards: Card[]\n    activeView: BoardView\n    readonly: boolean\n    addCard: (show: boolean) => Promise<void>\n    selectedCardIds: string[]\n    onCardClicked: (e: React.MouseEvent, card: Card) => void\n    hiddenCardsCount: number\n    showHiddenCardCountNotification: (show: boolean) => void\n}\n\nconst Gallery = (props: Props): JSX.Element => {\n    const {activeView, board, cards, hiddenCardsCount} = props\n    const visiblePropertyTemplates = useMemo(() => {\n        return board.cardProperties.filter(\n            (template: IPropertyTemplate) => activeView.fields.visiblePropertyIds.includes(template.id),\n        )\n    }, [board.cardProperties, activeView.fields.visiblePropertyIds])\n\n    const isManualSort = activeView.fields.sortOptions.length === 0\n\n    const onDropToCard = useCallback((srcCard: Card, dstCard: Card) => {\n        Utils.log(`onDropToCard: ${dstCard.title}`)\n        const {selectedCardIds} = props\n\n        const draggedCardIds = Array.from(new Set(selectedCardIds).add(srcCard.id))\n        const description = draggedCardIds.length > 1 ? `drag ${draggedCardIds.length} cards` : 'drag card'\n\n        // Update dstCard order\n        let cardOrder = Array.from(new Set([...activeView.fields.cardOrder, ...cards.map((o) => o.id)]))\n        const isDraggingDown = cardOrder.indexOf(srcCard.id) <= cardOrder.indexOf(dstCard.id)\n        cardOrder = cardOrder.filter((id) => !draggedCardIds.includes(id))\n        let destIndex = cardOrder.indexOf(dstCard.id)\n        if (isDraggingDown) {\n            destIndex += 1\n        }\n        cardOrder.splice(destIndex, 0, ...draggedCardIds)\n\n        mutator.performAsUndoGroup(async () => {\n            await mutator.changeViewCardOrder(board.id, activeView.id, activeView.fields.cardOrder, cardOrder, description)\n        })\n    }, [cards.map((o) => o.id).join(','), board.id, activeView.id, activeView.fields.cardOrder, props.selectedCardIds])\n\n    const visibleTitle = activeView.fields.visiblePropertyIds.includes(Constants.titleColumnId)\n    const visibleBadges = activeView.fields.visiblePropertyIds.includes(Constants.badgesColumnId)\n\n    return (\n\n        <div className='Gallery'>\n            {cards.filter((c) => c.boardId === board.id).map((card) => {\n                return (\n                    <GalleryCard\n                        key={card.id + card.updateAt}\n                        card={card}\n                        board={board}\n                        onClick={props.onCardClicked}\n                        visiblePropertyTemplates={visiblePropertyTemplates}\n                        visibleTitle={visibleTitle}\n                        visibleBadges={visibleBadges}\n                        isSelected={props.selectedCardIds.includes(card.id)}\n                        readonly={props.readonly}\n                        onDrop={onDropToCard}\n                        isManualSort={isManualSort}\n                    />\n                )\n            })}\n\n            {/* Add New row */}\n\n            {!props.readonly &&\n                <BoardPermissionGate permissions={[Permission.ManageBoardCards]}>\n                    <div\n                        className='octo-gallery-new'\n                        onClick={() => {\n                            props.addCard(true)\n                        }}\n                    >\n                        <FormattedMessage\n                            id='TableComponent.plus-new'\n                            defaultMessage='+ New'\n                        />\n                    </div>\n                </BoardPermissionGate>\n            }\n            {hiddenCardsCount > 0 &&\n            <div className='gallery-hidden-cards'>\n                <HiddenCardCount\n                    hiddenCardsCount={hiddenCardsCount}\n                    showHiddenCardNotification={props.showHiddenCardCountNotification}\n                />\n            </div>}\n        </div>\n    )\n}\n\nexport default Gallery\n"
  },
  {
    "path": "webapp/src/components/gallery/galleryCard.scss",
    "content": "@import '../../styles/z-index';\n\n.GalleryCard {\n    position: relative;\n    border: 1px solid rgba(var(--center-channel-color-rgb), 0.09);\n    border-radius: var(--default-rad);\n    width: 280px;\n    display: flex;\n    flex-direction: column;\n    margin-right: 10px;\n    margin-bottom: 10px;\n    cursor: pointer;\n\n    // HACK: Fixes Chrome drag and drop preview\n    transform: translate3d(0, 0, 0);\n\n    &.selected {\n        background-color: rgba(90, 200, 255, 0.2);\n    }\n\n    &:hover {\n        background-color: rgba(var(--center-channel-color-rgb), 0.05);\n\n        .optionsMenu {\n            display: block;\n        }\n    }\n\n    .optionsMenu {\n        @include z-index(gallery-options-menu);\n        background-color: rgb(var(--center-channel-bg-rgb));\n        border-radius: var(--default-rad);\n        display: none;\n        position: absolute;\n        right: 10px;\n        top: 10px;\n    }\n\n    .gallery-item {\n        background-color: rgba(var(--center-channel-color-rgb), 0.03);\n\n        flex-grow: 1;\n        overflow: hidden;\n        padding: 0 10px;\n        font-size: 0.7em;\n        opacity: 0.7;\n        max-height: 160px;\n        min-height: 160px;\n        pointer-events: none;\n\n        .CheckboxElement {\n            .Editable {\n                background: transparent;\n            }\n        }\n    }\n\n    .gallery-image {\n        flex-grow: 1;\n        overflow: hidden;\n        max-height: 160px;\n        min-height: 160px;\n        pointer-events: none;\n\n        .ImageElement {\n            width: 100%;\n        }\n    }\n\n    .gallery-title {\n        flex-grow: 0;\n        border-top: 1px solid rgba(var(--center-channel-color-rgb), 0.09);\n        margin: 0;\n        padding: 5px 10px;\n        display: flex;\n        overflow-wrap: anywhere;\n        font-weight: 600;\n\n        .octo-icon {\n            margin-right: 5px;\n        }\n\n        .octo-titletext {\n            display: -webkit-box; // stylelint-disable-line\n            overflow: hidden;\n            text-overflow: ellipsis;\n            -webkit-line-clamp: 5;\n            -webkit-box-orient: vertical; // stylelint-disable-line\n        }\n    }\n\n    .gallery-props {\n        display: flex;\n        flex-direction: column;\n        align-items: flex-start;\n        flex-grow: 0;\n        margin: 0;\n        padding: 5px 10px;\n        overflow-wrap: anywhere;\n\n        .octo-tooltip {\n            max-width: 100%;\n        }\n\n        .octo-icon {\n            margin-right: 5px;\n        }\n    }\n\n    .gallery-badges {\n        padding: 5px 10px;\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/gallery/galleryCard.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {act, render, screen} from '@testing-library/react'\n\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport userEvent from '@testing-library/user-event'\n\nimport {mocked} from 'jest-mock'\n\nimport {MockStoreEnhanced} from 'redux-mock-store'\n\nimport {wrapDNDIntl, mockStateStore} from '../../testUtils'\n\nimport {TestBlockFactory} from '../../test/testBlockFactory'\n\nimport mutator from '../../mutator'\n\nimport {Utils} from '../../utils'\n\nimport octoClient from '../../octoClient'\n\nimport GalleryCard from './galleryCard'\n\njest.mock('../../mutator')\njest.mock('../../utils')\njest.mock('../../octoClient')\n\ndescribe('src/components/gallery/GalleryCard', () => {\n    const mockedMutator = mocked(mutator, true)\n    const mockedUtils = mocked(Utils, true)\n    const mockedOcto = mocked(octoClient, true)\n    mockedOcto.getFileAsDataUrl.mockResolvedValue({url: 'test.jpg'})\n\n    const board = TestBlockFactory.createBoard()\n    board.id = 'boardId'\n\n    const activeView = TestBlockFactory.createBoardView(board)\n    activeView.fields.sortOptions = []\n\n    const card = TestBlockFactory.createCard(board)\n    card.id = 'cardId'\n\n    const contentImage = TestBlockFactory.createImage(card)\n    contentImage.id = 'contentId-image'\n    contentImage.fields.fileId = 'test.jpg'\n\n    const contentComment = TestBlockFactory.createComment(card)\n    contentComment.id = 'contentId-Comment'\n\n    let store: MockStoreEnhanced<unknown, unknown>\n\n    beforeEach(() => {\n        jest.clearAllMocks()\n    })\n\n    describe('without block content', () => {\n        beforeEach(() => {\n            const state = {\n                contents: {\n                    contents: {\n                    },\n                },\n                cards: {\n                    cards: {\n                        [card.id]: card,\n                    },\n                },\n                teams: {\n                    current: {id: 'team-id'},\n                },\n                boards: {\n                    current: board.id,\n                    boards: {\n                        [board.id]: board,\n                    },\n                    templates: [],\n                    myBoardMemberships: {\n                        [board.id]: {userId: 'user_id_1', schemeAdmin: true},\n                    },\n                },\n                comments: {\n                    comments: {},\n                    commentsByCard: {},\n                },\n                users: {\n                    me: {\n                        id: 'user_id_1',\n                        props: {},\n                    },\n                },\n            }\n            store = mockStateStore([], state)\n        })\n        test('should match snapshot', () => {\n            const {container} = render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <GalleryCard\n                        board={board}\n                        card={card}\n                        onClick={jest.fn()}\n                        visiblePropertyTemplates={[{id: card.id, name: 'testTemplateProperty', type: 'text', options: [{id: '1', value: 'testValue', color: 'blue'}]}]}\n                        visibleTitle={true}\n                        isSelected={true}\n                        visibleBadges={false}\n                        readonly={false}\n                        isManualSort={true}\n                        onDrop={jest.fn()}\n                    />\n                </ReduxProvider>,\n            ))\n            const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})\n            userEvent.click(buttonElement)\n            expect(container).toMatchSnapshot()\n        })\n        test('return GalleryCard and click on it', () => {\n            const mockedOnClick = jest.fn()\n            const {container} = render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <GalleryCard\n                        board={board}\n                        card={card}\n                        onClick={mockedOnClick}\n                        visiblePropertyTemplates={[]}\n                        visibleTitle={true}\n                        isSelected={true}\n                        visibleBadges={false}\n                        readonly={false}\n                        isManualSort={true}\n                        onDrop={jest.fn()}\n                    />\n                </ReduxProvider>,\n            ))\n            const galleryCardElement = container.querySelector('.GalleryCard')\n            userEvent.click(galleryCardElement!)\n            expect(mockedOnClick).toBeCalledTimes(1)\n        })\n        test('return GalleryCard and delete card', () => {\n            const {container} = render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <GalleryCard\n                        board={board}\n                        card={card}\n                        onClick={jest.fn()}\n                        visiblePropertyTemplates={[]}\n                        visibleTitle={true}\n                        isSelected={true}\n                        visibleBadges={false}\n                        readonly={false}\n                        isManualSort={true}\n                        onDrop={jest.fn()}\n                    />\n                </ReduxProvider>,\n            ))\n            const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})\n            userEvent.click(buttonElement)\n            const buttonDelete = screen.getByRole('button', {name: 'Delete'})\n            userEvent.click(buttonDelete)\n            expect(container).toMatchSnapshot()\n        })\n\n        test('return GalleryCard and duplicate card', () => {\n            const {container} = render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <GalleryCard\n                        board={board}\n                        card={card}\n                        onClick={jest.fn()}\n                        visiblePropertyTemplates={[]}\n                        visibleTitle={true}\n                        isSelected={true}\n                        visibleBadges={false}\n                        readonly={false}\n                        isManualSort={true}\n                        onDrop={jest.fn()}\n                    />\n                </ReduxProvider>,\n            ))\n            const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})\n            userEvent.click(buttonElement)\n            const buttonDuplicate = screen.getByRole('button', {name: 'Duplicate'})\n            userEvent.click(buttonDuplicate)\n            expect(container).toMatchSnapshot()\n            expect(mockedMutator.duplicateCard).toBeCalledTimes(1)\n            expect(mockedMutator.duplicateCard).toBeCalledWith(card.id, board.id)\n        })\n        test('return GalleryCard and copy link', () => {\n            const {container} = render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <GalleryCard\n                        board={board}\n                        card={card}\n                        onClick={jest.fn()}\n                        visiblePropertyTemplates={[]}\n                        visibleTitle={true}\n                        isSelected={true}\n                        visibleBadges={false}\n                        readonly={false}\n                        isManualSort={true}\n                        onDrop={jest.fn()}\n                    />\n                </ReduxProvider>,\n            ))\n            const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})\n            userEvent.click(buttonElement)\n            const buttonCopyLink = screen.getByRole('button', {name: 'Copy link'})\n            userEvent.click(buttonCopyLink)\n            expect(container).toMatchSnapshot()\n            expect(mockedUtils.copyTextToClipboard).toBeCalledTimes(1)\n        })\n        test('return GalleryCard and cancel', () => {\n            const {container} = render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <GalleryCard\n                        board={board}\n                        card={card}\n                        onClick={jest.fn()}\n                        visiblePropertyTemplates={[]}\n                        visibleTitle={true}\n                        isSelected={true}\n                        visibleBadges={false}\n                        readonly={false}\n                        isManualSort={true}\n                        onDrop={jest.fn()}\n                    />\n                </ReduxProvider>,\n            ))\n            const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})\n            userEvent.click(buttonElement)\n            const buttonCancel = screen.getByRole('button', {name: 'Cancel'})\n            userEvent.click(buttonCancel)\n            expect(container).toMatchSnapshot()\n        })\n    })\n    describe('with an image content', () => {\n        beforeEach(() => {\n            card.fields.contentOrder = [contentImage.id]\n            const state = {\n                contents: {\n                    contents: {\n                        [contentImage.id]: contentImage,\n                    },\n                    contentsByCard: {\n                        [card.id]: [contentImage],\n                    },\n                },\n                cards: {\n                    cards: {\n                        [card.id]: card,\n                    },\n                },\n                comments: {\n                    comments: {},\n                    commentsByCard: {},\n                },\n                teams: {\n                    current: {id: 'team-id'},\n                },\n                boards: {\n                    current: board.id,\n                    boards: {\n                        [board.id]: board,\n                    },\n                    templates: [],\n                    myBoardMemberships: {\n                        [board.id]: {userId: 'user_id_1', schemeAdmin: true},\n                    },\n                },\n                users: {\n                    me: {\n                        id: 'user_id_1',\n                        props: {},\n                    },\n                },\n            }\n            store = mockStateStore([], state)\n        })\n        test('should match snapshot', async () => {\n            const {container} = render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <GalleryCard\n                        board={board}\n                        card={card}\n                        onClick={jest.fn()}\n                        visiblePropertyTemplates={[]}\n                        visibleTitle={true}\n                        isSelected={true}\n                        visibleBadges={false}\n                        readonly={false}\n                        isManualSort={true}\n                        onDrop={jest.fn()}\n                    />\n                </ReduxProvider>,\n            ))\n            await act(async () => {\n                const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})\n                userEvent.click(buttonElement)\n            })\n            expect(container).toMatchSnapshot()\n        })\n    })\n\n    describe('with many images content', () => {\n        beforeEach(() => {\n            const contentImage2 = TestBlockFactory.createImage(card)\n            contentImage2.id = 'contentId-image2'\n            contentImage2.fields.fileId = 'test2.jpg'\n            card.fields.contentOrder = [contentImage.id, contentImage2.id]\n            const state = {\n                contents: {\n                    contents: {\n                        [contentImage.id]: [contentImage],\n                        [contentImage2.id]: [contentImage2],\n                    },\n                    contentsByCard: {\n                        [card.id]: [contentImage, contentImage2],\n                    },\n                },\n                cards: {\n                    cards: {\n                        [card.id]: card,\n                    },\n                },\n                comments: {\n                    comments: {},\n                    commentsByCard: {},\n                },\n                teams: {\n                    current: {id: 'team-id'},\n                },\n                boards: {\n                    current: board.id,\n                    boards: {\n                        [board.id]: board,\n                    },\n                    templates: [],\n                    myBoardMemberships: {\n                        [board.id]: {userId: 'user_id_1', schemeAdmin: true},\n                    },\n                },\n                users: {\n                    me: {\n                        id: 'user_id_1',\n                        props: {},\n                    },\n                },\n            }\n            store = mockStateStore([], state)\n        })\n        test('should match snapshot with only first image', async () => {\n            const {container} = render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <GalleryCard\n                        board={board}\n                        card={card}\n                        onClick={jest.fn()}\n                        visiblePropertyTemplates={[]}\n                        visibleTitle={true}\n                        isSelected={true}\n                        visibleBadges={false}\n                        readonly={false}\n                        isManualSort={true}\n                        onDrop={jest.fn()}\n                    />\n                </ReduxProvider>,\n            ))\n            await act(async () => {\n                const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})\n                userEvent.click(buttonElement)\n            })\n            expect(container).toMatchSnapshot()\n        })\n    })\n    describe('with a comment content', () => {\n        beforeEach(() => {\n            card.fields.contentOrder = [contentComment.id]\n            const state = {\n                contents: {\n                    contents: {\n                        [contentComment.id]: contentComment,\n                    },\n                    contentsByCard: {\n                        [card.id]: [contentComment],\n                    },\n                },\n                cards: {\n                    cards: {\n                        [card.id]: card,\n                    },\n                },\n                comments: {\n                    comments: {},\n                    commentsByCard: {},\n                },\n                teams: {\n                    current: {id: 'team-id'},\n                },\n                boards: {\n                    current: board.id,\n                    boards: {\n                        [board.id]: board,\n                    },\n                    templates: [],\n                    myBoardMemberships: {\n                        [board.id]: {userId: 'user_id_1', schemeAdmin: true},\n                    },\n                },\n                users: {\n                    me: {\n                        id: 'user_id_1',\n                        props: {},\n                    },\n                },\n            }\n            store = mockStateStore([], state)\n        })\n        test('should match snapshot', () => {\n            const {container} = render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <GalleryCard\n                        board={board}\n                        card={card}\n                        onClick={jest.fn()}\n                        visiblePropertyTemplates={[]}\n                        visibleTitle={true}\n                        isSelected={true}\n                        visibleBadges={false}\n                        readonly={false}\n                        isManualSort={true}\n                        onDrop={jest.fn()}\n                    />\n                </ReduxProvider>,\n            ))\n            const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})\n            userEvent.click(buttonElement)\n            expect(container).toMatchSnapshot()\n        })\n        test('return GalleryCard with content readonly', () => {\n            const {container} = render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <GalleryCard\n                        board={board}\n                        card={card}\n                        onClick={jest.fn()}\n                        visiblePropertyTemplates={[]}\n                        visibleTitle={true}\n                        isSelected={true}\n                        visibleBadges={false}\n                        readonly={true}\n                        isManualSort={true}\n                        onDrop={jest.fn()}\n                    />\n                </ReduxProvider>,\n            ))\n            expect(container).toMatchSnapshot()\n        })\n    })\n    describe('with many contents', () => {\n        const contentDivider = TestBlockFactory.createDivider(card)\n        contentDivider.id = 'contentId-Text2'\n        beforeEach(() => {\n            card.fields.contentOrder = [contentComment.id, contentDivider.id]\n            const state = {\n                contents: {\n                    contents: {\n                        [contentComment.id]: [contentComment, contentDivider],\n                    },\n                    contentsByCard: {\n                        [card.id]: [contentComment, contentDivider],\n                    },\n                },\n                cards: {\n                    cards: {\n                        [card.id]: card,\n                    },\n                },\n                comments: {\n                    comments: {},\n                    commentsByCard: {},\n                },\n                teams: {\n                    current: {id: 'team-id'},\n                },\n                boards: {\n                    current: board.id,\n                    boards: {\n                        [board.id]: board,\n                    },\n                    templates: [],\n                    myBoardMemberships: {\n                        [board.id]: {userId: 'user_id_1', schemeAdmin: true},\n                    },\n                },\n                users: {\n                    me: {\n                        id: 'user_id_1',\n                        props: {},\n                    },\n                },\n            }\n            store = mockStateStore([], state)\n        })\n        test('should match snapshot', () => {\n            const {container} = render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <GalleryCard\n                        board={board}\n                        card={card}\n                        onClick={jest.fn()}\n                        visiblePropertyTemplates={[]}\n                        visibleTitle={true}\n                        isSelected={true}\n                        visibleBadges={false}\n                        readonly={false}\n                        isManualSort={true}\n                        onDrop={jest.fn()}\n                    />\n                </ReduxProvider>,\n            ))\n            const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})\n            userEvent.click(buttonElement)\n            expect(container).toMatchSnapshot()\n        })\n        test('return GalleryCard with contents readonly', () => {\n            const {container} = render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <GalleryCard\n                        board={board}\n                        card={card}\n                        onClick={jest.fn()}\n                        visiblePropertyTemplates={[]}\n                        visibleTitle={true}\n                        isSelected={true}\n                        visibleBadges={false}\n                        readonly={true}\n                        isManualSort={true}\n                        onDrop={jest.fn()}\n                    />\n                </ReduxProvider>,\n            ))\n            expect(container).toMatchSnapshot()\n        })\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/gallery/galleryCard.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useMemo, useState, useCallback} from 'react'\nimport {useIntl, FormattedMessage} from 'react-intl'\n\nimport {Board, IPropertyTemplate} from '../../blocks/board'\nimport {Card} from '../../blocks/card'\nimport {ContentBlock} from '../../blocks/contentBlock'\nimport {useSortable} from '../../hooks/sortable'\nimport mutator from '../../mutator'\nimport {getCardContents} from '../../store/contents'\nimport {useAppSelector} from '../../store/hooks'\nimport TelemetryClient, {TelemetryActions, TelemetryCategory} from '../../telemetry/telemetryClient'\nimport MenuWrapper from '../../widgets/menuWrapper'\nimport Tooltip from '../../widgets/tooltip'\nimport {CardDetailProvider} from '../cardDetail/cardDetailContext'\nimport ContentElement from '../content/contentElement'\nimport ImageElement from '../content/imageElement'\nimport PropertyValueElement from '../propertyValueElement'\nimport './galleryCard.scss'\nimport CardBadges from '../cardBadges'\nimport CardActionsMenu from '../cardActionsMenu/cardActionsMenu'\nimport ConfirmationDialogBox, {ConfirmationDialogBoxProps} from '../confirmationDialogBox'\nimport CardActionsMenuIcon from '../cardActionsMenu/cardActionsMenuIcon'\n\ntype Props = {\n    board: Board\n    card: Card\n    onClick: (e: React.MouseEvent, card: Card) => void\n    visiblePropertyTemplates: IPropertyTemplate[]\n    visibleTitle: boolean\n    isSelected: boolean\n    visibleBadges: boolean\n    readonly: boolean\n    isManualSort: boolean\n    onDrop: (srcCard: Card, dstCard: Card) => void\n}\n\nconst GalleryCard = (props: Props) => {\n    const intl = useIntl()\n    const {card, board} = props\n    const [isDragging, isOver, cardRef] = useSortable('card', card, props.isManualSort && !props.readonly, props.onDrop)\n    const contents = useAppSelector(getCardContents(card.id))\n    const [showConfirmationDialogBox, setShowConfirmationDialogBox] = useState<boolean>(false)\n\n    const visiblePropertyTemplates = props.visiblePropertyTemplates || []\n\n    const handleDeleteCard = useCallback(() => {\n        mutator.deleteBlock(card, 'delete card')\n    }, [card, board.id])\n\n    const confirmDialogProps: ConfirmationDialogBoxProps = useMemo(() => {\n        return {\n            heading: intl.formatMessage({id: 'CardDialog.delete-confirmation-dialog-heading', defaultMessage: 'Confirm card delete!'}),\n            confirmButtonText: intl.formatMessage({id: 'CardDialog.delete-confirmation-dialog-button-text', defaultMessage: 'Delete'}),\n            onConfirm: handleDeleteCard,\n            onClose: () => {\n                setShowConfirmationDialogBox(false)\n            },\n        }\n    }, [handleDeleteCard])\n\n    const image: ContentBlock|undefined = useMemo(() => {\n        for (let i = 0; i < contents.length; ++i) {\n            if (Array.isArray(contents[i])) {\n                return (contents[i] as ContentBlock[]).find((c) => c.type === 'image')\n            } else if ((contents[i] as ContentBlock).type === 'image') {\n                return contents[i] as ContentBlock\n            }\n        }\n        return undefined\n    }, [contents])\n\n    let className = props.isSelected ? 'GalleryCard selected' : 'GalleryCard'\n    if (isOver) {\n        className += ' dragover'\n    }\n\n    return (\n        <>\n            <div\n                className={className}\n                onClick={(e: React.MouseEvent) => props.onClick(e, card)}\n                style={{opacity: isDragging ? 0.5 : 1}}\n                ref={cardRef}\n            >\n                {!props.readonly &&\n                    <MenuWrapper\n                        className='optionsMenu'\n                        stopPropagationOnToggle={true}\n                    >\n                        <CardActionsMenuIcon/>\n                        <CardActionsMenu\n                            cardId={card!.id}\n                            boardId={card!.boardId}\n                            onClickDelete={() => setShowConfirmationDialogBox(true)}\n                            onClickDuplicate={() => {\n                                TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.DuplicateCard, {board: board.id, card: card.id})\n                                mutator.duplicateCard(card.id, board.id)\n                            }}\n                        />\n                    </MenuWrapper>\n                }\n\n                {image &&\n                    <div className='gallery-image'>\n                        <ImageElement block={image}/>\n                    </div>}\n                {!image &&\n                    <CardDetailProvider card={card}>\n                        <div className='gallery-item'>\n                            {contents.map((block) => {\n                                if (Array.isArray(block)) {\n                                    return block.map((b) => (\n                                        <ContentElement\n                                            key={b.id}\n                                            block={b}\n                                            readonly={true}\n                                            cords={{x: 0}}\n                                        />\n                                    ))\n                                }\n\n                                return (\n                                    <ContentElement\n                                        key={block.id}\n                                        block={block}\n                                        readonly={true}\n                                        cords={{x: 0}}\n                                    />\n                                )\n                            })}\n                        </div>\n                    </CardDetailProvider>}\n                {props.visibleTitle &&\n                    <div className='gallery-title'>\n                        { card.fields.icon ? <div className='octo-icon'>{card.fields.icon}</div> : undefined }\n                        <div\n                            key='__title'\n                            className='octo-titletext'\n                        >\n                            {card.title ||\n                                <FormattedMessage\n                                    id='KanbanCard.untitled'\n                                    defaultMessage='Untitled'\n                                />}\n                        </div>\n                    </div>}\n                {visiblePropertyTemplates.length > 0 &&\n                    <div className='gallery-props'>\n                        {visiblePropertyTemplates.map((template) => (\n                            <Tooltip\n                                key={template.id}\n                                title={template.name}\n                                placement='top'\n                            >\n                                <PropertyValueElement\n                                    board={board}\n                                    readOnly={true}\n                                    card={card}\n                                    propertyTemplate={template}\n                                    showEmptyPlaceholder={false}\n                                />\n                            </Tooltip>\n                        ))}\n                    </div>}\n                {props.visibleBadges &&\n                    <CardBadges\n                        card={card}\n                        className='gallery-badges'\n                    />}\n            </div>\n            {showConfirmationDialogBox && <ConfirmationDialogBox dialogBox={confirmDialogProps}/>}\n        </>\n    )\n}\n\nexport default React.memo(GalleryCard)\n"
  },
  {
    "path": "webapp/src/components/globalHeader/__snapshots__/globalHeader.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/sidebar/GlobalHeader header menu should match snapshot 1`] = `\n<div>\n  <div\n    class=\"GlobalHeaderComponent\"\n  >\n    <span\n      class=\"spacer\"\n    />\n    <a\n      class=\"GlobalHeaderComponent__button help-button\"\n      href=\"https://www.focalboard.com/fwlink/doc-boards.html?v=8.0.0\"\n      rel=\"noreferrer\"\n      target=\"_blank\"\n    >\n      <i\n        class=\"CompassIcon icon-help-circle-outline HelpIcon\"\n      />\n    </a>\n    <div\n      class=\"GlobalHeaderSettingsMenu\"\n    >\n      <div\n        aria-label=\"menuwrapper\"\n        class=\"MenuWrapper\"\n        role=\"button\"\n      >\n        <div\n          class=\"GlobalHeaderComponent__button menu-entry\"\n        >\n          <i\n            class=\"CompassIcon icon-settings-outline SettingsIcon\"\n          />\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/globalHeader/__snapshots__/globalHeaderSettingsMenu.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/sidebar/GlobalHeaderSettingsMenu imports menu open should match snapshot 1`] = `\n<div>\n  <div\n    class=\"GlobalHeaderSettingsMenu\"\n  >\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper override menuOpened\"\n      role=\"button\"\n    >\n      <div\n        class=\"GlobalHeaderComponent__button menu-entry\"\n      >\n        <i\n          class=\"CompassIcon icon-settings-outline SettingsIcon\"\n        />\n      </div>\n      <div\n        class=\"Menu noselect left \"\n      >\n        <div\n          class=\"menu-contents\"\n        >\n          <div\n            class=\"menu-options\"\n          >\n            <div>\n              <div\n                class=\"MenuOption SubMenuOption menu-option open-left menu-option-active\"\n                id=\"import\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n                <div\n                  class=\"menu-name\"\n                >\n                  Import\n                </div>\n                <i\n                  class=\"CompassIcon icon-chevron-right\"\n                />\n                <div\n                  class=\"SubMenu Menu noselect left-bottom\"\n                >\n                  <div\n                    class=\"menu-contents\"\n                  >\n                    <div\n                      class=\"menu-options\"\n                    >\n                      <div\n                        aria-label=\"Import archive\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            Import archive\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                      <div\n                        aria-label=\"Trello\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            Trello\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                      <div\n                        aria-label=\"Asana\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            Asana\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                      <div\n                        aria-label=\"Notion\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            Notion\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                      <div\n                        aria-label=\"Jira\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            Jira\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                      <div\n                        aria-label=\"Todoist\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            Todoist\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                    </div>\n                    <div\n                      class=\"menu-spacer hideOnWidescreen\"\n                    />\n                    <div\n                      class=\"menu-options hideOnWidescreen\"\n                    >\n                      <div\n                        aria-label=\"Cancel\"\n                        class=\"MenuOption TextOption menu-option menu-cancel\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            Cancel\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                    </div>\n                  </div>\n                </div>\n              </div>\n            </div>\n            <div>\n              <div\n                class=\"MenuOption SubMenuOption menu-option open-left\"\n                id=\"lang\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n                <div\n                  class=\"menu-name\"\n                >\n                  Set language\n                </div>\n                <i\n                  class=\"CompassIcon icon-chevron-right\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Random icons\"\n                class=\"MenuOption SwitchOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n                <div\n                  class=\"menu-name\"\n                >\n                  Random icons\n                </div>\n                <div\n                  class=\"Switch override-main size--small on\"\n                >\n                  <div\n                    class=\"octo-switch-inner\"\n                  />\n                </div>\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Product tour\"\n                class=\"MenuOption TextOption menu-option product-tour\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Product tour\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"menu-spacer hideOnWidescreen\"\n          />\n          <div\n            class=\"menu-options hideOnWidescreen\"\n          >\n            <div\n              aria-label=\"Cancel\"\n              class=\"MenuOption TextOption menu-option menu-cancel\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Cancel\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/sidebar/GlobalHeaderSettingsMenu languages menu open should match snapshot 1`] = `\n<div>\n  <div\n    class=\"GlobalHeaderSettingsMenu\"\n  >\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper override menuOpened\"\n      role=\"button\"\n    >\n      <div\n        class=\"GlobalHeaderComponent__button menu-entry\"\n      >\n        <i\n          class=\"CompassIcon icon-settings-outline SettingsIcon\"\n        />\n      </div>\n      <div\n        class=\"Menu noselect left \"\n      >\n        <div\n          class=\"menu-contents\"\n        >\n          <div\n            class=\"menu-options\"\n          >\n            <div>\n              <div\n                class=\"MenuOption SubMenuOption menu-option open-left\"\n                id=\"import\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n                <div\n                  class=\"menu-name\"\n                >\n                  Import\n                </div>\n                <i\n                  class=\"CompassIcon icon-chevron-right\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                class=\"MenuOption SubMenuOption menu-option open-left menu-option-active\"\n                id=\"lang\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n                <div\n                  class=\"menu-name\"\n                >\n                  Set language\n                </div>\n                <i\n                  class=\"CompassIcon icon-chevron-right\"\n                />\n                <div\n                  class=\"SubMenu Menu noselect left-bottom\"\n                >\n                  <div\n                    class=\"menu-contents\"\n                  >\n                    <div\n                      class=\"menu-options\"\n                    >\n                      <div\n                        aria-label=\"English\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            English\n                          </div>\n                        </div>\n                        <svg\n                          class=\"CheckIcon Icon\"\n                          viewBox=\"0 0 100 100\"\n                          xmlns=\"http://www.w3.org/2000/svg\"\n                        >\n                          <polyline\n                            points=\"20,60 40,80 80,40\"\n                          />\n                        </svg>\n                      </div>\n                      <div\n                        aria-label=\"Español\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            Español\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                      <div\n                        aria-label=\"Deutsch\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            Deutsch\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                      <div\n                        aria-label=\"日本語\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            日本語\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                      <div\n                        aria-label=\"Français\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            Français\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                      <div\n                        aria-label=\"Nederlands\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            Nederlands\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                      <div\n                        aria-label=\"Pусский\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            Pусский\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                      <div\n                        aria-label=\"中文 (繁體)\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            中文 (繁體)\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                      <div\n                        aria-label=\"中文 (简体)\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            中文 (简体)\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                      <div\n                        aria-label=\"Türkçe\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            Türkçe\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                      <div\n                        aria-label=\"Occitan\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            Occitan\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                      <div\n                        aria-label=\"Português (Brasil)\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            Português (Brasil)\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                      <div\n                        aria-label=\"Català\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            Català\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                      <div\n                        aria-label=\"Ελληνικά\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            Ελληνικά\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                      <div\n                        aria-label=\"bahasa Indonesia\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            bahasa Indonesia\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                      <div\n                        aria-label=\"Italiano\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            Italiano\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                      <div\n                        aria-label=\"Svenska\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            Svenska\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                    </div>\n                    <div\n                      class=\"menu-spacer hideOnWidescreen\"\n                    />\n                    <div\n                      class=\"menu-options hideOnWidescreen\"\n                    >\n                      <div\n                        aria-label=\"Cancel\"\n                        class=\"MenuOption TextOption menu-option menu-cancel\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            Cancel\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                    </div>\n                  </div>\n                </div>\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Random icons\"\n                class=\"MenuOption SwitchOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n                <div\n                  class=\"menu-name\"\n                >\n                  Random icons\n                </div>\n                <div\n                  class=\"Switch override-main size--small on\"\n                >\n                  <div\n                    class=\"octo-switch-inner\"\n                  />\n                </div>\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Product tour\"\n                class=\"MenuOption TextOption menu-option product-tour\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Product tour\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"menu-spacer hideOnWidescreen\"\n          />\n          <div\n            class=\"menu-options hideOnWidescreen\"\n          >\n            <div\n              aria-label=\"Cancel\"\n              class=\"MenuOption TextOption menu-option menu-cancel\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Cancel\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/sidebar/GlobalHeaderSettingsMenu settings menu closed should match snapshot 1`] = `\n<div>\n  <div\n    class=\"GlobalHeaderSettingsMenu\"\n  >\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper\"\n      role=\"button\"\n    >\n      <div\n        class=\"GlobalHeaderComponent__button menu-entry\"\n      >\n        <i\n          class=\"CompassIcon icon-settings-outline SettingsIcon\"\n        />\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/sidebar/GlobalHeaderSettingsMenu settings menu open should match snapshot 1`] = `\n<div>\n  <div\n    class=\"GlobalHeaderSettingsMenu\"\n  >\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper override menuOpened\"\n      role=\"button\"\n    >\n      <div\n        class=\"GlobalHeaderComponent__button menu-entry\"\n      >\n        <i\n          class=\"CompassIcon icon-settings-outline SettingsIcon\"\n        />\n      </div>\n      <div\n        class=\"Menu noselect left \"\n      >\n        <div\n          class=\"menu-contents\"\n        >\n          <div\n            class=\"menu-options\"\n          >\n            <div>\n              <div\n                class=\"MenuOption SubMenuOption menu-option open-left\"\n                id=\"import\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n                <div\n                  class=\"menu-name\"\n                >\n                  Import\n                </div>\n                <i\n                  class=\"CompassIcon icon-chevron-right\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                class=\"MenuOption SubMenuOption menu-option open-left\"\n                id=\"lang\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n                <div\n                  class=\"menu-name\"\n                >\n                  Set language\n                </div>\n                <i\n                  class=\"CompassIcon icon-chevron-right\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Random icons\"\n                class=\"MenuOption SwitchOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n                <div\n                  class=\"menu-name\"\n                >\n                  Random icons\n                </div>\n                <div\n                  class=\"Switch override-main size--small on\"\n                >\n                  <div\n                    class=\"octo-switch-inner\"\n                  />\n                </div>\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Product tour\"\n                class=\"MenuOption TextOption menu-option product-tour\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Product tour\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"menu-spacer hideOnWidescreen\"\n          />\n          <div\n            class=\"menu-options hideOnWidescreen\"\n          >\n            <div\n              aria-label=\"Cancel\"\n              class=\"MenuOption TextOption menu-option menu-cancel\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Cancel\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/globalHeader/globalHeader.scss",
    "content": ".GlobalHeaderComponent {\n    display: flex;\n    flex-direction: row;\n    width: 100%;\n    margin-right: -8px;\n\n    .spacer {\n        flex-grow: 1;\n    }\n\n    .GlobalHeaderComponent__button {\n        width: 28px;\n        height: 28px;\n        display: flex;\n        align-items: center;\n        justify-content: center;\n        border-radius: 4px;\n        color: rgba(var(--sidebar-text-rgb), 0.56);\n        margin-left: 8px;\n\n        i {\n            font-size: 18px;\n            width: 18px;\n            height: 18px;\n            display: flex;\n\n            &::before {\n                margin: 0;\n            }\n        }\n\n        &:hover {\n            background-color: rgba(var(--sidebar-text-rgb), 0.08);\n            color: rgba(var(--sidebar-text-rgb), 0.72);\n        }\n\n        &:active {\n            background-color: rgba(var(--sidebar-text-rgb), 0.16);\n            color: rgba(var(--sidebar-text-rgb), 1);\n        }\n    }\n\n}\n"
  },
  {
    "path": "webapp/src/components/globalHeader/globalHeader.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {Provider as ReduxProvider} from 'react-redux'\nimport {createMemoryHistory} from 'history'\n\nimport {render} from '@testing-library/react'\n\nimport configureStore from 'redux-mock-store'\n\nimport {wrapIntl} from '../../testUtils'\n\nimport GlobalHeader from './globalHeader'\n\ndescribe('components/sidebar/GlobalHeader', () => {\n    const mockStore = configureStore([])\n    const history = createMemoryHistory()\n\n    let store = mockStore({})\n    beforeEach(() => {\n        store = mockStore({})\n    })\n    test('header menu should match snapshot', () => {\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <GlobalHeader history={history}/>\n            </ReduxProvider>,\n        )\n\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/globalHeader/globalHeader.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n//\nimport React from 'react'\nimport {Provider as ReduxProvider} from 'react-redux'\nimport {IntlProvider} from 'react-intl'\nimport {History} from 'history'\n\nimport HelpIcon from '../../widgets/icons/help'\nimport store from '../../store'\nimport {useAppSelector} from '../../store/hooks'\nimport {getLanguage} from '../../store/language'\nimport {getMessages} from '../../i18n'\n\nimport {Constants} from '../../constants'\n\nimport GlobalHeaderSettingsMenu from './globalHeaderSettingsMenu'\n\nimport './globalHeader.scss'\n\ntype HeaderItemProps = {\n    history: History<unknown>\n}\n\nconst HeaderItems = (props: HeaderItemProps) => {\n    const language = useAppSelector<string>(getLanguage)\n    const helpUrl = 'https://www.focalboard.com/fwlink/doc-boards.html?v=' + Constants.versionString\n\n    return (\n        <IntlProvider\n            locale={language.split(/[_]/)[0]}\n            messages={getMessages(language)}\n        >\n            <div className='GlobalHeaderComponent'>\n                <span className='spacer'/>\n                <a\n                    href={helpUrl}\n                    target='_blank'\n                    rel='noreferrer'\n                    className='GlobalHeaderComponent__button help-button'\n                >\n                    <HelpIcon/>\n                </a>\n                <GlobalHeaderSettingsMenu history={props.history}/>\n            </div>\n        </IntlProvider>\n    )\n}\n\ntype Props = {\n    history: History<unknown>\n}\n\nconst GlobalHeader = (props: Props): JSX.Element => {\n    return (\n        <ReduxProvider store={store}>\n            <HeaderItems history={props.history}/>\n        </ReduxProvider>\n    )\n}\n\nexport default GlobalHeader\n"
  },
  {
    "path": "webapp/src/components/globalHeader/globalHeaderSettingsMenu.scss",
    "content": ""
  },
  {
    "path": "webapp/src/components/globalHeader/globalHeaderSettingsMenu.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {Provider as ReduxProvider} from 'react-redux'\nimport {createMemoryHistory} from 'history'\n\nimport {render, act} from '@testing-library/react'\n\nimport userEvent from '@testing-library/user-event'\nimport configureStore from 'redux-mock-store'\n\nimport {mocked} from 'jest-mock'\n\nimport {wrapIntl} from '../../testUtils'\n\nimport TelemetryClient, {TelemetryCategory, TelemetryActions} from '../../telemetry/telemetryClient'\n\nimport client from '../../octoClient'\n\nimport GlobalHeaderSettingsMenu from './globalHeaderSettingsMenu'\n\njest.mock('../../telemetry/telemetryClient')\njest.mock('../../octoClient')\nconst mockedTelemetry = mocked(TelemetryClient, true)\nconst mockedOctoClient = mocked(client, true)\n\ndescribe('components/sidebar/GlobalHeaderSettingsMenu', () => {\n    const mockStore = configureStore([])\n    const history = createMemoryHistory()\n    let store = mockStore({})\n    beforeEach(() => {\n        store = mockStore({\n            teams: {\n                current: {id: 'team_id_1'},\n            },\n            boards: {\n                current: 'board_id',\n                boards: {\n                    board_id: {id: 'board_id'},\n                },\n                myBoardMemberships: {\n                    board_id: {userId: 'user_id_1', schemeAdmin: true},\n                },\n            },\n            users: {\n                me: {\n                    id: 'user-id',\n                },\n            },\n        })\n    })\n    test('settings menu closed should match snapshot', () => {\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <GlobalHeaderSettingsMenu history={history}/>\n            </ReduxProvider>,\n        )\n\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('settings menu open should match snapshot', () => {\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <GlobalHeaderSettingsMenu history={history}/>\n            </ReduxProvider>,\n        )\n\n        const {container} = render(component)\n        userEvent.click(container.querySelector('.menu-entry') as Element)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('languages menu open should match snapshot', () => {\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <GlobalHeaderSettingsMenu history={history}/>\n            </ReduxProvider>,\n        )\n\n        const {container} = render(component)\n        act(() => {\n            userEvent.click(container.querySelector('.menu-entry') as Element)\n        })\n        act(() => {\n            userEvent.hover(container.querySelector('#lang') as Element)\n        })\n        expect(container).toMatchSnapshot()\n    })\n\n    test('imports menu open should match snapshot', () => {\n        window.open = jest.fn()\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <GlobalHeaderSettingsMenu history={history}/>\n            </ReduxProvider>,\n        )\n\n        const {container} = render(component)\n        act(() => {\n            userEvent.click(container.querySelector('.menu-entry') as Element)\n        })\n        act(() => {\n            userEvent.hover(container.querySelector('#import') as Element)\n        })\n        expect(container).toMatchSnapshot()\n\n        userEvent.click(container.querySelector('[aria-label=\"Asana\"]') as Element)\n        expect(mockedTelemetry.trackEvent).toBeCalledWith(TelemetryCategory, TelemetryActions.ImportAsana)\n    })\n\n    test('Product Tour option restarts the tour', () => {\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <GlobalHeaderSettingsMenu history={history}/>\n            </ReduxProvider>,\n        )\n\n        const {container} = render(component)\n        act(() => {\n            userEvent.click(container.querySelector('.menu-entry') as Element)\n        })\n        act(() => {\n            userEvent.click(container.querySelector('.product-tour') as Element)\n        })\n\n        expect(mockedOctoClient.patchUserConfig).toBeCalledWith('user-id', {\n            updatedFields: {\n                onboardingTourStarted: '1',\n                onboardingTourStep: '0',\n                tourCategory: 'onboarding',\n            },\n        })\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/globalHeader/globalHeaderSettingsMenu.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useState} from 'react'\nimport {useIntl} from 'react-intl'\nimport {History} from 'history'\n\nimport {Archiver} from '../../archiver'\nimport Menu from '../../widgets/menu'\nimport MenuWrapper from '../../widgets/menuWrapper'\nimport {useAppDispatch, useAppSelector} from '../../store/hooks'\nimport {storeLanguage} from '../../store/language'\nimport {patchProps, getMe} from '../../store/users'\nimport {getCurrentTeam, Team} from '../../store/teams'\nimport {IUser, UserConfigPatch} from '../../user'\nimport octoClient from '../../octoClient'\nimport {UserSettings} from '../../userSettings'\nimport CheckIcon from '../../widgets/icons/check'\nimport SettingsIcon from '../../widgets/icons/settings'\n\nimport {Constants} from '../../constants'\nimport TelemetryClient, {TelemetryCategory, TelemetryActions} from '../../telemetry/telemetryClient'\n\nimport './globalHeaderSettingsMenu.scss'\n\ntype Props = {\n    history: History<unknown>\n}\n\nconst GlobalHeaderSettingsMenu = (props: Props) => {\n    const intl = useIntl()\n    const me = useAppSelector<IUser|null>(getMe)\n    const currentTeam = useAppSelector<Team|null>(getCurrentTeam)\n    const dispatch = useAppDispatch()\n\n    const [randomIcons, setRandomIcons] = useState(UserSettings.prefillRandomIcons)\n    const toggleRandomIcons = () => {\n        UserSettings.prefillRandomIcons = !UserSettings.prefillRandomIcons\n        setRandomIcons(!randomIcons)\n    }\n\n    return (\n        <div className='GlobalHeaderSettingsMenu'>\n            <MenuWrapper>\n                <div className='GlobalHeaderComponent__button menu-entry'>\n                    <SettingsIcon/>\n                </div>\n                <Menu position='left'>\n                    <Menu.SubMenu\n                        id='import'\n                        name={intl.formatMessage({id: 'Sidebar.import', defaultMessage: 'Import'})}\n                        position='left-bottom'\n                    >\n                        <Menu.Text\n                            id='import_archive'\n                            name={intl.formatMessage({id: 'Sidebar.import-archive', defaultMessage: 'Import archive'})}\n                            onClick={async () => {\n                                TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.ImportArchive)\n                                Archiver.importFullArchive()\n                            }}\n                        />\n                        {\n                            Constants.imports.map((i) => (\n                                <Menu.Text\n                                    key={`${i.id}-import`}\n                                    id={`${i.id}-import`}\n                                    name={i.displayName}\n                                    onClick={() => {\n                                        TelemetryClient.trackEvent(TelemetryCategory, i.telemetryName)\n                                        window.open(i.href)\n                                    }}\n                                />\n                            ))\n                        }\n                    </Menu.SubMenu>\n                    <Menu.SubMenu\n                        id='lang'\n                        name={intl.formatMessage({id: 'Sidebar.set-language', defaultMessage: 'Set language'})}\n                        position='left-bottom'\n                    >\n                        {\n                            Constants.languages.map((language) => (\n                                <Menu.Text\n                                    key={language.code}\n                                    id={`${language.name}-lang`}\n                                    name={language.displayName}\n                                    onClick={async () => dispatch(storeLanguage(language.code))}\n                                    rightIcon={intl.locale.toLowerCase() === language.code ? <CheckIcon/> : null}\n                                />\n                            ))\n                        }\n                    </Menu.SubMenu>\n                    <Menu.Switch\n                        id='random-icons'\n                        name={intl.formatMessage({id: 'Sidebar.random-icons', defaultMessage: 'Random icons'})}\n                        isOn={randomIcons}\n                        onClick={async () => toggleRandomIcons()}\n                        suppressItemClicked={true}\n                    />\n                    {me?.is_guest !== true &&\n                        <Menu.Text\n                            id='product-tour'\n                            className='product-tour'\n                            name={intl.formatMessage({id: 'Sidebar.product-tour', defaultMessage: 'Product tour'})}\n                            onClick={async () => {\n                                TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.StartTour)\n\n                                if (!me) {\n                                    return\n                                }\n                                if (!currentTeam) {\n                                    return\n                                }\n\n                                const patch: UserConfigPatch = {\n                                    updatedFields: {\n                                        onboardingTourStarted: '1',\n                                        onboardingTourStep: '0',\n                                        tourCategory: 'onboarding',\n                                    },\n                                }\n\n                                const patchedProps = await octoClient.patchUserConfig(me.id, patch)\n                                if (patchedProps) {\n                                    await dispatch(patchProps(patchedProps))\n                                }\n\n                                const onboardingData = await octoClient.prepareOnboarding(currentTeam.id)\n\n                                const newPath = `/team/${onboardingData?.teamID}/${onboardingData?.boardID}`\n\n                                props.history.push(newPath)\n                            }}\n                        />}\n                </Menu>\n            </MenuWrapper>\n        </div>\n    )\n}\n\nexport default React.memo(GlobalHeaderSettingsMenu)\n"
  },
  {
    "path": "webapp/src/components/guestNoBoards.scss",
    "content": ".GuestNoBoards {\n    height: 100%;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n\n    > div {\n        display: flex;\n        align-items: center;\n        flex-direction: column;\n        justify-content: center;\n        text-align: center;\n    }\n\n    .title {\n        font-size: 52px;\n        font-weight: 400;\n    }\n\n    .subtitle {\n        font-size: 20px;\n        margin: 16px 0;\n    }\n\n    svg {\n        margin: 56px 0;\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/guestNoBoards.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\nimport {FormattedMessage} from 'react-intl'\n\nimport ErrorIllustration from '../svg/error-illustration'\n\nimport './guestNoBoards.scss'\n\nconst GuestNoBoards = () => {\n    return (\n        <div className='GuestNoBoards'>\n            <div>\n                <div className='title'>\n                    <FormattedMessage\n                        id='guest-no-board.title'\n                        defaultMessage={'No boards yet'}\n                    />\n                </div>\n                <div className='subtitle'>\n                    <FormattedMessage\n                        id='guest-no-board.subtitle'\n                        defaultMessage={'You don\\'t have access to any board in this team yet, please wait until somebody adds you to any board.'}\n                    />\n                </div>\n                <ErrorIllustration/>\n            </div>\n        </div>\n    )\n}\n\nexport default React.memo(GuestNoBoards)\n"
  },
  {
    "path": "webapp/src/components/hiddenCardCount/hiddenCardCount.scss",
    "content": ".HiddenCardCount {\n    display: flex;\n    height: 30px;\n\n    &:hover {\n        cursor: pointer;\n    }\n\n    .hidden-card-title {\n        background: rgba(243, 192, 199, 0.2);\n        color: #d24b4e;\n        padding: 3px 6px;\n        border-radius: 4px;\n        text-transform: uppercase;\n        font-weight: bold;\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/hiddenCardCount/hiddenCardCount.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {useIntl} from 'react-intl'\n\nimport Button from '../../widgets/buttons/button'\n\nimport './hiddenCardCount.scss'\n\ntype Props = {\n    hiddenCardsCount: number\n    showHiddenCardNotification: (show: boolean) => void\n}\n\nconst HiddenCardCount = (props: Props): JSX.Element => {\n    const intl = useIntl()\n\n    const onClickHandler = () => {\n        props.showHiddenCardNotification(true)\n    }\n    return (\n        <div\n            className='HiddenCardCount'\n            onClick={onClickHandler}\n        >\n            <div className='hidden-card-title'>{intl.formatMessage({id: 'limitedCard.title', defaultMessage: 'Cards hidden'})}</div>\n            <Button title='hidden-card-count'>{props.hiddenCardsCount}</Button>\n        </div>\n    )\n}\n\nexport default HiddenCardCount\n"
  },
  {
    "path": "webapp/src/components/iconSelector.scss",
    "content": ".IconSelector {\n    .octo-icon {\n        display: flex;\n        line-height: 1.1;\n        margin-left: 0;\n        color: #000;\n        border-radius: 5px;\n        align-items: center;\n        justify-content: center;\n\n        &:not(.readonly):hover {\n            background-color: rgba(var(--center-channel-color-rgb), 0.1);\n        }\n\n        &.size-s {\n            height: 24px;\n            width: 24px;\n            font-size: 20px;\n        }\n\n        &.size-m {\n            height: 28px;\n            width: 28px;\n            font-size: 22px;\n        }\n\n        &.size-l {\n            height: 78px;\n            width: 78px;\n            font-size: 64px;\n        }\n\n        span {\n            display: block;\n        }\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/iconSelector.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\nimport {useIntl} from 'react-intl'\n\nimport RandomIcon from '../widgets/icons/random'\nimport EmojiPicker from '../widgets/emojiPicker'\nimport DeleteIcon from '../widgets/icons/delete'\nimport EmojiIcon from '../widgets/icons/emoji'\nimport Menu from '../widgets/menu'\nimport MenuWrapper from '../widgets/menuWrapper'\nimport './iconSelector.scss'\n\ntype Props = {\n    readonly?: boolean\n    iconElement: any\n    onAddRandomIcon: any\n    onSelectEmoji: any\n    onRemoveIcon: any\n}\n\nconst IconSelector = React.memo((props: Props) => {\n    const intl = useIntl()\n\n    return (\n        <div className='IconSelector'>\n            {props.readonly && props.iconElement}\n            {!props.readonly &&\n                <MenuWrapper>\n                    {props.iconElement}\n                    <Menu>\n                        <Menu.Text\n                            id='random'\n                            icon={<RandomIcon/>}\n                            name={intl.formatMessage({id: 'ViewTitle.random-icon', defaultMessage: 'Random'})}\n                            onClick={props.onAddRandomIcon}\n                        />\n                        <Menu.SubMenu\n                            id='pick'\n                            icon={<EmojiIcon/>}\n                            name={intl.formatMessage({id: 'ViewTitle.pick-icon', defaultMessage: 'Pick icon'})}\n                        >\n                            <EmojiPicker onSelect={props.onSelectEmoji}/>\n                        </Menu.SubMenu>\n                        <Menu.Text\n                            id='remove'\n                            icon={<DeleteIcon/>}\n                            name={intl.formatMessage({id: 'ViewTitle.remove-icon', defaultMessage: 'Remove icon'})}\n                            onClick={props.onRemoveIcon}\n                        />\n                    </Menu>\n                </MenuWrapper>\n            }\n        </div>\n    )\n})\n\nexport default IconSelector\n"
  },
  {
    "path": "webapp/src/components/kanban/__snapshots__/kanban.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`src/component/kanban/kanban do not return a kanban with groupByProperty undefined 1`] = `\n<div>\n  <div />\n</div>\n`;\n\nexports[`src/component/kanban/kanban return kanban and change title on KanbanColumnHeader 1`] = `\n<div>\n  <div\n    class=\"Kanban\"\n  >\n    <div\n      class=\"octo-board-header\"\n      id=\"mainBoardHeader\"\n    >\n      <div\n        class=\"octo-board-header-cell KanbanColumnHeader\"\n        draggable=\"true\"\n        style=\"opacity: 1;\"\n      >\n        <span\n          class=\"Label propColorOrange \"\n        >\n          <input\n            class=\"Editable \"\n            placeholder=\"New Select\"\n            spellcheck=\"true\"\n            title=\"New Q1\"\n            value=\"New Q1\"\n          />\n        </span>\n        <div\n          class=\"KanbanCalculation\"\n        >\n          <button\n            title=\"2\"\n            type=\"button\"\n          >\n            <span>\n              2\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"octo-spacer\"\n        />\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            type=\"button\"\n          >\n            <i\n              class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n            />\n          </button>\n        </div>\n        <button\n          type=\"button\"\n        >\n          <i\n            class=\"CompassIcon icon-plus AddIcon\"\n          />\n        </button>\n      </div>\n      <div\n        class=\"octo-board-header-cell KanbanColumnHeader\"\n        draggable=\"true\"\n        style=\"opacity: 1;\"\n      >\n        <span\n          class=\"Label propColorBlue \"\n        >\n          <input\n            class=\"Editable \"\n            placeholder=\"New Select\"\n            spellcheck=\"true\"\n            title=\"Q2\"\n            value=\"Q2\"\n          />\n        </span>\n        <div\n          class=\"KanbanCalculation\"\n        >\n          <button\n            title=\"1\"\n            type=\"button\"\n          >\n            <span>\n              1\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"octo-spacer\"\n        />\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            type=\"button\"\n          >\n            <i\n              class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n            />\n          </button>\n        </div>\n        <button\n          type=\"button\"\n        >\n          <i\n            class=\"CompassIcon icon-plus AddIcon\"\n          />\n        </button>\n      </div>\n      <div\n        class=\"octo-board-header-cell narrow\"\n      >\n        Hidden columns\n      </div>\n      <div\n        class=\"octo-board-header-cell narrow\"\n      >\n        <button\n          type=\"button\"\n        >\n          <span>\n            + Add a group\n          </span>\n        </button>\n      </div>\n    </div>\n    <div\n      class=\"octo-board-body\"\n      id=\"mainBoardBody\"\n    >\n      <div\n        class=\"octo-board-column\"\n      >\n        <div\n          class=\"KanbanCard\"\n          draggable=\"true\"\n          style=\"opacity: 1;\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper optionsMenu\"\n            role=\"button\"\n          >\n            <button\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n              />\n            </button>\n          </div>\n          <div\n            class=\"octo-icontitle\"\n          >\n            <div\n              class=\"octo-icon\"\n            >\n              i\n            </div>\n            <div\n              class=\"octo-titletext\"\n            >\n              title\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"KanbanCard\"\n          draggable=\"true\"\n          style=\"opacity: 1;\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper optionsMenu\"\n            role=\"button\"\n          >\n            <button\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n              />\n            </button>\n          </div>\n          <div\n            class=\"octo-icontitle\"\n          >\n            <div\n              class=\"octo-icon\"\n            >\n              i\n            </div>\n            <div\n              class=\"octo-titletext\"\n            >\n              title\n            </div>\n          </div>\n        </div>\n        <button\n          type=\"button\"\n        >\n          <span>\n            + New\n          </span>\n        </button>\n      </div>\n      <div\n        class=\"octo-board-column\"\n      >\n        <div\n          class=\"KanbanCard\"\n          draggable=\"true\"\n          style=\"opacity: 1;\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper optionsMenu\"\n            role=\"button\"\n          >\n            <button\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n              />\n            </button>\n          </div>\n          <div\n            class=\"octo-icontitle\"\n          >\n            <div\n              class=\"octo-icon\"\n            >\n              i\n            </div>\n            <div\n              class=\"octo-titletext\"\n            >\n              title\n            </div>\n          </div>\n        </div>\n        <button\n          type=\"button\"\n        >\n          <span>\n            + New\n          </span>\n        </button>\n      </div>\n      <div\n        class=\"octo-board-column narrow\"\n      >\n        <div\n          class=\"octo-board-hidden-item\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <span\n              class=\"Label propColorDefault \"\n            >\n              Q3\n            </span>\n          </div>\n          <button\n            type=\"button\"\n          >\n            <span>\n              0\n            </span>\n          </button>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`src/component/kanban/kanban return kanban and click on KanbanCalculationMenu 1`] = `\n<div>\n  <div\n    class=\"Kanban\"\n  >\n    <div\n      class=\"octo-board-header\"\n      id=\"mainBoardHeader\"\n    >\n      <div\n        class=\"octo-board-header-cell KanbanColumnHeader\"\n        draggable=\"true\"\n        style=\"opacity: 1;\"\n      >\n        <span\n          class=\"Label propColorOrange \"\n        >\n          <input\n            class=\"Editable \"\n            placeholder=\"New Select\"\n            spellcheck=\"true\"\n            title=\"Q1\"\n            value=\"Q1\"\n          />\n        </span>\n        <div\n          class=\"KanbanCalculation\"\n        >\n          <button\n            title=\"2\"\n            type=\"button\"\n          >\n            <span>\n              2\n            </span>\n          </button>\n          <div\n            class=\"CalculationOptions css-b62m3t-container\"\n          >\n            <span\n              class=\"css-1f43avz-a11yText-A11yText\"\n              id=\"react-select-2-live-region\"\n            />\n            <span\n              aria-atomic=\"false\"\n              aria-live=\"polite\"\n              aria-relevant=\"additions text\"\n              class=\"css-1f43avz-a11yText-A11yText\"\n              role=\"log\"\n            />\n            <div\n              class=\"CalculationOptions__control CalculationOptions__control--menu-is-open css-1s59geg-Control\"\n            >\n              <div\n                class=\"CalculationOptions__value-container CalculationOptions__value-container--has-value css-1kliayw-ValueContainer\"\n              >\n                <div\n                  class=\"CalculationOptions__single-value css-3hkq9s-singleValue\"\n                >\n                  Count\n                </div>\n                <input\n                  aria-activedescendant=\"\"\n                  aria-autocomplete=\"list\"\n                  aria-controls=\"react-select-2-listbox\"\n                  aria-expanded=\"true\"\n                  aria-haspopup=\"true\"\n                  aria-readonly=\"true\"\n                  class=\"css-mohuvp-dummyInput-DummyInput\"\n                  id=\"react-select-2-input\"\n                  inputmode=\"none\"\n                  role=\"combobox\"\n                  tabindex=\"0\"\n                  value=\"\"\n                />\n              </div>\n              <div\n                class=\"CalculationOptions__indicators css-1hb7zxy-IndicatorsContainer\"\n              >\n                <div\n                  aria-hidden=\"true\"\n                  class=\"CalculationOptions__indicator CalculationOptions__clear-indicator css-31haax-indicatorContainer\"\n                >\n                  <svg\n                    aria-hidden=\"true\"\n                    class=\"css-tj5bde-Svg\"\n                    focusable=\"false\"\n                    height=\"20\"\n                    viewBox=\"0 0 20 20\"\n                    width=\"20\"\n                  >\n                    <path\n                      d=\"M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z\"\n                    />\n                  </svg>\n                </div>\n                <span\n                  class=\"CalculationOptions__indicator-separator css-1uei4ir-indicatorSeparator\"\n                />\n                <div\n                  aria-hidden=\"true\"\n                  class=\"CalculationOptions__indicator CalculationOptions__dropdown-indicator css-y45573-indicatorContainer\"\n                >\n                  <i\n                    class=\"CompassIcon icon-chevron-up ChevronUpIcon\"\n                  />\n                </div>\n              </div>\n            </div>\n            <div\n              class=\"CalculationOptions__menu css-45h7mv-menu\"\n            >\n              <div\n                aria-multiselectable=\"false\"\n                class=\"CalculationOptions__menu-list css-1d1qzc4-MenuList\"\n                id=\"react-select-2-listbox\"\n                role=\"listbox\"\n              >\n                <div\n                  class=\"KanbanCalculationOptions_CustomOption active\"\n                >\n                  <span>\n                    Count\n                     \n                  </span>\n                </div>\n                <div\n                  class=\"KanbanCalculationOptions_CustomOption \"\n                >\n                  <span>\n                    Count Empty\n                     \n                    <i\n                      class=\"CompassIcon icon-chevron-right ChevronRightIcon\"\n                    />\n                  </span>\n                </div>\n                <div\n                  class=\"KanbanCalculationOptions_CustomOption \"\n                >\n                  <span>\n                    Count Not Empty\n                     \n                    <i\n                      class=\"CompassIcon icon-chevron-right ChevronRightIcon\"\n                    />\n                  </span>\n                </div>\n                <div\n                  class=\"KanbanCalculationOptions_CustomOption \"\n                >\n                  <span>\n                    Percent Empty\n                     \n                    <i\n                      class=\"CompassIcon icon-chevron-right ChevronRightIcon\"\n                    />\n                  </span>\n                </div>\n                <div\n                  class=\"KanbanCalculationOptions_CustomOption \"\n                >\n                  <span>\n                    Percent Not Empty\n                     \n                    <i\n                      class=\"CompassIcon icon-chevron-right ChevronRightIcon\"\n                    />\n                  </span>\n                </div>\n                <div\n                  class=\"KanbanCalculationOptions_CustomOption \"\n                >\n                  <span>\n                    Count Value\n                     \n                    <i\n                      class=\"CompassIcon icon-chevron-right ChevronRightIcon\"\n                    />\n                  </span>\n                </div>\n                <div\n                  class=\"KanbanCalculationOptions_CustomOption \"\n                >\n                  <span>\n                    Count Unique Values\n                     \n                    <i\n                      class=\"CompassIcon icon-chevron-right ChevronRightIcon\"\n                    />\n                  </span>\n                </div>\n              </div>\n            </div>\n            <input\n              name=\"calculation_options\"\n              type=\"hidden\"\n              value=\"count\"\n            />\n          </div>\n        </div>\n        <div\n          class=\"octo-spacer\"\n        />\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            type=\"button\"\n          >\n            <i\n              class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n            />\n          </button>\n        </div>\n        <button\n          type=\"button\"\n        >\n          <i\n            class=\"CompassIcon icon-plus AddIcon\"\n          />\n        </button>\n      </div>\n      <div\n        class=\"octo-board-header-cell KanbanColumnHeader\"\n        draggable=\"true\"\n        style=\"opacity: 1;\"\n      >\n        <span\n          class=\"Label propColorBlue \"\n        >\n          <input\n            class=\"Editable \"\n            placeholder=\"New Select\"\n            spellcheck=\"true\"\n            title=\"Q2\"\n            value=\"Q2\"\n          />\n        </span>\n        <div\n          class=\"KanbanCalculation\"\n        >\n          <button\n            title=\"1\"\n            type=\"button\"\n          >\n            <span>\n              1\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"octo-spacer\"\n        />\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            type=\"button\"\n          >\n            <i\n              class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n            />\n          </button>\n        </div>\n        <button\n          type=\"button\"\n        >\n          <i\n            class=\"CompassIcon icon-plus AddIcon\"\n          />\n        </button>\n      </div>\n      <div\n        class=\"octo-board-header-cell narrow\"\n      >\n        Hidden columns\n      </div>\n      <div\n        class=\"octo-board-header-cell narrow\"\n      >\n        <button\n          type=\"button\"\n        >\n          <span>\n            + Add a group\n          </span>\n        </button>\n      </div>\n    </div>\n    <div\n      class=\"octo-board-body\"\n      id=\"mainBoardBody\"\n    >\n      <div\n        class=\"octo-board-column\"\n      >\n        <div\n          class=\"KanbanCard\"\n          draggable=\"true\"\n          style=\"opacity: 1;\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper optionsMenu\"\n            role=\"button\"\n          >\n            <button\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n              />\n            </button>\n          </div>\n          <div\n            class=\"octo-icontitle\"\n          >\n            <div\n              class=\"octo-icon\"\n            >\n              i\n            </div>\n            <div\n              class=\"octo-titletext\"\n            >\n              title\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"KanbanCard\"\n          draggable=\"true\"\n          style=\"opacity: 1;\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper optionsMenu\"\n            role=\"button\"\n          >\n            <button\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n              />\n            </button>\n          </div>\n          <div\n            class=\"octo-icontitle\"\n          >\n            <div\n              class=\"octo-icon\"\n            >\n              i\n            </div>\n            <div\n              class=\"octo-titletext\"\n            >\n              title\n            </div>\n          </div>\n        </div>\n        <button\n          type=\"button\"\n        >\n          <span>\n            + New\n          </span>\n        </button>\n      </div>\n      <div\n        class=\"octo-board-column\"\n      >\n        <div\n          class=\"KanbanCard\"\n          draggable=\"true\"\n          style=\"opacity: 1;\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper optionsMenu\"\n            role=\"button\"\n          >\n            <button\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n              />\n            </button>\n          </div>\n          <div\n            class=\"octo-icontitle\"\n          >\n            <div\n              class=\"octo-icon\"\n            >\n              i\n            </div>\n            <div\n              class=\"octo-titletext\"\n            >\n              title\n            </div>\n          </div>\n        </div>\n        <button\n          type=\"button\"\n        >\n          <span>\n            + New\n          </span>\n        </button>\n      </div>\n      <div\n        class=\"octo-board-column narrow\"\n      >\n        <div\n          class=\"octo-board-hidden-item\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <span\n              class=\"Label propColorDefault \"\n            >\n              Q3\n            </span>\n          </div>\n          <button\n            type=\"button\"\n          >\n            <span>\n              0\n            </span>\n          </button>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`src/component/kanban/kanban should match snapshot 1`] = `\n<div>\n  <div\n    class=\"Kanban\"\n  >\n    <div\n      class=\"octo-board-header\"\n      id=\"mainBoardHeader\"\n    >\n      <div\n        class=\"octo-board-header-cell KanbanColumnHeader\"\n        draggable=\"true\"\n        style=\"opacity: 1;\"\n      >\n        <span\n          class=\"Label propColorOrange \"\n        >\n          <input\n            class=\"Editable \"\n            placeholder=\"New Select\"\n            spellcheck=\"true\"\n            title=\"Q1\"\n            value=\"Q1\"\n          />\n        </span>\n        <div\n          class=\"KanbanCalculation\"\n        >\n          <button\n            title=\"2\"\n            type=\"button\"\n          >\n            <span>\n              2\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"octo-spacer\"\n        />\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            type=\"button\"\n          >\n            <i\n              class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n            />\n          </button>\n        </div>\n        <button\n          type=\"button\"\n        >\n          <i\n            class=\"CompassIcon icon-plus AddIcon\"\n          />\n        </button>\n      </div>\n      <div\n        class=\"octo-board-header-cell KanbanColumnHeader\"\n        draggable=\"true\"\n        style=\"opacity: 1;\"\n      >\n        <span\n          class=\"Label propColorBlue \"\n        >\n          <input\n            class=\"Editable \"\n            placeholder=\"New Select\"\n            spellcheck=\"true\"\n            title=\"Q2\"\n            value=\"Q2\"\n          />\n        </span>\n        <div\n          class=\"KanbanCalculation\"\n        >\n          <button\n            title=\"1\"\n            type=\"button\"\n          >\n            <span>\n              1\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"octo-spacer\"\n        />\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            type=\"button\"\n          >\n            <i\n              class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n            />\n          </button>\n        </div>\n        <button\n          type=\"button\"\n        >\n          <i\n            class=\"CompassIcon icon-plus AddIcon\"\n          />\n        </button>\n      </div>\n      <div\n        class=\"octo-board-header-cell narrow\"\n      >\n        Hidden columns\n      </div>\n      <div\n        class=\"octo-board-header-cell narrow\"\n      >\n        <button\n          type=\"button\"\n        >\n          <span>\n            + Add a group\n          </span>\n        </button>\n      </div>\n    </div>\n    <div\n      class=\"octo-board-body\"\n      id=\"mainBoardBody\"\n    >\n      <div\n        class=\"octo-board-column\"\n      >\n        <div\n          class=\"KanbanCard\"\n          draggable=\"true\"\n          style=\"opacity: 1;\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper optionsMenu\"\n            role=\"button\"\n          >\n            <button\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n              />\n            </button>\n          </div>\n          <div\n            class=\"octo-icontitle\"\n          >\n            <div\n              class=\"octo-icon\"\n            >\n              i\n            </div>\n            <div\n              class=\"octo-titletext\"\n            >\n              title\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"KanbanCard\"\n          draggable=\"true\"\n          style=\"opacity: 1;\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper optionsMenu\"\n            role=\"button\"\n          >\n            <button\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n              />\n            </button>\n          </div>\n          <div\n            class=\"octo-icontitle\"\n          >\n            <div\n              class=\"octo-icon\"\n            >\n              i\n            </div>\n            <div\n              class=\"octo-titletext\"\n            >\n              title\n            </div>\n          </div>\n        </div>\n        <button\n          type=\"button\"\n        >\n          <span>\n            + New\n          </span>\n        </button>\n      </div>\n      <div\n        class=\"octo-board-column\"\n      >\n        <div\n          class=\"KanbanCard\"\n          draggable=\"true\"\n          style=\"opacity: 1;\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper optionsMenu\"\n            role=\"button\"\n          >\n            <button\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n              />\n            </button>\n          </div>\n          <div\n            class=\"octo-icontitle\"\n          >\n            <div\n              class=\"octo-icon\"\n            >\n              i\n            </div>\n            <div\n              class=\"octo-titletext\"\n            >\n              title\n            </div>\n          </div>\n        </div>\n        <button\n          type=\"button\"\n        >\n          <span>\n            + New\n          </span>\n        </button>\n      </div>\n      <div\n        class=\"octo-board-column narrow\"\n      >\n        <div\n          class=\"octo-board-hidden-item\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <span\n              class=\"Label propColorDefault \"\n            >\n              Q3\n            </span>\n          </div>\n          <button\n            type=\"button\"\n          >\n            <span>\n              0\n            </span>\n          </button>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`src/component/kanban/kanban should match snapshot without permissions 1`] = `\n<div>\n  <div\n    class=\"Kanban\"\n  >\n    <div\n      class=\"octo-board-header\"\n      id=\"mainBoardHeader\"\n    >\n      <div\n        class=\"octo-board-header-cell KanbanColumnHeader\"\n        draggable=\"false\"\n        style=\"opacity: 1;\"\n      >\n        <span\n          class=\"Label propColorOrange \"\n        >\n          <input\n            class=\"Editable readonly \"\n            placeholder=\"New Select\"\n            readonly=\"\"\n            spellcheck=\"true\"\n            title=\"Q1\"\n            value=\"Q1\"\n          />\n        </span>\n        <div\n          class=\"KanbanCalculation\"\n        >\n          <button\n            title=\"2\"\n            type=\"button\"\n          >\n            <span>\n              2\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"octo-spacer\"\n        />\n      </div>\n      <div\n        class=\"octo-board-header-cell KanbanColumnHeader\"\n        draggable=\"false\"\n        style=\"opacity: 1;\"\n      >\n        <span\n          class=\"Label propColorBlue \"\n        >\n          <input\n            class=\"Editable readonly \"\n            placeholder=\"New Select\"\n            readonly=\"\"\n            spellcheck=\"true\"\n            title=\"Q2\"\n            value=\"Q2\"\n          />\n        </span>\n        <div\n          class=\"KanbanCalculation\"\n        >\n          <button\n            title=\"1\"\n            type=\"button\"\n          >\n            <span>\n              1\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"octo-spacer\"\n        />\n      </div>\n      <div\n        class=\"octo-board-header-cell narrow\"\n      >\n        Hidden columns\n      </div>\n    </div>\n    <div\n      class=\"octo-board-body\"\n      id=\"mainBoardBody\"\n    >\n      <div\n        class=\"octo-board-column\"\n      >\n        <div\n          class=\"KanbanCard\"\n          draggable=\"true\"\n          style=\"opacity: 1;\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper optionsMenu\"\n            role=\"button\"\n          >\n            <button\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n              />\n            </button>\n          </div>\n          <div\n            class=\"octo-icontitle\"\n          >\n            <div\n              class=\"octo-icon\"\n            >\n              i\n            </div>\n            <div\n              class=\"octo-titletext\"\n            >\n              title\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"KanbanCard\"\n          draggable=\"true\"\n          style=\"opacity: 1;\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper optionsMenu\"\n            role=\"button\"\n          >\n            <button\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n              />\n            </button>\n          </div>\n          <div\n            class=\"octo-icontitle\"\n          >\n            <div\n              class=\"octo-icon\"\n            >\n              i\n            </div>\n            <div\n              class=\"octo-titletext\"\n            >\n              title\n            </div>\n          </div>\n        </div>\n      </div>\n      <div\n        class=\"octo-board-column\"\n      >\n        <div\n          class=\"KanbanCard\"\n          draggable=\"true\"\n          style=\"opacity: 1;\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper optionsMenu\"\n            role=\"button\"\n          >\n            <button\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n              />\n            </button>\n          </div>\n          <div\n            class=\"octo-icontitle\"\n          >\n            <div\n              class=\"octo-icon\"\n            >\n              i\n            </div>\n            <div\n              class=\"octo-titletext\"\n            >\n              title\n            </div>\n          </div>\n        </div>\n      </div>\n      <div\n        class=\"octo-board-column narrow\"\n      >\n        <div\n          class=\"octo-board-hidden-item\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <span\n              class=\"Label propColorDefault \"\n            >\n              Q3\n            </span>\n          </div>\n          <button\n            type=\"button\"\n          >\n            <span>\n              0\n            </span>\n          </button>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/kanban/__snapshots__/kanbanCard.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`src/components/kanban/kanbanCard return kanbanCard and click on copy link menu  1`] = `\n<div>\n  <div\n    class=\"KanbanCard\"\n    draggable=\"true\"\n    style=\"opacity: 1;\"\n  >\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper override menuOpened optionsMenu\"\n      role=\"button\"\n    >\n      <button\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n        />\n      </button>\n      <div\n        class=\"Menu noselect left \"\n      >\n        <div\n          class=\"menu-contents\"\n        >\n          <div\n            class=\"menu-options\"\n          >\n            <div>\n              <div\n                aria-label=\"Delete\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-trash-can-outline DeleteIcon trash-can-outline\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Delete\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                aria-label=\"Duplicate\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-content-copy content-copy\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Duplicate\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Copy link\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-link-variant LinkIcon\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Copy link\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div />\n          </div>\n          <div\n            class=\"menu-spacer hideOnWidescreen\"\n          />\n          <div\n            class=\"menu-options hideOnWidescreen\"\n          >\n            <div\n              aria-label=\"Cancel\"\n              class=\"MenuOption TextOption menu-option menu-cancel\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Cancel\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"octo-icontitle\"\n    >\n      <div\n        class=\"octo-icon\"\n      >\n        i\n      </div>\n      <div\n        class=\"octo-titletext\"\n      >\n        title\n      </div>\n    </div>\n    <div\n      class=\"octo-tooltip tooltip-top\"\n      data-tooltip=\"name\"\n    >\n      <div\n        class=\"octo-propertyvalue octo-propertyvalue--readonly\"\n      />\n    </div>\n  </div>\n</div>\n`;\n\nexports[`src/components/kanban/kanbanCard return kanbanCard and click on delete menu  1`] = `\n<div>\n  <div\n    class=\"KanbanCard\"\n    draggable=\"true\"\n    style=\"opacity: 1;\"\n  >\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper override menuOpened optionsMenu\"\n      role=\"button\"\n    >\n      <button\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n        />\n      </button>\n      <div\n        class=\"Menu noselect left \"\n      >\n        <div\n          class=\"menu-contents\"\n        >\n          <div\n            class=\"menu-options\"\n          >\n            <div>\n              <div\n                aria-label=\"Delete\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-trash-can-outline DeleteIcon trash-can-outline\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Delete\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                aria-label=\"Duplicate\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-content-copy content-copy\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Duplicate\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Copy link\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-link-variant LinkIcon\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Copy link\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div />\n          </div>\n          <div\n            class=\"menu-spacer hideOnWidescreen\"\n          />\n          <div\n            class=\"menu-options hideOnWidescreen\"\n          >\n            <div\n              aria-label=\"Cancel\"\n              class=\"MenuOption TextOption menu-option menu-cancel\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Cancel\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"octo-icontitle\"\n    >\n      <div\n        class=\"octo-icon\"\n      >\n        i\n      </div>\n      <div\n        class=\"octo-titletext\"\n      >\n        title\n      </div>\n    </div>\n    <div\n      class=\"octo-tooltip tooltip-top\"\n      data-tooltip=\"name\"\n    >\n      <div\n        class=\"octo-propertyvalue octo-propertyvalue--readonly\"\n      />\n    </div>\n  </div>\n</div>\n`;\n\nexports[`src/components/kanban/kanbanCard return kanbanCard and click on duplicate menu  1`] = `\n<div>\n  <div\n    class=\"KanbanCard\"\n    draggable=\"true\"\n    style=\"opacity: 1;\"\n  >\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper override menuOpened optionsMenu\"\n      role=\"button\"\n    >\n      <button\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n        />\n      </button>\n      <div\n        class=\"Menu noselect left \"\n      >\n        <div\n          class=\"menu-contents\"\n        >\n          <div\n            class=\"menu-options\"\n          >\n            <div>\n              <div\n                aria-label=\"Delete\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-trash-can-outline DeleteIcon trash-can-outline\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Delete\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                aria-label=\"Duplicate\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-content-copy content-copy\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Duplicate\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Copy link\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-link-variant LinkIcon\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Copy link\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div />\n          </div>\n          <div\n            class=\"menu-spacer hideOnWidescreen\"\n          />\n          <div\n            class=\"menu-options hideOnWidescreen\"\n          >\n            <div\n              aria-label=\"Cancel\"\n              class=\"MenuOption TextOption menu-option menu-cancel\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Cancel\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"octo-icontitle\"\n    >\n      <div\n        class=\"octo-icon\"\n      >\n        i\n      </div>\n      <div\n        class=\"octo-titletext\"\n      >\n        title\n      </div>\n    </div>\n    <div\n      class=\"octo-tooltip tooltip-top\"\n      data-tooltip=\"name\"\n    >\n      <div\n        class=\"octo-propertyvalue octo-propertyvalue--readonly\"\n      />\n    </div>\n  </div>\n</div>\n`;\n\nexports[`src/components/kanban/kanbanCard should match snapshot 1`] = `\n<div>\n  <div\n    class=\"KanbanCard\"\n    draggable=\"true\"\n    style=\"opacity: 1;\"\n  >\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper optionsMenu\"\n      role=\"button\"\n    >\n      <button\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n        />\n      </button>\n    </div>\n    <div\n      class=\"octo-icontitle\"\n    >\n      <div\n        class=\"octo-icon\"\n      >\n        i\n      </div>\n      <div\n        class=\"octo-titletext\"\n      >\n        title\n      </div>\n    </div>\n    <div\n      class=\"octo-tooltip tooltip-top\"\n      data-tooltip=\"name\"\n    >\n      <div\n        class=\"octo-propertyvalue octo-propertyvalue--readonly\"\n      />\n    </div>\n  </div>\n</div>\n`;\n\nexports[`src/components/kanban/kanbanCard should match snapshot with readonly 1`] = `\n<div>\n  <div\n    class=\"KanbanCard\"\n    draggable=\"false\"\n    style=\"opacity: 1;\"\n  >\n    <div\n      class=\"octo-icontitle\"\n    >\n      <div\n        class=\"octo-icon\"\n      >\n        i\n      </div>\n      <div\n        class=\"octo-titletext\"\n      >\n        title\n      </div>\n    </div>\n    <div\n      class=\"octo-tooltip tooltip-top\"\n      data-tooltip=\"name\"\n    >\n      <div\n        class=\"octo-propertyvalue octo-propertyvalue--readonly\"\n      />\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/kanban/__snapshots__/kanbanColumn.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`src/components/kanban/kanbanColumn should match snapshot 1`] = `\n<div>\n  <div\n    class=\"octo-board-column\"\n  />\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/kanban/__snapshots__/kanbanColumnHeader.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`src/components/kanban/kanbanColumnHeader return kanbanColumnHeader and click on menuwrapper 1`] = `\n<div>\n  <div\n    class=\"octo-board-header-cell KanbanColumnHeader\"\n    draggable=\"true\"\n    style=\"opacity: 1;\"\n  >\n    <span\n      class=\"Label propColorDefault \"\n    >\n      <input\n        class=\"Editable \"\n        placeholder=\"New Select\"\n        spellcheck=\"true\"\n        title=\"Title\"\n        value=\"Title\"\n      />\n    </span>\n    <div\n      class=\"KanbanCalculation\"\n    >\n      <button\n        class=\"Button\"\n        title=\"0\"\n        type=\"button\"\n      >\n        <span>\n          0\n        </span>\n      </button>\n    </div>\n    <div\n      class=\"octo-spacer\"\n    />\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper override menuOpened\"\n      role=\"button\"\n    >\n      <button\n        class=\"IconButton\"\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n        />\n      </button>\n      <div\n        class=\"Menu noselect bottom \"\n      >\n        <div\n          class=\"menu-contents\"\n        >\n          <div\n            class=\"menu-options\"\n          >\n            <div>\n              <div\n                aria-label=\"Hide\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <svg\n                      class=\"HideIcon Icon\"\n                      viewBox=\"0 0 640 512\"\n                      xmlns=\"http://www.w3.org/2000/svg\"\n                    >\n                      <path\n                        d=\"M634 471L36 3.51A16 16 0 0 0 13.51 6l-10 12.49A16 16 0 0 0 6 41l598 467.49a16 16 0 0 0 22.49-2.49l10-12.49A16 16 0 0 0 634 471zM296.79 146.47l134.79 105.38C429.36 191.91 380.48 144 320 144a112.26 112.26 0 0 0-23.21 2.47zm46.42 219.07L208.42 260.16C210.65 320.09 259.53 368 320 368a113 113 0 0 0 23.21-2.46zM320 112c98.65 0 189.09 55 237.93 144a285.53 285.53 0 0 1-44 60.2l37.74 29.5a333.7 333.7 0 0 0 52.9-75.11 32.35 32.35 0 0 0 0-29.19C550.29 135.59 442.93 64 320 64c-36.7 0-71.71 7-104.63 18.81l46.41 36.29c18.94-4.3 38.34-7.1 58.22-7.1zm0 288c-98.65 0-189.08-55-237.93-144a285.47 285.47 0 0 1 44.05-60.19l-37.74-29.5a333.6 333.6 0 0 0-52.89 75.1 32.35 32.35 0 0 0 0 29.19C89.72 376.41 197.08 448 320 448c36.7 0 71.71-7.05 104.63-18.81l-46.41-36.28C359.28 397.2 339.89 400 320 400z\"\n                      />\n                    </svg>\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Hide\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Delete\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-trash-can-outline DeleteIcon trash-can-outline\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Delete\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"MenuOption MenuSeparator menu-separator\"\n              />\n              <div\n                aria-label=\"Select Default Color\"\n                class=\"MenuOption ColorOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n                <div\n                  class=\"menu-name\"\n                >\n                  Default\n                </div>\n                <div\n                  class=\"menu-colorbox propColorDefault\"\n                />\n              </div>\n              <div\n                aria-label=\"Select Gray Color\"\n                class=\"MenuOption ColorOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n                <div\n                  class=\"menu-name\"\n                >\n                  Gray\n                </div>\n                <div\n                  class=\"menu-colorbox propColorGray\"\n                />\n              </div>\n              <div\n                aria-label=\"Select Brown Color\"\n                class=\"MenuOption ColorOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n                <div\n                  class=\"menu-name\"\n                >\n                  Brown\n                </div>\n                <div\n                  class=\"menu-colorbox propColorBrown\"\n                />\n              </div>\n              <div\n                aria-label=\"Select Orange Color\"\n                class=\"MenuOption ColorOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n                <div\n                  class=\"menu-name\"\n                >\n                  Orange\n                </div>\n                <div\n                  class=\"menu-colorbox propColorOrange\"\n                />\n              </div>\n              <div\n                aria-label=\"Select Yellow Color\"\n                class=\"MenuOption ColorOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n                <div\n                  class=\"menu-name\"\n                >\n                  Yellow\n                </div>\n                <div\n                  class=\"menu-colorbox propColorYellow\"\n                />\n              </div>\n              <div\n                aria-label=\"Select Green Color\"\n                class=\"MenuOption ColorOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n                <div\n                  class=\"menu-name\"\n                >\n                  Green\n                </div>\n                <div\n                  class=\"menu-colorbox propColorGreen\"\n                />\n              </div>\n              <div\n                aria-label=\"Select Blue Color\"\n                class=\"MenuOption ColorOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n                <div\n                  class=\"menu-name\"\n                >\n                  Blue\n                </div>\n                <div\n                  class=\"menu-colorbox propColorBlue\"\n                />\n              </div>\n              <div\n                aria-label=\"Select Purple Color\"\n                class=\"MenuOption ColorOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n                <div\n                  class=\"menu-name\"\n                >\n                  Purple\n                </div>\n                <div\n                  class=\"menu-colorbox propColorPurple\"\n                />\n              </div>\n              <div\n                aria-label=\"Select Pink Color\"\n                class=\"MenuOption ColorOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n                <div\n                  class=\"menu-name\"\n                >\n                  Pink\n                </div>\n                <div\n                  class=\"menu-colorbox propColorPink\"\n                />\n              </div>\n              <div\n                aria-label=\"Select Red Color\"\n                class=\"MenuOption ColorOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n                <div\n                  class=\"menu-name\"\n                >\n                  Red\n                </div>\n                <div\n                  class=\"menu-colorbox propColorRed\"\n                />\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"menu-spacer hideOnWidescreen\"\n          />\n          <div\n            class=\"menu-options hideOnWidescreen\"\n          >\n            <div\n              aria-label=\"Cancel\"\n              class=\"MenuOption TextOption menu-option menu-cancel\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Cancel\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <button\n      class=\"IconButton\"\n      type=\"button\"\n    >\n      <i\n        class=\"CompassIcon icon-plus AddIcon\"\n      />\n    </button>\n  </div>\n</div>\n`;\n\nexports[`src/components/kanban/kanbanColumnHeader return kanbanColumnHeader and edit title 1`] = `\n<div>\n  <div\n    class=\"octo-board-header-cell KanbanColumnHeader\"\n    draggable=\"true\"\n    style=\"opacity: 1;\"\n  >\n    <span\n      class=\"Label propColorDefault \"\n    >\n      <input\n        class=\"Editable \"\n        placeholder=\"New Select\"\n        spellcheck=\"true\"\n        title=\"New Title\"\n        value=\"New Title\"\n      />\n    </span>\n    <div\n      class=\"KanbanCalculation\"\n    >\n      <button\n        class=\"Button\"\n        title=\"0\"\n        type=\"button\"\n      >\n        <span>\n          0\n        </span>\n      </button>\n    </div>\n    <div\n      class=\"octo-spacer\"\n    />\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper\"\n      role=\"button\"\n    >\n      <button\n        class=\"IconButton\"\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n        />\n      </button>\n    </div>\n    <button\n      class=\"IconButton\"\n      type=\"button\"\n    >\n      <i\n        class=\"CompassIcon icon-plus AddIcon\"\n      />\n    </button>\n  </div>\n</div>\n`;\n\nexports[`src/components/kanban/kanbanColumnHeader should match snapshot 1`] = `\n<div>\n  <div\n    class=\"octo-board-header-cell KanbanColumnHeader\"\n    draggable=\"true\"\n    style=\"opacity: 1;\"\n  >\n    <span\n      class=\"Label propColorDefault \"\n    >\n      <input\n        class=\"Editable \"\n        placeholder=\"New Select\"\n        spellcheck=\"true\"\n        title=\"Title\"\n        value=\"Title\"\n      />\n    </span>\n    <div\n      class=\"KanbanCalculation\"\n    >\n      <button\n        class=\"Button\"\n        title=\"0\"\n        type=\"button\"\n      >\n        <span>\n          0\n        </span>\n      </button>\n    </div>\n    <div\n      class=\"octo-spacer\"\n    />\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper\"\n      role=\"button\"\n    >\n      <button\n        class=\"IconButton\"\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n        />\n      </button>\n    </div>\n    <button\n      class=\"IconButton\"\n      type=\"button\"\n    >\n      <i\n        class=\"CompassIcon icon-plus AddIcon\"\n      />\n    </button>\n  </div>\n</div>\n`;\n\nexports[`src/components/kanban/kanbanColumnHeader should match snapshot readonly 1`] = `\n<div>\n  <div\n    class=\"octo-board-header-cell KanbanColumnHeader\"\n    draggable=\"true\"\n    style=\"opacity: 1;\"\n  >\n    <span\n      class=\"Label propColorDefault \"\n    >\n      <input\n        class=\"Editable readonly \"\n        placeholder=\"New Select\"\n        readonly=\"\"\n        spellcheck=\"true\"\n        title=\"Title\"\n        value=\"Title\"\n      />\n    </span>\n    <div\n      class=\"KanbanCalculation\"\n    >\n      <button\n        class=\"Button\"\n        title=\"0\"\n        type=\"button\"\n      >\n        <span>\n          0\n        </span>\n      </button>\n    </div>\n    <div\n      class=\"octo-spacer\"\n    />\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/kanban/__snapshots__/kanbanHiddenColumnItem.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`src/components/kanban/kanbanHiddenColumnItem limited card check 1`] = `\n<div>\n  <div\n    class=\"octo-board-hidden-item\"\n  >\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper\"\n      role=\"button\"\n    >\n      <span\n        class=\"Label propColorDefault \"\n      >\n        propOption\n      </span>\n    </div>\n    <button\n      class=\"Button\"\n      title=\"hidden-card-count\"\n      type=\"button\"\n    >\n      <span>\n        2\n      </span>\n    </button>\n  </div>\n</div>\n`;\n\nexports[`src/components/kanban/kanbanHiddenColumnItem return kanbanHiddenColumnItem and click menuwrapper 1`] = `\n<div>\n  <div\n    class=\"octo-board-hidden-item\"\n  >\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper override menuOpened\"\n      role=\"button\"\n    >\n      <span\n        class=\"Label propColorDefault \"\n      >\n        propOption\n      </span>\n      <div\n        class=\"Menu noselect bottom \"\n      >\n        <div\n          class=\"menu-contents\"\n        >\n          <div\n            class=\"menu-options\"\n          >\n            <div>\n              <div\n                aria-label=\"Show\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <svg\n                      class=\"ShowIcon Icon\"\n                      viewBox=\"0 0 576 512\"\n                      xmlns=\"http://www.w3.org/2000/svg\"\n                    >\n                      <path\n                        d=\"M288 144a110.94 110.94 0 0 0-31.24 5 55.4 55.4 0 0 1 7.24 27 56 56 0 0 1-56 56 55.4 55.4 0 0 1-27-7.24A111.71 111.71 0 1 0 288 144zm284.52 97.4C518.29 135.59 410.93 64 288 64S57.68 135.64 3.48 241.41a32.35 32.35 0 0 0 0 29.19C57.71 376.41 165.07 448 288 448s230.32-71.64 284.52-177.41a32.35 32.35 0 0 0 0-29.19zM288 400c-98.65 0-189.09-55-237.93-144C98.91 167 189.34 112 288 112s189.09 55 237.93 144C477.1 345 386.66 400 288 400z\"\n                      />\n                    </svg>\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Show\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"menu-spacer hideOnWidescreen\"\n          />\n          <div\n            class=\"menu-options hideOnWidescreen\"\n          >\n            <div\n              aria-label=\"Cancel\"\n              class=\"MenuOption TextOption menu-option menu-cancel\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Cancel\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        1\n      </span>\n    </button>\n  </div>\n</div>\n`;\n\nexports[`src/components/kanban/kanbanHiddenColumnItem return kanbanHiddenColumnItem, click menuwrapper and click show 1`] = `\n<div>\n  <div\n    class=\"octo-board-hidden-item\"\n  >\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper override menuOpened\"\n      role=\"button\"\n    >\n      <span\n        class=\"Label propColorDefault \"\n      >\n        propOption\n      </span>\n      <div\n        class=\"Menu noselect bottom \"\n      >\n        <div\n          class=\"menu-contents\"\n        >\n          <div\n            class=\"menu-options\"\n          >\n            <div>\n              <div\n                aria-label=\"Show\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <svg\n                      class=\"ShowIcon Icon\"\n                      viewBox=\"0 0 576 512\"\n                      xmlns=\"http://www.w3.org/2000/svg\"\n                    >\n                      <path\n                        d=\"M288 144a110.94 110.94 0 0 0-31.24 5 55.4 55.4 0 0 1 7.24 27 56 56 0 0 1-56 56 55.4 55.4 0 0 1-27-7.24A111.71 111.71 0 1 0 288 144zm284.52 97.4C518.29 135.59 410.93 64 288 64S57.68 135.64 3.48 241.41a32.35 32.35 0 0 0 0 29.19C57.71 376.41 165.07 448 288 448s230.32-71.64 284.52-177.41a32.35 32.35 0 0 0 0-29.19zM288 400c-98.65 0-189.09-55-237.93-144C98.91 167 189.34 112 288 112s189.09 55 237.93 144C477.1 345 386.66 400 288 400z\"\n                      />\n                    </svg>\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Show\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"menu-spacer hideOnWidescreen\"\n          />\n          <div\n            class=\"menu-options hideOnWidescreen\"\n          >\n            <div\n              aria-label=\"Cancel\"\n              class=\"MenuOption TextOption menu-option menu-cancel\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Cancel\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        1\n      </span>\n    </button>\n  </div>\n</div>\n`;\n\nexports[`src/components/kanban/kanbanHiddenColumnItem should match snapshot 1`] = `\n<div>\n  <div\n    class=\"octo-board-hidden-item\"\n  >\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper\"\n      role=\"button\"\n    >\n      <span\n        class=\"Label propColorDefault \"\n      >\n        propOption\n      </span>\n    </div>\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        1\n      </span>\n    </button>\n  </div>\n</div>\n`;\n\nexports[`src/components/kanban/kanbanHiddenColumnItem should match snapshot readonly 1`] = `\n<div>\n  <div\n    class=\"octo-board-hidden-item\"\n  >\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper disabled\"\n      role=\"button\"\n    >\n      <span\n        class=\"Label propColorDefault \"\n      >\n        propOption\n      </span>\n    </div>\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        1\n      </span>\n    </button>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/kanban/calculation/__snapshots__/calculation.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/kanban/calculation/KanbanCalculation base case 1`] = `\n<div>\n  <div\n    class=\"KanbanCalculation\"\n  >\n    <button\n      class=\"Button\"\n      title=\"3\"\n      type=\"button\"\n    >\n      <span>\n        3\n      </span>\n    </button>\n  </div>\n</div>\n`;\n\nexports[`components/kanban/calculation/KanbanCalculation calculations menu open 1`] = `\n<div>\n  <div\n    class=\"KanbanCalculation\"\n  >\n    <button\n      class=\"Button\"\n      title=\"3\"\n      type=\"button\"\n    >\n      <span>\n        3\n      </span>\n    </button>\n    <div\n      class=\"CalculationOptions css-b62m3t-container\"\n    >\n      <span\n        class=\"css-1f43avz-a11yText-A11yText\"\n        id=\"react-select-2-live-region\"\n      />\n      <span\n        aria-atomic=\"false\"\n        aria-live=\"polite\"\n        aria-relevant=\"additions text\"\n        class=\"css-1f43avz-a11yText-A11yText\"\n        role=\"log\"\n      />\n      <div\n        class=\"CalculationOptions__control CalculationOptions__control--menu-is-open css-1s59geg-Control\"\n      >\n        <div\n          class=\"CalculationOptions__value-container CalculationOptions__value-container--has-value css-1kliayw-ValueContainer\"\n        >\n          <div\n            class=\"CalculationOptions__single-value css-3hkq9s-singleValue\"\n          >\n            Count\n          </div>\n          <input\n            aria-activedescendant=\"\"\n            aria-autocomplete=\"list\"\n            aria-controls=\"react-select-2-listbox\"\n            aria-expanded=\"true\"\n            aria-haspopup=\"true\"\n            aria-readonly=\"true\"\n            class=\"css-mohuvp-dummyInput-DummyInput\"\n            id=\"react-select-2-input\"\n            inputmode=\"none\"\n            role=\"combobox\"\n            tabindex=\"0\"\n            value=\"\"\n          />\n        </div>\n        <div\n          class=\"CalculationOptions__indicators css-1hb7zxy-IndicatorsContainer\"\n        >\n          <div\n            aria-hidden=\"true\"\n            class=\"CalculationOptions__indicator CalculationOptions__clear-indicator css-31haax-indicatorContainer\"\n          >\n            <svg\n              aria-hidden=\"true\"\n              class=\"css-tj5bde-Svg\"\n              focusable=\"false\"\n              height=\"20\"\n              viewBox=\"0 0 20 20\"\n              width=\"20\"\n            >\n              <path\n                d=\"M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z\"\n              />\n            </svg>\n          </div>\n          <span\n            class=\"CalculationOptions__indicator-separator css-1uei4ir-indicatorSeparator\"\n          />\n          <div\n            aria-hidden=\"true\"\n            class=\"CalculationOptions__indicator CalculationOptions__dropdown-indicator css-y45573-indicatorContainer\"\n          >\n            <i\n              class=\"CompassIcon icon-chevron-up ChevronUpIcon\"\n            />\n          </div>\n        </div>\n      </div>\n      <div\n        class=\"CalculationOptions__menu css-45h7mv-menu\"\n      >\n        <div\n          aria-multiselectable=\"false\"\n          class=\"CalculationOptions__menu-list css-1d1qzc4-MenuList\"\n          id=\"react-select-2-listbox\"\n          role=\"listbox\"\n        >\n          <div\n            class=\"KanbanCalculationOptions_CustomOption active\"\n          >\n            <span>\n              Count\n               \n            </span>\n          </div>\n          <div\n            class=\"KanbanCalculationOptions_CustomOption \"\n          >\n            <span>\n              Count Empty\n               \n              <i\n                class=\"CompassIcon icon-chevron-right ChevronRightIcon\"\n              />\n            </span>\n          </div>\n          <div\n            class=\"KanbanCalculationOptions_CustomOption \"\n          >\n            <span>\n              Count Not Empty\n               \n              <i\n                class=\"CompassIcon icon-chevron-right ChevronRightIcon\"\n              />\n            </span>\n          </div>\n          <div\n            class=\"KanbanCalculationOptions_CustomOption \"\n          >\n            <span>\n              Percent Empty\n               \n              <i\n                class=\"CompassIcon icon-chevron-right ChevronRightIcon\"\n              />\n            </span>\n          </div>\n          <div\n            class=\"KanbanCalculationOptions_CustomOption \"\n          >\n            <span>\n              Percent Not Empty\n               \n              <i\n                class=\"CompassIcon icon-chevron-right ChevronRightIcon\"\n              />\n            </span>\n          </div>\n          <div\n            class=\"KanbanCalculationOptions_CustomOption \"\n          >\n            <span>\n              Count Value\n               \n              <i\n                class=\"CompassIcon icon-chevron-right ChevronRightIcon\"\n              />\n            </span>\n          </div>\n          <div\n            class=\"KanbanCalculationOptions_CustomOption \"\n          >\n            <span>\n              Count Unique Values\n               \n              <i\n                class=\"CompassIcon icon-chevron-right ChevronRightIcon\"\n              />\n            </span>\n          </div>\n        </div>\n      </div>\n      <input\n        name=\"calculation_options\"\n        type=\"hidden\"\n        value=\"count\"\n      />\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/kanban/calculation/KanbanCalculation no menu should appear in readonly mode 1`] = `\n<div>\n  <div\n    class=\"KanbanCalculation\"\n  >\n    <button\n      class=\"Button\"\n      title=\"3\"\n      type=\"button\"\n    >\n      <span>\n        3\n      </span>\n    </button>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/kanban/calculation/__snapshots__/calculationOptions.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/kanban/calculations/KanbanCalculationOptions base case 1`] = `\n<div>\n  <div\n    class=\"CalculationOptions css-b62m3t-container\"\n  >\n    <span\n      class=\"css-1f43avz-a11yText-A11yText\"\n      id=\"react-select-2-live-region\"\n    />\n    <span\n      aria-atomic=\"false\"\n      aria-live=\"polite\"\n      aria-relevant=\"additions text\"\n      class=\"css-1f43avz-a11yText-A11yText\"\n      role=\"log\"\n    />\n    <div\n      class=\"CalculationOptions__control css-1s59geg-Control\"\n    >\n      <div\n        class=\"CalculationOptions__value-container CalculationOptions__value-container--has-value css-1kliayw-ValueContainer\"\n      >\n        <div\n          class=\"CalculationOptions__single-value css-3hkq9s-singleValue\"\n        >\n          Count\n        </div>\n        <input\n          aria-activedescendant=\"\"\n          aria-autocomplete=\"list\"\n          aria-expanded=\"false\"\n          aria-haspopup=\"true\"\n          aria-readonly=\"true\"\n          class=\"css-mohuvp-dummyInput-DummyInput\"\n          id=\"react-select-2-input\"\n          inputmode=\"none\"\n          role=\"combobox\"\n          tabindex=\"0\"\n          value=\"\"\n        />\n      </div>\n      <div\n        class=\"CalculationOptions__indicators css-1hb7zxy-IndicatorsContainer\"\n      >\n        <div\n          aria-hidden=\"true\"\n          class=\"CalculationOptions__indicator CalculationOptions__clear-indicator css-31haax-indicatorContainer\"\n        >\n          <svg\n            aria-hidden=\"true\"\n            class=\"css-tj5bde-Svg\"\n            focusable=\"false\"\n            height=\"20\"\n            viewBox=\"0 0 20 20\"\n            width=\"20\"\n          >\n            <path\n              d=\"M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z\"\n            />\n          </svg>\n        </div>\n        <span\n          class=\"CalculationOptions__indicator-separator css-1uei4ir-indicatorSeparator\"\n        />\n        <div\n          aria-hidden=\"true\"\n          class=\"CalculationOptions__indicator CalculationOptions__dropdown-indicator css-y45573-indicatorContainer\"\n        >\n          <i\n            class=\"CompassIcon icon-chevron-up ChevronUpIcon\"\n          />\n        </div>\n      </div>\n    </div>\n    <input\n      name=\"calculation_options\"\n      type=\"hidden\"\n      value=\"count\"\n    />\n  </div>\n</div>\n`;\n\nexports[`components/kanban/calculations/KanbanCalculationOptions with menu open 1`] = `\n<div>\n  <div\n    class=\"CalculationOptions css-b62m3t-container\"\n  >\n    <span\n      class=\"css-1f43avz-a11yText-A11yText\"\n      id=\"react-select-3-live-region\"\n    />\n    <span\n      aria-atomic=\"false\"\n      aria-live=\"polite\"\n      aria-relevant=\"additions text\"\n      class=\"css-1f43avz-a11yText-A11yText\"\n      role=\"log\"\n    />\n    <div\n      class=\"CalculationOptions__control CalculationOptions__control--menu-is-open css-1s59geg-Control\"\n    >\n      <div\n        class=\"CalculationOptions__value-container CalculationOptions__value-container--has-value css-1kliayw-ValueContainer\"\n      >\n        <div\n          class=\"CalculationOptions__single-value css-3hkq9s-singleValue\"\n        >\n          Count\n        </div>\n        <input\n          aria-activedescendant=\"\"\n          aria-autocomplete=\"list\"\n          aria-controls=\"react-select-3-listbox\"\n          aria-expanded=\"true\"\n          aria-haspopup=\"true\"\n          aria-readonly=\"true\"\n          class=\"css-mohuvp-dummyInput-DummyInput\"\n          id=\"react-select-3-input\"\n          inputmode=\"none\"\n          role=\"combobox\"\n          tabindex=\"0\"\n          value=\"\"\n        />\n      </div>\n      <div\n        class=\"CalculationOptions__indicators css-1hb7zxy-IndicatorsContainer\"\n      >\n        <div\n          aria-hidden=\"true\"\n          class=\"CalculationOptions__indicator CalculationOptions__clear-indicator css-31haax-indicatorContainer\"\n        >\n          <svg\n            aria-hidden=\"true\"\n            class=\"css-tj5bde-Svg\"\n            focusable=\"false\"\n            height=\"20\"\n            viewBox=\"0 0 20 20\"\n            width=\"20\"\n          >\n            <path\n              d=\"M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z\"\n            />\n          </svg>\n        </div>\n        <span\n          class=\"CalculationOptions__indicator-separator css-1uei4ir-indicatorSeparator\"\n        />\n        <div\n          aria-hidden=\"true\"\n          class=\"CalculationOptions__indicator CalculationOptions__dropdown-indicator css-y45573-indicatorContainer\"\n        >\n          <i\n            class=\"CompassIcon icon-chevron-up ChevronUpIcon\"\n          />\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"CalculationOptions__menu css-45h7mv-menu\"\n    >\n      <div\n        aria-multiselectable=\"false\"\n        class=\"CalculationOptions__menu-list css-1d1qzc4-MenuList\"\n        id=\"react-select-3-listbox\"\n        role=\"listbox\"\n      >\n        <div\n          class=\"KanbanCalculationOptions_CustomOption active\"\n        >\n          <span>\n            Count\n             \n          </span>\n        </div>\n        <div\n          class=\"KanbanCalculationOptions_CustomOption \"\n        >\n          <span>\n            Count Empty\n             \n            <i\n              class=\"CompassIcon icon-chevron-right ChevronRightIcon\"\n            />\n          </span>\n        </div>\n        <div\n          class=\"KanbanCalculationOptions_CustomOption \"\n        >\n          <span>\n            Count Not Empty\n             \n            <i\n              class=\"CompassIcon icon-chevron-right ChevronRightIcon\"\n            />\n          </span>\n        </div>\n        <div\n          class=\"KanbanCalculationOptions_CustomOption \"\n        >\n          <span>\n            Percent Empty\n             \n            <i\n              class=\"CompassIcon icon-chevron-right ChevronRightIcon\"\n            />\n          </span>\n        </div>\n        <div\n          class=\"KanbanCalculationOptions_CustomOption \"\n        >\n          <span>\n            Percent Not Empty\n             \n            <i\n              class=\"CompassIcon icon-chevron-right ChevronRightIcon\"\n            />\n          </span>\n        </div>\n        <div\n          class=\"KanbanCalculationOptions_CustomOption \"\n        >\n          <span>\n            Count Value\n             \n            <i\n              class=\"CompassIcon icon-chevron-right ChevronRightIcon\"\n            />\n          </span>\n        </div>\n        <div\n          class=\"KanbanCalculationOptions_CustomOption \"\n        >\n          <span>\n            Count Unique Values\n             \n            <i\n              class=\"CompassIcon icon-chevron-right ChevronRightIcon\"\n            />\n          </span>\n        </div>\n      </div>\n    </div>\n    <input\n      name=\"calculation_options\"\n      type=\"hidden\"\n      value=\"count\"\n    />\n  </div>\n</div>\n`;\n\nexports[`components/kanban/calculations/KanbanCalculationOptions with submenu open 1`] = `\n<div>\n  <div\n    class=\"CalculationOptions css-b62m3t-container\"\n  >\n    <span\n      class=\"css-1f43avz-a11yText-A11yText\"\n      id=\"react-select-4-live-region\"\n    />\n    <span\n      aria-atomic=\"false\"\n      aria-live=\"polite\"\n      aria-relevant=\"additions text\"\n      class=\"css-1f43avz-a11yText-A11yText\"\n      role=\"log\"\n    />\n    <div\n      class=\"CalculationOptions__control CalculationOptions__control--menu-is-open css-1s59geg-Control\"\n    >\n      <div\n        class=\"CalculationOptions__value-container CalculationOptions__value-container--has-value css-1kliayw-ValueContainer\"\n      >\n        <div\n          class=\"CalculationOptions__single-value css-3hkq9s-singleValue\"\n        >\n          Count\n        </div>\n        <input\n          aria-activedescendant=\"\"\n          aria-autocomplete=\"list\"\n          aria-controls=\"react-select-4-listbox\"\n          aria-expanded=\"true\"\n          aria-haspopup=\"true\"\n          aria-readonly=\"true\"\n          class=\"css-mohuvp-dummyInput-DummyInput\"\n          id=\"react-select-4-input\"\n          inputmode=\"none\"\n          role=\"combobox\"\n          tabindex=\"0\"\n          value=\"\"\n        />\n      </div>\n      <div\n        class=\"CalculationOptions__indicators css-1hb7zxy-IndicatorsContainer\"\n      >\n        <div\n          aria-hidden=\"true\"\n          class=\"CalculationOptions__indicator CalculationOptions__clear-indicator css-31haax-indicatorContainer\"\n        >\n          <svg\n            aria-hidden=\"true\"\n            class=\"css-tj5bde-Svg\"\n            focusable=\"false\"\n            height=\"20\"\n            viewBox=\"0 0 20 20\"\n            width=\"20\"\n          >\n            <path\n              d=\"M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z\"\n            />\n          </svg>\n        </div>\n        <span\n          class=\"CalculationOptions__indicator-separator css-1uei4ir-indicatorSeparator\"\n        />\n        <div\n          aria-hidden=\"true\"\n          class=\"CalculationOptions__indicator CalculationOptions__dropdown-indicator css-y45573-indicatorContainer\"\n        >\n          <i\n            class=\"CompassIcon icon-chevron-up ChevronUpIcon\"\n          />\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"CalculationOptions__menu css-45h7mv-menu\"\n    >\n      <div\n        aria-multiselectable=\"false\"\n        class=\"CalculationOptions__menu-list css-1d1qzc4-MenuList\"\n        id=\"react-select-4-listbox\"\n        role=\"listbox\"\n      >\n        <div\n          class=\"KanbanCalculationOptions_CustomOption active\"\n        >\n          <span>\n            Count\n             \n          </span>\n        </div>\n        <div\n          class=\"KanbanCalculationOptions_CustomOption \"\n        >\n          <span>\n            Count Empty\n             \n            <i\n              class=\"CompassIcon icon-chevron-right ChevronRightIcon\"\n            />\n          </span>\n        </div>\n        <div\n          class=\"KanbanCalculationOptions_CustomOption \"\n        >\n          <span>\n            Count Not Empty\n             \n            <i\n              class=\"CompassIcon icon-chevron-right ChevronRightIcon\"\n            />\n          </span>\n        </div>\n        <div\n          class=\"KanbanCalculationOptions_CustomOption \"\n        >\n          <span>\n            Percent Empty\n             \n            <i\n              class=\"CompassIcon icon-chevron-right ChevronRightIcon\"\n            />\n          </span>\n        </div>\n        <div\n          class=\"KanbanCalculationOptions_CustomOption \"\n        >\n          <span>\n            Percent Not Empty\n             \n            <i\n              class=\"CompassIcon icon-chevron-right ChevronRightIcon\"\n            />\n          </span>\n        </div>\n        <div\n          class=\"KanbanCalculationOptions_CustomOption \"\n        >\n          <span>\n            Count Value\n             \n            <i\n              class=\"CompassIcon icon-chevron-right ChevronRightIcon\"\n            />\n          </span>\n        </div>\n        <div\n          class=\"KanbanCalculationOptions_CustomOption \"\n        >\n          <span>\n            Count Unique Values\n             \n            <i\n              class=\"CompassIcon icon-chevron-right ChevronRightIcon\"\n            />\n          </span>\n          <div\n            class=\"dropdown-submenu\"\n            style=\"top: -10px; left: 0px;\"\n          >\n            <div\n              class=\"drops \"\n            >\n              <span>\n                Status\n              </span>\n            </div>\n            <div\n              class=\"drops active\"\n            >\n              <span>\n                Property 1\n              </span>\n            </div>\n            <div\n              class=\"drops \"\n            >\n              <span>\n                Property 2\n              </span>\n            </div>\n            <div\n              class=\"drops \"\n            >\n              <span>\n                Property 3\n              </span>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <input\n      name=\"calculation_options\"\n      type=\"hidden\"\n      value=\"count\"\n    />\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/kanban/calculation/__snapshots__/kanbanOption.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/kanban/calculations/Option base case 1`] = `\n<div>\n  <div\n    class=\"KanbanCalculationOptions_CustomOption \"\n  >\n    <span>\n      Count Unique Values\n       \n      <i\n        class=\"CompassIcon icon-chevron-right ChevronRightIcon\"\n      />\n    </span>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/kanban/calculation/calculation.scss",
    "content": ".KanbanCalculation {\n    position: relative;\n\n    button {\n        cursor: pointer !important;\n        height: 24px;\n        padding: 0 6px;\n        min-width: 24px;\n\n        span {\n            max-width: 35px;\n            overflow: hidden;\n            text-overflow: ellipsis;\n            white-space: nowrap;\n        }\n\n        &:hover {\n            background-color: rgba(var(--center-channel-color-rgb), 0.1);\n        }\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/kanban/calculation/calculation.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\n\nimport {render} from '@testing-library/react'\n\nimport {TestBlockFactory} from '../../../test/testBlockFactory'\n\nimport {wrapIntl} from '../../../testUtils'\n\nimport {KanbanCalculation} from './calculation'\n\ndescribe('components/kanban/calculation/KanbanCalculation', () => {\n    const board = TestBlockFactory.createBoard()\n    const cards = [\n        TestBlockFactory.createCard(board),\n        TestBlockFactory.createCard(board),\n        TestBlockFactory.createCard(board),\n    ]\n\n    test('base case', () => {\n        const component = wrapIntl((\n            <KanbanCalculation\n                cards={cards}\n                cardProperties={board.cardProperties}\n                menuOpen={false}\n                onMenuClose={() => {}}\n                onMenuOpen={() => {}}\n                onChange={() => {}}\n                value={'count'}\n                property={board.cardProperties[0]}\n                readonly={false}\n            />\n        ))\n\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('calculations menu open', () => {\n        const component = wrapIntl((\n            <KanbanCalculation\n                cards={cards}\n                cardProperties={board.cardProperties}\n                menuOpen={true}\n                onMenuClose={() => {}}\n                onMenuOpen={() => {}}\n                onChange={() => {}}\n                value={'count'}\n                property={board.cardProperties[0]}\n                readonly={false}\n            />\n        ))\n\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('no menu should appear in readonly mode', () => {\n        const component = wrapIntl((\n            <KanbanCalculation\n                cards={cards}\n                cardProperties={board.cardProperties}\n                menuOpen={true}\n                onMenuClose={() => {}}\n                onMenuOpen={() => {}}\n                onChange={() => {}}\n                value={'count'}\n                property={board.cardProperties[0]}\n                readonly={true}\n            />\n        ))\n\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/kanban/calculation/calculation.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\nimport {useIntl} from 'react-intl'\n\nimport {Card} from '../../../blocks/card'\nimport Button from '../../../widgets/buttons/button'\nimport './calculation.scss'\nimport {IPropertyTemplate} from '../../../blocks/board'\n\nimport Calculations from '../../calculations/calculations'\n\nimport {KanbanCalculationOptions} from './calculationOptions'\n\ntype Props = {\n    cards: Card[]\n    cardProperties: IPropertyTemplate[]\n    menuOpen: boolean\n    onMenuClose: () => void\n    onMenuOpen: () => void\n    onChange: (data: { calculation: string, propertyId: string }) => void\n    value: string\n    property: IPropertyTemplate\n    readonly: boolean\n}\n\nfunction KanbanCalculation(props: Props): JSX.Element {\n    const intl = useIntl()\n\n    return (\n        <div className='KanbanCalculation'>\n            <Button\n                onClick={() => (props.menuOpen ? props.onMenuClose : props.onMenuOpen)()}\n                onBlur={props.onMenuClose}\n                title={Calculations[props.value] ? Calculations[props.value](props.cards, props.property, intl) : ''}\n            >\n                {Calculations[props.value] ? Calculations[props.value](props.cards, props.property, intl) : ''}\n            </Button>\n\n            {\n                !props.readonly && props.menuOpen && (\n                    <KanbanCalculationOptions\n                        value={props.value}\n                        property={props.property}\n                        menuOpen={props.menuOpen}\n                        onChange={(data: { calculation: string, propertyId: string }) => {\n                            props.onChange(data)\n                            props.onMenuClose()\n                        }}\n                        cardProperties={props.cardProperties}\n                    />\n                )\n            }\n        </div>\n    )\n}\n\nexport {\n    KanbanCalculation,\n}\n"
  },
  {
    "path": "webapp/src/components/kanban/calculation/calculationOption.scss",
    "content": "@import '../../../styles/z-index';\n\n.KanbanCalculationOptions_CustomOption {\n    color: rgba(var(--center-channel-color-rgb), 1);\n    min-height: 36px;\n    max-width: 220px;\n    width: 240px;\n    padding: 2px 20px;\n    cursor: pointer;\n    position: relative;\n    display: flex;\n\n    &:hover {\n        background: rgba(var(--center-channel-color-rgb), 0.1);\n    }\n\n    span {\n        overflow: hidden;\n        align-items: center;\n        display: flex;\n        justify-content: space-between;\n        width: 100%;\n    }\n\n    .drops {\n        max-width: 220px;\n        width: 240px;\n        padding: 2px 20px;\n        height: 36px;\n        display: flex;\n        align-items: center;\n\n        &.active,\n        &.active:hover {\n            background-color: rgba(var(--button-bg-rgb), 0.08);\n            color: rgb(var(--button-text-rgb));\n        }\n    }\n\n    .customs:hover,\n    .drops:hover {\n        background: rgba(var(--center-channel-color-rgb), 0.08);\n    }\n\n    .dropdown-submenu {\n        @include z-index(calculation-dropdown);\n        position: fixed;\n        overflow: auto;\n        background: rgb(var(--center-channel-bg-rgb));\n        border: 0;\n        box-shadow: 0 0 0 1px hsla(0, 0%, 0%, 0.1), 0 4px 11px hsla(0, 0%, 0%, 0.1);\n        padding: 8px 0;\n        border-radius: 4px;\n    }\n\n    .CompassIcon.icon-chevron-right {\n        float: right;\n        font-size: 18px;\n        color: rgba(var(--center-channel-color-rgb), 0.56);\n    }\n\n    &.active {\n        background-color: rgba(var(--button-bg-rgb), 0.08);\n\n        > span {\n            color: rgb(var(--button-text-rgb));\n        }\n    }\n}\n\n.CalculationOptions__menu {\n    .CalculationOptions__menu-list {\n        max-height: 400px;\n        padding: 8px 0;\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/kanban/calculation/calculationOptions.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\n\nimport {render} from '@testing-library/react'\n\nimport userEvent from '@testing-library/user-event'\n\nimport {TestBlockFactory} from '../../../test/testBlockFactory'\nimport {wrapIntl} from '../../../testUtils'\n\nimport {KanbanCalculationOptions} from './calculationOptions'\n\ndescribe('components/kanban/calculations/KanbanCalculationOptions', () => {\n    const board = TestBlockFactory.createBoard()\n\n    test('base case', () => {\n        const component = wrapIntl(\n            <KanbanCalculationOptions\n                value={'count'}\n                property={board.cardProperties[1]}\n                menuOpen={false}\n                onChange={() => {}}\n                cardProperties={board.cardProperties}\n            />,\n        )\n\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('with menu open', () => {\n        const component = wrapIntl(\n            <KanbanCalculationOptions\n                value={'count'}\n                property={board.cardProperties[1]}\n                menuOpen={true}\n                onChange={() => {}}\n                cardProperties={board.cardProperties}\n            />,\n        )\n\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('with submenu open', () => {\n        const component = wrapIntl(\n            <KanbanCalculationOptions\n                value={'count'}\n                property={board.cardProperties[1]}\n                menuOpen={true}\n                onChange={() => {}}\n                cardProperties={board.cardProperties}\n            />,\n        )\n\n        const {container, getByText} = render(component)\n        const countUniqueValuesOption = getByText('Count Unique Values')\n        expect(countUniqueValuesOption).toBeDefined()\n        userEvent.hover(countUniqueValuesOption)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('duplicate property types', () => {\n        const boardWithProps = TestBlockFactory.createBoard()\n        boardWithProps.cardProperties.push({\n            id: 'number-property-1',\n            name: 'A Number Property - 1',\n            type: 'number',\n            options: [],\n        })\n        boardWithProps.cardProperties.push({\n            id: 'number-property-2',\n            name: 'A Number Propert - 2y',\n            type: 'number',\n            options: [],\n        })\n\n        const component = wrapIntl(\n            <KanbanCalculationOptions\n                value={'count'}\n                property={boardWithProps.cardProperties[1]}\n                menuOpen={true}\n                onChange={() => {}}\n                cardProperties={boardWithProps.cardProperties}\n            />,\n        )\n\n        const {getAllByText} = render(component)\n        const sumOptions = getAllByText('Sum')\n        expect(sumOptions).toBeDefined()\n        expect(sumOptions.length).toBe(1)\n    })\n\n    test('effectively date fields', () => {\n        // date, created time and updated time are effectively date fields.\n        // Only one set of date related menus should show up for all of them.\n\n        const boardWithProps = TestBlockFactory.createBoard()\n        boardWithProps.cardProperties.push({\n            id: 'date',\n            name: 'Date',\n            type: 'date',\n            options: [],\n        })\n        boardWithProps.cardProperties.push({\n            id: 'created-time',\n            name: 'Created Time',\n            type: 'createdTime',\n            options: [],\n        })\n        boardWithProps.cardProperties.push({\n            id: 'updated-time',\n            name: 'Updated Time',\n            type: 'updatedTime',\n            options: [],\n        })\n\n        const component = wrapIntl(\n            <KanbanCalculationOptions\n                value={'count'}\n                property={boardWithProps.cardProperties[1]}\n                menuOpen={true}\n                onChange={() => {}}\n                cardProperties={boardWithProps.cardProperties}\n            />,\n        )\n\n        const {getAllByText} = render(component)\n\n        const earliestDateMenu = getAllByText('Earliest Date')\n        expect(earliestDateMenu).toBeDefined()\n        expect(earliestDateMenu.length).toBe(1)\n\n        const latestDateMenu = getAllByText('Latest Date')\n        expect(latestDateMenu).toBeDefined()\n        expect(latestDateMenu.length).toBe(1)\n\n        const dateRangeMenu = getAllByText('Date Range')\n        expect(dateRangeMenu).toBeDefined()\n        expect(dateRangeMenu.length).toBe(1)\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/kanban/calculation/calculationOptions.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\n\nimport {\n    CalculationOptions,\n    CommonCalculationOptionProps,\n    optionsByType,\n} from '../../calculations/options'\nimport {IPropertyTemplate, PropertyTypeEnum} from '../../../blocks/board'\n\nimport './calculationOption.scss'\nimport {Option, OptionProps} from './kanbanOption'\n\ntype Props = CommonCalculationOptionProps & {\n    cardProperties: IPropertyTemplate[]\n    onChange: (data: {calculation: string, propertyId: string}) => void\n}\n\n// contains mapping of property types which are effectly the same as other property type.\nconst equivalentPropertyType = new Map<PropertyTypeEnum, PropertyTypeEnum>([\n    ['createdTime', 'date'],\n    ['updatedTime', 'date'],\n])\n\nexport function getEquivalentPropertyType(propertyType: PropertyTypeEnum): PropertyTypeEnum {\n    return equivalentPropertyType.get(propertyType) || propertyType\n}\n\nexport const KanbanCalculationOptions = (props: Props): JSX.Element => {\n    const options: OptionProps[] = []\n\n    // Show common options, first,\n    // followed by type-specific functions\n    optionsByType.get('common')!.forEach((typeOption) => {\n        if (typeOption.value !== 'none') {\n            options.push({\n                ...typeOption,\n                cardProperties: props.cardProperties,\n                onChange: props.onChange,\n                activeValue: props.value,\n                activeProperty: props.property!,\n            })\n        }\n    })\n\n    const seen: Record<string, boolean> = {}\n    props.cardProperties.forEach((property) => {\n        // skip already processed property types\n        if (seen[getEquivalentPropertyType(property.type)]) {\n            return\n        }\n\n        (optionsByType.get(property.type) || []).\n            forEach((typeOption) => {\n                options.push({\n                    ...typeOption,\n                    cardProperties: props.cardProperties,\n                    onChange: props.onChange,\n                    activeValue: props.value,\n                    activeProperty: props.property!,\n                })\n            })\n\n        seen[getEquivalentPropertyType(property.type)] = true\n    })\n\n    return (\n        <CalculationOptions\n            value={props.value}\n            menuOpen={props.menuOpen}\n            onClose={props.onClose}\n            onChange={props.onChange}\n            property={props.property}\n            options={options}\n            components={{Option}}\n        />\n    )\n}\n"
  },
  {
    "path": "webapp/src/components/kanban/calculation/kanbanOption.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\n\nimport {render} from '@testing-library/react'\n\nimport {TestBlockFactory} from '../../../test/testBlockFactory'\n\nimport {Option} from './kanbanOption'\n\ndescribe('components/kanban/calculations/Option', () => {\n    const board = TestBlockFactory.createBoard()\n\n    test('base case', () => {\n        const component = (\n            <Option\n                data={{\n                    label: 'Count Unique Values',\n                    displayName: 'Unique',\n                    value: 'countUniqueValue',\n                    cardProperties: board.cardProperties,\n                    onChange: () => {},\n                    activeValue: 'count',\n                    activeProperty: board.cardProperties[1],\n                }}\n            />\n        )\n\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/kanban/calculation/kanbanOption.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useState} from 'react'\n\nimport {Option as SelectOption, typesByOptions} from '../../calculations/options'\nimport {IPropertyTemplate} from '../../../blocks/board'\nimport ChevronRight from '../../../widgets/icons/chevronRight'\nimport {Constants} from '../../../constants'\n\ntype OptionProps = SelectOption & {\n    cardProperties: IPropertyTemplate[]\n    onChange: (data: {calculation: string, propertyId: string}) => void\n    activeValue: string\n    activeProperty: IPropertyTemplate\n}\n\nconst Option = (props: {data: OptionProps}): JSX.Element => {\n    const [submenu, setSubmenu] = useState(false)\n    const [height, setHeight] = useState(0)\n    const [menuOptionRight, setMenuOptionRight] = useState(0)\n    const [calculationToProperties, setCalculationToProperties] = useState<Map<string, IPropertyTemplate[]>>(new Map())\n\n    const toggleOption = (e: any) => {\n        if (submenu) {\n            setSubmenu(false)\n        } else {\n            const rect = e.target.getBoundingClientRect()\n            setHeight(rect.y)\n            setMenuOptionRight(rect.x + rect.width)\n            setSubmenu(true)\n        }\n    }\n\n    if (!calculationToProperties.get(props.data.value)) {\n        const supportedPropertyTypes = new Map<string, boolean>([])\n        if (typesByOptions.get(props.data.value)) {\n            (typesByOptions.get(props.data.value) || []).\n                forEach((propertyType) => supportedPropertyTypes.set(propertyType, true))\n        }\n\n        const supportedProperties = props.data.cardProperties.\n            filter((property) => supportedPropertyTypes.get(property.type) || supportedPropertyTypes.get('common'))\n\n        calculationToProperties.set(props.data.value, supportedProperties)\n        setCalculationToProperties(calculationToProperties)\n    }\n\n    return (\n        <div\n            className={`KanbanCalculationOptions_CustomOption ${props.data.activeValue === props.data.value ? 'active' : ''}`}\n            onMouseEnter={toggleOption}\n            onMouseLeave={toggleOption}\n            onClick={() => {\n                if (props.data.value !== 'count') {\n                    return\n                }\n\n                props.data.onChange({\n                    calculation: 'count',\n                    propertyId: Constants.titleColumnId,\n                })\n            }}\n        >\n            <span>\n                {props.data.label} {props.data.value !== 'count' && <ChevronRight/>}\n            </span>\n\n            {\n                submenu && props.data.value !== 'count' && (\n                    <div\n                        className='dropdown-submenu'\n                        style={{top: `${height - 10}px`, left: `${menuOptionRight}px`}}\n                    >\n\n                        {\n                            calculationToProperties.get(props.data.value) &&\n                            calculationToProperties.get(props.data.value)!.map((property) => (\n                                <div\n                                    key={property.id}\n                                    className={`drops ${props.data.activeProperty.id === property.id ? 'active' : ''}`}\n                                    onClick={() => {\n                                        props.data.onChange({\n                                            calculation: props.data.value,\n                                            propertyId: property.id,\n                                        })\n                                    }}\n                                >\n                                    <span>{property.name}</span>\n                                </div>\n                            ))\n                        }\n                    </div>\n                )\n            }\n        </div>\n    )\n}\n\nexport {\n    Option,\n    OptionProps,\n}\n"
  },
  {
    "path": "webapp/src/components/kanban/kanban.scss",
    "content": "@import '../../styles/z-index';\n\n.Kanban {\n    overflow: auto;\n    flex: 1;\n\n    .octo-board-header {\n        @include z-index(kanban-board-header);\n        display: flex;\n        flex-direction: row;\n        width: max-content;\n        min-height: 30px;\n        padding: 16px 0;\n        color: #909090;\n        position: sticky;\n        top: 0;\n        background: rgb(var(--center-channel-bg-rgb));\n    }\n\n    .octo-board-header-cell {\n        display: flex;\n        flex-shrink: 0;\n        align-items: center;\n        width: 260px;\n        margin-right: 15px;\n        vertical-align: middle;\n\n        &.narrow {\n            width: 220px;\n        }\n\n        > div {\n            &:last-child {\n                margin: 0;\n            }\n        }\n\n        .IconButton {\n            background-color: unset;\n\n            &:hover {\n                background-color: rgba(var(--center-channel-color-rgb), 0.1);\n            }\n        }\n\n        .Label {\n            max-width: 165px;\n            margin: 0 8px 0 0;\n\n            &.empty {\n                color: rgba(var(--center-channel-color-rgb), 1);\n                font-weight: 600;\n            }\n\n            .Editable {\n                color: rgba(var(--center-channel-color-rgb), 1);\n                background: transparent;\n            }\n        }\n\n        > .Button {\n            cursor: auto;\n\n            &.IconButton {\n                cursor: pointer;\n            }\n        }\n    }\n\n    .octo-board-body {\n        display: flex;\n        flex-direction: row;\n        flex: 0 1 auto;\n        margin-top: 2px;\n    }\n\n    .octo-board-column {\n        flex: 0 0 auto;\n\n        display: flex;\n        flex-direction: column;\n\n        width: 260px;\n        margin-right: 15px;\n\n        &.narrow {\n            width: 220px;\n        }\n    }\n\n    .octo-board-hidden-item {\n        display: flex;\n        flex-direction: row;\n        align-items: center;\n\n        > div {\n            margin-right: 5px;\n        }\n\n        .Label {\n            margin: 5px;\n        }\n    }\n\n    .hidden-card {\n        display: block;\n    }\n\n    .kanban-hidden-cards {\n        margin-left: 4px;\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/kanban/kanban.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {fireEvent, render, screen, waitFor} from '@testing-library/react'\nimport '@testing-library/jest-dom'\nimport React from 'react'\nimport {Provider as ReduxProvider} from 'react-redux'\nimport {MemoryRouter} from 'react-router-dom'\nimport {mocked} from 'jest-mock'\nimport userEvent from '@testing-library/user-event'\n\nimport {IPropertyOption, IPropertyTemplate} from '../../blocks/board'\nimport {TestBlockFactory} from '../../test/testBlockFactory'\nimport {mockDOM, mockStateStore, wrapDNDIntl} from '../../testUtils'\nimport {Utils} from '../../utils'\nimport {mutator} from '../../mutator'\n\nimport Kanban from './kanban'\n\nglobal.fetch = jest.fn()\njest.mock('../../utils')\nconst mockedUtils = mocked(Utils, true)\nconst mockedchangePropertyOptionValue = jest.spyOn(mutator, 'changePropertyOptionValue')\nconst mockedChangeViewCardOrder = jest.spyOn(mutator, 'changeViewCardOrder')\nconst mockedinsertPropertyOption = jest.spyOn(mutator, 'insertPropertyOption')\n\ndescribe('src/component/kanban/kanban', () => {\n    const board = TestBlockFactory.createBoard()\n    const activeView = TestBlockFactory.createBoardView(board)\n    const card1 = TestBlockFactory.createCard(board)\n    card1.id = 'id1'\n    card1.fields.properties = {id: 'property_value_id_1'}\n    const card2 = TestBlockFactory.createCard(board)\n    card2.id = 'id2'\n    card2.fields.properties = {id: 'property_value_id_1'}\n    const card3 = TestBlockFactory.createCard(board)\n    card3.id = 'id3'\n    card3.fields.properties = {id: 'property_value_id_2'}\n    activeView.fields.kanbanCalculations = {\n        id1: {\n            calculation: 'countEmpty',\n            propertyId: '1',\n\n        },\n    }\n    const optionQ1: IPropertyOption = {\n        color: 'propColorOrange',\n        id: 'property_value_id_1',\n        value: 'Q1',\n    }\n    const optionQ2: IPropertyOption = {\n        color: 'propColorBlue',\n        id: 'property_value_id_2',\n        value: 'Q2',\n    }\n    const optionQ3: IPropertyOption = {\n        color: 'propColorDefault',\n        id: 'property_value_id_3',\n        value: 'Q3',\n    }\n\n    const groupProperty: IPropertyTemplate = {\n        id: 'id',\n        name: 'name',\n        type: 'text',\n        options: [optionQ1, optionQ2],\n    }\n\n    const state = {\n        users: {\n            me: {\n                id: 'user_id_1',\n                props: {},\n            },\n        },\n        cards: {\n            cards: [card1, card2, card3],\n            templates: [],\n        },\n        teams: {\n            current: {id: 'team-id'},\n        },\n        boards: {\n            current: 'board_id_1',\n            boards: {\n                board_id_1: {id: 'board_id_1'},\n            },\n            myBoardMemberships: {\n                board_id_1: {userId: 'user_id_1', schemeAdmin: true},\n            },\n        },\n        views: {\n            views: {\n                boardView: activeView,\n            },\n            current: 'boardView',\n        },\n        contents: {},\n        comments: {\n            comments: {},\n        },\n    }\n    const store = mockStateStore([], state)\n    beforeAll(() => {\n        console.error = jest.fn()\n        mockDOM()\n    })\n    beforeEach(jest.resetAllMocks)\n    test('should match snapshot', () => {\n        const {container} = render(wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <Kanban\n                    board={board}\n                    activeView={activeView}\n                    cards={[card1, card2, card3]}\n                    groupByProperty={groupProperty}\n                    visibleGroups={[\n                        {\n                            option: optionQ1,\n                            cards: [card1, card2],\n                        }, {\n                            option: optionQ2,\n                            cards: [card3],\n                        },\n                    ]}\n                    hiddenGroups={[\n                        {\n                            option: optionQ3,\n                            cards: [],\n                        },\n                    ]}\n                    selectedCardIds={[]}\n                    readonly={false}\n                    onCardClicked={jest.fn()}\n                    addCard={jest.fn()}\n                    addCardFromTemplate={jest.fn()}\n                    showCard={jest.fn()}\n                    hiddenCardsCount={0}\n                    showHiddenCardCountNotification={jest.fn()}\n                />\n            </ReduxProvider>,\n        ), {wrapper: MemoryRouter})\n        expect(container).toMatchSnapshot()\n    })\n    test('should match snapshot without permissions', () => {\n        const localStore = mockStateStore([], {...state, teams: {current: undefined}})\n        const {container} = render(wrapDNDIntl(\n            <ReduxProvider store={localStore}>\n                <Kanban\n                    board={board}\n                    activeView={activeView}\n                    cards={[card1, card2, card3]}\n                    groupByProperty={groupProperty}\n                    visibleGroups={[\n                        {\n                            option: optionQ1,\n                            cards: [card1, card2],\n                        }, {\n                            option: optionQ2,\n                            cards: [card3],\n                        },\n                    ]}\n                    hiddenGroups={[\n                        {\n                            option: optionQ3,\n                            cards: [],\n                        },\n                    ]}\n                    selectedCardIds={[]}\n                    readonly={false}\n                    onCardClicked={jest.fn()}\n                    addCard={jest.fn()}\n                    addCardFromTemplate={jest.fn()}\n                    showCard={jest.fn()}\n                    hiddenCardsCount={0}\n                    showHiddenCardCountNotification={jest.fn()}\n                />\n            </ReduxProvider>,\n        ), {wrapper: MemoryRouter})\n        expect(container).toMatchSnapshot()\n    })\n    test('do not return a kanban with groupByProperty undefined', () => {\n        const {container} = render(wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <Kanban\n                    board={board}\n                    activeView={activeView}\n                    cards={[card1, card2]}\n                    groupByProperty={undefined}\n                    visibleGroups={[\n                        {\n                            option: optionQ1,\n                            cards: [card1, card2],\n                        }, {\n                            option: optionQ2,\n                            cards: [card3],\n                        },\n                    ]}\n                    hiddenGroups={[\n                        {\n                            option: optionQ3,\n                            cards: [],\n                        },\n                    ]}\n                    selectedCardIds={[]}\n                    readonly={false}\n                    onCardClicked={jest.fn()}\n                    addCard={jest.fn()}\n                    addCardFromTemplate={jest.fn()}\n                    showCard={jest.fn()}\n                    hiddenCardsCount={0}\n                    showHiddenCardCountNotification={jest.fn()}\n                />\n            </ReduxProvider>,\n        ), {wrapper: MemoryRouter})\n\n        expect(mockedUtils.assertFailure).toBeCalled()\n        expect(container).toMatchSnapshot()\n    })\n    test('return kanban and drag card to other card ', async () => {\n        const {container} = render(wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <Kanban\n                    board={board}\n                    activeView={activeView}\n                    cards={[card1, card2]}\n                    groupByProperty={groupProperty}\n                    visibleGroups={[\n                        {\n                            option: optionQ1,\n                            cards: [card1, card2],\n                        }, {\n                            option: optionQ2,\n                            cards: [card3],\n                        },\n                    ]}\n                    hiddenGroups={[\n                        {\n                            option: optionQ3,\n                            cards: [],\n                        },\n                    ]}\n                    selectedCardIds={[]}\n                    readonly={false}\n                    onCardClicked={jest.fn()}\n                    addCard={jest.fn()}\n                    addCardFromTemplate={jest.fn()}\n                    showCard={jest.fn()}\n                    hiddenCardsCount={0}\n                    showHiddenCardCountNotification={jest.fn()}\n                />\n            </ReduxProvider>,\n        ), {wrapper: MemoryRouter})\n\n        const cardsElement = container.querySelectorAll('.KanbanCard')\n        expect(cardsElement).not.toBeNull()\n        expect(cardsElement).toHaveLength(3)\n        fireEvent.dragStart(cardsElement[0])\n        fireEvent.dragEnter(cardsElement[1])\n        fireEvent.dragOver(cardsElement[1])\n        fireEvent.drop(cardsElement[1])\n        expect(mockedUtils.log).toBeCalled()\n\n        await waitFor(async () => {\n            expect(mockedChangeViewCardOrder).toBeCalled()\n        })\n    })\n    test('return kanban and change card column', async () => {\n        const {container} = render(wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <Kanban\n                    board={board}\n                    activeView={activeView}\n                    cards={[card1, card2]}\n                    groupByProperty={groupProperty}\n                    visibleGroups={[\n                        {\n                            option: optionQ1,\n                            cards: [card1, card2],\n                        }, {\n                            option: optionQ2,\n                            cards: [card3],\n                        },\n                    ]}\n                    hiddenGroups={[\n                        {\n                            option: optionQ3,\n                            cards: [],\n                        },\n                    ]}\n                    selectedCardIds={[]}\n                    readonly={false}\n                    onCardClicked={jest.fn()}\n                    addCard={jest.fn()}\n                    addCardFromTemplate={jest.fn()}\n                    showCard={jest.fn()}\n                    hiddenCardsCount={0}\n                    showHiddenCardCountNotification={jest.fn()}\n                />\n            </ReduxProvider>,\n        ), {wrapper: MemoryRouter})\n\n        const cardsElement = container.querySelectorAll('.KanbanCard')\n        expect(cardsElement).not.toBeNull()\n        expect(cardsElement).toHaveLength(3)\n        const columnQ2Element = container.querySelector('.octo-board-column:nth-child(2)')\n        expect(columnQ2Element).toBeDefined()\n        fireEvent.dragStart(cardsElement[0])\n        fireEvent.dragEnter(columnQ2Element!)\n        fireEvent.dragOver(columnQ2Element!)\n        fireEvent.drop(columnQ2Element!)\n        await waitFor(async () => {\n            expect(mockedChangeViewCardOrder).toBeCalled()\n        })\n    })\n    test('return kanban and change card column to hidden column', async () => {\n        const {container} = render(wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <Kanban\n                    board={board}\n                    activeView={activeView}\n                    cards={[card1, card2]}\n                    groupByProperty={groupProperty}\n                    visibleGroups={[\n                        {\n                            option: optionQ1,\n                            cards: [card1, card2],\n                        }, {\n                            option: optionQ2,\n                            cards: [card3],\n                        },\n                    ]}\n                    hiddenGroups={[\n                        {\n                            option: optionQ3,\n                            cards: [],\n                        },\n                    ]}\n                    selectedCardIds={[]}\n                    readonly={false}\n                    onCardClicked={jest.fn()}\n                    addCard={jest.fn()}\n                    addCardFromTemplate={jest.fn()}\n                    showCard={jest.fn()}\n                    hiddenCardsCount={0}\n                    showHiddenCardCountNotification={jest.fn()}\n                />\n            </ReduxProvider>,\n        ), {wrapper: MemoryRouter})\n\n        const cardsElement = container.querySelectorAll('.KanbanCard')\n        expect(cardsElement).not.toBeNull()\n        expect(cardsElement).toHaveLength(3)\n        const columnQ3Element = container.querySelector('.octo-board-hidden-item')\n        expect(columnQ3Element).toBeDefined()\n        fireEvent.dragStart(cardsElement[0]!)\n        fireEvent.dragEnter(columnQ3Element!)\n        fireEvent.dragOver(columnQ3Element!)\n        fireEvent.drop(columnQ3Element!)\n        await waitFor(async () => {\n            expect(mockedChangeViewCardOrder).toBeCalled()\n        })\n    })\n    test('return kanban and click on New', () => {\n        const mockedAddCard = jest.fn()\n        render(wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <Kanban\n                    board={board}\n                    activeView={activeView}\n                    cards={[card1, card2]}\n                    groupByProperty={groupProperty}\n                    visibleGroups={[\n                        {\n                            option: optionQ1,\n                            cards: [card1, card2],\n                        }, {\n                            option: optionQ2,\n                            cards: [card3],\n                        },\n                    ]}\n                    hiddenGroups={[\n                        {\n                            option: optionQ3,\n                            cards: [],\n                        },\n                    ]}\n                    selectedCardIds={[]}\n                    readonly={false}\n                    onCardClicked={jest.fn()}\n                    addCard={mockedAddCard}\n                    addCardFromTemplate={jest.fn()}\n                    showCard={jest.fn()}\n                    hiddenCardsCount={0}\n                    showHiddenCardCountNotification={jest.fn()}\n                />\n            </ReduxProvider>,\n        ), {wrapper: MemoryRouter})\n        const allButtonsNew = screen.getAllByRole('button', {name: '+ New'})\n        expect(allButtonsNew).not.toBeNull()\n        userEvent.click(allButtonsNew[0])\n        expect(mockedAddCard).toBeCalledTimes(1)\n    })\n\n    test('return kanban and click on KanbanCalculationMenu', () => {\n        const {container} = render(wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <Kanban\n                    board={board}\n                    activeView={activeView}\n                    cards={[card1, card2]}\n                    groupByProperty={groupProperty}\n                    visibleGroups={[\n                        {\n                            option: optionQ1,\n                            cards: [card1, card2],\n                        }, {\n                            option: optionQ2,\n                            cards: [card3],\n                        },\n                    ]}\n                    hiddenGroups={[\n                        {\n                            option: optionQ3,\n                            cards: [],\n                        },\n                    ]}\n                    selectedCardIds={[]}\n                    readonly={false}\n                    onCardClicked={jest.fn()}\n                    addCard={jest.fn()}\n                    addCardFromTemplate={jest.fn()}\n                    showCard={jest.fn()}\n                    hiddenCardsCount={0}\n                    showHiddenCardCountNotification={jest.fn()}\n                />\n            </ReduxProvider>,\n        ), {wrapper: MemoryRouter})\n        const buttonKanbanCalculation = screen.getByRole('button', {name: '2'})\n        expect(buttonKanbanCalculation).toBeDefined()\n        userEvent.click(buttonKanbanCalculation!)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('return kanban and change title on KanbanColumnHeader', async () => {\n        const {container} = render(wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <Kanban\n                    board={board}\n                    activeView={activeView}\n                    cards={[card1, card2]}\n                    groupByProperty={groupProperty}\n                    visibleGroups={[\n                        {\n                            option: optionQ1,\n                            cards: [card1, card2],\n                        }, {\n                            option: optionQ2,\n                            cards: [card3],\n                        },\n                    ]}\n                    hiddenGroups={[\n                        {\n                            option: optionQ3,\n                            cards: [],\n                        },\n                    ]}\n                    selectedCardIds={[]}\n                    readonly={false}\n                    onCardClicked={jest.fn()}\n                    addCard={jest.fn()}\n                    addCardFromTemplate={jest.fn()}\n                    showCard={jest.fn()}\n                    hiddenCardsCount={0}\n                    showHiddenCardCountNotification={jest.fn()}\n                />\n            </ReduxProvider>,\n        ), {wrapper: MemoryRouter})\n\n        const inputTitle = screen.getByRole('textbox', {name: optionQ1.value})\n        expect(inputTitle).toBeDefined()\n        fireEvent.change(inputTitle, {target: {value: ''}})\n        userEvent.type(inputTitle, 'New Q1')\n        fireEvent.blur(inputTitle)\n\n        await waitFor(async () => {\n            expect(mockedchangePropertyOptionValue).toBeCalledWith(board.id, board.cardProperties, groupProperty, optionQ1, 'New Q1')\n        })\n\n        expect(container).toMatchSnapshot()\n    })\n    test('return kanban and add a group', async () => {\n        render(wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <Kanban\n                    board={board}\n                    activeView={activeView}\n                    cards={[card1, card2]}\n                    groupByProperty={groupProperty}\n                    visibleGroups={[\n                        {\n                            option: optionQ1,\n                            cards: [card1, card2],\n                        }, {\n                            option: optionQ2,\n                            cards: [card3],\n                        },\n                    ]}\n                    hiddenGroups={[\n                        {\n                            option: optionQ3,\n                            cards: [],\n                        },\n                    ]}\n                    selectedCardIds={[]}\n                    readonly={false}\n                    onCardClicked={jest.fn()}\n                    addCard={jest.fn()}\n                    addCardFromTemplate={jest.fn()}\n                    showCard={jest.fn()}\n                    hiddenCardsCount={0}\n                    showHiddenCardCountNotification={jest.fn()}\n                />\n            </ReduxProvider>,\n        ), {wrapper: MemoryRouter})\n        const buttonAddGroup = screen.getByRole('button', {name: '+ Add a group'})\n        expect(buttonAddGroup).toBeDefined()\n        userEvent.click(buttonAddGroup)\n        await waitFor(() => {\n            expect(mockedinsertPropertyOption).toBeCalled()\n        })\n    })\n})\n\ndescribe('src/component/kanban/kanban', () => {\n    const board = TestBlockFactory.createBoard()\n    const activeView = TestBlockFactory.createBoardView(board)\n    const card1 = TestBlockFactory.createCard(board)\n    card1.id = 'id1'\n    card1.fields.properties = {id: 'property_value_id_1'}\n    const card2 = TestBlockFactory.createCard(board)\n    card2.id = 'id2'\n    card2.fields.properties = {id: 'property_value_id_1'}\n    const card3 = TestBlockFactory.createCard(board)\n    card3.id = 'id3'\n    card3.boardId = 'board_id_1'\n    card3.fields.properties = {id: 'property_value_id_2'}\n    activeView.fields.kanbanCalculations = {\n        id1: {\n            calculation: 'countEmpty',\n            propertyId: '1',\n\n        },\n    }\n    activeView.fields.defaultTemplateId = card3.id\n    const optionQ1: IPropertyOption = {\n        color: 'propColorOrange',\n        id: 'property_value_id_1',\n        value: 'Q1',\n    }\n    const optionQ2: IPropertyOption = {\n        color: 'propColorBlue',\n        id: 'property_value_id_2',\n        value: 'Q2',\n    }\n    const optionQ3: IPropertyOption = {\n        color: 'propColorDefault',\n        id: 'property_value_id_3',\n        value: 'Q3',\n    }\n\n    const groupProperty: IPropertyTemplate = {\n        id: 'id',\n        name: 'name',\n        type: 'text',\n        options: [optionQ1, optionQ2],\n    }\n\n    const state = {\n        users: {\n            me: {\n                id: 'user_id_1',\n                props: {},\n            },\n        },\n        cards: {\n            cards: [card1, card2],\n            templates: [card3],\n        },\n        teams: {\n            current: {id: 'team-id'},\n        },\n        boards: {\n            current: 'board_id_1',\n            boards: {\n                board_id_1: {id: 'board_id_1'},\n            },\n            myBoardMemberships: {\n                board_id_1: {userId: 'user_id_1', schemeAdmin: true},\n            },\n        },\n        views: {\n            views: {\n                boardView: activeView,\n            },\n            current: 'boardView',\n        },\n        contents: {},\n        comments: {\n            comments: {},\n        },\n    }\n    const store = mockStateStore([], state)\n    beforeAll(() => {\n        console.error = jest.fn()\n        mockDOM()\n    })\n    beforeEach(jest.resetAllMocks)\n    test('return kanban and click on New if view have already have defaultTemplateId', () => {\n        const mockedAddCard = jest.fn()\n        render(wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <Kanban\n                    board={board}\n                    activeView={activeView}\n                    cards={[card1, card2]}\n                    groupByProperty={groupProperty}\n                    visibleGroups={[\n                        {\n                            option: optionQ1,\n                            cards: [card1, card2],\n                        }, {\n                            option: optionQ2,\n                            cards: [card3],\n                        },\n                    ]}\n                    hiddenGroups={[\n                        {\n                            option: optionQ3,\n                            cards: [],\n                        },\n                    ]}\n                    selectedCardIds={[]}\n                    readonly={false}\n                    onCardClicked={jest.fn()}\n                    addCard={jest.fn()}\n                    addCardFromTemplate={mockedAddCard}\n                    showCard={jest.fn()}\n                    hiddenCardsCount={0}\n                    showHiddenCardCountNotification={jest.fn()}\n                />\n            </ReduxProvider>,\n        ), {wrapper: MemoryRouter})\n        const allButtonsNew = screen.getAllByRole('button', {name: '+ New'})\n        expect(allButtonsNew).not.toBeNull()\n        userEvent.click(allButtonsNew[0])\n        expect(mockedAddCard).toBeCalledTimes(1)\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/kanban/kanban.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n/* eslint-disable max-lines */\nimport React, {useCallback, useState, useMemo, useEffect} from 'react'\nimport {FormattedMessage, injectIntl, IntlShape} from 'react-intl'\n\nimport withScrolling, {createHorizontalStrength, createVerticalStrength} from 'react-dnd-scrolling'\n\nimport {useAppSelector} from '../../store/hooks'\n\nimport {Position} from '../cardDetail/cardDetailContents'\n\nimport {Board, IPropertyOption, IPropertyTemplate, BoardGroup} from '../../blocks/board'\nimport {Card} from '../../blocks/card'\nimport {BoardView} from '../../blocks/boardView'\nimport mutator from '../../mutator'\nimport {Utils, IDType} from '../../utils'\nimport Button from '../../widgets/buttons/button'\nimport {Constants, Permission} from '../../constants'\n\nimport {dragAndDropRearrange} from '../cardDetail/cardDetailContentsUtility'\n\nimport {getCurrentBoardTemplates} from '../../store/cards'\nimport BoardPermissionGate from '../permissions/boardPermissionGate'\nimport HiddenCardCount from '../../components/hiddenCardCount/hiddenCardCount'\n\nimport KanbanCard from './kanbanCard'\nimport KanbanColumn from './kanbanColumn'\nimport KanbanColumnHeader from './kanbanColumnHeader'\nimport KanbanHiddenColumnItem from './kanbanHiddenColumnItem'\n\nimport './kanban.scss'\n\ntype Props = {\n    board: Board\n    activeView: BoardView\n    cards: Card[]\n    groupByProperty?: IPropertyTemplate\n    visibleGroups: BoardGroup[]\n    hiddenGroups: BoardGroup[]\n    selectedCardIds: string[]\n    intl: IntlShape\n    readonly: boolean\n    onCardClicked: (e: React.MouseEvent, card: Card) => void\n    addCard: (groupByOptionId?: string, show?: boolean) => Promise<void>\n    addCardFromTemplate: (cardTemplateId: string, groupByOptionId?: string) => void\n    showCard: (cardId?: string) => void\n    hiddenCardsCount: number\n    showHiddenCardCountNotification: (show: boolean) => void\n}\n\nconst ScrollingComponent = withScrolling('div')\nconst hStrength = createHorizontalStrength(Utils.isMobile() ? 60 : 250)\nconst vStrength = createVerticalStrength(Utils.isMobile() ? 60 : 250)\n\nconst Kanban = (props: Props) => {\n    const cardTemplates: Card[] = useAppSelector(getCurrentBoardTemplates)\n    const {board, activeView, cards, groupByProperty, visibleGroups, hiddenGroups, hiddenCardsCount} = props\n    const [defaultTemplateID, setDefaultTemplateID] = useState<string>()\n\n    useEffect(() => {\n        if (activeView.fields.defaultTemplateId) {\n            if (cardTemplates.find((ct) => ct.id === activeView.fields.defaultTemplateId)) {\n                setDefaultTemplateID(activeView.fields.defaultTemplateId)\n            }\n        }\n    }, [activeView.fields.defaultTemplateId])\n\n    const propertyValues = groupByProperty?.options || []\n    Utils.log(`${propertyValues.length} propertyValues`)\n\n    const visiblePropertyTemplates = useMemo(() => {\n        return board.cardProperties.filter(\n            (template: IPropertyTemplate) => activeView.fields.visiblePropertyIds.includes(template.id),\n        )\n    }, [board.cardProperties, activeView.fields.visiblePropertyIds])\n    const isManualSort = activeView.fields.sortOptions.length === 0\n    const visibleBadges = activeView.fields.visiblePropertyIds.includes(Constants.badgesColumnId)\n\n    const propertyNameChanged = useCallback(async (option: IPropertyOption, text: string): Promise<void> => {\n        await mutator.changePropertyOptionValue(board.id, board.cardProperties, groupByProperty!, option, text)\n    }, [board, groupByProperty])\n\n    const addGroupClicked = useCallback(async () => {\n        Utils.log('onAddGroupClicked')\n\n        const option: IPropertyOption = {\n            id: Utils.createGuid(IDType.BlockID),\n            value: 'New group',\n            color: 'propColorDefault',\n        }\n\n        await mutator.insertPropertyOption(board.id, board.cardProperties, groupByProperty!, option, 'add group')\n    }, [board, groupByProperty])\n\n    const orderAfterMoveToColumn = useCallback((cardIds: string[], columnId?: string): string[] => {\n        let cardOrder = activeView.fields.cardOrder.slice()\n        const columnGroup = visibleGroups.find((g) => g.option.id === columnId)\n        const columnCards = columnGroup?.cards\n        if (!columnCards || columnCards.length === 0) {\n            return cardOrder\n        }\n        const lastCardId = columnCards[columnCards.length - 1].id\n        const setOfIds = new Set(cardIds)\n        cardOrder = cardOrder.filter((id) => !setOfIds.has(id))\n        const lastCardIndex = cardOrder.indexOf(lastCardId)\n        cardOrder.splice(lastCardIndex + 1, 0, ...cardIds)\n        return cardOrder\n    }, [activeView, visibleGroups])\n\n    const onDropToColumn = useCallback(async (option: IPropertyOption, card?: Card, dstOption?: IPropertyOption) => {\n        const {selectedCardIds} = props\n        const optionId = option ? option.id : undefined\n\n        let draggedCardIds = selectedCardIds\n        if (card) {\n            draggedCardIds = Array.from(new Set(selectedCardIds).add(card.id))\n        }\n\n        if (draggedCardIds.length > 0) {\n            await mutator.performAsUndoGroup(async () => {\n                const cardsById: { [key: string]: Card } = cards.reduce((acc: { [key: string]: Card }, c: Card): { [key: string]: Card } => {\n                    acc[c.id] = c\n                    return acc\n                }, {})\n                const draggedCards: Card[] = draggedCardIds.map((o: string) => cardsById[o]).filter((c) => c)\n                const description = draggedCards.length > 1 ? `drag ${draggedCards.length} cards` : 'drag card'\n                const awaits = []\n                for (const draggedCard of draggedCards) {\n                    Utils.log(`ondrop. Card: ${draggedCard.title}, column: ${optionId}`)\n                    const oldValue = draggedCard.fields.properties[groupByProperty!.id]\n                    if (optionId !== oldValue) {\n                        awaits.push(mutator.changePropertyValue(props.board.id, draggedCard, groupByProperty!.id, optionId, description))\n                    }\n                }\n                const newOrder = orderAfterMoveToColumn(draggedCardIds, optionId)\n                awaits.push(mutator.changeViewCardOrder(props.board.id, activeView.id, activeView.fields.cardOrder, newOrder, description))\n                await Promise.all(awaits)\n            })\n        } else if (dstOption) {\n            Utils.log(`ondrop. Header option: ${dstOption.value}, column: ${option?.value}`)\n\n            const visibleOptionIds = visibleGroups.map((o) => o.option.id)\n            const srcBlockX = visibleOptionIds.indexOf(option.id)\n            const dstBlockX = visibleOptionIds.indexOf(dstOption.id)\n\n            // Here aboveRow means to the left while belowRow means to the right\n            const moveTo = (srcBlockX > dstBlockX ? 'aboveRow' : 'belowRow') as Position\n\n            const visibleOptionIdsRearranged = dragAndDropRearrange({\n                contentOrder: visibleOptionIds,\n                srcBlockX,\n                srcBlockY: -1,\n                dstBlockX,\n                dstBlockY: -1,\n                srcBlockId: option.id,\n                dstBlockId: dstOption.id,\n                moveTo,\n            }) as string[]\n\n            await mutator.changeViewVisibleOptionIds(props.board.id, activeView.id, activeView.fields.visibleOptionIds, visibleOptionIdsRearranged)\n        }\n    }, [cards, visibleGroups, activeView.id, activeView.fields.cardOrder, groupByProperty, props.selectedCardIds])\n\n    const onDropToCard = useCallback(async (srcCard: Card, dstCard: Card) => {\n        if (srcCard.id === dstCard.id || !groupByProperty) {\n            return\n        }\n        Utils.log(`onDropToCard: ${dstCard.title}`)\n        const {selectedCardIds} = props\n        const optionId = dstCard.fields.properties[groupByProperty.id]\n\n        const draggedCardIds = Array.from(new Set(selectedCardIds).add(srcCard.id))\n\n        const description = draggedCardIds.length > 1 ? `drag ${draggedCardIds.length} cards` : 'drag card'\n\n        // Update dstCard order\n        const cardsById: { [key: string]: Card } = cards.reduce((acc: { [key: string]: Card }, card: Card): { [key: string]: Card } => {\n            acc[card.id] = card\n            return acc\n        }, {})\n        const draggedCards: Card[] = draggedCardIds.map((o: string) => cardsById[o]).filter((c) => c)\n        let cardOrder = cards.map((o) => o.id)\n        const isDraggingDown = cardOrder.indexOf(srcCard.id) <= cardOrder.indexOf(dstCard.id)\n        cardOrder = cardOrder.filter((id) => !draggedCardIds.includes(id))\n        let destIndex = cardOrder.indexOf(dstCard.id)\n        if (srcCard.fields.properties[groupByProperty!.id] === optionId && isDraggingDown) {\n            // If the cards are in the same column and dragging down, drop after the target dstCard\n            destIndex += 1\n        }\n        cardOrder.splice(destIndex, 0, ...draggedCardIds)\n\n        await mutator.performAsUndoGroup(async () => {\n            // Update properties of dragged cards\n            const awaits = []\n            for (const draggedCard of draggedCards) {\n                Utils.log(`draggedCard: ${draggedCard.title}, column: ${optionId}`)\n                const oldOptionId = draggedCard.fields.properties[groupByProperty!.id]\n                if (optionId !== oldOptionId) {\n                    awaits.push(mutator.changePropertyValue(props.board.id, draggedCard, groupByProperty!.id, optionId, description))\n                }\n            }\n            await Promise.all(awaits)\n            await mutator.changeViewCardOrder(props.board.id, activeView.id, activeView.fields.cardOrder, cardOrder, description)\n        })\n    }, [cards, activeView.id, activeView.fields.cardOrder, groupByProperty, props.selectedCardIds])\n\n    const [showCalculationsMenu, setShowCalculationsMenu] = useState<Map<string, boolean>>(new Map<string, boolean>())\n    const toggleOptions = (templateId: string, show: boolean) => {\n        const newShowOptions = new Map<string, boolean>(showCalculationsMenu)\n        newShowOptions.set(templateId, show)\n        setShowCalculationsMenu(newShowOptions)\n    }\n\n    if (!groupByProperty) {\n        Utils.assertFailure('Board views must have groupByProperty set')\n        return <div/>\n    }\n\n    return (\n        <ScrollingComponent\n            className='Kanban'\n            horizontalStrength={hStrength}\n            verticalStrength={vStrength}\n        >\n            <div\n                className='octo-board-header'\n                id='mainBoardHeader'\n            >\n                {/* Column headers */}\n\n                {visibleGroups.map((group) => (\n                    <KanbanColumnHeader\n                        key={group.option.id}\n                        group={group}\n                        board={board}\n                        activeView={activeView}\n                        intl={props.intl}\n                        groupByProperty={groupByProperty}\n                        addCard={props.addCard}\n                        readonly={props.readonly}\n                        propertyNameChanged={propertyNameChanged}\n                        onDropToColumn={onDropToColumn}\n                        calculationMenuOpen={showCalculationsMenu.get(group.option.id) || false}\n                        onCalculationMenuOpen={() => toggleOptions(group.option.id, true)}\n                        onCalculationMenuClose={() => toggleOptions(group.option.id, false)}\n                    />\n                ))}\n\n                {/* Hidden column header */}\n\n                {(hiddenGroups.length > 0 || hiddenCardsCount > 0) &&\n                    <div className='octo-board-header-cell narrow'>\n                        <FormattedMessage\n                            id='BoardComponent.hidden-columns'\n                            defaultMessage='Hidden columns'\n                        />\n                    </div>\n                }\n\n                {!props.readonly &&\n                    <BoardPermissionGate permissions={[Permission.ManageBoardProperties]}>\n                        <div className='octo-board-header-cell narrow'>\n                            <Button\n                                onClick={addGroupClicked}\n                            >\n                                <FormattedMessage\n                                    id='BoardComponent.add-a-group'\n                                    defaultMessage='+ Add a group'\n                                />\n                            </Button>\n                        </div>\n                    </BoardPermissionGate>\n                }\n            </div>\n\n            {/* Main content */}\n\n            <div\n                className='octo-board-body'\n                id='mainBoardBody'\n            >\n                {/* Columns */}\n\n                {visibleGroups.map((group) => (\n                    <KanbanColumn\n                        key={group.option.id || 'empty'}\n                        onDrop={(card: Card) => onDropToColumn(group.option, card)}\n                    >\n                        {group.cards.map((card) => (\n                            <KanbanCard\n                                card={card}\n                                board={board}\n                                visiblePropertyTemplates={visiblePropertyTemplates}\n                                visibleBadges={visibleBadges}\n                                key={card.id}\n                                readonly={props.readonly}\n                                isSelected={props.selectedCardIds.includes(card.id)}\n                                onClick={props.onCardClicked}\n                                onDrop={onDropToCard}\n                                showCard={props.showCard}\n                                isManualSort={isManualSort}\n                            />\n                        ))}\n                        {!props.readonly &&\n                            <BoardPermissionGate permissions={[Permission.ManageBoardCards]}>\n                                <Button\n                                    onClick={() => {\n                                        if (defaultTemplateID) {\n                                            props.addCardFromTemplate(defaultTemplateID, group.option.id)\n                                        } else {\n                                            props.addCard(group.option.id, true)\n                                        }\n                                    }}\n                                >\n                                    <FormattedMessage\n                                        id='BoardComponent.new'\n                                        defaultMessage='+ New'\n                                    />\n                                </Button>\n                            </BoardPermissionGate>\n                        }\n                    </KanbanColumn>\n                ))}\n\n                {/* Hidden columns */}\n\n                {(hiddenGroups.length > 0 || hiddenCardsCount > 0) &&\n                    <div className='octo-board-column narrow'>\n                        {hiddenGroups.map((group) => (\n                            <KanbanHiddenColumnItem\n                                key={group.option.id}\n                                group={group}\n                                activeView={activeView}\n                                intl={props.intl}\n                                readonly={props.readonly}\n                                onDrop={(card: Card) => onDropToColumn(group.option, card)}\n                            />\n                        ))}\n                        {hiddenCardsCount > 0 &&\n                        <div className='ml-1'>\n                            <HiddenCardCount\n                                hiddenCardsCount={hiddenCardsCount}\n                                showHiddenCardNotification={props.showHiddenCardCountNotification}\n                            />\n                        </div>}\n                    </div>}\n            </div>\n        </ScrollingComponent>\n    )\n}\n\nexport default injectIntl(Kanban)\n"
  },
  {
    "path": "webapp/src/components/kanban/kanbanCard.scss",
    "content": ".KanbanCard {\n    flex: 0 0 auto;\n    position: relative;\n    display: flex;\n    flex-direction: column;\n    align-items: flex-start;\n    overflow-wrap: anywhere;\n    border-radius: 4px;\n    margin-bottom: 16px;\n    padding: 12px 16px;\n    box-shadow: rgba(var(--center-channel-color-rgb), 0.1) 0 0 0 1px,\n        rgba(var(--center-channel-color-rgb), 0.1) 0 2px 4px;\n\n    cursor: pointer;\n    color: rgb(var(--center-channel-color-rgb));\n\n    transition: background 100ms ease-out 0s;\n\n    &:hover {\n        background-color: rgba(var(--center-channel-color-rgb), 0.08);\n\n        .optionsMenu {\n            display: block;\n        }\n    }\n\n    &.selected {\n        background-color: rgba(90, 200, 255, 0.2);\n    }\n\n    .optionsMenu {\n        background: rgb(var(--center-channel-bg-rgb));\n        display: none;\n        position: absolute;\n        right: 12px;\n\n        &.show {\n            display: block;\n        }\n    }\n\n    .octo-tooltip {\n        display: flex;\n        max-width: 100%;\n    }\n\n    .octo-propertyvalue {\n        input[value=''] {\n            display: none;\n        }\n\n        .Label.empty {\n            display: block;\n            padding: 0;\n            margin: 0;\n        }\n    }\n\n    .octo-icontitle {\n        flex: 1 1 auto;\n        font-weight: 600;\n        line-height: 20px;\n        margin-bottom: 8px;\n\n        .octo-icon {\n            font-size: 16px;\n            margin-right: 4px;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            width: 20px;\n            height: 20px;\n        }\n\n        .octo-titletext {\n            display: -webkit-box; // stylelint-disable-line\n            overflow: hidden;\n            text-overflow: ellipsis;\n            max-height: 100px;\n            -webkit-line-clamp: 5;\n            -webkit-box-orient: vertical; // stylelint-disable-line\n        }\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/kanban/kanbanCard.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\nimport {render, screen, within} from '@testing-library/react'\nimport '@testing-library/jest-dom'\nimport {MemoryRouter} from 'react-router-dom'\n\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport userEvent from '@testing-library/user-event'\n\nimport {mocked} from 'jest-mock'\n\nimport Mutator from '../../mutator'\nimport {Utils} from '../../utils'\n\nimport {TestBlockFactory} from '../../test/testBlockFactory'\nimport {IPropertyTemplate} from '../../blocks/board'\nimport {mockStateStore, wrapDNDIntl} from '../../testUtils'\n\nimport KanbanCard from './kanbanCard'\n\njest.mock('../../mutator')\njest.mock('../../utils')\njest.mock('../../telemetry/telemetryClient')\nconst mockedUtils = mocked(Utils, true)\nconst mockedMutator = mocked(Mutator, true)\n\ndescribe('src/components/kanban/kanbanCard', () => {\n    const board = TestBlockFactory.createBoard()\n    const card = TestBlockFactory.createCard(board)\n    const propertyTemplate: IPropertyTemplate = {\n        id: 'id',\n        name: 'name',\n        type: 'text',\n        options: [\n            {\n                color: 'propColorOrange',\n                id: 'property_value_id_1',\n                value: 'Q1',\n            },\n            {\n                color: 'propColorBlue',\n                id: 'property_value_id_2',\n                value: 'Q2',\n            },\n        ],\n    }\n    const state = {\n        cards: {\n            cards: [card],\n        },\n        teams: {\n            current: {id: 'team-id'},\n        },\n        boards: {\n            current: 'board_id_1',\n            boards: {\n                board_id_1: {id: 'board_id_1'},\n            },\n            myBoardMemberships: {\n                board_id_1: {userId: 'user_id_1', schemeAdmin: true},\n            },\n        },\n        contents: {},\n        comments: {\n            comments: {},\n        },\n        users: {\n            me: {\n                id: 'user_id_1',\n                props: {},\n            },\n        },\n    }\n    const store = mockStateStore([], state)\n    beforeEach(jest.clearAllMocks)\n    test('should match snapshot', () => {\n        const {container} = render(wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <KanbanCard\n                    card={card}\n                    board={board}\n                    visiblePropertyTemplates={[propertyTemplate]}\n                    visibleBadges={false}\n                    isSelected={false}\n                    readonly={false}\n                    onDrop={jest.fn()}\n                    showCard={jest.fn()}\n                    isManualSort={false}\n                />\n            </ReduxProvider>,\n        ), {wrapper: MemoryRouter})\n        expect(container).toMatchSnapshot()\n    })\n    test('should match snapshot with readonly', () => {\n        const {container} = render(wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <KanbanCard\n                    card={card}\n                    board={board}\n                    visiblePropertyTemplates={[propertyTemplate]}\n                    visibleBadges={false}\n                    isSelected={false}\n                    readonly={true}\n                    onDrop={jest.fn()}\n                    showCard={jest.fn()}\n                    isManualSort={false}\n                />\n            </ReduxProvider>,\n        ), {wrapper: MemoryRouter})\n        expect(container).toMatchSnapshot()\n    })\n    test('return kanbanCard and click on delete menu ', () => {\n        const result = render(wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <KanbanCard\n                    card={card}\n                    board={board}\n                    visiblePropertyTemplates={[propertyTemplate]}\n                    visibleBadges={false}\n                    isSelected={false}\n                    readonly={false}\n                    onDrop={jest.fn()}\n                    showCard={jest.fn()}\n                    isManualSort={false}\n                />\n            </ReduxProvider>,\n        ), {wrapper: MemoryRouter})\n\n        const {container} = result\n\n        const elementMenuWrapper = screen.getByRole('button', {name: 'menuwrapper'})\n        expect(elementMenuWrapper).not.toBeNull()\n        userEvent.click(elementMenuWrapper)\n        expect(container).toMatchSnapshot()\n        const elementButtonDelete = within(elementMenuWrapper).getByRole('button', {name: 'Delete'})\n        expect(elementButtonDelete).not.toBeNull()\n        userEvent.click(elementButtonDelete)\n\n        const confirmDialog = screen.getByTitle('Confirmation Dialog Box')\n        expect(confirmDialog).toBeDefined()\n        const confirmButton = within(confirmDialog).getByRole('button', {name: 'Delete'})\n        expect(confirmButton).toBeDefined()\n        userEvent.click(confirmButton)\n\n        expect(mockedMutator.deleteBlock).toBeCalledWith(card, 'delete card')\n    })\n\n    test('return kanbanCard and click on duplicate menu ', () => {\n        const {container} = render(wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <KanbanCard\n                    card={card}\n                    board={board}\n                    visiblePropertyTemplates={[propertyTemplate]}\n                    visibleBadges={false}\n                    isSelected={false}\n                    readonly={false}\n                    onDrop={jest.fn()}\n                    showCard={jest.fn()}\n                    isManualSort={false}\n                />\n            </ReduxProvider>,\n        ), {wrapper: MemoryRouter})\n        const elementMenuWrapper = screen.getByRole('button', {name: 'menuwrapper'})\n        expect(elementMenuWrapper).not.toBeNull()\n        userEvent.click(elementMenuWrapper)\n        expect(container).toMatchSnapshot()\n        const elementButtonDuplicate = within(elementMenuWrapper).getByRole('button', {name: 'Duplicate'})\n        expect(elementButtonDuplicate).not.toBeNull()\n        userEvent.click(elementButtonDuplicate)\n        expect(mockedMutator.duplicateCard).toBeCalledTimes(1)\n    })\n\n    test('return kanbanCard and click on copy link menu ', () => {\n        const {container} = render(wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <KanbanCard\n                    card={card}\n                    board={board}\n                    visiblePropertyTemplates={[propertyTemplate]}\n                    visibleBadges={false}\n                    isSelected={false}\n                    readonly={false}\n                    onDrop={jest.fn()}\n                    showCard={jest.fn()}\n                    isManualSort={false}\n                />\n            </ReduxProvider>,\n        ), {wrapper: MemoryRouter})\n        const elementMenuWrapper = screen.getByRole('button', {name: 'menuwrapper'})\n        expect(elementMenuWrapper).not.toBeNull()\n        userEvent.click(elementMenuWrapper)\n        expect(container).toMatchSnapshot()\n        const elementButtonCopyLink = within(elementMenuWrapper).getByRole('button', {name: 'Copy link'})\n        expect(elementButtonCopyLink).not.toBeNull()\n        userEvent.click(elementButtonCopyLink)\n        expect(mockedUtils.copyTextToClipboard).toBeCalledTimes(1)\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/kanban/kanbanCard.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useState, useCallback, useMemo} from 'react'\nimport {useIntl} from 'react-intl'\n\nimport {Board, IPropertyTemplate} from '../../blocks/board'\nimport {Card} from '../../blocks/card'\nimport {useSortable} from '../../hooks/sortable'\nimport mutator from '../../mutator'\nimport TelemetryClient, {TelemetryActions, TelemetryCategory} from '../../telemetry/telemetryClient'\nimport {Utils} from '../../utils'\nimport MenuWrapper from '../../widgets/menuWrapper'\nimport Tooltip from '../../widgets/tooltip'\nimport PropertyValueElement from '../propertyValueElement'\nimport ConfirmationDialogBox, {ConfirmationDialogBoxProps} from '../confirmationDialogBox'\nimport './kanbanCard.scss'\nimport CardBadges from '../cardBadges'\nimport CardActionsMenu from '../cardActionsMenu/cardActionsMenu'\nimport CardActionsMenuIcon from '../cardActionsMenu/cardActionsMenuIcon'\n\nexport const OnboardingCardClassName = 'onboardingCard'\n\ntype Props = {\n    card: Card\n    board: Board\n    visiblePropertyTemplates: IPropertyTemplate[]\n    isSelected: boolean\n    visibleBadges: boolean\n    onClick?: (e: React.MouseEvent, card: Card) => void\n    readonly: boolean\n    onDrop: (srcCard: Card, dstCard: Card) => void\n    showCard: (cardId?: string) => void\n    isManualSort: boolean\n}\n\nconst KanbanCard = (props: Props) => {\n    const {card, board} = props\n    const intl = useIntl()\n    const [isDragging, isOver, cardRef] = useSortable('card', card, !props.readonly, props.onDrop)\n    const visiblePropertyTemplates = props.visiblePropertyTemplates || []\n    let className = props.isSelected ? 'KanbanCard selected' : 'KanbanCard'\n    if (props.isManualSort && isOver) {\n        className += ' dragover'\n    }\n\n    const [showConfirmationDialogBox, setShowConfirmationDialogBox] = useState<boolean>(false)\n    const handleDeleteCard = useCallback(() => {\n        if (!card) {\n            Utils.assertFailure()\n            return\n        }\n        TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.DeleteCard, {board: board.id, card: card.id})\n        mutator.deleteBlock(card, 'delete card')\n    }, [card, board.id])\n\n    const confirmDialogProps: ConfirmationDialogBoxProps = useMemo(() => {\n        return {\n            heading: intl.formatMessage({id: 'CardDialog.delete-confirmation-dialog-heading', defaultMessage: 'Confirm card delete!'}),\n            confirmButtonText: intl.formatMessage({id: 'CardDialog.delete-confirmation-dialog-button-text', defaultMessage: 'Delete'}),\n            onConfirm: handleDeleteCard,\n            onClose: () => {\n                setShowConfirmationDialogBox(false)\n            },\n        }\n    }, [handleDeleteCard])\n\n    const handleDeleteButtonOnClick = useCallback(() => {\n        // user trying to delete a card with blank name\n        // but content present cannot be deleted without\n        // confirmation dialog\n        if (card?.title === '' && card?.fields?.contentOrder?.length === 0) {\n            handleDeleteCard()\n            return\n        }\n        setShowConfirmationDialogBox(true)\n    }, [handleDeleteCard, card.title, card?.fields?.contentOrder?.length])\n\n    const handleOnClick = useCallback((e: React.MouseEvent) => {\n        if (props.onClick) {\n            props.onClick(e, card)\n        }\n    }, [props.onClick, card])\n\n    return (\n        <>\n            <div\n                ref={props.readonly ? () => null : cardRef}\n                className={`${className}`}\n                draggable={!props.readonly}\n                style={{opacity: isDragging ? 0.5 : 1}}\n                onClick={handleOnClick}\n            >\n                {!props.readonly &&\n                <MenuWrapper\n                    className={'optionsMenu'}\n                    stopPropagationOnToggle={true}\n                >\n                    <CardActionsMenuIcon/>\n                    <CardActionsMenu\n                        cardId={card!.id}\n                        boardId={card!.boardId}\n                        onClickDelete={handleDeleteButtonOnClick}\n                        onClickDuplicate={() => {\n                            TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.DuplicateCard, {board: board.id, card: card.id})\n                            mutator.duplicateCard(\n                                card.id,\n                                board.id,\n                                false,\n                                'duplicate card',\n                                false,\n                                {},\n                                async (newCardId) => {\n                                    props.showCard(newCardId)\n                                },\n                                async () => {\n                                    props.showCard(undefined)\n                                },\n                            )\n                        }}\n                    />\n                </MenuWrapper>\n                }\n\n                <div className='octo-icontitle'>\n                    { card.fields.icon ? <div className='octo-icon'>{card.fields.icon}</div> : undefined }\n                    <div\n                        key='__title'\n                        className='octo-titletext'\n                    >\n                        {card.title || intl.formatMessage({id: 'KanbanCard.untitled', defaultMessage: 'Untitled'})}\n                    </div>\n                </div>\n                {visiblePropertyTemplates.map((template) => (\n                    <Tooltip\n                        key={template.id}\n                        title={template.name}\n                    >\n                        <PropertyValueElement\n                            board={board}\n                            readOnly={true}\n                            card={card}\n                            propertyTemplate={template}\n                            showEmptyPlaceholder={false}\n                        />\n                    </Tooltip>\n                ))}\n                {props.visibleBadges && <CardBadges card={card}/>}\n            </div>\n\n            {showConfirmationDialogBox && <ConfirmationDialogBox dialogBox={confirmDialogProps}/>}\n\n        </>\n    )\n}\n\nexport default React.memo(KanbanCard)\n"
  },
  {
    "path": "webapp/src/components/kanban/kanbanColumn.scss",
    "content": ".octo-board-column {\n    &.dragover {\n        background-color: rgba(128, 192, 255, 0.15);\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/kanban/kanbanColumn.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {render} from '@testing-library/react'\nimport React from 'react'\n\nimport {wrapDNDIntl} from '../../testUtils'\n\nimport KanbanColumn from './kanbanColumn'\ndescribe('src/components/kanban/kanbanColumn', () => {\n    test('should match snapshot', () => {\n        const {container} = render(wrapDNDIntl(\n            <KanbanColumn\n                onDrop={jest.fn()}\n            >\n                {}\n            </KanbanColumn>,\n        ))\n        expect(container).toMatchSnapshot()\n    })\n})\n\n"
  },
  {
    "path": "webapp/src/components/kanban/kanbanColumn.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\nimport {useDrop} from 'react-dnd'\n\nimport {Card} from '../../blocks/card'\nimport './kanbanColumn.scss'\n\ntype Props = {\n    onDrop: (card: Card) => void\n    children: React.ReactNode\n}\n\nconst KanbanColumn = (props: Props) => {\n    const [{isOver}, drop] = useDrop(() => ({\n        accept: 'card',\n        collect: (monitor) => ({\n            isOver: monitor.isOver(),\n        }),\n        drop: (item: Card, monitor) => {\n            if (monitor.isOver({shallow: true})) {\n                props.onDrop(item)\n            }\n        },\n    }), [props.onDrop])\n\n    let className = 'octo-board-column'\n    if (isOver) {\n        className += ' dragover'\n    }\n    return (\n        <div\n            ref={drop}\n            className={className}\n        >\n            {props.children}\n        </div>\n    )\n}\n\nexport default React.memo(KanbanColumn)\n"
  },
  {
    "path": "webapp/src/components/kanban/kanbanColumnHeader.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\nimport {fireEvent, render, screen, within} from '@testing-library/react'\nimport {createIntl} from 'react-intl'\nimport userEvent from '@testing-library/user-event'\nimport {mocked} from 'jest-mock'\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport Mutator from '../../mutator'\nimport {wrapDNDIntl, mockStateStore} from '../../testUtils'\nimport {TestBlockFactory} from '../../test/testBlockFactory'\nimport {IPropertyOption} from '../../blocks/board'\n\nimport KanbanColumnHeader from './kanbanColumnHeader'\njest.mock('../../mutator')\nconst mockedMutator = mocked(Mutator, true)\ndescribe('src/components/kanban/kanbanColumnHeader', () => {\n    const intl = createIntl({locale: 'en-us'})\n    const board = TestBlockFactory.createBoard()\n    const activeView = TestBlockFactory.createBoardView(board)\n    const card = TestBlockFactory.createCard(board)\n    card.id = 'id1'\n    activeView.fields.kanbanCalculations = {\n        id1: {\n            calculation: 'countEmpty',\n            propertyId: '1',\n\n        },\n    }\n    const option: IPropertyOption = {\n        id: 'id1',\n        value: 'Title',\n        color: 'propColorDefault',\n    }\n    const state = {\n        teams: {\n            current: {id: 'team-id'},\n        },\n        boards: {\n            current: board.id,\n            boards: {\n                [board.id]: board,\n            },\n            myBoardMemberships: {\n                [board.id]: {userId: 'user_id_1', schemeAdmin: true},\n            },\n        },\n    }\n    const store = mockStateStore([], state)\n    beforeAll(() => {\n        console.error = jest.fn()\n    })\n    beforeEach(jest.resetAllMocks)\n    test('should match snapshot', () => {\n        const {container} = render(wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <KanbanColumnHeader\n                    board={board}\n                    activeView={activeView}\n                    group={{\n                        option,\n                        cards: [card],\n                    }}\n                    intl={intl}\n                    readonly={false}\n                    addCard={jest.fn()}\n                    propertyNameChanged={jest.fn()}\n                    onDropToColumn={jest.fn()}\n                    calculationMenuOpen={false}\n                    onCalculationMenuOpen={jest.fn()}\n                    onCalculationMenuClose={jest.fn()}\n                />\n            </ReduxProvider>,\n        ))\n        expect(container).toMatchSnapshot()\n    })\n    test('should match snapshot readonly', () => {\n        const {container} = render(wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <KanbanColumnHeader\n                    board={board}\n                    activeView={activeView}\n                    group={{\n                        option,\n                        cards: [card],\n                    }}\n                    intl={intl}\n                    readonly={true}\n                    addCard={jest.fn()}\n                    propertyNameChanged={jest.fn()}\n                    onDropToColumn={jest.fn()}\n                    calculationMenuOpen={false}\n                    onCalculationMenuOpen={jest.fn()}\n                    onCalculationMenuClose={jest.fn()}\n                />\n            </ReduxProvider>,\n        ))\n        expect(container).toMatchSnapshot()\n    })\n    test('return kanbanColumnHeader and edit title', () => {\n        const mockedPropertyNameChanged = jest.fn()\n        const {container} = render(wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <KanbanColumnHeader\n                    board={board}\n                    activeView={activeView}\n                    group={{\n                        option,\n                        cards: [card],\n                    }}\n                    intl={intl}\n                    readonly={false}\n                    addCard={jest.fn()}\n                    propertyNameChanged={mockedPropertyNameChanged}\n                    onDropToColumn={jest.fn()}\n                    calculationMenuOpen={false}\n                    onCalculationMenuOpen={jest.fn()}\n                    onCalculationMenuClose={jest.fn()}\n                />\n            </ReduxProvider>,\n        ))\n        const inputTitle = screen.getByRole('textbox', {name: option.value})\n        expect(inputTitle).toBeDefined()\n        fireEvent.change(inputTitle, {target: {value: ''}})\n        userEvent.type(inputTitle, 'New Title')\n        fireEvent.blur(inputTitle)\n        expect(mockedPropertyNameChanged).toBeCalledWith(option, 'New Title')\n        expect(container).toMatchSnapshot()\n    })\n    test('return kanbanColumnHeader and click on menuwrapper', () => {\n        const {container} = render(wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <KanbanColumnHeader\n                    board={board}\n                    activeView={activeView}\n                    group={{\n                        option,\n                        cards: [card],\n                    }}\n                    intl={intl}\n                    readonly={false}\n                    addCard={jest.fn()}\n                    propertyNameChanged={jest.fn()}\n                    onDropToColumn={jest.fn()}\n                    calculationMenuOpen={false}\n                    onCalculationMenuOpen={jest.fn()}\n                    onCalculationMenuClose={jest.fn()}\n                />\n            </ReduxProvider>,\n        ))\n        const buttonMenuWrapper = screen.getByRole('button', {name: 'menuwrapper'})\n        expect(buttonMenuWrapper).toBeDefined()\n        userEvent.click(buttonMenuWrapper)\n        expect(container).toMatchSnapshot()\n    })\n    test('return kanbanColumnHeader, click on menuwrapper and click on hide menu', () => {\n        render(wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <KanbanColumnHeader\n                    board={board}\n                    activeView={activeView}\n                    group={{\n                        option,\n                        cards: [card],\n                    }}\n                    intl={intl}\n                    readonly={false}\n                    addCard={jest.fn()}\n                    propertyNameChanged={jest.fn()}\n                    onDropToColumn={jest.fn()}\n                    calculationMenuOpen={false}\n                    onCalculationMenuOpen={jest.fn()}\n                    onCalculationMenuClose={jest.fn()}\n                />\n            </ReduxProvider>,\n        ))\n        const buttonMenuWrapper = screen.getByRole('button', {name: 'menuwrapper'})\n        expect(buttonMenuWrapper).toBeDefined()\n        userEvent.click(buttonMenuWrapper)\n        const buttonHide = within(buttonMenuWrapper).getByRole('button', {name: 'Hide'})\n        expect(buttonHide).toBeDefined()\n        userEvent.click(buttonHide)\n        expect(mockedMutator.hideViewColumn).toBeCalledTimes(1)\n    })\n    test('return kanbanColumnHeader, click on menuwrapper and click on delete menu', () => {\n        render(wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <KanbanColumnHeader\n                    board={board}\n                    activeView={activeView}\n                    group={{\n                        option,\n                        cards: [card],\n                    }}\n                    intl={intl}\n                    readonly={false}\n                    addCard={jest.fn()}\n                    propertyNameChanged={jest.fn()}\n                    onDropToColumn={jest.fn()}\n                    calculationMenuOpen={false}\n                    onCalculationMenuOpen={jest.fn()}\n                    onCalculationMenuClose={jest.fn()}\n                />\n            </ReduxProvider>,\n        ))\n        const buttonMenuWrapper = screen.getByRole('button', {name: 'menuwrapper'})\n        expect(buttonMenuWrapper).toBeDefined()\n        userEvent.click(buttonMenuWrapper)\n        const buttonDelete = within(buttonMenuWrapper).getByRole('button', {name: 'Delete'})\n        expect(buttonDelete).toBeDefined()\n        userEvent.click(buttonDelete)\n        expect(mockedMutator.deletePropertyOption).toBeCalledTimes(1)\n    })\n    test('return kanbanColumnHeader, click on menuwrapper and click on blue color menu', () => {\n        render(wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <KanbanColumnHeader\n                    board={board}\n                    activeView={activeView}\n                    group={{\n                        option,\n                        cards: [card],\n                    }}\n                    intl={intl}\n                    readonly={false}\n                    addCard={jest.fn()}\n                    propertyNameChanged={jest.fn()}\n                    onDropToColumn={jest.fn()}\n                    calculationMenuOpen={false}\n                    onCalculationMenuOpen={jest.fn()}\n                    onCalculationMenuClose={jest.fn()}\n                />\n            </ReduxProvider>,\n        ))\n        const buttonMenuWrapper = screen.getByRole('button', {name: 'menuwrapper'})\n        expect(buttonMenuWrapper).toBeDefined()\n        userEvent.click(buttonMenuWrapper)\n        const buttonBlueColor = within(buttonMenuWrapper).getByRole('button', {name: 'Select Blue Color'})\n        expect(buttonBlueColor).toBeDefined()\n        userEvent.click(buttonBlueColor)\n        expect(mockedMutator.changePropertyOptionColor).toBeCalledTimes(1)\n    })\n\n    test('return kanbanColumnHeader and click to add card', () => {\n        const mockedAddCard = jest.fn()\n        const {container} = render(wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <KanbanColumnHeader\n                    board={board}\n                    activeView={activeView}\n                    group={{\n                        option,\n                        cards: [card],\n                    }}\n                    intl={intl}\n                    readonly={false}\n                    addCard={mockedAddCard}\n                    propertyNameChanged={jest.fn()}\n                    onDropToColumn={jest.fn()}\n                    calculationMenuOpen={false}\n                    onCalculationMenuOpen={jest.fn()}\n                    onCalculationMenuClose={jest.fn()}\n                />\n            </ReduxProvider>,\n        ))\n        const buttonAddCard = container.querySelector('.AddIcon')?.parentElement\n        expect(buttonAddCard).toBeDefined()\n        userEvent.click(buttonAddCard!)\n        expect(mockedAddCard).toBeCalledTimes(1)\n    })\n    test('return kanbanColumnHeader and click KanbanCalculationMenu', () => {\n        const mockedCalculationMenuOpen = jest.fn()\n        render(wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <KanbanColumnHeader\n                    board={board}\n                    activeView={activeView}\n                    group={{\n                        option,\n                        cards: [card],\n                    }}\n                    intl={intl}\n                    readonly={false}\n                    addCard={jest.fn()}\n                    propertyNameChanged={jest.fn()}\n                    onDropToColumn={jest.fn()}\n                    calculationMenuOpen={false}\n                    onCalculationMenuOpen={mockedCalculationMenuOpen}\n                    onCalculationMenuClose={jest.fn()}\n                />\n            </ReduxProvider>,\n        ))\n        const buttonKanbanCalculation = screen.getByText(/0/i).parentElement\n        expect(buttonKanbanCalculation).toBeDefined()\n        userEvent.click(buttonKanbanCalculation!)\n        expect(mockedCalculationMenuOpen).toBeCalledTimes(1)\n    })\n    test('return kanbanColumnHeader and click count on KanbanCalculationMenu', () => {\n        render(wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <KanbanColumnHeader\n                    board={board}\n                    activeView={activeView}\n                    group={{\n                        option,\n                        cards: [card],\n                    }}\n                    intl={intl}\n                    readonly={false}\n                    addCard={jest.fn()}\n                    propertyNameChanged={jest.fn()}\n                    onDropToColumn={jest.fn()}\n                    calculationMenuOpen={true}\n                    onCalculationMenuOpen={jest.fn()}\n                    onCalculationMenuClose={jest.fn()}\n                />\n            </ReduxProvider>,\n        ))\n        const menuCountEmpty = screen.getByText('Count')\n        expect(menuCountEmpty).toBeDefined()\n        userEvent.click(menuCountEmpty)\n        expect(mockedMutator.changeViewKanbanCalculations).toBeCalledTimes(1)\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/kanban/kanbanColumnHeader.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n/* eslint-disable max-lines */\nimport React, {useState, useEffect, useRef} from 'react'\nimport {FormattedMessage, IntlShape} from 'react-intl'\nimport {useDrop, useDrag} from 'react-dnd'\n\nimport {Constants, Permission} from '../../constants'\nimport {IPropertyOption, IPropertyTemplate, Board, BoardGroup} from '../../blocks/board'\nimport {BoardView} from '../../blocks/boardView'\nimport {Card} from '../../blocks/card'\nimport mutator from '../../mutator'\nimport IconButton from '../../widgets/buttons/iconButton'\nimport AddIcon from '../../widgets/icons/add'\nimport DeleteIcon from '../../widgets/icons/delete'\nimport HideIcon from '../../widgets/icons/hide'\nimport OptionsIcon from '../../widgets/icons/options'\nimport Menu from '../../widgets/menu'\nimport MenuWrapper from '../../widgets/menuWrapper'\nimport Editable from '../../widgets/editable'\nimport Label from '../../widgets/label'\nimport {useHasCurrentBoardPermissions} from '../../hooks/permissions'\n\nimport BoardPermissionGate from '../permissions/boardPermissionGate'\n\nimport {KanbanCalculation} from './calculation/calculation'\n\ntype Props = {\n    board: Board\n    activeView: BoardView\n    group: BoardGroup\n    groupByProperty?: IPropertyTemplate\n    intl: IntlShape\n    readonly: boolean\n    addCard: (groupByOptionId?: string, show?: boolean) => Promise<void>\n    propertyNameChanged: (option: IPropertyOption, text: string) => Promise<void>\n    onDropToColumn: (srcOption: IPropertyOption, card?: Card, dstOption?: IPropertyOption) => void\n    calculationMenuOpen: boolean\n    onCalculationMenuOpen: () => void\n    onCalculationMenuClose: () => void\n}\n\nconst defaultCalculation = 'count'\nconst defaultProperty: IPropertyTemplate = {\n    id: Constants.titleColumnId,\n} as IPropertyTemplate\n\nexport default function KanbanColumnHeader(props: Props): JSX.Element {\n    const {board, activeView, intl, group, groupByProperty} = props\n    const [groupTitle, setGroupTitle] = useState(group.option.value)\n    const canEditBoardProperties = useHasCurrentBoardPermissions([Permission.ManageBoardProperties])\n    const canEditOption = groupByProperty?.type !== 'person' && group.option.id\n\n    const headerRef = useRef<HTMLDivElement>(null)\n\n    const [{isDragging}, drag] = useDrag(() => ({\n        type: 'column',\n        item: group.option,\n        collect: (monitor) => ({\n            isDragging: monitor.isDragging(),\n        }),\n    }))\n    const [{isOver}, drop] = useDrop(() => ({\n        accept: 'column',\n        collect: (monitor) => ({\n            isOver: monitor.isOver(),\n        }),\n        drop: (item: IPropertyOption) => {\n            props.onDropToColumn(item, undefined, group.option)\n        },\n    }), [props.onDropToColumn])\n\n    useEffect(() => {\n        setGroupTitle(group.option.value)\n    }, [group.option.value])\n\n    if (canEditBoardProperties) {\n        drop(drag(headerRef))\n    }\n\n    let className = 'octo-board-header-cell KanbanColumnHeader'\n    if (isOver) {\n        className += ' dragover'\n    }\n\n    const groupCalculation = props.activeView.fields.kanbanCalculations[props.group.option.id]\n    const calculationValue = groupCalculation ? groupCalculation.calculation : defaultCalculation\n    const calculationProperty = groupCalculation ? props.board.cardProperties.find((property) => property.id === groupCalculation.propertyId) || defaultProperty : defaultProperty\n    return (\n        <div\n            key={group.option.id || 'empty'}\n            ref={headerRef}\n            style={{opacity: isDragging ? 0.5 : 1}}\n            className={className}\n            draggable={!props.readonly && canEditBoardProperties}\n        >\n            {!group.option.id &&\n                <Label\n                    title={intl.formatMessage({\n                        id: 'BoardComponent.no-property-title',\n                        defaultMessage: 'Items with an empty {property} property will go here. This column cannot be removed.',\n                    }, {property: groupByProperty!.name})}\n                >\n                    <FormattedMessage\n                        id='BoardComponent.no-property'\n                        defaultMessage='No {property}'\n                        values={{\n                            property: groupByProperty!.name,\n                        }}\n                    />\n                </Label>}\n            {groupByProperty?.type === 'person' &&\n                <Label>\n                    {groupTitle}\n                </Label>}\n            {canEditOption &&\n                <Label color={group.option.color}>\n                    <Editable\n                        value={groupTitle}\n                        placeholderText='New Select'\n                        onChange={setGroupTitle}\n                        onSave={() => {\n                            if (groupTitle.trim() === '') {\n                                setGroupTitle(group.option.value)\n                            }\n                            props.propertyNameChanged(group.option, groupTitle)\n                        }}\n                        onCancel={() => {\n                            setGroupTitle(group.option.value)\n                        }}\n                        readonly={props.readonly || !canEditBoardProperties}\n                        spellCheck={true}\n                    />\n                </Label>}\n            <KanbanCalculation\n                cards={group.cards}\n                menuOpen={props.calculationMenuOpen}\n                value={calculationValue}\n                property={calculationProperty}\n                onMenuClose={props.onCalculationMenuClose}\n                onMenuOpen={props.onCalculationMenuOpen}\n                cardProperties={board.cardProperties}\n                readonly={props.readonly || !canEditBoardProperties}\n                onChange={(data: {calculation: string, propertyId: string}) => {\n                    if (data.calculation === calculationValue && data.propertyId === calculationProperty.id) {\n                        return\n                    }\n\n                    const newCalculations = {\n                        ...props.activeView.fields.kanbanCalculations,\n                    }\n                    newCalculations[props.group.option.id] = {\n                        calculation: data.calculation,\n                        propertyId: data.propertyId,\n                    }\n\n                    mutator.changeViewKanbanCalculations(board.id, props.activeView.id, props.activeView.fields.kanbanCalculations, newCalculations)\n                }}\n            />\n            <div className='octo-spacer'/>\n            {!props.readonly &&\n                <>\n                    <BoardPermissionGate permissions={[Permission.ManageBoardProperties]}>\n                        <MenuWrapper>\n                            <IconButton icon={<OptionsIcon/>}/>\n                            <Menu>\n                                <Menu.Text\n                                    id='hide'\n                                    icon={<HideIcon/>}\n                                    name={intl.formatMessage({id: 'BoardComponent.hide', defaultMessage: 'Hide'})}\n                                    onClick={() => mutator.hideViewColumn(board.id, activeView, group.option.id || '')}\n                                />\n                                {canEditOption &&\n                                    <>\n                                        <Menu.Text\n                                            id='delete'\n                                            icon={<DeleteIcon/>}\n                                            name={intl.formatMessage({id: 'BoardComponent.delete', defaultMessage: 'Delete'})}\n                                            onClick={() => mutator.deletePropertyOption(board.id, board.cardProperties, groupByProperty!, group.option)}\n                                        />\n                                        <Menu.Separator/>\n                                        {Object.entries(Constants.menuColors).map(([key, color]) => (\n                                            <Menu.Color\n                                                key={key}\n                                                id={key}\n                                                name={color}\n                                                onClick={() => mutator.changePropertyOptionColor(board.id, board.cardProperties, groupByProperty!, group.option, key)}\n                                            />\n                                        ))}\n                                    </>}\n                            </Menu>\n                        </MenuWrapper>\n                    </BoardPermissionGate>\n                    <BoardPermissionGate permissions={[Permission.ManageBoardCards]}>\n                        <IconButton\n                            icon={<AddIcon/>}\n                            onClick={() => {\n                                props.addCard(group.option.id, true)\n                            }}\n                        />\n                    </BoardPermissionGate>\n                </>\n            }\n        </div>\n    )\n}\n"
  },
  {
    "path": "webapp/src/components/kanban/kanbanHiddenColumnItem.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\nimport {render, screen, within} from '@testing-library/react'\nimport '@testing-library/jest-dom'\nimport userEvent from '@testing-library/user-event'\nimport {createIntl} from 'react-intl'\nimport {mocked} from 'jest-mock'\n\nimport {wrapDNDIntl} from '../../testUtils'\nimport Mutator from '../../mutator'\nimport {TestBlockFactory} from '../../test/testBlockFactory'\nimport {IPropertyOption} from '../../blocks/board'\n\nimport KanbanHiddenColumnItem from './kanbanHiddenColumnItem'\n\njest.mock('../../mutator')\nconst mockedMutator = mocked(Mutator, true)\n\ndescribe('src/components/kanban/kanbanHiddenColumnItem', () => {\n    const intl = createIntl({locale: 'en-us'})\n    const board = TestBlockFactory.createBoard()\n    const activeView = TestBlockFactory.createBoardView(board)\n    const card = TestBlockFactory.createCard(board)\n    const card2 = TestBlockFactory.createCard(board)\n    const option: IPropertyOption = {\n        id: 'id1',\n        value: 'propOption',\n        color: 'propColorDefault',\n    }\n    beforeAll(() => {\n        console.error = jest.fn()\n    })\n    test('should match snapshot', () => {\n        const {container} = render(wrapDNDIntl(\n            <KanbanHiddenColumnItem\n                activeView={activeView}\n                group={{\n                    option,\n                    cards: [card],\n                }}\n                readonly={false}\n                onDrop={jest.fn()}\n                intl={intl}\n            />,\n        ))\n        expect(container).toMatchSnapshot()\n    })\n    test('should match snapshot readonly', () => {\n        const {container} = render(wrapDNDIntl(\n            <KanbanHiddenColumnItem\n                activeView={activeView}\n                group={{\n                    option,\n                    cards: [card],\n                }}\n                readonly={true}\n                onDrop={jest.fn()}\n                intl={intl}\n            />,\n        ))\n        expect(container).toMatchSnapshot()\n    })\n    test('return kanbanHiddenColumnItem and click menuwrapper', () => {\n        const {container} = render(wrapDNDIntl(\n            <KanbanHiddenColumnItem\n                activeView={activeView}\n                group={{\n                    option,\n                    cards: [card],\n                }}\n                readonly={false}\n                onDrop={jest.fn()}\n                intl={intl}\n            />,\n        ))\n        const buttonMenuWrapper = screen.getByRole('button', {name: 'menuwrapper'})\n        expect(buttonMenuWrapper).not.toBeNull()\n        userEvent.click(buttonMenuWrapper)\n        expect(container).toMatchSnapshot()\n    })\n    test('return kanbanHiddenColumnItem, click menuwrapper and click show', () => {\n        const {container} = render(wrapDNDIntl(\n            <KanbanHiddenColumnItem\n                activeView={activeView}\n                group={{\n                    option,\n                    cards: [card],\n                }}\n                readonly={false}\n                onDrop={jest.fn()}\n                intl={intl}\n            />,\n        ))\n        const buttonMenuWrapper = screen.getByRole('button', {name: 'menuwrapper'})\n        expect(buttonMenuWrapper).not.toBeNull()\n        userEvent.click(buttonMenuWrapper)\n        expect(container).toMatchSnapshot()\n        const buttonShow = within(buttonMenuWrapper).getByRole('button', {name: 'Show'})\n        userEvent.click(buttonShow)\n        expect(mockedMutator.unhideViewColumn).toBeCalledWith(activeView.boardId, activeView, option.id)\n    })\n\n    test('limited card check', () => {\n        card.limited = true\n        card2.limited = true\n        option.id = 'hidden-card-group-id'\n        const {container, getByTitle} = render(wrapDNDIntl(\n            <KanbanHiddenColumnItem\n                activeView={activeView}\n                group={{\n                    option,\n                    cards: [card, card2],\n                }}\n                readonly={false}\n                onDrop={jest.fn()}\n                intl={intl}\n            />,\n        ))\n        expect(getByTitle('hidden-card-count')).toHaveTextContent('2')\n        expect(container).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/kanban/kanbanHiddenColumnItem.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n/* eslint-disable max-lines */\nimport React from 'react'\nimport {IntlShape} from 'react-intl'\nimport {useDrop} from 'react-dnd'\n\nimport mutator from '../../mutator'\nimport Menu from '../../widgets/menu'\nimport MenuWrapper from '../../widgets/menuWrapper'\nimport ShowIcon from '../../widgets/icons/show'\nimport Label from '../../widgets/label'\nimport {Card} from '../../blocks/card'\nimport {BoardGroup} from '../../blocks/board'\nimport {BoardView} from '../../blocks/boardView'\n\nimport Button from '../../widgets/buttons/button'\n\ntype Props = {\n    activeView: BoardView\n    group: BoardGroup\n    intl: IntlShape\n    readonly: boolean\n    onDrop: (card: Card) => void\n}\n\nexport default function KanbanHiddenColumnItem(props: Props): JSX.Element {\n    const {activeView, intl, group} = props\n    const hiddenCardGroupId = 'hidden-card-group-id'\n\n    const [{isOver}, drop] = useDrop(() => ({\n        accept: 'card',\n        collect: (monitor) => ({\n            isOver: monitor.isOver(),\n        }),\n        drop: (item: Card) => {\n            props.onDrop(item)\n        },\n    }), [props.onDrop])\n\n    let className = 'octo-board-hidden-item'\n    if (isOver) {\n        className += ' dragover'\n    }\n\n    return (\n        <div\n            ref={drop}\n            key={group.option.id || 'empty'}\n            className={className}\n        >\n            <MenuWrapper\n                disabled={props.readonly}\n            >\n                <Label\n                    key={group.option.id || 'empty'}\n                    color={group.option.color}\n                >\n                    {group.option.value}\n                </Label>\n                <Menu>\n                    <Menu.Text\n                        id='show'\n                        icon={<ShowIcon/>}\n                        name={intl.formatMessage({id: 'BoardComponent.show', defaultMessage: 'Show'})}\n                        onClick={() => mutator.unhideViewColumn(activeView.boardId, activeView, group.option.id)}\n                    />\n                </Menu>\n            </MenuWrapper>\n            {props.group.option.id !== hiddenCardGroupId && <Button>{`${group.cards.length}`}</Button>}\n            {props.group.option.id === hiddenCardGroupId && <Button title='hidden-card-count'>{`${group.cards.length}`}</Button>}\n        </div>\n    )\n}\n\n"
  },
  {
    "path": "webapp/src/components/live-markdown-plugin/block-types/codeBlockStrategy.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {ContentBlock, ContentState, Modifier, SelectionState} from 'draft-js'\n\nimport {BlockStrategy} from '../pluginStrategy'\n\nimport findRangesWithRegex from '../utils/findRangesWithRegex'\n\nconst createCodeBlockStrategy = (): BlockStrategy => {\n    const blockType = 'code-block'\n    const CODE_BLOCK_REGEX = /^```/g\n\n    return {\n        type: blockType,\n        className: 'code-block',\n        mapBlockType: (contentState) => {\n            // Takes a ContentState and returns a ContentState with code block content\n            // block type applied\n            const blockMap = contentState.getBlockMap()\n            let newContentState = contentState\n            let codeBlockKeys: string[] = []\n            let notCodeBlockKeys: string[] = []\n            let tempKeys: string[] = []\n            let language: string\n\n            // Find all code blocks\n            blockMap.forEach((block, blockKey) => {\n                if (!block || !blockKey) {\n                    return\n                }\n                const text = block.getText()\n                const codeBlockDelimiterRanges = findRangesWithRegex(\n                    text,\n                    CODE_BLOCK_REGEX,\n                )\n                const precededByDelimiter = tempKeys.length > 0\n\n                // Parse out the language specified after the delimiter for use with the\n                // draft-js-prism-plugin for syntax highlighting\n                if (codeBlockDelimiterRanges.length > 0 && !precededByDelimiter) {\n                    language = (text.match(/\\w+/g) || [])[0] || 'javascript'\n                }\n\n                // If we find the opening code block delimiter we must maintain an array\n                // of all keys for content blocks that might need to be code blocks if we\n                // later find a closing code block delimiter\n                if (codeBlockDelimiterRanges.length > 0 || precededByDelimiter) {\n                    tempKeys.push(blockKey)\n                } else {\n                    notCodeBlockKeys.push(blockKey)\n                }\n\n                // If we find the closing code block delimiter ``` then store the keys for\n                // the sandwiched content blocks\n                if (codeBlockDelimiterRanges.length > 0 && precededByDelimiter) {\n                    codeBlockKeys = codeBlockKeys.concat(tempKeys)\n                    tempKeys = []\n                }\n            })\n\n            // Loop through keys for blocks that should not have code block type and remove\n            // code block type if necessary\n            notCodeBlockKeys = notCodeBlockKeys.concat(tempKeys)\n            notCodeBlockKeys.forEach((blockKey) => {\n                if (newContentState.getBlockForKey(blockKey).getType() === blockType) {\n                    newContentState = Modifier.setBlockType(\n                        newContentState,\n                        SelectionState.createEmpty(blockKey),\n                        'unstyled',\n                    )\n                }\n            })\n\n            // Loop through found code block keys and apply the block style and language\n            // metadata to the block\n            codeBlockKeys.forEach((blockKey, i) => {\n                // Apply language metadata to block (ignore delimiter blocks)\n                const isDelimiterBlock = i === 0 || i === codeBlockKeys.length - 1\n                const block = newContentState.getBlockForKey(blockKey)\n                const newBlockMap = newContentState.getBlockMap()\n                const data = block.\n                    getData().\n                    merge({language: isDelimiterBlock ? undefined : language})\n                const newBlock = block.merge({data}) as ContentBlock\n                newContentState = newContentState.merge({\n                    blockMap: newBlockMap.set(blockKey, newBlock),\n                }) as ContentState\n\n                // Apply block type to block\n                newContentState = Modifier.setBlockType(\n                    newContentState,\n                    SelectionState.createEmpty(blockKey),\n                    blockType,\n                )\n            })\n\n            return newContentState\n        },\n    }\n}\n\nexport default createCodeBlockStrategy\n"
  },
  {
    "path": "webapp/src/components/live-markdown-plugin/block-types/headingBlockStrategy.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {Modifier, SelectionState} from 'draft-js'\n\nimport {BlockStrategy} from '../pluginStrategy'\n\nimport findRangesWithRegex from '../utils/findRangesWithRegex'\n\nconst createHeadingBlockStrategy = (): BlockStrategy => {\n    const HEADING_REGEX = /(^#{1,6})\\s(.*)/gm\n    const HEADING_LEVELS = [\n        'header-one',\n        'header-two',\n        'header-three',\n        'header-four',\n        'header-five',\n        'header-six',\n    ]\n\n    return {\n        type: 'heading',\n        className: 'heading-block',\n        mapBlockType: (contentState) => {\n            // Takes a ContentState and returns a ContentState with heading content block\n            // type applied\n            const blockMap = contentState.getBlockMap()\n            let newContentState = contentState\n\n            // Find all heading blocks\n            blockMap.forEach((block, blockKey) => {\n                if (!block || !blockKey) {\n                    return\n                }\n\n                const text = block.getText()\n                const headingBlockDelimiterRanges = findRangesWithRegex(\n                    text,\n                    HEADING_REGEX,\n                )\n                let headingLevel = 1\n\n                // Determine what heading level it should be\n                if (headingBlockDelimiterRanges.length > 0) {\n                    headingLevel = (text.match(/#/g) || []).length\n                }\n\n                // Apply the corresponding heading block type\n                if (headingBlockDelimiterRanges.length > 0) {\n                    newContentState = Modifier.setBlockType(\n                        newContentState,\n                        SelectionState.createEmpty(blockKey),\n                        HEADING_LEVELS[headingLevel - 1],\n                    )\n                } else if (HEADING_LEVELS.includes(newContentState.getBlockForKey(blockKey).getType())) {\n                    // Remove any existing heading block type if there shouldn't be one\n                    newContentState = Modifier.setBlockType(\n                        newContentState,\n                        SelectionState.createEmpty(blockKey),\n                        'unstyled',\n                    )\n                }\n            })\n\n            return newContentState\n        },\n    }\n}\n\nexport default createHeadingBlockStrategy\n"
  },
  {
    "path": "webapp/src/components/live-markdown-plugin/inline-styles/boldStyleStrategy.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {InlineStrategy} from '../pluginStrategy'\nimport findRangesWithRegex from '../utils/findRangesWithRegex'\n\n// Bold can be delimited by: **, __, ***, and ___\nconst createBoldStyleStrategy = (): InlineStrategy => {\n    const asteriskDelimitedRegex =\n\t\t'(\\\\*\\\\*\\\\*)(.+?)(\\\\*\\\\*\\\\*)|(\\\\*\\\\*)(.+?)(\\\\*\\\\*)(?!\\\\*)'\n    const underscoreDelimitedRegex = '(___)(.+?)(___)|(__)(.+?)(__)(?!_)'\n    const boldRegex = new RegExp(\n        `${asteriskDelimitedRegex}|${underscoreDelimitedRegex}`,\n        'g',\n    )\n    const boldDelimiterRegex = /^(\\*\\*\\*|\\*\\*|___|__)|(\\*\\*\\*|\\*\\*|___|__)$/g\n\n    return {\n        style: 'BOLD',\n        delimiterStyle: 'BOLD-DELIMITER',\n        findStyleRanges: (block) => {\n            // Return an array of arrays containing start and end indices for ranges of\n            // text that should be bolded\n            // e.g. [[0,6], [10,20]]\n            const text = block.getText()\n            const boldRanges = findRangesWithRegex(text, boldRegex)\n            return boldRanges\n        },\n        findDelimiterRanges: (block, styleRanges) => {\n            // Find ranges for delimiters at the beginning/end of styled text ranges\n            // Returns an array of arrays containing start and end indices for delimiters\n            const text = block.getText()\n            let boldDelimiterRanges: number[][] = []\n            styleRanges.forEach((styleRange) => {\n                const delimiterRange = findRangesWithRegex(\n                    text.substring(styleRange[0], styleRange[1] + 1),\n                    boldDelimiterRegex,\n                ).map((indices) => indices.map((x) => x + styleRange[0]))\n                boldDelimiterRanges = boldDelimiterRanges.concat(delimiterRange)\n            })\n            return boldDelimiterRanges\n        },\n        delimiterStyles: {\n            opacity: 0.4,\n        },\n    }\n}\n\nexport default createBoldStyleStrategy\n"
  },
  {
    "path": "webapp/src/components/live-markdown-plugin/inline-styles/headingDelimiterStyleStrategy.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {InlineStrategy} from '../pluginStrategy'\nimport findRangesWithRegex from '../utils/findRangesWithRegex'\n\nconst createHeadingDelimiterStyleStrategy = (): InlineStrategy => {\n    const headingDelimiterRegex = /(^#{1,6})\\s/g\n\n    return {\n        style: 'HEADING-DELIMITER',\n        findStyleRanges: (block) => {\n            // Skip the text search if the block isn't a header block\n            if (block.getType().indexOf('header') < 0) {\n                return []\n            }\n\n            const text = block.getText()\n            const headingDelimiterRanges = findRangesWithRegex(\n                text,\n                headingDelimiterRegex,\n            )\n            return headingDelimiterRanges\n        },\n        styles: {\n            opacity: 0.4,\n        },\n    }\n}\n\nexport default createHeadingDelimiterStyleStrategy\n"
  },
  {
    "path": "webapp/src/components/live-markdown-plugin/inline-styles/inlineCodeStyleStrategy.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {InlineStrategy} from '../pluginStrategy'\nimport findRangesWithRegex from '../utils/findRangesWithRegex'\n\nconst createInlineCodeStyleStrategy = (): InlineStrategy => {\n    const codeRegex = /(`)([^\\n\\r`]+?)(`)/g\n\n    return {\n        style: 'INLINE-CODE',\n        findStyleRanges: (block) => {\n            // Don't allow inline code inside of code blocks\n            if (block.getType() === 'code-block') {\n                return []\n            }\n\n            const text = block.getText()\n            const codeRanges = findRangesWithRegex(text, codeRegex)\n            return codeRanges\n        },\n        styles: {\n            fontFamily: 'monospace',\n        },\n    }\n}\n\nexport default createInlineCodeStyleStrategy\n"
  },
  {
    "path": "webapp/src/components/live-markdown-plugin/inline-styles/italicStyleStrategy.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {InlineStrategy} from '../pluginStrategy'\nimport findRangesWithRegex from '../utils/findRangesWithRegex'\n\nconst createItalicStyleStrategy = (): InlineStrategy => {\n    const asteriskDelimitedRegex = '(?<!\\\\*)(\\\\*)(?!\\\\*)(.+?)(?<!\\\\*)\\\\*(?!\\\\*)' // *italic*\n    const underscoreDelimitedRegex = '(?<!_)(_)(?!_)(.+?)(?<!_)_(?!_)' // _italic_\n    const strongEmphasisRegex = '(\\\\*\\\\*\\\\*|___)(.+?)(\\\\*\\\\*\\\\*|___)' // ***bolditalic*** ___bolditalic___\n    const boldWrappedAsteriskRegex =\n\t\t'(?<=\\\\*\\\\*)(\\\\*)(?!\\\\*)(.*?[^\\\\*]+)(?<!\\\\*)\\\\*(?![^\\\\*]\\\\*)|(?<!\\\\*)(\\\\*)(?!\\\\*)(.*?[^\\\\*]+)(?<!\\\\*)\\\\*(?=\\\\*\\\\*)' // ***italic* and bold** **bold and *italic***\n    const boldWrappedUnderscoreRegex =\n\t\t'(?<=__)(_)(?!_)(.*?[^_]+)(?<!_)_(?![^_]_)|(?<!_)(_)(?!_)(.*?[^_]+)(?<!_)_(?=__)' // ___italic_ and bold__ __bold and _italic___\n    let italicRegex: RegExp\n    try {\n        italicRegex = new RegExp(\n            `${asteriskDelimitedRegex}|${underscoreDelimitedRegex}|${strongEmphasisRegex}|${boldWrappedAsteriskRegex}|${boldWrappedUnderscoreRegex}`,\n            'g',\n        )\n    } catch {\n        // Safari (as of 15.2) doesn't support RegEx lookbacks (https://caniuse.com/js-regexp-lookbehind)\n        const altAsteriskDelimitedRegex = '([^\\\\*]|^)(\\\\*)([^\\\\*]+)(\\\\*)(?!\\\\*)' // *italic*\n        const altUnderscoreDelimitedRegex = '([^_]|^)(_)([^_]+)(_)(?!_)' // _italic_\n        // TODO: Add support for boldWrappedAsteriskRegex and boldWrappedUnderscoreRegex\n        italicRegex = new RegExp(\n            `${altAsteriskDelimitedRegex}|${altUnderscoreDelimitedRegex}|${strongEmphasisRegex}`,\n            'g',\n        )\n    }\n\n    const italicDelimiterRegex = /^(\\*\\*\\*|\\*|___|_)|(\\*\\*\\*|\\*|___|_)$/g\n\n    return {\n        style: 'ITALIC',\n        delimiterStyle: 'ITALIC-DELIMITER',\n        findStyleRanges: (block) => {\n            // Return an array of arrays containing start and end indices for ranges of\n            // text that should be italicized\n            // e.g. [[0,6], [10,20]]\n            const text = block.getText()\n            const italicRanges = findRangesWithRegex(text, italicRegex)\n            return italicRanges\n        },\n        findDelimiterRanges: (block, styleRanges) => {\n            // Find ranges for delimiters at the beginning/end of styled text ranges\n            // Returns an array of arrays containing start and end indices for delimiters\n            const text = block.getText()\n            let italicDelimiterRanges: number[][] = []\n            styleRanges.forEach((styleRange) => {\n                const delimiterRange = findRangesWithRegex(\n                    text.substring(styleRange[0], styleRange[1] + 1),\n                    italicDelimiterRegex,\n                ).map((indices) => indices.map((x) => x + styleRange[0]))\n                italicDelimiterRanges = italicDelimiterRanges.concat(delimiterRange)\n            })\n            return italicDelimiterRanges\n        },\n        delimiterStyles: {\n            opacity: 0.4,\n        },\n    }\n}\n\nexport default createItalicStyleStrategy\n"
  },
  {
    "path": "webapp/src/components/live-markdown-plugin/inline-styles/olDelimiterStyleStrategy.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {InlineStrategy} from '../pluginStrategy'\nimport findRangesWithRegex from '../utils/findRangesWithRegex'\n\nconst createOLDelimiterStyleStrategy = (): InlineStrategy => {\n    const olDelimiterRegex = /^\\d{1,3}\\. /g\n\n    return {\n        style: 'OL-DELIMITER',\n        findStyleRanges: (block) => {\n            const text = block.getText()\n            const olDelimiterRanges = findRangesWithRegex(text, olDelimiterRegex)\n            return olDelimiterRanges\n        },\n        styles: {\n            fontWeight: 'bold',\n        },\n    }\n}\n\nexport default createOLDelimiterStyleStrategy\n"
  },
  {
    "path": "webapp/src/components/live-markdown-plugin/inline-styles/quoteStyleStrategy.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {InlineStrategy} from '../pluginStrategy'\nimport findRangesWithRegex from '../utils/findRangesWithRegex'\n\nconst createQuoteStyleStrategy = (): InlineStrategy => {\n    const quoteRegex = /^> (.*)/g\n    const quoteDelimiterRegex = /^> /g\n\n    return {\n        style: 'QUOTE',\n        delimiterStyle: 'QUOTE-DELIMITER',\n        findStyleRanges: (block) => {\n            const text = block.getText()\n            const quoteRanges = findRangesWithRegex(text, quoteRegex)\n            return quoteRanges\n        },\n        findDelimiterRanges: (block, styleRanges) => {\n            const text = block.getText()\n            let quoteDelimiterRanges: number[][] = []\n            styleRanges.forEach((styleRange) => {\n                const delimiterRange = findRangesWithRegex(\n                    text.substring(styleRange[0], styleRange[1] + 1),\n                    quoteDelimiterRegex,\n                ).map((indices) => indices.map((x) => x + styleRange[0]))\n                quoteDelimiterRanges = quoteDelimiterRanges.concat(delimiterRange)\n            })\n            return quoteDelimiterRanges\n        },\n        styles: {\n            opacity: 0.75,\n        },\n        delimiterStyles: {\n            opacity: 0.4,\n        },\n    }\n}\n\nexport default createQuoteStyleStrategy\n"
  },
  {
    "path": "webapp/src/components/live-markdown-plugin/inline-styles/strikethroughStyleStrategy.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {InlineStrategy} from '../pluginStrategy'\nimport findRangesWithRegex from '../utils/findRangesWithRegex'\n\nconst createStrikethroughStyleStrategy = (): InlineStrategy => {\n    const strikethroughRegex = /(~~)(.+?)(~~)/g\n    const strikethroughDelimiterRegex = /^(~~)|(~~)$/g\n\n    return {\n        style: 'STRIKETHROUGH',\n        delimiterStyle: 'STRIKETHROUGH-DELIMITER',\n        findStyleRanges: (block) => {\n            // Return an array of arrays containing start and end indices for ranges of\n            // text that should be crossed out\n            // e.g. [[0,6], [10,20]]\n            const text = block.getText()\n            const strikethroughRanges = findRangesWithRegex(text, strikethroughRegex)\n            return strikethroughRanges\n        },\n        findDelimiterRanges: (block, styleRanges) => {\n            // Find ranges for delimiters at the beginning/end of styled text ranges\n            // Returns an array of arrays containing start and end indices for delimiters\n            const text = block.getText()\n            let strikethroughDelimiterRanges: number[][] = []\n            styleRanges.forEach((styleRange) => {\n                const delimiterRange = findRangesWithRegex(\n                    text.substring(styleRange[0], styleRange[1] + 1),\n                    strikethroughDelimiterRegex,\n                ).map((indices) => indices.map((x) => x + styleRange[0]))\n                strikethroughDelimiterRanges = strikethroughDelimiterRanges.concat(\n                    delimiterRange,\n                )\n            })\n            return strikethroughDelimiterRanges\n        },\n        styles: {\n            textDecoration: 'line-through',\n        },\n        delimiterStyles: {\n            opacity: 0.4,\n            textDecoration: 'none',\n        },\n    }\n}\n\nexport default createStrikethroughStyleStrategy\n"
  },
  {
    "path": "webapp/src/components/live-markdown-plugin/inline-styles/ulDelimiterStyleStrategy.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {InlineStrategy} from '../pluginStrategy'\nimport findRangesWithRegex from '../utils/findRangesWithRegex'\n\nconst createULDelimiterStyleStrategy = (): InlineStrategy => {\n    const ulDelimiterRegex = /^\\* /g\n\n    return {\n        style: 'UL-DELIMITER',\n        findStyleRanges: (block) => {\n            const text = block.getText()\n            const ulDelimiterRanges = findRangesWithRegex(text, ulDelimiterRegex)\n            return ulDelimiterRanges\n        },\n        styles: {\n            fontWeight: 'bold',\n        },\n    }\n}\n\nexport default createULDelimiterStyleStrategy\n"
  },
  {
    "path": "webapp/src/components/live-markdown-plugin/liveMarkdownPlugin.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {\n    EditorState,\n    CharacterMetadata,\n    ContentState,\n    ContentBlock,\n    EditorChangeType,\n    DraftStyleMap,\n} from 'draft-js'\nimport {EditorPlugin} from '@draft-js-plugins/editor'\nimport {Repeat, List} from 'immutable'\n\n// Inline style handlers\nimport createBoldStyleStrategy from './inline-styles/boldStyleStrategy'\nimport createItalicStyleStrategy from './inline-styles/italicStyleStrategy'\nimport createStrikethroughStyleStrategy from './inline-styles/strikethroughStyleStrategy'\nimport createHeadingDelimiterStyleStrategy from './inline-styles/headingDelimiterStyleStrategy'\nimport createULDelimiterStyleStrategy from './inline-styles/ulDelimiterStyleStrategy'\nimport createOLDelimiterStyleStrategy from './inline-styles/olDelimiterStyleStrategy'\nimport createQuoteStyleStrategy from './inline-styles/quoteStyleStrategy'\nimport createInlineCodeStyleStrategy from './inline-styles/inlineCodeStyleStrategy'\n\n// Block type handlers\nimport createCodeBlockStrategy from './block-types/codeBlockStrategy'\nimport createHeadingBlockStrategy from './block-types/headingBlockStrategy'\nimport {BlockStrategy, InlineStrategy} from './pluginStrategy'\n\nexport interface LiveMarkdownPluginConfig {\n    inlineStyleStrategies?: InlineStrategy[]\n    blockTypeStrategies?: BlockStrategy[]\n}\n\nfunction createLiveMarkdownPlugin(config: LiveMarkdownPluginConfig = {}): EditorPlugin {\n    const {\n        inlineStyleStrategies = [\n            createBoldStyleStrategy(),\n            createItalicStyleStrategy(),\n            createStrikethroughStyleStrategy(),\n            createHeadingDelimiterStyleStrategy(),\n            createULDelimiterStyleStrategy(),\n            createOLDelimiterStyleStrategy(),\n            createQuoteStyleStrategy(),\n            createInlineCodeStyleStrategy(),\n        ],\n        blockTypeStrategies = [\n            createCodeBlockStrategy(),\n            createHeadingBlockStrategy(),\n        ],\n    } = config\n\n    // Construct the editor style map from our inline style strategies\n    const customStyleMap: DraftStyleMap = {}\n    inlineStyleStrategies.forEach((styleStrategy) => {\n        if (styleStrategy.style && styleStrategy.styles) {\n            customStyleMap[styleStrategy.style] = styleStrategy.styles\n        }\n        if (styleStrategy.delimiterStyle && styleStrategy.delimiterStyles) {\n            customStyleMap[styleStrategy.delimiterStyle] =\n                styleStrategy.delimiterStyles\n        }\n    })\n\n    // Construct the block style fn\n    const blockStyleMap = blockTypeStrategies.reduce((map: Record<string, string>, blockStrategy) => {\n        map[blockStrategy.type] = blockStrategy.className\n        return map\n    }, {})\n    const blockStyleFn = (block: ContentBlock) => {\n        const blockType = block.getType()\n        return blockStyleMap[blockType]\n    }\n\n    return {\n\n        // We must handle the maintenance of block types and inline styles on changes.\n        // To make sure the code is efficient we only perform maintenance on content\n        // blocks that have been changed. We only perform maintenance for change types\n        // that result in actual text changes (ignore cursing through text, etc).\n        onChange: (editorState) => {\n            // if (editorState.getLastChangeType() === 'insert-fragment')\n            //   return maintainWholeEditorState();\n            return maintainEditorState(\n                editorState,\n                blockTypeStrategies,\n                inlineStyleStrategies,\n            )\n        },\n        customStyleMap,\n        blockStyleFn,\n    }\n}\n\n// Takes an EditorState and returns a ContentState updated with block types and\n// inline styles according to the provided strategies\n// Takes a targeted approach that only updates the modified block/blocks\nconst maintainEditorState = (\n    editorState: EditorState,\n    blockTypeStrategies: BlockStrategy[],\n    inlineStyleStrategies: InlineStrategy[],\n) => {\n    // Bypass maintenance if text was not changed\n    const lastChangeType = editorState.getLastChangeType()\n    const bypassOnChangeTypes = [\n        'adjust-depth',\n        'apply-entity',\n        'change-block-data',\n        'change-block-type',\n        'change-inline-style',\n        'maintain-markdown',\n    ]\n    if (bypassOnChangeTypes.includes(lastChangeType)) {\n        return editorState\n    }\n\n    // Maintain block types then inline styles\n    // Order is important bc we want the inline style strategies to be able to\n    // look at block type to avoid unnecessary regex searching when possible\n    const contentState = editorState.getCurrentContent()\n    let newContentState = maintainBlockTypes(contentState, blockTypeStrategies)\n    newContentState = maintainInlineStyles(\n        newContentState,\n        editorState,\n        inlineStyleStrategies,\n    )\n\n    // Apply the updated content state\n    let newEditorState = editorState\n    if (contentState !== newContentState) {\n        newEditorState = EditorState.push(\n            editorState,\n            newContentState,\n            'maintain-markdown' as EditorChangeType,\n        )\n    }\n    newEditorState = EditorState.forceSelection(\n        newEditorState,\n        editorState.getSelection(),\n    )\n\n    return newEditorState\n}\n\n// Takes a ContentState and returns a ContentState with block types and inline styles\n// applied or removed as necessary\nconst maintainBlockTypes = (contentState: ContentState, blockTypeStrategies: BlockStrategy[]) => {\n    return blockTypeStrategies.reduce((cs, blockTypeStrategy) => {\n        return blockTypeStrategy.mapBlockType(cs)\n    }, contentState)\n}\n\n// Takes a ContentState (and EditorState for getting the selection and change type)\n// and returns a ContentState with inline styles applied or removed as necessary\nconst maintainInlineStyles = (\n    contentState: ContentState,\n    editorState: EditorState,\n    inlineStyleStrategies: InlineStrategy[],\n): ContentState => {\n    const lastChangeType = editorState.getLastChangeType()\n    const selection = editorState.getSelection()\n    const blockKey = selection.getStartKey()\n    const block = contentState.getBlockForKey(blockKey)\n    const blockMap = contentState.getBlockMap()\n    let newBlockMap = blockMap\n\n    // If text has been pasted (potentially modifying/creating multiple blocks) or\n    // the editor is new we must maintain the styles for all content blocks\n    if (lastChangeType === 'insert-fragment' || !lastChangeType) {\n        blockMap.forEach((b, k) => {\n            if (!b || !k) {\n                return\n            }\n            const newBlock = mapInlineStyles(b, inlineStyleStrategies) as ContentBlock\n            newBlockMap = newBlockMap.set(k, newBlock)\n        })\n    } else {\n        const newBlock = mapInlineStyles(block, inlineStyleStrategies) as ContentBlock\n        newBlockMap = newBlockMap.set(blockKey, newBlock)\n    }\n\n    // If enter was pressed (or the block was otherwise split) we must maintain\n    // styles in the previous block as well\n    if (lastChangeType === 'split-block') {\n        const newPrevBlock = mapInlineStyles(\n            contentState.getBlockBefore(blockKey)!,\n            inlineStyleStrategies,\n        ) as ContentBlock\n        newBlockMap = newBlockMap.set(\n            contentState.getKeyBefore(blockKey),\n            newPrevBlock,\n        )\n    }\n\n    const newContentState = contentState.merge({\n        blockMap: newBlockMap,\n    }) as ContentState\n\n    return newContentState\n}\n\n// Maps inline styles to the provided ContentBlock's CharacterMetadata list based\n// on the plugin's inline style strategies\nconst mapInlineStyles = (block: ContentBlock, strategies: InlineStrategy[]) => {\n    // This will be called upon any change that has the potential to effect the styles\n    // of a content block.\n    // Find all of the ranges that should have styles applied to them (i.e. all bold,\n    // italic, or strikethrough delimited ranges of the block).\n    const blockText = block.getText()\n\n    // Create a list of empty CharacterMetadata to map styles to\n    // eslint-disable-next-line new-cap\n    let characterMetadataList = List(\n        // eslint-disable-next-line new-cap\n        Repeat(CharacterMetadata.create(), blockText.length),\n    )\n\n    // Evaluate block text with each style strategy and apply styles to matching\n    // ranges of text and delimiters\n    strategies.forEach((strategy) => {\n        const styleRanges = strategy.findStyleRanges(block)\n        const delimiterRanges = strategy.findDelimiterRanges ? strategy.findDelimiterRanges(block, styleRanges) : []\n\n        characterMetadataList = applyStyleRangesToCharacterMetadata(\n            strategy.style,\n            styleRanges,\n            characterMetadataList,\n        )\n\n        characterMetadataList = applyStyleRangesToCharacterMetadata(\n            strategy.delimiterStyle,\n            delimiterRanges,\n            characterMetadataList,\n        )\n    })\n\n    // Apply the list of CharacterMetadata to the content block\n    return block.set('characterList', characterMetadataList)\n}\n\n// Applies the provided style to the corresponding ranges of the character metadata\nconst applyStyleRangesToCharacterMetadata = (\n    style: string | undefined,\n    ranges: number[][],\n    characterMetadataList: List<CharacterMetadata>,\n) => {\n    let styledCharacterMetadataList = characterMetadataList\n    if (!style) {\n        return styledCharacterMetadataList\n    }\n\n    ranges.forEach((range) => {\n        for (let i = range[0]; i <= range[1]; i++) {\n            const styled = CharacterMetadata.applyStyle(\n                characterMetadataList.get(i),\n                style,\n            )\n            styledCharacterMetadataList = styledCharacterMetadataList.set(i, styled)\n        }\n    })\n    return styledCharacterMetadataList\n}\n\nexport default createLiveMarkdownPlugin\n"
  },
  {
    "path": "webapp/src/components/live-markdown-plugin/pluginStrategy.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport * as React from 'react'\nimport {ContentBlock, ContentState} from 'draft-js'\n\nexport interface InlineStrategy {\n    style: string\n    findStyleRanges: (text: ContentBlock) => number[][]\n    findDelimiterRanges?: (text: ContentBlock, styleRanges: number[][]) => number[][]\n    delimiterStyle?: string\n    styles?: React.CSSProperties\n    delimiterStyles?: React.CSSProperties\n}\n\nexport interface BlockStrategy {\n    type: string\n    className: string\n    mapBlockType: (state: ContentState) => ContentState\n}\n"
  },
  {
    "path": "webapp/src/components/live-markdown-plugin/utils/findRangesWithRegex.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nconst findRangesWithRegex = (text: string, regex: RegExp): number[][] => {\n    const ranges: number[][] = []\n    let matches\n\n    do {\n        matches = regex.exec(text)\n        if (matches) {\n            ranges.push([matches.index, (matches.index + matches[0].length) - 1])\n        }\n    } while (matches)\n\n    return ranges\n}\n\nexport default findRangesWithRegex\n"
  },
  {
    "path": "webapp/src/components/markdownEditor.scss",
    "content": "/* Markdown Editor */\n\n.product-wrapper {\n    .octo-editor-preview {\n        a {\n            color: rgba(var(--link-color-rgb), 1);\n        }\n    }\n}\n\n.MarkdownEditor {\n    cursor: text;\n\n    .MarkdownEditorInput * {\n        user-select: text;\n    }\n\n    .octo-editor-preview * {\n        user-select: text;\n    }\n\n    .octo-editor-preview {\n        min-height: 2em;\n\n        a {\n            &:hover {\n                text-decoration: underline;\n            }\n        }\n\n        p {\n            margin: 0;\n            word-break: break-word;\n        }\n    }\n\n    .octo-editor-activeEditor {\n        overflow: hidden;\n        border-radius: 5px;\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/markdownEditor.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {act, fireEvent, render, screen} from '@testing-library/react'\nimport userEvent from '@testing-library/user-event'\nimport React from 'react'\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport {mockDOM, wrapDNDIntl, mockStateStore} from '../testUtils'\n\nimport {TestBlockFactory} from '../test/testBlockFactory'\n\nimport {MarkdownEditor} from './markdownEditor'\n\njest.mock('../utils')\njest.useFakeTimers()\njest.mock('draft-js/lib/generateRandomKey', () => () => '123')\n\ndescribe('components/markdownEditor', () => {\n    beforeAll(mockDOM)\n    beforeEach(jest.clearAllMocks)\n\n    const board1 = TestBlockFactory.createBoard()\n    board1.id = 'board-id-1'\n\n    const state = {\n        users: {\n            boardUsers: {\n                1: {username: 'abc'},\n                2: {username: 'd'},\n                3: {username: 'e'},\n                4: {username: 'f'},\n                5: {username: 'g'},\n            },\n        },\n        boards: {\n            current: 'board-id-1',\n            boards: {\n                [board1.id]: board1,\n            },\n        },\n        clientConfig: {\n            value: {},\n        },\n    }\n    const store = mockStateStore([], state)\n    test('should match snapshot', async () => {\n        let container\n        await act(async () => {\n            const result = render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <MarkdownEditor\n                        id={'test-id'}\n                        text={''}\n                        placeholderText={'placeholder'}\n                        className={'classname-test'}\n                        readonly={false}\n                        onChange={jest.fn()}\n                        onFocus={jest.fn()}\n                        onBlur={jest.fn()}\n                    />\n                </ReduxProvider>,\n            ))\n            container = result.container\n        })\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot with initial text', async () => {\n        let container\n        await act(async () => {\n            const result = render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n\n                    <MarkdownEditor\n                        id={'test-id'}\n                        text={'some initial text already set'}\n                        placeholderText={'placeholder'}\n                        className={'classname-test'}\n                        readonly={false}\n                        onChange={jest.fn()}\n                        onFocus={jest.fn()}\n                        onBlur={jest.fn()}\n                    />\n                </ReduxProvider>,\n\n            ))\n            container = result.container\n        })\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot with on click on preview element', async () => {\n        let container\n        await act(async () => {\n            const result = render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <MarkdownEditor\n                        id={'test-id'}\n                        text={'some initial text already set'}\n                        placeholderText={'placeholder'}\n                        className={'classname-test'}\n                        readonly={false}\n                        onChange={jest.fn()}\n                        onFocus={jest.fn()}\n                        onBlur={jest.fn()}\n                    />\n                </ReduxProvider>,\n\n            ))\n            container = result.container\n            const previewElement = screen.getByTestId('preview-element')\n            userEvent.click(previewElement)\n        })\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot with on click on preview element and then click out of it', async () => {\n        let container\n        await act(async () => {\n            const result = render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <MarkdownEditor\n                        id={'test-id'}\n                        text={'some initial text already set'}\n                        placeholderText={'placeholder'}\n                        className={'classname-test'}\n                        readonly={false}\n                        onChange={jest.fn()}\n                        onFocus={jest.fn()}\n                        onBlur={jest.fn()}\n                    />\n                </ReduxProvider>,\n\n            ))\n            container = result.container\n            const previewElement = screen.getByTestId('preview-element')\n            userEvent.click(previewElement)\n            fireEvent.keyDown(container, {\n                key: 'Escape',\n                code: 'Escape',\n                keyCode: 27,\n                charCode: 27,\n            })\n        })\n        expect(container).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/markdownEditor.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useState, Suspense} from 'react'\n\nimport {Utils} from '../utils'\nimport './markdownEditor.scss'\n\nconst MarkdownEditorInput = React.lazy(() => import('./markdownEditorInput/markdownEditorInput'))\n\ntype Props = {\n    id?: string\n    text?: string\n    placeholderText?: string\n    className?: string\n    readonly?: boolean\n\n    onChange?: (text: string) => void\n    onFocus?: () => void\n    onBlur?: (text: string) => void\n    onKeyDown?: (e: React.KeyboardEvent) => void\n    onEditorCancel?: () => void\n    autofocus?: boolean\n    saveOnEnter?: boolean\n}\n\nconst MarkdownEditor = (props: Props): JSX.Element => {\n    const {placeholderText, onFocus, onEditorCancel, onBlur, onChange, text, id, saveOnEnter} = props\n    const [isEditing, setIsEditing] = useState(Boolean(props.autofocus))\n    const html: string = Utils.htmlFromMarkdown(text || placeholderText || '')\n\n    const previewElement = (\n        <div\n            data-testid='preview-element'\n            className={text ? 'octo-editor-preview' : 'octo-editor-preview octo-placeholder'}\n            dangerouslySetInnerHTML={{__html: html}}\n            onClick={(e) => {\n                const LINK_TAG_NAME = 'a'\n                const element = e.target as Element\n                if (element.tagName.toLowerCase() === LINK_TAG_NAME) {\n                    e.stopPropagation()\n                    return\n                }\n\n                if (!props.readonly && !isEditing) {\n                    setIsEditing(true)\n                }\n            }}\n        />\n    )\n\n    const editorOnBlur = (newText: string) => {\n        setIsEditing(false)\n        onBlur && onBlur(newText)\n    }\n\n    const editorElement = (\n        <Suspense fallback={<></>}>\n            <MarkdownEditorInput\n                id={id}\n                onChange={onChange}\n                onFocus={onFocus}\n                onEditorCancel={onEditorCancel}\n                onBlur={editorOnBlur}\n                initialText={text}\n                isEditing={isEditing}\n                saveOnEnter={saveOnEnter}\n            />\n        </Suspense>\n    )\n\n    const element = (\n        <div className={`MarkdownEditor octo-editor ${props.className || ''} ${isEditing ? 'active' : ''}`}>\n            {isEditing ? editorElement : previewElement}\n        </div>\n    )\n\n    return element\n}\n\nexport {MarkdownEditor}\n"
  },
  {
    "path": "webapp/src/components/markdownEditorInput/entryComponent/entryComponent.scss",
    "content": ".EntryComponent {\n    display: flex;\n    align-items: center;\n    justify-content: space-between;\n    width: 100%;\n\n    .EntryComponent__left {\n        display: flex;\n        align-items: center;\n    }\n\n    .EntryComponent__hint {\n        opacity: 0.56;\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/markdownEditorInput/entryComponent/entryComponent.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {ReactElement} from 'react'\nimport {FormattedMessage} from 'react-intl'\nimport {EntryComponentProps} from '@draft-js-plugins/mention/lib/MentionSuggestions/Entry/Entry'\n\nimport GuestBadge from '../../../widgets/guestBadge'\n\nimport './entryComponent.scss'\n\nconst BotBadge = (window as any).Components?.BotBadge\n\nconst Entry = (props: EntryComponentProps): ReactElement => {\n    const {\n        mention,\n        theme,\n        ...parentProps\n    } = props\n\n    return (\n        <div\n            {...parentProps}\n        >\n            <div className={`${theme?.mentionSuggestionsEntryContainer} EntryComponent`}>\n                <div className='EntryComponent__left'>\n                    <img\n                        src={mention.avatar}\n                        className={theme?.mentionSuggestionsEntryAvatar}\n                        role='presentation'\n                    />\n                    <div className={theme?.mentionSuggestionsEntryText}>\n                        {mention.name}\n                        {BotBadge && mention.is_bot && <BotBadge/>}\n                        <GuestBadge show={mention.is_guest}/>\n                    </div>\n                    <div className={theme?.mentionSuggestionsEntryText}>\n                        {mention.displayName}\n                    </div>\n                </div>\n                {!mention.isBoardMember &&\n                    <div className={`EntryComponent__hint ${theme?.mentionSuggestionsEntryText}`}>\n                        <FormattedMessage\n                            id='MentionSuggestion.is-not-board-member'\n                            defaultMessage='(not board member)'\n                        />\n                    </div>}\n            </div>\n        </div>\n    )\n}\n\nexport default Entry\n"
  },
  {
    "path": "webapp/src/components/markdownEditorInput/markdownEditorInput.scss",
    "content": ".MarkdownEditorInput {\n    div[role=option] {\n        display: flex;\n        align-items: center;\n    }\n\n    span[data-testid='mentionText'] {\n        background: rgba(var(--button-bg-rgb), 0.16);\n        border-radius: 4px;\n        color: inherit;\n    }\n}\n\n.DraftEditor-root + div {\n    background: rgba(var(--center-channel-bg-rgb), 1);\n    box-shadow: var(--elevation-1);\n    border: 1px solid rgba(var(--center-channel-color-rgb), 0.16);\n    border-radius: 4px;\n\n    > div {\n        > div {\n            height: 40px;\n            padding: 0 20px;\n\n            &[aria-selected='true'] {\n                background: rgba(var(--center-channel-color-rgb), 0.08);\n            }\n\n            &:hover {\n                background: rgba(var(--center-channel-color-rgb), 0.08);\n            }\n\n            &:active {\n                background: rgba(var(--button-bg-rgb), 0.08);\n            }\n\n            .Badge {\n                margin-left: 12px;\n            }\n        }\n    }\n\n    .code-block {\n        background-color: unset;\n        border: 0;\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/markdownEditorInput/markdownEditorInput.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport Editor from '@draft-js-plugins/editor'\nimport createEmojiPlugin from '@draft-js-plugins/emoji'\nimport '@draft-js-plugins/emoji/lib/plugin.css'\nimport createMentionPlugin from '@draft-js-plugins/mention'\nimport '@draft-js-plugins/mention/lib/plugin.css'\nimport {ContentState, DraftHandleValue, EditorState, getDefaultKeyBinding} from 'draft-js'\nimport React, {\n    ReactElement, useCallback, useEffect,\n    useMemo, useRef,\n    useState,\n} from 'react'\n\nimport {debounce} from 'lodash'\n\nimport {useAppSelector} from '../../store/hooks'\nimport {IUser} from '../../user'\nimport {getBoardUsersList, getMe} from '../../store/users'\nimport createLiveMarkdownPlugin from '../live-markdown-plugin/liveMarkdownPlugin'\nimport {useHasPermissions} from '../../hooks/permissions'\nimport {Permission} from '../../constants'\nimport {BoardMember, BoardTypeOpen, MemberRole} from '../../blocks/board'\nimport mutator from '../../mutator'\nimport ConfirmAddUserForNotifications from '../confirmAddUserForNotifications'\nimport RootPortal from '../rootPortal'\n\nimport './markdownEditorInput.scss'\n\nimport {getCurrentBoard} from '../../store/boards'\nimport octoClient from '../../octoClient'\n\nimport {Utils} from '../../utils'\nimport {ClientConfig} from '../../config/clientConfig'\nimport {getClientConfig} from '../../store/clientConfig'\n\nimport Entry from './entryComponent/entryComponent'\n\nconst imageURLForUser = (window as any).Components?.imageURLForUser\n\ntype MentionUser = {\n    user: IUser\n    name: string\n    avatar: string\n    is_bot: boolean\n    is_guest: boolean\n    displayName: string\n    isBoardMember: boolean\n}\n\ntype Props = {\n    onChange?: (text: string) => void\n    onFocus?: () => void\n    onBlur?: (text: string) => void\n    onEditorCancel?: () => void\n    initialText?: string\n    id?: string\n    isEditing: boolean\n    saveOnEnter?: boolean\n}\n\nconst MarkdownEditorInput = (props: Props): ReactElement => {\n    const {onChange, onFocus, onBlur, initialText, id} = props\n    const boardUsers = useAppSelector<IUser[]>(getBoardUsersList)\n    const board = useAppSelector(getCurrentBoard)\n    const clientConfig = useAppSelector<ClientConfig>(getClientConfig)\n    const ref = useRef<Editor>(null)\n    const allowManageBoardRoles = useHasPermissions(board.teamId, board.id, [Permission.ManageBoardRoles])\n    const [confirmAddUser, setConfirmAddUser] = useState<IUser|null>(null)\n    const me = useAppSelector<IUser|null>(getMe)\n\n    const [suggestions, setSuggestions] = useState<MentionUser[]>([])\n\n    const loadSuggestions = async (term: string) => {\n        let users: IUser[]\n\n        if (!me?.is_guest && (allowManageBoardRoles || (board && board.type === BoardTypeOpen))) {\n            const excludeBots = true\n            users = await octoClient.searchTeamUsers(term, excludeBots)\n        } else {\n            users = boardUsers.\n                filter((user) => {\n                    // no search term\n                    if (!term) {\n                        return true\n                    }\n\n                    // does the search term occur anywhere in the display name?\n                    return Utils.getUserDisplayName(user, clientConfig.teammateNameDisplay).includes(term)\n                }).\n\n                // first 10 results\n                slice(0, 10)\n        }\n\n        const mentions: MentionUser[] = users.map(\n            (user: IUser): MentionUser => ({\n                name: user.username,\n                avatar: `${imageURLForUser ? imageURLForUser(user.id) : ''}`,\n                is_bot: user.is_bot,\n                is_guest: user.is_guest,\n                displayName: Utils.getUserDisplayName(user, clientConfig.teammateNameDisplay),\n                isBoardMember: Boolean(boardUsers.find((u) => u.id === user.id)),\n                user,\n            }))\n        setSuggestions(mentions)\n    }\n\n    const debouncedLoadSuggestion = useMemo(() => debounce(loadSuggestions, 200), [])\n\n    useEffect(() => {\n        // Get the ball rolling. Searching for empty string\n        // returns first 10 users in alphabetical order.\n        loadSuggestions('')\n    }, [])\n\n    const generateEditorState = (text?: string) => {\n        const state = EditorState.createWithContent(ContentState.createFromText(text || ''))\n        return EditorState.moveSelectionToEnd(state)\n    }\n\n    const [editorState, setEditorState] = useState(() => generateEditorState(initialText))\n\n    const addUser = useCallback(async (userId: string, role: string) => {\n        const newRole = role || MemberRole.Viewer\n        const newMember = {\n            boardId: board.id,\n            userId,\n            roles: role,\n            schemeAdmin: newRole === MemberRole.Admin,\n            schemeEditor: newRole === MemberRole.Admin || newRole === MemberRole.Editor,\n            schemeCommenter: newRole === MemberRole.Admin || newRole === MemberRole.Editor || newRole === MemberRole.Commenter,\n            schemeViewer: newRole === MemberRole.Admin || newRole === MemberRole.Editor || newRole === MemberRole.Commenter || newRole === MemberRole.Viewer,\n        } as BoardMember\n\n        setConfirmAddUser(null)\n        setEditorState(EditorState.moveSelectionToEnd(editorState))\n        ref.current?.focus()\n        await mutator.createBoardMember(newMember)\n    }, [board, editorState])\n\n    const [initialTextCache, setInitialTextCache] = useState<string | undefined>(initialText)\n    const [initialTextUsed, setInitialTextUsed] = useState<boolean>(false)\n\n    // avoiding stale closure\n    useEffect(() => {\n        // only change editor state when initialText actually changes from one defined value to another.\n        // This is needed to make the mentions plugin work. For some reason, if we don't check\n        // for this if condition here, mentions don't work. I suspect it's because without\n        // the in condition, we're changing editor state twice during component initialization\n        // and for some reason it causes mentions to not show up.\n\n        // initial text should only be used once, i'e', initially\n        // `initialTextUsed` flag records if the initialText prop has been used\n        // to se the editor state once as a truthy value.\n        // Once used, we don't react to its changing value\n\n        if (!initialTextUsed && initialText && initialText !== initialTextCache) {\n            setEditorState(generateEditorState(initialText || ''))\n            setInitialTextCache(initialText)\n            setInitialTextUsed(true)\n        }\n    }, [initialText])\n\n    const [isMentionPopoverOpen, setIsMentionPopoverOpen] = useState(false)\n    const [isEmojiPopoverOpen, setIsEmojiPopoverOpen] = useState(false)\n\n    const {MentionSuggestions, plugins, EmojiSuggestions} = useMemo(() => {\n        const mentionPlugin = createMentionPlugin({mentionPrefix: '@'})\n        const emojiPlugin = createEmojiPlugin()\n        const markdownPlugin = createLiveMarkdownPlugin()\n\n        // eslint-disable-next-line @typescript-eslint/no-shadow\n        const {EmojiSuggestions} = emojiPlugin\n        // eslint-disable-next-line @typescript-eslint/no-shadow\n        const {MentionSuggestions} = mentionPlugin\n        // eslint-disable-next-line @typescript-eslint/no-shadow\n        const plugins = [\n            mentionPlugin,\n            emojiPlugin,\n            markdownPlugin,\n        ]\n        return {plugins, MentionSuggestions, EmojiSuggestions}\n    }, [])\n\n    const onEditorStateChange = useCallback((newEditorState: EditorState) => {\n        // newEditorState.\n        const newText = newEditorState.getCurrentContent().getPlainText()\n\n        onChange && onChange(newText)\n        setEditorState(newEditorState)\n    }, [onChange])\n\n    const customKeyBindingFn = useCallback((e: React.KeyboardEvent) => {\n        if (isMentionPopoverOpen || isEmojiPopoverOpen) {\n            return undefined\n        }\n\n        if (e.key === 'Escape') {\n            return 'editor-blur'\n        }\n\n        if (e.key === 'Backspace') {\n            return 'backspace'\n        }\n\n        if (getDefaultKeyBinding(e) === 'undo') {\n            return 'editor-undo'\n        }\n\n        if (getDefaultKeyBinding(e) === 'redo') {\n            return 'editor-redo'\n        }\n\n        return getDefaultKeyBinding(e as any)\n    }, [isEmojiPopoverOpen, isMentionPopoverOpen])\n\n    const handleKeyCommand = useCallback((command: string, currentState: EditorState): DraftHandleValue => {\n        if (command === 'editor-blur') {\n            ref.current?.blur()\n            return 'handled'\n        }\n\n        if (command === 'editor-redo') {\n            const selectionRemovedState = EditorState.redo(currentState)\n            onEditorStateChange(EditorState.redo(selectionRemovedState))\n\n            return 'handled'\n        }\n\n        if (command === 'editor-undo') {\n            const selectionRemovedState = EditorState.undo(currentState)\n            onEditorStateChange(EditorState.undo(selectionRemovedState))\n\n            return 'handled'\n        }\n\n        if (command === 'backspace') {\n            if (props.onEditorCancel && editorState.getCurrentContent().getPlainText().length === 0) {\n                props.onEditorCancel()\n                return 'handled'\n            }\n        }\n\n        return 'not-handled'\n    }, [props.onEditorCancel, editorState])\n\n    const onEditorStateBlur = useCallback(() => {\n        if (confirmAddUser) {\n            return\n        }\n        const text = editorState.getCurrentContent().getPlainText()\n        onBlur && onBlur(text)\n    }, [editorState.getCurrentContent().getPlainText(), onBlur, confirmAddUser])\n\n    const onMentionPopoverOpenChange = useCallback((open: boolean) => {\n        setIsMentionPopoverOpen(open)\n    }, [])\n\n    const onEmojiPopoverOpen = useCallback(() => {\n        setIsEmojiPopoverOpen(true)\n    }, [])\n\n    const onEmojiPopoverClose = useCallback(() => {\n        setIsEmojiPopoverOpen(false)\n    }, [])\n\n    const onSearchChange = useCallback(({value}: { value: string }) => {\n        debouncedLoadSuggestion(value)\n    }, [suggestions])\n\n    const className = 'MarkdownEditorInput'\n\n    const handleReturn = (e: any, state: EditorState): DraftHandleValue => {\n        if (!e.shiftKey) {\n            const text = state.getCurrentContent().getPlainText()\n            onBlur && onBlur(text)\n            return 'handled'\n        }\n        return 'not-handled'\n    }\n\n    return (\n        <div\n            className={className}\n            onKeyDown={(e: React.KeyboardEvent) => {\n                if (isMentionPopoverOpen || isEmojiPopoverOpen) {\n                    e.stopPropagation()\n                }\n            }}\n        >\n            <Editor\n                editorKey={id}\n                editorState={editorState}\n                onChange={onEditorStateChange}\n                plugins={plugins}\n                ref={ref}\n                onBlur={onEditorStateBlur}\n                onFocus={onFocus}\n                keyBindingFn={customKeyBindingFn}\n                handleKeyCommand={handleKeyCommand}\n                handleReturn={props.saveOnEnter ? handleReturn : undefined}\n            />\n            <MentionSuggestions\n                open={isMentionPopoverOpen}\n                onOpenChange={onMentionPopoverOpenChange}\n                suggestions={suggestions}\n                onSearchChange={onSearchChange}\n                entryComponent={Entry}\n                onAddMention={(mention) => {\n                    if (mention.isBoardMember) {\n                        return\n                    }\n                    setConfirmAddUser(mention.user)\n                }}\n            />\n            <EmojiSuggestions\n                onOpen={onEmojiPopoverOpen}\n                onClose={onEmojiPopoverClose}\n            />\n            {confirmAddUser &&\n                <RootPortal>\n                    <ConfirmAddUserForNotifications\n                        allowManageBoardRoles={allowManageBoardRoles}\n                        minimumRole={board.minimumRole}\n                        user={confirmAddUser}\n                        onConfirm={addUser}\n                        onClose={() => {\n                            setConfirmAddUser(null)\n                            setEditorState(EditorState.moveSelectionToEnd(editorState))\n                            ref.current?.focus()\n                        }}\n                    />\n                </RootPortal>}\n        </div>\n    )\n}\n\nexport default MarkdownEditorInput\n"
  },
  {
    "path": "webapp/src/components/messages/versionMessage.scss",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n.VersionMessage {\n    background-color: rgb(var(--sidebar-text-active-border-rgb));\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    text-align: center;\n    font-weight: 600;\n\n    div {\n        width: 100%;\n    }\n\n    > .banner {\n        display: flex;\n        flex-direction: row;\n        align-items: center;\n        justify-content: center;\n        padding: 10px;\n        color: #fff;\n\n        .CompassIcon {\n            font-size: 18px;\n            margin-right: 2px;\n        }\n\n        .Button {\n            margin-left: 8px;\n            background-color: rgba(255, 255, 255, 0.16);\n        }\n    }\n\n    .IconButton {\n        float: right;\n        color: #fff;\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/messages/versionMessage.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport {render, screen} from '@testing-library/react'\nimport {mocked} from 'jest-mock'\nimport userEvent from '@testing-library/user-event'\n\nimport configureStore from 'redux-mock-store'\n\nimport {IUser} from '../../user'\n\nimport {wrapIntl} from '../../testUtils'\n\nimport client from '../../octoClient'\n\nimport {versionProperty} from '../../store/users'\n\nimport VersionMessage from './versionMessage'\n\njest.mock('../../octoClient')\nconst mockedOctoClient = mocked(client, true)\n\ndescribe('components/messages/VersionMessage', () => {\n    beforeEach(() => {\n        jest.clearAllMocks()\n    })\n\n    const mockStore = configureStore([])\n\n    if (versionProperty) {\n        test('single user mode, no display', () => {\n            const me: IUser = {\n                id: 'single-user',\n                username: 'username_1',\n                email: '',\n                nickname: '',\n                firstname: '',\n                lastname: '',\n                props: {},\n                create_at: 0,\n                update_at: 0,\n                is_bot: false,\n                is_guest: false,\n                roles: 'system_user',\n            }\n            const state = {\n                users: {\n                    me,\n                },\n            }\n\n            const store = mockStore(state)\n\n            const component = wrapIntl(\n                <ReduxProvider store={store}>\n                    <VersionMessage/>\n                </ReduxProvider>,\n            )\n            const {container} = render(component)\n            expect(container.firstChild).toBeNull()\n        })\n\n        test('property set, no message', () => {\n            const me: IUser = {\n                id: 'user-id-1',\n                username: 'username_1',\n                email: '',\n                nickname: '',\n                firstname: '',\n                lastname: '',\n                props: {},\n                create_at: 0,\n                update_at: 0,\n                is_bot: false,\n                is_guest: false,\n                roles: 'system_user',\n            }\n            const state = {\n                users: {\n                    me,\n                    myConfig: {\n                        [versionProperty]: {value: 'true'},\n                    },\n                },\n            }\n            const store = mockStore(state)\n\n            const component = wrapIntl(\n                <ReduxProvider store={store}>\n                    <VersionMessage/>\n                </ReduxProvider>,\n            )\n\n            const {container} = render(component)\n            expect(container.firstChild).toBeNull()\n        })\n\n        test('show message, click close', () => {\n            const me: IUser = {\n                id: 'user-id-1',\n                username: 'username_1',\n                email: '',\n                nickname: '',\n                firstname: '',\n                lastname: '',\n                props: {},\n                create_at: 0,\n                update_at: 0,\n                is_bot: false,\n                is_guest: false,\n                roles: 'system_user',\n            }\n            const state = {\n                users: {\n                    me,\n                },\n            }\n            const store = mockStore(state)\n\n            const component = wrapIntl(\n                <ReduxProvider store={store}>\n                    <VersionMessage/>\n                </ReduxProvider>,\n            )\n\n            render(component)\n            const buttonElement = screen.getByRole('button', {name: 'Close dialog'})\n            userEvent.click(buttonElement)\n            expect(mockedOctoClient.patchUserConfig).toBeCalledWith('user-id-1', {\n                updatedFields: {\n                    [versionProperty]: 'true',\n                },\n            })\n        })\n\n        test('no me, no message', () => {\n            const state = {\n                users: {},\n            }\n            const store = mockStore(state)\n            const component = wrapIntl(\n                <ReduxProvider store={store}>\n                    <VersionMessage/>\n                </ReduxProvider>,\n            )\n\n            const {container} = render(component)\n            expect(container.firstChild).toBeNull()\n        })\n    } else {\n        test('no version, does not display', () => {\n            const me: IUser = {\n                id: 'user-id-1',\n                username: 'username_1',\n                email: '',\n                nickname: '',\n                firstname: '',\n                lastname: '',\n                props: {\n                },\n                create_at: 0,\n                update_at: 0,\n                is_bot: false,\n                is_guest: false,\n                roles: 'system_user',\n            }\n            const state = {\n                users: {\n                    me,\n                },\n            }\n            const store = mockStore(state)\n\n            const component = wrapIntl(\n                <ReduxProvider store={store}>\n                    <VersionMessage/>\n                </ReduxProvider>,\n            )\n            const {container} = render(component)\n            expect(container.firstChild).toBeNull()\n        })\n    }\n})\n"
  },
  {
    "path": "webapp/src/components/messages/versionMessage.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\n\nimport {useIntl, FormattedMessage} from 'react-intl'\n\nimport IconButton from '../../widgets/buttons/iconButton'\nimport Button from '../../widgets/buttons/button'\n\nimport CloseIcon from '../../widgets/icons/close'\n\nimport {useAppSelector, useAppDispatch} from '../../store/hooks'\nimport octoClient from '../../octoClient'\nimport {IUser, UserConfigPatch} from '../../user'\nimport {getMe, patchProps, getVersionMessageCanceled, versionProperty} from '../../store/users'\n\nimport CompassIcon from '../../widgets/icons/compassIcon'\nimport TelemetryClient, {TelemetryCategory, TelemetryActions} from '../../telemetry/telemetryClient'\n\nimport './versionMessage.scss'\nconst helpURL = 'https://mattermost.com/pl/whats-new-boards/'\n\nconst VersionMessage = React.memo(() => {\n    const intl = useIntl()\n    const dispatch = useAppDispatch()\n    const me = useAppSelector<IUser|null>(getMe)\n    const versionMessageCanceled = useAppSelector(getVersionMessageCanceled)\n\n    if (!me || me.id === 'single-user' || versionMessageCanceled) {\n        return null\n    }\n\n    const closeDialogText = intl.formatMessage({\n        id: 'Dialog.closeDialog',\n        defaultMessage: 'Close dialog',\n    })\n\n    const onClose = async () => {\n        if (me) {\n            const patch: UserConfigPatch = {\n                updatedFields: {\n                    [versionProperty]: 'true',\n                },\n            }\n            const patchedProps = await octoClient.patchUserConfig(me.id, patch)\n            if (patchedProps) {\n                dispatch(patchProps(patchedProps))\n            }\n        }\n    }\n\n    return (\n        <div className='VersionMessage'>\n            <div className='banner'>\n                <CompassIcon\n                    icon='information-outline'\n                    className='CompassIcon'\n                />\n                <FormattedMessage\n                    id='VersionMessage.help'\n                    defaultMessage=\"Check out what's new in this version.\"\n                />\n\n                <Button\n                    title='Learn more'\n                    size='xsmall'\n                    emphasis='primary'\n                    onClick={() => {\n                        TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.VersionMoreInfo)\n                        window.open(helpURL)\n                    }}\n                >\n                    <FormattedMessage\n                        id='VersionMessage.learn-more'\n                        defaultMessage='Learn more'\n                    />\n                </Button>\n\n            </div>\n\n            <IconButton\n                className='margin-right'\n                onClick={onClose}\n                icon={<CloseIcon/>}\n                title={closeDialogText}\n                size='small'\n            />\n        </div>\n    )\n})\nexport default VersionMessage\n"
  },
  {
    "path": "webapp/src/components/modal.scss",
    "content": "@import '../styles/z-index';\n\n.Modal {\n    @include z-index(modal);\n    position: absolute;\n    top: 25px;\n    left: -240px;\n    background-color: rgb(var(--center-channel-bg-rgb));\n    padding: 10px;\n    border-radius: 4px;\n    border: 1px solid rgba(var(--center-channel-color-rgb), 0.16);\n    box-shadow: var(--elevation-4);\n\n    @media screen and (max-width: 430px) {\n        position: fixed;\n        top: 0;\n        left: 0;\n        right: 0;\n        bottom: 0;\n        min-width: 0;\n    }\n\n    @media not screen and (max-width: 430px) {\n        &.top {\n            top: auto;\n            bottom: 25px;\n            left: 25px;\n        }\n\n        &.bottom-right {\n            left: 0;\n        }\n    }\n\n    .hideOnWidescreen {\n        /* Hide controls (e.g. close button) on larger screens */\n        @media not screen and (max-width: 430px) {\n            display: none !important;\n        }\n    }\n\n    > .toolbar {\n        display: flex;\n        flex-direction: row;\n        height: 30px;\n        margin-bottom: 10px;\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/modal.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {render, screen} from '@testing-library/react'\nimport userEvent from '@testing-library/user-event'\nimport React from 'react'\n\nimport {mockDOM, wrapDNDIntl} from '../testUtils'\n\nimport Modal from './modal'\n\ndescribe('components/modal', () => {\n    beforeAll(mockDOM)\n    beforeEach(jest.clearAllMocks)\n    test('should match snapshot', () => {\n        const {container} = render(wrapDNDIntl(\n            <Modal\n                onClose={jest.fn()}\n            >\n                <div id='test'/>\n            </Modal>,\n        ))\n        expect(container).toMatchSnapshot()\n    })\n    test('return Modal and close', () => {\n        const onMockedClose = jest.fn()\n        render(wrapDNDIntl(\n            <Modal\n                onClose={onMockedClose}\n            >\n                <div id='test'/>\n            </Modal>,\n        ))\n        const buttonClose = screen.getByRole('button', {name: 'Close'})\n        userEvent.click(buttonClose)\n        expect(onMockedClose).toBeCalledTimes(1)\n    })\n    test('return Modal on position top', () => {\n        const {container} = render(wrapDNDIntl(\n            <Modal\n                position={'top'}\n                onClose={jest.fn()}\n            >\n                <div id='test'/>\n            </Modal>,\n        ))\n        expect(container).toMatchSnapshot()\n    })\n\n    test('return Modal on position bottom', () => {\n        const {container} = render(wrapDNDIntl(\n            <Modal\n                position={'bottom'}\n                onClose={jest.fn()}\n            >\n                <div id='test'/>\n            </Modal>,\n        ))\n        expect(container).toMatchSnapshot()\n    })\n\n    test('return Modal on position bottom-right', () => {\n        const {container} = render(wrapDNDIntl(\n            <Modal\n                position={'bottom-right'}\n                onClose={jest.fn()}\n            >\n                <div id='test'/>\n            </Modal>,\n        ))\n        expect(container).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/modal.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useRef, useEffect, useCallback} from 'react'\n\nimport IconButton from '../widgets/buttons/iconButton'\nimport CloseIcon from '../widgets/icons/close'\nimport './modal.scss'\n\ntype Props = {\n    onClose: () => void\n    position?: 'top'|'bottom'|'bottom-right'\n    children: React.ReactNode\n}\n\nconst Modal = (props: Props): JSX.Element => {\n    const node = useRef<HTMLDivElement>(null)\n\n    const {position, onClose, children} = props\n\n    const closeOnBlur = useCallback((e: Event) => {\n        if (e.target && node.current?.contains(e.target as Node)) {\n            return\n        }\n        onClose()\n    }, [onClose])\n\n    useEffect(() => {\n        document.addEventListener('click', closeOnBlur, true)\n        return () => {\n            document.removeEventListener('click', closeOnBlur, true)\n        }\n    }, [closeOnBlur])\n\n    return (\n        <div\n            className={'Modal ' + (position || 'bottom')}\n            ref={node}\n        >\n            <div className='toolbar hideOnWidescreen'>\n                <IconButton\n                    onClick={() => onClose()}\n                    icon={<CloseIcon/>}\n                    title={'Close'}\n                />\n            </div>\n            {children}\n        </div>\n    )\n}\n\nexport default React.memo(Modal)\n"
  },
  {
    "path": "webapp/src/components/modalWrapper.scss",
    "content": ".ModalWrapper {\n    position: relative;\n    overflow: unset;\n}\n"
  },
  {
    "path": "webapp/src/components/modalWrapper.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\nimport './modalWrapper.scss'\n\ntype Props = {\n    children: React.ReactNode\n}\n\nconst ModalWrapper = (props: Props) => {\n    return (\n        <div className='ModalWrapper'>\n            {props.children}\n        </div>\n    )\n}\n\nexport default React.memo(ModalWrapper)\n"
  },
  {
    "path": "webapp/src/components/newVersionBanner.scss",
    "content": ".NewVersionBanner {\n    background-color: var(--prop-blue);\n    text-align: center;\n    padding: 10px;\n}\n"
  },
  {
    "path": "webapp/src/components/newVersionBanner.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useState, useEffect} from 'react'\nimport {FormattedMessage} from 'react-intl'\n\nimport wsClient from '../wsclient'\n\nimport './newVersionBanner.scss'\n\nconst NewVersionBanner = () => {\n    const [appVersionChanged, setAppVersionChanged] = useState(false)\n    useEffect(() => {\n        wsClient.onAppVersionChangeHandler = setAppVersionChanged\n    }, [])\n\n    if (!appVersionChanged) {\n        return null\n    }\n\n    const newVersionReload = (e: any) => {\n        e.preventDefault()\n        location.reload()\n    }\n\n    return (\n        <div className='NewVersionBanner'>\n            <a\n                target='_blank'\n                rel='noreferrer'\n                onClick={newVersionReload}\n            >\n                <FormattedMessage\n                    id='BoardPage.newVersion'\n                    defaultMessage='A new version of Boards is available, click here to reload.'\n                />\n            </a>\n        </div>\n    )\n}\n\nexport default NewVersionBanner\n"
  },
  {
    "path": "webapp/src/components/onboardingTour/addComments/__snapshots__/addComments.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/onboardingTour/addComments/AddCommentTourStep after hover 1`] = `\n<div\n  class=\"tippy-box tutorial-tour-tip__box AddCommentTourStep\"\n  data-animation=\"scale-subtle\"\n  data-escaped=\"\"\n  data-placement=\"right-end\"\n  data-reference-hidden=\"\"\n  data-state=\"hidden\"\n  role=\"tooltip\"\n  style=\"max-width: 320px; transition-duration: 0ms;\"\n  tabindex=\"-1\"\n>\n  <div\n    class=\"tippy-content\"\n    data-state=\"hidden\"\n    style=\"transition-duration: 0ms;\"\n  >\n    <div>\n      <div>\n        <div\n          class=\"tutorial-tour-tip__header\"\n        >\n          <h4\n            class=\"tutorial-tour-tip__header__title\"\n          >\n            Add comments\n          </h4>\n          <button\n            class=\"IconButton tutorial-tour-tip__header__close size--small\"\n            type=\"button\"\n          >\n            <i\n              class=\"CompassIcon icon-close CloseIcon\"\n            />\n          </button>\n        </div>\n        <div\n          class=\"tutorial-tour-tip__body\"\n        >\n          You can comment on issues, and even @mention your fellow Mattermost users to get their attention.\n        </div>\n        <div\n          class=\"tutorial-tour-tip__image\"\n        >\n          <img\n            alt=\"tutorial tour tip product image\"\n            src=\"http://localhost/test-file-stub\"\n          />\n        </div>\n        <div\n          class=\"tutorial-tour-tip__footer\"\n        >\n          <div\n            class=\"tutorial-tour-tip__footer-buttons\"\n          >\n            <div\n              class=\"tutorial-tour-tip__circles-ctr\"\n            >\n              <div\n                class=\"tutorial-tour-tip__circular-ring\"\n              >\n                <a\n                  class=\"tutorial-tour-tip__circle\"\n                  data-screen=\"0\"\n                  href=\"#\"\n                />\n              </div>\n              <div\n                class=\"tutorial-tour-tip__circular-ring tutorial-tour-tip__circular-ring-active\"\n              >\n                <a\n                  class=\"tutorial-tour-tip__circle active\"\n                  data-screen=\"1\"\n                  href=\"#\"\n                />\n              </div>\n              <div\n                class=\"tutorial-tour-tip__circular-ring\"\n              >\n                <a\n                  class=\"tutorial-tour-tip__circle\"\n                  data-screen=\"2\"\n                  href=\"#\"\n                />\n              </div>\n            </div>\n            <div\n              class=\"tutorial-tour-tip__btn-ctr\"\n            >\n              <button\n                class=\"Button emphasis--tertiary size--small\"\n                title=\"Previous\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-chevron-left icon\"\n                />\n                <span>\n                  Previous\n                </span>\n              </button>\n              <button\n                class=\"Button filled size--small tipNextButton\"\n                type=\"button\"\n              >\n                <span>\n                  Next\n                </span>\n                <i\n                  class=\"CompassIcon icon-chevron-right icon\"\n                />\n              </button>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n  <div\n    class=\"tippy-arrow\"\n    style=\"position: absolute; top: 0px; transform: translate(0px, 3px);\"\n  />\n</div>\n`;\n\nexports[`components/onboardingTour/addComments/AddCommentTourStep before hover 1`] = `\n<div>\n  <div\n    aria-expanded=\"true\"\n    class=\"tutorial-tour-tip__pulsating-dot-ctr AddCommentTourStep\"\n  >\n    <span\n      class=\"pulsating_dot\"\n    />\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/onboardingTour/addComments/addComments.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport {render} from '@testing-library/react'\n\nimport configureStore from 'redux-mock-store'\n\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport {wrapIntl} from '../../../testUtils'\n\nimport AddCommentTourStep from './addComments'\n\ndescribe('components/onboardingTour/addComments/AddCommentTourStep', () => {\n    const mockStore = configureStore([])\n    const state = {\n        users: {\n            me: {\n                id: 'user_id_1',\n            },\n            myConfig: {\n                onboardingTourStarted: {value: true},\n                tourCategory: {value: 'card'},\n                onboardingTourStep: {value: '1'},\n            },\n        },\n        boards: {\n            boards: {\n                board_id_1: {title: 'Welcome to Boards!'},\n            },\n            current: 'board_id_1',\n        },\n        cards: {\n            cards: {\n                card_id_1: {title: 'Create a new card'},\n            },\n            current: 'card_id_1',\n        },\n        clientConfig: {\n            value: {},\n        },\n    }\n    let store = mockStore(state)\n\n    beforeEach(() => {\n        store = mockStore(state)\n    })\n\n    test('before hover', () => {\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <AddCommentTourStep/>\n            </ReduxProvider>,\n        )\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('after hover', () => {\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <AddCommentTourStep/>\n            </ReduxProvider>,\n        )\n        render(component)\n        const elements = document.querySelectorAll('.AddCommentTourStep')\n        expect(elements.length).toBe(2)\n        expect(elements[1]).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/onboardingTour/addComments/addComments.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\n\nimport {FormattedMessage} from 'react-intl'\n\nimport {useMeasurePunchouts} from '../../tutorial_tour_tip/hooks'\n\nimport './add_comments.scss'\nimport {Utils} from '../../../utils'\nimport addComment from '../../../../static/comment.gif'\n\nimport {CardTourSteps, TOUR_CARD} from '../index'\nimport TourTipRenderer from '../tourTipRenderer/tourTipRenderer'\n\nconst AddCommentTourStep = (): JSX.Element | null => {\n    const title = (\n        <FormattedMessage\n            id='OnboardingTour.AddComments.Title'\n            defaultMessage='Add comments'\n        />\n    )\n    const screen = (\n        <FormattedMessage\n            id='OnboardingTour.AddComments.Body'\n            defaultMessage='You can comment on issues, and even @mention your fellow Mattermost users to get their attention.'\n        />\n    )\n\n    const punchout = useMeasurePunchouts(['.CommentsList__new'], [])\n\n    return (\n        <TourTipRenderer\n            key='AddCommentTourStep'\n            requireCard={true}\n            category={TOUR_CARD}\n            step={CardTourSteps.ADD_COMMENTS}\n            screen={screen}\n            title={title}\n            punchout={punchout}\n            classname='AddCommentTourStep'\n            telemetryTag='tourPoint2b'\n            placement={'right-end'}\n            imageURL={Utils.buildURL(addComment, true)}\n            hideBackdrop={true}\n        />\n    )\n}\n\nexport default AddCommentTourStep\n"
  },
  {
    "path": "webapp/src/components/onboardingTour/addComments/add_comments.scss",
    "content": ".AddCommentTourStep {\n    &.tutorial-tour-tip__pulsating-dot-ctr {\n        left: 170px;\n        top: 18px;\n    }\n\n    .tippy-arrow {\n        top: -21px !important;\n    }\n\n    &.tippy-box.tutorial-tour-tip__box {\n        top: 24px;\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/onboardingTour/addDescription/__snapshots__/addDescription.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/onboardingTour/addComments/AddDescriptionTourStep after hover 1`] = `\n<div\n  class=\"tippy-box tutorial-tour-tip__box AddDescriptionTourStep\"\n  data-animation=\"scale-subtle\"\n  data-escaped=\"\"\n  data-placement=\"top-start\"\n  data-reference-hidden=\"\"\n  data-state=\"hidden\"\n  role=\"tooltip\"\n  style=\"max-width: 320px; transition-duration: 0ms;\"\n  tabindex=\"-1\"\n>\n  <div\n    class=\"tippy-content\"\n    data-state=\"hidden\"\n    style=\"transition-duration: 0ms;\"\n  >\n    <div>\n      <div>\n        <div\n          class=\"tutorial-tour-tip__header\"\n        >\n          <h4\n            class=\"tutorial-tour-tip__header__title\"\n          >\n            Add description\n          </h4>\n          <button\n            class=\"IconButton tutorial-tour-tip__header__close size--small\"\n            type=\"button\"\n          >\n            <i\n              class=\"CompassIcon icon-close CloseIcon\"\n            />\n          </button>\n        </div>\n        <div\n          class=\"tutorial-tour-tip__body\"\n        >\n          Add a description to your card so your teammates know what the card is about.\n        </div>\n        <div\n          class=\"tutorial-tour-tip__image\"\n        >\n          <img\n            alt=\"tutorial tour tip product image\"\n            src=\"http://localhost/test-file-stub\"\n          />\n        </div>\n        <div\n          class=\"tutorial-tour-tip__footer\"\n        >\n          <div\n            class=\"tutorial-tour-tip__footer-buttons\"\n          >\n            <div\n              class=\"tutorial-tour-tip__circles-ctr\"\n            >\n              <div\n                class=\"tutorial-tour-tip__circular-ring\"\n              >\n                <a\n                  class=\"tutorial-tour-tip__circle\"\n                  data-screen=\"0\"\n                  href=\"#\"\n                />\n              </div>\n              <div\n                class=\"tutorial-tour-tip__circular-ring\"\n              >\n                <a\n                  class=\"tutorial-tour-tip__circle\"\n                  data-screen=\"1\"\n                  href=\"#\"\n                />\n              </div>\n              <div\n                class=\"tutorial-tour-tip__circular-ring tutorial-tour-tip__circular-ring-active\"\n              >\n                <a\n                  class=\"tutorial-tour-tip__circle active\"\n                  data-screen=\"2\"\n                  href=\"#\"\n                />\n              </div>\n            </div>\n            <div\n              class=\"tutorial-tour-tip__btn-ctr\"\n            >\n              <button\n                class=\"Button emphasis--tertiary size--small\"\n                title=\"Previous\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-chevron-left icon\"\n                />\n                <span>\n                  Previous\n                </span>\n              </button>\n              <button\n                class=\"Button filled size--small tipNextButton\"\n                type=\"button\"\n              >\n                <span>\n                  Done\n                </span>\n                \n              </button>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n  <div\n    class=\"tippy-arrow\"\n    style=\"position: absolute; left: 0px; transform: translate(3px, 0px);\"\n  />\n</div>\n`;\n\nexports[`components/onboardingTour/addComments/AddDescriptionTourStep before hover 1`] = `\n<div>\n  <div\n    aria-expanded=\"true\"\n    class=\"tutorial-tour-tip__pulsating-dot-ctr AddDescriptionTourStep\"\n  >\n    <span\n      class=\"pulsating_dot\"\n    />\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/onboardingTour/addDescription/addDescription.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport {render} from '@testing-library/react'\n\nimport configureStore from 'redux-mock-store'\n\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport {wrapIntl} from '../../../testUtils'\n\nimport AddDescriptionTourStep from './add_description'\n\ndescribe('components/onboardingTour/addComments/AddDescriptionTourStep', () => {\n    const mockStore = configureStore([])\n    const state = {\n        users: {\n            me: {\n                id: 'user_id_1',\n            },\n            myConfig: {\n                onboardingTourStarted: {value: true},\n                tourCategory: {value: 'card'},\n                onboardingTourStep: {value: '2'},\n            },\n        },\n        boards: {\n            boards: {\n                board_id_1: {title: 'Welcome to Boards!'},\n            },\n            current: 'board_id_1',\n        },\n        cards: {\n            cards: {\n                card_id_1: {title: 'Create a new card'},\n            },\n            current: 'card_id_1',\n        },\n        clientConfig: {\n            value: {},\n        },\n    }\n    let store = mockStore(state)\n\n    beforeEach(() => {\n        store = mockStore(state)\n    })\n\n    test('before hover', () => {\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <AddDescriptionTourStep/>\n            </ReduxProvider>,\n        )\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('after hover', () => {\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <AddDescriptionTourStep/>\n            </ReduxProvider>,\n        )\n        render(component)\n        const elements = document.querySelectorAll('.AddDescriptionTourStep')\n        expect(elements.length).toBe(2)\n        expect(elements[1]).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/onboardingTour/addDescription/add_description.scss",
    "content": ".AddDescriptionTourStep {\n    &.tutorial-tour-tip__pulsating-dot-ctr {\n        top: -10px;\n        left: calc(120px + 20%);\n    }\n\n    .tippy-arrow {\n        left: 21px !important;\n    }\n\n    &.tippy-box.tutorial-tour-tip__box {\n        left: -21px;\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/onboardingTour/addDescription/add_description.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\n\nimport {FormattedMessage} from 'react-intl'\n\nimport {useMeasurePunchouts} from '../../tutorial_tour_tip/hooks'\n\nimport './add_description.scss'\nimport {Utils} from '../../../utils'\nimport addDescription from '../../../../static/addDescription.png'\n\nimport {CardTourSteps, TOUR_CARD} from '../index'\nimport TourTipRenderer from '../tourTipRenderer/tourTipRenderer'\n\nconst AddDescriptionTourStep = (): JSX.Element | null => {\n    const title = (\n        <FormattedMessage\n            id='OnboardingTour.AddDescription.Title'\n            defaultMessage='Add description'\n        />\n    )\n    const screen = (\n        <FormattedMessage\n            id='OnboardingTour.AddDescription.Body'\n            defaultMessage='Add a description to your card so your teammates know what the card is about.'\n        />\n    )\n\n    const punchout = useMeasurePunchouts(['.octo-content div:nth-child(1)'], [])\n\n    return (\n        <TourTipRenderer\n            key='AddDescriptionTourStep'\n            requireCard={true}\n            category={TOUR_CARD}\n            step={CardTourSteps.ADD_DESCRIPTION}\n            screen={screen}\n            title={title}\n            punchout={punchout}\n            classname='AddDescriptionTourStep'\n            telemetryTag='tourPoint2c'\n            placement={'top-start'}\n            imageURL={Utils.buildURL(addDescription, true)}\n            hideBackdrop={true}\n        />\n    )\n}\n\nexport default AddDescriptionTourStep\n"
  },
  {
    "path": "webapp/src/components/onboardingTour/addProperties/__snapshots__/addProperties.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/onboardingTour/addComments/AddPropertiesTourStep after hover 1`] = `\n<div\n  class=\"tippy-box tutorial-tour-tip__box AddPropertiesTourStep\"\n  data-animation=\"scale-subtle\"\n  data-escaped=\"\"\n  data-placement=\"right-end\"\n  data-reference-hidden=\"\"\n  data-state=\"hidden\"\n  role=\"tooltip\"\n  style=\"max-width: 320px; transition-duration: 0ms;\"\n  tabindex=\"-1\"\n>\n  <div\n    class=\"tippy-content\"\n    data-state=\"hidden\"\n    style=\"transition-duration: 0ms;\"\n  >\n    <div>\n      <div>\n        <div\n          class=\"tutorial-tour-tip__header\"\n        >\n          <h4\n            class=\"tutorial-tour-tip__header__title\"\n          >\n            Add properties\n          </h4>\n          <button\n            class=\"IconButton tutorial-tour-tip__header__close size--small\"\n            type=\"button\"\n          >\n            <i\n              class=\"CompassIcon icon-close CloseIcon\"\n            />\n          </button>\n        </div>\n        <div\n          class=\"tutorial-tour-tip__body\"\n        >\n          Add various properties to cards to make them more powerful!\n        </div>\n        <div\n          class=\"tutorial-tour-tip__image\"\n        >\n          <img\n            alt=\"tutorial tour tip product image\"\n            src=\"http://localhost/test-file-stub\"\n          />\n        </div>\n        <div\n          class=\"tutorial-tour-tip__footer\"\n        >\n          <div\n            class=\"tutorial-tour-tip__footer-buttons\"\n          >\n            <div\n              class=\"tutorial-tour-tip__circles-ctr\"\n            >\n              <div\n                class=\"tutorial-tour-tip__circular-ring tutorial-tour-tip__circular-ring-active\"\n              >\n                <a\n                  class=\"tutorial-tour-tip__circle active\"\n                  data-screen=\"0\"\n                  href=\"#\"\n                />\n              </div>\n              <div\n                class=\"tutorial-tour-tip__circular-ring\"\n              >\n                <a\n                  class=\"tutorial-tour-tip__circle\"\n                  data-screen=\"1\"\n                  href=\"#\"\n                />\n              </div>\n              <div\n                class=\"tutorial-tour-tip__circular-ring\"\n              >\n                <a\n                  class=\"tutorial-tour-tip__circle\"\n                  data-screen=\"2\"\n                  href=\"#\"\n                />\n              </div>\n            </div>\n            <div\n              class=\"tutorial-tour-tip__btn-ctr\"\n            >\n              <button\n                class=\"Button filled size--small tipNextButton\"\n                type=\"button\"\n              >\n                <span>\n                  Next\n                </span>\n                <i\n                  class=\"CompassIcon icon-chevron-right icon\"\n                />\n              </button>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n  <div\n    class=\"tippy-arrow\"\n    style=\"position: absolute; top: 0px; transform: translate(0px, 3px);\"\n  />\n</div>\n`;\n\nexports[`components/onboardingTour/addComments/AddPropertiesTourStep before hover 1`] = `\n<div>\n  <div\n    aria-expanded=\"true\"\n    class=\"tutorial-tour-tip__pulsating-dot-ctr AddPropertiesTourStep\"\n  >\n    <span\n      class=\"pulsating_dot\"\n    />\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/onboardingTour/addProperties/addProperties.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport {render} from '@testing-library/react'\n\nimport configureStore from 'redux-mock-store'\n\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport {wrapIntl} from '../../../testUtils'\n\nimport AddPropertiesTourStep from './add_properties'\n\ndescribe('components/onboardingTour/addComments/AddPropertiesTourStep', () => {\n    const mockStore = configureStore([])\n    const state = {\n        users: {\n            me: {\n                id: 'user_id_1',\n            },\n            myConfig: {\n                onboardingTourStarted: {value: true},\n                tourCategory: {value: 'card'},\n                onboardingTourStep: {value: '0'},\n            },\n        },\n        boards: {\n            boards: {\n                board_id_1: {title: 'Welcome to Boards!'},\n            },\n            current: 'board_id_1',\n        },\n        cards: {\n            cards: {\n                card_id_1: {title: 'Create a new card'},\n            },\n            current: 'card_id_1',\n        },\n        clientConfig: {\n            value: {},\n        },\n    }\n    let store = mockStore(state)\n\n    beforeEach(() => {\n        store = mockStore(state)\n    })\n\n    test('before hover', () => {\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <AddPropertiesTourStep/>\n            </ReduxProvider>,\n        )\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('after hover', () => {\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <AddPropertiesTourStep/>\n            </ReduxProvider>,\n        )\n        render(component)\n        const elements = document.querySelectorAll('.AddPropertiesTourStep')\n        expect(elements.length).toBe(2)\n        expect(elements[1]).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/onboardingTour/addProperties/add_properties.scss",
    "content": ".AddPropertiesTourStep {\n    &.tutorial-tour-tip__pulsating-dot-ctr {\n        left: 100%;\n        top: calc(50% - 6px);\n    }\n\n    .tippy-arrow {\n        top: -21px !important;\n    }\n\n    &.tippy-box.tutorial-tour-tip__box {\n        top: 24px;\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/onboardingTour/addProperties/add_properties.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useEffect} from 'react'\n\nimport {FormattedMessage} from 'react-intl'\n\nimport {useMeasurePunchouts} from '../../tutorial_tour_tip/hooks'\n\nimport './add_properties.scss'\nimport {Utils} from '../../../utils'\nimport addProperty from '../../../../static/addProperty.gif'\n\nimport {BaseTourSteps, CardTourSteps, TOUR_BASE, TOUR_CARD} from '../index'\nimport TourTipRenderer from '../tourTipRenderer/tourTipRenderer'\nimport {OnboardingBoardTitle, OnboardingCardTitle} from '../../cardDetail/cardDetail'\nimport {useAppDispatch, useAppSelector} from '../../../store/hooks'\nimport {\n    getMe,\n    getOnboardingTourCategory,\n    getOnboardingTourStarted,\n    getOnboardingTourStep,\n    patchProps,\n} from '../../../store/users'\nimport {IUser, UserConfigPatch} from '../../../user'\nimport mutator from '../../../mutator'\nimport {getCurrentBoard} from '../../../store/boards'\nimport {getCurrentCard} from '../../../store/cards'\n\nconst AddPropertiesTourStep = (): JSX.Element | null => {\n    const title = (\n        <FormattedMessage\n            id='OnboardingTour.AddProperties.Title'\n            defaultMessage='Add properties'\n        />\n    )\n    const screen = (\n        <FormattedMessage\n            id='OnboardingTour.AddProperties.Body'\n            defaultMessage='Add various properties to cards to make them more powerful!'\n        />\n    )\n\n    const punchout = useMeasurePunchouts(['.octo-propertyname.add-property'], [])\n\n    const me = useAppSelector<IUser|null>(getMe)\n    const dispatch = useAppDispatch()\n\n    const board = useAppSelector(getCurrentBoard)\n    const isOnboardingBoard = board ? board.title === OnboardingBoardTitle : false\n\n    const card = useAppSelector(getCurrentCard)\n    const isOnboardingCard = card ? card.title === OnboardingCardTitle : false\n\n    const onboardingTourStarted = useAppSelector(getOnboardingTourStarted)\n    const onboardingTourCategory = useAppSelector(getOnboardingTourCategory)\n    const onboardingTourStep = useAppSelector(getOnboardingTourStep)\n\n    // start the card tour if onboarding card is opened up\n    // and the user is still on the base tour\n    useEffect(() => {\n        async function task() {\n            if (!me || !card) {\n                return\n            }\n\n            const should = card.id &&\n                isOnboardingBoard &&\n                isOnboardingCard &&\n                onboardingTourStarted &&\n                onboardingTourCategory === TOUR_BASE &&\n                onboardingTourStep === BaseTourSteps.OPEN_A_CARD.toString()\n\n            if (!should) {\n                return\n            }\n\n            const patch: UserConfigPatch = {}\n            patch.updatedFields = {}\n            patch.updatedFields.tourCategory = TOUR_CARD\n            patch.updatedFields.onboardingTourStep = CardTourSteps.ADD_PROPERTIES.toString()\n\n            const updatedProps = await mutator.patchUserConfig(me.id, patch)\n            if (updatedProps) {\n                dispatch(patchProps(updatedProps))\n            }\n        }\n\n        // this hack is needed to allow performing async task in useEffect\n        task()\n    }, [card])\n\n    return (\n        <TourTipRenderer\n            key='AddPropertiesTourStep'\n            requireCard={true}\n            category={TOUR_CARD}\n            step={CardTourSteps.ADD_PROPERTIES}\n            screen={screen}\n            title={title}\n            punchout={punchout}\n            classname='AddPropertiesTourStep'\n            telemetryTag='tourPoint2a'\n            placement={'right-end'}\n            imageURL={Utils.buildURL(addProperty, true)}\n            hideBackdrop={true}\n        />\n    )\n}\n\nexport default AddPropertiesTourStep\n"
  },
  {
    "path": "webapp/src/components/onboardingTour/addView/__snapshots__/addView.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/onboardingTour/addComments/AddViewTourStep after hover 1`] = `\n<div\n  class=\"tippy-box tutorial-tour-tip__box AddViewTourStep\"\n  data-animation=\"scale-subtle\"\n  data-escaped=\"\"\n  data-placement=\"bottom-start\"\n  data-reference-hidden=\"\"\n  data-state=\"hidden\"\n  role=\"tooltip\"\n  style=\"max-width: 320px; transition-duration: 0ms;\"\n  tabindex=\"-1\"\n>\n  <div\n    class=\"tippy-content\"\n    data-state=\"hidden\"\n    style=\"transition-duration: 0ms;\"\n  >\n    <div>\n      <div>\n        <div\n          class=\"tutorial-tour-tip__header\"\n        >\n          <h4\n            class=\"tutorial-tour-tip__header__title\"\n          >\n            Add a new view\n          </h4>\n          <button\n            class=\"IconButton tutorial-tour-tip__header__close size--small\"\n            type=\"button\"\n          >\n            <i\n              class=\"CompassIcon icon-close CloseIcon\"\n            />\n          </button>\n        </div>\n        <div\n          class=\"tutorial-tour-tip__body\"\n        >\n          Go here to create a new view to organise your board using different layouts.\n        </div>\n        <div\n          class=\"tutorial-tour-tip__image\"\n        >\n          <img\n            alt=\"tutorial tour tip product image\"\n            src=\"http://localhost/test-file-stub\"\n          />\n        </div>\n        <div\n          class=\"tutorial-tour-tip__footer\"\n        >\n          <div\n            class=\"tutorial-tour-tip__footer-buttons\"\n          >\n            <div\n              class=\"tutorial-tour-tip__circles-ctr\"\n            >\n              <div\n                class=\"tutorial-tour-tip__circular-ring tutorial-tour-tip__circular-ring-active\"\n              >\n                <a\n                  class=\"tutorial-tour-tip__circle active\"\n                  data-screen=\"0\"\n                  href=\"#\"\n                />\n              </div>\n              <div\n                class=\"tutorial-tour-tip__circular-ring\"\n              >\n                <a\n                  class=\"tutorial-tour-tip__circle\"\n                  data-screen=\"1\"\n                  href=\"#\"\n                />\n              </div>\n              <div\n                class=\"tutorial-tour-tip__circular-ring\"\n              >\n                <a\n                  class=\"tutorial-tour-tip__circle\"\n                  data-screen=\"2\"\n                  href=\"#\"\n                />\n              </div>\n            </div>\n            <div\n              class=\"tutorial-tour-tip__btn-ctr\"\n            >\n              <button\n                class=\"Button filled size--small tipNextButton\"\n                type=\"button\"\n              >\n                <span>\n                  Next\n                </span>\n                <i\n                  class=\"CompassIcon icon-chevron-right icon\"\n                />\n              </button>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n  <div\n    class=\"tippy-arrow\"\n    style=\"position: absolute; left: 0px; transform: translate(3px, 0px);\"\n  />\n</div>\n`;\n\nexports[`components/onboardingTour/addComments/AddViewTourStep before hover 1`] = `\n<div>\n  <div\n    aria-expanded=\"true\"\n    class=\"tutorial-tour-tip__pulsating-dot-ctr AddViewTourStep\"\n  >\n    <span\n      class=\"pulsating_dot\"\n    />\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/onboardingTour/addView/addView.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport {render} from '@testing-library/react'\n\nimport configureStore from 'redux-mock-store'\n\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport {wrapIntl} from '../../../testUtils'\n\nimport AddViewTourStep from './add_view'\n\ndescribe('components/onboardingTour/addComments/AddViewTourStep', () => {\n    const mockStore = configureStore([])\n    const state = {\n        users: {\n            me: {\n                id: 'user_id_1',\n            },\n            myConfig: {\n                onboardingTourStarted: {value: true},\n                tourCategory: {value: 'board'},\n                onboardingTourStep: {value: '0'},\n            },\n        },\n        boards: {\n            boards: {\n                board_id_1: {title: 'Welcome to Boards!'},\n            },\n            current: 'board_id_1',\n        },\n        clientConfig: {\n            value: {},\n        },\n    }\n    let store = mockStore(state)\n\n    beforeEach(() => {\n        store = mockStore(state)\n    })\n\n    test('before hover', () => {\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <AddViewTourStep/>\n            </ReduxProvider>,\n        )\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('after hover', () => {\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <AddViewTourStep/>\n            </ReduxProvider>,\n        )\n        render(component)\n        const elements = document.querySelectorAll('.AddViewTourStep')\n        expect(elements.length).toBe(2)\n        expect(elements[1]).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/onboardingTour/addView/add_view.scss",
    "content": ".AddViewTourStep {\n    &.tutorial-tour-tip__pulsating-dot-ctr {\n        left: 91%;\n        top: 100%;\n    }\n\n    .tippy-arrow {\n        left: 21px !important;\n    }\n\n    &.tippy-box.tutorial-tour-tip__box {\n        right: 22px;\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/onboardingTour/addView/add_view.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\n\nimport {FormattedMessage} from 'react-intl'\n\nimport {useMeasurePunchouts} from '../../tutorial_tour_tip/hooks'\n\nimport './add_view.scss'\nimport {Utils} from '../../../utils'\nimport changeViews from '../../../../static/changeViews.gif'\n\nimport {BoardTourSteps, TOUR_BOARD} from '../index'\nimport TourTipRenderer from '../tourTipRenderer/tourTipRenderer'\n\nconst AddViewTourStep = (): JSX.Element => {\n    const title = (\n        <FormattedMessage\n            id='OnboardingTour.AddView.Title'\n            defaultMessage='Add a new view'\n        />\n    )\n    const screen = (\n        <FormattedMessage\n            id='OnboardingTour.AddView.Body'\n            defaultMessage='Go here to create a new view to organise your board using different layouts.'\n        />\n    )\n\n    const punchout = useMeasurePunchouts(['.viewSelector'], [])\n\n    return (\n        <TourTipRenderer\n            key='AddViewTourStep'\n            requireCard={false}\n            category={TOUR_BOARD}\n            step={BoardTourSteps.ADD_VIEW}\n            screen={screen}\n            title={title}\n            punchout={punchout}\n            classname='AddViewTourStep'\n            telemetryTag='tourPoint3a'\n            placement={'bottom-start'}\n            imageURL={Utils.buildURL(changeViews, true)}\n            hideBackdrop={false}\n        />\n    )\n}\n\nexport default AddViewTourStep\n"
  },
  {
    "path": "webapp/src/components/onboardingTour/copyLink/__snapshots__/copyLink.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/onboardingTour/addComments/CopyLinkTourStep after hover 1`] = `\n<div\n  class=\"tippy-box tutorial-tour-tip__box CopyLinkTourStep\"\n  data-animation=\"scale-subtle\"\n  data-escaped=\"\"\n  data-placement=\"right-start\"\n  data-reference-hidden=\"\"\n  data-state=\"hidden\"\n  role=\"tooltip\"\n  style=\"max-width: 320px; transition-duration: 0ms;\"\n  tabindex=\"-1\"\n>\n  <div\n    class=\"tippy-content\"\n    data-state=\"hidden\"\n    style=\"transition-duration: 0ms;\"\n  >\n    <div>\n      <div>\n        <div\n          class=\"tutorial-tour-tip__header\"\n        >\n          <h4\n            class=\"tutorial-tour-tip__header__title\"\n          >\n            Copy link\n          </h4>\n          <button\n            class=\"IconButton tutorial-tour-tip__header__close size--small\"\n            type=\"button\"\n          >\n            <i\n              class=\"CompassIcon icon-close CloseIcon\"\n            />\n          </button>\n        </div>\n        <div\n          class=\"tutorial-tour-tip__body\"\n        >\n          You can share your cards with teammates by copying the link and pasting it in a channel, direct message, or group message.\n        </div>\n        <div\n          class=\"tutorial-tour-tip__image\"\n        >\n          <img\n            alt=\"tutorial tour tip product image\"\n            src=\"http://localhost/test-file-stub\"\n          />\n        </div>\n        <div\n          class=\"tutorial-tour-tip__footer\"\n        >\n          <div\n            class=\"tutorial-tour-tip__footer-buttons\"\n          >\n            <div\n              class=\"tutorial-tour-tip__circles-ctr\"\n            >\n              <div\n                class=\"tutorial-tour-tip__circular-ring\"\n              >\n                <a\n                  class=\"tutorial-tour-tip__circle\"\n                  data-screen=\"0\"\n                  href=\"#\"\n                />\n              </div>\n              <div\n                class=\"tutorial-tour-tip__circular-ring tutorial-tour-tip__circular-ring-active\"\n              >\n                <a\n                  class=\"tutorial-tour-tip__circle active\"\n                  data-screen=\"1\"\n                  href=\"#\"\n                />\n              </div>\n              <div\n                class=\"tutorial-tour-tip__circular-ring\"\n              >\n                <a\n                  class=\"tutorial-tour-tip__circle\"\n                  data-screen=\"2\"\n                  href=\"#\"\n                />\n              </div>\n            </div>\n            <div\n              class=\"tutorial-tour-tip__btn-ctr\"\n            >\n              <button\n                class=\"Button emphasis--tertiary size--small\"\n                title=\"Previous\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-chevron-left icon\"\n                />\n                <span>\n                  Previous\n                </span>\n              </button>\n              <button\n                class=\"Button filled size--small tipNextButton\"\n                type=\"button\"\n              >\n                <span>\n                  Next\n                </span>\n                <i\n                  class=\"CompassIcon icon-chevron-right icon\"\n                />\n              </button>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n  <div\n    class=\"tippy-arrow\"\n    style=\"position: absolute; top: 0px; transform: translate(0px, 3px);\"\n  />\n</div>\n`;\n\nexports[`components/onboardingTour/addComments/CopyLinkTourStep before hover 1`] = `\n<div>\n  <div\n    aria-expanded=\"true\"\n    class=\"tutorial-tour-tip__pulsating-dot-ctr CopyLinkTourStep\"\n  >\n    <span\n      class=\"pulsating_dot\"\n    />\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/onboardingTour/copyLink/copyLink.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport {render} from '@testing-library/react'\n\nimport configureStore from 'redux-mock-store'\n\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport {wrapIntl} from '../../../testUtils'\n\nimport CopyLinkTourStep from './copy_link'\n\ndescribe('components/onboardingTour/addComments/CopyLinkTourStep', () => {\n    const mockStore = configureStore([])\n    const state = {\n        users: {\n            me: {\n                id: 'user_id_1',\n            },\n            myConfig: {\n                onboardingTourStarted: {value: true},\n                tourCategory: {value: 'board'},\n                onboardingTourStep: {value: '1'},\n            },\n        },\n        boards: {\n            boards: {\n                board_id_1: {title: 'Welcome to Boards!'},\n            },\n            current: 'board_id_1',\n        },\n        clientConfig: {\n            value: {},\n        },\n    }\n    let store = mockStore(state)\n\n    beforeEach(() => {\n        store = mockStore(state)\n    })\n\n    test('before hover', () => {\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <CopyLinkTourStep/>\n            </ReduxProvider>,\n        )\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('after hover', () => {\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <CopyLinkTourStep/>\n            </ReduxProvider>,\n        )\n        render(component)\n        const elements = document.querySelectorAll('.CopyLinkTourStep')\n        expect(elements.length).toBe(2)\n        expect(elements[1]).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/onboardingTour/copyLink/copy_link.scss",
    "content": ".CopyLinkTourStep {\n    &.tutorial-tour-tip__pulsating-dot-ctr {\n        left: calc(100% - 6px);\n        top: 18px;\n    }\n\n    .tippy-arrow {\n        top: 21px !important;\n    }\n\n    &.tippy-box.tutorial-tour-tip__box {\n        top: -24px;\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/onboardingTour/copyLink/copy_link.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\n\nimport {FormattedMessage} from 'react-intl'\n\nimport {useMeasurePunchouts} from '../../tutorial_tour_tip/hooks'\n\nimport './copy_link.scss'\nimport {Utils} from '../../../utils'\nimport copyLink from '../../../../static/copyLink.gif'\n\nimport {BoardTourSteps, TOUR_BOARD} from '../index'\nimport {OnboardingCardClassName} from '../../kanban/kanbanCard'\nimport TourTipRenderer from '../tourTipRenderer/tourTipRenderer'\n\nconst CopyLinkTourStep = (): JSX.Element | null => {\n    const title = (\n        <FormattedMessage\n            id='OnboardingTour.CopyLink.Title'\n            defaultMessage='Copy link'\n        />\n    )\n    const screen = (\n        <FormattedMessage\n            id='OnboardingTour.CopyLink.Body'\n            defaultMessage='You can share your cards with teammates by copying the link and pasting it in a channel, direct message, or group message.'\n        />\n    )\n\n    const punchout = useMeasurePunchouts([`.${OnboardingCardClassName} .optionsMenu`], [])\n\n    return (\n        <TourTipRenderer\n            key='CopyLinkTourStep'\n            requireCard={false}\n            category={TOUR_BOARD}\n            step={BoardTourSteps.COPY_LINK}\n            screen={screen}\n            title={title}\n            punchout={punchout}\n            classname='CopyLinkTourStep'\n            telemetryTag='tourPoint3b'\n            placement={'right-start'}\n            imageURL={Utils.buildURL(copyLink, true)}\n            hideBackdrop={true}\n        />\n    )\n}\n\nexport default CopyLinkTourStep\n"
  },
  {
    "path": "webapp/src/components/onboardingTour/index.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nexport const BaseTourSteps = {\n    OPEN_A_CARD: 0,\n}\n\nexport const CardTourSteps = {\n    ADD_PROPERTIES: 0,\n    ADD_COMMENTS: 1,\n    ADD_DESCRIPTION: 2,\n}\n\nexport const BoardTourSteps: {[key: string]: number} = {\n    ADD_VIEW: 0,\n    COPY_LINK: 1,\n    SHARE_BOARD: 2,\n}\n\nexport const SidebarTourSteps: {[key: string]: number} = {\n    SIDE_BAR: 0,\n    MANAGE_CATEGORIES: 1,\n    SEARCH_FOR_BOARDS: 2,\n}\n\nexport const FINISHED = 999\n\nexport const TOUR_BASE = 'onboarding'\nexport const TOUR_CARD = 'card'\nexport const TOUR_BOARD = 'board'\nexport const TOUR_SIDEBAR = 'sidebar'\n\nexport const TOUR_ORDER = [\n    TOUR_BASE,\n    TOUR_CARD,\n    TOUR_BOARD,\n    TOUR_SIDEBAR,\n]\n\nexport const TourCategoriesMapToSteps: Record<string, Record<string, number>> = {\n    [TOUR_BASE]: BaseTourSteps,\n    [TOUR_CARD]: CardTourSteps,\n    [TOUR_BOARD]: BoardTourSteps,\n    [TOUR_SIDEBAR]: SidebarTourSteps,\n}\n"
  },
  {
    "path": "webapp/src/components/onboardingTour/manageCategories/manageCategories.scss",
    "content": ".ManageCatergoies {\n    &.tutorial-tour-tip__pulsating-dot-ctr {\n        margin-left: 14px;\n        margin-top: 5px;\n    }\n\n    .tutorial-tour-tip__overlay {\n        cursor: pointer;\n    }\n\n    .tutorial-tour-tip__footer {\n        margin-top: 15px;\n    }\n\n}\n"
  },
  {
    "path": "webapp/src/components/onboardingTour/manageCategories/manageCategories.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\n\nimport {right} from '@popperjs/core'\n\nimport {FormattedMessage} from 'react-intl'\n\nimport {SidebarTourSteps, TOUR_SIDEBAR} from '..'\n\nimport {useMeasurePunchouts} from '../../tutorial_tour_tip/hooks'\n\nimport TourTipRenderer from '../tourTipRenderer/tourTipRenderer'\n\nimport {ClassForManageCategoriesTourStep} from '../../../components/sidebar/sidebarCategory'\n\nimport './manageCategories.scss'\n\nconst ManageCategoriesTourStep = (): JSX.Element | null => {\n    const title = (\n        <FormattedMessage\n            id='SidebarTour.ManageCategories.Title'\n            defaultMessage='Manage categories'\n        />\n    )\n\n    const screen = (\n        <FormattedMessage\n            id='SidebarTour.ManageCategories.Body'\n            defaultMessage='Create and manage custom categories. Categories are user-specific, so moving a board to your category won’t impact other members using the same board.'\n        />\n    )\n\n    const punchout = useMeasurePunchouts([`.${ClassForManageCategoriesTourStep}`], [])\n\n    return (\n        <TourTipRenderer\n            key='ManageCatergoriesTourStep'\n            requireCard={false}\n            category={TOUR_SIDEBAR}\n            step={SidebarTourSteps.MANAGE_CATEGORIES}\n            screen={screen}\n            title={title}\n            punchout={punchout}\n            classname='ManageCatergoies'\n            telemetryTag='tourPoint4b'\n            placement={right}\n            hideBackdrop={false}\n            showForce={true}\n        />\n    )\n}\n\nexport default ManageCategoriesTourStep\n"
  },
  {
    "path": "webapp/src/components/onboardingTour/openCard/__snapshots__/openCard.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/onboardingTour/addComments/OpenCardTourStep after hover 1`] = `\n<div\n  class=\"tippy-box tutorial-tour-tip__box OpenCardTourStep\"\n  data-animation=\"scale-subtle\"\n  data-escaped=\"\"\n  data-placement=\"top\"\n  data-reference-hidden=\"\"\n  data-state=\"hidden\"\n  role=\"tooltip\"\n  style=\"max-width: 320px; transition-duration: 0ms;\"\n  tabindex=\"-1\"\n>\n  <div\n    class=\"tippy-content\"\n    data-state=\"hidden\"\n    style=\"transition-duration: 0ms;\"\n  >\n    <div>\n      <div>\n        <div\n          class=\"tutorial-tour-tip__header\"\n        >\n          <h4\n            class=\"tutorial-tour-tip__header__title\"\n          >\n            Open a card\n          </h4>\n          <button\n            class=\"IconButton tutorial-tour-tip__header__close size--small\"\n            type=\"button\"\n          >\n            <i\n              class=\"CompassIcon icon-close CloseIcon\"\n            />\n          </button>\n        </div>\n        <div\n          class=\"tutorial-tour-tip__body\"\n        >\n          Open a card to explore the powerful ways that Boards can help you organize your work.\n        </div>\n        <div\n          class=\"tutorial-tour-tip__footer\"\n        >\n          <div\n            class=\"tutorial-tour-tip__footer-buttons\"\n          >\n            <div\n              class=\"tutorial-tour-tip__circles-ctr\"\n            />\n            <div\n              class=\"tutorial-tour-tip__btn-ctr\"\n            />\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n  <div\n    class=\"tippy-arrow\"\n    style=\"position: absolute; left: 0px; transform: translate(3px, 0px);\"\n  />\n</div>\n`;\n\nexports[`components/onboardingTour/addComments/OpenCardTourStep before hover 1`] = `\n<div>\n  <div\n    aria-expanded=\"true\"\n    class=\"tutorial-tour-tip__pulsating-dot-ctr OpenCardTourStep\"\n  >\n    <span\n      class=\"pulsating_dot\"\n    />\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/onboardingTour/openCard/openCard.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport {render} from '@testing-library/react'\n\nimport configureStore from 'redux-mock-store'\n\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport {wrapIntl} from '../../../testUtils'\n\nimport OpenCardTourStep from './open_card'\n\ndescribe('components/onboardingTour/addComments/OpenCardTourStep', () => {\n    const mockStore = configureStore([])\n    const state = {\n        users: {\n            me: {\n                id: 'user_id_1',\n            },\n            myConfig: {\n                onboardingTourStarted: {value: true},\n                tourCategory: {value: 'onboarding'},\n                onboardingTourStep: {value: '0'},\n            },\n        },\n        boards: {\n            boards: {\n                board_id_1: {title: 'Welcome to Boards!'},\n            },\n            current: 'board_id_1',\n        },\n        cards: {\n            cards: {\n                card_id_1: {title: 'Create a new card'},\n            },\n            current: 'card_id_1',\n        },\n        clientConfig: {\n            value: {},\n        },\n    }\n    let store = mockStore(state)\n\n    beforeEach(() => {\n        store = mockStore(state)\n    })\n\n    test('before hover', () => {\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <OpenCardTourStep/>\n            </ReduxProvider>,\n        )\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('after hover', () => {\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <OpenCardTourStep/>\n            </ReduxProvider>,\n        )\n        render(component)\n        const elements = document.querySelectorAll('.OpenCardTourStep')\n        expect(elements.length).toBe(2)\n        expect(elements[1]).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/onboardingTour/openCard/open_card.scss",
    "content": ".OpenCardTourStep {\n    &.tutorial-tour-tip__pulsating-dot-ctr {\n        left: 80%;\n        top: calc(100% - 6px);\n    }\n\n    .tutorial-tour-tip__overlay {\n        cursor: pointer;\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/onboardingTour/openCard/open_card.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\n\nimport {bottom} from '@popperjs/core'\n\nimport {FormattedMessage} from 'react-intl'\n\nimport {useMeasurePunchouts} from '../../tutorial_tour_tip/hooks'\n\nimport {BaseTourSteps, TOUR_BASE} from '../index'\n\nimport './open_card.scss'\nimport {OnboardingCardClassName} from '../../kanban/kanbanCard'\nimport TourTipRenderer from '../tourTipRenderer/tourTipRenderer'\n\nconst OpenCardTourStep = (): JSX.Element | null => {\n    const title = (\n        <FormattedMessage\n            id='OnboardingTour.OpenACard.Title'\n            defaultMessage='Open a card'\n        />\n    )\n    const screen = (\n        <FormattedMessage\n            id='OnboardingTour.OpenACard.Body'\n            defaultMessage='Open a card to explore the powerful ways that Boards can help you organize your work.'\n        />\n    )\n\n    const punchout = useMeasurePunchouts([`.${OnboardingCardClassName}`], [])\n\n    return (\n        <TourTipRenderer\n            key='OpenCardTourStep'\n            requireCard={false}\n            category={TOUR_BASE}\n            step={BaseTourSteps.OPEN_A_CARD}\n            screen={screen}\n            title={title}\n            punchout={punchout}\n            classname='OpenCardTourStep'\n            telemetryTag='tourPoint1'\n            placement={bottom}\n            singleTip={true}\n            hideNavButtons={true}\n            hideBackdrop={false}\n        />\n    )\n}\n\nexport default OpenCardTourStep\n"
  },
  {
    "path": "webapp/src/components/onboardingTour/searchForBoards/searchForBoards.scss",
    "content": ".SearchForBoards {\n    &.tutorial-tour-tip__pulsating-dot-ctr {\n        margin-top: 10px;\n    }\n\n    .tutorial-tour-tip__overlay {\n        cursor: pointer;\n    }\n\n    .tutorial-tour-tip__footer {\n        margin-top: 15px;\n    }\n\n}\n"
  },
  {
    "path": "webapp/src/components/onboardingTour/searchForBoards/searchForBoards.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\n\nimport {FormattedMessage} from 'react-intl'\nimport {right} from '@popperjs/core'\n\nimport {SidebarTourSteps, TOUR_SIDEBAR} from '..'\n\nimport {useMeasurePunchouts} from '../../tutorial_tour_tip/hooks'\nimport TourTipRenderer from '../tourTipRenderer/tourTipRenderer'\n\nimport './searchForBoards.scss'\n\nconst SearchForBoardsTourStep = (): JSX.Element | null => {\n    const title = (\n        <FormattedMessage\n            id='SidebarTour.SearchForBoards.Title'\n            defaultMessage='Search for boards'\n        />\n    )\n\n    const screen = (\n        <FormattedMessage\n            id='SidebarTour.SearchForBoards.Body'\n            defaultMessage='Open the board switcher (Cmd/Ctrl + K) to quickly search and add boards to your sidebar.'\n        />\n    )\n\n    const punchout = useMeasurePunchouts(['.BoardsSwitcher'], [])\n\n    return (\n        <TourTipRenderer\n            key='SearchForBoardsTourStep'\n            requireCard={false}\n            category={TOUR_SIDEBAR}\n            step={SidebarTourSteps.SEARCH_FOR_BOARDS}\n            screen={screen}\n            title={title}\n            punchout={punchout}\n            classname='SearchForBoards'\n            telemetryTag='tourPoint4d'\n            placement={right}\n            hideBackdrop={false}\n            showForce={true}\n        />\n    )\n}\n\nexport default SearchForBoardsTourStep\n"
  },
  {
    "path": "webapp/src/components/onboardingTour/shareBoard/__snapshots__/shareBoard.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/onboardingTour/addComments/ShareBoardTourStep after hover 1`] = `\n<div\n  class=\"tippy-box tutorial-tour-tip__box ShareBoardTourStep\"\n  data-animation=\"scale-subtle\"\n  data-escaped=\"\"\n  data-placement=\"bottom-end\"\n  data-reference-hidden=\"\"\n  data-state=\"hidden\"\n  role=\"tooltip\"\n  style=\"max-width: 320px; transition-duration: 0ms;\"\n  tabindex=\"-1\"\n>\n  <div\n    class=\"tippy-content\"\n    data-state=\"hidden\"\n    style=\"transition-duration: 0ms;\"\n  >\n    <div>\n      <div>\n        <div\n          class=\"tutorial-tour-tip__header\"\n        >\n          <h4\n            class=\"tutorial-tour-tip__header__title\"\n          >\n            Share board\n          </h4>\n          <button\n            class=\"IconButton tutorial-tour-tip__header__close size--small\"\n            type=\"button\"\n          >\n            <i\n              class=\"CompassIcon icon-close CloseIcon\"\n            />\n          </button>\n        </div>\n        <div\n          class=\"tutorial-tour-tip__body\"\n        >\n          You can share your board internally, within your team, or publish it publicly for visibility outside of your organization.\n        </div>\n        <div\n          class=\"tutorial-tour-tip__image\"\n        >\n          <img\n            alt=\"tutorial tour tip product image\"\n            src=\"http://localhost/test-file-stub\"\n          />\n        </div>\n        <div\n          class=\"tutorial-tour-tip__footer\"\n        >\n          <div\n            class=\"tutorial-tour-tip__footer-buttons\"\n          >\n            <div\n              class=\"tutorial-tour-tip__circles-ctr\"\n            >\n              <div\n                class=\"tutorial-tour-tip__circular-ring\"\n              >\n                <a\n                  class=\"tutorial-tour-tip__circle\"\n                  data-screen=\"0\"\n                  href=\"#\"\n                />\n              </div>\n              <div\n                class=\"tutorial-tour-tip__circular-ring\"\n              >\n                <a\n                  class=\"tutorial-tour-tip__circle\"\n                  data-screen=\"1\"\n                  href=\"#\"\n                />\n              </div>\n              <div\n                class=\"tutorial-tour-tip__circular-ring tutorial-tour-tip__circular-ring-active\"\n              >\n                <a\n                  class=\"tutorial-tour-tip__circle active\"\n                  data-screen=\"2\"\n                  href=\"#\"\n                />\n              </div>\n            </div>\n            <div\n              class=\"tutorial-tour-tip__btn-ctr\"\n            >\n              <button\n                class=\"Button emphasis--tertiary size--small\"\n                title=\"Previous\"\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-chevron-left icon\"\n                />\n                <span>\n                  Previous\n                </span>\n              </button>\n              <button\n                class=\"Button filled size--small tipNextButton\"\n                type=\"button\"\n              >\n                <span>\n                  Done\n                </span>\n                \n              </button>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n  <div\n    class=\"tippy-arrow\"\n    style=\"position: absolute; left: 0px; transform: translate(3px, 0px);\"\n  />\n</div>\n`;\n\nexports[`components/onboardingTour/addComments/ShareBoardTourStep before hover 1`] = `\n<div>\n  <div\n    aria-expanded=\"true\"\n    class=\"tutorial-tour-tip__pulsating-dot-ctr ShareBoardTourStep\"\n  >\n    <span\n      class=\"pulsating_dot\"\n    />\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/onboardingTour/shareBoard/shareBoard.scss",
    "content": ".ShareBoardTourStep {\n    &.tutorial-tour-tip__pulsating-dot-ctr {\n        left: calc(80% - 16px);\n        top: 27px;\n    }\n\n    .tippy-arrow {\n        left: -20px !important;\n    }\n\n    &.tippy-box.tutorial-tour-tip__box {\n        right: -22px;\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/onboardingTour/shareBoard/shareBoard.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport {render} from '@testing-library/react'\n\nimport configureStore from 'redux-mock-store'\n\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport {wrapIntl} from '../../../testUtils'\n\nimport ShareBoardTourStep from './shareBoard'\n\ndescribe('components/onboardingTour/addComments/ShareBoardTourStep', () => {\n    const mockStore = configureStore([])\n    const state = {\n        users: {\n            me: {\n                id: 'user_id_1',\n            },\n            myConfig: {\n                onboardingTourStarted: {value: true},\n                tourCategory: {value: 'board'},\n                onboardingTourStep: {value: '2'},\n            },\n        },\n        boards: {\n            boards: {\n                board_id_1: {title: 'Welcome to Boards!'},\n            },\n            current: 'board_id_1',\n        },\n        cards: {\n            cards: {\n                card_id_1: {title: 'Create a new card'},\n            },\n            current: 'card_id_1',\n        },\n        clientConfig: {\n            value: {},\n        },\n    }\n    let store = mockStore(state)\n\n    beforeEach(() => {\n        store = mockStore(state)\n    })\n\n    test('before hover', () => {\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <ShareBoardTourStep/>\n            </ReduxProvider>,\n        )\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('after hover', () => {\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <ShareBoardTourStep/>\n            </ReduxProvider>,\n        )\n        render(component)\n        const elements = document.querySelectorAll('.ShareBoardTourStep')\n        expect(elements.length).toBe(2)\n        expect(elements[1]).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/onboardingTour/shareBoard/shareBoard.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\n\nimport {FormattedMessage} from 'react-intl'\n\nimport {useMeasurePunchouts} from '../../tutorial_tour_tip/hooks'\n\nimport './shareBoard.scss'\nimport {Utils} from '../../../utils'\nimport shareBoard from '../../../../static/share.gif'\n\nimport {BoardTourSteps, TOUR_BOARD} from '../index'\nimport TourTipRenderer from '../tourTipRenderer/tourTipRenderer'\n\nconst ShareBoardTourStep = (): JSX.Element | null => {\n    const title = (\n        <FormattedMessage\n            id='OnboardingTour.ShareBoard.Title'\n            defaultMessage='Share board'\n        />\n    )\n    const screen = (\n        <FormattedMessage\n            id='OnboardingTour.ShareBoard.Body'\n            defaultMessage='You can share your board internally, within your team, or publish it publicly for visibility outside of your organization.'\n        />\n    )\n\n    const punchout = useMeasurePunchouts(['.ShareBoardButton > button'], [])\n\n    if (!BoardTourSteps.SHARE_BOARD) {\n        return null\n    }\n\n    return (\n        <TourTipRenderer\n            key='ShareBoardTourStep'\n            requireCard={false}\n            category={TOUR_BOARD}\n            step={BoardTourSteps.SHARE_BOARD}\n            screen={screen}\n            title={title}\n            punchout={punchout}\n            classname='ShareBoardTourStep'\n            telemetryTag='tourPoint2b'\n            placement={'bottom-end'}\n            imageURL={Utils.buildURL(shareBoard, true)}\n            hideBackdrop={true}\n        />\n    )\n}\n\nexport default ShareBoardTourStep\n"
  },
  {
    "path": "webapp/src/components/onboardingTour/sidebarCategories/sidebarCategories.scss",
    "content": ".SidebarCategories {\n    &.tutorial-tour-tip__pulsating-dot-ctr {\n        margin-left: 135px;\n        margin-top: -10px;\n    }\n\n    .tutorial-tour-tip__overlay {\n        cursor: pointer;\n    }\n\n    .tutorial-tour-tip__footer {\n        margin-top: 15px;\n    }\n\n}\n"
  },
  {
    "path": "webapp/src/components/onboardingTour/sidebarCategories/sidebarCategories.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useEffect} from 'react'\n\nimport {right} from '@popperjs/core'\n\nimport {FormattedMessage} from 'react-intl'\n\nimport {useMeasurePunchouts} from '../../tutorial_tour_tip/hooks'\nimport TourTipRenderer from '../tourTipRenderer/tourTipRenderer'\nimport {TOUR_BOARD, TOUR_SIDEBAR, SidebarTourSteps, FINISHED} from '../index'\nimport {useAppDispatch, useAppSelector} from '../../../store/hooks'\nimport {\n    getMe,\n    getOnboardingTourCategory,\n    getOnboardingTourStep,\n    patchProps,\n} from '../../../store/users'\nimport {IUser, UserConfigPatch} from '../../../user'\nimport mutator from '../../../mutator'\nimport {Constants} from '../../../constants'\n\nimport './sidebarCategories.scss'\n\nconst SidebarCategoriesTourStep = (): JSX.Element | null => {\n    const title = (\n        <FormattedMessage\n            id='SidebarTour.SidebarCategories.Title'\n            defaultMessage='Sidebar categories'\n        />\n    )\n    const screen = (\n        <div>\n            <FormattedMessage\n                id='SidebarTour.SidebarCategories.Body'\n                defaultMessage='All your boards are now organized under your new sidebar. No more switching between workspaces. One-time custom categories based on your prior workspaces may have automatically been created for you as part of your v7.2 upgrade. These can be removed or edited to your preference. '\n            />\n            <a\n                href='https://docs.mattermost.com/welcome/whats-new-in-v72.html'\n                target='_blank'\n                rel='noopener noreferrer'\n            >\n                <FormattedMessage\n                    id='SidebarTour.SidebarCategories.Link'\n                    defaultMessage='Learn more'\n                />\n            </a>\n        </div>\n    )\n\n    const punchout = useMeasurePunchouts(['.SidebarCategory'], [])\n\n    const me = useAppSelector<IUser|null>(getMe)\n    const dispatch = useAppDispatch()\n    const onboardingTourCategory = useAppSelector(getOnboardingTourCategory)\n    const onboardingTourStep = useAppSelector(getOnboardingTourStep)\n\n    useEffect(() => {\n        async function task() {\n            if (!me) {\n                return\n            }\n\n            const should = onboardingTourCategory === TOUR_BOARD &&\n                           onboardingTourStep === FINISHED.toString()\n\n            if (!should) {\n                return\n            }\n\n            const patch: UserConfigPatch = {}\n            patch.updatedFields = {}\n            patch.updatedFields.tourCategory = TOUR_SIDEBAR\n            patch.updatedFields.onboardingTourStep = SidebarTourSteps.SIDE_BAR.toString()\n            patch.updatedFields.lastWelcomeVersion = Constants.versionString\n\n            const updatedProps = await mutator.patchUserConfig(me.id, patch)\n            if (updatedProps) {\n                dispatch(patchProps(updatedProps))\n            }\n        }\n\n        // this hack is needed to allow performing async task in useEffect\n        task()\n    }, [])\n\n    return (\n        <TourTipRenderer\n            key='SidebardCategoriesTourStep'\n            requireCard={false}\n            category={TOUR_SIDEBAR}\n            step={SidebarTourSteps.SIDE_BAR}\n            screen={screen}\n            title={title}\n            punchout={punchout}\n            classname='SidebarCategories'\n            telemetryTag='tourPoint4a'\n            placement={right}\n            hideBackdrop={false}\n            showForce={true}\n        />\n    )\n}\n\nexport default SidebarCategoriesTourStep\n"
  },
  {
    "path": "webapp/src/components/onboardingTour/tourTipRenderer/tourTipRenderer.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\n\nimport {Placement} from 'tippy.js'\n\nimport {ClientConfig} from '../../../config/clientConfig'\nimport {getClientConfig} from '../../../store/clientConfig'\n\nimport {useAppSelector} from '../../../store/hooks'\nimport {getCurrentBoard} from '../../../store/boards'\nimport {getCurrentCard} from '../../../store/cards'\nimport {OnboardingBoardTitle, OnboardingCardTitle} from '../../cardDetail/cardDetail'\nimport {getOnboardingTourCategory, getOnboardingTourStarted, getOnboardingTourStep} from '../../../store/users'\nimport TourTip from '../../tutorial_tour_tip/tutorial_tour_tip'\nimport {TutorialTourTipPunchout} from '../../tutorial_tour_tip/tutorial_tour_tip_backdrop'\n\ntype Props = {\n    requireCard: boolean\n    category: string\n    step: number\n    screen: JSX.Element\n    title: JSX.Element\n    punchout: TutorialTourTipPunchout | null | undefined\n    classname: string\n    telemetryTag: string\n    placement: Placement | undefined\n    hideBackdrop: boolean\n    imageURL?: string\n    singleTip?: boolean\n    hideNavButtons?: boolean\n    showForce?: boolean\n}\n\nconst TourTipRenderer = (props: Props): JSX.Element | null => {\n    const board = useAppSelector(getCurrentBoard)\n    const clientConfig = useAppSelector<ClientConfig>(getClientConfig)\n\n    let isOnboardingBoard = board ? board.title === OnboardingBoardTitle : false\n    const onboardingTourStarted = useAppSelector(getOnboardingTourStarted)\n    const onboardingTourCategory = useAppSelector(getOnboardingTourCategory)\n    const onboardingTourStep = useAppSelector(getOnboardingTourStep)\n    const disableTour = clientConfig?.featureFlags?.disableTour || false\n\n    if (props.showForce) {\n        isOnboardingBoard = true\n    }\n\n    const showTour = !disableTour && isOnboardingBoard && onboardingTourStarted && onboardingTourCategory === props.category\n    let showTourTip = showTour && onboardingTourStep === props.step.toString()\n\n    if (props.requireCard) {\n        const card = useAppSelector(getCurrentCard)\n        const isOnboardingCard = card ? card.title === OnboardingCardTitle : false\n\n        showTourTip = showTourTip && isOnboardingCard\n    }\n\n    const currentStep = parseInt(useAppSelector(getOnboardingTourStep), 10)\n\n    if (!showTourTip) {\n        return null\n    }\n\n    return (\n        <TourTip\n            screen={props.screen}\n            title={props.title}\n            punchOut={props.punchout}\n            step={currentStep}\n            tutorialCategory={props.category}\n            placement={props.placement}\n            className={props.classname}\n            imageURL={props.imageURL}\n            telemetryTag={props.telemetryTag}\n            skipCategoryFromBackdrop={true}\n            autoTour={true}\n            hideBackdrop={props.hideBackdrop}\n            singleTip={props.singleTip}\n            hideNavButtons={props.hideNavButtons}\n        />\n    )\n}\n\nexport default TourTipRenderer\n"
  },
  {
    "path": "webapp/src/components/permissions/__snapshots__/boardPermissionGate.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/permission/boardPermissionGate match snapshot when the user doesnt have the permissions 1`] = `<div />`;\n\nexports[`components/permission/boardPermissionGate match snapshot when the user doesnt have the permissions with invert 1`] = `\n<div>\n  Content\n</div>\n`;\n\nexports[`components/permission/boardPermissionGate match snapshot when the user has the permissions 1`] = `\n<div>\n  Content\n</div>\n`;\n\nexports[`components/permission/boardPermissionGate match snapshot when the user has the permissions with invert 1`] = `<div />`;\n"
  },
  {
    "path": "webapp/src/components/permissions/boardPermissionGate.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {render} from '@testing-library/react'\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport '@testing-library/jest-dom'\n\nimport {TestBlockFactory} from '../../test/testBlockFactory'\nimport {Permission} from '../../constants'\n\nimport {wrapIntl, mockStateStore} from '../../testUtils'\n\nimport BoardPermissionGate from './boardPermissionGate'\n\nconst board = TestBlockFactory.createBoard()\n\ndescribe('components/permission/boardPermissionGate', () => {\n    const state = {\n        teams: {\n            current: {id: 'team-id'},\n        },\n        boards: {\n            current: board.id,\n            boards: {\n                [board.id]: board,\n            },\n            myBoardMemberships: {\n                [board.id]: {userId: 'user_id_1', schemeAdmin: true},\n            },\n        },\n    }\n    const store = mockStateStore([], state)\n    test('match snapshot when the user has the permissions', () => {\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <BoardPermissionGate\n                        permissions={[Permission.ManageBoardCards]}\n                    >\n                        {'Content'}\n                    </BoardPermissionGate>\n                </ReduxProvider>,\n            ),\n        )\n        expect(container).toMatchSnapshot()\n    })\n\n    test('match snapshot when the user has the permissions with invert', () => {\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <BoardPermissionGate\n                        permissions={[Permission.ManageBoardCards]}\n                        invert={true}\n                    >\n                        {'Content'}\n                    </BoardPermissionGate>\n                </ReduxProvider>,\n            ),\n        )\n        expect(container).toMatchSnapshot()\n    })\n\n    test('match snapshot when the user doesnt have the permissions', () => {\n        const localStore = mockStateStore([], {...state, teams: {current: undefined}})\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={localStore}>\n                    <BoardPermissionGate\n                        permissions={[Permission.ManageBoardCards]}\n                    >\n                        {'Content'}\n                    </BoardPermissionGate>\n                </ReduxProvider>,\n            ),\n        )\n        expect(container).toMatchSnapshot()\n    })\n\n    test('match snapshot when the user doesnt have the permissions with invert', () => {\n        const localStore = mockStateStore([], {...state, teams: {current: undefined}})\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={localStore}>\n                    <BoardPermissionGate\n                        permissions={[Permission.ManageBoardCards]}\n                        invert={true}\n                    >\n                        {'Content'}\n                    </BoardPermissionGate>\n                </ReduxProvider>,\n            ),\n        )\n        expect(container).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/permissions/boardPermissionGate.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport {useAppSelector} from '../../store/hooks'\nimport {getCurrentBoardId} from '../../store/boards'\nimport {getCurrentTeam} from '../../store/teams'\nimport {Permission} from '../../constants'\nimport {useHasPermissions} from '../../hooks/permissions'\n\ntype Props = {\n    boardId?: string\n    teamId?: string\n    permissions: Permission[]\n    invert?: boolean\n    children: React.ReactNode\n}\n\nconst BoardPermissionGate = React.memo((props: Props): React.ReactElement|null => {\n    const currentTeam = useAppSelector(getCurrentTeam)\n    const currentBoardId = useAppSelector(getCurrentBoardId)\n\n    const boardId = props.boardId || currentBoardId || ''\n    const teamId = props.teamId || currentTeam?.id || ''\n\n    let allowed = useHasPermissions(teamId, boardId, props.permissions)\n\n    if (props.invert) {\n        allowed = !allowed\n    }\n\n    if (allowed) {\n        return (<>{props.children}</>)\n    }\n    return null\n})\n\nexport default BoardPermissionGate\n"
  },
  {
    "path": "webapp/src/components/personSelector.scss",
    "content": ".Person {\n    display: flex;\n    align-items: center;\n    border-radius: 4px;\n    flex-wrap: wrap;\n    gap: 8px;\n\n    &.readonly {\n        overflow: hidden;\n        text-overflow: ellipsis;\n        min-width: unset;\n    }\n\n    .Person-item {\n        display: flex;\n        align-items: center;\n\n        img {\n            border-radius: 50px;\n            width: 24px;\n            height: 24px;\n            margin-right: 6px;\n        }\n    }\n\n    .react-select__menu {\n        background: rgba(var(--center-channel-bg-rgb), 1);\n        box-shadow: var(--elevation-4);\n        border: 1px solid rgba(var(--center-channel-color-rgb), 0.16);\n        border-radius: 4px;\n    }\n\n    .react-select__single-value {\n        margin: 0;\n        position: relative;\n        top: 0;\n        max-width: 100%;\n    }\n\n    .react-select__value-container--is-multi {\n        gap: 4px;\n        display: inline-flex;\n\n        .react-select__multi-value__label {\n            padding-left: 4px;\n        }\n\n        .react-select__multi-value {\n            background: rgba(var(--center-channel-color-rgb), 0.08);\n            border-radius: 24px;\n            display: inline-flex;\n            color: rgb(var(--center-channel-color-rgb));\n            margin: 0;\n            align-items: center;\n\n            .MultiPerson-item,\n            .react-select__multi-value__label {\n                color: inherit;\n            }\n        }\n    }\n\n    .react-select__multi-value__remove {\n        font-size: 18px;\n        color: rgba(var(--center-channel-color-rgb), 0.56);\n        margin: 6px;\n        border-radius: 100%;\n        margin-left: 0;\n        padding: 0;\n        width: 20px;\n        height: 20px;\n        display: flex;\n        align-items: center;\n        justify-content: center;\n\n        &:hover {\n            background: rgba(var(--center-channel-color-rgb), 0.26);\n        }\n    }\n\n    .react-select__option {\n        display: flex;\n        align-items: center;\n        height: 40px;\n        padding: 0 40px 0 20px;\n\n        &:hover {\n            background: rgba(var(--center-channel-color-rgb), 0.08);\n        }\n\n        &:active {\n            background: rgba(var(--button-bg-rgb), 0.08);\n        }\n\n        &.react-select__option--is-selected {\n            background: rgba(var(--button-bg-rgb), 0.08);\n            color: rgba(var(--center-channel-color-rgb), 1);\n        }\n\n        .Person-item {\n            img {\n                margin-right: 12px;\n            }\n        }\n    }\n\n    .react-select__menu-list {\n        border: 0;\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/personSelector.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport {render, waitFor} from '@testing-library/react'\n\nimport configureStore from 'redux-mock-store'\n\nimport {act} from 'react-dom/test-utils'\n\nimport userEvent from '@testing-library/user-event'\n\nimport {wrapIntl} from '../testUtils'\n\nimport PersonProperty from '../properties/person/property'\n\nimport PersonSelector from './personSelector'\n\ndescribe('properties/person', () => {\n    const mockStore = configureStore([])\n    const state = {\n        users: {\n            me: {\n                'user-id-1': {\n                    id: 'user-id-1',\n                    username: 'username-1',\n                    email: 'user-1@example.com',\n                    firstname: 'test',\n                    lastname: 'user',\n                    props: {},\n                    create_at: 1621315184,\n                    update_at: 1621315184,\n                    delete_at: 0,\n                },\n            },\n            boardUsers: {\n                'user-id-1': {\n                    id: 'user-id-1',\n                    username: 'username-1',\n                    email: 'user-1@example.com',\n                    firstname: 'test',\n                    lastname: 'user',\n                    props: {},\n                    create_at: 1621315184,\n                    update_at: 1621315184,\n                    delete_at: 0,\n                },\n                'user-id-2': {\n                    id: 'user-id-2',\n                    username: 'username-2',\n                    email: 'user-2@example.com',\n                    props: {},\n                    create_at: 1621315184,\n                    update_at: 1621315184,\n                    delete_at: 0,\n                },\n                'user-id-3': {\n                    id: 'user-id-3',\n                    username: 'username-3',\n                    email: 'user-3@example.com',\n                    props: {},\n                    create_at: 1621315184,\n                    update_at: 1621315184,\n                    delete_at: 0,\n                },\n            },\n        },\n        clientConfig: {\n            value: {\n                teammateNameDisplay: 'username',\n            },\n        },\n    }\n\n    test('not readOnly, show username', async () => {\n        const store = mockStore(state)\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <PersonSelector\n                    readOnly={false}\n                    userIDs={['user-id-1']}\n                    allowAddUsers={false}\n                    property={new PersonProperty()}\n                    emptyDisplayValue={'Empty'}\n                    isMulti={false}\n                    closeMenuOnSelect={true}\n                    onChange={() => {}}\n                />\n            </ReduxProvider>,\n        )\n\n        const renderResult = render(component)\n        const container = await waitFor(() => {\n            if (!renderResult.container) {\n                return Promise.reject(new Error('container not found'))\n            }\n            return Promise.resolve(renderResult.container)\n        })\n        expect(container).toMatchSnapshot()\n    })\n\n    test('not readOnly, show firstname', async () => {\n        const store = mockStore({\n            ...state,\n            clientConfig: {\n                value: {\n                    teammateNameDisplay: 'full_name',\n                },\n            },\n        })\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <PersonSelector\n                    readOnly={false}\n                    userIDs={['user-id-1']}\n                    allowAddUsers={false}\n                    property={new PersonProperty()}\n                    emptyDisplayValue={'Empty'}\n                    isMulti={false}\n                    closeMenuOnSelect={true}\n                    onChange={() => {}}\n                />\n            </ReduxProvider>,\n        )\n\n        const renderResult = render(component)\n        const container = await waitFor(() => {\n            if (!renderResult.container) {\n                return Promise.reject(new Error('container not found'))\n            }\n            return Promise.resolve(renderResult.container)\n        })\n        expect(container).toMatchSnapshot()\n    })\n\n    test('not readOnly, show modal', async () => {\n        const store = mockStore(state)\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <PersonSelector\n                    readOnly={false}\n                    userIDs={[]}\n                    allowAddUsers={false}\n                    property={new PersonProperty()}\n                    emptyDisplayValue={'Empty'}\n                    isMulti={false}\n                    closeMenuOnSelect={true}\n                    onChange={() => {}}\n                />\n            </ReduxProvider>,\n        )\n\n        const renderResult = render(component)\n        const container = await waitFor(() => {\n            if (!renderResult.container) {\n                return Promise.reject(new Error('container not found'))\n            }\n            return Promise.resolve(renderResult.container)\n        })\n        expect(container).toMatchSnapshot()\n\n        if (container) {\n            // this is the actual element where the click event triggers\n            // opening of the dropdown\n            const userProperty = container.querySelector('.Person > div > div:nth-child(1) > div:nth-child(2) > input')\n            expect(userProperty).not.toBeNull()\n\n            act(() => {\n                userEvent.click(userProperty as Element)\n            })\n\n            const userList = container.querySelector('.Person-item')\n            expect(userList).not.toBeNull()\n            expect(container).toMatchSnapshot()\n        } else {\n            throw new Error('container should have been initialized')\n        }\n    })\n\n    test('readOnly view', async () => {\n        const store = mockStore(state)\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <PersonSelector\n                    readOnly={true}\n                    userIDs={['user-id-1']}\n                    allowAddUsers={false}\n                    property={new PersonProperty()}\n                    emptyDisplayValue={'Empty'}\n                    isMulti={false}\n                    closeMenuOnSelect={true}\n                    onChange={() => {}}\n                />\n            </ReduxProvider>,\n        )\n\n        const renderResult = render(component)\n        const container = await waitFor(() => {\n            if (!renderResult.container) {\n                return Promise.reject(new Error('container not found'))\n            }\n            return Promise.resolve(renderResult.container)\n        })\n        expect(container).toMatchSnapshot()\n\n        if (container) {\n            // this is the actual element where the click event triggers\n            // opening of the dropdown\n            const userProperty = container.querySelector('.Person > div > div:nth-child(1) > div:nth-child(2) > input')\n            expect(userProperty).toBeNull()\n        } else {\n            throw new Error('container should have been initialized')\n        }\n    })\n\n    test('show multiple', async () => {\n        const store = mockStore(state)\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <PersonSelector\n                    readOnly={false}\n                    userIDs={['user-id-1', 'user-id-2']}\n                    allowAddUsers={false}\n                    property={new PersonProperty()}\n                    emptyDisplayValue={'Empty'}\n                    isMulti={true}\n                    closeMenuOnSelect={true}\n                    onChange={() => {}}\n                />\n            </ReduxProvider>,\n        )\n\n        const renderResult = render(component)\n        const container = await waitFor(() => {\n            if (!renderResult.container) {\n                return Promise.reject(new Error('container not found'))\n            }\n            return Promise.resolve(renderResult.container)\n        })\n        expect(container).toMatchSnapshot()\n    })\n    test('show multiple, display modal', async () => {\n        const store = mockStore(state)\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <PersonSelector\n                    readOnly={false}\n                    userIDs={['user-id-1', 'user-id-2']}\n                    allowAddUsers={false}\n                    property={new PersonProperty()}\n                    emptyDisplayValue={'(empty)'}\n                    isMulti={true}\n                    closeMenuOnSelect={true}\n                    onChange={() => {}}\n                />\n            </ReduxProvider>,\n        )\n\n        const renderResult = render(component)\n        const container = await waitFor(() => {\n            if (!renderResult.container) {\n                return Promise.reject(new Error('container not found'))\n            }\n            return Promise.resolve(renderResult.container)\n        })\n        expect(container).toMatchSnapshot()\n\n        if (container) {\n            // this is the actual element where the click event triggers\n            // opening of the dropdown\n            const userProperty = container.querySelector('.MultiPerson > div > div:nth-child(1) > div:nth-child(3) > input')\n            expect(userProperty).not.toBeNull()\n\n            act(() => {\n                userEvent.click(userProperty as Element)\n            })\n            const userList = container.querySelector('.MultiPerson-item')\n            expect(userList).not.toBeNull()\n            expect(container).toMatchSnapshot()\n        } else {\n            throw new Error('container should have been initialized')\n        }\n    })\n\n    test('not readOnly, show me', async () => {\n        const store = mockStore(state)\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <PersonSelector\n                    readOnly={false}\n                    showMe={true}\n                    userIDs={[]}\n                    allowAddUsers={false}\n                    property={new PersonProperty()}\n                    emptyDisplayValue={'Empty'}\n                    isMulti={false}\n                    closeMenuOnSelect={true}\n                    onChange={() => {}}\n                />\n            </ReduxProvider>,\n        )\n\n        const renderResult = render(component)\n        const container = await waitFor(() => {\n            if (!renderResult.container) {\n                return Promise.reject(new Error('container not found'))\n            }\n            return Promise.resolve(renderResult.container)\n        })\n\n        // expect(container).toMatchSnapshot()\n        if (container) {\n            // this is the actual element where the click event triggers\n            // opening of the dropdown\n            const userProperty = container.querySelector('.Person > div > div:nth-child(1) > div:nth-child(2) > input')\n            expect(userProperty).not.toBeNull()\n            act(() => {\n                userEvent.click(userProperty as Element)\n            })\n\n            const userList = container.querySelector('.Person-item')\n            expect(userList).not.toBeNull()\n            console.log('Text content ' + userList?.textContent)\n            expect(userList?.textContent).toBe('Me')\n            expect(container).toMatchSnapshot()\n        } else {\n            throw new Error('container should have been initialized')\n        }\n        expect(container).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/personSelector.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React, {useCallback} from 'react'\nimport {useIntl} from 'react-intl'\nimport Select from 'react-select/async'\nimport {CSSObject} from '@emotion/serialize'\n\nimport {ActionMeta} from 'react-select'\n\nimport {getSelectBaseStyle} from '../theme'\nimport {IUser} from '../user'\nimport {Utils} from '../utils'\nimport {useAppSelector} from '../store/hooks'\nimport {getBoardUsers, getBoardUsersList, getMe} from '../store/users'\n\nimport {ClientConfig} from '../config/clientConfig'\nimport {getClientConfig} from '../store/clientConfig'\nimport client from '../octoClient'\n\nimport GuestBadge from '../widgets/guestBadge'\nimport {PropertyType} from '../properties/types'\n\nimport './personSelector.scss'\n\nconst imageURLForUser = (window as any).Components?.imageURLForUser\n\ntype Props = {\n    readOnly: boolean\n    userIDs: string[]\n    allowAddUsers: boolean\n    property?: PropertyType\n    emptyDisplayValue: string\n    isMulti: boolean\n    closeMenuOnSelect?: boolean\n    showMe?: boolean\n    onChange: (items: any, action: ActionMeta<IUser>) => void\n}\n\nconst selectStyles = {\n    ...getSelectBaseStyle(),\n    option: (provided: CSSObject, state: {isFocused: boolean}): CSSObject => ({\n        ...provided,\n        background: state.isFocused ? 'rgba(var(--center-channel-color-rgb), 0.1)' : 'rgb(var(--center-channel-bg-rgb))',\n        color: state.isFocused ? 'rgb(var(--center-channel-color-rgb))' : 'rgb(var(--center-channel-color-rgb))',\n        padding: '8px',\n    }),\n    control: (): CSSObject => ({\n        border: 0,\n        width: '100%',\n        margin: '0',\n    }),\n    valueContainer: (provided: CSSObject): CSSObject => ({\n        ...provided,\n        padding: 'unset',\n        overflow: 'unset',\n    }),\n    singleValue: (provided: CSSObject): CSSObject => ({\n        ...provided,\n        position: 'static',\n        top: 'unset',\n        transform: 'unset',\n    }),\n    menu: (provided: CSSObject): CSSObject => ({\n        ...provided,\n        width: 'unset',\n        background: 'rgb(var(--center-channel-bg-rgb))',\n        minWidth: '260px',\n    }),\n}\n\nconst PersonSelector = (props: Props): JSX.Element => {\n    const {readOnly, userIDs, allowAddUsers, isMulti, closeMenuOnSelect = true, emptyDisplayValue, showMe = false, onChange} = props\n\n    const clientConfig = useAppSelector<ClientConfig>(getClientConfig)\n    const intl = useIntl()\n\n    const boardUsersById = useAppSelector<{[key: string]: IUser}>(getBoardUsers)\n    const boardUsers = useAppSelector<IUser[]>(getBoardUsersList)\n    const boardUsersKey = Object.keys(boardUsersById) ? Utils.hashCode(JSON.stringify(Object.keys(boardUsersById))) : 0\n    const me = useAppSelector<IUser|null>(getMe)\n\n    const formatOptionLabel = (user: any): JSX.Element => {\n        if (!user) {\n            return <div/>\n        }\n        let profileImg\n        if (imageURLForUser) {\n            profileImg = imageURLForUser(user.id)\n        }\n\n        return (\n            <div\n                key={user.id}\n                className={isMulti ? 'MultiPerson-item' : 'Person-item'}\n            >\n                {profileImg && (\n                    <img\n                        alt='Person-avatar'\n                        src={profileImg}\n                    />\n                )}\n                {Utils.getUserDisplayName(user, clientConfig.teammateNameDisplay)}\n                <GuestBadge show={Boolean(user?.is_guest)}/>\n            </div>\n        )\n    }\n\n    let users: IUser[] = []\n    if (Object.keys(boardUsersById).length > 0) {\n        users = userIDs.map((id) => boardUsersById[id])\n    }\n\n    const loadOptions = useCallback(async (value: string) => {\n        if (!allowAddUsers) {\n            const returnUsers: IUser[] = []\n            if (showMe && me) {\n                returnUsers.push({\n                    id: me.id,\n                    username: intl.formatMessage({id: 'PersonProperty.me', defaultMessage: 'Me'}),\n                    email: '',\n                    nickname: '',\n                    firstname: '',\n                    lastname: '',\n                    props: {},\n                    create_at: me.create_at,\n                    update_at: me.update_at,\n                    is_bot: false,\n                    is_guest: me.is_guest,\n                    roles: me.roles,\n                })\n                returnUsers.push(...boardUsers.filter((u) => u.id !== me.id))\n            } else {\n                returnUsers.push(...boardUsers)\n            }\n            if (value) {\n                return returnUsers.filter((u) => {\n                    return u.username.toLowerCase().includes(value.toLowerCase()) ||\n                        u.lastname.toLowerCase().includes(value.toLowerCase()) ||\n                        u.firstname.toLowerCase().includes(value.toLowerCase()) ||\n                        u.nickname.toLowerCase().includes(value.toLowerCase())\n                })\n            }\n            return returnUsers\n        }\n        const excludeBots = true\n        const allUsers = await client.searchTeamUsers(value, excludeBots)\n        const usersInsideBoard: IUser[] = []\n        const usersOutsideBoard: IUser[] = []\n        for (const u of allUsers) {\n            if (boardUsersById[u.id]) {\n                usersInsideBoard.push(u)\n            } else {\n                usersOutsideBoard.push(u)\n            }\n        }\n        return [\n            {label: intl.formatMessage({id: 'PersonProperty.board-members', defaultMessage: 'Board members'}), options: usersInsideBoard},\n            {label: intl.formatMessage({id: 'PersonProperty.non-board-members', defaultMessage: 'Not board members'}), options: usersOutsideBoard},\n        ]\n    }, [boardUsers, allowAddUsers, boardUsersById, me])\n\n    let primaryClass = 'Person'\n    if (isMulti) {\n        primaryClass = 'MultiPerson'\n    }\n    let secondaryClass = ''\n    if (props.property) {\n        secondaryClass = ` ${props.property.valueClassName(readOnly)}`\n    }\n\n    if (readOnly) {\n        return (\n            <div className={`${primaryClass}${secondaryClass}`}>\n                {users.map((user) => formatOptionLabel(user))}\n            </div>\n        )\n    }\n\n    return (\n        <>\n            <Select\n                key={boardUsersKey}\n                loadOptions={loadOptions}\n                isMulti={isMulti}\n                defaultOptions={true}\n                isSearchable={true}\n                isClearable={true}\n                backspaceRemovesValue={true}\n                closeMenuOnSelect={closeMenuOnSelect}\n                className={`${primaryClass}${secondaryClass}`}\n                classNamePrefix={'react-select'}\n                formatOptionLabel={formatOptionLabel}\n                styles={selectStyles}\n                placeholder={emptyDisplayValue}\n                getOptionLabel={(o: IUser) => o.username}\n                getOptionValue={(a: IUser) => a.id}\n                value={users}\n                onChange={onChange}\n            />\n        </>\n    )\n}\n\nexport default PersonSelector\n"
  },
  {
    "path": "webapp/src/components/propertyValueElement.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {render} from '@testing-library/react'\nimport '@testing-library/jest-dom'\nimport userEvent from '@testing-library/user-event'\n\nimport {wrapDNDIntl} from '../testUtils'\nimport 'isomorphic-fetch'\nimport {IPropertyTemplate, IPropertyOption} from '../blocks/board'\nimport {TestBlockFactory} from '../test/testBlockFactory'\n\nimport PropertyValueElement from './propertyValueElement'\n\ndescribe('components/propertyValueElement', () => {\n    const board = TestBlockFactory.createBoard()\n    const card = TestBlockFactory.createCard(board)\n\n    test('should match snapshot, select', async () => {\n        const propertyTemplate = board.cardProperties.find((p) => p.id === 'property1')\n        const component = wrapDNDIntl(\n            <PropertyValueElement\n                board={board}\n                readOnly={false}\n                card={card}\n                propertyTemplate={propertyTemplate || board.cardProperties[0]}\n                showEmptyPlaceholder={true}\n            />,\n        )\n\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot, select, read-only', async () => {\n        const propertyTemplate = board.cardProperties.find((p) => p.id === 'property1')\n        const component = wrapDNDIntl(\n            <PropertyValueElement\n                board={board}\n                readOnly={true}\n                card={card}\n                propertyTemplate={propertyTemplate || board.cardProperties[0]}\n                showEmptyPlaceholder={true}\n            />,\n        )\n\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot, multi-select', () => {\n        const options: IPropertyOption[] = []\n        for (let i = 0; i < 3; i++) {\n            const propertyOption: IPropertyOption = {\n                id: `ms${i}`,\n                value: `value ${i}`,\n                color: 'propColorBrown',\n            }\n            options.push(propertyOption)\n        }\n\n        const propertyTemplate: IPropertyTemplate = {\n            id: 'multiSelect',\n            name: 'MultiSelect',\n            type: 'multiSelect',\n            options,\n        }\n        card.fields.properties.multiSelect = ['ms1', 'ms2']\n        const component = wrapDNDIntl(\n            <PropertyValueElement\n                board={board}\n                readOnly={false}\n                card={card}\n                propertyTemplate={propertyTemplate}\n                showEmptyPlaceholder={true}\n            />,\n        )\n\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot, url, array value', () => {\n        const propertyTemplate: IPropertyTemplate = {\n            id: 'property_url',\n            name: 'Property URL',\n            type: 'url',\n            options: [],\n        }\n        card.fields.properties.property_url = 'http://localhost'\n\n        const component = wrapDNDIntl(\n            <PropertyValueElement\n                board={board}\n                readOnly={false}\n                card={card}\n                propertyTemplate={propertyTemplate}\n                showEmptyPlaceholder={true}\n            />,\n        )\n\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot, person, array value', () => {\n        const propertyTemplate: IPropertyTemplate = {\n            id: 'text',\n            name: 'Generic Text',\n            type: 'text',\n            options: [],\n        }\n        card.fields.properties.person = 'value1'\n\n        const component = wrapDNDIntl(\n            <PropertyValueElement\n                board={board}\n                readOnly={false}\n                card={card}\n                propertyTemplate={propertyTemplate}\n                showEmptyPlaceholder={true}\n            />,\n        )\n\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot, date, array value', () => {\n        const propertyTemplate: IPropertyTemplate = {\n            id: 'date',\n            name: 'Date',\n            type: 'date',\n            options: [],\n        }\n        card.fields.properties.date = 'invalid date'\n\n        const component = wrapDNDIntl(\n            <PropertyValueElement\n                board={board}\n                readOnly={false}\n                card={card}\n                propertyTemplate={propertyTemplate}\n                showEmptyPlaceholder={true}\n            />,\n        )\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('URL fields should allow cancel', () => {\n        const propertyTemplate: IPropertyTemplate = {\n            id: 'property_url',\n            name: 'Property URL',\n            type: 'url',\n            options: [],\n        }\n\n        const component = wrapDNDIntl(\n            <PropertyValueElement\n                board={board}\n                readOnly={false}\n                card={card}\n                propertyTemplate={propertyTemplate}\n                showEmptyPlaceholder={true}\n            />,\n        )\n\n        const {container} = render(component)\n        const editElement = container.querySelector('.Editable')\n        expect(editElement).toBeDefined()\n\n        userEvent.type(editElement!, 'http://test{esc}')\n        expect(container).toMatchSnapshot()\n    })\n\n    test('Generic fields should allow cancel', () => {\n        const propertyTemplate: IPropertyTemplate = {\n            id: 'text',\n            name: 'Generic Text',\n            type: 'text',\n            options: [],\n        }\n\n        const component = wrapDNDIntl(\n            <PropertyValueElement\n                board={board}\n                readOnly={false}\n                card={card}\n                propertyTemplate={propertyTemplate}\n                showEmptyPlaceholder={true}\n            />,\n        )\n\n        const {container} = render(component)\n        const editElement = container.querySelector('.Editable')\n        expect(editElement).toBeDefined()\n\n        userEvent.type(editElement!, 'http://test{esc}')\n        expect(container).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/propertyValueElement.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport {Board, IPropertyTemplate} from '../blocks/board'\nimport {Card} from '../blocks/card'\n\nimport propsRegistry from '../properties'\n\ntype Props = {\n    board: Board\n    readOnly: boolean\n    card: Card\n    propertyTemplate: IPropertyTemplate\n    showEmptyPlaceholder: boolean\n}\n\nconst PropertyValueElement = (props: Props): JSX.Element => {\n    const {card, propertyTemplate, readOnly, showEmptyPlaceholder, board} = props\n\n    let propertyValue = card.fields.properties[propertyTemplate.id]\n    if (propertyValue === undefined) {\n        propertyValue = ''\n    }\n    const property = propsRegistry.get(propertyTemplate.type)\n    const Editor = property.Editor\n    return (\n        <Editor\n            property={property}\n            card={card}\n            board={board}\n            readOnly={readOnly}\n            showEmptyPlaceholder={showEmptyPlaceholder}\n            propertyTemplate={propertyTemplate}\n            propertyValue={propertyValue}\n        />\n    )\n}\n\nexport default PropertyValueElement\n"
  },
  {
    "path": "webapp/src/components/pulsating_dot/index.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\n\nimport './pulsating_dot.scss'\nimport {Coords} from '../tutorial_tour_tip/tutorial_tour_tip_backdrop'\n\ntype Props = {\n    targetRef?: React.RefObject<HTMLImageElement>\n    className?: string\n    onClick?: (e: React.MouseEvent) => void\n    coords?: Coords\n}\n\nconst PulsatingDot = (props: Props): JSX.Element => {\n    let customStyles = {}\n    if (props?.coords) {\n        customStyles = {\n            transform: `translate(${props.coords?.x}px, ${props.coords?.y}px)`,\n        }\n    }\n    let effectiveClassName = 'pulsating_dot'\n    if (props.onClick) {\n        effectiveClassName += ' pulsating_dot-clickable'\n    }\n    if (props.className) {\n        effectiveClassName = effectiveClassName + ' ' + props.className\n    }\n\n    return (\n        <span\n            className={effectiveClassName}\n            onClick={props.onClick}\n            ref={props.targetRef}\n            style={{...customStyles}}\n        />\n    )\n}\n\nexport default PulsatingDot\n"
  },
  {
    "path": "webapp/src/components/pulsating_dot/pulsating_dot.scss",
    "content": ".pulsating_dot {\n    position: relative;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    margin-left: auto;\n    cursor: pointer;\n\n    &,\n    &::before,\n    &::after {\n        width: 12px;\n        height: 12px;\n        background-color: #3db887;\n        border-radius: 50%;\n    }\n\n    &::before,\n    &::after {\n        position: absolute;\n        top: 0;\n        left: 0;\n        display: block;\n        content: '';\n    }\n\n    &::after {\n        animation: pulse1 2s ease 0s infinite;\n    }\n\n    &::before {\n        animation: pulse2 2s ease 0s infinite;\n    }\n}\n\n@keyframes pulse1 {\n    0% {\n        opacity: 1;\n        transform: scale(1);\n    }\n\n    80% {\n        opacity: 0;\n        transform: scale(2.5);\n    }\n\n    100% {\n        opacity: 0;\n        transform: scale(2.5);\n    }\n}\n\n@keyframes pulse2 {\n    0% {\n        opacity: 1;\n        transform: scale(1);\n    }\n\n    30% {\n        opacity: 1;\n        transform: scale(1);\n    }\n\n    100% {\n        opacity: 0;\n        transform: scale(2.5);\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/rootPortal.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {render} from '@testing-library/react'\nimport '@testing-library/jest-dom'\n\nimport RootPortal from './rootPortal'\n\ndescribe('components/RootPortal', () => {\n    beforeEach(() => {\n        // Quick fix to disregard console error when unmounting a component\n        console.error = jest.fn()\n    })\n\n    test('should match snapshot', () => {\n        const rootPortalDiv = document.createElement('div')\n        rootPortalDiv.id = 'focalboard-root-portal'\n\n        const {getByText, container} = render(\n            <RootPortal>\n                <div>{'Testing Portal'}</div>\n            </RootPortal>,\n            {container: document.body.appendChild(rootPortalDiv)},\n        )\n\n        expect(getByText('Testing Portal')).toBeVisible()\n        expect(container).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/rootPortal.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React, {useState, useLayoutEffect} from 'react'\nimport ReactDOM from 'react-dom'\n\ntype Props = {\n    children: React.ReactNode\n}\n\nconst RootPortal = (props: Props): JSX.Element => {\n    const [el] = useState(document.createElement('div'))\n    const rootPortal = document.getElementById('focalboard-root-portal')\n\n    useLayoutEffect(() => {\n        if (rootPortal) {\n            rootPortal.appendChild(el)\n        }\n        return () => {\n            if (rootPortal) {\n                rootPortal.removeChild(el)\n            }\n        }\n    }, [])\n\n    return ReactDOM.createPortal(props.children, el)  // eslint-disable-line\n}\n\nexport default React.memo(RootPortal)\n"
  },
  {
    "path": "webapp/src/components/searchDialog/searchDialog.scss",
    "content": "\n.BoardSwitcherDialog {\n    color: rgba(var(--center-channel-color-rgb));\n\n    .wrapper {\n        .dialog {\n            position: relative;\n            width: 600px;\n            height: 450px;\n        }\n    }\n\n    .BoardSwitcherDialogBody {\n        display: flex;\n        flex-direction: column;\n        flex: 1;\n        overflow: hidden;\n        border-color: rgba(var(--center-channel-color-rgb), 0.16);\n        background: var(--center-channel-bg);\n        color: var(--center-channel-color);\n\n        .head,\n        .searchResults {\n            padding: 0 32px 24px;\n        }\n\n        .searchResults {\n            height: 100%;\n            overflow-y: auto;\n            overflow-x: hidden;\n            padding: 16px 0;\n            border-top: solid 1px rgba(var(--center-channel-color-rgb), 0.16);\n\n            .searchResult {\n                height: 40px;\n                justify-content: flex-start;\n                align-items: center;\n                display: flex;\n                padding: 0 32px;\n                cursor: pointer;\n                overflow: hidden;\n\n                &.freesize {\n                    height: unset;\n                }\n\n                &:hover,\n                &:focus {\n                    background: rgba(var(--center-channel-color-rgb), 0.08);\n                }\n            }\n\n            .iconWrapper {\n                width: 120px;\n                height: 120px;\n                background: rgba(var(--center-channel-color-rgb), 0.08);\n                border-radius: 50%;\n                display: flex;\n                /*! align-content: center; */\n                justify-content: center;\n                flex-direction: column;\n                align-items: center;\n            }\n\n            .CompassIcon.icon-magnify.MagnifyIcon {\n                font-size: 72px !important;\n                color: var(--button-bg);\n                display: inline-block;\n            }\n\n            .noResults {\n                display: flex;\n                flex-direction: column;\n                align-items: center;\n                gap: 6px;\n                width: 500px;\n                margin: 0 auto;\n                overflow: hidden;\n                word-wrap: anywhere;\n                margin-top: 30px;\n\n                .text-heading4 {\n                    line-height: 120%;\n                }\n\n                &.introScreen {\n                    margin-top: 48px;\n                }\n            }\n        }\n\n        .queryWrapper {\n            position: relative;\n            display: flex;\n            flex-direction: row;\n\n            .MagnifyIcon {\n                position: absolute;\n                left: 13px;\n                font-size: 18px;\n                width: 20px;\n                height: 100%;\n                opacity: 0.48;\n                display: flex;\n                align-items: center;\n            }\n\n            .searchQuery {\n                height: 48px;\n                font-size: 14px;\n                border-radius: 4px;\n                border: 1px solid rgba(var(--center-channel-color-rgb), 0.16);\n                background: var(--center-channel-bg);\n                color: var(--center-channel-color);\n                padding: 0 40px;\n                flex: 1;\n                transition: border 0.15s ease-in;\n\n                &:focus {\n                    border-color: var(--button-bg);\n                    box-shadow: inset 0 0 0 1px var(--button-bg);\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/searchDialog/searchDialog.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {ReactNode, useEffect, useMemo, useState} from 'react'\n\nimport './searchDialog.scss'\nimport {FormattedMessage} from 'react-intl'\n\nimport {debounce} from 'lodash'\n\nimport Dialog from '../dialog'\nimport {Utils} from '../../utils'\nimport Search from '../../widgets/icons/search'\nimport {Constants} from '../../constants'\n\ntype Props = {\n    onClose: () => void\n    title: string\n    subTitle?: string | ReactNode\n    searchHandler: (query: string) => Promise<ReactNode[]>\n    initialData?: ReactNode[]\n    selected: number\n    setSelected: (n: number) => void\n}\n\nexport const EmptySearch = (): JSX.Element => (\n    <div className='noResults introScreen'>\n        <div className='iconWrapper'>\n            <Search/>\n        </div>\n        <h4 className='text-heading4'>\n            <FormattedMessage\n                id='FindBoardsDialog.IntroText'\n                defaultMessage='Search for boards'\n            />\n        </h4>\n    </div>\n)\n\nexport const EmptyResults = (props: {query: string}): JSX.Element => (\n    <div className='noResults'>\n        <div className='iconWrapper'>\n            <Search/>\n        </div>\n        <h4 className='text-heading4'>\n            <FormattedMessage\n                id='FindBoardsDialog.NoResultsFor'\n                defaultMessage='No results for \"{searchQuery}\"'\n                values={{\n                    searchQuery: props.query,\n                }}\n            />\n        </h4>\n        <span>\n            <FormattedMessage\n                id='FindBoardsDialog.NoResultsSubtext'\n                defaultMessage='Check the spelling or try another search.'\n            />\n        </span>\n    </div>\n)\n\nconst SearchDialog = (props: Props): JSX.Element => {\n    const {selected, setSelected} = props\n    const [results, setResults] = useState<ReactNode[]>(props.initialData || [])\n    const [isSearching, setIsSearching] = useState<boolean>(false)\n    const [searchQuery, setSearchQuery] = useState<string>('')\n\n    const searchHandler = async (query: string): Promise<void> => {\n        setIsSearching(true)\n        setSelected(-1)\n        setSearchQuery(query)\n        const searchResults = await props.searchHandler(query)\n        setResults(searchResults)\n        setIsSearching(false)\n    }\n\n    const debouncedSearchHandler = useMemo(() => debounce(searchHandler, 200), [])\n\n    const emptyResult = results.length === 0 && !isSearching && searchQuery\n\n    const handleUpDownKeyPress = (e: KeyboardEvent) => {\n        if (Utils.isKeyPressed(e, Constants.keyCodes.DOWN)) {\n            e.preventDefault()\n            if (results.length > 0) {\n                setSelected(((selected + 1) < results.length) ? (selected + 1) : selected)\n            }\n        }\n\n        if (Utils.isKeyPressed(e, Constants.keyCodes.UP)) {\n            e.preventDefault()\n            if (results.length > 0) {\n                setSelected(((selected - 1) > -1) ? (selected - 1) : selected)\n            }\n        }\n    }\n\n    useEffect(() => {\n        document.addEventListener('keydown', handleUpDownKeyPress)\n\n        // cleanup function\n        return () => {\n            document.removeEventListener('keydown', handleUpDownKeyPress)\n        }\n    }, [results, selected])\n\n    return (\n        <Dialog\n            title={<div>{props.title}</div>}\n            subtitle={<div>{props.subTitle}</div>}\n            className='BoardSwitcherDialog'\n            onClose={props.onClose}\n        >\n            <div className='BoardSwitcherDialogBody'>\n                <div className='head'>\n                    <div className='queryWrapper'>\n                        <Search/>\n                        <input\n                            className='searchQuery'\n                            placeholder='Search for boards'\n                            type='text'\n                            onChange={(e) => debouncedSearchHandler(e.target.value)}\n                            autoFocus={true}\n                            maxLength={100}\n                        />\n                    </div>\n                </div>\n                <div className='searchResults'>\n                    {/*When there are results to show*/}\n                    {searchQuery && results.length > 0 &&\n                        results.map((result) => (\n                            <div\n                                key={Utils.uuid()}\n                                className='searchResult'\n                                tabIndex={-1}\n                            >\n                                {result}\n                            </div>\n                        ))\n                    }\n\n                    {/*when user searched for something and there were no results*/}\n                    {emptyResult && <EmptyResults query={searchQuery}/>}\n\n                    {/*default state, when user didn't search for anything. This is the initial screen*/}\n                    {!emptyResult && !searchQuery && <EmptySearch/>}\n                </div>\n            </div>\n        </Dialog>\n    )\n}\n\nexport default SearchDialog\n"
  },
  {
    "path": "webapp/src/components/shareBoard/__snapshots__/shareBoard.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`src/components/shareBoard/shareBoard return shareBoard and click Copy link 1`] = `\n<div>\n  <div\n    class=\"Dialog dialog-back ShareBoardDialog size--medium\"\n  >\n    <div\n      class=\"backdrop\"\n    />\n    <div\n      class=\"wrapper\"\n    >\n      <div\n        class=\"dialog\"\n        role=\"dialog\"\n      >\n        <div\n          class=\"toolbar\"\n        >\n          <div>\n            <h1\n              class=\"dialog-title\"\n            >\n              Share Board\n            </h1>\n          </div>\n          <div\n            class=\"toolbar--right\"\n          >\n            <button\n              aria-label=\"Close dialog\"\n              title=\"Close dialog\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-close CloseIcon\"\n              />\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"share-input__container\"\n        >\n          <div\n            class=\"share-input\"\n          >\n            <i\n              class=\"CompassIcon icon-magnify MagnifyIcon\"\n            />\n            <div\n              class=\"userSearchInput css-b62m3t-container\"\n            >\n              <span\n                class=\"css-1f43avz-a11yText-A11yText\"\n                id=\"react-select-4-live-region\"\n              />\n              <span\n                aria-atomic=\"false\"\n                aria-live=\"polite\"\n                aria-relevant=\"additions text\"\n                class=\"css-1f43avz-a11yText-A11yText\"\n                role=\"log\"\n              />\n              <div\n                class=\" css-1wmrr75-Control\"\n              >\n                <div\n                  class=\" css-b2z5qd-ValueContainer\"\n                >\n                  <div\n                    class=\" css-1jqq78o-placeholder\"\n                    id=\"react-select-4-placeholder\"\n                  >\n                    Search for people and channels\n                  </div>\n                  <div\n                    class=\" css-1p5v8kp-Input\"\n                    data-value=\"\"\n                  >\n                    <input\n                      aria-activedescendant=\"\"\n                      aria-autocomplete=\"list\"\n                      aria-describedby=\"react-select-4-placeholder\"\n                      aria-expanded=\"false\"\n                      aria-haspopup=\"true\"\n                      autocapitalize=\"none\"\n                      autocomplete=\"off\"\n                      autocorrect=\"off\"\n                      class=\"\"\n                      id=\"react-select-4-input\"\n                      role=\"combobox\"\n                      spellcheck=\"false\"\n                      style=\"opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;\"\n                      tabindex=\"0\"\n                      type=\"text\"\n                      value=\"\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\" css-1hb7zxy-IndicatorsContainer\"\n                />\n              </div>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"user-items\"\n        >\n          <div\n            class=\"user-item\"\n          >\n            <div\n              class=\"user-item__content\"\n            >\n              <div\n                class=\"ml-3\"\n              >\n                <strong>\n                  Everyone at Test Team Team\n                </strong>\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"user-item__button\"\n                >\n                  None\n                  <i\n                    class=\"CompassIcon icon-chevron-down CompassIcon\"\n                  />\n                </button>\n              </div>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"tabs-container\"\n        >\n          <button\n            class=\"tab-item tab-item--active\"\n          >\n            Share\n          </button>\n          <button\n            class=\"tab-item false\"\n          >\n            Publish\n          </button>\n        </div>\n        <div\n          class=\"tabs-content\"\n        >\n          <div>\n            <div\n              class=\"d-flex justify-content-between\"\n            >\n              <div\n                class=\"d-flex flex-column\"\n              >\n                <div\n                  class=\"text-heading2\"\n                >\n                  Share internally\n                </div>\n                <div\n                  class=\"text-light\"\n                >\n                  Users who have permissions will be able to use this link.\n                </div>\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"d-flex justify-content-between tabs-inputs\"\n          >\n            <div\n              class=\"d-flex input-container\"\n            >\n              <a\n                class=\"shareUrl\"\n                href=\"http://localhost/1/1\"\n                rel=\"noreferrer\"\n                target=\"_blank\"\n              >\n                http://localhost/1/1\n              </a>\n            </div>\n            <button\n              title=\"Copy link\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-content-copy CompassIcon\"\n              />\n              <span>\n                Copy link\n              </span>\n            </button>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`src/components/shareBoard/shareBoard return shareBoard and click Copy link 2`] = `\n<div>\n  <div\n    class=\"Dialog dialog-back ShareBoardDialog size--medium\"\n  >\n    <div\n      class=\"backdrop\"\n    />\n    <div\n      class=\"wrapper\"\n    >\n      <div\n        class=\"dialog\"\n        role=\"dialog\"\n      >\n        <div\n          class=\"toolbar\"\n        >\n          <div>\n            <h1\n              class=\"dialog-title\"\n            >\n              Share Board\n            </h1>\n          </div>\n          <div\n            class=\"toolbar--right\"\n          >\n            <button\n              aria-label=\"Close dialog\"\n              title=\"Close dialog\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-close CloseIcon\"\n              />\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"share-input__container\"\n        >\n          <div\n            class=\"share-input\"\n          >\n            <i\n              class=\"CompassIcon icon-magnify MagnifyIcon\"\n            />\n            <div\n              class=\"userSearchInput css-b62m3t-container\"\n            >\n              <span\n                class=\"css-1f43avz-a11yText-A11yText\"\n                id=\"react-select-4-live-region\"\n              />\n              <span\n                aria-atomic=\"false\"\n                aria-live=\"polite\"\n                aria-relevant=\"additions text\"\n                class=\"css-1f43avz-a11yText-A11yText\"\n                role=\"log\"\n              />\n              <div\n                class=\" css-1wmrr75-Control\"\n              >\n                <div\n                  class=\" css-b2z5qd-ValueContainer\"\n                >\n                  <div\n                    class=\" css-1jqq78o-placeholder\"\n                    id=\"react-select-4-placeholder\"\n                  >\n                    Search for people and channels\n                  </div>\n                  <div\n                    class=\" css-1p5v8kp-Input\"\n                    data-value=\"\"\n                  >\n                    <input\n                      aria-activedescendant=\"\"\n                      aria-autocomplete=\"list\"\n                      aria-describedby=\"react-select-4-placeholder\"\n                      aria-expanded=\"false\"\n                      aria-haspopup=\"true\"\n                      autocapitalize=\"none\"\n                      autocomplete=\"off\"\n                      autocorrect=\"off\"\n                      class=\"\"\n                      id=\"react-select-4-input\"\n                      role=\"combobox\"\n                      spellcheck=\"false\"\n                      style=\"opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;\"\n                      tabindex=\"0\"\n                      type=\"text\"\n                      value=\"\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\" css-1hb7zxy-IndicatorsContainer\"\n                />\n              </div>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"user-items\"\n        >\n          <div\n            class=\"user-item\"\n          >\n            <div\n              class=\"user-item__content\"\n            >\n              <div\n                class=\"ml-3\"\n              >\n                <strong>\n                  Everyone at Test Team Team\n                </strong>\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"user-item__button\"\n                >\n                  None\n                  <i\n                    class=\"CompassIcon icon-chevron-down CompassIcon\"\n                  />\n                </button>\n              </div>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"tabs-container\"\n        >\n          <button\n            class=\"tab-item tab-item--active\"\n          >\n            Share\n          </button>\n          <button\n            class=\"tab-item false\"\n          >\n            Publish\n          </button>\n        </div>\n        <div\n          class=\"tabs-content\"\n        >\n          <div>\n            <div\n              class=\"d-flex justify-content-between\"\n            >\n              <div\n                class=\"d-flex flex-column\"\n              >\n                <div\n                  class=\"text-heading2\"\n                >\n                  Share internally\n                </div>\n                <div\n                  class=\"text-light\"\n                >\n                  Users who have permissions will be able to use this link.\n                </div>\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"d-flex justify-content-between tabs-inputs\"\n          >\n            <div\n              class=\"d-flex input-container\"\n            >\n              <a\n                class=\"shareUrl\"\n                href=\"http://localhost/1/1\"\n                rel=\"noreferrer\"\n                target=\"_blank\"\n              >\n                http://localhost/1/1\n              </a>\n            </div>\n            <button\n              title=\"Copy link\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-content-copy CompassIcon\"\n              />\n              <span>\n                Copied!\n              </span>\n            </button>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`src/components/shareBoard/shareBoard return shareBoard and click Regenerate token 1`] = `\n<div>\n  <div\n    class=\"Dialog dialog-back ShareBoardDialog size--medium\"\n  >\n    <div\n      class=\"backdrop\"\n    />\n    <div\n      class=\"wrapper\"\n    >\n      <div\n        class=\"dialog\"\n        role=\"dialog\"\n      >\n        <div\n          class=\"toolbar\"\n        >\n          <div>\n            <h1\n              class=\"dialog-title\"\n            >\n              Share Board\n            </h1>\n          </div>\n          <div\n            class=\"toolbar--right\"\n          >\n            <button\n              aria-label=\"Close dialog\"\n              title=\"Close dialog\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-close CloseIcon\"\n              />\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"share-input__container\"\n        >\n          <div\n            class=\"share-input\"\n          >\n            <i\n              class=\"CompassIcon icon-magnify MagnifyIcon\"\n            />\n            <div\n              class=\"userSearchInput css-b62m3t-container\"\n            >\n              <span\n                class=\"css-1f43avz-a11yText-A11yText\"\n                id=\"react-select-5-live-region\"\n              />\n              <span\n                aria-atomic=\"false\"\n                aria-live=\"polite\"\n                aria-relevant=\"additions text\"\n                class=\"css-1f43avz-a11yText-A11yText\"\n                role=\"log\"\n              />\n              <div\n                class=\" css-1wmrr75-Control\"\n              >\n                <div\n                  class=\" css-b2z5qd-ValueContainer\"\n                >\n                  <div\n                    class=\" css-1jqq78o-placeholder\"\n                    id=\"react-select-5-placeholder\"\n                  >\n                    Search for people and channels\n                  </div>\n                  <div\n                    class=\" css-1p5v8kp-Input\"\n                    data-value=\"\"\n                  >\n                    <input\n                      aria-activedescendant=\"\"\n                      aria-autocomplete=\"list\"\n                      aria-describedby=\"react-select-5-placeholder\"\n                      aria-expanded=\"false\"\n                      aria-haspopup=\"true\"\n                      autocapitalize=\"none\"\n                      autocomplete=\"off\"\n                      autocorrect=\"off\"\n                      class=\"\"\n                      id=\"react-select-5-input\"\n                      role=\"combobox\"\n                      spellcheck=\"false\"\n                      style=\"opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;\"\n                      tabindex=\"0\"\n                      type=\"text\"\n                      value=\"\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\" css-1hb7zxy-IndicatorsContainer\"\n                />\n              </div>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"user-items\"\n        >\n          <div\n            class=\"user-item\"\n          >\n            <div\n              class=\"user-item__content\"\n            >\n              <div\n                class=\"ml-3\"\n              >\n                <strong>\n                  Everyone at Test Team Team\n                </strong>\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"user-item__button\"\n                >\n                  None\n                  <i\n                    class=\"CompassIcon icon-chevron-down CompassIcon\"\n                  />\n                </button>\n              </div>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"tabs-container\"\n        >\n          <button\n            class=\"tab-item false\"\n          >\n            Share\n          </button>\n          <button\n            class=\"tab-item tab-item--active\"\n          >\n            Publish\n          </button>\n        </div>\n        <div\n          class=\"tabs-content\"\n        >\n          <div>\n            <div\n              class=\"d-flex justify-content-between\"\n            >\n              <div\n                class=\"d-flex flex-column\"\n              >\n                <div\n                  class=\"text-heading2\"\n                >\n                  Publish to the web\n                </div>\n                <div\n                  class=\"text-light\"\n                >\n                  Publish and share a read-only link with everyone on the web.\n                </div>\n              </div>\n              <div>\n                <div\n                  class=\"Switch override-main size--medium on\"\n                >\n                  <div\n                    class=\"octo-switch-inner\"\n                  />\n                </div>\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"d-flex justify-content-between tabs-inputs\"\n          >\n            <div\n              class=\"d-flex input-container\"\n            >\n              <a\n                class=\"shareUrl\"\n                href=\"http://localhost/shared/1/1?r=anotherToken\"\n                rel=\"noreferrer\"\n                target=\"_blank\"\n              >\n                http://localhost/shared/1/1?r=anotherToken\n              </a>\n              <div\n                class=\"octo-tooltip tooltip-top\"\n                data-tooltip=\"Regenerate token\"\n              >\n                <button\n                  aria-label=\"Regenerate token\"\n                  title=\"Regenerate token\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-refresh\"\n                  />\n                </button>\n              </div>\n            </div>\n            <button\n              title=\"Copy public link\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-content-copy CompassIcon\"\n              />\n              <span>\n                Copy link\n              </span>\n            </button>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`src/components/shareBoard/shareBoard return shareBoard and click Select 1`] = `\n<div>\n  <div\n    class=\"Dialog dialog-back ShareBoardDialog size--medium\"\n  >\n    <div\n      class=\"backdrop\"\n    />\n    <div\n      class=\"wrapper\"\n    >\n      <div\n        class=\"dialog\"\n        role=\"dialog\"\n      >\n        <div\n          class=\"toolbar\"\n        >\n          <div>\n            <h1\n              class=\"dialog-title\"\n            >\n              Share Board\n            </h1>\n          </div>\n          <div\n            class=\"toolbar--right\"\n          >\n            <button\n              aria-label=\"Close dialog\"\n              title=\"Close dialog\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-close CloseIcon\"\n              />\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"share-input__container\"\n        >\n          <div\n            class=\"share-input\"\n          >\n            <i\n              class=\"CompassIcon icon-magnify MagnifyIcon\"\n            />\n            <div\n              class=\"userSearchInput css-b62m3t-container\"\n            >\n              <span\n                class=\"css-1f43avz-a11yText-A11yText\"\n                id=\"react-select-10-live-region\"\n              />\n              <span\n                aria-atomic=\"false\"\n                aria-live=\"polite\"\n                aria-relevant=\"additions text\"\n                class=\"css-1f43avz-a11yText-A11yText\"\n                role=\"log\"\n              />\n              <div\n                class=\" css-1wmrr75-Control\"\n              >\n                <div\n                  class=\" css-b2z5qd-ValueContainer\"\n                >\n                  <div\n                    class=\" css-1jqq78o-placeholder\"\n                    id=\"react-select-10-placeholder\"\n                  >\n                    Search for people and channels\n                  </div>\n                  <div\n                    class=\" css-1p5v8kp-Input\"\n                    data-value=\"\"\n                  >\n                    <input\n                      aria-activedescendant=\"\"\n                      aria-autocomplete=\"list\"\n                      aria-describedby=\"react-select-10-placeholder\"\n                      aria-expanded=\"false\"\n                      aria-haspopup=\"true\"\n                      autocapitalize=\"none\"\n                      autocomplete=\"off\"\n                      autocorrect=\"off\"\n                      class=\"\"\n                      id=\"react-select-10-input\"\n                      role=\"combobox\"\n                      spellcheck=\"false\"\n                      style=\"opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;\"\n                      tabindex=\"0\"\n                      type=\"text\"\n                      value=\"\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\" css-1hb7zxy-IndicatorsContainer\"\n                />\n              </div>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"user-items\"\n        >\n          <div\n            class=\"user-item\"\n          >\n            <div\n              class=\"user-item__content\"\n            >\n              <div\n                class=\"ml-3\"\n              >\n                <strong>\n                  Everyone at Test Team Team\n                </strong>\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"user-item__button\"\n                >\n                  None\n                  <i\n                    class=\"CompassIcon icon-chevron-down CompassIcon\"\n                  />\n                </button>\n              </div>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"tabs-content\"\n        >\n          <div>\n            <div\n              class=\"d-flex justify-content-between\"\n            >\n              <div\n                class=\"d-flex flex-column\"\n              >\n                <div\n                  class=\"text-heading2\"\n                >\n                  Share internally\n                </div>\n                <div\n                  class=\"text-light\"\n                >\n                  Users who have permissions will be able to use this link.\n                </div>\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"d-flex justify-content-between tabs-inputs\"\n          >\n            <div\n              class=\"d-flex input-container\"\n            >\n              <a\n                class=\"shareUrl\"\n                href=\"http://localhost/1/1\"\n                rel=\"noreferrer\"\n                target=\"_blank\"\n              >\n                http://localhost/1/1\n              </a>\n            </div>\n            <button\n              title=\"Copy link\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-content-copy CompassIcon\"\n              />\n              <span>\n                Copy link\n              </span>\n            </button>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`src/components/shareBoard/shareBoard return shareBoard and click Select 2`] = `\n<div>\n  <div\n    class=\"Dialog dialog-back ShareBoardDialog size--medium\"\n  >\n    <div\n      class=\"backdrop\"\n    />\n    <div\n      class=\"wrapper\"\n    >\n      <div\n        class=\"dialog\"\n        role=\"dialog\"\n      >\n        <div\n          class=\"toolbar\"\n        >\n          <div>\n            <h1\n              class=\"dialog-title\"\n            >\n              Share Board\n            </h1>\n          </div>\n          <div\n            class=\"toolbar--right\"\n          >\n            <button\n              aria-label=\"Close dialog\"\n              title=\"Close dialog\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-close CloseIcon\"\n              />\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"share-input__container\"\n        >\n          <div\n            class=\"share-input\"\n          >\n            <i\n              class=\"CompassIcon icon-magnify MagnifyIcon\"\n            />\n            <div\n              class=\"userSearchInput css-b62m3t-container\"\n            >\n              <span\n                class=\"css-1f43avz-a11yText-A11yText\"\n                id=\"react-select-10-live-region\"\n              />\n              <span\n                aria-atomic=\"false\"\n                aria-live=\"polite\"\n                aria-relevant=\"additions text\"\n                class=\"css-1f43avz-a11yText-A11yText\"\n                role=\"log\"\n              >\n                <span\n                  id=\"aria-selection\"\n                />\n                <span\n                  id=\"aria-focused\"\n                />\n                <span\n                  id=\"aria-results\"\n                >\n                  4 results available.\n                </span>\n                <span\n                  id=\"aria-guidance\"\n                >\n                  Use Up and Down to choose options, press Enter to select the currently focused option, press Escape to exit the menu, press Tab to select the option and exit the menu.\n                </span>\n              </span>\n              <div\n                class=\" css-1wmrr75-Control\"\n              >\n                <div\n                  class=\" css-b2z5qd-ValueContainer\"\n                >\n                  <div\n                    class=\" css-1jqq78o-placeholder\"\n                    id=\"react-select-10-placeholder\"\n                  >\n                    Search for people and channels\n                  </div>\n                  <div\n                    class=\" css-1p5v8kp-Input\"\n                    data-value=\"\"\n                  >\n                    <input\n                      aria-activedescendant=\"react-select-10-option-0\"\n                      aria-autocomplete=\"list\"\n                      aria-controls=\"react-select-10-listbox\"\n                      aria-describedby=\"react-select-10-placeholder\"\n                      aria-expanded=\"true\"\n                      aria-haspopup=\"true\"\n                      autocapitalize=\"none\"\n                      autocomplete=\"off\"\n                      autocorrect=\"off\"\n                      class=\"\"\n                      id=\"react-select-10-input\"\n                      role=\"combobox\"\n                      spellcheck=\"false\"\n                      style=\"opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;\"\n                      tabindex=\"0\"\n                      type=\"text\"\n                      value=\"\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\" css-1hb7zxy-IndicatorsContainer\"\n                />\n              </div>\n              <div\n                class=\" css-45h7mv-menu\"\n              >\n                <div\n                  aria-multiselectable=\"false\"\n                  class=\" css-1d1qzc4-MenuList\"\n                  id=\"react-select-10-listbox\"\n                  role=\"listbox\"\n                >\n                  <div\n                    aria-disabled=\"false\"\n                    aria-selected=\"false\"\n                    class=\" css-8e5kjb-option\"\n                    id=\"react-select-10-option-0\"\n                    role=\"option\"\n                    tabindex=\"-1\"\n                  >\n                    <div\n                      class=\"user-item\"\n                    >\n                      <div\n                        class=\"ml-3\"\n                      >\n                        <strong>\n                          username_1\n                        </strong>\n                        <strong\n                          class=\"ml-2 text-light\"\n                        >\n                          @username_1\n                        </strong>\n                      </div>\n                    </div>\n                  </div>\n                  <div\n                    aria-disabled=\"false\"\n                    aria-selected=\"false\"\n                    class=\" css-x3yilo-option\"\n                    id=\"react-select-10-option-1\"\n                    role=\"option\"\n                    tabindex=\"-1\"\n                  >\n                    <div\n                      class=\"user-item\"\n                    >\n                      <div\n                        class=\"ml-3\"\n                      >\n                        <strong>\n                          username_2\n                        </strong>\n                        <strong\n                          class=\"ml-2 text-light\"\n                        >\n                          @username_2\n                        </strong>\n                      </div>\n                    </div>\n                  </div>\n                  <div\n                    aria-disabled=\"false\"\n                    aria-selected=\"false\"\n                    class=\" css-x3yilo-option\"\n                    id=\"react-select-10-option-2\"\n                    role=\"option\"\n                    tabindex=\"-1\"\n                  >\n                    <div\n                      class=\"user-item\"\n                    >\n                      <div\n                        class=\"ml-3\"\n                      >\n                        <strong>\n                          username_3\n                        </strong>\n                        <strong\n                          class=\"ml-2 text-light\"\n                        >\n                          @username_3\n                        </strong>\n                      </div>\n                    </div>\n                  </div>\n                  <div\n                    aria-disabled=\"false\"\n                    aria-selected=\"false\"\n                    class=\" css-x3yilo-option\"\n                    id=\"react-select-10-option-3\"\n                    role=\"option\"\n                    tabindex=\"-1\"\n                  >\n                    <div\n                      class=\"user-item\"\n                    >\n                      <div\n                        class=\"ml-3\"\n                      >\n                        <strong>\n                          username_4\n                        </strong>\n                        <strong\n                          class=\"ml-2 text-light\"\n                        >\n                          @username_4\n                        </strong>\n                      </div>\n                    </div>\n                  </div>\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"user-items\"\n        >\n          <div\n            class=\"user-item\"\n          >\n            <div\n              class=\"user-item__content\"\n            >\n              <div\n                class=\"ml-3\"\n              >\n                <strong>\n                  Everyone at Test Team Team\n                </strong>\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"user-item__button\"\n                >\n                  None\n                  <i\n                    class=\"CompassIcon icon-chevron-down CompassIcon\"\n                  />\n                </button>\n              </div>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"tabs-content\"\n        >\n          <div>\n            <div\n              class=\"d-flex justify-content-between\"\n            >\n              <div\n                class=\"d-flex flex-column\"\n              >\n                <div\n                  class=\"text-heading2\"\n                >\n                  Share internally\n                </div>\n                <div\n                  class=\"text-light\"\n                >\n                  Users who have permissions will be able to use this link.\n                </div>\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"d-flex justify-content-between tabs-inputs\"\n          >\n            <div\n              class=\"d-flex input-container\"\n            >\n              <a\n                class=\"shareUrl\"\n                href=\"http://localhost/1/1\"\n                rel=\"noreferrer\"\n                target=\"_blank\"\n              >\n                http://localhost/1/1\n              </a>\n            </div>\n            <button\n              title=\"Copy link\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-content-copy CompassIcon\"\n              />\n              <span>\n                Copy link\n              </span>\n            </button>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`src/components/shareBoard/shareBoard return shareBoard and click Select, non-plugin mode 1`] = `\n<div>\n  <div\n    class=\"Dialog dialog-back ShareBoardDialog size--medium\"\n  >\n    <div\n      class=\"backdrop\"\n    />\n    <div\n      class=\"wrapper\"\n    >\n      <div\n        class=\"dialog\"\n        role=\"dialog\"\n      >\n        <div\n          class=\"toolbar\"\n        >\n          <div>\n            <h1\n              class=\"dialog-title\"\n            >\n              Share Board\n            </h1>\n          </div>\n          <div\n            class=\"toolbar--right\"\n          >\n            <button\n              aria-label=\"Close dialog\"\n              title=\"Close dialog\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-close CloseIcon\"\n              />\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"share-input__container\"\n        >\n          <div\n            class=\"share-input\"\n          >\n            <i\n              class=\"CompassIcon icon-magnify MagnifyIcon\"\n            />\n            <div\n              class=\"userSearchInput css-b62m3t-container\"\n            >\n              <span\n                class=\"css-1f43avz-a11yText-A11yText\"\n                id=\"react-select-11-live-region\"\n              />\n              <span\n                aria-atomic=\"false\"\n                aria-live=\"polite\"\n                aria-relevant=\"additions text\"\n                class=\"css-1f43avz-a11yText-A11yText\"\n                role=\"log\"\n              />\n              <div\n                class=\" css-1wmrr75-Control\"\n              >\n                <div\n                  class=\" css-b2z5qd-ValueContainer\"\n                >\n                  <div\n                    class=\" css-1jqq78o-placeholder\"\n                    id=\"react-select-11-placeholder\"\n                  >\n                    Search for people and channels\n                  </div>\n                  <div\n                    class=\" css-1p5v8kp-Input\"\n                    data-value=\"\"\n                  >\n                    <input\n                      aria-activedescendant=\"\"\n                      aria-autocomplete=\"list\"\n                      aria-describedby=\"react-select-11-placeholder\"\n                      aria-expanded=\"false\"\n                      aria-haspopup=\"true\"\n                      autocapitalize=\"none\"\n                      autocomplete=\"off\"\n                      autocorrect=\"off\"\n                      class=\"\"\n                      id=\"react-select-11-input\"\n                      role=\"combobox\"\n                      spellcheck=\"false\"\n                      style=\"opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;\"\n                      tabindex=\"0\"\n                      type=\"text\"\n                      value=\"\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\" css-1hb7zxy-IndicatorsContainer\"\n                />\n              </div>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"user-items\"\n        >\n          <div\n            class=\"user-item\"\n          >\n            <div\n              class=\"user-item__content\"\n            >\n              <div\n                class=\"ml-3\"\n              >\n                <strong>\n                  Everyone at Test Team Team\n                </strong>\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"user-item__button\"\n                >\n                  None\n                  <i\n                    class=\"CompassIcon icon-chevron-down CompassIcon\"\n                  />\n                </button>\n              </div>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"tabs-content\"\n        >\n          <div>\n            <div\n              class=\"d-flex justify-content-between\"\n            >\n              <div\n                class=\"d-flex flex-column\"\n              >\n                <div\n                  class=\"text-heading2\"\n                >\n                  Share internally\n                </div>\n                <div\n                  class=\"text-light\"\n                >\n                  Users who have permissions will be able to use this link.\n                </div>\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"d-flex justify-content-between tabs-inputs\"\n          >\n            <div\n              class=\"d-flex input-container\"\n            >\n              <a\n                class=\"shareUrl\"\n                href=\"http://localhost/1/1\"\n                rel=\"noreferrer\"\n                target=\"_blank\"\n              >\n                http://localhost/1/1\n              </a>\n            </div>\n            <button\n              title=\"Copy link\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-content-copy CompassIcon\"\n              />\n              <span>\n                Copy link\n              </span>\n            </button>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`src/components/shareBoard/shareBoard return shareBoard and click Select, non-plugin mode 2`] = `\n<div>\n  <div\n    class=\"Dialog dialog-back ShareBoardDialog size--medium\"\n  >\n    <div\n      class=\"backdrop\"\n    />\n    <div\n      class=\"wrapper\"\n    >\n      <div\n        class=\"dialog\"\n        role=\"dialog\"\n      >\n        <div\n          class=\"toolbar\"\n        >\n          <div>\n            <h1\n              class=\"dialog-title\"\n            >\n              Share Board\n            </h1>\n          </div>\n          <div\n            class=\"toolbar--right\"\n          >\n            <button\n              aria-label=\"Close dialog\"\n              title=\"Close dialog\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-close CloseIcon\"\n              />\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"share-input__container\"\n        >\n          <div\n            class=\"share-input\"\n          >\n            <i\n              class=\"CompassIcon icon-magnify MagnifyIcon\"\n            />\n            <div\n              class=\"userSearchInput css-b62m3t-container\"\n            >\n              <span\n                class=\"css-1f43avz-a11yText-A11yText\"\n                id=\"react-select-11-live-region\"\n              />\n              <span\n                aria-atomic=\"false\"\n                aria-live=\"polite\"\n                aria-relevant=\"additions text\"\n                class=\"css-1f43avz-a11yText-A11yText\"\n                role=\"log\"\n              >\n                <span\n                  id=\"aria-selection\"\n                />\n                <span\n                  id=\"aria-focused\"\n                />\n                <span\n                  id=\"aria-results\"\n                >\n                  4 results available.\n                </span>\n                <span\n                  id=\"aria-guidance\"\n                >\n                  Use Up and Down to choose options, press Enter to select the currently focused option, press Escape to exit the menu, press Tab to select the option and exit the menu.\n                </span>\n              </span>\n              <div\n                class=\" css-1wmrr75-Control\"\n              >\n                <div\n                  class=\" css-b2z5qd-ValueContainer\"\n                >\n                  <div\n                    class=\" css-1jqq78o-placeholder\"\n                    id=\"react-select-11-placeholder\"\n                  >\n                    Search for people and channels\n                  </div>\n                  <div\n                    class=\" css-1p5v8kp-Input\"\n                    data-value=\"\"\n                  >\n                    <input\n                      aria-activedescendant=\"react-select-11-option-0\"\n                      aria-autocomplete=\"list\"\n                      aria-controls=\"react-select-11-listbox\"\n                      aria-describedby=\"react-select-11-placeholder\"\n                      aria-expanded=\"true\"\n                      aria-haspopup=\"true\"\n                      autocapitalize=\"none\"\n                      autocomplete=\"off\"\n                      autocorrect=\"off\"\n                      class=\"\"\n                      id=\"react-select-11-input\"\n                      role=\"combobox\"\n                      spellcheck=\"false\"\n                      style=\"opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;\"\n                      tabindex=\"0\"\n                      type=\"text\"\n                      value=\"\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\" css-1hb7zxy-IndicatorsContainer\"\n                />\n              </div>\n              <div\n                class=\" css-45h7mv-menu\"\n              >\n                <div\n                  aria-multiselectable=\"false\"\n                  class=\" css-1d1qzc4-MenuList\"\n                  id=\"react-select-11-listbox\"\n                  role=\"listbox\"\n                >\n                  <div\n                    aria-disabled=\"false\"\n                    aria-selected=\"false\"\n                    class=\" css-8e5kjb-option\"\n                    id=\"react-select-11-option-0\"\n                    role=\"option\"\n                    tabindex=\"-1\"\n                  >\n                    <div\n                      class=\"user-item\"\n                    >\n                      <div\n                        class=\"ml-3\"\n                      >\n                        <strong>\n                          username_1\n                        </strong>\n                        <strong\n                          class=\"ml-2 text-light\"\n                        >\n                          @username_1\n                        </strong>\n                        <div\n                          class=\"AdminBadge\"\n                        >\n                          <div\n                            class=\"AdminBadge__box\"\n                          >\n                            Team Admin\n                          </div>\n                        </div>\n                      </div>\n                    </div>\n                  </div>\n                  <div\n                    aria-disabled=\"false\"\n                    aria-selected=\"false\"\n                    class=\" css-x3yilo-option\"\n                    id=\"react-select-11-option-1\"\n                    role=\"option\"\n                    tabindex=\"-1\"\n                  >\n                    <div\n                      class=\"user-item\"\n                    >\n                      <div\n                        class=\"ml-3\"\n                      >\n                        <strong>\n                          username_2\n                        </strong>\n                        <strong\n                          class=\"ml-2 text-light\"\n                        >\n                          @username_2\n                        </strong>\n                        <div\n                          class=\"AdminBadge\"\n                        >\n                          <div\n                            class=\"AdminBadge__box\"\n                          >\n                            Admin\n                          </div>\n                        </div>\n                      </div>\n                    </div>\n                  </div>\n                  <div\n                    aria-disabled=\"false\"\n                    aria-selected=\"false\"\n                    class=\" css-x3yilo-option\"\n                    id=\"react-select-11-option-2\"\n                    role=\"option\"\n                    tabindex=\"-1\"\n                  >\n                    <div\n                      class=\"user-item\"\n                    >\n                      <div\n                        class=\"ml-3\"\n                      >\n                        <strong>\n                          username_3\n                        </strong>\n                        <strong\n                          class=\"ml-2 text-light\"\n                        >\n                          @username_3\n                        </strong>\n                      </div>\n                    </div>\n                  </div>\n                  <div\n                    aria-disabled=\"false\"\n                    aria-selected=\"false\"\n                    class=\" css-x3yilo-option\"\n                    id=\"react-select-11-option-3\"\n                    role=\"option\"\n                    tabindex=\"-1\"\n                  >\n                    <div\n                      class=\"user-item\"\n                    >\n                      <div\n                        class=\"ml-3\"\n                      >\n                        <strong>\n                          username_4\n                        </strong>\n                        <strong\n                          class=\"ml-2 text-light\"\n                        >\n                          @username_4\n                        </strong>\n                      </div>\n                    </div>\n                  </div>\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"user-items\"\n        >\n          <div\n            class=\"user-item\"\n          >\n            <div\n              class=\"user-item__content\"\n            >\n              <div\n                class=\"ml-3\"\n              >\n                <strong>\n                  Everyone at Test Team Team\n                </strong>\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"user-item__button\"\n                >\n                  None\n                  <i\n                    class=\"CompassIcon icon-chevron-down CompassIcon\"\n                  />\n                </button>\n              </div>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"tabs-content\"\n        >\n          <div>\n            <div\n              class=\"d-flex justify-content-between\"\n            >\n              <div\n                class=\"d-flex flex-column\"\n              >\n                <div\n                  class=\"text-heading2\"\n                >\n                  Share internally\n                </div>\n                <div\n                  class=\"text-light\"\n                >\n                  Users who have permissions will be able to use this link.\n                </div>\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"d-flex justify-content-between tabs-inputs\"\n          >\n            <div\n              class=\"d-flex input-container\"\n            >\n              <a\n                class=\"shareUrl\"\n                href=\"http://localhost/1/1\"\n                rel=\"noreferrer\"\n                target=\"_blank\"\n              >\n                http://localhost/1/1\n              </a>\n            </div>\n            <button\n              title=\"Copy link\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-content-copy CompassIcon\"\n              />\n              <span>\n                Copy link\n              </span>\n            </button>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`src/components/shareBoard/shareBoard return shareBoard template and click Select 1`] = `\n<div>\n  <div\n    class=\"Dialog dialog-back ShareBoardDialog size--medium\"\n  >\n    <div\n      class=\"backdrop\"\n    />\n    <div\n      class=\"wrapper\"\n    >\n      <div\n        class=\"dialog\"\n        role=\"dialog\"\n      >\n        <div\n          class=\"toolbar\"\n        >\n          <div>\n            <h1\n              class=\"dialog-title\"\n            >\n              Share Template\n            </h1>\n          </div>\n          <div\n            class=\"toolbar--right\"\n          >\n            <button\n              aria-label=\"Close dialog\"\n              title=\"Close dialog\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-close CloseIcon\"\n              />\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"share-input__container\"\n        >\n          <div\n            class=\"share-input\"\n          >\n            <i\n              class=\"CompassIcon icon-magnify MagnifyIcon\"\n            />\n            <div\n              class=\"userSearchInput css-b62m3t-container\"\n            >\n              <span\n                class=\"css-1f43avz-a11yText-A11yText\"\n                id=\"react-select-13-live-region\"\n              />\n              <span\n                aria-atomic=\"false\"\n                aria-live=\"polite\"\n                aria-relevant=\"additions text\"\n                class=\"css-1f43avz-a11yText-A11yText\"\n                role=\"log\"\n              />\n              <div\n                class=\" css-1wmrr75-Control\"\n              >\n                <div\n                  class=\" css-b2z5qd-ValueContainer\"\n                >\n                  <div\n                    class=\" css-1jqq78o-placeholder\"\n                    id=\"react-select-13-placeholder\"\n                  >\n                    Search for people\n                  </div>\n                  <div\n                    class=\" css-1p5v8kp-Input\"\n                    data-value=\"\"\n                  >\n                    <input\n                      aria-activedescendant=\"\"\n                      aria-autocomplete=\"list\"\n                      aria-describedby=\"react-select-13-placeholder\"\n                      aria-expanded=\"false\"\n                      aria-haspopup=\"true\"\n                      autocapitalize=\"none\"\n                      autocomplete=\"off\"\n                      autocorrect=\"off\"\n                      class=\"\"\n                      id=\"react-select-13-input\"\n                      role=\"combobox\"\n                      spellcheck=\"false\"\n                      style=\"opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;\"\n                      tabindex=\"0\"\n                      type=\"text\"\n                      value=\"\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\" css-1hb7zxy-IndicatorsContainer\"\n                />\n              </div>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"user-items\"\n        >\n          <div\n            class=\"user-item\"\n          >\n            <div\n              class=\"user-item__content\"\n            >\n              <div\n                class=\"ml-3\"\n              >\n                <strong>\n                  Everyone at Test Team Team\n                </strong>\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"user-item__button\"\n                >\n                  None\n                  <i\n                    class=\"CompassIcon icon-chevron-down CompassIcon\"\n                  />\n                </button>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`src/components/shareBoard/shareBoard return shareBoard template and click Select 2`] = `\n<div>\n  <div\n    class=\"Dialog dialog-back ShareBoardDialog size--medium\"\n  >\n    <div\n      class=\"backdrop\"\n    />\n    <div\n      class=\"wrapper\"\n    >\n      <div\n        class=\"dialog\"\n        role=\"dialog\"\n      >\n        <div\n          class=\"toolbar\"\n        >\n          <div>\n            <h1\n              class=\"dialog-title\"\n            >\n              Share Template\n            </h1>\n          </div>\n          <div\n            class=\"toolbar--right\"\n          >\n            <button\n              aria-label=\"Close dialog\"\n              title=\"Close dialog\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-close CloseIcon\"\n              />\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"share-input__container\"\n        >\n          <div\n            class=\"share-input\"\n          >\n            <i\n              class=\"CompassIcon icon-magnify MagnifyIcon\"\n            />\n            <div\n              class=\"userSearchInput css-b62m3t-container\"\n            >\n              <span\n                class=\"css-1f43avz-a11yText-A11yText\"\n                id=\"react-select-13-live-region\"\n              />\n              <span\n                aria-atomic=\"false\"\n                aria-live=\"polite\"\n                aria-relevant=\"additions text\"\n                class=\"css-1f43avz-a11yText-A11yText\"\n                role=\"log\"\n              >\n                <span\n                  id=\"aria-selection\"\n                />\n                <span\n                  id=\"aria-focused\"\n                />\n                <span\n                  id=\"aria-results\"\n                >\n                  4 results available.\n                </span>\n                <span\n                  id=\"aria-guidance\"\n                >\n                  Use Up and Down to choose options, press Enter to select the currently focused option, press Escape to exit the menu, press Tab to select the option and exit the menu.\n                </span>\n              </span>\n              <div\n                class=\" css-1wmrr75-Control\"\n              >\n                <div\n                  class=\" css-b2z5qd-ValueContainer\"\n                >\n                  <div\n                    class=\" css-1jqq78o-placeholder\"\n                    id=\"react-select-13-placeholder\"\n                  >\n                    Search for people\n                  </div>\n                  <div\n                    class=\" css-1p5v8kp-Input\"\n                    data-value=\"\"\n                  >\n                    <input\n                      aria-activedescendant=\"react-select-13-option-0\"\n                      aria-autocomplete=\"list\"\n                      aria-controls=\"react-select-13-listbox\"\n                      aria-describedby=\"react-select-13-placeholder\"\n                      aria-expanded=\"true\"\n                      aria-haspopup=\"true\"\n                      autocapitalize=\"none\"\n                      autocomplete=\"off\"\n                      autocorrect=\"off\"\n                      class=\"\"\n                      id=\"react-select-13-input\"\n                      role=\"combobox\"\n                      spellcheck=\"false\"\n                      style=\"opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;\"\n                      tabindex=\"0\"\n                      type=\"text\"\n                      value=\"\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\" css-1hb7zxy-IndicatorsContainer\"\n                />\n              </div>\n              <div\n                class=\" css-45h7mv-menu\"\n              >\n                <div\n                  aria-multiselectable=\"false\"\n                  class=\" css-1d1qzc4-MenuList\"\n                  id=\"react-select-13-listbox\"\n                  role=\"listbox\"\n                >\n                  <div\n                    aria-disabled=\"false\"\n                    aria-selected=\"false\"\n                    class=\" css-8e5kjb-option\"\n                    id=\"react-select-13-option-0\"\n                    role=\"option\"\n                    tabindex=\"-1\"\n                  >\n                    <div\n                      class=\"user-item\"\n                    >\n                      <div\n                        class=\"ml-3\"\n                      >\n                        <strong>\n                          username_1\n                        </strong>\n                        <strong\n                          class=\"ml-2 text-light\"\n                        >\n                          @username_1\n                        </strong>\n                      </div>\n                    </div>\n                  </div>\n                  <div\n                    aria-disabled=\"false\"\n                    aria-selected=\"false\"\n                    class=\" css-x3yilo-option\"\n                    id=\"react-select-13-option-1\"\n                    role=\"option\"\n                    tabindex=\"-1\"\n                  >\n                    <div\n                      class=\"user-item\"\n                    >\n                      <div\n                        class=\"ml-3\"\n                      >\n                        <strong>\n                          username_2\n                        </strong>\n                        <strong\n                          class=\"ml-2 text-light\"\n                        >\n                          @username_2\n                        </strong>\n                      </div>\n                    </div>\n                  </div>\n                  <div\n                    aria-disabled=\"false\"\n                    aria-selected=\"false\"\n                    class=\" css-x3yilo-option\"\n                    id=\"react-select-13-option-2\"\n                    role=\"option\"\n                    tabindex=\"-1\"\n                  >\n                    <div\n                      class=\"user-item\"\n                    >\n                      <div\n                        class=\"ml-3\"\n                      >\n                        <strong>\n                          username_3\n                        </strong>\n                        <strong\n                          class=\"ml-2 text-light\"\n                        >\n                          @username_3\n                        </strong>\n                      </div>\n                    </div>\n                  </div>\n                  <div\n                    aria-disabled=\"false\"\n                    aria-selected=\"false\"\n                    class=\" css-x3yilo-option\"\n                    id=\"react-select-13-option-3\"\n                    role=\"option\"\n                    tabindex=\"-1\"\n                  >\n                    <div\n                      class=\"user-item\"\n                    >\n                      <div\n                        class=\"ml-3\"\n                      >\n                        <strong>\n                          username_4\n                        </strong>\n                        <strong\n                          class=\"ml-2 text-light\"\n                        >\n                          @username_4\n                        </strong>\n                      </div>\n                    </div>\n                  </div>\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"user-items\"\n        >\n          <div\n            class=\"user-item\"\n          >\n            <div\n              class=\"user-item__content\"\n            >\n              <div\n                class=\"ml-3\"\n              >\n                <strong>\n                  Everyone at Test Team Team\n                </strong>\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"user-item__button\"\n                >\n                  None\n                  <i\n                    class=\"CompassIcon icon-chevron-down CompassIcon\"\n                  />\n                </button>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`src/components/shareBoard/shareBoard return shareBoard, and click switch 1`] = `\n<div>\n  <div\n    class=\"Dialog dialog-back ShareBoardDialog size--medium\"\n  >\n    <div\n      class=\"backdrop\"\n    />\n    <div\n      class=\"wrapper\"\n    >\n      <div\n        class=\"dialog\"\n        role=\"dialog\"\n      >\n        <div\n          class=\"toolbar\"\n        >\n          <div>\n            <h1\n              class=\"dialog-title\"\n            >\n              Share Board\n            </h1>\n          </div>\n          <div\n            class=\"toolbar--right\"\n          >\n            <button\n              aria-label=\"Close dialog\"\n              title=\"Close dialog\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-close CloseIcon\"\n              />\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"share-input__container\"\n        >\n          <div\n            class=\"share-input\"\n          >\n            <i\n              class=\"CompassIcon icon-magnify MagnifyIcon\"\n            />\n            <div\n              class=\"userSearchInput css-b62m3t-container\"\n            >\n              <span\n                class=\"css-1f43avz-a11yText-A11yText\"\n                id=\"react-select-6-live-region\"\n              />\n              <span\n                aria-atomic=\"false\"\n                aria-live=\"polite\"\n                aria-relevant=\"additions text\"\n                class=\"css-1f43avz-a11yText-A11yText\"\n                role=\"log\"\n              />\n              <div\n                class=\" css-1wmrr75-Control\"\n              >\n                <div\n                  class=\" css-b2z5qd-ValueContainer\"\n                >\n                  <div\n                    class=\" css-1jqq78o-placeholder\"\n                    id=\"react-select-6-placeholder\"\n                  >\n                    Search for people and channels\n                  </div>\n                  <div\n                    class=\" css-1p5v8kp-Input\"\n                    data-value=\"\"\n                  >\n                    <input\n                      aria-activedescendant=\"\"\n                      aria-autocomplete=\"list\"\n                      aria-describedby=\"react-select-6-placeholder\"\n                      aria-expanded=\"false\"\n                      aria-haspopup=\"true\"\n                      autocapitalize=\"none\"\n                      autocomplete=\"off\"\n                      autocorrect=\"off\"\n                      class=\"\"\n                      id=\"react-select-6-input\"\n                      role=\"combobox\"\n                      spellcheck=\"false\"\n                      style=\"opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;\"\n                      tabindex=\"0\"\n                      type=\"text\"\n                      value=\"\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\" css-1hb7zxy-IndicatorsContainer\"\n                />\n              </div>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"user-items\"\n        >\n          <div\n            class=\"user-item\"\n          >\n            <div\n              class=\"user-item__content\"\n            >\n              <div\n                class=\"ml-3\"\n              >\n                <strong>\n                  Everyone at Test Team Team\n                </strong>\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"user-item__button\"\n                >\n                  None\n                  <i\n                    class=\"CompassIcon icon-chevron-down CompassIcon\"\n                  />\n                </button>\n              </div>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"tabs-container\"\n        >\n          <button\n            class=\"tab-item false\"\n          >\n            Share\n          </button>\n          <button\n            class=\"tab-item tab-item--active\"\n          >\n            Publish\n          </button>\n        </div>\n        <div\n          class=\"tabs-content\"\n        >\n          <div>\n            <div\n              class=\"d-flex justify-content-between\"\n            >\n              <div\n                class=\"d-flex flex-column\"\n              >\n                <div\n                  class=\"text-heading2\"\n                >\n                  Publish to the web\n                </div>\n                <div\n                  class=\"text-light\"\n                >\n                  Publish and share a read-only link with everyone on the web.\n                </div>\n              </div>\n              <div>\n                <div\n                  class=\"Switch override-main size--medium on\"\n                >\n                  <div\n                    class=\"octo-switch-inner\"\n                  />\n                </div>\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"d-flex justify-content-between tabs-inputs\"\n          >\n            <div\n              class=\"d-flex input-container\"\n            >\n              <a\n                class=\"shareUrl\"\n                href=\"http://localhost/shared/1/1?r=oneToken\"\n                rel=\"noreferrer\"\n                target=\"_blank\"\n              >\n                http://localhost/shared/1/1?r=oneToken\n              </a>\n              <div\n                class=\"octo-tooltip tooltip-top\"\n                data-tooltip=\"Regenerate token\"\n              >\n                <button\n                  aria-label=\"Regenerate token\"\n                  title=\"Regenerate token\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-refresh\"\n                  />\n                </button>\n              </div>\n            </div>\n            <button\n              title=\"Copy public link\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-content-copy CompassIcon\"\n              />\n              <span>\n                Copy link\n              </span>\n            </button>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`src/components/shareBoard/shareBoard return shareBoardComponent and click Switch without sharing 1`] = `\n<div>\n  <div\n    class=\"Dialog dialog-back ShareBoardDialog size--medium\"\n  >\n    <div\n      class=\"backdrop\"\n    />\n    <div\n      class=\"wrapper\"\n    >\n      <div\n        class=\"dialog\"\n        role=\"dialog\"\n      >\n        <div\n          class=\"toolbar\"\n        >\n          <div>\n            <h1\n              class=\"dialog-title\"\n            >\n              Share Board\n            </h1>\n          </div>\n          <div\n            class=\"toolbar--right\"\n          >\n            <button\n              aria-label=\"Close dialog\"\n              title=\"Close dialog\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-close CloseIcon\"\n              />\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"share-input__container\"\n        >\n          <div\n            class=\"share-input\"\n          >\n            <i\n              class=\"CompassIcon icon-magnify MagnifyIcon\"\n            />\n            <div\n              class=\"userSearchInput css-b62m3t-container\"\n            >\n              <span\n                class=\"css-1f43avz-a11yText-A11yText\"\n                id=\"react-select-7-live-region\"\n              />\n              <span\n                aria-atomic=\"false\"\n                aria-live=\"polite\"\n                aria-relevant=\"additions text\"\n                class=\"css-1f43avz-a11yText-A11yText\"\n                role=\"log\"\n              />\n              <div\n                class=\" css-1wmrr75-Control\"\n              >\n                <div\n                  class=\" css-b2z5qd-ValueContainer\"\n                >\n                  <div\n                    class=\" css-1jqq78o-placeholder\"\n                    id=\"react-select-7-placeholder\"\n                  >\n                    Search for people and channels\n                  </div>\n                  <div\n                    class=\" css-1p5v8kp-Input\"\n                    data-value=\"\"\n                  >\n                    <input\n                      aria-activedescendant=\"\"\n                      aria-autocomplete=\"list\"\n                      aria-describedby=\"react-select-7-placeholder\"\n                      aria-expanded=\"false\"\n                      aria-haspopup=\"true\"\n                      autocapitalize=\"none\"\n                      autocomplete=\"off\"\n                      autocorrect=\"off\"\n                      class=\"\"\n                      id=\"react-select-7-input\"\n                      role=\"combobox\"\n                      spellcheck=\"false\"\n                      style=\"opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;\"\n                      tabindex=\"0\"\n                      type=\"text\"\n                      value=\"\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\" css-1hb7zxy-IndicatorsContainer\"\n                />\n              </div>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"user-items\"\n        >\n          <div\n            class=\"user-item\"\n          >\n            <div\n              class=\"user-item__content\"\n            >\n              <div\n                class=\"ml-3\"\n              >\n                <strong>\n                  Everyone at Test Team Team\n                </strong>\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"user-item__button\"\n                >\n                  None\n                  <i\n                    class=\"CompassIcon icon-chevron-down CompassIcon\"\n                  />\n                </button>\n              </div>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"tabs-container\"\n        >\n          <button\n            class=\"tab-item false\"\n          >\n            Share\n          </button>\n          <button\n            class=\"tab-item tab-item--active\"\n          >\n            Publish\n          </button>\n        </div>\n        <div\n          class=\"tabs-content\"\n        >\n          <div>\n            <div\n              class=\"d-flex justify-content-between\"\n            >\n              <div\n                class=\"d-flex flex-column\"\n              >\n                <div\n                  class=\"text-heading2\"\n                >\n                  Publish to the web\n                </div>\n                <div\n                  class=\"text-light\"\n                >\n                  Publish and share a read-only link with everyone on the web.\n                </div>\n              </div>\n              <div>\n                <div\n                  class=\"Switch override-main size--medium on\"\n                >\n                  <div\n                    class=\"octo-switch-inner\"\n                  />\n                </div>\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"d-flex justify-content-between tabs-inputs\"\n          >\n            <div\n              class=\"d-flex input-container\"\n            >\n              <a\n                class=\"shareUrl\"\n                href=\"http://localhost/shared/1/1?r=aToken\"\n                rel=\"noreferrer\"\n                target=\"_blank\"\n              >\n                http://localhost/shared/1/1?r=aToken\n              </a>\n              <div\n                class=\"octo-tooltip tooltip-top\"\n                data-tooltip=\"Regenerate token\"\n              >\n                <button\n                  aria-label=\"Regenerate token\"\n                  title=\"Regenerate token\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-refresh\"\n                  />\n                </button>\n              </div>\n            </div>\n            <button\n              title=\"Copy public link\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-content-copy CompassIcon\"\n              />\n              <span>\n                Copy link\n              </span>\n            </button>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`src/components/shareBoard/shareBoard should match snapshot 1`] = `\n<div>\n  <div\n    class=\"Dialog dialog-back ShareBoardDialog size--medium\"\n  >\n    <div\n      class=\"backdrop\"\n    />\n    <div\n      class=\"wrapper\"\n    >\n      <div\n        class=\"dialog\"\n        role=\"dialog\"\n      >\n        <div\n          class=\"toolbar\"\n        >\n          <div>\n            <h1\n              class=\"dialog-title\"\n            >\n              Share Board\n            </h1>\n          </div>\n          <div\n            class=\"toolbar--right\"\n          >\n            <button\n              aria-label=\"Close dialog\"\n              title=\"Close dialog\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-close CloseIcon\"\n              />\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"share-input__container\"\n        >\n          <div\n            class=\"share-input\"\n          >\n            <i\n              class=\"CompassIcon icon-magnify MagnifyIcon\"\n            />\n            <div\n              class=\"userSearchInput css-b62m3t-container\"\n            >\n              <span\n                class=\"css-1f43avz-a11yText-A11yText\"\n                id=\"react-select-2-live-region\"\n              />\n              <span\n                aria-atomic=\"false\"\n                aria-live=\"polite\"\n                aria-relevant=\"additions text\"\n                class=\"css-1f43avz-a11yText-A11yText\"\n                role=\"log\"\n              />\n              <div\n                class=\" css-1wmrr75-Control\"\n              >\n                <div\n                  class=\" css-b2z5qd-ValueContainer\"\n                >\n                  <div\n                    class=\" css-1jqq78o-placeholder\"\n                    id=\"react-select-2-placeholder\"\n                  >\n                    Search for people and channels\n                  </div>\n                  <div\n                    class=\" css-1p5v8kp-Input\"\n                    data-value=\"\"\n                  >\n                    <input\n                      aria-activedescendant=\"\"\n                      aria-autocomplete=\"list\"\n                      aria-describedby=\"react-select-2-placeholder\"\n                      aria-expanded=\"false\"\n                      aria-haspopup=\"true\"\n                      autocapitalize=\"none\"\n                      autocomplete=\"off\"\n                      autocorrect=\"off\"\n                      class=\"\"\n                      id=\"react-select-2-input\"\n                      role=\"combobox\"\n                      spellcheck=\"false\"\n                      style=\"opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;\"\n                      tabindex=\"0\"\n                      type=\"text\"\n                      value=\"\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\" css-1hb7zxy-IndicatorsContainer\"\n                />\n              </div>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"user-items\"\n        >\n          <div\n            class=\"user-item\"\n          >\n            <div\n              class=\"user-item__content\"\n            >\n              <div\n                class=\"ml-3\"\n              >\n                <strong>\n                  Everyone at Test Team Team\n                </strong>\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"user-item__button\"\n                >\n                  None\n                  <i\n                    class=\"CompassIcon icon-chevron-down CompassIcon\"\n                  />\n                </button>\n              </div>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"tabs-container\"\n        >\n          <button\n            class=\"tab-item tab-item--active\"\n          >\n            Share\n          </button>\n          <button\n            class=\"tab-item false\"\n          >\n            Publish\n          </button>\n        </div>\n        <div\n          class=\"tabs-content\"\n        >\n          <div>\n            <div\n              class=\"d-flex justify-content-between\"\n            >\n              <div\n                class=\"d-flex flex-column\"\n              >\n                <div\n                  class=\"text-heading2\"\n                >\n                  Share internally\n                </div>\n                <div\n                  class=\"text-light\"\n                >\n                  Users who have permissions will be able to use this link.\n                </div>\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"d-flex justify-content-between tabs-inputs\"\n          >\n            <div\n              class=\"d-flex input-container\"\n            >\n              <a\n                class=\"shareUrl\"\n                href=\"http://localhost/1/1\"\n                rel=\"noreferrer\"\n                target=\"_blank\"\n              >\n                http://localhost/1/1\n              </a>\n            </div>\n            <button\n              title=\"Copy link\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-content-copy CompassIcon\"\n              />\n              <span>\n                Copy link\n              </span>\n            </button>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`src/components/shareBoard/shareBoard should match snapshot with sharing 1`] = `\n<div>\n  <div\n    class=\"Dialog dialog-back ShareBoardDialog size--medium\"\n  >\n    <div\n      class=\"backdrop\"\n    />\n    <div\n      class=\"wrapper\"\n    >\n      <div\n        class=\"dialog\"\n        role=\"dialog\"\n      >\n        <div\n          class=\"toolbar\"\n        >\n          <div>\n            <h1\n              class=\"dialog-title\"\n            >\n              Share Board\n            </h1>\n          </div>\n          <div\n            class=\"toolbar--right\"\n          >\n            <button\n              aria-label=\"Close dialog\"\n              title=\"Close dialog\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-close CloseIcon\"\n              />\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"share-input__container\"\n        >\n          <div\n            class=\"share-input\"\n          >\n            <i\n              class=\"CompassIcon icon-magnify MagnifyIcon\"\n            />\n            <div\n              class=\"userSearchInput css-b62m3t-container\"\n            >\n              <span\n                class=\"css-1f43avz-a11yText-A11yText\"\n                id=\"react-select-3-live-region\"\n              />\n              <span\n                aria-atomic=\"false\"\n                aria-live=\"polite\"\n                aria-relevant=\"additions text\"\n                class=\"css-1f43avz-a11yText-A11yText\"\n                role=\"log\"\n              />\n              <div\n                class=\" css-1wmrr75-Control\"\n              >\n                <div\n                  class=\" css-b2z5qd-ValueContainer\"\n                >\n                  <div\n                    class=\" css-1jqq78o-placeholder\"\n                    id=\"react-select-3-placeholder\"\n                  >\n                    Search for people and channels\n                  </div>\n                  <div\n                    class=\" css-1p5v8kp-Input\"\n                    data-value=\"\"\n                  >\n                    <input\n                      aria-activedescendant=\"\"\n                      aria-autocomplete=\"list\"\n                      aria-describedby=\"react-select-3-placeholder\"\n                      aria-expanded=\"false\"\n                      aria-haspopup=\"true\"\n                      autocapitalize=\"none\"\n                      autocomplete=\"off\"\n                      autocorrect=\"off\"\n                      class=\"\"\n                      id=\"react-select-3-input\"\n                      role=\"combobox\"\n                      spellcheck=\"false\"\n                      style=\"opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;\"\n                      tabindex=\"0\"\n                      type=\"text\"\n                      value=\"\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\" css-1hb7zxy-IndicatorsContainer\"\n                />\n              </div>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"user-items\"\n        >\n          <div\n            class=\"user-item\"\n          >\n            <div\n              class=\"user-item__content\"\n            >\n              <div\n                class=\"ml-3\"\n              >\n                <strong>\n                  Everyone at Test Team Team\n                </strong>\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"user-item__button\"\n                >\n                  None\n                  <i\n                    class=\"CompassIcon icon-chevron-down CompassIcon\"\n                  />\n                </button>\n              </div>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"tabs-container\"\n        >\n          <button\n            class=\"tab-item tab-item--active\"\n          >\n            Share\n          </button>\n          <button\n            class=\"tab-item false\"\n          >\n            Publish\n          </button>\n        </div>\n        <div\n          class=\"tabs-content\"\n        >\n          <div>\n            <div\n              class=\"d-flex justify-content-between\"\n            >\n              <div\n                class=\"d-flex flex-column\"\n              >\n                <div\n                  class=\"text-heading2\"\n                >\n                  Share internally\n                </div>\n                <div\n                  class=\"text-light\"\n                >\n                  Users who have permissions will be able to use this link.\n                </div>\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"d-flex justify-content-between tabs-inputs\"\n          >\n            <div\n              class=\"d-flex input-container\"\n            >\n              <a\n                class=\"shareUrl\"\n                href=\"http://localhost/1/1\"\n                rel=\"noreferrer\"\n                target=\"_blank\"\n              >\n                http://localhost/1/1\n              </a>\n            </div>\n            <button\n              title=\"Copy link\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-content-copy CompassIcon\"\n              />\n              <span>\n                Copy link\n              </span>\n            </button>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`src/components/shareBoard/shareBoard should match snapshot with sharing and subpath 1`] = `\n<div>\n  <div\n    class=\"Dialog dialog-back ShareBoardDialog size--medium\"\n  >\n    <div\n      class=\"backdrop\"\n    />\n    <div\n      class=\"wrapper\"\n    >\n      <div\n        class=\"dialog\"\n        role=\"dialog\"\n      >\n        <div\n          class=\"toolbar\"\n        >\n          <div>\n            <h1\n              class=\"dialog-title\"\n            >\n              Share Board\n            </h1>\n          </div>\n          <div\n            class=\"toolbar--right\"\n          >\n            <button\n              aria-label=\"Close dialog\"\n              title=\"Close dialog\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-close CloseIcon\"\n              />\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"share-input__container\"\n        >\n          <div\n            class=\"share-input\"\n          >\n            <i\n              class=\"CompassIcon icon-magnify MagnifyIcon\"\n            />\n            <div\n              class=\"userSearchInput css-b62m3t-container\"\n            >\n              <span\n                class=\"css-1f43avz-a11yText-A11yText\"\n                id=\"react-select-9-live-region\"\n              />\n              <span\n                aria-atomic=\"false\"\n                aria-live=\"polite\"\n                aria-relevant=\"additions text\"\n                class=\"css-1f43avz-a11yText-A11yText\"\n                role=\"log\"\n              />\n              <div\n                class=\" css-1wmrr75-Control\"\n              >\n                <div\n                  class=\" css-b2z5qd-ValueContainer\"\n                >\n                  <div\n                    class=\" css-1jqq78o-placeholder\"\n                    id=\"react-select-9-placeholder\"\n                  >\n                    Search for people and channels\n                  </div>\n                  <div\n                    class=\" css-1p5v8kp-Input\"\n                    data-value=\"\"\n                  >\n                    <input\n                      aria-activedescendant=\"\"\n                      aria-autocomplete=\"list\"\n                      aria-describedby=\"react-select-9-placeholder\"\n                      aria-expanded=\"false\"\n                      aria-haspopup=\"true\"\n                      autocapitalize=\"none\"\n                      autocomplete=\"off\"\n                      autocorrect=\"off\"\n                      class=\"\"\n                      id=\"react-select-9-input\"\n                      role=\"combobox\"\n                      spellcheck=\"false\"\n                      style=\"opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;\"\n                      tabindex=\"0\"\n                      type=\"text\"\n                      value=\"\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\" css-1hb7zxy-IndicatorsContainer\"\n                />\n              </div>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"user-items\"\n        >\n          <div\n            class=\"user-item\"\n          >\n            <div\n              class=\"user-item__content\"\n            >\n              <div\n                class=\"ml-3\"\n              >\n                <strong>\n                  Everyone at Test Team Team\n                </strong>\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"user-item__button\"\n                >\n                  None\n                  <i\n                    class=\"CompassIcon icon-chevron-down CompassIcon\"\n                  />\n                </button>\n              </div>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"tabs-container\"\n        >\n          <button\n            class=\"tab-item tab-item--active\"\n          >\n            Share\n          </button>\n          <button\n            class=\"tab-item false\"\n          >\n            Publish\n          </button>\n        </div>\n        <div\n          class=\"tabs-content\"\n        >\n          <div>\n            <div\n              class=\"d-flex justify-content-between\"\n            >\n              <div\n                class=\"d-flex flex-column\"\n              >\n                <div\n                  class=\"text-heading2\"\n                >\n                  Share internally\n                </div>\n                <div\n                  class=\"text-light\"\n                >\n                  Users who have permissions will be able to use this link.\n                </div>\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"d-flex justify-content-between tabs-inputs\"\n          >\n            <div\n              class=\"d-flex input-container\"\n            >\n              <a\n                class=\"shareUrl\"\n                href=\"http://localhost/test-subpath/plugins/boards1/1\"\n                rel=\"noreferrer\"\n                target=\"_blank\"\n              >\n                http://localhost/test-subpath/plugins/boards1/1\n              </a>\n            </div>\n            <button\n              title=\"Copy link\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-content-copy CompassIcon\"\n              />\n              <span>\n                Copy link\n              </span>\n            </button>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`src/components/shareBoard/shareBoard should match snapshot with sharing and without workspaceId and subpath 1`] = `\n<div>\n  <div\n    class=\"Dialog dialog-back ShareBoardDialog size--medium\"\n  >\n    <div\n      class=\"backdrop\"\n    />\n    <div\n      class=\"wrapper\"\n    >\n      <div\n        class=\"dialog\"\n        role=\"dialog\"\n      >\n        <div\n          class=\"toolbar\"\n        >\n          <div>\n            <h1\n              class=\"dialog-title\"\n            >\n              Share Board\n            </h1>\n          </div>\n          <div\n            class=\"toolbar--right\"\n          >\n            <button\n              aria-label=\"Close dialog\"\n              title=\"Close dialog\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-close CloseIcon\"\n              />\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"share-input__container\"\n        >\n          <div\n            class=\"share-input\"\n          >\n            <i\n              class=\"CompassIcon icon-magnify MagnifyIcon\"\n            />\n            <div\n              class=\"userSearchInput css-b62m3t-container\"\n            >\n              <span\n                class=\"css-1f43avz-a11yText-A11yText\"\n                id=\"react-select-8-live-region\"\n              />\n              <span\n                aria-atomic=\"false\"\n                aria-live=\"polite\"\n                aria-relevant=\"additions text\"\n                class=\"css-1f43avz-a11yText-A11yText\"\n                role=\"log\"\n              />\n              <div\n                class=\" css-1wmrr75-Control\"\n              >\n                <div\n                  class=\" css-b2z5qd-ValueContainer\"\n                >\n                  <div\n                    class=\" css-1jqq78o-placeholder\"\n                    id=\"react-select-8-placeholder\"\n                  >\n                    Search for people and channels\n                  </div>\n                  <div\n                    class=\" css-1p5v8kp-Input\"\n                    data-value=\"\"\n                  >\n                    <input\n                      aria-activedescendant=\"\"\n                      aria-autocomplete=\"list\"\n                      aria-describedby=\"react-select-8-placeholder\"\n                      aria-expanded=\"false\"\n                      aria-haspopup=\"true\"\n                      autocapitalize=\"none\"\n                      autocomplete=\"off\"\n                      autocorrect=\"off\"\n                      class=\"\"\n                      id=\"react-select-8-input\"\n                      role=\"combobox\"\n                      spellcheck=\"false\"\n                      style=\"opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;\"\n                      tabindex=\"0\"\n                      type=\"text\"\n                      value=\"\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\" css-1hb7zxy-IndicatorsContainer\"\n                />\n              </div>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"user-items\"\n        >\n          <div\n            class=\"user-item\"\n          >\n            <div\n              class=\"user-item__content\"\n            >\n              <div\n                class=\"ml-3\"\n              >\n                <strong>\n                  Everyone at Test Team Team\n                </strong>\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"user-item__button\"\n                >\n                  None\n                  <i\n                    class=\"CompassIcon icon-chevron-down CompassIcon\"\n                  />\n                </button>\n              </div>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"tabs-container\"\n        >\n          <button\n            class=\"tab-item tab-item--active\"\n          >\n            Share\n          </button>\n          <button\n            class=\"tab-item false\"\n          >\n            Publish\n          </button>\n        </div>\n        <div\n          class=\"tabs-content\"\n        >\n          <div>\n            <div\n              class=\"d-flex justify-content-between\"\n            >\n              <div\n                class=\"d-flex flex-column\"\n              >\n                <div\n                  class=\"text-heading2\"\n                >\n                  Share internally\n                </div>\n                <div\n                  class=\"text-light\"\n                >\n                  Users who have permissions will be able to use this link.\n                </div>\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"d-flex justify-content-between tabs-inputs\"\n          >\n            <div\n              class=\"d-flex input-container\"\n            >\n              <a\n                class=\"shareUrl\"\n                href=\"http://localhost/test-subpath/plugins/boards1/1\"\n                rel=\"noreferrer\"\n                target=\"_blank\"\n              >\n                http://localhost/test-subpath/plugins/boards1/1\n              </a>\n            </div>\n            <button\n              title=\"Copy link\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-content-copy CompassIcon\"\n              />\n              <span>\n                Copy link\n              </span>\n            </button>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`src/components/shareBoard/shareBoard should match snapshot, with template 1`] = `\n<div>\n  <div\n    class=\"Dialog dialog-back ShareBoardDialog size--medium\"\n  >\n    <div\n      class=\"backdrop\"\n    />\n    <div\n      class=\"wrapper\"\n    >\n      <div\n        class=\"dialog\"\n        role=\"dialog\"\n      >\n        <div\n          class=\"toolbar\"\n        >\n          <div>\n            <h1\n              class=\"dialog-title\"\n            >\n              Share Template\n            </h1>\n          </div>\n          <div\n            class=\"toolbar--right\"\n          >\n            <button\n              aria-label=\"Close dialog\"\n              title=\"Close dialog\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-close CloseIcon\"\n              />\n            </button>\n          </div>\n        </div>\n        <div\n          class=\"share-input__container\"\n        >\n          <div\n            class=\"share-input\"\n          >\n            <i\n              class=\"CompassIcon icon-magnify MagnifyIcon\"\n            />\n            <div\n              class=\"userSearchInput css-b62m3t-container\"\n            >\n              <span\n                class=\"css-1f43avz-a11yText-A11yText\"\n                id=\"react-select-12-live-region\"\n              />\n              <span\n                aria-atomic=\"false\"\n                aria-live=\"polite\"\n                aria-relevant=\"additions text\"\n                class=\"css-1f43avz-a11yText-A11yText\"\n                role=\"log\"\n              />\n              <div\n                class=\" css-1wmrr75-Control\"\n              >\n                <div\n                  class=\" css-b2z5qd-ValueContainer\"\n                >\n                  <div\n                    class=\" css-1jqq78o-placeholder\"\n                    id=\"react-select-12-placeholder\"\n                  >\n                    Search for people\n                  </div>\n                  <div\n                    class=\" css-1p5v8kp-Input\"\n                    data-value=\"\"\n                  >\n                    <input\n                      aria-activedescendant=\"\"\n                      aria-autocomplete=\"list\"\n                      aria-describedby=\"react-select-12-placeholder\"\n                      aria-expanded=\"false\"\n                      aria-haspopup=\"true\"\n                      autocapitalize=\"none\"\n                      autocomplete=\"off\"\n                      autocorrect=\"off\"\n                      class=\"\"\n                      id=\"react-select-12-input\"\n                      role=\"combobox\"\n                      spellcheck=\"false\"\n                      style=\"opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;\"\n                      tabindex=\"0\"\n                      type=\"text\"\n                      value=\"\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\" css-1hb7zxy-IndicatorsContainer\"\n                />\n              </div>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"user-items\"\n        >\n          <div\n            class=\"user-item\"\n          >\n            <div\n              class=\"user-item__content\"\n            >\n              <div\n                class=\"ml-3\"\n              >\n                <strong>\n                  Everyone at Test Team Team\n                </strong>\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"user-item__button\"\n                >\n                  None\n                  <i\n                    class=\"CompassIcon icon-chevron-down CompassIcon\"\n                  />\n                </button>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/shareBoard/__snapshots__/shareBoardButton.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`src/components/shareBoard/shareBoard should match snapshot, Open Board 1`] = `\n<div>\n  <div\n    class=\"ShareBoardButton\"\n  >\n    <button\n      class=\"Button emphasis--primary size--medium\"\n      title=\"Share board\"\n      type=\"button\"\n    >\n      <i\n        class=\"CompassIcon icon-globe GlobeIcon\"\n      />\n      <span>\n        Share\n      </span>\n    </button>\n  </div>\n</div>\n`;\n\nexports[`src/components/shareBoard/shareBoard should match snapshot, Private Board 1`] = `\n<div>\n  <div\n    class=\"ShareBoardButton\"\n  >\n    <button\n      class=\"Button emphasis--primary size--medium\"\n      title=\"Share board\"\n      type=\"button\"\n    >\n      <i\n        class=\"CompassIcon icon-lock-outline LockOutlineIcon\"\n      />\n      <span>\n        Share\n      </span>\n    </button>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/shareBoard/__snapshots__/shareBoardLoginButton.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`src/components/shareBoard/shareBoardLoginButton should match snapshot 1`] = `\n<div>\n  <div\n    class=\"ShareBoardLoginButton\"\n  >\n    <button\n      class=\"Button emphasis--primary size--medium\"\n      title=\"Login\"\n      type=\"button\"\n    >\n      <span>\n        Login\n      </span>\n    </button>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/shareBoard/__snapshots__/teamPermissionsRow.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`src/components/shareBoard/teamPermissionsRow should match snapshot 1`] = `\n<div>\n  <div\n    class=\"user-item\"\n  >\n    <div\n      class=\"user-item__content\"\n    >\n      <div\n        class=\"ml-3\"\n      >\n        <strong>\n          Everyone at Test Team Team\n        </strong>\n      </div>\n    </div>\n    <div>\n      <div\n        aria-label=\"menuwrapper\"\n        class=\"MenuWrapper override menuOpened\"\n        role=\"button\"\n      >\n        <button\n          class=\"user-item__button\"\n        >\n          None\n          <i\n            class=\"CompassIcon icon-chevron-down CompassIcon\"\n          />\n        </button>\n        <div\n          class=\"Menu noselect left \"\n        >\n          <div\n            class=\"menu-contents\"\n          >\n            <div\n              class=\"menu-options\"\n            >\n              <div>\n                <div\n                  aria-label=\"Editor\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex menu-option__check\"\n                  >\n                    <div\n                      class=\"menu-option__icon\"\n                    >\n                      <div\n                        class=\"empty-icon\"\n                      />\n                    </div>\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Editor\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n              </div>\n              <div>\n                <div\n                  aria-label=\"Commenter\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex\"\n                  >\n                    <div\n                      class=\"menu-option__icon\"\n                    >\n                      <div\n                        class=\"empty-icon\"\n                      />\n                    </div>\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Commenter\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n              </div>\n              <div>\n                <div\n                  aria-label=\"Viewer\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex\"\n                  >\n                    <div\n                      class=\"menu-option__icon\"\n                    >\n                      <div\n                        class=\"empty-icon\"\n                      />\n                    </div>\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Viewer\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n              </div>\n              <div>\n                <div\n                  aria-label=\"None\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex menu-option__check\"\n                  >\n                    <div\n                      class=\"menu-option__icon\"\n                    >\n                      <svg\n                        class=\"CheckIcon Icon\"\n                        viewBox=\"0 0 100 100\"\n                        xmlns=\"http://www.w3.org/2000/svg\"\n                      >\n                        <polyline\n                          points=\"20,60 40,80 80,40\"\n                        />\n                      </svg>\n                    </div>\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      None\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n              </div>\n            </div>\n            <div\n              class=\"menu-spacer hideOnWidescreen\"\n            />\n            <div\n              class=\"menu-options hideOnWidescreen\"\n            >\n              <div\n                aria-label=\"Cancel\"\n                class=\"MenuOption TextOption menu-option menu-cancel\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Cancel\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`src/components/shareBoard/teamPermissionsRow should match snapshot in plugin mode 1`] = `\n<div>\n  <div\n    class=\"user-item\"\n  >\n    <div\n      class=\"user-item__content\"\n    >\n      <div\n        class=\"ml-3\"\n      >\n        <strong>\n          Everyone at Test Team Team\n        </strong>\n      </div>\n    </div>\n    <div>\n      <div\n        aria-label=\"menuwrapper\"\n        class=\"MenuWrapper override menuOpened\"\n        role=\"button\"\n      >\n        <button\n          class=\"user-item__button\"\n        >\n          None\n          <i\n            class=\"CompassIcon icon-chevron-down CompassIcon\"\n          />\n        </button>\n        <div\n          class=\"Menu noselect left \"\n        >\n          <div\n            class=\"menu-contents\"\n          >\n            <div\n              class=\"menu-options\"\n            >\n              <div>\n                <div\n                  aria-label=\"Editor\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex menu-option__check\"\n                  >\n                    <div\n                      class=\"menu-option__icon\"\n                    >\n                      <div\n                        class=\"empty-icon\"\n                      />\n                    </div>\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Editor\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n              </div>\n              <div>\n                <div\n                  aria-label=\"Commenter\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex\"\n                  >\n                    <div\n                      class=\"menu-option__icon\"\n                    >\n                      <div\n                        class=\"empty-icon\"\n                      />\n                    </div>\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Commenter\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n              </div>\n              <div>\n                <div\n                  aria-label=\"Viewer\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex\"\n                  >\n                    <div\n                      class=\"menu-option__icon\"\n                    >\n                      <div\n                        class=\"empty-icon\"\n                      />\n                    </div>\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Viewer\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n              </div>\n              <div>\n                <div\n                  aria-label=\"None\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex menu-option__check\"\n                  >\n                    <div\n                      class=\"menu-option__icon\"\n                    >\n                      <svg\n                        class=\"CheckIcon Icon\"\n                        viewBox=\"0 0 100 100\"\n                        xmlns=\"http://www.w3.org/2000/svg\"\n                      >\n                        <polyline\n                          points=\"20,60 40,80 80,40\"\n                        />\n                      </svg>\n                    </div>\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      None\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n              </div>\n            </div>\n            <div\n              class=\"menu-spacer hideOnWidescreen\"\n            />\n            <div\n              class=\"menu-options hideOnWidescreen\"\n            >\n              <div\n                aria-label=\"Cancel\"\n                class=\"MenuOption TextOption menu-option menu-cancel\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Cancel\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`src/components/shareBoard/teamPermissionsRow should match snapshot in template 1`] = `\n<div>\n  <div\n    class=\"user-item\"\n  >\n    <div\n      class=\"user-item__content\"\n    >\n      <div\n        class=\"ml-3\"\n      >\n        <strong>\n          Everyone at Test Team Team\n        </strong>\n      </div>\n    </div>\n    <div>\n      <div\n        aria-label=\"menuwrapper\"\n        class=\"MenuWrapper override menuOpened\"\n        role=\"button\"\n      >\n        <button\n          class=\"user-item__button\"\n        >\n          None\n          <i\n            class=\"CompassIcon icon-chevron-down CompassIcon\"\n          />\n        </button>\n        <div\n          class=\"Menu noselect left \"\n        >\n          <div\n            class=\"menu-contents\"\n          >\n            <div\n              class=\"menu-options\"\n            >\n              <div />\n              <div />\n              <div>\n                <div\n                  aria-label=\"Viewer\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex\"\n                  >\n                    <div\n                      class=\"menu-option__icon\"\n                    >\n                      <div\n                        class=\"empty-icon\"\n                      />\n                    </div>\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Viewer\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n              </div>\n              <div>\n                <div\n                  aria-label=\"None\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex menu-option__check\"\n                  >\n                    <div\n                      class=\"menu-option__icon\"\n                    >\n                      <svg\n                        class=\"CheckIcon Icon\"\n                        viewBox=\"0 0 100 100\"\n                        xmlns=\"http://www.w3.org/2000/svg\"\n                      >\n                        <polyline\n                          points=\"20,60 40,80 80,40\"\n                        />\n                      </svg>\n                    </div>\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      None\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n              </div>\n            </div>\n            <div\n              class=\"menu-spacer hideOnWidescreen\"\n            />\n            <div\n              class=\"menu-options hideOnWidescreen\"\n            >\n              <div\n                aria-label=\"Cancel\"\n                class=\"MenuOption TextOption menu-option menu-cancel\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Cancel\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/shareBoard/__snapshots__/userPermissionsRow.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`src/components/shareBoard/userPermissionsRow should match snapshot 1`] = `\n<div>\n  <div\n    class=\"user-item\"\n  >\n    <div\n      class=\"user-item__content\"\n    >\n      <div\n        class=\"ml-3\"\n      >\n        <strong />\n        <strong\n          class=\"ml-2 text-light\"\n        >\n          @username_1\n        </strong>\n        <strong\n          class=\"ml-2 text-light\"\n        >\n          (You)\n        </strong>\n      </div>\n    </div>\n    <div>\n      <div\n        aria-label=\"menuwrapper\"\n        class=\"MenuWrapper override menuOpened\"\n        role=\"button\"\n      >\n        <button\n          class=\"user-item__button\"\n        >\n          Admin\n          <i\n            class=\"CompassIcon icon-chevron-down CompassIcon\"\n          />\n        </button>\n        <div\n          class=\"Menu noselect left \"\n          style=\"top: 40px;\"\n        >\n          <div\n            class=\"menu-contents\"\n          >\n            <div\n              class=\"menu-options\"\n            >\n              <div>\n                <div\n                  aria-label=\"Viewer\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex menu-option__check\"\n                  >\n                    <div\n                      class=\"menu-option__icon\"\n                    >\n                      <div\n                        class=\"empty-icon\"\n                      />\n                    </div>\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Viewer\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n              </div>\n              <div>\n                <div\n                  aria-label=\"Commenter\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex menu-option__check\"\n                  >\n                    <div\n                      class=\"menu-option__icon\"\n                    >\n                      <div\n                        class=\"empty-icon\"\n                      />\n                    </div>\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Commenter\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n              </div>\n              <div>\n                <div\n                  aria-label=\"Editor\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex menu-option__check\"\n                  >\n                    <div\n                      class=\"menu-option__icon\"\n                    >\n                      <div\n                        class=\"empty-icon\"\n                      />\n                    </div>\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Editor\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n              </div>\n              <div>\n                <div\n                  aria-label=\"Admin\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex menu-option__check\"\n                  >\n                    <div\n                      class=\"menu-option__icon\"\n                    >\n                      <svg\n                        class=\"CheckIcon Icon\"\n                        viewBox=\"0 0 100 100\"\n                        xmlns=\"http://www.w3.org/2000/svg\"\n                      >\n                        <polyline\n                          points=\"20,60 40,80 80,40\"\n                        />\n                      </svg>\n                    </div>\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Admin\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n              </div>\n              <div>\n                <div\n                  class=\"MenuOption MenuSeparator menu-separator\"\n                />\n              </div>\n              <div>\n                <div\n                  aria-label=\"Remove member\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex\"\n                  >\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Remove member\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n              </div>\n            </div>\n            <div\n              class=\"menu-spacer hideOnWidescreen\"\n            />\n            <div\n              class=\"menu-options hideOnWidescreen\"\n            >\n              <div\n                aria-label=\"Cancel\"\n                class=\"MenuOption TextOption menu-option menu-cancel\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Cancel\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`src/components/shareBoard/userPermissionsRow should match snapshot in plugin mode 1`] = `\n<div>\n  <div\n    class=\"user-item\"\n  >\n    <div\n      class=\"user-item__content\"\n    >\n      <div\n        class=\"ml-3\"\n      >\n        <strong />\n        <strong\n          class=\"ml-2 text-light\"\n        >\n          @username_1\n        </strong>\n        <strong\n          class=\"ml-2 text-light\"\n        >\n          (You)\n        </strong>\n      </div>\n    </div>\n    <div>\n      <div\n        aria-label=\"menuwrapper\"\n        class=\"MenuWrapper override menuOpened\"\n        role=\"button\"\n      >\n        <button\n          class=\"user-item__button\"\n        >\n          Admin\n          <i\n            class=\"CompassIcon icon-chevron-down CompassIcon\"\n          />\n        </button>\n        <div\n          class=\"Menu noselect left \"\n          style=\"top: 40px;\"\n        >\n          <div\n            class=\"menu-contents\"\n          >\n            <div\n              class=\"menu-options\"\n            >\n              <div>\n                <div\n                  aria-label=\"Viewer\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex menu-option__check\"\n                  >\n                    <div\n                      class=\"menu-option__icon\"\n                    >\n                      <div\n                        class=\"empty-icon\"\n                      />\n                    </div>\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Viewer\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n              </div>\n              <div>\n                <div\n                  aria-label=\"Commenter\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex menu-option__check\"\n                  >\n                    <div\n                      class=\"menu-option__icon\"\n                    >\n                      <div\n                        class=\"empty-icon\"\n                      />\n                    </div>\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Commenter\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n              </div>\n              <div>\n                <div\n                  aria-label=\"Editor\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex menu-option__check\"\n                  >\n                    <div\n                      class=\"menu-option__icon\"\n                    >\n                      <div\n                        class=\"empty-icon\"\n                      />\n                    </div>\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Editor\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n              </div>\n              <div>\n                <div\n                  aria-label=\"Admin\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex menu-option__check\"\n                  >\n                    <div\n                      class=\"menu-option__icon\"\n                    >\n                      <svg\n                        class=\"CheckIcon Icon\"\n                        viewBox=\"0 0 100 100\"\n                        xmlns=\"http://www.w3.org/2000/svg\"\n                      >\n                        <polyline\n                          points=\"20,60 40,80 80,40\"\n                        />\n                      </svg>\n                    </div>\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Admin\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n              </div>\n              <div>\n                <div\n                  class=\"MenuOption MenuSeparator menu-separator\"\n                />\n              </div>\n              <div>\n                <div\n                  aria-label=\"Remove member\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex\"\n                  >\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Remove member\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n              </div>\n            </div>\n            <div\n              class=\"menu-spacer hideOnWidescreen\"\n            />\n            <div\n              class=\"menu-options hideOnWidescreen\"\n            >\n              <div\n                aria-label=\"Cancel\"\n                class=\"MenuOption TextOption menu-option menu-cancel\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Cancel\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`src/components/shareBoard/userPermissionsRow should match snapshot in template 1`] = `\n<div>\n  <div\n    class=\"user-item\"\n  >\n    <div\n      class=\"user-item__content\"\n    >\n      <div\n        class=\"ml-3\"\n      >\n        <strong />\n        <strong\n          class=\"ml-2 text-light\"\n        >\n          @username_1\n        </strong>\n        <strong\n          class=\"ml-2 text-light\"\n        >\n          (You)\n        </strong>\n      </div>\n    </div>\n    <div>\n      <div\n        aria-label=\"menuwrapper\"\n        class=\"MenuWrapper override menuOpened\"\n        role=\"button\"\n      >\n        <button\n          class=\"user-item__button\"\n        >\n          Admin\n          <i\n            class=\"CompassIcon icon-chevron-down CompassIcon\"\n          />\n        </button>\n        <div\n          class=\"Menu noselect left \"\n          style=\"top: 40px;\"\n        >\n          <div\n            class=\"menu-contents\"\n          >\n            <div\n              class=\"menu-options\"\n            >\n              <div>\n                <div\n                  aria-label=\"Viewer\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex menu-option__check\"\n                  >\n                    <div\n                      class=\"menu-option__icon\"\n                    >\n                      <div\n                        class=\"empty-icon\"\n                      />\n                    </div>\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Viewer\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n              </div>\n              <div />\n              <div>\n                <div\n                  aria-label=\"Editor\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex menu-option__check\"\n                  >\n                    <div\n                      class=\"menu-option__icon\"\n                    >\n                      <div\n                        class=\"empty-icon\"\n                      />\n                    </div>\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Editor\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n              </div>\n              <div>\n                <div\n                  aria-label=\"Admin\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex menu-option__check\"\n                  >\n                    <div\n                      class=\"menu-option__icon\"\n                    >\n                      <svg\n                        class=\"CheckIcon Icon\"\n                        viewBox=\"0 0 100 100\"\n                        xmlns=\"http://www.w3.org/2000/svg\"\n                      >\n                        <polyline\n                          points=\"20,60 40,80 80,40\"\n                        />\n                      </svg>\n                    </div>\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Admin\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n              </div>\n              <div>\n                <div\n                  class=\"MenuOption MenuSeparator menu-separator\"\n                />\n              </div>\n              <div>\n                <div\n                  aria-label=\"Remove member\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex\"\n                  >\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Remove member\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n              </div>\n            </div>\n            <div\n              class=\"menu-spacer hideOnWidescreen\"\n            />\n            <div\n              class=\"menu-options hideOnWidescreen\"\n            >\n              <div\n                aria-label=\"Cancel\"\n                class=\"MenuOption TextOption menu-option menu-cancel\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Cancel\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`src/components/shareBoard/userPermissionsRow should match snapshot-admin 1`] = `\n<div>\n  <div\n    class=\"user-item\"\n  >\n    <div\n      class=\"user-item__content\"\n    >\n      <div\n        class=\"ml-3\"\n      >\n        <strong />\n        <strong\n          class=\"ml-2 text-light\"\n        >\n          @username_1\n        </strong>\n        <strong\n          class=\"ml-2 text-light\"\n        >\n          (You)\n        </strong>\n        <div\n          class=\"AdminBadge\"\n        >\n          <div\n            class=\"AdminBadge__box\"\n          >\n            Admin\n          </div>\n        </div>\n      </div>\n    </div>\n    <div>\n      <div\n        aria-label=\"menuwrapper\"\n        class=\"MenuWrapper override menuOpened\"\n        role=\"button\"\n      >\n        <button\n          class=\"user-item__button\"\n        >\n          Admin\n          <i\n            class=\"CompassIcon icon-chevron-down CompassIcon\"\n          />\n        </button>\n        <div\n          class=\"Menu noselect left \"\n          style=\"top: 40px;\"\n        >\n          <div\n            class=\"menu-contents\"\n          >\n            <div\n              class=\"menu-options\"\n            >\n              <div>\n                <div\n                  aria-label=\"Viewer\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex menu-option__check\"\n                  >\n                    <div\n                      class=\"menu-option__icon\"\n                    >\n                      <div\n                        class=\"empty-icon\"\n                      />\n                    </div>\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Viewer\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n              </div>\n              <div>\n                <div\n                  aria-label=\"Commenter\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex menu-option__check\"\n                  >\n                    <div\n                      class=\"menu-option__icon\"\n                    >\n                      <div\n                        class=\"empty-icon\"\n                      />\n                    </div>\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Commenter\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n              </div>\n              <div>\n                <div\n                  aria-label=\"Editor\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex menu-option__check\"\n                  >\n                    <div\n                      class=\"menu-option__icon\"\n                    >\n                      <div\n                        class=\"empty-icon\"\n                      />\n                    </div>\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Editor\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n              </div>\n              <div>\n                <div\n                  aria-label=\"Admin\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex menu-option__check\"\n                  >\n                    <div\n                      class=\"menu-option__icon\"\n                    >\n                      <svg\n                        class=\"CheckIcon Icon\"\n                        viewBox=\"0 0 100 100\"\n                        xmlns=\"http://www.w3.org/2000/svg\"\n                      >\n                        <polyline\n                          points=\"20,60 40,80 80,40\"\n                        />\n                      </svg>\n                    </div>\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Admin\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n              </div>\n              <div>\n                <div\n                  class=\"MenuOption MenuSeparator menu-separator\"\n                />\n              </div>\n              <div>\n                <div\n                  aria-label=\"Remove member\"\n                  class=\"MenuOption TextOption menu-option\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex\"\n                  >\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Remove member\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n              </div>\n            </div>\n            <div\n              class=\"menu-spacer hideOnWidescreen\"\n            />\n            <div\n              class=\"menu-options hideOnWidescreen\"\n            >\n              <div\n                aria-label=\"Cancel\"\n                class=\"MenuOption TextOption menu-option menu-cancel\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Cancel\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/shareBoard/shareBoard.scss",
    "content": "\n@import '../../styles/z-index';\n\n.ShareBoardDialog {\n    .dialog {\n        width: 600px;\n        max-width: 90%;\n        height: auto;\n        position: relative;\n        overflow: unset;\n    }\n\n    .confirmation-dialog-box {\n        .dialog {\n            position: fixed;\n            width: 500px;\n        }\n\n        .action-buttons {\n            .Button {\n                height: unset;\n                min-height: 40px;\n                padding: 5px 20px;\n            }\n        }\n    }\n\n    .tabs-container {\n        display: flex;\n        padding: 24px 32px 0;\n        border-bottom: 1px solid rgba(var(--center-channel-color-rgb), 0.16);\n\n        .tab-item {\n            margin-right: 12px;\n            display: flex;\n            font-family: Metropolis, sans-serif;\n            font-size: 16px;\n            background-color: transparent;\n            border: 0;\n            padding: 0 8px;\n            height: 37px;\n            font-weight: 600;\n            color: rgba(var(--center-channel-color-rgb), 0.56);\n\n            &:hover {\n                color: rgba(var(--center-channel-color-rgb), 0.72);\n            }\n\n            &.tab-item--active {\n                color: inherit;\n                border-bottom: 3px solid rgba(var(--button-bg-rgb), 1);\n            }\n        }\n    }\n\n    .tabs-content {\n        padding: 24px 32px;\n        border-radius: 8px;\n\n        .d-flex {\n            display: flex;\n        }\n\n        .justify-content-between {\n            justify-content: space-between;\n        }\n\n        .flex-column {\n            flex-direction: column;\n        }\n\n        .tabs-inputs {\n            margin-top: 16px;\n\n            .input-container {\n                border: 1px solid rgba(var(--center-channel-color-rgb), 0.16);\n                border-radius: 4px;\n                flex: 1;\n                margin-right: 12px;\n                height: 40px;\n                width: 400px;\n                align-items: center;\n                display: flex;\n                padding: 0 4px 0 8px;\n            }\n\n            .icon-refresh {\n                margin: 4px;\n                font-size: 18px;\n            }\n\n            .icon-content-copy {\n                margin-right: 4px;\n                font-size: 18px;\n            }\n\n            .IconButton {\n                height: 100%;\n                width: 40px;\n            }\n\n            .icon-refresh {\n                margin: 4px;\n                font-size: 18px;\n            }\n\n            .icon-content-copy {\n                margin-right: 4px;\n                font-size: 18px;\n            }\n\n            .shareUrl {\n                flex-grow: 1;\n                white-space: nowrap;\n                overflow: hidden;\n                text-overflow: ellipsis;\n                margin: auto 4px;\n                padding: 0 4px;\n                border-radius: 4px;\n\n                :hover {\n                    background-color: rgb(var(--center-channel-color-rgb), 0.1);\n                }\n            }\n\n            input {\n                width: 100%;\n                height: 40px;\n                border-radius: 4px;\n                border: 1px solid rgba(var(--center-channel-color-rgb), 0.16);\n                padding: 0 16px;\n\n                &:focus {\n                    border: 2px solid var(--button-bg);\n                    padding: 0 15px;\n                }\n            }\n        }\n    }\n\n    .share-input__container {\n        padding: 0 32px 24px;\n        border-bottom: 1px solid rgba(var(--center-channel-color-rgb), 0.16);\n    }\n\n    .share-input {\n        height: 48px;\n        padding: 0 16px;\n        display: flex;\n        align-items: center;\n        position: relative;\n        width: 100%;\n\n        i {\n            @include z-index(share-board-element);\n            color: rgba(var(--center-channel-color-rgb), 0.64);\n            position: relative;\n            font-size: 18px;\n        }\n\n        .userSearchInput {\n            position: absolute;\n            top: 0;\n            left: 0;\n            width: 100%;\n            height: 100%;\n            border-radius: 4px;\n            border: 1px solid rgba(var(--center-channel-color-rgb), 0.16);\n            padding-left: 35px;\n            font-size: 16px;\n\n            &:focus {\n                border: 2px solid var(--button-bg);\n                padding-left: 39px;\n            }\n        }\n    }\n\n    .user-items {\n        padding: 16px;\n        max-height: 282px;\n        overflow: auto;\n        border-bottom: 1px solid rgba(var(--center-channel-color-rgb), 0.16);\n\n        .user-item {\n            justify-content: space-between;\n        }\n    }\n\n    .user-item {\n        display: flex;\n        align-items: center;\n        height: 56px;\n        padding: 0 16px;\n\n        .user-item__img {\n            border-radius: 50px;\n            width: 32px;\n            height: 32px;\n            font-size: 36px;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n        }\n\n        .user-item__title {\n            font-weight: bold;\n        }\n\n        .user-item__content {\n            display: flex;\n            align-items: center;\n        }\n\n        .user-item__button {\n            background: transparent;\n            border: 0;\n            color: rgba(var(--link-color-rgb), 1);\n            padding: 0;\n            display: flex;\n            align-items: center;\n            gap: 4px;\n\n            &:hover {\n                text-decoration: underline;\n            }\n        }\n\n        .Menu {\n            position: fixed;\n            left: 53%;\n            right: auto;\n\n            .menu-contents {\n                min-width: 240px;\n                max-width: 240px;\n            }\n        }\n    }\n\n    .ShareBoard-user-selector-item {\n        display: flex;\n        align-items: center;\n\n        img {\n            border-radius: 50px;\n            width: 24px;\n            height: 24px;\n            margin-right: 6px;\n        }\n    }\n\n}\n"
  },
  {
    "path": "webapp/src/components/shareBoard/shareBoard.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {act, render, screen} from '@testing-library/react'\nimport userEvent from '@testing-library/user-event'\nimport {Provider as ReduxProvider} from 'react-redux'\nimport thunk from 'redux-thunk'\n\nimport React from 'react'\nimport {MemoryRouter} from 'react-router'\nimport {mocked} from 'jest-mock'\n\nimport {IUser} from '../../user'\nimport {ISharing} from '../../blocks/sharing'\nimport {Channel} from '../../store/channels'\nimport {TestBlockFactory} from '../../test/testBlockFactory'\nimport {mockStateStore, wrapDNDIntl} from '../../testUtils'\nimport client from '../../octoClient'\nimport {Utils} from '../../utils'\n\nimport ShareBoard from './shareBoard'\n\njest.useFakeTimers()\n\nconst boardId = '1'\nconst workspaceId: string|undefined = boardId\nconst viewId = boardId\n\njest.mock('../../octoClient')\njest.mock('../../utils')\n\nconst mockedOctoClient = mocked(client, true)\nconst mockedUtils = mocked(Utils, true)\n\nlet params = {}\njest.mock('react-router', () => {\n    const originalModule = jest.requireActual('react-router')\n\n    return {\n        ...originalModule,\n        useRouteMatch: jest.fn(() => {\n            return {\n                url: 'http://localhost/',\n                path: '/',\n                params,\n                isExact: true,\n            }\n        }),\n    }\n})\n\nconst board = TestBlockFactory.createBoard()\nboard.id = boardId\nboard.teamId = 'team-id'\nboard.cardProperties = [\n    {\n        id: 'property1',\n        name: 'Property 1',\n        type: 'text',\n        options: [\n            {\n                id: 'value1',\n                value: 'value 1',\n                color: 'propColorBrown',\n            },\n        ],\n    },\n    {\n        id: 'property2',\n        name: 'Property 2',\n        type: 'select',\n        options: [\n            {\n                id: 'value2',\n                value: 'value 2',\n                color: 'propColorBlue',\n            },\n        ],\n    },\n]\nboard.channelId = 'channel_1'\n\nconst activeView = TestBlockFactory.createBoardView(board)\nactiveView.id = 'view1'\nactiveView.fields.hiddenOptionIds = []\nactiveView.fields.visiblePropertyIds = ['property1']\nactiveView.fields.visibleOptionIds = ['value1']\n\nconst fakeBoard = {id: board.id}\nactiveView.boardId = fakeBoard.id\n\nconst card1 = TestBlockFactory.createCard(board)\ncard1.id = 'card1'\ncard1.title = 'card-1'\ncard1.boardId = fakeBoard.id\n\nconst card2 = TestBlockFactory.createCard(board)\ncard2.id = 'card2'\ncard2.title = 'card-2'\ncard2.boardId = fakeBoard.id\n\nconst card3 = TestBlockFactory.createCard(board)\ncard3.id = 'card3'\ncard3.title = 'card-3'\ncard3.boardId = fakeBoard.id\n\nconst me: IUser = {\n    id: 'user-id-1',\n    username: 'username_1',\n    email: '',\n    nickname: '',\n    firstname: '',\n    lastname: '',\n    props: {},\n    create_at: 0,\n    update_at: 0,\n    is_bot: false,\n    is_guest: false,\n    roles: 'system_user',\n}\n\nconst categoryAttribute1 = TestBlockFactory.createCategoryBoards()\ncategoryAttribute1.name = 'Category 1'\ncategoryAttribute1.boardMetadata = [{boardID: board.id, hidden: false}]\n\ndescribe('src/components/shareBoard/shareBoard', () => {\n    const w = (window as any)\n    const oldBaseURL = w.baseURL\n\n    const state = {\n        teams: {\n            current: {id: 'team-id', title: 'Test Team'},\n        },\n        users: {\n            me,\n            boardUsers: {[me.id]: me},\n            blockSubscriptions: [],\n        },\n        boards: {\n            current: board.id,\n            boards: {\n                [board.id]: board,\n            },\n            templates: [],\n            membersInBoards: {\n                [board.id]: {},\n            },\n            myBoardMemberships: {\n                [board.id]: {userId: me.id, schemeAdmin: true},\n            },\n        },\n        globalTemplates: {\n            value: [],\n        },\n        views: {\n            views: {\n                [activeView.id]: activeView,\n            },\n            current: activeView.id,\n        },\n        cards: {\n            templates: [],\n            cards: [card1, card2, card3],\n        },\n        searchText: {},\n        clientConfig: {\n            value: {\n                telemetry: true,\n                telemetryid: 'telemetry',\n                enablePublicSharedBoards: true,\n                teammateNameDisplay: 'username',\n            },\n        },\n        contents: {\n            contents: {},\n        },\n        comments: {\n            comments: {},\n        },\n        sidebar: {\n            categoryAttributes: [\n                categoryAttribute1,\n            ],\n        },\n    }\n\n    const store = mockStateStore([thunk], state)\n    beforeEach(() => {\n        jest.clearAllMocks()\n        mockedUtils.buildURL.mockImplementation((path) => (w.baseURL || '') + path)\n\n        params = {\n            boardId,\n            viewId,\n            workspaceId,\n        }\n\n        mockedOctoClient.getChannel.mockResolvedValue({type: 'P', display_name: 'Dunder Mifflin Party Planing Committee'} as Channel)\n    })\n\n    afterEach(() => {\n        w.baseURL = oldBaseURL\n    })\n\n    test('should match snapshot', async () => {\n        const sharing: ISharing = {\n            id: '',\n            enabled: false,\n            token: '',\n        }\n        mockedOctoClient.getSharing.mockResolvedValue(sharing)\n        let container\n        await act(async () => {\n            const result = render(\n                wrapDNDIntl(\n                    <ReduxProvider store={store}>\n                        <ShareBoard\n                            onClose={jest.fn()}\n                            enableSharedBoards={true}\n                        />\n                    </ReduxProvider>),\n                {wrapper: MemoryRouter},\n            )\n            container = result.container\n        })\n\n        expect(container).toMatchSnapshot()\n        const shareButton = screen.getByRole('button', {name: 'Share'})\n        expect(shareButton).toBeDefined()\n        const closeButton = screen.getByRole('button', {name: 'Close dialog'})\n        expect(closeButton).toBeDefined()\n    })\n\n    test('should match snapshot with sharing', async () => {\n        const sharing: ISharing = {\n            id: boardId,\n            enabled: true,\n            token: 'oneToken',\n        }\n        mockedOctoClient.getSharing.mockResolvedValue(sharing)\n\n        let container\n        await act(async () => {\n            const result = render(\n                wrapDNDIntl(\n                    <ReduxProvider store={store}>\n                        <ShareBoard\n                            onClose={jest.fn()}\n                            enableSharedBoards={true}\n                        />\n                    </ReduxProvider>),\n                {wrapper: MemoryRouter},\n            )\n            container = result.container\n        })\n        const copyLinkElement = screen.getByTitle('Copy link')\n        expect(copyLinkElement).toBeDefined()\n\n        expect(container).toMatchSnapshot()\n    })\n\n    test('return shareBoard and click Copy link', async () => {\n        const sharing: ISharing = {\n            id: boardId,\n            enabled: true,\n            token: 'oneToken',\n        }\n        mockedOctoClient.getSharing.mockResolvedValue(sharing)\n\n        let container\n        await act(async () => {\n            const result = render(\n                wrapDNDIntl(\n                    <ReduxProvider store={store}>\n                        <ShareBoard\n                            onClose={jest.fn()}\n                            enableSharedBoards={true}\n                        />\n                    </ReduxProvider>),\n                {wrapper: MemoryRouter},\n            )\n            container = result.container\n        })\n\n        expect(container).toMatchSnapshot()\n\n        const copyLinkElement = screen.getByTitle('Copy link')\n        expect(copyLinkElement).toBeDefined()\n\n        act(() => {\n            userEvent.click(copyLinkElement!)\n        })\n\n        expect(mockedUtils.copyTextToClipboard).toBeCalledTimes(1)\n        expect(container).toMatchSnapshot()\n\n        const copiedLinkElement = screen.getByText('Copied!')\n        expect(copiedLinkElement).toBeDefined()\n    })\n\n    test('return shareBoard and click Regenerate token', async () => {\n        window.confirm = jest.fn(() => {\n            return true\n        })\n        const sharing: ISharing = {\n            id: boardId,\n            enabled: true,\n            token: 'oneToken',\n        }\n        mockedOctoClient.getSharing.mockResolvedValue(sharing)\n\n        let container\n        await act(async () => {\n            const result = render(\n                wrapDNDIntl(\n                    <ReduxProvider store={store}>\n                        <ShareBoard\n                            onClose={jest.fn()}\n                            enableSharedBoards={true}\n                        />\n                    </ReduxProvider>),\n                {wrapper: MemoryRouter},\n            )\n            container = result.container\n        })\n\n        sharing.token = 'anotherToken'\n        mockedUtils.createGuid.mockReturnValue('anotherToken')\n        mockedOctoClient.getSharing.mockResolvedValue(sharing)\n        mockedOctoClient.setSharing.mockResolvedValue(true)\n\n        const publishButton = screen.getByRole('button', {name: 'Publish'})\n        expect(publishButton).toBeDefined()\n        userEvent.click(publishButton)\n        await act(async () => {\n            jest.runOnlyPendingTimers()\n        })\n\n        const regenerateTokenElement = screen.getByRole('button', {name: 'Regenerate token'})\n        expect(regenerateTokenElement).toBeDefined()\n        userEvent.click(regenerateTokenElement)\n        await act(async () => {\n            jest.runOnlyPendingTimers()\n        })\n        expect(mockedOctoClient.setSharing).toBeCalledTimes(1)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('return shareBoard, and click switch', async () => {\n        const sharing: ISharing = {\n            id: boardId,\n            enabled: true,\n            token: 'oneToken',\n        }\n        mockedOctoClient.getSharing.mockResolvedValue(sharing)\n        let container: Element | undefined\n        await act(async () => {\n            const result = render(\n                wrapDNDIntl(\n                    <ReduxProvider store={store}>\n                        <ShareBoard\n                            onClose={jest.fn()}\n                            enableSharedBoards={true}\n                        />\n                    </ReduxProvider>),\n                {wrapper: MemoryRouter},\n            )\n            container = result.container\n        })\n\n        const publishButton = screen.getByRole('button', {name: 'Publish'})\n        expect(publishButton).toBeDefined()\n        userEvent.click(publishButton)\n        await act(async () => {\n            jest.runOnlyPendingTimers()\n        })\n\n        const switchElement = container?.querySelector('.Switch')\n        expect(switchElement).toBeDefined()\n        await act(async () => {\n            userEvent.click(switchElement!)\n        })\n\n        expect(mockedOctoClient.setSharing).toBeCalledTimes(1)\n        expect(mockedOctoClient.getSharing).toBeCalledTimes(2)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('return shareBoardComponent and click Switch without sharing', async () => {\n        const sharing: ISharing = {\n            id: '',\n            enabled: false,\n            token: '',\n        }\n        mockedOctoClient.getSharing.mockResolvedValue(sharing)\n        mockedUtils.createGuid.mockReturnValue('aToken')\n        let container: Element | undefined\n        await act(async () => {\n            const result = render(\n                wrapDNDIntl(\n                    <ReduxProvider store={store}>\n                        <ShareBoard\n                            onClose={jest.fn()}\n                            enableSharedBoards={true}\n                        />\n                    </ReduxProvider>),\n                {wrapper: MemoryRouter},\n            )\n            container = result.container\n            mockedOctoClient.getSharing.mockResolvedValue({\n                id: boardId,\n                enabled: true,\n                token: 'aToken',\n            })\n\n            const publishButton = screen.getByRole('button', {name: 'Publish'})\n            expect(publishButton).toBeDefined()\n            userEvent.click(publishButton)\n            jest.runOnlyPendingTimers()\n\n            const switchElement = container?.querySelector('.Switch')\n            expect(switchElement).toBeDefined()\n            userEvent.click(switchElement!)\n            jest.runOnlyPendingTimers()\n            result.rerender(\n                wrapDNDIntl(\n                    <ReduxProvider store={store}>\n                        <ShareBoard\n                            onClose={jest.fn()}\n                            enableSharedBoards={true}\n                        />\n                    </ReduxProvider>))\n        })\n\n        expect(mockedOctoClient.setSharing).toBeCalledTimes(1)\n        expect(mockedOctoClient.getSharing).toBeCalledTimes(2)\n        expect(mockedUtils.createGuid).toBeCalledTimes(1)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot with sharing and without workspaceId and subpath', async () => {\n        w.baseURL = '/test-subpath/plugins/boards'\n        const sharing: ISharing = {\n            id: boardId,\n            enabled: true,\n            token: 'oneToken',\n        }\n        params = {\n            boardId,\n            viewId,\n        }\n        mockedOctoClient.getSharing.mockResolvedValue(sharing)\n        let container\n        await act(async () => {\n            const result = render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <ShareBoard\n                        onClose={jest.fn()}\n                        enableSharedBoards={true}\n                    />\n                </ReduxProvider>),\n            {wrapper: MemoryRouter})\n            container = result.container\n        })\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot with sharing and subpath', async () => {\n        w.baseURL = '/test-subpath/plugins/boards'\n        const sharing: ISharing = {\n            id: boardId,\n            enabled: true,\n            token: 'oneToken',\n        }\n        mockedOctoClient.getSharing.mockResolvedValue(sharing)\n        let container\n        await act(async () => {\n            const result = render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <ShareBoard\n                        onClose={jest.fn()}\n                        enableSharedBoards={true}\n                    />\n                </ReduxProvider>),\n            {wrapper: MemoryRouter})\n            container = result.container\n        })\n        expect(container).toMatchSnapshot()\n    })\n\n    test('return shareBoard and click Select', async () => {\n        const sharing: ISharing = {\n            id: '',\n            enabled: false,\n            token: '',\n        }\n        mockedOctoClient.getSharing.mockResolvedValue(sharing)\n        mockedUtils.getUserDisplayName.mockImplementation((u) => u.username)\n\n        const users: IUser[] = [\n            {id: 'userid1', username: 'username_1'} as IUser,\n            {id: 'userid2', username: 'username_2'} as IUser,\n            {id: 'userid3', username: 'username_3'} as IUser,\n            {id: 'userid4', username: 'username_4'} as IUser,\n        ]\n        const channels: Channel[] = [\n            {id: 'channel1', type: 'P', display_name: 'Channel 1'} as Channel,\n            {id: 'channel2', type: 'P', display_name: 'Channel 2'} as Channel,\n            {id: 'channel3', type: 'O', display_name: 'Channel 3'} as Channel,\n            {id: 'channel4', type: 'O', display_name: 'Channel 4'} as Channel,\n        ]\n\n        mockedOctoClient.searchTeamUsers.mockResolvedValue(users)\n        mockedOctoClient.searchUserChannels.mockResolvedValue(channels)\n\n        let container\n        await act(async () => {\n            const result = render(\n                wrapDNDIntl(\n                    <ReduxProvider store={store}>\n                        <ShareBoard\n                            onClose={jest.fn()}\n                            enableSharedBoards={false}\n                        />\n                    </ReduxProvider>),\n                {wrapper: MemoryRouter},\n            )\n            container = result.container\n        })\n\n        expect(container).toMatchSnapshot()\n        const selectElement = screen.getByText('Search for people and channels')\n        expect(selectElement).toBeDefined()\n\n        await act(async () => {\n            userEvent.click(selectElement!)\n        })\n\n        expect(container).toMatchSnapshot()\n    })\n\n    test('return shareBoard and click Select, non-plugin mode', async () => {\n        const sharing: ISharing = {\n            id: '',\n            enabled: false,\n            token: '',\n        }\n        mockedOctoClient.getSharing.mockResolvedValue(sharing)\n        const users: IUser[] = [\n            {id: 'userid1', username: 'username_1', permissions: ['manage_team']} as IUser,\n            {id: 'userid2', username: 'username_2', permissions: ['manage_system']} as IUser,\n            {id: 'userid3', username: 'username_3'} as IUser,\n            {id: 'userid4', username: 'username_4'} as IUser,\n        ]\n        const channels: Channel[] = [\n            {id: 'channel1', type: 'P', display_name: 'Channel 1'} as Channel,\n            {id: 'channel2', type: 'P', display_name: 'Channel 2'} as Channel,\n            {id: 'channel3', type: 'O', display_name: 'Channel 3'} as Channel,\n            {id: 'channel4', type: 'O', display_name: 'Channel 4'} as Channel,\n        ]\n\n        mockedOctoClient.searchTeamUsers.mockResolvedValue(users)\n        mockedOctoClient.searchUserChannels.mockResolvedValue(channels)\n\n        let container\n        await act(async () => {\n            const result = render(\n                wrapDNDIntl(\n                    <ReduxProvider store={store}>\n                        <ShareBoard\n                            onClose={jest.fn()}\n                            enableSharedBoards={false}\n                        />\n                    </ReduxProvider>),\n                {wrapper: MemoryRouter},\n            )\n            container = result.container\n        })\n\n        expect(container).toMatchSnapshot()\n        const selectElement = screen.getByText('Search for people and channels')\n        expect(selectElement).toBeDefined()\n\n        await act(async () => {\n            userEvent.click(selectElement!)\n        })\n\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot, with template', async () => {\n        const sharing: ISharing = {\n            id: '',\n            enabled: false,\n            token: '',\n        }\n        mockedOctoClient.getSharing.mockResolvedValue(sharing)\n\n        board.isTemplate = true\n        const myStore = mockStateStore([thunk], state)\n\n        let container\n        await act(async () => {\n            const result = render(\n                wrapDNDIntl(\n                    <ReduxProvider store={myStore}>\n                        <ShareBoard\n                            onClose={jest.fn()}\n                            enableSharedBoards={true}\n                        />\n                    </ReduxProvider>),\n                {wrapper: MemoryRouter},\n            )\n            container = result.container\n        })\n\n        expect(container).toMatchSnapshot()\n        const closeButton = screen.getByRole('button', {name: 'Close dialog'})\n        expect(closeButton).toBeDefined()\n    })\n\n    test('return shareBoard template and click Select', async () => {\n        const sharing: ISharing = {\n            id: '',\n            enabled: false,\n            token: '',\n        }\n        mockedOctoClient.getSharing.mockResolvedValue(sharing)\n        mockedUtils.getUserDisplayName.mockImplementation((u) => u.username)\n\n        const users: IUser[] = [\n            {id: 'userid1', username: 'username_1'} as IUser,\n            {id: 'userid2', username: 'username_2'} as IUser,\n            {id: 'userid3', username: 'username_3'} as IUser,\n            {id: 'userid4', username: 'username_4'} as IUser,\n        ]\n        const channels: Channel[] = [\n            {id: 'channel1', type: 'P', display_name: 'Channel 1'} as Channel,\n            {id: 'channel2', type: 'P', display_name: 'Channel 2'} as Channel,\n            {id: 'channel3', type: 'O', display_name: 'Channel 3'} as Channel,\n            {id: 'channel4', type: 'O', display_name: 'Channel 4'} as Channel,\n        ]\n\n        mockedOctoClient.searchTeamUsers.mockResolvedValue(users)\n        mockedOctoClient.searchUserChannels.mockResolvedValue(channels)\n\n        board.isTemplate = true\n        const myStore = mockStateStore([thunk], state)\n\n        let container\n        await act(async () => {\n            const result = render(\n                wrapDNDIntl(\n                    <ReduxProvider store={myStore}>\n                        <ShareBoard\n                            onClose={jest.fn()}\n                            enableSharedBoards={false}\n                        />\n                    </ReduxProvider>),\n                {wrapper: MemoryRouter},\n            )\n            container = result.container\n        })\n\n        expect(container).toMatchSnapshot()\n        const selectElement = screen.getByText('Search for people')\n        expect(selectElement).toBeDefined()\n\n        await act(async () => {\n            userEvent.click(selectElement!)\n        })\n\n        expect(mockedOctoClient.searchUserChannels).not.toBeCalled()\n        expect(container).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/shareBoard/shareBoard.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React, {useState, useEffect} from 'react'\n\nimport {useIntl, FormattedMessage} from 'react-intl'\nimport {generatePath, useRouteMatch} from 'react-router-dom'\nimport Select from 'react-select/async'\nimport {CSSObject} from '@emotion/serialize'\n\nimport {useAppSelector} from '../../store/hooks'\nimport {getCurrentBoard, getCurrentBoardMembers} from '../../store/boards'\nimport {Channel} from '../../store/channels'\nimport {getMe, getBoardUsersList} from '../../store/users'\n\nimport {ClientConfig} from '../../config/clientConfig'\nimport {getClientConfig} from '../../store/clientConfig'\n\nimport {Utils, IDType} from '../../utils'\nimport Tooltip from '../../widgets/tooltip'\nimport mutator from '../../mutator'\n\nimport {ISharing} from '../../blocks/sharing'\nimport {BoardMember, createBoard, MemberRole} from '../../blocks/board'\n\nimport client from '../../octoClient'\nimport Dialog from '../dialog'\nimport ConfirmationDialog from '../confirmationDialogBox'\nimport {IUser} from '../../user'\nimport Switch from '../../widgets/switch'\nimport Button from '../../widgets/buttons/button'\nimport {sendFlashMessage} from '../flashMessages'\nimport {Permission} from '../../constants'\nimport GuestBadge from '../../widgets/guestBadge'\nimport AdminBadge from '../../widgets/adminBadge/adminBadge'\n\nimport TelemetryClient, {TelemetryActions, TelemetryCategory} from '../../telemetry/telemetryClient'\n\nimport {getSelectBaseStyle} from '../../theme'\nimport CompassIcon from '../../widgets/icons/compassIcon'\nimport IconButton from '../../widgets/buttons/iconButton'\nimport SearchIcon from '../../widgets/icons/search'\n\nimport BoardPermissionGate from '../permissions/boardPermissionGate'\n\nimport {useHasPermissions} from '../../hooks/permissions'\n\nimport TeamPermissionsRow from './teamPermissionsRow'\nimport UserPermissionsRow from './userPermissionsRow'\n\nimport './shareBoard.scss'\n\ntype Props = {\n    onClose: () => void\n    enableSharedBoards: boolean\n}\n\nconst baseStyles = getSelectBaseStyle()\n\nconst styles = {\n    ...baseStyles,\n    control: (): CSSObject => ({\n        border: 0,\n        width: '100%',\n        height: '100%',\n        margin: '0',\n        display: 'flex',\n        flexDirection: 'row',\n    }),\n    menu: (provided: CSSObject): CSSObject => ({\n        ...provided,\n        minWidth: '100%',\n        width: 'max-content',\n        background: 'rgb(var(--center-channel-bg-rgb))',\n        left: '0',\n        marginBottom: '0',\n    }),\n    singleValue: (provided: CSSObject): CSSObject => ({\n        ...baseStyles.singleValue(provided),\n        opacity: '0.8',\n        fontSize: '12px',\n        right: '0',\n        textTransform: 'uppercase',\n    }),\n}\n\nfunction isLastAdmin(members: BoardMember[]) {\n    let adminCount = 0\n    for (const member of members) {\n        if (member.schemeAdmin) {\n            if (++adminCount > 1) {\n                return false\n            }\n        }\n    }\n    return true\n}\n\nexport default function ShareBoardDialog(props: Props): JSX.Element {\n    const [wasCopiedPublic, setWasCopiedPublic] = useState(false)\n    const [wasCopiedInternal, setWasCopiedInternal] = useState(false)\n    const [showLinkChannelConfirmation, setShowLinkChannelConfirmation] = useState<Channel|null>(null)\n    const [sharing, setSharing] = useState<ISharing|undefined>(undefined)\n    const [selectedUser, setSelectedUser] = useState<IUser|Channel|null>(null)\n    const clientConfig = useAppSelector<ClientConfig>(getClientConfig)\n\n    // members of the current board\n    const members = useAppSelector<{[key: string]: BoardMember}>(getCurrentBoardMembers)\n    const board = useAppSelector(getCurrentBoard)\n    const boardId = board.id\n    const boardUsers = useAppSelector<IUser[]>(getBoardUsersList)\n    const me = useAppSelector<IUser|null>(getMe)\n\n    const [publish, setPublish] = useState(false)\n\n    const intl = useIntl()\n    const match = useRouteMatch<{teamId?: string, boardId: string, viewId: string}>()\n\n    const hasSharePermissions = useHasPermissions(board.teamId, boardId, [Permission.ShareBoard])\n\n    const loadData = async () => {\n        if (hasSharePermissions) {\n            const newSharing = await client.getSharing(boardId)\n            setSharing(newSharing)\n            setWasCopiedPublic(false)\n        }\n    }\n\n    const createSharingInfo = () => {\n        const newSharing: ISharing = {\n            id: boardId,\n            enabled: true,\n            token: Utils.createGuid(IDType.Token),\n        }\n        return newSharing\n    }\n\n    const onShareChanged = async (isOn: boolean) => {\n        const newSharing: ISharing = sharing || createSharingInfo()\n        newSharing.id = boardId\n        newSharing.enabled = isOn\n        TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.ShareBoard, {board: boardId, shareBoardEnabled: isOn})\n        await client.setSharing(boardId, newSharing)\n        await loadData()\n    }\n\n    const onLinkBoard = async (channel: Channel, confirmed?: boolean) => {\n        if (!confirmed) {\n            setShowLinkChannelConfirmation(channel)\n            return\n        }\n        setShowLinkChannelConfirmation(null)\n        const newBoard = createBoard(board)\n        newBoard.channelId = channel.id // This is a channel ID hardcoded here as an example\n        mutator.updateBoard(newBoard, board, 'linked channel')\n    }\n\n    const onRegenerateToken = async () => {\n        // eslint-disable-next-line no-alert\n        const accept = window.confirm(intl.formatMessage({id: 'ShareBoard.confirmRegenerateToken', defaultMessage: 'This will invalidate previously shared links. Continue?'}))\n        if (accept) {\n            const newSharing: ISharing = sharing || createSharingInfo()\n            newSharing.token = Utils.createGuid(IDType.Token)\n            await client.setSharing(boardId, newSharing)\n            await loadData()\n\n            const description = intl.formatMessage({id: 'ShareBoard.tokenRegenrated', defaultMessage: 'Token regenerated'})\n            sendFlashMessage({content: description, severity: 'low'})\n        }\n    }\n\n    const addUser = (user: IUser) => {\n        const minimumRole = board.minimumRole || MemberRole.Viewer\n        const newMember = {\n            boardId,\n            userId: user.id,\n            roles: minimumRole,\n            schemeEditor: minimumRole === MemberRole.Editor,\n            schemeCommenter: minimumRole === MemberRole.Editor || minimumRole === MemberRole.Commenter,\n            schemeViewer: minimumRole === MemberRole.Editor || minimumRole === MemberRole.Commenter || minimumRole === MemberRole.Viewer,\n        } as BoardMember\n        mutator.createBoardMember(newMember)\n    }\n\n    const onUpdateBoardMember = (member: BoardMember, newPermission: string) => {\n        if (member.userId === me?.id && isLastAdmin(Object.values(members))) {\n            sendFlashMessage({content: intl.formatMessage({id: 'shareBoard.lastAdmin', defaultMessage: 'Boards must have at least one Administrator'}), severity: 'low'})\n            return\n        }\n\n        const newMember = {\n            boardId: member.boardId,\n            userId: member.userId,\n            roles: member.roles,\n        } as BoardMember\n\n        switch (newPermission) {\n        case MemberRole.Admin:\n            if (member.schemeAdmin) {\n                return\n            }\n            newMember.schemeAdmin = true\n            newMember.schemeEditor = true\n            break\n        case MemberRole.Editor:\n            if (!member.schemeAdmin && member.schemeEditor) {\n                return\n            }\n            newMember.schemeAdmin = false\n            newMember.schemeEditor = true\n            break\n        case MemberRole.Commenter:\n            if (!member.schemeAdmin && !member.schemeEditor && member.schemeCommenter) {\n                return\n            }\n            newMember.schemeAdmin = false\n            newMember.schemeEditor = false\n            newMember.schemeCommenter = true\n            break\n        case MemberRole.Viewer:\n            if (!member.schemeAdmin && !member.schemeEditor && !member.schemeCommenter && member.schemeViewer) {\n                return\n            }\n            newMember.schemeAdmin = false\n            newMember.schemeEditor = false\n            newMember.schemeCommenter = false\n            newMember.schemeViewer = true\n            break\n        default:\n            return\n        }\n\n        mutator.updateBoardMember(newMember, member)\n    }\n\n    const onDeleteBoardMember = (member: BoardMember) => {\n        if (member.userId === me?.id && isLastAdmin(Object.values(members))) {\n            sendFlashMessage({content: intl.formatMessage({id: 'shareBoard.lastAdmin', defaultMessage: 'Boards must have at least one Administrator'}), severity: 'low'})\n            return\n        }\n        mutator.deleteBoardMember(member)\n    }\n\n    useEffect(() => {\n        loadData()\n    }, [])\n\n    const isSharing = Boolean(sharing && sharing.id === boardId && sharing.enabled)\n    const readToken = (sharing && isSharing) ? sharing.token : ''\n    const shareUrl = new URL(window.location.toString())\n    shareUrl.searchParams.set('r', readToken)\n    const boardUrl = new URL(window.location.toString())\n\n    if (match.params.teamId) {\n        const newPath = generatePath('/team/:teamId/shared/:boardId/:viewId', {\n            boardId: match.params.boardId,\n            viewId: match.params.viewId,\n            teamId: match.params.teamId,\n        })\n        shareUrl.pathname = Utils.buildURL(newPath)\n\n        const boardPath = generatePath('/team/:teamId/:boardId/:viewId', {\n            boardId: match.params.boardId,\n            viewId: match.params.viewId,\n            teamId: match.params.teamId,\n        })\n        boardUrl.pathname = Utils.getFrontendBaseURL() + boardPath\n    } else {\n        const newPath = generatePath('/shared/:boardId/:viewId', {\n            boardId: match.params.boardId,\n            viewId: match.params.viewId,\n        })\n        shareUrl.pathname = Utils.buildURL(newPath)\n        boardUrl.pathname = Utils.buildURL(\n            generatePath(':boardId/:viewId', {\n                boardId: match.params.boardId,\n                viewId: match.params.viewId,\n            },\n            ))\n    }\n\n    const shareBoardTitle = (\n        <FormattedMessage\n            id={'ShareBoard.Title'}\n            defaultMessage={'Share Board'}\n        />\n    )\n\n    const shareTemplateTitle = (\n        <FormattedMessage\n            id={'ShareTemplate.Title'}\n            defaultMessage={'Share Template'}\n        />\n    )\n\n    const formatOptionLabel = (userOrChannel: IUser | Channel) => {\n        if ((userOrChannel as IUser).username) {\n            const user = userOrChannel as IUser\n            return (\n                <div className='user-item'>\n                    <div className='ml-3'>\n                        <strong>{Utils.getUserDisplayName(user, clientConfig.teammateNameDisplay)}</strong>\n                        <strong className='ml-2 text-light'>{`@${user.username}`}</strong>\n                        <GuestBadge show={Boolean(user?.is_guest)}/>\n                        <AdminBadge permissions={user.permissions}/>\n                    </div>\n                </div>\n            )\n        }\n\n        return null\n    }\n\n    let confirmSubtext\n    let confirmButtonText\n    if (board.channelId === '') {\n        confirmSubtext = intl.formatMessage({id: 'shareBoard.confirm-link-channel-subtext', defaultMessage: 'When you link a channel to a board, all members of the channel (existing and new) will be able to edit it. This excludes members who are guests.'})\n        confirmButtonText = intl.formatMessage({id: 'shareBoard.confirm-link-channel-button', defaultMessage: 'Link channel'})\n    } else {\n        confirmSubtext = intl.formatMessage({id: 'shareBoard.confirm-link-channel-subtext-with-other-channel', defaultMessage: 'When you link a channel to a board, all members of the channel (existing and new) will be able to edit it. This excludes members who are guests.{lineBreak}This board is currently linked to another channel.\\nIt will be unlinked if you choose to link it here.'}, {lineBreak: <p/>})\n        confirmButtonText = intl.formatMessage({id: 'shareBoard.confirm-link-channel-button-with-other-channel', defaultMessage: 'Unlink and link here'})\n    }\n\n    return (\n        <Dialog\n            onClose={props.onClose}\n            title={board.isTemplate ? shareTemplateTitle : shareBoardTitle}\n            className='ShareBoardDialog'\n        >\n            {showLinkChannelConfirmation &&\n                <ConfirmationDialog\n                    dialogBox={{\n                        heading: intl.formatMessage({id: 'shareBoard.confirm-link-channel', defaultMessage: 'Link board to channel'}),\n                        subText: confirmSubtext,\n                        confirmButtonText,\n                        destructive: board.channelId !== '',\n                        onConfirm: () => onLinkBoard(showLinkChannelConfirmation, true),\n                        onClose: () => setShowLinkChannelConfirmation(null),\n                    }}\n                />}\n            <BoardPermissionGate permissions={[Permission.ManageBoardRoles]}>\n                <div className='share-input__container'>\n                    <div className='share-input'>\n                        <SearchIcon/>\n                        <Select\n                            styles={styles}\n                            value={selectedUser}\n                            className={'userSearchInput'}\n                            cacheOptions={true}\n                            filterOption={(o) => {\n                                // render non-explicit members\n                                if (members[o.value]) {\n                                    return members[o.value].synthetic\n                                }\n\n                                // not a member, definitely render\n                                return true\n                            }}\n                            loadOptions={async (inputValue: string) => {\n                                const result = []\n                                const users = await client.searchTeamUsers(inputValue) || []\n                                result.push(...users)\n                                return result\n                            }}\n                            components={{DropdownIndicator: () => null, IndicatorSeparator: () => null}}\n                            defaultOptions={true}\n                            formatOptionLabel={formatOptionLabel}\n                            getOptionValue={(u) => u.id}\n                            getOptionLabel={(u: IUser|Channel) => (u as IUser).username || (u as Channel).display_name}\n                            isMulti={false}\n                            placeholder={board.isTemplate ? intl.formatMessage({id: 'ShareTemplate.searchPlaceholder', defaultMessage: 'Search for people'}) : intl.formatMessage({id: 'ShareBoard.searchPlaceholder', defaultMessage: 'Search for people and channels'})\n                            }\n                            onChange={(newValue) => {\n                                if (newValue && (newValue as IUser).username) {\n                                    addUser(newValue as IUser)\n                                    setSelectedUser(null)\n                                } else if (newValue) {\n                                    onLinkBoard(newValue as Channel)\n                                }\n                            }}\n                        />\n                    </div>\n                </div>\n            </BoardPermissionGate>\n            <div className='user-items'>\n                <TeamPermissionsRow/>\n\n                {boardUsers.map((user) => {\n                    if (!members[user.id]) {\n                        return null\n                    }\n                    if (members[user.id].synthetic) {\n                        return null\n                    }\n                    return (\n                        <UserPermissionsRow\n                            key={user.id}\n                            user={user}\n                            member={members[user.id]}\n                            teammateNameDisplay={me?.props?.teammateNameDisplay || clientConfig.teammateNameDisplay}\n                            onDeleteBoardMember={onDeleteBoardMember}\n                            onUpdateBoardMember={onUpdateBoardMember}\n                            isMe={user.id === me?.id}\n                        />\n                    )\n                })}\n            </div>\n\n            {props.enableSharedBoards && !board.isTemplate && (\n                <div className='tabs-container'>\n                    <button\n                        onClick={() => setPublish(false)}\n                        className={`tab-item ${!publish && 'tab-item--active'}`}\n                    >\n                        <FormattedMessage\n                            id='share-board.share'\n                            defaultMessage='Share'\n                        />\n                    </button>\n                    <BoardPermissionGate permissions={[Permission.ShareBoard]}>\n                        <button\n                            onClick={() => setPublish(true)}\n                            className={`tab-item ${publish && 'tab-item--active'}`}\n                        >\n                            <FormattedMessage\n                                id='share-board.publish'\n                                defaultMessage='Publish'\n                            />\n                        </button>\n                    </BoardPermissionGate>\n                </div>\n            )}\n            {(props.enableSharedBoards && publish && !board.isTemplate) &&\n            (<BoardPermissionGate permissions={[Permission.ShareBoard]}>\n                <div className='tabs-content'>\n                    <div>\n                        <div className='d-flex justify-content-between'>\n                            <div className='d-flex flex-column'>\n                                <div className='text-heading2'>{intl.formatMessage({id: 'ShareBoard.PublishTitle', defaultMessage: 'Publish to the web'})}</div>\n                                <div className='text-light'>{intl.formatMessage({id: 'ShareBoard.PublishDescription', defaultMessage: 'Publish and share a read-only link with everyone on the web.'})}</div>\n                            </div>\n                            <div>\n                                <Switch\n                                    isOn={isSharing}\n                                    size='medium'\n                                    onChanged={onShareChanged}\n                                />\n                            </div>\n                        </div>\n                    </div>\n                    {isSharing &&\n                            (<div className='d-flex justify-content-between tabs-inputs'>\n                                <div className='d-flex input-container'>\n                                    <a\n                                        className='shareUrl'\n                                        href={shareUrl.toString()}\n                                        target='_blank'\n                                        rel='noreferrer'\n                                    >\n                                        {shareUrl.toString()}\n                                    </a>\n                                    <Tooltip\n                                        key={'regenerateToken'}\n                                        title={intl.formatMessage({id: 'ShareBoard.regenerate', defaultMessage: 'Regenerate token'})}\n                                    >\n                                        <IconButton\n                                            size='small'\n                                            onClick={onRegenerateToken}\n                                            icon={\n                                                <CompassIcon\n                                                    icon='refresh'\n                                                />}\n                                            title={intl.formatMessage({id: 'ShareBoard.regenerate', defaultMessage: 'Regenerate token'})}\n                                        />\n                                    </Tooltip>\n                                </div>\n                                <Button\n                                    emphasis='secondary'\n                                    size='medium'\n                                    title='Copy public link'\n                                    icon={\n                                        <CompassIcon\n                                            icon='content-copy'\n                                            className='CompassIcon'\n                                        />\n                                    }\n                                    onClick={() => {\n                                        TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.ShareLinkPublicCopy, {board: boardId})\n                                        Utils.copyTextToClipboard(shareUrl.toString())\n                                        setWasCopiedPublic(true)\n                                        setWasCopiedInternal(false)\n                                    }}\n                                >\n                                    {wasCopiedPublic &&\n                                        <FormattedMessage\n                                            id='ShareBoard.copiedLink'\n                                            defaultMessage='Copied!'\n                                        />}\n                                    {!wasCopiedPublic &&\n                                        <FormattedMessage\n                                            id='ShareBoard.copyLink'\n                                            defaultMessage='Copy link'\n                                        />}\n                                </Button>\n                            </div>)\n                    }\n                </div>\n            </BoardPermissionGate>\n            )}\n\n            {!publish && !board.isTemplate && (\n                <div className='tabs-content'>\n                    <div>\n                        <div className='d-flex justify-content-between'>\n                            <div className='d-flex flex-column'>\n                                <div className='text-heading2'>{intl.formatMessage({id: 'ShareBoard.ShareInternal', defaultMessage: 'Share internally'})}</div>\n                                <div className='text-light'>{intl.formatMessage({id: 'ShareBoard.ShareInternalDescription', defaultMessage: 'Users who have permissions will be able to use this link.'})}</div>\n                            </div>\n                        </div>\n                    </div>\n                    <div className='d-flex justify-content-between tabs-inputs'>\n                        <div className='d-flex input-container'>\n                            <a\n                                className='shareUrl'\n                                href={boardUrl.toString()}\n                                target='_blank'\n                                rel='noreferrer'\n                            >\n                                {boardUrl.toString()}\n                            </a>\n                        </div>\n                        <Button\n                            emphasis='secondary'\n                            size='medium'\n                            title={intl.formatMessage({id: 'ShareBoard.copyLink', defaultMessage: 'Copy link'})}\n                            onClick={() => {\n                                TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.ShareLinkInternalCopy, {board: boardId})\n                                Utils.copyTextToClipboard(boardUrl.toString())\n                                setWasCopiedPublic(false)\n                                setWasCopiedInternal(true)\n                            }}\n                            icon={\n                                <CompassIcon\n                                    icon='content-copy'\n                                    className='CompassIcon'\n                                />\n                            }\n                        >\n                            {wasCopiedInternal &&\n                                <FormattedMessage\n                                    id='ShareBoard.copiedLink'\n                                    defaultMessage='Copied!'\n                                />}\n                            {!wasCopiedInternal &&\n                                <FormattedMessage\n                                    id='ShareBoard.copyLink'\n                                    defaultMessage='Copy link'\n                                />}\n                        </Button>\n                    </div>\n                </div>\n            )}\n        </Dialog>\n    )\n}\n"
  },
  {
    "path": "webapp/src/components/shareBoard/shareBoardButton.scss",
    "content": ""
  },
  {
    "path": "webapp/src/components/shareBoard/shareBoardButton.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {render} from '@testing-library/react'\nimport React from 'react'\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport {BoardTypeOpen} from '../../blocks/board'\n\nimport {TestBlockFactory} from '../../test/testBlockFactory'\nimport {wrapDNDIntl, mockStateStore} from '../../testUtils'\n\nimport ShareBoardButton from './shareBoardButton'\n\njest.useFakeTimers()\n\nconst boardId = '1'\n\nconst board = TestBlockFactory.createBoard()\nboard.id = boardId\n\ndescribe('src/components/shareBoard/shareBoard', () => {\n    const state = {\n        boards: {\n            boards: {\n                [board.id]: board,\n            },\n            current: board.id,\n        },\n    }\n\n    const store = mockStateStore([], state)\n\n    test('should match snapshot, Private Board', async () => {\n        const result = render(\n            wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <ShareBoardButton\n                        enableSharedBoards={true}\n                    />\n                </ReduxProvider>))\n\n        const renderer = result.container\n\n        expect(renderer).toMatchSnapshot()\n    })\n\n    test('should match snapshot, Open Board', async () => {\n        board.type = BoardTypeOpen\n        const result = render(\n            wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <ShareBoardButton\n                        enableSharedBoards={true}\n                    />\n                </ReduxProvider>))\n\n        const renderer = result.container\n\n        expect(renderer).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/shareBoard/shareBoardButton.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React, {useState} from 'react'\nimport {FormattedMessage} from 'react-intl'\n\nimport Button from '../../widgets/buttons/button'\nimport TelemetryClient, {TelemetryActions, TelemetryCategory} from '../../telemetry/telemetryClient'\nimport {useAppSelector} from '../../store/hooks'\nimport {getCurrentBoard} from '../../store/boards'\nimport Globe from '../../widgets/icons/globe'\nimport LockOutline from '../../widgets/icons/lockOutline'\nimport {BoardTypeOpen} from '../../blocks/board'\n\nimport './shareBoardButton.scss'\n\nimport ShareBoardDialog from './shareBoard'\n\ntype Props = {\n    enableSharedBoards: boolean\n}\nconst ShareBoardButton = (props: Props) => {\n    const [showShareDialog, setShowShareDialog] = useState(false)\n    const board = useAppSelector(getCurrentBoard)\n\n    const iconForBoardType = () => {\n        if (board.type === BoardTypeOpen) {\n            return <Globe/>\n        }\n        return <LockOutline/>\n    }\n\n    return (\n        <div className='ShareBoardButton'>\n            <Button\n                title='Share board'\n                size='medium'\n                emphasis='primary'\n                icon={iconForBoardType()}\n                onClick={() => {\n                    TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.ShareBoardOpenModal, {board: board.id})\n                    setShowShareDialog(!showShareDialog)\n                }}\n            >\n                <FormattedMessage\n                    id='CenterPanel.Share'\n                    defaultMessage='Share'\n                />\n            </Button>\n            {showShareDialog &&\n                <ShareBoardDialog\n                    onClose={() => setShowShareDialog(false)}\n                    enableSharedBoards={props.enableSharedBoards}\n                />}\n        </div>\n    )\n}\n\nexport default React.memo(ShareBoardButton)\n"
  },
  {
    "path": "webapp/src/components/shareBoard/shareBoardLoginButton.scss",
    "content": ""
  },
  {
    "path": "webapp/src/components/shareBoard/shareBoardLoginButton.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {render} from '@testing-library/react'\nimport React from 'react'\n\nimport {TestBlockFactory} from '../../test/testBlockFactory'\nimport {wrapDNDIntl} from '../../testUtils'\n\nimport ShareBoardLoginButton from './shareBoardLoginButton'\njest.useFakeTimers()\n\nconst boardId = '1'\n\nconst board = TestBlockFactory.createBoard()\nboard.id = boardId\n\njest.mock('react-router-dom', () => {\n    const originalModule = jest.requireActual('react-router-dom')\n\n    return {\n        ...originalModule,\n        useRouteMatch: jest.fn(() => {\n            return {\n                teamId: 'team1',\n                boardId: 'boardId1',\n                viewId: 'viewId1',\n                cardId: 'cardId1',\n            }\n        }),\n    }\n})\n\ndescribe('src/components/shareBoard/shareBoardLoginButton', () => {\n    const savedLocation = window.location\n\n    afterEach(() => {\n        window.location = savedLocation\n    })\n\n    test('should match snapshot', async () => {\n        // delete window.location\n        window.location = Object.assign(new URL('https://example.org/mattermost'))\n        const result = render(\n            wrapDNDIntl(\n                <ShareBoardLoginButton/>,\n            ))\n        const renderer = result.container\n\n        expect(renderer).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/shareBoard/shareBoardLoginButton.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React, {useCallback} from 'react'\nimport {FormattedMessage} from 'react-intl'\nimport {generatePath, useRouteMatch, useHistory} from 'react-router-dom'\n\nimport Button from '../../widgets/buttons/button'\nimport TelemetryClient, {TelemetryActions, TelemetryCategory} from '../../telemetry/telemetryClient'\nimport {Utils} from '../../utils'\n\nimport './shareBoardLoginButton.scss'\n\nconst ShareBoardLoginButton = () => {\n    const match = useRouteMatch<{teamId: string, boardId: string, viewId?: string, cardId?: string}>()\n    const history = useHistory()\n\n    let redirectQueryParam = 'r=' + encodeURIComponent(generatePath('/:boardId?/:viewId?/:cardId?', match.params))\n    if (Utils.isFocalboardLegacy()) {\n        redirectQueryParam = 'redirect_to=' + encodeURIComponent(generatePath('/boards/team/:teamId/:boardId?/:viewId?/:cardId?', match.params))\n    }\n    const loginPath = '/login?' + redirectQueryParam\n\n    const onLoginClick = useCallback(() => {\n        TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.ShareBoardLogin)\n        if (Utils.isFocalboardLegacy()) {\n            location.assign(loginPath)\n        } else {\n            history.push(loginPath)\n        }\n    }, [])\n\n    return (\n        <div className='ShareBoardLoginButton'>\n            <Button\n                title='Login'\n                size='medium'\n                emphasis='primary'\n                onClick={() => onLoginClick()}\n            >\n                <FormattedMessage\n                    id='CenterPanel.Login'\n                    defaultMessage='Login'\n                />\n            </Button>\n        </div>\n    )\n}\n\nexport default React.memo(ShareBoardLoginButton)\n"
  },
  {
    "path": "webapp/src/components/shareBoard/teamPermissionsRow.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {act, render} from '@testing-library/react'\nimport userEvent from '@testing-library/user-event'\nimport {Provider as ReduxProvider} from 'react-redux'\nimport thunk from 'redux-thunk'\n\nimport React from 'react'\nimport {MemoryRouter} from 'react-router'\n\nimport {IUser} from '../../user'\nimport {TestBlockFactory} from '../../test/testBlockFactory'\nimport {mockStateStore, wrapDNDIntl} from '../../testUtils'\n\nimport {MemberRole} from '../../blocks/board'\n\nimport TeamPermissionsRow from './teamPermissionsRow'\n\njest.useFakeTimers()\n\nconst boardId = '1'\n\njest.mock('../../utils')\n\nconst board = TestBlockFactory.createBoard()\nboard.id = boardId\nboard.teamId = 'team-id'\nboard.channelId = 'channel_1'\nboard.minimumRole = MemberRole.Editor\n\ndescribe('src/components/shareBoard/teamPermissionsRow', () => {\n    const me: IUser = {\n        id: 'user-id-1',\n        username: 'username_1',\n        email: '',\n        nickname: '',\n        firstname: '',\n        lastname: '',\n        props: {},\n        create_at: 0,\n        update_at: 0,\n        is_bot: false,\n        is_guest: false,\n        roles: 'system_user',\n    }\n\n    const state = {\n        teams: {\n            current: {id: 'team-id', title: 'Test Team'},\n        },\n        users: {\n            me,\n            boardUsers: [me],\n            blockSubscriptions: [],\n        },\n        boards: {\n            current: board.id,\n            boards: {\n                [board.id]: board,\n            },\n            templates: [],\n            membersInBoards: {\n                [board.id]: {},\n            },\n            myBoardMemberships: {\n                [board.id]: {userId: me.id, schemeAdmin: true},\n            },\n        },\n    }\n\n    beforeEach(() => {\n        jest.clearAllMocks()\n    })\n\n    test('should match snapshot', async () => {\n        let container: Element | undefined\n        const store = mockStateStore([thunk], state)\n        await act(async () => {\n            const result = render(\n                wrapDNDIntl(\n                    <ReduxProvider store={store}>\n                        <TeamPermissionsRow/>\n                    </ReduxProvider>),\n                {wrapper: MemoryRouter},\n            )\n            container = result.container\n        })\n\n        const buttonElement = container?.querySelector('.user-item__button')\n        expect(buttonElement).toBeDefined()\n        userEvent.click(buttonElement!)\n\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot in plugin mode', async () => {\n        let container: Element | undefined\n        const store = mockStateStore([thunk], state)\n        await act(async () => {\n            const result = render(\n                wrapDNDIntl(\n                    <ReduxProvider store={store}>\n                        <TeamPermissionsRow/>\n                    </ReduxProvider>),\n                {wrapper: MemoryRouter},\n            )\n            container = result.container\n        })\n\n        const buttonElement = container?.querySelector('.user-item__button')\n        expect(buttonElement).toBeDefined()\n        userEvent.click(buttonElement!)\n\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot in template', async () => {\n        let container: Element | undefined\n        const testState = {\n            ...state,\n            boards: {\n                ...state.boards,\n                boards: {},\n                templates: {\n                    [board.id]: {...board, isTemplate: true},\n                },\n            },\n        }\n        const store = mockStateStore([thunk], testState)\n        await act(async () => {\n            const result = render(\n                wrapDNDIntl(\n                    <ReduxProvider store={store}>\n                        <TeamPermissionsRow/>\n                    </ReduxProvider>),\n                {wrapper: MemoryRouter},\n            )\n            container = result.container\n        })\n\n        const buttonElement = container?.querySelector('.user-item__button')\n        expect(buttonElement).toBeDefined()\n        userEvent.click(buttonElement!)\n\n        expect(container).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/shareBoard/teamPermissionsRow.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React, {useState} from 'react'\nimport {useIntl} from 'react-intl'\n\nimport MenuWrapper from '../../widgets/menuWrapper'\nimport Menu from '../../widgets/menu'\n\nimport CheckIcon from '../../widgets/icons/check'\nimport CompassIcon from '../../widgets/icons/compassIcon'\n\nimport {Board, createBoard, BoardTypeOpen, BoardTypePrivate, MemberRole} from '../../blocks/board'\nimport {useAppSelector} from '../../store/hooks'\nimport {getCurrentTeam} from '../../store/teams'\nimport {getCurrentBoard} from '../../store/boards'\nimport {Permission} from '../../constants'\n\nimport BoardPermissionGate from '../permissions/boardPermissionGate'\nimport ConfirmationDialogBox from '../confirmationDialogBox'\n\nimport mutator from '../../mutator'\n\nasync function updateBoardType(board: Board, newType: string, newMinimumRole: MemberRole) {\n    if (board.type === newType && board.minimumRole === newMinimumRole) {\n        return\n    }\n\n    const newBoard = createBoard(board)\n    newBoard.type = newType\n    newBoard.minimumRole = newMinimumRole\n\n    await mutator.updateBoard(newBoard, board, 'update board type')\n}\n\nconst TeamPermissionsRow = (): JSX.Element => {\n    const intl = useIntl()\n    const team = useAppSelector(getCurrentTeam)\n    const board = useAppSelector(getCurrentBoard)\n    const [changeRoleConfirmation, setChangeRoleConfirmation] = useState<MemberRole|null>(null)\n\n    const onChangeRole = async () => {\n        if (changeRoleConfirmation !== null) {\n            await updateBoardType(board, BoardTypeOpen, changeRoleConfirmation)\n            setChangeRoleConfirmation(null)\n        }\n    }\n\n    let currentRoleName = intl.formatMessage({id: 'BoardMember.schemeAdmin', defaultMessage: 'Admin'})\n    if (board.type === BoardTypeOpen) {\n        currentRoleName = intl.formatMessage({id: 'BoardMember.schemeEditor', defaultMessage: 'Editor'})\n        if (board.minimumRole === MemberRole.Editor) {\n            if (board.isTemplate) {\n                currentRoleName = intl.formatMessage({id: 'BoardMember.schemeViewer', defaultMessage: 'Viewer'})\n            } else {\n                currentRoleName = intl.formatMessage({id: 'BoardMember.schemeEditor', defaultMessage: 'Editor'})\n            }\n        } else if (board.minimumRole === MemberRole.Commenter) {\n            currentRoleName = intl.formatMessage({id: 'BoardMember.schemeCommenter', defaultMessage: 'Commenter'})\n        } else if (board.minimumRole === MemberRole.Viewer) {\n            currentRoleName = intl.formatMessage({id: 'BoardMember.schemeViewer', defaultMessage: 'Viewer'})\n        }\n    } else {\n        currentRoleName = intl.formatMessage({id: 'BoardMember.schemeNone', defaultMessage: 'None'})\n    }\n\n    const confirmationDialog = (\n        <ConfirmationDialogBox\n            dialogBox={{\n                heading: intl.formatMessage({\n                    id: 'shareBoard.confirm-change-team-role.title',\n                    defaultMessage: 'Change minimum board role',\n                }),\n                subText: intl.formatMessage({\n                    id: 'shareBoard.confirm-change-team-role.body',\n                    defaultMessage: 'Everyone on this board with a lower permission than the \"{role}\" role will <b>now be promoted to {role}</b>. Are you sure you want to change the minimum role for the board?',\n                }, {\n                    b: (...chunks) => <b>{chunks}</b>,\n                    role: changeRoleConfirmation === MemberRole.Editor ? intl.formatMessage({id: 'BoardMember.schemeEditor', defaultMessage: 'Editor'}) : intl.formatMessage({id: 'BoardMember.schemeCommenter', defaultMessage: 'Commenter'}),\n                }),\n                confirmButtonText: intl.formatMessage({\n                    id: 'shareBoard.confirm-change-team-role.confirmBtnText',\n                    defaultMessage: 'Change minimum board role',\n                }),\n                onConfirm: onChangeRole,\n                onClose: () => setChangeRoleConfirmation(null),\n            }}\n        />\n    )\n\n    return (\n        <div className='user-item'>\n            {changeRoleConfirmation && confirmationDialog}\n            <div className='user-item__content'>\n                <div className='ml-3'><strong>{intl.formatMessage({id: 'ShareBoard.teamPermissionsText', defaultMessage: 'Everyone at {teamName} Team'}, {teamName: team?.title})}</strong></div>\n            </div>\n            <div>\n                <BoardPermissionGate permissions={[Permission.ManageBoardType]}>\n                    <MenuWrapper>\n                        <button className='user-item__button'>\n                            {currentRoleName}\n                            <CompassIcon\n                                icon='chevron-down'\n                                className='CompassIcon'\n                            />\n                        </button>\n                        <Menu position='left'>\n                            {!board.isTemplate &&\n                                <Menu.Text\n                                    id={MemberRole.Editor}\n                                    check={board.minimumRole === undefined || board.minimumRole === MemberRole.Editor}\n                                    icon={board.type === BoardTypeOpen && board.minimumRole === MemberRole.Editor ? <CheckIcon/> : <div className='empty-icon'/>}\n                                    name={intl.formatMessage({id: 'BoardMember.schemeEditor', defaultMessage: 'Editor'})}\n                                    onClick={() => setChangeRoleConfirmation(MemberRole.Editor)}\n                                />}\n                            {!board.isTemplate &&\n                                <Menu.Text\n                                    id={MemberRole.Commenter}\n                                    check={board.minimumRole === MemberRole.Commenter}\n                                    icon={board.type === BoardTypeOpen && board.minimumRole === MemberRole.Commenter ? <CheckIcon/> : <div className='empty-icon'/>}\n                                    name={intl.formatMessage({id: 'BoardMember.schemeCommenter', defaultMessage: 'Commenter'})}\n                                    onClick={() => setChangeRoleConfirmation(MemberRole.Commenter)}\n                                />}\n                            <Menu.Text\n                                id={MemberRole.Viewer}\n                                check={board.minimumRole === MemberRole.Viewer}\n                                icon={board.type === BoardTypeOpen && board.minimumRole === MemberRole.Viewer ? <CheckIcon/> : <div className='empty-icon'/>}\n                                name={intl.formatMessage({id: 'BoardMember.schemeViewer', defaultMessage: 'Viewer'})}\n                                onClick={() => updateBoardType(board, BoardTypeOpen, MemberRole.Viewer)}\n                            />\n                            <Menu.Text\n                                id={MemberRole.None}\n                                check={true}\n                                icon={board.type === BoardTypePrivate ? <CheckIcon/> : <div className='empty-icon'/>}\n                                name={intl.formatMessage({id: 'BoardMember.schemeNone', defaultMessage: 'None'})}\n                                onClick={() => updateBoardType(board, BoardTypePrivate, MemberRole.None)}\n                            />\n                        </Menu>\n                    </MenuWrapper>\n                </BoardPermissionGate>\n                <BoardPermissionGate\n                    permissions={[Permission.ManageBoardType]}\n                    invert={true}\n                >\n                    <span>{currentRoleName}</span>\n                </BoardPermissionGate>\n            </div>\n        </div>\n    )\n}\n\nexport default TeamPermissionsRow\n"
  },
  {
    "path": "webapp/src/components/shareBoard/userPermissionsRow.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {act, render} from '@testing-library/react'\nimport userEvent from '@testing-library/user-event'\nimport {Provider as ReduxProvider} from 'react-redux'\nimport thunk from 'redux-thunk'\n\nimport React from 'react'\nimport {MemoryRouter} from 'react-router'\n\nimport {BoardMember} from '../../blocks/board'\n\nimport {IUser} from '../../user'\nimport {TestBlockFactory} from '../../test/testBlockFactory'\nimport {mockStateStore, wrapDNDIntl} from '../../testUtils'\n\nimport UserPermissionsRow from './userPermissionsRow'\n\njest.useFakeTimers()\n\nconst boardId = '1'\n\njest.mock('../../utils')\n\nconst board = TestBlockFactory.createBoard()\nboard.id = boardId\nboard.teamId = 'team-id'\nboard.channelId = 'channel_1'\n\ndescribe('src/components/shareBoard/userPermissionsRow', () => {\n    const me: IUser = {\n        id: 'user-id-1',\n        username: 'username_1',\n        email: '',\n        nickname: '',\n        firstname: '',\n        lastname: '',\n        props: {},\n        create_at: 0,\n        update_at: 0,\n        is_bot: false,\n        is_guest: false,\n        roles: 'system_user',\n    }\n\n    const state = {\n        teams: {\n            current: {id: 'team-id', title: 'Test Team'},\n        },\n        users: {\n            me,\n            boardUsers: [me],\n            blockSubscriptions: [],\n        },\n        boards: {\n            current: board.id,\n            boards: {\n                [board.id]: board,\n            },\n            templates: [],\n            membersInBoards: {\n                [board.id]: {},\n            },\n            myBoardMemberships: {\n                [board.id]: {userId: me.id, schemeAdmin: true},\n            },\n        },\n    }\n\n    beforeEach(() => {\n        jest.clearAllMocks()\n    })\n\n    test('should match snapshot', async () => {\n        let container: Element | undefined\n        const store = mockStateStore([thunk], state)\n        await act(async () => {\n            const result = render(\n                wrapDNDIntl(\n                    <ReduxProvider store={store}>\n                        <UserPermissionsRow\n                            user={me}\n                            isMe={true}\n                            member={state.boards.myBoardMemberships[board.id] as BoardMember}\n                            teammateNameDisplay={'test'}\n                            onDeleteBoardMember={() => {}}\n                            onUpdateBoardMember={() => {}}\n                        />\n                    </ReduxProvider>),\n                {wrapper: MemoryRouter},\n            )\n            container = result.container\n        })\n\n        const buttonElement = container?.querySelector('.user-item__button')\n        expect(buttonElement).toBeDefined()\n        userEvent.click(buttonElement!)\n\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot-admin', async () => {\n        let container: Element | undefined\n        const store = mockStateStore([thunk], state)\n\n        const newMe = Object.assign({}, me)\n        newMe.permissions = ['manage_system']\n        await act(async () => {\n            const result = render(\n                wrapDNDIntl(\n                    <ReduxProvider store={store}>\n                        <UserPermissionsRow\n                            user={newMe}\n                            isMe={true}\n                            member={state.boards.myBoardMemberships[board.id] as BoardMember}\n                            teammateNameDisplay={'test'}\n                            onDeleteBoardMember={() => {}}\n                            onUpdateBoardMember={() => {}}\n                        />\n                    </ReduxProvider>),\n                {wrapper: MemoryRouter},\n            )\n            container = result.container\n        })\n\n        const buttonElement = container?.querySelector('.user-item__button')\n        expect(buttonElement).toBeDefined()\n        userEvent.click(buttonElement!)\n\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot in plugin mode', async () => {\n        let container: Element | undefined\n        const store = mockStateStore([thunk], state)\n        await act(async () => {\n            const result = render(\n                wrapDNDIntl(\n                    <ReduxProvider store={store}>\n                        <UserPermissionsRow\n                            user={me}\n                            isMe={true}\n                            member={state.boards.myBoardMemberships[board.id] as BoardMember}\n                            teammateNameDisplay={'test'}\n                            onDeleteBoardMember={() => {}}\n                            onUpdateBoardMember={() => {}}\n                        />\n                    </ReduxProvider>),\n                {wrapper: MemoryRouter},\n            )\n            container = result.container\n        })\n\n        const buttonElement = container?.querySelector('.user-item__button')\n        expect(buttonElement).toBeDefined()\n        userEvent.click(buttonElement!)\n\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot in template', async () => {\n        let container: Element | undefined\n        const testState = {\n            ...state,\n            boards: {\n                ...state.boards,\n                boards: {},\n                templates: {\n                    [board.id]: {...board, isTemplate: true},\n                },\n            },\n        }\n        const store = mockStateStore([thunk], testState)\n        await act(async () => {\n            const result = render(\n                wrapDNDIntl(\n                    <ReduxProvider store={store}>\n                        <UserPermissionsRow\n                            user={me}\n                            isMe={true}\n                            member={state.boards.myBoardMemberships[board.id] as BoardMember}\n                            teammateNameDisplay={'test'}\n                            onDeleteBoardMember={() => {}}\n                            onUpdateBoardMember={() => {}}\n                        />\n                    </ReduxProvider>),\n                {wrapper: MemoryRouter},\n            )\n            container = result.container\n        })\n\n        const buttonElement = container?.querySelector('.user-item__button')\n        expect(buttonElement).toBeDefined()\n        userEvent.click(buttonElement!)\n\n        expect(container).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/shareBoard/userPermissionsRow.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React, {useRef} from 'react'\nimport {useIntl} from 'react-intl'\n\nimport MenuWrapper from '../../widgets/menuWrapper'\nimport Menu from '../../widgets/menu'\n\nimport CheckIcon from '../../widgets/icons/check'\nimport CompassIcon from '../../widgets/icons/compassIcon'\n\nimport {BoardMember, MemberRole} from '../../blocks/board'\nimport {IUser} from '../../user'\nimport {Utils} from '../../utils'\nimport {Permission} from '../../constants'\nimport GuestBadge from '../../widgets/guestBadge'\nimport AdminBadge from '../../widgets/adminBadge/adminBadge'\nimport {useAppSelector} from '../../store/hooks'\nimport {getCurrentBoard} from '../../store/boards'\n\nimport BoardPermissionGate from '../permissions/boardPermissionGate'\n\ntype Props = {\n    user: IUser\n    member: BoardMember\n    isMe: boolean\n    teammateNameDisplay: string\n    onDeleteBoardMember: (member: BoardMember) => void\n    onUpdateBoardMember: (member: BoardMember, permission: string) => void\n}\n\nconst UserPermissionsRow = (props: Props): JSX.Element => {\n    const intl = useIntl()\n    const board = useAppSelector(getCurrentBoard)\n    const {user, member, isMe, teammateNameDisplay} = props\n    let currentRole = MemberRole.Viewer\n    let displayRole = intl.formatMessage({id: 'BoardMember.schemeViewer', defaultMessage: 'Viewer'})\n    if (member.schemeAdmin) {\n        currentRole = MemberRole.Admin\n        displayRole = intl.formatMessage({id: 'BoardMember.schemeAdmin', defaultMessage: 'Admin'})\n    } else if (member.schemeEditor || member.minimumRole === MemberRole.Editor) {\n        currentRole = MemberRole.Editor\n        displayRole = intl.formatMessage({id: 'BoardMember.schemeEditor', defaultMessage: 'Editor'})\n    } else if (member.schemeCommenter || member.minimumRole === MemberRole.Commenter) {\n        currentRole = MemberRole.Commenter\n        displayRole = intl.formatMessage({id: 'BoardMember.schemeCommenter', defaultMessage: 'Commenter'})\n    }\n\n    const menuWrapperRef = useRef<HTMLDivElement>(null)\n\n    return (\n        <div\n            className='user-item'\n            ref={menuWrapperRef}\n        >\n            <div className='user-item__content'>\n                <div className='ml-3'>\n                    <strong>{Utils.getUserDisplayName(user, teammateNameDisplay)}</strong>\n                    <strong className='ml-2 text-light'>{`@${user.username}`}</strong>\n                    {isMe && <strong className='ml-2 text-light'>{intl.formatMessage({id: 'ShareBoard.userPermissionsYouText', defaultMessage: '(You)'})}</strong>}\n                    <GuestBadge show={user.is_guest}/>\n                    <AdminBadge permissions={user.permissions}/>\n                </div>\n            </div>\n            <div>\n                <BoardPermissionGate permissions={[Permission.ManageBoardRoles]}>\n                    <MenuWrapper>\n                        <button className='user-item__button'>\n                            {displayRole}\n                            <CompassIcon\n                                icon='chevron-down'\n                                className='CompassIcon'\n                            />\n                        </button>\n                        <Menu\n                            position='left'\n                            parentRef={menuWrapperRef}\n                        >\n                            {(board.minimumRole === MemberRole.Viewer || board.minimumRole === MemberRole.None) &&\n                                <Menu.Text\n                                    id={MemberRole.Viewer}\n                                    check={true}\n                                    icon={currentRole === MemberRole.Viewer ? <CheckIcon/> : <div className='empty-icon'/>}\n                                    name={intl.formatMessage({id: 'BoardMember.schemeViewer', defaultMessage: 'Viewer'})}\n                                    onClick={() => props.onUpdateBoardMember(member, MemberRole.Viewer)}\n                                />}\n                            {!board.isTemplate && (board.minimumRole === MemberRole.None || board.minimumRole === MemberRole.Commenter || board.minimumRole === MemberRole.Viewer) &&\n                                <Menu.Text\n                                    id={MemberRole.Commenter}\n                                    check={true}\n                                    icon={currentRole === MemberRole.Commenter ? <CheckIcon/> : <div className='empty-icon'/>}\n                                    name={intl.formatMessage({id: 'BoardMember.schemeCommenter', defaultMessage: 'Commenter'})}\n                                    onClick={() => props.onUpdateBoardMember(member, MemberRole.Commenter)}\n                                />}\n                            <Menu.Text\n                                id={MemberRole.Editor}\n                                check={true}\n                                icon={currentRole === MemberRole.Editor ? <CheckIcon/> : <div className='empty-icon'/>}\n                                name={intl.formatMessage({id: 'BoardMember.schemeEditor', defaultMessage: 'Editor'})}\n                                onClick={() => props.onUpdateBoardMember(member, MemberRole.Editor)}\n                            />\n                            {user.is_guest !== true &&\n                                <Menu.Text\n                                    id={MemberRole.Admin}\n                                    check={true}\n                                    icon={currentRole === MemberRole.Admin ? <CheckIcon/> : <div className='empty-icon'/>}\n                                    name={intl.formatMessage({id: 'BoardMember.schemeAdmin', defaultMessage: 'Admin'})}\n                                    onClick={() => props.onUpdateBoardMember(member, MemberRole.Admin)}\n                                />}\n                            <Menu.Separator/>\n                            <Menu.Text\n                                id='Remove'\n                                name={intl.formatMessage({id: 'ShareBoard.userPermissionsRemoveMemberText', defaultMessage: 'Remove member'})}\n                                onClick={() => props.onDeleteBoardMember(member)}\n                            />\n                        </Menu>\n                    </MenuWrapper>\n                </BoardPermissionGate>\n                <BoardPermissionGate\n                    permissions={[Permission.ManageBoardRoles]}\n                    invert={true}\n                >\n                    {displayRole}\n                </BoardPermissionGate>\n            </div>\n        </div>\n    )\n}\n\nexport default UserPermissionsRow\n"
  },
  {
    "path": "webapp/src/components/sidebar/__snapshots__/deleteBoardDialog.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/sidebar/DeleteBoardDialog Cancel should not submit 1`] = `\n<div\n  id=\"focalboard-root-portal\"\n>\n  exists\n</div>\n`;\n\nexports[`components/sidebar/DeleteBoardDialog Delete should submit 1`] = `\n<div\n  id=\"focalboard-root-portal\"\n>\n  deleted\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/sidebar/__snapshots__/registrationLink.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/sidebar/RegistrationLink renders with signupToken in URL query param 1`] = `\n<div>\n  <div\n    class=\"Modal bottom-right\"\n  >\n    <div\n      class=\"toolbar hideOnWidescreen\"\n    >\n      <button\n        aria-label=\"Close\"\n        class=\"IconButton\"\n        title=\"Close\"\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-close CloseIcon\"\n        />\n      </button>\n    </div>\n    <div\n      class=\"RegistrationLink\"\n    >\n      <div\n        class=\"row\"\n      >\n        Share this link for others to create accounts:\n      </div>\n      <div\n        class=\"row\"\n      >\n        <a\n          class=\"shareUrl\"\n          href=\"http://localhost/register?t=abc123\"\n          rel=\"noreferrer\"\n          target=\"_blank\"\n        >\n          http://localhost/register?t=abc123\n        </a>\n        <button\n          class=\"Button filled size--small\"\n          type=\"button\"\n        >\n          <span>\n            Copy link\n          </span>\n        </button>\n      </div>\n      <div\n        class=\"row\"\n      >\n        <button\n          class=\"Button emphasis--secondary size--small\"\n          type=\"button\"\n        >\n          <span>\n            Regenerate token\n          </span>\n        </button>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/sidebar/__snapshots__/sidebar.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/sidebarSidebar dont show hidden boards 1`] = `\n<div>\n  <div\n    class=\"Sidebar octo-sidebar\"\n  >\n    <div\n      class=\"octo-sidebar-header\"\n    >\n      <div\n        class=\"heading\"\n      >\n        <div\n          class=\"SidebarUserMenu\"\n        >\n          <div\n            class=\"ModalWrapper\"\n          >\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <div\n                class=\"logo\"\n              >\n                <div\n                  class=\"logo-title\"\n                >\n                  <svg\n                    class=\"FocalboardLogoIcon Icon\"\n                    version=\"1.1\"\n                    viewBox=\"0 0 52.589677 64\"\n                    x=\"0px\"\n                    y=\"0px\"\n                  >\n                    <path\n                      d=\"m 33.071077,12.069805 c -12.663,-3.4670001 -27.0530002,3.289 -31.6760002,16.943 -4.655,13.75 2.719,28.67 16.4690002,33.325 13.75,4.655 28.67,-2.719 33.326,-16.469 3.804,-11.235 -0.462,-22.701 -8.976,-29.249 l -0.46,4.871 h -0.001 c 4.631,4.896 6.709,11.941 4.325,18.985 -3.362,9.931 -14.447,15.151 -24.76,11.66 -10.313,-3.49 -15.9480002,-14.37 -12.5870002,-24.301 2.9750002,-8.788 11.9980002,-13.715 20.7430002,-12.625 v -10e-4 z m -6.175,16.488 c 3.456,-0.665 6.986,2.754 5.762,6.37 -0.854,2.522 -3.67,3.85 -6.291,2.962 -2.62,-0.887 -4.052,-3.651 -3.197,-6.174 0.573,-1.697 2.034,-2.852 3.726,-3.158 z m -1.285,-4.944 c -1.786,0.323 -3.45,1.104 -4.812,2.258 -1.299,1.101 -2.319,2.545 -2.898,4.258 -0.879,2.597 -0.579,5.323 0.617,7.632 1.206,2.329 3.325,4.234 6.07,5.164 2.744,0.929 5.584,0.701 7.959,-0.417 2.352,-1.107 4.246,-3.091 5.125,-5.688 0.555,-1.639 0.633,-3.254 0.344,-4.761 -0.21,-1.093 -0.615,-2.134 -1.174,-3.091 l 1.019,-5.107 c 0.189,0.187 0.374,0.378 0.552,0.574 1.75,1.919 3.008,4.283 3.508,6.877 0.415,2.154 0.304,4.457 -0.484,6.784 -1.239,3.661 -3.898,6.453 -7.193,8.005 -3.273,1.541 -7.175,1.858 -10.93,0.588 -3.754,-1.271 -6.661,-3.895 -8.326,-7.108 -1.674,-3.233 -2.09,-7.065 -0.851,-10.728 0.819,-2.419 2.26,-4.46 4.097,-6.016 1.88,-1.593 4.181,-2.673 6.656,-3.125 l -0.001,-0.004 c 1.759,-0.339 3.522,-0.313 5.213,0.016 l -3.583,3.761 c -0.294,0.028 -0.588,0.071 -0.883,0.127 h -0.025 z\"\n                    />\n                    <polygon\n                      points=\"26.057,32.594 37.495,11.658 36.79,8.44 41.066,0.207 43.683,4.611 48.803,4.434 44.185,12.48 40.902,13.697 29.542,34.491 \"\n                      transform=\"translate(7.6780426e-5,-0.21919512)\"\n                    />\n                  </svg>\n                  <span>\n                    Focalboard\n                  </span>\n                  <div\n                    class=\"versionFrame\"\n                  >\n                    <div\n                      class=\"version\"\n                      title=\"v8.0.0\"\n                    >\n                      v8.0.0\n                    </div>\n                  </div>\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n      <div\n        class=\"octo-spacer\"\n      />\n      <div\n        class=\"sidebarSwitcher\"\n      >\n        <button\n          class=\"IconButton\"\n          type=\"button\"\n        >\n          <svg\n            class=\"HideSidebarIcon Icon\"\n            viewBox=\"0 0 100 100\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n          >\n            <polyline\n              points=\"80,20 50,50 80,80\"\n            />\n            <polyline\n              points=\"50,20 20,50, 50,80\"\n            />\n          </svg>\n        </button>\n      </div>\n    </div>\n    <div\n      class=\"WorkspaceTitle\"\n    />\n    <div\n      class=\"BoardsSwitcherWrapper\"\n    >\n      <div\n        class=\"BoardsSwitcher\"\n      >\n        <i\n          class=\"CompassIcon icon-magnify MagnifyIcon\"\n        />\n        <div>\n          <span>\n            Find Boards\n          </span>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"octo-sidebar-list\"\n      data-rbd-droppable-context-id=\"2\"\n      data-rbd-droppable-id=\"lhs-categories\"\n    >\n      <div\n        data-rbd-draggable-context-id=\"2\"\n        data-rbd-draggable-id=\"category1\"\n      >\n        <div\n          class=\"SidebarCategory\"\n        >\n          <div\n            class=\"categoryBoardsDroppableArea\"\n            data-rbd-droppable-context-id=\"2\"\n            data-rbd-droppable-id=\"category1\"\n          >\n            <div\n              class=\"octo-sidebar-item category expanded \"\n            >\n              <div\n                aria-describedby=\"rbd-hidden-text-2-hidden-text-10\"\n                class=\"octo-sidebar-title category-title\"\n                data-rbd-drag-handle-context-id=\"2\"\n                data-rbd-drag-handle-draggable-id=\"category1\"\n                draggable=\"false\"\n                role=\"button\"\n                tabindex=\"0\"\n                title=\"Category 1\"\n              >\n                <i\n                  class=\"CompassIcon icon-chevron-down ChevronDownIcon\"\n                />\n                Category 1\n                <div\n                  class=\"sidebarCategoriesTour\"\n                />\n              </div>\n              <div\n                class=\"\"\n              >\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper\"\n                  role=\"button\"\n                >\n                  <button\n                    class=\"IconButton\"\n                    type=\"button\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                    />\n                  </button>\n                </div>\n              </div>\n            </div>\n            <div>\n              <div\n                class=\"octo-sidebar-item subitem no-views\"\n              >\n                No boards inside\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"octo-spacer\"\n    />\n    <div\n      class=\"add-board\"\n    >\n      + Add board\n    </div>\n    <div\n      class=\"SidebarSettingsMenu\"\n    >\n      <div\n        aria-label=\"menuwrapper\"\n        class=\"MenuWrapper\"\n        role=\"button\"\n      >\n        <div\n          class=\"menu-entry\"\n        >\n          Settings\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/sidebarSidebar should assign default category if current board doesnt have a category 1`] = `\n<div>\n  <div\n    class=\"Sidebar octo-sidebar\"\n  >\n    <div\n      class=\"octo-sidebar-header\"\n    >\n      <div\n        class=\"heading\"\n      >\n        <div\n          class=\"SidebarUserMenu\"\n        >\n          <div\n            class=\"ModalWrapper\"\n          >\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <div\n                class=\"logo\"\n              >\n                <div\n                  class=\"logo-title\"\n                >\n                  <svg\n                    class=\"FocalboardLogoIcon Icon\"\n                    version=\"1.1\"\n                    viewBox=\"0 0 52.589677 64\"\n                    x=\"0px\"\n                    y=\"0px\"\n                  >\n                    <path\n                      d=\"m 33.071077,12.069805 c -12.663,-3.4670001 -27.0530002,3.289 -31.6760002,16.943 -4.655,13.75 2.719,28.67 16.4690002,33.325 13.75,4.655 28.67,-2.719 33.326,-16.469 3.804,-11.235 -0.462,-22.701 -8.976,-29.249 l -0.46,4.871 h -0.001 c 4.631,4.896 6.709,11.941 4.325,18.985 -3.362,9.931 -14.447,15.151 -24.76,11.66 -10.313,-3.49 -15.9480002,-14.37 -12.5870002,-24.301 2.9750002,-8.788 11.9980002,-13.715 20.7430002,-12.625 v -10e-4 z m -6.175,16.488 c 3.456,-0.665 6.986,2.754 5.762,6.37 -0.854,2.522 -3.67,3.85 -6.291,2.962 -2.62,-0.887 -4.052,-3.651 -3.197,-6.174 0.573,-1.697 2.034,-2.852 3.726,-3.158 z m -1.285,-4.944 c -1.786,0.323 -3.45,1.104 -4.812,2.258 -1.299,1.101 -2.319,2.545 -2.898,4.258 -0.879,2.597 -0.579,5.323 0.617,7.632 1.206,2.329 3.325,4.234 6.07,5.164 2.744,0.929 5.584,0.701 7.959,-0.417 2.352,-1.107 4.246,-3.091 5.125,-5.688 0.555,-1.639 0.633,-3.254 0.344,-4.761 -0.21,-1.093 -0.615,-2.134 -1.174,-3.091 l 1.019,-5.107 c 0.189,0.187 0.374,0.378 0.552,0.574 1.75,1.919 3.008,4.283 3.508,6.877 0.415,2.154 0.304,4.457 -0.484,6.784 -1.239,3.661 -3.898,6.453 -7.193,8.005 -3.273,1.541 -7.175,1.858 -10.93,0.588 -3.754,-1.271 -6.661,-3.895 -8.326,-7.108 -1.674,-3.233 -2.09,-7.065 -0.851,-10.728 0.819,-2.419 2.26,-4.46 4.097,-6.016 1.88,-1.593 4.181,-2.673 6.656,-3.125 l -0.001,-0.004 c 1.759,-0.339 3.522,-0.313 5.213,0.016 l -3.583,3.761 c -0.294,0.028 -0.588,0.071 -0.883,0.127 h -0.025 z\"\n                    />\n                    <polygon\n                      points=\"26.057,32.594 37.495,11.658 36.79,8.44 41.066,0.207 43.683,4.611 48.803,4.434 44.185,12.48 40.902,13.697 29.542,34.491 \"\n                      transform=\"translate(7.6780426e-5,-0.21919512)\"\n                    />\n                  </svg>\n                  <span>\n                    Focalboard\n                  </span>\n                  <div\n                    class=\"versionFrame\"\n                  >\n                    <div\n                      class=\"version\"\n                      title=\"v8.0.0\"\n                    >\n                      v8.0.0\n                    </div>\n                  </div>\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n      <div\n        class=\"octo-spacer\"\n      />\n      <div\n        class=\"sidebarSwitcher\"\n      >\n        <button\n          class=\"IconButton\"\n          type=\"button\"\n        >\n          <svg\n            class=\"HideSidebarIcon Icon\"\n            viewBox=\"0 0 100 100\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n          >\n            <polyline\n              points=\"80,20 50,50 80,80\"\n            />\n            <polyline\n              points=\"50,20 20,50, 50,80\"\n            />\n          </svg>\n        </button>\n      </div>\n    </div>\n    <div\n      class=\"WorkspaceTitle\"\n    />\n    <div\n      class=\"BoardsSwitcherWrapper\"\n    >\n      <div\n        class=\"BoardsSwitcher\"\n      >\n        <i\n          class=\"CompassIcon icon-magnify MagnifyIcon\"\n        />\n        <div>\n          <span>\n            Find Boards\n          </span>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"octo-sidebar-list\"\n      data-rbd-droppable-context-id=\"4\"\n      data-rbd-droppable-id=\"lhs-categories\"\n    >\n      <div\n        data-rbd-draggable-context-id=\"4\"\n        data-rbd-draggable-id=\"category1\"\n      >\n        <div\n          class=\"SidebarCategory\"\n        >\n          <div\n            class=\"categoryBoardsDroppableArea\"\n            data-rbd-droppable-context-id=\"4\"\n            data-rbd-droppable-id=\"category1\"\n          >\n            <div\n              class=\"octo-sidebar-item category expanded \"\n            >\n              <div\n                aria-describedby=\"rbd-hidden-text-4-hidden-text-21\"\n                class=\"octo-sidebar-title category-title\"\n                data-rbd-drag-handle-context-id=\"4\"\n                data-rbd-drag-handle-draggable-id=\"category1\"\n                draggable=\"false\"\n                role=\"button\"\n                tabindex=\"0\"\n                title=\"Category 1\"\n              >\n                <i\n                  class=\"CompassIcon icon-chevron-down ChevronDownIcon\"\n                />\n                Category 1\n                <div\n                  class=\"sidebarCategoriesTour\"\n                />\n              </div>\n              <div\n                class=\"\"\n              >\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper\"\n                  role=\"button\"\n                >\n                  <button\n                    class=\"IconButton\"\n                    type=\"button\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                    />\n                  </button>\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n      <div\n        data-rbd-draggable-context-id=\"4\"\n        data-rbd-draggable-id=\"default_category\"\n      >\n        <div\n          class=\"SidebarCategory\"\n        >\n          <div\n            class=\"categoryBoardsDroppableArea\"\n            data-rbd-droppable-context-id=\"4\"\n            data-rbd-droppable-id=\"default_category\"\n          >\n            <div\n              class=\"octo-sidebar-item category expanded \"\n            >\n              <div\n                aria-describedby=\"rbd-hidden-text-4-hidden-text-21\"\n                class=\"octo-sidebar-title category-title\"\n                data-rbd-drag-handle-context-id=\"4\"\n                data-rbd-drag-handle-draggable-id=\"default_category\"\n                draggable=\"false\"\n                role=\"button\"\n                tabindex=\"0\"\n                title=\"Boards\"\n              >\n                <i\n                  class=\"CompassIcon icon-chevron-down ChevronDownIcon\"\n                />\n                Boards\n                <div\n                  class=\"sidebarCategoriesTour\"\n                />\n              </div>\n              <div\n                class=\"\"\n              >\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper\"\n                  role=\"button\"\n                >\n                  <button\n                    class=\"IconButton\"\n                    type=\"button\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                    />\n                  </button>\n                </div>\n              </div>\n            </div>\n            <div>\n              <div\n                class=\"octo-sidebar-item subitem no-views\"\n              >\n                No boards inside\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"octo-spacer\"\n    />\n    <div\n      class=\"add-board\"\n    >\n      + Add board\n    </div>\n    <div\n      class=\"SidebarSettingsMenu\"\n    >\n      <div\n        aria-label=\"menuwrapper\"\n        class=\"MenuWrapper\"\n        role=\"button\"\n      >\n        <div\n          class=\"menu-entry\"\n        >\n          Settings\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/sidebarSidebar shouldnt do any category assignment is board is in a category 1`] = `\n<div>\n  <div\n    class=\"Sidebar octo-sidebar\"\n  >\n    <div\n      class=\"octo-sidebar-header\"\n    >\n      <div\n        class=\"heading\"\n      >\n        <div\n          class=\"SidebarUserMenu\"\n        >\n          <div\n            class=\"ModalWrapper\"\n          >\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <div\n                class=\"logo\"\n              >\n                <div\n                  class=\"logo-title\"\n                >\n                  <svg\n                    class=\"FocalboardLogoIcon Icon\"\n                    version=\"1.1\"\n                    viewBox=\"0 0 52.589677 64\"\n                    x=\"0px\"\n                    y=\"0px\"\n                  >\n                    <path\n                      d=\"m 33.071077,12.069805 c -12.663,-3.4670001 -27.0530002,3.289 -31.6760002,16.943 -4.655,13.75 2.719,28.67 16.4690002,33.325 13.75,4.655 28.67,-2.719 33.326,-16.469 3.804,-11.235 -0.462,-22.701 -8.976,-29.249 l -0.46,4.871 h -0.001 c 4.631,4.896 6.709,11.941 4.325,18.985 -3.362,9.931 -14.447,15.151 -24.76,11.66 -10.313,-3.49 -15.9480002,-14.37 -12.5870002,-24.301 2.9750002,-8.788 11.9980002,-13.715 20.7430002,-12.625 v -10e-4 z m -6.175,16.488 c 3.456,-0.665 6.986,2.754 5.762,6.37 -0.854,2.522 -3.67,3.85 -6.291,2.962 -2.62,-0.887 -4.052,-3.651 -3.197,-6.174 0.573,-1.697 2.034,-2.852 3.726,-3.158 z m -1.285,-4.944 c -1.786,0.323 -3.45,1.104 -4.812,2.258 -1.299,1.101 -2.319,2.545 -2.898,4.258 -0.879,2.597 -0.579,5.323 0.617,7.632 1.206,2.329 3.325,4.234 6.07,5.164 2.744,0.929 5.584,0.701 7.959,-0.417 2.352,-1.107 4.246,-3.091 5.125,-5.688 0.555,-1.639 0.633,-3.254 0.344,-4.761 -0.21,-1.093 -0.615,-2.134 -1.174,-3.091 l 1.019,-5.107 c 0.189,0.187 0.374,0.378 0.552,0.574 1.75,1.919 3.008,4.283 3.508,6.877 0.415,2.154 0.304,4.457 -0.484,6.784 -1.239,3.661 -3.898,6.453 -7.193,8.005 -3.273,1.541 -7.175,1.858 -10.93,0.588 -3.754,-1.271 -6.661,-3.895 -8.326,-7.108 -1.674,-3.233 -2.09,-7.065 -0.851,-10.728 0.819,-2.419 2.26,-4.46 4.097,-6.016 1.88,-1.593 4.181,-2.673 6.656,-3.125 l -0.001,-0.004 c 1.759,-0.339 3.522,-0.313 5.213,0.016 l -3.583,3.761 c -0.294,0.028 -0.588,0.071 -0.883,0.127 h -0.025 z\"\n                    />\n                    <polygon\n                      points=\"26.057,32.594 37.495,11.658 36.79,8.44 41.066,0.207 43.683,4.611 48.803,4.434 44.185,12.48 40.902,13.697 29.542,34.491 \"\n                      transform=\"translate(7.6780426e-5,-0.21919512)\"\n                    />\n                  </svg>\n                  <span>\n                    Focalboard\n                  </span>\n                  <div\n                    class=\"versionFrame\"\n                  >\n                    <div\n                      class=\"version\"\n                      title=\"v8.0.0\"\n                    >\n                      v8.0.0\n                    </div>\n                  </div>\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n      <div\n        class=\"octo-spacer\"\n      />\n      <div\n        class=\"sidebarSwitcher\"\n      >\n        <button\n          class=\"IconButton\"\n          type=\"button\"\n        >\n          <svg\n            class=\"HideSidebarIcon Icon\"\n            viewBox=\"0 0 100 100\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n          >\n            <polyline\n              points=\"80,20 50,50 80,80\"\n            />\n            <polyline\n              points=\"50,20 20,50, 50,80\"\n            />\n          </svg>\n        </button>\n      </div>\n    </div>\n    <div\n      class=\"WorkspaceTitle\"\n    />\n    <div\n      class=\"BoardsSwitcherWrapper\"\n    >\n      <div\n        class=\"BoardsSwitcher\"\n      >\n        <i\n          class=\"CompassIcon icon-magnify MagnifyIcon\"\n        />\n        <div>\n          <span>\n            Find Boards\n          </span>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"octo-sidebar-list\"\n      data-rbd-droppable-context-id=\"5\"\n      data-rbd-droppable-id=\"lhs-categories\"\n    >\n      <div\n        data-rbd-draggable-context-id=\"5\"\n        data-rbd-draggable-id=\"category1\"\n      >\n        <div\n          class=\"SidebarCategory\"\n        >\n          <div\n            class=\"categoryBoardsDroppableArea\"\n            data-rbd-droppable-context-id=\"5\"\n            data-rbd-droppable-id=\"category1\"\n          >\n            <div\n              class=\"octo-sidebar-item category expanded \"\n            >\n              <div\n                aria-describedby=\"rbd-hidden-text-5-hidden-text-27\"\n                class=\"octo-sidebar-title category-title\"\n                data-rbd-drag-handle-context-id=\"5\"\n                data-rbd-drag-handle-draggable-id=\"category1\"\n                draggable=\"false\"\n                role=\"button\"\n                tabindex=\"0\"\n                title=\"Category 1\"\n              >\n                <i\n                  class=\"CompassIcon icon-chevron-down ChevronDownIcon\"\n                />\n                Category 1\n                <div\n                  class=\"sidebarCategoriesTour\"\n                />\n              </div>\n              <div\n                class=\"\"\n              >\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper\"\n                  role=\"button\"\n                >\n                  <button\n                    class=\"IconButton\"\n                    type=\"button\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                    />\n                  </button>\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n      <div\n        data-rbd-draggable-context-id=\"5\"\n        data-rbd-draggable-id=\"category2\"\n      >\n        <div\n          class=\"SidebarCategory\"\n        >\n          <div\n            class=\"categoryBoardsDroppableArea\"\n            data-rbd-droppable-context-id=\"5\"\n            data-rbd-droppable-id=\"category2\"\n          >\n            <div\n              class=\"octo-sidebar-item category expanded \"\n            >\n              <div\n                aria-describedby=\"rbd-hidden-text-5-hidden-text-27\"\n                class=\"octo-sidebar-title category-title\"\n                data-rbd-drag-handle-context-id=\"5\"\n                data-rbd-drag-handle-draggable-id=\"category2\"\n                draggable=\"false\"\n                role=\"button\"\n                tabindex=\"0\"\n                title=\"Category 2\"\n              >\n                <i\n                  class=\"CompassIcon icon-chevron-down ChevronDownIcon\"\n                />\n                Category 2\n                <div\n                  class=\"sidebarCategoriesTour\"\n                />\n              </div>\n              <div\n                class=\"\"\n              >\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper\"\n                  role=\"button\"\n                >\n                  <button\n                    class=\"IconButton\"\n                    type=\"button\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                    />\n                  </button>\n                </div>\n              </div>\n            </div>\n            <div\n              data-rbd-draggable-context-id=\"5\"\n              data-rbd-draggable-id=\"board2\"\n            >\n              <div\n                aria-describedby=\"rbd-hidden-text-5-hidden-text-27\"\n                class=\"SidebarBoardItem subitem \"\n                data-rbd-drag-handle-context-id=\"5\"\n                data-rbd-drag-handle-draggable-id=\"board2\"\n                draggable=\"false\"\n                role=\"button\"\n                tabindex=\"0\"\n              >\n                <div\n                  class=\"octo-sidebar-icon\"\n                >\n                  i\n                </div>\n                <div\n                  class=\"octo-sidebar-title\"\n                  title=\"board title\"\n                >\n                  board title\n                </div>\n                <div>\n                  <div\n                    aria-label=\"menuwrapper\"\n                    class=\"MenuWrapper x\"\n                    role=\"button\"\n                  >\n                    <button\n                      class=\"IconButton\"\n                      type=\"button\"\n                    >\n                      <i\n                        class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                      />\n                    </button>\n                  </div>\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n      <div\n        data-rbd-draggable-context-id=\"5\"\n        data-rbd-draggable-id=\"default_category\"\n      >\n        <div\n          class=\"SidebarCategory\"\n        >\n          <div\n            class=\"categoryBoardsDroppableArea\"\n            data-rbd-droppable-context-id=\"5\"\n            data-rbd-droppable-id=\"default_category\"\n          >\n            <div\n              class=\"octo-sidebar-item category expanded \"\n            >\n              <div\n                aria-describedby=\"rbd-hidden-text-5-hidden-text-27\"\n                class=\"octo-sidebar-title category-title\"\n                data-rbd-drag-handle-context-id=\"5\"\n                data-rbd-drag-handle-draggable-id=\"default_category\"\n                draggable=\"false\"\n                role=\"button\"\n                tabindex=\"0\"\n                title=\"Boards\"\n              >\n                <i\n                  class=\"CompassIcon icon-chevron-down ChevronDownIcon\"\n                />\n                Boards\n                <div\n                  class=\"sidebarCategoriesTour\"\n                />\n              </div>\n              <div\n                class=\"\"\n              >\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper\"\n                  role=\"button\"\n                >\n                  <button\n                    class=\"IconButton\"\n                    type=\"button\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                    />\n                  </button>\n                </div>\n              </div>\n            </div>\n            <div>\n              <div\n                class=\"octo-sidebar-item subitem no-views\"\n              >\n                No boards inside\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"octo-spacer\"\n    />\n    <div\n      class=\"add-board\"\n    >\n      + Add board\n    </div>\n    <div\n      class=\"SidebarSettingsMenu\"\n    >\n      <div\n        aria-label=\"menuwrapper\"\n        class=\"MenuWrapper\"\n        role=\"button\"\n      >\n        <div\n          class=\"menu-entry\"\n        >\n          Settings\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/sidebarSidebar sidebar expect hidden 1`] = `\n<div>\n  <div\n    class=\"Sidebar octo-sidebar hidden\"\n  >\n    <div\n      class=\"octo-sidebar-header show-button\"\n    >\n      <div\n        class=\"hamburger-icon\"\n      >\n        <button\n          class=\"IconButton\"\n          type=\"button\"\n        >\n          <svg\n            class=\"HamburgerIcon Icon\"\n            viewBox=\"0 0 100 100\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n          >\n            <polyline\n              points=\"20,25 80,25\"\n            />\n            <polyline\n              points=\"20,50 80,50\"\n            />\n            <polyline\n              points=\"20,75 80,75\"\n            />\n          </svg>\n        </button>\n      </div>\n      <div\n        class=\"show-icon\"\n      >\n        <button\n          class=\"IconButton\"\n          type=\"button\"\n        >\n          <svg\n            class=\"ShowSidebarIcon Icon\"\n            viewBox=\"0 0 100 100\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n          >\n            <polyline\n              points=\"20,20 50,50 20,80\"\n            />\n            <polyline\n              points=\"50,20 80,50, 50,80\"\n            />\n          </svg>\n        </button>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/sidebarSidebar sidebar hidden 1`] = `\n<div>\n  <div\n    class=\"Sidebar octo-sidebar\"\n  >\n    <div\n      class=\"octo-sidebar-header\"\n    >\n      <div\n        class=\"heading\"\n      >\n        <div\n          class=\"SidebarUserMenu\"\n        >\n          <div\n            class=\"ModalWrapper\"\n          >\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <div\n                class=\"logo\"\n              >\n                <div\n                  class=\"logo-title\"\n                >\n                  <svg\n                    class=\"FocalboardLogoIcon Icon\"\n                    version=\"1.1\"\n                    viewBox=\"0 0 52.589677 64\"\n                    x=\"0px\"\n                    y=\"0px\"\n                  >\n                    <path\n                      d=\"m 33.071077,12.069805 c -12.663,-3.4670001 -27.0530002,3.289 -31.6760002,16.943 -4.655,13.75 2.719,28.67 16.4690002,33.325 13.75,4.655 28.67,-2.719 33.326,-16.469 3.804,-11.235 -0.462,-22.701 -8.976,-29.249 l -0.46,4.871 h -0.001 c 4.631,4.896 6.709,11.941 4.325,18.985 -3.362,9.931 -14.447,15.151 -24.76,11.66 -10.313,-3.49 -15.9480002,-14.37 -12.5870002,-24.301 2.9750002,-8.788 11.9980002,-13.715 20.7430002,-12.625 v -10e-4 z m -6.175,16.488 c 3.456,-0.665 6.986,2.754 5.762,6.37 -0.854,2.522 -3.67,3.85 -6.291,2.962 -2.62,-0.887 -4.052,-3.651 -3.197,-6.174 0.573,-1.697 2.034,-2.852 3.726,-3.158 z m -1.285,-4.944 c -1.786,0.323 -3.45,1.104 -4.812,2.258 -1.299,1.101 -2.319,2.545 -2.898,4.258 -0.879,2.597 -0.579,5.323 0.617,7.632 1.206,2.329 3.325,4.234 6.07,5.164 2.744,0.929 5.584,0.701 7.959,-0.417 2.352,-1.107 4.246,-3.091 5.125,-5.688 0.555,-1.639 0.633,-3.254 0.344,-4.761 -0.21,-1.093 -0.615,-2.134 -1.174,-3.091 l 1.019,-5.107 c 0.189,0.187 0.374,0.378 0.552,0.574 1.75,1.919 3.008,4.283 3.508,6.877 0.415,2.154 0.304,4.457 -0.484,6.784 -1.239,3.661 -3.898,6.453 -7.193,8.005 -3.273,1.541 -7.175,1.858 -10.93,0.588 -3.754,-1.271 -6.661,-3.895 -8.326,-7.108 -1.674,-3.233 -2.09,-7.065 -0.851,-10.728 0.819,-2.419 2.26,-4.46 4.097,-6.016 1.88,-1.593 4.181,-2.673 6.656,-3.125 l -0.001,-0.004 c 1.759,-0.339 3.522,-0.313 5.213,0.016 l -3.583,3.761 c -0.294,0.028 -0.588,0.071 -0.883,0.127 h -0.025 z\"\n                    />\n                    <polygon\n                      points=\"26.057,32.594 37.495,11.658 36.79,8.44 41.066,0.207 43.683,4.611 48.803,4.434 44.185,12.48 40.902,13.697 29.542,34.491 \"\n                      transform=\"translate(7.6780426e-5,-0.21919512)\"\n                    />\n                  </svg>\n                  <span>\n                    Focalboard\n                  </span>\n                  <div\n                    class=\"versionFrame\"\n                  >\n                    <div\n                      class=\"version\"\n                      title=\"v8.0.0\"\n                    >\n                      v8.0.0\n                    </div>\n                  </div>\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n      <div\n        class=\"octo-spacer\"\n      />\n      <div\n        class=\"sidebarSwitcher\"\n      >\n        <button\n          class=\"IconButton\"\n          type=\"button\"\n        >\n          <svg\n            class=\"HideSidebarIcon Icon\"\n            viewBox=\"0 0 100 100\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n          >\n            <polyline\n              points=\"80,20 50,50 80,80\"\n            />\n            <polyline\n              points=\"50,20 20,50, 50,80\"\n            />\n          </svg>\n        </button>\n      </div>\n    </div>\n    <div\n      class=\"WorkspaceTitle\"\n    />\n    <div\n      class=\"BoardsSwitcherWrapper\"\n    >\n      <div\n        class=\"BoardsSwitcher\"\n      >\n        <i\n          class=\"CompassIcon icon-magnify MagnifyIcon\"\n        />\n        <div>\n          <span>\n            Find Boards\n          </span>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"octo-sidebar-list\"\n      data-rbd-droppable-context-id=\"0\"\n      data-rbd-droppable-id=\"lhs-categories\"\n    >\n      <div\n        data-rbd-draggable-context-id=\"0\"\n        data-rbd-draggable-id=\"category1\"\n      >\n        <div\n          class=\"SidebarCategory\"\n        >\n          <div\n            class=\"categoryBoardsDroppableArea\"\n            data-rbd-droppable-context-id=\"0\"\n            data-rbd-droppable-id=\"category1\"\n          >\n            <div\n              class=\"octo-sidebar-item category expanded \"\n            >\n              <div\n                aria-describedby=\"rbd-hidden-text-0-hidden-text-0\"\n                class=\"octo-sidebar-title category-title\"\n                data-rbd-drag-handle-context-id=\"0\"\n                data-rbd-drag-handle-draggable-id=\"category1\"\n                draggable=\"false\"\n                role=\"button\"\n                tabindex=\"0\"\n                title=\"Category 1\"\n              >\n                <i\n                  class=\"CompassIcon icon-chevron-down ChevronDownIcon\"\n                />\n                Category 1\n                <div\n                  class=\"sidebarCategoriesTour\"\n                />\n              </div>\n              <div\n                class=\"\"\n              >\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper\"\n                  role=\"button\"\n                >\n                  <button\n                    class=\"IconButton\"\n                    type=\"button\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                    />\n                  </button>\n                </div>\n              </div>\n            </div>\n            <div\n              data-rbd-draggable-context-id=\"0\"\n              data-rbd-draggable-id=\"board1\"\n            >\n              <div\n                aria-describedby=\"rbd-hidden-text-0-hidden-text-0\"\n                class=\"SidebarBoardItem subitem \"\n                data-rbd-drag-handle-context-id=\"0\"\n                data-rbd-drag-handle-draggable-id=\"board1\"\n                draggable=\"false\"\n                role=\"button\"\n                tabindex=\"0\"\n              >\n                <div\n                  class=\"octo-sidebar-icon\"\n                >\n                  i\n                </div>\n                <div\n                  class=\"octo-sidebar-title\"\n                  title=\"board title\"\n                >\n                  board title\n                </div>\n                <div>\n                  <div\n                    aria-label=\"menuwrapper\"\n                    class=\"MenuWrapper x\"\n                    role=\"button\"\n                  >\n                    <button\n                      class=\"IconButton\"\n                      type=\"button\"\n                    >\n                      <i\n                        class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                      />\n                    </button>\n                  </div>\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"octo-spacer\"\n    />\n    <div\n      class=\"add-board\"\n    >\n      + Add board\n    </div>\n    <div\n      class=\"SidebarSettingsMenu\"\n    >\n      <div\n        aria-label=\"menuwrapper\"\n        class=\"MenuWrapper\"\n        role=\"button\"\n      >\n        <div\n          class=\"menu-entry\"\n        >\n          Settings\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/sidebarSidebar sidebar hidden 2`] = `\n<div>\n  <div\n    class=\"Sidebar octo-sidebar hidden\"\n  >\n    <div\n      class=\"octo-sidebar-header show-button\"\n    >\n      <div\n        class=\"hamburger-icon\"\n      >\n        <button\n          class=\"IconButton\"\n          type=\"button\"\n        >\n          <svg\n            class=\"HamburgerIcon Icon\"\n            viewBox=\"0 0 100 100\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n          >\n            <polyline\n              points=\"20,25 80,25\"\n            />\n            <polyline\n              points=\"20,50 80,50\"\n            />\n            <polyline\n              points=\"20,75 80,75\"\n            />\n          </svg>\n        </button>\n      </div>\n      <div\n        class=\"show-icon\"\n      >\n        <button\n          class=\"IconButton\"\n          type=\"button\"\n        >\n          <svg\n            class=\"ShowSidebarIcon Icon\"\n            viewBox=\"0 0 100 100\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n          >\n            <polyline\n              points=\"20,20 50,50 20,80\"\n            />\n            <polyline\n              points=\"50,20 80,50, 50,80\"\n            />\n          </svg>\n        </button>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/sidebarSidebar some categories hidden 1`] = `\n<div>\n  <div\n    class=\"Sidebar octo-sidebar\"\n  >\n    <div\n      class=\"octo-sidebar-header\"\n    >\n      <div\n        class=\"heading\"\n      >\n        <div\n          class=\"SidebarUserMenu\"\n        >\n          <div\n            class=\"ModalWrapper\"\n          >\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper\"\n              role=\"button\"\n            >\n              <div\n                class=\"logo\"\n              >\n                <div\n                  class=\"logo-title\"\n                >\n                  <svg\n                    class=\"FocalboardLogoIcon Icon\"\n                    version=\"1.1\"\n                    viewBox=\"0 0 52.589677 64\"\n                    x=\"0px\"\n                    y=\"0px\"\n                  >\n                    <path\n                      d=\"m 33.071077,12.069805 c -12.663,-3.4670001 -27.0530002,3.289 -31.6760002,16.943 -4.655,13.75 2.719,28.67 16.4690002,33.325 13.75,4.655 28.67,-2.719 33.326,-16.469 3.804,-11.235 -0.462,-22.701 -8.976,-29.249 l -0.46,4.871 h -0.001 c 4.631,4.896 6.709,11.941 4.325,18.985 -3.362,9.931 -14.447,15.151 -24.76,11.66 -10.313,-3.49 -15.9480002,-14.37 -12.5870002,-24.301 2.9750002,-8.788 11.9980002,-13.715 20.7430002,-12.625 v -10e-4 z m -6.175,16.488 c 3.456,-0.665 6.986,2.754 5.762,6.37 -0.854,2.522 -3.67,3.85 -6.291,2.962 -2.62,-0.887 -4.052,-3.651 -3.197,-6.174 0.573,-1.697 2.034,-2.852 3.726,-3.158 z m -1.285,-4.944 c -1.786,0.323 -3.45,1.104 -4.812,2.258 -1.299,1.101 -2.319,2.545 -2.898,4.258 -0.879,2.597 -0.579,5.323 0.617,7.632 1.206,2.329 3.325,4.234 6.07,5.164 2.744,0.929 5.584,0.701 7.959,-0.417 2.352,-1.107 4.246,-3.091 5.125,-5.688 0.555,-1.639 0.633,-3.254 0.344,-4.761 -0.21,-1.093 -0.615,-2.134 -1.174,-3.091 l 1.019,-5.107 c 0.189,0.187 0.374,0.378 0.552,0.574 1.75,1.919 3.008,4.283 3.508,6.877 0.415,2.154 0.304,4.457 -0.484,6.784 -1.239,3.661 -3.898,6.453 -7.193,8.005 -3.273,1.541 -7.175,1.858 -10.93,0.588 -3.754,-1.271 -6.661,-3.895 -8.326,-7.108 -1.674,-3.233 -2.09,-7.065 -0.851,-10.728 0.819,-2.419 2.26,-4.46 4.097,-6.016 1.88,-1.593 4.181,-2.673 6.656,-3.125 l -0.001,-0.004 c 1.759,-0.339 3.522,-0.313 5.213,0.016 l -3.583,3.761 c -0.294,0.028 -0.588,0.071 -0.883,0.127 h -0.025 z\"\n                    />\n                    <polygon\n                      points=\"26.057,32.594 37.495,11.658 36.79,8.44 41.066,0.207 43.683,4.611 48.803,4.434 44.185,12.48 40.902,13.697 29.542,34.491 \"\n                      transform=\"translate(7.6780426e-5,-0.21919512)\"\n                    />\n                  </svg>\n                  <span>\n                    Focalboard\n                  </span>\n                  <div\n                    class=\"versionFrame\"\n                  >\n                    <div\n                      class=\"version\"\n                      title=\"v8.0.0\"\n                    >\n                      v8.0.0\n                    </div>\n                  </div>\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n      <div\n        class=\"octo-spacer\"\n      />\n      <div\n        class=\"sidebarSwitcher\"\n      >\n        <button\n          class=\"IconButton\"\n          type=\"button\"\n        >\n          <svg\n            class=\"HideSidebarIcon Icon\"\n            viewBox=\"0 0 100 100\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n          >\n            <polyline\n              points=\"80,20 50,50 80,80\"\n            />\n            <polyline\n              points=\"50,20 20,50, 50,80\"\n            />\n          </svg>\n        </button>\n      </div>\n    </div>\n    <div\n      class=\"WorkspaceTitle\"\n    />\n    <div\n      class=\"BoardsSwitcherWrapper\"\n    >\n      <div\n        class=\"BoardsSwitcher\"\n      >\n        <i\n          class=\"CompassIcon icon-magnify MagnifyIcon\"\n        />\n        <div>\n          <span>\n            Find Boards\n          </span>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"octo-sidebar-list\"\n      data-rbd-droppable-context-id=\"3\"\n      data-rbd-droppable-id=\"lhs-categories\"\n    >\n      <div\n        data-rbd-draggable-context-id=\"3\"\n        data-rbd-draggable-id=\"category1\"\n      >\n        <div\n          class=\"SidebarCategory\"\n        >\n          <div\n            class=\"categoryBoardsDroppableArea\"\n            data-rbd-droppable-context-id=\"3\"\n            data-rbd-droppable-id=\"category1\"\n          >\n            <div\n              class=\"octo-sidebar-item category expanded \"\n            >\n              <div\n                aria-describedby=\"rbd-hidden-text-3-hidden-text-14\"\n                class=\"octo-sidebar-title category-title\"\n                data-rbd-drag-handle-context-id=\"3\"\n                data-rbd-drag-handle-draggable-id=\"category1\"\n                draggable=\"false\"\n                role=\"button\"\n                tabindex=\"0\"\n                title=\"Category 1\"\n              >\n                <i\n                  class=\"CompassIcon icon-chevron-down ChevronDownIcon\"\n                />\n                Category 1\n                <div\n                  class=\"sidebarCategoriesTour\"\n                />\n              </div>\n              <div\n                class=\"\"\n              >\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper\"\n                  role=\"button\"\n                >\n                  <button\n                    class=\"IconButton\"\n                    type=\"button\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                    />\n                  </button>\n                </div>\n              </div>\n            </div>\n            <div\n              data-rbd-draggable-context-id=\"3\"\n              data-rbd-draggable-id=\"board1\"\n            >\n              <div\n                aria-describedby=\"rbd-hidden-text-3-hidden-text-14\"\n                class=\"SidebarBoardItem subitem \"\n                data-rbd-drag-handle-context-id=\"3\"\n                data-rbd-drag-handle-draggable-id=\"board1\"\n                draggable=\"false\"\n                role=\"button\"\n                tabindex=\"0\"\n              >\n                <div\n                  class=\"octo-sidebar-icon\"\n                >\n                  i\n                </div>\n                <div\n                  class=\"octo-sidebar-title\"\n                  title=\"board title\"\n                >\n                  board title\n                </div>\n                <div>\n                  <div\n                    aria-label=\"menuwrapper\"\n                    class=\"MenuWrapper x\"\n                    role=\"button\"\n                  >\n                    <button\n                      class=\"IconButton\"\n                      type=\"button\"\n                    >\n                      <i\n                        class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                      />\n                    </button>\n                  </div>\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n      <div\n        data-rbd-draggable-context-id=\"3\"\n        data-rbd-draggable-id=\"categoryCollapsed\"\n      >\n        <div\n          class=\"SidebarCategory\"\n        >\n          <div\n            class=\"categoryBoardsDroppableArea\"\n            data-rbd-droppable-context-id=\"3\"\n            data-rbd-droppable-id=\"categoryCollapsed\"\n          >\n            <div\n              class=\"octo-sidebar-item category collapsed \"\n            >\n              <div\n                aria-describedby=\"rbd-hidden-text-3-hidden-text-14\"\n                class=\"octo-sidebar-title category-title\"\n                data-rbd-drag-handle-context-id=\"3\"\n                data-rbd-drag-handle-draggable-id=\"categoryCollapsed\"\n                draggable=\"false\"\n                role=\"button\"\n                tabindex=\"0\"\n                title=\"Category 2\"\n              >\n                <i\n                  class=\"CompassIcon icon-chevron-right ChevronRightIcon\"\n                />\n                Category 2\n                <div\n                  class=\"sidebarCategoriesTour\"\n                />\n              </div>\n              <div\n                class=\"\"\n              >\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper\"\n                  role=\"button\"\n                >\n                  <button\n                    class=\"IconButton\"\n                    type=\"button\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                    />\n                  </button>\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"octo-spacer\"\n    />\n    <div\n      class=\"add-board\"\n    >\n      + Add board\n    </div>\n    <div\n      class=\"SidebarSettingsMenu\"\n    >\n      <div\n        aria-label=\"menuwrapper\"\n        class=\"MenuWrapper\"\n        role=\"button\"\n      >\n        <div\n          class=\"menu-entry\"\n        >\n          Settings\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/sidebar/__snapshots__/sidebarBoardItem.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/sidebarBoardItem renders default icon if no custom icon set 1`] = `\n<div>\n  <div\n    data-rbd-droppable-context-id=\"1\"\n    data-rbd-droppable-id=\"droppable_id\"\n  >\n    <div\n      data-rbd-draggable-context-id=\"1\"\n      data-rbd-draggable-id=\"board_id_1\"\n    >\n      <div\n        aria-describedby=\"rbd-hidden-text-1-hidden-text-3\"\n        class=\"SidebarBoardItem subitem active\"\n        data-rbd-drag-handle-context-id=\"1\"\n        data-rbd-drag-handle-draggable-id=\"board_id_1\"\n        draggable=\"false\"\n        role=\"button\"\n        tabindex=\"0\"\n      >\n        <div\n          class=\"octo-sidebar-icon\"\n        >\n          <i\n            class=\"CompassIcon icon-product-boards\"\n          />\n        </div>\n        <div\n          class=\"octo-sidebar-title\"\n          title=\"board title\"\n        >\n          board title\n        </div>\n        <div>\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper x\"\n            role=\"button\"\n          >\n            <button\n              class=\"IconButton\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n              />\n            </button>\n          </div>\n        </div>\n      </div>\n      <div\n        class=\"SidebarBoardItem sidebar-view-item active\"\n      >\n        <svg\n          class=\"BoardIcon Icon\"\n          fill=\"currentColor\"\n          height=\"24\"\n          viewBox=\"0 0 24 24\"\n          width=\"24\"\n          xmlns=\"http://www.w3.org/2000/svg\"\n        >\n          <g\n            opacity=\"0.8\"\n          >\n            <path\n              clip-rule=\"evenodd\"\n              d=\"M4 4H20V20H4V4ZM2 4C2 2.89543 2.89543 2 4 2H20C21.1046 2 22 2.89543 22 4V20C22 21.1046 21.1046 22 20 22H4C2.89543 22 2 21.1046 2 20V4ZM8 6H6V12H8V6ZM11 6H13V16H11V6ZM18 6H16V9H18V6Z\"\n              fill=\"currentColor\"\n              fill-rule=\"evenodd\"\n            />\n          </g>\n        </svg>\n        <div\n          class=\"octo-sidebar-title\"\n          title=\"view title\"\n        >\n          view title\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/sidebarBoardItem sidebar board item 1`] = `\n<div>\n  <div\n    data-rbd-droppable-context-id=\"0\"\n    data-rbd-droppable-id=\"droppable_id\"\n  >\n    <div\n      data-rbd-draggable-context-id=\"0\"\n      data-rbd-draggable-id=\"board_id_1\"\n    >\n      <div\n        aria-describedby=\"rbd-hidden-text-0-hidden-text-0\"\n        class=\"SidebarBoardItem subitem active\"\n        data-rbd-drag-handle-context-id=\"0\"\n        data-rbd-drag-handle-draggable-id=\"board_id_1\"\n        draggable=\"false\"\n        role=\"button\"\n        tabindex=\"0\"\n      >\n        <div\n          class=\"octo-sidebar-icon\"\n        >\n          i\n        </div>\n        <div\n          class=\"octo-sidebar-title\"\n          title=\"board title\"\n        >\n          board title\n        </div>\n        <div>\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper override menuOpened menuOpen\"\n            role=\"button\"\n          >\n            <button\n              class=\"IconButton\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n              />\n            </button>\n            <div\n              class=\"Menu noselect auto  fixed\"\n              style=\"top: 40px;\"\n            >\n              <div\n                class=\"menu-contents\"\n              >\n                <div\n                  class=\"menu-options\"\n                >\n                  <div>\n                    <div\n                      class=\"MenuOption SubMenuOption menu-option boardMoveToCategorySubmenu\"\n                      id=\"moveBlock\"\n                    >\n                      <div\n                        class=\"menu-option__icon\"\n                      >\n                        <i\n                          class=\"CompassIcon icon-folder-plus-outline CreateNewFolderIcon\"\n                        />\n                      </div>\n                      <div\n                        class=\"menu-name\"\n                      >\n                        Move To...\n                      </div>\n                      <i\n                        class=\"CompassIcon icon-chevron-right\"\n                      />\n                    </div>\n                  </div>\n                  <div>\n                    <div\n                      aria-label=\"Duplicate board\"\n                      class=\"MenuOption TextOption menu-option\"\n                      role=\"button\"\n                    >\n                      <div\n                        class=\"d-flex\"\n                      >\n                        <div\n                          class=\"menu-option__icon\"\n                        >\n                          <i\n                            class=\"CompassIcon icon-content-copy content-copy\"\n                          />\n                        </div>\n                      </div>\n                      <div\n                        class=\"menu-option__content\"\n                      >\n                        <div\n                          class=\"menu-name\"\n                        >\n                          Duplicate board\n                        </div>\n                      </div>\n                      <div\n                        class=\"noicon\"\n                      />\n                    </div>\n                  </div>\n                  <div>\n                    <div\n                      aria-label=\"New template from board\"\n                      class=\"MenuOption TextOption menu-option\"\n                      role=\"button\"\n                    >\n                      <div\n                        class=\"d-flex\"\n                      >\n                        <div\n                          class=\"menu-option__icon\"\n                        >\n                          <i\n                            class=\"CompassIcon icon-plus AddIcon\"\n                          />\n                        </div>\n                      </div>\n                      <div\n                        class=\"menu-option__content\"\n                      >\n                        <div\n                          class=\"menu-name\"\n                        >\n                          New template from board\n                        </div>\n                      </div>\n                      <div\n                        class=\"noicon\"\n                      />\n                    </div>\n                  </div>\n                  <div>\n                    <div\n                      aria-label=\"Export board archive\"\n                      class=\"MenuOption TextOption menu-option\"\n                      role=\"button\"\n                    >\n                      <div\n                        class=\"d-flex\"\n                      >\n                        <div\n                          class=\"menu-option__icon\"\n                        >\n                          <i\n                            class=\"CompassIcon icon-export-variant\"\n                          />\n                        </div>\n                      </div>\n                      <div\n                        class=\"menu-option__content\"\n                      >\n                        <div\n                          class=\"menu-name\"\n                        >\n                          Export board archive\n                        </div>\n                      </div>\n                      <div\n                        class=\"noicon\"\n                      />\n                    </div>\n                  </div>\n                  <div>\n                    <div\n                      aria-label=\"Hide board\"\n                      class=\"MenuOption TextOption menu-option\"\n                      role=\"button\"\n                    >\n                      <div\n                        class=\"d-flex\"\n                      >\n                        <div\n                          class=\"menu-option__icon\"\n                        >\n                          <i\n                            class=\"CompassIcon icon-close CloseIcon\"\n                          />\n                        </div>\n                      </div>\n                      <div\n                        class=\"menu-option__content\"\n                      >\n                        <div\n                          class=\"menu-name\"\n                        >\n                          Hide board\n                        </div>\n                      </div>\n                      <div\n                        class=\"noicon\"\n                      />\n                    </div>\n                  </div>\n                  <div>\n                    <div\n                      aria-label=\"Delete board\"\n                      class=\"MenuOption TextOption menu-option text-danger\"\n                      role=\"button\"\n                    >\n                      <div\n                        class=\"d-flex\"\n                      >\n                        <div\n                          class=\"menu-option__icon\"\n                        >\n                          <i\n                            class=\"CompassIcon icon-trash-can-outline DeleteIcon trash-can-outline\"\n                          />\n                        </div>\n                      </div>\n                      <div\n                        class=\"menu-option__content\"\n                      >\n                        <div\n                          class=\"menu-name\"\n                        >\n                          Delete board\n                        </div>\n                      </div>\n                      <div\n                        class=\"noicon\"\n                      />\n                    </div>\n                  </div>\n                </div>\n                <div\n                  class=\"menu-spacer hideOnWidescreen\"\n                />\n                <div\n                  class=\"menu-options hideOnWidescreen\"\n                >\n                  <div\n                    aria-label=\"Cancel\"\n                    class=\"MenuOption TextOption menu-option menu-cancel\"\n                    role=\"button\"\n                  >\n                    <div\n                      class=\"d-flex\"\n                    >\n                      <div\n                        class=\"noicon\"\n                      />\n                    </div>\n                    <div\n                      class=\"menu-option__content\"\n                    >\n                      <div\n                        class=\"menu-name\"\n                      >\n                        Cancel\n                      </div>\n                    </div>\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n      <div\n        class=\"SidebarBoardItem sidebar-view-item active\"\n      >\n        <svg\n          class=\"BoardIcon Icon\"\n          fill=\"currentColor\"\n          height=\"24\"\n          viewBox=\"0 0 24 24\"\n          width=\"24\"\n          xmlns=\"http://www.w3.org/2000/svg\"\n        >\n          <g\n            opacity=\"0.8\"\n          >\n            <path\n              clip-rule=\"evenodd\"\n              d=\"M4 4H20V20H4V4ZM2 4C2 2.89543 2.89543 2 4 2H20C21.1046 2 22 2.89543 22 4V20C22 21.1046 21.1046 22 20 22H4C2.89543 22 2 21.1046 2 20V4ZM8 6H6V12H8V6ZM11 6H13V16H11V6ZM18 6H16V9H18V6Z\"\n              fill=\"currentColor\"\n              fill-rule=\"evenodd\"\n            />\n          </g>\n        </svg>\n        <div\n          class=\"octo-sidebar-title\"\n          title=\"view title\"\n        >\n          view title\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/sidebarBoardItem sidebar board item for guest 1`] = `\n<div>\n  <div\n    data-rbd-droppable-context-id=\"2\"\n    data-rbd-droppable-id=\"droppable_id\"\n  >\n    <div\n      data-rbd-draggable-context-id=\"2\"\n      data-rbd-draggable-id=\"board_id_1\"\n    >\n      <div\n        aria-describedby=\"rbd-hidden-text-2-hidden-text-6\"\n        class=\"SidebarBoardItem subitem active\"\n        data-rbd-drag-handle-context-id=\"2\"\n        data-rbd-drag-handle-draggable-id=\"board_id_1\"\n        draggable=\"false\"\n        role=\"button\"\n        tabindex=\"0\"\n      >\n        <div\n          class=\"octo-sidebar-icon\"\n        >\n          i\n        </div>\n        <div\n          class=\"octo-sidebar-title\"\n          title=\"board title\"\n        >\n          board title\n        </div>\n        <div>\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper override menuOpened menuOpen\"\n            role=\"button\"\n          >\n            <button\n              class=\"IconButton\"\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n              />\n            </button>\n            <div\n              class=\"Menu noselect auto  fixed\"\n              style=\"top: 40px;\"\n            >\n              <div\n                class=\"menu-contents\"\n              >\n                <div\n                  class=\"menu-options\"\n                >\n                  <div>\n                    <div\n                      class=\"MenuOption SubMenuOption menu-option boardMoveToCategorySubmenu\"\n                      id=\"moveBlock\"\n                    >\n                      <div\n                        class=\"menu-option__icon\"\n                      >\n                        <i\n                          class=\"CompassIcon icon-folder-plus-outline CreateNewFolderIcon\"\n                        />\n                      </div>\n                      <div\n                        class=\"menu-name\"\n                      >\n                        Move To...\n                      </div>\n                      <i\n                        class=\"CompassIcon icon-chevron-right\"\n                      />\n                    </div>\n                  </div>\n                  <div />\n                  <div />\n                  <div>\n                    <div\n                      aria-label=\"Export board archive\"\n                      class=\"MenuOption TextOption menu-option\"\n                      role=\"button\"\n                    >\n                      <div\n                        class=\"d-flex\"\n                      >\n                        <div\n                          class=\"menu-option__icon\"\n                        >\n                          <i\n                            class=\"CompassIcon icon-export-variant\"\n                          />\n                        </div>\n                      </div>\n                      <div\n                        class=\"menu-option__content\"\n                      >\n                        <div\n                          class=\"menu-name\"\n                        >\n                          Export board archive\n                        </div>\n                      </div>\n                      <div\n                        class=\"noicon\"\n                      />\n                    </div>\n                  </div>\n                  <div>\n                    <div\n                      aria-label=\"Hide board\"\n                      class=\"MenuOption TextOption menu-option\"\n                      role=\"button\"\n                    >\n                      <div\n                        class=\"d-flex\"\n                      >\n                        <div\n                          class=\"menu-option__icon\"\n                        >\n                          <i\n                            class=\"CompassIcon icon-close CloseIcon\"\n                          />\n                        </div>\n                      </div>\n                      <div\n                        class=\"menu-option__content\"\n                      >\n                        <div\n                          class=\"menu-name\"\n                        >\n                          Hide board\n                        </div>\n                      </div>\n                      <div\n                        class=\"noicon\"\n                      />\n                    </div>\n                  </div>\n                  <div>\n                    <div\n                      aria-label=\"Delete board\"\n                      class=\"MenuOption TextOption menu-option text-danger\"\n                      role=\"button\"\n                    >\n                      <div\n                        class=\"d-flex\"\n                      >\n                        <div\n                          class=\"menu-option__icon\"\n                        >\n                          <i\n                            class=\"CompassIcon icon-trash-can-outline DeleteIcon trash-can-outline\"\n                          />\n                        </div>\n                      </div>\n                      <div\n                        class=\"menu-option__content\"\n                      >\n                        <div\n                          class=\"menu-name\"\n                        >\n                          Delete board\n                        </div>\n                      </div>\n                      <div\n                        class=\"noicon\"\n                      />\n                    </div>\n                  </div>\n                </div>\n                <div\n                  class=\"menu-spacer hideOnWidescreen\"\n                />\n                <div\n                  class=\"menu-options hideOnWidescreen\"\n                >\n                  <div\n                    aria-label=\"Cancel\"\n                    class=\"MenuOption TextOption menu-option menu-cancel\"\n                    role=\"button\"\n                  >\n                    <div\n                      class=\"d-flex\"\n                    >\n                      <div\n                        class=\"noicon\"\n                      />\n                    </div>\n                    <div\n                      class=\"menu-option__content\"\n                    >\n                      <div\n                        class=\"menu-name\"\n                      >\n                        Cancel\n                      </div>\n                    </div>\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n      <div\n        class=\"SidebarBoardItem sidebar-view-item active\"\n      >\n        <svg\n          class=\"BoardIcon Icon\"\n          fill=\"currentColor\"\n          height=\"24\"\n          viewBox=\"0 0 24 24\"\n          width=\"24\"\n          xmlns=\"http://www.w3.org/2000/svg\"\n        >\n          <g\n            opacity=\"0.8\"\n          >\n            <path\n              clip-rule=\"evenodd\"\n              d=\"M4 4H20V20H4V4ZM2 4C2 2.89543 2.89543 2 4 2H20C21.1046 2 22 2.89543 22 4V20C22 21.1046 21.1046 22 20 22H4C2.89543 22 2 21.1046 2 20V4ZM8 6H6V12H8V6ZM11 6H13V16H11V6ZM18 6H16V9H18V6Z\"\n              fill=\"currentColor\"\n              fill-rule=\"evenodd\"\n            />\n          </g>\n        </svg>\n        <div\n          class=\"octo-sidebar-title\"\n          title=\"view title\"\n        >\n          view title\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/sidebar/__snapshots__/sidebarCategory.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/sidebarCategory sidebar call hideSidebar 1`] = `\n<div>\n  <div\n    data-rbd-droppable-context-id=\"0\"\n    data-rbd-droppable-id=\"droppable_id\"\n  >\n    <div\n      data-rbd-draggable-context-id=\"0\"\n      data-rbd-draggable-id=\"category_1_id\"\n    >\n      <div\n        class=\"SidebarCategory\"\n      >\n        <div\n          class=\"categoryBoardsDroppableArea\"\n          data-rbd-droppable-context-id=\"0\"\n          data-rbd-droppable-id=\"category_1_id\"\n        >\n          <div\n            class=\"octo-sidebar-item category expanded \"\n          >\n            <div\n              aria-describedby=\"rbd-hidden-text-0-hidden-text-0\"\n              class=\"octo-sidebar-title category-title\"\n              data-rbd-drag-handle-context-id=\"0\"\n              data-rbd-drag-handle-draggable-id=\"category_1_id\"\n              draggable=\"false\"\n              role=\"button\"\n              tabindex=\"0\"\n              title=\"Category 1\"\n            >\n              <i\n                class=\"CompassIcon icon-chevron-down ChevronDownIcon\"\n              />\n              Category 1\n              <div\n                class=\"sidebarCategoriesTour\"\n              />\n            </div>\n            <div\n              class=\"\"\n            >\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n            </div>\n          </div>\n          <div\n            data-rbd-draggable-context-id=\"0\"\n            data-rbd-draggable-id=\"board_1_id\"\n          >\n            <div\n              aria-describedby=\"rbd-hidden-text-0-hidden-text-0\"\n              class=\"SidebarBoardItem subitem \"\n              data-rbd-drag-handle-context-id=\"0\"\n              data-rbd-drag-handle-draggable-id=\"board_1_id\"\n              draggable=\"false\"\n              role=\"button\"\n              tabindex=\"0\"\n            >\n              <div\n                class=\"octo-sidebar-icon\"\n              >\n                i\n              </div>\n              <div\n                class=\"octo-sidebar-title\"\n                title=\"board title\"\n              >\n                board title\n              </div>\n              <div>\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper x\"\n                  role=\"button\"\n                >\n                  <button\n                    class=\"IconButton\"\n                    type=\"button\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                    />\n                  </button>\n                </div>\n              </div>\n            </div>\n          </div>\n          <div\n            data-rbd-draggable-context-id=\"0\"\n            data-rbd-draggable-id=\"board_2_id\"\n          >\n            <div\n              aria-describedby=\"rbd-hidden-text-0-hidden-text-0\"\n              class=\"SidebarBoardItem subitem \"\n              data-rbd-drag-handle-context-id=\"0\"\n              data-rbd-drag-handle-draggable-id=\"board_2_id\"\n              draggable=\"false\"\n              role=\"button\"\n              tabindex=\"0\"\n            >\n              <div\n                class=\"octo-sidebar-icon\"\n              >\n                i\n              </div>\n              <div\n                class=\"octo-sidebar-title\"\n                title=\"board title\"\n              >\n                board title\n              </div>\n              <div>\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper x\"\n                  role=\"button\"\n                >\n                  <button\n                    class=\"IconButton\"\n                    type=\"button\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                    />\n                  </button>\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/sidebarCategory sidebar call hideSidebar 2`] = `\n<div>\n  <div\n    data-rbd-droppable-context-id=\"0\"\n    data-rbd-droppable-id=\"droppable_id\"\n  >\n    <div\n      data-rbd-draggable-context-id=\"0\"\n      data-rbd-draggable-id=\"category_1_id\"\n    >\n      <div\n        class=\"SidebarCategory\"\n      >\n        <div\n          class=\"categoryBoardsDroppableArea\"\n          data-rbd-droppable-context-id=\"0\"\n          data-rbd-droppable-id=\"category_1_id\"\n        >\n          <div\n            class=\"octo-sidebar-item category expanded \"\n          >\n            <div\n              aria-describedby=\"rbd-hidden-text-0-hidden-text-0\"\n              class=\"octo-sidebar-title category-title\"\n              data-rbd-drag-handle-context-id=\"0\"\n              data-rbd-drag-handle-draggable-id=\"category_1_id\"\n              draggable=\"false\"\n              role=\"button\"\n              tabindex=\"0\"\n              title=\"Category 1\"\n            >\n              <i\n                class=\"CompassIcon icon-chevron-down ChevronDownIcon\"\n              />\n              Category 1\n              <div\n                class=\"sidebarCategoriesTour\"\n              />\n            </div>\n            <div\n              class=\"\"\n            >\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n            </div>\n          </div>\n          <div\n            data-rbd-draggable-context-id=\"0\"\n            data-rbd-draggable-id=\"board_1_id\"\n          >\n            <div\n              aria-describedby=\"rbd-hidden-text-0-hidden-text-0\"\n              class=\"SidebarBoardItem subitem \"\n              data-rbd-drag-handle-context-id=\"0\"\n              data-rbd-drag-handle-draggable-id=\"board_1_id\"\n              draggable=\"false\"\n              role=\"button\"\n              tabindex=\"0\"\n            >\n              <div\n                class=\"octo-sidebar-icon\"\n              >\n                i\n              </div>\n              <div\n                class=\"octo-sidebar-title\"\n                title=\"board title\"\n              >\n                board title\n              </div>\n              <div>\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper x\"\n                  role=\"button\"\n                >\n                  <button\n                    class=\"IconButton\"\n                    type=\"button\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                    />\n                  </button>\n                </div>\n              </div>\n            </div>\n          </div>\n          <div\n            data-rbd-draggable-context-id=\"0\"\n            data-rbd-draggable-id=\"board_2_id\"\n          >\n            <div\n              aria-describedby=\"rbd-hidden-text-0-hidden-text-0\"\n              class=\"SidebarBoardItem subitem \"\n              data-rbd-drag-handle-context-id=\"0\"\n              data-rbd-drag-handle-draggable-id=\"board_2_id\"\n              draggable=\"false\"\n              role=\"button\"\n              tabindex=\"0\"\n            >\n              <div\n                class=\"octo-sidebar-icon\"\n              >\n                i\n              </div>\n              <div\n                class=\"octo-sidebar-title\"\n                title=\"board title\"\n              >\n                board title\n              </div>\n              <div>\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper x\"\n                  role=\"button\"\n                >\n                  <button\n                    class=\"IconButton\"\n                    type=\"button\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                    />\n                  </button>\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/sidebarCategory sidebar collapsed with active board in it 1`] = `\n<div>\n  <div\n    data-rbd-droppable-context-id=\"2\"\n    data-rbd-droppable-id=\"droppable_id\"\n  >\n    <div\n      data-rbd-draggable-context-id=\"2\"\n      data-rbd-draggable-id=\"category_1_id\"\n    >\n      <div\n        class=\"SidebarCategory\"\n      >\n        <div\n          class=\"categoryBoardsDroppableArea\"\n          data-rbd-droppable-context-id=\"2\"\n          data-rbd-droppable-id=\"category_1_id\"\n        >\n          <div\n            class=\"octo-sidebar-item category collapsed \"\n          >\n            <div\n              aria-describedby=\"rbd-hidden-text-2-hidden-text-12\"\n              class=\"octo-sidebar-title category-title\"\n              data-rbd-drag-handle-context-id=\"2\"\n              data-rbd-drag-handle-draggable-id=\"category_1_id\"\n              draggable=\"false\"\n              role=\"button\"\n              tabindex=\"0\"\n              title=\"Category 1\"\n            >\n              <i\n                class=\"CompassIcon icon-chevron-right ChevronRightIcon\"\n              />\n              Category 1\n              <div\n                class=\"sidebarCategoriesTour\"\n              />\n            </div>\n            <div\n              class=\"\"\n            >\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n            </div>\n          </div>\n          <div\n            data-rbd-draggable-context-id=\"2\"\n            data-rbd-draggable-id=\"board_1_id\"\n          >\n            <div\n              aria-describedby=\"rbd-hidden-text-2-hidden-text-12\"\n              class=\"SidebarBoardItem subitem active\"\n              data-rbd-drag-handle-context-id=\"2\"\n              data-rbd-drag-handle-draggable-id=\"board_1_id\"\n              draggable=\"false\"\n              role=\"button\"\n              tabindex=\"0\"\n            >\n              <div\n                class=\"octo-sidebar-icon\"\n              >\n                i\n              </div>\n              <div\n                class=\"octo-sidebar-title\"\n                title=\"board title\"\n              >\n                board title\n              </div>\n              <div>\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper x\"\n                  role=\"button\"\n                >\n                  <button\n                    class=\"IconButton\"\n                    type=\"button\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                    />\n                  </button>\n                </div>\n              </div>\n            </div>\n            <div\n              class=\"SidebarBoardItem sidebar-view-item active\"\n            >\n              <svg\n                class=\"BoardIcon Icon\"\n                fill=\"currentColor\"\n                height=\"24\"\n                viewBox=\"0 0 24 24\"\n                width=\"24\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n              >\n                <g\n                  opacity=\"0.8\"\n                >\n                  <path\n                    clip-rule=\"evenodd\"\n                    d=\"M4 4H20V20H4V4ZM2 4C2 2.89543 2.89543 2 4 2H20C21.1046 2 22 2.89543 22 4V20C22 21.1046 21.1046 22 20 22H4C2.89543 22 2 21.1046 2 20V4ZM8 6H6V12H8V6ZM11 6H13V16H11V6ZM18 6H16V9H18V6Z\"\n                    fill=\"currentColor\"\n                    fill-rule=\"evenodd\"\n                  />\n                </g>\n              </svg>\n              <div\n                class=\"octo-sidebar-title\"\n                title=\"view title\"\n              >\n                view title\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/sidebarCategory sidebar collapsed without active board 1`] = `\n<div>\n  <div\n    data-rbd-droppable-context-id=\"1\"\n    data-rbd-droppable-id=\"droppable_id\"\n  >\n    <div\n      data-rbd-draggable-context-id=\"1\"\n      data-rbd-draggable-id=\"category_1_id\"\n    >\n      <div\n        class=\"SidebarCategory\"\n      >\n        <div\n          class=\"categoryBoardsDroppableArea\"\n          data-rbd-droppable-context-id=\"1\"\n          data-rbd-droppable-id=\"category_1_id\"\n        >\n          <div\n            class=\"octo-sidebar-item category collapsed \"\n          >\n            <div\n              aria-describedby=\"rbd-hidden-text-1-hidden-text-6\"\n              class=\"octo-sidebar-title category-title\"\n              data-rbd-drag-handle-context-id=\"1\"\n              data-rbd-drag-handle-draggable-id=\"category_1_id\"\n              draggable=\"false\"\n              role=\"button\"\n              tabindex=\"0\"\n              title=\"Category 1\"\n            >\n              <i\n                class=\"CompassIcon icon-chevron-right ChevronRightIcon\"\n              />\n              Category 1\n              <div\n                class=\"sidebarCategoriesTour\"\n              />\n            </div>\n            <div\n              class=\"\"\n            >\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/sidebarCategory sidebar template close other 1`] = `\n<div>\n  <div\n    data-rbd-droppable-context-id=\"4\"\n    data-rbd-droppable-id=\"droppable_id\"\n  >\n    <div\n      data-rbd-draggable-context-id=\"4\"\n      data-rbd-draggable-id=\"category_1_id\"\n    >\n      <div\n        class=\"SidebarCategory\"\n      >\n        <div\n          class=\"categoryBoardsDroppableArea\"\n          data-rbd-droppable-context-id=\"4\"\n          data-rbd-droppable-id=\"category_1_id\"\n        >\n          <div\n            class=\"octo-sidebar-item category expanded \"\n          >\n            <div\n              aria-describedby=\"rbd-hidden-text-4-hidden-text-25\"\n              class=\"octo-sidebar-title category-title\"\n              data-rbd-drag-handle-context-id=\"4\"\n              data-rbd-drag-handle-draggable-id=\"category_1_id\"\n              draggable=\"false\"\n              role=\"button\"\n              tabindex=\"0\"\n              title=\"Category 1\"\n            >\n              <i\n                class=\"CompassIcon icon-chevron-down ChevronDownIcon\"\n              />\n              Category 1\n              <div\n                class=\"sidebarCategoriesTour\"\n              />\n            </div>\n            <div\n              class=\"\"\n            >\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n            </div>\n          </div>\n          <div\n            data-rbd-draggable-context-id=\"4\"\n            data-rbd-draggable-id=\"board_1_id\"\n          >\n            <div\n              aria-describedby=\"rbd-hidden-text-4-hidden-text-25\"\n              class=\"SidebarBoardItem subitem \"\n              data-rbd-drag-handle-context-id=\"4\"\n              data-rbd-drag-handle-draggable-id=\"board_1_id\"\n              draggable=\"false\"\n              role=\"button\"\n              tabindex=\"0\"\n            >\n              <div\n                class=\"octo-sidebar-icon\"\n              >\n                i\n              </div>\n              <div\n                class=\"octo-sidebar-title\"\n                title=\"board title\"\n              >\n                board title\n              </div>\n              <div>\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper x\"\n                  role=\"button\"\n                >\n                  <button\n                    class=\"IconButton\"\n                    type=\"button\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                    />\n                  </button>\n                </div>\n              </div>\n            </div>\n          </div>\n          <div\n            data-rbd-draggable-context-id=\"4\"\n            data-rbd-draggable-id=\"board_2_id\"\n          >\n            <div\n              aria-describedby=\"rbd-hidden-text-4-hidden-text-25\"\n              class=\"SidebarBoardItem subitem active\"\n              data-rbd-drag-handle-context-id=\"4\"\n              data-rbd-drag-handle-draggable-id=\"board_2_id\"\n              draggable=\"false\"\n              role=\"button\"\n              tabindex=\"0\"\n            >\n              <div\n                class=\"octo-sidebar-icon\"\n              >\n                i\n              </div>\n              <div\n                class=\"octo-sidebar-title\"\n                title=\"board title\"\n              >\n                board title\n              </div>\n              <div>\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper x\"\n                  role=\"button\"\n                >\n                  <button\n                    class=\"IconButton\"\n                    type=\"button\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                    />\n                  </button>\n                </div>\n              </div>\n            </div>\n            <div\n              class=\"SidebarBoardItem sidebar-view-item active\"\n            >\n              <svg\n                class=\"BoardIcon Icon\"\n                fill=\"currentColor\"\n                height=\"24\"\n                viewBox=\"0 0 24 24\"\n                width=\"24\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n              >\n                <g\n                  opacity=\"0.8\"\n                >\n                  <path\n                    clip-rule=\"evenodd\"\n                    d=\"M4 4H20V20H4V4ZM2 4C2 2.89543 2.89543 2 4 2H20C21.1046 2 22 2.89543 22 4V20C22 21.1046 21.1046 22 20 22H4C2.89543 22 2 21.1046 2 20V4ZM8 6H6V12H8V6ZM11 6H13V16H11V6ZM18 6H16V9H18V6Z\"\n                    fill=\"currentColor\"\n                    fill-rule=\"evenodd\"\n                  />\n                </g>\n              </svg>\n              <div\n                class=\"octo-sidebar-title\"\n                title=\"view title\"\n              >\n                view title\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/sidebarCategory sidebar template close self 1`] = `\n<div>\n  <div\n    data-rbd-droppable-context-id=\"3\"\n    data-rbd-droppable-id=\"droppable_id\"\n  >\n    <div\n      data-rbd-draggable-context-id=\"3\"\n      data-rbd-draggable-id=\"category_1_id\"\n    >\n      <div\n        class=\"SidebarCategory\"\n      >\n        <div\n          class=\"categoryBoardsDroppableArea\"\n          data-rbd-droppable-context-id=\"3\"\n          data-rbd-droppable-id=\"category_1_id\"\n        >\n          <div\n            class=\"octo-sidebar-item category expanded \"\n          >\n            <div\n              aria-describedby=\"rbd-hidden-text-3-hidden-text-19\"\n              class=\"octo-sidebar-title category-title\"\n              data-rbd-drag-handle-context-id=\"3\"\n              data-rbd-drag-handle-draggable-id=\"category_1_id\"\n              draggable=\"false\"\n              role=\"button\"\n              tabindex=\"0\"\n              title=\"Category 1\"\n            >\n              <i\n                class=\"CompassIcon icon-chevron-down ChevronDownIcon\"\n              />\n              Category 1\n              <div\n                class=\"sidebarCategoriesTour\"\n              />\n            </div>\n            <div\n              class=\"\"\n            >\n              <div\n                aria-label=\"menuwrapper\"\n                class=\"MenuWrapper\"\n                role=\"button\"\n              >\n                <button\n                  class=\"IconButton\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n            </div>\n          </div>\n          <div\n            data-rbd-draggable-context-id=\"3\"\n            data-rbd-draggable-id=\"board_1_id\"\n          >\n            <div\n              aria-describedby=\"rbd-hidden-text-3-hidden-text-19\"\n              class=\"SidebarBoardItem subitem active\"\n              data-rbd-drag-handle-context-id=\"3\"\n              data-rbd-drag-handle-draggable-id=\"board_1_id\"\n              draggable=\"false\"\n              role=\"button\"\n              tabindex=\"0\"\n            >\n              <div\n                class=\"octo-sidebar-icon\"\n              >\n                i\n              </div>\n              <div\n                class=\"octo-sidebar-title\"\n                title=\"board title\"\n              >\n                board title\n              </div>\n              <div>\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper x\"\n                  role=\"button\"\n                >\n                  <button\n                    class=\"IconButton\"\n                    type=\"button\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                    />\n                  </button>\n                </div>\n              </div>\n            </div>\n            <div\n              class=\"SidebarBoardItem sidebar-view-item active\"\n            >\n              <svg\n                class=\"BoardIcon Icon\"\n                fill=\"currentColor\"\n                height=\"24\"\n                viewBox=\"0 0 24 24\"\n                width=\"24\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n              >\n                <g\n                  opacity=\"0.8\"\n                >\n                  <path\n                    clip-rule=\"evenodd\"\n                    d=\"M4 4H20V20H4V4ZM2 4C2 2.89543 2.89543 2 4 2H20C21.1046 2 22 2.89543 22 4V20C22 21.1046 21.1046 22 20 22H4C2.89543 22 2 21.1046 2 20V4ZM8 6H6V12H8V6ZM11 6H13V16H11V6ZM18 6H16V9H18V6Z\"\n                    fill=\"currentColor\"\n                    fill-rule=\"evenodd\"\n                  />\n                </g>\n              </svg>\n              <div\n                class=\"octo-sidebar-title\"\n                title=\"view title\"\n              >\n                view title\n              </div>\n            </div>\n          </div>\n          <div\n            data-rbd-draggable-context-id=\"3\"\n            data-rbd-draggable-id=\"board_2_id\"\n          >\n            <div\n              aria-describedby=\"rbd-hidden-text-3-hidden-text-19\"\n              class=\"SidebarBoardItem subitem \"\n              data-rbd-drag-handle-context-id=\"3\"\n              data-rbd-drag-handle-draggable-id=\"board_2_id\"\n              draggable=\"false\"\n              role=\"button\"\n              tabindex=\"0\"\n            >\n              <div\n                class=\"octo-sidebar-icon\"\n              >\n                i\n              </div>\n              <div\n                class=\"octo-sidebar-title\"\n                title=\"board title\"\n              >\n                board title\n              </div>\n              <div>\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper x\"\n                  role=\"button\"\n                >\n                  <button\n                    class=\"IconButton\"\n                    type=\"button\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                    />\n                  </button>\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/sidebar/__snapshots__/sidebarSettingsMenu.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/sidebar/SidebarSettingsMenu imports menu open should match snapshot 1`] = `\n<div>\n  <div\n    class=\"SidebarSettingsMenu\"\n  >\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper override menuOpened\"\n      role=\"button\"\n    >\n      <div\n        class=\"menu-entry\"\n      >\n        Settings\n      </div>\n      <div\n        class=\"Menu noselect top \"\n      >\n        <div\n          class=\"menu-contents\"\n        >\n          <div\n            class=\"menu-options\"\n          >\n            <div>\n              <div\n                class=\"MenuOption SubMenuOption menu-option menu-option-active\"\n                id=\"import\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n                <div\n                  class=\"menu-name\"\n                >\n                  Import\n                </div>\n                <i\n                  class=\"CompassIcon icon-chevron-right\"\n                />\n                <div\n                  class=\"SubMenu Menu noselect top\"\n                >\n                  <div\n                    class=\"menu-contents\"\n                  >\n                    <div\n                      class=\"menu-options\"\n                    >\n                      <div\n                        aria-label=\"Import archive\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            Import archive\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                      <div\n                        aria-label=\"Trello\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            Trello\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                      <div\n                        aria-label=\"Asana\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            Asana\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                      <div\n                        aria-label=\"Notion\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            Notion\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                      <div\n                        aria-label=\"Jira\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            Jira\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                      <div\n                        aria-label=\"Todoist\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            Todoist\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                    </div>\n                    <div\n                      class=\"menu-spacer hideOnWidescreen\"\n                    />\n                    <div\n                      class=\"menu-options hideOnWidescreen\"\n                    >\n                      <div\n                        aria-label=\"Cancel\"\n                        class=\"MenuOption TextOption menu-option menu-cancel\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            Cancel\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                    </div>\n                  </div>\n                </div>\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Export archive\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Export archive\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                class=\"MenuOption SubMenuOption menu-option\"\n                id=\"lang\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n                <div\n                  class=\"menu-name\"\n                >\n                  Set language\n                </div>\n                <i\n                  class=\"CompassIcon icon-chevron-right\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                class=\"MenuOption SubMenuOption menu-option\"\n                id=\"theme\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n                <div\n                  class=\"menu-name\"\n                >\n                  Set theme\n                </div>\n                <i\n                  class=\"CompassIcon icon-chevron-right\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Random icons\"\n                class=\"MenuOption SwitchOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n                <div\n                  class=\"menu-name\"\n                >\n                  Random icons\n                </div>\n                <div\n                  class=\"Switch override-main size--small on\"\n                >\n                  <div\n                    class=\"octo-switch-inner\"\n                  />\n                </div>\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"menu-spacer hideOnWidescreen\"\n          />\n          <div\n            class=\"menu-options hideOnWidescreen\"\n          >\n            <div\n              aria-label=\"Cancel\"\n              class=\"MenuOption TextOption menu-option menu-cancel\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Cancel\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/sidebar/SidebarSettingsMenu languages menu open should match snapshot 1`] = `\n<div>\n  <div\n    class=\"SidebarSettingsMenu\"\n  >\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper override menuOpened\"\n      role=\"button\"\n    >\n      <div\n        class=\"menu-entry\"\n      >\n        Settings\n      </div>\n      <div\n        class=\"Menu noselect top \"\n      >\n        <div\n          class=\"menu-contents\"\n        >\n          <div\n            class=\"menu-options\"\n          >\n            <div>\n              <div\n                class=\"MenuOption SubMenuOption menu-option\"\n                id=\"import\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n                <div\n                  class=\"menu-name\"\n                >\n                  Import\n                </div>\n                <i\n                  class=\"CompassIcon icon-chevron-right\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Export archive\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Export archive\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                class=\"MenuOption SubMenuOption menu-option menu-option-active\"\n                id=\"lang\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n                <div\n                  class=\"menu-name\"\n                >\n                  Set language\n                </div>\n                <i\n                  class=\"CompassIcon icon-chevron-right\"\n                />\n                <div\n                  class=\"SubMenu Menu noselect top\"\n                >\n                  <div\n                    class=\"menu-contents\"\n                  >\n                    <div\n                      class=\"menu-options\"\n                    >\n                      <div\n                        aria-label=\"English\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            English\n                          </div>\n                        </div>\n                        <svg\n                          class=\"CheckIcon Icon\"\n                          viewBox=\"0 0 100 100\"\n                          xmlns=\"http://www.w3.org/2000/svg\"\n                        >\n                          <polyline\n                            points=\"20,60 40,80 80,40\"\n                          />\n                        </svg>\n                      </div>\n                      <div\n                        aria-label=\"Español\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            Español\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                      <div\n                        aria-label=\"Deutsch\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            Deutsch\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                      <div\n                        aria-label=\"日本語\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            日本語\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                      <div\n                        aria-label=\"Français\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            Français\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                      <div\n                        aria-label=\"Nederlands\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            Nederlands\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                      <div\n                        aria-label=\"Pусский\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            Pусский\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                      <div\n                        aria-label=\"中文 (繁體)\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            中文 (繁體)\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                      <div\n                        aria-label=\"中文 (简体)\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            中文 (简体)\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                      <div\n                        aria-label=\"Türkçe\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            Türkçe\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                      <div\n                        aria-label=\"Occitan\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            Occitan\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                      <div\n                        aria-label=\"Português (Brasil)\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            Português (Brasil)\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                      <div\n                        aria-label=\"Català\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            Català\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                      <div\n                        aria-label=\"Ελληνικά\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            Ελληνικά\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                      <div\n                        aria-label=\"bahasa Indonesia\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            bahasa Indonesia\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                      <div\n                        aria-label=\"Italiano\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            Italiano\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                      <div\n                        aria-label=\"Svenska\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            Svenska\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                    </div>\n                    <div\n                      class=\"menu-spacer hideOnWidescreen\"\n                    />\n                    <div\n                      class=\"menu-options hideOnWidescreen\"\n                    >\n                      <div\n                        aria-label=\"Cancel\"\n                        class=\"MenuOption TextOption menu-option menu-cancel\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            Cancel\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                    </div>\n                  </div>\n                </div>\n              </div>\n            </div>\n            <div>\n              <div\n                class=\"MenuOption SubMenuOption menu-option\"\n                id=\"theme\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n                <div\n                  class=\"menu-name\"\n                >\n                  Set theme\n                </div>\n                <i\n                  class=\"CompassIcon icon-chevron-right\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Random icons\"\n                class=\"MenuOption SwitchOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n                <div\n                  class=\"menu-name\"\n                >\n                  Random icons\n                </div>\n                <div\n                  class=\"Switch override-main size--small on\"\n                >\n                  <div\n                    class=\"octo-switch-inner\"\n                  />\n                </div>\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"menu-spacer hideOnWidescreen\"\n          />\n          <div\n            class=\"menu-options hideOnWidescreen\"\n          >\n            <div\n              aria-label=\"Cancel\"\n              class=\"MenuOption TextOption menu-option menu-cancel\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Cancel\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/sidebar/SidebarSettingsMenu settings menu closed should match snapshot 1`] = `\n<div>\n  <div\n    class=\"SidebarSettingsMenu\"\n  >\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper\"\n      role=\"button\"\n    >\n      <div\n        class=\"menu-entry\"\n      >\n        Settings\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/sidebar/SidebarSettingsMenu settings menu open should match snapshot 1`] = `\n<div>\n  <div\n    class=\"SidebarSettingsMenu\"\n  >\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper override menuOpened\"\n      role=\"button\"\n    >\n      <div\n        class=\"menu-entry\"\n      >\n        Settings\n      </div>\n      <div\n        class=\"Menu noselect top \"\n      >\n        <div\n          class=\"menu-contents\"\n        >\n          <div\n            class=\"menu-options\"\n          >\n            <div>\n              <div\n                class=\"MenuOption SubMenuOption menu-option\"\n                id=\"import\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n                <div\n                  class=\"menu-name\"\n                >\n                  Import\n                </div>\n                <i\n                  class=\"CompassIcon icon-chevron-right\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Export archive\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Export archive\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                class=\"MenuOption SubMenuOption menu-option\"\n                id=\"lang\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n                <div\n                  class=\"menu-name\"\n                >\n                  Set language\n                </div>\n                <i\n                  class=\"CompassIcon icon-chevron-right\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                class=\"MenuOption SubMenuOption menu-option\"\n                id=\"theme\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n                <div\n                  class=\"menu-name\"\n                >\n                  Set theme\n                </div>\n                <i\n                  class=\"CompassIcon icon-chevron-right\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Random icons\"\n                class=\"MenuOption SwitchOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n                <div\n                  class=\"menu-name\"\n                >\n                  Random icons\n                </div>\n                <div\n                  class=\"Switch override-main size--small on\"\n                >\n                  <div\n                    class=\"octo-switch-inner\"\n                  />\n                </div>\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"menu-spacer hideOnWidescreen\"\n          />\n          <div\n            class=\"menu-options hideOnWidescreen\"\n          >\n            <div\n              aria-label=\"Cancel\"\n              class=\"MenuOption TextOption menu-option menu-cancel\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Cancel\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/sidebar/SidebarSettingsMenu theme menu open should match snapshot 1`] = `\n<div>\n  <div\n    class=\"SidebarSettingsMenu\"\n  >\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper override menuOpened\"\n      role=\"button\"\n    >\n      <div\n        class=\"menu-entry\"\n      >\n        Settings\n      </div>\n      <div\n        class=\"Menu noselect top \"\n      >\n        <div\n          class=\"menu-contents\"\n        >\n          <div\n            class=\"menu-options\"\n          >\n            <div>\n              <div\n                class=\"MenuOption SubMenuOption menu-option\"\n                id=\"import\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n                <div\n                  class=\"menu-name\"\n                >\n                  Import\n                </div>\n                <i\n                  class=\"CompassIcon icon-chevron-right\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Export archive\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Export archive\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                class=\"MenuOption SubMenuOption menu-option\"\n                id=\"lang\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n                <div\n                  class=\"menu-name\"\n                >\n                  Set language\n                </div>\n                <i\n                  class=\"CompassIcon icon-chevron-right\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                class=\"MenuOption SubMenuOption menu-option menu-option-active\"\n                id=\"theme\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n                <div\n                  class=\"menu-name\"\n                >\n                  Set theme\n                </div>\n                <i\n                  class=\"CompassIcon icon-chevron-right\"\n                />\n                <div\n                  class=\"SubMenu Menu noselect top\"\n                >\n                  <div\n                    class=\"menu-contents\"\n                  >\n                    <div\n                      class=\"menu-options\"\n                    >\n                      <div\n                        aria-label=\"Default theme\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            Default theme\n                          </div>\n                        </div>\n                        <svg\n                          class=\"CheckIcon Icon\"\n                          viewBox=\"0 0 100 100\"\n                          xmlns=\"http://www.w3.org/2000/svg\"\n                        >\n                          <polyline\n                            points=\"20,60 40,80 80,40\"\n                          />\n                        </svg>\n                      </div>\n                      <div\n                        aria-label=\"Dark theme\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            Dark theme\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                      <div\n                        aria-label=\"Light theme\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            Light theme\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                      <div\n                        aria-label=\"System theme\"\n                        class=\"MenuOption TextOption menu-option\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            System theme\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                    </div>\n                    <div\n                      class=\"menu-spacer hideOnWidescreen\"\n                    />\n                    <div\n                      class=\"menu-options hideOnWidescreen\"\n                    >\n                      <div\n                        aria-label=\"Cancel\"\n                        class=\"MenuOption TextOption menu-option menu-cancel\"\n                        role=\"button\"\n                      >\n                        <div\n                          class=\"d-flex\"\n                        >\n                          <div\n                            class=\"noicon\"\n                          />\n                        </div>\n                        <div\n                          class=\"menu-option__content\"\n                        >\n                          <div\n                            class=\"menu-name\"\n                          >\n                            Cancel\n                          </div>\n                        </div>\n                        <div\n                          class=\"noicon\"\n                        />\n                      </div>\n                    </div>\n                  </div>\n                </div>\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Random icons\"\n                class=\"MenuOption SwitchOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n                <div\n                  class=\"menu-name\"\n                >\n                  Random icons\n                </div>\n                <div\n                  class=\"Switch override-main size--small on\"\n                >\n                  <div\n                    class=\"octo-switch-inner\"\n                  />\n                </div>\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"menu-spacer hideOnWidescreen\"\n          />\n          <div\n            class=\"menu-options hideOnWidescreen\"\n          >\n            <div\n              aria-label=\"Cancel\"\n              class=\"MenuOption TextOption menu-option menu-cancel\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Cancel\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/sidebar/deleteBoardDialog.scss",
    "content": ".DeleteBoardDialog {\n    .dialog {\n        @media not screen and (max-width: 975px) {\n            max-width: 512px;\n            height: max-content;\n        }\n\n        > .toolbar {\n            padding-bottom: 0;\n            justify-content: end;\n        }\n    }\n\n    .container {\n        display: flex;\n        flex-direction: column;\n        flex-grow: 1;\n        padding: 0 40px;\n        max-width: 100%;\n\n        .header {\n            flex-shrink: 0;\n            display: flex;\n            justify-content: center;\n            margin-top: 0;\n            margin-bottom: 0;\n        }\n\n        p.body {\n            display: flex;\n            flex-grow: 1;\n            justify-content: center;\n            text-align: center;\n            margin: 10px 0;\n        }\n\n        .footer {\n            display: flex;\n            flex-direction: row;\n            flex-shrink: 0;\n            justify-content: center;\n            padding: 20px 0 40px;\n\n            button:first-child {\n                margin-right: 10px;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/sidebar/deleteBoardDialog.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React, {useState} from 'react'\nimport {IntlProvider} from 'react-intl'\n\nimport userEvent from '@testing-library/user-event'\nimport {act, render} from '@testing-library/react'\n\nimport DeleteBoardDialog from './deleteBoardDialog'\n\ndescribe('components/sidebar/DeleteBoardDialog', () => {\n    it('Cancel should not submit', async () => {\n        const container = renderTest()\n\n        const cancelButton = container.querySelector('.dialog .footer button:not(.danger)')\n        expect(cancelButton).not.toBeFalsy()\n        expect(cancelButton?.textContent).toBe('Cancel')\n        await act(async () => userEvent.click(cancelButton as Element))\n\n        expect(container).toMatchSnapshot()\n    })\n\n    it('Delete should submit', async () => {\n        const container = renderTest()\n\n        const deleteButton = container.querySelector('.dialog .footer button.danger')\n        expect(deleteButton).not.toBeFalsy()\n        expect(deleteButton?.textContent).toBe('Delete')\n        await act(async () => userEvent.click(deleteButton as Element))\n\n        expect(container).toMatchSnapshot()\n    })\n\n    function renderTest() {\n        const rootPortalDiv = document.createElement('div')\n        rootPortalDiv.id = 'focalboard-root-portal'\n\n        const {container} = render(<TestComponent/>, {container: document.body.appendChild(rootPortalDiv)})\n        return container\n    }\n\n    function TestComponent() {\n        const [isDeleted, setDeleted] = useState(false)\n        const [isOpen, setOpen] = useState(true)\n\n        return (<IntlProvider locale='en'>\n            {isDeleted ? 'deleted' : 'exists'}\n            {isOpen &&\n            <DeleteBoardDialog\n                boardTitle={'Delete'}\n                onClose={() => setOpen(false)}\n                onDelete={async () => setDeleted(true)}\n            />}\n        </IntlProvider>)\n    }\n})\n"
  },
  {
    "path": "webapp/src/components/sidebar/deleteBoardDialog.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useState} from 'react'\nimport {FormattedMessage} from 'react-intl'\n\nimport {Utils} from '../../utils'\nimport Button from '../../widgets/buttons/button'\n\nimport Dialog from '../dialog'\nimport RootPortal from '../rootPortal'\n\nimport './deleteBoardDialog.scss'\n\ntype Props = {\n    boardTitle: string\n    onClose: () => void\n    onDelete: () => Promise<void>\n    isTemplate?: boolean\n}\n\nexport default function DeleteBoardDialog(props: Props): JSX.Element {\n    const [isSubmitting, setSubmitting] = useState(false)\n\n    return (\n        <RootPortal>\n            <Dialog\n                onClose={props.onClose}\n                toolsMenu={null}\n                className='DeleteBoardDialog'\n            >\n                <div className='container'>\n                    <h2 className='header text-heading5'>\n                        {props.isTemplate &&\n                            <FormattedMessage\n                                id='DeleteBoardDialog.confirm-tite-template'\n                                defaultMessage='Confirm delete board template'\n                            />}\n                        {!props.isTemplate &&\n                            <FormattedMessage\n                                id='DeleteBoardDialog.confirm-tite'\n                                defaultMessage='Confirm delete board'\n                            />}\n                    </h2>\n                    <p className='body'>\n                        {props.isTemplate &&\n                            <FormattedMessage\n                                id='DeleteBoardDialog.confirm-info-template'\n                                defaultMessage='Are you sure you want to delete the board template “{boardTitle}”?'\n                                values={{\n                                    boardTitle: props.boardTitle,\n                                }}\n                            />}\n                        {!props.isTemplate &&\n                            <FormattedMessage\n                                id='DeleteBoardDialog.confirm-info'\n                                defaultMessage='Are you sure you want to delete the board “{boardTitle}”? Deleting it will delete all cards in the board.'\n                                values={{\n                                    boardTitle: props.boardTitle,\n                                }}\n                            />}\n                    </p>\n                    <div className='footer'>\n                        <Button\n                            size={'medium'}\n                            emphasis={'tertiary'}\n                            onClick={(e: React.MouseEvent) => {\n                                e.stopPropagation()\n                                !isSubmitting && props.onClose()\n                            }}\n                        >\n                            <FormattedMessage\n                                id='DeleteBoardDialog.confirm-cancel'\n                                defaultMessage='Cancel'\n                            />\n                        </Button>\n                        <Button\n                            size={'medium'}\n                            filled={true}\n                            danger={true}\n                            onClick={async (e: React.MouseEvent) => {\n                                e.stopPropagation()\n                                try {\n                                    setSubmitting(true)\n                                    await props.onDelete()\n                                    setSubmitting(false)\n                                    props.onClose()\n                                } catch (err) {\n                                    setSubmitting(false)\n                                    Utils.logError(`Delete board ERROR: ${err}`)\n\n                                    // TODO: display error on screen\n                                }\n                            }}\n                        >\n                            <FormattedMessage\n                                id='DeleteBoardDialog.confirm-delete'\n                                defaultMessage='Delete'\n                            />\n                        </Button>\n                    </div>\n                </div>\n            </Dialog>\n        </RootPortal>\n    )\n}\n"
  },
  {
    "path": "webapp/src/components/sidebar/registrationLink.scss",
    "content": ".RegistrationLink {\n    display: flex;\n    flex-direction: column;\n    padding: 5px;\n    color: rgb(var(--center-channel-color-rgb));\n    font-weight: normal;\n    line-height: normal;\n\n    > .row {\n        display: flex;\n        flex-direction: row;\n        align-items: center;\n        margin: 0 0 10px;\n    }\n\n    > .row:last-child {\n        margin-bottom: 0;\n    }\n\n    .spacer {\n        flex-grow: 1;\n    }\n\n    a.shareUrl {\n        max-width: 320px;\n        flex-grow: 1;\n        white-space: nowrap;\n        overflow: hidden;\n        text-overflow: ellipsis;\n        margin-right: 5px;\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/sidebar/registrationLink.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport configureStore from 'redux-mock-store'\n\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport {render} from '@testing-library/react'\n\nimport {wrapIntl} from '../../testUtils'\n\nimport RegistrationLink from './registrationLink'\n\ndescribe('components/sidebar/RegistrationLink', () => {\n    const mockStore = configureStore([])\n    const state = {\n        teams: {\n            current: {\n                id: 'team-id',\n                signupToken: 'abc123',\n            },\n        },\n    }\n\n    test('renders with signupToken in URL query param', () => {\n        const store = mockStore(state)\n\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <RegistrationLink\n                    onClose={() => {}}\n                />\n            </ReduxProvider>,\n        )\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n\n        const anchor = container.querySelector('.shareUrl')\n        const url = new URL(anchor?.getAttribute('href') as string)\n        expect(url.searchParams.get('t')).toStrictEqual(state.teams.current.signupToken)\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/sidebar/registrationLink.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useEffect, useState} from 'react'\nimport {useIntl} from 'react-intl'\n\nimport {sendFlashMessage} from '../flashMessages'\nimport {Utils} from '../../utils'\nimport Button from '../../widgets/buttons/button'\nimport {useAppDispatch, useAppSelector} from '../../store/hooks'\nimport {getCurrentTeam, Team, refreshCurrentTeam, regenerateSignupToken} from '../../store/teams'\n\nimport Modal from '../modal'\n\nimport './registrationLink.scss'\n\ntype Props = {\n    onClose: () => void\n}\n\nconst RegistrationLink = (props: Props) => {\n    const {onClose} = props\n    const intl = useIntl()\n\n    const team = useAppSelector<Team|null>(getCurrentTeam)\n    const signupToken = team?.signupToken\n    const dispatch = useAppDispatch()\n\n    const [wasCopied, setWasCopied] = useState(false)\n\n    useEffect(() => {\n        /* dispatch(fetchWorkspace()) */\n    }, [])\n\n    const regenerateToken = async () => {\n        // eslint-disable-next-line no-alert\n        const accept = window.confirm(intl.formatMessage({id: 'RegistrationLink.confirmRegenerateToken', defaultMessage: 'This will invalidate previously shared links. Continue?'}))\n        if (accept) {\n            await dispatch(regenerateSignupToken())\n            await dispatch(refreshCurrentTeam())\n            setWasCopied(false)\n\n            const description = intl.formatMessage({id: 'RegistrationLink.tokenRegenerated', defaultMessage: 'Registration link regenerated'})\n            sendFlashMessage({content: description, severity: 'low'})\n        }\n    }\n\n    const registrationUrl = `${Utils.getBaseURL(true).replace(/\\/$/, '')}/register?t=${signupToken}`\n\n    return (\n        <Modal\n            position='bottom-right'\n            onClose={onClose}\n        >\n            <div className='RegistrationLink'>\n                {signupToken && <>\n                    <div className='row'>\n                        {intl.formatMessage({id: 'RegistrationLink.description', defaultMessage: 'Share this link for others to create accounts:'})}\n                    </div>\n                    <div className='row'>\n                        <a\n                            className='shareUrl'\n                            href={registrationUrl}\n                            target='_blank'\n                            rel='noreferrer'\n                        >\n                            {registrationUrl}\n                        </a>\n                        <Button\n                            filled={true}\n                            size='small'\n                            onClick={() => {\n                                Utils.copyTextToClipboard(registrationUrl)\n                                setWasCopied(true)\n                            }}\n                        >\n                            {wasCopied ? intl.formatMessage({id: 'RegistrationLink.copiedLink', defaultMessage: 'Copied!'}) : intl.formatMessage({id: 'RegistrationLink.copyLink', defaultMessage: 'Copy link'})}\n                        </Button>\n                    </div>\n                    <div className='row'>\n                        <Button\n                            onClick={regenerateToken}\n                            emphasis='secondary'\n                            size='small'\n                        >\n                            {intl.formatMessage({id: 'RegistrationLink.regenerateToken', defaultMessage: 'Regenerate token'})}\n                        </Button>\n                    </div>\n                </>}\n            </div>\n        </Modal>\n    )\n}\n\nexport default React.memo(RegistrationLink)\n"
  },
  {
    "path": "webapp/src/components/sidebar/sidebar.scss",
    "content": "@import '../../styles/z-index';\n\n.Sidebar {\n    flex: 0 0 240px;\n    max-width: 240px;\n    display: flex;\n    flex-direction: column;\n\n    min-height: 100%;\n    color: rgb(var(--sidebar-text-rgb));\n    background-color: rgb(var(--sidebar-bg-rgb));\n    padding: 24px 0 12px;\n\n    &.hidden {\n        @include z-index(sidebar-hidden);\n        position: absolute;\n        top: 0;\n        left: 0;\n        min-height: 0;\n        height: 50px;\n        width: 50px;\n        background: none;\n        padding: 0;\n        padding-left: 10px;\n        display: block !important;\n\n        > div {\n            padding: 0;\n            margin: auto;\n        }\n    }\n\n    > * {\n        flex: 0 0 auto;\n    }\n\n    &__onboarding {\n        text-align: center;\n        padding: 16px 32px;\n\n        div {\n            margin-top: 32px;\n            font-weight: 600;\n            opacity: 0.6;\n        }\n    }\n\n    .IconButton {\n        padding: 0;\n\n        .Icon {\n            width: 24px;\n            height: 24px;\n            margin: 0;\n        }\n    }\n\n    .WorkspaceTitle {\n        display: flex;\n        flex-direction: row;\n        padding: 0 16px;\n        font-weight: 600;\n    }\n\n    .octo-sidebar-list {\n        flex: 1 1 auto;\n        overflow-y: auto;\n        max-width: 100%;\n        margin-top: 12px;\n\n        &::-webkit-scrollbar-thumb {\n            background: rgba(var(--sidebar-text-rgb), 0.24);\n        }\n\n        > div:last-child {\n            margin-bottom: 16px;\n        }\n    }\n\n    .octo-sidebar-header {\n        display: flex;\n        flex-direction: row;\n        font-weight: 600;\n        padding: 3px 16px;\n        margin-bottom: 16px;\n        height: 48px;\n        align-items: center;\n\n        &:hover {\n            background-color: rgba(var(--sidebar-text-rgb), 0.08);\n        }\n\n        > .heading {\n            line-height: 30px;\n            cursor: default;\n            flex-grow: 1;\n        }\n\n        > .IconButton {\n            background-color: var(--sidebar-bg-rgb);\n\n            &:hover {\n                background-color: rgba(var(--sidebar-text-rgb), 0.1);\n            }\n        }\n\n        &.show-button {\n            .hamburger-icon {\n                display: block;\n            }\n\n            .show-icon {\n                display: none;\n            }\n\n            &:hover {\n                .hamburger-icon {\n                    display: none;\n                }\n\n                .show-icon {\n                    display: block;\n                }\n            }\n        }\n    }\n\n    .IconButton {\n        color: rgba(var(--sidebar-text-rgb), 0.56);\n\n        &:hover {\n            color: rgb(var(--sidebar-text-rgb));\n        }\n    }\n\n    .DisclosureTriangleIcon,\n    .DotIcon {\n        fill: rgba(var(--sidebar-text-rgb), 0.56);\n        flex-shrink: 0;\n    }\n\n    .Menu .OptionsIcon {\n        color: unset;\n    }\n\n    .HideSidebarIcon {\n        stroke: rgba(var(--sidebar-text-rgb), 0.5);\n        stroke-width: 6px;\n    }\n\n    .add-board {\n        display: flex;\n        padding: 0 16px 0 20px;\n        cursor: pointer;\n        color: rgba(var(--sidebar-text-rgb), 0.64);\n        height: 36px;\n        align-items: center;\n\n        &:hover {\n            background-color: rgba(var(--sidebar-text-rgb), 0.08);\n        }\n    }\n\n    @media screen and (min-width: 768px) {\n        .WorkspaceTitle .sidebarSwitcher {\n            display: none;\n        }\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/sidebar/sidebar.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\nimport configureStore from 'redux-mock-store'\n\nimport {createMemoryHistory} from 'history'\nimport {Provider as ReduxProvider} from 'react-redux'\nimport {Router} from 'react-router-dom'\n\nimport {render} from '@testing-library/react'\nimport userEvent from '@testing-library/user-event'\n\nimport thunk from 'redux-thunk'\n\nimport {mocked} from 'jest-mock'\n\nimport {mockMatchMedia, wrapIntl} from '../../testUtils'\n\nimport {TestBlockFactory} from '../../test/testBlockFactory'\nimport octoClient from '../../../../webapp/src/octoClient'\n\nimport Sidebar from './sidebar'\n\njest.mock('../../../../webapp/src/octoClient')\nconst mockedOctoClient = mocked(octoClient, true)\n\nbeforeAll(() => {\n    mockMatchMedia({matches: true})\n})\n\ndescribe('components/sidebarSidebar', () => {\n    beforeEach(() => {\n        jest.clearAllMocks()\n    })\n\n    const mockStore = configureStore([thunk])\n\n    const board = TestBlockFactory.createBoard()\n    board.id = 'board1'\n\n    const categoryAttribute1 = TestBlockFactory.createCategoryBoards()\n    categoryAttribute1.id = 'category1'\n    categoryAttribute1.name = 'Category 1'\n    categoryAttribute1.boardMetadata = [{boardID: board.id, hidden: false}]\n\n    const defaultCategory = TestBlockFactory.createCategoryBoards()\n    defaultCategory.id = 'default_category'\n    defaultCategory.name = 'Boards'\n    defaultCategory.boardMetadata = []\n\n    test('sidebar hidden', () => {\n        const store = mockStore({\n            teams: {\n                current: {id: 'team-id'},\n            },\n            boards: {\n                current: board.id,\n                boards: {\n                    [board.id]: board,\n                },\n                myBoardMemberships: {\n                    [board.id]: board,\n                },\n            },\n            cards: {\n                cards: {\n                    card_id_1: {title: 'Card'},\n                },\n                current: 'card_id_1',\n            },\n            views: {\n                views: [],\n            },\n            users: {\n                me: {\n                    id: 'user_id_1',\n                    props: {},\n                },\n            },\n            sidebar: {\n                categoryAttributes: [\n                    categoryAttribute1,\n                ],\n                hiddenBoardIDs: [],\n            },\n        })\n\n        const history = createMemoryHistory()\n        const onBoardTemplateSelectorOpen = jest.fn()\n\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <Router history={history}>\n                    <Sidebar onBoardTemplateSelectorOpen={onBoardTemplateSelectorOpen}/>\n                </Router>\n            </ReduxProvider>,\n        )\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n\n        const hideSidebar = container.querySelector('button > .HideSidebarIcon')\n        expect(hideSidebar).toBeDefined()\n\n        userEvent.click(hideSidebar as Element)\n        expect(container).toMatchSnapshot()\n\n        const showSidebar = container.querySelector('button > .ShowSidebarIcon')\n        expect(showSidebar).toBeDefined()\n    })\n\n    test('sidebar expect hidden', () => {\n        const customGlobal = global as any\n\n        customGlobal.innerWidth = 500\n\n        const localCategoryAttribute = TestBlockFactory.createCategoryBoards()\n        localCategoryAttribute.id = 'category1'\n        localCategoryAttribute.name = 'Category 1'\n        categoryAttribute1.boardMetadata = [{boardID: board.id, hidden: false}]\n\n        const store = mockStore({\n            teams: {\n                current: {id: 'team-id'},\n            },\n            boards: {\n                current: board.id,\n                boards: {\n                    [board.id]: board,\n                },\n                myBoardMemberships: {\n                    [board.id]: board,\n                },\n            },\n            cards: {\n                cards: {\n                    card_id_1: {title: 'Card'},\n                },\n                current: 'card_id_1',\n            },\n            views: {\n                views: [],\n            },\n            users: {\n                me: {\n                    id: 'user_id_1',\n                    props: {},\n                },\n            },\n            sidebar: {\n                categoryAttributes: [\n                    categoryAttribute1,\n                ],\n                hiddenBoardIDs: [],\n            },\n        })\n\n        const history = createMemoryHistory()\n        const onBoardTemplateSelectorOpen = jest.fn()\n\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <Router history={history}>\n                    <Sidebar onBoardTemplateSelectorOpen={onBoardTemplateSelectorOpen}/>\n                </Router>\n            </ReduxProvider>,\n        )\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n\n        const hideSidebar = container.querySelector('button > .HideSidebarIcon')\n        expect(hideSidebar).toBeNull()\n\n        const showSidebar = container.querySelector('button > .ShowSidebarIcon')\n        expect(showSidebar).toBeDefined()\n\n        customGlobal.innerWidth = 1024\n    })\n\n    test('dont show hidden boards', () => {\n        const localCategoryAttribute = TestBlockFactory.createCategoryBoards()\n        localCategoryAttribute.id = 'category1'\n        localCategoryAttribute.name = 'Category 1'\n        localCategoryAttribute.boardMetadata = [{boardID: board.id, hidden: true}]\n\n        const store = mockStore({\n            teams: {\n                current: {id: 'team-id'},\n            },\n            boards: {\n                current: board.id,\n                boards: {\n                    [board.id]: board,\n                },\n                myBoardMemberships: {\n                    [board.id]: board,\n                },\n            },\n            cards: {\n                cards: {\n                    card_id_1: {title: 'Card'},\n                },\n                current: 'card_id_1',\n            },\n            views: {\n                views: [],\n            },\n            users: {\n                me: {\n                    id: 'user_id_1',\n                },\n                myConfig: {\n                    hiddenBoardIDs: {value: {\n                        [board.id]: true,\n                    }},\n                },\n            },\n            sidebar: {\n                categoryAttributes: [\n                    localCategoryAttribute,\n                ],\n                hiddenBoardIDs: [board.id],\n            },\n        })\n\n        const history = createMemoryHistory()\n        const onBoardTemplateSelectorOpen = jest.fn()\n\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <Router history={history}>\n                    <Sidebar onBoardTemplateSelectorOpen={onBoardTemplateSelectorOpen}/>\n                </Router>\n            </ReduxProvider>,\n        )\n        const {container, getAllByText} = render(component)\n        expect(container).toMatchSnapshot()\n\n        const sidebarBoards = container.getElementsByClassName('SidebarBoardItem')\n\n        // The only board in redux store is hidden, so there should\n        // be no boards visible in sidebar\n        expect(sidebarBoards.length).toBe(0)\n\n        const noBoardsText = getAllByText('No boards inside')\n        expect(noBoardsText.length).toBe(1)\n    })\n\n    test('some categories hidden', () => {\n        const collapsedCategory = TestBlockFactory.createCategoryBoards()\n        collapsedCategory.id = 'categoryCollapsed'\n        collapsedCategory.name = 'Category 2'\n        collapsedCategory.collapsed = true\n        collapsedCategory.boardMetadata = []\n\n        const store = mockStore({\n            teams: {\n                current: {id: 'team-id'},\n            },\n            boards: {\n                current: board.id,\n                boards: {\n                    [board.id]: board,\n                },\n                myBoardMemberships: {\n                    [board.id]: board,\n                },\n            },\n            cards: {\n                cards: {\n                    card_id_1: {title: 'Card'},\n                },\n                current: 'card_id_1',\n            },\n            views: {\n                views: [],\n            },\n            users: {\n                me: {\n                    id: 'user_id_1',\n                    props: {},\n                },\n            },\n            sidebar: {\n                categoryAttributes: [\n                    categoryAttribute1,\n                    collapsedCategory,\n                ],\n                hiddenBoardIDs: [],\n            },\n        })\n\n        const history = createMemoryHistory()\n        const onBoardTemplateSelectorOpen = jest.fn()\n\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <Router history={history}>\n                    <Sidebar onBoardTemplateSelectorOpen={onBoardTemplateSelectorOpen}/>\n                </Router>\n            </ReduxProvider>,\n        )\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n\n        const sidebarCollapsedCategory = container.querySelectorAll('.octo-sidebar-item.category.collapsed')\n        expect(sidebarCollapsedCategory.length).toBe(1)\n    })\n\n    test('should assign default category if current board doesnt have a category', () => {\n        const board2 = TestBlockFactory.createBoard()\n        board2.id = 'board2'\n\n        const store = mockStore({\n            teams: {\n                current: {id: 'team-id'},\n            },\n            boards: {\n                current: board2.id,\n                boards: {\n                    [board2.id]: board2,\n                },\n                myBoardMemberships: {\n                    [board2.id]: board2,\n                },\n            },\n            cards: {\n                cards: {\n                    card_id_1: {title: 'Card'},\n                },\n                current: 'card_id_1',\n            },\n            views: {\n                views: [],\n            },\n            users: {\n                me: {\n                    id: 'user_id_1',\n                    props: {},\n                },\n            },\n            sidebar: {\n                categoryAttributes: [\n                    categoryAttribute1,\n                    defaultCategory,\n                ],\n                hiddenBoardIDs: [],\n            },\n        })\n\n        const history = createMemoryHistory()\n        const onBoardTemplateSelectorOpen = jest.fn()\n\n        mockedOctoClient.moveBoardToCategory.mockResolvedValueOnce({} as Response)\n\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <Router history={history}>\n                    <Sidebar onBoardTemplateSelectorOpen={onBoardTemplateSelectorOpen}/>\n                </Router>\n            </ReduxProvider>,\n        )\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n\n        expect(mockedOctoClient.moveBoardToCategory).toBeCalledWith('team-id', 'board2', 'default_category', '')\n    })\n\n    test('shouldnt do any category assignment is board is in a category', () => {\n        const board2 = TestBlockFactory.createBoard()\n        board2.id = 'board2'\n\n        const categoryAttribute2 = TestBlockFactory.createCategoryBoards()\n        categoryAttribute2.id = 'category2'\n        categoryAttribute2.name = 'Category 2'\n        categoryAttribute2.boardMetadata = [{boardID: board2.id, hidden: false}]\n\n        const store = mockStore({\n            teams: {\n                current: {id: 'team-id'},\n            },\n            boards: {\n                current: board2.id,\n                boards: {\n                    [board2.id]: board2,\n                },\n                myBoardMemberships: {\n                    [board2.id]: board2,\n                },\n            },\n            cards: {\n                cards: {\n                    card_id_1: {title: 'Card'},\n                },\n                current: 'card_id_1',\n            },\n            views: {\n                views: [],\n            },\n            users: {\n                me: {\n                    id: 'user_id_1',\n                    props: {},\n                },\n            },\n            sidebar: {\n                categoryAttributes: [\n                    categoryAttribute1,\n                    categoryAttribute2,\n                    defaultCategory,\n                ],\n            },\n        })\n\n        const history = createMemoryHistory()\n        const onBoardTemplateSelectorOpen = jest.fn()\n\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <Router history={history}>\n                    <Sidebar onBoardTemplateSelectorOpen={onBoardTemplateSelectorOpen}/>\n                </Router>\n            </ReduxProvider>,\n        )\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n\n        expect(mockedOctoClient.moveBoardToCategory).toBeCalledTimes(0)\n    })\n\n    // TODO: Fix this later\n    // test('global templates', () => {\n    //     const store = mockStore({\n    //         teams: {\n    //             current: {id: 'team-id'},\n    //         },\n    //         boards: {\n    //             boards: [],\n    //             templates: [\n    //                 {id: '1', title: 'Template 1', fields: {icon: '🚴🏻‍♂️'}},\n    //                 {id: '2', title: 'Template 2', fields: {icon: '🚴🏻‍♂️'}},\n    //                 {id: '3', title: 'Template 3', fields: {icon: '🚴🏻‍♂️'}},\n    //                 {id: '4', title: 'Template 4', fields: {icon: '🚴🏻‍♂️'}},\n    //             ],\n    //         },\n    //         views: {\n    //             views: [],\n    //         },\n    //         users: {\n    //             me: {},\n    //         },\n    //         globalTemplates: {\n    //             value: [],\n    //         },\n    //         sidebar: {\n    //             categoryAttributes: [\n    //                 categoryAttribute1,\n    //             ],\n    //         },\n    //     })\n\n    //     const history = createMemoryHistory()\n\n    //     const component = wrapIntl(\n    //         <ReduxProvider store={store}>\n    //             <Router history={history}>\n    //                 <Sidebar onBoardTemplateSelectorOpen={onBoardTemplateSelectorOpen}/>\n    //             </Router>\n    //         </ReduxProvider>,\n    //     )\n    //     const {container} = render(component)\n    //     expect(container).toMatchSnapshot()\n\n    //     const addBoardButton = container.querySelector('.SidebarAddBoardMenu > .MenuWrapper')\n    //     expect(addBoardButton).toBeDefined()\n    //     userEvent.click(addBoardButton as Element)\n    //     const templates = container.querySelectorAll('.SidebarAddBoardMenu > .MenuWrapper div:not(.hideOnWidescreen).menu-options .menu-name')\n    //     expect(templates).toBeDefined()\n\n    //     console.log(templates[0].innerHTML)\n    //     console.log(templates[1].innerHTML)\n\n    //     // 4 mocked templates, one \"Select a template\", one \"Empty Board\" and one \"+ New Template\"\n    //     expect(templates.length).toBe(7)\n    // })\n})\n"
  },
  {
    "path": "webapp/src/components/sidebar/sidebar.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useCallback, useEffect, useState} from 'react'\nimport {FormattedMessage} from 'react-intl'\nimport {DragDropContext, Droppable, DropResult} from 'react-beautiful-dnd'\n\nimport {getActiveThemeName, loadTheme} from '../../theme'\nimport IconButton from '../../widgets/buttons/iconButton'\nimport HamburgerIcon from '../../widgets/icons/hamburger'\nimport HideSidebarIcon from '../../widgets/icons/hideSidebar'\nimport ShowSidebarIcon from '../../widgets/icons/showSidebar'\nimport {getCurrentBoard, getMySortedBoards} from '../../store/boards'\nimport {useAppDispatch, useAppSelector} from '../../store/hooks'\nimport {Utils} from '../../utils'\nimport {IUser} from '../../user'\n\nimport './sidebar.scss'\n\nimport {\n    BoardCategoryWebsocketData,\n    Category,\n    CategoryBoards,\n    fetchSidebarCategories,\n    getSidebarCategories,\n    updateBoardCategories,\n    updateCategories,\n    updateCategoryBoardsOrder,\n    updateCategoryOrder,\n} from '../../store/sidebar'\n\nimport BoardsSwitcher from '../boardsSwitcher/boardsSwitcher'\n\nimport wsClient, {WSClient} from '../../wsclient'\n\nimport {getCurrentTeam, getCurrentTeamId} from '../../store/teams'\n\nimport {Constants} from '../../constants'\n\nimport {getMe} from '../../store/users'\nimport {getCurrentViewId} from '../../store/views'\n\nimport octoClient from '../../octoClient'\n\nimport {useWebsockets} from '../../hooks/websockets'\n\nimport mutator from '../../mutator'\n\nimport {Board} from '../../blocks/board'\n\nimport SidebarCategory from './sidebarCategory'\nimport SidebarSettingsMenu from './sidebarSettingsMenu'\nimport SidebarUserMenu from './sidebarUserMenu'\n\ntype Props = {\n    activeBoardId?: string\n    onBoardTemplateSelectorOpen: () => void\n    onBoardTemplateSelectorClose?: () => void\n}\n\nfunction getWindowDimensions() {\n    const {innerWidth: width, innerHeight: height} = window\n    return {\n        width,\n        height,\n    }\n}\n\nconst Sidebar = (props: Props) => {\n    const [isHidden, setHidden] = useState(false)\n    const [userHidden, setUserHidden] = useState(false)\n    const [windowDimensions, setWindowDimensions] = useState(getWindowDimensions())\n    const boards = useAppSelector(getMySortedBoards)\n    const dispatch = useAppDispatch()\n    const sidebarCategories = useAppSelector<CategoryBoards[]>(getSidebarCategories)\n    const me = useAppSelector<IUser|null>(getMe)\n    const activeViewID = useAppSelector(getCurrentViewId)\n    const currentBoard = useAppSelector(getCurrentBoard)\n\n    useEffect(() => {\n        const categoryOnChangeHandler = (_: WSClient, categories: Category[]) => {\n            dispatch(updateCategories(categories))\n        }\n\n        const blockCategoryOnChangeHandler = (_: WSClient, blockCategories: BoardCategoryWebsocketData[]) => {\n            dispatch(updateBoardCategories(blockCategories))\n        }\n\n        wsClient.addOnChange(categoryOnChangeHandler, 'category')\n        wsClient.addOnChange(blockCategoryOnChangeHandler, 'blockCategories')\n\n        return function cleanup() {\n            wsClient.removeOnChange(categoryOnChangeHandler, 'category')\n            wsClient.removeOnChange(blockCategoryOnChangeHandler, 'blockCategories')\n        }\n    }, [])\n\n    const teamId = useAppSelector(getCurrentTeamId)\n    const team = useAppSelector(getCurrentTeam)\n\n    useEffect(() => {\n        if (team) {\n            dispatch(fetchSidebarCategories(team!.id))\n        }\n    }, [team?.id])\n\n    useEffect(() => {\n        loadTheme()\n    }, [])\n\n    useEffect(() => {\n        function handleResize() {\n            setWindowDimensions(getWindowDimensions())\n        }\n\n        window.addEventListener('resize', handleResize)\n        return () => window.removeEventListener('resize', handleResize)\n    }, [])\n\n    useEffect(() => {\n        hideSidebar()\n    }, [windowDimensions])\n\n    // This handles the case when a user opens a linked board from Channels RHS\n    // and thats the first time that user is opening that board.\n    // Here we check if that board has a associated category for the user. If not,\n    // we assign it to the default \"Boards\" category.\n    // We do this on the client side rather than the server side like for all other cases\n    // because there is no good, explicit API call to add this logic to when opening\n    // a board that you have implicit access to.\n    useEffect(() => {\n        if (!sidebarCategories || sidebarCategories.length === 0 || !currentBoard || !team || currentBoard.isTemplate) {\n            return\n        }\n\n        // find the category the current board belongs to\n        // const category = sidebarCategories.find((c) => c.boardIDs.indexOf(currentBoard.id) >= 0)\n        const category = sidebarCategories.find((c) => c.boardMetadata.find((boardMetadata) => boardMetadata.boardID === currentBoard.id))\n        if (category) {\n            // Boards does belong to a category.\n            // All good here. Nothing to do\n            return\n        }\n\n        // if the board doesn't belong to a category\n        // we need to move it to the default \"Boards\" category\n        const boardsCategory = sidebarCategories.find((c) => c.name === 'Boards')\n        if (!boardsCategory) {\n            Utils.logError('Boards category not found for user')\n            return\n        }\n\n        octoClient.moveBoardToCategory(team.id, currentBoard.id, boardsCategory.id, '')\n    }, [sidebarCategories, currentBoard, team])\n\n    useWebsockets(teamId, (websocketClient: WSClient) => {\n        const onCategoryReorderHandler = (_: WSClient, newCategoryOrder: string[]): void => {\n            dispatch(updateCategoryOrder(newCategoryOrder))\n        }\n\n        websocketClient.addOnChange(onCategoryReorderHandler, 'categoryOrder')\n        return () => {\n            websocketClient.removeOnChange(onCategoryReorderHandler, 'categoryOrder')\n        }\n    }, [teamId])\n\n    if (!boards) {\n        return <div/>\n    }\n\n    const hideSidebar = () => {\n        if (!userHidden) {\n            if (windowDimensions.width < 768) {\n                setHidden(true)\n            } else {\n                setHidden(false)\n            }\n        }\n    }\n\n    const handleCategoryDND = useCallback(async (result: DropResult) => {\n        const {destination, source} = result\n        if (!team || !destination) {\n            return\n        }\n\n        const categories = sidebarCategories\n\n        // creating a mutable copy\n        const newCategories = Array.from(categories)\n\n        // remove category from old index\n        newCategories.splice(source.index, 1)\n\n        // add it to new index\n        newCategories.splice(destination.index, 0, categories[source.index])\n\n        const newCategoryOrder = newCategories.map((category) => category.id)\n\n        // optimistically updating the store to produce a lag-free UI\n        await dispatch(updateCategoryOrder(newCategoryOrder))\n        await octoClient.reorderSidebarCategories(team.id, newCategoryOrder)\n    }, [team, sidebarCategories])\n\n    const handleCategoryBoardDND = useCallback(async (result: DropResult) => {\n        const {source, destination, draggableId} = result\n\n        if (!team || !destination) {\n            return\n        }\n\n        const fromCategoryID = source.droppableId\n        const toCategoryID = destination.droppableId\n        const boardID = draggableId\n\n        if (fromCategoryID === toCategoryID) {\n            // board re-arranged withing the same category\n            const toSidebarCategory = sidebarCategories.find((category) => category.id === toCategoryID)\n            if (!toSidebarCategory) {\n                Utils.logError(`toCategoryID not found in list of sidebar categories. toCategoryID: ${toCategoryID}`)\n                return\n            }\n\n            const categoryBoardMetadata = [...toSidebarCategory.boardMetadata]\n            categoryBoardMetadata.splice(source.index, 1)\n            categoryBoardMetadata.splice(destination.index, 0, toSidebarCategory.boardMetadata[source.index])\n\n            dispatch(updateCategoryBoardsOrder({categoryID: toCategoryID, boardsMetadata: categoryBoardMetadata}))\n\n            const reorderedBoardIDs = categoryBoardMetadata.map((m) => m.boardID)\n            await octoClient.reorderSidebarCategoryBoards(team.id, toCategoryID, reorderedBoardIDs)\n        } else {\n            // board moved to a different category\n            const toSidebarCategory = sidebarCategories.find((category) => category.id === toCategoryID)\n            const fromSidebarCategory = sidebarCategories.find((category) => category.id === fromCategoryID)\n\n            if (!toSidebarCategory) {\n                Utils.logError(`toCategoryID not found in list of sidebar categories. toCategoryID: ${toCategoryID}`)\n                return\n            }\n\n            if (!fromSidebarCategory) {\n                Utils.logError(`fromCategoryID not found in list of sidebar categories. fromCategoryID: ${fromCategoryID}`)\n                return\n            }\n\n            const categoryBoardMetadata = [...toSidebarCategory.boardMetadata]\n            const fromCategoryBoardMetadata = fromSidebarCategory.boardMetadata[source.index]\n            categoryBoardMetadata.splice(destination.index, 0, fromCategoryBoardMetadata)\n\n            // optimistically updating the store to create a lag-free UI.\n            await dispatch(updateCategoryBoardsOrder({categoryID: toCategoryID, boardsMetadata: categoryBoardMetadata}))\n            dispatch(updateBoardCategories([{...fromCategoryBoardMetadata, categoryID: toCategoryID}]))\n\n            await mutator.moveBoardToCategory(team.id, boardID, toCategoryID, fromCategoryID)\n\n            const reorderedBoardIDs = categoryBoardMetadata.map((m) => m.boardID)\n            await octoClient.reorderSidebarCategoryBoards(team.id, toCategoryID, reorderedBoardIDs)\n        }\n    }, [team, sidebarCategories])\n\n    const onDragEnd = useCallback(async (result: DropResult) => {\n        const {destination, source, type} = result\n\n        if (!team || !destination) {\n            setDraggedItemID('')\n            setIsCategoryBeingDragged(false)\n            return\n        }\n\n        if (destination.droppableId === source.droppableId && destination.index === source.index) {\n            setDraggedItemID('')\n            setIsCategoryBeingDragged(false)\n            return\n        }\n\n        if (type === 'category') {\n            handleCategoryDND(result)\n        } else if (type === 'board') {\n            handleCategoryBoardDND(result)\n        } else {\n            Utils.logWarn(`unknown drag type encountered, type: ${type}`)\n        }\n\n        setDraggedItemID('')\n        setIsCategoryBeingDragged(false)\n    }, [team, sidebarCategories])\n\n    const [draggedItemID, setDraggedItemID] = useState<string>('')\n    const [isCategoryBeingDragged, setIsCategoryBeingDragged] = useState<boolean>(false)\n\n    if (!boards) {\n        return <div/>\n    }\n\n    if (!me) {\n        return <div/>\n    }\n\n    if (isHidden) {\n        return (\n            <div className='Sidebar octo-sidebar hidden'>\n                <div className='octo-sidebar-header show-button'>\n                    <div className='hamburger-icon'>\n                        <IconButton\n                            icon={<HamburgerIcon/>}\n                            onClick={() => {\n                                setUserHidden(false)\n                                setHidden(false)\n                            }}\n                        />\n                    </div>\n                    <div className='show-icon'>\n                        <IconButton\n                            icon={<ShowSidebarIcon/>}\n                            onClick={() => {\n                                setUserHidden(false)\n                                setHidden(false)\n                            }}\n                        />\n                    </div>\n                </div>\n            </div>\n        )\n    }\n\n    const getSortedCategoryBoards = (category: CategoryBoards): Board[] => {\n        const categoryBoardsByID = new Map<string, Board>()\n        boards.forEach((board) => {\n            if (!category.boardMetadata.find((m) => m.boardID === board.id)) {\n                return\n            }\n\n            categoryBoardsByID.set(board.id, board)\n        })\n\n        const sortedBoards: Board[] = []\n        category.boardMetadata.forEach((boardMetadata) => {\n            const b = categoryBoardsByID.get(boardMetadata.boardID)\n            if (b) {\n                sortedBoards.push(b)\n            }\n        })\n        return sortedBoards\n    }\n\n    return (\n        <div className='Sidebar octo-sidebar'>\n            <div className='octo-sidebar-header'>\n                <div className='heading'>\n                    <SidebarUserMenu/>\n                </div>\n\n                <div className='octo-spacer'/>\n                <div className='sidebarSwitcher'>\n                    <IconButton\n                        onClick={() => {\n                            setUserHidden(true)\n                            setHidden(true)\n                        }}\n                        icon={<HideSidebarIcon/>}\n                    />\n                </div>\n            </div>\n\n            {team && team.id !== Constants.globalTeamId &&\n                <div className='WorkspaceTitle'/>\n            }\n\n            <BoardsSwitcher/>\n\n            <DragDropContext\n                onDragEnd={onDragEnd}\n            >\n                <Droppable\n                    droppableId='lhs-categories'\n                    type='category'\n                    key={sidebarCategories.length}\n                >\n                    {(provided) => (\n                        <div\n                            ref={provided.innerRef}\n                            {...provided.droppableProps}\n                            className='octo-sidebar-list'\n                        >\n                            {\n                                sidebarCategories.map((category, index) => (\n                                    <SidebarCategory\n                                        hideSidebar={hideSidebar}\n                                        key={category.id}\n                                        activeBoardID={props.activeBoardId}\n                                        activeViewID={activeViewID}\n                                        categoryBoards={category}\n                                        boards={getSortedCategoryBoards(category)}\n                                        allCategories={sidebarCategories}\n                                        index={index}\n                                        onBoardTemplateSelectorClose={props.onBoardTemplateSelectorClose}\n                                        draggedItemID={draggedItemID}\n                                        forceCollapse={isCategoryBeingDragged}\n                                    />\n                                ))\n                            }\n                            {provided.placeholder}\n                        </div>\n                    )}\n                </Droppable>\n            </DragDropContext>\n\n            <div className='octo-spacer'/>\n\n            <div\n                className='add-board'\n                onClick={props.onBoardTemplateSelectorOpen}\n            >\n                <FormattedMessage\n                    id='Sidebar.add-board'\n                    defaultMessage='+ Add board'\n                />\n            </div>\n\n            <SidebarSettingsMenu activeTheme={getActiveThemeName()}/>\n        </div>\n    )\n}\n\nexport default React.memo(Sidebar)\n"
  },
  {
    "path": "webapp/src/components/sidebar/sidebarBoardItem.scss",
    "content": ".SidebarBoardItem {\n    display: flex;\n    flex-direction: row;\n    font-weight: 500;\n    padding: 0 12px 0 20px;\n    height: 36px;\n    align-items: center;\n    gap: 8px;\n\n    &.category {\n        padding-left: 0;\n    }\n\n    &:hover {\n        background-color: rgba(var(--sidebar-text-rgb), 0.08);\n        display: flex;\n    }\n\n    .subitem {\n        padding-left: 20px;\n        color: rgba(var(--sidebar-text-rgb), 0.64);\n        font-weight: 400;\n    }\n\n    &.sidebar-view-item {\n        padding-left: 42px;\n        color: rgba(var(--sidebar-text-rgb), 0.64);\n        font-weight: 400;\n    }\n\n    &.active {\n        background: rgba(var(--sidebar-text-rgb), 0.1);\n        position: relative;\n        color: rgb(var(--sidebar-text-rgb));\n\n        .octo-sidebar-title {\n            color: inherit;\n        }\n\n        &:not(.subitem) {\n            &::before {\n                content: '';\n                background: rgb(var(--sidebar-text-active-border-rgb));\n                width: 2px;\n                height: 100%;\n                position: absolute;\n                left: 0;\n                top: 0;\n            }\n        }\n    }\n\n    &.no-views {\n        color: rgba(var(--sidebar-text-rgb), 0.4);\n\n        &:hover {\n            background-color: rgba(var(--sidebar-bg-rgb));\n        }\n    }\n\n    &:not(.no-views) {\n        cursor: pointer;\n    }\n\n    > .Icon {\n        height: 18px;\n        width: 18px;\n        flex-shrink: 0;\n\n        &.GalleryIcon {\n            fill: rgba(var(--sidebar-text-rgb), 0.3);\n            stroke: unset;\n        }\n    }\n\n    > .IconButton {\n        background-color: var(--sidebar-bg-rgb);\n        color: rgb(var(--sidebar-text-rgb));\n        margin-left: 4px;\n\n        &:hover {\n            background-color: rgba(var(--sidebar-text-rgb), 0.1);\n        }\n    }\n\n    .Menu.left {\n        right: -11px;\n    }\n\n    &.expanded {\n        .DisclosureTriangleIcon {\n            transform: rotate(90deg);\n        }\n    }\n\n    .DisclosureTriangleIcon {\n        transition: 200ms ease-in-out;\n        transform: rotate(0deg);\n    }\n\n    &.category {\n        .MenuWrapper {\n            display: none;\n\n            &.menuOpen {\n                display: block;\n            }\n        }\n\n        &:hover {\n            .MenuWrapper {\n                display: block;\n            }\n        }\n    }\n\n    &.subitem {\n        .MenuWrapper {\n            display: none;\n\n            &.menuOpen {\n                display: block;\n            }\n        }\n\n        &:hover {\n            .MenuWrapper {\n                display: block;\n            }\n        }\n    }\n\n    .MenuWrapper {\n        border-radius: 4px;\n\n        &:hover {\n            background: rgba(var(--sidebar-text-rgb), 0.08);\n        }\n    }\n\n    .octo-sidebar-icon {\n        .CompassIcon {\n            font-size: 18px;\n            margin: 0 -2px 0 -1px;\n\n            &::before {\n                margin: 0;\n            }\n        }\n\n        > .Icon {\n            width: 16px;\n            vertical-align: top;\n            margin-left: -2px;\n        }\n    }\n\n    .Menu.noselect.left {\n        position: fixed;\n        right: auto;\n        left: auto;\n    }\n\n    .boardMoveToCategorySubmenu {\n        .menu-options {\n            max-height: 600px;\n            overflow-y: auto;\n        }\n\n        @media only screen and (max-height: 768px) {\n            .menu-options {\n                max-height: min(350px, 50vh);\n            }\n        }\n    }\n}\n\n.team-sidebar + .product-wrapper {\n    .SidebarBoardItem {\n        .Menu.noselect.left {\n            right: calc(100% - 480px - 64px + 50px);\n            left: calc(64px + 240px - 50px);\n        }\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/sidebar/sidebarBoardItem.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\n\nimport {createMemoryHistory} from 'history'\nimport {Router} from 'react-router-dom'\n\nimport {render} from '@testing-library/react'\nimport userEvent from '@testing-library/user-event'\n\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport configureStore from 'redux-mock-store'\n\nimport {TestBlockFactory} from '../../test/testBlockFactory'\n\nimport {wrapIntl, wrapRBDNDDroppable} from '../../testUtils'\n\nimport SidebarBoardItem from './sidebarBoardItem'\n\ndescribe('components/sidebarBoardItem', () => {\n    const board = TestBlockFactory.createBoard()\n    board.id = 'board_id_1'\n\n    const view = TestBlockFactory.createBoardView(board)\n    view.fields.sortOptions = []\n    const history = createMemoryHistory()\n\n    const categoryBoards1 = TestBlockFactory.createCategoryBoards()\n    categoryBoards1.name = 'Category 1'\n    categoryBoards1.boardMetadata = [{boardID: board.id, hidden: false}]\n\n    const categoryBoards2 = TestBlockFactory.createCategoryBoards()\n    categoryBoards2.name = 'Category 2'\n\n    const categoryBoards3 = TestBlockFactory.createCategoryBoards()\n    categoryBoards3.name = 'Category 3'\n\n    const allCategoryBoards = [\n        categoryBoards1,\n        categoryBoards2,\n        categoryBoards3,\n    ]\n\n    const state = {\n        users: {\n            me: {\n                id: 'user_id_1',\n            },\n        },\n        boards: {\n            current: board.id,\n            boards: {\n                [board.id]: board,\n            },\n            myBoardMemberships: {\n                [board.id]: {userId: 'user_id_1', schemeAdmin: true},\n            },\n        },\n        views: {\n            current: view.id,\n            views: {\n                [view.id]: view,\n            },\n        },\n        teams: {\n            current: {\n                id: 'team-id',\n            },\n        },\n    }\n\n    test('sidebar board item', () => {\n        const mockStore = configureStore([])\n        const store = mockStore(state)\n\n        const component = wrapRBDNDDroppable(wrapIntl(\n            <ReduxProvider store={store}>\n                <Router history={history}>\n                    <SidebarBoardItem\n                        index={0}\n                        categoryBoards={categoryBoards1}\n                        board={board}\n                        allCategories={allCategoryBoards}\n                        isActive={true}\n                        showBoard={jest.fn()}\n                        showView={jest.fn()}\n                        onDeleteRequest={jest.fn()}\n                    />\n                </Router>\n            </ReduxProvider>,\n        ))\n        const {container} = render(component)\n        const elementMenuWrapper = container.querySelector('.SidebarBoardItem div.MenuWrapper')\n        expect(elementMenuWrapper).not.toBeNull()\n        userEvent.click(elementMenuWrapper!)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('renders default icon if no custom icon set', () => {\n        const mockStore = configureStore([])\n        const store = mockStore(state)\n        const noIconBoard = {...board, icon: ''}\n\n        const component = wrapRBDNDDroppable(wrapIntl(\n            <ReduxProvider store={store}>\n                <Router history={history}>\n                    <SidebarBoardItem\n                        index={0}\n                        categoryBoards={categoryBoards1}\n                        board={noIconBoard}\n                        allCategories={allCategoryBoards}\n                        isActive={true}\n                        showBoard={jest.fn()}\n                        showView={jest.fn()}\n                        onDeleteRequest={jest.fn()}\n                    />\n                </Router>\n            </ReduxProvider>,\n        ))\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('sidebar board item for guest', () => {\n        const mockStore = configureStore([])\n        const store = mockStore({...state, users: {me: {is_guest: true}}})\n\n        const component = wrapRBDNDDroppable(wrapIntl(\n            <ReduxProvider store={store}>\n                <Router history={history}>\n                    <SidebarBoardItem\n                        index={0}\n                        categoryBoards={categoryBoards1}\n                        board={board}\n                        allCategories={allCategoryBoards}\n                        isActive={true}\n                        showBoard={jest.fn()}\n                        showView={jest.fn()}\n                        onDeleteRequest={jest.fn()}\n                    />\n                </Router>\n            </ReduxProvider>,\n        ))\n        const {container} = render(component)\n        const elementMenuWrapper = container.querySelector('.SidebarBoardItem div.MenuWrapper')\n        expect(elementMenuWrapper).not.toBeNull()\n        userEvent.click(elementMenuWrapper!)\n        expect(container).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/sidebar/sidebarBoardItem.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useCallback, useRef, useState} from 'react'\nimport {useIntl} from 'react-intl'\nimport {generatePath, useHistory, useRouteMatch} from 'react-router-dom'\nimport {Draggable} from 'react-beautiful-dnd'\n\nimport {Board} from '../../blocks/board'\nimport {BoardView, IViewType} from '../../blocks/boardView'\nimport mutator from '../../mutator'\nimport IconButton from '../../widgets/buttons/iconButton'\nimport DeleteIcon from '../../widgets/icons/delete'\nimport OptionsIcon from '../../widgets/icons/options'\nimport Menu from '../../widgets/menu'\nimport MenuWrapper from '../../widgets/menuWrapper'\nimport BoardPermissionGate from '../permissions/boardPermissionGate'\n\nimport './sidebarBoardItem.scss'\nimport {CategoryBoards, updateBoardCategories} from '../../store/sidebar'\nimport CreateNewFolder from '../../widgets/icons/newFolder'\nimport {useAppDispatch, useAppSelector} from '../../store/hooks'\nimport {getCurrentBoardViews, getCurrentViewId} from '../../store/views'\nimport Folder from '../../widgets/icons/folder'\nimport Check from '../../widgets/icons/checkIcon'\nimport CompassIcon from '../../widgets/icons/compassIcon'\nimport BoardIcon from '../../widgets/icons/board'\nimport TableIcon from '../../widgets/icons/table'\nimport GalleryIcon from '../../widgets/icons/gallery'\nimport CalendarIcon from '../../widgets/icons/calendar'\n\nimport {getCurrentTeam} from '../../store/teams'\nimport {Permission} from '../../constants'\nimport DuplicateIcon from '../../widgets/icons/duplicate'\nimport {Utils} from '../../utils'\n\nimport AddIcon from '../../widgets/icons/add'\nimport CloseIcon from '../../widgets/icons/close'\nimport {getMe} from '../../store/users'\nimport octoClient from '../../octoClient'\nimport {getCurrentBoardId} from '../../store/boards'\nimport {UserSettings} from '../../userSettings'\nimport {Archiver} from '../../archiver'\n\nconst iconForViewType = (viewType: IViewType): JSX.Element => {\n    switch (viewType) {\n    case 'board': return <BoardIcon/>\n    case 'table': return <TableIcon/>\n    case 'gallery': return <GalleryIcon/>\n    case 'calendar': return <CalendarIcon/>\n    default: return <div/>\n    }\n}\n\ntype Props = {\n    isActive: boolean\n    categoryBoards: CategoryBoards\n    board: Board\n    allCategories: CategoryBoards[]\n    onDeleteRequest: (board: Board) => void\n    showBoard: (boardId: string) => void\n    showView: (viewId: string, boardId: string) => void\n    index: number\n    draggedItemID?: string\n    hideViews?: boolean\n}\n\nconst SidebarBoardItem = (props: Props) => {\n    const intl = useIntl()\n\n    const [boardsMenuOpen, setBoardsMenuOpen] = useState<{[key: string]: boolean}>({})\n\n    const team = useAppSelector(getCurrentTeam)\n    const boardViews = useAppSelector(getCurrentBoardViews)\n    const currentViewId = useAppSelector(getCurrentViewId)\n    const teamID = team?.id || ''\n    const me = useAppSelector(getMe)\n\n    const match = useRouteMatch<{boardId: string, viewId?: string, cardId?: string, teamId?: string}>()\n    const history = useHistory()\n    const dispatch = useAppDispatch()\n    const currentBoardID = useAppSelector(getCurrentBoardId)\n\n    const generateMoveToCategoryOptions = (boardID: string) => {\n        return props.allCategories.map((category) => (\n            <Menu.Text\n                key={category.id}\n                id={category.id}\n                name={category.name}\n                icon={category.id === props.categoryBoards.id ? <Check/> : <Folder/>}\n                onClick={async (toCategoryID) => {\n                    const fromCategoryID = props.categoryBoards.id\n                    if (fromCategoryID !== toCategoryID) {\n                        await mutator.moveBoardToCategory(teamID, boardID, toCategoryID, fromCategoryID)\n                    }\n                }}\n            />\n        ))\n    }\n\n    const board = props.board\n\n    const handleDuplicateBoard = useCallback(async (asTemplate: boolean) => {\n        const blocksAndBoards = await mutator.duplicateBoard(\n            board.id,\n            undefined,\n            asTemplate,\n            undefined,\n            () => {\n                Utils.showBoard(board.id, match, history)\n                return Promise.resolve()\n            },\n        )\n\n        if (blocksAndBoards.boards.length === 0) {\n            return\n        }\n\n        const boardId = blocksAndBoards.boards[0].id\n\n        // If the source board is in a custom category, set the new board in\n        // the same category. Even though the server does this as well on its side,\n        // we need to do this to avoid the duplicated board showing up in default \"Boards\" category first\n        // then jumping to the custom category.\n        // The jump would happen because when server clones a board from a custom category,\n        // two WS events are sent - first to indicate the new board belongs to the specific category,\n        // second, to indicate the new board is created. Depending on the order of execution of the two\n        // WS event handlers, if the handler for second events executes first, it will show the new board\n        // in default category in LHS, then when the handler for first events gets executed, it moves the board\n        // to the correct category.\n        // By not waiting for the board-category WS event and setting the right category for the board,\n        // we avoid the jumping behavior.\n        if (props.categoryBoards.id !== '') {\n            await dispatch(updateBoardCategories([{\n                boardID: boardId,\n                categoryID: props.categoryBoards.id,\n                hidden: false,\n            }]))\n        }\n\n        Utils.showBoard(boardId, match, history)\n    }, [board.id])\n\n    const showTemplatePicker = () => {\n        // if the same board, reuse the match params\n        // otherwise remove viewId and cardId, results in first view being selected\n        const params = {teamId: match.params.teamId}\n        const newPath = generatePath('/team/:teamId?', params)\n        history.push(newPath)\n    }\n\n    const handleHideBoard = async () => {\n        if (!me) {\n            return\n        }\n\n        await octoClient.hideBoard(props.categoryBoards.id, board.id)\n        dispatch(updateBoardCategories([\n            {\n                boardID: board.id,\n                categoryID: props.categoryBoards.id,\n                hidden: true,\n            },\n        ]))\n\n        // If we're hiding the board we're currently on,\n        // we need to switch to a different board once its hidden.\n        if (currentBoardID === props.board.id) {\n            // There's no special logic on what the next board needs to be.\n            // To keep things simple, we just switch to the first unhidden board\n\n            // Empty board ID navigates to template picker, which is\n            // fine if there are no more visible boards to switch to.\n\n            // find the first visible board\n            let visibleBoardID: string | null = null\n            for (const iterCategory of props.allCategories) {\n                const visibleBoardMetadata = iterCategory.boardMetadata.find((categoryBoardMetadata) => !categoryBoardMetadata.hidden && categoryBoardMetadata.boardID !== props.board.id)\n                if (visibleBoardMetadata) {\n                    visibleBoardID = visibleBoardMetadata.boardID\n                    break\n                }\n            }\n\n            if (visibleBoardID === null) {\n                UserSettings.setLastBoardID(match.params.teamId!, null)\n                showTemplatePicker()\n            } else {\n                props.showBoard(visibleBoardID)\n            }\n        }\n    }\n\n    const boardItemRef = useRef<HTMLDivElement>(null)\n\n    const title = board.title || intl.formatMessage({id: 'Sidebar.untitled-board', defaultMessage: '(Untitled Board)'})\n    return (\n        <Draggable\n            draggableId={props.board.id}\n            key={props.board.id}\n            index={props.index}\n        >\n            {(provided, snapshot) => (\n                <div\n                    {...provided.draggableProps}\n                    ref={provided.innerRef}\n                >\n                    <div\n                        {...provided.dragHandleProps}\n                        className={`SidebarBoardItem subitem ${props.isActive ? 'active' : ''}`}\n                        onClick={() => props.showBoard(board.id)}\n                        ref={boardItemRef}\n                    >\n                        <div className='octo-sidebar-icon'>\n                            {board.icon || <CompassIcon icon='product-boards'/>}\n                        </div>\n                        <div\n                            className='octo-sidebar-title'\n                            title={title}\n                        >\n                            {title}\n                        </div>\n                        <div>\n                            <MenuWrapper\n                                className={boardsMenuOpen[board.id] ? 'menuOpen' : 'x'}\n                                stopPropagationOnToggle={true}\n                                onToggle={(open) => {\n                                    setBoardsMenuOpen((menuState) => {\n                                        const newState = {...menuState}\n                                        newState[board.id] = open\n                                        return newState\n                                    })\n                                }}\n                            >\n                                <IconButton icon={<OptionsIcon/>}/>\n                                <Menu\n                                    fixed={true}\n                                    position='auto'\n                                    parentRef={boardItemRef}\n                                >\n                                    <Menu.SubMenu\n                                        key={`moveBlock-${board.id}`}\n                                        id='moveBlock'\n                                        className='boardMoveToCategorySubmenu'\n                                        name={intl.formatMessage({id: 'SidebarCategories.BlocksMenu.Move', defaultMessage: 'Move To...'})}\n                                        icon={<CreateNewFolder/>}\n                                        position='auto'\n                                    >\n                                        {generateMoveToCategoryOptions(board.id)}\n                                    </Menu.SubMenu>\n                                    {!me?.is_guest &&\n                                        <Menu.Text\n                                            id='duplicateBoard'\n                                            name={intl.formatMessage({id: 'Sidebar.duplicate-board', defaultMessage: 'Duplicate board'})}\n                                            icon={<DuplicateIcon/>}\n                                            onClick={() => handleDuplicateBoard(board.isTemplate)}\n                                        />}\n                                    {!me?.is_guest &&\n                                        <Menu.Text\n                                            id='templateFromBoard'\n                                            name={intl.formatMessage({id: 'Sidebar.template-from-board', defaultMessage: 'New template from board'})}\n                                            icon={<AddIcon/>}\n                                            onClick={() => handleDuplicateBoard(true)}\n                                        />}\n                                    <Menu.Text\n                                        id='exportBoardArchive'\n                                        name={intl.formatMessage({id: 'ViewHeader.export-board-archive', defaultMessage: 'Export board archive'})}\n                                        icon={<CompassIcon icon='export-variant'/>}\n                                        onClick={() => Archiver.exportBoardArchive(board)}\n                                    />\n                                    <Menu.Text\n                                        id='hideBoard'\n                                        name={intl.formatMessage({id: 'HideBoard.MenuOption', defaultMessage: 'Hide board'})}\n                                        icon={<CloseIcon/>}\n                                        onClick={() => handleHideBoard()}\n                                    />\n                                    <BoardPermissionGate\n                                        boardId={board.id}\n                                        permissions={[Permission.DeleteBoard]}\n                                    >\n                                        <Menu.Text\n                                            key={`deleteBlock-${board.id}`}\n                                            id='deleteBlock'\n                                            className='text-danger'\n                                            name={intl.formatMessage({id: 'Sidebar.delete-board', defaultMessage: 'Delete board'})}\n                                            icon={<DeleteIcon/>}\n                                            onClick={() => {\n                                                props.onDeleteRequest(board)\n                                            }}\n                                        />\n                                    </BoardPermissionGate>\n                                </Menu>\n                            </MenuWrapper>\n                        </div>\n                    </div>\n                    {props.isActive && !snapshot.isDragging && !props.hideViews && boardViews.map((view: BoardView) => (\n                        <div\n                            key={view.id}\n                            className={`SidebarBoardItem sidebar-view-item ${view.id === currentViewId ? 'active' : ''}`}\n                            onClick={() => props.showView(view.id, board.id)}\n                        >\n                            {iconForViewType(view.fields.viewType)}\n                            <div\n                                className='octo-sidebar-title'\n                                title={view.title || intl.formatMessage({id: 'Sidebar.untitled-view', defaultMessage: '(Untitled View)'})}\n                            >\n                                {view.title || intl.formatMessage({id: 'Sidebar.untitled-view', defaultMessage: '(Untitled View)'})}\n                            </div>\n                        </div>\n                    ))}\n                </div>\n            )}\n        </Draggable>\n    )\n}\n\nexport default React.memo(SidebarBoardItem)\n"
  },
  {
    "path": "webapp/src/components/sidebar/sidebarCategory.scss",
    "content": ".SidebarCategory {\n    margin-top: 12px;\n    width: calc(100% - 4px);\n\n    &:first-child {\n        margin-top: 0;\n    }\n\n    .dialog {\n        color: rgba(var(--center-channel-color-rgb));\n    }\n\n    .octo-sidebar-item {\n        display: flex;\n        flex-direction: row;\n        font-weight: 500;\n        padding: 0 16px 0 8px;\n        height: 32px;\n        align-items: center;\n        gap: 4px;\n\n        &.category {\n            padding-left: 0;\n        }\n\n        &:hover {\n            display: flex;\n\n            .octo-sidebar-title {\n                color: rgba(var(--sidebar-text-rgb), 1);\n            }\n        }\n\n        &.subitem {\n            padding-left: 20px;\n            color: rgba(var(--sidebar-text-rgb), 0.64);\n            font-weight: 400;\n        }\n\n        &.active {\n            background: rgba(var(--sidebar-text-rgb), 0.1);\n            position: relative;\n            color: rgb(var(--sidebar-text-rgb));\n\n            &:not(.subitem) {\n                &::before {\n                    content: '';\n                    background: rgb(var(--sidebar-text-active-border-rgb));\n                    width: 2px;\n                    height: 100%;\n                    position: absolute;\n                    left: 0;\n                    top: 0;\n                }\n            }\n        }\n\n        &.no-views {\n            color: rgba(var(--sidebar-text-rgb), 0.4);\n\n            &:hover {\n                background-color: rgba(var(--sidebar-bg-rgb));\n            }\n        }\n\n        &:not(.no-views) {\n            cursor: pointer;\n        }\n\n        > .Icon {\n            height: 18px;\n            width: 18px;\n            margin-right: 8px;\n            flex-shrink: 0;\n\n            &.GalleryIcon {\n                fill: rgba(var(--sidebar-text-rgb), 0.3);\n                stroke: unset;\n            }\n        }\n\n        > .IconButton {\n            background-color: var(--sidebar-bg-rgb);\n            color: rgb(var(--sidebar-text-rgb));\n            margin-left: 4px;\n\n            &:hover {\n                background-color: rgba(var(--sidebar-text-rgb), 0.1);\n            }\n        }\n\n        .Menu.left {\n            right: -11px;\n        }\n\n        &.expanded {\n            .DisclosureTriangleIcon {\n                transform: rotate(90deg);\n            }\n        }\n\n        .DisclosureTriangleIcon {\n            transition: 200ms ease-in-out;\n            transform: rotate(0deg);\n        }\n\n        &.category {\n            .MenuWrapper {\n                display: none;\n\n                &.menuOpen {\n                    display: block;\n                }\n            }\n\n            &:hover {\n                .MenuWrapper {\n                    display: block;\n                }\n\n                .badge.newCategoryBadge {\n                    display: none;\n                }\n            }\n        }\n\n        &.subitem {\n            .MenuWrapper {\n                display: none;\n\n                &.menuOpen {\n                    display: block;\n                }\n            }\n\n            &:hover {\n                .MenuWrapper {\n                    display: block;\n                }\n            }\n        }\n    }\n\n    .MenuWrapper {\n        border-radius: 4px;\n\n        &:hover {\n            background: rgba(var(--sidebar-text-rgb), 0.08);\n        }\n    }\n\n    .octo-sidebar-title {\n        flex-grow: 1;\n        white-space: nowrap;\n        overflow: hidden;\n        text-overflow: ellipsis;\n        display: flex;\n        align-items: baseline;\n        color: rgba(var(--sidebar-text-rgb), 0.72);\n\n        &.category-title {\n            font-size: 12px;\n            text-transform: uppercase;\n            font-weight: 600;\n        }\n\n        .octo-icon {\n            margin-right: 6px;\n        }\n\n        .octo-sidebar-name {\n            flex: 1;\n            min-width: 0;\n            overflow: hidden;\n            text-overflow: ellipsis;\n        }\n    }\n\n    .Menu.noselect:not(.SubMenu) {\n        position: fixed;\n\n        > .left {\n            right: calc(100% - 480px - 64px + 50px);\n            left: calc(64px + 240px - 50px);\n        }\n    }\n\n    .manageCategoriesTourStep {\n        background: rgba(var(--sidebar-text-rgb), 0.08);\n    }\n\n    .badge.newCategoryBadge {\n\n        background-color: rgba(var(--sidebar-text-rgb));\n        color: rgb(var(--sidebar-bg-rgb));\n        display: flex;\n        width: 32px;\n        height: 16px;\n        align-items: center;\n        padding: 0 4px;\n        border-radius: 4px;\n        font-size: 10px;\n        font-weight: 600;\n        letter-spacing: 0.01em;\n        line-height: 16px;\n        text-transform: uppercase;\n\n        &:hover {\n            display: none;\n        }\n    }\n\n    .newCategoryDragArea {\n        display: flex;\n        align-items: center;\n        padding: 10px;\n        border: 1px dashed rgba(var(--sidebar-text-rgb), 0.3);\n        margin: 0 16px;\n        border-radius: 4px;\n        opacity: 1;\n        color: rgba(var(--sidebar-text-rgb), 0.72);\n        font-size: 12px;\n        line-height: 16px;\n        gap: 12px;\n\n        svg {\n            height: 18px;\n        }\n    }\n    \n    &.draggingOver {\n        border: 1px solid rgba(var(--sidebar-text-rgb), 1);\n        border-radius: 4px;\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/sidebar/sidebarCategory.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\n\nimport {createMemoryHistory} from 'history'\nimport {Router} from 'react-router-dom'\n\nimport {render} from '@testing-library/react'\nimport userEvent from '@testing-library/user-event'\n\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport configureStore from 'redux-mock-store'\n\nimport {TestBlockFactory} from '../../test/testBlockFactory'\n\nimport {wrapIntl, wrapRBDNDDroppable} from '../../testUtils'\n\nimport SidebarCategory from './sidebarCategory'\n\ndescribe('components/sidebarCategory', () => {\n    const board = TestBlockFactory.createBoard()\n    board.id = 'board_id'\n\n    const view = TestBlockFactory.createBoardView(board)\n    view.fields.sortOptions = []\n    const history = createMemoryHistory()\n\n    const board1 = TestBlockFactory.createBoard()\n    board1.id = 'board_1_id'\n\n    const board2 = TestBlockFactory.createBoard()\n    board2.id = 'board_2_id'\n\n    const boards = [board1, board2]\n    const categoryBoards1 = TestBlockFactory.createCategoryBoards()\n    categoryBoards1.id = 'category_1_id'\n    categoryBoards1.name = 'Category 1'\n    categoryBoards1.boardMetadata = [{boardID: board1.id, hidden: false}, {boardID: board2.id, hidden: false}]\n\n    const categoryBoards2 = TestBlockFactory.createCategoryBoards()\n    categoryBoards2.id = 'category_2_id'\n    categoryBoards2.name = 'Category 2'\n\n    const categoryBoards3 = TestBlockFactory.createCategoryBoards()\n    categoryBoards3.id = 'category_id_3'\n    categoryBoards3.name = 'Category 3'\n\n    const allCategoryBoards = [\n        categoryBoards1,\n        categoryBoards2,\n        categoryBoards3,\n    ]\n\n    const state = {\n        users: {\n            me: {\n                id: 'user_id_1',\n                props: {},\n            },\n        },\n        boards: {\n            current: board.id,\n            boards: {\n                [board.id]: board,\n            },\n        },\n        cards: {\n            cards: {\n                card_id_1: {title: 'Card'},\n            },\n            current: 'card_id_1',\n        },\n        views: {\n            current: view.id,\n            views: {\n                [view.id]: view,\n            },\n        },\n        teams: {\n            current: {\n                id: 'team-id',\n            },\n        },\n    }\n\n    test('sidebar call hideSidebar', () => {\n        const mockStore = configureStore([])\n        const store = mockStore(state)\n\n        const component = wrapRBDNDDroppable(wrapIntl(\n            <ReduxProvider store={store}>\n                <Router history={history}>\n                    <SidebarCategory\n                        hideSidebar={() => {}}\n                        categoryBoards={categoryBoards1}\n                        boards={boards}\n                        allCategories={allCategoryBoards}\n                        index={0}\n                    />\n                </Router>\n            </ReduxProvider>,\n        ))\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n\n        // testing collapsed state of category\n        const subItems = container.querySelectorAll('.category')\n        expect(subItems).toBeDefined()\n        userEvent.click(subItems[0] as Element)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('sidebar collapsed without active board', () => {\n        const mockStore = configureStore([])\n        const store = mockStore(state)\n\n        const component = wrapRBDNDDroppable(wrapIntl(\n            <ReduxProvider store={store}>\n                <Router history={history}>\n                    <SidebarCategory\n                        hideSidebar={() => {}}\n                        categoryBoards={categoryBoards1}\n                        boards={boards}\n                        allCategories={allCategoryBoards}\n                        index={0}\n                    />\n                </Router>\n            </ReduxProvider>,\n        ))\n        const {container} = render(component)\n\n        const subItems = container.querySelectorAll('.category-title')\n        expect(subItems).toBeDefined()\n        userEvent.click(subItems[0] as Element)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('sidebar collapsed with active board in it', () => {\n        const mockStore = configureStore([])\n        const store = mockStore(state)\n\n        const component = wrapRBDNDDroppable(wrapIntl(\n            <ReduxProvider store={store}>\n                <Router history={history}>\n                    <SidebarCategory\n                        hideSidebar={() => {}}\n                        activeBoardID={board1.id}\n                        categoryBoards={categoryBoards1}\n                        boards={boards}\n                        allCategories={allCategoryBoards}\n                        index={0}\n                    />\n                </Router>\n            </ReduxProvider>,\n        ))\n        const {container} = render(component)\n\n        const subItems = container.querySelectorAll('.category-title')\n        expect(subItems).toBeDefined()\n        userEvent.click(subItems[0] as Element)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('sidebar template close self', () => {\n        const mockStore = configureStore([])\n        const store = mockStore(state)\n\n        const mockTemplateClose = jest.fn()\n\n        const component = wrapRBDNDDroppable(wrapIntl(\n            <ReduxProvider store={store}>\n                <Router history={history}>\n                    <SidebarCategory\n                        activeBoardID={board1.id}\n                        hideSidebar={() => {}}\n                        categoryBoards={categoryBoards1}\n                        boards={boards}\n                        allCategories={allCategoryBoards}\n                        index={0}\n                        onBoardTemplateSelectorClose={mockTemplateClose}\n                    />\n                </Router>\n            </ReduxProvider>,\n        ))\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n\n        // testing collapsed state of category\n        const subItems = container.querySelectorAll('.subitem')\n        expect(subItems).toBeDefined()\n        userEvent.click(subItems[0] as Element)\n        expect(mockTemplateClose).toBeCalled()\n    })\n\n    test('sidebar template close other', () => {\n        const mockStore = configureStore([])\n        const store = mockStore(state)\n\n        const mockTemplateClose = jest.fn()\n\n        const component = wrapRBDNDDroppable(wrapIntl(\n            <ReduxProvider store={store}>\n                <Router history={history}>\n                    <SidebarCategory\n                        activeBoardID={board2.id}\n                        hideSidebar={() => {}}\n                        categoryBoards={categoryBoards1}\n                        boards={boards}\n                        allCategories={allCategoryBoards}\n                        index={0}\n                        onBoardTemplateSelectorClose={mockTemplateClose}\n                    />\n                </Router>\n            </ReduxProvider>,\n        ))\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n\n        // testing collapsed state of category\n        const subItems = container.querySelectorAll('.category-title')\n        expect(subItems).toBeDefined()\n        userEvent.click(subItems[0] as Element)\n        expect(mockTemplateClose).not.toBeCalled()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/sidebar/sidebarCategory.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'\nimport {FormattedMessage, useIntl} from 'react-intl'\nimport {generatePath, useHistory, useRouteMatch} from 'react-router-dom'\n\nimport {debounce} from 'lodash'\n\nimport {Draggable, Droppable} from 'react-beautiful-dnd'\n\nimport {HandRightIcon} from '@mattermost/compass-icons/components'\n\nimport {Board} from '../../blocks/board'\nimport mutator from '../../mutator'\nimport IconButton from '../../widgets/buttons/iconButton'\nimport DeleteIcon from '../../widgets/icons/delete'\nimport CompassIcon from '../../widgets/icons/compassIcon'\nimport OptionsIcon from '../../widgets/icons/options'\nimport Menu from '../../widgets/menu'\nimport MenuWrapper from '../../widgets/menuWrapper'\n\nimport './sidebarCategory.scss'\nimport {Category, CategoryBoardMetadata, CategoryBoards} from '../../store/sidebar'\nimport ChevronDown from '../../widgets/icons/chevronDown'\nimport ChevronRight from '../../widgets/icons/chevronRight'\nimport CreateNewFolder from '../../widgets/icons/newFolder'\nimport CreateCategory from '../createCategory/createCategory'\nimport {useAppSelector} from '../../store/hooks'\nimport {\n    getOnboardingTourCategory,\n    getOnboardingTourStep,\n} from '../../store/users'\n\nimport {getCurrentCard} from '../../store/cards'\nimport {Utils} from '../../utils'\n\nimport {TOUR_SIDEBAR, SidebarTourSteps, TOUR_BOARD, FINISHED} from '../../components/onboardingTour/index'\nimport telemetryClient, {TelemetryActions, TelemetryCategory} from '../../telemetry/telemetryClient'\n\nimport {getCurrentTeam} from '../../store/teams'\n\nimport ConfirmationDialogBox, {ConfirmationDialogBoxProps} from '../confirmationDialogBox'\n\nimport SidebarCategoriesTourStep from '../../components/onboardingTour/sidebarCategories/sidebarCategories'\nimport ManageCategoriesTourStep from '../../components/onboardingTour/manageCategories/manageCategories'\n\nimport DeleteBoardDialog from './deleteBoardDialog'\nimport SidebarBoardItem from './sidebarBoardItem'\n\ntype Props = {\n    activeCategoryId?: string\n    activeBoardID?: string\n    activeViewID?: string\n    hideSidebar: () => void\n    categoryBoards: CategoryBoards\n    boards: Board[]\n    allCategories: CategoryBoards[]\n    index: number\n    onBoardTemplateSelectorClose?: () => void\n    draggedItemID?: string\n    forceCollapse?: boolean\n}\n\nexport const ClassForManageCategoriesTourStep = 'manageCategoriesTourStep'\n\nconst SidebarCategory = (props: Props) => {\n    const [collapsed, setCollapsed] = useState(props.categoryBoards.collapsed)\n    const intl = useIntl()\n    const history = useHistory()\n\n    const [deleteBoard, setDeleteBoard] = useState<Board|null>()\n    const [showDeleteCategoryDialog, setShowDeleteCategoryDialog] = useState<boolean>(false)\n    const [categoryMenuOpen, setCategoryMenuOpen] = useState<boolean>(false)\n\n    const match = useRouteMatch<{boardId: string, viewId?: string, cardId?: string, teamId?: string}>()\n    const [showCreateCategoryModal, setShowCreateCategoryModal] = useState(false)\n    const [showUpdateCategoryModal, setShowUpdateCategoryModal] = useState(false)\n\n    const onboardingTourCategory = useAppSelector(getOnboardingTourCategory)\n    const onboardingTourStep = useAppSelector(getOnboardingTourStep)\n    const currentCard = useAppSelector(getCurrentCard)\n    const noCardOpen = !currentCard\n    const team = useAppSelector(getCurrentTeam)\n    const teamID = team?.id || ''\n\n    const menuWrapperRef = useRef<HTMLDivElement>(null)\n\n    const [boardDraggingOver, setBoardDraggingOver] = useState<boolean>(false)\n\n    const shouldViewSidebarTour = props.boards.length !== 0 &&\n                                  noCardOpen &&\n                                  (onboardingTourCategory === TOUR_SIDEBAR || onboardingTourCategory === TOUR_BOARD) &&\n                                  ((onboardingTourCategory === TOUR_SIDEBAR && onboardingTourStep === SidebarTourSteps.SIDE_BAR.toString()) || (onboardingTourCategory === TOUR_BOARD && onboardingTourStep === FINISHED.toString()))\n\n    const shouldViewManageCatergoriesTour = props.boards.length !== 0 &&\n                                            noCardOpen &&\n                                            onboardingTourCategory === TOUR_SIDEBAR &&\n                                            onboardingTourStep === SidebarTourSteps.MANAGE_CATEGORIES.toString()\n\n    useEffect(() => {\n        if (shouldViewManageCatergoriesTour && props.index === 0) {\n            setCategoryMenuOpen(true)\n        }\n    }, [shouldViewManageCatergoriesTour])\n\n    const showBoard = useCallback((boardId) => {\n        if (boardId === props.activeBoardID && props.onBoardTemplateSelectorClose) {\n            props.onBoardTemplateSelectorClose()\n        }\n        Utils.showBoard(boardId, match, history)\n        props.hideSidebar()\n    }, [match, history])\n\n    const showView = useCallback((viewId, boardId) => {\n        if (viewId === props.activeViewID && props.onBoardTemplateSelectorClose) {\n            props.onBoardTemplateSelectorClose()\n        }\n\n        // if the same board, reuse the match params\n        // otherwise remove viewId and cardId, results in first view being selected\n        const params = {...match.params, boardId: boardId || '', viewId: viewId || ''}\n        if (boardId !== match.params.boardId && viewId !== match.params.viewId) {\n            params.cardId = undefined\n        }\n        const newPath = generatePath(Utils.getBoardPagePath(match.path), params)\n        history.push(newPath)\n        props.hideSidebar()\n    }, [match, history])\n\n    const isBoardVisible = (boardID: string, existingBoardMetadata?: CategoryBoardMetadata): boolean => {\n        const categoryBoardMetadata = existingBoardMetadata || sidebarBoardMetadata.find((metadata) => metadata.boardID === boardID)\n\n        // hide if board doesn't belong to current category\n        if (!categoryBoardMetadata) {\n            return false\n        }\n\n        // hide if board was hidden by the user\n        return !categoryBoardMetadata.hidden\n    }\n\n    const sidebarBoardMetadata = props.categoryBoards.boardMetadata || []\n    const visibleBlocks = props.categoryBoards.boardMetadata.filter((boardMetadata) => isBoardVisible(boardMetadata.boardID, boardMetadata))\n\n    const handleCreateNewCategory = () => {\n        setShowCreateCategoryModal(true)\n    }\n\n    const handleDeleteCategory = async () => {\n        await mutator.deleteCategory(teamID, props.categoryBoards.id)\n    }\n\n    const handleUpdateCategory = async () => {\n        setShowUpdateCategoryModal(true)\n    }\n\n    const deleteCategoryProps: ConfirmationDialogBoxProps = {\n        heading: intl.formatMessage({\n            id: 'SidebarCategories.CategoryMenu.DeleteModal.Title',\n            defaultMessage: 'Delete this category?',\n        }),\n        subText: intl.formatMessage(\n            {\n                id: 'SidebarCategories.CategoryMenu.DeleteModal.Body',\n                defaultMessage: 'Boards in <b>{categoryName}</b> will move back to the Boards categories. You\\'re not removed from any boards.',\n            },\n            {\n                categoryName: props.categoryBoards.name,\n                b: (...chunks) => <b>{chunks}</b>,\n            },\n        ),\n        onConfirm: () => handleDeleteCategory(),\n        onClose: () => setShowDeleteCategoryDialog(false),\n    }\n\n    const onDeleteBoard = useCallback(async () => {\n        if (!deleteBoard) {\n            return\n        }\n        telemetryClient.trackEvent(TelemetryCategory, TelemetryActions.DeleteBoard, {board: deleteBoard.id})\n        mutator.deleteBoard(\n            deleteBoard,\n            intl.formatMessage({id: 'Sidebar.delete-board', defaultMessage: 'Delete board'}),\n            async () => {\n                let nextBoardId: number | undefined\n                if (props.boards.length > 1) {\n                    const deleteBoardIndex = props.boards.findIndex((board) => board.id === deleteBoard.id)\n                    nextBoardId = deleteBoardIndex + 1 === props.boards.length ? deleteBoardIndex - 1 : deleteBoardIndex + 1\n                }\n\n                if (nextBoardId) {\n                // This delay is needed because WSClient has a default 100 ms notification delay before updates\n                    setTimeout(() => {\n                        showBoard(props.boards[nextBoardId as number].id)\n                    }, 120)\n                }\n            },\n            async () => {\n                showBoard(deleteBoard.id)\n            },\n        )\n    }, [showBoard, deleteBoard, props.boards])\n\n    const updateCategory = useCallback(async (value: boolean) => {\n        const updatedCategory: Category = {\n            ...props.categoryBoards,\n            collapsed: value,\n        }\n        await mutator.updateCategory(updatedCategory)\n    }, [props.categoryBoards])\n\n    const debouncedUpdateCategory = useMemo(() => debounce(updateCategory, 400), [updateCategory])\n\n    const toggleCollapse = async () => {\n        const newVal = !collapsed\n        await setCollapsed(newVal)\n\n        // The default 'Boards' category isn't stored in database,\n        // so avoid making the API call for it\n        if (props.categoryBoards.id !== '') {\n            debouncedUpdateCategory(newVal)\n        }\n    }\n\n    const newCategoryBadge = (\n        <div className='badge newCategoryBadge'>\n            <span>\n                {\n                    intl.formatMessage({\n                        id: 'Sidebar.new-category.badge',\n                        defaultMessage: 'New',\n                    })\n                }\n            </span>\n        </div>\n    )\n\n    const newCategoryDragArea = (\n        <div className='newCategoryDragArea'>\n            <HandRightIcon/>\n            <span>\n                {\n                    intl.formatMessage({\n                        id: 'Sidebar.new-category.drag-boards-cta',\n                        defaultMessage: 'Drag boards here...',\n                    })\n                }\n            </span>\n        </div>\n    )\n\n    const delayedSetBoardDraggingOver = (isDraggingOver: boolean) => {\n        setTimeout(() => {\n            setBoardDraggingOver(isDraggingOver)\n        }, 200)\n    }\n\n    return (\n        <Draggable\n            draggableId={props.categoryBoards.id}\n            key={props.categoryBoards.id}\n            index={props.index}\n        >\n            {(provided, snapshot) => (\n                <div\n                    ref={provided.innerRef}\n                    {...provided.draggableProps}\n                >\n                    <div\n                        className={`SidebarCategory${props.categoryBoards.isNew ? ' new' : ''}${boardDraggingOver ? ' draggingOver' : ''}`}\n                        ref={menuWrapperRef}\n                    >\n                        <Droppable\n                            droppableId={props.categoryBoards.id}\n                            type='board'\n                        >\n                            {(categoryProvided, categorySnapshot) => {\n                                if (boardDraggingOver !== categorySnapshot.isDraggingOver) {\n                                    delayedSetBoardDraggingOver(categorySnapshot.isDraggingOver)\n                                }\n\n                                return (\n                                    <div\n                                        className={`categoryBoardsDroppableArea${categorySnapshot.isDraggingOver ? ' draggingOver' : ''}`}\n                                        ref={categoryProvided.innerRef}\n                                        {...categoryProvided.droppableProps}\n                                    >\n                                        <div\n                                            className={`octo-sidebar-item category ${collapsed || props.forceCollapse ? 'collapsed' : 'expanded'} ${props.categoryBoards.id === props.activeCategoryId ? 'active' : ''}`}\n                                        >\n                                            <div\n                                                className='octo-sidebar-title category-title'\n                                                title={props.categoryBoards.name}\n                                                onClick={toggleCollapse}\n                                                {...provided.dragHandleProps}\n                                            >\n                                                {collapsed || snapshot.isDragging || props.forceCollapse ? <ChevronRight/> : <ChevronDown/>}\n                                                {props.categoryBoards.name}\n                                                <div className='sidebarCategoriesTour'>\n                                                    {props.index === 0 && shouldViewSidebarTour && <SidebarCategoriesTourStep/>}\n                                                </div>\n                                            </div>\n                                            <div className={(props.index === 0 && shouldViewManageCatergoriesTour) ? `${ClassForManageCategoriesTourStep}` : ''}>\n                                                {props.index === 0 && shouldViewManageCatergoriesTour && <ManageCategoriesTourStep/>}\n\n                                                {props.categoryBoards.isNew && !categoryMenuOpen && newCategoryBadge}\n\n                                                <MenuWrapper\n                                                    className={categoryMenuOpen ? 'menuOpen' : ''}\n                                                    stopPropagationOnToggle={true}\n                                                    onToggle={(open) => setCategoryMenuOpen(open)}\n                                                >\n                                                    <IconButton icon={<OptionsIcon/>}/>\n                                                    <Menu\n                                                        position='auto'\n                                                        fixed={true}\n                                                        parentRef={menuWrapperRef}\n                                                    >\n                                                        {\n                                                            props.categoryBoards.type === 'custom' &&\n                                                            <React.Fragment>\n                                                                <Menu.Text\n                                                                    id='updateCategory'\n                                                                    name={intl.formatMessage({id: 'SidebarCategories.CategoryMenu.Update', defaultMessage: 'Rename Category'})}\n                                                                    icon={<CompassIcon icon='pencil-outline'/>}\n                                                                    onClick={handleUpdateCategory}\n                                                                />\n                                                                <Menu.Text\n                                                                    id='deleteCategory'\n                                                                    className='text-danger'\n                                                                    name={intl.formatMessage({id: 'SidebarCategories.CategoryMenu.Delete', defaultMessage: 'Delete Category'})}\n                                                                    icon={<DeleteIcon/>}\n                                                                    onClick={() => setShowDeleteCategoryDialog(true)}\n                                                                />\n                                                                <Menu.Separator/>\n                                                            </React.Fragment>\n                                                        }\n                                                        <Menu.Text\n                                                            id='createNewCategory'\n                                                            name={intl.formatMessage({id: 'SidebarCategories.CategoryMenu.CreateNew', defaultMessage: 'Create New Category'})}\n                                                            icon={<CreateNewFolder/>}\n                                                            onClick={handleCreateNewCategory}\n                                                        />\n                                                    </Menu>\n                                                </MenuWrapper>\n                                            </div>\n                                        </div>\n                                        {!(collapsed || props.forceCollapse || snapshot.isDragging || props.draggedItemID === props.categoryBoards.id) && visibleBlocks.length === 0 &&\n                                            (\n                                                <div>\n                                                    {!props.categoryBoards.isNew && (\n                                                        <div className='octo-sidebar-item subitem no-views'>\n                                                            <FormattedMessage\n                                                                id='Sidebar.no-boards-in-category'\n                                                                defaultMessage='No boards inside'\n                                                            />\n                                                        </div>\n                                                    )}\n\n                                                    {props.categoryBoards.isNew && newCategoryDragArea}\n                                                </div>\n                                            )\n                                        }\n                                        {!props.forceCollapse && collapsed && !snapshot.isDragging && props.draggedItemID !== props.categoryBoards.id && props.boards.filter((board: Board) => board.id === props.activeBoardID).map((board: Board, zzz) => {\n                                            if (!isBoardVisible(board.id)) {\n                                                return null\n                                            }\n                                            return (\n                                                <SidebarBoardItem\n                                                    index={zzz}\n                                                    key={board.id}\n                                                    board={board}\n                                                    categoryBoards={props.categoryBoards}\n                                                    allCategories={props.allCategories}\n                                                    isActive={board.id === props.activeBoardID}\n                                                    showBoard={showBoard}\n                                                    showView={showView}\n                                                    onDeleteRequest={setDeleteBoard}\n                                                />\n                                            )\n                                        })}\n                                        {!(collapsed || props.forceCollapse || snapshot.isDragging || props.draggedItemID === props.categoryBoards.id) && props.boards.filter((board) => isBoardVisible(board.id) && !board.isTemplate).map((board: Board, zzz) => {\n                                            return (\n                                                <SidebarBoardItem\n                                                    index={zzz}\n                                                    key={board.id}\n                                                    board={board}\n                                                    categoryBoards={props.categoryBoards}\n                                                    allCategories={props.allCategories}\n                                                    isActive={board.id === props.activeBoardID}\n                                                    showBoard={showBoard}\n                                                    showView={showView}\n                                                    onDeleteRequest={setDeleteBoard}\n                                                    hideViews={props.draggedItemID === board.id || props.draggedItemID === props.categoryBoards.id}\n                                                />\n                                            )\n                                        })}\n                                        {categoryProvided.placeholder}\n                                    </div>\n                                )\n                            }}\n                        </Droppable>\n\n                        {\n                            showCreateCategoryModal && (\n                                <CreateCategory\n                                    onClose={() => setShowCreateCategoryModal(false)}\n                                    title={(\n                                        <FormattedMessage\n                                            id='SidebarCategories.CategoryMenu.CreateNew'\n                                            defaultMessage='Create New Category'\n                                        />\n                                    )}\n                                />\n                            )\n                        }\n\n                        {\n                            showUpdateCategoryModal && (\n                                <CreateCategory\n                                    initialValue={props.categoryBoards.name}\n                                    title={(\n                                        <FormattedMessage\n                                            id='SidebarCategories.CategoryMenu.Update'\n                                            defaultMessage='Rename Category'\n                                        />\n                                    )}\n                                    onClose={() => setShowUpdateCategoryModal(false)}\n                                    boardCategoryId={props.categoryBoards.id}\n                                    renameModal={true}\n                                />\n                            )\n                        }\n\n                        { deleteBoard &&\n                        <DeleteBoardDialog\n                            boardTitle={deleteBoard.title}\n                            onClose={() => setDeleteBoard(null)}\n                            onDelete={onDeleteBoard}\n                        />\n                        }\n\n                        {\n                            showDeleteCategoryDialog && <ConfirmationDialogBox dialogBox={deleteCategoryProps}/>\n                        }\n                    </div>\n                </div>\n            )}\n        </Draggable>\n    )\n}\n\nexport default React.memo(SidebarCategory)\n"
  },
  {
    "path": "webapp/src/components/sidebar/sidebarSettingsMenu.scss",
    "content": ".SidebarSettingsMenu {\n    .menu-entry {\n        display: flex;\n        cursor: pointer;\n        flex-direction: row;\n        padding: 0 16px 0 8px;\n        height: 36px;\n        align-items: center;\n        padding-left: 20px;\n        color: rgba(var(--sidebar-text-rgb), 0.64);\n        font-weight: 400;\n\n        &:hover {\n            background-color: rgba(var(--sidebar-text-rgb), 0.08);\n            display: flex;\n        }\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/sidebar/sidebarSettingsMenu.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport {render, act} from '@testing-library/react'\n\nimport userEvent from '@testing-library/user-event'\nimport configureStore from 'redux-mock-store'\n\nimport {mocked} from 'jest-mock'\n\nimport {wrapIntl} from '../../testUtils'\n\nimport {defaultThemeName} from '../../theme'\n\nimport TelemetryClient, {TelemetryCategory, TelemetryActions} from '../../telemetry/telemetryClient'\n\nimport SidebarSettingsMenu from './sidebarSettingsMenu'\n\njest.mock('../../telemetry/telemetryClient')\nconst mockedTelemetry = mocked(TelemetryClient, true)\n\ndescribe('components/sidebar/SidebarSettingsMenu', () => {\n    const mockStore = configureStore([])\n    let store = mockStore({})\n    beforeEach(() => {\n        store = mockStore({\n            teams: {\n                current: {id: 'team-id'},\n            },\n            boards: {\n                current: 'board_id',\n                boards: {\n                    board_id: {id: 'board_id'},\n                },\n                templates: [],\n                myBoardMemberships: {\n                    board_id: {userId: 'user_id_1', schemeAdmin: true},\n                },\n            },\n        })\n    })\n    test('settings menu closed should match snapshot', () => {\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <SidebarSettingsMenu activeTheme={defaultThemeName}/>\n            </ReduxProvider>,\n        )\n\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('settings menu open should match snapshot', () => {\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <SidebarSettingsMenu activeTheme={defaultThemeName}/>\n            </ReduxProvider>,\n        )\n\n        const {container} = render(component)\n        userEvent.click(container.querySelector('.menu-entry') as Element)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('theme menu open should match snapshot', () => {\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <SidebarSettingsMenu activeTheme={defaultThemeName}/>\n            </ReduxProvider>,\n        )\n\n        const {container} = render(component)\n        act(() => {\n            userEvent.click(container.querySelector('.menu-entry') as Element)\n        })\n        act(() => {\n            userEvent.hover(container.querySelector('#theme') as Element)\n        })\n        expect(container).toMatchSnapshot()\n    })\n\n    test('languages menu open should match snapshot', () => {\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <SidebarSettingsMenu activeTheme={defaultThemeName}/>\n            </ReduxProvider>,\n        )\n\n        const {container} = render(component)\n        act(() => {\n            userEvent.click(container.querySelector('.menu-entry') as Element)\n        })\n        act(() => {\n            userEvent.hover(container.querySelector('#lang') as Element)\n        })\n        expect(container).toMatchSnapshot()\n    })\n\n    test('imports menu open should match snapshot', () => {\n        window.open = jest.fn()\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <SidebarSettingsMenu activeTheme={defaultThemeName}/>\n            </ReduxProvider>,\n        )\n\n        const {container} = render(component)\n        act(() => {\n            userEvent.click(container.querySelector('.menu-entry') as Element)\n        })\n        act(() => {\n            userEvent.hover(container.querySelector('#import') as Element)\n        })\n        expect(container).toMatchSnapshot()\n\n        userEvent.click(container.querySelector('[aria-label=\"Asana\"]') as Element)\n        expect(mockedTelemetry.trackEvent).toBeCalledWith(TelemetryCategory, TelemetryActions.ImportAsana)\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/sidebar/sidebarSettingsMenu.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useState} from 'react'\nimport {FormattedMessage, useIntl} from 'react-intl'\n\nimport {Archiver} from '../../archiver'\nimport {\n    darkTheme,\n    darkThemeName,\n    defaultTheme,\n    defaultThemeName,\n    lightTheme,\n    lightThemeName,\n    setTheme, systemThemeName,\n    Theme,\n} from '../../theme'\nimport Menu from '../../widgets/menu'\nimport MenuWrapper from '../../widgets/menuWrapper'\nimport {useAppDispatch, useAppSelector} from '../../store/hooks'\nimport {storeLanguage} from '../../store/language'\nimport {getCurrentTeam, Team} from '../../store/teams'\nimport {UserSettings} from '../../userSettings'\n\nimport './sidebarSettingsMenu.scss'\nimport CheckIcon from '../../widgets/icons/check'\nimport {Constants} from '../../constants'\n\nimport TelemetryClient, {TelemetryCategory, TelemetryActions} from '../../telemetry/telemetryClient'\n\ntype Props = {\n    activeTheme: string\n}\n\nconst SidebarSettingsMenu = (props: Props) => {\n    const intl = useIntl()\n    const dispatch = useAppDispatch()\n    const currentTeam = useAppSelector<Team|null>(getCurrentTeam)\n\n    // we need this as the sidebar doesn't always need to re-render\n    // on theme change. This can cause props and the actual\n    // active theme can go out of sync\n    const [themeName, setThemeName] = useState(props.activeTheme)\n\n    const updateTheme = (theme: Theme | null, name: string) => {\n        setTheme(theme)\n        setThemeName(name)\n    }\n\n    const [randomIcons, setRandomIcons] = useState(UserSettings.prefillRandomIcons)\n    const toggleRandomIcons = () => {\n        UserSettings.prefillRandomIcons = !UserSettings.prefillRandomIcons\n        setRandomIcons(!randomIcons)\n    }\n\n    const themes = [\n        {\n            id: defaultThemeName,\n            displayName: 'Default theme',\n            theme: defaultTheme,\n        },\n        {\n            id: darkThemeName,\n            displayName: 'Dark theme',\n            theme: darkTheme,\n        },\n        {\n            id: lightThemeName,\n            displayName: 'Light theme',\n            theme: lightTheme,\n        },\n        {\n            id: systemThemeName,\n            displayName: 'System theme',\n            theme: null,\n        },\n    ]\n\n    return (\n        <div className='SidebarSettingsMenu'>\n            <MenuWrapper>\n                <div className='menu-entry'>\n                    <FormattedMessage\n                        id='Sidebar.settings'\n                        defaultMessage='Settings'\n                    />\n                </div>\n                <Menu position='top'>\n                    <Menu.SubMenu\n                        id='import'\n                        name={intl.formatMessage({id: 'Sidebar.import', defaultMessage: 'Import'})}\n                        position='top'\n                    >\n                        <Menu.Text\n                            id='import_archive'\n                            name={intl.formatMessage({id: 'Sidebar.import-archive', defaultMessage: 'Import archive'})}\n                            onClick={async () => {\n                                TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.ImportArchive)\n                                Archiver.importFullArchive()\n                            }}\n                        />\n                        {\n                            Constants.imports.map((i) => (\n                                <Menu.Text\n                                    key={`${i.id}-import`}\n                                    id={`${i.id}-import`}\n                                    name={i.displayName}\n                                    onClick={() => {\n                                        TelemetryClient.trackEvent(TelemetryCategory, i.telemetryName)\n                                        window.open(i.href)\n                                    }}\n                                />\n                            ))\n                        }\n                    </Menu.SubMenu>\n                    <Menu.Text\n                        id='export'\n                        name={intl.formatMessage({id: 'Sidebar.export-archive', defaultMessage: 'Export archive'})}\n                        onClick={async () => {\n                            if (currentTeam) {\n                                TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.ExportArchive)\n                                Archiver.exportFullArchive(currentTeam.id)\n                            }\n                        }}\n                    />\n                    <Menu.SubMenu\n                        id='lang'\n                        name={intl.formatMessage({id: 'Sidebar.set-language', defaultMessage: 'Set language'})}\n                        position='top'\n                    >\n                        {\n                            Constants.languages.map((language) => (\n                                <Menu.Text\n                                    key={language.code}\n                                    id={`${language.name}-lang`}\n                                    name={language.displayName}\n                                    onClick={async () => dispatch(storeLanguage(language.code))}\n                                    rightIcon={intl.locale.toLowerCase() === language.code ? <CheckIcon/> : null}\n                                />\n                            ))\n                        }\n                    </Menu.SubMenu>\n                    <Menu.SubMenu\n                        id='theme'\n                        name={intl.formatMessage({id: 'Sidebar.set-theme', defaultMessage: 'Set theme'})}\n                        position='top'\n                    >\n                        {\n                            themes.map((theme) =>\n                                (\n                                    <Menu.Text\n                                        key={theme.id}\n                                        id={theme.id}\n                                        name={intl.formatMessage({id: `Sidebar.${theme.id}`, defaultMessage: theme.displayName})}\n                                        onClick={async () => updateTheme(theme.theme, theme.id)}\n                                        rightIcon={themeName === theme.id ? <CheckIcon/> : null}\n                                    />\n                                ),\n                            )\n                        }\n                    </Menu.SubMenu>\n                    <Menu.Switch\n                        id='random-icons'\n                        name={intl.formatMessage({id: 'Sidebar.random-icons', defaultMessage: 'Random icons'})}\n                        isOn={randomIcons}\n                        onClick={async () => toggleRandomIcons()}\n                        suppressItemClicked={true}\n                    />\n                </Menu>\n            </MenuWrapper>\n        </div>\n    )\n}\n\nexport default React.memo(SidebarSettingsMenu)\n"
  },
  {
    "path": "webapp/src/components/sidebar/sidebarUserMenu.scss",
    "content": ".SidebarUserMenu {\n    .logo {\n        display: flex;\n        flex-direction: row;\n        align-items: center;\n\n        .logo-title {\n            font-family: Metropolis, sans-serif;\n            display: flex;\n            align-items: center;\n            font-size: 16px;\n            margin-right: 5px;\n            cursor: pointer;\n\n            .FocalboardLogoIcon {\n                fill: rgba(var(--sidebar-text-rgb), 1);\n                color: rgba(var(--sidebar-text-rgb), 1);\n                height: 24px;\n                width: 22px;\n                margin-right: 3px;\n                position: relative;\n                bottom: 2px;\n            }\n\n            span {\n                height: 32px;\n            }\n        }\n\n        .versionFrame {\n            display: flex;\n            flex-direction: column;\n            align-items: center;\n            margin-left: 8px;\n        }\n\n        .version {\n            font-size: 11px;\n            line-height: 11px;\n            font-weight: 500;\n            color: rgba(var(--sidebar-text-rgb), 0.8);\n        }\n\n        .versionBadge {\n            font-size: 10px;\n            line-height: 11px;\n            font-weight: 500;\n            color: rgba(var(--sidebar-text-rgb), 0.8);\n        }\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/sidebar/sidebarUserMenu.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React, {useState} from 'react'\nimport {useIntl} from 'react-intl'\nimport {useHistory} from 'react-router-dom'\n\nimport {Constants} from '../../constants'\nimport octoClient from '../../octoClient'\nimport {IUser} from '../../user'\nimport FocalboardLogoIcon from '../../widgets/icons/focalboard_logo'\nimport Menu from '../../widgets/menu'\nimport MenuWrapper from '../../widgets/menuWrapper'\nimport {getMe, setMe} from '../../store/users'\nimport {useAppSelector, useAppDispatch} from '../../store/hooks'\n\nimport ModalWrapper from '../modalWrapper'\n\nimport {IAppWindow} from '../../types'\n\nimport RegistrationLink from './registrationLink'\n\nimport './sidebarUserMenu.scss'\n\ndeclare let window: IAppWindow\n\nconst SidebarUserMenu = () => {\n    const dispatch = useAppDispatch()\n    const history = useHistory()\n    const [showRegistrationLinkDialog, setShowRegistrationLinkDialog] = useState(false)\n    const user = useAppSelector<IUser|null>(getMe)\n    const intl = useIntl()\n\n    return (\n        <div className='SidebarUserMenu'>\n            <ModalWrapper>\n                <MenuWrapper>\n                    <div className='logo'>\n                        <div className='logo-title'>\n                            <FocalboardLogoIcon/>\n                            <span>{'Focalboard'}</span>\n                            <div className='versionFrame'>\n                                <div\n                                    className='version'\n                                    title={`v${Constants.versionString}`}\n                                >\n                                    {`v${Constants.versionString}`}\n                                </div>\n                            </div>\n                        </div>\n                    </div>\n                    <Menu>\n                        {user && user.username !== 'single-user' && <>\n                            <Menu.Label><b>{user.username}</b></Menu.Label>\n                            <Menu.Text\n                                id='logout'\n                                name={intl.formatMessage({id: 'Sidebar.logout', defaultMessage: 'Log out'})}\n                                onClick={async () => {\n                                    await octoClient.logout()\n                                    dispatch(setMe(null))\n                                    history.push('/login')\n                                }}\n                            />\n                            <Menu.Text\n                                id='changePassword'\n                                name={intl.formatMessage({id: 'Sidebar.changePassword', defaultMessage: 'Change password'})}\n                                onClick={async () => {\n                                    history.push('/change_password')\n                                }}\n                            />\n                            <Menu.Text\n                                id='invite'\n                                name={intl.formatMessage({id: 'Sidebar.invite-users', defaultMessage: 'Invite users'})}\n                                onClick={async () => {\n                                    setShowRegistrationLinkDialog(true)\n                                }}\n                            />\n\n                            <Menu.Separator/>\n                        </>}\n\n                        <Menu.Text\n                            id='about'\n                            name={intl.formatMessage({id: 'Sidebar.about', defaultMessage: 'About Focalboard'})}\n                            onClick={async () => {\n                                window.open('https://www.focalboard.com?utm_source=webapp', '_blank')\n\n                                // TODO: Review if this is needed in the future, this is to fix the problem with linux webview links\n                                if (window.openInNewBrowser) {\n                                    window.openInNewBrowser('https://www.focalboard.com?utm_source=webapp')\n                                }\n                            }}\n                        />\n                    </Menu>\n                </MenuWrapper>\n\n                {showRegistrationLinkDialog &&\n                    <RegistrationLink\n                        onClose={() => {\n                            setShowRegistrationLinkDialog(false)\n                        }}\n                    />\n                }\n            </ModalWrapper>\n        </div>\n    )\n}\n\nexport default React.memo(SidebarUserMenu)\n"
  },
  {
    "path": "webapp/src/components/table/__snapshots__/table.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/table/Table extended should have Duplicate Button 1`] = `\n<div>\n  <div\n    class=\"Table\"\n  >\n    <div\n      class=\"octo-table-body\"\n    >\n      <div\n        class=\"octo-table-header TableHeaders\"\n        id=\"mainBoardHeader\"\n      >\n        <div\n          class=\"octo-table-cell header-cell\"\n          style=\"overflow: unset; opacity: 1; width: 100px;\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <span\n              class=\"Label empty \"\n            >\n              Name\n            </span>\n          </div>\n          <div\n            class=\"octo-spacer\"\n          />\n          <div\n            class=\"HorizontalGrip\"\n          />\n        </div>\n        <div\n          class=\"octo-table-cell header-cell\"\n          draggable=\"true\"\n          style=\"overflow: unset; opacity: 1; width: 100px;\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <span\n              class=\"Label empty \"\n            >\n              Property 1\n              <svg\n                class=\"SortDownIcon Icon\"\n                viewBox=\"0 0 100 100\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n              >\n                <polyline\n                  points=\"50,20 50,80\"\n                />\n                <polyline\n                  points=\"30,60 50,80 70,60\"\n                />\n              </svg>\n            </span>\n          </div>\n          <div\n            class=\"octo-spacer\"\n          />\n          <div\n            class=\"HorizontalGrip\"\n          />\n        </div>\n        <div\n          class=\"octo-table-cell header-cell\"\n          draggable=\"true\"\n          style=\"overflow: unset; opacity: 1; width: 100px;\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <span\n              class=\"Label empty \"\n            >\n              Property 2\n              <svg\n                class=\"SortUpIcon Icon\"\n                viewBox=\"0 0 100 100\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n              >\n                <polyline\n                  points=\"50,20 50,80\"\n                />\n                <polyline\n                  points=\"30,40 50,20 70,40\"\n                />\n              </svg>\n            </span>\n          </div>\n          <div\n            class=\"octo-spacer\"\n          />\n          <div\n            class=\"HorizontalGrip\"\n          />\n        </div>\n        <div\n          class=\"octo-table-cell header-cell\"\n          draggable=\"true\"\n          style=\"overflow: unset; opacity: 1; width: 100px;\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <span\n              class=\"Label empty \"\n            >\n              Status\n            </span>\n          </div>\n          <div\n            class=\"octo-spacer\"\n          />\n          <div\n            class=\"HorizontalGrip\"\n          />\n        </div>\n      </div>\n      <div\n        class=\"table-row-container\"\n      >\n        <div\n          class=\"TableRow octo-table-row\"\n          draggable=\"true\"\n          style=\"opacity: 1;\"\n        >\n          <div\n            class=\"action-cell octo-table-cell-btn\"\n          >\n            <button\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-drag-vertical\"\n              />\n            </button>\n          </div>\n          <div\n            class=\"octo-table-cell title-cell\"\n            id=\"mainBoardHeader\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-icontitle\"\n            >\n              <div\n                class=\"octo-icon\"\n              >\n                i\n              </div>\n              <input\n                class=\"Editable \"\n                placeholder=\"Untitled\"\n                spellcheck=\"true\"\n                title=\"card1\"\n                value=\"card1\"\n              />\n            </div>\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper optionsMenu ml-2 mr-2\"\n              role=\"button\"\n            >\n              <div\n                class=\"octo-tooltip tooltip-top\"\n                data-tooltip=\"More actions\"\n              >\n                <button\n                  aria-label=\"MenuBtn\"\n                  title=\"MenuBtn\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n            </div>\n            <div\n              class=\"open-button\"\n            >\n              <button\n                type=\"button\"\n              >\n                <span>\n                  Open\n                </span>\n              </button>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-cell\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-propertyvalue\"\n              data-testid=\"select-non-editable\"\n              tabindex=\"0\"\n            >\n              <span\n                class=\"Label propColorBrown \"\n              >\n                <span\n                  class=\"Label-text\"\n                >\n                  value 1\n                </span>\n              </span>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-cell\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-propertyvalue\"\n              data-testid=\"select-non-editable\"\n              tabindex=\"0\"\n            >\n              <span\n                class=\"Label empty \"\n              >\n                <span\n                  class=\"Label-text\"\n                />\n              </span>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-cell\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-propertyvalue\"\n              data-testid=\"select-non-editable\"\n              tabindex=\"0\"\n            >\n              <span\n                class=\"Label empty \"\n              >\n                <span\n                  class=\"Label-text\"\n                />\n              </span>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"TableRow octo-table-row\"\n          draggable=\"true\"\n          style=\"opacity: 1;\"\n        >\n          <div\n            class=\"action-cell octo-table-cell-btn\"\n          >\n            <button\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-drag-vertical\"\n              />\n            </button>\n          </div>\n          <div\n            class=\"octo-table-cell title-cell\"\n            id=\"mainBoardHeader\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-icontitle\"\n            >\n              <div\n                class=\"octo-icon\"\n              >\n                i\n              </div>\n              <input\n                class=\"Editable \"\n                placeholder=\"Untitled\"\n                spellcheck=\"true\"\n                title=\"card2\"\n                value=\"card2\"\n              />\n            </div>\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper optionsMenu ml-2 mr-2\"\n              role=\"button\"\n            >\n              <div\n                class=\"octo-tooltip tooltip-top\"\n                data-tooltip=\"More actions\"\n              >\n                <button\n                  aria-label=\"MenuBtn\"\n                  title=\"MenuBtn\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n            </div>\n            <div\n              class=\"open-button\"\n            >\n              <button\n                type=\"button\"\n              >\n                <span>\n                  Open\n                </span>\n              </button>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-cell\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-propertyvalue\"\n              data-testid=\"select-non-editable\"\n              tabindex=\"0\"\n            >\n              <span\n                class=\"Label propColorBrown \"\n              >\n                <span\n                  class=\"Label-text\"\n                >\n                  value 1\n                </span>\n              </span>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-cell\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-propertyvalue\"\n              data-testid=\"select-non-editable\"\n              tabindex=\"0\"\n            >\n              <span\n                class=\"Label empty \"\n              >\n                <span\n                  class=\"Label-text\"\n                />\n              </span>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-cell\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-propertyvalue\"\n              data-testid=\"select-non-editable\"\n              tabindex=\"0\"\n            >\n              <span\n                class=\"Label empty \"\n              >\n                <span\n                  class=\"Label-text\"\n                />\n              </span>\n            </div>\n          </div>\n        </div>\n      </div>\n      <div\n        class=\"octo-table-footer\"\n      >\n        <div\n          class=\"octo-table-cell\"\n        >\n          + New\n        </div>\n      </div>\n      <div\n        class=\"CalculationRow octo-table-row\"\n      >\n        <div\n          class=\"Calculation count octo-table-cell   \"\n          style=\"width: 100px;\"\n          tabindex=\"0\"\n        >\n          <span\n            class=\"calculationLabel\"\n          >\n            Count\n          </span>\n          <span\n            class=\"calculationValue\"\n          >\n            2\n          </span>\n        </div>\n        <div\n          class=\"Calculation none octo-table-cell   \"\n          style=\"width: 100px;\"\n          tabindex=\"0\"\n        >\n          <span\n            class=\"calculationLabel\"\n          >\n            Calculate\n          </span>\n          <i\n            class=\"CompassIcon icon-chevron-up ChevronUpIcon\"\n          />\n        </div>\n        <div\n          class=\"Calculation none octo-table-cell   \"\n          style=\"width: 100px;\"\n          tabindex=\"0\"\n        >\n          <span\n            class=\"calculationLabel\"\n          >\n            Calculate\n          </span>\n          <i\n            class=\"CompassIcon icon-chevron-up ChevronUpIcon\"\n          />\n        </div>\n        <div\n          class=\"Calculation none octo-table-cell   \"\n          style=\"width: 100px;\"\n          tabindex=\"0\"\n        >\n          <span\n            class=\"calculationLabel\"\n          >\n            Calculate\n          </span>\n          <i\n            class=\"CompassIcon icon-chevron-up ChevronUpIcon\"\n          />\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/table/Table extended should match snapshot with CreatedAt 1`] = `\n<div>\n  <div\n    class=\"Table\"\n  >\n    <div\n      class=\"octo-table-body\"\n    >\n      <div\n        class=\"octo-table-header TableHeaders\"\n        id=\"mainBoardHeader\"\n      >\n        <div\n          class=\"octo-table-cell header-cell\"\n          style=\"overflow: unset; opacity: 1; width: 100px;\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper disabled\"\n            role=\"button\"\n          >\n            <span\n              class=\"Label empty \"\n            >\n              Name\n            </span>\n          </div>\n          <div\n            class=\"octo-spacer\"\n          />\n        </div>\n        <div\n          class=\"octo-table-cell header-cell\"\n          draggable=\"true\"\n          style=\"overflow: unset; opacity: 1; width: 100px;\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper disabled\"\n            role=\"button\"\n          >\n            <span\n              class=\"Label empty \"\n            >\n              Property 1\n              <svg\n                class=\"SortDownIcon Icon\"\n                viewBox=\"0 0 100 100\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n              >\n                <polyline\n                  points=\"50,20 50,80\"\n                />\n                <polyline\n                  points=\"30,60 50,80 70,60\"\n                />\n              </svg>\n            </span>\n          </div>\n          <div\n            class=\"octo-spacer\"\n          />\n        </div>\n        <div\n          class=\"octo-table-cell header-cell\"\n          draggable=\"true\"\n          style=\"overflow: unset; opacity: 1; width: 100px;\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper disabled\"\n            role=\"button\"\n          >\n            <span\n              class=\"Label empty \"\n            >\n              Property 2\n              <svg\n                class=\"SortUpIcon Icon\"\n                viewBox=\"0 0 100 100\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n              >\n                <polyline\n                  points=\"50,20 50,80\"\n                />\n                <polyline\n                  points=\"30,40 50,20 70,40\"\n                />\n              </svg>\n            </span>\n          </div>\n          <div\n            class=\"octo-spacer\"\n          />\n        </div>\n        <div\n          class=\"octo-table-cell header-cell\"\n          draggable=\"true\"\n          style=\"overflow: unset; opacity: 1; width: 100px;\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper disabled\"\n            role=\"button\"\n          >\n            <span\n              class=\"Label empty \"\n            >\n              Status\n            </span>\n          </div>\n          <div\n            class=\"octo-spacer\"\n          />\n        </div>\n      </div>\n      <div\n        class=\"table-row-container\"\n      >\n        <div\n          class=\"TableRow octo-table-row readonly\"\n          draggable=\"true\"\n          style=\"opacity: 1;\"\n        >\n          <div\n            class=\"action-cell octo-table-cell-btn\"\n          />\n          <div\n            class=\"octo-table-cell title-cell\"\n            id=\"mainBoardHeader\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-icontitle\"\n            >\n              <div\n                class=\"octo-icon\"\n              >\n                i\n              </div>\n              <input\n                class=\"Editable readonly \"\n                placeholder=\"Untitled\"\n                readonly=\"\"\n                spellcheck=\"true\"\n                title=\"title\"\n                value=\"title\"\n              />\n            </div>\n            <div\n              class=\"open-button\"\n            >\n              <button\n                type=\"button\"\n              >\n                <span>\n                  Open\n                </span>\n              </button>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-cell\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-propertyvalue octo-propertyvalue--readonly\"\n              data-testid=\"select-non-editable\"\n              tabindex=\"0\"\n            >\n              <span\n                class=\"Label propColorBrown \"\n              >\n                <span\n                  class=\"Label-text\"\n                >\n                  value 1\n                </span>\n              </span>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-cell\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-propertyvalue octo-propertyvalue--readonly\"\n              data-testid=\"select-non-editable\"\n              tabindex=\"0\"\n            >\n              <span\n                class=\"Label empty \"\n              >\n                <span\n                  class=\"Label-text\"\n                />\n              </span>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-cell\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-propertyvalue octo-propertyvalue--readonly\"\n              data-testid=\"select-non-editable\"\n              tabindex=\"0\"\n            >\n              <span\n                class=\"Label empty \"\n              >\n                <span\n                  class=\"Label-text\"\n                />\n              </span>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"TableRow octo-table-row readonly\"\n          draggable=\"true\"\n          style=\"opacity: 1;\"\n        >\n          <div\n            class=\"action-cell octo-table-cell-btn\"\n          />\n          <div\n            class=\"octo-table-cell title-cell\"\n            id=\"mainBoardHeader\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-icontitle\"\n            >\n              <div\n                class=\"octo-icon\"\n              >\n                i\n              </div>\n              <input\n                class=\"Editable readonly \"\n                placeholder=\"Untitled\"\n                readonly=\"\"\n                spellcheck=\"true\"\n                title=\"title\"\n                value=\"title\"\n              />\n            </div>\n            <div\n              class=\"open-button\"\n            >\n              <button\n                type=\"button\"\n              >\n                <span>\n                  Open\n                </span>\n              </button>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-cell\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-propertyvalue octo-propertyvalue--readonly\"\n              data-testid=\"select-non-editable\"\n              tabindex=\"0\"\n            >\n              <span\n                class=\"Label propColorBrown \"\n              >\n                <span\n                  class=\"Label-text\"\n                >\n                  value 1\n                </span>\n              </span>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-cell\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-propertyvalue octo-propertyvalue--readonly\"\n              data-testid=\"select-non-editable\"\n              tabindex=\"0\"\n            >\n              <span\n                class=\"Label empty \"\n              >\n                <span\n                  class=\"Label-text\"\n                />\n              </span>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-cell\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-propertyvalue octo-propertyvalue--readonly\"\n              data-testid=\"select-non-editable\"\n              tabindex=\"0\"\n            >\n              <span\n                class=\"Label empty \"\n              >\n                <span\n                  class=\"Label-text\"\n                />\n              </span>\n            </div>\n          </div>\n        </div>\n      </div>\n      <div\n        class=\"octo-table-footer\"\n      />\n      <div\n        class=\"CalculationRow octo-table-row\"\n      >\n        <div\n          class=\"Calculation count octo-table-cell disabled  \"\n          style=\"width: 100px;\"\n          tabindex=\"0\"\n        >\n          <span\n            class=\"calculationLabel\"\n          >\n            Count\n          </span>\n          <span\n            class=\"calculationValue\"\n          >\n            2\n          </span>\n        </div>\n        <div\n          class=\"Calculation none octo-table-cell disabled  \"\n          style=\"width: 100px;\"\n          tabindex=\"0\"\n        >\n          <span\n            class=\"calculationLabel\"\n          >\n            Calculate\n          </span>\n          <i\n            class=\"CompassIcon icon-chevron-up ChevronUpIcon\"\n          />\n        </div>\n        <div\n          class=\"Calculation none octo-table-cell disabled  \"\n          style=\"width: 100px;\"\n          tabindex=\"0\"\n        >\n          <span\n            class=\"calculationLabel\"\n          >\n            Calculate\n          </span>\n          <i\n            class=\"CompassIcon icon-chevron-up ChevronUpIcon\"\n          />\n        </div>\n        <div\n          class=\"Calculation none octo-table-cell disabled  \"\n          style=\"width: 100px;\"\n          tabindex=\"0\"\n        >\n          <span\n            class=\"calculationLabel\"\n          >\n            Calculate\n          </span>\n          <i\n            class=\"CompassIcon icon-chevron-up ChevronUpIcon\"\n          />\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/table/Table extended should match snapshot with CreatedBy 1`] = `\n<div>\n  <div\n    class=\"Table\"\n  >\n    <div\n      class=\"octo-table-body\"\n    >\n      <div\n        class=\"octo-table-header TableHeaders\"\n        id=\"mainBoardHeader\"\n      >\n        <div\n          class=\"octo-table-cell header-cell\"\n          style=\"overflow: unset; opacity: 1; width: 100px;\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <span\n              class=\"Label empty \"\n            >\n              Name\n            </span>\n          </div>\n          <div\n            class=\"octo-spacer\"\n          />\n          <div\n            class=\"HorizontalGrip\"\n          />\n        </div>\n        <div\n          class=\"octo-table-cell header-cell\"\n          draggable=\"true\"\n          style=\"overflow: unset; opacity: 1; width: 100px;\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <span\n              class=\"Label empty \"\n            >\n              Property 1\n              <svg\n                class=\"SortDownIcon Icon\"\n                viewBox=\"0 0 100 100\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n              >\n                <polyline\n                  points=\"50,20 50,80\"\n                />\n                <polyline\n                  points=\"30,60 50,80 70,60\"\n                />\n              </svg>\n            </span>\n          </div>\n          <div\n            class=\"octo-spacer\"\n          />\n          <div\n            class=\"HorizontalGrip\"\n          />\n        </div>\n        <div\n          class=\"octo-table-cell header-cell\"\n          draggable=\"true\"\n          style=\"overflow: unset; opacity: 1; width: 100px;\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <span\n              class=\"Label empty \"\n            >\n              Property 2\n              <svg\n                class=\"SortUpIcon Icon\"\n                viewBox=\"0 0 100 100\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n              >\n                <polyline\n                  points=\"50,20 50,80\"\n                />\n                <polyline\n                  points=\"30,40 50,20 70,40\"\n                />\n              </svg>\n            </span>\n          </div>\n          <div\n            class=\"octo-spacer\"\n          />\n          <div\n            class=\"HorizontalGrip\"\n          />\n        </div>\n        <div\n          class=\"octo-table-cell header-cell\"\n          draggable=\"true\"\n          style=\"overflow: unset; opacity: 1; width: 100px;\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <span\n              class=\"Label empty \"\n            >\n              Status\n            </span>\n          </div>\n          <div\n            class=\"octo-spacer\"\n          />\n          <div\n            class=\"HorizontalGrip\"\n          />\n        </div>\n      </div>\n      <div\n        class=\"table-row-container\"\n      >\n        <div\n          class=\"TableRow octo-table-row\"\n          draggable=\"true\"\n          style=\"opacity: 1;\"\n        >\n          <div\n            class=\"action-cell octo-table-cell-btn\"\n          >\n            <button\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-drag-vertical\"\n              />\n            </button>\n          </div>\n          <div\n            class=\"octo-table-cell title-cell\"\n            id=\"mainBoardHeader\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-icontitle\"\n            >\n              <div\n                class=\"octo-icon\"\n              >\n                i\n              </div>\n              <input\n                class=\"Editable \"\n                placeholder=\"Untitled\"\n                spellcheck=\"true\"\n                title=\"title\"\n                value=\"title\"\n              />\n            </div>\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper optionsMenu ml-2 mr-2\"\n              role=\"button\"\n            >\n              <div\n                class=\"octo-tooltip tooltip-top\"\n                data-tooltip=\"More actions\"\n              >\n                <button\n                  aria-label=\"MenuBtn\"\n                  title=\"MenuBtn\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n            </div>\n            <div\n              class=\"open-button\"\n            >\n              <button\n                type=\"button\"\n              >\n                <span>\n                  Open\n                </span>\n              </button>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-cell\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-propertyvalue\"\n              data-testid=\"select-non-editable\"\n              tabindex=\"0\"\n            >\n              <span\n                class=\"Label propColorBrown \"\n              >\n                <span\n                  class=\"Label-text\"\n                >\n                  value 1\n                </span>\n              </span>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-cell\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-propertyvalue\"\n              data-testid=\"select-non-editable\"\n              tabindex=\"0\"\n            >\n              <span\n                class=\"Label empty \"\n              >\n                <span\n                  class=\"Label-text\"\n                />\n              </span>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-cell\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-propertyvalue\"\n              data-testid=\"select-non-editable\"\n              tabindex=\"0\"\n            >\n              <span\n                class=\"Label empty \"\n              >\n                <span\n                  class=\"Label-text\"\n                />\n              </span>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"TableRow octo-table-row\"\n          draggable=\"true\"\n          style=\"opacity: 1;\"\n        >\n          <div\n            class=\"action-cell octo-table-cell-btn\"\n          >\n            <button\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-drag-vertical\"\n              />\n            </button>\n          </div>\n          <div\n            class=\"octo-table-cell title-cell\"\n            id=\"mainBoardHeader\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-icontitle\"\n            >\n              <div\n                class=\"octo-icon\"\n              >\n                i\n              </div>\n              <input\n                class=\"Editable \"\n                placeholder=\"Untitled\"\n                spellcheck=\"true\"\n                title=\"title\"\n                value=\"title\"\n              />\n            </div>\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper optionsMenu ml-2 mr-2\"\n              role=\"button\"\n            >\n              <div\n                class=\"octo-tooltip tooltip-top\"\n                data-tooltip=\"More actions\"\n              >\n                <button\n                  aria-label=\"MenuBtn\"\n                  title=\"MenuBtn\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n            </div>\n            <div\n              class=\"open-button\"\n            >\n              <button\n                type=\"button\"\n              >\n                <span>\n                  Open\n                </span>\n              </button>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-cell\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-propertyvalue\"\n              data-testid=\"select-non-editable\"\n              tabindex=\"0\"\n            >\n              <span\n                class=\"Label propColorBrown \"\n              >\n                <span\n                  class=\"Label-text\"\n                >\n                  value 1\n                </span>\n              </span>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-cell\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-propertyvalue\"\n              data-testid=\"select-non-editable\"\n              tabindex=\"0\"\n            >\n              <span\n                class=\"Label empty \"\n              >\n                <span\n                  class=\"Label-text\"\n                />\n              </span>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-cell\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-propertyvalue\"\n              data-testid=\"select-non-editable\"\n              tabindex=\"0\"\n            >\n              <span\n                class=\"Label empty \"\n              >\n                <span\n                  class=\"Label-text\"\n                />\n              </span>\n            </div>\n          </div>\n        </div>\n      </div>\n      <div\n        class=\"octo-table-footer\"\n      >\n        <div\n          class=\"octo-table-cell\"\n        >\n          + New\n        </div>\n      </div>\n      <div\n        class=\"CalculationRow octo-table-row\"\n      >\n        <div\n          class=\"Calculation count octo-table-cell   \"\n          style=\"width: 100px;\"\n          tabindex=\"0\"\n        >\n          <span\n            class=\"calculationLabel\"\n          >\n            Count\n          </span>\n          <span\n            class=\"calculationValue\"\n          >\n            2\n          </span>\n        </div>\n        <div\n          class=\"Calculation none octo-table-cell   \"\n          style=\"width: 100px;\"\n          tabindex=\"0\"\n        >\n          <span\n            class=\"calculationLabel\"\n          >\n            Calculate\n          </span>\n          <i\n            class=\"CompassIcon icon-chevron-up ChevronUpIcon\"\n          />\n        </div>\n        <div\n          class=\"Calculation none octo-table-cell   \"\n          style=\"width: 100px;\"\n          tabindex=\"0\"\n        >\n          <span\n            class=\"calculationLabel\"\n          >\n            Calculate\n          </span>\n          <i\n            class=\"CompassIcon icon-chevron-up ChevronUpIcon\"\n          />\n        </div>\n        <div\n          class=\"Calculation none octo-table-cell   \"\n          style=\"width: 100px;\"\n          tabindex=\"0\"\n        >\n          <span\n            class=\"calculationLabel\"\n          >\n            Calculate\n          </span>\n          <i\n            class=\"CompassIcon icon-chevron-up ChevronUpIcon\"\n          />\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/table/Table extended should match snapshot with UpdatedAt 1`] = `\n<div>\n  <div\n    class=\"Table\"\n  >\n    <div\n      class=\"octo-table-body\"\n    >\n      <div\n        class=\"octo-table-header TableHeaders\"\n        id=\"mainBoardHeader\"\n      >\n        <div\n          class=\"octo-table-cell header-cell\"\n          style=\"overflow: unset; opacity: 1; width: 100px;\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <span\n              class=\"Label empty \"\n            >\n              Name\n            </span>\n          </div>\n          <div\n            class=\"octo-spacer\"\n          />\n          <div\n            class=\"HorizontalGrip\"\n          />\n        </div>\n        <div\n          class=\"octo-table-cell header-cell\"\n          draggable=\"true\"\n          style=\"overflow: unset; opacity: 1; width: 100px;\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <span\n              class=\"Label empty \"\n            >\n              Property 1\n              <svg\n                class=\"SortDownIcon Icon\"\n                viewBox=\"0 0 100 100\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n              >\n                <polyline\n                  points=\"50,20 50,80\"\n                />\n                <polyline\n                  points=\"30,60 50,80 70,60\"\n                />\n              </svg>\n            </span>\n          </div>\n          <div\n            class=\"octo-spacer\"\n          />\n          <div\n            class=\"HorizontalGrip\"\n          />\n        </div>\n        <div\n          class=\"octo-table-cell header-cell\"\n          draggable=\"true\"\n          style=\"overflow: unset; opacity: 1; width: 100px;\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <span\n              class=\"Label empty \"\n            >\n              Property 2\n              <svg\n                class=\"SortUpIcon Icon\"\n                viewBox=\"0 0 100 100\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n              >\n                <polyline\n                  points=\"50,20 50,80\"\n                />\n                <polyline\n                  points=\"30,40 50,20 70,40\"\n                />\n              </svg>\n            </span>\n          </div>\n          <div\n            class=\"octo-spacer\"\n          />\n          <div\n            class=\"HorizontalGrip\"\n          />\n        </div>\n        <div\n          class=\"octo-table-cell header-cell\"\n          draggable=\"true\"\n          style=\"overflow: unset; opacity: 1; width: 100px;\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <span\n              class=\"Label empty \"\n            >\n              Status\n            </span>\n          </div>\n          <div\n            class=\"octo-spacer\"\n          />\n          <div\n            class=\"HorizontalGrip\"\n          />\n        </div>\n      </div>\n      <div\n        class=\"table-row-container\"\n      >\n        <div\n          class=\"TableRow octo-table-row\"\n          draggable=\"true\"\n          style=\"opacity: 1;\"\n        >\n          <div\n            class=\"action-cell octo-table-cell-btn\"\n          >\n            <button\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-drag-vertical\"\n              />\n            </button>\n          </div>\n          <div\n            class=\"octo-table-cell title-cell\"\n            id=\"mainBoardHeader\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-icontitle\"\n            >\n              <div\n                class=\"octo-icon\"\n              >\n                i\n              </div>\n              <input\n                class=\"Editable \"\n                placeholder=\"Untitled\"\n                spellcheck=\"true\"\n                title=\"title\"\n                value=\"title\"\n              />\n            </div>\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper optionsMenu ml-2 mr-2\"\n              role=\"button\"\n            >\n              <div\n                class=\"octo-tooltip tooltip-top\"\n                data-tooltip=\"More actions\"\n              >\n                <button\n                  aria-label=\"MenuBtn\"\n                  title=\"MenuBtn\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n            </div>\n            <div\n              class=\"open-button\"\n            >\n              <button\n                type=\"button\"\n              >\n                <span>\n                  Open\n                </span>\n              </button>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-cell\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-propertyvalue\"\n              data-testid=\"select-non-editable\"\n              tabindex=\"0\"\n            >\n              <span\n                class=\"Label propColorBrown \"\n              >\n                <span\n                  class=\"Label-text\"\n                >\n                  value 1\n                </span>\n              </span>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-cell\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-propertyvalue\"\n              data-testid=\"select-non-editable\"\n              tabindex=\"0\"\n            >\n              <span\n                class=\"Label empty \"\n              >\n                <span\n                  class=\"Label-text\"\n                />\n              </span>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-cell\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-propertyvalue\"\n              data-testid=\"select-non-editable\"\n              tabindex=\"0\"\n            >\n              <span\n                class=\"Label empty \"\n              >\n                <span\n                  class=\"Label-text\"\n                />\n              </span>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"TableRow octo-table-row\"\n          draggable=\"true\"\n          style=\"opacity: 1;\"\n        >\n          <div\n            class=\"action-cell octo-table-cell-btn\"\n          >\n            <button\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-drag-vertical\"\n              />\n            </button>\n          </div>\n          <div\n            class=\"octo-table-cell title-cell\"\n            id=\"mainBoardHeader\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-icontitle\"\n            >\n              <div\n                class=\"octo-icon\"\n              >\n                i\n              </div>\n              <input\n                class=\"Editable \"\n                placeholder=\"Untitled\"\n                spellcheck=\"true\"\n                title=\"title\"\n                value=\"title\"\n              />\n            </div>\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper optionsMenu ml-2 mr-2\"\n              role=\"button\"\n            >\n              <div\n                class=\"octo-tooltip tooltip-top\"\n                data-tooltip=\"More actions\"\n              >\n                <button\n                  aria-label=\"MenuBtn\"\n                  title=\"MenuBtn\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n            </div>\n            <div\n              class=\"open-button\"\n            >\n              <button\n                type=\"button\"\n              >\n                <span>\n                  Open\n                </span>\n              </button>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-cell\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-propertyvalue\"\n              data-testid=\"select-non-editable\"\n              tabindex=\"0\"\n            >\n              <span\n                class=\"Label propColorBrown \"\n              >\n                <span\n                  class=\"Label-text\"\n                >\n                  value 1\n                </span>\n              </span>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-cell\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-propertyvalue\"\n              data-testid=\"select-non-editable\"\n              tabindex=\"0\"\n            >\n              <span\n                class=\"Label empty \"\n              >\n                <span\n                  class=\"Label-text\"\n                />\n              </span>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-cell\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-propertyvalue\"\n              data-testid=\"select-non-editable\"\n              tabindex=\"0\"\n            >\n              <span\n                class=\"Label empty \"\n              >\n                <span\n                  class=\"Label-text\"\n                />\n              </span>\n            </div>\n          </div>\n        </div>\n      </div>\n      <div\n        class=\"octo-table-footer\"\n      >\n        <div\n          class=\"octo-table-cell\"\n        >\n          + New\n        </div>\n      </div>\n      <div\n        class=\"CalculationRow octo-table-row\"\n      >\n        <div\n          class=\"Calculation count octo-table-cell   \"\n          style=\"width: 100px;\"\n          tabindex=\"0\"\n        >\n          <span\n            class=\"calculationLabel\"\n          >\n            Count\n          </span>\n          <span\n            class=\"calculationValue\"\n          >\n            2\n          </span>\n        </div>\n        <div\n          class=\"Calculation none octo-table-cell   \"\n          style=\"width: 100px;\"\n          tabindex=\"0\"\n        >\n          <span\n            class=\"calculationLabel\"\n          >\n            Calculate\n          </span>\n          <i\n            class=\"CompassIcon icon-chevron-up ChevronUpIcon\"\n          />\n        </div>\n        <div\n          class=\"Calculation none octo-table-cell   \"\n          style=\"width: 100px;\"\n          tabindex=\"0\"\n        >\n          <span\n            class=\"calculationLabel\"\n          >\n            Calculate\n          </span>\n          <i\n            class=\"CompassIcon icon-chevron-up ChevronUpIcon\"\n          />\n        </div>\n        <div\n          class=\"Calculation none octo-table-cell   \"\n          style=\"width: 100px;\"\n          tabindex=\"0\"\n        >\n          <span\n            class=\"calculationLabel\"\n          >\n            Calculate\n          </span>\n          <i\n            class=\"CompassIcon icon-chevron-up ChevronUpIcon\"\n          />\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/table/Table extended should match snapshot with UpdatedBy 1`] = `\n<div>\n  <div\n    class=\"Table\"\n  >\n    <div\n      class=\"octo-table-body\"\n    >\n      <div\n        class=\"octo-table-header TableHeaders\"\n        id=\"mainBoardHeader\"\n      >\n        <div\n          class=\"octo-table-cell header-cell\"\n          style=\"overflow: unset; opacity: 1; width: 100px;\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <span\n              class=\"Label empty \"\n            >\n              Name\n            </span>\n          </div>\n          <div\n            class=\"octo-spacer\"\n          />\n          <div\n            class=\"HorizontalGrip\"\n          />\n        </div>\n        <div\n          class=\"octo-table-cell header-cell\"\n          draggable=\"true\"\n          style=\"overflow: unset; opacity: 1; width: 100px;\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <span\n              class=\"Label empty \"\n            >\n              Property 1\n              <svg\n                class=\"SortDownIcon Icon\"\n                viewBox=\"0 0 100 100\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n              >\n                <polyline\n                  points=\"50,20 50,80\"\n                />\n                <polyline\n                  points=\"30,60 50,80 70,60\"\n                />\n              </svg>\n            </span>\n          </div>\n          <div\n            class=\"octo-spacer\"\n          />\n          <div\n            class=\"HorizontalGrip\"\n          />\n        </div>\n        <div\n          class=\"octo-table-cell header-cell\"\n          draggable=\"true\"\n          style=\"overflow: unset; opacity: 1; width: 100px;\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <span\n              class=\"Label empty \"\n            >\n              Property 2\n              <svg\n                class=\"SortUpIcon Icon\"\n                viewBox=\"0 0 100 100\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n              >\n                <polyline\n                  points=\"50,20 50,80\"\n                />\n                <polyline\n                  points=\"30,40 50,20 70,40\"\n                />\n              </svg>\n            </span>\n          </div>\n          <div\n            class=\"octo-spacer\"\n          />\n          <div\n            class=\"HorizontalGrip\"\n          />\n        </div>\n        <div\n          class=\"octo-table-cell header-cell\"\n          draggable=\"true\"\n          style=\"overflow: unset; opacity: 1; width: 100px;\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper\"\n            role=\"button\"\n          >\n            <span\n              class=\"Label empty \"\n            >\n              Status\n            </span>\n          </div>\n          <div\n            class=\"octo-spacer\"\n          />\n          <div\n            class=\"HorizontalGrip\"\n          />\n        </div>\n      </div>\n      <div\n        class=\"table-row-container\"\n      >\n        <div\n          class=\"TableRow octo-table-row\"\n          draggable=\"true\"\n          style=\"opacity: 1;\"\n        >\n          <div\n            class=\"action-cell octo-table-cell-btn\"\n          >\n            <button\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-drag-vertical\"\n              />\n            </button>\n          </div>\n          <div\n            class=\"octo-table-cell title-cell\"\n            id=\"mainBoardHeader\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-icontitle\"\n            >\n              <div\n                class=\"octo-icon\"\n              >\n                i\n              </div>\n              <input\n                class=\"Editable \"\n                placeholder=\"Untitled\"\n                spellcheck=\"true\"\n                title=\"title\"\n                value=\"title\"\n              />\n            </div>\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper optionsMenu ml-2 mr-2\"\n              role=\"button\"\n            >\n              <div\n                class=\"octo-tooltip tooltip-top\"\n                data-tooltip=\"More actions\"\n              >\n                <button\n                  aria-label=\"MenuBtn\"\n                  title=\"MenuBtn\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n            </div>\n            <div\n              class=\"open-button\"\n            >\n              <button\n                type=\"button\"\n              >\n                <span>\n                  Open\n                </span>\n              </button>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-cell\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-propertyvalue\"\n              data-testid=\"select-non-editable\"\n              tabindex=\"0\"\n            >\n              <span\n                class=\"Label propColorBrown \"\n              >\n                <span\n                  class=\"Label-text\"\n                >\n                  value 1\n                </span>\n              </span>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-cell\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-propertyvalue\"\n              data-testid=\"select-non-editable\"\n              tabindex=\"0\"\n            >\n              <span\n                class=\"Label empty \"\n              >\n                <span\n                  class=\"Label-text\"\n                />\n              </span>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-cell\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-propertyvalue\"\n              data-testid=\"select-non-editable\"\n              tabindex=\"0\"\n            >\n              <span\n                class=\"Label empty \"\n              >\n                <span\n                  class=\"Label-text\"\n                />\n              </span>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"TableRow octo-table-row\"\n          draggable=\"true\"\n          style=\"opacity: 1;\"\n        >\n          <div\n            class=\"action-cell octo-table-cell-btn\"\n          >\n            <button\n              type=\"button\"\n            >\n              <i\n                class=\"CompassIcon icon-drag-vertical\"\n              />\n            </button>\n          </div>\n          <div\n            class=\"octo-table-cell title-cell\"\n            id=\"mainBoardHeader\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-icontitle\"\n            >\n              <div\n                class=\"octo-icon\"\n              >\n                i\n              </div>\n              <input\n                class=\"Editable \"\n                placeholder=\"Untitled\"\n                spellcheck=\"true\"\n                title=\"title\"\n                value=\"title\"\n              />\n            </div>\n            <div\n              aria-label=\"menuwrapper\"\n              class=\"MenuWrapper optionsMenu ml-2 mr-2\"\n              role=\"button\"\n            >\n              <div\n                class=\"octo-tooltip tooltip-top\"\n                data-tooltip=\"More actions\"\n              >\n                <button\n                  aria-label=\"MenuBtn\"\n                  title=\"MenuBtn\"\n                  type=\"button\"\n                >\n                  <i\n                    class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                  />\n                </button>\n              </div>\n            </div>\n            <div\n              class=\"open-button\"\n            >\n              <button\n                type=\"button\"\n              >\n                <span>\n                  Open\n                </span>\n              </button>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-cell\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-propertyvalue\"\n              data-testid=\"select-non-editable\"\n              tabindex=\"0\"\n            >\n              <span\n                class=\"Label propColorBrown \"\n              >\n                <span\n                  class=\"Label-text\"\n                >\n                  value 1\n                </span>\n              </span>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-cell\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-propertyvalue\"\n              data-testid=\"select-non-editable\"\n              tabindex=\"0\"\n            >\n              <span\n                class=\"Label empty \"\n              >\n                <span\n                  class=\"Label-text\"\n                />\n              </span>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-cell\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-propertyvalue\"\n              data-testid=\"select-non-editable\"\n              tabindex=\"0\"\n            >\n              <span\n                class=\"Label empty \"\n              >\n                <span\n                  class=\"Label-text\"\n                />\n              </span>\n            </div>\n          </div>\n        </div>\n      </div>\n      <div\n        class=\"octo-table-footer\"\n      >\n        <div\n          class=\"octo-table-cell\"\n        >\n          + New\n        </div>\n      </div>\n      <div\n        class=\"CalculationRow octo-table-row\"\n      >\n        <div\n          class=\"Calculation count octo-table-cell   \"\n          style=\"width: 100px;\"\n          tabindex=\"0\"\n        >\n          <span\n            class=\"calculationLabel\"\n          >\n            Count\n          </span>\n          <span\n            class=\"calculationValue\"\n          >\n            2\n          </span>\n        </div>\n        <div\n          class=\"Calculation none octo-table-cell   \"\n          style=\"width: 100px;\"\n          tabindex=\"0\"\n        >\n          <span\n            class=\"calculationLabel\"\n          >\n            Calculate\n          </span>\n          <i\n            class=\"CompassIcon icon-chevron-up ChevronUpIcon\"\n          />\n        </div>\n        <div\n          class=\"Calculation none octo-table-cell   \"\n          style=\"width: 100px;\"\n          tabindex=\"0\"\n        >\n          <span\n            class=\"calculationLabel\"\n          >\n            Calculate\n          </span>\n          <i\n            class=\"CompassIcon icon-chevron-up ChevronUpIcon\"\n          />\n        </div>\n        <div\n          class=\"Calculation none octo-table-cell   \"\n          style=\"width: 100px;\"\n          tabindex=\"0\"\n        >\n          <span\n            class=\"calculationLabel\"\n          >\n            Calculate\n          </span>\n          <i\n            class=\"CompassIcon icon-chevron-up ChevronUpIcon\"\n          />\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/table/Table limited card in table view 1`] = `\n<div>\n  <div\n    class=\"Table\"\n  >\n    <div\n      class=\"octo-table-body\"\n    >\n      <div\n        class=\"octo-table-header TableHeaders\"\n        id=\"mainBoardHeader\"\n      >\n        <div\n          class=\"octo-table-cell header-cell\"\n          style=\"overflow: unset; opacity: 1; width: 100px;\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper disabled\"\n            role=\"button\"\n          >\n            <span\n              class=\"Label empty \"\n            >\n              Name\n            </span>\n          </div>\n          <div\n            class=\"octo-spacer\"\n          />\n        </div>\n        <div\n          class=\"octo-table-cell header-cell\"\n          draggable=\"true\"\n          style=\"overflow: unset; opacity: 1; width: 100px;\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper disabled\"\n            role=\"button\"\n          >\n            <span\n              class=\"Label empty \"\n            >\n              Property 1\n              <svg\n                class=\"SortDownIcon Icon\"\n                viewBox=\"0 0 100 100\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n              >\n                <polyline\n                  points=\"50,20 50,80\"\n                />\n                <polyline\n                  points=\"30,60 50,80 70,60\"\n                />\n              </svg>\n            </span>\n          </div>\n          <div\n            class=\"octo-spacer\"\n          />\n        </div>\n        <div\n          class=\"octo-table-cell header-cell\"\n          draggable=\"true\"\n          style=\"overflow: unset; opacity: 1; width: 100px;\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper disabled\"\n            role=\"button\"\n          >\n            <span\n              class=\"Label empty \"\n            >\n              Property 2\n              <svg\n                class=\"SortUpIcon Icon\"\n                viewBox=\"0 0 100 100\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n              >\n                <polyline\n                  points=\"50,20 50,80\"\n                />\n                <polyline\n                  points=\"30,40 50,20 70,40\"\n                />\n              </svg>\n            </span>\n          </div>\n          <div\n            class=\"octo-spacer\"\n          />\n        </div>\n      </div>\n      <div\n        class=\"table-row-container\"\n      >\n        <div\n          class=\"TableRow octo-table-row readonly\"\n          draggable=\"true\"\n          style=\"opacity: 1;\"\n        >\n          <div\n            class=\"action-cell octo-table-cell-btn\"\n          />\n          <div\n            class=\"octo-table-cell title-cell\"\n            id=\"mainBoardHeader\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-icontitle\"\n            >\n              <div\n                class=\"octo-icon\"\n              >\n                i\n              </div>\n              <input\n                class=\"Editable readonly \"\n                placeholder=\"Untitled\"\n                readonly=\"\"\n                spellcheck=\"true\"\n                title=\"title\"\n                value=\"title\"\n              />\n            </div>\n            <div\n              class=\"open-button\"\n            >\n              <button\n                type=\"button\"\n              >\n                <span>\n                  Open\n                </span>\n              </button>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-cell\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-propertyvalue octo-propertyvalue--readonly\"\n              data-testid=\"select-non-editable\"\n              tabindex=\"0\"\n            >\n              <span\n                class=\"Label propColorBrown \"\n              >\n                <span\n                  class=\"Label-text\"\n                >\n                  value 1\n                </span>\n              </span>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-cell\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-propertyvalue octo-propertyvalue--readonly\"\n              data-testid=\"select-non-editable\"\n              tabindex=\"0\"\n            >\n              <span\n                class=\"Label empty \"\n              >\n                <span\n                  class=\"Label-text\"\n                />\n              </span>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"TableRow octo-table-row readonly\"\n          draggable=\"true\"\n          style=\"opacity: 1;\"\n        >\n          <div\n            class=\"action-cell octo-table-cell-btn\"\n          />\n          <div\n            class=\"octo-table-cell title-cell\"\n            id=\"mainBoardHeader\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-icontitle\"\n            >\n              <div\n                class=\"octo-icon\"\n              >\n                i\n              </div>\n              <input\n                class=\"Editable readonly \"\n                placeholder=\"Untitled\"\n                readonly=\"\"\n                spellcheck=\"true\"\n                title=\"title\"\n                value=\"title\"\n              />\n            </div>\n            <div\n              class=\"open-button\"\n            >\n              <button\n                type=\"button\"\n              >\n                <span>\n                  Open\n                </span>\n              </button>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-cell\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-propertyvalue octo-propertyvalue--readonly\"\n              data-testid=\"select-non-editable\"\n              tabindex=\"0\"\n            >\n              <span\n                class=\"Label propColorBrown \"\n              >\n                <span\n                  class=\"Label-text\"\n                >\n                  value 1\n                </span>\n              </span>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-cell\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-propertyvalue octo-propertyvalue--readonly\"\n              data-testid=\"select-non-editable\"\n              tabindex=\"0\"\n            >\n              <span\n                class=\"Label empty \"\n              >\n                <span\n                  class=\"Label-text\"\n                />\n              </span>\n            </div>\n          </div>\n        </div>\n      </div>\n      <div\n        class=\"octo-table-footer\"\n      />\n      <div\n        class=\"CalculationRow octo-table-row\"\n      >\n        <div\n          class=\"Calculation count octo-table-cell disabled  \"\n          style=\"width: 100px;\"\n          tabindex=\"0\"\n        >\n          <span\n            class=\"calculationLabel\"\n          >\n            Count\n          </span>\n          <span\n            class=\"calculationValue\"\n          >\n            2\n          </span>\n        </div>\n        <div\n          class=\"Calculation none octo-table-cell disabled  \"\n          style=\"width: 100px;\"\n          tabindex=\"0\"\n        >\n          <span\n            class=\"calculationLabel\"\n          >\n            Calculate\n          </span>\n          <i\n            class=\"CompassIcon icon-chevron-up ChevronUpIcon\"\n          />\n        </div>\n        <div\n          class=\"Calculation none octo-table-cell disabled  \"\n          style=\"width: 100px;\"\n          tabindex=\"0\"\n        >\n          <span\n            class=\"calculationLabel\"\n          >\n            Calculate\n          </span>\n          <i\n            class=\"CompassIcon icon-chevron-up ChevronUpIcon\"\n          />\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"HiddenCardCount\"\n    >\n      <div\n        class=\"hidden-card-title\"\n      >\n        Cards hidden\n      </div>\n      <button\n        title=\"hidden-card-count\"\n        type=\"button\"\n      >\n        <span>\n          2\n        </span>\n      </button>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/table/Table should match snapshot 1`] = `\n<div>\n  <div\n    class=\"Table\"\n  >\n    <div\n      class=\"octo-table-body\"\n    >\n      <div\n        class=\"octo-table-header TableHeaders\"\n        id=\"mainBoardHeader\"\n      >\n        <div\n          class=\"octo-table-cell header-cell\"\n          style=\"overflow: unset; opacity: 1; width: 100px;\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper disabled\"\n            role=\"button\"\n          >\n            <span\n              class=\"Label empty \"\n            >\n              Name\n            </span>\n          </div>\n          <div\n            class=\"octo-spacer\"\n          />\n        </div>\n        <div\n          class=\"octo-table-cell header-cell\"\n          draggable=\"true\"\n          style=\"overflow: unset; opacity: 1; width: 100px;\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper disabled\"\n            role=\"button\"\n          >\n            <span\n              class=\"Label empty \"\n            >\n              Property 1\n              <svg\n                class=\"SortDownIcon Icon\"\n                viewBox=\"0 0 100 100\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n              >\n                <polyline\n                  points=\"50,20 50,80\"\n                />\n                <polyline\n                  points=\"30,60 50,80 70,60\"\n                />\n              </svg>\n            </span>\n          </div>\n          <div\n            class=\"octo-spacer\"\n          />\n        </div>\n        <div\n          class=\"octo-table-cell header-cell\"\n          draggable=\"true\"\n          style=\"overflow: unset; opacity: 1; width: 100px;\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper disabled\"\n            role=\"button\"\n          >\n            <span\n              class=\"Label empty \"\n            >\n              Property 2\n              <svg\n                class=\"SortUpIcon Icon\"\n                viewBox=\"0 0 100 100\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n              >\n                <polyline\n                  points=\"50,20 50,80\"\n                />\n                <polyline\n                  points=\"30,40 50,20 70,40\"\n                />\n              </svg>\n            </span>\n          </div>\n          <div\n            class=\"octo-spacer\"\n          />\n        </div>\n      </div>\n      <div\n        class=\"table-row-container\"\n      >\n        <div\n          class=\"TableRow octo-table-row readonly\"\n          draggable=\"true\"\n          style=\"opacity: 1;\"\n        >\n          <div\n            class=\"action-cell octo-table-cell-btn\"\n          />\n          <div\n            class=\"octo-table-cell title-cell\"\n            id=\"mainBoardHeader\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-icontitle\"\n            >\n              <div\n                class=\"octo-icon\"\n              >\n                i\n              </div>\n              <input\n                class=\"Editable readonly \"\n                placeholder=\"Untitled\"\n                readonly=\"\"\n                spellcheck=\"true\"\n                title=\"title\"\n                value=\"title\"\n              />\n            </div>\n            <div\n              class=\"open-button\"\n            >\n              <button\n                type=\"button\"\n              >\n                <span>\n                  Open\n                </span>\n              </button>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-cell\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-propertyvalue octo-propertyvalue--readonly\"\n              data-testid=\"select-non-editable\"\n              tabindex=\"0\"\n            >\n              <span\n                class=\"Label propColorBrown \"\n              >\n                <span\n                  class=\"Label-text\"\n                >\n                  value 1\n                </span>\n              </span>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-cell\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-propertyvalue octo-propertyvalue--readonly\"\n              data-testid=\"select-non-editable\"\n              tabindex=\"0\"\n            >\n              <span\n                class=\"Label empty \"\n              >\n                <span\n                  class=\"Label-text\"\n                />\n              </span>\n            </div>\n          </div>\n        </div>\n      </div>\n      <div\n        class=\"octo-table-footer\"\n      />\n      <div\n        class=\"CalculationRow octo-table-row\"\n      >\n        <div\n          class=\"Calculation count octo-table-cell disabled  \"\n          style=\"width: 100px;\"\n          tabindex=\"0\"\n        >\n          <span\n            class=\"calculationLabel\"\n          >\n            Count\n          </span>\n          <span\n            class=\"calculationValue\"\n          >\n            1\n          </span>\n        </div>\n        <div\n          class=\"Calculation none octo-table-cell disabled  \"\n          style=\"width: 100px;\"\n          tabindex=\"0\"\n        >\n          <span\n            class=\"calculationLabel\"\n          >\n            Calculate\n          </span>\n          <i\n            class=\"CompassIcon icon-chevron-up ChevronUpIcon\"\n          />\n        </div>\n        <div\n          class=\"Calculation none octo-table-cell disabled  \"\n          style=\"width: 100px;\"\n          tabindex=\"0\"\n        >\n          <span\n            class=\"calculationLabel\"\n          >\n            Calculate\n          </span>\n          <i\n            class=\"CompassIcon icon-chevron-up ChevronUpIcon\"\n          />\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/table/Table should match snapshot with GroupBy 1`] = `\n<div>\n  <div\n    class=\"Table\"\n  >\n    <div\n      class=\"octo-table-body\"\n    >\n      <div\n        class=\"octo-table-header TableHeaders\"\n        id=\"mainBoardHeader\"\n      >\n        <div\n          class=\"octo-table-cell header-cell\"\n          style=\"overflow: unset; opacity: 1; width: 100px;\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper disabled\"\n            role=\"button\"\n          >\n            <span\n              class=\"Label empty \"\n            >\n              Name\n            </span>\n          </div>\n          <div\n            class=\"octo-spacer\"\n          />\n        </div>\n        <div\n          class=\"octo-table-cell header-cell\"\n          draggable=\"true\"\n          style=\"overflow: unset; opacity: 1; width: 100px;\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper disabled\"\n            role=\"button\"\n          >\n            <span\n              class=\"Label empty \"\n            >\n              Property 1\n              <svg\n                class=\"SortDownIcon Icon\"\n                viewBox=\"0 0 100 100\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n              >\n                <polyline\n                  points=\"50,20 50,80\"\n                />\n                <polyline\n                  points=\"30,60 50,80 70,60\"\n                />\n              </svg>\n            </span>\n          </div>\n          <div\n            class=\"octo-spacer\"\n          />\n        </div>\n        <div\n          class=\"octo-table-cell header-cell\"\n          draggable=\"true\"\n          style=\"overflow: unset; opacity: 1; width: 100px;\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper disabled\"\n            role=\"button\"\n          >\n            <span\n              class=\"Label empty \"\n            >\n              Property 2\n              <svg\n                class=\"SortUpIcon Icon\"\n                viewBox=\"0 0 100 100\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n              >\n                <polyline\n                  points=\"50,20 50,80\"\n                />\n                <polyline\n                  points=\"30,40 50,20 70,40\"\n                />\n              </svg>\n            </span>\n          </div>\n          <div\n            class=\"octo-spacer\"\n          />\n        </div>\n      </div>\n      <div\n        class=\"table-row-container\"\n      >\n        <div\n          class=\"octo-table-group\"\n        >\n          <div\n            class=\"octo-group-header-cell expanded\"\n            draggable=\"true\"\n            style=\"opacity: 1;\"\n          >\n            <div\n              class=\"octo-table-cell\"\n              style=\"width: 100px;\"\n            >\n              <button\n                type=\"button\"\n              >\n                <i\n                  class=\"CompassIcon icon-menu-right\"\n                />\n              </button>\n              <span\n                class=\"Label empty \"\n                title=\"Items with an empty Property 1 property will go here. This column cannot be removed.\"\n              >\n                No Property 1\n              </span>\n              \n            </div>\n            <button\n              type=\"button\"\n            >\n              <span>\n                0\n              </span>\n            </button>\n          </div>\n        </div>\n      </div>\n      <div\n        class=\"octo-table-footer\"\n      />\n      <div\n        class=\"CalculationRow octo-table-row\"\n      >\n        <div\n          class=\"Calculation count octo-table-cell disabled  \"\n          style=\"width: 100px;\"\n          tabindex=\"0\"\n        >\n          <span\n            class=\"calculationLabel\"\n          >\n            Count\n          </span>\n          <span\n            class=\"calculationValue\"\n          >\n            1\n          </span>\n        </div>\n        <div\n          class=\"Calculation none octo-table-cell disabled  \"\n          style=\"width: 100px;\"\n          tabindex=\"0\"\n        >\n          <span\n            class=\"calculationLabel\"\n          >\n            Calculate\n          </span>\n          <i\n            class=\"CompassIcon icon-chevron-up ChevronUpIcon\"\n          />\n        </div>\n        <div\n          class=\"Calculation none octo-table-cell disabled  \"\n          style=\"width: 100px;\"\n          tabindex=\"0\"\n        >\n          <span\n            class=\"calculationLabel\"\n          >\n            Calculate\n          </span>\n          <i\n            class=\"CompassIcon icon-chevron-up ChevronUpIcon\"\n          />\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/table/Table should match snapshot without permissions 1`] = `\n<div>\n  <div\n    class=\"Table\"\n  >\n    <div\n      class=\"octo-table-body\"\n    >\n      <div\n        class=\"octo-table-header TableHeaders\"\n        id=\"mainBoardHeader\"\n      >\n        <div\n          class=\"octo-table-cell header-cell\"\n          style=\"overflow: unset; opacity: 1; width: 100px;\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper disabled\"\n            role=\"button\"\n          >\n            <span\n              class=\"Label empty \"\n            >\n              Name\n            </span>\n          </div>\n          <div\n            class=\"octo-spacer\"\n          />\n        </div>\n        <div\n          class=\"octo-table-cell header-cell\"\n          draggable=\"true\"\n          style=\"overflow: unset; opacity: 1; width: 100px;\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper disabled\"\n            role=\"button\"\n          >\n            <span\n              class=\"Label empty \"\n            >\n              Property 1\n              <svg\n                class=\"SortDownIcon Icon\"\n                viewBox=\"0 0 100 100\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n              >\n                <polyline\n                  points=\"50,20 50,80\"\n                />\n                <polyline\n                  points=\"30,60 50,80 70,60\"\n                />\n              </svg>\n            </span>\n          </div>\n          <div\n            class=\"octo-spacer\"\n          />\n        </div>\n        <div\n          class=\"octo-table-cell header-cell\"\n          draggable=\"true\"\n          style=\"overflow: unset; opacity: 1; width: 100px;\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper disabled\"\n            role=\"button\"\n          >\n            <span\n              class=\"Label empty \"\n            >\n              Property 2\n              <svg\n                class=\"SortUpIcon Icon\"\n                viewBox=\"0 0 100 100\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n              >\n                <polyline\n                  points=\"50,20 50,80\"\n                />\n                <polyline\n                  points=\"30,40 50,20 70,40\"\n                />\n              </svg>\n            </span>\n          </div>\n          <div\n            class=\"octo-spacer\"\n          />\n        </div>\n      </div>\n      <div\n        class=\"table-row-container\"\n      >\n        <div\n          class=\"TableRow octo-table-row readonly\"\n          draggable=\"true\"\n          style=\"opacity: 1;\"\n        >\n          <div\n            class=\"action-cell octo-table-cell-btn\"\n          />\n          <div\n            class=\"octo-table-cell title-cell\"\n            id=\"mainBoardHeader\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-icontitle\"\n            >\n              <div\n                class=\"octo-icon\"\n              >\n                i\n              </div>\n              <input\n                class=\"Editable readonly \"\n                placeholder=\"Untitled\"\n                readonly=\"\"\n                spellcheck=\"true\"\n                title=\"title\"\n                value=\"title\"\n              />\n            </div>\n            <div\n              class=\"open-button\"\n            >\n              <button\n                type=\"button\"\n              >\n                <span>\n                  Open\n                </span>\n              </button>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-cell\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-propertyvalue octo-propertyvalue--readonly\"\n              data-testid=\"select-non-editable\"\n              tabindex=\"0\"\n            >\n              <span\n                class=\"Label propColorBrown \"\n              >\n                <span\n                  class=\"Label-text\"\n                >\n                  value 1\n                </span>\n              </span>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-cell\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-propertyvalue octo-propertyvalue--readonly\"\n              data-testid=\"select-non-editable\"\n              tabindex=\"0\"\n            >\n              <span\n                class=\"Label empty \"\n              >\n                <span\n                  class=\"Label-text\"\n                />\n              </span>\n            </div>\n          </div>\n        </div>\n      </div>\n      <div\n        class=\"octo-table-footer\"\n      />\n      <div\n        class=\"CalculationRow octo-table-row\"\n      >\n        <div\n          class=\"Calculation count octo-table-cell disabled  \"\n          style=\"width: 100px;\"\n          tabindex=\"0\"\n        >\n          <span\n            class=\"calculationLabel\"\n          >\n            Count\n          </span>\n          <span\n            class=\"calculationValue\"\n          >\n            1\n          </span>\n        </div>\n        <div\n          class=\"Calculation none octo-table-cell disabled  \"\n          style=\"width: 100px;\"\n          tabindex=\"0\"\n        >\n          <span\n            class=\"calculationLabel\"\n          >\n            Calculate\n          </span>\n          <i\n            class=\"CompassIcon icon-chevron-up ChevronUpIcon\"\n          />\n        </div>\n        <div\n          class=\"Calculation none octo-table-cell disabled  \"\n          style=\"width: 100px;\"\n          tabindex=\"0\"\n        >\n          <span\n            class=\"calculationLabel\"\n          >\n            Calculate\n          </span>\n          <i\n            class=\"CompassIcon icon-chevron-up ChevronUpIcon\"\n          />\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/table/Table should match snapshot, read-only 1`] = `\n<div>\n  <div\n    class=\"Table\"\n  >\n    <div\n      class=\"octo-table-body\"\n    >\n      <div\n        class=\"octo-table-header TableHeaders\"\n        id=\"mainBoardHeader\"\n      >\n        <div\n          class=\"octo-table-cell header-cell\"\n          style=\"overflow: unset; opacity: 1; width: 100px;\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper disabled\"\n            role=\"button\"\n          >\n            <span\n              class=\"Label empty \"\n            >\n              Name\n            </span>\n          </div>\n          <div\n            class=\"octo-spacer\"\n          />\n        </div>\n        <div\n          class=\"octo-table-cell header-cell\"\n          draggable=\"true\"\n          style=\"overflow: unset; opacity: 1; width: 100px;\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper disabled\"\n            role=\"button\"\n          >\n            <span\n              class=\"Label empty \"\n            >\n              Property 1\n              <svg\n                class=\"SortDownIcon Icon\"\n                viewBox=\"0 0 100 100\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n              >\n                <polyline\n                  points=\"50,20 50,80\"\n                />\n                <polyline\n                  points=\"30,60 50,80 70,60\"\n                />\n              </svg>\n            </span>\n          </div>\n          <div\n            class=\"octo-spacer\"\n          />\n        </div>\n        <div\n          class=\"octo-table-cell header-cell\"\n          draggable=\"true\"\n          style=\"overflow: unset; opacity: 1; width: 100px;\"\n        >\n          <div\n            aria-label=\"menuwrapper\"\n            class=\"MenuWrapper disabled\"\n            role=\"button\"\n          >\n            <span\n              class=\"Label empty \"\n            >\n              Property 2\n              <svg\n                class=\"SortUpIcon Icon\"\n                viewBox=\"0 0 100 100\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n              >\n                <polyline\n                  points=\"50,20 50,80\"\n                />\n                <polyline\n                  points=\"30,40 50,20 70,40\"\n                />\n              </svg>\n            </span>\n          </div>\n          <div\n            class=\"octo-spacer\"\n          />\n        </div>\n      </div>\n      <div\n        class=\"table-row-container\"\n      >\n        <div\n          class=\"TableRow octo-table-row readonly\"\n          draggable=\"true\"\n          style=\"opacity: 1;\"\n        >\n          <div\n            class=\"action-cell octo-table-cell-btn\"\n          />\n          <div\n            class=\"octo-table-cell title-cell\"\n            id=\"mainBoardHeader\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-icontitle\"\n            >\n              <div\n                class=\"octo-icon\"\n              >\n                i\n              </div>\n              <input\n                class=\"Editable readonly \"\n                placeholder=\"Untitled\"\n                readonly=\"\"\n                spellcheck=\"true\"\n                title=\"title\"\n                value=\"title\"\n              />\n            </div>\n            <div\n              class=\"open-button\"\n            >\n              <button\n                type=\"button\"\n              >\n                <span>\n                  Open\n                </span>\n              </button>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-cell\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-propertyvalue octo-propertyvalue--readonly\"\n              data-testid=\"select-non-editable\"\n              tabindex=\"0\"\n            >\n              <span\n                class=\"Label propColorBrown \"\n              >\n                <span\n                  class=\"Label-text\"\n                >\n                  value 1\n                </span>\n              </span>\n            </div>\n          </div>\n          <div\n            class=\"octo-table-cell\"\n            style=\"width: 100px;\"\n          >\n            <div\n              class=\"octo-propertyvalue octo-propertyvalue--readonly\"\n              data-testid=\"select-non-editable\"\n              tabindex=\"0\"\n            >\n              <span\n                class=\"Label empty \"\n              >\n                <span\n                  class=\"Label-text\"\n                />\n              </span>\n            </div>\n          </div>\n        </div>\n      </div>\n      <div\n        class=\"octo-table-footer\"\n      />\n      <div\n        class=\"CalculationRow octo-table-row\"\n      >\n        <div\n          class=\"Calculation count octo-table-cell disabled  \"\n          style=\"width: 100px;\"\n          tabindex=\"0\"\n        >\n          <span\n            class=\"calculationLabel\"\n          >\n            Count\n          </span>\n          <span\n            class=\"calculationValue\"\n          >\n            1\n          </span>\n        </div>\n        <div\n          class=\"Calculation none octo-table-cell disabled  \"\n          style=\"width: 100px;\"\n          tabindex=\"0\"\n        >\n          <span\n            class=\"calculationLabel\"\n          >\n            Calculate\n          </span>\n          <i\n            class=\"CompassIcon icon-chevron-up ChevronUpIcon\"\n          />\n        </div>\n        <div\n          class=\"Calculation none octo-table-cell disabled  \"\n          style=\"width: 100px;\"\n          tabindex=\"0\"\n        >\n          <span\n            class=\"calculationLabel\"\n          >\n            Calculate\n          </span>\n          <i\n            class=\"CompassIcon icon-chevron-up ChevronUpIcon\"\n          />\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/table/__snapshots__/tableGroupHeaderRow.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`should match snapshot on read only 1`] = `\n<div>\n  <div\n    class=\"octo-group-header-cell expanded\"\n    draggable=\"true\"\n    style=\"opacity: 1;\"\n  >\n    <div\n      class=\"octo-table-cell\"\n      style=\"width: 100px;\"\n    >\n      <button\n        class=\"IconButton octo-table-cell__expand readonly\"\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-menu-right\"\n        />\n      </button>\n      <span\n        class=\"Label propColorBrown \"\n      >\n        <input\n          class=\"Editable readonly \"\n          placeholder=\"New Select\"\n          readonly=\"\"\n          spellcheck=\"true\"\n          title=\"value 1\"\n          value=\"value 1\"\n        />\n      </span>\n    </div>\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        0\n      </span>\n    </button>\n  </div>\n</div>\n`;\n\nexports[`should match snapshot with Group 1`] = `\n<div>\n  <div\n    class=\"octo-group-header-cell expanded\"\n    draggable=\"true\"\n    style=\"opacity: 1;\"\n  >\n    <div\n      class=\"octo-table-cell\"\n      style=\"width: 100px;\"\n    >\n      <button\n        class=\"IconButton octo-table-cell__expand \"\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-menu-right\"\n        />\n      </button>\n      <span\n        class=\"Label propColorBrown \"\n      >\n        <input\n          class=\"Editable \"\n          placeholder=\"New Select\"\n          spellcheck=\"true\"\n          title=\"value 1\"\n          value=\"value 1\"\n        />\n      </span>\n    </div>\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        0\n      </span>\n    </button>\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper\"\n      role=\"button\"\n    >\n      <button\n        class=\"IconButton\"\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n        />\n      </button>\n    </div>\n    <button\n      class=\"IconButton\"\n      type=\"button\"\n    >\n      <i\n        class=\"CompassIcon icon-plus AddIcon\"\n      />\n    </button>\n  </div>\n</div>\n`;\n\nexports[`should match snapshot, add new 1`] = `\n<div>\n  <div\n    class=\"octo-group-header-cell expanded\"\n    draggable=\"true\"\n    style=\"opacity: 1;\"\n  >\n    <div\n      class=\"octo-table-cell\"\n      style=\"width: 100px;\"\n    >\n      <button\n        class=\"IconButton octo-table-cell__expand \"\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-menu-right\"\n        />\n      </button>\n      <span\n        class=\"Label propColorBrown \"\n      >\n        <input\n          class=\"Editable \"\n          placeholder=\"New Select\"\n          spellcheck=\"true\"\n          title=\"value 1\"\n          value=\"value 1\"\n        />\n      </span>\n    </div>\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        0\n      </span>\n    </button>\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper\"\n      role=\"button\"\n    >\n      <button\n        class=\"IconButton\"\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n        />\n      </button>\n    </div>\n    <button\n      class=\"IconButton\"\n      type=\"button\"\n    >\n      <i\n        class=\"CompassIcon icon-plus AddIcon\"\n      />\n    </button>\n  </div>\n</div>\n`;\n\nexports[`should match snapshot, edit title 1`] = `\n<div>\n  <div\n    class=\"octo-group-header-cell expanded\"\n    draggable=\"true\"\n    style=\"opacity: 1;\"\n  >\n    <div\n      class=\"octo-table-cell\"\n      style=\"width: 100px;\"\n    >\n      <button\n        class=\"IconButton octo-table-cell__expand \"\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-menu-right\"\n        />\n      </button>\n      <span\n        class=\"Label propColorBrown \"\n      >\n        <input\n          class=\"Editable \"\n          placeholder=\"New Select\"\n          spellcheck=\"true\"\n          title=\"value 1\"\n          value=\"value 1\"\n        />\n      </span>\n    </div>\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        0\n      </span>\n    </button>\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper\"\n      role=\"button\"\n    >\n      <button\n        class=\"IconButton\"\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n        />\n      </button>\n    </div>\n    <button\n      class=\"IconButton\"\n      type=\"button\"\n    >\n      <i\n        class=\"CompassIcon icon-plus AddIcon\"\n      />\n    </button>\n  </div>\n</div>\n`;\n\nexports[`should match snapshot, hide group 1`] = `\n<div>\n  <div\n    class=\"octo-group-header-cell\"\n    draggable=\"true\"\n    style=\"opacity: 1;\"\n  >\n    <div\n      class=\"octo-table-cell\"\n      style=\"width: 100px;\"\n    >\n      <button\n        class=\"IconButton octo-table-cell__expand \"\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-menu-right\"\n        />\n      </button>\n      <span\n        class=\"Label propColorBrown \"\n      >\n        <input\n          class=\"Editable \"\n          placeholder=\"New Select\"\n          spellcheck=\"true\"\n          title=\"value 1\"\n          value=\"value 1\"\n        />\n      </span>\n    </div>\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        0\n      </span>\n    </button>\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper\"\n      role=\"button\"\n    >\n      <button\n        class=\"IconButton\"\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n        />\n      </button>\n    </div>\n    <button\n      class=\"IconButton\"\n      type=\"button\"\n    >\n      <i\n        class=\"CompassIcon icon-plus AddIcon\"\n      />\n    </button>\n  </div>\n</div>\n`;\n\nexports[`should match snapshot, no groups 1`] = `\n<div>\n  <div\n    class=\"octo-group-header-cell expanded\"\n    draggable=\"true\"\n    style=\"opacity: 1;\"\n  >\n    <div\n      class=\"octo-table-cell\"\n      style=\"width: 100px;\"\n    >\n      <button\n        class=\"IconButton octo-table-cell__expand \"\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-menu-right\"\n        />\n      </button>\n      <span\n        class=\"Label empty \"\n        title=\"Items with an empty Property 1 property will go here. This column cannot be removed.\"\n      >\n        No Property 1\n      </span>\n      \n    </div>\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        0\n      </span>\n    </button>\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper\"\n      role=\"button\"\n    >\n      <button\n        class=\"IconButton\"\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n        />\n      </button>\n    </div>\n    <button\n      class=\"IconButton\"\n      type=\"button\"\n    >\n      <i\n        class=\"CompassIcon icon-plus AddIcon\"\n      />\n    </button>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/table/__snapshots__/tableHeader.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/table/TableHeaderMenu should match snapshot, title column 1`] = `\n<div>\n  <div\n    class=\"octo-table-cell header-cell\"\n    draggable=\"true\"\n    style=\"overflow: unset; opacity: 1; width: 100px;\"\n  >\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper\"\n      role=\"button\"\n    >\n      <span\n        class=\"Label empty \"\n      >\n        my Name\n      </span>\n    </div>\n    <div\n      class=\"octo-spacer\"\n    />\n    <div\n      class=\"HorizontalGrip\"\n    />\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/table/__snapshots__/tableHeaderMenu.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/table/TableHeaderMenu should match snapshot, other column 1`] = `\n<div>\n  <div\n    class=\"Menu noselect bottom \"\n  >\n    <div\n      class=\"menu-contents\"\n    >\n      <div\n        class=\"menu-options\"\n      >\n        <div>\n          <div\n            aria-label=\"Sort ascending\"\n            class=\"MenuOption TextOption menu-option\"\n            role=\"button\"\n          >\n            <div\n              class=\"d-flex\"\n            >\n              <div\n                class=\"noicon\"\n              />\n            </div>\n            <div\n              class=\"menu-option__content\"\n            >\n              <div\n                class=\"menu-name\"\n              >\n                Sort ascending\n              </div>\n            </div>\n            <div\n              class=\"noicon\"\n            />\n          </div>\n        </div>\n        <div>\n          <div\n            aria-label=\"Sort descending\"\n            class=\"MenuOption TextOption menu-option\"\n            role=\"button\"\n          >\n            <div\n              class=\"d-flex\"\n            >\n              <div\n                class=\"noicon\"\n              />\n            </div>\n            <div\n              class=\"menu-option__content\"\n            >\n              <div\n                class=\"menu-name\"\n              >\n                Sort descending\n              </div>\n            </div>\n            <div\n              class=\"noicon\"\n            />\n          </div>\n        </div>\n        <div>\n          <div\n            aria-label=\"Insert left\"\n            class=\"MenuOption TextOption menu-option\"\n            role=\"button\"\n          >\n            <div\n              class=\"d-flex\"\n            >\n              <div\n                class=\"noicon\"\n              />\n            </div>\n            <div\n              class=\"menu-option__content\"\n            >\n              <div\n                class=\"menu-name\"\n              >\n                Insert left\n              </div>\n            </div>\n            <div\n              class=\"noicon\"\n            />\n          </div>\n        </div>\n        <div>\n          <div\n            aria-label=\"Insert right\"\n            class=\"MenuOption TextOption menu-option\"\n            role=\"button\"\n          >\n            <div\n              class=\"d-flex\"\n            >\n              <div\n                class=\"noicon\"\n              />\n            </div>\n            <div\n              class=\"menu-option__content\"\n            >\n              <div\n                class=\"menu-name\"\n              >\n                Insert right\n              </div>\n            </div>\n            <div\n              class=\"noicon\"\n            />\n          </div>\n        </div>\n        <div>\n          <div\n            aria-label=\"Hide\"\n            class=\"MenuOption TextOption menu-option\"\n            role=\"button\"\n          >\n            <div\n              class=\"d-flex\"\n            >\n              <div\n                class=\"noicon\"\n              />\n            </div>\n            <div\n              class=\"menu-option__content\"\n            >\n              <div\n                class=\"menu-name\"\n              >\n                Hide\n              </div>\n            </div>\n            <div\n              class=\"noicon\"\n            />\n          </div>\n          <div\n            aria-label=\"Duplicate\"\n            class=\"MenuOption TextOption menu-option\"\n            role=\"button\"\n          >\n            <div\n              class=\"d-flex\"\n            >\n              <div\n                class=\"noicon\"\n              />\n            </div>\n            <div\n              class=\"menu-option__content\"\n            >\n              <div\n                class=\"menu-name\"\n              >\n                Duplicate\n              </div>\n            </div>\n            <div\n              class=\"noicon\"\n            />\n          </div>\n          <div\n            aria-label=\"Delete\"\n            class=\"MenuOption TextOption menu-option\"\n            role=\"button\"\n          >\n            <div\n              class=\"d-flex\"\n            >\n              <div\n                class=\"noicon\"\n              />\n            </div>\n            <div\n              class=\"menu-option__content\"\n            >\n              <div\n                class=\"menu-name\"\n              >\n                Delete\n              </div>\n            </div>\n            <div\n              class=\"noicon\"\n            />\n          </div>\n        </div>\n      </div>\n      <div\n        class=\"menu-spacer hideOnWidescreen\"\n      />\n      <div\n        class=\"menu-options hideOnWidescreen\"\n      >\n        <div\n          aria-label=\"Cancel\"\n          class=\"MenuOption TextOption menu-option menu-cancel\"\n          role=\"button\"\n        >\n          <div\n            class=\"d-flex\"\n          >\n            <div\n              class=\"noicon\"\n            />\n          </div>\n          <div\n            class=\"menu-option__content\"\n          >\n            <div\n              class=\"menu-name\"\n            >\n              Cancel\n            </div>\n          </div>\n          <div\n            class=\"noicon\"\n          />\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/table/TableHeaderMenu should match snapshot, title column 1`] = `\n<div>\n  <div\n    class=\"Menu noselect bottom \"\n  >\n    <div\n      class=\"menu-contents\"\n    >\n      <div\n        class=\"menu-options\"\n      >\n        <div>\n          <div\n            aria-label=\"Sort ascending\"\n            class=\"MenuOption TextOption menu-option\"\n            role=\"button\"\n          >\n            <div\n              class=\"d-flex\"\n            >\n              <div\n                class=\"noicon\"\n              />\n            </div>\n            <div\n              class=\"menu-option__content\"\n            >\n              <div\n                class=\"menu-name\"\n              >\n                Sort ascending\n              </div>\n            </div>\n            <div\n              class=\"noicon\"\n            />\n          </div>\n        </div>\n        <div>\n          <div\n            aria-label=\"Sort descending\"\n            class=\"MenuOption TextOption menu-option\"\n            role=\"button\"\n          >\n            <div\n              class=\"d-flex\"\n            >\n              <div\n                class=\"noicon\"\n              />\n            </div>\n            <div\n              class=\"menu-option__content\"\n            >\n              <div\n                class=\"menu-name\"\n              >\n                Sort descending\n              </div>\n            </div>\n            <div\n              class=\"noicon\"\n            />\n          </div>\n        </div>\n        <div>\n          <div\n            aria-label=\"Insert left\"\n            class=\"MenuOption TextOption menu-option\"\n            role=\"button\"\n          >\n            <div\n              class=\"d-flex\"\n            >\n              <div\n                class=\"noicon\"\n              />\n            </div>\n            <div\n              class=\"menu-option__content\"\n            >\n              <div\n                class=\"menu-name\"\n              >\n                Insert left\n              </div>\n            </div>\n            <div\n              class=\"noicon\"\n            />\n          </div>\n        </div>\n        <div>\n          <div\n            aria-label=\"Insert right\"\n            class=\"MenuOption TextOption menu-option\"\n            role=\"button\"\n          >\n            <div\n              class=\"d-flex\"\n            >\n              <div\n                class=\"noicon\"\n              />\n            </div>\n            <div\n              class=\"menu-option__content\"\n            >\n              <div\n                class=\"menu-name\"\n              >\n                Insert right\n              </div>\n            </div>\n            <div\n              class=\"noicon\"\n            />\n          </div>\n        </div>\n        <div />\n      </div>\n      <div\n        class=\"menu-spacer hideOnWidescreen\"\n      />\n      <div\n        class=\"menu-options hideOnWidescreen\"\n      >\n        <div\n          aria-label=\"Cancel\"\n          class=\"MenuOption TextOption menu-option menu-cancel\"\n          role=\"button\"\n        >\n          <div\n            class=\"d-flex\"\n          >\n            <div\n              class=\"noicon\"\n            />\n          </div>\n          <div\n            class=\"menu-option__content\"\n          >\n            <div\n              class=\"menu-name\"\n            >\n              Cancel\n            </div>\n          </div>\n          <div\n            class=\"noicon\"\n          />\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/table/__snapshots__/tableHeaders.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/table/TableHeaders should match snapshot 1`] = `\n<div>\n  <div\n    class=\"octo-table-header TableHeaders\"\n    id=\"mainBoardHeader\"\n  >\n    <div\n      class=\"octo-table-cell header-cell\"\n      style=\"overflow: unset; opacity: 1; width: 100px;\"\n    >\n      <div\n        aria-label=\"menuwrapper\"\n        class=\"MenuWrapper\"\n        role=\"button\"\n      >\n        <span\n          class=\"Label empty \"\n        >\n          Name\n        </span>\n      </div>\n      <div\n        class=\"octo-spacer\"\n      />\n      <div\n        class=\"HorizontalGrip\"\n      />\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/table/__snapshots__/tableRow.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/table/TableRow should match snapshot 1`] = `\n<div>\n  <div\n    class=\"TableRow octo-table-row\"\n    draggable=\"true\"\n    style=\"opacity: 1;\"\n  >\n    <div\n      class=\"action-cell octo-table-cell-btn\"\n    >\n      <button\n        class=\"IconButton\"\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-drag-vertical\"\n        />\n      </button>\n    </div>\n    <div\n      class=\"octo-table-cell title-cell\"\n      id=\"mainBoardHeader\"\n      style=\"width: 100px;\"\n    >\n      <div\n        class=\"octo-icontitle\"\n      >\n        <div\n          class=\"octo-icon\"\n        >\n          i\n        </div>\n        <input\n          class=\"Editable \"\n          placeholder=\"Untitled\"\n          spellcheck=\"true\"\n          title=\"title\"\n          value=\"title\"\n        />\n      </div>\n      <div\n        aria-label=\"menuwrapper\"\n        class=\"MenuWrapper optionsMenu ml-2 mr-2\"\n        role=\"button\"\n      >\n        <div\n          class=\"octo-tooltip tooltip-top\"\n          data-tooltip=\"More actions\"\n        >\n          <button\n            aria-label=\"MenuBtn\"\n            class=\"IconButton\"\n            title=\"MenuBtn\"\n            type=\"button\"\n          >\n            <i\n              class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n            />\n          </button>\n        </div>\n      </div>\n      <div\n        class=\"open-button\"\n      >\n        <button\n          class=\"Button\"\n          type=\"button\"\n        >\n          <span>\n            Open\n          </span>\n        </button>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/table/TableRow should match snapshot, collapsed tree 1`] = `\n<div>\n  <div\n    class=\"TableRow octo-table-row hidden\"\n    draggable=\"true\"\n    style=\"opacity: 1;\"\n  >\n    <div\n      class=\"action-cell octo-table-cell-btn\"\n    >\n      <button\n        class=\"IconButton\"\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-drag-vertical\"\n        />\n      </button>\n    </div>\n    <div\n      class=\"octo-table-cell title-cell\"\n      id=\"mainBoardHeader\"\n      style=\"width: 100px;\"\n    >\n      <div\n        class=\"octo-icontitle\"\n      >\n        <div\n          class=\"octo-icon\"\n        >\n          i\n        </div>\n        <input\n          class=\"Editable \"\n          placeholder=\"Untitled\"\n          spellcheck=\"true\"\n          title=\"title\"\n          value=\"title\"\n        />\n      </div>\n      <div\n        aria-label=\"menuwrapper\"\n        class=\"MenuWrapper optionsMenu ml-2 mr-2\"\n        role=\"button\"\n      >\n        <div\n          class=\"octo-tooltip tooltip-top\"\n          data-tooltip=\"More actions\"\n        >\n          <button\n            aria-label=\"MenuBtn\"\n            class=\"IconButton\"\n            title=\"MenuBtn\"\n            type=\"button\"\n          >\n            <i\n              class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n            />\n          </button>\n        </div>\n      </div>\n      <div\n        class=\"open-button\"\n      >\n        <button\n          class=\"Button\"\n          type=\"button\"\n        >\n          <span>\n            Open\n          </span>\n        </button>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/table/TableRow should match snapshot, display properties 1`] = `\n<div>\n  <div\n    class=\"TableRow octo-table-row\"\n    draggable=\"true\"\n    style=\"opacity: 1;\"\n  >\n    <div\n      class=\"action-cell octo-table-cell-btn\"\n    >\n      <button\n        class=\"IconButton\"\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-drag-vertical\"\n        />\n      </button>\n    </div>\n    <div\n      class=\"octo-table-cell title-cell\"\n      id=\"mainBoardHeader\"\n      style=\"width: 100px;\"\n    >\n      <div\n        class=\"octo-icontitle\"\n      >\n        <div\n          class=\"octo-icon\"\n        >\n          i\n        </div>\n        <input\n          class=\"Editable \"\n          placeholder=\"Untitled\"\n          spellcheck=\"true\"\n          title=\"title\"\n          value=\"title\"\n        />\n      </div>\n      <div\n        aria-label=\"menuwrapper\"\n        class=\"MenuWrapper optionsMenu ml-2 mr-2\"\n        role=\"button\"\n      >\n        <div\n          class=\"octo-tooltip tooltip-top\"\n          data-tooltip=\"More actions\"\n        >\n          <button\n            aria-label=\"MenuBtn\"\n            class=\"IconButton\"\n            title=\"MenuBtn\"\n            type=\"button\"\n          >\n            <i\n              class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n            />\n          </button>\n        </div>\n      </div>\n      <div\n        class=\"open-button\"\n      >\n        <button\n          class=\"Button\"\n          type=\"button\"\n        >\n          <span>\n            Open\n          </span>\n        </button>\n      </div>\n    </div>\n    <div\n      class=\"octo-table-cell\"\n      style=\"width: 100px;\"\n    >\n      <div\n        class=\"octo-propertyvalue\"\n        data-testid=\"select-non-editable\"\n        tabindex=\"0\"\n      >\n        <span\n          class=\"Label propColorBrown \"\n        >\n          <span\n            class=\"Label-text\"\n          >\n            value 1\n          </span>\n        </span>\n      </div>\n    </div>\n    <div\n      class=\"octo-table-cell\"\n      style=\"width: 100px;\"\n    >\n      <div\n        class=\"octo-propertyvalue\"\n        data-testid=\"select-non-editable\"\n        tabindex=\"0\"\n      >\n        <span\n          class=\"Label empty \"\n        >\n          <span\n            class=\"Label-text\"\n          />\n        </span>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/table/TableRow should match snapshot, isSelected 1`] = `\n<div>\n  <div\n    class=\"TableRow octo-table-row selected\"\n    draggable=\"true\"\n    style=\"opacity: 1;\"\n  >\n    <div\n      class=\"action-cell octo-table-cell-btn\"\n    >\n      <button\n        class=\"IconButton\"\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-drag-vertical\"\n        />\n      </button>\n    </div>\n    <div\n      class=\"octo-table-cell title-cell\"\n      id=\"mainBoardHeader\"\n      style=\"width: 100px;\"\n    >\n      <div\n        class=\"octo-icontitle\"\n      >\n        <div\n          class=\"octo-icon\"\n        >\n          i\n        </div>\n        <input\n          class=\"Editable \"\n          placeholder=\"Untitled\"\n          spellcheck=\"true\"\n          title=\"title\"\n          value=\"title\"\n        />\n      </div>\n      <div\n        aria-label=\"menuwrapper\"\n        class=\"MenuWrapper optionsMenu ml-2 mr-2\"\n        role=\"button\"\n      >\n        <div\n          class=\"octo-tooltip tooltip-top\"\n          data-tooltip=\"More actions\"\n        >\n          <button\n            aria-label=\"MenuBtn\"\n            class=\"IconButton\"\n            title=\"MenuBtn\"\n            type=\"button\"\n          >\n            <i\n              class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n            />\n          </button>\n        </div>\n      </div>\n      <div\n        class=\"open-button\"\n      >\n        <button\n          class=\"Button\"\n          type=\"button\"\n        >\n          <span>\n            Open\n          </span>\n        </button>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/table/TableRow should match snapshot, read-only 1`] = `\n<div>\n  <div\n    class=\"TableRow octo-table-row readonly\"\n    draggable=\"true\"\n    style=\"opacity: 1;\"\n  >\n    <div\n      class=\"action-cell octo-table-cell-btn\"\n    />\n    <div\n      class=\"octo-table-cell title-cell\"\n      id=\"mainBoardHeader\"\n      style=\"width: 100px;\"\n    >\n      <div\n        class=\"octo-icontitle\"\n      >\n        <div\n          class=\"octo-icon\"\n        >\n          i\n        </div>\n        <input\n          class=\"Editable readonly \"\n          placeholder=\"Untitled\"\n          readonly=\"\"\n          spellcheck=\"true\"\n          title=\"title\"\n          value=\"title\"\n        />\n      </div>\n      <div\n        class=\"open-button\"\n      >\n        <button\n          class=\"Button\"\n          type=\"button\"\n        >\n          <span>\n            Open\n          </span>\n        </button>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/table/TableRow should match snapshot, resizing column 1`] = `\n<div>\n  <div\n    class=\"TableRow octo-table-row\"\n    draggable=\"true\"\n    style=\"opacity: 1;\"\n  >\n    <div\n      class=\"action-cell octo-table-cell-btn\"\n    >\n      <button\n        class=\"IconButton\"\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-drag-vertical\"\n        />\n      </button>\n    </div>\n    <div\n      class=\"octo-table-cell title-cell\"\n      id=\"mainBoardHeader\"\n      style=\"width: 100px;\"\n    >\n      <div\n        class=\"octo-icontitle\"\n      >\n        <div\n          class=\"octo-icon\"\n        >\n          i\n        </div>\n        <input\n          class=\"Editable \"\n          placeholder=\"Untitled\"\n          spellcheck=\"true\"\n          title=\"title\"\n          value=\"title\"\n        />\n      </div>\n      <div\n        aria-label=\"menuwrapper\"\n        class=\"MenuWrapper optionsMenu ml-2 mr-2\"\n        role=\"button\"\n      >\n        <div\n          class=\"octo-tooltip tooltip-top\"\n          data-tooltip=\"More actions\"\n        >\n          <button\n            aria-label=\"MenuBtn\"\n            class=\"IconButton\"\n            title=\"MenuBtn\"\n            type=\"button\"\n          >\n            <i\n              class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n            />\n          </button>\n        </div>\n      </div>\n      <div\n        class=\"open-button\"\n      >\n        <button\n          class=\"Button\"\n          type=\"button\"\n        >\n          <span>\n            Open\n          </span>\n        </button>\n      </div>\n    </div>\n    <div\n      class=\"octo-table-cell\"\n      style=\"width: 100px;\"\n    >\n      <div\n        class=\"octo-propertyvalue\"\n        data-testid=\"select-non-editable\"\n        tabindex=\"0\"\n      >\n        <span\n          class=\"Label propColorBrown \"\n        >\n          <span\n            class=\"Label-text\"\n          >\n            value 1\n          </span>\n        </span>\n      </div>\n    </div>\n    <div\n      class=\"octo-table-cell\"\n      style=\"width: 100px;\"\n    >\n      <div\n        class=\"octo-propertyvalue\"\n        data-testid=\"select-non-editable\"\n        tabindex=\"0\"\n      >\n        <span\n          class=\"Label empty \"\n        >\n          <span\n            class=\"Label-text\"\n          />\n        </span>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/table/__snapshots__/tableRows.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/table/TableRows should match snapshot, fire events 1`] = `\n<div>\n  <div\n    class=\"TableRow octo-table-row\"\n    draggable=\"true\"\n    style=\"opacity: 1;\"\n  >\n    <div\n      class=\"action-cell octo-table-cell-btn\"\n    >\n      <button\n        class=\"IconButton\"\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-drag-vertical\"\n        />\n      </button>\n    </div>\n    <div\n      class=\"octo-table-cell title-cell\"\n      id=\"mainBoardHeader\"\n      style=\"width: 100px;\"\n    >\n      <div\n        class=\"octo-icontitle\"\n      >\n        <div\n          class=\"octo-icon\"\n        >\n          i\n        </div>\n        <input\n          class=\"Editable \"\n          placeholder=\"Untitled\"\n          spellcheck=\"true\"\n          title=\"title\"\n          value=\"title\"\n        />\n      </div>\n      <div\n        aria-label=\"menuwrapper\"\n        class=\"MenuWrapper optionsMenu ml-2 mr-2\"\n        role=\"button\"\n      >\n        <div\n          class=\"octo-tooltip tooltip-top\"\n          data-tooltip=\"More actions\"\n        >\n          <button\n            aria-label=\"MenuBtn\"\n            class=\"IconButton\"\n            title=\"MenuBtn\"\n            type=\"button\"\n          >\n            <i\n              class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n            />\n          </button>\n        </div>\n      </div>\n      <div\n        class=\"open-button\"\n      >\n        <button\n          class=\"Button\"\n          type=\"button\"\n        >\n          <span>\n            Open\n          </span>\n        </button>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/table/calculation/__snapshots__/calculationRow.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/table/calculation/CalculationRow should match snapshot 1`] = `\n<div>\n  <div\n    class=\"CalculationRow octo-table-row\"\n  >\n    <div\n      class=\"Calculation count octo-table-cell   \"\n      style=\"width: 100px;\"\n      tabindex=\"0\"\n    >\n      <span\n        class=\"calculationLabel\"\n      >\n        Count\n      </span>\n      <span\n        class=\"calculationValue\"\n      >\n        2\n      </span>\n    </div>\n    <div\n      class=\"Calculation count octo-table-cell   \"\n      style=\"width: 100px;\"\n      tabindex=\"0\"\n    >\n      <span\n        class=\"calculationLabel\"\n      >\n        Count\n      </span>\n      <span\n        class=\"calculationValue\"\n      >\n        2\n      </span>\n    </div>\n    <div\n      class=\"Calculation countValue octo-table-cell   \"\n      style=\"width: 100px;\"\n      tabindex=\"0\"\n    >\n      <span\n        class=\"calculationLabel\"\n      >\n        Values\n      </span>\n      <span\n        class=\"calculationValue\"\n      >\n        1\n      </span>\n    </div>\n    <div\n      class=\"Calculation countUniqueValue octo-table-cell   \"\n      style=\"width: 100px;\"\n      tabindex=\"0\"\n    >\n      <span\n        class=\"calculationLabel\"\n      >\n        Unique\n      </span>\n      <span\n        class=\"calculationValue\"\n      >\n        1\n      </span>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/table/calculation/CalculationRow should render three calculation elements 1`] = `\n<div>\n  <div\n    class=\"CalculationRow octo-table-row\"\n  >\n    <div\n      class=\"Calculation count octo-table-cell   \"\n      style=\"width: 100px;\"\n      tabindex=\"0\"\n    >\n      <span\n        class=\"calculationLabel\"\n      >\n        Count\n      </span>\n      <span\n        class=\"calculationValue\"\n      >\n        2\n      </span>\n    </div>\n    <div\n      class=\"Calculation none octo-table-cell   \"\n      style=\"width: 100px;\"\n      tabindex=\"0\"\n    >\n      <span\n        class=\"calculationLabel\"\n      >\n        Calculate\n      </span>\n      <i\n        class=\"CompassIcon icon-chevron-up ChevronUpIcon\"\n      />\n    </div>\n    <div\n      class=\"Calculation none octo-table-cell   \"\n      style=\"width: 100px;\"\n      tabindex=\"0\"\n    >\n      <span\n        class=\"calculationLabel\"\n      >\n        Calculate\n      </span>\n      <i\n        class=\"CompassIcon icon-chevron-up ChevronUpIcon\"\n      />\n    </div>\n    <div\n      class=\"Calculation none octo-table-cell   \"\n      style=\"width: 100px;\"\n      tabindex=\"0\"\n    >\n      <span\n        class=\"calculationLabel\"\n      >\n        Calculate\n      </span>\n      <i\n        class=\"CompassIcon icon-chevron-up ChevronUpIcon\"\n      />\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/table/calculation/calculationRow.scss",
    "content": ".CalculationRow {\n    padding-left: 32px;\n    position: sticky;\n    bottom: 0;\n    height: 44px;\n    border-top: solid 1px rgba(var(--body-color), 0.09);\n    // To override table's specific border\n    border-bottom: 0 !important;\n    background-color: rgb(var(--center-channel-bg-rgb));\n\n    .octo-table-cell {\n        background-color: rgb(var(--center-channel-bg-rgb));\n        align-items: center;\n        justify-content: flex-end;\n        border: 0;\n    }\n\n    .title {\n        padding-left: 40px;\n    }\n\n    .CalculationOptions {\n        width: 100%;\n    }\n}\n\n.Table {\n    .octo-table-cell.Calculation:focus-within {\n        border: unset;\n        background-color: rgb(var(--center-channel-bg-rgb));\n    }\n\n    .octo-table-cell.Calculation:hover {\n        background-color: rgb(var(--center-channel-bg-rgb));\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/table/calculation/calculationRow.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\n\nimport {render} from '@testing-library/react'\nimport '@testing-library/jest-dom'\n\nimport {TestBlockFactory} from '../../../test/testBlockFactory'\nimport {FetchMock} from '../../../test/fetchMock'\nimport 'isomorphic-fetch'\nimport {wrapDNDIntl} from '../../../testUtils'\n\nimport {ColumnResizeProvider} from '../tableColumnResizeContext'\n\nimport CalculationRow from './calculationRow'\n\nglobal.fetch = FetchMock.fn\n\nbeforeEach(() => {\n    FetchMock.fn.mockReset()\n})\n\ndescribe('components/table/calculation/CalculationRow', () => {\n    const board = TestBlockFactory.createBoard()\n    board.cardProperties.push({\n        id: 'property_2',\n        name: 'Property 2',\n        type: 'text',\n        options: [],\n    })\n    board.cardProperties.push({\n        id: 'property_3',\n        name: 'Property 3',\n        type: 'text',\n        options: [],\n    })\n    board.cardProperties.push({\n        id: 'property_4',\n        name: 'Property 4',\n        type: 'text',\n        options: [],\n    })\n\n    const view = TestBlockFactory.createBoardView(board)\n    view.fields.visiblePropertyIds.push(...['property_2', 'property_3', 'property_4'])\n\n    const card = TestBlockFactory.createCard(board)\n    card.fields.properties.property_2 = 'Foo'\n    card.fields.properties.property_3 = 'Bar'\n    card.fields.properties.property_4 = 'Baz'\n\n    const card2 = TestBlockFactory.createCard(board)\n    card2.fields.properties.property_2 = 'Lorem'\n    card2.fields.properties.property_3 = ''\n    card2.fields.properties.property_4 = 'Baz'\n\n    test('should render three calculation elements', async () => {\n        FetchMock.fn.mockReturnValueOnce(FetchMock.jsonResponse(JSON.stringify([board, view, card])))\n\n        const component = wrapDNDIntl(\n            <ColumnResizeProvider\n                columnWidths={{}}\n                onResizeColumn={jest.fn()}\n            >\n                <CalculationRow\n                    board={board}\n                    cards={[card, card2]}\n                    activeView={view}\n                    readonly={false}\n                />\n            </ColumnResizeProvider>,\n        )\n\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot', async () => {\n        view.fields.columnCalculations = {\n            property_2: 'count',\n            property_3: 'countValue',\n            property_4: 'countUniqueValue',\n        }\n\n        const component = wrapDNDIntl(\n            <ColumnResizeProvider\n                columnWidths={{}}\n                onResizeColumn={jest.fn()}\n            >\n                <CalculationRow\n                    board={board}\n                    cards={[card, card2]}\n                    activeView={view}\n                    readonly={false}\n                />\n            </ColumnResizeProvider>,\n        )\n\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/table/calculation/calculationRow.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useState, useMemo} from 'react'\n\nimport {Constants} from '../../../constants'\n\nimport './calculationRow.scss'\nimport {Board, IPropertyTemplate} from '../../../blocks/board'\n\nimport mutator from '../../../mutator'\nimport Calculation from '../../calculations/calculation'\nimport {BoardView} from '../../../blocks/boardView'\nimport {Card} from '../../../blocks/card'\nimport {Options} from '../../calculations/options'\n\nimport {TableCalculationOptions} from './tableCalculationOptions'\n\ntype Props = {\n    board: Board\n    cards: Card[]\n    activeView: BoardView\n    readonly: boolean\n}\n\nconst CalculationRow = (props: Props): JSX.Element => {\n    const {board, cards, activeView, readonly} = props\n    const toggleOptions = (templateId: string, show: boolean) => {\n        const newShowOptions = new Map<string, boolean>(showOptions)\n        newShowOptions.set(templateId, show)\n        setShowOptions(newShowOptions)\n    }\n\n    const [showOptions, setShowOptions] = useState<Map<string, boolean>>(new Map<string, boolean>())\n    const titleTemplate: IPropertyTemplate = {\n        id: Constants.titleColumnId,\n    } as IPropertyTemplate\n\n    const visiblePropertyTemplates = useMemo(() => ([\n        titleTemplate,\n        ...activeView.fields.visiblePropertyIds.map((id) => board.cardProperties.find((t) => t.id === id)).filter((i) => i) as IPropertyTemplate[],\n    ]), [board.cardProperties, activeView.fields.visiblePropertyIds])\n\n    const selectedCalculations = activeView.fields.columnCalculations || []\n\n    const [hovered, setHovered] = useState(false)\n\n    return (\n        <div\n            className={'CalculationRow octo-table-row'}\n            onMouseEnter={() => setHovered(!readonly)}\n            onMouseLeave={() => setHovered(false)}\n        >\n            {\n                visiblePropertyTemplates.map((template) => {\n                    const defaultValue = template.id === Constants.titleColumnId ? Options.count.value : Options.none.value\n                    const value = selectedCalculations[template.id] || defaultValue\n\n                    return (\n                        <Calculation\n                            key={template.id}\n                            class={`octo-table-cell ${readonly ? 'disabled' : ''}`}\n                            value={value}\n                            menuOpen={Boolean(readonly ? false : showOptions.get(template.id))}\n                            onMenuClose={() => toggleOptions(template.id, false)}\n                            onMenuOpen={() => toggleOptions(template.id, true)}\n                            onChange={(v: string) => {\n                                const calculations = {...selectedCalculations}\n                                calculations[template.id] = v\n                                mutator.changeViewColumnCalculations(board.id, activeView.id, selectedCalculations, calculations, 'change column calculation')\n                                setHovered(false)\n                            }}\n                            cards={cards}\n                            property={template}\n                            hovered={hovered}\n                            optionsComponent={TableCalculationOptions}\n                        />\n                    )\n                })\n            }\n        </div>\n    )\n}\n\nexport default CalculationRow\n"
  },
  {
    "path": "webapp/src/components/table/calculation/tableCalculationOptions.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\n\nimport {CalculationOptions, CommonCalculationOptionProps, optionsByType} from '../../calculations/options'\n\nexport const TableCalculationOptions = (props: CommonCalculationOptionProps): JSX.Element => {\n    const options = [...optionsByType.get('common')!]\n    if (props.property && optionsByType.get(props.property.type)) {\n        options.push(...optionsByType.get(props.property.type)!)\n    }\n\n    return (\n        <CalculationOptions\n            value={props.value}\n            menuOpen={props.menuOpen}\n            onClose={props.onClose}\n            onChange={props.onChange}\n            property={props.property}\n            options={options}\n        />\n    )\n}\n"
  },
  {
    "path": "webapp/src/components/table/horizontalGrip.scss",
    "content": ".HorizontalGrip {\n    width: 5px;\n    cursor: ew-resize;\n    flex-shrink: 0;\n\n    &:hover {\n        background-color: rgba(90, 192, 255, 0.7);\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/table/horizontalGrip.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useCallback, useEffect, useRef} from 'react'\n\nimport {useColumnResize} from './tableColumnResizeContext'\nimport './horizontalGrip.scss'\n\ntype Props = {\n    templateId: string\n    columnWidth: number\n    onAutoSizeColumn: (columnID: string) => void\n}\n\ntype OffsetCallback = (offset: number) => void\n\nfunction useResizable(liveOffset: OffsetCallback, finalOffset: OffsetCallback) {\n    const state = useRef({\n        initialX: 0,\n        lastOffset: 0,\n        isResizing: false,\n    })\n\n    const updateOffset = useCallback((event: MouseEvent) => {\n        state.current.lastOffset = event.clientX - state.current.initialX\n        liveOffset(state.current.lastOffset)\n    }, [liveOffset])\n\n    const stopResizing = useCallback(() => {\n        if (state.current.isResizing) {\n            state.current.isResizing = false\n            document.removeEventListener('mousemove', updateOffset)\n            document.removeEventListener('mouseup', stopResizing)\n            document.body.style.userSelect = ''\n            finalOffset(state.current.lastOffset)\n        }\n    }, [updateOffset])\n\n    useEffect(() => stopResizing, [stopResizing])\n\n    return useCallback((event: React.MouseEvent) => {\n        state.current = {\n            initialX: event.clientX,\n            lastOffset: 0,\n            isResizing: true,\n        }\n        document.addEventListener('mousemove', updateOffset)\n        document.addEventListener('mouseup', stopResizing)\n        document.body.style.userSelect = 'none'\n        event.preventDefault()\n    }, [updateOffset, stopResizing])\n}\n\nconst HorizontalGrip = (props: Props): JSX.Element => {\n    const {templateId, onAutoSizeColumn} = props\n    const columnResize = useColumnResize()\n\n    const liveOffset = useCallback((offset: number) => {\n        columnResize.updateOffset(templateId, offset)\n    }, [columnResize, templateId])\n\n    const finalOffset = useCallback((offset: number) => {\n        const width = columnResize.width(templateId) + offset\n        columnResize.updateWidth(templateId, width)\n    }, [columnResize, templateId])\n\n    const startResize = useResizable(liveOffset, finalOffset)\n\n    return (\n        <div\n            className='HorizontalGrip'\n            onDoubleClick={() => onAutoSizeColumn(templateId)}\n            onMouseDown={startResize}\n        />\n    )\n}\n\nexport default React.memo(HorizontalGrip)\n"
  },
  {
    "path": "webapp/src/components/table/table.scss",
    "content": "@import '../../styles/z-index';\n\n.Table {\n    margin-top: 16px;\n    margin-left: 0 !important;\n    overflow: auto;\n    position: relative;\n    flex: 1;\n\n    .octo-group-header-cell {\n        display: flex;\n        flex-shrink: 0;\n        align-items: center;\n        height: 50px;\n        margin-right: 15px;\n        margin-top: 15px;\n        vertical-align: middle;\n        border-bottom: solid 1px rgba(var(--center-channel-color-rgb), 0.08);\n\n        &.narrow {\n            width: 220px;\n        }\n\n        > div {\n            margin-right: 8px;\n            height: auto;\n\n            &:first-child {\n                padding-left: 0;\n                border: 0;\n\n                &:hover {\n                    background-color: transparent;\n                }\n\n                &:focus-within {\n                    background: transparent;\n                    border: 0;\n                }\n            }\n\n            &:last-child {\n                margin: 0;\n            }\n        }\n\n        .IconButton {\n            background-color: unset;\n\n            &:hover:not(.readonly) {\n                background-color: rgba(var(--center-channel-color-rgb), 0.1);\n            }\n\n            &.readonly {\n                opacity: 0.5;\n            }\n        }\n\n        .Label {\n            display: inline-flex;\n            align-items: center;\n            padding: 2px 8px;\n            border-radius: 3px;\n            line-height: 20px;\n            margin: 0 4px 0 0;\n            color: rgba(var(--center-channel-color-rgb), 1);\n            white-space: nowrap;\n            text-transform: none;\n            font-weight: 600;\n            font-size: 14px;\n            width: 100%;\n            height: 32px;\n\n            input {\n                background: transparent;\n                width: 100%;\n                text-transform: none;\n                font-size: inherit;\n                color: inherit;\n                padding: 0;\n            }\n        }\n\n        > .Button {\n            cursor: auto;\n\n            &.IconButton:not(.readonly) {\n                cursor: pointer;\n            }\n        }\n\n        &.expanded {\n            .icon-menu-right {\n                transform: rotate(90deg);\n            }\n        }\n    }\n\n    .octo-table-cell__expand {\n        margin-right: 4px;\n        font-size: 24px;\n\n        i {\n            font-size: inherit;\n        }\n    }\n\n    .octo-table-cell {\n        flex: 0 0 auto;\n        display: flex;\n        flex-direction: row;\n        color: rgb(var(--center-channel-color-rgb));\n        border-right: solid 1px rgba(var(--center-channel-color-rgb), 0.08);\n        border-bottom: solid 1px rgba(var(--center-channel-color-rgb), 0.08);\n        border-left: 1px solid transparent;\n        border-top: 1px solid transparent;\n        box-sizing: border-box;\n        padding: 8px;\n        height: 44px;\n        font-size: 14px;\n        position: relative;\n        text-overflow: ellipsis;\n\n        .optionsMenu {\n            display: none;\n        }\n\n        &:hover {\n            background-color: rgba(var(--center-channel-color-rgb), 0.05);\n\n            .optionsMenu {\n                display: block;\n            }\n        }\n\n        &.title-cell {\n            &:hover {\n                background: transparent;\n            }\n        }\n\n        .octo-icontitle {\n            flex: 1 1 auto;\n            font-weight: 600;\n\n            .octo-icon {\n                min-width: 20px;\n                margin-top: 3px;\n                font-size: 16px;\n                margin-right: 4px;\n                display: flex;\n                align-items: center;\n                justify-content: center;\n                width: 20px;\n                height: 20px;\n            }\n\n            .Editable {\n                flex: 1 1 auto;\n            }\n        }\n\n        &.header-cell {\n            padding-right: 0;\n\n            .Icon {\n                width: 16px;\n                height: 16px;\n                vertical-align: middle;\n                margin-left: 5px;\n            }\n        }\n\n        &:focus-within {\n            background-color: rgba(46, 170, 220, 0.15);\n            border: 1px solid rgba(46, 170, 220, 0.6);\n        }\n\n        .Editable {\n            padding: 0 5px;\n            left: -5px;\n        }\n\n        .Editable.Editable.active {\n            overflow: hidden;\n        }\n\n        .octo-propertyvalue {\n            line-height: 17px;\n            overflow: hidden;\n            text-overflow: ellipsis;\n\n            .Label {\n                margin: 0 5px;\n                padding: 0 8px;\n            }\n        }\n\n        .Editable,\n        .octo-propertyvalue {\n            text-align: left;\n            white-space: nowrap;\n            width: inherit;\n        }\n\n        .MultiPerson.octo-propertyvalue,\n        .Person.octo-propertyvalue,\n        .DateRange.octo-propertyvalue {\n            overflow: unset;\n        }\n    }\n\n    .octo-table-body {\n        display: flex;\n        flex-direction: column;\n        width: fit-content;\n    }\n\n    .octo-table-header,\n    .octo-table-footer {\n        display: flex;\n        flex-direction: row;\n        border-bottom: solid 1px rgba(var(--center-channel-color-rgb), 0.09);\n        margin-left: 32px;\n\n        &.hidden {\n            display: none;\n        }\n    }\n\n    .octo-table-row {\n        display: flex;\n        flex-direction: row;\n    }\n\n    .octo-table-header {\n        @include z-index(table-header);\n        position: sticky;\n        top: -2px;\n        background: rgb(var(--center-channel-bg-rgb));\n        width: fit-content;\n\n        .octo-table-cell {\n            color: rgba(var(--center-channel-color-rgb), 0.6);\n            background: rgb(var(--center-channel-bg-rgb));\n\n            .Label {\n                color: rgba(var(--center-channel-color-rgb), 0.6);\n            }\n        }\n    }\n\n    .table-row-container {\n        width: fit-content;\n\n        .octo-table-cell {\n            align-items: center;\n\n            .octo-propertyvalue {\n                font-size: inherit;\n                font-weight: normal;\n            }\n\n        }\n    }\n\n    .octo-table-footer {\n        .octo-table-cell {\n            color: rgba(var(--center-channel-color-rgb), 0.6);\n            cursor: pointer;\n            width: 100%;\n            padding-left: 15px;\n\n            &:hover {\n                background-color: rgba(var(--center-channel-color-rgb), 0.08);\n            }\n        }\n    }\n\n    .MenuWrapper {\n        max-width: calc(100% - 5px);\n\n        .Label {\n            width: 100%;\n            display: inline-block;\n            text-overflow: ellipsis;\n            overflow: hidden;\n        }\n    }\n\n    .MultiPerson .react-select__value-container--is-multi {\n        display: block;\n        white-space: nowrap;\n\n        .react-select__multi-value {\n            background: rgba(var(--center-channel-color-rgb), 0.08);\n            border-radius: 24px;\n            display: inline-flex;\n            color: rgb(var(--center-channel-color-rgb));\n\n            .MultiPerson-item,\n            .react-select__multi-value__label {\n                color: inherit;\n            }\n        }\n    }\n\n    @media screen and (max-width: 768px) {\n        margin-left: 0 !important;\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/table/table.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {Provider as ReduxProvider} from 'react-redux'\nimport {render, screen} from '@testing-library/react'\nimport configureStore from 'redux-mock-store'\nimport '@testing-library/jest-dom'\nimport userEvents from '@testing-library/user-event'\n\nimport 'isomorphic-fetch'\nimport {mocked} from 'jest-mock'\n\nimport {TestBlockFactory} from '../../test/testBlockFactory'\nimport {FetchMock} from '../../test/fetchMock'\nimport {BoardView} from '../../blocks/boardView'\n\nimport {IUser} from '../../user'\n\nimport {Utils, IDType} from '../../utils'\n\nimport {wrapDNDIntl} from '../../testUtils'\n\nimport Mutator from '../../mutator'\n\nimport Table from './table'\n\nglobal.fetch = FetchMock.fn\n\nbeforeEach(() => {\n    FetchMock.fn.mockReset()\n})\n\njest.mock('../../mutator')\njest.mock('../../utils')\njest.mock('../../telemetry/telemetryClient')\nconst mockedMutator = mocked(Mutator, true)\n\ndescribe('components/table/Table', () => {\n    const board = TestBlockFactory.createBoard()\n    const view = TestBlockFactory.createBoardView(board)\n    view.fields.viewType = 'table'\n    view.fields.groupById = undefined\n    view.fields.visiblePropertyIds = ['property1', 'property2']\n\n    const view2 = TestBlockFactory.createBoardView(board)\n    view2.fields.sortOptions = []\n\n    const card = TestBlockFactory.createCard(board)\n    const cardTemplate = TestBlockFactory.createCard(board)\n    cardTemplate.fields.isTemplate = true\n\n    const state = {\n        users: {\n            boardUsers: {\n                'user-id-1': {username: 'username_1'} as IUser,\n                'user-id-2': {username: 'username_2'} as IUser,\n                'user-id-3': {username: 'username_3'} as IUser,\n                'user-id-4': {username: 'username_4'} as IUser,\n            },\n        },\n        comments: {\n            comments: {},\n            commentsByCard: {},\n        },\n        contents: {\n            contents: {},\n            contentsByCard: {},\n        },\n        cards: {\n            cards: {\n                [card.id]: card,\n            },\n        },\n        teams: {\n            current: {id: 'team-id'},\n        },\n        boards: {\n            current: board.id,\n            boards: {\n                [board.id]: board,\n            },\n            myBoardMemberships: {\n                [board.id]: {userId: 'user_id_1', schemeAdmin: true},\n            },\n        },\n    }\n\n    test('should match snapshot', async () => {\n        const callback = jest.fn()\n        const addCard = jest.fn()\n\n        const mockStore = configureStore([])\n        const store = mockStore(state)\n\n        const component = wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <Table\n                    board={board}\n                    activeView={view}\n                    visibleGroups={[]}\n                    cards={[card]}\n                    views={[view, view2]}\n                    selectedCardIds={[]}\n                    readonly={false}\n                    cardIdToFocusOnRender=''\n                    showCard={callback}\n                    addCard={addCard}\n                    onCardClicked={jest.fn()}\n                    hiddenCardsCount={0}\n                    showHiddenCardCountNotification={jest.fn()}\n                />\n            </ReduxProvider>,\n        )\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot without permissions', async () => {\n        const callback = jest.fn()\n        const addCard = jest.fn()\n\n        const mockStore = configureStore([])\n        const store = mockStore({...state, teams: {current: undefined}})\n\n        const component = wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <Table\n                    board={board}\n                    activeView={view}\n                    visibleGroups={[]}\n                    cards={[card]}\n                    views={[view, view2]}\n                    selectedCardIds={[]}\n                    readonly={false}\n                    cardIdToFocusOnRender=''\n                    showCard={callback}\n                    addCard={addCard}\n                    onCardClicked={jest.fn()}\n                    hiddenCardsCount={0}\n                    showHiddenCardCountNotification={jest.fn()}\n                />\n            </ReduxProvider>,\n        )\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot, read-only', async () => {\n        const callback = jest.fn()\n        const addCard = jest.fn()\n\n        const mockStore = configureStore([])\n        const store = mockStore(state)\n\n        const component = wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <Table\n                    board={board}\n                    activeView={view}\n                    visibleGroups={[]}\n                    cards={[card]}\n                    views={[view, view2]}\n                    selectedCardIds={[]}\n                    readonly={true}\n                    cardIdToFocusOnRender=''\n                    showCard={callback}\n                    addCard={addCard}\n                    onCardClicked={jest.fn()}\n                    hiddenCardsCount={0}\n                    showHiddenCardCountNotification={jest.fn()}\n                />\n            </ReduxProvider>,\n        )\n\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot with GroupBy', async () => {\n        const callback = jest.fn()\n        const addCard = jest.fn()\n\n        const mockStore = configureStore([])\n        const store = mockStore(state)\n\n        const component = wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <Table\n                    board={board}\n                    activeView={{...view, fields: {...view.fields, groupById: 'property1'}} as BoardView}\n                    visibleGroups={[{option: {id: '', value: 'test', color: ''}, cards: []}]}\n                    groupByProperty={{\n                        id: '',\n                        name: 'Property 1',\n                        type: 'text',\n                        options: [{id: 'property1', value: 'Property 1', color: ''}],\n                    }}\n                    cards={[card]}\n                    views={[view, view2]}\n                    selectedCardIds={[]}\n                    readonly={false}\n                    cardIdToFocusOnRender=''\n                    showCard={callback}\n                    addCard={addCard}\n                    onCardClicked={jest.fn()}\n                    hiddenCardsCount={0}\n                    showHiddenCardCountNotification={jest.fn()}\n                />\n            </ReduxProvider>,\n        )\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('limited card in table view', () => {\n        const callback = jest.fn()\n        const addCard = jest.fn()\n        const boardTest = TestBlockFactory.createBoard()\n        const card1 = TestBlockFactory.createCard(boardTest)\n        const card2 = TestBlockFactory.createCard(boardTest)\n        const mockStore = configureStore([])\n\n        const stateTest = {\n            comments: {\n                comments: {},\n            },\n            contents: {\n                contents: {},\n            },\n            cards: {\n                cards: {\n                    [card1.id]: card1,\n                    [card2.id]: card2,\n                },\n            },\n            teams: {\n                current: {id: 'team-id'},\n            },\n            boards: {\n                current: boardTest.id,\n                boards: {\n                    [boardTest.id]: boardTest,\n                },\n                myBoardMemberships: {\n                    [boardTest.id]: {userId: 'user_id_1', schemeAdmin: true},\n                },\n            },\n        }\n\n        const storeTest = mockStore(stateTest)\n        card.limited = true\n\n        const component = wrapDNDIntl(\n            <ReduxProvider store={storeTest}>\n                <Table\n                    board={boardTest}\n                    activeView={view}\n                    visibleGroups={[]}\n                    cards={[card1, card2]}\n                    views={[view, view2]}\n                    selectedCardIds={[]}\n                    readonly={true}\n                    cardIdToFocusOnRender=''\n                    showCard={callback}\n                    addCard={addCard}\n                    onCardClicked={jest.fn()}\n                    hiddenCardsCount={2}\n                    showHiddenCardCountNotification={jest.fn()}\n                />\n            </ReduxProvider>,\n        )\n        const {container, getByTitle} = render(component)\n        expect(getByTitle('hidden-card-count')).toHaveTextContent('2')\n        expect(container).toMatchSnapshot()\n    })\n})\n\ndescribe('components/table/Table extended', () => {\n    const state = {\n        users: {\n            boardUsers: {\n                'user-id-1': {username: 'username_1'} as IUser,\n                'user-id-2': {username: 'username_2'} as IUser,\n                'user-id-3': {username: 'username_3'} as IUser,\n                'user-id-4': {username: 'username_4'} as IUser,\n            },\n        },\n        comments: {\n            comments: {},\n            commentsByCard: {},\n        },\n        contents: {\n            contents: {},\n            contentsByCard: {},\n        },\n        cards: {\n            cards: {},\n        },\n        teams: {\n            current: {id: 'team-id'},\n        },\n        boards: {\n            current: 'board_id',\n            boards: {\n                board_id: {id: 'board_id'},\n            },\n            myBoardMemberships: {\n                board_id: {userId: 'user_id_1', schemeAdmin: true},\n            },\n        },\n        clientConfig: {\n            value: {\n                teammateNameDisplay: 'username',\n            },\n        },\n    }\n\n    test('should match snapshot with CreatedAt', async () => {\n        const board = TestBlockFactory.createBoard()\n\n        const dateCreatedId = Utils.createGuid(IDType.User)\n        board.cardProperties.push({\n            id: dateCreatedId,\n            name: 'Date Created',\n            type: 'createdTime',\n            options: [],\n        })\n\n        const card1 = TestBlockFactory.createCard(board)\n        card1.createAt = Date.parse('15 Jun 2021 16:22:00')\n\n        const card2 = TestBlockFactory.createCard(board)\n        card2.createAt = Date.parse('15 Jun 2021 16:22:00')\n\n        const view = TestBlockFactory.createBoardView(board)\n        view.fields.viewType = 'table'\n        view.fields.groupById = undefined\n        view.fields.visiblePropertyIds = ['property1', 'property2', dateCreatedId]\n\n        const callback = jest.fn()\n        const addCard = jest.fn()\n\n        const mockStore = configureStore([])\n        const store = mockStore({\n            ...state,\n            cards: {\n                cards: {\n                    [card1.id]: card1,\n                    [card2.id]: card2,\n                },\n            },\n            teams: {\n                current: {id: 'team-id'},\n            },\n            boards: {\n                current: board.id,\n                boards: {\n                    [board.id]: board,\n                },\n                myBoardMemberships: {\n                    [board.id]: {userId: 'user_id_1', schemeAdmin: true},\n                },\n            },\n        })\n\n        const component = wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <Table\n                    board={board}\n                    activeView={view}\n                    visibleGroups={[]}\n                    cards={[card1, card2]}\n                    views={[view]}\n                    selectedCardIds={[]}\n                    readonly={false}\n                    cardIdToFocusOnRender=''\n                    showCard={callback}\n                    addCard={addCard}\n                    onCardClicked={jest.fn()}\n                    hiddenCardsCount={0}\n                    showHiddenCardCountNotification={jest.fn()}\n                />\n            </ReduxProvider>,\n        )\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot with UpdatedAt', async () => {\n        const board = TestBlockFactory.createBoard()\n        const dateUpdatedId = Utils.createGuid(IDType.User)\n        board.cardProperties.push({\n            id: dateUpdatedId,\n            name: 'Date Updated',\n            type: 'updatedTime',\n            options: [],\n        })\n\n        const card1 = TestBlockFactory.createCard(board)\n        card1.updateAt = Date.parse('20 Jun 2021 12:22:00')\n\n        const card2 = TestBlockFactory.createCard(board)\n        card2.updateAt = Date.parse('20 Jun 2021 12:22:00')\n\n        const card2Comment = TestBlockFactory.createCard(board)\n        card2Comment.parentId = card2.id\n        card2Comment.type = 'comment'\n        card2Comment.updateAt = Date.parse('21 Jun 2021 15:23:00')\n\n        const card2Text = TestBlockFactory.createCard(board)\n        card2Text.parentId = card2.id\n        card2Text.type = 'text'\n        card2Text.updateAt = Date.parse('22 Jun 2021 11:23:00')\n\n        card2.fields.contentOrder = [card2Text.id]\n\n        const view = TestBlockFactory.createBoardView(board)\n        view.fields.viewType = 'table'\n        view.fields.groupById = undefined\n        view.fields.visiblePropertyIds = ['property1', 'property2', dateUpdatedId]\n\n        const callback = jest.fn()\n        const addCard = jest.fn()\n\n        const mockStore = configureStore([])\n        const store = mockStore({\n            ...state,\n            comments: {\n                comments: {\n                    [card2Comment.id]: card2Comment,\n                },\n                commentsByCard: {\n                    [card2.id]: [card2Comment],\n                },\n            },\n            contents: {\n                contents: {\n                    [card2Text.id]: card2Text,\n                },\n                contentsByCard: {\n                    [card2.id]: [card2Text],\n                },\n            },\n            cards: {\n                cards: {\n                    [card1.id]: card1,\n                    [card2.id]: card2,\n                },\n            },\n        })\n\n        const component = wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <Table\n                    board={board}\n                    activeView={view}\n                    visibleGroups={[]}\n                    cards={[card1, card2]}\n                    views={[view]}\n                    selectedCardIds={[]}\n                    readonly={false}\n                    cardIdToFocusOnRender=''\n                    showCard={callback}\n                    addCard={addCard}\n                    onCardClicked={jest.fn()}\n                    hiddenCardsCount={0}\n                    showHiddenCardCountNotification={jest.fn()}\n                />\n            </ReduxProvider>,\n        )\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot with CreatedBy', async () => {\n        const board = TestBlockFactory.createBoard()\n\n        const createdById = Utils.createGuid(IDType.User)\n        board.cardProperties.push({\n            id: createdById,\n            name: 'Created By',\n            type: 'createdBy',\n            options: [],\n        })\n\n        const card1 = TestBlockFactory.createCard(board)\n        card1.createdBy = 'user-id-1'\n\n        const card2 = TestBlockFactory.createCard(board)\n        card2.createdBy = 'user-id-2'\n\n        const view = TestBlockFactory.createBoardView(board)\n        view.fields.viewType = 'table'\n        view.fields.groupById = undefined\n        view.fields.visiblePropertyIds = ['property1', 'property2', createdById]\n\n        const callback = jest.fn()\n        const addCard = jest.fn()\n\n        const mockStore = configureStore([])\n        const store = mockStore({\n            ...state,\n            cards: {\n                cards: {\n                    [card1.id]: card1,\n                    [card2.id]: card2,\n                },\n            },\n        })\n\n        const component = wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <Table\n                    board={board}\n                    activeView={view}\n                    visibleGroups={[]}\n                    cards={[card1, card2]}\n                    views={[view]}\n                    selectedCardIds={[]}\n                    readonly={false}\n                    cardIdToFocusOnRender=''\n                    showCard={callback}\n                    addCard={addCard}\n                    onCardClicked={jest.fn()}\n                    hiddenCardsCount={0}\n                    showHiddenCardCountNotification={jest.fn()}\n                />\n            </ReduxProvider>,\n        )\n\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot with UpdatedBy', async () => {\n        const board = TestBlockFactory.createBoard()\n\n        const modifiedById = Utils.createGuid(IDType.User)\n        board.cardProperties.push({\n            id: modifiedById,\n            name: 'Last Modified By',\n            type: 'updatedBy',\n            options: [],\n        })\n\n        const card1 = TestBlockFactory.createCard(board)\n        card1.modifiedBy = 'user-id-1'\n        card1.updateAt = Date.parse('15 Jun 2021 16:22:00')\n\n        const card1Text = TestBlockFactory.createCard(board)\n        card1Text.parentId = card1.id\n        card1Text.type = 'text'\n        card1Text.modifiedBy = 'user-id-4'\n        card1Text.updateAt = Date.parse('16 Jun 2021 16:22:00')\n\n        card1.fields.contentOrder = [card1Text.id]\n\n        const card2 = TestBlockFactory.createCard(board)\n        card2.modifiedBy = 'user-id-2'\n        card2.updateAt = Date.parse('15 Jun 2021 16:22:00')\n\n        const card2Comment = TestBlockFactory.createCard(board)\n        card2Comment.parentId = card2.id\n        card2Comment.type = 'comment'\n        card2Comment.modifiedBy = 'user-id-3'\n        card2.updateAt = Date.parse('16 Jun 2021 16:22:00')\n\n        const view = TestBlockFactory.createBoardView(board)\n        view.fields.viewType = 'table'\n        view.fields.groupById = undefined\n        view.fields.visiblePropertyIds = ['property1', 'property2', modifiedById]\n\n        const callback = jest.fn()\n        const addCard = jest.fn()\n\n        const mockStore = configureStore([])\n        const store = mockStore({\n            ...state,\n            comments: {\n                comments: {\n                    [card2Comment.id]: card2Comment,\n                },\n                commentsByCard: {\n                    [card2.id]: [card2Comment],\n                },\n            },\n            contents: {\n                contents: {\n                    [card1Text.id]: card1Text,\n                },\n                contentsByCard: {\n                    [card1.id]: [card1Text],\n                },\n            },\n            cards: {\n                cards: {\n                    [card1.id]: card1,\n                    [card2.id]: card2,\n                },\n            },\n        })\n\n        const component = wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <Table\n                    board={board}\n                    activeView={view}\n                    visibleGroups={[]}\n                    cards={[card1, card2]}\n                    views={[view]}\n                    selectedCardIds={[]}\n                    readonly={false}\n                    cardIdToFocusOnRender=''\n                    showCard={callback}\n                    addCard={addCard}\n                    onCardClicked={jest.fn()}\n                    hiddenCardsCount={0}\n                    showHiddenCardCountNotification={jest.fn()}\n                />\n            </ReduxProvider>,\n        )\n\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should delete snapshot', async () => {\n        const board = TestBlockFactory.createBoard()\n\n        const modifiedById = Utils.createGuid(IDType.User)\n        board.cardProperties.push({\n            id: modifiedById,\n            name: 'Last Modified By',\n            type: 'updatedBy',\n            options: [],\n        })\n        const card1 = TestBlockFactory.createCard(board)\n        card1.title = 'card1'\n        const card2 = TestBlockFactory.createCard(board)\n        card2.title = 'card2'\n        const view = TestBlockFactory.createBoardView(board)\n        view.fields.viewType = 'table'\n        view.fields.groupById = undefined\n        view.fields.visiblePropertyIds = ['property1', 'property2', modifiedById]\n        const mockStore = configureStore([])\n        const store = mockStore({\n            ...state,\n            cards: {\n                cards: {\n                    [card1.id]: card1,\n                    [card2.id]: card2,\n                },\n            },\n        })\n\n        const component = wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <Table\n                    board={board}\n                    activeView={view}\n                    visibleGroups={[]}\n                    cards={[card1, card2]}\n                    views={[view]}\n                    selectedCardIds={[]}\n                    readonly={false}\n                    cardIdToFocusOnRender=''\n                    showCard={jest.fn()}\n                    addCard={jest.fn()}\n                    onCardClicked={jest.fn()}\n                    hiddenCardsCount={0}\n                    showHiddenCardCountNotification={jest.fn()}\n                />\n            </ReduxProvider>,\n        )\n\n        const {getByTitle, getByRole, getAllByTitle} = render(component)\n        const card1Name = getByTitle(card1.title)\n        userEvents.hover(card1Name)\n        const menuBtn = getAllByTitle('MenuBtn')\n        userEvents.click(menuBtn[0])\n        const deleteBtn = getByRole('button', {name: 'Delete'})\n        userEvents.click(deleteBtn)\n        const dailogDeleteBtn = screen.getByRole('button', {name: 'Delete'})\n        userEvents.click(dailogDeleteBtn)\n        expect(mockedMutator.deleteBlock).toBeCalledTimes(1)\n    })\n\n    test('should have Duplicate Button', async () => {\n        const board = TestBlockFactory.createBoard()\n\n        const modifiedById = Utils.createGuid(IDType.User)\n        board.cardProperties.push({\n            id: modifiedById,\n            name: 'Last Modified By',\n            type: 'updatedBy',\n            options: [],\n        })\n        const card1 = TestBlockFactory.createCard(board)\n        card1.title = 'card1'\n        const card2 = TestBlockFactory.createCard(board)\n        card2.title = 'card2'\n        const view = TestBlockFactory.createBoardView(board)\n        view.fields.viewType = 'table'\n        view.fields.groupById = undefined\n        view.fields.visiblePropertyIds = ['property1', 'property2', modifiedById]\n        const mockStore = configureStore([])\n        const store = mockStore({\n            ...state,\n            cards: {\n                cards: {\n                    [card1.id]: card1,\n                    [card2.id]: card2,\n                },\n            },\n        })\n\n        const component = wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <Table\n                    board={board}\n                    activeView={view}\n                    visibleGroups={[]}\n                    cards={[card1, card2]}\n                    views={[view]}\n                    selectedCardIds={[]}\n                    readonly={false}\n                    cardIdToFocusOnRender=''\n                    showCard={jest.fn()}\n                    addCard={jest.fn()}\n                    onCardClicked={jest.fn()}\n                    hiddenCardsCount={0}\n                    showHiddenCardCountNotification={jest.fn()}\n                />\n            </ReduxProvider>,\n        )\n\n        const {getByTitle, getByRole, getAllByTitle, container} = render(component)\n        const card1Name = getByTitle(card1.title)\n        userEvents.hover(card1Name)\n        const menuBtn = getAllByTitle('MenuBtn')\n        userEvents.click(menuBtn[0])\n        const duplicateBtn = getByRole('button', {name: 'Duplicate'})\n        expect(duplicateBtn).not.toBe(null)\n        userEvents.click(duplicateBtn)\n        expect(mockedMutator.duplicateCard).toBeCalledTimes(1)\n        expect(container).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/table/table.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useCallback} from 'react'\n\nimport {FormattedMessage} from 'react-intl'\n\nimport {IPropertyOption, IPropertyTemplate, Board, BoardGroup} from '../../blocks/board'\nimport {createBoardView, BoardView} from '../../blocks/boardView'\nimport {Card} from '../../blocks/card'\nimport {Constants, Permission} from '../../constants'\nimport mutator from '../../mutator'\nimport {Utils} from '../../utils'\nimport {useAppDispatch} from '../../store/hooks'\nimport {updateView} from '../../store/views'\nimport {useHasCurrentBoardPermissions} from '../../hooks/permissions'\n\nimport BoardPermissionGate from '../permissions/boardPermissionGate'\n\nimport './table.scss'\n\nimport HiddenCardCount from '../../components/hiddenCardCount/hiddenCardCount'\n\nimport TableHeaders from './tableHeaders'\nimport TableRows from './tableRows'\nimport TableGroup from './tableGroup'\nimport CalculationRow from './calculation/calculationRow'\nimport {ColumnResizeProvider} from './tableColumnResizeContext'\n\ntype Props = {\n    selectedCardIds: string[]\n    board: Board\n    cards: Card[]\n    activeView: BoardView\n    views: BoardView[]\n    visibleGroups: BoardGroup[]\n    groupByProperty?: IPropertyTemplate\n    readonly: boolean\n    cardIdToFocusOnRender: string\n    showCard: (cardId?: string) => void\n    addCard: (groupByOptionId?: string) => Promise<void>\n    onCardClicked: (e: React.MouseEvent, card: Card) => void\n    hiddenCardsCount: number\n    showHiddenCardCountNotification: (show: boolean) => void\n}\n\nconst Table = (props: Props): JSX.Element => {\n    const {board, cards, activeView, visibleGroups, groupByProperty, views, hiddenCardsCount} = props\n    const isManualSort = activeView.fields.sortOptions?.length === 0\n    const canEditBoardProperties = useHasCurrentBoardPermissions([Permission.ManageBoardProperties])\n    const canEditCards = useHasCurrentBoardPermissions([Permission.ManageBoardCards])\n    const dispatch = useAppDispatch()\n\n    const resizeColumn = useCallback(async (columnId: string, width: number) => {\n        const columnWidths = {...activeView.fields.columnWidths}\n        const newWidth = Math.max(Constants.minColumnWidth, width)\n        if (newWidth !== columnWidths[columnId]) {\n            Utils.log(`Resize of column finished: prev=${columnWidths[columnId]}, new=${newWidth}`)\n\n            columnWidths[columnId] = newWidth\n\n            const newView = createBoardView(activeView)\n            newView.fields.columnWidths = columnWidths\n            try {\n                dispatch(updateView(newView))\n                await mutator.updateBlock(board.id, newView, activeView, 'resize column')\n            } catch {\n                dispatch(updateView(activeView))\n            }\n        }\n    }, [activeView])\n\n    const hideGroup = useCallback((groupById: string): void => {\n        const index: number = activeView.fields.collapsedOptionIds.indexOf(groupById)\n        const newValue: string[] = [...activeView.fields.collapsedOptionIds]\n        if (index > -1) {\n            newValue.splice(index, 1)\n        } else if (groupById !== '') {\n            newValue.push(groupById)\n        }\n\n        const newView = createBoardView(activeView)\n        newView.fields.collapsedOptionIds = newValue\n        mutator.performAsUndoGroup(async () => {\n            await mutator.updateBlock(board.id, newView, activeView, 'hide group')\n        })\n    }, [activeView])\n\n    const onDropToGroupHeader = useCallback(async (option: IPropertyOption, dstOption?: IPropertyOption) => {\n        if (dstOption) {\n            Utils.log(`ondrop. Header target: ${dstOption.value}, source: ${option?.value}`)\n\n            // Move option to new index\n            const visibleOptionIds = visibleGroups.map((o) => o.option.id)\n            const srcIndex = visibleOptionIds.indexOf(dstOption.id)\n            const destIndex = visibleOptionIds.indexOf(option.id)\n\n            visibleOptionIds.splice(srcIndex, 0, visibleOptionIds.splice(destIndex, 1)[0])\n            Utils.log(`ondrop. updated visibleoptionids: ${visibleOptionIds}`)\n\n            await mutator.changeViewVisibleOptionIds(board.id, activeView.id, activeView.fields.visibleOptionIds, visibleOptionIds)\n        }\n    }, [activeView, visibleGroups])\n\n    const onDropToCard = useCallback((srcCard: Card, dstCard: Card) => {\n        Utils.log(`onDropToCard: ${dstCard.title}`)\n        onDropToGroup(srcCard, dstCard.fields.properties[activeView.fields.groupById!] as string, dstCard.id)\n    }, [activeView.fields.groupById, cards])\n\n    const onDropToGroup = useCallback((srcCard: Card, groupID: string, dstCardID: string) => {\n        Utils.log(`onDropToGroup: ${srcCard.title}`)\n        const {selectedCardIds} = props\n\n        const draggedCardIds = Array.from(new Set(selectedCardIds).add(srcCard.id))\n        const description = draggedCardIds.length > 1 ? `drag ${draggedCardIds.length} cards` : 'drag card'\n\n        if (activeView.fields.groupById !== undefined) {\n            const cardsById: { [key: string]: Card } = cards.reduce((acc: { [key: string]: Card }, card: Card): { [key: string]: Card } => {\n                acc[card.id] = card\n                return acc\n            }, {})\n            const draggedCards: Card[] = draggedCardIds.map((o: string) => cardsById[o])\n\n            mutator.performAsUndoGroup(async () => {\n                // Update properties of dragged cards\n                const awaits = []\n                for (const draggedCard of draggedCards) {\n                    Utils.log(`draggedCard: ${draggedCard.title}, column: ${draggedCard.fields.properties}`)\n                    Utils.log(`droppedColumn:  ${groupID}`)\n                    const oldOptionId = draggedCard.fields.properties[groupByProperty!.id]\n                    Utils.log(`ondrop. oldValue: ${oldOptionId}`)\n\n                    if (groupID !== oldOptionId) {\n                        awaits.push(mutator.changePropertyValue(board.id, draggedCard, groupByProperty!.id, groupID, description))\n                    }\n                }\n                await Promise.all(awaits)\n            })\n        }\n\n        // Update dstCard order\n        if (isManualSort) {\n            let cardOrder = Array.from(new Set([...activeView.fields.cardOrder, ...cards.map((o) => o.id)]))\n            if (dstCardID) {\n                const isDraggingDown = cardOrder.indexOf(srcCard.id) <= cardOrder.indexOf(dstCardID)\n                cardOrder = cardOrder.filter((id) => !draggedCardIds.includes(id))\n                let destIndex = cardOrder.indexOf(dstCardID)\n                if (isDraggingDown) {\n                    destIndex += 1\n                }\n                cardOrder.splice(destIndex, 0, ...draggedCardIds)\n            } else {\n                // Find index of first group item\n                const firstCard = cards.find((card) => card.fields.properties[activeView.fields.groupById!] === groupID)\n                if (firstCard) {\n                    const destIndex = cardOrder.indexOf(firstCard.id)\n                    cardOrder.splice(destIndex, 0, ...draggedCardIds)\n                } else {\n                    // if not found, this is the only item in group.\n                    return\n                }\n            }\n\n            mutator.performAsUndoGroup(async () => {\n                await mutator.changeViewCardOrder(board.id, activeView.id, activeView.fields.cardOrder, cardOrder, description)\n            })\n        }\n    }, [activeView, cards, props.selectedCardIds, groupByProperty])\n\n    const propertyNameChanged = useCallback(async (option: IPropertyOption, text: string): Promise<void> => {\n        await mutator.changePropertyOptionValue(board.id, board.cardProperties, groupByProperty!, option, text)\n    }, [board, groupByProperty])\n\n    return (\n        <div className='Table'>\n            <ColumnResizeProvider\n                columnWidths={activeView.fields.columnWidths}\n                onResizeColumn={resizeColumn}\n            >\n                <div className='octo-table-body'>\n                    <TableHeaders\n                        board={board}\n                        cards={cards}\n                        activeView={activeView}\n                        views={views}\n                        readonly={props.readonly || !canEditBoardProperties}\n                    />\n\n                    {/* Table rows */}\n                    <div className='table-row-container'>\n                        {activeView.fields.groupById &&\n                    visibleGroups.map((group) => {\n                        return (\n                            <TableGroup\n                                key={group.option.id}\n                                board={board}\n                                activeView={activeView}\n                                groupByProperty={groupByProperty}\n                                group={group}\n                                readonly={props.readonly || !canEditCards}\n                                selectedCardIds={props.selectedCardIds}\n                                cardIdToFocusOnRender={props.cardIdToFocusOnRender}\n                                hideGroup={hideGroup}\n                                addCard={props.addCard}\n                                showCard={props.showCard}\n                                propertyNameChanged={propertyNameChanged}\n                                onCardClicked={props.onCardClicked}\n                                onDropToGroupHeader={onDropToGroupHeader}\n                                onDropToCard={onDropToCard}\n                                onDropToGroup={onDropToGroup}\n                            />)\n                    })\n                        }\n\n                        {/* No Grouping, Rows, one per card */}\n                        {!activeView.fields.groupById &&\n                        <TableRows\n                            board={board}\n                            activeView={activeView}\n                            cards={cards}\n                            selectedCardIds={props.selectedCardIds}\n                            readonly={props.readonly || !canEditCards}\n                            cardIdToFocusOnRender={props.cardIdToFocusOnRender}\n                            showCard={props.showCard}\n                            addCard={props.addCard}\n                            onCardClicked={props.onCardClicked}\n                            onDrop={onDropToCard}\n                        />\n                        }\n                    </div>\n\n                    {/* Add New row */}\n                    <div className='octo-table-footer'>\n                        {!props.readonly && !activeView.fields.groupById &&\n                        <BoardPermissionGate permissions={[Permission.ManageBoardCards]}>\n                            <div\n                                className='octo-table-cell'\n                                onClick={() => {\n                                    props.addCard('')\n                                }}\n                            >\n                                <FormattedMessage\n                                    id='TableComponent.plus-new'\n                                    defaultMessage='+ New'\n                                />\n                            </div>\n                        </BoardPermissionGate>\n                        }\n                    </div>\n\n                    <CalculationRow\n                        board={board}\n                        cards={cards}\n                        activeView={activeView}\n                        readonly={props.readonly || !canEditBoardProperties}\n                    />\n                </div>\n            </ColumnResizeProvider>\n\n            {hiddenCardsCount > 0 &&\n            <HiddenCardCount\n                showHiddenCardNotification={props.showHiddenCardCountNotification}\n                hiddenCardsCount={hiddenCardsCount}\n            />}\n        </div>\n    )\n}\n\nexport default Table\n"
  },
  {
    "path": "webapp/src/components/table/tableColumnResizeContext.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {createContext, ReactElement, ReactNode, useCallback, useContext, useMemo} from 'react'\n\nimport {Constants} from '../../constants'\n\nexport type ColumnResizeContextType = {\n    updateRef: (cardId: string, columnId: string, element: HTMLDivElement | null) => void\n    cellRef: (columnId: string) => HTMLDivElement | undefined\n    width: (columnId: string) => number\n    updateOffset: (columnId: string, offset: number) => void\n    updateWidth: (columnId: string, width: number) => void\n}\n\nconst ColumnResizeContext = createContext<ColumnResizeContextType | null>(null)\n\nexport function useColumnResize(): ColumnResizeContextType {\n    const context = useContext(ColumnResizeContext)\n    if (!context) {\n        throw new Error('ColumnResizeContext is not available!')\n    }\n    return context\n}\n\nexport type ColumnResizeProviderProps = {\n    children: ReactNode\n    columnWidths: Record<string, number>\n    onResizeColumn: (columnId: string, width: number) => void\n}\n\nconst columnWidth = (columnId: string, columnWidths: Record<string, number>, offset: number): string => {\n    return `${Math.max(Constants.minColumnWidth, (columnWidths[columnId] || 0) + offset)}px`\n}\n\nexport const ColumnResizeProvider = (props: ColumnResizeProviderProps): ReactElement => {\n    const {children, columnWidths, onResizeColumn} = props\n\n    type ElementsMap = Map<string, HTMLDivElement>\n    const columns = useMemo(() => new Map<string, ElementsMap>(), [])\n\n    const updateWidth = useCallback((columnId: string, elements: ElementsMap, offset: number) => {\n        const width = columnWidth(columnId, columnWidths, offset)\n        for (const element of elements.values()) {\n            element.style.width = width\n        }\n    }, [columnWidths])\n\n    const contextValue = useMemo((): ColumnResizeContextType => ({\n        updateRef: (cardId, columnId, element) => {\n            let elements = columns.get(columnId)\n            if (element) {\n                if (!elements) {\n                    elements = new Map()\n                    columns.set(columnId, elements)\n                }\n                elements.set(cardId, element)\n            } else if (elements) {\n                elements.delete(cardId)\n            }\n        },\n        cellRef: (columnId): HTMLDivElement | undefined => {\n            const iter = columns.get(columnId)?.values()\n            if (iter) {\n                const {value, done} = iter.next()\n                return done ? value : iter.next().value\n            }\n            return undefined\n        },\n        width: (columnId) => {\n            return Math.max(Constants.minColumnWidth, (columnWidths[columnId] || 0))\n        },\n        updateOffset: (columnId, offset) => {\n            const elements = columns.get(columnId)\n            if (elements) {\n                updateWidth(columnId, elements, offset)\n            }\n        },\n        updateWidth: (columnId, width) => {\n            onResizeColumn(columnId, width)\n        },\n    }), [columnWidths, onResizeColumn])\n\n    return (\n        <ColumnResizeContext.Provider value={contextValue}>\n            {children}\n        </ColumnResizeContext.Provider>\n    )\n}\n"
  },
  {
    "path": "webapp/src/components/table/tableGroup.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n/* eslint-disable max-lines */\nimport React from 'react'\n\nimport {useDrop} from 'react-dnd'\n\nimport {Board, IPropertyOption, IPropertyTemplate, BoardGroup} from '../../blocks/board'\nimport {BoardView} from '../../blocks/boardView'\nimport {Card} from '../../blocks/card'\n\nimport TableGroupHeaderRow from './tableGroupHeaderRow'\nimport TableRows from './tableRows'\n\ntype Props = {\n    board: Board\n    activeView: BoardView\n    groupByProperty?: IPropertyTemplate\n    group: BoardGroup\n    readonly: boolean\n    selectedCardIds: string[]\n    cardIdToFocusOnRender: string\n    hideGroup: (groupByOptionId: string) => void\n    addCard: (groupByOptionId?: string) => Promise<void>\n    showCard: (cardId?: string) => void\n    propertyNameChanged: (option: IPropertyOption, text: string) => Promise<void>\n    onCardClicked: (e: React.MouseEvent, card: Card) => void\n    onDropToGroupHeader: (srcOption: IPropertyOption, dstOption?: IPropertyOption) => void\n    onDropToCard: (srcCard: Card, dstCard: Card) => void\n    onDropToGroup: (srcCard: Card, groupID: string, dstCardID: string) => void\n}\n\nconst TableGroup = (props: Props): JSX.Element => {\n    const {board, activeView, group, onDropToGroup, groupByProperty} = props\n    const groupId = group.option.id\n\n    const [{isOver}, drop] = useDrop(() => ({\n        accept: 'card',\n        collect: (monitor) => ({\n            isOver: monitor.isOver(),\n        }),\n        drop: (item: Card, monitor) => {\n            if (monitor.isOver({shallow: true})) {\n                onDropToGroup(item, groupId, '')\n            }\n        },\n    }), [onDropToGroup, groupId])\n\n    let className = 'octo-table-group'\n    if (isOver) {\n        className += ' dragover'\n    }\n\n    return (\n        <div\n            ref={drop}\n            className={className}\n            key={group.option.id}\n        >\n            <TableGroupHeaderRow\n                group={group}\n                board={board}\n                activeView={activeView}\n                groupByProperty={groupByProperty}\n                hideGroup={props.hideGroup}\n                addCard={props.addCard}\n                readonly={props.readonly}\n                propertyNameChanged={props.propertyNameChanged}\n                onDrop={props.onDropToGroupHeader}\n            />\n\n            {(group.cards.length > 0) &&\n            <TableRows\n                board={board}\n                activeView={activeView}\n                cards={group.cards}\n                selectedCardIds={props.selectedCardIds}\n                readonly={props.readonly}\n                cardIdToFocusOnRender={props.cardIdToFocusOnRender}\n                showCard={props.showCard}\n                addCard={props.addCard}\n                onCardClicked={props.onCardClicked}\n                onDrop={props.onDropToCard}\n            />}\n        </div>\n    )\n}\n\nexport default React.memo(TableGroup)\n"
  },
  {
    "path": "webapp/src/components/table/tableGroupHeaderRow.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {fireEvent, render} from '@testing-library/react'\nimport '@testing-library/jest-dom'\n\nimport 'isomorphic-fetch'\n\nimport {act} from 'react-dom/test-utils'\n\nimport userEvent from '@testing-library/user-event'\n\nimport {wrapDNDIntl} from '../../testUtils'\n\nimport {TestBlockFactory} from '../../test/testBlockFactory'\n\nimport {ColumnResizeProvider} from './tableColumnResizeContext'\nimport TableGroupHeaderRowElement from './tableGroupHeaderRow'\n\nconst board = TestBlockFactory.createBoard()\nconst view = TestBlockFactory.createBoardView(board)\n\nconst view2 = TestBlockFactory.createBoardView(board)\nview2.fields.sortOptions = []\n\nconst boardTreeNoGroup = {\n    option: {\n        id: '',\n        value: '',\n        color: 'propColorBrown',\n    },\n    cards: [],\n}\n\nconst boardTreeGroup = {\n    option: {\n        id: 'value1',\n        value: 'value 1',\n        color: 'propColorBrown',\n    },\n    cards: [],\n}\n\nconst Wrapper: React.FC = ({children}) => {\n    return wrapDNDIntl(\n        <ColumnResizeProvider\n            columnWidths={{}}\n            onResizeColumn={jest.fn()}\n        >\n            {children}\n        </ColumnResizeProvider>,\n    )\n}\n\ntest('should match snapshot, no groups', async () => {\n    const {container} = render(\n        <Wrapper>\n            <TableGroupHeaderRowElement\n                board={board}\n                activeView={view}\n                group={boardTreeNoGroup}\n                readonly={false}\n                hideGroup={jest.fn()}\n                addCard={jest.fn()}\n                propertyNameChanged={jest.fn()}\n                onDrop={jest.fn()}\n                groupByProperty={{\n                    id: '',\n                    name: 'Property 1',\n                    type: 'text',\n                    options: [{id: 'property1', value: 'Property 1', color: ''}],\n                }}\n            />\n        </Wrapper>,\n    )\n    expect(container).toMatchSnapshot()\n})\n\ntest('should match snapshot with Group', async () => {\n    const {container} = render(\n        <Wrapper>\n            <TableGroupHeaderRowElement\n                board={board}\n                activeView={view}\n                group={boardTreeGroup}\n                readonly={false}\n                hideGroup={jest.fn()}\n                addCard={jest.fn()}\n                propertyNameChanged={jest.fn()}\n                onDrop={jest.fn()}\n            />\n        </Wrapper>,\n    )\n    expect(container).toMatchSnapshot()\n})\n\ntest('should match snapshot on read only', async () => {\n    const {container} = render(\n        <Wrapper>\n            <TableGroupHeaderRowElement\n                board={board}\n                activeView={view}\n                group={boardTreeGroup}\n                readonly={true}\n                hideGroup={jest.fn()}\n                addCard={jest.fn()}\n                propertyNameChanged={jest.fn()}\n                onDrop={jest.fn()}\n            />\n        </Wrapper>,\n    )\n    expect(container).toMatchSnapshot()\n})\n\ntest('should match snapshot, hide group', async () => {\n    const hideGroup = jest.fn()\n\n    const collapsedOptionsView = TestBlockFactory.createBoardView(board)\n    collapsedOptionsView.fields.collapsedOptionIds = [boardTreeGroup.option.id]\n\n    const {container} = render(\n        <Wrapper>\n            <TableGroupHeaderRowElement\n                board={board}\n                activeView={collapsedOptionsView}\n                group={boardTreeGroup}\n                readonly={false}\n                hideGroup={hideGroup}\n                addCard={jest.fn()}\n                propertyNameChanged={jest.fn()}\n                onDrop={jest.fn()}\n            />\n        </Wrapper>,\n    )\n\n    const triangle = container.querySelector('.octo-table-cell__expand')\n    expect(triangle).not.toBeNull()\n\n    act(() => {\n        fireEvent.click(triangle as Element)\n    })\n    expect(hideGroup).toBeCalled()\n    expect(container).toMatchSnapshot()\n})\n\ntest('should match snapshot, add new', async () => {\n    const addNew = jest.fn()\n\n    const {container} = render(\n        <Wrapper>\n            <TableGroupHeaderRowElement\n                board={board}\n                activeView={view}\n                group={boardTreeGroup}\n                readonly={false}\n                hideGroup={jest.fn()}\n                addCard={addNew}\n                propertyNameChanged={jest.fn()}\n                onDrop={jest.fn()}\n            />\n        </Wrapper>,\n    )\n\n    const triangle = container.querySelector('i.AddIcon')\n    expect(triangle).not.toBeNull()\n\n    act(() => {\n        fireEvent.click(triangle as Element)\n    })\n    expect(addNew).toBeCalled()\n    expect(container).toMatchSnapshot()\n})\n\ntest('should match snapshot, edit title', async () => {\n    const {container, getByTitle} = render(\n        <Wrapper>\n            <TableGroupHeaderRowElement\n                board={board}\n                activeView={view}\n                group={boardTreeGroup}\n                readonly={false}\n                hideGroup={jest.fn()}\n                addCard={jest.fn()}\n                propertyNameChanged={jest.fn()}\n                onDrop={jest.fn()}\n            />\n        </Wrapper>,\n    )\n\n    const input = getByTitle(/value 1/)\n    act(() => {\n        userEvent.click(input)\n        userEvent.keyboard('{enter}')\n    })\n\n    expect(container).toMatchSnapshot()\n})\n"
  },
  {
    "path": "webapp/src/components/table/tableGroupHeaderRow.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n/* eslint-disable max-lines */\nimport React, {useState, useEffect} from 'react'\nimport {FormattedMessage, useIntl} from 'react-intl'\n\nimport {Constants} from '../../constants'\nimport {IPropertyOption, Board, IPropertyTemplate, BoardGroup} from '../../blocks/board'\nimport {BoardView} from '../../blocks/boardView'\nimport {useSortable} from '../../hooks/sortable'\nimport mutator from '../../mutator'\nimport Button from '../../widgets/buttons/button'\nimport IconButton from '../../widgets/buttons/iconButton'\nimport AddIcon from '../../widgets/icons/add'\nimport DeleteIcon from '../../widgets/icons/delete'\nimport CompassIcon from '../../widgets/icons/compassIcon'\nimport HideIcon from '../../widgets/icons/hide'\nimport OptionsIcon from '../../widgets/icons/options'\nimport Menu from '../../widgets/menu'\nimport MenuWrapper from '../../widgets/menuWrapper'\nimport Editable from '../../widgets/editable'\nimport Label from '../../widgets/label'\n\nimport {useColumnResize} from './tableColumnResizeContext'\n\ntype Props = {\n    board: Board\n    activeView: BoardView\n    group: BoardGroup\n    groupByProperty?: IPropertyTemplate\n    readonly: boolean\n    hideGroup: (groupByOptionId: string) => void\n    addCard: (groupByOptionId?: string) => Promise<void>\n    propertyNameChanged: (option: IPropertyOption, text: string) => Promise<void>\n    onDrop: (srcOption: IPropertyOption, dstOption?: IPropertyOption) => void\n}\n\nconst TableGroupHeaderRow = (props: Props): JSX.Element => {\n    const {board, activeView, group, groupByProperty} = props\n    const [groupTitle, setGroupTitle] = useState(group.option.value)\n\n    const [isDragging, isOver, groupHeaderRef] = useSortable('groupHeader', group.option, !props.readonly, props.onDrop)\n    const intl = useIntl()\n    const columnResize = useColumnResize()\n\n    useEffect(() => {\n        setGroupTitle(group.option.value)\n    }, [group.option.value])\n    let className = 'octo-group-header-cell'\n    if (isOver) {\n        className += ' dragover'\n    }\n    if (activeView.fields.collapsedOptionIds.indexOf(group.option.id || 'undefined') < 0) {\n        className += ' expanded'\n    }\n\n    const canEditOption = groupByProperty?.type !== 'person' && group.option.id\n\n    return (\n        <div\n            key={group.option.id + 'header'}\n            ref={groupHeaderRef}\n            style={{opacity: isDragging ? 0.5 : 1}}\n            className={className}\n        >\n            <div\n                className='octo-table-cell'\n                style={{width: columnResize.width(Constants.titleColumnId)}}\n                ref={(ref) => columnResize.updateRef(group.option.id, Constants.titleColumnId, ref)}\n            >\n                <IconButton\n                    icon={\n                        <CompassIcon\n                            icon='menu-right'\n                        />}\n                    onClick={() => (props.readonly ? {} : props.hideGroup(group.option.id || 'undefined'))}\n                    className={`octo-table-cell__expand ${props.readonly ? 'readonly' : ''}`}\n                />\n\n                {!group.option.id &&\n                    <Label\n                        title={intl.formatMessage({\n                            id: 'BoardComponent.no-property-title',\n                            defaultMessage: 'Items with an empty {property} property will go here. This column cannot be removed.',\n                        }, {property: groupByProperty?.name})}\n                    >\n                        <FormattedMessage\n                            id='BoardComponent.no-property'\n                            defaultMessage='No {property}'\n                            values={{\n                                property: groupByProperty?.name,\n                            }}\n                        />\n                    </Label>}\n                {groupByProperty?.type === 'person' &&\n                    <Label>\n                        {groupTitle}\n                    </Label>}\n                {canEditOption &&\n                    <Label color={group.option.color}>\n                        <Editable\n                            value={groupTitle}\n                            placeholderText='New Select'\n                            onChange={setGroupTitle}\n                            onSave={() => {\n                                if (groupTitle.trim() === '') {\n                                    setGroupTitle(group.option.value)\n                                }\n                                props.propertyNameChanged(group.option, groupTitle)\n                            }}\n                            onCancel={() => {\n                                setGroupTitle(group.option.value)\n                            }}\n                            readonly={props.readonly || !group.option.id}\n                            spellCheck={true}\n                        />\n                    </Label>}\n            </div>\n            <Button>{`${group.cards.length}`}</Button>\n            {!props.readonly &&\n                <>\n                    <MenuWrapper>\n                        <IconButton icon={<OptionsIcon/>}/>\n                        <Menu>\n                            <Menu.Text\n                                id='hide'\n                                icon={<HideIcon/>}\n                                name={intl.formatMessage({id: 'BoardComponent.hide', defaultMessage: 'Hide'})}\n                                onClick={() => mutator.hideViewColumn(board.id, activeView, group.option.id || '')}\n                            />\n                            {canEditOption &&\n                                <>\n                                    <Menu.Text\n                                        id='delete'\n                                        icon={<DeleteIcon/>}\n                                        name={intl.formatMessage({id: 'BoardComponent.delete', defaultMessage: 'Delete'})}\n                                        onClick={() => mutator.deletePropertyOption(board.id, board.cardProperties, groupByProperty!, group.option)}\n                                    />\n                                    <Menu.Separator/>\n                                    {Object.entries(Constants.menuColors).map(([key, color]) => (\n                                        <Menu.Color\n                                            key={key}\n                                            id={key}\n                                            name={color}\n                                            onClick={() => mutator.changePropertyOptionColor(board.id, board.cardProperties, groupByProperty!, group.option, key)}\n                                        />\n                                    ))}\n                                </>}\n                        </Menu>\n                    </MenuWrapper>\n                    <IconButton\n                        icon={<AddIcon/>}\n                        onClick={() => props.addCard(group.option.id)}\n                    />\n                </>\n            }\n        </div>\n    )\n}\n\nexport default React.memo(TableGroupHeaderRow)\n"
  },
  {
    "path": "webapp/src/components/table/tableHeader.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {render} from '@testing-library/react'\nimport '@testing-library/jest-dom'\n\nimport 'isomorphic-fetch'\nimport {wrapDNDIntl} from '../../testUtils'\n\nimport {TestBlockFactory} from '../../test/testBlockFactory'\n\nimport {ColumnResizeProvider} from './tableColumnResizeContext'\nimport TableHeader from './tableHeader'\n\ndescribe('components/table/TableHeaderMenu', () => {\n    const board = TestBlockFactory.createBoard()\n    const view = TestBlockFactory.createBoardView(board)\n\n    const view2 = TestBlockFactory.createBoardView(board)\n    view2.fields.sortOptions = []\n\n    test('should match snapshot, title column', async () => {\n        const onAutoSizeColumn = jest.fn()\n        const component = wrapDNDIntl(\n            <ColumnResizeProvider\n                columnWidths={{}}\n                onResizeColumn={jest.fn()}\n            >\n                <TableHeader\n                    readonly={false}\n                    sorted={'none'}\n                    name={'my Name'}\n                    board={board}\n                    activeView={view}\n                    cards={[]}\n                    views={[view, view2]}\n                    template={board.cardProperties[0]}\n                    onDrop={jest.fn()}\n                    onAutoSizeColumn={onAutoSizeColumn}\n                />\n            </ColumnResizeProvider>,\n        )\n\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/table/tableHeader.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\n\nimport {Board, IPropertyTemplate} from '../../blocks/board'\nimport {Constants} from '../../constants'\nimport {Card} from '../../blocks/card'\nimport {BoardView} from '../../blocks/boardView'\nimport SortDownIcon from '../../widgets/icons/sortDown'\nimport SortUpIcon from '../../widgets/icons/sortUp'\nimport MenuWrapper from '../../widgets/menuWrapper'\nimport Label from '../../widgets/label'\nimport {useSortable} from '../../hooks/sortable'\nimport {Utils} from '../../utils'\n\nimport HorizontalGrip from './horizontalGrip'\n\nimport './table.scss'\nimport TableHeaderMenu from './tableHeaderMenu'\nimport {useColumnResize} from './tableColumnResizeContext'\n\ntype Props = {\n    readonly: boolean\n    sorted: 'up'|'down'|'none'\n    name: React.ReactNode\n    board: Board\n    activeView: BoardView\n    cards: Card[]\n    views: BoardView[]\n    template: IPropertyTemplate\n    onDrop: (template: IPropertyTemplate, container: IPropertyTemplate) => void\n    onAutoSizeColumn: (columnID: string, headerWidth: number) => void\n}\n\nconst TableHeader = (props: Props): JSX.Element => {\n    const [isDragging, isOver, columnRef] = useSortable('column', props.template, !props.readonly, props.onDrop)\n\n    const columnResize = useColumnResize()\n\n    const onAutoSizeColumn = (templateId: string) => {\n        let width = Constants.minColumnWidth\n        if (columnRef.current) {\n            const {fontDescriptor, padding} = Utils.getFontAndPaddingFromCell(columnRef.current)\n            const textWidth = Utils.getTextWidth(columnRef.current.innerText.toUpperCase(), fontDescriptor)\n            width = textWidth + padding\n        }\n        props.onAutoSizeColumn(templateId, width)\n    }\n\n    let className = 'octo-table-cell header-cell'\n    if (isOver) {\n        className += ' dragover'\n    }\n\n    const templateId = props.template.id\n\n    return (\n        <div\n            className={className}\n            style={{\n                overflow: 'unset',\n                opacity: isDragging ? 0.5 : 1,\n                width: columnResize.width(templateId),\n            }}\n            ref={(ref) => {\n                if (ref && templateId !== Constants.titleColumnId) {\n                    (columnRef as React.MutableRefObject<HTMLDivElement>).current = ref\n                }\n                columnResize.updateRef(Constants.tableHeaderId, templateId, ref)\n            }}\n        >\n            <MenuWrapper disabled={props.readonly}>\n                <Label>\n                    {props.name}\n                    {props.sorted === 'up' && <SortUpIcon/>}\n                    {props.sorted === 'down' && <SortDownIcon/>}\n                </Label>\n                <TableHeaderMenu\n                    board={props.board}\n                    activeView={props.activeView}\n                    views={props.views}\n                    cards={props.cards}\n                    templateId={templateId}\n                />\n            </MenuWrapper>\n\n            <div className='octo-spacer'/>\n\n            {!props.readonly &&\n                <HorizontalGrip\n                    templateId={templateId}\n                    columnWidth={props.activeView.fields.columnWidths[templateId] || 0}\n                    onAutoSizeColumn={onAutoSizeColumn}\n                />\n            }\n        </div>\n    )\n}\n\nexport default React.memo(TableHeader)\n"
  },
  {
    "path": "webapp/src/components/table/tableHeaderMenu.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {fireEvent, render} from '@testing-library/react'\n\nimport '@testing-library/jest-dom'\nimport {wrapIntl} from '../../testUtils'\n\nimport 'isomorphic-fetch'\n\nimport {Constants} from '../../constants'\nimport mutator from '../../mutator'\n\nimport {TestBlockFactory} from '../../test/testBlockFactory'\nimport {FetchMock} from '../../test/fetchMock'\n\nimport TableHeaderMenu from './tableHeaderMenu'\n\nglobal.fetch = FetchMock.fn\n\n// import mutator from '../../mutator'\n\njest.mock('../../mutator', () => ({\n    changeViewSortOptions: jest.fn(),\n    insertPropertyTemplate: jest.fn(),\n    changeViewVisibleProperties: jest.fn(),\n    duplicatePropertyTemplate: jest.fn(),\n    deleteProperty: jest.fn(),\n}))\n\nbeforeEach(() => {\n    jest.resetAllMocks()\n    FetchMock.fn.mockReset()\n})\n\ndescribe('components/table/TableHeaderMenu', () => {\n    const board = TestBlockFactory.createBoard()\n    const view = TestBlockFactory.createBoardView(board)\n\n    const view2 = TestBlockFactory.createBoardView(board)\n    view2.fields.sortOptions = []\n\n    test('should match snapshot, title column', async () => {\n        const component = wrapIntl(\n            <TableHeaderMenu\n                templateId={Constants.titleColumnId}\n                board={board}\n                activeView={view}\n                views={[view, view2]}\n                cards={[]}\n            />,\n        )\n        const {container, getByText} = render(component)\n\n        let sort = getByText(/Sort ascending/i)\n        fireEvent.click(sort)\n        sort = getByText(/Sort descending/i)\n        fireEvent.click(sort)\n        expect(mutator.changeViewSortOptions).toHaveBeenCalledTimes(2)\n\n        let insert = getByText(/Insert left/i)\n        fireEvent.click(insert)\n        insert = getByText(/Insert right/i)\n        fireEvent.click(insert)\n        expect(mutator.insertPropertyTemplate).toHaveBeenCalledTimes(0)\n\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot, other column', async () => {\n        const component = wrapIntl(\n            <TableHeaderMenu\n                templateId={'property 1'}\n                board={board}\n                activeView={view}\n                views={[view, view2]}\n                cards={[]}\n            />,\n        )\n        const {container, getByText} = render(component)\n\n        let sort = getByText(/Sort ascending/i)\n        fireEvent.click(sort)\n        sort = getByText(/Sort descending/i)\n        fireEvent.click(sort)\n        expect(mutator.changeViewSortOptions).toHaveBeenCalledTimes(2)\n\n        let insert = getByText(/Insert left/i)\n        fireEvent.click(insert)\n        insert = getByText(/Insert right/i)\n        fireEvent.click(insert)\n        expect(mutator.insertPropertyTemplate).toHaveBeenCalledTimes(2)\n\n        const hide = getByText(/Hide/i)\n        fireEvent.click(hide)\n        expect(mutator.changeViewVisibleProperties).toHaveBeenCalled()\n        const duplicate = getByText(/Duplicate/i)\n        fireEvent.click(duplicate)\n        expect(mutator.duplicatePropertyTemplate).toHaveBeenCalled()\n        const del = getByText(/Delete/i)\n        fireEvent.click(del)\n        expect(mutator.deleteProperty).toHaveBeenCalled()\n\n        expect(container).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/table/tableHeaderMenu.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n//\nimport React, {FC} from 'react'\nimport {useIntl} from 'react-intl'\n\nimport {Constants} from '../../constants'\nimport {Board, IPropertyTemplate} from '../../blocks/board'\nimport {BoardView} from '../../blocks/boardView'\nimport {Card} from '../../blocks/card'\nimport mutator from '../../mutator'\nimport Menu from '../../widgets/menu'\n\ntype Props = {\n    templateId: string\n    board: Board\n    activeView: BoardView\n    views: BoardView[]\n    cards: Card[]\n}\n\nconst TableHeaderMenu: FC<Props> = (props: Props): JSX.Element => {\n    const {board, activeView, templateId, views, cards} = props\n    const intl = useIntl()\n    return (\n        <Menu>\n            <Menu.Text\n                id='sortAscending'\n                name={intl.formatMessage({id: 'TableHeaderMenu.sort-ascending', defaultMessage: 'Sort ascending'})}\n                onClick={() => mutator.changeViewSortOptions(board.id, activeView.id, activeView.fields.sortOptions, [{propertyId: templateId, reversed: false}])}\n            />\n            <Menu.Text\n                id='sortDescending'\n                name={intl.formatMessage({id: 'TableHeaderMenu.sort-descending', defaultMessage: 'Sort descending'})}\n                onClick={() => mutator.changeViewSortOptions(board.id, activeView.id, activeView.fields.sortOptions, [{propertyId: templateId, reversed: true}])}\n            />\n            <Menu.Text\n                id='insertLeft'\n                name={intl.formatMessage({id: 'TableHeaderMenu.insert-left', defaultMessage: 'Insert left'})}\n                onClick={() => {\n                    if (props.templateId === Constants.titleColumnId) {\n                        // eslint-disable-next-line no-warning-comments\n                        // TODO: Handle name column\n                    } else {\n                        const index = board.cardProperties.findIndex((o: IPropertyTemplate) => o.id === templateId)\n                        mutator.insertPropertyTemplate(board, activeView, index)\n                    }\n                }}\n            />\n            <Menu.Text\n                id='insertRight'\n                name={intl.formatMessage({id: 'TableHeaderMenu.insert-right', defaultMessage: 'Insert right'})}\n                onClick={() => {\n                    if (templateId === Constants.titleColumnId) {\n                        // eslint-disable-next-line no-warning-comments\n                        // TODO: Handle title column\n                    } else {\n                        const index = board.cardProperties.findIndex((o: IPropertyTemplate) => o.id === templateId) + 1\n                        mutator.insertPropertyTemplate(board, activeView, index)\n                    }\n                }}\n            />\n            {props.templateId !== Constants.titleColumnId &&\n                <>\n                    <Menu.Text\n                        id='hide'\n                        name={intl.formatMessage({id: 'TableHeaderMenu.hide', defaultMessage: 'Hide'})}\n                        onClick={() => mutator.changeViewVisibleProperties(board.id, activeView.id, activeView.fields.visiblePropertyIds, activeView.fields.visiblePropertyIds.filter((o: string) => o !== templateId))}\n                    />\n                    <Menu.Text\n                        id='duplicate'\n                        name={intl.formatMessage({id: 'TableHeaderMenu.duplicate', defaultMessage: 'Duplicate'})}\n                        onClick={() => mutator.duplicatePropertyTemplate(board, activeView, templateId)}\n                    />\n                    <Menu.Text\n                        id='delete'\n                        name={intl.formatMessage({id: 'TableHeaderMenu.delete', defaultMessage: 'Delete'})}\n                        onClick={() => mutator.deleteProperty(board, views, cards, templateId)}\n                    />\n                </>}\n        </Menu>\n    )\n}\n\nexport default TableHeaderMenu\n"
  },
  {
    "path": "webapp/src/components/table/tableHeaders.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {render} from '@testing-library/react'\nimport '@testing-library/jest-dom'\n\nimport 'isomorphic-fetch'\nimport {wrapDNDIntl} from '../../testUtils'\n\nimport {TestBlockFactory} from '../../test/testBlockFactory'\n\nimport {ColumnResizeProvider} from './tableColumnResizeContext'\nimport TableHeaders from './tableHeaders'\n\ndescribe('components/table/TableHeaders', () => {\n    const board = TestBlockFactory.createBoard()\n    const card = TestBlockFactory.createCard(board)\n    const view = TestBlockFactory.createBoardView(board)\n\n    test('should match snapshot', async () => {\n        const component = wrapDNDIntl(\n            <ColumnResizeProvider\n                columnWidths={{}}\n                onResizeColumn={() => {}}\n            >\n                <TableHeaders\n                    board={board}\n                    cards={[card]}\n                    activeView={view}\n                    views={[view]}\n                    readonly={false}\n                />\n            </ColumnResizeProvider>,\n        )\n\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/table/tableHeaders.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useCallback, useMemo} from 'react'\n\nimport {FormattedMessage, useIntl} from 'react-intl'\n\nimport {IPropertyTemplate, Board} from '../../blocks/board'\nimport {createBoardView, BoardView, ISortOption} from '../../blocks/boardView'\nimport {Card} from '../../blocks/card'\nimport {Constants} from '../../constants'\nimport mutator from '../../mutator'\nimport {Utils} from '../../utils'\nimport propsRegistry from '../../properties'\n\nimport './table.scss'\n\nimport TableHeader from './tableHeader'\nimport {useColumnResize} from './tableColumnResizeContext'\n\ntype Props = {\n    board: Board\n    cards: Card[]\n    activeView: BoardView\n    views: BoardView[]\n    readonly: boolean\n}\n\nconst TableHeaders = (props: Props): JSX.Element => {\n    const {board, cards, activeView, views} = props\n    const intl = useIntl()\n    const columnResize = useColumnResize()\n\n    const onAutoSizeColumn = useCallback((columnID: string, headerWidth: number) => {\n        let longestSize = headerWidth\n        const visibleProperties = board.cardProperties.filter(() => activeView.fields.visiblePropertyIds.includes(columnID)) || []\n        const columnRef = columnResize.cellRef(columnID)\n        if (!columnRef) {\n            return\n        }\n\n        let template: IPropertyTemplate | undefined\n        const columnFontPadding = Utils.getFontAndPaddingFromCell(columnRef)\n        let perItemPadding = 0\n        if (columnID !== Constants.titleColumnId) {\n            template = visibleProperties.find((t: IPropertyTemplate) => t.id === columnID)\n            if (!template) {\n                return\n            }\n            if (template.type === 'multiSelect') {\n                // For multiselect, the padding calculated above depends on the number selected when calculating the padding.\n                // Need to calculate it manually here.\n                // DOM Object hierarchy should be {cell -> property -> [value1, value2, etc]}\n                let valueCount = 0\n                if (columnRef.childElementCount > 0) {\n                    const propertyElement = columnRef.children.item(0) as Element\n                    if (propertyElement) {\n                        valueCount = propertyElement.childElementCount\n                        if (valueCount > 0) {\n                            const statusPadding = Utils.getFontAndPaddingFromChildren(propertyElement.children, 0)\n                            perItemPadding = statusPadding.padding / valueCount\n                        }\n                    }\n                }\n\n                // remove the \"value\" portion of the original calculation\n                columnFontPadding.padding -= (perItemPadding * valueCount)\n            }\n        }\n\n        cards.forEach((card) => {\n            let thisLen = 0\n            if (columnID === Constants.titleColumnId) {\n                thisLen = Utils.getTextWidth(card.title, columnFontPadding.fontDescriptor) + columnFontPadding.padding\n            } else if (template) {\n                const property = propsRegistry.get(template.type)\n                property.valueLength(card.fields.properties[columnID], card, template as IPropertyTemplate, intl, columnFontPadding.fontDescriptor, perItemPadding)\n                thisLen += columnFontPadding.padding\n            }\n            if (thisLen > longestSize) {\n                longestSize = thisLen\n            }\n        })\n\n        const columnWidths = {...activeView.fields.columnWidths}\n        columnWidths[columnID] = longestSize\n        const newView = createBoardView(activeView)\n        newView.fields.columnWidths = columnWidths\n        mutator.updateBlock(board.id, newView, activeView, 'autosize column')\n    }, [activeView, board, cards])\n\n    const visiblePropertyTemplates = useMemo(() => (\n        activeView.fields.visiblePropertyIds.map((id) => board.cardProperties.find((t) => t.id === id)).filter((i) => i) as IPropertyTemplate[]\n    ), [board.cardProperties, activeView.fields.visiblePropertyIds])\n\n    const onDropToColumn = useCallback(async (template: IPropertyTemplate, container: IPropertyTemplate) => {\n        Utils.log(`ondrop. Source column: ${template.name}, dest column: ${container.name}`)\n\n        // Move template to new index\n        const destIndex = container ? activeView.fields.visiblePropertyIds.indexOf(container.id) : 0\n        await mutator.changeViewVisiblePropertiesOrder(board.id, activeView, template, destIndex >= 0 ? destIndex : 0)\n    }, [board.id, activeView.fields.visiblePropertyIds])\n\n    const titleSortOption = activeView.fields.sortOptions?.find((o) => o.propertyId === Constants.titleColumnId)\n    let titleSorted: 'up' | 'down' | 'none' = 'none'\n    if (titleSortOption) {\n        titleSorted = titleSortOption.reversed ? 'down' : 'up'\n    }\n\n    return (\n        <div\n            className='octo-table-header TableHeaders'\n            id='mainBoardHeader'\n        >\n            <TableHeader\n                name={\n                    <FormattedMessage\n                        id='TableComponent.name'\n                        defaultMessage='Name'\n                    />\n                }\n                sorted={titleSorted}\n                readonly={props.readonly}\n                board={board}\n                activeView={activeView}\n                cards={cards}\n                views={views}\n                template={{id: Constants.titleColumnId, name: 'title', type: 'text', options: []}}\n                onDrop={onDropToColumn}\n                onAutoSizeColumn={onAutoSizeColumn}\n            />\n\n            {/* Table header row */}\n            {visiblePropertyTemplates.map((template) => {\n                let sorted: 'up' | 'down' | 'none' = 'none'\n                const sortOption = activeView.fields.sortOptions.find((o: ISortOption) => o.propertyId === template.id)\n                if (sortOption) {\n                    sorted = sortOption.reversed ? 'down' : 'up'\n                }\n                return (\n                    <TableHeader\n                        name={template.name}\n                        sorted={sorted}\n                        readonly={props.readonly}\n                        board={board}\n                        activeView={activeView}\n                        cards={cards}\n                        views={views}\n                        template={template}\n                        key={template.id}\n                        onDrop={onDropToColumn}\n                        onAutoSizeColumn={onAutoSizeColumn}\n                    />\n                )\n            })}\n        </div>\n    )\n}\n\nexport default TableHeaders\n"
  },
  {
    "path": "webapp/src/components/table/tableRow.scss",
    "content": "@import '../../styles/z-index';\n\n.TableRow {\n    .open-button {\n        display: none;\n\n        .Button {\n            padding: 2px 6px;\n            font-size: 11px;\n            text-transform: uppercase;\n            font-weight: 600;\n            color: rgba(var(--center-channel-color-rgb), 0.64);\n            background-color: rgb(var(--center-channel-bg-rgb));\n            box-shadow: rgba(var(--center-channel-color-rgb), 0.1) 0 0 0 1px,\n                rgba(var(--center-channel-color-rgb), 0.1) 0 2px 4px;\n            height: 24px;\n            width: 100%;\n        }\n    }\n\n    &.selected {\n        background-color: rgba(90, 200, 255, 0.2);\n    }\n\n    &:hover {\n        background-color: rgba(var(--center-channel-color-rgb), 0.05);\n        overflow: initial;\n\n        .delete-button {\n            display: block;\n        }\n\n        .open-button {\n            display: block;\n        }\n\n        .action-cell {\n            @include z-index(table-row-action-cell);\n            position: relative;\n            align-items: center;\n        }\n\n        .octo-table-cell-btn {\n            visibility: visible;\n        }\n\n        .title-cell {\n            margin-left: 0;\n        }\n    }\n\n    &.readonly:hover {\n        margin-left: unset;\n    }\n\n    .action-cell {\n        display: flex;\n        visibility: hidden;\n        margin-left: 8px;\n        width: 24px;\n    }\n\n    .URLProperty:hover .Button_Copy {\n        display: none;\n    }\n\n    .octo-propertyvalue--readonly {\n        flex-wrap: nowrap;\n        overflow: hidden !important;\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/table/tableRow.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {Provider as ReduxProvider} from 'react-redux'\nimport {render} from '@testing-library/react'\nimport configureStore from 'redux-mock-store'\n\nimport '@testing-library/jest-dom'\nimport {wrapDNDIntl} from '../../testUtils'\n\nimport 'isomorphic-fetch'\n\nimport {TestBlockFactory} from '../../test/testBlockFactory'\n\nimport {ColumnResizeProvider} from './tableColumnResizeContext'\nimport TableRow from './tableRow'\n\ndescribe('components/table/TableRow', () => {\n    const board = TestBlockFactory.createBoard()\n    const view = TestBlockFactory.createBoardView(board)\n\n    const view2 = TestBlockFactory.createBoardView(board)\n    view2.fields.sortOptions = []\n\n    const card = TestBlockFactory.createCard(board)\n    const cardTemplate = TestBlockFactory.createCard(board)\n    cardTemplate.fields.isTemplate = true\n\n    const state = {\n        users: {},\n        comments: {\n            comments: {},\n        },\n        contents: {\n            contents: {},\n        },\n        cards: {\n            cards: {\n                [card.id]: card,\n            },\n        },\n    }\n\n    const mockStore = configureStore([])\n\n    const Wrapper: React.FC = ({children}) => {\n        const store = mockStore(state)\n        return wrapDNDIntl(\n            <ColumnResizeProvider\n                columnWidths={{}}\n                onResizeColumn={jest.fn()}\n            >\n                <ReduxProvider store={store}>\n                    {children}\n                </ReduxProvider>\n            </ColumnResizeProvider>,\n        )\n    }\n\n    test('should match snapshot', async () => {\n        const {container} = render(\n            <Wrapper>\n                <TableRow\n                    board={board}\n                    columnWidths={view.fields.columnWidths}\n                    addCard={jest.fn()}\n                    visiblePropertyIds={view.fields.visiblePropertyIds}\n                    isManualSort={view.fields.sortOptions.length === 0}\n                    groupById={view.fields.groupById}\n                    isLastCard={false}\n                    collapsedOptionIds={view.fields.collapsedOptionIds}\n                    card={card}\n                    isSelected={false}\n                    focusOnMount={false}\n                    showCard={jest.fn()}\n                    readonly={false}\n                    onDrop={jest.fn()}\n                />\n            </Wrapper>,\n        )\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot, read-only', async () => {\n        const {container} = render(\n            <Wrapper>\n                <TableRow\n                    board={board}\n                    card={card}\n                    columnWidths={view.fields.columnWidths}\n                    addCard={jest.fn()}\n                    visiblePropertyIds={view.fields.visiblePropertyIds}\n                    isManualSort={view.fields.sortOptions.length === 0}\n                    groupById={view.fields.groupById}\n                    isLastCard={false}\n                    collapsedOptionIds={view.fields.collapsedOptionIds}\n                    isSelected={false}\n                    focusOnMount={false}\n                    showCard={jest.fn()}\n                    readonly={true}\n                    onDrop={jest.fn()}\n                />\n            </Wrapper>,\n        )\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot, isSelected', async () => {\n        const {container} = render(\n            <Wrapper>\n                <TableRow\n                    board={board}\n                    card={card}\n                    columnWidths={view.fields.columnWidths}\n                    addCard={jest.fn()}\n                    visiblePropertyIds={view.fields.visiblePropertyIds}\n                    isManualSort={view.fields.sortOptions.length === 0}\n                    groupById={view.fields.groupById}\n                    isLastCard={false}\n                    collapsedOptionIds={view.fields.collapsedOptionIds}\n                    isSelected={true}\n                    focusOnMount={false}\n                    showCard={jest.fn()}\n                    readonly={false}\n                    onDrop={jest.fn()}\n                />\n            </Wrapper>,\n        )\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot, collapsed tree', async () => {\n        const {container} = render(\n            <Wrapper>\n                <TableRow\n                    board={board}\n                    card={card}\n                    columnWidths={view.fields.columnWidths}\n                    addCard={jest.fn()}\n                    visiblePropertyIds={view.fields.visiblePropertyIds}\n                    isManualSort={view.fields.sortOptions.length === 0}\n                    groupById={view.fields.groupById}\n                    isLastCard={false}\n                    collapsedOptionIds={['value1']}\n                    isSelected={false}\n                    focusOnMount={false}\n                    showCard={jest.fn()}\n                    readonly={false}\n                    onDrop={jest.fn()}\n                />\n            </Wrapper>,\n        )\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot, display properties', async () => {\n        const {container} = render(\n            <Wrapper>\n                <TableRow\n                    board={board}\n                    card={card}\n                    visiblePropertyIds={['property1', 'property2']}\n                    columnWidths={view.fields.columnWidths}\n                    addCard={jest.fn()}\n                    isManualSort={view.fields.sortOptions.length === 0}\n                    groupById={view.fields.groupById}\n                    collapsedOptionIds={view.fields.collapsedOptionIds}\n                    isLastCard={false}\n                    isSelected={false}\n                    focusOnMount={false}\n                    showCard={jest.fn()}\n                    readonly={false}\n                    onDrop={jest.fn()}\n                />\n            </Wrapper>,\n        )\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot, resizing column', async () => {\n        const {container} = render(\n            <Wrapper>\n                <TableRow\n                    board={board}\n                    card={card}\n                    visiblePropertyIds={['property1', 'property2']}\n                    columnWidths={view.fields.columnWidths}\n                    addCard={jest.fn()}\n                    isManualSort={view.fields.sortOptions.length === 0}\n                    groupById={view.fields.groupById}\n                    isLastCard={false}\n                    collapsedOptionIds={view.fields.collapsedOptionIds}\n                    isSelected={false}\n                    focusOnMount={false}\n                    showCard={jest.fn()}\n                    readonly={false}\n                    onDrop={jest.fn()}\n                />\n            </Wrapper>,\n        )\n        expect(container).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/table/tableRow.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useEffect, useRef, useState, useMemo, useCallback} from 'react'\nimport {FormattedMessage, useIntl} from 'react-intl'\n\nimport {Card} from '../../blocks/card'\nimport {Board, IPropertyTemplate} from '../../blocks/board'\nimport {Constants} from '../../constants'\nimport mutator from '../../mutator'\nimport Button from '../../widgets/buttons/button'\nimport Editable from '../../widgets/editable'\nimport {useSortable} from '../../hooks/sortable'\n\nimport {Utils} from '../../utils'\n\nimport PropertyValueElement from '../propertyValueElement'\nimport MenuWrapper from '../../widgets/menuWrapper'\nimport IconButton from '../../widgets/buttons/iconButton'\nimport CompassIcon from '../../widgets/icons/compassIcon'\nimport OptionsIcon from '../../widgets/icons/options'\nimport Tooltip from '../../widgets/tooltip'\nimport ConfirmationDialogBox, {ConfirmationDialogBoxProps} from '../confirmationDialogBox'\nimport TelemetryClient, {TelemetryActions, TelemetryCategory} from '../../telemetry/telemetryClient'\nimport CardActionsMenu from '../cardActionsMenu/cardActionsMenu'\n\nimport {useColumnResize} from './tableColumnResizeContext'\n\nimport './tableRow.scss'\n\ntype Props = {\n    board: Board\n    columnWidths: Record<string, number>\n    isManualSort: boolean\n    groupById?: string\n    visiblePropertyIds: string[]\n    collapsedOptionIds: string[]\n    card: Card\n    isSelected: boolean\n    focusOnMount: boolean\n    isLastCard: boolean\n    showCard: (cardId?: string) => void\n    readonly: boolean\n    addCard: (groupByOptionId?: string) => Promise<void>\n    onClick?: (e: React.MouseEvent<HTMLDivElement>, card: Card) => void\n    onDrop: (srcCard: Card, dstCard: Card) => void\n}\n\nconst TableRow = (props: Props) => {\n    const intl = useIntl()\n    const {board, card, isManualSort, groupById, visiblePropertyIds, collapsedOptionIds} = props\n\n    const titleRef = useRef<{ focus(selectAll?: boolean): void }>(null)\n    const [title, setTitle] = useState(props.card.title || '')\n    const isGrouped = Boolean(groupById)\n    const [isDragging, isOver, cardRef] = useSortable('card', card, !props.readonly && (isManualSort || isGrouped), props.onDrop)\n    const [showConfirmationDialogBox, setShowConfirmationDialogBox] = useState<boolean>(false)\n    const columnResize = useColumnResize()\n\n    useEffect(() => {\n        if (props.focusOnMount) {\n            setTimeout(() => titleRef.current?.focus(), 10)\n        }\n    }, [])\n\n    const onClick = useCallback((e: React.MouseEvent<HTMLDivElement>) => {\n        props.onClick && props.onClick(e, card)\n    }, [card, props.onClick])\n\n    const onSaveWithEnter = useCallback(() => {\n        if (props.isLastCard) {\n            props.addCard(groupById ? card.fields.properties[groupById!] as string : '')\n        }\n    }, [groupById && card.fields.properties[groupById!], props.isLastCard, props.addCard])\n\n    const onSave = useCallback((saveType) => {\n        if (card.title !== title) {\n            mutator.changeBlockTitle(props.board.id, card.id, card.title, title)\n            if (saveType === 'onEnter') {\n                onSaveWithEnter()\n            }\n        }\n    }, [card.title, title, onSaveWithEnter, board.id, card.id])\n\n    const onTitleChange = useCallback((newTitle: string) => {\n        setTitle(newTitle)\n    }, [title, setTitle])\n\n    const visiblePropertyTemplates = useMemo(() => (\n        visiblePropertyIds.map((id) => board.cardProperties.find((t) => t.id === id)).filter((i) => i) as IPropertyTemplate[]\n    ), [board.cardProperties, visiblePropertyIds])\n\n    let className = props.isSelected ? 'TableRow octo-table-row selected' : 'TableRow octo-table-row'\n    if (isOver) {\n        className += ' dragover'\n    }\n    if (isGrouped) {\n        const groupID = groupById || ''\n        let groupValue = card.fields.properties[groupID] as string || 'undefined'\n        if (groupValue === 'undefined') {\n            const template = board.cardProperties.find((p) => p.id === groupById) //templates.find((o) => o.id === groupById)\n            if (template && template.type === 'createdBy') {\n                groupValue = card.createdBy\n            } else if (template && template.type === 'updatedBy') {\n                groupValue = card.modifiedBy\n            }\n        } else if (Array.isArray(groupValue)) {\n            groupValue = groupValue[0]\n        }\n        if (collapsedOptionIds.indexOf(groupValue) > -1) {\n            className += ' hidden'\n        }\n    }\n    if (props.readonly) {\n        className += ' readonly'\n    }\n\n    const handleDeleteCard = useCallback(async () => {\n        if (!card) {\n            Utils.assertFailure()\n            return\n        }\n        TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.DeleteCard, {board: board.id, card: card.id})\n        await mutator.deleteBlock(card, 'delete card')\n    }, [card, board.id])\n\n    const confirmDialogProps: ConfirmationDialogBoxProps = useMemo(() => {\n        return {\n            heading: intl.formatMessage({id: 'CardDialog.delete-confirmation-dialog-heading', defaultMessage: 'Confirm card delete!'}),\n            confirmButtonText: intl.formatMessage({id: 'CardDialog.delete-confirmation-dialog-button-text', defaultMessage: 'Delete'}),\n            onConfirm: handleDeleteCard,\n            onClose: () => {\n                setShowConfirmationDialogBox(false)\n            },\n        }\n    }, [handleDeleteCard])\n\n    const handleDeleteButtonOnClick = useCallback(() => {\n        // user trying to delete a card with blank name\n        // but content present cannot be deleted without\n        // confirmation dialog\n        if (card?.title === '' && card?.fields.contentOrder.length === 0) {\n            handleDeleteCard()\n            return\n        }\n        setShowConfirmationDialogBox(true)\n    }, [card.title, card.fields.contentOrder, handleDeleteCard])\n\n    return (\n        <div\n            className={className}\n            onClick={onClick}\n            ref={cardRef}\n            style={{opacity: isDragging ? 0.5 : 1}}\n        >\n\n            <div className='action-cell octo-table-cell-btn'>\n                {!props.readonly && (\n                    <IconButton icon={<CompassIcon icon='drag-vertical'/>}/>\n                )}\n            </div>\n\n            {/* Name / title */}\n            <div\n                className='octo-table-cell title-cell'\n                id='mainBoardHeader'\n                style={{width: columnResize.width(Constants.titleColumnId)}}\n                ref={(ref) => columnResize.updateRef(card.id, Constants.titleColumnId, ref)}\n            >\n                <div className='octo-icontitle'>\n                    <div className='octo-icon'>{card.fields.icon}</div>\n                    <Editable\n                        ref={titleRef}\n                        value={title}\n                        placeholderText='Untitled'\n                        onChange={onTitleChange}\n                        onSave={onSave}\n                        onCancel={() => setTitle(card.title || '')}\n                        readonly={props.readonly}\n                        spellCheck={true}\n                    />\n                </div>\n\n                {!props.readonly && (\n                    <MenuWrapper\n                        className='optionsMenu ml-2 mr-2'\n                        stopPropagationOnToggle={true}\n                    >\n                        <Tooltip\n                            title={intl.formatMessage({id: 'TableRow.MoreOption', defaultMessage: 'More actions'})}\n                        >\n                            <IconButton\n                                title='MenuBtn'\n                                icon={<OptionsIcon/>}\n                            />\n                        </Tooltip>\n                        <CardActionsMenu\n                            cardId={card.id}\n                            boardId={card.boardId}\n                            onClickDelete={handleDeleteButtonOnClick}\n                            onClickDuplicate={() => {\n                                mutator.duplicateCard(\n                                    card.id,\n                                    board.id,\n                                    false,\n                                    intl.formatMessage({id: 'TableRow.DuplicateCard', defaultMessage: 'duplicate card'}),\n                                    false,\n                                    {},\n                                    async (newCardId) => {\n                                        props.showCard(newCardId)\n                                    },\n                                    async () => {\n                                        props.showCard(undefined)\n                                    },\n                                )\n                            }}\n                        />\n                    </MenuWrapper>\n                )}\n\n                <div className='open-button'>\n                    <Button onClick={() => props.showCard(props.card.id || '')}>\n                        <FormattedMessage\n                            id='TableRow.open'\n                            defaultMessage='Open'\n                        />\n                    </Button>\n                </div>\n            </div>\n\n            {/* Columns, one per property */}\n            {visiblePropertyTemplates.map((template) => {\n                return (\n                    <div\n                        className='octo-table-cell'\n                        key={template.id}\n                        style={{width: columnResize.width(template.id)}}\n                        ref={(ref) => columnResize.updateRef(card.id, template.id, ref)}\n                    >\n                        <PropertyValueElement\n                            readOnly={props.readonly}\n                            card={card}\n                            board={board}\n                            propertyTemplate={template}\n                            showEmptyPlaceholder={false}\n                        />\n                    </div>\n                )\n            })}\n\n            {showConfirmationDialogBox && <ConfirmationDialogBox dialogBox={confirmDialogProps}/>}\n        </div>\n    )\n}\n\nexport default React.memo(TableRow)\n"
  },
  {
    "path": "webapp/src/components/table/tableRows.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {Provider as ReduxProvider} from 'react-redux'\nimport {fireEvent, render} from '@testing-library/react'\nimport configureStore from 'redux-mock-store'\nimport '@testing-library/jest-dom'\n\nimport 'isomorphic-fetch'\n\nimport {TestBlockFactory} from '../../test/testBlockFactory'\nimport {FetchMock} from '../../test/fetchMock'\nimport {wrapDNDIntl} from '../../testUtils'\n\nimport {ColumnResizeProvider} from './tableColumnResizeContext'\nimport TableRows from './tableRows'\n\nglobal.fetch = FetchMock.fn\n\nbeforeEach(() => {\n    FetchMock.fn.mockReset()\n})\n\ndescribe('components/table/TableRows', () => {\n    const board = TestBlockFactory.createBoard()\n    const view = TestBlockFactory.createBoardView(board)\n\n    const view2 = TestBlockFactory.createBoardView(board)\n    view2.fields.sortOptions = []\n\n    const card = TestBlockFactory.createCard(board)\n    const cardTemplate = TestBlockFactory.createCard(board)\n    cardTemplate.fields.isTemplate = true\n\n    const mockStore = configureStore([])\n    const state = {\n        users: {},\n        comments: {\n            comments: {},\n        },\n        contents: {\n            contents: {},\n        },\n        cards: {\n            cards: {\n                [card.id]: card,\n            },\n            templates: {\n                [cardTemplate.id]: cardTemplate,\n            },\n        },\n    }\n\n    test('should match snapshot, fire events', async () => {\n        const callback = jest.fn()\n        const addCard = jest.fn()\n\n        const store = mockStore(state)\n        const component = wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <ColumnResizeProvider\n                    columnWidths={{}}\n                    onResizeColumn={() => {}}\n                >\n                    <TableRows\n                        board={board}\n                        activeView={view}\n                        cards={[card]}\n                        selectedCardIds={[]}\n                        readonly={false}\n                        cardIdToFocusOnRender=''\n                        showCard={callback}\n                        addCard={addCard}\n                        onCardClicked={jest.fn()}\n                        onDrop={jest.fn()}\n                    />\n                </ColumnResizeProvider>\n            </ReduxProvider>,\n        )\n\n        const {container, getByText} = render(component)\n\n        const open = getByText(/Open/i)\n        fireEvent.click(open)\n        expect(callback).toBeCalled()\n        expect(container).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/table/tableRows.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useCallback} from 'react'\n\nimport {Card} from '../../blocks/card'\nimport {Board} from '../../blocks/board'\nimport {BoardView} from '../../blocks/boardView'\n\nimport './table.scss'\n\nimport TableRow from './tableRow'\n\ntype Props = {\n    board: Board\n    activeView: BoardView\n    cards: readonly Card[]\n    selectedCardIds: string[]\n    readonly: boolean\n    cardIdToFocusOnRender: string\n    showCard: (cardId?: string) => void\n    addCard: (groupByOptionId?: string) => Promise<void>\n    onCardClicked: (e: React.MouseEvent, card: Card) => void\n    onDrop: (srcCard: Card, dstCard: Card) => void\n}\n\nconst TableRows = (props: Props): JSX.Element => {\n    const {board, cards, activeView} = props\n\n    const onClickRow = useCallback((e: React.MouseEvent<HTMLDivElement>, card: Card) => {\n        props.onCardClicked(e, card)\n    }, [props.onCardClicked])\n\n    return (\n        <>\n            {cards.map((card, idx) => {\n                return (\n                    <TableRow\n                        key={card.id + card.updateAt}\n                        board={board}\n                        columnWidths={activeView.fields.columnWidths}\n                        isManualSort={activeView.fields.sortOptions.length === 0}\n                        groupById={activeView.fields.groupById}\n                        visiblePropertyIds={activeView.fields.visiblePropertyIds}\n                        collapsedOptionIds={activeView.fields.collapsedOptionIds}\n                        card={card}\n                        addCard={props.addCard}\n                        isSelected={props.selectedCardIds.includes(card.id)}\n                        focusOnMount={props.cardIdToFocusOnRender === card.id}\n                        isLastCard={idx === (cards.length - 1)}\n                        onClick={onClickRow}\n                        showCard={props.showCard}\n                        readonly={props.readonly}\n                        onDrop={props.onDrop}\n                    />)\n            })}\n        </>\n    )\n}\n\nexport default TableRows\n"
  },
  {
    "path": "webapp/src/components/topBar.scss",
    "content": "@import '../styles/z-index';\n\n.TopBar {\n    @include z-index(top-bar);\n    position: absolute;\n    top: 8px;\n    right: 32px;\n\n    display: flex;\n    flex-direction: row;\n\n    a {\n        display: flex;\n        align-items: center;\n        padding: 2px;\n    }\n\n    a.link {\n        font-size: 12px;\n        line-height: 14px;\n        padding: 2px 10px;\n        color: rgba(var(--center-channel-color-rgb), 0.72);\n\n        &:hover {\n            color: rgba(var(--center-channel-color-rgb), 1);\n        }\n    }\n\n    .HelpIcon {\n        font-size: 20px;\n        color: currentColor;\n    }\n\n    .versionFrame {\n        display: flex;\n        flex-direction: row;\n        align-items: center;\n    }\n\n    .version {\n        font-size: 11px;\n        line-height: 14px;\n        font-weight: 500;\n        color: currentColor;\n    }\n\n    .versionBadge {\n        font-size: 10px;\n        line-height: 14px;\n        font-weight: 500;\n        margin-left: 3px;\n        color: currentColor;\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/topBar.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {render} from '@testing-library/react'\nimport React from 'react'\n\nimport {wrapDNDIntl} from '../testUtils'\nimport {Constants} from '../constants'\n\nimport TopBar from './topBar'\n\nObject.defineProperty(Constants, 'versionString', {value: '1.0.0'})\njest.mock('../utils')\n\ndescribe('src/components/topBar', () => {\n    beforeEach(jest.resetAllMocks)\n    test('should match snapshot for focalboardPlugin', () => {\n        const {container} = render(wrapDNDIntl(\n            <TopBar/>,\n        ))\n        expect(container).toMatchSnapshot()\n    })\n    test('should match snapshot for none focalboardPlugin', () => {\n        const {container} = render(wrapDNDIntl(\n            <TopBar/>,\n        ))\n        expect(container).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/topBar.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport './topBar.scss'\nimport {FormattedMessage} from 'react-intl'\n\nimport HelpIcon from '../widgets/icons/help'\nimport {Constants} from '../constants'\n\nconst TopBar = (): JSX.Element => {\n    const focalboardFeedbackUrl = 'https://www.focalboard.com/fwlink/feedback-focalboard.html?v=' + Constants.versionString\n    return (\n        <div\n            className='TopBar'\n        >\n            <a\n                className='link'\n                href={focalboardFeedbackUrl}\n                target='_blank'\n                rel='noreferrer'\n            >\n                <FormattedMessage\n                    id='TopBar.give-feedback'\n                    defaultMessage='Give feedback'\n                />\n            </a>\n            <a\n                href='https://www.focalboard.com/guide/user?utm_source=webapp'\n                target='_blank'\n                rel='noreferrer'\n            >\n                <HelpIcon/>\n            </a>\n        </div>\n    )\n}\n\nexport default React.memo(TopBar)\n"
  },
  {
    "path": "webapp/src/components/tutorial_tour_tip/hooks.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {useLayoutEffect, useMemo, useState} from 'react'\nimport throttle from 'lodash/throttle'\n\nimport useElementAvailable from './useElementAvailable'\n\nimport {TutorialTourTipPunchout} from './tutorial_tour_tip_backdrop'\n\ntype PunchoutOffset = {\n    x: number\n    y: number\n    width: number\n    height: number\n}\n\nexport function useMeasurePunchouts(elementIds: string[], additionalDeps: any[], offset?: PunchoutOffset): TutorialTourTipPunchout | null | undefined {\n    const elementsAvailable = useElementAvailable(elementIds)\n    const [size, setSize] = useState<DOMRect>()\n    const updateSize = throttle(() => {\n        setSize(document.getElementById('root')?.getBoundingClientRect())\n    }, 100)\n\n    useLayoutEffect(() => {\n        window.addEventListener('resize', updateSize)\n        return () =>\n            window.removeEventListener('resize', updateSize)\n    }, [])\n\n    const channelPunchout = useMemo(() => {\n        let minX = Number.MAX_SAFE_INTEGER\n        let minY = Number.MAX_SAFE_INTEGER\n        let maxX = Number.MIN_SAFE_INTEGER\n        let maxY = Number.MIN_SAFE_INTEGER\n        for (let i = 0; i < elementIds.length; i++) {\n            const rectangle = document.querySelector(elementIds[i])?.getBoundingClientRect()\n            if (!rectangle) {\n                return null\n            }\n            if (rectangle.x < minX) {\n                minX = rectangle.x\n            }\n            if (rectangle.y < minY) {\n                minY = rectangle.y\n            }\n            if (rectangle.x + rectangle.width > maxX) {\n                maxX = rectangle.x + rectangle.width\n            }\n            if (rectangle.y + rectangle.height > maxY) {\n                maxY = rectangle.y + rectangle.height\n            }\n        }\n\n        return {\n            x: `${minX + (offset ? offset.x : 0)}px`,\n            y: `${minY + (offset ? offset.y : 0)}px`,\n            width: `${(maxX - minX) + (offset ? offset.width : 0)}px`,\n            height: `${(maxY - minY) + (offset ? offset.height : 0)}px`,\n        }\n    }, [...elementIds, ...additionalDeps, size, elementsAvailable])\n    return channelPunchout\n}\n"
  },
  {
    "path": "webapp/src/components/tutorial_tour_tip/tutorial_tour_tip.scss",
    "content": "\n@import '../../styles/z-index';\n\n.tutorial-tour-tip {\n    display: flex;\n\n    &__box {\n        &.tippy-box {\n            padding: 18px 24px 24px;\n            border: 1px solid rgba(var(--center-channel-color-rgb), 0.16);\n            background: rgb(var(--center-channel-bg-rgb));\n            border-radius: 4px;\n            color: var(--center-channel-color-rgb);\n            filter: drop-shadow(0 12px 32px rgba(0, 0, 0, 0.12));\n\n            .tippy-content {\n                padding: 0;\n            }\n\n            .tippy-arrow {\n                width: 12px;\n                height: 12px;\n                border-color: rgba(var(--center-channel-color-rgb), 0.16);\n                color: rgb(var(--center-channel-bg-rgb));\n            }\n\n            .tippy-arrow::before {\n                width: 12px;\n                height: 12px;\n                border-color: rgba(var(--center-channel-color-rgb), 0.16);\n                background: rgb(var(--center-channel-bg-rgb));\n                color: var(--center-channel-bg);\n                transform-origin: center;\n            }\n        }\n    }\n\n    &__pulsating-dot-ctr {\n        position: absolute;\n        width: 14px;\n        height: 14px;\n    }\n\n    &__pulsating-dot-ctr[data-pulsating-dot-placement=top] {\n        top: 0;\n        left: calc(50% - 6px);\n        transform: translate(0, 6px);\n    }\n\n    &__pulsating-dot-ctr[data-pulsating-dot-placement=top-start] {\n        top: 0;\n        left: 0;\n        transform: translate(6px, 6px);\n    }\n\n    &__pulsating-dot-ctr[data-pulsating-dot-placement=top-end] {\n        top: 0;\n        right: 0;\n        transform: translate(-6px, 6px);\n    }\n\n    &__pulsating-dot-ctr[data-pulsating-dot-placement=bottom] {\n        bottom: 0;\n        left: calc(50% - 6px);\n        transform: translate(0, -6px);\n    }\n\n    &__pulsating-dot-ctr[data-pulsating-dot-placement=bottom-start] {\n        bottom: 0;\n        left: 0;\n        transform: translate(6px, -6px);\n    }\n\n    &__pulsating-dot-ctr[data-pulsating-dot-placement=bottom-end] {\n        right: 0;\n        bottom: 0;\n        transform: translate(-6px, -6px);\n    }\n\n    &__pulsating-dot-ctr[data-pulsating-dot-placement=left] {\n        top: calc(50% - 6px);\n        left: 0;\n        transform: translate(6px, 0);\n    }\n\n    &__pulsating-dot-ctr[data-pulsating-dot-placement=left-start] {\n        top: 0;\n        left: 0;\n        transform: translate(6px, 6px);\n    }\n\n    &__pulsating-dot-ctr[data-pulsating-dot-placement=left-end] {\n        bottom: 0;\n        left: 0;\n        transform: translate(6px, -6px);\n    }\n\n    &__pulsating-dot-ctr[data-pulsating-dot-placement=right] {\n        top: calc(50% - 6px);\n        right: 0;\n        transform: translate(-6px, 0);\n    }\n\n    &__pulsating-dot-ctr[data-pulsating-dot-placement=right-start] {\n        top: 0;\n        right: 0;\n        transform: translate(-6px, 6px);\n    }\n\n    &__pulsating-dot-ctr[data-pulsating-dot-placement=right-end] {\n        right: 0;\n        bottom: 0;\n        transform: translate(-6px, -6px);\n    }\n\n    &__overlay {\n        @include z-index(tour-tip-overlay);\n        position: fixed;\n        top: 0;\n        right: 0;\n        bottom: 0;\n        left: 0;\n    }\n\n    &__header {\n        display: flex;\n        align-items: center;\n        justify-content: flex-start;\n\n        &__title {\n            flex: none;\n            flex-grow: 1;\n            order: 0;\n            margin: 0;\n            font-family: inherit;\n            font-style: normal;\n            font-weight: 600;\n            line-height: 2rem;\n            font-size: 14px;\n\n            @media (min-width: 2560px) {\n                font-size: 16px;\n            }\n        }\n    }\n\n    &__body {\n        display: flex;\n        flex-direction: column;\n        margin-top: 0.6rem;\n\n        @media (min-width: 2560px) {\n            font-size: 16px;\n        }\n\n        p {\n            margin: 0 0 0.8rem;\n            font-size: 1.4rem;\n            line-height: 2rem;\n\n            &:last-of-type {\n                margin-bottom: 0;\n            }\n        }\n    }\n\n    &__image {\n        display: flex;\n        align-items: center;\n        justify-content: center;\n        margin: 24px 0;\n\n        img {\n            width: 100%;\n            border-radius: 4px;\n            object-fit: cover;\n        }\n    }\n\n    &__btn-ctr {\n        display: flex;\n        flex-grow: 1;\n        justify-content: flex-end;\n\n        button {\n            &:last-child {\n                margin-left: 4px;\n            }\n        }\n    }\n\n    &__btn {\n        display: flex;\n        height: 32px;\n        align-items: center;\n        padding: 10px 16px;\n        border: unset;\n        border-radius: 4px;\n        font-size: 12px;\n        font-weight: 600;\n        line-height: 12px;\n    }\n\n    &__confirm-btn {\n        background: rgb(var(--sidebar-bg-rgb));\n        color: rgb(var(--center-channel-bg-rgb));\n\n        &:hover:not(.disabled) {\n            background:\n                linear-gradient(0deg,\n                rgba(var(--center-channel-color-rgb), 0.16),\n                rgba(var(--center-channel-color-rgb), 0.16)),\n                rgb(var(--sidebar-bg-rgb));\n        }\n\n        &:active {\n            background:\n                linear-gradient(0deg,\n                rgba(var(--center-channel-color-rgb), 0.32),\n                rgba(var(--center-channel-color-rgb), 0.32)),\n                var(--button-bg);\n        }\n\n        &:focus {\n            box-shadow: inset 0 0 0 2px var(--sidebar-text-active-border);\n        }\n\n        .icon-chevron-right::before {\n            margin-right: -7px;\n        }\n    }\n\n    &__cancel-btn {\n        margin-right: 4px;\n        background: rgba(var(--button-bg-rgb), 0.08);\n        border-radius: 4px;\n        color: var(--button-bg);\n        text-decoration: none;\n\n        &:hover {\n            background: rgba(var(--button-bg-rgb), 0.04);\n        }\n\n        &:active {\n            background: rgba(var(--button-bg-rgb), 0.08);\n        }\n\n        &:focus {\n            box-shadow: inset 0 0 0 2px var(--sidebar-text-active-border);\n        }\n\n        .icon-chevron-left::before {\n            margin-left: -7px;\n        }\n    }\n\n    &__circles-ctr {\n        display: flex;\n        align-items: center;\n        justify-content: flex-start;\n    }\n\n    &__circular-ring {\n        position: relative;\n        display: flex;\n        width: 12px;\n        height: 12px;\n        align-items: center;\n        justify-content: center;\n        margin-right: 4px;\n        background: transparent;\n        border-radius: 50%;\n\n        &:last-child {\n            margin-right: 0;\n        }\n    }\n\n    &__circular-ring-active {\n        background: rgba(var(--button-bg-rgb), 0.16);\n    }\n\n    &__circle {\n        position: absolute;\n        top: 3px;\n        left: 3px;\n        width: 6px;\n        height: 6px;\n        background: rgba(var(--button-bg-rgb), 0.32);\n        border-radius: 6px;\n\n        &.active {\n            background: rgba(var(--button-bg-rgb), 1);\n        }\n    }\n\n    &__footer {\n        display: flex;\n        flex-direction: column;\n\n        &:empty {\n            display: none;\n        }\n\n        &-buttons {\n            display: flex;\n            align-items: center;\n            justify-content: start;\n        }\n    }\n\n    &__opt {\n        align-self: flex-end;\n        margin-top: 1.2rem;\n        font-size: 12px;\n\n        span {\n            opacity: 0.9;\n        }\n    }\n}\n\n.tippy-box[data-placement^=right] > .tippy-arrow::before {\n    border-width: 1px 0 0 1px !important;\n    transform: rotate(-45deg);\n}\n\n.tippy-box[data-placement^=left] > .tippy-arrow::before {\n    border-width: 1px 1px 0 0 !important;\n    transform: rotate(45deg);\n}\n\n.tippy-box[data-placement^=bottom] > .tippy-arrow::before {\n    border-width: 1px 0 0 1px !important;\n    transform: rotate(45deg);\n}\n\n.tippy-box[data-placement^=top] > .tippy-arrow::before {\n    border-width: 0 0 1px 1px !important;\n    transform: rotate(-45deg);\n}\n\n.tip-backdrop {\n    @include z-index(tour-tip-backdrop);\n    position: absolute;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    background: transparent;\n    border-radius: 4px;\n    box-shadow: rgb(33 33 33 / 50%) 0 0 0 5000px;\n}\n\n.tutorial-tour-tip__btn {\n    cursor: pointer;\n}\n"
  },
  {
    "path": "webapp/src/components/tutorial_tour_tip/tutorial_tour_tip.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React, {useRef} from 'react'\nimport Tippy from '@tippyjs/react'\nimport ReactDOM from 'react-dom'\nimport {FormattedMessage} from 'react-intl'\n\nimport './tutorial_tour_tip.scss'\nimport 'tippy.js/dist/tippy.css'\nimport 'tippy.js/themes/light-border.css'\nimport 'tippy.js/animations/scale-subtle.css'\nimport 'tippy.js/animations/perspective-subtle.css'\n\nimport {Placement} from 'tippy.js'\n\nimport CloseIcon from '../../widgets/icons/close'\nimport Button from '../../widgets/buttons/button'\nimport IconButton from '../../widgets/buttons/iconButton'\nimport CompassIcon from '../../widgets/icons/compassIcon'\n\nimport PulsatingDot from '../pulsating_dot'\n\nimport TutorialTourTipBackdrop, {Coords, TutorialTourTipPunchout} from './tutorial_tour_tip_backdrop'\n\nimport useTutorialTourTipManager, {TutorialTourTipManagerProps} from './tutorial_tour_tip_manager'\n\nconst TourTipOverlay = ({\n    children,\n    show,\n    onClick,\n}: { children: React.ReactNode, show: boolean, onClick: (e: React.MouseEvent) => void }) =>\n    (show ? ReactDOM.createPortal(\n        <div\n            className='tutorial-tour-tip__overlay'\n            onClick={onClick}\n        >\n            {children}\n        </div>,\n        document.body,\n    ) : null)\n\ntype Props = {\n    screen: JSX.Element\n    title: JSX.Element\n    imageURL?: string\n    punchOut?: TutorialTourTipPunchout | null\n    step: number\n    singleTip?: boolean\n    showOptOut?: boolean\n    placement?: Placement\n    telemetryTag?: string\n    stopPropagation?: boolean\n    preventDefault?: boolean\n    tutorialCategory: string\n    onNextNavigateTo?: () => void\n    onPrevNavigateTo?: () => void\n    autoTour?: boolean\n    pulsatingDotPosition?: Coords | undefined\n    width?: string | number\n    className?: string\n    hideNavButtons?: boolean\n    hideBackdrop?: boolean\n    clickThroughPunchhole?: boolean\n    onPunchholeClick?: (e: React.MouseEvent) => void\n    skipCategoryFromBackdrop?: boolean\n}\n\nconst TutorialTourTip = ({\n    title,\n    screen,\n    imageURL,\n    punchOut,\n    autoTour,\n    tutorialCategory,\n    singleTip,\n    step,\n    onNextNavigateTo,\n    onPrevNavigateTo,\n    telemetryTag,\n    placement,\n    showOptOut,\n    pulsatingDotPosition,\n    stopPropagation = true,\n    preventDefault = true,\n    width = window.innerWidth > 2559 ? 500 : 320,\n    className,\n    hideNavButtons = false,\n    hideBackdrop = false,\n    onPunchholeClick,\n    skipCategoryFromBackdrop,\n}: Props): JSX.Element => {\n    const managerProps: TutorialTourTipManagerProps = {\n        step,\n        autoTour,\n        telemetryTag,\n        tutorialCategory,\n        onNextNavigateTo,\n        onPrevNavigateTo,\n        stopPropagation,\n        preventDefault,\n    }\n\n    const triggerRef = useRef(null)\n    const {\n        show,\n        tourSteps,\n        handleOpen,\n        handleHide,\n        handleDismiss,\n        handleNext,\n        handlePrevious,\n        handleSkipTutorial,\n        handleSavePreferences,\n        getLastStep,\n        handleEventPropagationAndDefault,\n        handleSendToNextTour,\n    } = useTutorialTourTipManager(managerProps)\n\n    const getButtonText = (): JSX.Element => {\n        let buttonText = (\n            <FormattedMessage\n                id={'tutorial_tip.ok'}\n                defaultMessage={'Next'}\n            />\n        )\n        if (singleTip) {\n            buttonText = (\n                <FormattedMessage\n                    id={'tutorial_tip.got_it'}\n                    defaultMessage={'Got it'}\n                />\n            )\n            return buttonText\n        }\n\n        const lastStep = getLastStep()\n        if (step === lastStep) {\n            buttonText = (\n                <FormattedMessage\n                    id={'tutorial_tip.finish_tour'}\n                    defaultMessage={'Done'}\n                />\n            )\n        }\n\n        return buttonText\n    }\n\n    const dots = []\n\n    if (!singleTip && tourSteps) {\n        for (let i = 0; i < (Object.values(tourSteps).length); i++) {\n            let classname = 'tutorial-tour-tip__circle'\n            let circularRing = 'tutorial-tour-tip__circular-ring'\n\n            if (i === step) {\n                classname += ' active'\n                circularRing += ' tutorial-tour-tip__circular-ring-active'\n            }\n\n            dots.push(\n                <div\n                    key={'dotactive' + i}\n                    className={circularRing}\n                >\n                    <a\n                        href='#'\n                        className={classname}\n                        data-screen={i}\n                        onClick={() => handleSavePreferences(i)}\n                    />\n                </div>,\n            )\n        }\n    }\n\n    const content = (\n        <div\n            onClick={(e) => {\n                e.stopPropagation()\n            }}\n        >\n            <div className='tutorial-tour-tip__header'>\n                <h4 className='tutorial-tour-tip__header__title'>\n                    {title}\n                </h4>\n                <IconButton\n                    className='tutorial-tour-tip__header__close'\n                    size='small'\n                    icon={<CloseIcon/>}\n                    onClick={(e) => {\n                        if (skipCategoryFromBackdrop) {\n                            handleDismiss(e)\n                            handleSendToNextTour(tutorialCategory)\n                        }\n                    }}\n                />\n            </div>\n            <div className='tutorial-tour-tip__body'>\n                {screen}\n            </div>\n            {imageURL && (\n                <div className='tutorial-tour-tip__image'>\n                    <img\n                        src={imageURL}\n                        alt={'tutorial tour tip product image'}\n                    />\n                </div>\n            )}\n            <div className='tutorial-tour-tip__footer'>\n                <div className='tutorial-tour-tip__footer-buttons'>\n                    <div className='tutorial-tour-tip__circles-ctr'>{dots}</div>\n                    <div className={'tutorial-tour-tip__btn-ctr'}>\n                        {!hideNavButtons && step !== 0 && (\n                            <Button\n                                title='Previous'\n                                size='small'\n                                emphasis='tertiary'\n                                onClick={handlePrevious}\n                                icon={\n                                    <CompassIcon\n                                        className='icon'\n                                        icon='chevron-left'\n                                    />}\n                            >\n                                <FormattedMessage\n                                    id='generic.previous'\n                                    defaultMessage='Previous'\n                                />\n                            </Button>\n                        )}\n\n                        {\n                            !hideNavButtons && (\n                                <Button\n                                    className='tipNextButton'\n                                    size='small'\n                                    filled={true}\n                                    onClick={handleNext}\n                                    rightIcon={true}\n                                    icon={(singleTip || step === getLastStep()) ? '' : (\n                                        <CompassIcon\n                                            className='icon'\n                                            icon='chevron-right'\n                                        />\n                                    )\n                                    }\n                                >\n                                    {getButtonText()}\n                                </Button>\n                            )\n                        }\n                    </div>\n                </div>\n                {showOptOut && <div className='tutorial-tour-tip__opt'>\n                    <FormattedMessage\n                        id='tutorial_tip.seen'\n                        defaultMessage='Seen this before? '\n                    />\n                    <a\n                        href='#'\n                        onClick={handleSkipTutorial}\n                    >\n                        <FormattedMessage\n                            id='tutorial_tip.out'\n                            defaultMessage='Opt out of these tips.'\n                        />\n                    </a>\n                </div>}\n            </div>\n        </div>\n    )\n\n    return (\n        <>\n            <div\n                ref={triggerRef}\n                onClick={handleOpen}\n                className={`tutorial-tour-tip__pulsating-dot-ctr ${className || ''}`}\n            >\n                <PulsatingDot coords={pulsatingDotPosition}/>\n            </div>\n            <TourTipOverlay\n                show={!hideBackdrop && show}\n                onClick={(e) => {\n                    handleEventPropagationAndDefault(e)\n                    handleHide(e)\n                    if (onPunchholeClick) {\n                        onPunchholeClick(e)\n                    }\n                }}\n            >\n                <TutorialTourTipBackdrop\n                    x={punchOut?.x}\n                    y={punchOut?.y}\n                    width={punchOut?.width}\n                    height={punchOut?.height}\n                    handleClick={(e) => {\n                        if (skipCategoryFromBackdrop) {\n                            e.preventDefault()\n                            e.stopPropagation()\n                            handleSendToNextTour(tutorialCategory)\n                        }\n                    }}\n                />\n            </TourTipOverlay>\n            {show && (\n                <Tippy\n                    showOnCreate={show}\n                    content={content}\n                    animation='scale-subtle'\n                    trigger='click'\n                    duration={[250, 150]}\n                    maxWidth={width}\n                    aria={{content: 'labelledby'}}\n                    allowHTML={true}\n                    zIndex={9999}\n                    reference={triggerRef}\n                    interactive={true}\n                    appendTo={document.body}\n                    className={`tutorial-tour-tip__box ${className || ''}`}\n                    placement={placement}\n                />\n            )}\n        </>\n    )\n}\n\nexport default TutorialTourTip\n"
  },
  {
    "path": "webapp/src/components/tutorial_tour_tip/tutorial_tour_tip_backdrop.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\n\nexport type Coords = {\n    x?: string\n    y?: string\n}\nexport type TutorialTourTipPunchout = Coords & {\n    width?: string\n    height?: string\n    handleClick?: (e: React.MouseEvent) => void\n}\n\nconst TutorialTourTipBackdrop = (props: TutorialTourTipPunchout) => {\n    const {x, y, width, height} = props\n    if (!x || !y || !width || !height) {\n        return null\n    }\n\n    const vertices = []\n\n    // draw to top left of punch out\n    vertices.push('0% 0%')\n    vertices.push('0% 100%')\n    vertices.push('100% 100%')\n    vertices.push('100% 0%')\n    vertices.push(`${x} 0%`)\n    vertices.push(`${x} ${y}`)\n\n    // draw punch out\n    vertices.push(`calc(${x} + ${width}) ${y}`)\n    vertices.push(`calc(${x} + ${width}) calc(${y} + ${height})`)\n    vertices.push(`${x} calc(${y} + ${height})`)\n    vertices.push(`${x} ${y}`)\n\n    // close off punch out\n    vertices.push(`${x} 0%`)\n    vertices.push('0% 0%')\n\n    return (\n        <div\n            className={'tip-backdrop'}\n            style={{\n                left: x,\n                top: y,\n                width,\n                height,\n            }}\n            onClick={props.handleClick}\n        />\n    )\n}\n\nexport default TutorialTourTipBackdrop\n"
  },
  {
    "path": "webapp/src/components/tutorial_tour_tip/tutorial_tour_tip_manager.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React, {useCallback, useEffect, useState} from 'react'\n\nimport {useDispatch} from 'react-redux'\n\nimport {FINISHED, TourCategoriesMapToSteps, TOUR_ORDER} from '../onboardingTour'\nimport {useAppSelector} from '../../store/hooks'\nimport {getMe, getOnboardingTourStep, patchProps} from '../../store/users'\nimport {UserConfigPatch} from '../../user'\nimport octoClient from '../../octoClient'\nimport {Utils, KeyCodes} from '../../utils'\nimport TelemetryClient, {TelemetryCategory} from '../../telemetry/telemetryClient'\n\nexport interface TutorialTourTipManager {\n    show: boolean\n    tourSteps: Record<string, number>\n    getLastStep: () => number\n    handleOpen: (e: React.MouseEvent) => void\n    handleHide: (e: React.MouseEvent) => void\n    handleSkipTutorial: (e: React.MouseEvent) => void\n    handleDismiss: (e: React.MouseEvent) => void\n    handleSavePreferences: (step: number) => void\n    handlePrevious: (e: React.MouseEvent) => void\n    handleNext: (e?: React.MouseEvent) => void\n    handleEventPropagationAndDefault: (e: React.MouseEvent | KeyboardEvent) => void\n    handleSendToNextTour: (currentTourCategory: string) => Promise<void>\n}\n\nexport type TutorialTourTipManagerProps = {\n    autoTour?: boolean\n    telemetryTag?: string\n    tutorialCategory: string\n    step: number\n    onNextNavigateTo?: () => void\n    onPrevNavigateTo?: () => void\n    stopPropagation?: boolean\n    preventDefault?: boolean\n}\n\nconst useTutorialTourTipManager = ({\n    autoTour,\n    telemetryTag,\n    tutorialCategory,\n    onNextNavigateTo,\n    onPrevNavigateTo,\n    stopPropagation,\n    preventDefault,\n}: TutorialTourTipManagerProps): TutorialTourTipManager => {\n    const [show, setShow] = useState(false)\n    const tourSteps = TourCategoriesMapToSteps[tutorialCategory]\n\n    // Function to save the tutorial step in redux store start here which needs to be modified\n    const dispatch = useDispatch()\n    const me = useAppSelector(getMe)\n    const currentUserId = me?.id\n    const currentStep = parseInt(useAppSelector(getOnboardingTourStep), 10)\n    const savePreferences = useCallback(\n        async (useID: string, stepValue: string, tourCategory?: string) => {\n            if (!currentUserId) {\n                return\n            }\n\n            const patch: UserConfigPatch = {\n                updatedFields: {\n                    onboardingTourStep: stepValue,\n                },\n            }\n\n            if (tourCategory) {\n                patch.updatedFields!.tourCategory = tourCategory\n            }\n\n            const patchedProps = await octoClient.patchUserConfig(currentUserId, patch)\n            if (patchedProps) {\n                await dispatch(patchProps(patchedProps))\n            }\n        },\n        [dispatch],\n    )\n\n    const trackEvent = useCallback((category, event, props?) => {\n        TelemetryClient.trackEvent(category, event, props)\n    }, [])\n\n    const handleEventPropagationAndDefault = (e: React.MouseEvent | KeyboardEvent) => {\n        if (stopPropagation) {\n            e.stopPropagation()\n        }\n        if (preventDefault) {\n            e.preventDefault()\n        }\n    }\n\n    const handleKeyDown = useCallback((e: KeyboardEvent): void => {\n        if (Utils.isKeyPressed(e, KeyCodes.ENTER) && show) {\n            handleNext()\n        }\n    }, [show])\n\n    useEffect(() => {\n        if (autoTour) {\n            setShow(true)\n        }\n    }, [autoTour])\n\n    useEffect(() => {\n        window.addEventListener('keydown', handleKeyDown)\n        return () =>\n            window.removeEventListener('keydown', handleKeyDown)\n    }, [])\n\n    const handleHide = (): void => {\n        setShow(false)\n    }\n\n    const handleOpen = (e: React.MouseEvent): void => {\n        handleEventPropagationAndDefault(e)\n        setShow(true)\n    }\n\n    const handleDismiss = (e: React.MouseEvent): void => {\n        handleEventPropagationAndDefault(e)\n        handleHide()\n        const tag = telemetryTag + '_skip'\n        trackEvent(TelemetryCategory, tag)\n    }\n\n    const handleSavePreferences = async (nextStep: boolean | number): Promise<void> => {\n        if (!currentUserId) {\n            return\n        }\n\n        let stepValue = currentStep\n        if (nextStep === true) {\n            stepValue += 1\n        } else if (nextStep === false) {\n            stepValue -= 1\n        } else {\n            stepValue = nextStep\n        }\n        handleHide()\n        await savePreferences(currentUserId, stepValue.toString())\n        if (onNextNavigateTo && nextStep === true && autoTour) {\n            onNextNavigateTo()\n        } else if (onPrevNavigateTo && nextStep === false && autoTour) {\n            onPrevNavigateTo()\n        }\n    }\n\n    const handlePrevious = (e: React.MouseEvent): void => {\n        handleEventPropagationAndDefault(e)\n\n        if (telemetryTag) {\n            const tag = telemetryTag + '_previous'\n            trackEvent(TelemetryCategory, tag)\n        }\n\n        handleSavePreferences(false)\n    }\n\n    const handleNext = (e?: React.MouseEvent): void => {\n        if (e) {\n            handleEventPropagationAndDefault(e)\n        }\n        if (telemetryTag) {\n            const tag = telemetryTag + '_next'\n            trackEvent(TelemetryCategory, tag)\n        }\n        if (getLastStep() === currentStep) {\n            handleSavePreferences(FINISHED)\n        } else {\n            handleSavePreferences(true)\n        }\n    }\n\n    const handleSkipTutorial = (e: React.MouseEvent): void => {\n        handleEventPropagationAndDefault(e)\n\n        if (telemetryTag) {\n            const tag = telemetryTag + '_skip'\n            trackEvent(TelemetryCategory, tag)\n        }\n\n        if (currentUserId) {\n            savePreferences(currentUserId, FINISHED.toString())\n        }\n    }\n\n    const getLastStep = () => {\n        return Object.values(tourSteps).reduce((maxStep, candidateMaxStep) => {\n            // ignore the \"opt out\" FINISHED step as the max step.\n            if (candidateMaxStep > maxStep && candidateMaxStep !== tourSteps.FINISHED) {\n                return candidateMaxStep\n            }\n            return maxStep\n        }, Number.MIN_SAFE_INTEGER)\n    }\n\n    const handleSendToNextTour = (currentTourCategory: string): Promise<void> => {\n        if (!currentUserId) {\n            return Promise.resolve()\n        }\n\n        const i = TOUR_ORDER.indexOf(currentTourCategory)\n        if (i === -1) {\n            Utils.logError(`Unknown tour category encountered: ${currentTourCategory}`)\n        }\n\n        let stepValue\n        let tourCategory: string\n        if (i === TOUR_ORDER.length - 1) {\n            stepValue = FINISHED\n            tourCategory = currentTourCategory\n        } else {\n            stepValue = 0\n            tourCategory = TOUR_ORDER[i + 1]\n        }\n\n        return savePreferences(currentUserId, stepValue.toString(), tourCategory)\n    }\n\n    return {\n        show,\n        tourSteps,\n        handleOpen,\n        handleHide,\n        handleDismiss,\n        handleNext,\n        handlePrevious,\n        handleSkipTutorial,\n        handleSavePreferences,\n        getLastStep,\n        handleEventPropagationAndDefault,\n        handleSendToNextTour,\n    }\n}\n\nexport default useTutorialTourTipManager\n"
  },
  {
    "path": "webapp/src/components/tutorial_tour_tip/useElementAvailable.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {useEffect, useRef, useState} from 'react'\n\nexport default function useElementAvailable(\n    elementIds: string[],\n): boolean {\n    const checkAvailableInterval = useRef<NodeJS.Timeout | null>(null)\n    const [available, setAvailable] = useState(false)\n    useEffect(() => {\n        if (available) {\n            if (checkAvailableInterval.current) {\n                clearInterval(checkAvailableInterval.current)\n                checkAvailableInterval.current = null\n            }\n            return\n        } else if (checkAvailableInterval.current) {\n            return\n        }\n        checkAvailableInterval.current = setInterval(() => {\n            if (elementIds.every((x) => document.querySelector(x))) {\n                setAvailable(true)\n                if (checkAvailableInterval.current) {\n                    clearInterval(checkAvailableInterval.current)\n                    checkAvailableInterval.current = null\n                }\n            }\n        }, 500)\n    }, [])\n\n    return available\n}\n"
  },
  {
    "path": "webapp/src/components/viewHeader/__snapshots__/dateFilter.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/viewHeader/dateFilter handle clear 1`] = `\n<div>\n  <div\n    class=\"DateFilter  \"\n  >\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        June 15\n      </span>\n    </button>\n  </div>\n</div>\n`;\n\nexports[`components/viewHeader/dateFilter handles \\`Today\\` button click event 1`] = `\n<div>\n  <div\n    class=\"DateFilter empty \"\n  >\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        Empty\n      </span>\n    </button>\n  </div>\n</div>\n`;\n\nexports[`components/viewHeader/dateFilter handles calendar click event 1`] = `\n<IntlProvider\n  defaultFormats={Object {}}\n  defaultLocale=\"en\"\n  fallbackOnEmptyString={true}\n  formats={Object {}}\n  locale=\"en\"\n  messages={Object {}}\n  onError={[Function]}\n  onWarn={[Function]}\n  textComponent={Symbol(react.fragment)}\n>\n  <DateFilter\n    filter={\n      Object {\n        \"condition\": \"is\",\n        \"propertyId\": \"myPropertyId\",\n        \"values\": Array [],\n      }\n    }\n    view={\n      Object {\n        \"boardId\": \"testBoardID\",\n        \"createAt\": 1672228800000,\n        \"createdBy\": \"\",\n        \"deleteAt\": 0,\n        \"fields\": Object {\n          \"cardOrder\": Array [\n            \"card1\",\n            \"card2\",\n            \"card3\",\n          ],\n          \"collapsedOptionIds\": Array [],\n          \"columnCalculations\": Object {},\n          \"columnWidths\": Object {\n            \"column1\": 100,\n            \"column2\": 200,\n          },\n          \"dateDisplayPropertyId\": undefined,\n          \"defaultTemplateId\": \"\",\n          \"filter\": Object {\n            \"filters\": Array [\n              Object {\n                \"condition\": \"is\",\n                \"propertyId\": \"myPropertyId\",\n                \"values\": Array [],\n              },\n            ],\n            \"operation\": \"and\",\n          },\n          \"groupById\": \"property1\",\n          \"hiddenOptionIds\": Array [\n            \"value1\",\n          ],\n          \"kanbanCalculations\": Object {},\n          \"sortOptions\": Array [\n            Object {\n              \"propertyId\": \"property1\",\n              \"reversed\": true,\n            },\n            Object {\n              \"propertyId\": \"property2\",\n              \"reversed\": false,\n            },\n          ],\n          \"viewType\": \"board\",\n          \"visibleOptionIds\": Array [],\n          \"visiblePropertyIds\": Array [],\n        },\n        \"id\": \"testViewID\",\n        \"limited\": false,\n        \"modifiedBy\": \"\",\n        \"parentId\": \"\",\n        \"schema\": 1,\n        \"title\": \"view title\",\n        \"type\": \"view\",\n        \"updateAt\": 1672228800000,\n      }\n    }\n  />\n</IntlProvider>\n`;\n\nexports[`components/viewHeader/dateFilter return dateFilter default value 1`] = `\n<div>\n  <div\n    class=\"DateFilter empty \"\n  >\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        Empty\n      </span>\n    </button>\n  </div>\n</div>\n`;\n\nexports[`components/viewHeader/dateFilter return dateFilter invalid value 1`] = `\n<div>\n  <div\n    class=\"DateFilter  \"\n  >\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        Invalid Date\n      </span>\n    </button>\n  </div>\n</div>\n`;\n\nexports[`components/viewHeader/dateFilter return dateFilter valid value 1`] = `\n<div>\n  <div\n    class=\"DateFilter  \"\n  >\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        June 15\n      </span>\n    </button>\n  </div>\n</div>\n`;\n\nexports[`components/viewHeader/dateFilter returns local correctly - es local 1`] = `\n<div>\n  <div\n    class=\"DateFilter  \"\n  >\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        15 de junio\n      </span>\n    </button>\n  </div>\n</div>\n`;\n\nexports[`components/viewHeader/dateFilter set via text input 1`] = `\n<div>\n  <div\n    class=\"DateFilter empty \"\n  >\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        Empty\n      </span>\n    </button>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/viewHeader/__snapshots__/emptyCardButton.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/viewHeader/emptyCardButton return EmptyCardButton 1`] = `\n<div>\n  <div\n    aria-label=\"Empty card\"\n    class=\"MenuOption TextOption menu-option bold-menu-text\"\n    role=\"button\"\n  >\n    <div\n      class=\"d-flex\"\n    >\n      <div\n        class=\"menu-option__icon\"\n      >\n        <svg\n          class=\"CardIcon Icon\"\n          viewBox=\"0 0 100 100\"\n          xmlns=\"http://www.w3.org/2000/svg\"\n        >\n          <rect\n            height=\"40\"\n            rx=\"3\"\n            ry=\"3\"\n            width=\"60\"\n            x=\"20\"\n            y=\"30\"\n          />\n        </svg>\n      </div>\n    </div>\n    <div\n      class=\"menu-option__content\"\n    >\n      <div\n        class=\"menu-name\"\n      >\n        Empty card\n      </div>\n    </div>\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper\"\n      role=\"button\"\n    >\n      <button\n        class=\"IconButton\"\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n        />\n      </button>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/viewHeader/emptyCardButton return EmptyCardButton and Set Template 1`] = `\n<div>\n  <div\n    aria-label=\"Empty card\"\n    class=\"MenuOption TextOption menu-option bold-menu-text\"\n    role=\"button\"\n  >\n    <div\n      class=\"d-flex\"\n    >\n      <div\n        class=\"menu-option__icon\"\n      >\n        <svg\n          class=\"CardIcon Icon\"\n          viewBox=\"0 0 100 100\"\n          xmlns=\"http://www.w3.org/2000/svg\"\n        >\n          <rect\n            height=\"40\"\n            rx=\"3\"\n            ry=\"3\"\n            width=\"60\"\n            x=\"20\"\n            y=\"30\"\n          />\n        </svg>\n      </div>\n    </div>\n    <div\n      class=\"menu-option__content\"\n    >\n      <div\n        class=\"menu-name\"\n      >\n        Empty card\n      </div>\n    </div>\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper override menuOpened\"\n      role=\"button\"\n    >\n      <button\n        class=\"IconButton\"\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n        />\n      </button>\n      <div\n        class=\"Menu noselect left \"\n      >\n        <div\n          class=\"menu-contents\"\n        >\n          <div\n            class=\"menu-options\"\n          >\n            <div>\n              <div\n                aria-label=\"Set as default\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <svg\n                      class=\"CheckIcon Icon\"\n                      viewBox=\"0 0 100 100\"\n                      xmlns=\"http://www.w3.org/2000/svg\"\n                    >\n                      <polyline\n                        points=\"20,60 40,80 80,40\"\n                      />\n                    </svg>\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Set as default\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"menu-spacer hideOnWidescreen\"\n          />\n          <div\n            class=\"menu-options hideOnWidescreen\"\n          >\n            <div\n              aria-label=\"Cancel\"\n              class=\"MenuOption TextOption menu-option menu-cancel\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Cancel\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/viewHeader/emptyCardButton return EmptyCardButton and addCard 1`] = `\n<div>\n  <div\n    aria-label=\"Empty card\"\n    class=\"MenuOption TextOption menu-option bold-menu-text\"\n    role=\"button\"\n  >\n    <div\n      class=\"d-flex\"\n    >\n      <div\n        class=\"menu-option__icon\"\n      >\n        <svg\n          class=\"CardIcon Icon\"\n          viewBox=\"0 0 100 100\"\n          xmlns=\"http://www.w3.org/2000/svg\"\n        >\n          <rect\n            height=\"40\"\n            rx=\"3\"\n            ry=\"3\"\n            width=\"60\"\n            x=\"20\"\n            y=\"30\"\n          />\n        </svg>\n      </div>\n    </div>\n    <div\n      class=\"menu-option__content\"\n    >\n      <div\n        class=\"menu-name\"\n      >\n        Empty card\n      </div>\n    </div>\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper\"\n      role=\"button\"\n    >\n      <button\n        class=\"IconButton\"\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n        />\n      </button>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/viewHeader/__snapshots__/filterComponent.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/viewHeader/filterComponent return filterComponent 1`] = `\n<div>\n  <div\n    class=\"Modal bottom\"\n  >\n    <div\n      class=\"toolbar hideOnWidescreen\"\n    >\n      <button\n        aria-label=\"Close\"\n        class=\"IconButton\"\n        title=\"Close\"\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-close CloseIcon\"\n        />\n      </button>\n    </div>\n    <div\n      class=\"FilterComponent\"\n    >\n      <div\n        class=\"FilterEntry\"\n      >\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper override menuOpened\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              Status\n            </span>\n          </button>\n          <div\n            class=\"Menu noselect bottom \"\n          >\n            <div\n              class=\"menu-contents\"\n            >\n              <div\n                class=\"menu-options\"\n              >\n                <div>\n                  <div\n                    aria-label=\"Title\"\n                    class=\"MenuOption TextOption menu-option\"\n                    role=\"button\"\n                  >\n                    <div\n                      class=\"d-flex\"\n                    >\n                      <div\n                        class=\"noicon\"\n                      />\n                    </div>\n                    <div\n                      class=\"menu-option__content\"\n                    >\n                      <div\n                        class=\"menu-name\"\n                      >\n                        Title\n                      </div>\n                    </div>\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                </div>\n                <div>\n                  <div\n                    aria-label=\"Status\"\n                    class=\"MenuOption TextOption menu-option\"\n                    role=\"button\"\n                  >\n                    <div\n                      class=\"d-flex\"\n                    >\n                      <div\n                        class=\"noicon\"\n                      />\n                    </div>\n                    <div\n                      class=\"menu-option__content\"\n                    >\n                      <div\n                        class=\"menu-name\"\n                      >\n                        Status\n                      </div>\n                    </div>\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                </div>\n                <div>\n                  <div\n                    aria-label=\"Property 1\"\n                    class=\"MenuOption TextOption menu-option\"\n                    role=\"button\"\n                  >\n                    <div\n                      class=\"d-flex\"\n                    >\n                      <div\n                        class=\"noicon\"\n                      />\n                    </div>\n                    <div\n                      class=\"menu-option__content\"\n                    >\n                      <div\n                        class=\"menu-name\"\n                      >\n                        Property 1\n                      </div>\n                    </div>\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                </div>\n                <div>\n                  <div\n                    aria-label=\"Property 2\"\n                    class=\"MenuOption TextOption menu-option\"\n                    role=\"button\"\n                  >\n                    <div\n                      class=\"d-flex\"\n                    >\n                      <div\n                        class=\"noicon\"\n                      />\n                    </div>\n                    <div\n                      class=\"menu-option__content\"\n                    >\n                      <div\n                        class=\"menu-name\"\n                      >\n                        Property 2\n                      </div>\n                    </div>\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                </div>\n                <div>\n                  <div\n                    aria-label=\"Property 3\"\n                    class=\"MenuOption TextOption menu-option\"\n                    role=\"button\"\n                  >\n                    <div\n                      class=\"d-flex\"\n                    >\n                      <div\n                        class=\"noicon\"\n                      />\n                    </div>\n                    <div\n                      class=\"menu-option__content\"\n                    >\n                      <div\n                        class=\"menu-name\"\n                      >\n                        Property 3\n                      </div>\n                    </div>\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                </div>\n              </div>\n              <div\n                class=\"menu-spacer hideOnWidescreen\"\n              />\n              <div\n                class=\"menu-options hideOnWidescreen\"\n              >\n                <div\n                  aria-label=\"Cancel\"\n                  class=\"MenuOption TextOption menu-option menu-cancel\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex\"\n                  >\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Cancel\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              includes\n            </span>\n          </button>\n        </div>\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper filterValue\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              Status\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"octo-spacer\"\n        />\n        <button\n          class=\"Button\"\n          type=\"button\"\n        >\n          <span>\n            Delete\n          </span>\n        </button>\n      </div>\n      <br />\n      <button\n        class=\"Button\"\n        type=\"button\"\n      >\n        <span>\n          + Add filter\n        </span>\n      </button>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/viewHeader/filterComponent return filterComponent and add Filter 1`] = `\n<div>\n  <div\n    class=\"Modal bottom\"\n  >\n    <div\n      class=\"toolbar hideOnWidescreen\"\n    >\n      <button\n        aria-label=\"Close\"\n        class=\"IconButton\"\n        title=\"Close\"\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-close CloseIcon\"\n        />\n      </button>\n    </div>\n    <div\n      class=\"FilterComponent\"\n    >\n      <div\n        class=\"FilterEntry\"\n      >\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper override menuOpened\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              Status\n            </span>\n          </button>\n          <div\n            class=\"Menu noselect bottom \"\n          >\n            <div\n              class=\"menu-contents\"\n            >\n              <div\n                class=\"menu-options\"\n              >\n                <div>\n                  <div\n                    aria-label=\"Title\"\n                    class=\"MenuOption TextOption menu-option\"\n                    role=\"button\"\n                  >\n                    <div\n                      class=\"d-flex\"\n                    >\n                      <div\n                        class=\"noicon\"\n                      />\n                    </div>\n                    <div\n                      class=\"menu-option__content\"\n                    >\n                      <div\n                        class=\"menu-name\"\n                      >\n                        Title\n                      </div>\n                    </div>\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                </div>\n                <div>\n                  <div\n                    aria-label=\"Status\"\n                    class=\"MenuOption TextOption menu-option\"\n                    role=\"button\"\n                  >\n                    <div\n                      class=\"d-flex\"\n                    >\n                      <div\n                        class=\"noicon\"\n                      />\n                    </div>\n                    <div\n                      class=\"menu-option__content\"\n                    >\n                      <div\n                        class=\"menu-name\"\n                      >\n                        Status\n                      </div>\n                    </div>\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                </div>\n                <div>\n                  <div\n                    aria-label=\"Property 1\"\n                    class=\"MenuOption TextOption menu-option\"\n                    role=\"button\"\n                  >\n                    <div\n                      class=\"d-flex\"\n                    >\n                      <div\n                        class=\"noicon\"\n                      />\n                    </div>\n                    <div\n                      class=\"menu-option__content\"\n                    >\n                      <div\n                        class=\"menu-name\"\n                      >\n                        Property 1\n                      </div>\n                    </div>\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                </div>\n                <div>\n                  <div\n                    aria-label=\"Property 2\"\n                    class=\"MenuOption TextOption menu-option\"\n                    role=\"button\"\n                  >\n                    <div\n                      class=\"d-flex\"\n                    >\n                      <div\n                        class=\"noicon\"\n                      />\n                    </div>\n                    <div\n                      class=\"menu-option__content\"\n                    >\n                      <div\n                        class=\"menu-name\"\n                      >\n                        Property 2\n                      </div>\n                    </div>\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                </div>\n                <div>\n                  <div\n                    aria-label=\"Property 3\"\n                    class=\"MenuOption TextOption menu-option\"\n                    role=\"button\"\n                  >\n                    <div\n                      class=\"d-flex\"\n                    >\n                      <div\n                        class=\"noicon\"\n                      />\n                    </div>\n                    <div\n                      class=\"menu-option__content\"\n                    >\n                      <div\n                        class=\"menu-name\"\n                      >\n                        Property 3\n                      </div>\n                    </div>\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                </div>\n              </div>\n              <div\n                class=\"menu-spacer hideOnWidescreen\"\n              />\n              <div\n                class=\"menu-options hideOnWidescreen\"\n              >\n                <div\n                  aria-label=\"Cancel\"\n                  class=\"MenuOption TextOption menu-option menu-cancel\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex\"\n                  >\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Cancel\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              includes\n            </span>\n          </button>\n        </div>\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper filterValue\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              Status\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"octo-spacer\"\n        />\n        <button\n          class=\"Button\"\n          type=\"button\"\n        >\n          <span>\n            Delete\n          </span>\n        </button>\n      </div>\n      <br />\n      <button\n        class=\"Button\"\n        type=\"button\"\n      >\n        <span>\n          + Add filter\n        </span>\n      </button>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/viewHeader/filterComponent return filterComponent and click is empty 1`] = `\n<div>\n  <div\n    class=\"Modal bottom\"\n  >\n    <div\n      class=\"toolbar hideOnWidescreen\"\n    >\n      <button\n        aria-label=\"Close\"\n        class=\"IconButton\"\n        title=\"Close\"\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-close CloseIcon\"\n        />\n      </button>\n    </div>\n    <div\n      class=\"FilterComponent\"\n    >\n      <div\n        class=\"FilterEntry\"\n      >\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              Status\n            </span>\n          </button>\n        </div>\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper override menuOpened\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              includes\n            </span>\n          </button>\n          <div\n            class=\"Menu noselect bottom \"\n          >\n            <div\n              class=\"menu-contents\"\n            >\n              <div\n                class=\"menu-options\"\n              >\n                <div>\n                  <div\n                    aria-label=\"includes\"\n                    class=\"MenuOption TextOption menu-option\"\n                    role=\"button\"\n                  >\n                    <div\n                      class=\"d-flex\"\n                    >\n                      <div\n                        class=\"noicon\"\n                      />\n                    </div>\n                    <div\n                      class=\"menu-option__content\"\n                    >\n                      <div\n                        class=\"menu-name\"\n                      >\n                        includes\n                      </div>\n                    </div>\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                  <div\n                    aria-label=\"doesn't include\"\n                    class=\"MenuOption TextOption menu-option\"\n                    role=\"button\"\n                  >\n                    <div\n                      class=\"d-flex\"\n                    >\n                      <div\n                        class=\"noicon\"\n                      />\n                    </div>\n                    <div\n                      class=\"menu-option__content\"\n                    >\n                      <div\n                        class=\"menu-name\"\n                      >\n                        doesn't include\n                      </div>\n                    </div>\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                  <div\n                    aria-label=\"is empty\"\n                    class=\"MenuOption TextOption menu-option\"\n                    role=\"button\"\n                  >\n                    <div\n                      class=\"d-flex\"\n                    >\n                      <div\n                        class=\"noicon\"\n                      />\n                    </div>\n                    <div\n                      class=\"menu-option__content\"\n                    >\n                      <div\n                        class=\"menu-name\"\n                      >\n                        is empty\n                      </div>\n                    </div>\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                  <div\n                    aria-label=\"is not empty\"\n                    class=\"MenuOption TextOption menu-option\"\n                    role=\"button\"\n                  >\n                    <div\n                      class=\"d-flex\"\n                    >\n                      <div\n                        class=\"noicon\"\n                      />\n                    </div>\n                    <div\n                      class=\"menu-option__content\"\n                    >\n                      <div\n                        class=\"menu-name\"\n                      >\n                        is not empty\n                      </div>\n                    </div>\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                </div>\n                <div />\n                <div />\n                <div />\n                <div />\n                <div />\n                <div />\n              </div>\n              <div\n                class=\"menu-spacer hideOnWidescreen\"\n              />\n              <div\n                class=\"menu-options hideOnWidescreen\"\n              >\n                <div\n                  aria-label=\"Cancel\"\n                  class=\"MenuOption TextOption menu-option menu-cancel\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex\"\n                  >\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Cancel\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper filterValue\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              Status\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"octo-spacer\"\n        />\n        <button\n          class=\"Button\"\n          type=\"button\"\n        >\n          <span>\n            Delete\n          </span>\n        </button>\n      </div>\n      <br />\n      <button\n        class=\"Button\"\n        type=\"button\"\n      >\n        <span>\n          + Add filter\n        </span>\n      </button>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/viewHeader/filterComponent return filterComponent and filter by status 1`] = `\n<div>\n  <div\n    class=\"Modal bottom\"\n  >\n    <div\n      class=\"toolbar hideOnWidescreen\"\n    >\n      <button\n        aria-label=\"Close\"\n        class=\"IconButton\"\n        title=\"Close\"\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-close CloseIcon\"\n        />\n      </button>\n    </div>\n    <div\n      class=\"FilterComponent\"\n    >\n      <div\n        class=\"FilterEntry\"\n      >\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper override menuOpened\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              (unknown)\n            </span>\n          </button>\n          <div\n            class=\"Menu noselect bottom \"\n          >\n            <div\n              class=\"menu-contents\"\n            >\n              <div\n                class=\"menu-options\"\n              >\n                <div>\n                  <div\n                    aria-label=\"Title\"\n                    class=\"MenuOption TextOption menu-option\"\n                    role=\"button\"\n                  >\n                    <div\n                      class=\"d-flex\"\n                    >\n                      <div\n                        class=\"noicon\"\n                      />\n                    </div>\n                    <div\n                      class=\"menu-option__content\"\n                    >\n                      <div\n                        class=\"menu-name\"\n                      >\n                        Title\n                      </div>\n                    </div>\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                </div>\n                <div>\n                  <div\n                    aria-label=\"Status\"\n                    class=\"MenuOption TextOption menu-option\"\n                    role=\"button\"\n                  >\n                    <div\n                      class=\"d-flex\"\n                    >\n                      <div\n                        class=\"noicon\"\n                      />\n                    </div>\n                    <div\n                      class=\"menu-option__content\"\n                    >\n                      <div\n                        class=\"menu-name\"\n                      >\n                        Status\n                      </div>\n                    </div>\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                </div>\n                <div>\n                  <div\n                    aria-label=\"Property 1\"\n                    class=\"MenuOption TextOption menu-option\"\n                    role=\"button\"\n                  >\n                    <div\n                      class=\"d-flex\"\n                    >\n                      <div\n                        class=\"noicon\"\n                      />\n                    </div>\n                    <div\n                      class=\"menu-option__content\"\n                    >\n                      <div\n                        class=\"menu-name\"\n                      >\n                        Property 1\n                      </div>\n                    </div>\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                </div>\n                <div>\n                  <div\n                    aria-label=\"Property 2\"\n                    class=\"MenuOption TextOption menu-option\"\n                    role=\"button\"\n                  >\n                    <div\n                      class=\"d-flex\"\n                    >\n                      <div\n                        class=\"noicon\"\n                      />\n                    </div>\n                    <div\n                      class=\"menu-option__content\"\n                    >\n                      <div\n                        class=\"menu-name\"\n                      >\n                        Property 2\n                      </div>\n                    </div>\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                </div>\n                <div>\n                  <div\n                    aria-label=\"Property 3\"\n                    class=\"MenuOption TextOption menu-option\"\n                    role=\"button\"\n                  >\n                    <div\n                      class=\"d-flex\"\n                    >\n                      <div\n                        class=\"noicon\"\n                      />\n                    </div>\n                    <div\n                      class=\"menu-option__content\"\n                    >\n                      <div\n                        class=\"menu-name\"\n                      >\n                        Property 3\n                      </div>\n                    </div>\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                </div>\n              </div>\n              <div\n                class=\"menu-spacer hideOnWidescreen\"\n              />\n              <div\n                class=\"menu-options hideOnWidescreen\"\n              >\n                <div\n                  aria-label=\"Cancel\"\n                  class=\"MenuOption TextOption menu-option menu-cancel\"\n                  role=\"button\"\n                >\n                  <div\n                    class=\"d-flex\"\n                  >\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                  <div\n                    class=\"menu-option__content\"\n                  >\n                    <div\n                      class=\"menu-name\"\n                    >\n                      Cancel\n                    </div>\n                  </div>\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n        <div\n          aria-label=\"menuwrapper\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"Button\"\n            type=\"button\"\n          >\n            <span>\n              (unknown)\n            </span>\n          </button>\n        </div>\n        <div\n          class=\"octo-spacer\"\n        />\n        <button\n          class=\"Button\"\n          type=\"button\"\n        >\n          <span>\n            Delete\n          </span>\n        </button>\n      </div>\n      <br />\n      <button\n        class=\"Button\"\n        type=\"button\"\n      >\n        <span>\n          + Add filter\n        </span>\n      </button>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/viewHeader/__snapshots__/filterEntry.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/viewHeader/filterEntry return filterEntry 1`] = `\n<div>\n  <div\n    class=\"FilterEntry\"\n  >\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper override menuOpened\"\n      role=\"button\"\n    >\n      <button\n        class=\"Button\"\n        type=\"button\"\n      >\n        <span>\n          Status\n        </span>\n      </button>\n      <div\n        class=\"Menu noselect bottom \"\n      >\n        <div\n          class=\"menu-contents\"\n        >\n          <div\n            class=\"menu-options\"\n          >\n            <div>\n              <div\n                aria-label=\"Title\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Title\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Status\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Status\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Property 1\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Property 1\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Property 2\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Property 2\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Property 3\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Property 3\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"menu-spacer hideOnWidescreen\"\n          />\n          <div\n            class=\"menu-options hideOnWidescreen\"\n          >\n            <div\n              aria-label=\"Cancel\"\n              class=\"MenuOption TextOption menu-option menu-cancel\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Cancel\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper\"\n      role=\"button\"\n    >\n      <button\n        class=\"Button\"\n        type=\"button\"\n      >\n        <span>\n          includes\n        </span>\n      </button>\n    </div>\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper filterValue\"\n      role=\"button\"\n    >\n      <button\n        class=\"Button\"\n        type=\"button\"\n      >\n        <span>\n          Status\n        </span>\n      </button>\n    </div>\n    <div\n      class=\"octo-spacer\"\n    />\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        Delete\n      </span>\n    </button>\n  </div>\n</div>\n`;\n\nexports[`components/viewHeader/filterEntry return filterEntry and click on delete 1`] = `\n<div>\n  <div\n    class=\"FilterEntry\"\n  >\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper\"\n      role=\"button\"\n    >\n      <button\n        class=\"Button\"\n        type=\"button\"\n      >\n        <span>\n          Status\n        </span>\n      </button>\n    </div>\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper override menuOpened\"\n      role=\"button\"\n    >\n      <button\n        class=\"Button\"\n        type=\"button\"\n      >\n        <span>\n          includes\n        </span>\n      </button>\n      <div\n        class=\"Menu noselect bottom \"\n      >\n        <div\n          class=\"menu-contents\"\n        >\n          <div\n            class=\"menu-options\"\n          >\n            <div>\n              <div\n                aria-label=\"includes\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    includes\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                aria-label=\"doesn't include\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    doesn't include\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                aria-label=\"is empty\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    is empty\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                aria-label=\"is not empty\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    is not empty\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div />\n            <div />\n            <div />\n            <div />\n            <div />\n            <div />\n          </div>\n          <div\n            class=\"menu-spacer hideOnWidescreen\"\n          />\n          <div\n            class=\"menu-options hideOnWidescreen\"\n          >\n            <div\n              aria-label=\"Cancel\"\n              class=\"MenuOption TextOption menu-option menu-cancel\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Cancel\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper filterValue\"\n      role=\"button\"\n    >\n      <button\n        class=\"Button\"\n        type=\"button\"\n      >\n        <span>\n          Status\n        </span>\n      </button>\n    </div>\n    <div\n      class=\"octo-spacer\"\n    />\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        Delete\n      </span>\n    </button>\n  </div>\n</div>\n`;\n\nexports[`components/viewHeader/filterEntry return filterEntry and click on different property type 1`] = `\n<div>\n  <div\n    class=\"FilterEntry\"\n  >\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper override menuOpened\"\n      role=\"button\"\n    >\n      <button\n        class=\"Button\"\n        type=\"button\"\n      >\n        <span>\n          Status\n        </span>\n      </button>\n      <div\n        class=\"Menu noselect bottom \"\n      >\n        <div\n          class=\"menu-contents\"\n        >\n          <div\n            class=\"menu-options\"\n          >\n            <div>\n              <div\n                aria-label=\"Title\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Title\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Status\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Status\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Property 1\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Property 1\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Property 2\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Property 2\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Property 3\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Property 3\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"menu-spacer hideOnWidescreen\"\n          />\n          <div\n            class=\"menu-options hideOnWidescreen\"\n          >\n            <div\n              aria-label=\"Cancel\"\n              class=\"MenuOption TextOption menu-option menu-cancel\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Cancel\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper\"\n      role=\"button\"\n    >\n      <button\n        class=\"Button\"\n        type=\"button\"\n      >\n        <span>\n          includes\n        </span>\n      </button>\n    </div>\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper filterValue\"\n      role=\"button\"\n    >\n      <button\n        class=\"Button\"\n        type=\"button\"\n      >\n        <span>\n          Status\n        </span>\n      </button>\n    </div>\n    <div\n      class=\"octo-spacer\"\n    />\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        Delete\n      </span>\n    </button>\n  </div>\n</div>\n`;\n\nexports[`components/viewHeader/filterEntry return filterEntry and click on different property type, but same filterOperation 1`] = `\n<div>\n  <div\n    class=\"FilterEntry\"\n  >\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper override menuOpened\"\n      role=\"button\"\n    >\n      <button\n        class=\"Button\"\n        type=\"button\"\n      >\n        <span>\n          Property 1\n        </span>\n      </button>\n      <div\n        class=\"Menu noselect bottom \"\n      >\n        <div\n          class=\"menu-contents\"\n        >\n          <div\n            class=\"menu-options\"\n          >\n            <div>\n              <div\n                aria-label=\"Title\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Title\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Status\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Status\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Property 1\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Property 1\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Property 2\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Property 2\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Property 3\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Property 3\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"menu-spacer hideOnWidescreen\"\n          />\n          <div\n            class=\"menu-options hideOnWidescreen\"\n          >\n            <div\n              aria-label=\"Cancel\"\n              class=\"MenuOption TextOption menu-option menu-cancel\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Cancel\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper\"\n      role=\"button\"\n    >\n      <button\n        class=\"Button\"\n        type=\"button\"\n      >\n        <span>\n          is set\n        </span>\n      </button>\n    </div>\n    <div\n      class=\"octo-spacer\"\n    />\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        Delete\n      </span>\n    </button>\n  </div>\n</div>\n`;\n\nexports[`components/viewHeader/filterEntry return filterEntry and click on doesn't include 1`] = `\n<div>\n  <div\n    class=\"FilterEntry\"\n  >\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper\"\n      role=\"button\"\n    >\n      <button\n        class=\"Button\"\n        type=\"button\"\n      >\n        <span>\n          Status\n        </span>\n      </button>\n    </div>\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper override menuOpened\"\n      role=\"button\"\n    >\n      <button\n        class=\"Button\"\n        type=\"button\"\n      >\n        <span>\n          includes\n        </span>\n      </button>\n      <div\n        class=\"Menu noselect bottom \"\n      >\n        <div\n          class=\"menu-contents\"\n        >\n          <div\n            class=\"menu-options\"\n          >\n            <div>\n              <div\n                aria-label=\"includes\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    includes\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                aria-label=\"doesn't include\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    doesn't include\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                aria-label=\"is empty\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    is empty\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                aria-label=\"is not empty\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    is not empty\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div />\n            <div />\n            <div />\n            <div />\n            <div />\n            <div />\n          </div>\n          <div\n            class=\"menu-spacer hideOnWidescreen\"\n          />\n          <div\n            class=\"menu-options hideOnWidescreen\"\n          >\n            <div\n              aria-label=\"Cancel\"\n              class=\"MenuOption TextOption menu-option menu-cancel\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Cancel\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper filterValue\"\n      role=\"button\"\n    >\n      <button\n        class=\"Button\"\n        type=\"button\"\n      >\n        <span>\n          Status\n        </span>\n      </button>\n    </div>\n    <div\n      class=\"octo-spacer\"\n    />\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        Delete\n      </span>\n    </button>\n  </div>\n</div>\n`;\n\nexports[`components/viewHeader/filterEntry return filterEntry and click on includes 1`] = `\n<div>\n  <div\n    class=\"FilterEntry\"\n  >\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper\"\n      role=\"button\"\n    >\n      <button\n        class=\"Button\"\n        type=\"button\"\n      >\n        <span>\n          Status\n        </span>\n      </button>\n    </div>\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper override menuOpened\"\n      role=\"button\"\n    >\n      <button\n        class=\"Button\"\n        type=\"button\"\n      >\n        <span>\n          includes\n        </span>\n      </button>\n      <div\n        class=\"Menu noselect bottom \"\n      >\n        <div\n          class=\"menu-contents\"\n        >\n          <div\n            class=\"menu-options\"\n          >\n            <div>\n              <div\n                aria-label=\"includes\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    includes\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                aria-label=\"doesn't include\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    doesn't include\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                aria-label=\"is empty\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    is empty\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                aria-label=\"is not empty\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    is not empty\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div />\n            <div />\n            <div />\n            <div />\n            <div />\n            <div />\n          </div>\n          <div\n            class=\"menu-spacer hideOnWidescreen\"\n          />\n          <div\n            class=\"menu-options hideOnWidescreen\"\n          >\n            <div\n              aria-label=\"Cancel\"\n              class=\"MenuOption TextOption menu-option menu-cancel\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Cancel\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper filterValue\"\n      role=\"button\"\n    >\n      <button\n        class=\"Button\"\n        type=\"button\"\n      >\n        <span>\n          Status\n        </span>\n      </button>\n    </div>\n    <div\n      class=\"octo-spacer\"\n    />\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        Delete\n      </span>\n    </button>\n  </div>\n</div>\n`;\n\nexports[`components/viewHeader/filterEntry return filterEntry and click on is empty 1`] = `\n<div>\n  <div\n    class=\"FilterEntry\"\n  >\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper\"\n      role=\"button\"\n    >\n      <button\n        class=\"Button\"\n        type=\"button\"\n      >\n        <span>\n          Status\n        </span>\n      </button>\n    </div>\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper override menuOpened\"\n      role=\"button\"\n    >\n      <button\n        class=\"Button\"\n        type=\"button\"\n      >\n        <span>\n          includes\n        </span>\n      </button>\n      <div\n        class=\"Menu noselect bottom \"\n      >\n        <div\n          class=\"menu-contents\"\n        >\n          <div\n            class=\"menu-options\"\n          >\n            <div>\n              <div\n                aria-label=\"includes\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    includes\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                aria-label=\"doesn't include\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    doesn't include\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                aria-label=\"is empty\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    is empty\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                aria-label=\"is not empty\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    is not empty\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div />\n            <div />\n            <div />\n            <div />\n            <div />\n            <div />\n          </div>\n          <div\n            class=\"menu-spacer hideOnWidescreen\"\n          />\n          <div\n            class=\"menu-options hideOnWidescreen\"\n          >\n            <div\n              aria-label=\"Cancel\"\n              class=\"MenuOption TextOption menu-option menu-cancel\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Cancel\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper filterValue\"\n      role=\"button\"\n    >\n      <button\n        class=\"Button\"\n        type=\"button\"\n      >\n        <span>\n          Status\n        </span>\n      </button>\n    </div>\n    <div\n      class=\"octo-spacer\"\n    />\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        Delete\n      </span>\n    </button>\n  </div>\n</div>\n`;\n\nexports[`components/viewHeader/filterEntry return filterEntry and click on is not empty 1`] = `\n<div>\n  <div\n    class=\"FilterEntry\"\n  >\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper\"\n      role=\"button\"\n    >\n      <button\n        class=\"Button\"\n        type=\"button\"\n      >\n        <span>\n          Status\n        </span>\n      </button>\n    </div>\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper override menuOpened\"\n      role=\"button\"\n    >\n      <button\n        class=\"Button\"\n        type=\"button\"\n      >\n        <span>\n          includes\n        </span>\n      </button>\n      <div\n        class=\"Menu noselect bottom \"\n      >\n        <div\n          class=\"menu-contents\"\n        >\n          <div\n            class=\"menu-options\"\n          >\n            <div>\n              <div\n                aria-label=\"includes\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    includes\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                aria-label=\"doesn't include\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    doesn't include\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                aria-label=\"is empty\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    is empty\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                aria-label=\"is not empty\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    is not empty\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div />\n            <div />\n            <div />\n            <div />\n            <div />\n            <div />\n          </div>\n          <div\n            class=\"menu-spacer hideOnWidescreen\"\n          />\n          <div\n            class=\"menu-options hideOnWidescreen\"\n          >\n            <div\n              aria-label=\"Cancel\"\n              class=\"MenuOption TextOption menu-option menu-cancel\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Cancel\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper filterValue\"\n      role=\"button\"\n    >\n      <button\n        class=\"Button\"\n        type=\"button\"\n      >\n        <span>\n          Status\n        </span>\n      </button>\n    </div>\n    <div\n      class=\"octo-spacer\"\n    />\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        Delete\n      </span>\n    </button>\n  </div>\n</div>\n`;\n\nexports[`components/viewHeader/filterEntry return filterEntry and click on status 1`] = `\n<div>\n  <div\n    class=\"FilterEntry\"\n  >\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper override menuOpened\"\n      role=\"button\"\n    >\n      <button\n        class=\"Button\"\n        type=\"button\"\n      >\n        <span>\n          (unknown)\n        </span>\n      </button>\n      <div\n        class=\"Menu noselect bottom \"\n      >\n        <div\n          class=\"menu-contents\"\n        >\n          <div\n            class=\"menu-options\"\n          >\n            <div>\n              <div\n                aria-label=\"Title\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Title\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Status\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Status\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Property 1\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Property 1\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Property 2\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Property 2\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Property 3\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Property 3\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"menu-spacer hideOnWidescreen\"\n          />\n          <div\n            class=\"menu-options hideOnWidescreen\"\n          >\n            <div\n              aria-label=\"Cancel\"\n              class=\"MenuOption TextOption menu-option menu-cancel\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Cancel\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper\"\n      role=\"button\"\n    >\n      <button\n        class=\"Button\"\n        type=\"button\"\n      >\n        <span>\n          (unknown)\n        </span>\n      </button>\n    </div>\n    <div\n      class=\"octo-spacer\"\n    />\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        Delete\n      </span>\n    </button>\n  </div>\n</div>\n`;\n\nexports[`components/viewHeader/filterEntry return filterEntry for boolean field 1`] = `\n<div>\n  <div\n    class=\"FilterEntry\"\n  >\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper\"\n      role=\"button\"\n    >\n      <button\n        class=\"Button\"\n        type=\"button\"\n      >\n        <span>\n          Property 1\n        </span>\n      </button>\n    </div>\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper\"\n      role=\"button\"\n    >\n      <button\n        class=\"Button\"\n        type=\"button\"\n      >\n        <span>\n          is set\n        </span>\n      </button>\n    </div>\n    <div\n      class=\"octo-spacer\"\n    />\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        Delete\n      </span>\n    </button>\n  </div>\n</div>\n`;\n\nexports[`components/viewHeader/filterEntry return filterEntry for boolean field 2`] = `\n<div>\n  <div\n    class=\"FilterEntry\"\n  >\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper\"\n      role=\"button\"\n    >\n      <button\n        class=\"Button\"\n        type=\"button\"\n      >\n        <span>\n          Property 1\n        </span>\n      </button>\n    </div>\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper override menuOpened\"\n      role=\"button\"\n    >\n      <button\n        class=\"Button\"\n        type=\"button\"\n      >\n        <span>\n          is set\n        </span>\n      </button>\n      <div\n        class=\"Menu noselect bottom \"\n      >\n        <div\n          class=\"menu-contents\"\n        >\n          <div\n            class=\"menu-options\"\n          >\n            <div />\n            <div />\n            <div />\n            <div>\n              <div\n                aria-label=\"is set\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    is set\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                aria-label=\"is not set\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    is not set\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div />\n            <div />\n            <div />\n          </div>\n          <div\n            class=\"menu-spacer hideOnWidescreen\"\n          />\n          <div\n            class=\"menu-options hideOnWidescreen\"\n          >\n            <div\n              aria-label=\"Cancel\"\n              class=\"MenuOption TextOption menu-option menu-cancel\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Cancel\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"octo-spacer\"\n    />\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        Delete\n      </span>\n    </button>\n  </div>\n</div>\n`;\n\nexports[`components/viewHeader/filterEntry return filterEntry for date field 1`] = `\n<div>\n  <div\n    class=\"FilterEntry\"\n  >\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper\"\n      role=\"button\"\n    >\n      <button\n        class=\"Button\"\n        type=\"button\"\n      >\n        <span>\n          Property 3\n        </span>\n      </button>\n    </div>\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper\"\n      role=\"button\"\n    >\n      <button\n        class=\"Button\"\n        type=\"button\"\n      >\n        <span>\n          is\n        </span>\n      </button>\n    </div>\n    <div\n      class=\"DateFilter empty \"\n    >\n      <button\n        class=\"Button\"\n        type=\"button\"\n      >\n        <span>\n          Empty\n        </span>\n      </button>\n    </div>\n    <div\n      class=\"octo-spacer\"\n    />\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        Delete\n      </span>\n    </button>\n  </div>\n</div>\n`;\n\nexports[`components/viewHeader/filterEntry return filterEntry for date field 2`] = `\n<div>\n  <div\n    class=\"FilterEntry\"\n  >\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper\"\n      role=\"button\"\n    >\n      <button\n        class=\"Button\"\n        type=\"button\"\n      >\n        <span>\n          Property 3\n        </span>\n      </button>\n    </div>\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper override menuOpened\"\n      role=\"button\"\n    >\n      <button\n        class=\"Button\"\n        type=\"button\"\n      >\n        <span>\n          is\n        </span>\n      </button>\n      <div\n        class=\"Menu noselect bottom \"\n      >\n        <div\n          class=\"menu-contents\"\n        >\n          <div\n            class=\"menu-options\"\n          >\n            <div />\n            <div />\n            <div />\n            <div />\n            <div />\n            <div>\n              <div\n                aria-label=\"is\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    is\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                aria-label=\"is before\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    is before\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                aria-label=\"is after\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    is after\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"is set\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    is set\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                aria-label=\"is not set\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    is not set\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"menu-spacer hideOnWidescreen\"\n          />\n          <div\n            class=\"menu-options hideOnWidescreen\"\n          >\n            <div\n              aria-label=\"Cancel\"\n              class=\"MenuOption TextOption menu-option menu-cancel\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Cancel\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"DateFilter empty \"\n    >\n      <button\n        class=\"Button\"\n        type=\"button\"\n      >\n        <span>\n          Empty\n        </span>\n      </button>\n    </div>\n    <div\n      class=\"octo-spacer\"\n    />\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        Delete\n      </span>\n    </button>\n  </div>\n</div>\n`;\n\nexports[`components/viewHeader/filterEntry return filterEntry for text field 1`] = `\n<div>\n  <div\n    class=\"FilterEntry\"\n  >\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper\"\n      role=\"button\"\n    >\n      <button\n        class=\"Button\"\n        type=\"button\"\n      >\n        <span>\n          Property 2\n        </span>\n      </button>\n    </div>\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper\"\n      role=\"button\"\n    >\n      <button\n        class=\"Button\"\n        type=\"button\"\n      >\n        <span>\n          contains\n        </span>\n      </button>\n    </div>\n    <input\n      class=\"Editable \"\n      placeholder=\"filter text\"\n      title=\"\"\n      value=\"\"\n    />\n    <div\n      class=\"octo-spacer\"\n    />\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        Delete\n      </span>\n    </button>\n  </div>\n</div>\n`;\n\nexports[`components/viewHeader/filterEntry return filterEntry for text field 2`] = `\n<div>\n  <div\n    class=\"FilterEntry\"\n  >\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper\"\n      role=\"button\"\n    >\n      <button\n        class=\"Button\"\n        type=\"button\"\n      >\n        <span>\n          Property 2\n        </span>\n      </button>\n    </div>\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper override menuOpened\"\n      role=\"button\"\n    >\n      <button\n        class=\"Button\"\n        type=\"button\"\n      >\n        <span>\n          contains\n        </span>\n      </button>\n      <div\n        class=\"Menu noselect bottom \"\n      >\n        <div\n          class=\"menu-contents\"\n        >\n          <div\n            class=\"menu-options\"\n          >\n            <div />\n            <div />\n            <div />\n            <div />\n            <div>\n              <div\n                aria-label=\"is\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    is\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                aria-label=\"contains\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    contains\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                aria-label=\"doesn't contain\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    doesn't contain\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                aria-label=\"starts with\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    starts with\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                aria-label=\"doesn't start with\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    doesn't start with\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                aria-label=\"ends with\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    ends with\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                aria-label=\"doesn't end with\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    doesn't end with\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div />\n            <div />\n          </div>\n          <div\n            class=\"menu-spacer hideOnWidescreen\"\n          />\n          <div\n            class=\"menu-options hideOnWidescreen\"\n          >\n            <div\n              aria-label=\"Cancel\"\n              class=\"MenuOption TextOption menu-option menu-cancel\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Cancel\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <input\n      class=\"Editable \"\n      placeholder=\"filter text\"\n      title=\"\"\n      value=\"\"\n    />\n    <div\n      class=\"octo-spacer\"\n    />\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        Delete\n      </span>\n    </button>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/viewHeader/__snapshots__/filterValue.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/viewHeader/filterValue return date filter value 1`] = `\n<div>\n  <div\n    class=\"DateFilter empty \"\n  >\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        Empty\n      </span>\n    </button>\n  </div>\n</div>\n`;\n\nexports[`components/viewHeader/filterValue return filterValue 1`] = `\n<div>\n  <div\n    aria-label=\"menuwrapper\"\n    class=\"MenuWrapper override menuOpened filterValue\"\n    role=\"button\"\n  >\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        Status\n      </span>\n    </button>\n    <div\n      class=\"Menu noselect bottom \"\n    >\n      <div\n        class=\"menu-contents\"\n      >\n        <div\n          class=\"menu-options\"\n        >\n          <div>\n            <div\n              aria-label=\"Status\"\n              class=\"MenuOption SwitchOption menu-option\"\n              role=\"button\"\n            >\n              <div\n                class=\"noicon\"\n              />\n              <div\n                class=\"menu-name\"\n              >\n                Status\n              </div>\n              <div\n                class=\"Switch override-main size--small on\"\n              >\n                <div\n                  class=\"octo-switch-inner\"\n                />\n              </div>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"menu-spacer hideOnWidescreen\"\n        />\n        <div\n          class=\"menu-options hideOnWidescreen\"\n        >\n          <div\n            aria-label=\"Cancel\"\n            class=\"MenuOption TextOption menu-option menu-cancel\"\n            role=\"button\"\n          >\n            <div\n              class=\"d-flex\"\n            >\n              <div\n                class=\"noicon\"\n              />\n            </div>\n            <div\n              class=\"menu-option__content\"\n            >\n              <div\n                class=\"menu-name\"\n              >\n                Cancel\n              </div>\n            </div>\n            <div\n              class=\"noicon\"\n            />\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/viewHeader/filterValue return filterValue and click Status 1`] = `\n<div>\n  <div\n    aria-label=\"menuwrapper\"\n    class=\"MenuWrapper override menuOpened filterValue\"\n    role=\"button\"\n  >\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        Status\n      </span>\n    </button>\n    <div\n      class=\"Menu noselect bottom \"\n    >\n      <div\n        class=\"menu-contents\"\n      >\n        <div\n          class=\"menu-options\"\n        >\n          <div>\n            <div\n              aria-label=\"Status\"\n              class=\"MenuOption SwitchOption menu-option\"\n              role=\"button\"\n            >\n              <div\n                class=\"noicon\"\n              />\n              <div\n                class=\"menu-name\"\n              >\n                Status\n              </div>\n              <div\n                class=\"Switch override-main size--small on\"\n              >\n                <div\n                  class=\"octo-switch-inner\"\n                />\n              </div>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"menu-spacer hideOnWidescreen\"\n        />\n        <div\n          class=\"menu-options hideOnWidescreen\"\n        >\n          <div\n            aria-label=\"Cancel\"\n            class=\"MenuOption TextOption menu-option menu-cancel\"\n            role=\"button\"\n          >\n            <div\n              class=\"d-flex\"\n            >\n              <div\n                class=\"noicon\"\n              />\n            </div>\n            <div\n              class=\"menu-option__content\"\n            >\n              <div\n                class=\"menu-name\"\n              >\n                Cancel\n              </div>\n            </div>\n            <div\n              class=\"noicon\"\n            />\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/viewHeader/filterValue return filterValue and click Status with Status not in filter 1`] = `\n<div>\n  <div\n    aria-label=\"menuwrapper\"\n    class=\"MenuWrapper override menuOpened filterValue\"\n    role=\"button\"\n  >\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        (Unknown)\n      </span>\n    </button>\n    <div\n      class=\"Menu noselect bottom \"\n    >\n      <div\n        class=\"menu-contents\"\n      >\n        <div\n          class=\"menu-options\"\n        >\n          <div>\n            <div\n              aria-label=\"Status\"\n              class=\"MenuOption SwitchOption menu-option\"\n              role=\"button\"\n            >\n              <div\n                class=\"noicon\"\n              />\n              <div\n                class=\"menu-name\"\n              >\n                Status\n              </div>\n              <div\n                class=\"Switch override-main size--small\"\n              >\n                <div\n                  class=\"octo-switch-inner\"\n                />\n              </div>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"menu-spacer hideOnWidescreen\"\n        />\n        <div\n          class=\"menu-options hideOnWidescreen\"\n        >\n          <div\n            aria-label=\"Cancel\"\n            class=\"MenuOption TextOption menu-option menu-cancel\"\n            role=\"button\"\n          >\n            <div\n              class=\"d-flex\"\n            >\n              <div\n                class=\"noicon\"\n              />\n            </div>\n            <div\n              class=\"menu-option__content\"\n            >\n              <div\n                class=\"menu-name\"\n              >\n                Cancel\n              </div>\n            </div>\n            <div\n              class=\"noicon\"\n            />\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/viewHeader/__snapshots__/newCardButton.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/viewHeader/newCardButton return NewCardButton 1`] = `\n<div>\n  <div\n    class=\"ButtonWithMenu\"\n  >\n    <div\n      class=\"button-text\"\n    >\n      New\n    </div>\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper override menuOpened\"\n      role=\"button\"\n    >\n      <div\n        class=\"button-dropdown\"\n      >\n        <i\n          class=\"CompassIcon icon-chevron-down DropdownIcon\"\n        />\n      </div>\n      <div\n        class=\"Menu noselect left \"\n      >\n        <div\n          class=\"menu-contents\"\n        >\n          <div\n            class=\"menu-options\"\n          >\n            <div />\n            <div>\n              <div\n                aria-label=\"Empty card\"\n                class=\"MenuOption TextOption menu-option bold-menu-text\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <svg\n                      class=\"CardIcon Icon\"\n                      viewBox=\"0 0 100 100\"\n                      xmlns=\"http://www.w3.org/2000/svg\"\n                    >\n                      <rect\n                        height=\"40\"\n                        rx=\"3\"\n                        ry=\"3\"\n                        width=\"60\"\n                        x=\"20\"\n                        y=\"30\"\n                      />\n                    </svg>\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Empty card\n                  </div>\n                </div>\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper\"\n                  role=\"button\"\n                >\n                  <button\n                    class=\"IconButton\"\n                    type=\"button\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                    />\n                  </button>\n                </div>\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"New template\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-plus AddIcon\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    New template\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"menu-spacer hideOnWidescreen\"\n          />\n          <div\n            class=\"menu-options hideOnWidescreen\"\n          >\n            <div\n              aria-label=\"Cancel\"\n              class=\"MenuOption TextOption menu-option menu-cancel\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Cancel\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/viewHeader/newCardButton return NewCardButton and addCard 1`] = `\n<div>\n  <div\n    class=\"ButtonWithMenu\"\n  >\n    <div\n      class=\"button-text\"\n    >\n      New\n    </div>\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper override menuOpened\"\n      role=\"button\"\n    >\n      <div\n        class=\"button-dropdown\"\n      >\n        <i\n          class=\"CompassIcon icon-chevron-down DropdownIcon\"\n        />\n      </div>\n      <div\n        class=\"Menu noselect left \"\n      >\n        <div\n          class=\"menu-contents\"\n        >\n          <div\n            class=\"menu-options\"\n          >\n            <div />\n            <div>\n              <div\n                aria-label=\"Empty card\"\n                class=\"MenuOption TextOption menu-option bold-menu-text\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <svg\n                      class=\"CardIcon Icon\"\n                      viewBox=\"0 0 100 100\"\n                      xmlns=\"http://www.w3.org/2000/svg\"\n                    >\n                      <rect\n                        height=\"40\"\n                        rx=\"3\"\n                        ry=\"3\"\n                        width=\"60\"\n                        x=\"20\"\n                        y=\"30\"\n                      />\n                    </svg>\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Empty card\n                  </div>\n                </div>\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper\"\n                  role=\"button\"\n                >\n                  <button\n                    class=\"IconButton\"\n                    type=\"button\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                    />\n                  </button>\n                </div>\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"New template\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-plus AddIcon\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    New template\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"menu-spacer hideOnWidescreen\"\n          />\n          <div\n            class=\"menu-options hideOnWidescreen\"\n          >\n            <div\n              aria-label=\"Cancel\"\n              class=\"MenuOption TextOption menu-option menu-cancel\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Cancel\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/viewHeader/newCardButton return NewCardButton and addCardTemplate 1`] = `\n<div>\n  <div\n    class=\"ButtonWithMenu\"\n  >\n    <div\n      class=\"button-text\"\n    >\n      New\n    </div>\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper override menuOpened\"\n      role=\"button\"\n    >\n      <div\n        class=\"button-dropdown\"\n      >\n        <i\n          class=\"CompassIcon icon-chevron-down DropdownIcon\"\n        />\n      </div>\n      <div\n        class=\"Menu noselect left \"\n      >\n        <div\n          class=\"menu-contents\"\n        >\n          <div\n            class=\"menu-options\"\n          >\n            <div />\n            <div>\n              <div\n                aria-label=\"Empty card\"\n                class=\"MenuOption TextOption menu-option bold-menu-text\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <svg\n                      class=\"CardIcon Icon\"\n                      viewBox=\"0 0 100 100\"\n                      xmlns=\"http://www.w3.org/2000/svg\"\n                    >\n                      <rect\n                        height=\"40\"\n                        rx=\"3\"\n                        ry=\"3\"\n                        width=\"60\"\n                        x=\"20\"\n                        y=\"30\"\n                      />\n                    </svg>\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Empty card\n                  </div>\n                </div>\n                <div\n                  aria-label=\"menuwrapper\"\n                  class=\"MenuWrapper\"\n                  role=\"button\"\n                >\n                  <button\n                    class=\"IconButton\"\n                    type=\"button\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n                    />\n                  </button>\n                </div>\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"New template\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-plus AddIcon\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    New template\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"menu-spacer hideOnWidescreen\"\n          />\n          <div\n            class=\"menu-options hideOnWidescreen\"\n          >\n            <div\n              aria-label=\"Cancel\"\n              class=\"MenuOption TextOption menu-option menu-cancel\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Cancel\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/viewHeader/__snapshots__/newCardButtonTemplateItem.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/viewHeader/newCardButtonTemplateItem return NewCardButtonTemplateItem 1`] = `\n<div>\n  <div\n    aria-label=\"title\"\n    class=\"MenuOption TextOption menu-option\"\n    role=\"button\"\n  >\n    <div\n      class=\"d-flex\"\n    >\n      <div\n        class=\"menu-option__icon\"\n      >\n        <div\n          class=\"Icon\"\n        >\n          i\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"menu-option__content\"\n    >\n      <div\n        class=\"menu-name\"\n      >\n        title\n      </div>\n    </div>\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper override menuOpened\"\n      role=\"button\"\n    >\n      <button\n        class=\"IconButton\"\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n        />\n      </button>\n      <div\n        class=\"Menu noselect left \"\n      >\n        <div\n          class=\"menu-contents\"\n        >\n          <div\n            class=\"menu-options\"\n          >\n            <div>\n              <div\n                aria-label=\"Set as default\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <svg\n                      class=\"CheckIcon Icon\"\n                      viewBox=\"0 0 100 100\"\n                      xmlns=\"http://www.w3.org/2000/svg\"\n                    >\n                      <polyline\n                        points=\"20,60 40,80 80,40\"\n                      />\n                    </svg>\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Set as default\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Edit\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-pencil-outline EditIcon\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Edit\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Delete\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-trash-can-outline DeleteIcon trash-can-outline\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Delete\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"menu-spacer hideOnWidescreen\"\n          />\n          <div\n            class=\"menu-options hideOnWidescreen\"\n          >\n            <div\n              aria-label=\"Cancel\"\n              class=\"MenuOption TextOption menu-option menu-cancel\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Cancel\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/viewHeader/newCardButtonTemplateItem return NewCardButtonTemplateItem and Set as default 1`] = `\n<div>\n  <div\n    aria-label=\"title\"\n    class=\"MenuOption TextOption menu-option\"\n    role=\"button\"\n  >\n    <div\n      class=\"d-flex\"\n    >\n      <div\n        class=\"menu-option__icon\"\n      >\n        <div\n          class=\"Icon\"\n        >\n          i\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"menu-option__content\"\n    >\n      <div\n        class=\"menu-name\"\n      >\n        title\n      </div>\n    </div>\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper override menuOpened\"\n      role=\"button\"\n    >\n      <button\n        class=\"IconButton\"\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n        />\n      </button>\n      <div\n        class=\"Menu noselect left \"\n      >\n        <div\n          class=\"menu-contents\"\n        >\n          <div\n            class=\"menu-options\"\n          >\n            <div>\n              <div\n                aria-label=\"Set as default\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <svg\n                      class=\"CheckIcon Icon\"\n                      viewBox=\"0 0 100 100\"\n                      xmlns=\"http://www.w3.org/2000/svg\"\n                    >\n                      <polyline\n                        points=\"20,60 40,80 80,40\"\n                      />\n                    </svg>\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Set as default\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Edit\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-pencil-outline EditIcon\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Edit\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Delete\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-trash-can-outline DeleteIcon trash-can-outline\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Delete\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"menu-spacer hideOnWidescreen\"\n          />\n          <div\n            class=\"menu-options hideOnWidescreen\"\n          >\n            <div\n              aria-label=\"Cancel\"\n              class=\"MenuOption TextOption menu-option menu-cancel\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Cancel\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/viewHeader/newCardButtonTemplateItem return NewCardButtonTemplateItem and add Card from template 1`] = `\n<div>\n  <div\n    aria-label=\"title\"\n    class=\"MenuOption TextOption menu-option\"\n    role=\"button\"\n  >\n    <div\n      class=\"d-flex\"\n    >\n      <div\n        class=\"menu-option__icon\"\n      >\n        <div\n          class=\"Icon\"\n        >\n          i\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"menu-option__content\"\n    >\n      <div\n        class=\"menu-name\"\n      >\n        title\n      </div>\n    </div>\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper\"\n      role=\"button\"\n    >\n      <button\n        class=\"IconButton\"\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n        />\n      </button>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/viewHeader/newCardButtonTemplateItem return NewCardButtonTemplateItem and delete 1`] = `\n<div>\n  <div\n    aria-label=\"title\"\n    class=\"MenuOption TextOption menu-option\"\n    role=\"button\"\n  >\n    <div\n      class=\"d-flex\"\n    >\n      <div\n        class=\"menu-option__icon\"\n      >\n        <div\n          class=\"Icon\"\n        >\n          i\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"menu-option__content\"\n    >\n      <div\n        class=\"menu-name\"\n      >\n        title\n      </div>\n    </div>\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper override menuOpened\"\n      role=\"button\"\n    >\n      <button\n        class=\"IconButton\"\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n        />\n      </button>\n      <div\n        class=\"Menu noselect left \"\n      >\n        <div\n          class=\"menu-contents\"\n        >\n          <div\n            class=\"menu-options\"\n          >\n            <div>\n              <div\n                aria-label=\"Set as default\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <svg\n                      class=\"CheckIcon Icon\"\n                      viewBox=\"0 0 100 100\"\n                      xmlns=\"http://www.w3.org/2000/svg\"\n                    >\n                      <polyline\n                        points=\"20,60 40,80 80,40\"\n                      />\n                    </svg>\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Set as default\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Edit\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-pencil-outline EditIcon\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Edit\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Delete\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-trash-can-outline DeleteIcon trash-can-outline\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Delete\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"menu-spacer hideOnWidescreen\"\n          />\n          <div\n            class=\"menu-options hideOnWidescreen\"\n          >\n            <div\n              aria-label=\"Cancel\"\n              class=\"MenuOption TextOption menu-option menu-cancel\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Cancel\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/viewHeader/newCardButtonTemplateItem return NewCardButtonTemplateItem and edit 1`] = `\n<div>\n  <div\n    aria-label=\"title\"\n    class=\"MenuOption TextOption menu-option\"\n    role=\"button\"\n  >\n    <div\n      class=\"d-flex\"\n    >\n      <div\n        class=\"menu-option__icon\"\n      >\n        <div\n          class=\"Icon\"\n        >\n          i\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"menu-option__content\"\n    >\n      <div\n        class=\"menu-name\"\n      >\n        title\n      </div>\n    </div>\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper override menuOpened\"\n      role=\"button\"\n    >\n      <button\n        class=\"IconButton\"\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n        />\n      </button>\n      <div\n        class=\"Menu noselect left \"\n      >\n        <div\n          class=\"menu-contents\"\n        >\n          <div\n            class=\"menu-options\"\n          >\n            <div>\n              <div\n                aria-label=\"Set as default\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <svg\n                      class=\"CheckIcon Icon\"\n                      viewBox=\"0 0 100 100\"\n                      xmlns=\"http://www.w3.org/2000/svg\"\n                    >\n                      <polyline\n                        points=\"20,60 40,80 80,40\"\n                      />\n                    </svg>\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Set as default\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Edit\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-pencil-outline EditIcon\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Edit\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Delete\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"menu-option__icon\"\n                  >\n                    <i\n                      class=\"CompassIcon icon-trash-can-outline DeleteIcon trash-can-outline\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Delete\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"menu-spacer hideOnWidescreen\"\n          />\n          <div\n            class=\"menu-options hideOnWidescreen\"\n          >\n            <div\n              aria-label=\"Cancel\"\n              class=\"MenuOption TextOption menu-option menu-cancel\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Cancel\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/viewHeader/__snapshots__/viewHeader.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/viewHeader/viewHeader return viewHeader 1`] = `\n<div>\n  <div\n    class=\"ViewHeader\"\n  >\n    <div\n      class=\"viewSelector\"\n    >\n      <input\n        class=\"Editable \"\n        placeholder=\"Untitled View\"\n        spellcheck=\"true\"\n        title=\"view title\"\n        value=\"view title\"\n      />\n      <div>\n        <div\n          aria-label=\"View menu\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"IconButton\"\n            type=\"button\"\n          >\n            <i\n              class=\"CompassIcon icon-chevron-down DropdownIcon\"\n            />\n          </button>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"octo-spacer\"\n    />\n    <div\n      aria-label=\"Properties menu\"\n      class=\"MenuWrapper\"\n      role=\"button\"\n    >\n      <button\n        class=\"Button\"\n        type=\"button\"\n      >\n        <span>\n          Properties\n        </span>\n      </button>\n    </div>\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper\"\n      role=\"button\"\n    >\n      <button\n        class=\"Button\"\n        type=\"button\"\n      >\n        <span>\n          Group by: \n          <span\n            id=\"groupByLabel\"\n          >\n            Status\n          </span>\n        </span>\n      </button>\n    </div>\n    <div\n      class=\"ModalWrapper\"\n    >\n      <button\n        class=\"Button active\"\n        type=\"button\"\n      >\n        <span>\n          Filter\n        </span>\n      </button>\n    </div>\n    <div\n      aria-label=\"menuwrapper\"\n      class=\"MenuWrapper\"\n      role=\"button\"\n    >\n      <button\n        class=\"Button active\"\n        type=\"button\"\n      >\n        <span>\n          Sort\n        </span>\n      </button>\n    </div>\n    <div\n      class=\"board-search-field\"\n    >\n      <i\n        class=\"CompassIcon icon-magnify board-search-icon\"\n      />\n      <input\n        class=\"Editable \"\n        placeholder=\"Search cards\"\n        value=\"\"\n      />\n    </div>\n    <div\n      class=\"ModalWrapper\"\n    >\n      <div\n        aria-label=\"View header menu\"\n        class=\"MenuWrapper\"\n        role=\"button\"\n      >\n        <button\n          class=\"IconButton\"\n          type=\"button\"\n        >\n          <i\n            class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n          />\n        </button>\n      </div>\n    </div>\n    <div\n      class=\"ButtonWithMenu\"\n    >\n      <div\n        class=\"button-text\"\n      >\n        New\n      </div>\n      <div\n        aria-label=\"menuwrapper\"\n        class=\"MenuWrapper\"\n        role=\"button\"\n      >\n        <div\n          class=\"button-dropdown\"\n        >\n          <i\n            class=\"CompassIcon icon-chevron-down DropdownIcon\"\n          />\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/viewHeader/viewHeader return viewHeader readonly 1`] = `\n<div>\n  <div\n    class=\"ViewHeader\"\n  >\n    <div\n      class=\"viewSelector\"\n    >\n      <input\n        class=\"Editable readonly \"\n        placeholder=\"Untitled View\"\n        readonly=\"\"\n        spellcheck=\"true\"\n        title=\"view title\"\n        value=\"view title\"\n      />\n    </div>\n    <div\n      class=\"octo-spacer\"\n    />\n    <div\n      class=\"board-search-field\"\n    >\n      <i\n        class=\"CompassIcon icon-magnify board-search-icon\"\n      />\n      <input\n        class=\"Editable \"\n        placeholder=\"Search cards\"\n        value=\"\"\n      />\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/viewHeader/viewHeader return viewHeader without permissions 1`] = `\n<div>\n  <div\n    class=\"ViewHeader\"\n  >\n    <div\n      class=\"viewSelector\"\n    >\n      <input\n        class=\"Editable readonly \"\n        placeholder=\"Untitled View\"\n        readonly=\"\"\n        spellcheck=\"true\"\n        title=\"view title\"\n        value=\"view title\"\n      />\n      <div>\n        <div\n          aria-label=\"View menu\"\n          class=\"MenuWrapper\"\n          role=\"button\"\n        >\n          <button\n            class=\"IconButton\"\n            type=\"button\"\n          >\n            <i\n              class=\"CompassIcon icon-chevron-down DropdownIcon\"\n            />\n          </button>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"octo-spacer\"\n    />\n    <div\n      class=\"board-search-field\"\n    >\n      <i\n        class=\"CompassIcon icon-magnify board-search-icon\"\n      />\n      <input\n        class=\"Editable \"\n        placeholder=\"Search cards\"\n        value=\"\"\n      />\n    </div>\n    <div\n      class=\"ModalWrapper\"\n    >\n      <div\n        aria-label=\"View header menu\"\n        class=\"MenuWrapper\"\n        role=\"button\"\n      >\n        <button\n          class=\"IconButton\"\n          type=\"button\"\n        >\n          <i\n            class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n          />\n        </button>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/viewHeader/__snapshots__/viewHeaderActionsMenu.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/viewHeader/viewHeaderActionsMenu return menu 1`] = `\n<div>\n  <div\n    class=\"ModalWrapper\"\n  >\n    <div\n      aria-label=\"View header menu\"\n      class=\"MenuWrapper override menuOpened\"\n      role=\"button\"\n    >\n      <button\n        class=\"IconButton\"\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n        />\n      </button>\n      <div\n        class=\"Menu noselect left \"\n      >\n        <div\n          class=\"menu-contents\"\n        >\n          <div\n            class=\"menu-options\"\n          >\n            <div>\n              <div\n                aria-label=\"Export to CSV\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Export to CSV\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Export board archive\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Export board archive\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"menu-spacer hideOnWidescreen\"\n          />\n          <div\n            class=\"menu-options hideOnWidescreen\"\n          >\n            <div\n              aria-label=\"Cancel\"\n              class=\"MenuOption TextOption menu-option menu-cancel\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Cancel\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/viewHeader/viewHeaderActionsMenu return menu and verify call to board archive 1`] = `\n<div>\n  <div\n    class=\"ModalWrapper\"\n  >\n    <div\n      aria-label=\"View header menu\"\n      class=\"MenuWrapper override menuOpened\"\n      role=\"button\"\n    >\n      <button\n        class=\"IconButton\"\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n        />\n      </button>\n      <div\n        class=\"Menu noselect left \"\n      >\n        <div\n          class=\"menu-contents\"\n        >\n          <div\n            class=\"menu-options\"\n          >\n            <div>\n              <div\n                aria-label=\"Export to CSV\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Export to CSV\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Export board archive\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Export board archive\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"menu-spacer hideOnWidescreen\"\n          />\n          <div\n            class=\"menu-options hideOnWidescreen\"\n          >\n            <div\n              aria-label=\"Cancel\"\n              class=\"MenuOption TextOption menu-option menu-cancel\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Cancel\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/viewHeader/viewHeaderActionsMenu return menu and verify call to csv exporter 1`] = `\n<div>\n  <div\n    class=\"ModalWrapper\"\n  >\n    <div\n      aria-label=\"View header menu\"\n      class=\"MenuWrapper override menuOpened\"\n      role=\"button\"\n    >\n      <button\n        class=\"IconButton\"\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-dots-horizontal OptionsIcon\"\n        />\n      </button>\n      <div\n        class=\"Menu noselect left \"\n      >\n        <div\n          class=\"menu-contents\"\n        >\n          <div\n            class=\"menu-options\"\n          >\n            <div>\n              <div\n                aria-label=\"Export to CSV\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Export to CSV\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n            <div>\n              <div\n                aria-label=\"Export board archive\"\n                class=\"MenuOption TextOption menu-option\"\n                role=\"button\"\n              >\n                <div\n                  class=\"d-flex\"\n                >\n                  <div\n                    class=\"noicon\"\n                  />\n                </div>\n                <div\n                  class=\"menu-option__content\"\n                >\n                  <div\n                    class=\"menu-name\"\n                  >\n                    Export board archive\n                  </div>\n                </div>\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n            </div>\n          </div>\n          <div\n            class=\"menu-spacer hideOnWidescreen\"\n          />\n          <div\n            class=\"menu-options hideOnWidescreen\"\n          >\n            <div\n              aria-label=\"Cancel\"\n              class=\"MenuOption TextOption menu-option menu-cancel\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Cancel\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/viewHeader/__snapshots__/viewHeaderGroupByMenu.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/viewHeader/viewHeaderGroupByMenu For viewType table render only HideEmptyGroupsButton when hiddenGroups is empty 1`] = `\n<div>\n  <div\n    aria-label=\"menuwrapper\"\n    class=\"MenuWrapper override menuOpened\"\n    role=\"button\"\n  >\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        Group by: \n        <span\n          id=\"groupByLabel\"\n        >\n          Status\n        </span>\n      </span>\n    </button>\n    <div\n      class=\"Menu noselect bottom \"\n    >\n      <div\n        class=\"menu-contents\"\n      >\n        <div\n          class=\"menu-options\"\n        >\n          <div>\n            <div\n              aria-label=\"Hide 1 empty groups\"\n              class=\"MenuOption TextOption menu-option\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Hide 1 empty groups\n                </div>\n              </div>\n              <svg\n                class=\"HideIcon Icon\"\n                viewBox=\"0 0 640 512\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n              >\n                <path\n                  d=\"M634 471L36 3.51A16 16 0 0 0 13.51 6l-10 12.49A16 16 0 0 0 6 41l598 467.49a16 16 0 0 0 22.49-2.49l10-12.49A16 16 0 0 0 634 471zM296.79 146.47l134.79 105.38C429.36 191.91 380.48 144 320 144a112.26 112.26 0 0 0-23.21 2.47zm46.42 219.07L208.42 260.16C210.65 320.09 259.53 368 320 368a113 113 0 0 0 23.21-2.46zM320 112c98.65 0 189.09 55 237.93 144a285.53 285.53 0 0 1-44 60.2l37.74 29.5a333.7 333.7 0 0 0 52.9-75.11 32.35 32.35 0 0 0 0-29.19C550.29 135.59 442.93 64 320 64c-36.7 0-71.71 7-104.63 18.81l46.41 36.29c18.94-4.3 38.34-7.1 58.22-7.1zm0 288c-98.65 0-189.08-55-237.93-144a285.47 285.47 0 0 1 44.05-60.19l-37.74-29.5a333.6 333.6 0 0 0-52.89 75.1 32.35 32.35 0 0 0 0 29.19C89.72 376.41 197.08 448 320 448c36.7 0 71.71-7.05 104.63-18.81l-46.41-36.28C359.28 397.2 339.89 400 320 400z\"\n                />\n              </svg>\n            </div>\n            <div\n              aria-label=\"Ungroup\"\n              class=\"MenuOption TextOption menu-option\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Ungroup\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n            <div\n              class=\"MenuOption MenuSeparator menu-separator\"\n            />\n          </div>\n          <div>\n            <div\n              aria-label=\"Status\"\n              class=\"MenuOption TextOption menu-option\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Status\n                </div>\n              </div>\n              <svg\n                class=\"CheckIcon Icon\"\n                viewBox=\"0 0 100 100\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n              >\n                <polyline\n                  points=\"20,60 40,80 80,40\"\n                />\n              </svg>\n            </div>\n          </div>\n          <div>\n            <div\n              aria-label=\"Property 1\"\n              class=\"MenuOption TextOption menu-option\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Property 1\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n          <div>\n            <div\n              aria-label=\"Property 2\"\n              class=\"MenuOption TextOption menu-option\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Property 2\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n          <div>\n            <div\n              aria-label=\"Property 3\"\n              class=\"MenuOption TextOption menu-option\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Property 3\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"menu-spacer hideOnWidescreen\"\n        />\n        <div\n          class=\"menu-options hideOnWidescreen\"\n        >\n          <div\n            aria-label=\"Cancel\"\n            class=\"MenuOption TextOption menu-option menu-cancel\"\n            role=\"button\"\n          >\n            <div\n              class=\"d-flex\"\n            >\n              <div\n                class=\"noicon\"\n              />\n            </div>\n            <div\n              class=\"menu-option__content\"\n            >\n              <div\n                class=\"menu-name\"\n              >\n                Cancel\n              </div>\n            </div>\n            <div\n              class=\"noicon\"\n            />\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/viewHeader/viewHeaderGroupByMenu For viewType table render only ShowHiddenGroupsButton when there are no emptyGroups 1`] = `\n<div>\n  <div\n    aria-label=\"menuwrapper\"\n    class=\"MenuWrapper override menuOpened\"\n    role=\"button\"\n  >\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        Group by: \n        <span\n          id=\"groupByLabel\"\n        >\n          Status\n        </span>\n      </span>\n    </button>\n    <div\n      class=\"Menu noselect bottom \"\n    >\n      <div\n        class=\"menu-contents\"\n      >\n        <div\n          class=\"menu-options\"\n        >\n          <div>\n            <div\n              aria-label=\"Show 1 hidden groups\"\n              class=\"MenuOption TextOption menu-option\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Show 1 hidden groups\n                </div>\n              </div>\n              <svg\n                class=\"ShowIcon Icon\"\n                viewBox=\"0 0 576 512\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n              >\n                <path\n                  d=\"M288 144a110.94 110.94 0 0 0-31.24 5 55.4 55.4 0 0 1 7.24 27 56 56 0 0 1-56 56 55.4 55.4 0 0 1-27-7.24A111.71 111.71 0 1 0 288 144zm284.52 97.4C518.29 135.59 410.93 64 288 64S57.68 135.64 3.48 241.41a32.35 32.35 0 0 0 0 29.19C57.71 376.41 165.07 448 288 448s230.32-71.64 284.52-177.41a32.35 32.35 0 0 0 0-29.19zM288 400c-98.65 0-189.09-55-237.93-144C98.91 167 189.34 112 288 112s189.09 55 237.93 144C477.1 345 386.66 400 288 400z\"\n                />\n              </svg>\n            </div>\n            <div\n              aria-label=\"Ungroup\"\n              class=\"MenuOption TextOption menu-option\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Ungroup\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n            <div\n              class=\"MenuOption MenuSeparator menu-separator\"\n            />\n          </div>\n          <div>\n            <div\n              aria-label=\"Status\"\n              class=\"MenuOption TextOption menu-option\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Status\n                </div>\n              </div>\n              <svg\n                class=\"CheckIcon Icon\"\n                viewBox=\"0 0 100 100\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n              >\n                <polyline\n                  points=\"20,60 40,80 80,40\"\n                />\n              </svg>\n            </div>\n          </div>\n          <div>\n            <div\n              aria-label=\"Property 1\"\n              class=\"MenuOption TextOption menu-option\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Property 1\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n          <div>\n            <div\n              aria-label=\"Property 2\"\n              class=\"MenuOption TextOption menu-option\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Property 2\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n          <div>\n            <div\n              aria-label=\"Property 3\"\n              class=\"MenuOption TextOption menu-option\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Property 3\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"menu-spacer hideOnWidescreen\"\n        />\n        <div\n          class=\"menu-options hideOnWidescreen\"\n        >\n          <div\n            aria-label=\"Cancel\"\n            class=\"MenuOption TextOption menu-option menu-cancel\"\n            role=\"button\"\n          >\n            <div\n              class=\"d-flex\"\n            >\n              <div\n                class=\"noicon\"\n              />\n            </div>\n            <div\n              class=\"menu-option__content\"\n            >\n              <div\n                class=\"menu-name\"\n              >\n                Cancel\n              </div>\n            </div>\n            <div\n              class=\"noicon\"\n            />\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/viewHeader/viewHeaderGroupByMenu return groupBy menu 1`] = `\n<div>\n  <div\n    aria-label=\"menuwrapper\"\n    class=\"MenuWrapper override menuOpened\"\n    role=\"button\"\n  >\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        Group by: \n        <span\n          id=\"groupByLabel\"\n        >\n          Status\n        </span>\n      </span>\n    </button>\n    <div\n      class=\"Menu noselect bottom \"\n    >\n      <div\n        class=\"menu-contents\"\n      >\n        <div\n          class=\"menu-options\"\n        >\n          <div />\n          <div>\n            <div\n              aria-label=\"Status\"\n              class=\"MenuOption TextOption menu-option\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Status\n                </div>\n              </div>\n              <svg\n                class=\"CheckIcon Icon\"\n                viewBox=\"0 0 100 100\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n              >\n                <polyline\n                  points=\"20,60 40,80 80,40\"\n                />\n              </svg>\n            </div>\n          </div>\n          <div>\n            <div\n              aria-label=\"Property 1\"\n              class=\"MenuOption TextOption menu-option\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Property 1\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n          <div>\n            <div\n              aria-label=\"Property 2\"\n              class=\"MenuOption TextOption menu-option\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Property 2\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n          <div>\n            <div\n              aria-label=\"Property 3\"\n              class=\"MenuOption TextOption menu-option\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Property 3\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"menu-spacer hideOnWidescreen\"\n        />\n        <div\n          class=\"menu-options hideOnWidescreen\"\n        >\n          <div\n            aria-label=\"Cancel\"\n            class=\"MenuOption TextOption menu-option menu-cancel\"\n            role=\"button\"\n          >\n            <div\n              class=\"d-flex\"\n            >\n              <div\n                class=\"noicon\"\n              />\n            </div>\n            <div\n              class=\"menu-option__content\"\n            >\n              <div\n                class=\"menu-name\"\n              >\n                Cancel\n              </div>\n            </div>\n            <div\n              class=\"noicon\"\n            />\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/viewHeader/viewHeaderGroupByMenu return groupBy menu and groupBy Status 1`] = `\n<div>\n  <div\n    aria-label=\"menuwrapper\"\n    class=\"MenuWrapper\"\n    role=\"button\"\n  >\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        Group by: \n        <span\n          id=\"groupByLabel\"\n        >\n          Status\n        </span>\n      </span>\n    </button>\n  </div>\n</div>\n`;\n\nexports[`components/viewHeader/viewHeaderGroupByMenu return groupBy menu, hideEmptyGroups and ungroup in viewType table 1`] = `\n<div>\n  <div\n    aria-label=\"menuwrapper\"\n    class=\"MenuWrapper override menuOpened\"\n    role=\"button\"\n  >\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        Group by: \n        <span\n          id=\"groupByLabel\"\n        >\n          Status\n        </span>\n      </span>\n    </button>\n    <div\n      class=\"Menu noselect bottom \"\n    >\n      <div\n        class=\"menu-contents\"\n      >\n        <div\n          class=\"menu-options\"\n        >\n          <div>\n            <div\n              aria-label=\"Hide 1 empty groups\"\n              class=\"MenuOption TextOption menu-option\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Hide 1 empty groups\n                </div>\n              </div>\n              <svg\n                class=\"HideIcon Icon\"\n                viewBox=\"0 0 640 512\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n              >\n                <path\n                  d=\"M634 471L36 3.51A16 16 0 0 0 13.51 6l-10 12.49A16 16 0 0 0 6 41l598 467.49a16 16 0 0 0 22.49-2.49l10-12.49A16 16 0 0 0 634 471zM296.79 146.47l134.79 105.38C429.36 191.91 380.48 144 320 144a112.26 112.26 0 0 0-23.21 2.47zm46.42 219.07L208.42 260.16C210.65 320.09 259.53 368 320 368a113 113 0 0 0 23.21-2.46zM320 112c98.65 0 189.09 55 237.93 144a285.53 285.53 0 0 1-44 60.2l37.74 29.5a333.7 333.7 0 0 0 52.9-75.11 32.35 32.35 0 0 0 0-29.19C550.29 135.59 442.93 64 320 64c-36.7 0-71.71 7-104.63 18.81l46.41 36.29c18.94-4.3 38.34-7.1 58.22-7.1zm0 288c-98.65 0-189.08-55-237.93-144a285.47 285.47 0 0 1 44.05-60.19l-37.74-29.5a333.6 333.6 0 0 0-52.89 75.1 32.35 32.35 0 0 0 0 29.19C89.72 376.41 197.08 448 320 448c36.7 0 71.71-7.05 104.63-18.81l-46.41-36.28C359.28 397.2 339.89 400 320 400z\"\n                />\n              </svg>\n            </div>\n            <div\n              aria-label=\"Show 1 hidden groups\"\n              class=\"MenuOption TextOption menu-option\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Show 1 hidden groups\n                </div>\n              </div>\n              <svg\n                class=\"ShowIcon Icon\"\n                viewBox=\"0 0 576 512\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n              >\n                <path\n                  d=\"M288 144a110.94 110.94 0 0 0-31.24 5 55.4 55.4 0 0 1 7.24 27 56 56 0 0 1-56 56 55.4 55.4 0 0 1-27-7.24A111.71 111.71 0 1 0 288 144zm284.52 97.4C518.29 135.59 410.93 64 288 64S57.68 135.64 3.48 241.41a32.35 32.35 0 0 0 0 29.19C57.71 376.41 165.07 448 288 448s230.32-71.64 284.52-177.41a32.35 32.35 0 0 0 0-29.19zM288 400c-98.65 0-189.09-55-237.93-144C98.91 167 189.34 112 288 112s189.09 55 237.93 144C477.1 345 386.66 400 288 400z\"\n                />\n              </svg>\n            </div>\n            <div\n              aria-label=\"Ungroup\"\n              class=\"MenuOption TextOption menu-option\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Ungroup\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n            <div\n              class=\"MenuOption MenuSeparator menu-separator\"\n            />\n          </div>\n          <div>\n            <div\n              aria-label=\"Status\"\n              class=\"MenuOption TextOption menu-option\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Status\n                </div>\n              </div>\n              <svg\n                class=\"CheckIcon Icon\"\n                viewBox=\"0 0 100 100\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n              >\n                <polyline\n                  points=\"20,60 40,80 80,40\"\n                />\n              </svg>\n            </div>\n          </div>\n          <div>\n            <div\n              aria-label=\"Property 1\"\n              class=\"MenuOption TextOption menu-option\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Property 1\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n          <div>\n            <div\n              aria-label=\"Property 2\"\n              class=\"MenuOption TextOption menu-option\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Property 2\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n          <div>\n            <div\n              aria-label=\"Property 3\"\n              class=\"MenuOption TextOption menu-option\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Property 3\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"menu-spacer hideOnWidescreen\"\n        />\n        <div\n          class=\"menu-options hideOnWidescreen\"\n        >\n          <div\n            aria-label=\"Cancel\"\n            class=\"MenuOption TextOption menu-option menu-cancel\"\n            role=\"button\"\n          >\n            <div\n              class=\"d-flex\"\n            >\n              <div\n                class=\"noicon\"\n              />\n            </div>\n            <div\n              class=\"menu-option__content\"\n            >\n              <div\n                class=\"menu-name\"\n              >\n                Cancel\n              </div>\n            </div>\n            <div\n              class=\"noicon\"\n            />\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/viewHeader/__snapshots__/viewHeaderPropertiesMenu.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/viewHeader/viewHeaderPropertiesMenu return properties menu 1`] = `\n<div>\n  <div\n    aria-label=\"Properties menu\"\n    class=\"MenuWrapper override menuOpened\"\n    role=\"button\"\n  >\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        Properties\n      </span>\n    </button>\n    <div\n      class=\"Menu noselect bottom \"\n    >\n      <div\n        class=\"menu-contents\"\n      >\n        <div\n          class=\"menu-options\"\n        >\n          <div />\n          <div>\n            <div\n              aria-label=\"Status\"\n              class=\"MenuOption SwitchOption menu-option\"\n              role=\"button\"\n            >\n              <div\n                class=\"noicon\"\n              />\n              <div\n                class=\"menu-name\"\n              >\n                Status\n              </div>\n              <div\n                class=\"Switch override-main size--small\"\n              >\n                <div\n                  class=\"octo-switch-inner\"\n                />\n              </div>\n            </div>\n          </div>\n          <div>\n            <div\n              aria-label=\"Property 1\"\n              class=\"MenuOption SwitchOption menu-option\"\n              role=\"button\"\n            >\n              <div\n                class=\"noicon\"\n              />\n              <div\n                class=\"menu-name\"\n              >\n                Property 1\n              </div>\n              <div\n                class=\"Switch override-main size--small\"\n              >\n                <div\n                  class=\"octo-switch-inner\"\n                />\n              </div>\n            </div>\n          </div>\n          <div>\n            <div\n              aria-label=\"Property 2\"\n              class=\"MenuOption SwitchOption menu-option\"\n              role=\"button\"\n            >\n              <div\n                class=\"noicon\"\n              />\n              <div\n                class=\"menu-name\"\n              >\n                Property 2\n              </div>\n              <div\n                class=\"Switch override-main size--small\"\n              >\n                <div\n                  class=\"octo-switch-inner\"\n                />\n              </div>\n            </div>\n          </div>\n          <div>\n            <div\n              aria-label=\"Property 3\"\n              class=\"MenuOption SwitchOption menu-option\"\n              role=\"button\"\n            >\n              <div\n                class=\"noicon\"\n              />\n              <div\n                class=\"menu-name\"\n              >\n                Property 3\n              </div>\n              <div\n                class=\"Switch override-main size--small\"\n              >\n                <div\n                  class=\"octo-switch-inner\"\n                />\n              </div>\n            </div>\n          </div>\n          <div>\n            <div\n              aria-label=\"Comments and description\"\n              class=\"MenuOption SwitchOption menu-option\"\n              role=\"button\"\n            >\n              <div\n                class=\"noicon\"\n              />\n              <div\n                class=\"menu-name\"\n              >\n                Comments and description\n              </div>\n              <div\n                class=\"Switch override-main size--small\"\n              >\n                <div\n                  class=\"octo-switch-inner\"\n                />\n              </div>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"menu-spacer hideOnWidescreen\"\n        />\n        <div\n          class=\"menu-options hideOnWidescreen\"\n        >\n          <div\n            aria-label=\"Cancel\"\n            class=\"MenuOption TextOption menu-option menu-cancel\"\n            role=\"button\"\n          >\n            <div\n              class=\"d-flex\"\n            >\n              <div\n                class=\"noicon\"\n              />\n            </div>\n            <div\n              class=\"menu-option__content\"\n            >\n              <div\n                class=\"menu-name\"\n              >\n                Cancel\n              </div>\n            </div>\n            <div\n              class=\"noicon\"\n            />\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/viewHeader/viewHeaderPropertiesMenu return properties menu with gallery typeview 1`] = `\n<div>\n  <div\n    aria-label=\"Properties menu\"\n    class=\"MenuWrapper override menuOpened\"\n    role=\"button\"\n  >\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        Properties\n      </span>\n    </button>\n    <div\n      class=\"Menu noselect bottom \"\n    >\n      <div\n        class=\"menu-contents\"\n      >\n        <div\n          class=\"menu-options\"\n        >\n          <div>\n            <div\n              aria-label=\"Title\"\n              class=\"MenuOption SwitchOption menu-option\"\n              role=\"button\"\n            >\n              <div\n                class=\"noicon\"\n              />\n              <div\n                class=\"menu-name\"\n              >\n                Title\n              </div>\n              <div\n                class=\"Switch override-main size--small\"\n              >\n                <div\n                  class=\"octo-switch-inner\"\n                />\n              </div>\n            </div>\n          </div>\n          <div>\n            <div\n              aria-label=\"Status\"\n              class=\"MenuOption SwitchOption menu-option\"\n              role=\"button\"\n            >\n              <div\n                class=\"noicon\"\n              />\n              <div\n                class=\"menu-name\"\n              >\n                Status\n              </div>\n              <div\n                class=\"Switch override-main size--small\"\n              >\n                <div\n                  class=\"octo-switch-inner\"\n                />\n              </div>\n            </div>\n          </div>\n          <div>\n            <div\n              aria-label=\"Property 1\"\n              class=\"MenuOption SwitchOption menu-option\"\n              role=\"button\"\n            >\n              <div\n                class=\"noicon\"\n              />\n              <div\n                class=\"menu-name\"\n              >\n                Property 1\n              </div>\n              <div\n                class=\"Switch override-main size--small\"\n              >\n                <div\n                  class=\"octo-switch-inner\"\n                />\n              </div>\n            </div>\n          </div>\n          <div>\n            <div\n              aria-label=\"Property 2\"\n              class=\"MenuOption SwitchOption menu-option\"\n              role=\"button\"\n            >\n              <div\n                class=\"noicon\"\n              />\n              <div\n                class=\"menu-name\"\n              >\n                Property 2\n              </div>\n              <div\n                class=\"Switch override-main size--small\"\n              >\n                <div\n                  class=\"octo-switch-inner\"\n                />\n              </div>\n            </div>\n          </div>\n          <div>\n            <div\n              aria-label=\"Property 3\"\n              class=\"MenuOption SwitchOption menu-option\"\n              role=\"button\"\n            >\n              <div\n                class=\"noicon\"\n              />\n              <div\n                class=\"menu-name\"\n              >\n                Property 3\n              </div>\n              <div\n                class=\"Switch override-main size--small\"\n              >\n                <div\n                  class=\"octo-switch-inner\"\n                />\n              </div>\n            </div>\n          </div>\n          <div>\n            <div\n              aria-label=\"Comments and description\"\n              class=\"MenuOption SwitchOption menu-option\"\n              role=\"button\"\n            >\n              <div\n                class=\"noicon\"\n              />\n              <div\n                class=\"menu-name\"\n              >\n                Comments and description\n              </div>\n              <div\n                class=\"Switch override-main size--small\"\n              >\n                <div\n                  class=\"octo-switch-inner\"\n                />\n              </div>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"menu-spacer hideOnWidescreen\"\n        />\n        <div\n          class=\"menu-options hideOnWidescreen\"\n        >\n          <div\n            aria-label=\"Cancel\"\n            class=\"MenuOption TextOption menu-option menu-cancel\"\n            role=\"button\"\n          >\n            <div\n              class=\"d-flex\"\n            >\n              <div\n                class=\"noicon\"\n              />\n            </div>\n            <div\n              class=\"menu-option__content\"\n            >\n              <div\n                class=\"menu-name\"\n              >\n                Cancel\n              </div>\n            </div>\n            <div\n              class=\"noicon\"\n            />\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/viewHeader/__snapshots__/viewHeaderSearch.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/viewHeader/ViewHeaderSearch return search menu 1`] = `\n<div>\n  <div\n    class=\"board-search-field\"\n  >\n    <i\n      class=\"CompassIcon icon-magnify board-search-icon\"\n    />\n    <input\n      class=\"Editable \"\n      placeholder=\"Search cards\"\n      value=\"\"\n    />\n  </div>\n</div>\n`;\n\nexports[`components/viewHeader/ViewHeaderSearch search text after input 1`] = `\n<div>\n  <div\n    class=\"board-search-field\"\n  >\n    <i\n      class=\"CompassIcon icon-magnify board-search-icon\"\n    />\n    <input\n      class=\"Editable \"\n      placeholder=\"Search cards\"\n      title=\"Hello\"\n      value=\"Hello\"\n    />\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/viewHeader/__snapshots__/viewHeaderSortMenu.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`components/viewHeader/viewHeaderSortMenu return sort menu 1`] = `\n<div>\n  <div\n    aria-label=\"menuwrapper\"\n    class=\"MenuWrapper override menuOpened\"\n    role=\"button\"\n  >\n    <button\n      class=\"Button active\"\n      type=\"button\"\n    >\n      <span>\n        Sort\n      </span>\n    </button>\n    <div\n      class=\"Menu noselect bottom \"\n    >\n      <div\n        class=\"menu-contents\"\n      >\n        <div\n          class=\"menu-options\"\n        >\n          <div>\n            <div\n              aria-label=\"Manual\"\n              class=\"MenuOption TextOption menu-option\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Manual\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n            <div\n              aria-label=\"Revert\"\n              class=\"MenuOption TextOption menu-option\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Revert\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n            <div\n              class=\"MenuOption MenuSeparator menu-separator\"\n            />\n          </div>\n          <div>\n            <div\n              aria-label=\"Name\"\n              class=\"MenuOption TextOption menu-option\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Name\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n          <div>\n            <div\n              aria-label=\"Status\"\n              class=\"MenuOption TextOption menu-option\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Status\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n          <div>\n            <div\n              aria-label=\"Property 1\"\n              class=\"MenuOption TextOption menu-option\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Property 1\n                </div>\n              </div>\n              <svg\n                class=\"SortDownIcon Icon\"\n                viewBox=\"0 0 100 100\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n              >\n                <polyline\n                  points=\"50,20 50,80\"\n                />\n                <polyline\n                  points=\"30,60 50,80 70,60\"\n                />\n              </svg>\n            </div>\n          </div>\n          <div>\n            <div\n              aria-label=\"Property 2\"\n              class=\"MenuOption TextOption menu-option\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Property 2\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n          <div>\n            <div\n              aria-label=\"Property 3\"\n              class=\"MenuOption TextOption menu-option\"\n              role=\"button\"\n            >\n              <div\n                class=\"d-flex\"\n              >\n                <div\n                  class=\"noicon\"\n                />\n              </div>\n              <div\n                class=\"menu-option__content\"\n              >\n                <div\n                  class=\"menu-name\"\n                >\n                  Property 3\n                </div>\n              </div>\n              <div\n                class=\"noicon\"\n              />\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"menu-spacer hideOnWidescreen\"\n        />\n        <div\n          class=\"menu-options hideOnWidescreen\"\n        >\n          <div\n            aria-label=\"Cancel\"\n            class=\"MenuOption TextOption menu-option menu-cancel\"\n            role=\"button\"\n          >\n            <div\n              class=\"d-flex\"\n            >\n              <div\n                class=\"noicon\"\n              />\n            </div>\n            <div\n              class=\"menu-option__content\"\n            >\n              <div\n                class=\"menu-name\"\n              >\n                Cancel\n              </div>\n            </div>\n            <div\n              class=\"noicon\"\n            />\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`components/viewHeader/viewHeaderSortMenu return sort menu and do Name sort 1`] = `\n<div>\n  <div\n    aria-label=\"menuwrapper\"\n    class=\"MenuWrapper\"\n    role=\"button\"\n  >\n    <button\n      class=\"Button active\"\n      type=\"button\"\n    >\n      <span>\n        Sort\n      </span>\n    </button>\n  </div>\n</div>\n`;\n\nexports[`components/viewHeader/viewHeaderSortMenu return sort menu and do manual 1`] = `\n<div>\n  <div\n    aria-label=\"menuwrapper\"\n    class=\"MenuWrapper\"\n    role=\"button\"\n  >\n    <button\n      class=\"Button active\"\n      type=\"button\"\n    >\n      <span>\n        Sort\n      </span>\n    </button>\n  </div>\n</div>\n`;\n\nexports[`components/viewHeader/viewHeaderSortMenu return sort menu and do revert 1`] = `\n<div>\n  <div\n    aria-label=\"menuwrapper\"\n    class=\"MenuWrapper\"\n    role=\"button\"\n  >\n    <button\n      class=\"Button active\"\n      type=\"button\"\n    >\n      <span>\n        Sort\n      </span>\n    </button>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/components/viewHeader/dateFilter.scss",
    "content": ".DateFilter {\n    .inputContainer {\n        display: flex;\n\n        .Editable {\n            width: 50% !important;\n        }\n    }\n\n    input {\n        cursor: text;\n        overflow: hidden;\n        text-overflow: ellipsis;\n        min-height: 24px;\n        width: 100%;\n        margin: 2px;\n\n        &.active {\n            min-width: 100px;\n        }\n\n        &::placeholder {\n            color: rgba(var(--body-color), 0.4);\n            opacity: 1;\n        }\n\n        &.error {\n            border: 1px solid var(--error-color);\n            border-radius: var(--default-rad);\n        }\n    }\n\n    .Modal {\n        position: absolute;\n        top: 0;\n        left: 0;\n        margin-bottom: 100px;\n    }\n\n    &.empty .Button {\n        min-height: 24px;\n        color: rgba(var(--center-channel-color-rgb), 0.4);\n        padding: 0 3px;\n    }\n\n    .Button {\n        // width: calc(100% - 16px);\n        height: 100%;\n        justify-content: left;\n        padding: 0;\n\n        &.--empty {\n            opacity: 0;\n        }\n\n        &:hover {\n            background-color: transparent;\n        }\n    }\n\n    .menu-option {\n        display: flex;\n        flex-direction: row;\n        align-items: center;\n\n        font-size: 14px;\n        line-height: 24px;\n        font-weight: 400;\n        height: 32px;\n        padding: 4px 0;\n        cursor: pointer;\n\n        &:hover {\n            background: rgba(var(--button-bg-rgb), 0.08);\n        }\n\n        >* {\n            margin-left: 6px;\n        }\n\n        >*:first-child {\n            margin-left: 0;\n        }\n\n        >.menu-name {\n            display: flex;\n            flex-grow: 1;\n            white-space: nowrap;\n        }\n\n        >.SubmenuTriangleIcon {\n            fill: rgba(var(--body-color), 0.7);\n        }\n\n        >.Icon {\n            opacity: 0.56;\n            width: 16px;\n            height: 16px;\n            line-height: 16px;\n        }\n\n        >.IconButton .Icon {\n            margin-right: 0;\n        }\n    }\n\n    .DayPicker {\n        font-size: inherit;\n    }\n\n    .DayPickerInput-Overlay {\n        background-color: rgba(var(--center-channel-bg-rgb));\n        box-shadow: 0 0 0 1px hsla(0, 0%, 0%, 0.1), 0 4px 11px hsla(0, 0%, 0%, 0.1);\n        line-height: 100%;\n    }\n\n    .DayPicker-wrapper {\n        padding: 0;\n    }\n\n    .DayPicker-Month {\n        margin: 0;\n        margin-top: 1em;\n        display: flex;\n        flex-direction: column;\n    }\n\n    .DayPicker-Body {\n        display: flex;\n        flex-direction: column;\n    }\n\n    .inputContainer {\n        max-width: 252px;\n    }\n\n    .DayPicker-Weekdays {\n        margin: 0;\n    }\n\n    .DayPicker-WeekdaysRow {\n        display: flex;\n        flex-direction: row;\n    }\n\n    .DayPicker-Weekday {\n        width: 36px;\n        height: 36px;\n    }\n\n    .DayPicker-NavButton {\n        right: 0;\n    }\n\n    .DayPicker-Week {\n        display: flex;\n        flex-direction: row;\n    }\n\n    .DayPicker-Day {\n        display: flex;\n        align-items: center;\n        justify-content: center;\n        padding: 0;\n        width: 36px;\n        height: 36px;\n        border-radius: 50px;\n\n        &::before {\n            content: unset;\n        }\n\n        &:not(.DayPicker-Day--selected) {\n            &:hover {\n                background: rgba(var(--button-bg-rgb), 0.08) !important;\n                color: rgba(var(--button-bg-rgb), 1) !important;\n            }\n        }\n    }\n\n    .DayPicker-Day--today {\n        background: transparent;\n        color: #c74655;\n    }\n\n    .DayPicker-Day--start {\n        border-top-left-radius: 50% !important;\n        border-bottom-left-radius: 50% !important;\n        border-top-right-radius: unset;\n        border-bottom-right-radius: unset;\n    }\n\n    .DayPicker-Day--end {\n        border-top-left-radius: unset;\n        border-bottom-left-radius: unset;\n        border-top-right-radius: 50% !important;\n        border-bottom-right-radius: 50% !important;\n    }\n\n    .DayPicker-Day--selected:not(.DayPicker-Day--disabled):not(.DayPicker-Day--outside) {\n        border-radius: unset;\n        background-color: rgb(var(--button-bg-rgb));\n        color: rgba(var(--button-color-rgb), 1);\n\n        &:hover {\n            background-color: rgb(var(--button-bg-rgb)) !important;\n        }\n    }\n\n    .DayPicker-Day--selected:not(.DayPicker-Day--start):not(.DayPicker-Day--end):not(.DayPicker-Day--outside) {\n        color: rgb(var(--button-bg-rgb));\n        background-color: rgba(var(--button-bg-rgb), 0.08);\n\n        &:hover {\n            background-color: rgba(var(--button-bg-rgb), 0.08) !important;\n        }\n    }\n\n    .DayPicker:not(.DayPicker--interactionDisabled) .DayPicker-Day:not(.DayPicker-Day--disabled):not(.DayPicker-Day--selected):not(.DayPicker-Day--outside):hover {\n        background-color: rgba(var(--body-color), 0.2);\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/viewHeader/dateFilter.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {render} from '@testing-library/react'\nimport userEvent from '@testing-library/user-event'\nimport {IntlProvider} from 'react-intl'\nimport {mocked} from 'jest-mock'\n\nimport '@testing-library/jest-dom'\n\nimport {wrapIntl} from '../../testUtils'\nimport mutator from '../../mutator'\nimport {TestBlockFactory} from '../../test/testBlockFactory'\n\nimport {createFilterClause, FilterClause} from '../../blocks/filterClause'\nimport {createFilterGroup} from '../../blocks/filterGroup'\n\nimport DateFilter from './dateFilter'\n\njest.mock('../../mutator')\nconst mockedMutator = mocked(mutator, true)\n\n// create Dates for specific days for this year.\nconst June15 = new Date(Date.UTC(new Date().getFullYear(), 5, 15, 12))\n\ndescribe('components/viewHeader/dateFilter', () => {\n    const emptyFilterClause = createFilterClause({\n        propertyId: 'myPropertyId',\n        condition: 'is',\n        values: [],\n    })\n    const board = TestBlockFactory.createBoard()\n    board.id = 'testBoardID'\n\n    const activeView = TestBlockFactory.createBoardView(board)\n    const dateFixed = Date.UTC(2022, 11, 28, 12) //Date.parse('28 Dec 2022')\n    activeView.createAt = dateFixed\n    activeView.updateAt = dateFixed\n\n    activeView.id = 'testViewID'\n    activeView.fields.filter = createFilterGroup()\n    activeView.fields.filter.filters = [emptyFilterClause]\n\n    beforeEach(() => {\n        // Quick fix to disregard console error when unmounting a component\n        console.error = jest.fn()\n        document.execCommand = jest.fn()\n        jest.resetAllMocks()\n    })\n\n    test('return dateFilter default value', () => {\n        const {container} = render(\n            wrapIntl(\n                <DateFilter\n                    view={activeView}\n                    filter={emptyFilterClause}\n                />,\n            ),\n        )\n        expect(container).toMatchSnapshot()\n    })\n\n    test('return dateFilter invalid value', () => {\n        const {container} = render(\n            wrapIntl(\n                <DateFilter\n                    view={activeView}\n                    filter={{\n                        propertyId: 'myPropertyId',\n                        condition: 'is',\n                        values: ['string is not valid'],\n                    }}\n                />,\n            ),\n        )\n        expect(container).toMatchSnapshot()\n    })\n\n    test('return dateFilter valid value', () => {\n        const june15 = June15.getTime().toString()\n        const {container} = render(\n            wrapIntl(\n                <DateFilter\n                    view={activeView}\n                    filter={{\n                        propertyId: 'myPropertyId',\n                        condition: 'is',\n                        values: [june15.toString()],\n                    }}\n                />,\n            ),\n        )\n        expect(container).toMatchSnapshot()\n    })\n\n    test('returns local correctly - es local', () => {\n        const todayFilterClause = createFilterClause(emptyFilterClause)\n        todayFilterClause.values = [June15.getTime().toString()]\n        activeView.fields.filter = createFilterGroup()\n        activeView.fields.filter.filters = [todayFilterClause]\n\n        const component = (\n            <IntlProvider locale='es'>\n                <DateFilter\n                    view={activeView}\n                    filter={todayFilterClause}\n                />\n            </IntlProvider>\n        )\n\n        const {container, getByText} = render(component)\n        const input = getByText('15 de junio')\n        expect(input).not.toBeNull()\n        expect(container).toMatchSnapshot()\n    })\n\n    test('handles calendar click event', () => {\n        activeView.fields.filter = createFilterGroup()\n        activeView.fields.filter.filters = [emptyFilterClause]\n\n        const component =\n            wrapIntl(\n                <DateFilter\n                    view={activeView}\n                    filter={emptyFilterClause}\n                />,\n            )\n        expect(component).toMatchSnapshot()\n        const {getByText, getByTitle} = render(component)\n\n        const dayDisplay = getByText('Empty')\n        userEvent.click(dayDisplay)\n\n        const day = getByText('15')\n        const modal = getByTitle('Close').children[0]\n        userEvent.click(day)\n        userEvent.click(modal)\n\n        const newFilterGroup = createFilterGroup(activeView.fields.filter)\n        const date = new Date()\n        const fifteenth = Date.UTC(date.getFullYear(), date.getMonth(), 15, 12)\n\n        const v = newFilterGroup.filters[0] as FilterClause\n        v.values = [fifteenth.toString()]\n        expect(mockedMutator.changeViewFilter).toHaveBeenCalledWith(board.id, activeView.id, activeView.fields.filter, newFilterGroup)\n    })\n\n    test('handle clear', () => {\n        const todayFilterClause = createFilterClause(emptyFilterClause)\n        todayFilterClause.values = [June15.getTime().toString()]\n        activeView.fields.filter = createFilterGroup()\n        activeView.fields.filter.filters = [todayFilterClause]\n\n        const component =\n            wrapIntl(\n                <DateFilter\n                    view={activeView}\n                    filter={todayFilterClause}\n                />,\n            )\n        const {container, getByText, getByTitle} = render(component)\n        expect(container).toMatchSnapshot()\n\n        // open modal\n        const dayDisplay = getByText('June 15')\n        userEvent.click(dayDisplay)\n\n        const clear = getByText('Clear')\n        const modal = getByTitle('Close').children[0]\n        userEvent.click(clear)\n        userEvent.click(modal)\n\n        const newFilterGroup = createFilterGroup(activeView.fields.filter)\n        const v = newFilterGroup.filters[0] as FilterClause\n        v.values = []\n        expect(mockedMutator.changeViewFilter).toHaveBeenCalledWith(board.id, activeView.id, activeView.fields.filter, newFilterGroup)\n    })\n\n    test('set via text input', () => {\n        activeView.fields.filter = createFilterGroup()\n        activeView.fields.filter.filters = [emptyFilterClause]\n\n        const component =\n            wrapIntl(\n                <DateFilter\n                    view={activeView}\n                    filter={emptyFilterClause}\n                />,\n            )\n\n        const {container, getByText, getByTitle, getByPlaceholderText} = render(component)\n        expect(container).toMatchSnapshot()\n\n        // open modal\n        const dayDisplay = getByText('Empty')\n        userEvent.click(dayDisplay)\n\n        const input = getByPlaceholderText('MM/DD/YYYY')\n        userEvent.type(input, '{selectall}{delay}07/15/2021{enter}')\n\n        const July15 = new Date(Date.UTC(2021, 6, 15, 12))\n        const modal = getByTitle('Close').children[0]\n        userEvent.click(modal)\n\n        const newFilterGroup = createFilterGroup(activeView.fields.filter)\n        const v = newFilterGroup.filters[0] as FilterClause\n        v.values = [July15.getTime().toString()]\n        expect(mockedMutator.changeViewFilter).toHaveBeenCalledWith(board.id, activeView.id, activeView.fields.filter, newFilterGroup)\n    })\n\n    test('handles `Today` button click event', () => {\n        const component =\n            wrapIntl(\n                <DateFilter\n                    view={activeView}\n                    filter={emptyFilterClause}\n                />,\n            )\n\n        console.log('handle today')\n\n        const {container, getByText, getByTitle} = render(component)\n        expect(container).toMatchSnapshot()\n\n        // To see if 'Today' button correctly selects today's date,\n        // we can check it against `new Date()`.\n        // About `Date()`\n        // > \"When called as a function, returns a string representation of the current date and time\"\n        const date = new Date()\n        const today = Date.UTC(date.getFullYear(), date.getMonth(), date.getDate(), 12)\n\n        // open modal\n        const dayDisplay = getByText('Empty')\n        userEvent.click(dayDisplay)\n\n        const day = getByText('Today')\n        const modal = getByTitle('Close').children[0]\n        userEvent.click(day)\n        userEvent.click(modal)\n\n        const newFilterGroup = createFilterGroup(activeView.fields.filter)\n        const v = newFilterGroup.filters[0] as FilterClause\n        v.values = [today.toString()]\n        expect(mockedMutator.changeViewFilter).toHaveBeenCalledWith(board.id, activeView.id, activeView.fields.filter, newFilterGroup)\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/viewHeader/dateFilter.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useState, useCallback} from 'react'\nimport {useIntl} from 'react-intl'\nimport {DateUtils} from 'react-day-picker'\nimport MomentLocaleUtils from 'react-day-picker/moment'\nimport DayPicker from 'react-day-picker/DayPicker'\n\nimport moment from 'moment'\n\nimport mutator from '../../mutator'\n\nimport Editable from '../../widgets/editable'\nimport Button from '../../widgets/buttons/button'\nimport {BoardView} from '../../blocks/boardView'\n\nimport Modal from '../../components/modal'\nimport ModalWrapper from '../../components/modalWrapper'\nimport {Utils} from '../../utils'\n\nimport 'react-day-picker/lib/style.css'\nimport './dateFilter.scss'\n\nimport {FilterClause} from '../../blocks/filterClause'\nimport {createFilterGroup} from '../../blocks/filterGroup'\n\nexport type DateProperty = {\n    from?: number\n    to?: number\n    includeTime?: boolean\n    timeZone?: string\n}\n\ntype Props = {\n    view: BoardView\n    filter: FilterClause\n}\n\nconst loadedLocales: Record<string, moment.Locale> = {}\n\nfunction DateFilter(props: Props): JSX.Element {\n    const {filter, view} = props\n    const [showDialog, setShowDialog] = useState(false)\n\n    const filterValue = filter.values\n\n    let dateValue: Date | undefined\n    if (filterValue && filterValue.length > 0) {\n        dateValue = new Date(parseInt(filterValue[0], 10))\n    }\n\n    const [value, setValue] = useState(dateValue)\n    const intl = useIntl()\n\n    const onChange = useCallback((newValue) => {\n        if (value !== newValue) {\n            const adjustedValue = newValue ? new Date(newValue.getTime() - timeZoneOffset(newValue.getTime())) : undefined\n            setValue(adjustedValue)\n\n            const filterIndex = view.fields.filter.filters.indexOf(filter)\n            Utils.assert(filterIndex >= 0, \"Can't find filter\")\n\n            const filterGroup = createFilterGroup(view.fields.filter)\n            const newFilter = filterGroup.filters[filterIndex] as FilterClause\n            Utils.assert(newFilter, `No filter at index ${filterIndex}`)\n\n            newFilter.values = []\n            if (adjustedValue) {\n                newFilter.values = [adjustedValue.getTime().toString()]\n            }\n            mutator.changeViewFilter(view.boardId, view.id, view.fields.filter, filterGroup)\n        }\n    }, [value, view.boardId, view.id, view.fields.filter])\n\n    const getDisplayDate = (date: Date | null | undefined) => {\n        let displayDate = ''\n        if (date) {\n            displayDate = Utils.displayDate(date, intl)\n        }\n        return displayDate\n    }\n\n    const timeZoneOffset = (date: number): number => {\n        return new Date(date).getTimezoneOffset() * 60 * 1000\n    }\n\n    // Keep date value as UTC, property dates are stored as 12:00 pm UTC\n    // date will need converted to local time, to ensure date stays consistent\n    // dateFrom / dateTo will be used for input and calendar dates\n    const offsetDate = value ? new Date(value.getTime() + timeZoneOffset(value.getTime())) : undefined\n    const [input, setInput] = useState<string>(getDisplayDate(offsetDate))\n\n    const locale = intl.locale.toLowerCase()\n    if (locale && locale !== 'en' && !loadedLocales[locale]) {\n        // eslint-disable-next-line global-require\n        loadedLocales[locale] = require(`moment/locale/${locale}`)\n    }\n\n    const handleTodayClick = (day: Date) => {\n        day.setHours(12)\n        saveValue(day)\n    }\n\n    const handleDayClick = (day: Date) => {\n        saveValue(day)\n    }\n\n    const onClear = () => {\n        saveValue(undefined)\n    }\n\n    const saveValue = (newValue: Date | undefined) => {\n        onChange(newValue)\n        setInput(newValue ? Utils.inputDate(newValue, intl) : '')\n    }\n\n    const onClose = () => {\n        setShowDialog(false)\n    }\n\n    let displayValue = ''\n    if (offsetDate) {\n        displayValue = getDisplayDate(offsetDate)\n    }\n\n    let buttonText = displayValue\n    if (!buttonText) {\n        buttonText = intl.formatMessage({id: 'DateFilter.empty', defaultMessage: 'Empty'})\n    }\n\n    const className = 'DateFilter'\n    return (\n        <div className={`DateFilter ${displayValue ? '' : 'empty'} `}>\n            <Button\n                onClick={() => setShowDialog(true)}\n            >\n                {buttonText}\n            </Button>\n\n            {showDialog &&\n            <ModalWrapper>\n                <Modal\n                    onClose={() => onClose()}\n                >\n                    <div\n                        className={className + '-overlayWrapper'}\n                    >\n                        <div className={className + '-overlay'}>\n                            <div className={'inputContainer'}>\n                                <Editable\n                                    value={input}\n                                    placeholderText={moment.localeData(locale).longDateFormat('L')}\n                                    onFocus={() => {\n                                        if (offsetDate) {\n                                            return setInput(Utils.inputDate(offsetDate, intl))\n                                        }\n                                        return undefined\n                                    }}\n                                    onChange={setInput}\n                                    onSave={() => {\n                                        const newDate = MomentLocaleUtils.parseDate(input, 'L', intl.locale)\n                                        if (newDate && DateUtils.isDate(newDate)) {\n                                            newDate.setHours(12)\n                                            saveValue(newDate)\n                                        } else {\n                                            setInput(getDisplayDate(offsetDate))\n                                        }\n                                    }}\n                                    onCancel={() => {\n                                        setInput(getDisplayDate(offsetDate))\n                                    }}\n                                />\n                            </div>\n                            <DayPicker\n                                onDayClick={handleDayClick}\n                                initialMonth={offsetDate || new Date()}\n                                showOutsideDays={false}\n                                locale={locale}\n                                localeUtils={MomentLocaleUtils}\n                                todayButton={intl.formatMessage({id: 'DateRange.today', defaultMessage: 'Today'})}\n                                onTodayButtonClick={handleTodayClick}\n                                month={offsetDate}\n                                selectedDays={offsetDate}\n                            />\n                            <hr/>\n                            <div\n                                className='MenuOption menu-option'\n                            >\n                                <Button\n                                    onClick={onClear}\n                                >\n                                    {intl.formatMessage({id: 'DateRange.clear', defaultMessage: 'Clear'})}\n                                </Button>\n                            </div>\n                        </div>\n                    </div>\n                </Modal>\n            </ModalWrapper>\n            }\n        </div>\n    )\n}\n\nexport default DateFilter\n"
  },
  {
    "path": "webapp/src/components/viewHeader/emptyCardButton.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\nimport {render, screen} from '@testing-library/react'\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport '@testing-library/jest-dom'\nimport userEvent from '@testing-library/user-event'\n\nimport {mocked} from 'jest-mock'\n\nimport {wrapIntl, mockStateStore} from '../../testUtils'\n\nimport {TestBlockFactory} from '../../test/testBlockFactory'\n\nimport mutator from '../../mutator'\n\nimport EmptyCardButton from './emptyCardButton'\n\nconst board = TestBlockFactory.createBoard()\nconst activeView = TestBlockFactory.createBoardView(board)\n\njest.mock('../../mutator')\nconst mockedMutator = mocked(mutator, true)\ndescribe('components/viewHeader/emptyCardButton', () => {\n    const state = {\n        users: {\n            me: {\n                id: 'user-id-1',\n                username: 'username_1'},\n        },\n        boards: {\n            current: board.id,\n            boards: {\n                [board.id]: board,\n            },\n        },\n        views: {\n            current: 0,\n            views: [activeView],\n        },\n    }\n\n    const store = mockStateStore([], state)\n    const mockFunction = jest.fn()\n\n    beforeEach(() => {\n        jest.clearAllMocks()\n    })\n    test('return EmptyCardButton', () => {\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <EmptyCardButton\n                        addCard={mockFunction}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        expect(container).toMatchSnapshot()\n    })\n    test('return EmptyCardButton and addCard', () => {\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <EmptyCardButton\n                        addCard={mockFunction}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        expect(container).toMatchSnapshot()\n        const buttonEmpty = screen.getByRole('button', {name: 'Empty card'})\n        userEvent.click(buttonEmpty)\n        expect(mockFunction).toBeCalledTimes(1)\n    })\n    test('return EmptyCardButton and Set Template', () => {\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <EmptyCardButton\n                        addCard={mockFunction}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})\n        userEvent.click(buttonElement)\n        expect(container).toMatchSnapshot()\n        const buttonDefault = screen.getByRole('button', {name: 'Set as default'})\n        userEvent.click(buttonDefault)\n        expect(mockedMutator.clearDefaultTemplate).toBeCalledTimes(1)\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/viewHeader/emptyCardButton.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport {useIntl} from 'react-intl'\n\nimport CardIcon from '../../widgets/icons/card'\nimport Menu from '../../widgets/menu'\n\nimport MenuWrapper from '../../widgets/menuWrapper'\nimport OptionsIcon from '../../widgets/icons/options'\nimport IconButton from '../../widgets/buttons/iconButton'\nimport CheckIcon from '../../widgets/icons/check'\nimport mutator from '../../mutator'\nimport {useAppSelector} from '../../store/hooks'\nimport {getCurrentView} from '../../store/views'\nimport {getCurrentBoardId} from '../../store/boards'\n\ntype Props = {\n    addCard: () => void\n}\n\nconst EmptyCardButton = (props: Props) => {\n    const currentView = useAppSelector(getCurrentView)\n    const boardId = useAppSelector(getCurrentBoardId)\n    const intl = useIntl()\n\n    return (\n        <Menu.Text\n            icon={<CardIcon/>}\n            id='empty-template'\n            name={intl.formatMessage({id: 'ViewHeader.empty-card', defaultMessage: 'Empty card'})}\n            className={currentView.fields.defaultTemplateId ? '' : 'bold-menu-text'}\n            onClick={() => {\n                props.addCard()\n            }}\n            rightIcon={\n                <MenuWrapper stopPropagationOnToggle={true}>\n                    <IconButton icon={<OptionsIcon/>}/>\n                    <Menu position='left'>\n                        <Menu.Text\n                            icon={<CheckIcon/>}\n                            id='default'\n                            name={intl.formatMessage({\n                                id: 'ViewHeader.set-default-template',\n                                defaultMessage: 'Set as default',\n                            })}\n                            onClick={async () => {\n                                await mutator.clearDefaultTemplate(boardId, currentView.id, currentView.fields.defaultTemplateId)\n                            }}\n                        />\n                    </Menu>\n                </MenuWrapper>\n            }\n        />)\n}\n\nexport default React.memo(EmptyCardButton)\n"
  },
  {
    "path": "webapp/src/components/viewHeader/filterComponent.scss",
    "content": ".FilterComponent {\n    min-width: 430px;\n}\n"
  },
  {
    "path": "webapp/src/components/viewHeader/filterComponent.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {render, screen} from '@testing-library/react'\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport {mocked} from 'jest-mock'\nimport '@testing-library/jest-dom'\n\nimport userEvent from '@testing-library/user-event'\n\nimport {FilterClause} from '../../blocks/filterClause'\n\nimport {TestBlockFactory} from '../../test/testBlockFactory'\nimport mutator from '../../mutator'\n\nimport {wrapIntl, mockStateStore} from '../../testUtils'\n\nimport FilterComponenet from './filterComponent'\n\njest.mock('../../mutator')\nconst mockedMutator = mocked(mutator, true)\n\nconst board = TestBlockFactory.createBoard()\nconst activeView = TestBlockFactory.createBoardView(board)\n\nconst filter: FilterClause = {\n    propertyId: board.cardProperties[0].id,\n    condition: 'includes',\n    values: ['Status'],\n}\nconst unknownFilter: FilterClause = {\n    propertyId: 'unknown',\n    condition: 'includes',\n    values: [],\n}\n\nconst state = {\n    users: {\n        me: {\n            id: 'user-id-1',\n            username: 'username_1',\n        },\n    },\n}\nconst store = mockStateStore([], state)\ndescribe('components/viewHeader/filterComponent', () => {\n    beforeEach(() => {\n        jest.clearAllMocks()\n        board.cardProperties[0].options = [{id: 'Status', value: 'Status', color: ''}]\n        activeView.fields.filter.filters = [filter]\n    })\n    test('return filterComponent', () => {\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <FilterComponenet\n                        board={board}\n                        activeView={activeView}\n                        onClose={jest.fn()}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        const buttonElement = screen.getAllByRole('button', {name: 'menuwrapper'})[0]\n        userEvent.click(buttonElement)\n        expect(container).toMatchSnapshot()\n    })\n    test('return filterComponent and add Filter', () => {\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <FilterComponenet\n                        board={board}\n                        activeView={activeView}\n                        onClose={jest.fn()}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        const buttonElement = screen.getAllByRole('button', {name: 'menuwrapper'})[0]\n        userEvent.click(buttonElement)\n        expect(container).toMatchSnapshot()\n        const buttonAdd = screen.getByText('+ Add filter')\n        userEvent.click(buttonAdd)\n        expect(mockedMutator.changeViewFilter).toBeCalledTimes(1)\n    })\n\n    test('return filterComponent and filter by status', () => {\n        activeView.fields.filter.filters = [unknownFilter]\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <FilterComponenet\n                        board={board}\n                        activeView={activeView}\n                        onClose={jest.fn()}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        const buttonElement = screen.getAllByRole('button', {name: 'menuwrapper'})[0]\n        userEvent.click(buttonElement)\n        expect(container).toMatchSnapshot()\n        const buttonStatus = screen.getByRole('button', {name: 'Status'})\n        userEvent.click(buttonStatus)\n        expect(mockedMutator.changeViewFilter).toBeCalledTimes(1)\n    })\n\n    test('return filterComponent and click is empty', () => {\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <FilterComponenet\n                        board={board}\n                        activeView={activeView}\n                        onClose={jest.fn()}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        const buttonElement = screen.getAllByRole('button', {name: 'menuwrapper'})[1]\n        userEvent.click(buttonElement)\n        expect(container).toMatchSnapshot()\n        const buttonNotInclude = screen.getByRole('button', {name: 'is empty'})\n        userEvent.click(buttonNotInclude)\n        expect(mockedMutator.changeViewFilter).toBeCalledTimes(1)\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/viewHeader/filterComponent.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\nimport {FormattedMessage} from 'react-intl'\n\nimport {FilterClause, FilterCondition, createFilterClause} from '../../blocks/filterClause'\nimport {createFilterGroup, isAFilterGroupInstance} from '../../blocks/filterGroup'\nimport {Board, IPropertyTemplate} from '../../blocks/board'\nimport {BoardView} from '../../blocks/boardView'\nimport mutator from '../../mutator'\nimport {Utils} from '../../utils'\nimport Button from '../../widgets/buttons/button'\nimport propsRegistry from '../../properties'\n\nimport Modal from '../modal'\n\nimport FilterEntry from './filterEntry'\n\nimport './filterComponent.scss'\n\ntype Props = {\n    board: Board\n    activeView: BoardView\n    onClose: () => void\n}\n\nconst FilterComponent = (props: Props): JSX.Element => {\n    const conditionClicked = (optionId: string, filter: FilterClause): void => {\n        const {activeView} = props\n\n        const filterIndex = activeView.fields.filter.filters.indexOf(filter)\n        Utils.assert(filterIndex >= 0, \"Can't find filter\")\n\n        const filterGroup = createFilterGroup(activeView.fields.filter)\n        const newFilter = filterGroup.filters[filterIndex] as FilterClause\n\n        Utils.assert(newFilter, `No filter at index ${filterIndex}`)\n        if (newFilter.condition !== optionId) {\n            newFilter.condition = optionId as FilterCondition\n            mutator.changeViewFilter(board.id, activeView.id, activeView.fields.filter, filterGroup)\n        }\n    }\n\n    const addFilterClicked = () => {\n        const {board, activeView} = props\n\n        const filters = activeView.fields.filter?.filters.filter((o) => !isAFilterGroupInstance(o)) as FilterClause[] || []\n        const filterGroup = createFilterGroup(activeView.fields.filter)\n        const filter = createFilterClause()\n\n        // Pick the first filterable property that isn't already filtered on\n        const selectProperty = board.cardProperties.\n            filter((o: IPropertyTemplate) => !filters.find((f) => f.propertyId === o.id)).\n            find((o: IPropertyTemplate) => propsRegistry.get(o.type).canFilter)\n        if (selectProperty) {\n            filter.propertyId = selectProperty.id\n        }\n        filterGroup.filters.push(filter)\n\n        mutator.changeViewFilter(board.id, activeView.id, activeView.fields.filter, filterGroup)\n    }\n\n    const {board, activeView} = props\n\n    const filters: FilterClause[] = activeView.fields.filter?.filters.filter((o) => !isAFilterGroupInstance(o)) as FilterClause[] || []\n\n    return (\n        <Modal\n            onClose={props.onClose}\n        >\n            <div\n                className='FilterComponent'\n            >\n                {filters.map((filter) => (\n                    <FilterEntry\n                        key={`${filter.propertyId}-${filter.condition}`}\n                        board={board}\n                        view={activeView}\n                        conditionClicked={conditionClicked}\n                        filter={filter}\n                    />\n                ))}\n\n                <br/>\n\n                <Button onClick={() => addFilterClicked()}>\n                    <FormattedMessage\n                        id='FilterComponent.add-filter'\n                        defaultMessage='+ Add filter'\n                    />\n                </Button>\n            </div>\n        </Modal>\n    )\n}\n\nexport default React.memo(FilterComponent)\n"
  },
  {
    "path": "webapp/src/components/viewHeader/filterEntry.scss",
    "content": ".FilterEntry {\n    display: flex;\n    flex-direction: row;\n\n    > div {\n        display: flex;\n        flex-direction: row;\n    }\n\n    .Button {\n        overflow: hidden;\n        max-width: 220px;\n        text-overflow: ellipsis;\n        white-space: normal;\n        display: block;\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/viewHeader/filterEntry.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {render, screen} from '@testing-library/react'\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport '@testing-library/jest-dom'\nimport userEvent from '@testing-library/user-event'\n\nimport {mocked} from 'jest-mock'\n\nimport {FilterClause} from '../../blocks/filterClause'\n\nimport {TestBlockFactory} from '../../test/testBlockFactory'\n\nimport {wrapIntl, mockStateStore} from '../../testUtils'\n\nimport mutator from '../../mutator'\n\nimport FilterEntry from './filterEntry'\n\njest.mock('../../mutator')\nconst mockedMutator = mocked(mutator, true)\n\nconst board = TestBlockFactory.createBoard()\nconst activeView = TestBlockFactory.createBoardView(board)\nboard.cardProperties[1].type = 'checkbox'\nboard.cardProperties[2].type = 'text'\nboard.cardProperties[3].type = 'date'\nconst statusFilter: FilterClause = {\n    propertyId: board.cardProperties[0].id,\n    condition: 'includes',\n    values: ['Status'],\n}\nconst booleanFilter: FilterClause = {\n    propertyId: board.cardProperties[1].id,\n    condition: 'isSet',\n    values: [],\n}\nconst textFilter: FilterClause = {\n    propertyId: board.cardProperties[2].id,\n    condition: 'contains',\n    values: [],\n}\nconst dateFilter: FilterClause = {\n    propertyId: board.cardProperties[3].id,\n    condition: 'is',\n    values: [],\n}\n\nconst unknownFilter: FilterClause = {\n    propertyId: 'unknown',\n    condition: 'includes',\n    values: [],\n}\nconst state = {\n    users: {\n        me: {\n            id: 'user-id-1',\n            username: 'username_1',\n        },\n    },\n}\nconst store = mockStateStore([], state)\nconst mockedConditionClicked = jest.fn()\n\ndescribe('components/viewHeader/filterEntry', () => {\n    beforeEach(() => {\n        jest.clearAllMocks()\n        board.cardProperties[0].options = [{id: 'Status', value: 'Status', color: ''}]\n        activeView.fields.filter.filters = [statusFilter]\n    })\n    test('return filterEntry', () => {\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <FilterEntry\n                        board={board}\n                        view={activeView}\n                        conditionClicked={mockedConditionClicked}\n                        filter={statusFilter}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        const buttonElement = screen.getAllByRole('button', {name: 'menuwrapper'})[0]\n        userEvent.click(buttonElement)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('return filterEntry for boolean field', () => {\n        activeView.fields.filter.filters = [booleanFilter]\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <FilterEntry\n                        board={board}\n                        view={activeView}\n                        conditionClicked={mockedConditionClicked}\n                        filter={booleanFilter}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        expect(container).toMatchSnapshot()\n        const buttonElement = screen.getAllByRole('button', {name: 'menuwrapper'})[1]\n        userEvent.click(buttonElement)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('return filterEntry for text field', () => {\n        activeView.fields.filter.filters = [textFilter]\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <FilterEntry\n                        board={board}\n                        view={activeView}\n                        conditionClicked={mockedConditionClicked}\n                        filter={textFilter}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        expect(container).toMatchSnapshot()\n        const buttonElement = screen.getAllByRole('button', {name: 'menuwrapper'})[1]\n        userEvent.click(buttonElement)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('return filterEntry for date field', () => {\n        activeView.fields.filter.filters = [dateFilter]\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <FilterEntry\n                        board={board}\n                        view={activeView}\n                        conditionClicked={mockedConditionClicked}\n                        filter={dateFilter}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        expect(container).toMatchSnapshot()\n        const buttonElement = screen.getAllByRole('button', {name: 'menuwrapper'})[1]\n        userEvent.click(buttonElement)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('return filterEntry and click on status', () => {\n        activeView.fields.filter.filters = [unknownFilter]\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <FilterEntry\n                        board={board}\n                        view={activeView}\n                        conditionClicked={mockedConditionClicked}\n                        filter={unknownFilter}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        const buttonElement = screen.getAllByRole('button', {name: 'menuwrapper'})[0]\n        userEvent.click(buttonElement)\n        expect(container).toMatchSnapshot()\n        const buttonStatus = screen.getByRole('button', {name: 'Status'})\n        userEvent.click(buttonStatus)\n        expect(mockedMutator.changeViewFilter).toBeCalledTimes(1)\n    })\n    test('return filterEntry and click on includes', () => {\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <FilterEntry\n                        board={board}\n                        view={activeView}\n                        conditionClicked={mockedConditionClicked}\n                        filter={statusFilter}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        const buttonElement = screen.getAllByRole('button', {name: 'menuwrapper'})[1]\n        userEvent.click(buttonElement)\n        expect(container).toMatchSnapshot()\n        const buttonIncludes = screen.getAllByRole('button', {name: 'includes'})[1]\n        userEvent.click(buttonIncludes)\n        expect(mockedConditionClicked).toBeCalledTimes(1)\n    })\n    test('return filterEntry and click on doesn\\'t include', () => {\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <FilterEntry\n                        board={board}\n                        view={activeView}\n                        conditionClicked={mockedConditionClicked}\n                        filter={statusFilter}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        const buttonElement = screen.getAllByRole('button', {name: 'menuwrapper'})[1]\n        userEvent.click(buttonElement)\n        expect(container).toMatchSnapshot()\n        const buttonNotInclude = screen.getByRole('button', {name: 'doesn\\'t include'})\n        userEvent.click(buttonNotInclude)\n        expect(mockedConditionClicked).toBeCalledTimes(1)\n    })\n    test('return filterEntry and click on is empty', () => {\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <FilterEntry\n                        board={board}\n                        view={activeView}\n                        conditionClicked={mockedConditionClicked}\n                        filter={statusFilter}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        const buttonElement = screen.getAllByRole('button', {name: 'menuwrapper'})[1]\n        userEvent.click(buttonElement)\n        expect(container).toMatchSnapshot()\n        const buttonEmpty = screen.getByRole('button', {name: 'is empty'})\n        userEvent.click(buttonEmpty)\n        expect(mockedConditionClicked).toBeCalledTimes(1)\n    })\n    test('return filterEntry and click on is not empty', () => {\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <FilterEntry\n                        board={board}\n                        view={activeView}\n                        conditionClicked={mockedConditionClicked}\n                        filter={statusFilter}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        const buttonElement = screen.getAllByRole('button', {name: 'menuwrapper'})[1]\n        userEvent.click(buttonElement)\n        expect(container).toMatchSnapshot()\n        const buttonNotEmpty = screen.getByRole('button', {name: 'is not empty'})\n        userEvent.click(buttonNotEmpty)\n        expect(mockedConditionClicked).toBeCalledTimes(1)\n    })\n    test('return filterEntry and click on delete', () => {\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <FilterEntry\n                        board={board}\n                        view={activeView}\n                        conditionClicked={mockedConditionClicked}\n                        filter={statusFilter}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        const buttonElement = screen.getAllByRole('button', {name: 'menuwrapper'})[1]\n        userEvent.click(buttonElement)\n        expect(container).toMatchSnapshot()\n        const allButton = screen.getAllByRole('button')\n        userEvent.click(allButton[allButton.length - 1])\n        expect(mockedMutator.changeViewFilter).toBeCalledTimes(1)\n    })\n    test('return filterEntry and click on different property type', () => {\n        activeView.fields.filter.filters = [statusFilter]\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <FilterEntry\n                        board={board}\n                        view={activeView}\n                        conditionClicked={mockedConditionClicked}\n                        filter={statusFilter}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        const buttonElement = screen.getAllByRole('button', {name: 'menuwrapper'})[0]\n        userEvent.click(buttonElement)\n        expect(container).toMatchSnapshot()\n        const buttonDate = screen.getByRole('button', {name: 'Property 3'})\n        userEvent.click(buttonDate)\n        expect(mockedMutator.changeViewFilter).toBeCalledWith(\n            board.id, activeView.id,\n            {operation: 'and', filters: [statusFilter]},\n            {operation: 'and', filters: [dateFilter]})\n    })\n    test('return filterEntry and click on different property type, but same filterOperation', () => {\n        activeView.fields.filter.filters = [booleanFilter]\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <FilterEntry\n                        board={board}\n                        view={activeView}\n                        conditionClicked={mockedConditionClicked}\n                        filter={booleanFilter}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        const buttonElement = screen.getAllByRole('button', {name: 'menuwrapper'})[0]\n        userEvent.click(buttonElement)\n        expect(container).toMatchSnapshot()\n        const buttonDate = screen.getByRole('button', {name: 'Property 3'})\n        userEvent.click(buttonDate)\n        expect(mockedMutator.changeViewFilter).toBeCalledWith(\n            board.id, activeView.id,\n            {operation: 'and', filters: [booleanFilter]},\n            {operation: 'and',\n                filters: [{\n                    propertyId: board.cardProperties[3].id,\n                    condition: 'isSet',\n                    values: [],\n                }]})\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/viewHeader/filterEntry.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\nimport {FormattedMessage, useIntl} from 'react-intl'\n\nimport {FilterClause, areEqual as areFilterClausesEqual} from '../../blocks/filterClause'\nimport {createFilterGroup, isAFilterGroupInstance} from '../../blocks/filterGroup'\nimport mutator from '../../mutator'\nimport {OctoUtils} from '../../octoUtils'\nimport {Utils} from '../../utils'\nimport {Board, IPropertyTemplate} from '../../blocks/board'\nimport {BoardView} from '../../blocks/boardView'\nimport Button from '../../widgets/buttons/button'\nimport Menu from '../../widgets/menu'\nimport MenuWrapper from '../../widgets/menuWrapper'\nimport propsRegistry from '../../properties'\n\nimport FilterValue from './filterValue'\n\nimport './filterEntry.scss'\n\ntype Props = {\n    board: Board\n    view: BoardView\n    conditionClicked: (optionId: string, filter: FilterClause) => void\n    filter: FilterClause\n}\n\nconst FilterEntry = (props: Props): JSX.Element => {\n    const {board, view, filter} = props\n    const intl = useIntl()\n\n    const template = board.cardProperties.find((o: IPropertyTemplate) => o.id === filter.propertyId)\n    let propertyType = propsRegistry.get(template?.type || 'unknown')\n    let propertyName = template ? template.name : '(unknown)'\n    if (filter.propertyId === 'title') {\n        propertyType = propsRegistry.get('text')\n        propertyName = 'Title'\n    }\n    const key = `${filter.propertyId}-${filter.condition}}`\n    return (\n        <div\n            className='FilterEntry'\n            key={key}\n        >\n            <MenuWrapper>\n                <Button>{propertyName}</Button>\n                <Menu>\n                    <Menu.Text\n                        key={'title'}\n                        id={'title'}\n                        name={'Title'}\n                        onClick={(optionId: string) => {\n                            const filterIndex = view.fields.filter.filters.indexOf(filter)\n                            Utils.assert(filterIndex >= 0, \"Can't find filter\")\n                            const filterGroup = createFilterGroup(view.fields.filter)\n                            const newFilter = filterGroup.filters[filterIndex] as FilterClause\n                            Utils.assert(newFilter, `No filter at index ${filterIndex}`)\n                            if (newFilter.propertyId !== optionId) {\n                                newFilter.propertyId = optionId\n                                newFilter.values = []\n                                mutator.changeViewFilter(props.board.id, view.id, view.fields.filter, filterGroup)\n                            }\n                        }}\n                    />\n                    {board.cardProperties.filter((o: IPropertyTemplate) => propsRegistry.get(o.type).canFilter).map((o: IPropertyTemplate) => (\n                        <Menu.Text\n                            key={o.id}\n                            id={o.id}\n                            name={o.name}\n                            onClick={(optionId: string) => {\n                                const filterIndex = view.fields.filter.filters.indexOf(filter)\n                                Utils.assert(filterIndex >= 0, \"Can't find filter\")\n                                const filterGroup = createFilterGroup(view.fields.filter)\n                                const newFilter = filterGroup.filters[filterIndex] as FilterClause\n                                Utils.assert(newFilter, `No filter at index ${filterIndex}`)\n                                if (newFilter.propertyId !== optionId) {\n                                    newFilter.propertyId = optionId\n                                    newFilter.condition = OctoUtils.filterConditionValidOrDefault(propsRegistry.get(o.type).filterValueType, newFilter.condition)\n                                    newFilter.values = []\n                                    mutator.changeViewFilter(props.board.id, view.id, view.fields.filter, filterGroup)\n                                }\n                            }}\n                        />))}\n                </Menu>\n            </MenuWrapper>\n            <MenuWrapper>\n                <Button>{OctoUtils.filterConditionDisplayString(filter.condition, intl, propertyType.filterValueType)}</Button>\n                <Menu>\n                    {propertyType.filterValueType === 'options' &&\n                        <>\n                            <Menu.Text\n                                id='includes'\n                                name={intl.formatMessage({id: 'Filter.includes', defaultMessage: 'includes'})}\n                                onClick={(id) => props.conditionClicked(id, filter)}\n                            />\n                            <Menu.Text\n                                id='notIncludes'\n                                name={intl.formatMessage({id: 'Filter.not-includes', defaultMessage: 'doesn\\'t include'})}\n                                onClick={(id) => props.conditionClicked(id, filter)}\n                            />\n                            <Menu.Text\n                                id='isEmpty'\n                                name={intl.formatMessage({id: 'Filter.is-empty', defaultMessage: 'is empty'})}\n                                onClick={(id) => props.conditionClicked(id, filter)}\n                            />\n                            <Menu.Text\n                                id='isNotEmpty'\n                                name={intl.formatMessage({id: 'Filter.is-not-empty', defaultMessage: 'is not empty'})}\n                                onClick={(id) => props.conditionClicked(id, filter)}\n                            />\n                        </>}\n                    {propertyType.filterValueType === 'person' &&\n                        <>\n                            <Menu.Text\n                                id='includes'\n                                name={intl.formatMessage({id: 'Filter.includes', defaultMessage: 'includes'})}\n                                onClick={(id) => props.conditionClicked(id, filter)}\n                            />\n                            <Menu.Text\n                                id='notIncludes'\n                                name={intl.formatMessage({id: 'Filter.not-includes', defaultMessage: 'doesn\\'t include'})}\n                                onClick={(id) => props.conditionClicked(id, filter)}\n                            />\n                        </>}\n                    {(propertyType.type === 'person' || propertyType.type === 'multiPerson') &&\n                        <>\n                            <Menu.Text\n                                id='isEmpty'\n                                name={intl.formatMessage({id: 'Filter.is-empty', defaultMessage: 'is empty'})}\n                                onClick={(id) => props.conditionClicked(id, filter)}\n                            />\n                            <Menu.Text\n                                id='isNotEmpty'\n                                name={intl.formatMessage({id: 'Filter.is-not-empty', defaultMessage: 'is not empty'})}\n                                onClick={(id) => props.conditionClicked(id, filter)}\n                            />\n                        </>}\n                    {propertyType.filterValueType === 'boolean' &&\n                        <>\n                            <Menu.Text\n                                id='isSet'\n                                name={intl.formatMessage({id: 'Filter.is-set', defaultMessage: 'is set'})}\n                                onClick={(id) => props.conditionClicked(id, filter)}\n                            />\n                            <Menu.Text\n                                id='isNotSet'\n                                name={intl.formatMessage({id: 'Filter.is-not-set', defaultMessage: 'is not set'})}\n                                onClick={(id) => props.conditionClicked(id, filter)}\n                            />\n                        </>}\n                    {propertyType.filterValueType === 'text' &&\n                        <>\n                            <Menu.Text\n                                id='is'\n                                name={intl.formatMessage({id: 'Filter.is', defaultMessage: 'is'})}\n                                onClick={(id) => props.conditionClicked(id, filter)}\n                            />\n                            <Menu.Text\n                                id='contains'\n                                name={intl.formatMessage({id: 'Filter.contains', defaultMessage: 'contains'})}\n                                onClick={(id) => props.conditionClicked(id, filter)}\n                            />\n                            <Menu.Text\n                                id='notContains'\n                                name={intl.formatMessage({id: 'Filter.not-contains', defaultMessage: 'doesn\\'t contain'})}\n                                onClick={(id) => props.conditionClicked(id, filter)}\n                            />\n                            <Menu.Text\n                                id='startsWith'\n                                name={intl.formatMessage({id: 'Filter.starts-with', defaultMessage: 'starts with'})}\n                                onClick={(id) => props.conditionClicked(id, filter)}\n                            />\n                            <Menu.Text\n                                id='notStartsWith'\n                                name={intl.formatMessage({id: 'Filter.not-starts-with', defaultMessage: 'doesn\\'t start with'})}\n                                onClick={(id) => props.conditionClicked(id, filter)}\n                            />\n                            <Menu.Text\n                                id='endsWith'\n                                name={intl.formatMessage({id: 'Filter.ends-with', defaultMessage: 'ends with'})}\n                                onClick={(id) => props.conditionClicked(id, filter)}\n                            />\n                            <Menu.Text\n                                id='notEndsWith'\n                                name={intl.formatMessage({id: 'Filter.not-ends-with', defaultMessage: 'doesn\\'t end with'})}\n                                onClick={(id) => props.conditionClicked(id, filter)}\n                            />\n                        </>}\n                    {propertyType.filterValueType === 'date' &&\n                        <>\n                            <Menu.Text\n                                id='is'\n                                name={intl.formatMessage({id: 'Filter.is', defaultMessage: 'is'})}\n                                onClick={(id) => props.conditionClicked(id, filter)}\n                            />\n                            <Menu.Text\n                                id='isBefore'\n                                name={intl.formatMessage({id: 'Filter.isbefore', defaultMessage: 'is before'})}\n                                onClick={(id) => props.conditionClicked(id, filter)}\n                            />\n                            <Menu.Text\n                                id='isAfter'\n                                name={intl.formatMessage({id: 'Filter.isafter', defaultMessage: 'is after'})}\n                                onClick={(id) => props.conditionClicked(id, filter)}\n                            />\n                        </>}\n                    {propertyType.type === 'date' &&\n                        <>\n                            <Menu.Text\n                                id='isSet'\n                                name={intl.formatMessage({id: 'Filter.is-set', defaultMessage: 'is set'})}\n                                onClick={(id) => props.conditionClicked(id, filter)}\n                            />\n                            <Menu.Text\n                                id='isNotSet'\n                                name={intl.formatMessage({id: 'Filter.is-not-set', defaultMessage: 'is not set'})}\n                                onClick={(id) => props.conditionClicked(id, filter)}\n                            />\n                        </>}\n                </Menu>\n            </MenuWrapper>\n            <FilterValue\n                filter={filter}\n                template={template}\n                view={view}\n                propertyType={propertyType}\n            />\n            <div className='octo-spacer'/>\n            <Button\n                onClick={() => {\n                    const filterGroup = createFilterGroup(view.fields.filter)\n                    filterGroup.filters = filterGroup.filters.filter((o) => isAFilterGroupInstance(o) || !areFilterClausesEqual(o, filter))\n                    mutator.changeViewFilter(props.board.id, view.id, view.fields.filter, filterGroup)\n                }}\n            >\n                <FormattedMessage\n                    id='FilterComponent.delete'\n                    defaultMessage='Delete'\n                />\n            </Button>\n        </div>\n    )\n}\n\nexport default React.memo(FilterEntry)\n"
  },
  {
    "path": "webapp/src/components/viewHeader/filterValue.scss",
    "content": ".filterValue {\n    .Menu {\n        max-height: 400px;\n        overflow-y: auto;\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/viewHeader/filterValue.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {render, screen} from '@testing-library/react'\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport '@testing-library/jest-dom'\nimport userEvent from '@testing-library/user-event'\n\nimport {mocked} from 'jest-mock'\n\nimport {FilterClause} from '../../blocks/filterClause'\nimport {IPropertyTemplate} from '../../blocks/board'\n\nimport {TestBlockFactory} from '../../test/testBlockFactory'\n\nimport {wrapIntl, mockStateStore} from '../../testUtils'\n\nimport mutator from '../../mutator'\nimport propsRegistry from '../../properties'\n\nimport FilterValue from './filterValue'\n\njest.mock('../../mutator')\nconst mockedMutator = mocked(mutator, true)\n\nconst board = TestBlockFactory.createBoard()\nconst activeView = TestBlockFactory.createBoardView(board)\nconst state = {\n    users: {\n        me: {\n            id: 'user-id-1',\n            username: 'username_1',\n        },\n    },\n}\nconst store = mockStateStore([], state)\nconst filter: FilterClause = {\n    propertyId: '1',\n    condition: 'includes',\n    values: ['Status'],\n}\n\ndescribe('components/viewHeader/filterValue', () => {\n    beforeEach(() => {\n        jest.clearAllMocks()\n        board.cardProperties[0].options = [{id: 'Status', value: 'Status', color: ''}]\n        activeView.fields.filter.filters = [filter]\n    })\n    test('return filterValue', () => {\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <FilterValue\n                        view={activeView}\n                        filter={filter}\n                        template={board.cardProperties[0]}\n                        propertyType={propsRegistry.get(board.cardProperties[0].type)}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})\n        userEvent.click(buttonElement)\n        expect(container).toMatchSnapshot()\n    })\n    test('return filterValue and click Status', () => {\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <FilterValue\n                        view={activeView}\n                        filter={filter}\n                        template={board.cardProperties[0]}\n                        propertyType={propsRegistry.get(board.cardProperties[0].type)}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})\n        userEvent.click(buttonElement)\n        const switchStatus = screen.getAllByText('Status')[1]\n        userEvent.click(switchStatus)\n        expect(mockedMutator.changeViewFilter).toBeCalledTimes(1)\n        expect(container).toMatchSnapshot()\n    })\n    test('return filterValue and click Status with Status not in filter', () => {\n        filter.values = ['test']\n        activeView.fields.filter.filters = [filter]\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <FilterValue\n                        view={activeView}\n                        filter={filter}\n                        template={board.cardProperties[0]}\n                        propertyType={propsRegistry.get(board.cardProperties[0].type)}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})\n        userEvent.click(buttonElement)\n        const switchStatus = screen.getAllByText('Status')[0]\n        userEvent.click(switchStatus)\n        expect(mockedMutator.changeViewFilter).toBeCalledTimes(1)\n        expect(container).toMatchSnapshot()\n    })\n    test('return filterValue and verify that menu is not closed after clicking on the item', () => {\n        filter.values = []\n        activeView.fields.filter.filters = [filter]\n        render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <FilterValue\n                        view={activeView}\n                        filter={filter}\n                        template={board.cardProperties[0]}\n                        propertyType={propsRegistry.get(board.cardProperties[0].type)}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        const buttonElement = screen.getByRole('button', {name: '(empty)'})\n        userEvent.click(buttonElement)\n\n        const switchStatus = screen.getByRole('button', {name: 'Status'})\n        userEvent.click(switchStatus)\n        expect(switchStatus).toBeInTheDocument()\n    })\n\n    test('return date filter value', () => {\n        const propertyTemplate: IPropertyTemplate = {\n            id: 'datePropertyID',\n            name: 'My Date Property',\n            type: 'date',\n            options: [],\n        }\n        board.cardProperties.push(propertyTemplate)\n\n        const dateFilter: FilterClause = {\n            propertyId: 'datePropertyID',\n            condition: 'is',\n            values: [],\n        }\n\n        // filter.values = []\n        activeView.fields.filter.filters = [dateFilter]\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <FilterValue\n                        view={activeView}\n                        filter={filter}\n                        template={propertyTemplate}\n                        propertyType={propsRegistry.get(propertyTemplate.type)}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        expect(container).toMatchSnapshot()\n\n        const buttonElement = screen.getByRole('button', {name: 'Empty'})\n        userEvent.click(buttonElement)\n\n        // make sure modal is displayed\n        const clearButton = screen.getByRole('button', {name: 'Clear'})\n        expect(clearButton).toBeInTheDocument()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/viewHeader/filterValue.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useState} from 'react'\n\nimport {useIntl} from 'react-intl'\n\nimport {PropertyType} from '../../properties/types'\nimport {IPropertyTemplate} from '../../blocks/board'\nimport {FilterClause} from '../../blocks/filterClause'\nimport {createFilterGroup} from '../../blocks/filterGroup'\nimport {BoardView} from '../../blocks/boardView'\nimport mutator from '../../mutator'\nimport {Utils} from '../../utils'\nimport Button from '../../widgets/buttons/button'\nimport Menu from '../../widgets/menu'\nimport Editable from '../../widgets/editable'\nimport MenuWrapper from '../../widgets/menuWrapper'\n\nimport DateFilter from './dateFilter'\n\nimport './filterValue.scss'\nimport MultiPersonFilterValue from './multipersonFilterValue'\n\ntype Props = {\n    view: BoardView\n    filter: FilterClause\n    template?: IPropertyTemplate\n    propertyType: PropertyType\n}\n\nconst filterValue = (props: Props): JSX.Element|null => {\n    const {filter, template, view, propertyType} = props\n    const [value, setValue] = useState(filter.values.length > 0 ? filter.values[0] : '')\n    const intl = useIntl()\n\n    if (propertyType.filterValueType === 'none') {\n        return null\n    }\n\n    if (propertyType.filterValueType === 'boolean') {\n        return null\n    }\n\n    if ((propertyType.filterValueType === 'options' || propertyType.filterValueType === 'person') && filter.condition !== 'includes' && filter.condition !== 'notIncludes') {\n        return null\n    }\n\n    if (propertyType.filterValueType === 'text') {\n        return (\n            <Editable\n                onChange={setValue}\n                value={value}\n                placeholderText={intl.formatMessage({id: 'FilterByText.placeholder', defaultMessage: 'filter text'})}\n                onSave={() => {\n                    const filterIndex = view.fields.filter.filters.indexOf(filter)\n                    Utils.assert(filterIndex >= 0, \"Can't find filter\")\n\n                    const filterGroup = createFilterGroup(view.fields.filter)\n                    const newFilter = filterGroup.filters[filterIndex] as FilterClause\n                    Utils.assert(newFilter, `No filter at index ${filterIndex}`)\n\n                    newFilter.values = [value]\n                    mutator.changeViewFilter(view.boardId, view.id, view.fields.filter, filterGroup)\n                }}\n            />\n        )\n    }\n\n    if (propertyType.filterValueType === 'person') {\n        return (\n            <MultiPersonFilterValue\n                view={view}\n                filter={filter}\n            />\n        )\n    }\n    if (propertyType.filterValueType === 'date') {\n        if (filter.condition === 'isSet' || filter.condition === 'isNotSet') {\n            return null\n        }\n\n        return (\n            <DateFilter\n                view={view}\n                filter={filter}\n            />\n        )\n    }\n\n    let displayValue: string\n    if (filter.values.length > 0) {\n        displayValue = filter.values.map((id) => {\n            const option = template?.options.find((o) => o.id === id)\n            return option?.value || '(Unknown)'\n        }).join(', ')\n    } else {\n        displayValue = intl.formatMessage({id: 'FilterValue.empty', defaultMessage: '(empty)'})\n    }\n\n    return (\n        <MenuWrapper className='filterValue'>\n            <Button>{displayValue}</Button>\n\n            <Menu>\n                {template?.options.map((o) => (\n                    <Menu.Switch\n                        key={o.id}\n                        id={o.id}\n                        name={o.value}\n                        isOn={filter.values.includes(o.id)}\n                        suppressItemClicked={true}\n                        onClick={(optionId) => {\n                            const filterIndex = view.fields.filter.filters.indexOf(filter)\n                            Utils.assert(filterIndex >= 0, \"Can't find filter\")\n\n                            const filterGroup = createFilterGroup(view.fields.filter)\n                            const newFilter = filterGroup.filters[filterIndex] as FilterClause\n                            Utils.assert(newFilter, `No filter at index ${filterIndex}`)\n                            if (filter.values.includes(o.id)) {\n                                newFilter.values = newFilter.values.filter((id) => id !== optionId)\n                                mutator.changeViewFilter(view.boardId, view.id, view.fields.filter, filterGroup)\n                            } else {\n                                newFilter.values.push(optionId)\n                                mutator.changeViewFilter(view.boardId, view.id, view.fields.filter, filterGroup)\n                            }\n                        }}\n                    />\n                ))}\n            </Menu>\n        </MenuWrapper>\n    )\n}\n\nexport default filterValue\n"
  },
  {
    "path": "webapp/src/components/viewHeader/multiperson.scss",
    "content": ".MultiPerson {\n    display: flex;\n    align-items: center;\n    border-radius: 4px;\n    flex-wrap: wrap;\n    gap: 8px;\n\n    &.readonly {\n        overflow: hidden;\n        text-overflow: ellipsis;\n        min-width: unset;\n    }\n\n    .MultiPerson-item {\n        display: flex;\n        align-items: center;\n\n        img {\n            border-radius: 50px;\n            width: 24px;\n            height: 24px;\n            margin-right: 6px;\n        }\n    }\n\n    .react-select__menu {\n        background: rgba(var(--center-channel-bg-rgb), 1);\n        box-shadow: var(--elevation-4);\n        border: 1px solid rgba(var(--center-channel-color-rgb), 0.16);\n        border-radius: 4px;\n    }\n\n    .react-select__single-value {\n        margin: 0;\n        position: relative;\n        top: 0;\n        max-width: 100%;\n    }\n\n    .react-select__value-container--is-multi {\n        gap: 4px;\n        display: inline-flex;\n\n        .react-select__multi-value__label {\n            padding-left: 4px;\n        }\n\n        .react-select__multi-value {\n            background: rgba(var(--center-channel-color-rgb), 0.08);\n            border-radius: 24px;\n            display: inline-flex;\n            color: rgb(var(--center-channel-color-rgb));\n            margin: 0;\n            align-items: center;\n\n            .MultiPerson-item,\n            .react-select__multi-value__label {\n                color: inherit;\n            }\n        }\n    }\n\n    .react-select__multi-value__remove {\n        font-size: 18px;\n        color: rgba(var(--center-channel-color-rgb), 0.56);\n        margin: 6px;\n        border-radius: 100%;\n        margin-left: 0;\n        padding: 0;\n        width: 20px;\n        height: 20px;\n        display: flex;\n        align-items: center;\n        justify-content: center;\n\n        &:hover {\n            background: rgba(var(--center-channel-color-rgb), 0.26);\n        }\n    }\n\n    .react-select__option {\n        display: flex;\n        align-items: center;\n        height: 40px;\n        padding: 0 40px 0 20px;\n\n        &:hover {\n            background: rgba(var(--center-channel-color-rgb), 0.08);\n        }\n\n        &:active {\n            background: rgba(var(--button-bg-rgb), 0.08);\n        }\n\n        &.react-select__option--is-selected {\n            background: rgba(var(--button-bg-rgb), 0.08);\n            color: rgba(var(--center-channel-color-rgb), 1);\n        }\n\n        .MultiPerson-item {\n            img {\n                margin-right: 12px;\n            }\n        }\n    }\n\n    .react-select__menu-list {\n        border: 0;\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/viewHeader/multipersonFilterValue.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {useIntl} from 'react-intl'\n\nimport {MultiValue} from 'react-select'\n\nimport {Utils} from '../../utils'\nimport mutator from '../../mutator'\nimport {BoardView} from '../../blocks/boardView'\n\nimport {FilterClause} from '../../blocks/filterClause'\nimport {createFilterGroup} from '../../blocks/filterGroup'\n\nimport PersonSelector from '../personSelector'\nimport {IUser} from '../../user'\n\nimport './multiperson.scss'\n\ntype Props = {\n    view: BoardView\n    filter: FilterClause\n}\n\nconst MultiPersonFilterValue = (props: Props): JSX.Element => {\n    const {filter, view} = props\n    const intl = useIntl()\n    const emptyDisplayValue = intl.formatMessage({id: 'ConfirmPerson.search', defaultMessage: 'Search...'})\n\n    return (\n        <PersonSelector\n            userIDs={filter.values}\n            allowAddUsers={false}\n            isMulti={true}\n            readOnly={false}\n            emptyDisplayValue={emptyDisplayValue}\n            showMe={true}\n            closeMenuOnSelect={false}\n            onChange={(items: MultiValue<IUser>, action) => {\n                const filterIndex = view.fields.filter.filters.indexOf(filter)\n                Utils.assert(filterIndex >= 0, \"Can't find filter\")\n\n                const filterGroup = createFilterGroup(view.fields.filter)\n                const newFilter = filterGroup.filters[filterIndex] as FilterClause\n                Utils.assert(newFilter, `No filter at index ${filterIndex}`)\n\n                if (action.action === 'select-option') {\n                    newFilter.values = items.map((a) => a.id)\n                } else if (action.action === 'clear') {\n                    newFilter.values = []\n                } else if (action.action === 'remove-value') {\n                    newFilter.values = items.filter((a) => a.id !== action.removedValue.id).map((b) => b.id) || []\n                }\n                mutator.changeViewFilter(view.boardId, view.id, view.fields.filter, filterGroup)\n            }}\n        />\n    )\n}\n\nexport default MultiPersonFilterValue\n"
  },
  {
    "path": "webapp/src/components/viewHeader/newCardButton.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\nimport {render, screen} from '@testing-library/react'\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport '@testing-library/jest-dom'\nimport userEvent from '@testing-library/user-event'\n\nimport {wrapIntl, mockStateStore} from '../../testUtils'\n\nimport {TestBlockFactory} from '../../test/testBlockFactory'\n\nimport NewCardButton from './newCardButton'\n\nconst board = TestBlockFactory.createBoard()\nconst activeView = TestBlockFactory.createBoardView(board)\n\ndescribe('components/viewHeader/newCardButton', () => {\n    const state = {\n        users: {\n            me: {\n                id: 'user-id-1',\n                username: 'username_1'},\n        },\n        boards: {\n            current: board.id,\n            boards: {\n                [board.id]: board,\n            },\n        },\n        cards: {\n            templates: [],\n        },\n        views: {\n            current: 0,\n            views: [activeView],\n        },\n    }\n\n    const store = mockStateStore([], state)\n    const mockFunction = jest.fn()\n\n    beforeEach(() => {\n        jest.clearAllMocks()\n    })\n    test('return NewCardButton', () => {\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <NewCardButton\n                        addCard={jest.fn()}\n                        addCardTemplate={jest.fn()}\n                        addCardFromTemplate={jest.fn()}\n                        editCardTemplate={jest.fn()}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})\n        userEvent.click(buttonElement)\n        expect(container).toMatchSnapshot()\n    })\n    test('return NewCardButton and addCard', () => {\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <NewCardButton\n                        addCard={mockFunction}\n                        addCardTemplate={jest.fn()}\n                        addCardFromTemplate={jest.fn()}\n                        editCardTemplate={jest.fn()}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})\n        userEvent.click(buttonElement)\n        expect(container).toMatchSnapshot()\n        const buttonAdd = screen.getByRole('button', {name: 'Empty card'})\n        userEvent.click(buttonAdd)\n        expect(mockFunction).toBeCalledTimes(1)\n    })\n    test('return NewCardButton and addCardTemplate', () => {\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <NewCardButton\n                        addCard={jest.fn()}\n                        addCardTemplate={mockFunction}\n                        addCardFromTemplate={jest.fn()}\n                        editCardTemplate={jest.fn()}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})\n        userEvent.click(buttonElement)\n        expect(container).toMatchSnapshot()\n        const buttonAddTemplate = screen.getByRole('button', {name: 'New template'})\n        userEvent.click(buttonAddTemplate)\n        expect(mockFunction).toBeCalledTimes(1)\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/viewHeader/newCardButton.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {FormattedMessage, useIntl} from 'react-intl'\n\nimport {Card} from '../../blocks/card'\nimport ButtonWithMenu from '../../widgets/buttons/buttonWithMenu'\nimport AddIcon from '../../widgets/icons/add'\nimport Menu from '../../widgets/menu'\nimport {useAppSelector} from '../../store/hooks'\nimport {getCurrentBoardTemplates} from '../../store/cards'\nimport {getCurrentView} from '../../store/views'\n\nimport NewCardButtonTemplateItem from './newCardButtonTemplateItem'\nimport EmptyCardButton from './emptyCardButton'\n\ntype Props = {\n    addCard: () => void\n    addCardFromTemplate: (cardTemplateId: string) => void\n    addCardTemplate: () => void\n    editCardTemplate: (cardTemplateId: string) => void\n}\n\nconst NewCardButton = (props: Props): JSX.Element => {\n    const cardTemplates: Card[] = useAppSelector(getCurrentBoardTemplates)\n    const currentView = useAppSelector(getCurrentView)\n    let defaultTemplateID = ''\n    const intl = useIntl()\n\n    return (\n        <ButtonWithMenu\n            onClick={() => {\n                if (defaultTemplateID) {\n                    props.addCardFromTemplate(defaultTemplateID)\n                } else {\n                    props.addCard()\n                }\n            }}\n            text={(\n                <FormattedMessage\n                    id='ViewHeader.new'\n                    defaultMessage='New'\n                />\n            )}\n        >\n            <Menu position='left'>\n                {cardTemplates.length > 0 && <>\n                    <Menu.Label>\n                        <b>\n                            <FormattedMessage\n                                id='ViewHeader.select-a-template'\n                                defaultMessage='Select a template'\n                            />\n                        </b>\n                    </Menu.Label>\n\n                    <Menu.Separator/>\n                </>}\n\n                {cardTemplates.map((cardTemplate) => {\n                    if (cardTemplate.id === currentView.fields.defaultTemplateId) {\n                        defaultTemplateID = currentView.fields.defaultTemplateId\n                    }\n                    return (\n                        <NewCardButtonTemplateItem\n                            key={cardTemplate.id}\n                            cardTemplate={cardTemplate}\n                            addCardFromTemplate={props.addCardFromTemplate}\n                            editCardTemplate={props.editCardTemplate}\n                        />\n                    )\n                })}\n\n                <EmptyCardButton\n                    addCard={props.addCard}\n                />\n\n                <Menu.Text\n                    icon={<AddIcon/>}\n                    id='add-template'\n                    name={intl.formatMessage({id: 'ViewHeader.add-template', defaultMessage: 'New template'})}\n                    onClick={() => props.addCardTemplate()}\n                />\n            </Menu>\n        </ButtonWithMenu>\n    )\n}\n\nexport default React.memo(NewCardButton)\n"
  },
  {
    "path": "webapp/src/components/viewHeader/newCardButtonTemplateItem.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {render, screen} from '@testing-library/react'\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport '@testing-library/jest-dom'\nimport userEvent from '@testing-library/user-event'\n\nimport {mocked} from 'jest-mock'\n\nimport {wrapIntl, mockStateStore} from '../../testUtils'\n\nimport {TestBlockFactory} from '../../test/testBlockFactory'\n\nimport mutator from '../../mutator'\n\nimport NewCardButtonTemplateItem from './newCardButtonTemplateItem'\n\njest.mock('../../mutator')\nconst mockedMutator = mocked(mutator, true)\n\nconst board = TestBlockFactory.createBoard()\nconst activeView = TestBlockFactory.createBoardView(board)\nconst card = TestBlockFactory.createCard(board)\n\ndescribe('components/viewHeader/newCardButtonTemplateItem', () => {\n    const state = {\n        users: {\n            me: {\n                id: 'user-id-1',\n                username: 'username_1'},\n        },\n        boards: {\n            current: board.id,\n            boards: {\n                [board.id]: {id: board.id},\n            },\n        },\n        views: {\n            current: 0,\n            views: [activeView],\n        },\n    }\n\n    const store = mockStateStore([], state)\n    const mockFunction = jest.fn()\n\n    beforeEach(() => {\n        jest.clearAllMocks()\n    })\n    test('return NewCardButtonTemplateItem', () => {\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <NewCardButtonTemplateItem\n                        cardTemplate={card}\n                        addCardFromTemplate={jest.fn()}\n                        editCardTemplate={jest.fn()}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})\n        userEvent.click(buttonElement)\n        expect(container).toMatchSnapshot()\n    })\n    test('return NewCardButtonTemplateItem and edit', () => {\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <NewCardButtonTemplateItem\n                        cardTemplate={card}\n                        addCardFromTemplate={jest.fn()}\n                        editCardTemplate={mockFunction}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})\n        userEvent.click(buttonElement)\n        expect(container).toMatchSnapshot()\n        const buttonEdit = screen.getByRole('button', {name: 'Edit'})\n        userEvent.click(buttonEdit)\n        expect(mockFunction).toBeCalledTimes(1)\n        expect(mockFunction).toBeCalledWith(card.id)\n    })\n\n    test('return NewCardButtonTemplateItem and add Card from template', () => {\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <NewCardButtonTemplateItem\n                        cardTemplate={card}\n                        addCardFromTemplate={mockFunction}\n                        editCardTemplate={jest.fn()}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        const buttonAdd = screen.getByRole('button', {name: 'title'})\n        userEvent.click(buttonAdd)\n        expect(container).toMatchSnapshot()\n        expect(mockFunction).toBeCalledTimes(1)\n    })\n    test('return NewCardButtonTemplateItem and delete', () => {\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <NewCardButtonTemplateItem\n                        cardTemplate={card}\n                        addCardFromTemplate={jest.fn()}\n                        editCardTemplate={jest.fn()}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})\n        userEvent.click(buttonElement)\n        expect(container).toMatchSnapshot()\n        const buttonDelete = screen.getByRole('button', {name: 'Delete'})\n        userEvent.click(buttonDelete)\n        expect(mockedMutator.performAsUndoGroup).toBeCalledTimes(1)\n    })\n    test('return NewCardButtonTemplateItem and Set as default', () => {\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <NewCardButtonTemplateItem\n                        cardTemplate={card}\n                        addCardFromTemplate={jest.fn()}\n                        editCardTemplate={jest.fn()}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})\n        userEvent.click(buttonElement)\n        expect(container).toMatchSnapshot()\n        const buttonSetAsDefault = screen.getByRole('button', {name: 'Set as default'})\n        userEvent.click(buttonSetAsDefault)\n        expect(mockedMutator.setDefaultTemplate).toBeCalledTimes(1)\n        expect(mockedMutator.setDefaultTemplate).toBeCalledWith(activeView.boardId, activeView.id, activeView.fields.defaultTemplateId, card.id)\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/viewHeader/newCardButtonTemplateItem.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {useIntl} from 'react-intl'\n\nimport mutator from '../../mutator'\nimport {Card} from '../../blocks/card'\nimport IconButton from '../../widgets/buttons/iconButton'\nimport DeleteIcon from '../../widgets/icons/delete'\nimport EditIcon from '../../widgets/icons/edit'\nimport OptionsIcon from '../../widgets/icons/options'\nimport Menu from '../../widgets/menu'\nimport MenuWrapper from '../../widgets/menuWrapper'\nimport CheckIcon from '../../widgets/icons/check'\nimport {useAppSelector} from '../../store/hooks'\nimport {getCurrentView} from '../../store/views'\nimport {getCurrentBoardId} from '../../store/boards'\n\ntype Props = {\n    cardTemplate: Card\n    addCardFromTemplate: (cardTemplateId: string) => void\n    editCardTemplate: (cardTemplateId: string) => void\n}\n\nconst NewCardButtonTemplateItem = (props: Props) => {\n    const currentView = useAppSelector(getCurrentView)\n    const {cardTemplate} = props\n    const intl = useIntl()\n    const displayName = cardTemplate.title || intl.formatMessage({id: 'ViewHeader.untitled', defaultMessage: 'Untitled'})\n    const isDefaultTemplate = currentView.fields.defaultTemplateId === cardTemplate.id\n    const boardId = useAppSelector(getCurrentBoardId)\n\n    return (\n        <Menu.Text\n            key={cardTemplate.id}\n            id={cardTemplate.id}\n            name={displayName}\n            icon={<div className='Icon'>{cardTemplate.fields.icon}</div>}\n            className={isDefaultTemplate ? 'bold-menu-text' : ''}\n            onClick={() => {\n                props.addCardFromTemplate(cardTemplate.id)\n            }}\n            rightIcon={\n                <MenuWrapper stopPropagationOnToggle={true}>\n                    <IconButton icon={<OptionsIcon/>}/>\n                    <Menu position='left'>\n                        <Menu.Text\n                            icon={<CheckIcon/>}\n                            id='default'\n                            name={intl.formatMessage({id: 'ViewHeader.set-default-template', defaultMessage: 'Set as default'})}\n                            onClick={async () => {\n                                await mutator.setDefaultTemplate(boardId, currentView.id, currentView.fields.defaultTemplateId, cardTemplate.id)\n                            }}\n                        />\n                        <Menu.Text\n                            icon={<EditIcon/>}\n                            id='edit'\n                            name={intl.formatMessage({id: 'ViewHeader.edit-template', defaultMessage: 'Edit'})}\n                            onClick={() => {\n                                props.editCardTemplate(cardTemplate.id)\n                            }}\n                        />\n                        <Menu.Text\n                            icon={<DeleteIcon/>}\n                            id='delete'\n                            name={intl.formatMessage({id: 'ViewHeader.delete-template', defaultMessage: 'Delete'})}\n                            onClick={async () => {\n                                await mutator.performAsUndoGroup(async () => {\n                                    if (currentView.fields.defaultTemplateId === cardTemplate.id) {\n                                        await mutator.clearDefaultTemplate(boardId, currentView.id, currentView.fields.defaultTemplateId)\n                                    }\n                                    await mutator.deleteBlock(cardTemplate, 'delete card template')\n                                })\n                            }}\n                        />\n                    </Menu>\n                </MenuWrapper>\n            }\n        />\n    )\n}\n\nexport default React.memo(NewCardButtonTemplateItem)\n"
  },
  {
    "path": "webapp/src/components/viewHeader/viewHeader.scss",
    "content": ".ViewHeader {\n    flex: 0 0 auto;\n    display: flex;\n    flex-direction: row;\n\n    border-bottom: solid 1px rgba(var(--center-channel-color-rgb), 0.16);\n    margin: 16px 0 0;\n    padding: 8px 0;\n    color: rgba(var(--center-channel-color-rgb), 0.64);\n    align-items: center;\n\n    @media (max-width: 768px) {\n        overflow: auto;\n        -webkit-overflow-scrolling: touch;\n    }\n\n    > div {\n        margin-right: 12px;\n        white-space: nowrap;\n\n        &:last-child {\n            margin: 0;\n        }\n    }\n\n    #groupByLabel {\n        margin-left: 0.3em;\n    }\n\n    .IconButton {\n        background: none;\n        padding: 0;\n\n        .Icon {\n            width: 24px;\n            height: 24px;\n            margin: 0;\n        }\n\n        &:hover {\n            background: rgba(var(--center-channel-color-rgb), 0.1);\n        }\n    }\n\n    .viewSelector {\n        display: flex;\n        flex-direction: row;\n        position: relative;\n\n        .MenuWrapper {\n            display: flex;\n            align-items: center;\n        }\n    }\n}\n\n.board-search-field {\n    position: relative;\n\n    input {\n        font-size: 12px;\n        border-radius: 4px;\n        padding: 0 12px 0 32px;\n        height: 32px;\n        border: 1px solid rgba(var(--center-channel-color-rgb), 0.16);\n        padding-bottom: 1px;\n\n        &:focus {\n            border-color: rgba(var(--button-bg-rgb), 1);\n        }\n    }\n\n    .board-search-icon {\n        position: absolute;\n        left: 10px;\n        color: rgba(var(--center-channel-color-rgb), 0.64);\n        display: flex;\n        align-items: center;\n        height: 100%;\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/viewHeader/viewHeader.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {render} from '@testing-library/react'\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport '@testing-library/jest-dom'\n\nimport {TestBlockFactory} from '../../test/testBlockFactory'\n\nimport {wrapIntl, mockStateStore} from '../../testUtils'\n\nimport ViewHeader from './viewHeader'\n\nconst board = TestBlockFactory.createBoard()\nconst activeView = TestBlockFactory.createBoardView(board)\nconst card = TestBlockFactory.createCard(board)\nconst card2 = TestBlockFactory.createCard(board)\n\njest.mock('react-router-dom', () => {\n    const originalModule = jest.requireActual('react-router-dom')\n\n    return {\n        ...originalModule,\n        useRouteMatch: jest.fn(() => {\n            return {url: '/board/view'}\n        }),\n    }\n})\n\ndescribe('components/viewHeader/viewHeader', () => {\n    const state = {\n        users: {\n            me: {\n                id: 'user-id-1',\n                username: 'username_1',\n                props: {},\n            },\n        },\n        searchText: {\n        },\n        teams: {\n            current: {id: 'team-id'},\n        },\n        boards: {\n            current: board.id,\n            boards: {\n                [board.id]: board,\n            },\n            templates: [],\n            myBoardMemberships: {\n                [board.id]: {userId: 'user_id_1', schemeAdmin: true},\n            },\n        },\n        cards: {\n            templates: [card],\n            cards: {\n                [card2.id]: card2,\n            },\n            current: card2.id,\n        },\n        views: {\n            views: {\n                boardView: activeView,\n            },\n            current: 'boardView',\n        },\n        limits: {\n            limits: {\n                views: 0,\n            },\n        },\n    }\n    const store = mockStateStore([], state)\n    test('return viewHeader', () => {\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <ViewHeader\n                        board={board}\n                        activeView={activeView}\n                        views={[activeView]}\n                        cards={[card]}\n                        groupByProperty={board.cardProperties[0]}\n                        addCard={jest.fn()}\n                        addCardFromTemplate={jest.fn()}\n                        addCardTemplate={jest.fn()}\n                        editCardTemplate={jest.fn()}\n                        readonly={false}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        expect(container).toMatchSnapshot()\n    })\n    test('return viewHeader without permissions', () => {\n        const localStore = mockStateStore([], {...state, teams: {current: undefined}})\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={localStore}>\n                    <ViewHeader\n                        board={board}\n                        activeView={activeView}\n                        views={[activeView]}\n                        cards={[card]}\n                        groupByProperty={board.cardProperties[0]}\n                        addCard={jest.fn()}\n                        addCardFromTemplate={jest.fn()}\n                        addCardTemplate={jest.fn()}\n                        editCardTemplate={jest.fn()}\n                        readonly={false}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        expect(container).toMatchSnapshot()\n    })\n    test('return viewHeader readonly', () => {\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <ViewHeader\n                        board={board}\n                        activeView={activeView}\n                        views={[activeView]}\n                        cards={[card]}\n                        groupByProperty={board.cardProperties[0]}\n                        addCard={jest.fn()}\n                        addCardFromTemplate={jest.fn()}\n                        addCardTemplate={jest.fn()}\n                        editCardTemplate={jest.fn()}\n                        readonly={true}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        expect(container).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/viewHeader/viewHeader.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useState, useEffect} from 'react'\nimport {FormattedMessage, useIntl} from 'react-intl'\n\nimport ViewMenu from '../../components/viewMenu'\nimport mutator from '../../mutator'\nimport {Board, IPropertyTemplate} from '../../blocks/board'\nimport {BoardView} from '../../blocks/boardView'\nimport {Card} from '../../blocks/card'\nimport Button from '../../widgets/buttons/button'\nimport IconButton from '../../widgets/buttons/iconButton'\nimport DropdownIcon from '../../widgets/icons/dropdown'\nimport MenuWrapper from '../../widgets/menuWrapper'\nimport Editable from '../../widgets/editable'\n\nimport ModalWrapper from '../modalWrapper'\n\nimport {useAppSelector} from '../../store/hooks'\nimport {Permission} from '../../constants'\nimport {useHasCurrentBoardPermissions} from '../../hooks/permissions'\nimport {\n    getOnboardingTourCategory,\n    getOnboardingTourStarted,\n    getOnboardingTourStep,\n} from '../../store/users'\nimport {\n    BoardTourSteps,\n    TOUR_BOARD,\n    TourCategoriesMapToSteps,\n} from '../onboardingTour'\nimport {OnboardingBoardTitle} from '../cardDetail/cardDetail'\nimport AddViewTourStep from '../onboardingTour/addView/add_view'\nimport {getCurrentCard} from '../../store/cards'\nimport BoardPermissionGate from '../permissions/boardPermissionGate'\n\nimport NewCardButton from './newCardButton'\nimport ViewHeaderPropertiesMenu from './viewHeaderPropertiesMenu'\nimport ViewHeaderGroupByMenu from './viewHeaderGroupByMenu'\nimport ViewHeaderDisplayByMenu from './viewHeaderDisplayByMenu'\nimport ViewHeaderSortMenu from './viewHeaderSortMenu'\nimport ViewHeaderActionsMenu from './viewHeaderActionsMenu'\nimport ViewHeaderSearch from './viewHeaderSearch'\nimport FilterComponent from './filterComponent'\n\nimport './viewHeader.scss'\n\ntype Props = {\n    board: Board\n    activeView: BoardView\n    views: BoardView[]\n    cards: Card[]\n    groupByProperty?: IPropertyTemplate\n    addCard: () => void\n    addCardFromTemplate: (cardTemplateId: string) => void\n    addCardTemplate: () => void\n    editCardTemplate: (cardTemplateId: string) => void\n    readonly: boolean\n    dateDisplayProperty?: IPropertyTemplate\n}\n\nconst ViewHeader = (props: Props) => {\n    const [showFilter, setShowFilter] = useState(false)\n    const [lockFilterOnClose, setLockFilterOnClose] = useState(false)\n    const intl = useIntl()\n    const canEditBoardProperties = useHasCurrentBoardPermissions([Permission.ManageBoardProperties])\n\n    const {board, activeView, views, groupByProperty, cards, dateDisplayProperty} = props\n\n    const withGroupBy = activeView.fields.viewType === 'board' || activeView.fields.viewType === 'table'\n    const withDisplayBy = activeView.fields.viewType === 'calendar'\n    const withSortBy = activeView.fields.viewType !== 'calendar'\n\n    const [viewTitle, setViewTitle] = useState(activeView.title)\n\n    useEffect(() => {\n        setViewTitle(activeView.title)\n    }, [activeView.title])\n\n    const hasFilter = activeView.fields.filter && activeView.fields.filter.filters?.length > 0\n\n    const isOnboardingBoard = props.board.title === OnboardingBoardTitle\n    const onboardingTourStarted = useAppSelector(getOnboardingTourStarted)\n    const onboardingTourCategory = useAppSelector(getOnboardingTourCategory)\n    const onboardingTourStep = useAppSelector(getOnboardingTourStep)\n\n    const currentCard = useAppSelector(getCurrentCard)\n    const noCardOpen = !currentCard\n\n    const showTourBaseCondition = isOnboardingBoard &&\n        onboardingTourStarted &&\n        noCardOpen &&\n        onboardingTourCategory === TOUR_BOARD &&\n        onboardingTourStep === BoardTourSteps.ADD_VIEW.toString()\n\n    const [delayComplete, setDelayComplete] = useState(false)\n\n    useEffect(() => {\n        if (showTourBaseCondition) {\n            setTimeout(() => {\n                setDelayComplete(true)\n            }, 800)\n        }\n    }, [showTourBaseCondition])\n\n    useEffect(() => {\n        if (!BoardTourSteps.SHARE_BOARD) {\n            BoardTourSteps.SHARE_BOARD = 2\n        }\n\n        TourCategoriesMapToSteps[TOUR_BOARD] = BoardTourSteps\n    }, [])\n\n    const showAddViewTourStep = showTourBaseCondition && delayComplete\n\n    return (\n        <div className='ViewHeader'>\n            <div className='viewSelector'>\n                <Editable\n                    value={viewTitle}\n                    placeholderText='Untitled View'\n                    onSave={(): void => {\n                        mutator.changeBlockTitle(activeView.boardId, activeView.id, activeView.title, viewTitle)\n                    }}\n                    onCancel={(): void => {\n                        setViewTitle(activeView.title)\n                    }}\n                    onChange={setViewTitle}\n                    saveOnEsc={true}\n                    readonly={props.readonly || !canEditBoardProperties}\n                    spellCheck={true}\n                    autoExpand={false}\n                />\n                {!props.readonly && (<div>\n                    <MenuWrapper label={intl.formatMessage({id: 'ViewHeader.view-menu', defaultMessage: 'View menu'})}>\n                        <IconButton icon={<DropdownIcon/>}/>\n                        <ViewMenu\n                            board={board}\n                            activeView={activeView}\n                            views={views}\n                            readonly={props.readonly || !canEditBoardProperties}\n                        />\n                    </MenuWrapper>\n                    {showAddViewTourStep && <AddViewTourStep/>}\n                </div>)}\n\n            </div>\n\n            <div className='octo-spacer'/>\n\n            {!props.readonly && canEditBoardProperties &&\n            <>\n                {/* Card properties */}\n\n                <ViewHeaderPropertiesMenu\n                    properties={board.cardProperties}\n                    activeView={activeView}\n                />\n\n                {/* Group by */}\n\n                {withGroupBy &&\n                <ViewHeaderGroupByMenu\n                    properties={board.cardProperties}\n                    activeView={activeView}\n                    groupByProperty={groupByProperty}\n                />}\n\n                {/* Display by */}\n\n                {withDisplayBy &&\n                <ViewHeaderDisplayByMenu\n                    properties={board.cardProperties}\n                    activeView={activeView}\n                    dateDisplayPropertyName={dateDisplayProperty?.name}\n                />}\n\n                {/* Filter */}\n\n                <ModalWrapper>\n                    <Button\n                        active={hasFilter}\n                        onClick={() => setShowFilter(!showFilter)}\n                        onMouseOver={() => setLockFilterOnClose(true)}\n                        onMouseLeave={() => setLockFilterOnClose(false)}\n                    >\n                        <FormattedMessage\n                            id='ViewHeader.filter'\n                            defaultMessage='Filter'\n                        />\n                    </Button>\n                    {showFilter &&\n                    <FilterComponent\n                        board={board}\n                        activeView={activeView}\n                        onClose={() => {\n                            if (!lockFilterOnClose) {\n                                setShowFilter(false)\n                            }\n                        }}\n                    />}\n                </ModalWrapper>\n\n                {/* Sort */}\n\n                {withSortBy &&\n                <ViewHeaderSortMenu\n                    properties={board.cardProperties}\n                    activeView={activeView}\n                    orderedCards={cards}\n                />\n                }\n            </>\n            }\n\n            {/* Search */}\n\n            <ViewHeaderSearch/>\n\n            {/* Options menu */}\n\n            {!props.readonly &&\n            <>\n                <ViewHeaderActionsMenu\n                    board={board}\n                    activeView={activeView}\n                    cards={cards}\n                />\n\n                {/* New card button */}\n\n                <BoardPermissionGate permissions={[Permission.ManageBoardCards]}>\n                    <NewCardButton\n                        addCard={props.addCard}\n                        addCardFromTemplate={props.addCardFromTemplate}\n                        addCardTemplate={props.addCardTemplate}\n                        editCardTemplate={props.editCardTemplate}\n                    />\n                </BoardPermissionGate>\n            </>}\n        </div>\n    )\n}\n\nexport default React.memo(ViewHeader)\n"
  },
  {
    "path": "webapp/src/components/viewHeader/viewHeaderActionsMenu.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {render, screen} from '@testing-library/react'\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport '@testing-library/jest-dom'\nimport userEvent from '@testing-library/user-event'\n\nimport {mocked} from 'jest-mock'\n\nimport {TestBlockFactory} from '../../test/testBlockFactory'\n\nimport {wrapIntl, mockStateStore} from '../../testUtils'\n\nimport {Archiver} from '../../archiver'\n\nimport {CsvExporter} from '../../csvExporter'\n\nimport ViewHeaderActionsMenu from './viewHeaderActionsMenu'\n\njest.mock('../../archiver')\njest.mock('../../csvExporter')\njest.mock('../../mutator')\nconst mockedArchiver = mocked(Archiver, true)\nconst mockedCsvExporter = mocked(CsvExporter, true)\n\nconst board = TestBlockFactory.createBoard()\nconst activeView = TestBlockFactory.createBoardView(board)\nconst card = TestBlockFactory.createCard(board)\n\ndescribe('components/viewHeader/viewHeaderActionsMenu', () => {\n    const state = {\n        users: {\n            me: {\n                id: 'user-id-1',\n                username: 'username_1',\n            },\n        },\n    }\n    const store = mockStateStore([], state)\n    beforeEach(() => {\n        jest.clearAllMocks()\n    })\n\n    test('return menu', () => {\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <ViewHeaderActionsMenu\n                        board={board}\n                        activeView={activeView}\n                        cards={[card]}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        const buttonElement = screen.getByRole('button', {\n            name: 'View header menu',\n        })\n        userEvent.click(buttonElement)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('return menu and verify call to csv exporter', () => {\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <ViewHeaderActionsMenu\n                        board={board}\n                        activeView={activeView}\n                        cards={[card]}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        const buttonElement = screen.getByRole('button', {name: 'View header menu'})\n        userEvent.click(buttonElement)\n        expect(container).toMatchSnapshot()\n        const buttonExportCSV = screen.getByRole('button', {name: 'Export to CSV'})\n        userEvent.click(buttonExportCSV)\n        expect(mockedCsvExporter.exportTableCsv).toBeCalledTimes(1)\n    })\n\n    test('return menu and verify call to board archive', () => {\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <ViewHeaderActionsMenu\n                        board={board}\n                        activeView={activeView}\n                        cards={[card]}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        const buttonElement = screen.getByRole('button', {name: 'View header menu'})\n        userEvent.click(buttonElement)\n        expect(container).toMatchSnapshot()\n        const buttonExportBoardArchive = screen.getByRole('button', {name: 'Export board archive'})\n        userEvent.click(buttonExportBoardArchive)\n        expect(mockedArchiver.exportBoardArchive).toBeCalledTimes(1)\n        expect(mockedArchiver.exportBoardArchive).toBeCalledWith(board)\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/viewHeader/viewHeaderActionsMenu.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\nimport {useIntl, IntlShape} from 'react-intl'\n\nimport {CsvExporter} from '../../csvExporter'\nimport {Archiver} from '../../archiver'\nimport {Board} from '../../blocks/board'\nimport {BoardView} from '../../blocks/boardView'\nimport {Card} from '../../blocks/card'\nimport IconButton from '../../widgets/buttons/iconButton'\nimport OptionsIcon from '../../widgets/icons/options'\nimport Menu from '../../widgets/menu'\nimport MenuWrapper from '../../widgets/menuWrapper'\nimport {Utils} from '../../utils'\n\nimport ModalWrapper from '../modalWrapper'\nimport {sendFlashMessage} from '../flashMessages'\n\ntype Props = {\n    board: Board\n    activeView: BoardView\n    cards: Card[]\n}\n\n// import {mutator} from '../../mutator'\n// import {CardFilter} from '../../cardFilter'\n// import {BlockIcons} from '../../blockIcons'\n// async function testAddCards(board: Board, activeView: BoardView, startCount: number, count: number) {\n//     let optionIndex = 0\n\n//     mutator.performAsUndoGroup(async () => {\n//         for (let i = 0; i < count; i++) {\n//             const card = new Card()\n//             card.parentId = board.id\n//             card.boardId = board.boardId\n//             card.fields.properties = CardFilter.propertiesThatMeetFilterGroup(activeView.fields.filter, board.cardProperties)\n//             card.title = `Test Card ${startCount + i + 1}`\n//             card.fields.icon = BlockIcons.shared.randomIcon()\n\n//             const groupByProperty = board.cardProperties.find((o) => o.id === activeView.fields.groupById)\n//             if (groupByProperty && groupByProperty.options.length > 0) {\n//                 // Cycle through options\n//                 const option = groupByProperty.options[optionIndex]\n//                 optionIndex = (optionIndex + 1) % groupByProperty.options.length\n//                 card.fields.properties[groupByProperty.id] = option.id\n//             }\n//             mutator.insertBlock(card, 'test add card')\n//         }\n//     })\n// }\n\n// async function testDistributeCards(boardTree: BoardTree) {\n//     mutator.performAsUndoGroup(async () => {\n//         let optionIndex = 0\n//         for (const card of boardTree.cards) {\n//             if (boardTree.groupByProperty && boardTree.groupByProperty.options.length > 0) {\n//                 // Cycle through options\n//                 const option = boardTree.groupByProperty.options[optionIndex]\n//                 optionIndex = (optionIndex + 1) % boardTree.groupByProperty.options.length\n//                 const newCard = new Card(card)\n//                 if (newCard.properties[boardTree.groupByProperty.id] !== option.id) {\n//                     newCard.properties[boardTree.groupByProperty.id] = option.id\n//                     mutator.updateBlock(newCard, card, 'test distribute cards')\n//                 }\n//             }\n//         }\n//     })\n// }\n\n// async function testRandomizeIcons(boardTree: BoardTree) {\n//     mutator.performAsUndoGroup(async () => {\n//         for (const card of boardTree.cards) {\n//             mutator.changeIcon(card.id, card.fields.icon, BlockIcons.shared.randomIcon(), 'randomize icon')\n//         }\n//     })\n// }\n\nfunction onExportCsvTrigger(board: Board, activeView: BoardView, cards: Card[], intl: IntlShape) {\n    try {\n        CsvExporter.exportTableCsv(board, activeView, cards, intl)\n        const exportCompleteMessage = intl.formatMessage({\n            id: 'ViewHeader.export-complete',\n            defaultMessage: 'Export complete!',\n        })\n        sendFlashMessage({content: exportCompleteMessage, severity: 'normal'})\n    } catch (e) {\n        Utils.logError(`ExportCSV ERROR: ${e}`)\n        const exportFailedMessage = intl.formatMessage({\n            id: 'ViewHeader.export-failed',\n            defaultMessage: 'Export failed!',\n        })\n        sendFlashMessage({content: exportFailedMessage, severity: 'high'})\n    }\n}\n\nconst ViewHeaderActionsMenu = (props: Props) => {\n    const {board, activeView, cards} = props\n    const intl = useIntl()\n\n    return (\n        <ModalWrapper>\n            <MenuWrapper label={intl.formatMessage({id: 'ViewHeader.view-header-menu', defaultMessage: 'View header menu'})}>\n                <IconButton icon={<OptionsIcon/>}/>\n                <Menu position='left'>\n                    <Menu.Text\n                        id='exportCsv'\n                        name={intl.formatMessage({id: 'ViewHeader.export-csv', defaultMessage: 'Export to CSV'})}\n                        onClick={() => onExportCsvTrigger(board, activeView, cards, intl)}\n                    />\n                    <Menu.Text\n                        id='exportBoardArchive'\n                        name={intl.formatMessage({id: 'ViewHeader.export-board-archive', defaultMessage: 'Export board archive'})}\n                        onClick={() => Archiver.exportBoardArchive(board)}\n                    />\n                    {/*\n                    <Menu.Separator/>\n\n                    <Menu.Text\n                        id='testAdd100Cards'\n                        name={intl.formatMessage({id: 'ViewHeader.test-add-100-cards', defaultMessage: 'TEST: Add 100 cards'})}\n                        onClick={() => testAddCards(board, activeView, cards.length, 100)}\n                    />\n                    <Menu.Text\n                        id='testAdd1000Cards'\n                        name={intl.formatMessage({id: 'ViewHeader.test-add-1000-cards', defaultMessage: 'TEST: Add 1,000 cards'})}\n                        onClick={() => testAddCards(board, activeView, cards.length, 1000)}\n                    />\n                    <Menu.Text\n                        id='testDistributeCards'\n                        name={intl.formatMessage({id: 'ViewHeader.test-distribute-cards', defaultMessage: 'TEST: Distribute cards'})}\n                        onClick={() => testDistributeCards()}\n                    />\n                    <Menu.Text\n                        id='testRandomizeIcons'\n                        name={intl.formatMessage({id: 'ViewHeader.test-randomize-icons', defaultMessage: 'TEST: Randomize icons'})}\n                        onClick={() => testRandomizeIcons()}\n                    />\n                    */}\n                </Menu>\n            </MenuWrapper>\n        </ModalWrapper>\n    )\n}\n\nexport default React.memo(ViewHeaderActionsMenu)\n"
  },
  {
    "path": "webapp/src/components/viewHeader/viewHeaderDisplayByMenu.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {FormattedMessage, useIntl} from 'react-intl'\n\nimport {DatePropertyType} from '../../properties/types'\n\nimport {IPropertyTemplate} from '../../blocks/board'\nimport {BoardView} from '../../blocks/boardView'\nimport mutator from '../../mutator'\nimport Button from '../../widgets/buttons/button'\nimport Menu from '../../widgets/menu'\nimport MenuWrapper from '../../widgets/menuWrapper'\nimport CheckIcon from '../../widgets/icons/check'\n\nimport propsRegistry from '../../properties'\n\ntype Props = {\n    properties: readonly IPropertyTemplate[]\n    activeView: BoardView\n    dateDisplayPropertyName?: string\n}\n\nconst ViewHeaderDisplayByMenu = (props: Props) => {\n    const {properties, activeView, dateDisplayPropertyName} = props\n    const intl = useIntl()\n\n    const createdDateName = propsRegistry.get('createdTime').displayName(intl)\n\n    const getDateProperties = (): IPropertyTemplate[] => {\n        return properties?.filter((o: IPropertyTemplate) => propsRegistry.get(o.type) instanceof DatePropertyType)\n    }\n\n    return (\n        <MenuWrapper>\n            <Button>\n                <FormattedMessage\n                    id='ViewHeader.display-by'\n                    defaultMessage='Display by: {property}'\n                    values={{\n                        property: (\n                            <span\n                                style={{color: 'rgb(var(--center-channel-color-rgb))'}}\n                                id='displayByLabel'\n                            >\n                                {dateDisplayPropertyName || createdDateName}\n                            </span>\n                        ),\n                    }}\n                />\n            </Button>\n            <Menu>\n                {getDateProperties().length > 0 && getDateProperties().map((date: IPropertyTemplate) => (\n                    <Menu.Text\n                        key={date.id}\n                        id={date.id}\n                        name={date.name}\n                        rightIcon={activeView.fields.dateDisplayPropertyId === date.id ? <CheckIcon/> : undefined}\n                        onClick={(id) => {\n                            if (activeView.fields.dateDisplayPropertyId === id) {\n                                return\n                            }\n                            mutator.changeViewDateDisplayPropertyId(activeView.boardId, activeView.id, activeView.fields.dateDisplayPropertyId, id)\n                        }}\n                    />\n                ))}\n                {getDateProperties().length === 0 &&\n                    <Menu.Text\n                        key={'createdDate'}\n                        id={'createdDate'}\n                        name={createdDateName}\n                        rightIcon={<CheckIcon/>}\n                        onClick={() => {}}\n                    />\n                }\n            </Menu>\n        </MenuWrapper>\n    )\n}\n\nexport default React.memo(ViewHeaderDisplayByMenu)\n"
  },
  {
    "path": "webapp/src/components/viewHeader/viewHeaderGroupByMenu.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\nimport {render, screen} from '@testing-library/react'\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport '@testing-library/jest-dom'\nimport userEvent from '@testing-library/user-event'\nimport {mocked} from 'jest-mock'\n\nimport {MockStoreEnhanced} from 'redux-mock-store'\n\nimport {TestBlockFactory} from '../../test/testBlockFactory'\n\nimport mutator from '../../mutator'\n\nimport {wrapIntl, mockStateStore} from '../../testUtils'\nimport {IPropertyOption} from '../../blocks/board'\n\nimport ViewHeaderGroupByMenu from './viewHeaderGroupByMenu'\n\njest.mock('../../mutator')\nconst mockedMutator = mocked(mutator, true)\n\nconst board = TestBlockFactory.createBoard()\nconst activeView = TestBlockFactory.createBoardView(board)\nconst property = board.cardProperties.find((p) => p.name === 'Status')!\nconst optionQ1: IPropertyOption = {\n    color: 'propColorOrange',\n    id: 'property_value_id_1',\n    value: 'Q1',\n}\nconst optionQ2: IPropertyOption = {\n    color: 'propColorBlue',\n    id: 'property_value_id_2',\n    value: 'Q2',\n}\nconst optionQ3: IPropertyOption = {\n    color: 'propColorDefault',\n    id: 'property_value_id_3',\n    value: 'Q3',\n}\nproperty.options = [optionQ1, optionQ2, optionQ3]\n\nactiveView.fields.filter = {filters: [], operation: 'and'}\nactiveView.fields.visibleOptionIds = [optionQ1.id, optionQ2.id]\n\nconst card1 = TestBlockFactory.createCard(board)\ncard1.fields.properties = {[property.id]: 'property_value_id_1'}\nconst card2 = TestBlockFactory.createCard(board)\ncard2.fields.properties = {[property.id]: 'property_value_id_2'}\nconst card3 = TestBlockFactory.createCard(board)\ncard3.fields.properties = {[property.id]: 'property_value_id_3'}\n\ndescribe('components/viewHeader/viewHeaderGroupByMenu', () => {\n    let state: any\n    let store: MockStoreEnhanced<unknown, unknown>\n\n    const setDefaultOptions = () => {\n        activeView.fields.hiddenOptionIds = [optionQ3.id]\n\n        state = {\n            users: {\n                me: {\n                    id: 'user-id-1',\n                    username: 'username_1',\n                },\n                boardUsers: {\n                    'user-id-1': {username: 'username_1'},\n                },\n            },\n            boards: {\n                current: board.id,\n                boards: {\n                    [board.id]: board,\n                },\n                templates: [],\n            },\n            views: {\n                views: {\n                    [activeView.id]: activeView,\n                },\n                current: activeView.id,\n            },\n            cards: {\n                templates: [],\n                cards: [card1, card2, card3],\n            },\n            searchText: {},\n        }\n        store = mockStateStore([], state)\n    }\n\n    beforeEach(() => {\n        jest.clearAllMocks()\n        setDefaultOptions()\n    })\n    test('return groupBy menu', () => {\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <ViewHeaderGroupByMenu\n                        activeView={activeView}\n                        groupByProperty={property}\n                        properties={board.cardProperties}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})\n        userEvent.click(buttonElement)\n        expect(container).toMatchSnapshot()\n    })\n    test('return groupBy menu and groupBy Status', () => {\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <ViewHeaderGroupByMenu\n                        activeView={activeView}\n                        groupByProperty={property}\n                        properties={board.cardProperties}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})\n        userEvent.click(buttonElement)\n        const buttonStatus = screen.getByRole('button', {name: 'Status'})\n        userEvent.click(buttonStatus)\n        expect(container).toMatchSnapshot()\n        expect(mockedMutator.changeViewGroupById).toBeCalledTimes(1)\n    })\n    test('return groupBy menu, hideEmptyGroups and ungroup in viewType table', () => {\n        activeView.fields.viewType = 'table'\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <ViewHeaderGroupByMenu\n                        activeView={activeView}\n                        groupByProperty={property}\n                        properties={board.cardProperties}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n\n        const menuButton = screen.getByRole('button', {name: 'menuwrapper'})\n        userEvent.click(menuButton)\n        expect(container).toMatchSnapshot()\n\n        const hideEmptyGroupsButton = screen.getByRole('button', {name: /Hide.+groups/i})\n        expect(hideEmptyGroupsButton)\n        userEvent.click(hideEmptyGroupsButton)\n        expect(mockedMutator.hideViewColumns).toBeCalledTimes(1)\n\n        userEvent.click(menuButton)\n        const showHiddenGroupsButton = screen.getByRole('button', {name: /Show.+groups/i})\n        userEvent.click(showHiddenGroupsButton)\n        expect(mockedMutator.unhideViewColumns).toBeCalledTimes(1)\n\n        userEvent.click(menuButton)\n        const ungroupButton = screen.getByRole('button', {name: 'Ungroup'})\n        userEvent.click(ungroupButton)\n        expect(mockedMutator.changeViewGroupById).toBeCalledTimes(1)\n    })\n\n    test('For viewType table render only HideEmptyGroupsButton when hiddenGroups is empty', () => {\n        activeView.fields.viewType = 'table'\n        activeView.fields.hiddenOptionIds = []\n\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <ViewHeaderGroupByMenu\n                        activeView={activeView}\n                        groupByProperty={property}\n                        properties={board.cardProperties}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})\n        userEvent.click(buttonElement)\n        expect(container).toMatchSnapshot()\n\n        const hideEmptyGroupsButton = screen.queryByRole('button', {name: /Hide.+groups/i})\n        expect(hideEmptyGroupsButton).toBeInTheDocument()\n\n        const showHiddenGroupsButton = screen.queryByRole('button', {name: /Show.+groups/i})\n        expect(showHiddenGroupsButton).not.toBeInTheDocument()\n    })\n\n    test('For viewType table render only ShowHiddenGroupsButton when there are no emptyGroups', () => {\n        activeView.fields.viewType = 'table'\n\n        const cardToFillTheEmptyGroup = TestBlockFactory.createCard(board)\n        state.cards.cards.push(cardToFillTheEmptyGroup)\n        store = mockStateStore([], state)\n\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <ViewHeaderGroupByMenu\n                        activeView={activeView}\n                        groupByProperty={property}\n                        properties={board.cardProperties}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})\n        userEvent.click(buttonElement)\n        expect(container).toMatchSnapshot()\n\n        const showHiddenGroupsButton = screen.queryByRole('button', {name: /Show.+groups/i})\n        expect(showHiddenGroupsButton).toBeInTheDocument()\n\n        const hideEmptyGroupsButton = screen.queryByRole('button', {name: /Hide.+groups/i})\n        expect(hideEmptyGroupsButton).not.toBeInTheDocument()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/viewHeader/viewHeaderGroupByMenu.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {FormattedMessage, useIntl} from 'react-intl'\n\nimport {BoardGroup, IPropertyTemplate} from '../../blocks/board'\nimport {BoardView} from '../../blocks/boardView'\nimport mutator from '../../mutator'\nimport Button from '../../widgets/buttons/button'\nimport Menu from '../../widgets/menu'\nimport MenuWrapper from '../../widgets/menuWrapper'\nimport CheckIcon from '../../widgets/icons/check'\nimport HideIcon from '../../widgets/icons/hide'\nimport ShowIcon from '../../widgets/icons/show'\nimport {useAppSelector} from '../../store/hooks'\nimport {getCurrentViewCardsSortedFilteredAndGrouped} from '../../store/cards'\nimport {getVisibleAndHiddenGroups} from '../../boardUtils'\nimport propsRegistry from '../../properties'\n\ntype Props = {\n    properties: readonly IPropertyTemplate[]\n    activeView: BoardView\n    groupByProperty?: IPropertyTemplate\n}\n\nconst ViewHeaderGroupByMenu = (props: Props) => {\n    const {properties, activeView, groupByProperty} = props\n    const intl = useIntl()\n\n    const cards = useAppSelector(getCurrentViewCardsSortedFilteredAndGrouped)\n    const {visible: visibleGroups, hidden: hiddenGroups} = getVisibleAndHiddenGroups(cards, activeView.fields.visibleOptionIds, activeView.fields.hiddenOptionIds, groupByProperty)\n\n    const emptyVisibleGroups = visibleGroups.filter((g) => !g.cards.length)\n    const emptyVisibleGroupsCount = emptyVisibleGroups.length\n    const hiddenGroupsCount = hiddenGroups.length\n\n    const handleToggleGroups = (show: boolean) => {\n        const getColumnIds = (groups: BoardGroup[]) => groups.map((g) => g.option.id)\n\n        if (show) {\n            const columnsToShow = getColumnIds(hiddenGroups)\n            mutator.unhideViewColumns(activeView.boardId, activeView, columnsToShow)\n        } else {\n            const columnsToHide = getColumnIds(emptyVisibleGroups)\n            mutator.hideViewColumns(activeView.boardId, activeView, columnsToHide)\n        }\n    }\n\n    return (\n        <MenuWrapper>\n            <Button>\n                <FormattedMessage\n                    id='ViewHeader.group-by'\n                    defaultMessage='Group by: {property}'\n                    values={{\n                        property: (\n                            <span\n                                style={{color: 'rgb(var(--center-channel-color-rgb))'}}\n                                id='groupByLabel'\n                            >\n                                {groupByProperty?.name}\n                            </span>\n                        ),\n                    }}\n                />\n            </Button>\n            <Menu>\n                {activeView.fields.viewType === 'table' && activeView.fields.groupById &&\n                    <>\n                        {emptyVisibleGroupsCount > 0 &&\n                            <Menu.Text\n                                key={'hideEmptyGroups'}\n                                id={'hideEmptyGroups'}\n                                name={intl.formatMessage({id: 'GroupBy.hideEmptyGroups', defaultMessage: 'Hide {count} empty groups'}, {count: emptyVisibleGroupsCount})}\n                                rightIcon={<HideIcon/>}\n                                onClick={() => handleToggleGroups(false)}\n                            />}\n                        {hiddenGroupsCount > 0 &&\n                            <Menu.Text\n                                key={'showHiddenGroups'}\n                                id={'showHiddenGroups'}\n                                name={intl.formatMessage({id: 'GroupBy.showHiddenGroups', defaultMessage: 'Show {count} hidden groups'}, {count: hiddenGroupsCount})}\n                                rightIcon={<ShowIcon/>}\n                                onClick={() => handleToggleGroups(true)}\n                            />}\n                        <Menu.Text\n                            key={'ungroup'}\n                            id={''}\n                            name={intl.formatMessage({id: 'GroupBy.ungroup', defaultMessage: 'Ungroup'})}\n                            rightIcon={activeView.fields.groupById === '' ? <CheckIcon/> : undefined}\n                            onClick={(id) => {\n                                if (activeView.fields.groupById === id) {\n                                    return\n                                }\n                                mutator.changeViewGroupById(activeView.boardId, activeView.id, activeView.fields.groupById, id)\n                            }}\n                        />\n                        <Menu.Separator/>\n                    </>}\n                {properties?.filter((o: IPropertyTemplate) => propsRegistry.get(o.type).canGroup).map((option: IPropertyTemplate) => (\n                    <Menu.Text\n                        key={option.id}\n                        id={option.id}\n                        name={option.name}\n                        rightIcon={groupByProperty?.id === option.id ? <CheckIcon/> : undefined}\n                        onClick={(id) => {\n                            if (activeView.fields.groupById === id) {\n                                return\n                            }\n\n                            mutator.changeViewGroupById(activeView.boardId, activeView.id, activeView.fields.groupById, id)\n                        }}\n                    />\n                ))}\n            </Menu>\n        </MenuWrapper>\n    )\n}\n\nexport default React.memo(ViewHeaderGroupByMenu)\n"
  },
  {
    "path": "webapp/src/components/viewHeader/viewHeaderPropertiesMenu.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\nimport {render, screen} from '@testing-library/react'\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport '@testing-library/jest-dom'\nimport userEvent from '@testing-library/user-event'\n\nimport {mocked} from 'jest-mock'\n\nimport {BoardView} from '../../blocks/boardView'\n\nimport {TestBlockFactory} from '../../test/testBlockFactory'\n\nimport mutator from '../../mutator'\n\nimport {mockStateStore, wrapIntl} from '../../testUtils'\n\nimport {Constants} from '../../constants'\n\nimport ViewHeaderPropertiesMenu from './viewHeaderPropertiesMenu'\n\njest.mock('../../mutator')\nconst mockedMutator = mocked(mutator, true)\n\nconst board = TestBlockFactory.createBoard()\nlet activeView: BoardView\n\ndescribe('components/viewHeader/viewHeaderPropertiesMenu', () => {\n    const state = {\n        users: {\n            me: {\n                id: 'user-id-1',\n                username: 'username_1'},\n        },\n    }\n    const store = mockStateStore([], state)\n    beforeEach(() => {\n        jest.clearAllMocks()\n        activeView = TestBlockFactory.createBoardView(board)\n    })\n    test('return properties menu', () => {\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <ViewHeaderPropertiesMenu\n                        activeView={activeView}\n                        properties={board.cardProperties}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        const buttonElement = screen.getByRole('button', {name: 'Properties menu'})\n        userEvent.click(buttonElement)\n        expect(container).toMatchSnapshot()\n    })\n    test('return properties menu with gallery typeview', () => {\n        activeView.fields.viewType = 'gallery'\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <ViewHeaderPropertiesMenu\n                        activeView={activeView}\n                        properties={board.cardProperties}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        const buttonElement = screen.getByRole('button', {name: 'Properties menu'})\n        userEvent.click(buttonElement)\n        expect(container).toMatchSnapshot()\n    })\n    test('show menu and verify the call for showing card badges', () => {\n        render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <ViewHeaderPropertiesMenu\n                        activeView={activeView}\n                        properties={board.cardProperties}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        const menuButton = screen.getByRole('button', {name: 'Properties menu'})\n        userEvent.click(menuButton)\n        const badgesButton = screen.getByRole('button', {name: 'Comments and description'})\n        userEvent.click(badgesButton)\n        expect(mockedMutator.changeViewVisibleProperties).toHaveBeenCalledWith(\n            activeView.boardId,\n            activeView.id,\n            activeView.fields.visiblePropertyIds,\n            [...activeView.fields.visiblePropertyIds, Constants.badgesColumnId],\n        )\n    })\n    test('show menu and verify that it is not closed after clicking on the item', () => {\n        render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <ViewHeaderPropertiesMenu\n                        activeView={activeView}\n                        properties={board.cardProperties}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        const menuButton = screen.getByRole('button', {name: 'Properties menu'})\n        userEvent.click(menuButton)\n\n        const property1Button = screen.getByRole('button', {name: 'Property 1'})\n        userEvent.click(property1Button)\n        expect(property1Button).toBeInTheDocument()\n\n        const property2Button = screen.getByRole('button', {name: 'Property 2'})\n        userEvent.click(property2Button)\n        expect(property2Button).toBeInTheDocument()\n\n        expect(mockedMutator.changeViewVisibleProperties).toHaveBeenCalledTimes(2)\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/viewHeader/viewHeaderPropertiesMenu.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\nimport {FormattedMessage, useIntl} from 'react-intl'\n\nimport {Constants} from '../../constants'\nimport {IPropertyTemplate} from '../../blocks/board'\nimport {BoardView} from '../../blocks/boardView'\nimport mutator from '../../mutator'\nimport Button from '../../widgets/buttons/button'\nimport Menu from '../../widgets/menu'\nimport MenuWrapper from '../../widgets/menuWrapper'\n\ntype Props = {\n    properties: readonly IPropertyTemplate[]\n    activeView: BoardView\n}\nconst ViewHeaderPropertiesMenu = (props: Props) => {\n    const {properties, activeView} = props\n    const intl = useIntl()\n    const {viewType, visiblePropertyIds} = activeView.fields\n    const canShowBadges = viewType === 'board' || viewType === 'gallery' || viewType === 'calendar'\n\n    const toggleVisibility = (propertyId: string) => {\n        let newVisiblePropertyIds = []\n        if (visiblePropertyIds.includes(propertyId)) {\n            newVisiblePropertyIds = visiblePropertyIds.filter((o: string) => o !== propertyId)\n        } else {\n            newVisiblePropertyIds = [...visiblePropertyIds, propertyId]\n        }\n        mutator.changeViewVisibleProperties(activeView.boardId, activeView.id, visiblePropertyIds, newVisiblePropertyIds)\n    }\n\n    return (\n        <MenuWrapper label={intl.formatMessage({id: 'ViewHeader.properties-menu', defaultMessage: 'Properties menu'})}>\n            <Button>\n                <FormattedMessage\n                    id='ViewHeader.properties'\n                    defaultMessage='Properties'\n                />\n            </Button>\n            <Menu>\n                {activeView.fields.viewType === 'gallery' &&\n                    <Menu.Switch\n                        key={Constants.titleColumnId}\n                        id={Constants.titleColumnId}\n                        name={intl.formatMessage({id: 'default-properties.title', defaultMessage: 'Title'})}\n                        isOn={visiblePropertyIds.includes(Constants.titleColumnId)}\n                        suppressItemClicked={true}\n                        onClick={toggleVisibility}\n                    />}\n                {properties?.map((option: IPropertyTemplate) => (\n                    <Menu.Switch\n                        key={option.id}\n                        id={option.id}\n                        name={option.name}\n                        isOn={visiblePropertyIds.includes(option.id)}\n                        suppressItemClicked={true}\n                        onClick={toggleVisibility}\n                    />\n                ))}\n                {canShowBadges &&\n                    <Menu.Switch\n                        key={Constants.badgesColumnId}\n                        id={Constants.badgesColumnId}\n                        name={intl.formatMessage({id: 'default-properties.badges', defaultMessage: 'Comments and description'})}\n                        isOn={visiblePropertyIds.includes(Constants.badgesColumnId)}\n                        suppressItemClicked={true}\n                        onClick={toggleVisibility}\n                    />}\n            </Menu>\n        </MenuWrapper>\n    )\n}\n\nexport default React.memo(ViewHeaderPropertiesMenu)\n"
  },
  {
    "path": "webapp/src/components/viewHeader/viewHeaderSearch.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\nimport {render, screen} from '@testing-library/react'\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport '@testing-library/jest-dom'\nimport userEvent from '@testing-library/user-event'\n\nimport {mockStateStore, wrapIntl} from '../../testUtils'\n\nimport ViewHeaderSearch from './viewHeaderSearch'\n\njest.mock('react-router-dom', () => {\n    const originalModule = jest.requireActual('react-router-dom')\n\n    return {\n        ...originalModule,\n        useRouteMatch: jest.fn(() => {\n            return {url: '/board/view'}\n        }),\n    }\n})\n\ndescribe('components/viewHeader/ViewHeaderSearch', () => {\n    const state = {\n        users: {\n            me: {\n                id: 'user-id-1',\n                username: 'username_1'},\n        },\n        searchText: {\n        },\n    }\n\n    const store = mockStateStore([], state)\n    beforeEach(() => {\n        jest.clearAllMocks()\n    })\n    test('return search menu', () => {\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <ViewHeaderSearch/>\n                </ReduxProvider>,\n            ),\n        )\n        expect(container).toMatchSnapshot()\n    })\n    test('search text after input', () => {\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <ViewHeaderSearch/>\n                </ReduxProvider>,\n            ),\n        )\n        const elementSearchText = screen.getByPlaceholderText('Search cards')\n        userEvent.type(elementSearchText, 'Hello')\n        expect(container).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/viewHeader/viewHeaderSearch.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useState, useRef, useEffect, useMemo} from 'react'\nimport {useRouteMatch} from 'react-router-dom'\nimport {useIntl} from 'react-intl'\nimport {useHotkeys} from 'react-hotkeys-hook'\nimport {debounce} from 'lodash'\n\nimport CompassIcon from '../../widgets/icons/compassIcon'\nimport Editable from '../../widgets/editable'\n\nimport {useAppSelector, useAppDispatch} from '../../store/hooks'\nimport {getSearchText, setSearchText} from '../../store/searchText'\n\nconst ViewHeaderSearch = (): JSX.Element => {\n    const searchText = useAppSelector<string>(getSearchText)\n    const dispatch = useAppDispatch()\n    const intl = useIntl()\n    const match = useRouteMatch<{viewId?: string}>()\n\n    const searchFieldRef = useRef<{focus(selectAll?: boolean): void}>(null)\n    const [searchValue, setSearchValue] = useState(searchText)\n    const [currentView, setCurrentView] = useState(match.params?.viewId)\n\n    const dispatchSearchText = (value: string) => {\n        dispatch(setSearchText(value))\n    }\n\n    const debouncedDispatchSearchText = useMemo(\n        () => debounce(dispatchSearchText, 200), [])\n\n    useEffect(() => {\n        const viewId = match.params?.viewId\n        if (viewId !== currentView) {\n            setCurrentView(viewId)\n            setSearchValue('')\n\n            // Previously debounced calls to change the search text should be cancelled\n            // to avoid resetting the search text.\n            debouncedDispatchSearchText.cancel()\n            dispatchSearchText('')\n        }\n    }, [match.url])\n\n    useEffect(() => {\n        return () => {\n            debouncedDispatchSearchText.cancel()\n        }\n    }, [])\n\n    useHotkeys('ctrl+shift+f,cmd+shift+f', () => {\n        searchFieldRef.current?.focus(true)\n    })\n\n    return (\n        <div className='board-search-field'>\n            <CompassIcon\n                icon='magnify'\n                className='board-search-icon'\n            />\n            <Editable\n                ref={searchFieldRef}\n                value={searchValue}\n                placeholderText={intl.formatMessage({id: 'ViewHeader.search-text', defaultMessage: 'Search cards'})}\n                onChange={(value) => {\n                    setSearchValue(value)\n                    debouncedDispatchSearchText(value)\n                }}\n                onCancel={() => {\n                    setSearchValue('')\n                    debouncedDispatchSearchText('')\n                }}\n                onSave={() => {\n                    debouncedDispatchSearchText(searchValue)\n                }}\n            />\n        </div>\n    )\n}\n\nexport default ViewHeaderSearch\n"
  },
  {
    "path": "webapp/src/components/viewHeader/viewHeaderSortMenu.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\nimport {render, screen} from '@testing-library/react'\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport '@testing-library/jest-dom'\nimport userEvent from '@testing-library/user-event'\n\nimport {mocked} from 'jest-mock'\n\nimport {wrapIntl, mockStateStore} from '../../testUtils'\n\nimport {TestBlockFactory} from '../../test/testBlockFactory'\n\nimport mutator from '../../mutator'\n\nimport ViewHeaderSortMenu from './viewHeaderSortMenu'\n\njest.mock('../../mutator')\nconst mockedMutator = mocked(mutator, true)\n\nconst board = TestBlockFactory.createBoard()\nconst activeView = TestBlockFactory.createBoardView(board)\nconst cards = [TestBlockFactory.createCard(board), TestBlockFactory.createCard(board)]\n\ndescribe('components/viewHeader/viewHeaderSortMenu', () => {\n    const state = {\n        users: {\n            me: {\n                id: 'user-id-1',\n                username: 'username_1'},\n        },\n    }\n    const store = mockStateStore([], state)\n    beforeEach(() => {\n        jest.clearAllMocks()\n    })\n    test('return sort menu', () => {\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <ViewHeaderSortMenu\n                        activeView={activeView}\n                        orderedCards={cards}\n                        properties={board.cardProperties}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})\n        userEvent.click(buttonElement)\n        expect(container).toMatchSnapshot()\n    })\n    test('return sort menu and do manual', () => {\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <ViewHeaderSortMenu\n                        activeView={activeView}\n                        orderedCards={cards}\n                        properties={board.cardProperties}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})\n        userEvent.click(buttonElement)\n        const buttonManual = screen.getByRole('button', {name: 'Manual'})\n        userEvent.click(buttonManual)\n        expect(container).toMatchSnapshot()\n        expect(mockedMutator.updateBlock).toBeCalledTimes(1)\n    })\n    test('return sort menu and do revert', () => {\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <ViewHeaderSortMenu\n                        activeView={activeView}\n                        orderedCards={cards}\n                        properties={board.cardProperties}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})\n        userEvent.click(buttonElement)\n        const buttonRevert = screen.getByRole('button', {name: 'Revert'})\n        userEvent.click(buttonRevert)\n        expect(container).toMatchSnapshot()\n        expect(mockedMutator.changeViewSortOptions).toBeCalledTimes(1)\n        expect(mockedMutator.changeViewSortOptions).toBeCalledWith(activeView.boardId, activeView.id, activeView.fields.sortOptions, [])\n    })\n    test('return sort menu and do Name sort', () => {\n        const {container} = render(\n            wrapIntl(\n                <ReduxProvider store={store}>\n                    <ViewHeaderSortMenu\n                        activeView={activeView}\n                        orderedCards={cards}\n                        properties={board.cardProperties}\n                    />\n                </ReduxProvider>,\n            ),\n        )\n        const buttonElement = screen.getByRole('button', {name: 'menuwrapper'})\n        userEvent.click(buttonElement)\n        const buttonName = screen.getByRole('button', {name: 'Name'})\n        userEvent.click(buttonName)\n        expect(container).toMatchSnapshot()\n        expect(mockedMutator.changeViewSortOptions).toBeCalledTimes(1)\n        expect(mockedMutator.changeViewSortOptions).toBeCalledWith(activeView.boardId, activeView.id, activeView.fields.sortOptions, [{propertyId: '__title', reversed: false}])\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/viewHeader/viewHeaderSortMenu.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useCallback} from 'react'\nimport {FormattedMessage} from 'react-intl'\n\nimport {IPropertyTemplate} from '../../blocks/board'\nimport {BoardView, ISortOption} from '../../blocks/boardView'\nimport {Constants} from '../../constants'\nimport {Card} from '../../blocks/card'\nimport mutator from '../../mutator'\nimport Button from '../../widgets/buttons/button'\nimport Menu from '../../widgets/menu'\nimport MenuWrapper from '../../widgets/menuWrapper'\nimport SortDownIcon from '../../widgets/icons/sortDown'\nimport SortUpIcon from '../../widgets/icons/sortUp'\n\ntype Props = {\n    properties: readonly IPropertyTemplate[]\n    activeView: BoardView\n    orderedCards: Card[]\n}\nconst ViewHeaderSortMenu = (props: Props) => {\n    const {properties, activeView, orderedCards} = props\n    const hasSort = activeView.fields.sortOptions?.length > 0\n    const sortDisplayOptions = properties?.map((o) => ({id: o.id, name: o.name}))\n    sortDisplayOptions?.unshift({id: Constants.titleColumnId, name: 'Name'})\n\n    const sortChanged = useCallback((propertyId: string) => {\n        let newSortOptions: ISortOption[] = []\n        if (activeView.fields.sortOptions && activeView.fields.sortOptions[0] && activeView.fields.sortOptions[0].propertyId === propertyId) {\n            // Already sorting by name, so reverse it\n            newSortOptions = [\n                {propertyId, reversed: !activeView.fields.sortOptions[0].reversed},\n            ]\n        } else {\n            newSortOptions = [\n                {propertyId, reversed: false},\n            ]\n        }\n        mutator.changeViewSortOptions(activeView.boardId, activeView.id, activeView.fields.sortOptions, newSortOptions)\n    }, [activeView.id, activeView.fields.sortOptions])\n\n    const onManualSort = useCallback(() => {\n        // This sets the manual card order to the currently displayed order\n        // Note: Perform this as a single update to change both properties correctly\n        const newView = {...activeView, fields: {...activeView.fields}}\n        newView.fields.cardOrder = orderedCards.map((o) => o.id || '') || []\n        newView.fields.sortOptions = []\n        mutator.updateBlock(activeView.boardId, newView, activeView, 'reorder')\n    }, [activeView, orderedCards])\n\n    const onRevertSort = useCallback(() => {\n        mutator.changeViewSortOptions(activeView.boardId, activeView.id, activeView.fields.sortOptions, [])\n    }, [activeView.id, activeView.fields.sortOptions])\n\n    return (\n        <MenuWrapper>\n            <Button active={hasSort}>\n                <FormattedMessage\n                    id='ViewHeader.sort'\n                    defaultMessage='Sort'\n                />\n            </Button>\n            <Menu>\n                {(activeView.fields.sortOptions?.length > 0) &&\n                <>\n                    <Menu.Text\n                        id='manual'\n                        name='Manual'\n                        onClick={onManualSort}\n                    />\n\n                    <Menu.Text\n                        id='revert'\n                        name='Revert'\n                        onClick={onRevertSort}\n                    />\n\n                    <Menu.Separator/>\n                </>\n                }\n\n                {sortDisplayOptions?.map((option) => {\n                    let rightIcon: JSX.Element | undefined\n                    if (activeView.fields.sortOptions?.length > 0) {\n                        const sortOption = activeView.fields.sortOptions[0]\n                        if (sortOption.propertyId === option.id) {\n                            rightIcon = sortOption.reversed ? <SortDownIcon/> : <SortUpIcon/>\n                        }\n                    }\n                    return (\n                        <Menu.Text\n                            key={option.id}\n                            id={option.id}\n                            name={option.name}\n                            rightIcon={rightIcon}\n                            onClick={sortChanged}\n                        />\n                    )\n                })}\n            </Menu>\n        </MenuWrapper>\n    )\n}\n\nexport default React.memo(ViewHeaderSortMenu)\n"
  },
  {
    "path": "webapp/src/components/viewMenu.scss",
    "content": ".ViewMenu {\n\n    margin-top: -200px;\n\n    .view-list {\n        max-height: 30vh;\n        overflow-y: auto;\n        height: auto;\n    }\n\n    .subMenu {\n        height: 100%;\n        overflow: visible;\n    }\n\n    @media (max-height: 370px) {\n        margin-top: -230px;\n\n        .subMenu {\n            height: 50px;\n            overflow-y: scroll;\n        }\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/viewMenu.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport '@testing-library/jest-dom'\nimport {render} from '@testing-library/react'\nimport 'isomorphic-fetch'\n\nimport React from 'react'\nimport {Provider as ReduxProvider} from 'react-redux'\nimport {Router} from 'react-router-dom'\nimport {createMemoryHistory} from 'history'\n\nimport configureStore from 'redux-mock-store'\n\nimport {FetchMock} from '../test/fetchMock'\nimport {TestBlockFactory} from '../test/testBlockFactory'\nimport {wrapDNDIntl} from '../testUtils'\n\nimport ViewMenu from './viewMenu'\n\nglobal.fetch = FetchMock.fn\n\nbeforeEach(() => {\n    FetchMock.fn.mockReset()\n})\n\ndescribe('/components/viewMenu', () => {\n    const board = TestBlockFactory.createBoard()\n    const boardView = TestBlockFactory.createBoardView(board)\n    const tableView = TestBlockFactory.createTableView(board)\n    const activeView = boardView\n    const views = [boardView, tableView]\n\n    const card = TestBlockFactory.createCard(board)\n    activeView.fields.viewType = 'table'\n    activeView.fields.groupById = undefined\n    activeView.fields.visiblePropertyIds = ['property1', 'property2']\n\n    const state = {\n        users: {\n            me: {\n                id: 'user-id-1',\n                username: 'username_1',\n            },\n        },\n        searchText: {},\n        teams: {\n            current: {id: 'team-id'},\n        },\n        boards: {\n            current: board.id,\n            boards: {\n                [board.id]: board,\n            },\n            myBoardMemberships: {\n                [board.id]: {userId: 'user_id_1', schemeAdmin: true},\n            },\n        },\n        cards: {\n            templates: [card],\n        },\n        views: {\n            views: {\n                boardView: activeView,\n            },\n            current: 'boardView',\n        },\n        clientConfig: {},\n    }\n\n    const history = createMemoryHistory()\n\n    it('should match snapshot', () => {\n        const mockStore = configureStore([])\n        const store = mockStore(state)\n\n        const component = wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <Router history={history}>\n                    <ViewMenu\n                        board={board}\n                        activeView={activeView}\n                        views={views}\n                        readonly={false}\n                    />\n                </Router>\n            </ReduxProvider>,\n        )\n\n        const container = render(component)\n        expect(container).toMatchSnapshot()\n    })\n\n    it('should match snapshot, read only', () => {\n        const mockStore = configureStore([])\n        const store = mockStore(state)\n\n        const component = wrapDNDIntl(\n            <ReduxProvider store={store}>\n                <Router history={history}>\n                    <ViewMenu\n                        board={board}\n                        activeView={activeView}\n                        views={views}\n                        readonly={true}\n                    />\n                </Router>\n            </ReduxProvider>,\n        )\n\n        const container = render(component)\n        expect(container).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/viewMenu.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useCallback} from 'react'\nimport {injectIntl, IntlShape} from 'react-intl'\nimport {generatePath, useHistory, useRouteMatch} from 'react-router-dom'\n\nimport {Board, IPropertyTemplate} from '../blocks/board'\nimport {BoardView, createBoardView, IViewType} from '../blocks/boardView'\nimport {Constants, Permission} from '../constants'\nimport mutator from '../mutator'\nimport TelemetryClient, {TelemetryActions, TelemetryCategory} from '../telemetry/telemetryClient'\nimport {Block} from '../blocks/block'\nimport {IDType, Utils} from '../utils'\nimport AddIcon from '../widgets/icons/add'\nimport BoardIcon from '../widgets/icons/board'\nimport CalendarIcon from '../widgets/icons/calendar'\nimport DeleteIcon from '../widgets/icons/delete'\nimport DuplicateIcon from '../widgets/icons/duplicate'\nimport GalleryIcon from '../widgets/icons/gallery'\nimport TableIcon from '../widgets/icons/table'\nimport Menu from '../widgets/menu'\n\nimport BoardPermissionGate from './permissions/boardPermissionGate'\nimport './viewMenu.scss'\n\ntype Props = {\n    board: Board\n    activeView: BoardView\n    views: BoardView[]\n    intl: IntlShape\n    readonly: boolean\n}\n\nconst ViewMenu = (props: Props) => {\n    const history = useHistory()\n    const match = useRouteMatch()\n\n    const showView = useCallback((viewId) => {\n        let newPath = generatePath(Utils.getBoardPagePath(match.path), {...match.params, viewId: viewId || ''})\n        if (props.readonly) {\n            newPath += `?r=${Utils.getReadToken()}`\n        }\n        history.push(newPath)\n    }, [match, history])\n\n    const handleDuplicateView = useCallback(() => {\n        const {board, activeView} = props\n        Utils.log('duplicateView')\n\n        TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.DuplicateBoardView, {board: board.id, view: activeView.id})\n        const currentViewId = activeView.id\n        const newView = createBoardView(activeView)\n        newView.title = `${activeView.title} copy`\n        newView.id = Utils.createGuid(IDType.View)\n        mutator.insertBlock(\n            newView.boardId,\n            newView,\n            'duplicate view',\n            async (block: Block) => {\n                // This delay is needed because WSClient has a default 100 ms notification delay before updates\n                setTimeout(() => {\n                    showView(block.id)\n                }, 120)\n            },\n            async () => {\n                showView(currentViewId)\n            },\n        )\n    }, [props.activeView, showView])\n\n    const handleDeleteView = useCallback(() => {\n        const {board, activeView, views} = props\n        Utils.log('deleteView')\n        TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.DeleteBoardView, {board: board.id, view: activeView.id})\n        const view = activeView\n        const nextView = views.find((o) => o.id !== view.id)\n        mutator.deleteBlock(view, 'delete view')\n        if (nextView) {\n            showView(nextView.id)\n        }\n    }, [props.views, props.activeView, showView])\n\n    const handleViewClick = useCallback((id: string) => {\n        const {views} = props\n        Utils.log('view ' + id)\n        const view = views.find((o) => o.id === id)\n        Utils.assert(view, `view not found: ${id}`)\n        if (view) {\n            showView(view.id)\n        }\n    }, [props.views, showView])\n\n    const handleAddViewBoard = useCallback(() => {\n        const {board, activeView, intl} = props\n        Utils.log('addview-board')\n\n        TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.CreateBoardView, {board: board.id, view: activeView.id})\n        const view = createBoardView()\n        view.title = intl.formatMessage({id: 'View.NewBoardTitle', defaultMessage: 'Board view'})\n        view.fields.viewType = 'board'\n        view.boardId = board.id\n\n        const oldViewId = activeView.id\n\n        mutator.insertBlock(\n            view.boardId,\n            view,\n            'add view',\n            async (block: Block) => {\n                // This delay is needed because WSClient has a default 100 ms notification delay before updates\n                setTimeout(() => {\n                    showView(block.id)\n                }, 120)\n            },\n            async () => {\n                showView(oldViewId)\n            })\n    }, [props.activeView, props.board, props.intl, showView])\n\n    const handleAddViewTable = useCallback(() => {\n        const {board, activeView, intl} = props\n\n        Utils.log('addview-table')\n\n        const view = createBoardView()\n        view.title = intl.formatMessage({id: 'View.NewTableTitle', defaultMessage: 'Table view'})\n        view.fields.viewType = 'table'\n        view.boardId = board.id\n        view.fields.visiblePropertyIds = board.cardProperties.map((o: IPropertyTemplate) => o.id)\n        view.fields.columnWidths = {}\n        view.fields.columnWidths[Constants.titleColumnId] = Constants.defaultTitleColumnWidth\n\n        const oldViewId = activeView.id\n\n        mutator.insertBlock(\n            view.boardId,\n            view,\n            'add view',\n            async (block: Block) => {\n                // This delay is needed because WSClient has a default 100 ms notification delay before updates\n                setTimeout(() => {\n                    Utils.log(`showView: ${block.id}`)\n                    showView(block.id)\n                }, 120)\n            },\n            async () => {\n                showView(oldViewId)\n            })\n    }, [props.activeView, props.board, props.intl, showView])\n\n    const handleAddViewGallery = useCallback(() => {\n        const {board, activeView, intl} = props\n\n        Utils.log('addview-gallery')\n\n        const view = createBoardView()\n        view.title = intl.formatMessage({id: 'View.NewGalleryTitle', defaultMessage: 'Gallery view'})\n        view.fields.viewType = 'gallery'\n        view.boardId = board.id\n        view.fields.visiblePropertyIds = [Constants.titleColumnId]\n\n        const oldViewId = activeView.id\n\n        mutator.insertBlock(\n            view.boardId,\n            view,\n            'add view',\n            async (block: Block) => {\n                // This delay is needed because WSClient has a default 100 ms notification delay before updates\n                setTimeout(() => {\n                    Utils.log(`showView: ${block.id}`)\n                    showView(block.id)\n                }, 120)\n            },\n            async () => {\n                showView(oldViewId)\n            })\n    }, [props.board, props.activeView, props.intl, showView])\n\n    const handleAddViewCalendar = useCallback(() => {\n        const {board, activeView, intl} = props\n\n        Utils.log('addview-calendar')\n\n        const view = createBoardView()\n        view.title = intl.formatMessage({id: 'View.NewCalendarTitle', defaultMessage: 'Calendar view'})\n        view.fields.viewType = 'calendar'\n        view.parentId = board.id\n        view.boardId = board.id\n        view.fields.visiblePropertyIds = [Constants.titleColumnId]\n\n        const oldViewId = activeView.id\n\n        // Find first date property\n        view.fields.dateDisplayPropertyId = board.cardProperties.find((o: IPropertyTemplate) => o.type === 'date')?.id\n\n        mutator.insertBlock(\n            view.boardId,\n            view,\n            'add view',\n            async (block: Block) => {\n                // This delay is needed because WSClient has a default 100 ms notification delay before updates\n                setTimeout(() => {\n                    Utils.log(`showView: ${block.id}`)\n                    showView(block.id)\n                }, 120)\n            },\n            async () => {\n                showView(oldViewId)\n            })\n    }, [props.board, props.activeView, props.intl, showView])\n\n    const {views, intl} = props\n\n    const duplicateViewText = intl.formatMessage({\n        id: 'View.DuplicateView',\n        defaultMessage: 'Duplicate view',\n    })\n    const deleteViewText = intl.formatMessage({\n        id: 'View.DeleteView',\n        defaultMessage: 'Delete view',\n    })\n    const addViewText = intl.formatMessage({\n        id: 'View.AddView',\n        defaultMessage: 'Add view',\n    })\n    const boardText = intl.formatMessage({\n        id: 'View.Board',\n        defaultMessage: 'Board',\n    })\n    const tableText = intl.formatMessage({\n        id: 'View.Table',\n        defaultMessage: 'Table',\n    })\n    const galleryText = intl.formatMessage({\n        id: 'View.Gallery',\n        defaultMessage: 'Gallery',\n    })\n\n    const iconForViewType = (viewType: IViewType) => {\n        switch (viewType) {\n        case 'board': return <BoardIcon/>\n        case 'table': return <TableIcon/>\n        case 'gallery': return <GalleryIcon/>\n        case 'calendar': return <CalendarIcon/>\n        default: return <div/>\n        }\n    }\n\n    return (\n        <div className='ViewMenu'>\n            <Menu>\n                <div className='view-list'>\n                    {views.map((view: BoardView) => (\n                        <Menu.Text\n                            key={view.id}\n                            id={view.id}\n                            name={view.title}\n                            icon={iconForViewType(view.fields.viewType)}\n                            onClick={handleViewClick}\n                        />))}\n                </div>\n                <BoardPermissionGate permissions={[Permission.ManageBoardProperties]}>\n                    <Menu.Separator/>\n                </BoardPermissionGate>\n                {!props.readonly &&\n                <BoardPermissionGate permissions={[Permission.ManageBoardProperties]}>\n                    <Menu.Text\n                        id='__duplicateView'\n                        name={duplicateViewText}\n                        icon={<DuplicateIcon/>}\n                        onClick={handleDuplicateView}\n                    />\n                </BoardPermissionGate>\n                }\n                {!props.readonly && views.length > 1 &&\n                <BoardPermissionGate permissions={[Permission.ManageBoardProperties]}>\n                    <Menu.Text\n                        id='__deleteView'\n                        name={deleteViewText}\n                        icon={<DeleteIcon/>}\n                        onClick={handleDeleteView}\n                    />\n                </BoardPermissionGate>\n                }\n                {!props.readonly &&\n                <BoardPermissionGate permissions={[Permission.ManageBoardProperties]}>\n                    <Menu.SubMenu\n                        id='__addView'\n                        name={addViewText}\n                        icon={<AddIcon/>}\n                    >\n                        <div className='subMenu'>\n                            <Menu.Text\n                                id='board'\n                                name={boardText}\n                                icon={<BoardIcon/>}\n                                onClick={handleAddViewBoard}\n                            />\n                            <Menu.Text\n                                id='table'\n                                name={tableText}\n                                icon={<TableIcon/>}\n                                onClick={handleAddViewTable}\n                            />\n                            <Menu.Text\n                                id='gallery'\n                                name={galleryText}\n                                icon={<GalleryIcon/>}\n                                onClick={handleAddViewGallery}\n                            />\n                            <Menu.Text\n                                id='calendar'\n                                name='Calendar'\n                                icon={<CalendarIcon/>}\n                                onClick={handleAddViewCalendar}\n                            />\n                        </div>\n                    </Menu.SubMenu>\n                </BoardPermissionGate>\n                }\n            </Menu>\n        </div>\n    )\n}\n\nexport default injectIntl(React.memo(ViewMenu))\n"
  },
  {
    "path": "webapp/src/components/viewTitle.scss",
    "content": ".ViewTitle {\n    > div {\n        flex: 0 0 auto;\n        display: flex;\n        flex-direction: row;\n        align-items: flex-start;\n        min-width: 300px;\n        min-height: 28px;\n    }\n\n    .description {\n        max-height: 80px;\n        overflow: auto;\n    }\n\n    > .add-buttons {\n        flex-direction: row;\n        color: rgba(var(--center-channel-color-rgb), 0.4);\n        width: 100%;\n        align-items: flex-start;\n        position: absolute;\n        top: -28px;\n\n        .Icon:last-child {\n            margin-left: 6px;\n        }\n\n        .Icon:first-child {\n            margin-right: 6px;\n        }\n\n        .Button {\n            display: none;\n            line-height: 1;\n        }\n\n        &.add-visible:hover {\n            .Button {\n                display: flex;\n            }\n        }\n    }\n\n    .IconSelector {\n        margin-right: 8px;\n    }\n\n    .Editable {\n        margin-bottom: 0;\n        flex-grow: 1;\n        font-size: 25px;\n        line-height: 30px;\n    }\n\n    >.description>* {\n        flex-grow: 1;\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/viewTitle.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport '@testing-library/jest-dom'\nimport {act, render, screen, fireEvent} from '@testing-library/react'\nimport userEvent from '@testing-library/user-event'\n\nimport React from 'react'\nimport {Provider as ReduxProvider} from 'react-redux'\nimport {mocked} from 'jest-mock'\n\nimport mutator from '../mutator'\nimport {Utils} from '../utils'\nimport {TestBlockFactory} from '../test/testBlockFactory'\nimport {mockDOM, mockStateStore, wrapIntl} from '../testUtils'\n\nimport ViewTitle from './viewTitle'\n\njest.mock('../mutator')\njest.mock('../utils')\njest.mock('draft-js/lib/generateRandomKey', () => () => '123')\n\nconst mockedMutator = mocked(mutator, true)\nconst mockedUtils = mocked(Utils, true)\nmockedUtils.createGuid.mockReturnValue('test-id')\n\nbeforeAll(() => {\n    mockDOM()\n})\n\ndescribe('components/viewTitle', () => {\n    const board = TestBlockFactory.createBoard()\n    board.id = 'test-id'\n    const state = {\n        users: {\n            boardUsers: {\n                1: {username: 'abc'},\n                2: {username: 'd'},\n                3: {username: 'e'},\n                4: {username: 'f'},\n                5: {username: 'g'},\n            },\n        },\n        teams: {\n            current: {id: 'team-id'},\n        },\n        boards: {\n            current: board.id,\n            boards: {\n                [board.id]: board,\n            },\n            myBoardMemberships: {\n                [board.id]: {userId: 'user_id_1', schemeAdmin: true},\n            },\n        },\n        clientConfig: {\n            value: {},\n        },\n    }\n    const store = mockStateStore([], state)\n\n    beforeEach(() => {\n        jest.clearAllMocks()\n    })\n\n    test('should match snapshot', async () => {\n        let container\n        await act(async () => {\n            const result = render(wrapIntl(\n                <ReduxProvider store={store}>\n                    <ViewTitle\n                        board={board}\n                        readonly={false}\n                    />\n                </ReduxProvider>,\n            ))\n            container = result.container\n        })\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot readonly', async () => {\n        let container\n        await act(async () => {\n            const result = render(wrapIntl(\n                <ReduxProvider store={store}>\n                    <ViewTitle\n                        board={board}\n                        readonly={true}\n                    />\n                </ReduxProvider>,\n            ))\n            container = result.container\n        })\n        expect(container).toMatchSnapshot()\n    })\n\n    test('show description', async () => {\n        board.showDescription = true\n        let container\n        await act(async () => {\n            const result = render(wrapIntl(\n                <ReduxProvider store={store}>\n                    <ViewTitle\n                        board={board}\n                        readonly={false}\n                    />\n                </ReduxProvider>,\n            ))\n            container = result.container\n        })\n        expect(container).toMatchSnapshot()\n        const hideDescriptionButton = screen.getAllByRole('button')[0]\n        userEvent.click(hideDescriptionButton)\n        expect(mockedMutator.showBoardDescription).toBeCalledTimes(1)\n    })\n\n    test('hide description', async () => {\n        board.showDescription = false\n        let container\n        await act(async () => {\n            const result = render(wrapIntl(\n                <ReduxProvider store={store}>\n                    <ViewTitle\n                        board={board}\n                        readonly={false}\n                    />\n                </ReduxProvider>,\n            ))\n            container = result.container\n        })\n        expect(container).toMatchSnapshot()\n        const showDescriptionButton = screen.getAllByRole('button')[0]\n        userEvent.click(showDescriptionButton)\n        expect(mockedMutator.showBoardDescription).toBeCalledTimes(1)\n    })\n\n    test('add random icon', async () => {\n        board.icon = ''\n        let container\n        await act(async () => {\n            const result = render(wrapIntl(\n                <ReduxProvider store={store}>\n                    <ViewTitle\n                        board={board}\n                        readonly={false}\n                    />\n                </ReduxProvider>,\n            ))\n            container = result.container\n        })\n        expect(container).toMatchSnapshot()\n        const randomIconButton = screen.getAllByRole('button')[0]\n        userEvent.click(randomIconButton)\n        expect(mockedMutator.changeBoardIcon).toBeCalledTimes(1)\n    })\n\n    test('change title', async () => {\n        await act(async () => {\n            render(wrapIntl(\n                <ReduxProvider store={store}>\n                    <ViewTitle\n                        board={board}\n                        readonly={false}\n                    />\n                </ReduxProvider>,\n            ))\n        })\n        const titleInput = screen.getAllByRole('textbox')[0]\n        userEvent.type(titleInput, 'other title')\n        fireEvent.blur(titleInput)\n        expect(mockedMutator.changeBoardTitle).toBeCalledTimes(1)\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/viewTitle.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useState, useCallback} from 'react'\nimport {FormattedMessage, useIntl} from 'react-intl'\n\nimport {BlockIcons} from '../blockIcons'\nimport {Board} from '../blocks/board'\nimport mutator from '../mutator'\nimport Button from '../widgets/buttons/button'\nimport Editable from '../widgets/editable'\nimport CompassIcon from '../widgets/icons/compassIcon'\nimport {Permission} from '../constants'\nimport {useHasCurrentBoardPermissions} from '../hooks/permissions'\n\nimport BoardIconSelector from './boardIconSelector'\nimport {MarkdownEditor} from './markdownEditor'\nimport './viewTitle.scss'\n\ntype Props = {\n    board: Board\n    readonly: boolean\n}\n\nconst ViewTitle = (props: Props) => {\n    const {board} = props\n\n    const [title, setTitle] = useState(board.title)\n    const onEditTitleSave = useCallback(() => mutator.changeBoardTitle(board.id, board.title, title), [board.id, board.title, title])\n    const onEditTitleCancel = useCallback(() => setTitle(board.title), [board.title])\n    const onDescriptionBlur = useCallback((text) => mutator.changeBoardDescription(board.id, board.id, board.description, text), [board.id, board.description])\n    const onAddRandomIcon = useCallback(() => {\n        const newIcon = BlockIcons.shared.randomIcon()\n        mutator.changeBoardIcon(board.id, board.icon, newIcon)\n    }, [board.id, board.icon])\n    const onShowDescription = useCallback(() => mutator.showBoardDescription(board.id, Boolean(board.showDescription), true), [board.id, board.showDescription])\n    const onHideDescription = useCallback(() => mutator.showBoardDescription(board.id, Boolean(board.showDescription), false), [board.id, board.showDescription])\n    const canEditBoardProperties = useHasCurrentBoardPermissions([Permission.ManageBoardProperties])\n\n    const readonly = props.readonly || !canEditBoardProperties\n\n    const intl = useIntl()\n\n    return (\n        <div className='ViewTitle'>\n            <div className='add-buttons add-visible'>\n                {!readonly && !board.icon &&\n                    <Button\n                        emphasis='default'\n                        size='xsmall'\n                        onClick={onAddRandomIcon}\n                        icon={\n                            <CompassIcon\n                                icon='emoticon-outline'\n                            />}\n                    >\n                        <FormattedMessage\n                            id='TableComponent.add-icon'\n                            defaultMessage='Add icon'\n                        />\n                    </Button>\n                }\n                {!readonly && board.showDescription &&\n                    <Button\n                        emphasis='default'\n                        size='xsmall'\n                        onClick={onHideDescription}\n                        icon={\n                            <CompassIcon\n                                icon='eye-off-outline'\n                            />}\n                    >\n                        <FormattedMessage\n                            id='ViewTitle.hide-description'\n                            defaultMessage='hide description'\n                        />\n                    </Button>\n                }\n                {!readonly && !board.showDescription &&\n                    <Button\n                        emphasis='default'\n                        size='xsmall'\n                        onClick={onShowDescription}\n                        icon={\n                            <CompassIcon\n                                icon='eye-outline'\n                            />}\n                    >\n                        <FormattedMessage\n                            id='ViewTitle.show-description'\n                            defaultMessage='show description'\n                        />\n                    </Button>\n                }\n            </div>\n\n            <div className='title'>\n                <BoardIconSelector\n                    board={board}\n                    readonly={readonly}\n                />\n                <Editable\n                    className='title'\n                    value={title}\n                    placeholderText={intl.formatMessage({id: 'ViewTitle.untitled-board', defaultMessage: 'Untitled board'})}\n                    onChange={(newTitle) => setTitle(newTitle)}\n                    saveOnEsc={true}\n                    onSave={onEditTitleSave}\n                    onCancel={onEditTitleCancel}\n                    readonly={readonly}\n                    spellCheck={true}\n                />\n            </div>\n\n            {board.showDescription &&\n                <div className='description'>\n                    <MarkdownEditor\n                        text={board.description}\n                        placeholderText='Add a description...'\n                        onBlur={onDescriptionBlur}\n                        readonly={readonly}\n                    />\n                </div>\n            }\n        </div>\n    )\n}\n\nexport default React.memo(ViewTitle)\n"
  },
  {
    "path": "webapp/src/components/withWebSockets.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useEffect} from 'react'\n\nimport wsClient, {MMWebSocketClient} from '../wsclient'\nimport {Utils} from '../utils'\n\ntype Props = {\n    userId?: string\n    manifest?: {\n        id: string\n        version: string\n    }\n    webSocketClient?: MMWebSocketClient\n    children: React.ReactNode\n}\n\n// WithWebSockets component initialises the websocket connection if\n// it's not yet running and subscribes to the current team\nconst WithWebSockets = (props: Props): React.ReactElement => {\n    const queryString = new URLSearchParams(window.location.search)\n\n    useEffect(() => {\n        // if the websocket client was already connected, do nothing\n        if (wsClient.state !== 'init') {\n            return\n        }\n\n        // this is a temporary solution to disable websocket\n        // connections on legacy routes, as there is no such thing as\n        // an anonymous websocket connection\n        if (Utils.isFocalboardLegacy()) {\n            return\n        }\n\n        const token = localStorage.getItem('focalboardSessionId') || queryString.get('r') || ''\n        if (token) {\n            wsClient.authenticate(token)\n        }\n        wsClient.open()\n    }, [props.webSocketClient])\n\n    useEffect(() => {\n        if (!props.userId) {\n            return\n        }\n\n        const token = localStorage.getItem('focalboardSessionId') || queryString.get('r') || ''\n        if (wsClient.token !== token) {\n            wsClient.authenticate(token)\n        }\n    }, [props.userId])\n\n    return (\n        <>\n            {props.children}\n        </>\n    )\n}\n\nexport default WithWebSockets\n"
  },
  {
    "path": "webapp/src/components/workspace.scss",
    "content": "@import '../styles/z-index';\n\n.Workspace {\n    flex: 1 1 auto;\n    display: flex;\n    flex-direction: row;\n    overflow: hidden;\n    position: relative;\n\n    > .mainFrame {\n        @include z-index(workspace-main-frame);\n        position: relative;\n        flex: 1 1 auto;\n        display: flex;\n        flex-direction: column;\n        overflow: hidden;\n\n        > .banner {\n            background-color: rgba(230, 220, 192, 0.9);\n            text-align: center;\n            padding: 10px;\n            color: rgb(63, 67, 80);\n            font-weight: bold;\n        }\n    }\n}\n"
  },
  {
    "path": "webapp/src/components/workspace.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {act, render, waitFor} from '@testing-library/react'\nimport React from 'react'\nimport {Provider as ReduxProvider} from 'react-redux'\nimport {MemoryRouter} from 'react-router-dom'\nimport {mocked} from 'jest-mock'\n\nimport userEvent from '@testing-library/user-event'\n\nimport thunk from 'redux-thunk'\n\nimport {IUser} from '../user'\nimport octoClient from '../octoClient'\nimport {TestBlockFactory} from '../test/testBlockFactory'\nimport {mockDOM, mockMatchMedia, mockStateStore, wrapDNDIntl} from '../testUtils'\nimport {Constants} from '../constants'\nimport {Utils} from '../utils'\n\nimport Workspace from './workspace'\n\nObject.defineProperty(Constants, 'versionString', {value: '1.0.0'})\njest.useFakeTimers()\njest.mock('../utils')\njest.mock('../octoClient')\njest.mock('draft-js/lib/generateRandomKey', () => () => '123')\nconst mockedUtils = mocked(Utils, true)\nconst mockedOctoClient = mocked(octoClient, true)\nconst board = TestBlockFactory.createBoard()\nboard.id = 'board1'\nboard.teamId = 'team-id'\nboard.cardProperties = [\n    {\n        id: 'property1',\n        name: 'Property 1',\n        type: 'text',\n        options: [\n            {\n                id: 'value1',\n                value: 'value 1',\n                color: 'propColorBrown',\n            },\n        ],\n    },\n    {\n        id: 'property2',\n        name: 'Property 2',\n        type: 'select',\n        options: [\n            {\n                id: 'value2',\n                value: 'value 2',\n                color: 'propColorBlue',\n            },\n        ],\n    },\n]\nconst activeView = TestBlockFactory.createBoardView(board)\nactiveView.id = 'view1'\nactiveView.fields.hiddenOptionIds = []\nactiveView.fields.visiblePropertyIds = ['property1']\nactiveView.fields.visibleOptionIds = ['value1']\nconst fakeBoard = {id: board.id}\nactiveView.boardId = fakeBoard.id\nconst card1 = TestBlockFactory.createCard(board)\ncard1.id = 'card1'\ncard1.title = 'card-1'\ncard1.boardId = fakeBoard.id\nconst card2 = TestBlockFactory.createCard(board)\ncard2.id = 'card2'\ncard2.title = 'card-2'\ncard2.boardId = fakeBoard.id\nconst card3 = TestBlockFactory.createCard(board)\ncard3.id = 'card3'\ncard3.title = 'card-3'\ncard3.boardId = fakeBoard.id\n\nconst me: IUser = {\n    id: 'user-id-1',\n    username: 'username_1',\n    email: '',\n    nickname: '',\n    firstname: '',\n    lastname: '',\n    props: {},\n    create_at: 0,\n    update_at: 0,\n    is_bot: false,\n    is_guest: false,\n    roles: 'system_user',\n}\n\nconst categoryAttribute1 = TestBlockFactory.createCategoryBoards()\ncategoryAttribute1.name = 'Category 1'\ncategoryAttribute1.boardMetadata = [{boardID: board.id, hidden: false}]\n\njest.mock('react-router-dom', () => {\n    const originalModule = jest.requireActual('react-router-dom')\n\n    return {\n        ...originalModule,\n        useRouteMatch: jest.fn(() => {\n            return {\n                params: {\n                    boardId: board.id,\n                    viewId: activeView.id,\n                },\n            }\n        }),\n    }\n})\n\ndescribe('src/components/workspace', () => {\n    const state = {\n        teams: {\n            current: {id: 'team-id', title: 'Test Team'},\n        },\n        users: {\n            me,\n            boardUsers: {[me.id]: me},\n            blockSubscriptions: [],\n        },\n        boards: {\n            current: board.id,\n            boards: {\n                [board.id]: board,\n            },\n            templates: [],\n            myBoardMemberships: {\n                [board.id]: {userId: 'user_id_1', schemeAdmin: true},\n            },\n        },\n        limits: {\n            limits: {\n                cards: 0,\n                used_cards: 0,\n                card_limit_timestamp: 0,\n                views: 0,\n            },\n        },\n        globalTemplates: {\n            value: [],\n        },\n        views: {\n            views: {\n                [activeView.id]: activeView,\n            },\n            current: activeView.id,\n        },\n        cards: {\n            templates: [],\n            cards: [card1, card2, card3],\n        },\n        searchText: {},\n        clientConfig: {\n            value: {\n                telemetry: true,\n                telemetryid: 'telemetry',\n                enablePublicSharedBoards: true,\n                teammateNameDisplay: 'username',\n            },\n        },\n        contents: {\n            contents: {},\n        },\n        comments: {\n            comments: {},\n        },\n        sidebar: {\n            categoryAttributes: [\n                categoryAttribute1,\n            ],\n            hiddenBoardIDs: [],\n        },\n    }\n    mockedOctoClient.searchTeamUsers.mockResolvedValue(Object.values(state.users.boardUsers))\n    const store = mockStateStore([thunk], state)\n    beforeAll(() => {\n        mockDOM()\n        mockMatchMedia({matches: true})\n    })\n    beforeEach(() => {\n        jest.clearAllMocks()\n        mockedUtils.createGuid.mockReturnValue('test-id')\n    })\n    test('should match snapshot', async () => {\n        let container\n        await act(async () => {\n            const result = render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <Workspace readonly={false}/>\n                </ReduxProvider>,\n            ), {wrapper: MemoryRouter})\n            container = result.container\n            jest.runOnlyPendingTimers()\n        })\n        expect(container).toMatchSnapshot()\n    })\n    test('should match snapshot with readonly', async () => {\n        let container\n        await act(async () => {\n            const result = render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <Workspace readonly={true}/>\n                </ReduxProvider>,\n            ), {wrapper: MemoryRouter})\n            container = result.container\n            jest.runOnlyPendingTimers()\n        })\n        expect(container).toMatchSnapshot()\n    })\n\n    test('return workspace and showcard', async () => {\n        let container: Element | undefined\n        await act(async () => {\n            const result = render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <Workspace readonly={false}/>\n                </ReduxProvider>,\n            ), {wrapper: MemoryRouter})\n            container = result.container\n            jest.runOnlyPendingTimers()\n            const cardElements = container!.querySelectorAll('.KanbanCard')\n            expect(cardElements).toBeDefined()\n            const cardElement = cardElements[0]\n            userEvent.click(cardElement)\n        })\n        expect(container).toMatchSnapshot()\n    })\n\n    test('return workspace readonly and showcard', async () => {\n        let container: Element | undefined\n        await act(async () => {\n            const result = render(wrapDNDIntl(\n                <ReduxProvider store={store}>\n                    <Workspace readonly={true}/>\n                </ReduxProvider>,\n            ), {wrapper: MemoryRouter})\n            container = result.container\n            jest.runOnlyPendingTimers()\n            const cardElements = container!.querySelectorAll('.KanbanCard')\n            expect(cardElements).toBeDefined()\n            const cardElement = cardElements[0]\n            userEvent.click(cardElement)\n        })\n        expect(container).toMatchSnapshot()\n        expect(mockedUtils.getReadToken).toBeCalledTimes(1)\n    })\n\n    test('return workspace with BoardTemplateSelector component', async () => {\n        const emptyStore = mockStateStore([], {\n            users: {\n                me,\n                boardUsers: {[me.id]: me},\n            },\n            teams: {\n                current: {id: 'team-id', title: 'Test Team'},\n            },\n            boards: {\n                current: board.id,\n                boards: {\n                    [board.id]: board,\n                },\n                templates: [],\n                myBoardMemberships: {\n                    [board.id]: {userId: 'user_id_1', schemeAdmin: true},\n                },\n            },\n            views: {\n                views: {},\n            },\n            cards: {\n                cards: [],\n            },\n            globalTemplates: {\n                value: [],\n            },\n            searchText: {},\n            clientConfig: {\n                value: {\n                    telemetry: true,\n                    telemetryid: 'telemetry',\n                    enablePublicSharedBoards: true,\n                    teammateNameDisplay: 'username',\n                },\n            },\n            sidebar: {\n                categoryAttributes: [\n                    categoryAttribute1,\n                ],\n                hiddenBoardIDs: [],\n            },\n        })\n        let container: Element | undefined\n        await act(async () => {\n            const result = render(wrapDNDIntl(\n                <ReduxProvider store={emptyStore}>\n                    <Workspace readonly={true}/>\n                </ReduxProvider>,\n            ), {wrapper: MemoryRouter})\n            container = result.container\n            jest.runOnlyPendingTimers()\n        })\n\n        expect(container).toMatchSnapshot()\n    })\n\n    test('show add new view tooltip', async () => {\n        const welcomeBoard = TestBlockFactory.createBoard()\n        welcomeBoard.title = 'Welcome to Boards!'\n\n        const onboardingCard = TestBlockFactory.createCard(welcomeBoard)\n        onboardingCard.id = 'card1'\n        onboardingCard.title = 'Create a new card'\n        onboardingCard.boardId = welcomeBoard.id\n\n        const localState = {\n            teams: {\n                current: {id: 'team-id', title: 'Test Team'},\n            },\n            users: {\n                me: {\n                    id: 'user-id-1',\n                    username: 'username_1',\n                    email: '',\n                    nickname: '',\n                    firstname: '',\n                    lastname: '',\n                    create_at: 0,\n                    update_at: 0,\n                    is_bot: false,\n                    roles: 'system_user',\n                },\n                myConfig: {\n                    welcomePageViewed: {value: '1'},\n                    onboardingTourStarted: {value: true},\n                    tourCategory: {value: 'board'},\n                    onboardingTourStep: {value: '0'},\n                },\n                boardUsers: {[me.id]: me},\n                blockSubscriptions: [],\n            },\n            boards: {\n                current: welcomeBoard.id,\n                boards: {\n                    [welcomeBoard.id]: welcomeBoard,\n                },\n                templates: [],\n                myBoardMemberships: {\n                    [welcomeBoard.id]: {userId: 'user_id_1', schemeAdmin: true},\n                },\n            },\n            limits: {\n                limits: {\n                    cards: 0,\n                    used_cards: 0,\n                    card_limit_timestamp: 0,\n                    views: 0,\n                },\n            },\n            globalTemplates: {\n                value: [],\n            },\n            views: {\n                views: {\n                    [activeView.id]: activeView,\n                },\n                current: activeView.id,\n            },\n            cards: {\n                templates: [],\n                cards: [onboardingCard, card1, card2, card3],\n            },\n            searchText: {},\n            clientConfig: {\n                value: {\n                    telemetry: true,\n                    telemetryid: 'telemetry',\n                    enablePublicSharedBoards: true,\n                    teammateNameDisplay: 'username',\n                },\n            },\n            contents: {\n                contents: {},\n            },\n            comments: {\n                comments: {},\n            },\n            sidebar: {\n                categoryAttributes: [\n                    categoryAttribute1,\n                ],\n                hiddenBoardIDs: [],\n            },\n        }\n        const localStore = mockStateStore([thunk], localState)\n\n        await act(async () => {\n            render(wrapDNDIntl(\n                <ReduxProvider store={localStore}>\n                    <Workspace readonly={false}/>\n                </ReduxProvider>,\n            ), {wrapper: MemoryRouter})\n        })\n\n        jest.runOnlyPendingTimers()\n\n        await waitFor(() => expect(document.querySelectorAll('.AddViewTourStep')).toBeDefined(), {timeout: 5000})\n\n        const elements = document.querySelectorAll('.AddViewTourStep')\n        expect(elements.length).toBe(2)\n        expect(elements[1]).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/components/workspace.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useCallback, useEffect, useState} from 'react'\nimport {generatePath, useRouteMatch, useHistory} from 'react-router-dom'\nimport {FormattedMessage} from 'react-intl'\n\nimport {DatePropertyType} from '../properties/types'\n\nimport {getCurrentBoard, isLoadingBoard, getTemplates} from '../store/boards'\nimport {refreshCards, getCardLimitTimestamp, getCurrentBoardHiddenCardsCount, setLimitTimestamp, getCurrentViewCardsSortedFilteredAndGrouped, setCurrent as setCurrentCard} from '../store/cards'\nimport {\n    getCurrentBoardViews,\n    getCurrentViewGroupBy,\n    getCurrentViewId,\n    getCurrentViewDisplayBy,\n    getCurrentView,\n} from '../store/views'\nimport {useAppSelector, useAppDispatch} from '../store/hooks'\n\nimport {getClientConfig, setClientConfig} from '../store/clientConfig'\n\nimport wsClient, {WSClient} from '../wsclient'\nimport {ClientConfig} from '../config/clientConfig'\nimport {Utils} from '../utils'\nimport {IUser} from '../user'\nimport propsRegistry from '../properties'\n\nimport {getMe} from '../store/users'\n\nimport {getHiddenBoardIDs} from '../store/sidebar'\n\nimport CenterPanel from './centerPanel'\nimport BoardTemplateSelector from './boardTemplateSelector/boardTemplateSelector'\nimport GuestNoBoards from './guestNoBoards'\n\nimport Sidebar from './sidebar/sidebar'\n\nimport './workspace.scss'\n\ntype Props = {\n    readonly: boolean\n}\n\nfunction CenterContent(props: Props) {\n    const isLoading = useAppSelector(isLoadingBoard)\n    const match = useRouteMatch<{boardId: string, viewId: string, cardId?: string, channelId?: string}>()\n    const board = useAppSelector(getCurrentBoard)\n    const templates = useAppSelector(getTemplates)\n    const cards = useAppSelector(getCurrentViewCardsSortedFilteredAndGrouped)\n    const activeView = useAppSelector(getCurrentView)\n    const views = useAppSelector(getCurrentBoardViews)\n    const groupByProperty = useAppSelector(getCurrentViewGroupBy)\n    const dateDisplayProperty = useAppSelector(getCurrentViewDisplayBy)\n    const clientConfig = useAppSelector(getClientConfig)\n    const hiddenCardsCount = useAppSelector(getCurrentBoardHiddenCardsCount)\n    const cardLimitTimestamp = useAppSelector(getCardLimitTimestamp)\n    const history = useHistory()\n    const dispatch = useAppDispatch()\n    const me = useAppSelector<IUser|null>(getMe)\n    const hiddenBoardIDs = useAppSelector(getHiddenBoardIDs)\n\n    const isBoardHidden = () => {\n        return hiddenBoardIDs.includes(board.id)\n    }\n\n    const showCard = useCallback((cardId?: string) => {\n        const params = {...match.params, cardId}\n        let newPath = generatePath(Utils.getBoardPagePath(match.path), params)\n        if (props.readonly) {\n            newPath += `?r=${Utils.getReadToken()}`\n        }\n        history.push(newPath)\n        dispatch(setCurrentCard(cardId || ''))\n    }, [match, history])\n\n    useEffect(() => {\n        const onConfigChangeHandler = (_: WSClient, config: ClientConfig) => {\n            dispatch(setClientConfig(config))\n        }\n        wsClient.addOnConfigChange(onConfigChangeHandler)\n\n        const onCardLimitTimestampChangeHandler = (_: WSClient, timestamp: number) => {\n            dispatch(setLimitTimestamp({timestamp, templates}))\n            if (cardLimitTimestamp > timestamp) {\n                dispatch(refreshCards(timestamp))\n            }\n        }\n        wsClient.addOnCardLimitTimestampChange(onCardLimitTimestampChangeHandler)\n\n        return () => {\n            wsClient.removeOnConfigChange(onConfigChangeHandler)\n        }\n    }, [cardLimitTimestamp, match.params.boardId, templates])\n\n    const templateSelector = (\n        <BoardTemplateSelector\n            title={\n                <FormattedMessage\n                    id='BoardTemplateSelector.plugin.no-content-title'\n                    defaultMessage='Create a board'\n                />\n            }\n            description={\n                <FormattedMessage\n                    id='BoardTemplateSelector.plugin.no-content-description'\n                    defaultMessage='Add a board to the sidebar using any of the templates defined below or start from scratch.'\n                />\n            }\n            channelId={match.params.channelId}\n        />\n    )\n\n    if (match.params.channelId) {\n        if (me?.is_guest) {\n            return <GuestNoBoards/>\n        }\n        return templateSelector\n    }\n\n    if (board && !isBoardHidden() && activeView) {\n        let property = groupByProperty\n        if ((!property || !propsRegistry.get(property.type).canGroup) && activeView.fields.viewType === 'board') {\n            property = board?.cardProperties.find((o) => propsRegistry.get(o.type).canGroup)\n        }\n\n        let displayProperty = dateDisplayProperty\n        if (!displayProperty && activeView.fields.viewType === 'calendar') {\n            displayProperty = board.cardProperties.find((o) => propsRegistry.get(o.type) instanceof DatePropertyType)\n        }\n\n        return (\n            <CenterPanel\n                clientConfig={clientConfig}\n                readonly={props.readonly}\n                board={board}\n                cards={cards}\n                shownCardId={match.params.cardId}\n                showCard={showCard}\n                activeView={activeView}\n                groupByProperty={property}\n                dateDisplayProperty={displayProperty}\n                views={views}\n                hiddenCardsCount={hiddenCardsCount}\n            />\n        )\n    }\n\n    if ((board && !isBoardHidden()) || isLoading) {\n        return null\n    }\n\n    if (me?.is_guest) {\n        return <GuestNoBoards/>\n    }\n\n    return templateSelector\n}\n\nconst Workspace = (props: Props) => {\n    const board = useAppSelector(getCurrentBoard)\n\n    const viewId = useAppSelector(getCurrentViewId)\n    const [boardTemplateSelectorOpen, setBoardTemplateSelectorOpen] = useState(false)\n\n    const closeBoardTemplateSelector = useCallback(() => {\n        setBoardTemplateSelectorOpen(false)\n    }, [])\n    const openBoardTemplateSelector = useCallback(() => {\n        if (board) {\n            setBoardTemplateSelectorOpen(true)\n        }\n    }, [board])\n    useEffect(() => {\n        setBoardTemplateSelectorOpen(false)\n    }, [board, viewId])\n\n    return (\n        <div className='Workspace'>\n            {!props.readonly &&\n                <Sidebar\n                    onBoardTemplateSelectorOpen={openBoardTemplateSelector}\n                    onBoardTemplateSelectorClose={closeBoardTemplateSelector}\n                    activeBoardId={board?.id}\n                />\n            }\n            <div className='mainFrame'>\n                {boardTemplateSelectorOpen &&\n                    <BoardTemplateSelector onClose={closeBoardTemplateSelector}/>}\n                {(board?.isTemplate) &&\n                <div className='banner'>\n                    <FormattedMessage\n                        id='Workspace.editing-board-template'\n                        defaultMessage=\"You're editing a board template.\"\n                    />\n                </div>}\n                <CenterContent\n                    readonly={props.readonly}\n                />\n            </div>\n        </div>\n    )\n}\n\nexport default React.memo(Workspace)\n"
  },
  {
    "path": "webapp/src/config/clientConfig.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nexport type ClientConfig = {\n    telemetry: boolean\n    telemetryid: string\n    enablePublicSharedBoards: boolean\n    featureFlags: Record<string, string>\n    teammateNameDisplay: string\n    maxFileSize: number\n}\n"
  },
  {
    "path": "webapp/src/constants.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {TelemetryActions} from './telemetry/telemetryClient'\n\nenum Permission {\n    ManageBoardType = 'manage_board_type',\n    DeleteBoard = 'delete_board',\n    ShareBoard = 'share_board',\n    ManageBoardRoles = 'manage_board_roles',\n    ChannelCreatePost = 'create_post',\n    ManageBoardCards = 'manage_board_cards',\n    ManageBoardProperties = 'manage_board_properties',\n    CommentBoardCards = 'comment_board_cards',\n    ViewBoard = 'view_board',\n    DeleteOthersComments = 'delete_others_comments'\n}\n\nclass Constants {\n    static readonly menuColors: {[key: string]: string} = {\n        propColorDefault: 'Default',\n        propColorGray: 'Gray',\n        propColorBrown: 'Brown',\n        propColorOrange: 'Orange',\n        propColorYellow: 'Yellow',\n        propColorGreen: 'Green',\n        propColorBlue: 'Blue',\n        propColorPurple: 'Purple',\n        propColorPink: 'Pink',\n        propColorRed: 'Red',\n    }\n\n    static readonly minColumnWidth = 100\n    static readonly defaultTitleColumnWidth = 280\n    static readonly tableHeaderId = '__header'\n    static readonly tableCalculationId = '__calculation'\n    static readonly titleColumnId = '__title'\n    static readonly badgesColumnId = '__badges'\n\n    static readonly versionString = '8.0.0'\n    static readonly versionDisplayString = 'June 2024'\n\n    static readonly archiveHelpPage = 'https://docs.mattermost.com/boards/migrate-to-boards.html'\n    static readonly imports = [\n        {\n            id: 'trello',\n            displayName: 'Trello',\n            telemetryName: TelemetryActions.ImportTrello,\n            href: Constants.archiveHelpPage + '#import-from-trello',\n        },\n        {\n            id: 'asana',\n            displayName: 'Asana',\n            telemetryName: TelemetryActions.ImportAsana,\n            href: Constants.archiveHelpPage + '#import-from-asana',\n        },\n        {\n            id: 'notion',\n            displayName: 'Notion',\n            telemetryName: TelemetryActions.ImportNotion,\n            href: Constants.archiveHelpPage + '#import-from-notion',\n        },\n        {\n            id: 'jira',\n            displayName: 'Jira',\n            telemetryName: TelemetryActions.ImportJira,\n            href: Constants.archiveHelpPage + '#import-from-jira',\n        },\n        {\n            id: 'todoist',\n            displayName: 'Todoist',\n            telemetryName: TelemetryActions.ImportTodoist,\n            href: Constants.archiveHelpPage + '#import-from-todoist',\n        },\n    ]\n\n    static readonly languages = [\n        {\n            code: 'en',\n            name: 'english',\n            displayName: 'English',\n        },\n        {\n            code: 'es',\n            name: 'spanish',\n            displayName: 'Español',\n        },\n        {\n            code: 'de',\n            name: 'german',\n            displayName: 'Deutsch',\n        },\n        {\n            code: 'ja',\n            name: 'japanese',\n            displayName: '日本語',\n        },\n        {\n            code: 'fr',\n            name: 'french',\n            displayName: 'Français',\n        },\n        {\n            code: 'nl',\n            name: 'dutch',\n            displayName: 'Nederlands',\n        },\n        {\n            code: 'ru',\n            name: 'russian',\n            displayName: 'Pусский',\n        },\n        {\n            code: 'zh-tw',\n            name: 'traditional-chinese',\n            displayName: '中文 (繁體)',\n        },\n        {\n            code: 'zh-cn',\n            name: 'simplified-chinese',\n            displayName: '中文 (简体)',\n        },\n        {\n            code: 'tr',\n            name: 'turkish',\n            displayName: 'Türkçe',\n        },\n        {\n            code: 'oc',\n            name: 'occitan',\n            displayName: 'Occitan',\n        },\n        {\n            code: 'pt-br',\n            name: 'portuguese',\n            displayName: 'Português (Brasil)',\n        },\n        {\n            code: 'ca',\n            name: 'catalan',\n            displayName: 'Català',\n        },\n        {\n            code: 'el',\n            name: 'greek',\n            displayName: 'Ελληνικά',\n        },\n        {\n            code: 'id',\n            name: 'indonesian',\n            displayName: 'bahasa Indonesia',\n        },\n        {\n            code: 'it',\n            name: 'italian',\n            displayName: 'Italiano',\n        },\n        {\n            code: 'sv',\n            name: 'swedish',\n            displayName: 'Svenska',\n        },\n    ]\n\n    static readonly keyCodes: {[key: string]: [string, number]} = {\n        COMPOSING: ['Composing', 229],\n        ESC: ['Esc', 27],\n        UP: ['Up', 38],\n        DOWN: ['Down', 40],\n        ENTER: ['Enter', 13],\n        A: ['a', 65],\n        B: ['b', 66],\n        C: ['c', 67],\n        D: ['d', 68],\n        E: ['e', 69],\n        F: ['f', 70],\n        G: ['g', 71],\n        H: ['h', 72],\n        I: ['i', 73],\n        J: ['j', 74],\n        K: ['k', 75],\n        L: ['l', 76],\n        M: ['m', 77],\n        N: ['n', 78],\n        O: ['o', 79],\n        P: ['p', 80],\n        Q: ['q', 81],\n        R: ['r', 82],\n        S: ['s', 83],\n        T: ['t', 84],\n        U: ['u', 85],\n        V: ['v', 86],\n        W: ['w', 87],\n        X: ['x', 88],\n        Y: ['y', 89],\n        Z: ['z', 90],\n    }\n\n    static readonly globalTeamId = '0'\n    static readonly noChannelID = '0'\n\n    static readonly SystemUserID = 'system'\n}\n\nexport {Constants, Permission}\n"
  },
  {
    "path": "webapp/src/csvExporter.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {IntlShape} from 'react-intl'\n\nimport {BoardView} from './blocks/boardView'\nimport {Board, IPropertyTemplate} from './blocks/board'\nimport {Card} from './blocks/card'\nimport {Utils} from './utils'\nimport {IAppWindow} from './types'\nimport propsRegistry from './properties'\n\ndeclare let window: IAppWindow\nconst hashSignToken = '___hash_sign___'\n\nclass CsvExporter {\n    static exportTableCsv(board: Board, activeView: BoardView, cards: Card[], intl: IntlShape, view?: BoardView): void {\n        const viewToExport = view ?? activeView\n\n        if (!viewToExport) {\n            return\n        }\n\n        const rows = CsvExporter.generateTableArray(board, cards, viewToExport, intl)\n\n        let csvContent = 'data:text/csv;charset=utf-8,'\n\n        rows.forEach((row) => {\n            const encodedRow = row.join(',')\n            csvContent += encodedRow + '\\r\\n'\n        })\n\n        const encodedUri = encodeURI(csvContent).replace(hashSignToken, '%23')\n\n        const filename = `${Utils.sanitizeFilename(viewToExport.title || 'Untitled')}.csv`\n        const link = document.createElement('a')\n        link.style.display = 'none'\n        link.setAttribute('href', encodedUri)\n        link.setAttribute('download', filename)\n        document.body.appendChild(link)\t\t\t\t\t\t// FireFox support\n\n        link.click()\n\n        // TODO: Review if this is needed in the future, this is to fix the problem with linux webview links\n        if (window.openInNewBrowser) {\n            window.openInNewBrowser(encodedUri)\n        }\n\n        // TODO: Remove or reuse link\n    }\n\n    private static encodeText(text: string): string {\n        return text.replace(/\"/g, '\"\"').replace(/#/g, hashSignToken)\n    }\n\n    private static generateTableArray(board: Board, cards: Card[], viewToExport: BoardView, intl: IntlShape): string[][] {\n        const rows: string[][] = []\n        const visibleProperties = board.cardProperties.filter((template: IPropertyTemplate) => viewToExport.fields.visiblePropertyIds.includes(template.id))\n\n        if (viewToExport.fields.viewType === 'calendar' &&\n            viewToExport.fields.dateDisplayPropertyId &&\n            !viewToExport.fields.visiblePropertyIds.includes(viewToExport.fields.dateDisplayPropertyId)) {\n            const dateDisplay = board.cardProperties.find((template: IPropertyTemplate) => viewToExport.fields.dateDisplayPropertyId === template.id)\n            if (dateDisplay) {\n                visibleProperties.push(dateDisplay)\n            }\n        }\n\n        {\n            // Header row\n            const row: string[] = [intl.formatMessage({id: 'TableComponent.name', defaultMessage: 'Name'})]\n            visibleProperties.forEach((template: IPropertyTemplate) => {\n                row.push(template.name)\n            })\n            rows.push(row)\n        }\n\n        cards.forEach((card) => {\n            const row: string[] = []\n            row.push(`\"${this.encodeText(card.title)}\"`)\n            visibleProperties.forEach((template: IPropertyTemplate) => {\n                let propertyValue = card.fields.properties[template.id]\n                const property = propsRegistry.get(template.type)\n                if (property.type === 'createdBy') {\n                    propertyValue = card.createdBy\n                }\n                if (property.type === 'updatedBy') {\n                    propertyValue = card.modifiedBy\n                }\n                row.push(property.exportValue(propertyValue, card, template, intl))\n            })\n            rows.push(row)\n        })\n\n        return rows\n    }\n}\n\nexport {CsvExporter}\n"
  },
  {
    "path": "webapp/src/emojiList.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nconst emojiList = ['😀', '😁', '😂', '🤣', '😃', '😄', '😅', '😆', '😉', '😊', '😋', '😎', '😍', '😘', '😗', '😙', '😚', '☺', '🙂', '🤗', '🤔', '😐', '😑', '😶', '🙄', '😏', '😣', '😥', '😮', '🤐', '😯', '😪', '😫', '😴', '😌', '🤓', '😛', '😜', '😝', '🤤', '😒', '😓', '😔', '😕', '🙃', '🤑', '😲', '☹', '🙁', '😖', '😞', '😟', '😤', '😢', '😭', '😦', '😧', '😨', '😩', '😬', '😰', '😱', '😳', '😵', '😡', '😠', '😇', '🤠', '🤡', '🤥', '😷', '🤒', '🤕', '🤢', '🤧', '😈', '👿', '👹', '👺', '💀', '☠', '👻', '👽', '👾', '🤖', '💩', '😺', '😸', '😹', '😻', '😼', '😽', '🙀', '😿', '😾', '🙈', '🙉', '🙊', '👦', '👦🏻', '👦🏼', '👦🏽', '👦🏾', '👦🏿', '👧', '👧🏻', '👧🏼', '👧🏽', '👧🏾', '👧🏿', '👨', '👨🏻', '👨🏼', '👨🏽', '👨🏾', '👨🏿', '👩', '👩🏻', '👩🏼', '👩🏽', '👩🏾', '👩🏿', '👴', '👴🏻', '👴🏼', '👴🏽', '👴🏾', '👴🏿', '👵', '👵🏻', '👵🏼', '👵🏽', '👵🏾', '👵🏿', '👶', '👶🏻', '👶🏼', '👶🏽', '👶🏾', '👶🏿', '👼', '👼🏻', '👼🏼', '👼🏽', '👼🏾', '👼🏿', '👮', '👮🏻', '👮🏼', '👮🏽', '👮🏾', '👮🏿', '🕵', '🕵🏻', '🕵🏼', '🕵🏽', '🕵🏾', '🕵🏿', '💂', '💂🏻', '💂🏼', '💂🏽', '💂🏾', '💂🏿', '👷', '👷🏻', '👷🏼', '👷🏽', '👷🏾', '👷🏿', '👳', '👳🏻', '👳🏼', '👳🏽', '👳🏾', '👳🏿', '👱', '👱🏻', '👱🏼', '👱🏽', '👱🏾', '👱🏿', '🎅', '🎅🏻', '🎅🏼', '🎅🏽', '🎅🏾', '🎅🏿', '🤶', '🤶🏻', '🤶🏼', '🤶🏽', '🤶🏾', '🤶🏿', '👸', '👸🏻', '👸🏼', '👸🏽', '👸🏾', '👸🏿', '🤴', '🤴🏻', '🤴🏼', '🤴🏽', '🤴🏾', '🤴🏿', '👰', '👰🏻', '👰🏼', '👰🏽', '👰🏾', '👰🏿', '🤵', '🤵🏻', '🤵🏼', '🤵🏽', '🤵🏾', '🤵🏿', '🤰', '🤰🏻', '🤰🏼', '🤰🏽', '🤰🏾', '🤰🏿', '👲', '👲🏻', '👲🏼', '👲🏽', '👲🏾', '👲🏿', '🙍', '🙍🏻', '🙍🏼', '🙍🏽', '🙍🏾', '🙍🏿', '🙎', '🙎🏻', '🙎🏼', '🙎🏽', '🙎🏾', '🙎🏿', '🙅', '🙅🏻', '🙅🏼', '🙅🏽', '🙅🏾', '🙅🏿', '🙆', '🙆🏻', '🙆🏼', '🙆🏽', '🙆🏾', '🙆🏿', '💁', '💁🏻', '💁🏼', '💁🏽', '💁🏾', '💁🏿', '🙋', '🙋🏻', '🙋🏼', '🙋🏽', '🙋🏾', '🙋🏿', '🙇', '🙇🏻', '🙇🏼', '🙇🏽', '🙇🏾', '🙇🏿', '🤦', '🤦🏻', '🤦🏼', '🤦🏽', '🤦🏾', '🤦🏿', '🤷', '🤷🏻', '🤷🏼', '🤷🏽', '🤷🏾', '🤷🏿', '💆', '💆🏻', '💆🏼', '💆🏽', '💆🏾', '💆🏿', '💇', '💇🏻', '💇🏼', '💇🏽', '💇🏾', '💇🏿', '🚶', '🚶🏻', '🚶🏼', '🚶🏽', '🚶🏾', '🚶🏿', '🏃', '🏃🏻', '🏃🏼', '🏃🏽', '🏃🏾', '🏃🏿', '💃', '💃🏻', '💃🏼', '💃🏽', '💃🏾', '💃🏿', '🕺', '🕺🏻', '🕺🏼', '🕺🏽', '🕺🏾', '🕺🏿', '👯', '🕴', '🗣', '👤', '👥', '🤺', '🏇', '⛷', '🏂', '🏌', '🏄', '🏄🏻', '🏄🏼', '🏄🏽', '🏄🏾', '🏄🏿', '🚣', '🚣🏻', '🚣🏼', '🚣🏽', '🚣🏾', '🚣🏿', '🏊', '🏊🏻', '🏊🏼', '🏊🏽', '🏊🏾', '🏊🏿', '⛹', '⛹🏻', '⛹🏼', '⛹🏽', '⛹🏾', '⛹🏿', '🏋', '🏋🏻', '🏋🏼', '🏋🏽', '🏋🏾', '🏋🏿', '🚴', '🚴🏻', '🚴🏼', '🚴🏽', '🚴🏾', '🚴🏿', '🚵', '🚵🏻', '🚵🏼', '🚵🏽', '🚵🏾', '🚵🏿', '🏎', '🏍', '🤸', '🤸🏻', '🤸🏼', '🤸🏽', '🤸🏾', '🤸🏿', '🤼', '🤼🏻', '🤼🏼', '🤼🏽', '🤼🏾', '🤼🏿', '🤽', '🤽🏻', '🤽🏼', '🤽🏽', '🤽🏾', '🤽🏿', '🤾', '🤾🏻', '🤾🏼', '🤾🏽', '🤾🏾', '🤾🏿', '🤹', '🤹🏻', '🤹🏼', '🤹🏽', '🤹🏾', '🤹🏿', '👫', '👬', '👭', '💏', '👩‍❤️‍💋‍👨', '👨‍❤️‍💋‍👨', '👩‍❤️‍💋‍👩', '💑', '👩‍❤️‍👨', '👨‍❤️‍👨', '👩‍❤️‍👩', '👪', '👨‍👩‍👦', '👨‍👩‍👧', '👨‍👩‍👧‍👦', '👨‍👩‍👦‍👦', '👨‍👩‍👧‍👧', '👨‍👨‍👦', '👨‍👨‍👧', '👨‍👨‍👧‍👦', '👨‍👨‍👦‍👦', '👨‍👨‍👧‍👧', '👩‍👩‍👦', '👩‍👩‍👧', '👩‍👩‍👧‍👦', '👩‍👩‍👦‍👦', '👩‍👩‍👧‍👧', '🏻', '🏼', '🏽', '🏾', '🏿', '💪', '💪🏻', '💪🏼', '💪🏽', '💪🏾', '💪🏿', '🤳', '🤳🏻', '🤳🏼', '🤳🏽', '🤳🏾', '🤳🏿', '👈', '👈🏻', '👈🏼', '👈🏽', '👈🏾', '👈🏿', '👉', '👉🏻', '👉🏼', '👉🏽', '👉🏾', '👉🏿', '☝', '☝🏻', '☝🏼', '☝🏽', '☝🏾', '☝🏿', '👆', '👆🏻', '👆🏼', '👆🏽', '👆🏾', '👆🏿', '🖕', '🖕🏻', '🖕🏼', '🖕🏽', '🖕🏾', '🖕🏿', '👇', '👇🏻', '👇🏼', '👇🏽', '👇🏾', '👇🏿', '✌', '✌🏻', '✌🏼', '✌🏽', '✌🏾', '✌🏿', '🤞', '🤞🏻', '🤞🏼', '🤞🏽', '🤞🏾', '🤞🏿', '🖖', '🖖🏻', '🖖🏼', '🖖🏽', '🖖🏾', '🖖🏿', '🤘', '🤘🏻', '🤘🏼', '🤘🏽', '🤘🏾', '🤘🏿', '🤙', '🤙🏻', '🤙🏼', '🤙🏽', '🤙🏾', '🤙🏿', '🖐', '🖐🏻', '🖐🏼', '🖐🏽', '🖐🏾', '🖐🏿', '✋', '✋🏻', '✋🏼', '✋🏽', '✋🏾', '✋🏿', '👌', '👌🏻', '👌🏼', '👌🏽', '👌🏾', '👌🏿', '👍', '👍🏻', '👍🏼', '👍🏽', '👍🏾', '👍🏿', '👎', '👎🏻', '👎🏼', '👎🏽', '👎🏾', '👎🏿', '✊', '✊🏻', '✊🏼', '✊🏽', '✊🏾', '✊🏿', '👊', '👊🏻', '👊🏼', '👊🏽', '👊🏾', '👊🏿', '🤛', '🤛🏻', '🤛🏼', '🤛🏽', '🤛🏾', '🤛🏿', '🤜', '🤜🏻', '🤜🏼', '🤜🏽', '🤜🏾', '🤜🏿', '🤚', '🤚🏻', '🤚🏼', '🤚🏽', '🤚🏾', '🤚🏿', '👋', '👋🏻', '👋🏼', '👋🏽', '👋🏾', '👋🏿', '👏', '👏🏻', '👏🏼', '👏🏽', '👏🏾', '👏🏿', '✍', '✍🏻', '✍🏼', '✍🏽', '✍🏾', '✍🏿', '👐', '👐🏻', '👐🏼', '👐🏽', '👐🏾', '👐🏿', '🙌', '🙌🏻', '🙌🏼', '🙌🏽', '🙌🏾', '🙌🏿', '🙏', '🙏🏻', '🙏🏼', '🙏🏽', '🙏🏾', '🙏🏿', '🤝', '🤝🏻', '🤝🏼', '🤝🏽', '🤝🏾', '🤝🏿', '💅', '💅🏻', '💅🏼', '💅🏽', '💅🏾', '💅🏿', '👂', '👂🏻', '👂🏼', '👂🏽', '👂🏾', '👂🏿', '👃', '👃🏻', '👃🏼', '👃🏽', '👃🏾', '👃🏿', '👣', '👀', '👁', '👁‍🗨', '👅', '👄', '💋', '💘', '❤', '💓', '💔', '💕', '💖', '💗', '💙', '💚', '💛', '💜', '🖤', '💝', '💞', '💟', '❣', '💌', '💤', '💢', '💣', '💥', '💦', '💨', '💫', '💬', '🗨', '🗯', '💭', '🕳', '👓', '🕶', '👔', '👕', '👖', '👗', '👘', '👙', '👚', '👛', '👜', '👝', '🛍', '🎒', '👞', '👟', '👠', '👡', '👢', '👑', '👒', '🎩', '🎓', '⛑', '📿', '💄', '💍', '💎', '🐵', '🐒', '🦍', '🐶', '🐕', '🐩', '🐺', '🦊', '🐱', '🐈', '🦁', '🐯', '🐅', '🐆', '🐴', '🐎', '🦌', '🦄', '🐮', '🐂', '🐃', '🐄', '🐷', '🐖', '🐗', '🐽', '🐏', '🐑', '🐐', '🐪', '🐫', '🐘', '🦏', '🐭', '🐁', '🐀', '🐹', '🐰', '🐇', '🐿', '🦇', '🐻', '🐨', '🐼', '🐾', '🦃', '🐔', '🐓', '🐣', '🐤', '🐥', '🐦', '🐧', '🕊', '🦅', '🦆', '🦉', '🐸', '🐊', '🐢', '🦎', '🐍', '🐲', '🐉', '🐳', '🐋', '🐬', '🐟', '🐠', '🐡', '🦈', '🐙', '🐚', '🦀', '🦐', '🦑', '🦋', '🐌', '🐛', '🐜', '🐝', '🐞', '🕷', '🕸', '🦂', '💐', '🌸', '💮', '🏵', '🌹', '🥀', '🌺', '🌻', '🌼', '🌷', '🌱', '🌲', '🌳', '🌴', '🌵', '🌾', '🌿', '☘', '🍀', '🍁', '🍂', '🍃', '🍇', '🍈', '🍉', '🍊', '🍋', '🍌', '🍍', '🍎', '🍏', '🍐', '🍑', '🍒', '🍓', '🥝', '🍅', '🥑', '🍆', '🥔', '🥕', '🌽', '🌶', '🥒', '🍄', '🥜', '🌰', '🍞', '🥐', '🥖', '🥞', '🧀', '🍖', '🍗', '🥓', '🍔', '🍟', '🍕', '🌭', '🌮', '🌯', '🥙', '🥚', '🍳', '🥘', '🍲', '🥗', '🍿', '🍱', '🍘', '🍙', '🍚', '🍛', '🍜', '🍝', '🍠', '🍢', '🍣', '🍤', '🍥', '🍡', '🍦', '🍧', '🍨', '🍩', '🍪', '🎂', '🍰', '🍫', '🍬', '🍭', '🍮', '🍯', '🍼', '🥛', '☕', '🍵', '🍶', '🍾', '🍷', '🍸', '🍹', '🍺', '🍻', '🥂', '🥃', '🍽', '🍴', '🥄', '🔪', '🏺', '🌍', '🌎', '🌏', '🌐', '🗺', '🗾', '🏔', '⛰', '🌋', '🗻', '🏕', '🏖', '🏜', '🏝', '🏞', '🏟', '🏛', '🏗', '🏘', '🏙', '🏚', '🏠', '🏡', '🏢', '🏣', '🏤', '🏥', '🏦', '🏨', '🏩', '🏪', '🏫', '🏬', '🏭', '🏯', '🏰', '💒', '🗼', '🗽', '⛪', '🕌', '🕍', '⛩', '🕋', '⛲', '⛺', '🌁', '🌃', '🌄', '🌅', '🌆', '🌇', '🌉', '♨', '🌌', '🎠', '🎡', '🎢', '💈', '🎪', '🎭', '🖼', '🎨', '🎰', '🚂', '🚃', '🚄', '🚅', '🚆', '🚇', '🚈', '🚉', '🚊', '🚝', '🚞', '🚋', '🚌', '🚍', '🚎', '🚐', '🚑', '🚒', '🚓', '🚔', '🚕', '🚖', '🚗', '🚘', '🚙', '🚚', '🚛', '🚜', '🚲', '🛴', '🛵', '🚏', '🛣', '🛤', '⛽', '🚨', '🚥', '🚦', '🚧', '🛑', '⚓', '⛵', '🛶', '🚤', '🛳', '⛴', '🛥', '🚢', '✈', '🛩', '🛫', '🛬', '💺', '🚁', '🚟', '🚠', '🚡', '🚀', '🛰', '🛎', '🚪', '🛌', '🛏', '🛋', '🚽', '🚿', '🛀', '🛀🏻', '🛀🏼', '🛀🏽', '🛀🏾', '🛀🏿', '🛁', '⌛', '⏳', '⌚', '⏰', '⏱', '⏲', '🕰', '🕛', '🕧', '🕐', '🕜', '🕑', '🕝', '🕒', '🕞', '🕓', '🕟', '🕔', '🕠', '🕕', '🕡', '🕖', '🕢', '🕗', '🕣', '🕘', '🕤', '🕙', '🕥', '🕚', '🕦', '🌑', '🌒', '🌓', '🌔', '🌕', '🌖', '🌗', '🌘', '🌙', '🌚', '🌛', '🌜', '🌡', '☀', '🌝', '🌞', '⭐', '🌟', '🌠', '☁', '⛅', '⛈', '🌤', '🌥', '🌦', '🌧', '🌨', '🌩', '🌪', '🌫', '🌬', '🌀', '🌈', '🌂', '☂', '☔', '⛱', '⚡', '❄', '☃', '⛄', '☄', '🔥', '💧', '🌊', '🎃', '🎄', '🎆', '🎇', '✨', '🎈', '🎉', '🎊', '🎋', '🎍', '🎎', '🎏', '🎐', '🎑', '🎀', '🎁', '🎗', '🎟', '🎫', '🎖', '🏆', '🏅', '🥇', '🥈', '🥉', '⚽', '⚾', '🏀', '🏐', '🏈', '🏉', '🎾', '🎱', '🎳', '🏏', '🏑', '🏒', '🏓', '🏸', '🥊', '🥋', '🥅', '🎯', '⛳', '⛸', '🎣', '🎽', '🎿', '🎮', '🕹', '🎲', '♠', '♥', '♦', '♣', '🃏', '🀄', '🎴', '🔇', '🔈', '🔉', '🔊', '📢', '📣', '📯', '🔔', '🔕', '🎼', '🎵', '🎶', '🎙', '🎚', '🎛', '🎤', '🎧', '📻', '🎷', '🎸', '🎹', '🎺', '🎻', '🥁', '📱', '📲', '☎', '📞', '📟', '📠', '🔋', '🔌', '💻', '🖥', '🖨', '⌨', '🖱', '🖲', '💽', '💾', '💿', '📀', '🎥', '🎞', '📽', '🎬', '📺', '📷', '📸', '📹', '📼', '🔍', '🔎', '🔬', '🔭', '📡', '🕯', '💡', '🔦', '🏮', '📔', '📕', '📖', '📗', '📘', '📙', '📚', '📓', '📒', '📃', '📜', '📄', '📰', '🗞', '📑', '🔖', '🏷', '💰', '💴', '💵', '💶', '💷', '💸', '💳', '💹', '💱', '💲', '✉', '📧', '📨', '📩', '📤', '📥', '📦', '📫', '📪', '📬', '📭', '📮', '🗳', '✏', '✒', '🖋', '🖊', '🖌', '🖍', '📝', '💼', '📁', '📂', '🗂', '📅', '📆', '🗒', '🗓', '📇', '📈', '📉', '📊', '📋', '📌', '📍', '📎', '🖇', '📏', '📐', '✂', '🗃', '🗄', '🗑', '🔒', '🔓', '🔏', '🔐', '🔑', '🗝', '🔨', '⛏', '⚒', '🛠', '🗡', '⚔', '🔫', '🏹', '🛡', '🔧', '🔩', '⚙', '🗜', '⚗', '⚖', '🔗', '⛓', '💉', '💊', '🚬', '⚰', '⚱', '🗿', '🛢', '🔮', '🛒', '🏧', '🚮', '🚰', '♿', '🚹', '🚺', '🚻', '🚼', '🚾', '🛂', '🛃', '🛄', '🛅', '⚠', '🚸', '⛔', '🚫', '🚳', '🚭', '🚯', '🚱', '🚷', '📵', '🔞', '☢', '☣', '⬆', '↗', '➡', '↘', '⬇', '↙', '⬅', '↖', '↕', '↔', '↩', '↪', '⤴', '⤵', '🔃', '🔄', '🔙', '🔚', '🔛', '🔜', '🔝', '🛐', '⚛', '🕉', '✡', '☸', '☯', '✝', '☦', '☪', '☮', '🕎', '🔯', '♈', '♉', '♊', '♋', '♌', '♍', '♎', '♏', '♐', '♑', '♒', '♓', '⛎', '🔀', '🔁', '🔂', '▶', '⏩', '⏭', '⏯', '◀', '⏪', '⏮', '🔼', '⏫', '🔽', '⏬', '⏸', '⏹', '⏺', '⏏', '🎦', '🔅', '🔆', '📶', '📳', '📴', '♻', '📛', '⚜', '🔰', '🔱', '⭕', '✅', '☑', '✔', '✖', '❌', '❎', '➕', '➖', '➗', '➰', '➿', '〽', '✳', '✴', '❇', '‼', '⁉', '❓', '❔', '❕', '❗', '〰', '©', '®', '™', '#️⃣', '*️⃣', '0️⃣', '1️⃣', '2️⃣', '3️⃣', '4️⃣', '5️⃣', '6️⃣', '7️⃣', '8️⃣', '9️⃣', '🔟', '💯', '🔠', '🔡', '🔢', '🔣', '🔤', '🅰', '🆎', '🅱', '🆑', '🆒', '🆓', 'ℹ', '🆔', 'Ⓜ', '🆕', '🆖', '🅾', '🆗', '🅿', '🆘', '🆙', '🆚', '🈁', '🈂', '🈷', '🈶', '🈯', '🉐', '🈹', '🈚', '🈲', '🉑', '🈸', '🈴', '🈳', '㊗', '㊙', '🈺', '🈵', '▪', '▫', '◻', '◼', '◽', '◾', '⬛', '⬜', '🔶', '🔷', '🔸', '🔹', '🔺', '🔻', '💠', '🔘', '🔲', '🔳', '⚪', '⚫', '🔴', '🔵', '🏁', '🚩', '🎌', '🏴', '🏳', '🇦🇨', '🇦🇩', '🇦🇪', '🇦🇫', '🇦🇬', '🇦🇮', '🇦🇱', '🇦🇲', '🇦🇴', '🇦🇶', '🇦🇷', '🇦🇸', '🇦🇹', '🇦🇺', '🇦🇼', '🇦🇽', '🇦🇿', '🇧🇦', '🇧🇧', '🇧🇩', '🇧🇪', '🇧🇫', '🇧🇬', '🇧🇭', '🇧🇮', '🇧🇯', '🇧🇱', '🇧🇲', '🇧🇳', '🇧🇴', '🇧🇶', '🇧🇷', '🇧🇸', '🇧🇹', '🇧🇻', '🇧🇼', '🇧🇾', '🇧🇿', '🇨🇦', '🇨🇨', '🇨🇩', '🇨🇫', '🇨🇬', '🇨🇭', '🇨🇮', '🇨🇰', '🇨🇱', '🇨🇲', '🇨🇳', '🇨🇴', '🇨🇵', '🇨🇷', '🇨🇺', '🇨🇻', '🇨🇼', '🇨🇽', '🇨🇾', '🇨🇿', '🇩🇪', '🇩🇬', '🇩🇯', '🇩🇰', '🇩🇲', '🇩🇴', '🇩🇿', '🇪🇦', '🇪🇨', '🇪🇪', '🇪🇬', '🇪🇭', '🇪🇷', '🇪🇸', '🇪🇹', '🇪🇺', '🇫🇮', '🇫🇯', '🇫🇰', '🇫🇲', '🇫🇴', '🇫🇷', '🇬🇦', '🇬🇧', '🇬🇩', '🇬🇪', '🇬🇫', '🇬🇬', '🇬🇭', '🇬🇮', '🇬🇱', '🇬🇲', '🇬🇳', '🇬🇵', '🇬🇶', '🇬🇷', '🇬🇸', '🇬🇹', '🇬🇺', '🇬🇼', '🇬🇾', '🇭🇰', '🇭🇲', '🇭🇳', '🇭🇷', '🇭🇹', '🇭🇺', '🇮🇨', '🇮🇩', '🇮🇪', '🇮🇱', '🇮🇲', '🇮🇳', '🇮🇴', '🇮🇶', '🇮🇷', '🇮🇸', '🇮🇹', '🇯🇪', '🇯🇲', '🇯🇴', '🇯🇵', '🇰🇪', '🇰🇬', '🇰🇭', '🇰🇮', '🇰🇲', '🇰🇳', '🇰🇵', '🇰🇷', '🇰🇼', '🇰🇾', '🇰🇿', '🇱🇦', '🇱🇧', '🇱🇨', '🇱🇮', '🇱🇰', '🇱🇷', '🇱🇸', '🇱🇹', '🇱🇺', '🇱🇻', '🇱🇾', '🇲🇦', '🇲🇨', '🇲🇩', '🇲🇪', '🇲🇫', '🇲🇬', '🇲🇭', '🇲🇰', '🇲🇱', '🇲🇲', '🇲🇳', '🇲🇴', '🇲🇵', '🇲🇶', '🇲🇷', '🇲🇸', '🇲🇹', '🇲🇺', '🇲🇻', '🇲🇼', '🇲🇽', '🇲🇾', '🇲🇿', '🇳🇦', '🇳🇨', '🇳🇪', '🇳🇫', '🇳🇬', '🇳🇮', '🇳🇱', '🇳🇴', '🇳🇵', '🇳🇷', '🇳🇺', '🇳🇿', '🇴🇲', '🇵🇦', '🇵🇪', '🇵🇫', '🇵🇬', '🇵🇭', '🇵🇰', '🇵🇱', '🇵🇲', '🇵🇳', '🇵🇷', '🇵🇸', '🇵🇹', '🇵🇼', '🇵🇾', '🇶🇦', '🇷🇪', '🇷🇴', '🇷🇸', '🇷🇺', '🇷🇼', '🇸🇦', '🇸🇧', '🇸🇨', '🇸🇩', '🇸🇪', '🇸🇬', '🇸🇭', '🇸🇮', '🇸🇯', '🇸🇰', '🇸🇱', '🇸🇲', '🇸🇳', '🇸🇴', '🇸🇷', '🇸🇸', '🇸🇹', '🇸🇻', '🇸🇽', '🇸🇾', '🇸🇿', '🇹🇦', '🇹🇨', '🇹🇩', '🇹🇫', '🇹🇬', '🇹🇭', '🇹🇯', '🇹🇰', '🇹🇱', '🇹🇲', '🇹🇳', '🇹🇴', '🇹🇷', '🇹🇹', '🇹🇻', '🇹🇼', '🇹🇿', '🇺🇦', '🇺🇬', '🇺🇲', '🇺🇸', '🇺🇾', '🇺🇿', '🇻🇦', '🇻🇨', '🇻🇪', '🇻🇬', '🇻🇮', '🇻🇳', '🇻🇺', '🇼🇫', '🇼🇸', '🇽🇰', '🇾🇪', '🇾🇹', '🇿🇦', '🇿🇲', '🇿🇼']\n\nconst randomEmojiList = ['👀', '👁', '👁‍🗨', '👅', '👄', '💋', '💘', '❤', '💓', '💔', '💕', '💖', '💗', '💙', '💚', '💛', '💜', '🖤', '💝', '💞', '💟', '❣', '💌', '💣', '💥', '💦', '💨', '💫', '💬', '🗨', '🗯', '💭', '🕳', '👓', '🕶', '👔', '👕', '👖', '👗', '👘', '👙', '👚', '👛', '👜', '👝', '🛍', '🎒', '👞', '👟', '👠', '👡', '👢', '👑', '👒', '🎩', '🎓', '⛑', '📿', '💄', '💍', '💎', '🐵', '🐒', '🦍', '🐶', '🐕', '🐩', '🐺', '🦊', '🐱', '🐈', '🦁', '🐯', '🐅', '🐆', '🐴', '🐎', '🦌', '🦄', '🐮', '🐂', '🐃', '🐄', '🐷', '🐖', '🐗', '🐽', '🐏', '🐑', '🐐', '🐪', '🐫', '🐘', '🦏', '🐭', '🐁', '🐀', '🐹', '🐰', '🐇', '🐿', '🦇', '🐻', '🐨', '🐼', '🐾', '🦃', '🐔', '🐓', '🐣', '🐤', '🐥', '🐦', '🐧', '🕊', '🦅', '🦆', '🦉', '🐸', '🐊', '🐢', '🦎', '🐍', '🐲', '🐉', '🐳', '🐋', '🐬', '🐟', '🐠', '🐡', '🦈', '🐙', '🐚', '🦀', '🦐', '🦑', '🦋', '🐌', '🐛', '🐜', '🐝', '🐞', '🕷', '🕸', '🦂', '💐', '🌸', '💮', '🏵', '🌹', '🥀', '🌺', '🌻', '🌼', '🌷', '🌱', '🌲', '🌳', '🌴', '🌵', '🌾', '🌿', '☘', '🍀', '🍁', '🍂', '🍃', '🍇', '🍈', '🍉', '🍊', '🍋', '🍌', '🍍', '🍎', '🍏', '🍐', '🍑', '🍒', '🍓', '🥝', '🍅', '🥑', '🍆', '🥔', '🥕', '🌽', '🌶', '🥒', '🍄', '🥜', '🌰', '🍞', '🥐', '🥖', '🥞', '🧀', '🍖', '🍗', '🥓', '🍔', '🍟', '🍕', '🌭', '🌮', '🌯', '🥙', '🥚', '🍳', '🥘', '🍲', '🥗', '🍿', '🍱', '🍘', '🍙', '🍚', '🍛', '🍜', '🍝', '🍠', '🍢', '🍣', '🍤', '🍥', '🍡', '🍦', '🍧', '🍨', '🍩', '🍪', '🎂', '🍰', '🍫', '🍬', '🍭', '🍮', '🍯', '🍼', '🥛', '☕', '🍵', '🍶', '🍾', '🍷', '🍸', '🍹', '🍺', '🍻', '🥂', '🥃', '🍽', '🍴', '🥄', '🔪', '🏺', '🌍', '🌎', '🌏', '🌐', '🗺', '🗾', '🏔', '⛰', '🌋', '🗻', '🏕', '🏖', '🏜', '🏝', '🏞', '🏟', '🏛', '🏗', '🏘', '🏙', '🏚', '🏠', '🏡', '🏢', '🏣', '🏤', '🏥', '🏦', '🏨', '🏩', '🏪', '🏫', '🏬', '🏭', '🏯', '🏰', '💒', '🗼', '🗽', '⛪', '🕌', '🕍', '⛩', '🕋', '⛲', '⛺', '🌁', '🌃', '🌄', '🌅', '🌆', '🌇', '🌉', '♨', '🌌', '🎠', '🎡', '🎢', '💈', '🎪', '🎭', '🖼', '🎨', '🎰', '🚂', '🚃', '🚄', '🚅', '🚆', '🚇', '🚈', '🚉', '🚊', '🚝', '🚞', '🚋', '🚌', '🚍', '🚎', '🚐', '🚑', '🚒', '🚓', '🚔', '🚕', '🚖', '🚗', '🚘', '🚙', '🚚', '🚛', '🚜', '🚲', '🛴', '🛵', '🚏', '🛣', '🛤', '⛽', '🚨', '🚥', '🚦', '🚧', '🛑', '⚓', '⛵', '🛶', '🚤', '🛳', '⛴', '🛥', '🚢', '✈', '🛩', '🛫', '🛬', '💺', '🚁', '🚟', '🚠', '🚡', '🚀', '🛰', '🛎', '🚪', '🛌', '🛏', '🛋', '🚽', '🚿', '🛀', '🛀🏻', '🛀🏼', '🛀🏽', '🛀🏾', '🛀🏿', '🛁', '⌛', '⏳', '⌚', '⏰', '⏱', '⏲', '🕰', '🕛', '🕧', '🕐', '🕜', '🕑', '🕝', '🕒', '🕞', '🕓', '🕟', '🕔', '🕠', '🕕', '🕡', '🕖', '🕢', '🕗', '🕣', '🕘', '🕤', '🕙', '🕥', '🕚', '🕦', '🌑', '🌒', '🌓', '🌔', '🌕', '🌖', '🌗', '🌘', '🌙', '🌚', '🌛', '🌜', '🌡', '☀', '🌝', '🌞', '⭐', '🌟', '🌠', '☁', '⛅', '⛈', '🌤', '🌥', '🌦', '🌧', '🌨', '🌩', '🌪', '🌫', '🌬', '🌀', '🌈', '🌂', '☂', '☔', '⛱', '⚡', '❄', '☃', '⛄', '☄', '🔥', '💧', '🌊', '🎃', '🎄', '🎆', '🎇', '✨', '🎈', '🎉', '🎊', '🎋', '🎍', '🎎', '🎏', '🎐', '🎑', '🎀', '🎁', '🎗', '🎟', '🎫', '🎖', '🏆', '🏅', '🥇', '🥈', '🥉', '⚽', '⚾', '🏀', '🏐', '🏈', '🏉', '🎾', '🎱', '🎳', '🏏', '🏑', '🏒', '🏓', '🏸', '🥊', '🥋', '🥅', '🎯', '⛳', '⛸', '🎣', '🎽', '🎿', '🎮', '🕹', '🎲', '♠', '♥', '♦', '♣', '🃏', '🀄', '🎴', '🔇', '🔈', '🔉', '🔊', '📢', '📣', '📯', '🔔', '🔕', '🎼', '🎵', '🎶', '🎙', '🎚', '🎛', '🎤', '🎧', '📻', '🎷', '🎸', '🎹', '🎺', '🎻', '🥁', '📱', '📲', '☎', '📞', '📟', '📠', '🔋', '🔌', '💻', '🖥', '🖨', '⌨', '🖱', '🖲', '💽', '💾', '💿', '📀', '🎥', '🎞', '📽', '🎬', '📺', '📷', '📸', '📹', '📼', '🔍', '🔎', '🔬', '🔭', '📡', '🕯', '💡', '🔦', '🏮', '📔', '📕', '📖', '📗', '📘', '📙', '📚', '📓', '📒', '📃', '📜', '📄', '📰', '🗞', '📑', '🔖', '🏷', '💰', '💴', '💵', '💶', '💷', '💸', '💳', '💹', '💱', '💲', '✉', '📧', '📨', '📩', '📤', '📥', '📦', '📫', '📪', '📬', '📭', '📮', '🗳', '✏', '✒', '🖋', '🖊', '🖌', '🖍', '📝', '💼', '📁', '📂', '🗂', '📅', '📆', '🗒', '🗓', '📇', '📈', '📉', '📊', '📋', '📌', '📍', '📎', '🖇', '📏', '📐', '✂', '🗃', '🗄', '🗑', '🔒', '🔓', '🔏', '🔐', '🔑', '🗝', '🔨', '⛏', '⚒', '🛠', '🗡', '⚔', '🔫', '🏹', '🛡', '🔧', '🔩', '⚙', '🗜', '⚗', '⚖', '🔗', '⛓', '💉', '💊', '🚬', '⚰', '⚱', '🗿', '🛢', '🔮', '🛒', '🏧', '🚮', '🚰', '♿', '🚹', '🚺', '🚻', '🚼', '🚾', '🛂', '🛃', '🛄', '🛅', '⚠', '🚸', '⛔', '🚫', '🚳', '🚭', '🚯', '🚱', '🚷', '📵', '🔞', '☢', '☣', '🔃', '🔄', '🔙', '🔚', '🔛', '🔜', '🔝', '🛐', '⚛', '♈', '♉', '♊', '♋', '♌', '♍', '♎', '♏', '♐', '♑', '♒', '♓', '⛎', '🔅', '🔆', '♻', '📛', '⚜', '🔰', '🔱', '💯', '🏁', '🚩']\n\nexport {emojiList, randomEmojiList}\n\n"
  },
  {
    "path": "webapp/src/errors.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {useIntl} from 'react-intl'\n\nimport {UserSettings} from './userSettings'\n\nenum ErrorId {\n    TeamUndefined = 'team-undefined',\n    NotLoggedIn = 'not-logged-in',\n    InvalidReadOnlyBoard = 'invalid-read-only-board',\n    BoardNotFound = 'board-not-found',\n}\n\ntype ErrorDef = {\n    title: string\n\n    button1Enabled: boolean\n    button1Text: string\n    button1Redirect: string | ((params: URLSearchParams) => string)\n    button1Fill: boolean\n    button1ClearHistory: boolean\n\n    button2Enabled: boolean\n    button2Text: string\n    button2Redirect: string | ((params: URLSearchParams) => string)\n    button2Fill: boolean\n    button2ClearHistory: boolean\n}\n\nfunction errorDefFromId(id: ErrorId | null): ErrorDef {\n    const errDef: ErrorDef = {\n        title: '',\n        button1Enabled: false,\n        button1Text: '',\n        button1Redirect: '',\n        button1Fill: false,\n        button1ClearHistory: false,\n        button2Enabled: false,\n        button2Text: '',\n        button2Redirect: '',\n        button2Fill: false,\n        button2ClearHistory: false,\n    }\n\n    const intl = useIntl()\n\n    switch (id) {\n    case ErrorId.TeamUndefined: {\n        errDef.title = intl.formatMessage({id: 'error.team-undefined', defaultMessage: 'Not a valid team.'})\n        errDef.button1Enabled = true\n        errDef.button1Text = intl.formatMessage({id: 'error.back-to-home', defaultMessage: 'Back to Home'})\n        errDef.button1Redirect = (): string => {\n            UserSettings.setLastTeamID(null)\n            return window.location.origin\n        }\n        errDef.button1Fill = true\n        break\n    }\n    case ErrorId.BoardNotFound: {\n        errDef.title = intl.formatMessage({id: 'error.board-not-found', defaultMessage: 'Board not found.'})\n        errDef.button1Enabled = true\n        errDef.button1Text = intl.formatMessage({id: 'error.back-to-team', defaultMessage: 'Back to team'})\n        errDef.button1Redirect = '/'\n        errDef.button1Fill = true\n        break\n    }\n    case ErrorId.NotLoggedIn: {\n        errDef.title = intl.formatMessage({id: 'error.not-logged-in', defaultMessage: 'Your session may have expired or you\\'re not logged in. Log in again to access Boards.'})\n        errDef.button1Enabled = true\n        errDef.button1Text = intl.formatMessage({id: 'error.go-login', defaultMessage: 'Log in'})\n        errDef.button1Redirect = '/login'\n        errDef.button1Redirect = (params: URLSearchParams): string => {\n            const r = params.get('r')\n            if (r) {\n                return `/login?r=${r}`\n            }\n            return '/login'\n        }\n        errDef.button1Fill = true\n        break\n    }\n    case ErrorId.InvalidReadOnlyBoard: {\n        errDef.title = intl.formatMessage({id: 'error.invalid-read-only-board', defaultMessage: 'You don\\'t have access to this board. Log in to access Boards.'})\n        errDef.button1Enabled = true\n        errDef.button1Text = intl.formatMessage({id: 'error.go-login', defaultMessage: 'Log in'})\n        errDef.button1Redirect = (): string => {\n            return window.location.origin\n        }\n        errDef.button1Fill = true\n        break\n    }\n    default: {\n        errDef.title = intl.formatMessage({id: 'error.unknown', defaultMessage: 'An error occurred.'})\n        errDef.button1Enabled = true\n        errDef.button1Text = intl.formatMessage({id: 'error.back-to-home', defaultMessage: 'Back to Home'})\n        errDef.button1Redirect = '/'\n        errDef.button1Fill = true\n        errDef.button1ClearHistory = true\n        break\n    }\n    }\n    return errDef\n}\n\nexport {ErrorId, ErrorDef, errorDefFromId}\n"
  },
  {
    "path": "webapp/src/file.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nconst Files: Record<string, string[]> = {\n    AUDIO_TYPES: ['mp3', 'wav', 'wma', 'm4a', 'flac', 'aac', 'ogg'],\n    CODE_TYPES: ['as', 'applescript', 'osascript', 'scpt', 'bash', 'sh', 'zsh', 'clj', 'boot', 'cl2', 'cljc', 'cljs', 'cljs.hl', 'cljscm', 'cljx', 'hic', 'coffee', '_coffee', 'cake', 'cjsx', 'cson', 'iced', 'cpp', 'c', 'cc', 'h', 'c++', 'h++', 'hpp', 'cs', 'csharp', 'css', 'd', 'di', 'dart', 'delphi', 'dpr', 'dfm', 'pas', 'pascal', 'freepascal', 'lazarus', 'lpr', 'lfm', 'diff', 'django', 'jinja', 'dockerfile', 'docker', 'erl', 'f90', 'f95', 'fsharp', 'fs', 'gcode', 'nc', 'go', 'groovy', 'handlebars', 'hbs', 'html.hbs', 'html.handlebars', 'hs', 'hx', 'java', 'jsp', 'js', 'jsx', 'json', 'jl', 'kt', 'ktm', 'kts', 'less', 'lisp', 'lua', 'mk', 'mak', 'md', 'mkdown', 'mkd', 'matlab', 'm', 'mm', 'objc', 'obj-c', 'ml', 'perl', 'pl', 'php', 'php3', 'php4', 'php5', 'php6', 'ps', 'ps1', 'pp', 'py', 'gyp', 'r', 'ruby', 'rb', 'gemspec', 'podspec', 'thor', 'irb', 'rs', 'scala', 'scm', 'sld', 'scss', 'st', 'sql', 'swift', 'tex', 'vbnet', 'vb', 'bas', 'vbs', 'v', 'veo', 'xml', 'html', 'xhtml', 'rss', 'atom', 'xsl', 'plist', 'yaml'],\n    IMAGE_TYPES: ['jpg', 'gif', 'bmp', 'png', 'jpeg', 'tiff', 'tif'],\n    PATCH_TYPES: ['patch'],\n    PDF_TYPES: ['pdf'],\n    PRESENTATION_TYPES: ['ppt', 'pptx'],\n    SPREADSHEET_TYPES: ['xlsx', 'csv'],\n    TEXT_TYPES: ['txt', 'rtf'],\n    VIDEO_TYPES: ['mp4', 'avi', 'webm', 'mkv', 'wmv', 'mpg', 'mov', 'flv'],\n    WORD_TYPES: ['doc', 'docx'],\n    COMPRESSED_TYPES: ['arc', 'arj', 'b64', 'btoa', 'bz', 'bz2', 'cab', 'cpt', 'gz', 'hqx', 'iso', 'lha', 'lzh', 'mim', 'mme', 'pak', 'pf', 'rar', 'rpm', 'sea', 'sit', 'sitx', 'tar', 'gz', 'tbz', 'tbz2', 'tgz', 'uu', 'uue', 'z', 'zip', 'zipx', 'zoo'],\n}\n\nexport default Files\n"
  },
  {
    "path": "webapp/src/fileIcons.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nconst FileIcons: Record<string, string> = {\n    AUDIO_TYPES: 'file-audio-outline',\n    CODE_TYPES: 'file-code-outline-large',\n    IMAGE_TYPES: 'file-image-outline-large',\n    PDF_TYPES: 'file-pdf-outline-large',\n    PATCH_TYPES: 'file-patch-outline-large',\n    PRESENTATION_TYPES: 'file-powerpoint-outline-large',\n    SPREADSHEET_TYPES: 'file-excel-outline-large',\n    TEXT_TYPES: 'file-text-outline-large',\n    VIDEO_TYPES: 'file-video-outline-large',\n    WORD_TYPES: 'file-word-outline-large',\n    COMPRESSED_TYPES: 'file-zip-outline-large',\n}\n\nexport default FileIcons\n"
  },
  {
    "path": "webapp/src/hooks/permissions.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {useAppSelector} from '../store/hooks'\nimport {getMyBoardMembership, getCurrentBoardId, getBoard} from '../store/boards'\nimport {getCurrentTeam} from '../store/teams'\nimport {Permission} from '../constants'\nimport {MemberRole} from '../blocks/board'\n\nexport const useHasPermissions = (teamId: string, boardId: string, permissions: Permission[]): boolean => {\n    if (!boardId || !teamId) {\n        return false\n    }\n\n    const member = useAppSelector(getMyBoardMembership(boardId))\n    const board = useAppSelector(getBoard(boardId))\n\n    if (!board) {\n        return false\n    }\n\n    if (!member) {\n        return false\n    }\n\n    const adminPermissions = [Permission.ManageBoardType, Permission.DeleteBoard, Permission.ShareBoard, Permission.ManageBoardRoles, Permission.DeleteOthersComments]\n    const editorPermissions = [Permission.ManageBoardCards, Permission.ManageBoardProperties]\n    const commenterPermissions = [Permission.CommentBoardCards]\n    const viewerPermissions = [Permission.ViewBoard]\n\n    for (const permission of permissions) {\n        if (adminPermissions.includes(permission) && member.schemeAdmin) {\n            return true\n        }\n        if (editorPermissions.includes(permission) && (member.schemeAdmin || member.schemeEditor || board.minimumRole === MemberRole.Editor)) {\n            return true\n        }\n        if (commenterPermissions.includes(permission) && (member.schemeAdmin || member.schemeEditor || member.schemeCommenter || board.minimumRole === MemberRole.Commenter || board.minimumRole === MemberRole.Editor)) {\n            return true\n        }\n        if (viewerPermissions.includes(permission) && (member.schemeAdmin || member.schemeEditor || member.schemeCommenter || member.schemeViewer || board.minimumRole === MemberRole.Viewer || board.minimumRole === MemberRole.Commenter || board.minimumRole === MemberRole.Editor)) {\n            return true\n        }\n    }\n    return false\n}\n\nexport const useHasCurrentTeamPermissions = (boardId: string, permissions: Permission[]): boolean => {\n    const currentTeam = useAppSelector(getCurrentTeam)\n    return useHasPermissions(currentTeam?.id || '', boardId, permissions)\n}\n\nexport const useHasCurrentBoardPermissions = (permissions: Permission[]): boolean => {\n    const currentBoardId = useAppSelector(getCurrentBoardId)\n\n    return useHasCurrentTeamPermissions(currentBoardId || '', permissions)\n}\n"
  },
  {
    "path": "webapp/src/hooks/sortable.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useRef} from 'react'\nimport {useDrag, useDrop, DragElementWrapper, DragSourceOptions, DragPreviewOptions} from 'react-dnd'\n\nimport {IContentBlockWithCords} from '../blocks/contentBlock'\nimport {Block} from '../blocks/block'\ninterface ISortableWithGripItem {\n    block: Block | Block[]\n    cords: {x: number, y?: number, z?: number}\n}\n\nfunction useSortableBase<T>(itemType: string, item: T, enabled: boolean, handler: (src: T, st: T) => void): [boolean, boolean, DragElementWrapper<DragSourceOptions>, DragElementWrapper<DragSourceOptions>, DragElementWrapper<DragPreviewOptions>] {\n    const [{isDragging}, drag, preview] = useDrag(() => ({\n        type: itemType,\n        item,\n        collect: (monitor) => ({\n            isDragging: monitor.isDragging(),\n        }),\n        canDrag: () => enabled,\n    }), [itemType, item, enabled])\n    const [{isOver}, drop] = useDrop(() => ({\n        accept: itemType,\n        collect: (monitor) => ({\n            isOver: monitor.isOver(),\n        }),\n        drop: (dragItem: T) => {\n            handler(dragItem, item)\n        },\n        canDrop: () => enabled,\n    }), [item, handler, enabled])\n\n    return [isDragging, isOver, drag, drop, preview]\n}\n\nexport function useSortable<T>(itemType: string, item: T, enabled: boolean, handler: (src: T, st: T) => void): [boolean, boolean, React.RefObject<HTMLDivElement>] {\n    const ref = useRef<HTMLDivElement>(null)\n    const [isDragging, isOver, drag, drop] = useSortableBase(itemType, item, enabled, handler)\n    drop(drag(ref))\n    return [isDragging, isOver, ref]\n}\n\nexport function useSortableWithGrip(itemType: string, item: ISortableWithGripItem, enabled: boolean, handler: (src: IContentBlockWithCords, st: IContentBlockWithCords) => void): [boolean, boolean, React.RefObject<HTMLDivElement>, React.RefObject<HTMLDivElement>] {\n    const ref = useRef<HTMLDivElement>(null)\n    const previewRef = useRef<HTMLDivElement>(null)\n    const [isDragging, isOver, drag, drop, preview] = useSortableBase(itemType, item as IContentBlockWithCords, enabled, handler)\n    drag(ref)\n    drop(preview(previewRef))\n    return [isDragging, isOver, ref, previewRef]\n}\n"
  },
  {
    "path": "webapp/src/hooks/useGetAllTemplates.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {useEffect, useMemo} from 'react'\n\nimport {Board} from '../blocks/board'\n\nimport octoClient from '../octoClient'\n\nimport {useAppDispatch, useAppSelector} from '../store/hooks'\nimport {fetchGlobalTemplates, getGlobalTemplates} from '../store/globalTemplates'\nimport {getTemplates} from '../store/boards'\n\nimport {Constants} from '../constants'\n\nexport const useGetAllTemplates = () => {\n    const dispatch = useAppDispatch()\n    const globalTemplates = useAppSelector<Board[]>(getGlobalTemplates) || []\n\n    useEffect(() => {\n        if (octoClient.teamId !== Constants.globalTeamId && globalTemplates.length === 0) {\n            dispatch(fetchGlobalTemplates())\n        }\n    }, [octoClient.teamId])\n\n    const unsortedTemplates = useAppSelector(getTemplates)\n    const templates = useMemo(() => Object.values(unsortedTemplates).sort((a: Board, b: Board) => a.createAt - b.createAt), [unsortedTemplates])\n\n    return useMemo(() => globalTemplates.concat(templates), [globalTemplates])\n}\n"
  },
  {
    "path": "webapp/src/hooks/websockets.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {useEffect} from 'react'\n\nimport wsClient, {WSClient} from '../wsclient'\n\nexport const useWebsockets = (teamId: string, fn: (wsClient: WSClient) => () => void, deps: any[] = []): void => {\n    useEffect(() => {\n        if (!teamId) {\n            return () => {}\n        }\n\n        wsClient.subscribeToTeam(teamId)\n        const teardown = fn(wsClient)\n\n        return () => {\n            teardown()\n            wsClient.unsubscribeToTeam(teamId)\n        }\n    }, [teamId, ...deps])\n}\n"
  },
  {
    "path": "webapp/src/i18n.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport messages_ca from '../i18n/ca.json'\nimport messages_de from '../i18n/de.json'\nimport messages_el from '../i18n/el.json'\nimport messages_en from '../i18n/en.json'\nimport messages_es from '../i18n/es.json'\nimport messages_fr from '../i18n/fr.json'\nimport messages_id from '../i18n/id.json'\nimport messages_it from '../i18n/it.json'\nimport messages_ja from '../i18n/ja.json'\nimport messages_nl from '../i18n/nl.json'\nimport messages_oc from '../i18n/oc.json'\nimport messages_ptBr from '../i18n/pt_BR.json'\nimport messages_ru from '../i18n/ru.json'\nimport messages_sv from '../i18n/sv.json'\nimport messages_tr from '../i18n/tr.json'\nimport messages_zhHans from '../i18n/zh_Hans.json'\nimport messages_zhHant from '../i18n/zh_Hant.json'\n\nimport {UserSettings} from './userSettings'\n\nconst supportedLanguages = ['ca', 'de', 'el', 'en', 'es', 'fr', 'id', 'it', 'ja', 'nl', 'oc', 'pt-br', 'ru', 'sv', 'tr', 'zh-cn', 'zh-tw']\n\nexport function getMessages(lang: string): {[key: string]: string} {\n    switch (lang) {\n    case 'ca':\n        return messages_ca\n    case 'de':\n        return messages_de\n    case 'el':\n        return messages_el\n    case 'es':\n        return messages_es\n    case 'fr':\n        return messages_fr\n    case 'id':\n        return messages_id\n    case 'it':\n        return messages_it\n    case 'ja':\n        return messages_ja\n    case 'nl':\n        return messages_nl\n    case 'oc':\n        return messages_oc\n    case 'pt-br':\n        return messages_ptBr\n    case 'ru':\n        return messages_ru\n    case 'sv':\n        return messages_sv\n    case 'tr':\n        return messages_tr\n    case 'zh-cn':\n        return messages_zhHans\n    case 'zh-tw':\n        return messages_zhHant\n    }\n    return messages_en\n}\nexport function getCurrentLanguage(): string {\n    let lang = UserSettings.language\n    if (!lang) {\n        if (supportedLanguages.includes(navigator.language)) {\n            lang = navigator.language\n        } else if (supportedLanguages.includes(navigator.language.split(/[-_]/)[0])) {\n            lang = navigator.language.split(/[-_]/)[0]\n        } else {\n            lang = 'en'\n        }\n    }\n    return lang\n}\n\nexport function storeLanguage(lang: string): void {\n    UserSettings.language = lang\n}\n"
  },
  {
    "path": "webapp/src/insights/index.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nexport type TopBoard = {\n    boardID: string\n    icon: string\n    title: string\n    activityCount: number\n    activeUsers: string\n    createdBy: string\n}\n\nexport type TopBoardResponse = {\n    has_next: boolean\n    items: TopBoard[]\n}\n"
  },
  {
    "path": "webapp/src/main.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\nimport ReactDOM from 'react-dom'\nimport {Provider as ReduxProvider} from 'react-redux'\nimport {store as emojiMartStore} from 'emoji-mart'\n\nimport App from './app'\nimport {initThemes} from './theme'\nimport {importNativeAppSettings} from './nativeApp'\nimport {UserSettings} from './userSettings'\n\nimport {IUser} from './user'\nimport {getMe} from './store/users'\nimport {useAppSelector} from './store/hooks'\n\nimport '@mattermost/compass-icons/css/compass-icons.css'\n\nimport './styles/variables.scss'\nimport './styles/main.scss'\nimport './styles/labels.scss'\nimport './styles/_markdown.scss'\n\nimport store from './store'\nimport WithWebSockets from './components/withWebSockets'\n\nemojiMartStore.setHandlers({getter: UserSettings.getEmojiMartSetting, setter: UserSettings.setEmojiMartSetting})\nimportNativeAppSettings()\n\ninitThemes()\n\nconst MainApp = () => {\n    const me = useAppSelector<IUser|null>(getMe)\n\n    return (\n        <WithWebSockets userId={me?.id}>\n            <App/>\n        </WithWebSockets>\n    )\n}\n\nReactDOM.render(\n    (\n        <ReduxProvider store={store}>\n            <MainApp/>\n        </ReduxProvider>\n    ),\n    document.getElementById('focalboard-app'),\n)\n"
  },
  {
    "path": "webapp/src/mutator.test.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport mutator from './mutator'\nimport {TestBlockFactory} from './test/testBlockFactory'\nimport 'isomorphic-fetch'\nimport {FetchMock} from './test/fetchMock'\nimport {mockDOM} from './testUtils'\n\nglobal.fetch = FetchMock.fn\n\nbeforeEach(() => {\n    FetchMock.fn.mockReset()\n})\n\nbeforeAll(() => {\n    mockDOM()\n})\n\ndescribe('Mutator', () => {\n    test('changePropertyValue', async () => {\n        const board = TestBlockFactory.createBoard()\n        const card = TestBlockFactory.createCard()\n        card.boardId = board.id\n        card.fields.properties.property_1 = 'hello'\n\n        await mutator.changePropertyValue(board.id, card, 'property_1', 'hello')\n\n        // No API call should be made as property value DIDN'T CHANGE\n        expect(FetchMock.fn).toBeCalledTimes(0)\n\n        await mutator.changePropertyValue(board.id, card, 'property_1', 'hello world')\n\n        // 1 API call should be made as property value DID CHANGE\n        expect(FetchMock.fn).toBeCalledTimes(1)\n    })\n\n    test('duplicateCard', async () => {\n        const board = TestBlockFactory.createBoard()\n        const card = TestBlockFactory.createCard(board)\n\n        FetchMock.fn.mockReturnValueOnce(FetchMock.jsonResponse(JSON.stringify([card])))\n        const [newBlocks, newCardID] = await mutator.duplicateCard(card.id, board.id)\n\n        expect(newBlocks).toHaveLength(1)\n\n        const duplicatedCard = newBlocks[0]\n        expect(duplicatedCard.type).toBe('card')\n        expect(duplicatedCard.id).toBe(newCardID)\n        expect(duplicatedCard.fields.icon).toBe(card.fields.icon)\n        expect(duplicatedCard.fields.contentOrder).toHaveLength(card.fields.contentOrder.length)\n        expect(duplicatedCard.boardId).toBe(board.id)\n    })\n})\n"
  },
  {
    "path": "webapp/src/mutator.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {IntlShape} from 'react-intl'\nimport {batch} from 'react-redux'\nimport cloneDeep from 'lodash/cloneDeep'\n\nimport {BlockIcons} from './blockIcons'\nimport {Block, BlockPatch, createPatchesFromBlocks} from './blocks/block'\nimport {Board, BoardMember, BoardsAndBlocks, IPropertyOption, IPropertyTemplate, PropertyTypeEnum, createBoard, createPatchesFromBoards, createPatchesFromBoardsAndBlocks, createCardPropertiesPatches} from './blocks/board'\nimport {BoardView, ISortOption, createBoardView, KanbanCalculationFields} from './blocks/boardView'\nimport {Card, createCard} from './blocks/card'\nimport {ContentBlock} from './blocks/contentBlock'\nimport {CommentBlock} from './blocks/commentBlock'\nimport {AttachmentBlock} from './blocks/attachmentBlock'\nimport {FilterGroup} from './blocks/filterGroup'\nimport octoClient from './octoClient'\nimport undoManager from './undomanager'\nimport {Utils, IDType} from './utils'\nimport {UserSettings} from './userSettings'\nimport TelemetryClient, {TelemetryCategory, TelemetryActions} from './telemetry/telemetryClient'\nimport {Category} from './store/sidebar'\n\n/* eslint-disable max-lines */\nimport {UserConfigPatch, UserPreference} from './user'\nimport store from './store'\nimport {updateBoards} from './store/boards'\nimport {updateViews} from './store/views'\nimport {updateCards} from './store/cards'\nimport {updateAttachments} from './store/attachments'\nimport {updateComments} from './store/comments'\nimport {updateContents} from './store/contents'\nimport {addBoardUsers, removeBoardUsersById} from './store/users'\n\nfunction updateAllBoardsAndBlocks(boards: Board[], blocks: Block[]) {\n    return batch(() => {\n        store.dispatch(updateBoards(boards.filter((b: Board) => b.deleteAt !== 0) as Board[]))\n        store.dispatch(updateViews(blocks.filter((b: Block) => b.type === 'view' || b.deleteAt !== 0) as BoardView[]))\n        store.dispatch(updateCards(blocks.filter((b: Block) => b.type === 'card' || b.deleteAt !== 0) as Card[]))\n        store.dispatch(updateAttachments(blocks.filter((b: Block) => b.type === 'attachment' || b.deleteAt !== 0) as AttachmentBlock[]))\n        store.dispatch(updateComments(blocks.filter((b: Block) => b.type === 'comment' || b.deleteAt !== 0) as CommentBlock[]))\n        store.dispatch(updateContents(blocks.filter((b: Block) => b.type !== 'card' && b.type !== 'view' && b.type !== 'board' && b.type !== 'comment') as ContentBlock[]))\n    })\n}\n\n//\n// The Mutator is used to make all changes to server state\n// It also ensures that the Undo-manager is called for each action\n//\nclass Mutator {\n    private undoGroupId?: string\n    private undoDisplayId?: string\n\n    private beginUndoGroup(): string | undefined {\n        if (this.undoGroupId) {\n            Utils.assertFailure('UndoManager does not support nested groups')\n            return undefined\n        }\n        this.undoGroupId = Utils.createGuid(IDType.None)\n        return this.undoGroupId\n    }\n\n    private endUndoGroup(groupId: string) {\n        if (this.undoGroupId !== groupId) {\n            Utils.assertFailure('Mismatched groupId. UndoManager does not support nested groups')\n            return\n        }\n        this.undoGroupId = undefined\n    }\n\n    async performAsUndoGroup(actions: () => Promise<void>): Promise<void> {\n        const groupId = this.beginUndoGroup()\n        try {\n            await actions()\n        } catch (err) {\n            Utils.assertFailure(`ERROR: ${err}`)\n        }\n        if (groupId) {\n            this.endUndoGroup(groupId)\n        }\n    }\n\n    async updateBlock(boardId: string, newBlock: Block, oldBlock: Block, description: string): Promise<void> {\n        const [updatePatch, undoPatch] = createPatchesFromBlocks(newBlock, oldBlock)\n        await undoManager.perform(\n            async () => {\n                await octoClient.patchBlock(boardId, newBlock.id, updatePatch)\n            },\n            async () => {\n                await octoClient.patchBlock(boardId, oldBlock.id, undoPatch)\n            },\n            description,\n            this.undoGroupId,\n        )\n    }\n\n    private async updateBlocks(boardId: string, newBlocks: Block[], oldBlocks: Block[], description: string): Promise<void> {\n        if (newBlocks.length !== oldBlocks.length) {\n            throw new Error('new and old blocks must have the same length when updating blocks')\n        }\n\n        const updatePatches = [] as BlockPatch[]\n        const undoPatches = [] as BlockPatch[]\n\n        newBlocks.forEach((newBlock, i) => {\n            const [updatePatch, undoPatch] = createPatchesFromBlocks(newBlock, oldBlocks[i])\n            updatePatches.push(updatePatch)\n            undoPatches.push(undoPatch)\n        })\n\n        return undoManager.perform(\n            async () => {\n                await Promise.all(\n                    updatePatches.map((patch, i) => octoClient.patchBlock(boardId, newBlocks[i].id, patch)),\n                )\n            },\n            async () => {\n                await Promise.all(\n                    undoPatches.map((patch, i) => octoClient.patchBlock(boardId, newBlocks[i].id, patch)),\n                )\n            },\n            description,\n            this.undoGroupId,\n        )\n    }\n\n    //eslint-disable-next-line no-shadow\n    async insertBlock(boardId: string, block: Block, description = 'add', afterRedo?: (block: Block) => Promise<void>, beforeUndo?: (block: Block) => Promise<void>): Promise<Block> {\n        return undoManager.perform(\n            async () => {\n                const res = await octoClient.insertBlock(boardId, block)\n                const jsonres = await res.json()\n                const newBlock = jsonres[0] as Block\n                await afterRedo?.(newBlock)\n                return newBlock\n            },\n            async (newBlock: Block) => {\n                await beforeUndo?.(newBlock)\n                await octoClient.deleteBlock(boardId, newBlock.id)\n            },\n            description,\n            this.undoGroupId,\n        )\n    }\n\n    //eslint-disable-next-line no-shadow\n    async insertBlocks(boardId: string, blocks: Block[], description = 'add', afterRedo?: (blocks: Block[]) => Promise<void>, beforeUndo?: () => Promise<void>, sourceBoardID?: string) {\n        return undoManager.perform(\n            async () => {\n                const res = await octoClient.insertBlocks(boardId, blocks, sourceBoardID)\n                const newBlocks = (await res.json()) as Block[]\n                updateAllBoardsAndBlocks([], newBlocks)\n                await afterRedo?.(newBlocks)\n                return newBlocks\n            },\n            async (newBlocks: Block[]) => {\n                await beforeUndo?.()\n                const awaits = []\n                for (const block of newBlocks) {\n                    awaits.push(octoClient.deleteBlock(boardId, block.id))\n                }\n                await Promise.all(awaits)\n            },\n            description,\n            this.undoGroupId,\n        )\n    }\n\n    async deleteBlock(block: Block, description?: string, beforeRedo?: () => Promise<void>, afterUndo?: () => Promise<void>) {\n        const actualDescription = description || `delete ${block.type}`\n\n        await undoManager.perform(\n            async () => {\n                await beforeRedo?.()\n                await octoClient.deleteBlock(block.boardId, block.id)\n            },\n            async () => {\n                await octoClient.undeleteBlock(block.boardId, block.id)\n                await afterUndo?.()\n            },\n            actualDescription,\n            this.undoGroupId,\n        )\n    }\n\n    async createBoardsAndBlocks(bab: BoardsAndBlocks, description = 'add', afterRedo?: (b: BoardsAndBlocks) => Promise<void>, beforeUndo?: (b: BoardsAndBlocks) => Promise<void>): Promise<BoardsAndBlocks> {\n        return undoManager.perform(\n            async () => {\n                const res = await octoClient.createBoardsAndBlocks(bab)\n                const newBab = (await res.json()) as BoardsAndBlocks\n                await afterRedo?.(newBab)\n                return newBab\n            },\n            async (newBab: BoardsAndBlocks) => {\n                await beforeUndo?.(newBab)\n\n                const boardIds = newBab.boards.map((b) => b.id)\n                const blockIds = newBab.blocks.map((b) => b.id)\n                await octoClient.deleteBoardsAndBlocks(boardIds, blockIds)\n            },\n            description,\n            this.undoGroupId,\n        )\n    }\n\n    async updateBoard(newBoard: Board, oldBoard: Board, description: string): Promise<void> {\n        const [updatePatch, undoPatch] = createPatchesFromBoards(newBoard, oldBoard)\n        await undoManager.perform(\n            async () => {\n                await octoClient.patchBoard(newBoard.id, updatePatch)\n            },\n            async () => {\n                await octoClient.patchBoard(oldBoard.id, undoPatch)\n            },\n            description,\n            this.undoGroupId,\n        )\n    }\n\n    async deleteBoard(board: Board, description?: string, afterRedo?: (b: Board) => Promise<void>, beforeUndo?: (b: Board) => Promise<void>) {\n        await undoManager.perform(\n            async () => {\n                await octoClient.deleteBoard(board.id)\n                await afterRedo?.(board)\n            },\n            async () => {\n                await beforeUndo?.(board)\n                await octoClient.undeleteBoard(board.id)\n            },\n            description,\n            this.undoGroupId,\n        )\n    }\n\n    async changeBlockTitle(boardId: string, blockId: string, oldTitle: string, newTitle: string, description = 'change block title') {\n        await undoManager.perform(\n            async () => {\n                await octoClient.patchBlock(boardId, blockId, {title: newTitle})\n            },\n            async () => {\n                await octoClient.patchBlock(boardId, blockId, {title: oldTitle})\n            },\n            description,\n            this.undoGroupId,\n        )\n    }\n\n    async changeBoardTitle(boardId: string, oldTitle: string, newTitle: string, description = 'change board title') {\n        await undoManager.perform(\n            async () => {\n                await octoClient.patchBoard(boardId, {title: newTitle})\n            },\n            async () => {\n                await octoClient.patchBoard(boardId, {title: oldTitle})\n            },\n            description,\n            this.undoGroupId,\n        )\n    }\n\n    async setDefaultTemplate(boardId: string, blockId: string, oldTemplateId: string, templateId: string, description = 'set default template') {\n        await undoManager.perform(\n            async () => {\n                await octoClient.patchBlock(boardId, blockId, {updatedFields: {defaultTemplateId: templateId}})\n            },\n            async () => {\n                await octoClient.patchBlock(boardId, blockId, {updatedFields: {defaultTemplateId: oldTemplateId}})\n            },\n            description,\n            this.undoGroupId,\n        )\n    }\n\n    async clearDefaultTemplate(boardId: string, blockId: string, oldTemplateId: string, description = 'set default template') {\n        await undoManager.perform(\n            async () => {\n                await octoClient.patchBlock(boardId, blockId, {updatedFields: {defaultTemplateId: ''}})\n            },\n            async () => {\n                await octoClient.patchBlock(boardId, blockId, {updatedFields: {defaultTemplateId: oldTemplateId}})\n            },\n            description,\n            this.undoGroupId,\n        )\n    }\n\n    async changeBoardIcon(boardId: string, oldIcon: string|undefined, icon: string, description = 'change board icon') {\n        await undoManager.perform(\n            async () => {\n                await octoClient.patchBoard(boardId, {icon})\n            },\n            async () => {\n                await octoClient.patchBoard(boardId, {icon: oldIcon})\n            },\n            description,\n            this.undoGroupId,\n        )\n    }\n\n    async changeBlockIcon(boardId: string, blockId: string, oldIcon: string|undefined, icon: string, description = 'change block icon') {\n        await undoManager.perform(\n            async () => {\n                await octoClient.patchBlock(boardId, blockId, {updatedFields: {icon}})\n            },\n            async () => {\n                await octoClient.patchBlock(boardId, blockId, {updatedFields: {icon: oldIcon}})\n            },\n            description,\n            this.undoGroupId,\n        )\n    }\n\n    async changeBoardDescription(boardId: string, blockId: string, oldBlockDescription: string|undefined, blockDescription: string, description = 'change description') {\n        await undoManager.perform(\n            async () => {\n                await octoClient.patchBoard(boardId, {description: blockDescription})\n            },\n            async () => {\n                await octoClient.patchBoard(boardId, {description: oldBlockDescription})\n            },\n            description,\n            this.undoGroupId,\n        )\n    }\n\n    async showBoardDescription(boardId: string, oldShowDescription: boolean, showDescription = true, description?: string) {\n        let actionDescription = description\n        if (!actionDescription) {\n            actionDescription = showDescription ? 'show description' : 'hide description'\n        }\n\n        await undoManager.perform(\n            async () => {\n                await octoClient.patchBoard(boardId, {showDescription})\n            },\n            async () => {\n                await octoClient.patchBoard(boardId, {showDescription: oldShowDescription})\n            },\n            actionDescription,\n            this.undoGroupId,\n        )\n    }\n\n    async changeCardContentOrder(boardId: string, cardId: string, oldContentOrder: Array<string | string[]>, contentOrder: Array<string | string[]>, description = 'reorder'): Promise<void> {\n        await undoManager.perform(\n            async () => {\n                await octoClient.patchBlock(boardId, cardId, {updatedFields: {contentOrder}})\n            },\n            async () => {\n                await octoClient.patchBlock(boardId, cardId, {updatedFields: {contentOrder: oldContentOrder}})\n            },\n            description,\n            this.undoGroupId,\n        )\n    }\n\n    // Board Members\n\n    async createBoardMember(member: BoardMember, description = 'create board member'): Promise<void> {\n        await undoManager.perform(\n            async () => {\n                await octoClient.createBoardMember(member)\n            },\n            async () => {\n                await octoClient.deleteBoardMember(member)\n            },\n            description,\n            this.undoGroupId,\n        )\n    }\n\n    async updateBoardMember(newMember: BoardMember, oldMember: BoardMember, description = 'update board member'): Promise<void> {\n        await undoManager.perform(\n            async () => {\n                await octoClient.updateBoardMember(newMember)\n            },\n            async () => {\n                await octoClient.updateBoardMember(oldMember)\n            },\n            description,\n            this.undoGroupId,\n        )\n    }\n\n    async deleteBoardMember(member: BoardMember, description = 'delete board member'): Promise<void> {\n        await undoManager.perform(\n            async () => {\n                await octoClient.deleteBoardMember(member)\n                store.dispatch(removeBoardUsersById([member.userId]))\n            },\n            async () => {\n                await octoClient.createBoardMember(member)\n                const user = await octoClient.getUser(member.userId)\n                if (user) {\n                    store.dispatch(addBoardUsers([user]))\n                }\n            },\n            description,\n            this.undoGroupId,\n        )\n    }\n\n    // Property Templates\n\n    async insertPropertyTemplate(board: Board, activeView: BoardView, index = -1, template?: IPropertyTemplate): Promise<string> {\n        if (!activeView) {\n            Utils.assertFailure('insertPropertyTemplate: no activeView')\n            return ''\n        }\n\n        const newTemplate = template || {\n            id: Utils.createGuid(IDType.BlockID),\n            name: 'New Property',\n            type: 'text',\n            options: [],\n        }\n\n        const oldBlocks: Block[] = []\n        const oldBoard: Board = board\n        const newBoard = createBoard(board)\n\n        const startIndex = (index >= 0) ? index : board.cardProperties.length\n        if (index >= 0) {\n            newBoard.cardProperties.splice(startIndex, 0, newTemplate)\n        } else {\n            newBoard.cardProperties.push(newTemplate)\n        }\n\n        if (activeView.fields.viewType === 'table') {\n            const changedBlocks: Block[] = []\n            const changedBlockIDs: string[] = []\n\n            oldBlocks.push(activeView)\n\n            const newActiveView = createBoardView(activeView)\n\n            // insert in proper location in activeview.fields.visiblePropetyIds\n            const viewIndex = index > 0 ? index : activeView.fields.visiblePropertyIds.length\n            newActiveView.fields.visiblePropertyIds.splice(viewIndex, 0, newTemplate.id)\n            changedBlocks.push(newActiveView)\n            changedBlockIDs.push(activeView.id)\n\n            const [updatePatch, undoPatch] = createPatchesFromBoardsAndBlocks(newBoard, oldBoard, changedBlockIDs, changedBlocks, oldBlocks)\n            await undoManager.perform(\n                async () => {\n                    await octoClient.patchBoardsAndBlocks(updatePatch)\n                },\n                async () => {\n                    await octoClient.patchBoardsAndBlocks(undoPatch)\n                },\n                'add column',\n                this.undoGroupId,\n            )\n        } else {\n            this.updateBoard(newBoard, oldBoard, 'add property')\n        }\n\n        return newTemplate.id\n    }\n\n    async duplicatePropertyTemplate(board: Board, activeView: BoardView, propertyId: string) {\n        if (!activeView) {\n            Utils.assertFailure('duplicatePropertyTemplate: no activeView')\n        }\n\n        const oldBlocks: Block[] = []\n        const oldBoard: Board = board\n\n        const newBoard = createBoard(board)\n        const changedBlocks: Block[] = []\n        const changedBlockIDs: string[] = []\n        const index = newBoard.cardProperties.findIndex((o: IPropertyTemplate) => o.id === propertyId)\n        if (index === -1) {\n            Utils.assertFailure(`Cannot find template with id: ${propertyId}`)\n            return\n        }\n        const srcTemplate = newBoard.cardProperties[index]\n        const newTemplate: IPropertyTemplate = {\n            id: Utils.createGuid(IDType.BlockID),\n            name: `${srcTemplate.name} copy`,\n            type: srcTemplate.type,\n            options: srcTemplate.options.slice(),\n        }\n        newBoard.cardProperties.splice(index + 1, 0, newTemplate)\n\n        let description = 'duplicate property'\n        if (activeView.fields.viewType === 'table') {\n            oldBlocks.push(activeView)\n\n            const newActiveView = createBoardView(activeView)\n            newActiveView.fields.visiblePropertyIds.push(newTemplate.id)\n            changedBlocks.push(newActiveView)\n            changedBlockIDs.push(newActiveView.id)\n\n            description = 'duplicate column'\n            const [updatePatch, undoPatch] = createPatchesFromBoardsAndBlocks(newBoard, oldBoard, changedBlockIDs, changedBlocks, oldBlocks)\n            await undoManager.perform(\n                async () => {\n                    await octoClient.patchBoardsAndBlocks(updatePatch)\n                },\n                async () => {\n                    await octoClient.patchBoardsAndBlocks(undoPatch)\n                },\n                description,\n                this.undoGroupId,\n            )\n        } else {\n            this.updateBoard(newBoard, oldBoard, description)\n        }\n    }\n\n    async changePropertyTemplateOrder(board: Board, template: IPropertyTemplate, destIndex: number) {\n        const templates = board.cardProperties\n        const newValue = templates.slice()\n\n        const srcIndex = templates.indexOf(template)\n        Utils.log(`srcIndex: ${srcIndex}, destIndex: ${destIndex}`)\n        newValue.splice(destIndex, 0, newValue.splice(srcIndex, 1)[0])\n\n        const newBoard = createBoard(board)\n        newBoard.cardProperties = newValue\n\n        await this.updateBoard(newBoard, board, 'reorder properties')\n    }\n\n    async deleteProperty(board: Board, views: BoardView[], cards: Card[], propertyId: string) {\n        const newBoard = createBoard(board)\n        newBoard.cardProperties = board.cardProperties.filter((o: IPropertyTemplate) => o.id !== propertyId)\n\n        const oldBlocks: Block[] = []\n        const changedBlocks: Block[] = []\n        const changedBlockIDs: string[] = []\n\n        views.forEach((view) => {\n            if (view.fields.visiblePropertyIds.includes(propertyId)) {\n                oldBlocks.push(view)\n\n                const newView = createBoardView(view)\n                newView.fields.visiblePropertyIds = view.fields.visiblePropertyIds.filter((o: string) => o !== propertyId)\n                changedBlocks.push(newView)\n                changedBlockIDs.push(newView.id)\n            }\n        })\n        cards.forEach((card) => {\n            if (card.fields.properties[propertyId]) {\n                oldBlocks.push(card)\n\n                const newCard = createCard(card)\n                delete newCard.fields.properties[propertyId]\n                changedBlocks.push(newCard)\n                changedBlockIDs.push(newCard.id)\n            }\n        })\n\n        const [updatePatch, undoPatch] = createPatchesFromBoardsAndBlocks(newBoard, board, changedBlockIDs, changedBlocks, oldBlocks)\n        await undoManager.perform(\n            async () => {\n                await octoClient.patchBoardsAndBlocks(updatePatch)\n            },\n            async () => {\n                await octoClient.patchBoardsAndBlocks(undoPatch)\n            },\n            'delete property',\n            this.undoGroupId,\n        )\n    }\n\n    // Properties\n\n    async updateBoardCardProperties(boardId: string, oldProperties: IPropertyTemplate[], newProperties: IPropertyTemplate[], description = 'update card properties') {\n        const [updatePatch, undoPatch] = createCardPropertiesPatches(newProperties, oldProperties)\n        await undoManager.perform(\n            async () => {\n                await octoClient.patchBoard(boardId, updatePatch)\n            },\n            async () => {\n                await octoClient.patchBoard(boardId, undoPatch)\n            },\n            description,\n            this.undoGroupId,\n        )\n    }\n\n    async insertPropertyOption(boardId: string, oldCardProperties: IPropertyTemplate[], template: IPropertyTemplate, option: IPropertyOption, description = 'add option') {\n        Utils.assert(oldCardProperties.includes(template))\n\n        const newCardProperties: IPropertyTemplate[] = cloneDeep(oldCardProperties)\n        const newTemplate = newCardProperties.find((o: IPropertyTemplate) => o.id === template.id)!\n        newTemplate.options.push(option)\n\n        await this.updateBoardCardProperties(boardId, oldCardProperties, newCardProperties, description)\n    }\n\n    async deletePropertyOption(boardId: string, oldCardProperties: IPropertyTemplate[], template: IPropertyTemplate, option: IPropertyOption) {\n        const newCardProperties: IPropertyTemplate[] = cloneDeep(oldCardProperties)\n        const newTemplate = newCardProperties.find((o: IPropertyTemplate) => o.id === template.id)!\n        newTemplate.options = newTemplate.options.filter((o) => o.id !== option.id)\n\n        await this.updateBoardCardProperties(boardId, oldCardProperties, newCardProperties, 'delete option')\n    }\n\n    async changePropertyOptionOrder(boardId: string, oldCardProperties: IPropertyTemplate[], template: IPropertyTemplate, option: IPropertyOption, destIndex: number) {\n        const srcIndex = template.options.indexOf(option)\n        Utils.log(`srcIndex: ${srcIndex}, destIndex: ${destIndex}`)\n\n        const newCardProperties: IPropertyTemplate[] = cloneDeep(oldCardProperties)\n        const newTemplate = newCardProperties.find((o: IPropertyTemplate) => o.id === template.id)!\n        newTemplate.options.splice(destIndex, 0, newTemplate.options.splice(srcIndex, 1)[0])\n\n        await this.updateBoardCardProperties(boardId, oldCardProperties, newCardProperties, 'reorder option')\n    }\n\n    async changePropertyOptionValue(boardId: string, oldCardProperties: IPropertyTemplate[], propertyTemplate: IPropertyTemplate, option: IPropertyOption, value: string) {\n        const newCardProperties: IPropertyTemplate[] = cloneDeep(oldCardProperties)\n        const newTemplate = newCardProperties.find((o: IPropertyTemplate) => o.id === propertyTemplate.id)!\n        const newOption = newTemplate.options.find((o) => o.id === option.id)!\n        newOption.value = value\n\n        await this.updateBoardCardProperties(boardId, oldCardProperties, newCardProperties, 'rename option')\n\n        return newCardProperties\n    }\n\n    async changePropertyOptionColor(boardId: string, oldCardProperties: IPropertyTemplate[], template: IPropertyTemplate, option: IPropertyOption, color: string) {\n        const newCardProperties: IPropertyTemplate[] = cloneDeep(oldCardProperties)\n        const newTemplate = newCardProperties.find((o: IPropertyTemplate) => o.id === template.id)!\n        const newOption = newTemplate.options.find((o) => o.id === option.id)!\n        newOption.color = color\n        await this.updateBoardCardProperties(boardId, oldCardProperties, newCardProperties, 'rename option')\n    }\n\n    async changePropertyValue(boardId: string, card: Card, propertyId: string, value?: string | string[], description = 'change property') {\n        const oldValue = card.fields.properties[propertyId]\n\n        // dont save anything if property value was not changed.\n        if (oldValue === value) {\n            return\n        }\n\n        const newCard = createCard(card)\n        if (value) {\n            newCard.fields.properties[propertyId] = value\n        } else {\n            delete newCard.fields.properties[propertyId]\n        }\n        await this.updateBlock(boardId, newCard, card, description)\n        TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.EditCardProperty, {board: card.boardId, card: card.id})\n    }\n\n    async changePropertyTypeAndName(board: Board, cards: Card[], propertyTemplate: IPropertyTemplate, newType: PropertyTypeEnum, newName: string) {\n        if (propertyTemplate.type === newType && propertyTemplate.name === newName) {\n            return\n        }\n\n        const oldBoard: Board = board\n        const newBoard = createBoard(board)\n        const newTemplate = newBoard.cardProperties.find((o: IPropertyTemplate) => o.id === propertyTemplate.id)!\n\n        if (propertyTemplate.type !== newType) {\n            newTemplate.options = []\n        }\n\n        newTemplate.type = newType\n        newTemplate.name = newName\n\n        const oldBlocks: Block[] = []\n        const newBlocks: Block[] = []\n        const newBlockIDs: string[] = []\n\n        if (propertyTemplate.type !== newType) {\n            const isNewTypeSelectOrMulti = newType === 'select' || newType === 'multiSelect'\n            const isNewTypePersonOrMulti = newType === 'person' || newType === 'multiPerson'\n\n            const isOldTypeSelectOrMulti = propertyTemplate.type === 'select' || propertyTemplate.type === 'multiSelect'\n            const isOldTypePersonOrMulti = propertyTemplate.type === 'person' || propertyTemplate.type === 'multiPerson'\n\n            // If the old type was either select/multiselect or person/multiperson\n            if (isOldTypeSelectOrMulti || isOldTypePersonOrMulti) {\n                for (const card of cards) {\n                    // if array get first value, if exists\n                    const oldValue = Array.isArray(card.fields.properties[propertyTemplate.id]) ? (card.fields.properties[propertyTemplate.id].length > 0 && card.fields.properties[propertyTemplate.id][0] as string) : card.fields.properties[propertyTemplate.id] as string\n                    if (oldValue) {\n                        let newValue: string | undefined\n                        if (isOldTypePersonOrMulti) {\n                            if (isNewTypePersonOrMulti) {\n                                newValue = oldValue\n                            }\n                        } else if (isNewTypeSelectOrMulti) {\n                            if (isOldTypeSelectOrMulti) {\n                                newValue = propertyTemplate.options.find((o) => o.id === oldValue)?.id\n                            } else {\n                                newValue = propertyTemplate.options.find((o) => o.id === oldValue)?.value\n                            }\n                        }\n                        const newCard = createCard(card)\n                        if (newValue) {\n                            if (newType === 'multiSelect' || newType === 'multiPerson') {\n                                newCard.fields.properties[propertyTemplate.id] = [newValue]\n                            } else {\n                                newCard.fields.properties[propertyTemplate.id] = newValue\n                            }\n                        } else {\n                            // This was an invalid select option or old person id, so delete it\n                            delete newCard.fields.properties[propertyTemplate.id]\n                        }\n\n                        newBlocks.push(newCard)\n                        newBlockIDs.push(newCard.id)\n                        oldBlocks.push(card)\n                    }\n\n                    if (isNewTypeSelectOrMulti) {\n                        newTemplate.options = propertyTemplate.options\n                    }\n                }\n            } else if (isNewTypeSelectOrMulti) { // if the new type is either select or multiselect - old type is other\n                // Map values to new template option IDs\n                for (const card of cards) {\n                    const oldValue = card.fields.properties[propertyTemplate.id] as string\n                    if (oldValue) {\n                        let option = newTemplate.options.find((o: IPropertyOption) => o.value === oldValue)\n                        if (!option) {\n                            option = {\n                                id: Utils.createGuid(IDType.None),\n                                value: oldValue,\n                                color: 'propColorDefault',\n                            }\n                            newTemplate.options.push(option)\n                        }\n\n                        const newCard = createCard(card)\n                        newCard.fields.properties[propertyTemplate.id] = newType === 'multiSelect' ? [option.id] : option.id\n\n                        newBlocks.push(newCard)\n                        newBlockIDs.push(newCard.id)\n                        oldBlocks.push(card)\n                    }\n                }\n            } else if (isNewTypePersonOrMulti) { // if the new type is either person or multiperson - old type is other\n                // Clear old values\n                for (const card of cards) {\n                    const oldValue = card.fields.properties[propertyTemplate.id] as string\n                    if (oldValue) {\n                        const newCard = createCard(card)\n                        delete newCard.fields.properties[propertyTemplate.id]\n                        newBlocks.push(newCard)\n                        newBlockIDs.push(newCard.id)\n                        oldBlocks.push(card)\n                    }\n                }\n            }\n        }\n\n        if (newBlockIDs.length > 0) {\n            const [updatePatch, undoPatch] = createPatchesFromBoardsAndBlocks(newBoard, board, newBlockIDs, newBlocks, oldBlocks)\n            await undoManager.perform(\n                async () => {\n                    await octoClient.patchBoardsAndBlocks(updatePatch)\n                },\n                async () => {\n                    await octoClient.patchBoardsAndBlocks(undoPatch)\n                },\n                'change property type and name',\n                this.undoGroupId,\n            )\n        } else {\n            this.updateBoard(newBoard, oldBoard, 'change property name')\n        }\n    }\n\n    // Views\n\n    async changeViewSortOptions(boardId: string, viewId: string, oldSortOptions: ISortOption[], sortOptions: ISortOption[]): Promise<void> {\n        await undoManager.perform(\n            async () => {\n                await octoClient.patchBlock(boardId, viewId, {updatedFields: {sortOptions}})\n            },\n            async () => {\n                await octoClient.patchBlock(boardId, viewId, {updatedFields: {sortOptions: oldSortOptions}})\n            },\n            'sort',\n            this.undoGroupId,\n        )\n    }\n\n    async changeViewFilter(boardId: string, viewId: string, oldFilter: FilterGroup, filter: FilterGroup): Promise<void> {\n        await undoManager.perform(\n            async () => {\n                await octoClient.patchBlock(boardId, viewId, {updatedFields: {filter}})\n            },\n            async () => {\n                await octoClient.patchBlock(boardId, viewId, {updatedFields: {filter: oldFilter}})\n            },\n            'filter',\n            this.undoGroupId,\n        )\n    }\n\n    async changeViewGroupById(boardId: string, viewId: string, oldGroupById: string|undefined, groupById: string): Promise<void> {\n        await undoManager.perform(\n            async () => {\n                await octoClient.patchBlock(boardId, viewId, {updatedFields: {groupById}})\n            },\n            async () => {\n                await octoClient.patchBlock(boardId, viewId, {updatedFields: {groupById: oldGroupById}})\n            },\n            'group by',\n            this.undoGroupId,\n        )\n    }\n\n    async changeViewDateDisplayPropertyId(boardId: string, viewId: string, oldDateDisplayPropertyId: string|undefined, dateDisplayPropertyId: string): Promise<void> {\n        await undoManager.perform(\n            async () => {\n                await octoClient.patchBlock(boardId, viewId, {updatedFields: {dateDisplayPropertyId}})\n            },\n            async () => {\n                await octoClient.patchBlock(boardId, viewId, {updatedFields: {dateDisplayPropertyId: oldDateDisplayPropertyId}})\n            },\n            'display by',\n            this.undoDisplayId,\n        )\n    }\n\n    async changeViewVisiblePropertiesOrder(boardId: string, view: BoardView, template: IPropertyTemplate, destIndex: number, description = 'change property order'): Promise<void> {\n        const oldVisiblePropertyIds = view.fields.visiblePropertyIds\n        const newOrder = oldVisiblePropertyIds.slice()\n\n        const srcIndex = oldVisiblePropertyIds.indexOf(template.id)\n        Utils.log(`srcIndex: ${srcIndex}, destIndex: ${destIndex}`)\n\n        newOrder.splice(destIndex, 0, newOrder.splice(srcIndex, 1)[0])\n\n        await undoManager.perform(\n            async () => {\n                await octoClient.patchBlock(boardId, view.id, {updatedFields: {visiblePropertyIds: newOrder}})\n            },\n            async () => {\n                await octoClient.patchBlock(boardId, view.id, {updatedFields: {visiblePropertyIds: oldVisiblePropertyIds}})\n            },\n            description,\n            this.undoGroupId,\n        )\n    }\n\n    async changeViewVisibleProperties(boardId: string, viewId: string, oldVisiblePropertyIds: string[], visiblePropertyIds: string[], description = 'show / hide property'): Promise<void> {\n        await undoManager.perform(\n            async () => {\n                await octoClient.patchBlock(boardId, viewId, {updatedFields: {visiblePropertyIds}})\n            },\n            async () => {\n                await octoClient.patchBlock(boardId, viewId, {updatedFields: {visiblePropertyIds: oldVisiblePropertyIds}})\n            },\n            description,\n            this.undoGroupId,\n        )\n    }\n\n    async changeViewVisibleOptionIds(boardId: string, viewId: string, oldVisibleOptionIds: string[], visibleOptionIds: string[], description = 'reorder'): Promise<void> {\n        await undoManager.perform(\n            async () => {\n                await octoClient.patchBlock(boardId, viewId, {updatedFields: {visibleOptionIds}})\n            },\n            async () => {\n                await octoClient.patchBlock(boardId, viewId, {updatedFields: {visibleOptionIds: oldVisibleOptionIds}})\n            },\n            description,\n            this.undoGroupId,\n        )\n    }\n\n    async changeViewHiddenOptionIds(boardId: string, viewId: string, oldHiddenOptionIds: string[], hiddenOptionIds: string[], description = 'reorder'): Promise<void> {\n        await undoManager.perform(\n            async () => {\n                await octoClient.patchBlock(boardId, viewId, {updatedFields: {hiddenOptionIds}})\n            },\n            async () => {\n                await octoClient.patchBlock(boardId, viewId, {updatedFields: {hiddenOptionIds: oldHiddenOptionIds}})\n            },\n            description,\n            this.undoGroupId,\n        )\n    }\n\n    async changeViewKanbanCalculations(boardId: string, viewId: string, oldCalculations: Record<string, KanbanCalculationFields>, calculations: Record<string, KanbanCalculationFields>, description = 'updated kanban calculations'): Promise<void> {\n        await undoManager.perform(\n            async () => {\n                await octoClient.patchBlock(boardId, viewId, {updatedFields: {kanbanCalculations: calculations}})\n            },\n            async () => {\n                await octoClient.patchBlock(boardId, viewId, {updatedFields: {kanbanCalculations: oldCalculations}})\n            },\n            description,\n            this.undoGroupId,\n        )\n    }\n\n    async changeViewColumnCalculations(boardId: string, viewId: string, oldCalculations: Record<string, string>, calculations: Record<string, string>, description = 'updated kanban calculations'): Promise<void> {\n        await undoManager.perform(\n            async () => {\n                await octoClient.patchBlock(boardId, viewId, {updatedFields: {columnCalculations: calculations}})\n            },\n            async () => {\n                await octoClient.patchBlock(boardId, viewId, {updatedFields: {columnCalculations: oldCalculations}})\n            },\n            description,\n            this.undoGroupId,\n        )\n    }\n\n    async changeViewCardOrder(boardId: string, viewId: string, oldCardOrder: string[], cardOrder: string[], description = 'reorder'): Promise<void> {\n        await undoManager.perform(\n            async () => {\n                await octoClient.patchBlock(boardId, viewId, {updatedFields: {cardOrder}})\n            },\n            async () => {\n                await octoClient.patchBlock(boardId, viewId, {updatedFields: {cardOrder: oldCardOrder}})\n            },\n            description,\n            this.undoGroupId,\n        )\n    }\n\n    async hideViewColumns(boardId: string, view: BoardView, columnOptionIds: string[]): Promise<void> {\n        if (columnOptionIds.every((o) => view.fields.hiddenOptionIds.includes(o))) {\n            return\n        }\n\n        const newView = createBoardView(view)\n        newView.fields.visibleOptionIds = newView.fields.visibleOptionIds.filter((o) => !columnOptionIds.includes(o))\n        newView.fields.hiddenOptionIds = [...newView.fields.hiddenOptionIds, ...columnOptionIds]\n        await this.updateBlock(boardId, newView, view, 'hide column')\n    }\n\n    async hideViewColumn(boardId: string, view: BoardView, columnOptionId: string): Promise<void> {\n        return this.hideViewColumns(boardId, view, [columnOptionId])\n    }\n\n    async unhideViewColumns(boardId: string, view: BoardView, columnOptionIds: string[]): Promise<void> {\n        if (columnOptionIds.every((o) => view.fields.visibleOptionIds.includes(o))) {\n            return\n        }\n\n        const newView = createBoardView(view)\n        newView.fields.hiddenOptionIds = newView.fields.hiddenOptionIds.filter((o) => !columnOptionIds.includes(o))\n\n        // Put the columns at the end of the visible list\n        newView.fields.visibleOptionIds = newView.fields.visibleOptionIds.filter((o) => !columnOptionIds.includes(o))\n        newView.fields.visibleOptionIds = [...newView.fields.visibleOptionIds, ...columnOptionIds]\n        await this.updateBlock(boardId, newView, view, 'show column')\n    }\n\n    async unhideViewColumn(boardId: string, view: BoardView, columnOptionId: string): Promise<void> {\n        return this.unhideViewColumns(boardId, view, [columnOptionId])\n    }\n\n    async createCategory(category: Category): Promise<void> {\n        await octoClient.createSidebarCategory(category)\n    }\n\n    async deleteCategory(teamID: string, categoryID: string): Promise<void> {\n        await octoClient.deleteSidebarCategory(teamID, categoryID)\n    }\n\n    async updateCategory(category: Category): Promise<void> {\n        await octoClient.updateSidebarCategory(category)\n    }\n\n    async moveBoardToCategory(teamID: string, blockID: string, toCategoryID: string, fromCategoryID: string): Promise<void> {\n        await octoClient.moveBoardToCategory(teamID, blockID, toCategoryID, fromCategoryID)\n    }\n\n    async followBlock(blockId: string, blockType: string, userId: string) {\n        await undoManager.perform(\n            async () => {\n                await octoClient.followBlock(blockId, blockType, userId)\n            },\n            async () => {\n                await octoClient.unfollowBlock(blockId, blockType, userId)\n            },\n            'follow block',\n            this.undoGroupId,\n        )\n    }\n\n    async unfollowBlock(blockId: string, blockType: string, userId: string) {\n        await undoManager.perform(\n            async () => {\n                await octoClient.unfollowBlock(blockId, blockType, userId)\n            },\n            async () => {\n                await octoClient.followBlock(blockId, blockType, userId)\n            },\n            'follow block',\n            this.undoGroupId,\n        )\n    }\n\n    async patchUserConfig(userID: string, patch: UserConfigPatch): Promise<UserPreference[] | undefined> {\n        return octoClient.patchUserConfig(userID, patch)\n    }\n\n    // Duplicate\n\n    async duplicateCard(\n        cardId: string,\n        boardId: string,\n        fromTemplate = false,\n        description = 'duplicate card',\n        asTemplate = false,\n        propertyOverrides?: Record<string, string>,\n        afterRedo?: (newCardId: string) => Promise<void>,\n        beforeUndo?: () => Promise<void>,\n    ): Promise<[Block[], string]> {\n        return undoManager.perform(\n            async () => {\n                const blocks = await octoClient.duplicateBlock(boardId, cardId, asTemplate)\n                const newRootBlock = blocks && blocks[0]\n                if (!newRootBlock) {\n                    Utils.log('Unable to duplicate card')\n                    return [[], '']\n                }\n                if (asTemplate === fromTemplate) {\n                    // Copy template\n                    newRootBlock.title = `${newRootBlock.title} copy`\n                } else if (asTemplate) {\n                    // Template from card\n                    newRootBlock.title = 'New card template'\n                } else {\n                    // Card from template\n                    newRootBlock.title = ''\n\n                    // If the template doesn't specify an icon, initialize it to a random one\n                    if (!newRootBlock.fields.icon && UserSettings.prefillRandomIcons) {\n                        newRootBlock.fields.icon = BlockIcons.shared.randomIcon()\n                    }\n                }\n                const patch = {\n                    updatedFields: {\n                        icon: newRootBlock.fields.icon,\n                        properties: {...newRootBlock.fields.properties, ...propertyOverrides},\n                    },\n                    title: newRootBlock.title,\n                }\n                await octoClient.patchBlock(newRootBlock.boardId, newRootBlock.id, patch)\n                if (blocks) {\n                    updateAllBoardsAndBlocks([], blocks)\n                    await afterRedo?.(newRootBlock.id)\n                }\n                return [blocks, newRootBlock.id]\n            },\n            async (newBlocks: Block[]) => {\n                await beforeUndo?.()\n                const newRootBlock = newBlocks && newBlocks[0]\n                if (newRootBlock) {\n                    await octoClient.deleteBlock(newRootBlock.boardId, newRootBlock.id)\n                }\n            },\n            description,\n            this.undoGroupId,\n        )\n    }\n\n    async duplicateBoard(\n        boardId: string,\n        description = 'duplicate board',\n        asTemplate = false,\n        afterRedo?: (newBoardId: string) => Promise<void>,\n        beforeUndo?: () => Promise<void>,\n        toTeam?: string,\n    ): Promise<BoardsAndBlocks> {\n        return undoManager.perform(\n            async () => {\n                const boardsAndBlocks = await octoClient.duplicateBoard(boardId, asTemplate, toTeam)\n                if (boardsAndBlocks) {\n                    updateAllBoardsAndBlocks(boardsAndBlocks.boards, boardsAndBlocks.blocks)\n                    await afterRedo?.(boardsAndBlocks.boards[0]?.id)\n                }\n                return boardsAndBlocks\n            },\n            async (boardsAndBlocks: BoardsAndBlocks) => {\n                await beforeUndo?.()\n                const awaits = []\n                for (const block of boardsAndBlocks.blocks) {\n                    awaits.push(octoClient.deleteBlock(block.boardId, block.id))\n                }\n                for (const board of boardsAndBlocks.boards) {\n                    awaits.push(octoClient.deleteBoard(board.id))\n                }\n                await Promise.all(awaits)\n            },\n            description,\n            this.undoGroupId,\n        )\n    }\n\n    async moveContentBlock(blockId: string, dstBlockId: string, where: 'after'|'before', srcBlockId: string, srcWhere: 'after'|'before', description: string): Promise<void> {\n        return undoManager.perform(\n            async () => {\n                await octoClient.moveBlockTo(blockId, where, dstBlockId)\n            },\n            async () => {\n                await octoClient.moveBlockTo(blockId, srcWhere, srcBlockId)\n            },\n            description,\n            this.undoGroupId,\n        )\n    }\n\n    async addBoardFromTemplate(\n        teamId: string,\n        intl: IntlShape,\n        afterRedo: (id: string) => Promise<void>,\n        beforeUndo: () => Promise<void>,\n        boardTemplateId: string,\n        toTeam?: string,\n    ): Promise<BoardsAndBlocks> {\n        const asTemplate = false\n        const actionDescription = intl.formatMessage({id: 'Mutator.new-board-from-template', defaultMessage: 'new board from template'})\n        return mutator.duplicateBoard(boardTemplateId, actionDescription, asTemplate, afterRedo, beforeUndo, toTeam)\n    }\n\n    async addEmptyBoard(\n        teamId: string,\n        intl: IntlShape,\n        afterRedo?: (id: string) => Promise<void>,\n        beforeUndo?: () => Promise<void>,\n    ): Promise<BoardsAndBlocks> {\n        const board = createBoard()\n        board.teamId = teamId\n\n        const view = createBoardView()\n        view.fields.viewType = 'board'\n        view.parentId = board.id\n        view.boardId = board.id\n        view.title = intl.formatMessage({id: 'View.NewBoardTitle', defaultMessage: 'Board view'})\n\n        return mutator.createBoardsAndBlocks(\n            {boards: [board], blocks: [view]},\n            'add board',\n            async (bab: BoardsAndBlocks) => {\n                const newBoard = bab.boards[0]\n                TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.CreateBoard, {board: newBoard?.id})\n                afterRedo && await afterRedo(newBoard?.id || '')\n            },\n            beforeUndo,\n        )\n    }\n\n    async addEmptyBoardTemplate(\n        teamId: string,\n        intl: IntlShape,\n        afterRedo: (id: string) => Promise<void>,\n        beforeUndo: () => Promise<void>,\n    ): Promise<BoardsAndBlocks> {\n        const boardTemplate = createBoard()\n        boardTemplate.isTemplate = true\n        boardTemplate.teamId = teamId\n        boardTemplate.title = intl.formatMessage({id: 'View.NewTemplateDefaultTitle', defaultMessage: 'Untitled Template'})\n\n        const view = createBoardView()\n        view.fields.viewType = 'board'\n        view.parentId = boardTemplate.id\n        view.boardId = boardTemplate.id\n        view.title = intl.formatMessage({id: 'View.NewBoardTitle', defaultMessage: 'Board view'})\n\n        return mutator.createBoardsAndBlocks(\n            {boards: [boardTemplate], blocks: [view]},\n            'add board template',\n            async (bab: BoardsAndBlocks) => {\n                const newBoard = bab.boards[0]\n                TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.CreateBoardTemplate, {board: newBoard?.id})\n                afterRedo(newBoard?.id || '')\n            },\n            beforeUndo,\n        )\n    }\n\n    // Other methods\n\n    // Not a mutator, but convenient to put here since Mutator wraps OctoClient\n    async exportBoardArchive(boardID: string): Promise<Response> {\n        return octoClient.exportBoardArchive(boardID)\n    }\n\n    // Not a mutator, but convenient to put here since Mutator wraps OctoClient\n    async exportFullArchive(teamID: string): Promise<Response> {\n        return octoClient.exportFullArchive(teamID)\n    }\n\n    // Not a mutator, but convenient to put here since Mutator wraps OctoClient\n    async importFullArchive(file: File): Promise<Response> {\n        return octoClient.importFullArchive(file)\n    }\n\n    get canUndo(): boolean {\n        return undoManager.canUndo\n    }\n\n    get canRedo(): boolean {\n        return undoManager.canRedo\n    }\n\n    get undoDescription(): string | undefined {\n        return undoManager.undoDescription\n    }\n\n    get redoDescription(): string | undefined {\n        return undoManager.redoDescription\n    }\n\n    async undo() {\n        await undoManager.undo()\n    }\n\n    async redo() {\n        await undoManager.redo()\n    }\n}\n\nconst mutator = new Mutator()\nexport default mutator\n\nexport {mutator}\n"
  },
  {
    "path": "webapp/src/nativeApp.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {IAppWindow} from './types'\nimport {exportUserSettingsBlob, importUserSettingsBlob} from './userSettings'\n\ndeclare interface INativeApp {\n    settingsBlob: string | null\n}\n\ndeclare const NativeApp: INativeApp\ndeclare let window: IAppWindow\n\nexport function importNativeAppSettings(): void {\n    if (typeof NativeApp === 'undefined' || !NativeApp.settingsBlob) {\n        return\n    }\n    const importedKeys = importUserSettingsBlob(NativeApp.settingsBlob)\n    const messageType = importedKeys.length ? 'didImportUserSettings' : 'didNotImportUserSettings'\n    postWebKitMessage({type: messageType, settingsBlob: exportUserSettingsBlob(), keys: importedKeys})\n    NativeApp.settingsBlob = null\n}\n\nexport function notifySettingsChanged(key: string): void {\n    postWebKitMessage({type: 'didChangeUserSettings', settingsBlob: exportUserSettingsBlob(), key})\n}\n\nfunction postWebKitMessage<T>(message: T) {\n    window.webkit?.messageHandlers.nativeApp?.postMessage(message)\n}\n"
  },
  {
    "path": "webapp/src/octoClient.test.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\n// Disable console log\nconsole.log = jest.fn()\n\nimport {Block} from './blocks/block'\nimport {createCard} from './blocks/card'\nimport octoClient from './octoClient'\nimport 'isomorphic-fetch'\nimport {FetchMock} from './test/fetchMock'\n\nglobal.fetch = FetchMock.fn\n\nbeforeEach(() => {\n    FetchMock.fn.mockReset()\n})\n\ntest('OctoClient: get blocks', async () => {\n    const blocks = createBlocks()\n\n    FetchMock.fn.mockReturnValueOnce(FetchMock.jsonResponse(JSON.stringify(blocks)))\n    let boards = await octoClient.getBlocksWithType('card')\n    expect(boards.length).toBe(blocks.length)\n\n    FetchMock.fn.mockReturnValueOnce(FetchMock.jsonResponse(JSON.stringify(blocks)))\n    let response = await octoClient.exportBoardArchive('board')\n    expect(response.status).toBe(200)\n\n    FetchMock.fn.mockReturnValueOnce(FetchMock.jsonResponse(JSON.stringify(blocks)))\n    response = await octoClient.exportFullArchive('team')\n    expect(response.status).toBe(200)\n\n    FetchMock.fn.mockReturnValueOnce(FetchMock.jsonResponse(JSON.stringify(blocks)))\n    const parentId = 'id1'\n    boards = await octoClient.getBlocksWithParent(parentId)\n    expect(boards.length).toBe(blocks.length)\n\n    FetchMock.fn.mockReturnValueOnce(FetchMock.jsonResponse(JSON.stringify(blocks)))\n    boards = await octoClient.getBlocksWithParent(parentId, 'card')\n    expect(boards.length).toBe(blocks.length)\n})\n\ntest('OctoClient: insert blocks', async () => {\n    const blocks = createBlocks()\n\n    await octoClient.insertBlocks('board-id', blocks)\n\n    expect(FetchMock.fn).toBeCalledTimes(1)\n    expect(FetchMock.fn).toHaveBeenCalledWith(\n        expect.anything(),\n        expect.objectContaining({\n            method: 'POST',\n            body: JSON.stringify(blocks),\n        }))\n})\n\ntest('OctoClient: importFullArchive', async () => {\n    const archive = new File([''], 'test')\n\n    await octoClient.importFullArchive(archive)\n\n    expect(FetchMock.fn).toBeCalledTimes(1)\n    expect(FetchMock.fn).toHaveBeenCalledWith(\n        expect.anything(),\n        expect.objectContaining({\n            method: 'POST',\n        }))\n})\n\nfunction createBlocks(): Block[] {\n    const blocks = []\n\n    for (let i = 0; i < 5; i++) {\n        const block = createCard()\n        block.id = `block${i + 1}`\n        blocks.push(block)\n    }\n\n    return blocks\n}\n\ntest('OctoClient: GetFileInfo', async () => {\n    FetchMock.fn.mockReturnValueOnce(FetchMock.jsonResponse(JSON.stringify({\n        name: 'test.txt',\n        size: 2300,\n        extension: '.txt',\n    })))\n    await octoClient.getFileInfo('board-id', 'file-id')\n    expect(FetchMock.fn).toBeCalledTimes(1)\n    expect(FetchMock.fn).toHaveBeenCalledWith(\n        'http://localhost/api/v2/files/teams/0/board-id/file-id/info',\n        expect.objectContaining({\n            headers: {\n                Accept: 'application/json',\n                Authorization: '',\n                'Content-Type': 'application/json',\n                'X-Requested-With': 'XMLHttpRequest',\n            }}))\n})\n"
  },
  {
    "path": "webapp/src/octoClient.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {Block, BlockPatch, FileInfo} from './blocks/block'\nimport {Board, BoardsAndBlocks, BoardsAndBlocksPatch, BoardPatch, BoardMember} from './blocks/board'\nimport {ISharing} from './blocks/sharing'\nimport {OctoUtils} from './octoUtils'\nimport {IUser, UserConfigPatch, UserPreference} from './user'\nimport {Utils} from './utils'\nimport {ClientConfig} from './config/clientConfig'\nimport {UserSettings} from './userSettings'\nimport {Category, CategoryBoards} from './store/sidebar'\nimport {Channel} from './store/channels'\nimport {Team} from './store/teams'\nimport {Subscription} from './wsclient'\nimport {PrepareOnboardingResponse} from './onboardingTour'\nimport {Constants} from './constants'\n\nimport {BoardsCloudLimits} from './boardsCloudLimits'\nimport {TopBoardResponse} from './insights'\nimport {BoardSiteStatistics} from './statistics'\n\n//\n// OctoClient is the client interface to the server APIs\n//\nclass OctoClient {\n    readonly serverUrl: string | undefined\n    private logged = false\n\n    // this need to be a function rather than a const because\n    // one of the global variable (`window.baseURL`) is set at runtime\n    // after the first instance of OctoClient is created.\n    // Avoiding the race condition becomes more complex than making\n    // the base URL dynamic though a function\n    private getBaseURL(): string {\n        const baseURL = (this.serverUrl || Utils.getBaseURL(true)).replace(/\\/$/, '')\n\n        // Logging this for debugging.\n        // Logging just once to avoid log noise.\n        if (!this.logged) {\n            Utils.log(`OctoClient baseURL: ${baseURL}`)\n            this.logged = true\n        }\n\n        return baseURL\n    }\n\n    get token(): string {\n        return localStorage.getItem('focalboardSessionId') || ''\n    }\n    set token(value: string) {\n        localStorage.setItem('focalboardSessionId', value)\n    }\n\n    constructor(serverUrl?: string, public teamId = Constants.globalTeamId, public channelId = Constants.noChannelID) {\n        this.serverUrl = serverUrl\n    }\n\n    private async getJson<T>(response: Response, defaultValue: T): Promise<T> {\n        // The server may return null or malformed json\n        try {\n            const value = await response.json()\n            return value || defaultValue\n        } catch {\n            return defaultValue\n        }\n    }\n\n    async login(username: string, password: string): Promise<boolean> {\n        const path = '/api/v2/login'\n        const body = JSON.stringify({username, password, type: 'normal'})\n        const response = await fetch(this.getBaseURL() + path, {\n            method: 'POST',\n            headers: this.headers(),\n            body,\n        })\n        if (response.status !== 200) {\n            return false\n        }\n\n        const responseJson = (await this.getJson(response, {})) as {token?: string}\n        if (responseJson.token) {\n            localStorage.setItem('focalboardSessionId', responseJson.token)\n            return true\n        }\n        return false\n    }\n\n    async logout(): Promise<boolean> {\n        const path = '/api/v2/logout'\n        const response = await fetch(this.getBaseURL() + path, {\n            method: 'POST',\n            headers: this.headers(),\n        })\n        localStorage.removeItem('focalboardSessionId')\n\n        if (response.status !== 200) {\n            return false\n        }\n        return true\n    }\n\n    async getClientConfig(): Promise<ClientConfig | null> {\n        const path = '/api/v2/clientConfig'\n        const response = await fetch(this.getBaseURL() + path, {\n            method: 'GET',\n            headers: this.headers(),\n        })\n        if (response.status !== 200) {\n            return null\n        }\n\n        const json = (await this.getJson(response, {})) as ClientConfig\n        return json\n    }\n\n    async register(email: string, username: string, password: string, token?: string): Promise<{code: number, json: {error?: string}}> {\n        const path = '/api/v2/register'\n        const body = JSON.stringify({email, username, password, token})\n        const response = await fetch(this.getBaseURL() + path, {\n            method: 'POST',\n            headers: this.headers(),\n            body,\n        })\n        const json = (await this.getJson(response, {})) as {error?: string}\n        return {code: response.status, json}\n    }\n\n    async changePassword(userId: string, oldPassword: string, newPassword: string): Promise<{code: number, json: {error?: string}}> {\n        const path = `/api/v2/users/${encodeURIComponent(userId)}/changepassword`\n        const body = JSON.stringify({oldPassword, newPassword})\n        const response = await fetch(this.getBaseURL() + path, {\n            method: 'POST',\n            headers: this.headers(),\n            body,\n        })\n        const json = (await this.getJson(response, {})) as {error?: string}\n        return {code: response.status, json}\n    }\n\n    private headers() {\n        return {\n            Accept: 'application/json',\n            'Content-Type': 'application/json',\n            Authorization: this.token ? 'Bearer ' + this.token : '',\n            'X-Requested-With': 'XMLHttpRequest',\n        }\n    }\n\n    private teamPath(teamId?: string): string {\n        let teamIdToUse = teamId\n        if (!teamId) {\n            teamIdToUse = this.teamId === Constants.globalTeamId ? UserSettings.lastTeamId || this.teamId : this.teamId\n        }\n\n        return `/api/v2/teams/${teamIdToUse}`\n    }\n\n    private teamsPath(): string {\n        return '/api/v2/teams'\n    }\n\n    async getMe(): Promise<IUser | undefined> {\n        let path = '/api/v2/users/me'\n        let parameters = ''\n        if (this.teamId !== Constants.globalTeamId) {\n            parameters = `teamID=${this.teamId}`\n        }\n        if (this.channelId !== Constants.noChannelID) {\n            const channelClause = `channelID=${this.channelId}`\n            if (parameters) {\n                parameters += '&' + channelClause\n            } else {\n                parameters = channelClause\n            }\n        }\n        if (parameters) {\n            path += '?' + parameters\n        }\n        const response = await fetch(this.getBaseURL() + path, {headers: this.headers()})\n        if (response.status !== 200) {\n            return undefined\n        }\n        const user = (await this.getJson(response, {})) as IUser\n        return user\n    }\n\n    async getMyBoardMemberships(): Promise<BoardMember[]> {\n        const path = '/api/v2/users/me/memberships'\n        const response = await fetch(this.getBaseURL() + path, {headers: this.headers()})\n        if (response.status !== 200) {\n            return []\n        }\n        const members = (await this.getJson(response, [])) as BoardMember[]\n        return members\n    }\n\n    async getUser(userId: string): Promise<IUser | undefined> {\n        const path = `/api/v2/users/${encodeURIComponent(userId)}`\n        const response = await fetch(this.getBaseURL() + path, {headers: this.headers()})\n        if (response.status !== 200) {\n            return undefined\n        }\n        const user = (await this.getJson(response, {})) as IUser\n        return user\n    }\n\n    async getUsersList(userIds: string[]): Promise<IUser[] | []> {\n        const path = '/api/v2/users'\n        const body = JSON.stringify(userIds)\n        const response = await fetch(this.getBaseURL() + path, {\n            headers: this.headers(),\n            method: 'POST',\n            body,\n        })\n\n        if (response.status !== 200) {\n            return []\n        }\n\n        return (await this.getJson(response, [])) as IUser[]\n    }\n\n    async getMyConfig(): Promise<UserPreference[] | undefined> {\n        const path = '/api/v2/users/me/config'\n        const response = await fetch(this.getBaseURL() + path, {\n            headers: this.headers(),\n            method: 'GET',\n        })\n\n        if (response.status !== 200) {\n            return undefined\n        }\n\n        return (await this.getJson(response, [])) as UserPreference[]\n    }\n\n    async patchUserConfig(userID: string, patch: UserConfigPatch): Promise<UserPreference[] | undefined> {\n        const path = `/api/v2/users/${encodeURIComponent(userID)}/config`\n        const body = JSON.stringify(patch)\n        const response = await fetch(this.getBaseURL() + path, {\n            headers: this.headers(),\n            method: 'PUT',\n            body,\n        })\n\n        if (response.status !== 200) {\n            return undefined\n        }\n\n        return (await this.getJson(response, {})) as UserPreference[]\n    }\n\n    async exportBoardArchive(boardID: string): Promise<Response> {\n        const path = `/api/v2/boards/${boardID}/archive/export`\n        return fetch(this.getBaseURL() + path, {headers: this.headers()})\n    }\n\n    async exportFullArchive(teamID: string): Promise<Response> {\n        const path = `/api/v2/teams/${teamID}/archive/export`\n        return fetch(this.getBaseURL() + path, {headers: this.headers()})\n    }\n\n    async importFullArchive(file: File): Promise<Response> {\n        const formData = new FormData()\n        formData.append('file', file)\n\n        const headers = this.headers() as Record<string, string>\n\n        // TIPTIP: Leave out Content-Type here, it will be automatically set by the browser\n        delete headers['Content-Type']\n\n        return fetch(this.getBaseURL() + this.teamPath() + '/archive/import', {\n            method: 'POST',\n            headers,\n            body: formData,\n        })\n    }\n\n    async getBlocksWithParent(parentId: string, type?: string): Promise<Block[]> {\n        let path: string\n        if (type) {\n            path = this.teamPath() + `/blocks?parent_id=${encodeURIComponent(parentId)}&type=${encodeURIComponent(type)}`\n        } else {\n            path = this.teamPath() + `/blocks?parent_id=${encodeURIComponent(parentId)}`\n        }\n        return this.getBlocksWithPath(path)\n    }\n\n    async getBlocksWithType(type: string): Promise<Block[]> {\n        const path = this.teamPath() + `/blocks?type=${encodeURIComponent(type)}`\n        return this.getBlocksWithPath(path)\n    }\n\n    async getBlocksWithBlockID(blockID: string, boardID: string, optionalReadToken?: string): Promise<Block[]> {\n        let path = `/api/v2/boards/${boardID}/blocks?block_id=${blockID}`\n        const readToken = optionalReadToken || Utils.getReadToken()\n        if (readToken) {\n            path += `&read_token=${readToken}`\n        }\n        return this.getBlocksWithPath(path)\n    }\n\n    async getAllBlocks(boardID: string): Promise<Block[]> {\n        let path = `/api/v2/boards/${boardID}/blocks?all=true`\n        const readToken = Utils.getReadToken()\n        if (readToken) {\n            path += `&read_token=${readToken}`\n        }\n        return this.getBlocksWithPath(path)\n    }\n\n    private async getBlocksWithPath(path: string): Promise<Block[]> {\n        const response = await fetch(this.getBaseURL() + path, {headers: this.headers()})\n        if (response.status !== 200) {\n            return []\n        }\n        const blocks = (await this.getJson(response, [])) as Block[]\n        return this.fixBlocks(blocks)\n    }\n\n    private async getBoardsWithPath(path: string): Promise<Board[]> {\n        const response = await fetch(this.getBaseURL() + path, {headers: this.headers()})\n        if (response.status !== 200) {\n            return []\n        }\n        const boards = (await this.getJson(response, [])) as Board[]\n        return boards\n    }\n\n    private async getBoardMembersWithPath(path: string): Promise<BoardMember[]> {\n        const response = await fetch(this.getBaseURL() + path, {headers: this.headers()})\n        if (response.status !== 200) {\n            return []\n        }\n        const boardMembers = (await this.getJson(response, [])) as BoardMember[]\n        return boardMembers\n    }\n\n    fixBlocks(blocks: Block[]): Block[] {\n        if (!blocks) {\n            return []\n        }\n\n        // Hydrate is important, as it ensures that each block is complete to the current model\n        const fixedBlocks = OctoUtils.hydrateBlocks(blocks)\n\n        return fixedBlocks\n    }\n\n    async patchBlock(boardId: string, blockId: string, blockPatch: BlockPatch): Promise<Response> {\n        Utils.log(`patchBlock: ${blockId} block`)\n        const body = JSON.stringify(blockPatch)\n        return fetch(`${this.getBaseURL()}/api/v2/boards/${boardId}/blocks/${blockId}`, {\n            method: 'PATCH',\n            headers: this.headers(),\n            body,\n        })\n    }\n\n    async patchBlocks(blocks: Block[], blockPatches: BlockPatch[]): Promise<Response> {\n        Utils.log(`patchBlocks: ${blocks.length} blocks`)\n        const blockIds = blocks.map((block) => block.id)\n        const body = JSON.stringify({block_ids: blockIds, block_patches: blockPatches})\n\n        const path = this.getBaseURL() + this.teamPath() + '/blocks'\n        const response = fetch(path, {\n            method: 'PATCH',\n            headers: this.headers(),\n            body,\n        })\n        return response\n    }\n\n    async deleteBlock(boardId: string, blockId: string): Promise<Response> {\n        Utils.log(`deleteBlock: ${blockId} on board ${boardId}`)\n        return fetch(`${this.getBaseURL()}/api/v2/boards/${boardId}/blocks/${encodeURIComponent(blockId)}`, {\n            method: 'DELETE',\n            headers: this.headers(),\n        })\n    }\n\n    async undeleteBlock(boardId: string, blockId: string): Promise<Response> {\n        Utils.log(`undeleteBlock: ${blockId}`)\n        return fetch(`${this.getBaseURL()}/api/v2/boards/${encodeURIComponent(boardId)}/blocks/${encodeURIComponent(blockId)}/undelete`, {\n            method: 'POST',\n            headers: this.headers(),\n        })\n    }\n\n    async undeleteBoard(boardId: string): Promise<Response> {\n        Utils.log(`undeleteBoard: ${boardId}`)\n        return fetch(`${this.getBaseURL()}/api/v2/boards/${boardId}/undelete`, {\n            method: 'POST',\n            headers: this.headers(),\n        })\n    }\n\n    async followBlock(blockId: string, blockType: string, userId: string): Promise<Response> {\n        const body: Subscription = {\n            blockType,\n            blockId,\n            subscriberType: 'user',\n            subscriberId: userId,\n        }\n\n        return fetch(this.getBaseURL() + '/api/v2/subscriptions', {\n            method: 'POST',\n            headers: this.headers(),\n            body: JSON.stringify(body),\n        })\n    }\n\n    async unfollowBlock(blockId: string, blockType: string, userId: string): Promise<Response> {\n        return fetch(this.getBaseURL() + `/api/v2/subscriptions/${blockId}/${userId}`, {\n            method: 'DELETE',\n            headers: this.headers(),\n        })\n    }\n\n    async insertBlock(boardId: string, block: Block): Promise<Response> {\n        return this.insertBlocks(boardId, [block])\n    }\n\n    async insertBlocks(boardId: string, blocks: Block[], sourceBoardID?: string): Promise<Response> {\n        Utils.log(`insertBlocks: ${blocks.length} blocks(s) on board ${boardId}`)\n        blocks.forEach((block) => {\n            Utils.log(`\\t ${block.type}, ${block.id}, ${block.title?.substr(0, 50) || ''}`)\n        })\n        const body = JSON.stringify(blocks)\n        return fetch(`${this.getBaseURL()}/api/v2/boards/${boardId}/blocks` + (sourceBoardID ? `?sourceBoardID=${encodeURIComponent(sourceBoardID)}` : ''), {\n            method: 'POST',\n            headers: this.headers(),\n            body,\n        })\n    }\n\n    async createBoardsAndBlocks(bab: BoardsAndBlocks): Promise<Response> {\n        Utils.log(`createBoardsAndBlocks: ${bab.boards.length} board(s) ${bab.blocks.length} block(s)`)\n        bab.boards.forEach((board: Board) => {\n            Utils.log(`\\t Board ${board.id}, ${board.type}, ${board.title?.substr(0, 50) || ''}`)\n        })\n        bab.blocks.forEach((block: Block) => {\n            Utils.log(`\\t Block ${block.id}, ${block.type}, ${block.title?.substr(0, 50) || ''}`)\n        })\n\n        const body = JSON.stringify(bab)\n        return fetch(this.getBaseURL() + '/api/v2/boards-and-blocks', {\n            method: 'POST',\n            headers: this.headers(),\n            body,\n        })\n    }\n\n    async deleteBoardsAndBlocks(boardIds: string[], blockIds: string[]): Promise<Response> {\n        Utils.log(`deleteBoardsAndBlocks: ${boardIds.length} board(s) ${blockIds.length} block(s)`)\n        Utils.log(`\\t Boards ${boardIds.join(', ')}`)\n        Utils.log(`\\t Blocks ${blockIds.join(', ')}`)\n\n        const body = JSON.stringify({boards: boardIds, blocks: blockIds})\n        return fetch(this.getBaseURL() + '/api/v2/boards-and-blocks', {\n            method: 'DELETE',\n            headers: this.headers(),\n            body,\n        })\n    }\n\n    // BoardMember\n    async createBoardMember(member: Partial<BoardMember>): Promise<BoardMember|undefined> {\n        Utils.log(`createBoardMember: user ${member.userId} and board ${member.boardId}`)\n\n        const body = JSON.stringify(member)\n        const response = await fetch(this.getBaseURL() + `/api/v2/boards/${member.boardId}/members`, {\n            method: 'POST',\n            headers: this.headers(),\n            body,\n        })\n\n        if (response.status !== 200) {\n            return undefined\n        }\n\n        return this.getJson<BoardMember>(response, {} as BoardMember)\n    }\n\n    async joinBoard(boardId: string, allowAdmin: boolean): Promise<BoardMember|undefined> {\n        Utils.log(`joinBoard: board ${boardId}`)\n        let path = `/api/v2/boards/${boardId}/join`\n        if (allowAdmin) {\n            path += '?allow_admin'\n        }\n        const response = await fetch(this.getBaseURL() + path, {\n            headers: this.headers(),\n            method: 'POST',\n        })\n\n        if (response.status !== 200) {\n            return undefined\n        }\n\n        return this.getJson<BoardMember>(response, {} as BoardMember)\n    }\n\n    async updateBoardMember(member: BoardMember): Promise<Response> {\n        Utils.log(`udpateBoardMember: user ${member.userId} and board ${member.boardId}`)\n\n        const body = JSON.stringify(member)\n        return fetch(this.getBaseURL() + `/api/v2/boards/${member.boardId}/members/${member.userId}`, {\n            method: 'PUT',\n            headers: this.headers(),\n            body,\n        })\n    }\n\n    async deleteBoardMember(member: BoardMember): Promise<Response> {\n        Utils.log(`deleteBoardMember: user ${member.userId} and board ${member.boardId}`)\n\n        return fetch(this.getBaseURL() + `/api/v2/boards/${member.boardId}/members/${member.userId}`, {\n            method: 'DELETE',\n            headers: this.headers(),\n        })\n    }\n\n    async patchBoardsAndBlocks(babp: BoardsAndBlocksPatch): Promise<Response> {\n        Utils.log(`patchBoardsAndBlocks: ${babp.boardIDs.length} board(s) ${babp.blockIDs.length} block(s)`)\n        Utils.log(`\\t Board ${babp.boardIDs.join(', ')}`)\n        Utils.log(`\\t Blocks ${babp.blockIDs.join(', ')}`)\n\n        const body = JSON.stringify(babp)\n        return fetch(this.getBaseURL() + '/api/v2/boards-and-blocks', {\n            method: 'PATCH',\n            headers: this.headers(),\n            body,\n        })\n    }\n\n    // Sharing\n    async getSharing(boardID: string): Promise<ISharing | undefined> {\n        const path = `/api/v2/boards/${boardID}/sharing`\n        const response = await fetch(this.getBaseURL() + path, {headers: this.headers()})\n        if (response.status !== 200) {\n            return undefined\n        }\n        return this.getJson(response, undefined)\n    }\n\n    async setSharing(boardID: string, sharing: ISharing): Promise<boolean> {\n        const path = `/api/v2/boards/${boardID}/sharing`\n        const body = JSON.stringify(sharing)\n        const response = await fetch(\n            this.getBaseURL() + path,\n            {\n                method: 'POST',\n                headers: this.headers(),\n                body,\n            },\n        )\n        if (response.status !== 200) {\n            return false\n        }\n\n        return true\n    }\n\n    async regenerateTeamSignupToken(): Promise<void> {\n        const path = this.teamPath() + '/regenerate_signup_token'\n        await fetch(this.getBaseURL() + path, {\n            method: 'POST',\n            headers: this.headers(),\n        })\n    }\n\n    // Files\n\n    // Returns fileId of uploaded file, or undefined on failure\n    async uploadFile(rootID: string, file: File): Promise<string | undefined> {\n        // IMPORTANT: We need to post the image as a form. The browser will convert this to a application/x-www-form-urlencoded POST\n        const formData = new FormData()\n        formData.append('file', file)\n\n        try {\n            const headers = this.headers() as Record<string, string>\n\n            // TIPTIP: Leave out Content-Type here, it will be automatically set by the browser\n            delete headers['Content-Type']\n\n            const response = await fetch(this.getBaseURL() + this.teamPath() + '/' + rootID + '/files', {\n                method: 'POST',\n                headers,\n                body: formData,\n            })\n            if (response.status !== 200) {\n                return undefined\n            }\n\n            try {\n                const text = await response.text()\n                Utils.log(`uploadFile response: ${text}`)\n                const json = JSON.parse(text)\n\n                return json.fileId\n            } catch (e) {\n                Utils.logError(`uploadFile json ERROR: ${e}`)\n            }\n        } catch (e) {\n            Utils.logError(`uploadFile ERROR: ${e}`)\n        }\n\n        return undefined\n    }\n\n    async uploadAttachment(rootID: string, file: File): Promise<XMLHttpRequest | undefined> {\n        const formData = new FormData()\n        formData.append('file', file)\n\n        const xhr = new XMLHttpRequest()\n\n        xhr.open('POST', this.getBaseURL() + this.teamPath() + '/' + rootID + '/files', true)\n        const headers = this.headers() as Record<string, string>\n        delete headers['Content-Type']\n\n        xhr.setRequestHeader('Accept', 'application/json')\n        xhr.setRequestHeader('Authorization', this.token ? 'Bearer ' + this.token : '')\n        xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest')\n\n        if (xhr.upload) {\n            xhr.upload.onprogress = () => {}\n        }\n        xhr.send(formData)\n        return xhr\n    }\n\n    async getFileInfo(boardId: string, fileId: string): Promise<FileInfo> {\n        let path = '/api/v2/files/teams/' + this.teamId + '/' + boardId + '/' + fileId + '/info'\n        const readToken = Utils.getReadToken()\n        if (readToken) {\n            path += `?read_token=${readToken}`\n        }\n        const response = await fetch(this.getBaseURL() + path, {headers: this.headers()})\n        let fileInfo: FileInfo = {}\n\n        if (response.status === 200) {\n            fileInfo = this.getJson(response, {}) as FileInfo\n        } else if (response.status === 400) {\n            fileInfo = await this.getJson(response, {}) as FileInfo\n        }\n\n        return fileInfo\n    }\n\n    async getFileAsDataUrl(boardId: string, fileId: string): Promise<FileInfo> {\n        let path = '/api/v2/files/teams/' + this.teamId + '/' + boardId + '/' + fileId\n        const readToken = Utils.getReadToken()\n        if (readToken) {\n            path += `?read_token=${readToken}`\n        }\n        const response = await fetch(this.getBaseURL() + path, {headers: this.headers()})\n        let fileInfo: FileInfo = {}\n\n        if (response.status === 200) {\n            const blob = await response.blob()\n            fileInfo.url = URL.createObjectURL(blob)\n        } else if (response.status === 400) {\n            fileInfo = await this.getJson(response, {}) as FileInfo\n        }\n\n        return fileInfo\n    }\n\n    async getTeam(): Promise<Team | null> {\n        const path = this.teamPath()\n        const response = await fetch(this.getBaseURL() + path, {headers: this.headers()})\n        if (response.status !== 200) {\n            return null\n        }\n\n        return this.getJson(response, null)\n    }\n\n    async getTeams(): Promise<Team[]> {\n        const path = this.teamsPath()\n        const response = await fetch(this.getBaseURL() + path, {headers: this.headers()})\n        if (response.status !== 200) {\n            return []\n        }\n\n        return this.getJson<Team[]>(response, [])\n    }\n\n    async getTeamUsers(excludeBots?: boolean): Promise<IUser[]> {\n        let path = this.teamPath() + '/users'\n        if (excludeBots) {\n            path += '?exclude_bots=true'\n        }\n        const response = await fetch(this.getBaseURL() + path, {headers: this.headers()})\n        if (response.status !== 200) {\n            return []\n        }\n        return (await this.getJson(response, [])) as IUser[]\n    }\n\n    async getTeamUsersList(userIds: string[], teamId: string): Promise<IUser[] | []> {\n        const path = this.teamPath(teamId) + '/users'\n        const body = JSON.stringify(userIds)\n        const response = await fetch(this.getBaseURL() + path, {\n            headers: this.headers(),\n            method: 'POST',\n            body,\n        })\n\n        if (response.status !== 200) {\n            return []\n        }\n\n        return (await this.getJson(response, [])) as IUser[]\n    }\n\n    async searchTeamUsers(searchQuery: string, excludeBots?: boolean): Promise<IUser[]> {\n        let path = this.teamPath() + `/users?search=${searchQuery}`\n        if (excludeBots) {\n            path += '&exclude_bots=true'\n        }\n        const response = await fetch(this.getBaseURL() + path, {headers: this.headers()})\n        if (response.status !== 200) {\n            return []\n        }\n        return (await this.getJson(response, [])) as IUser[]\n    }\n\n    async getTeamTemplates(teamId?: string): Promise<Board[]> {\n        const path = this.teamPath(teamId) + '/templates'\n        return this.getBoardsWithPath(path)\n    }\n\n    async getBoards(): Promise<Board[]> {\n        const path = this.teamPath() + '/boards'\n        return this.getBoardsWithPath(path)\n    }\n\n    async getBoard(boardID: string): Promise<Board | undefined> {\n        let path = `/api/v2/boards/${boardID}`\n        const readToken = Utils.getReadToken()\n        if (readToken) {\n            path += `?read_token=${readToken}`\n        }\n        const response = await fetch(this.getBaseURL() + path, {\n            method: 'GET',\n            headers: this.headers(),\n        })\n\n        if (response.status !== 200) {\n            return undefined\n        }\n\n        return this.getJson<Board>(response, {} as Board)\n    }\n\n    async duplicateBoard(boardID: string, asTemplate: boolean, toTeam?: string): Promise<BoardsAndBlocks | undefined> {\n        let query = '?asTemplate=false'\n        if (asTemplate) {\n            query = '?asTemplate=true'\n        }\n        if (toTeam) {\n            query += `&toTeam=${encodeURIComponent(toTeam)}`\n        }\n\n        const path = `/api/v2/boards/${boardID}/duplicate${query}`\n        const response = await fetch(this.getBaseURL() + path, {\n            method: 'POST',\n            headers: this.headers(),\n        })\n\n        if (response.status !== 200) {\n            return undefined\n        }\n\n        return this.getJson<BoardsAndBlocks>(response, {} as BoardsAndBlocks)\n    }\n\n    async duplicateBlock(boardID: string, blockID: string, asTemplate: boolean): Promise<Block[] | undefined> {\n        let query = '?asTemplate=false'\n        if (asTemplate) {\n            query = '?asTemplate=true'\n        }\n        const path = `/api/v2/boards/${boardID}/blocks/${blockID}/duplicate${query}`\n        const response = await fetch(this.getBaseURL() + path, {\n            method: 'POST',\n            headers: this.headers(),\n        })\n\n        if (response.status !== 200) {\n            return undefined\n        }\n\n        return this.getJson<Block[]>(response, [] as Block[])\n    }\n\n    async getBlocksForBoard(teamId: string, boardId: string): Promise<Board[]> {\n        const path = this.teamPath(teamId) + `/boards/${boardId}`\n        return this.getBoardsWithPath(path)\n    }\n\n    async getBoardMembers(teamId: string, boardId: string): Promise<BoardMember[]> {\n        const path = `/api/v2/boards/${boardId}/members`\n        return this.getBoardMembersWithPath(path)\n    }\n\n    async createBoard(board: Board): Promise<Response> {\n        Utils.log(`createBoard: ${board.title} [${board.type}]`)\n        return fetch(this.getBaseURL() + this.teamPath(board.teamId) + '/boards', {\n            method: 'POST',\n            headers: this.headers(),\n            body: JSON.stringify(board),\n        })\n    }\n\n    async patchBoard(boardId: string, boardPatch: BoardPatch): Promise<Response> {\n        Utils.log(`patchBoard: ${boardId} board`)\n        const body = JSON.stringify(boardPatch)\n        return fetch(`${this.getBaseURL()}/api/v2/boards/${boardId}`, {\n            method: 'PATCH',\n            headers: this.headers(),\n            body,\n        })\n    }\n\n    async deleteBoard(boardId: string): Promise<Response> {\n        Utils.log(`deleteBoard: ${boardId}`)\n        return fetch(`${this.getBaseURL()}/api/v2/boards/${boardId}`, {\n            method: 'DELETE',\n            headers: this.headers(),\n        })\n    }\n\n    async getSidebarCategories(teamID: string): Promise<CategoryBoards[]> {\n        const path = `/api/v2/teams/${teamID}/categories`\n        const response = await fetch(this.getBaseURL() + path, {headers: this.headers()})\n        if (response.status !== 200) {\n            return []\n        }\n\n        return (await this.getJson(response, [])) as CategoryBoards[]\n    }\n\n    async createSidebarCategory(category: Category): Promise<Response> {\n        const path = `/api/v2/teams/${category.teamID}/categories`\n        const body = JSON.stringify(category)\n        return fetch(this.getBaseURL() + path, {\n            method: 'POST',\n            headers: this.headers(),\n            body,\n        })\n    }\n\n    async deleteSidebarCategory(teamID: string, categoryID: string): Promise<Response> {\n        const url = `/api/v2/teams/${teamID}/categories/${categoryID}`\n        return fetch(this.getBaseURL() + url, {\n            method: 'DELETE',\n            headers: this.headers(),\n        })\n    }\n\n    async updateSidebarCategory(category: Category): Promise<Response> {\n        const path = `/api/v2/teams/${category.teamID}/categories/${category.id}`\n        const body = JSON.stringify(category)\n        return fetch(this.getBaseURL() + path, {\n            method: 'PUT',\n            headers: this.headers(),\n            body,\n        })\n    }\n\n    async reorderSidebarCategories(teamID: string, newCategoryOrder: string[]): Promise<string[]> {\n        const path = `/api/v2/teams/${teamID}/categories/reorder`\n        const body = JSON.stringify(newCategoryOrder)\n        const response = await fetch(this.getBaseURL() + path, {\n            method: 'PUT',\n            headers: this.headers(),\n            body,\n        })\n\n        if (response.status !== 200) {\n            return []\n        }\n\n        return (await this.getJson(response, [])) as string[]\n    }\n\n    async reorderSidebarCategoryBoards(teamID: string, categoryID: string, newBoardsOrder: string[]): Promise<string[]> {\n        const path = `/api/v2/teams/${teamID}/categories/${categoryID}/boards/reorder`\n        const body = JSON.stringify(newBoardsOrder)\n        const response = await fetch(this.getBaseURL() + path, {\n            method: 'PUT',\n            headers: this.headers(),\n            body,\n        })\n\n        if (response.status !== 200) {\n            return []\n        }\n\n        return (await this.getJson(response, [])) as string[]\n    }\n\n    async moveBoardToCategory(teamID: string, boardID: string, toCategoryID: string, fromCategoryID: string): Promise<Response> {\n        const url = `/api/v2/teams/${teamID}/categories/${toCategoryID || '0'}/boards/${boardID}`\n        const payload = {\n            fromCategoryID,\n        }\n        const body = JSON.stringify(payload)\n\n        return fetch(this.getBaseURL() + url, {\n            method: 'POST',\n            headers: this.headers(),\n            body,\n        })\n    }\n\n    async search(teamID: string, query: string): Promise<Board[]> {\n        const url = `${this.teamPath(teamID)}/boards/search?q=${encodeURIComponent(query)}`\n        const response = await fetch(this.getBaseURL() + url, {\n            method: 'GET',\n            headers: this.headers(),\n        })\n\n        if (response.status !== 200) {\n            return []\n        }\n\n        return (await this.getJson(response, [])) as Board[]\n    }\n\n    async searchLinkableBoards(teamID: string, query: string): Promise<Board[]> {\n        const url = `${this.teamPath(teamID)}/boards/search/linkable?q=${encodeURIComponent(query)}`\n        const response = await fetch(this.getBaseURL() + url, {\n            method: 'GET',\n            headers: this.headers(),\n        })\n\n        if (response.status !== 200) {\n            return []\n        }\n\n        return (await this.getJson(response, [])) as Board[]\n    }\n\n    async searchAll(query: string): Promise<Board[]> {\n        const url = `/api/v2/boards/search?q=${encodeURIComponent(query)}`\n        const response = await fetch(this.getBaseURL() + url, {\n            method: 'GET',\n            headers: this.headers(),\n        })\n\n        if (response.status !== 200) {\n            return []\n        }\n\n        return (await this.getJson(response, [])) as Board[]\n    }\n\n    async getUserBlockSubscriptions(userId: string): Promise<Subscription[]> {\n        const path = `/api/v2/subscriptions/${userId}`\n        const response = await fetch(this.getBaseURL() + path, {headers: this.headers()})\n        if (response.status !== 200) {\n            return []\n        }\n\n        return (await this.getJson(response, [])) as Subscription[]\n    }\n\n    async searchUserChannels(teamId: string, searchQuery: string): Promise<Channel[] | undefined> {\n        const path = `/api/v2/teams/${teamId}/channels?search=${searchQuery}`\n        const response = await fetch(this.getBaseURL() + path, {\n            headers: this.headers(),\n            method: 'GET',\n        })\n        if (response.status !== 200) {\n            return undefined\n        }\n\n        return (await this.getJson(response, [])) as Channel[]\n    }\n\n    async getChannel(teamId: string, channelId: string): Promise<Channel | undefined> {\n        const path = `/api/v2/teams/${teamId}/channels/${channelId}`\n        const response = await fetch(this.getBaseURL() + path, {\n            headers: this.headers(),\n            method: 'GET',\n        })\n        if (response.status !== 200) {\n            return undefined\n        }\n\n        return (await this.getJson(response, {})) as Channel\n    }\n\n    // onboarding\n    async prepareOnboarding(teamId: string): Promise<PrepareOnboardingResponse | undefined> {\n        const path = `/api/v2/teams/${teamId}/onboard`\n        const response = await fetch(this.getBaseURL() + path, {\n            headers: this.headers(),\n            method: 'POST',\n        })\n        if (response.status !== 200) {\n            return undefined\n        }\n\n        return (await this.getJson(response, {})) as PrepareOnboardingResponse\n    }\n\n    async notifyAdminUpgrade(): Promise<void> {\n        const path = `${this.teamPath()}/notifyadminupgrade`\n        await fetch(this.getBaseURL() + path, {\n            headers: this.headers(),\n            method: 'POST',\n        })\n    }\n\n    async getBoardsCloudLimits(): Promise<BoardsCloudLimits | undefined> {\n        const path = '/api/v2/limits'\n        const response = await fetch(this.getBaseURL() + path, {headers: this.headers()})\n        if (response.status !== 200) {\n            return undefined\n        }\n\n        const limits = (await this.getJson(response, {})) as BoardsCloudLimits\n        Utils.log(`Cloud limits: cards=${limits.cards}   views=${limits.views}`)\n        return limits\n    }\n\n    async getSiteStatistics(): Promise<BoardSiteStatistics | undefined> {\n        const path = '/api/v2/statistics'\n        const response = await fetch(this.getBaseURL() + path, {headers: this.headers()})\n        if (response.status !== 200) {\n            return undefined\n        }\n\n        const stats = (await this.getJson(response, {})) as BoardSiteStatistics\n        Utils.log(`Site Statistics: cards=${stats.card_count}   boards=${stats.board_count}`)\n        return stats\n    }\n\n    // insights\n    async getMyTopBoards(timeRange: string, page: number, perPage: number, teamId: string): Promise<TopBoardResponse | undefined> {\n        const path = `/api/v2/users/me/boards/insights?time_range=${timeRange}&page=${page}&per_page=${perPage}&team_id=${teamId}`\n        const response = await fetch(this.getBaseURL() + path, {headers: this.headers()})\n        if (response.status !== 200) {\n            return undefined\n        }\n\n        return (await this.getJson(response, {})) as TopBoardResponse\n    }\n\n    async getTeamTopBoards(timeRange: string, page: number, perPage: number, teamId: string): Promise<TopBoardResponse | undefined> {\n        const path = `/api/v2/teams/${teamId}/boards/insights?time_range=${timeRange}&page=${page}&per_page=${perPage}`\n        const response = await fetch(this.getBaseURL() + path, {headers: this.headers()})\n        if (response.status !== 200) {\n            return undefined\n        }\n\n        return (await this.getJson(response, {})) as TopBoardResponse\n    }\n\n    async moveBlockTo(blockId: string, where: 'before'|'after', dstBlockId: string): Promise<Response> {\n        return fetch(`${this.getBaseURL()}/api/v2/content-blocks/${blockId}/moveto/${where}/${dstBlockId}`, {\n            method: 'POST',\n            headers: this.headers(),\n            body: '{}',\n        })\n    }\n\n    async hideBoard(categoryID: string, boardID: string): Promise<Response> {\n        const path = `${this.teamPath()}/categories/${categoryID}/boards/${boardID}/hide`\n        return fetch(this.getBaseURL() + path, {\n            method: 'PUT',\n            headers: this.headers(),\n        })\n    }\n\n    async unhideBoard(categoryID: string, boardID: string): Promise<Response> {\n        const path = `${this.teamPath()}/categories/${categoryID}/boards/${boardID}/unhide`\n        return fetch(this.getBaseURL() + path, {\n            method: 'PUT',\n            headers: this.headers(),\n        })\n    }\n}\n\nconst octoClient = new OctoClient()\n\nexport {OctoClient}\nexport default octoClient\n"
  },
  {
    "path": "webapp/src/octoUtils.test.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {Block} from './blocks/block'\nimport {OctoUtils} from './octoUtils'\n\nimport {TestBlockFactory} from './test/testBlockFactory'\n\ntest('duplicateBlockTree: Card', async () => {\n    const [blocks, sourceBlock] = createCardTree()\n\n    const [newBlocks, newSourceBlock, idMap] = OctoUtils.duplicateBlockTree(blocks, sourceBlock.id)\n\n    expect(newBlocks.length).toBe(blocks.length)\n    expect(newSourceBlock.id).not.toBe(sourceBlock.id)\n    expect(newSourceBlock.type).toBe(sourceBlock.type)\n\n    // When duplicating a non-root block, the boardId should not be re-mapped\n    expect(newSourceBlock.boardId).toBe(sourceBlock.boardId)\n    expect(idMap[sourceBlock.id]).toBe(newSourceBlock.id)\n\n    for (const newBlock of newBlocks) {\n        expect(newBlock.boardId).toBe(newSourceBlock.boardId)\n    }\n\n    for (const textBlock of newBlocks.filter((o) => o.type === 'text')) {\n        expect(textBlock.parentId).toBe(newSourceBlock.id)\n    }\n})\n\ntest('filterConditionValidOrDefault', async () => {\n    // Test 'options'\n    expect(OctoUtils.filterConditionValidOrDefault('options', 'includes')).toBe('includes')\n    expect(OctoUtils.filterConditionValidOrDefault('options', 'notIncludes')).toBe('notIncludes')\n    expect(OctoUtils.filterConditionValidOrDefault('options', 'isEmpty')).toBe('isEmpty')\n    expect(OctoUtils.filterConditionValidOrDefault('options', 'isNotEmpty')).toBe('isNotEmpty')\n    expect(OctoUtils.filterConditionValidOrDefault('options', 'is')).toBe('includes')\n\n    expect(OctoUtils.filterConditionValidOrDefault('boolean', 'isSet')).toBe('isSet')\n    expect(OctoUtils.filterConditionValidOrDefault('boolean', 'isNotSet')).toBe('isNotSet')\n    expect(OctoUtils.filterConditionValidOrDefault('boolean', 'includes')).toBe('isSet')\n\n    expect(OctoUtils.filterConditionValidOrDefault('text', 'is')).toBe('is')\n    expect(OctoUtils.filterConditionValidOrDefault('text', 'contains')).toBe('contains')\n    expect(OctoUtils.filterConditionValidOrDefault('text', 'notContains')).toBe('notContains')\n    expect(OctoUtils.filterConditionValidOrDefault('text', 'startsWith')).toBe('startsWith')\n    expect(OctoUtils.filterConditionValidOrDefault('text', 'notStartsWith')).toBe('notStartsWith')\n    expect(OctoUtils.filterConditionValidOrDefault('text', 'endsWith')).toBe('endsWith')\n    expect(OctoUtils.filterConditionValidOrDefault('text', 'notEndsWith')).toBe('notEndsWith')\n    expect(OctoUtils.filterConditionValidOrDefault('text', 'isEmpty')).toBe('is')\n\n    expect(OctoUtils.filterConditionValidOrDefault('date', 'is')).toBe('is')\n    expect(OctoUtils.filterConditionValidOrDefault('date', 'isBefore')).toBe('isBefore')\n    expect(OctoUtils.filterConditionValidOrDefault('date', 'isAfter')).toBe('isAfter')\n    expect(OctoUtils.filterConditionValidOrDefault('date', 'isSet')).toBe('isSet')\n    expect(OctoUtils.filterConditionValidOrDefault('date', 'isNotSet')).toBe('isNotSet')\n    expect(OctoUtils.filterConditionValidOrDefault('date', 'isEmpty')).toBe('is')\n})\n\nfunction createCardTree(): [Block[], Block] {\n    const blocks: Block[] = []\n\n    const card = TestBlockFactory.createCard()\n    card.id = 'card1'\n    card.boardId = 'board1'\n    blocks.push(card)\n\n    for (let i = 0; i < 5; i++) {\n        const textBlock = TestBlockFactory.createText(card)\n        textBlock.id = `text${i}`\n        blocks.push(textBlock)\n    }\n\n    return [blocks, card]\n}\n"
  },
  {
    "path": "webapp/src/octoUtils.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {IntlShape} from 'react-intl'\n\nimport {FilterValueType} from './properties/types'\nimport {Block, createBlock} from './blocks/block'\nimport {BoardView, createBoardView} from './blocks/boardView'\nimport {Card, createCard} from './blocks/card'\nimport {createCommentBlock} from './blocks/commentBlock'\nimport {createCheckboxBlock} from './blocks/checkboxBlock'\nimport {createDividerBlock} from './blocks/dividerBlock'\nimport {createImageBlock} from './blocks/imageBlock'\nimport {createTextBlock} from './blocks/textBlock'\nimport {createH1Block} from './blocks/h1Block'\nimport {createH2Block} from './blocks/h2Block'\nimport {createH3Block} from './blocks/h3Block'\nimport {FilterCondition} from './blocks/filterClause'\nimport {createAttachmentBlock} from './blocks/attachmentBlock'\nimport {Utils} from './utils'\n\nclass OctoUtils {\n    static hydrateBlock(block: Block): Block {\n        switch (block.type) {\n        case 'view': { return createBoardView(block) }\n        case 'card': { return createCard(block) }\n        case 'text': { return createTextBlock(block) }\n        case 'h1': { return createH1Block(block) }\n        case 'h2': { return createH2Block(block) }\n        case 'h3': { return createH3Block(block) }\n        case 'image': { return createImageBlock(block) }\n        case 'divider': { return createDividerBlock(block) }\n        case 'comment': { return createCommentBlock(block) }\n        case 'checkbox': { return createCheckboxBlock(block) }\n        case 'attachment': { return createAttachmentBlock(block) }\n        default: {\n            Utils.assertFailure(`Can't hydrate unknown block type: ${block.type}`)\n            return createBlock(block)\n        }\n        }\n    }\n\n    static hydrateBlocks(blocks: readonly Block[]): Block[] {\n        return blocks.map((block) => this.hydrateBlock(block))\n    }\n\n    static mergeBlocks(blocks: readonly Block[], updatedBlocks: readonly Block[]): Block[] {\n        const updatedBlockIds = updatedBlocks.map((o) => o.id)\n        const newBlocks = blocks.filter((o) => !updatedBlockIds.includes(o.id))\n        const updatedAndNotDeletedBlocks = updatedBlocks.filter((o) => o.deleteAt === 0)\n        newBlocks.push(...updatedAndNotDeletedBlocks)\n        return newBlocks\n    }\n\n    // Creates a copy of the blocks with new ids and parentIDs\n    static duplicateBlockTree(blocks: readonly Block[], sourceBlockId: string): [Block[], Block, Readonly<Record<string, string>>] {\n        const idMap: Record<string, string> = {}\n        const now = Date.now()\n        const newBlocks = blocks.map((block) => {\n            const newBlock = this.hydrateBlock(block)\n            newBlock.id = Utils.createGuid(Utils.blockTypeToIDType(newBlock.type))\n            newBlock.createAt = now\n            newBlock.updateAt = now\n            idMap[block.id] = newBlock.id\n            return newBlock\n        })\n\n        const newSourceBlockId = idMap[sourceBlockId]\n\n        // Determine the new boardId if needed\n        let newBoardId: string\n        const sourceBlock = blocks.find((block) => block.id === sourceBlockId)!\n        if (sourceBlock.boardId === sourceBlock.id) {\n            // Special case: when duplicating a tree from root, remap all the descendant boardIds\n            const newSourceRootBlock = newBlocks.find((block) => block.id === newSourceBlockId)!\n            newBoardId = newSourceRootBlock.id\n        }\n\n        newBlocks.forEach((newBlock) => {\n            // Note: Don't remap the parent of the new root block\n            if (newBlock.id !== newSourceBlockId && newBlock.parentId) {\n                newBlock.parentId = idMap[newBlock.parentId] || newBlock.parentId\n                Utils.assert(newBlock.parentId, `Block ${newBlock.id} (${newBlock.type} ${newBlock.title}) has no parent`)\n            }\n\n            // Remap the boardIds if we are duplicating a tree from root\n            if (newBoardId) {\n                newBlock.boardId = newBoardId\n            }\n\n            // Remap manual card order\n            if (newBlock.type === 'view') {\n                const view = newBlock as BoardView\n                view.fields.cardOrder = view.fields.cardOrder.map((o) => idMap[o])\n            }\n\n            // Remap card content order\n            if (newBlock.type === 'card') {\n                const card = newBlock as Card\n                card.fields.contentOrder = card.fields.contentOrder.map((o) => (Array.isArray(o) ? o.map((o2) => idMap[o2]) : idMap[o]))\n            }\n        })\n\n        const newSourceBlock = newBlocks.find((block) => block.id === newSourceBlockId)!\n        return [newBlocks, newSourceBlock, idMap]\n    }\n\n    static filterConditionDisplayString(filterCondition: FilterCondition, intl: IntlShape, filterValueType: string): string {\n        if (filterValueType === 'options' || filterValueType === 'person') {\n            switch (filterCondition) {\n            case 'includes': return intl.formatMessage({id: 'Filter.includes', defaultMessage: 'includes'})\n            case 'notIncludes': return intl.formatMessage({id: 'Filter.not-includes', defaultMessage: 'doesn\\'t include'})\n            case 'isEmpty': return intl.formatMessage({id: 'Filter.is-empty', defaultMessage: 'is empty'})\n            case 'isNotEmpty': return intl.formatMessage({id: 'Filter.is-not-empty', defaultMessage: 'is not empty'})\n            default: {\n                return intl.formatMessage({id: 'Filter.includes', defaultMessage: 'includes'})\n            }\n            }\n        } else if (filterValueType === 'boolean') {\n            switch (filterCondition) {\n            case 'isSet': return intl.formatMessage({id: 'Filter.is-set', defaultMessage: 'is set'})\n            case 'isNotSet': return intl.formatMessage({id: 'Filter.is-not-set', defaultMessage: 'is not set'})\n            default: {\n                return intl.formatMessage({id: 'Filter.is-set', defaultMessage: 'is set'})\n            }\n            }\n        } else if (filterValueType === 'text') {\n            switch (filterCondition) {\n            case 'is': return intl.formatMessage({id: 'Filter.is', defaultMessage: 'is'})\n            case 'contains': return intl.formatMessage({id: 'Filter.contains', defaultMessage: 'contains'})\n            case 'notContains': return intl.formatMessage({id: 'Filter.not-contains', defaultMessage: 'doesn\\'t contain'})\n            case 'startsWith': return intl.formatMessage({id: 'Filter.starts-with', defaultMessage: 'starts with'})\n            case 'notStartsWith': return intl.formatMessage({id: 'Filter.not-starts-with', defaultMessage: 'doesn\\'t start with'})\n            case 'endsWith': return intl.formatMessage({id: 'Filter.ends-with', defaultMessage: 'ends with'})\n            case 'notEndsWith': return intl.formatMessage({id: 'Filter.not-ends-with', defaultMessage: 'doesn\\'t end with'})\n            default: {\n                return intl.formatMessage({id: 'Filter.is', defaultMessage: 'is'})\n            }\n            }\n        } else if (filterValueType === 'date') {\n            switch (filterCondition) {\n            case 'is': return intl.formatMessage({id: 'Filter.is', defaultMessage: 'is'})\n            case 'isBefore': return intl.formatMessage({id: 'Filter.is-before', defaultMessage: 'is before'})\n            case 'isAfter': return intl.formatMessage({id: 'Filter.is-after', defaultMessage: 'is after'})\n            case 'isSet': return intl.formatMessage({id: 'Filter.is-set', defaultMessage: 'is set'})\n            case 'isNotSet': return intl.formatMessage({id: 'Filter.is-not-set', defaultMessage: 'is not set'})\n            default: {\n                return intl.formatMessage({id: 'Filter.is', defaultMessage: 'is'})\n            }\n            }\n        } else {\n            Utils.assertFailure()\n            return '(unknown)'\n        }\n    }\n\n    static filterConditionValidOrDefault(filterValueType: FilterValueType, currentFilterCondition: FilterCondition): FilterCondition {\n        if (filterValueType === 'options') {\n            switch (currentFilterCondition) {\n            case 'includes':\n            case 'notIncludes':\n            case 'isEmpty':\n            case 'isNotEmpty':\n                return currentFilterCondition\n            default: {\n                return 'includes'\n            }\n            }\n        } else if (filterValueType === 'boolean') {\n            switch (currentFilterCondition) {\n            case 'isSet':\n            case 'isNotSet':\n                return currentFilterCondition\n            default: {\n                return 'isSet'\n            }\n            }\n        } else if (filterValueType === 'text') {\n            switch (currentFilterCondition) {\n            case 'is':\n            case 'contains':\n            case 'notContains':\n            case 'startsWith':\n            case 'notStartsWith':\n            case 'endsWith':\n            case 'notEndsWith':\n                return currentFilterCondition\n            default: {\n                return 'is'\n            }\n            }\n        } else if (filterValueType === 'date') {\n            switch (currentFilterCondition) {\n            case 'is':\n            case 'isBefore':\n            case 'isAfter':\n            case 'isSet':\n            case 'isNotSet':\n                return currentFilterCondition\n            default: {\n                return 'is'\n            }\n            }\n        }\n        Utils.assertFailure()\n        return 'includes'\n    }\n}\nexport {OctoUtils}\n"
  },
  {
    "path": "webapp/src/onboardingTour/index.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nexport interface PrepareOnboardingResponse {\n    teamID: string\n    boardID: string\n}\n"
  },
  {
    "path": "webapp/src/pages/boardPage/backwardCompatibilityQueryParamsRedirect.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nconst BackwardCompatibilityQueryParamsRedirect = (): null => {\n    // useEffect(() => {\n    //     // Backward compatibility: This can be removed in the future, this is for\n    //     // transform the old query params into routes\n    //     const queryBoardId = queryString.get('id')\n    //     const params = {...match.params}\n    //     let needsRedirect = false\n    //     if (queryBoardId) {\n    //         params.boardId = queryBoardId\n    //         needsRedirect = true\n    //     }\n    //     const queryViewId = queryString.get('v')\n    //     if (queryViewId) {\n    //         params.viewId = queryViewId\n    //         needsRedirect = true\n    //     }\n    //     const queryCardId = queryString.get('c')\n    //     if (queryCardId) {\n    //         params.cardId = queryCardId\n    //         needsRedirect = true\n    //     }\n    //     if (needsRedirect) {\n    //         const newPath = generatePath(match.path, params)\n    //         history.replace(newPath)\n    //         return\n    //     }\n    //\n    //     // Backward compatibility end\n    //     const boardId = match.params.boardId\n    //     const viewId = match.params.viewId === '0' ? '' : match.params.viewId\n    //\n    //     // TODO use actual team ID here\n    //     const teamID = 'atjjg8ofqb8kjnwy15yhezdgoh'\n    //\n    //     if (!boardId) {\n    //         // Load last viewed boardView\n    //         const lastBoardId = UserSettings.lastBoardId[teamID] || undefined\n    //         const lastViewId = lastBoardId ? UserSettings.lastViewId[lastBoardId] : undefined\n    //         if (lastBoardId) {\n    //             let newPath = generatePath(match.path, {...match.params, boardId: lastBoardId})\n    //             if (lastViewId) {\n    //                 newPath = generatePath(match.path, {...match.params, boardId: lastBoardId, viewId: lastViewId})\n    //             }\n    //             history.replace(newPath)\n    //             return\n    //         }\n    //         return\n    //     }\n    //\n    //     Utils.log(`attachToBoard: ${boardId}`)\n    //\n    //     // Ensure boardViews is for our boardId before redirecting\n    //     const isCorrectBoardView = boardViews.length > 0 && boardViews[0].parentId === boardId\n    //     if (!viewId && isCorrectBoardView) {\n    //         const newPath = generatePath(match.path, {...match.params, boardId, viewId: boardViews[0].id})\n    //         history.replace(newPath)\n    //         return\n    //     }\n    //\n    //     UserSettings.setLastBoardID(teamId, boardId || '')\n    //     if (boardId !== '') {\n    //         UserSettings.setLastViewId(boardId, viewId)\n    //     }\n    //\n    //     dispatch(setCurrentBoard(boardId || ''))\n    //     dispatch(setCurrentView(viewId || ''))\n    // }, [match.params.boardId, match.params.viewId, boardViews])\n    return null\n}\n\nexport default BackwardCompatibilityQueryParamsRedirect\n"
  },
  {
    "path": "webapp/src/pages/boardPage/boardPage.scss",
    "content": ".BoardPage {\n    position: relative;\n\n    .VersionMessage:not(:first-child) {\n        position: absolute;\n        top: 0;\n        left: 0;\n        width: 100%;\n    }\n\n    > .error {\n        background-color: rgba(230, 192, 192, 0.9);\n        text-align: center;\n        padding: 10px;\n    }\n\n    > .WSConnection {\n        background-color: rgba(230, 220, 192, 0.9);\n        text-align: center;\n        padding: 10px;\n    }\n\n    > .WSConnection.error {\n        background-color: rgba(230, 192, 192, 0.9);\n    }\n\n    > .mobileWarning {\n        background-color: rgba(230, 192, 192, 0.9);\n        text-align: center;\n        padding: 10px;\n        display: none;\n\n        div {\n            width: 100%;\n        }\n\n        .IconButton {\n            float: right;\n            width: 16px;\n            min-width: 16px;\n            height: 100%;\n\n            i {\n                font-size: 16px;\n            }\n        }\n    }\n\n    @media screen and (max-width: 768px) { \n        .mobileWarning {display: flex;}\n    }\n}\n\n.Dialog.dialog-back.workspaceSwitcherModal > div > .dialog {\n    width: 600px;\n    height: 468px;\n}\n"
  },
  {
    "path": "webapp/src/pages/boardPage/boardPage.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useEffect, useState, useMemo, useCallback} from 'react'\nimport {batch} from 'react-redux'\nimport {FormattedMessage, useIntl} from 'react-intl'\nimport {useRouteMatch, useHistory} from 'react-router-dom'\n\nimport Workspace from '../../components/workspace'\nimport VersionMessage from '../../components/messages/versionMessage'\nimport octoClient from '../../octoClient'\nimport {Subscription, WSClient} from '../../wsclient'\nimport {Utils} from '../../utils'\nimport {useWebsockets} from '../../hooks/websockets'\nimport {IUser} from '../../user'\nimport {Block} from '../../blocks/block'\nimport {ContentBlock} from '../../blocks/contentBlock'\nimport {CommentBlock} from '../../blocks/commentBlock'\nimport {AttachmentBlock} from '../../blocks/attachmentBlock'\nimport {Board, BoardMember} from '../../blocks/board'\nimport {BoardView} from '../../blocks/boardView'\nimport {Card} from '../../blocks/card'\nimport {\n    updateBoards,\n    updateMembersEnsuringBoardsAndUsers,\n    getCurrentBoardId,\n    setCurrent as setCurrentBoard,\n    fetchBoardMembers,\n    addMyBoardMemberships,\n} from '../../store/boards'\nimport {getCurrentViewId, setCurrent as setCurrentView, updateViews} from '../../store/views'\nimport ConfirmationDialog from '../../components/confirmationDialogBox'\nimport {initialLoad, initialReadOnlyLoad, loadBoardData} from '../../store/initialLoad'\nimport {useAppSelector, useAppDispatch} from '../../store/hooks'\nimport {setTeam} from '../../store/teams'\nimport {updateCards} from '../../store/cards'\nimport {updateComments} from '../../store/comments'\nimport {updateAttachments} from '../../store/attachments'\nimport {updateContents} from '../../store/contents'\nimport {\n    getMe,\n    followBlock,\n    unfollowBlock,\n} from '../../store/users'\nimport {setGlobalError} from '../../store/globalError'\nimport {UserSettings} from '../../userSettings'\n\nimport IconButton from '../../widgets/buttons/iconButton'\nimport CloseIcon from '../../widgets/icons/close'\n\nimport TelemetryClient, {TelemetryActions, TelemetryCategory} from '../../telemetry/telemetryClient'\n\nimport {Constants} from '../../constants'\n\nimport {getCategoryOfBoard, getHiddenBoardIDs} from '../../store/sidebar'\n\nimport SetWindowTitleAndIcon from './setWindowTitleAndIcon'\nimport TeamToBoardAndViewRedirect from './teamToBoardAndViewRedirect'\nimport UndoRedoHotKeys from './undoRedoHotKeys'\nimport BackwardCompatibilityQueryParamsRedirect from './backwardCompatibilityQueryParamsRedirect'\nimport WebsocketConnection from './websocketConnection'\n\nimport './boardPage.scss'\n\ntype Props = {\n    readonly?: boolean\n    new?: boolean\n}\n\nconst BoardPage = (props: Props): JSX.Element => {\n    const intl = useIntl()\n    const activeBoardId = useAppSelector(getCurrentBoardId)\n    const activeViewId = useAppSelector(getCurrentViewId)\n    const dispatch = useAppDispatch()\n    const match = useRouteMatch<{boardId: string, viewId: string, cardId?: string, teamId?: string}>()\n    const [mobileWarningClosed, setMobileWarningClosed] = useState(UserSettings.mobileWarningClosed)\n    const teamId = match.params.teamId || UserSettings.lastTeamId || Constants.globalTeamId\n    const viewId = match.params.viewId\n    const me = useAppSelector<IUser|null>(getMe)\n    const hiddenBoardIDs = useAppSelector(getHiddenBoardIDs)\n    const category = useAppSelector(getCategoryOfBoard(activeBoardId))\n    const [showJoinBoardDialog, setShowJoinBoardDialog] = useState<boolean>(false)\n    const history = useHistory()\n\n    // if we're in a legacy route and not showing a shared board,\n    // redirect to the new URL schema equivalent\n    if (Utils.isFocalboardLegacy() && !props.readonly) {\n        window.location.href = window.location.href.replace('/plugins/focalboard', '/boards')\n    }\n\n    // TODO: Make this less brittle. This only works because this is the root render function\n    useEffect(() => {\n        UserSettings.lastTeamId = teamId\n        octoClient.teamId = teamId\n        dispatch(setTeam(teamId))\n    }, [teamId])\n\n    const loadAction: (boardId: string) => any = useMemo(() => {\n        if (props.readonly) {\n            return initialReadOnlyLoad\n        }\n        return initialLoad\n    }, [props.readonly])\n\n    useWebsockets(teamId, (wsClient) => {\n        const incrementalBlockUpdate = (_: WSClient, blocks: Block[]) => {\n            const teamBlocks = blocks\n\n            batch(() => {\n                dispatch(updateViews(teamBlocks.filter((b: Block) => b.type === 'view' || b.deleteAt !== 0) as BoardView[]))\n                dispatch(updateCards(teamBlocks.filter((b: Block) => b.type === 'card' || b.deleteAt !== 0) as Card[]))\n                dispatch(updateComments(teamBlocks.filter((b: Block) => b.type === 'comment' || b.deleteAt !== 0) as CommentBlock[]))\n                dispatch(updateAttachments(teamBlocks.filter((b: Block) => b.type === 'attachment' || b.deleteAt !== 0) as AttachmentBlock[]))\n                dispatch(updateContents(teamBlocks.filter((b: Block) => b.type !== 'card' && b.type !== 'view' && b.type !== 'board' && b.type !== 'comment' && b.type !== 'attachment') as ContentBlock[]))\n            })\n        }\n\n        const incrementalBoardUpdate = (_: WSClient, boards: Board[]) => {\n            // only takes into account the entities that belong to the team or the user boards\n            const teamBoards = boards.filter((b: Board) => b.teamId === Constants.globalTeamId || b.teamId === teamId)\n            const activeBoard = teamBoards.find((b: Board) => b.id === activeBoardId)\n            dispatch(updateBoards(teamBoards))\n\n            if (activeBoard) {\n                dispatch(fetchBoardMembers({\n                    teamId,\n                    boardId: activeBoardId,\n                }))\n            }\n        }\n\n        const incrementalBoardMemberUpdate = (_: WSClient, members: BoardMember[]) => {\n            dispatch(updateMembersEnsuringBoardsAndUsers(members))\n\n            if (me) {\n                const myBoardMemberships = members.filter((boardMember) => boardMember.userId === me.id)\n                dispatch(addMyBoardMemberships(myBoardMemberships))\n            }\n        }\n\n        const dispatchLoadAction = () => {\n            dispatch(loadAction(match.params.boardId))\n        }\n\n        Utils.log('useWEbsocket adding onChange handler')\n        wsClient.addOnChange(incrementalBlockUpdate, 'block')\n        wsClient.addOnChange(incrementalBoardUpdate, 'board')\n        wsClient.addOnChange(incrementalBoardMemberUpdate, 'boardMembers')\n        wsClient.addOnReconnect(dispatchLoadAction)\n\n        wsClient.setOnFollowBlock((_: WSClient, subscription: Subscription): void => {\n            if (subscription.subscriberId === me?.id) {\n                dispatch(followBlock(subscription))\n            }\n        })\n        wsClient.setOnUnfollowBlock((_: WSClient, subscription: Subscription): void => {\n            if (subscription.subscriberId === me?.id) {\n                dispatch(unfollowBlock(subscription))\n            }\n        })\n\n        return () => {\n            Utils.log('useWebsocket cleanup')\n            wsClient.removeOnChange(incrementalBlockUpdate, 'block')\n            wsClient.removeOnChange(incrementalBoardUpdate, 'board')\n            wsClient.removeOnChange(incrementalBoardMemberUpdate, 'boardMembers')\n            wsClient.removeOnReconnect(dispatchLoadAction)\n        }\n    }, [me?.id, activeBoardId])\n\n    const onConfirmJoin = async () => {\n        if (me) {\n            joinBoard(me, teamId, match.params.boardId, true)\n            setShowJoinBoardDialog(false)\n        }\n    }\n\n    const joinBoard = async (myUser: IUser, boardTeamId: string, boardId: string, allowAdmin: boolean) => {\n        const member = await octoClient.joinBoard(boardId, allowAdmin)\n        if (!member) {\n            if (myUser.permissions?.find((s) => s === 'manage_system' || s === 'manage_team')) {\n                setShowJoinBoardDialog(true)\n                return\n            }\n            UserSettings.setLastBoardID(boardTeamId, null)\n            UserSettings.setLastViewId(boardId, null)\n            dispatch(setGlobalError('board-not-found'))\n            return\n        }\n        const result: any = await dispatch(loadBoardData(boardId))\n        if (result.payload.blocks.length > 0 && myUser.id) {\n            // set board as most recently viewed board\n            UserSettings.setLastBoardID(boardTeamId, boardId)\n        }\n    }\n\n    const loadOrJoinBoard = useCallback(async (myUser: IUser, boardTeamId: string, boardId: string) => {\n        // and fetch its data\n        const result: any = await dispatch(loadBoardData(boardId))\n        if (result.payload.blocks.length === 0 && myUser.id) {\n            joinBoard(myUser, boardTeamId, boardId, false)\n        } else {\n            // set board as most recently viewed board\n            UserSettings.setLastBoardID(boardTeamId, boardId)\n        }\n\n        dispatch(fetchBoardMembers({\n            teamId: boardTeamId,\n            boardId,\n        }))\n    }, [])\n\n    useEffect(() => {\n        dispatch(loadAction(match.params.boardId))\n\n        if (match.params.boardId) {\n            // set the active board\n            dispatch(setCurrentBoard(match.params.boardId))\n\n            if (viewId !== Constants.globalTeamId) {\n                // reset current, even if empty string\n                dispatch(setCurrentView(viewId))\n                if (viewId) {\n                    // don't reset per board if empty string\n                    UserSettings.setLastViewId(match.params.boardId, viewId)\n                }\n            }\n        }\n    }, [teamId, match.params.boardId, viewId, me?.id])\n\n    useEffect(() => {\n        if (match.params.boardId && !props.readonly && me) {\n            loadOrJoinBoard(me, teamId, match.params.boardId)\n        }\n    }, [teamId, match.params.boardId, me?.id])\n\n    const handleUnhideBoard = async (boardID: string) => {\n        if (!me || !category) {\n            return\n        }\n\n        await octoClient.unhideBoard(category.id, boardID)\n    }\n\n    useEffect(() => {\n        if (!teamId || !match.params.boardId) {\n            return\n        }\n\n        if (hiddenBoardIDs.indexOf(match.params.boardId) >= 0) {\n            handleUnhideBoard(match.params.boardId)\n        }\n    }, [me?.id, teamId, match.params.boardId])\n\n    if (props.readonly) {\n        useEffect(() => {\n            if (activeBoardId && activeViewId) {\n                TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.ViewSharedBoard, {board: activeBoardId, view: activeViewId})\n            }\n        }, [activeBoardId, activeViewId])\n    }\n\n    return (\n        <>\n            {showJoinBoardDialog &&\n                <ConfirmationDialog\n                    dialogBox={{\n                        heading: intl.formatMessage({id: 'boardPage.confirm-join-title', defaultMessage: 'Join private board'}),\n                        subText: intl.formatMessage({\n                            id: 'boardPage.confirm-join-text',\n                            defaultMessage: 'You are about to join a private board without explicitly being added by the board admin. Are you sure you wish to join this private board?',\n                        }),\n                        confirmButtonText: intl.formatMessage({id: 'boardPage.confirm-join-button', defaultMessage: 'Join'}),\n                        destructive: true, //board.channelId !== '',\n\n                        onConfirm: onConfirmJoin,\n                        onClose: () => {\n                            setShowJoinBoardDialog(false)\n                            history.goBack()\n                        },\n                    }}\n                />}\n\n            {!showJoinBoardDialog &&\n                <div className='BoardPage'>\n                    {!props.new && <TeamToBoardAndViewRedirect/>}\n                    <BackwardCompatibilityQueryParamsRedirect/>\n                    <SetWindowTitleAndIcon/>\n                    <UndoRedoHotKeys/>\n                    <WebsocketConnection/>\n                    <VersionMessage/>\n\n                    {!mobileWarningClosed &&\n                        <div className='mobileWarning'>\n                            <div>\n                                <FormattedMessage\n                                    id='Error.mobileweb'\n                                    defaultMessage='Mobile web support is currently in early beta. Not all functionality may be present.'\n                                />\n                            </div>\n                            <IconButton\n                                onClick={() => {\n                                    UserSettings.mobileWarningClosed = true\n                                    setMobileWarningClosed(true)\n                                }}\n                                icon={<CloseIcon/>}\n                                title='Close'\n                                className='margin-right'\n                            />\n                        </div>}\n\n                    {props.readonly && activeBoardId === undefined &&\n                        <div className='error'>\n                            {intl.formatMessage({id: 'BoardPage.syncFailed', defaultMessage: 'Board may be deleted or access revoked.'})}\n                        </div>}\n                    {\n\n                        // Don't display Templates page\n                        // if readonly mode and no board defined.\n                        (!props.readonly || activeBoardId !== undefined) &&\n                        <Workspace\n                            readonly={props.readonly || false}\n                        />\n                    }\n                </div>\n            }\n        </>\n    )\n}\n\nexport default BoardPage\n"
  },
  {
    "path": "webapp/src/pages/boardPage/setWindowTitleAndIcon.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {useEffect} from 'react'\n\nimport {Utils} from '../../utils'\nimport {getCurrentBoard} from '../../store/boards'\nimport {getCurrentView} from '../../store/views'\nimport {useAppSelector} from '../../store/hooks'\n\nconst SetWindowTitleAndIcon = (): null => {\n    const board = useAppSelector(getCurrentBoard)\n    const activeView = useAppSelector(getCurrentView)\n\n    useEffect(() => {\n        Utils.setFavicon(board?.icon)\n    }, [board?.icon])\n\n    useEffect(() => {\n        if (board) {\n            let title = `${board.title}`\n            if (activeView?.title) {\n                title += ` | ${activeView.title}`\n            }\n            document.title = title\n        } else {\n            document.title = 'Focalboard'\n        }\n    }, [board?.title, activeView?.title])\n\n    return null\n}\n\nexport default SetWindowTitleAndIcon\n"
  },
  {
    "path": "webapp/src/pages/boardPage/teamToBoardAndViewRedirect.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {useEffect} from 'react'\nimport {generatePath, useHistory, useRouteMatch} from 'react-router-dom'\n\nimport {getBoards, getCurrentBoardId} from '../../store/boards'\nimport {setCurrent as setCurrentView, getCurrentBoardViews} from '../../store/views'\nimport {useAppSelector, useAppDispatch} from '../../store/hooks'\nimport {UserSettings} from '../../userSettings'\nimport {Utils} from '../../utils'\nimport {getSidebarCategories} from '../../store/sidebar'\nimport {Constants} from '../../constants'\n\nconst TeamToBoardAndViewRedirect = (): null => {\n    const boardId = useAppSelector(getCurrentBoardId)\n    const boardViews = useAppSelector(getCurrentBoardViews)\n    const dispatch = useAppDispatch()\n    const history = useHistory()\n    const match = useRouteMatch<{boardId: string, viewId: string, cardId?: string, teamId?: string}>()\n    const categories = useAppSelector(getSidebarCategories)\n    const boards = useAppSelector(getBoards)\n    const teamId = match.params.teamId || UserSettings.lastTeamId || Constants.globalTeamId\n\n    useEffect(() => {\n        let boardID = match.params.boardId\n        if (!match.params.boardId) {\n            // first preference is for last visited board\n            boardID = UserSettings.lastBoardId[teamId]\n\n            // if last visited board is unavailable, use the first board in categories list\n            if (!boardID && categories.length > 0) {\n                let goToBoardID: string | null = null\n\n                for (const category of categories) {\n                    for (const boardMetadata of category.boardMetadata) {\n                        // pick the first category board that exists and is not hidden\n                        if (!boardMetadata.hidden && boards[boardMetadata.boardID]) {\n                            goToBoardID = boardMetadata.boardID\n                            break\n                        }\n                    }\n                }\n\n                // there may even be no boards at all\n                if (goToBoardID) {\n                    boardID = goToBoardID\n                }\n            }\n\n            if (boardID) {\n                const newPath = generatePath(Utils.getBoardPagePath(match.path), {...match.params, boardId: boardID, viewID: undefined})\n                history.replace(newPath)\n\n                // return from here because the loadBoardData() call\n                // will fetch the data to be used below. We'll\n                // use it in the next render cycle.\n                return\n            }\n        }\n\n        let viewID = match.params.viewId\n\n        // when a view isn't open,\n        // but the data is available, try opening a view\n        if ((!viewID || viewID === '0') && boardId && boardId === match.params.boardId && boardViews && boardViews.length > 0) {\n            // most recent view gets the first preference\n            viewID = UserSettings.lastViewId[boardID]\n            if (viewID) {\n                UserSettings.setLastViewId(boardID, viewID)\n                dispatch(setCurrentView(viewID))\n            } else if (boardViews.length > 0) {\n                // if most recent view is unavailable, pick the first view\n                viewID = boardViews[0].id\n                UserSettings.setLastViewId(boardID, viewID)\n                dispatch(setCurrentView(viewID))\n            }\n\n            if (viewID) {\n                const newPath = generatePath(Utils.getBoardPagePath(match.path), {...match.params, viewId: viewID})\n                history.replace(newPath)\n            }\n        }\n    }, [teamId, match.params.boardId, match.params.viewId, categories.length, boardViews.length, boardId])\n\n    return null\n}\n\nexport default TeamToBoardAndViewRedirect\n"
  },
  {
    "path": "webapp/src/pages/boardPage/undoRedoHotKeys.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {useHotkeys} from 'react-hotkeys-hook'\nimport {useIntl} from 'react-intl'\n\nimport {sendFlashMessage} from '../../components/flashMessages'\nimport mutator from '../../mutator'\nimport {Utils} from '../../utils'\n\nconst UndoRedoHotKeys = (): null => {\n    const intl = useIntl()\n\n    useHotkeys('ctrl+z,cmd+z', () => {\n        Utils.log('Undo')\n        if (mutator.canUndo) {\n            const description = mutator.undoDescription\n            mutator.undo().then(() => {\n                if (description) {\n                    sendFlashMessage({\n                        content: intl.formatMessage({id: 'UndoRedoHotKeys.canUndo-with-description', defaultMessage: 'Undo {description}'}, {description}),\n                        severity: 'low',\n                    })\n                } else {\n                    sendFlashMessage({\n                        content: intl.formatMessage({id: 'UndoRedoHotKeys.canUndo', defaultMessage: 'Undo'}),\n                        severity: 'low'})\n                }\n            })\n        } else {\n            sendFlashMessage({\n                content: intl.formatMessage({id: 'UndoRedoHotKeys.cannotUndo', defaultMessage: 'Nothing to Undo'}),\n                severity: 'low',\n            })\n        }\n    })\n\n    useHotkeys('shift+ctrl+z,shift+cmd+z', () => {\n        Utils.log('Redo')\n        if (mutator.canRedo) {\n            const description = mutator.redoDescription\n            mutator.redo().then(() => {\n                if (description) {\n                    sendFlashMessage({\n                        content: intl.formatMessage({id: 'UndoRedoHotKeys.canRedo-with-description', defaultMessage: 'Redo {description}'}, {description}),\n                        severity: 'low',\n                    })\n                } else {\n                    sendFlashMessage({\n                        content: intl.formatMessage({id: 'UndoRedoHotKeys.canRedo', defaultMessage: 'Redo'}),\n                        severity: 'low',\n                    })\n                }\n            })\n        } else {\n            sendFlashMessage({\n                content: intl.formatMessage({id: 'UndoRedoHotKeys.cannotRedo', defaultMessage: 'Nothing to Redo'}),\n                severity: 'low',\n            })\n        }\n    })\n    return null\n}\n\nexport default UndoRedoHotKeys\n"
  },
  {
    "path": "webapp/src/pages/boardPage/websocketConnection.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useEffect, useState} from 'react'\nimport {FormattedMessage} from 'react-intl'\n\nimport wsClient, {WSClient} from '../../wsclient'\nimport {useAppSelector} from '../../store/hooks'\n\nimport {getMe} from '../../store/users'\nimport {IUser} from '../../user'\n\nconst websocketTimeoutForBanner = 5000\n\n// WebsocketConnection component checks the websockets client for\n// state changes and if the connection is closed, shows a banner\n// indicating that there has been a connection error\nconst WebsocketConnection = () => {\n    const [websocketClosed, setWebsocketClosed] = useState(false)\n    const me = useAppSelector<IUser|null>(getMe)\n\n    useEffect(() => {\n        let timeout: ReturnType<typeof setTimeout>\n        const updateWebsocketState = (_: WSClient, newState: 'init'|'open'|'close'): void => {\n            if (timeout) {\n                clearTimeout(timeout)\n            }\n\n            if (newState === 'close') {\n                timeout = setTimeout(() => {\n                    setWebsocketClosed(true)\n                }, websocketTimeoutForBanner)\n            } else {\n                setWebsocketClosed(false)\n            }\n        }\n\n        wsClient.addOnStateChange(updateWebsocketState)\n\n        return () => {\n            if (timeout) {\n                clearTimeout(timeout)\n            }\n            wsClient.removeOnStateChange(updateWebsocketState)\n        }\n    }, [me?.id])\n\n    if (websocketClosed) {\n        return (\n            <div className='WSConnection error'>\n                <a\n                    href='https://www.focalboard.com/fwlink/websocket-connect-error.html'\n                    target='_blank'\n                    rel='noreferrer'\n                >\n                    <FormattedMessage\n                        id='Error.websocket-closed'\n                        defaultMessage='Websocket connection closed, connection interrupted. If this persists, check your server or web proxy configuration.'\n                    />\n                </a>\n            </div>\n        )\n    }\n\n    return null\n}\n\nexport default WebsocketConnection\n"
  },
  {
    "path": "webapp/src/pages/changePasswordPage.scss",
    "content": ".ChangePasswordPage {\n    border: 1px solid #ccc;\n    border-radius: 15px;\n    width: 450px;\n    height: 400px;\n    margin: 150px auto;\n    padding: 40px;\n    display: flex;\n    align-items: center;\n    justify-content: flex-start;\n    flex-direction: column;\n    box-shadow: rgba(var(--center-channel-color-rgb), 0.1) 0 0 0 1px,\n        rgba(var(--center-channel-color-rgb), 0.3) 0 4px 8px;\n\n    form {\n        display: flex;\n        flex-direction: column;\n        align-items: flex-start;\n        justify-content: center;\n    }\n\n    @media screen and (max-width: 430px) {\n        position: fixed;\n        top: 0;\n        left: 0;\n        right: 0;\n        bottom: 0;\n        width: 100%;\n        height: 100%;\n        margin: auto;\n        padding-top: 10px;\n    }\n\n    .title {\n        font-size: 16px;\n        font-weight: 500;\n    }\n\n    .oldPassword,\n    .newPassword {\n        margin-bottom: 10px;\n\n        label {\n            display: inline-block;\n            width: 140px;\n        }\n\n        input {\n            display: inline-block;\n            width: 250px;\n            border: 1px solid #ccc;\n            border-radius: 4px;\n            padding: 7px;\n            min-height: 44px;\n        }\n    }\n\n    form > .Button {\n        margin-top: 10px;\n        margin-bottom: 20px;\n        min-height: 38px;\n        min-width: 250px;\n    }\n\n    .error {\n        color: #900000;\n    }\n\n    .succeeded {\n        background-color: #cfc;\n        padding: 5px;\n    }\n}\n"
  },
  {
    "path": "webapp/src/pages/changePasswordPage.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useState} from 'react'\nimport {Link} from 'react-router-dom'\n\nimport Button from '../widgets/buttons/button'\nimport client from '../octoClient'\nimport './changePasswordPage.scss'\nimport {IUser} from '../user'\nimport {useAppSelector} from '../store/hooks'\nimport {getMe} from '../store/users'\n\nconst ChangePasswordPage = () => {\n    const [oldPassword, setOldPassword] = useState('')\n    const [newPassword, setNewPassword] = useState('')\n    const [errorMessage, setErrorMessage] = useState('')\n    const [succeeded, setSucceeded] = useState(false)\n    const user = useAppSelector<IUser|null>(getMe)\n\n    if (!user) {\n        return (\n            <div className='ChangePasswordPage'>\n                <div className='title'>{'Change Password'}</div>\n                <Link to='/login'>{'Log in first'}</Link>\n            </div>\n        )\n    }\n\n    const handleSubmit = async (userId: string): Promise<void> => {\n        const response = await client.changePassword(userId, oldPassword, newPassword)\n        if (response.code === 200) {\n            setOldPassword('')\n            setNewPassword('')\n            setErrorMessage('')\n            setSucceeded(true)\n        } else {\n            setErrorMessage(`Change password failed: ${response.json?.error}`)\n        }\n    }\n\n    return (\n        <div className='ChangePasswordPage'>\n            <div className='title'>{'Change Password'}</div>\n            <form\n                onSubmit={(e: React.FormEvent) => {\n                    e.preventDefault()\n                    handleSubmit(user.id)\n                }}\n            >\n                <div className='oldPassword'>\n                    <input\n                        id='login-oldpassword'\n                        type='password'\n                        placeholder={'Enter current password'}\n                        value={oldPassword}\n                        onChange={(e) => {\n                            setOldPassword(e.target.value)\n                            setErrorMessage('')\n                        }}\n                    />\n                </div>\n                <div className='newPassword'>\n                    <input\n                        id='login-newpassword'\n                        type='password'\n                        placeholder={'Enter new password'}\n                        value={newPassword}\n                        onChange={(e) => {\n                            setNewPassword(e.target.value)\n                            setErrorMessage('')\n                        }}\n                    />\n                </div>\n                <Button\n                    filled={true}\n                    submit={true}\n                >\n                    {'Change password'}\n                </Button>\n            </form>\n            {errorMessage &&\n                <div className='error'>\n                    {errorMessage}\n                </div>\n            }\n            {succeeded &&\n                <Link\n                    className='succeeded'\n                    to='/'\n                >{'Password changed, click to continue.'}</Link>\n            }\n            {!succeeded &&\n                <Link to='/'>{'Cancel'}</Link>\n            }\n        </div>\n    )\n}\n\nexport default React.memo(ChangePasswordPage)\n"
  },
  {
    "path": "webapp/src/pages/errorPage.scss",
    "content": ".ErrorPage {\n    height: 100%;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n\n    > div {\n        display: flex;\n        align-items: center;\n        flex-direction: column;\n        justify-content: center;\n        text-align: center;\n    }\n\n    .title {\n        font-size: 52px;\n        font-weight: 400;\n    }\n\n    .subtitle {\n        font-size: 20px;\n        margin: 16px 0;\n    }\n\n    svg {\n        margin: 56px 0;\n    }\n}\n"
  },
  {
    "path": "webapp/src/pages/errorPage.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useCallback} from 'react'\nimport {useHistory, useLocation} from 'react-router-dom'\nimport {FormattedMessage} from 'react-intl'\n\nimport ErrorIllustration from '../svg/error-illustration'\n\nimport Button from '../widgets/buttons/button'\nimport './errorPage.scss'\n\nimport {errorDefFromId, ErrorId} from '../errors'\n\nconst ErrorPage = () => {\n    const history = useHistory()\n    const queryParams = new URLSearchParams(useLocation().search)\n    const errid = queryParams.get('id')\n    const errorDef = errorDefFromId(errid as ErrorId)\n\n    const handleButtonClick = useCallback((path: string | ((params: URLSearchParams) => string)) => {\n        let url = '/'\n        if (typeof path === 'function') {\n            url = path(queryParams)\n        } else if (path) {\n            url = path as string\n        }\n        if (url === window.location.origin) {\n            window.location.href = url\n        } else {\n            history.push(url)\n        }\n    }, [history])\n\n    const makeButton = ((path: string | ((params: URLSearchParams) => string), txt: string, fill: boolean) => {\n        return (\n            <Button\n                filled={fill}\n                size='large'\n                onClick={async () => {\n                    handleButtonClick(path)\n                }}\n            >\n                {txt}\n            </Button>\n        )\n    })\n\n    if (errid === ErrorId.NotLoggedIn) {\n        handleButtonClick(errorDef.button1Redirect)\n    }\n\n    return (\n        <div className='ErrorPage'>\n            <div>\n                <div className='title'>\n                    <FormattedMessage\n                        id='error.page.title'\n                        defaultMessage={'Sorry, something went wrong'}\n                    />\n                </div>\n                <div className='subtitle'>\n                    {errorDef.title}\n                </div>\n                <ErrorIllustration/>\n                <br/>\n                {\n                    (errorDef.button1Enabled ? makeButton(errorDef.button1Redirect, errorDef.button1Text, errorDef.button1Fill) : null)\n                }\n                {\n                    (errorDef.button2Enabled ? makeButton(errorDef.button2Redirect, errorDef.button2Text, errorDef.button2Fill) : null)\n                }\n            </div>\n        </div>\n    )\n}\n\nexport default React.memo(ErrorPage)\n"
  },
  {
    "path": "webapp/src/pages/loginPage.scss",
    "content": ".LoginPage {\n    border: 1px solid #ccc;\n    border-radius: 15px;\n    width: 450px;\n    height: 400px;\n    margin: 150px auto;\n    display: flex;\n    align-items: center;\n    justify-content: flex-start;\n    flex-direction: column;\n    box-shadow: rgba(var(--center-channel-color-rgb), 0.1) 0 0 0 1px,\n        rgba(var(--center-channel-color-rgb), 0.3) 0 4px 8px;\n\n    form {\n        display: flex;\n        flex-direction: column;\n        align-items: flex-start;\n        justify-content: center;\n        margin: 50px auto;\n    }\n\n    @media screen and (max-width: 430px) {\n        position: fixed;\n        top: 0;\n        left: 0;\n        right: 0;\n        bottom: 0;\n        width: 100%;\n        height: 100%;\n        margin: auto;\n        padding-top: 10px;\n    }\n\n    .title {\n        font-size: 16px;\n        font-weight: 500;\n    }\n\n    .username,\n    .password {\n        margin-bottom: 10px;\n\n        label {\n            display: inline-block;\n            width: 140px;\n        }\n\n        input {\n            display: inline-block;\n            width: 250px;\n            border: 1px solid #ccc;\n            border-radius: 4px;\n            padding: 7px;\n            min-height: 44px;\n        }\n    }\n\n    form > .Button {\n        margin-top: 10px;\n        margin-bottom: 20px;\n        min-height: 38px;\n        min-width: 250px;\n    }\n\n    .error {\n        color: #900000;\n    }\n}\n"
  },
  {
    "path": "webapp/src/pages/loginPage.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useState} from 'react'\nimport {Link, Redirect, useLocation, useHistory} from 'react-router-dom'\nimport {FormattedMessage} from 'react-intl'\n\nimport {useAppDispatch, useAppSelector} from '../store/hooks'\nimport {fetchMe, getLoggedIn} from '../store/users'\n\nimport Button from '../widgets/buttons/button'\nimport client from '../octoClient'\nimport './loginPage.scss'\n\nconst LoginPage = () => {\n    const [username, setUsername] = useState('')\n    const [password, setPassword] = useState('')\n    const [errorMessage, setErrorMessage] = useState('')\n    const dispatch = useAppDispatch()\n    const loggedIn = useAppSelector<boolean|null>(getLoggedIn)\n    const queryParams = new URLSearchParams(useLocation().search)\n    const history = useHistory()\n\n    const handleLogin = async (): Promise<void> => {\n        const logged = await client.login(username, password)\n        if (logged) {\n            await dispatch(fetchMe())\n            if (queryParams) {\n                history.push(queryParams.get('r') || '/')\n            } else {\n                history.push('/')\n            }\n        } else {\n            setErrorMessage('Login failed')\n        }\n    }\n\n    if (loggedIn) {\n        return <Redirect to={'/'}/>\n    }\n\n    return (\n        <div className='LoginPage'>\n            <form\n                onSubmit={(e: React.FormEvent) => {\n                    e.preventDefault()\n                    handleLogin()\n                }}\n            >\n                <div className='title'>\n                    <FormattedMessage\n                        id='login.log-in-title'\n                        defaultMessage='Log in'\n                    />\n                </div>\n                <div className='username'>\n                    <input\n                        id='login-username'\n                        placeholder={'Enter username'}\n                        value={username}\n                        onChange={(e) => {\n                            setUsername(e.target.value)\n                            setErrorMessage('')\n                        }}\n                    />\n                </div>\n                <div className='password'>\n                    <input\n                        id='login-password'\n                        type='password'\n                        placeholder={'Enter password'}\n                        value={password}\n                        onChange={(e) => {\n                            setPassword(e.target.value)\n                            setErrorMessage('')\n                        }}\n                    />\n                </div>\n                <Button\n                    filled={true}\n                    submit={true}\n                >\n                    <FormattedMessage\n                        id='login.log-in-button'\n                        defaultMessage='Log in'\n                    />\n                </Button>\n            </form>\n            <Link to='/register'>\n                <FormattedMessage\n                    id='login.register-button'\n                    defaultMessage={'or create an account if you don\\'t have one'}\n                />\n            </Link>\n            {errorMessage &&\n                <div className='error'>\n                    {errorMessage}\n                </div>\n            }\n        </div>\n    )\n}\n\nexport default React.memo(LoginPage)\n"
  },
  {
    "path": "webapp/src/pages/registerPage.scss",
    "content": ".RegisterPage {\n    border: 1px solid #ccc;\n    border-radius: 15px;\n    width: 450px;\n    height: 600px;\n    margin: 150px auto;\n    padding: 40px;\n    display: flex;\n    align-items: center;\n    justify-content: flex-start;\n    flex-direction: column;\n    box-shadow: rgba(var(--center-channel-color-rgb), 0.1) 0 0 0 1px,\n        rgba(var(--center-channel-color-rgb), 0.3) 0 4px 8px;\n\n    form {\n        display: flex;\n        flex-direction: column;\n        align-items: flex-start;\n        justify-content: center;\n    }\n\n    @media screen and (max-width: 430px) {\n        position: fixed;\n        top: 0;\n        left: 0;\n        right: 0;\n        bottom: 0;\n        width: 100%;\n        height: 100%;\n        margin: auto;\n        padding-top: 10px;\n    }\n\n    .title {\n        font-size: 16px;\n        font-weight: 500;\n    }\n\n    .email,\n    .username,\n    .password {\n        margin-bottom: 10px;\n\n        label {\n            display: inline-block;\n            width: 140px;\n        }\n\n        input {\n            display: inline-block;\n            width: 250px;\n            border: 1px solid #ccc;\n            border-radius: 4px;\n            min-height: 44px;\n            padding: 7px;\n        }\n    }\n\n    form > .Button {\n        margin-top: 10px;\n        margin-bottom: 20px;\n        min-height: 38px;\n        min-width: 250px;\n    }\n\n    .error {\n        color: #900000;\n    }\n}\n"
  },
  {
    "path": "webapp/src/pages/registerPage.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useState} from 'react'\nimport {useHistory, Link, Redirect} from 'react-router-dom'\nimport {FormattedMessage} from 'react-intl'\n\nimport {useAppDispatch, useAppSelector} from '../store/hooks'\nimport {fetchMe, getLoggedIn} from '../store/users'\n\nimport Button from '../widgets/buttons/button'\nimport client from '../octoClient'\nimport './registerPage.scss'\n\nconst RegisterPage = () => {\n    const [username, setUsername] = useState('')\n    const [password, setPassword] = useState('')\n    const [email, setEmail] = useState('')\n    const [errorMessage, setErrorMessage] = useState('')\n    const history = useHistory()\n    const dispatch = useAppDispatch()\n    const loggedIn = useAppSelector<boolean|null>(getLoggedIn)\n\n    const handleRegister = async (): Promise<void> => {\n        const queryString = new URLSearchParams(window.location.search)\n        const signupToken = queryString.get('t') || ''\n\n        const response = await client.register(email, username, password, signupToken)\n        if (response.code === 200) {\n            const logged = await client.login(username, password)\n            if (logged) {\n                await dispatch(fetchMe())\n                history.push('/')\n            }\n        } else if (response.code === 401) {\n            setErrorMessage('Invalid registration link, please contact your administrator')\n        } else {\n            setErrorMessage(`${response.json?.error}`)\n        }\n    }\n\n    if (loggedIn) {\n        return <Redirect to={'/'}/>\n    }\n\n    return (\n        <div className='RegisterPage'>\n            <form\n                onSubmit={(e: React.FormEvent) => {\n                    e.preventDefault()\n                    handleRegister()\n                }}\n            >\n                <div className='title'>\n                    <FormattedMessage\n                        id='register.signup-title'\n                        defaultMessage='Sign up for your account'\n                    />\n                </div>\n                <div className='email'>\n                    <input\n                        id='login-email'\n                        placeholder={'Enter email'}\n                        value={email}\n                        onChange={(e) => setEmail(e.target.value.trim())}\n                    />\n                </div>\n                <div className='username'>\n                    <input\n                        id='login-username'\n                        placeholder={'Enter username'}\n                        value={username}\n                        onChange={(e) => setUsername(e.target.value.trim())}\n                    />\n                </div>\n                <div className='password'>\n                    <input\n                        id='login-password'\n                        type='password'\n                        placeholder={'Enter password'}\n                        value={password}\n                        onChange={(e) => setPassword(e.target.value)}\n                    />\n                </div>\n                <Button\n                    filled={true}\n                    submit={true}\n                >\n                    {'Register'}\n                </Button>\n            </form>\n            <Link to='/login'>\n                <FormattedMessage\n                    id='register.login-button'\n                    defaultMessage={'or log in if you already have an account'}\n                />\n            </Link>\n            {errorMessage &&\n                <div className='error'>\n                    {errorMessage}\n                </div>\n            }\n        </div>\n    )\n}\n\nexport default React.memo(RegisterPage)\n"
  },
  {
    "path": "webapp/src/pages/welcome/__snapshots__/welcomePage.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`pages/welcome Welcome Page shows Explore Page 1`] = `\n<div>\n  <div\n    class=\"WelcomePage\"\n  >\n    <div\n      class=\"wrapper\"\n    >\n      <h1\n        class=\"text-heading9\"\n      >\n        Welcome To Boards\n      </h1>\n      <div\n        class=\"WelcomePage__subtitle\"\n      >\n        Boards is a project management tool that helps define, organize, track, and manage work across teams using a familiar Kanban board view.\n      </div>\n      <div\n        class=\"WelcomePage__content\"\n      >\n        <img\n          alt=\"Boards Welcome Image\"\n          class=\"WelcomePage__image WelcomePage__image--large\"\n          src=\"http://localhost/test-file-stub\"\n        />\n        <img\n          alt=\"Boards Welcome Image\"\n          class=\"WelcomePage__image WelcomePage__image--small\"\n          src=\"http://localhost/test-file-stub\"\n        />\n        <div\n          class=\"WelcomePage__buttons\"\n        >\n          <button\n            class=\"Button filled size--large\"\n            type=\"button\"\n          >\n            <span>\n              Take a tour\n            </span>\n            <i\n              class=\"CompassIcon icon-chevron-right Icon Icon--right\"\n            />\n          </button>\n          <a\n            class=\"skip\"\n          >\n            No thanks, I'll figure it out myself\n          </a>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`pages/welcome Welcome Page shows Explore Page with subpath 1`] = `\n<div>\n  <div\n    class=\"WelcomePage\"\n  >\n    <div\n      class=\"wrapper\"\n    >\n      <h1\n        class=\"text-heading9\"\n      >\n        Welcome To Boards\n      </h1>\n      <div\n        class=\"WelcomePage__subtitle\"\n      >\n        Boards is a project management tool that helps define, organize, track, and manage work across teams using a familiar Kanban board view.\n      </div>\n      <div\n        class=\"WelcomePage__content\"\n      >\n        <img\n          alt=\"Boards Welcome Image\"\n          class=\"WelcomePage__image WelcomePage__image--large\"\n          src=\"http://localhost/subpath/test-file-stub\"\n        />\n        <img\n          alt=\"Boards Welcome Image\"\n          class=\"WelcomePage__image WelcomePage__image--small\"\n          src=\"http://localhost/subpath/test-file-stub\"\n        />\n        <div\n          class=\"WelcomePage__buttons\"\n        >\n          <button\n            class=\"Button filled size--large\"\n            type=\"button\"\n          >\n            <span>\n              Take a tour\n            </span>\n            <i\n              class=\"CompassIcon icon-chevron-right Icon Icon--right\"\n            />\n          </button>\n          <a\n            class=\"skip\"\n          >\n            No thanks, I'll figure it out myself\n          </a>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/pages/welcome/welcomePage.scss",
    "content": ".WelcomePage {\n    align-items: center;\n    display: flex;\n    flex-direction: column;\n    height: 100%;\n    justify-content: center;\n    padding: 80px 40px;\n    text-align: center;\n\n    @media (max-height: 768px) {\n        justify-content: flex-start;\n        height: auto;\n        padding-top: 40px;\n    }\n\n    .WelcomePage__content {\n        display: flex;\n        flex-direction: column;\n        align-items: center;\n        gap: 12px;\n\n        @media (max-height: 800px) {\n            flex-direction: column-reverse;\n            margin-top: 16px;\n        }\n    }\n\n    .WelcomePage__buttons {\n        display: flex;\n        flex-direction: column;\n        align-items: center;\n        gap: 12px;\n    }\n\n    > div {\n        max-width: 808px;\n        width: 100%;\n        padding: 0;\n\n        @media (min-width: 2000px) {\n            max-width: 1120px;\n        }\n    }\n\n    .Button {\n        display: inline-flex;\n    }\n\n    .wrapper {\n        display: flex;\n        flex-direction: column;\n        align-items: center;\n        gap: 12px;\n    }\n\n    .skip {\n        color: rgba(var(--link-color-rgb), 1);\n        cursor: pointer;\n\n        &:hover {\n            background: none;\n        }\n    }\n}\n\n.WelcomePage__image {\n    max-width: 100%;\n    width: 808px;\n    display: none;\n\n    &--small {\n        margin: 24px auto 32px;\n        display: block;\n    }\n\n    @media (min-width: 2000px) {\n        width: 1120px;\n\n        &--small {\n            display: none;\n        }\n\n        &--large {\n            margin: 48px auto;\n            display: block;\n        }\n    }\n}\n\n.WelcomePage__subtitle {\n    font-size: 16px;\n    line-height: 1.6;\n\n    @media (min-width: 2000px) {\n        font-size: 20px;\n    }\n}\n"
  },
  {
    "path": "webapp/src/pages/welcome/welcomePage.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\n\nimport {render, screen, waitFor} from '@testing-library/react'\n\nimport {createMemoryHistory} from 'history'\n\nimport {Router} from 'react-router-dom'\n\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport userEvent from '@testing-library/user-event'\n\nimport configureStore from 'redux-mock-store'\n\nimport {mocked} from 'jest-mock'\n\nimport thunk from 'redux-thunk'\n\nimport {wrapIntl} from '../../testUtils'\n\nimport mutator from '../../mutator'\n\nimport octoClient from '../../octoClient'\n\nimport {IUser} from '../../user'\n\nimport WelcomePage from './welcomePage'\n\nconst w = (window as any)\nconst oldBaseURL = w.baseURL\n\njest.mock('../../mutator')\nconst mockedMutator = mocked(mutator, true)\n\njest.mock('../../octoClient')\nconst mockedOctoClient = mocked(octoClient, true)\n\nbeforeEach(() => {\n    jest.resetAllMocks()\n    mockedMutator.patchUserConfig.mockImplementation(() => Promise.resolve([\n        {\n            user_id: '',\n            category: 'focalboard',\n            name: 'welcomePageViewed',\n            value: '1',\n        },\n    ]))\n    mockedOctoClient.prepareOnboarding.mockResolvedValue({\n        teamID: 'team_id_1',\n        boardID: 'board_id_1',\n    })\n})\n\nafterEach(() => {\n    w.baseURL = oldBaseURL\n})\n\ndescribe('pages/welcome', () => {\n    let history = createMemoryHistory()\n    const mockStore = configureStore([thunk])\n    const store = mockStore({\n        teams: {\n            current: {id: 'team_id_1'},\n        },\n        users: {\n            me: {\n                props: {},\n            },\n            myConfig: {\n                onboardingTourStep: {value: '0'},\n                tourCategory: {value: 'onboarding'},\n            },\n        },\n    })\n\n    beforeEach(() => {\n        history = createMemoryHistory()\n    })\n\n    test('Welcome Page shows Explore Page', () => {\n        const component = (\n            <ReduxProvider store={store}>\n                {\n                    wrapIntl(\n                        <Router history={history}>\n                            <WelcomePage/>\n                        </Router>,\n                    )\n                }\n            </ReduxProvider>\n        )\n\n        const {container} = render(component)\n        expect(screen.getByText('Take a tour')).toBeDefined()\n        expect(container).toMatchSnapshot()\n    })\n\n    test('Welcome Page shows Explore Page with subpath', () => {\n        w.baseURL = '/subpath'\n        const component = (\n            <ReduxProvider store={store}>\n                {\n                    wrapIntl(\n                        <Router history={history}>\n                            <WelcomePage/>\n                        </Router>,\n                    )\n                }\n            </ReduxProvider>\n        )\n\n        const {container} = render(component)\n        expect(screen.getByText('Take a tour')).toBeDefined()\n        expect(container).toMatchSnapshot()\n    })\n\n    test('Welcome Page shows Explore Page And Then Proceeds after Clicking Explore', async () => {\n        history.replace = jest.fn()\n\n        const component = (\n            <ReduxProvider store={store}>\n                {\n                    wrapIntl(\n                        <Router history={history}>\n                            <WelcomePage/>\n                        </Router>,\n                    )\n                }\n            </ReduxProvider>\n        )\n\n        render(component)\n        const exploreButton = screen.getByText('No thanks, I\\'ll figure it out myself')\n        expect(exploreButton).toBeDefined()\n        userEvent.click(exploreButton)\n        await waitFor(() => {\n            expect(history.replace).toBeCalledWith('/team/team_id_1')\n            expect(mockedMutator.patchUserConfig).toBeCalledTimes(1)\n        })\n    })\n\n    test('Welcome Page does not render explore page the second time we visit it', async () => {\n        history.replace = jest.fn()\n        const customStore = mockStore({\n            teams: {\n                current: {id: 'team_id_1'},\n            },\n            users: {\n                me: {},\n                myConfig: {\n                    welcomePageViewed: {value: '1'},\n                },\n            },\n        })\n\n        const component = (\n            <ReduxProvider store={customStore}>\n                {\n                    wrapIntl(\n                        <Router history={history}>\n                            <WelcomePage/>\n                        </Router>,\n                    )\n                }\n            </ReduxProvider>\n        )\n\n        render(component)\n        await waitFor(() => {\n            expect(history.replace).toBeCalledWith('/team/team_id_1')\n        })\n    })\n\n    test('Welcome Page redirects us when we have a r query parameter with welcomePageViewed set to true', async () => {\n        history.replace = jest.fn()\n        history.location.search = 'r=123'\n\n        const customStore = mockStore({\n            teams: {\n                current: {id: 'team_id_1'},\n            },\n            users: {\n                me: {},\n                myConfig: {\n                    welcomePageViewed: {value: '1'},\n                },\n            },\n        })\n        const component = (\n            <ReduxProvider store={customStore}>\n                {\n                    wrapIntl(\n                        <Router history={history}>\n                            <WelcomePage/>\n                        </Router>,\n                    )\n                }\n            </ReduxProvider>\n        )\n\n        render(component)\n        await waitFor(() => {\n            expect(history.replace).toBeCalledWith('123')\n        })\n    })\n\n    test('Welcome Page redirects us when we have a r query parameter with welcomePageViewed set to null', async () => {\n        history.replace = jest.fn()\n        history.location.search = 'r=123'\n\n        const localStore = mockStore({\n            teams: {\n                current: {id: 'team_id_1'},\n            },\n            users: {\n                me: {\n                    props: {},\n                },\n            },\n        })\n\n        const component = (\n            <ReduxProvider store={localStore}>\n                {\n                    wrapIntl(\n                        <Router history={history}>\n                            <WelcomePage/>\n                        </Router>,\n                    )\n                }\n            </ReduxProvider>\n        )\n        render(component)\n        const exploreButton = screen.getByText('No thanks, I\\'ll figure it out myself')\n        expect(exploreButton).toBeDefined()\n        userEvent.click(exploreButton)\n        await waitFor(() => {\n            expect(history.replace).toBeCalledWith('123')\n            expect(mockedMutator.patchUserConfig).toBeCalledTimes(1)\n        })\n    })\n\n    test('Welcome page starts tour on clicking Take a tour button', async () => {\n        history.replace = jest.fn()\n        const user = {} as unknown as IUser\n        mockedOctoClient.getMe.mockResolvedValue(user)\n\n        const component = (\n            <ReduxProvider store={store}>\n                {\n                    wrapIntl(\n                        <Router history={history}>\n                            <WelcomePage/>\n                        </Router>,\n                    )\n                }\n            </ReduxProvider>\n        )\n        render(component)\n        const exploreButton = screen.getByText('Take a tour')\n        expect(exploreButton).toBeDefined()\n        userEvent.click(exploreButton)\n        await waitFor(() => expect(mockedOctoClient.prepareOnboarding).toBeCalledTimes(1))\n        await waitFor(() => expect(history.replace).toBeCalledWith('/team/team_id_1/board_id_1'))\n    })\n\n    test('Welcome page skips tour on clicking no thanks option', async () => {\n        history.replace = jest.fn()\n        const user = {} as unknown as IUser\n        mockedOctoClient.getMe.mockResolvedValue(user)\n\n        const component = (\n            <ReduxProvider store={store}>\n                {\n                    wrapIntl(\n                        <Router history={history}>\n                            <WelcomePage/>\n                        </Router>,\n                    )\n                }\n            </ReduxProvider>\n        )\n        render(component)\n        const exploreButton = screen.getByText('No thanks, I\\'ll figure it out myself')\n        expect(exploreButton).toBeDefined()\n        userEvent.click(exploreButton)\n        await waitFor(() => expect(history.replace).toBeCalledWith('/team/team_id_1'))\n    })\n})\n"
  },
  {
    "path": "webapp/src/pages/welcome/welcomePage.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\nimport {FormattedMessage} from 'react-intl'\n\nimport {useLocation, useHistory} from 'react-router-dom'\n\nimport BoardWelcomePNG from '../../../static/boards-welcome.png'\nimport BoardWelcomeSmallPNG from '../../../static/boards-welcome-small.png'\n\nimport Button from '../../widgets/buttons/button'\nimport CompassIcon from '../../widgets/icons/compassIcon'\nimport {Utils} from '../../utils'\n\nimport './welcomePage.scss'\nimport mutator from '../../mutator'\nimport {useAppDispatch, useAppSelector} from '../../store/hooks'\nimport {IUser, UserConfigPatch} from '../../user'\nimport {fetchMe, getMe, getMyConfig, patchProps} from '../../store/users'\nimport {getCurrentTeam, Team} from '../../store/teams'\nimport octoClient from '../../octoClient'\nimport {FINISHED, TOUR_ORDER} from '../../components/onboardingTour'\nimport TelemetryClient, {TelemetryActions, TelemetryCategory} from '../../telemetry/telemetryClient'\nimport {UserSettingKey} from '../../userSettings'\n\nconst WelcomePage = () => {\n    const history = useHistory()\n    const queryString = new URLSearchParams(useLocation().search)\n    const me = useAppSelector<IUser|null>(getMe)\n    const myConfig = useAppSelector(getMyConfig)\n    const currentTeam = useAppSelector<Team|null>(getCurrentTeam)\n    const dispatch = useAppDispatch()\n\n    const setWelcomePageViewed = async (userID: string): Promise<any> => {\n        const patch: UserConfigPatch = {}\n        patch.updatedFields = {}\n        patch.updatedFields[UserSettingKey.WelcomePageViewed] = '1'\n\n        const updatedProps = await mutator.patchUserConfig(userID, patch)\n        if (updatedProps) {\n            return dispatch(patchProps(updatedProps))\n        }\n\n        return Promise.resolve()\n    }\n\n    const goForward = () => {\n        if (queryString.get('r')) {\n            history.replace(queryString.get('r')!)\n            return\n        }\n        if (currentTeam) {\n            history.replace(`/team/${currentTeam?.id}`)\n        } else {\n            history.replace('/')\n        }\n    }\n\n    const skipTour = async () => {\n        TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.SkipTour)\n\n        if (me) {\n            await setWelcomePageViewed(me.id)\n            const patch: UserConfigPatch = {\n                updatedFields: {\n                    tourCategory: TOUR_ORDER[TOUR_ORDER.length - 1],\n                    onboardingTourStep: FINISHED.toString(),\n                },\n            }\n\n            const patchedProps = await octoClient.patchUserConfig(me.id, patch)\n            if (patchedProps) {\n                await dispatch(patchProps(patchedProps))\n            }\n        }\n\n        goForward()\n    }\n\n    const startTour = async () => {\n        TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.StartTour)\n\n        if (!me) {\n            return\n        }\n        if (!currentTeam) {\n            return\n        }\n\n        await setWelcomePageViewed(me.id)\n        const onboardingData = await octoClient.prepareOnboarding(currentTeam.id)\n        await dispatch(fetchMe())\n        const newPath = `/team/${onboardingData?.teamID}/${onboardingData?.boardID}`\n        history.replace(newPath)\n    }\n\n    // It's still possible for a guest to end up at this route/page directly, so\n    // let's mark it as viewed, if necessary, and route them forward\n    if (me?.is_guest) {\n        if (!myConfig[UserSettingKey.WelcomePageViewed]) {\n            (async () => {\n                await setWelcomePageViewed(me.id)\n            })()\n        }\n        goForward()\n        return null\n    }\n\n    if (myConfig[UserSettingKey.WelcomePageViewed]) {\n        goForward()\n        return null\n    }\n\n    return (\n        <div className='WelcomePage'>\n            <div className='wrapper'>\n                <h1 className='text-heading9'>\n                    <FormattedMessage\n                        id='WelcomePage.Heading'\n                        defaultMessage='Welcome To Boards'\n                    />\n                </h1>\n                <div className='WelcomePage__subtitle'>\n                    <FormattedMessage\n                        id='WelcomePage.Description'\n                        defaultMessage='Boards is a project management tool that helps define, organize, track, and manage work across teams using a familiar Kanban board view.'\n                    />\n                </div>\n\n                <div className='WelcomePage__content'>\n                    {/* This image will be rendered on large screens over 2000px */}\n                    <img\n                        src={Utils.buildURL(BoardWelcomePNG, true)}\n                        className='WelcomePage__image WelcomePage__image--large'\n                        alt='Boards Welcome Image'\n                    />\n\n                    {/* This image will be rendered on small screens below 2000px */}\n                    <img\n                        src={Utils.buildURL(BoardWelcomeSmallPNG, true)}\n                        className='WelcomePage__image WelcomePage__image--small'\n                        alt='Boards Welcome Image'\n                    />\n\n                    <div className='WelcomePage__buttons'>\n                        {me?.is_guest !== true &&\n                        <Button\n                            onClick={startTour}\n                            filled={true}\n                            size='large'\n                            icon={\n                                <CompassIcon\n                                    icon='chevron-right'\n                                    className='Icon Icon--right'\n                                />}\n                            rightIcon={true}\n                        >\n                            <FormattedMessage\n                                id='WelcomePage.Explore.Button'\n                                defaultMessage='Take a tour'\n                            />\n                        </Button>}\n\n                        {me?.is_guest !== true &&\n                        <a\n                            className='skip'\n                            onClick={skipTour}\n                        >\n                            <FormattedMessage\n                                id='WelcomePage.NoThanks.Text'\n                                defaultMessage=\"No thanks, I'll figure it out myself\"\n                            />\n                        </a>}\n                        {me?.is_guest === true &&\n                        <Button\n                            onClick={skipTour}\n                            filled={true}\n                            size='large'\n                        >\n                            <FormattedMessage\n                                id='WelcomePage.StartUsingIt.Text'\n                                defaultMessage='Start using it'\n                            />\n                        </Button>}\n                    </div>\n                </div>\n            </div>\n        </div>\n    )\n}\n\nexport default React.memo(WelcomePage)\n"
  },
  {
    "path": "webapp/src/properties/baseTextEditor.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React, {useCallback, useState, useRef, useEffect} from 'react'\n\nimport {useIntl} from 'react-intl'\n\nimport mutator from '../mutator'\nimport Editable from '../widgets/editable'\n\nimport {PropertyProps} from './types'\n\nconst BaseTextEditor = (props: PropertyProps & {validator: () => boolean, spellCheck?: boolean}): JSX.Element => {\n    const [value, setValue] = useState(props.card.fields.properties[props.propertyTemplate.id || ''] || '')\n    const onCancel = useCallback(() => setValue(props.propertyValue || ''), [props.propertyValue])\n\n    const saveTextProperty = useCallback(() => {\n        if (value !== (props.card.fields.properties[props.propertyTemplate?.id || ''] || '')) {\n            mutator.changePropertyValue(props.board.id, props.card, props.propertyTemplate?.id || '', value)\n        }\n    }, [props.board.id, props.card, props.propertyTemplate?.id, value])\n\n    const saveTextPropertyRef = useRef<() => void>(saveTextProperty)\n    if (props.readOnly) {\n        saveTextPropertyRef.current = () => null\n    } else {\n        saveTextPropertyRef.current = saveTextProperty\n    }\n\n    const intl = useIntl()\n    const emptyDisplayValue = props.showEmptyPlaceholder ? intl.formatMessage({id: 'PropertyValueElement.empty', defaultMessage: 'Empty'}) : ''\n\n    useEffect(() => {\n        return () => {\n            saveTextPropertyRef.current && saveTextPropertyRef.current()\n        }\n    }, [])\n\n    if (!props.readOnly) {\n        return (\n            <Editable\n                className={props.property.valueClassName(props.readOnly)}\n                placeholderText={emptyDisplayValue}\n                value={value.toString()}\n                autoExpand={true}\n                onChange={setValue}\n                onSave={saveTextProperty}\n                onCancel={onCancel}\n                validator={props.validator}\n                spellCheck={props.spellCheck}\n            />\n        )\n    }\n    return <div className={props.property.valueClassName(true)}>{props.propertyValue}</div>\n}\n\nexport default BaseTextEditor\n"
  },
  {
    "path": "webapp/src/properties/checkbox/checkbox.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport mutator from '../../mutator'\nimport Switch from '../../widgets/switch'\n\nimport {PropertyProps} from '../types'\n\nconst Checkbox = (props: PropertyProps): JSX.Element => {\n    const {card, board, propertyTemplate, propertyValue} = props\n    return (\n        <Switch\n            isOn={Boolean(propertyValue)}\n            onChanged={(newBool: boolean) => {\n                const newValue = newBool ? 'true' : ''\n                mutator.changePropertyValue(board.id, card, propertyTemplate?.id || '', newValue)\n            }}\n            readOnly={props.readOnly}\n        />\n    )\n}\nexport default Checkbox\n"
  },
  {
    "path": "webapp/src/properties/checkbox/property.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {IntlShape} from 'react-intl'\n\nimport {PropertyType, PropertyTypeEnum, FilterValueType} from '../types'\n\nimport Checkbox from './checkbox'\n\nexport default class CheckboxProperty extends PropertyType {\n    Editor = Checkbox\n    name = 'Checkbox'\n    type = 'checkbox' as PropertyTypeEnum\n    displayName = (intl: IntlShape) => intl.formatMessage({id: 'PropertyType.Checkbox', defaultMessage: 'Checkbox'})\n    canFilter = true\n    filterValueType = 'boolean' as FilterValueType\n}\n"
  },
  {
    "path": "webapp/src/properties/createdBy/__snapshots__/createdBy.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`properties/createdBy should match snapshot 1`] = `\n<div>\n  <div\n    class=\"Person octo-propertyvalue octo-propertyvalue--readonly\"\n  >\n    <div\n      class=\"Person-item\"\n    >\n      username_1\n    </div>\n  </div>\n</div>\n`;\n\nexports[`properties/createdBy should match snapshot as guest 1`] = `\n<div>\n  <div\n    class=\"Person octo-propertyvalue octo-propertyvalue--readonly\"\n  >\n    <div\n      class=\"Person-item\"\n    >\n      username_1\n      <div\n        class=\"GuestBadge\"\n      >\n        <div\n          class=\"GuestBadge__box\"\n        >\n          Guest\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/properties/createdBy/createdBy.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport {render} from '@testing-library/react'\nimport configureStore from 'redux-mock-store'\n\nimport {IUser} from '../../user'\nimport {createCard} from '../../blocks/card'\nimport {Board, IPropertyTemplate} from '../../blocks/board'\n\nimport {wrapIntl} from '../../testUtils'\n\nimport CreatedByProperty from './property'\nimport CreatedBy from './createdBy'\n\ndescribe('properties/createdBy', () => {\n    test('should match snapshot', () => {\n        const card = createCard()\n        card.createdBy = 'user-id-1'\n\n        const mockStore = configureStore([])\n        const store = mockStore({\n            users: {\n                boardUsers: {\n                    'user-id-1': {username: 'username_1'} as IUser,\n                },\n            },\n            clientConfig: {\n                value: {\n                    teammateNameDisplay: 'username',\n                },\n            },\n        })\n\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <CreatedBy\n                    property={new CreatedByProperty()}\n                    board={{} as Board}\n                    card={card}\n                    readOnly={false}\n                    propertyTemplate={{} as IPropertyTemplate}\n                    propertyValue={''}\n                    showEmptyPlaceholder={false}\n                />\n            </ReduxProvider>,\n        )\n\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot as guest', () => {\n        const card = createCard()\n        card.createdBy = 'user-id-1'\n\n        const mockStore = configureStore([])\n        const store = mockStore({\n            users: {\n                boardUsers: {\n                    'user-id-1': {username: 'username_1', is_guest: true} as IUser,\n                },\n            },\n            clientConfig: {\n                value: {\n                    teammateNameDisplay: 'username',\n                },\n            },\n        })\n\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <CreatedBy\n                    property={new CreatedByProperty()}\n                    board={{} as Board}\n                    card={card}\n                    readOnly={false}\n                    propertyTemplate={{} as IPropertyTemplate}\n                    propertyValue={''}\n                    showEmptyPlaceholder={false}\n                />\n            </ReduxProvider>,\n        )\n\n        const {container} = render(wrapIntl(component))\n        expect(container).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/properties/createdBy/createdBy.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport Person from '../person/person'\nimport {PropertyProps} from '../types'\n\nconst CreatedBy = (props: PropertyProps): JSX.Element => {\n    return (\n        <Person\n            {...props}\n            propertyValue={props.card.createdBy}\n            readOnly={true} // created by is an immutable property, so will always be readonly\n        />\n    )\n}\n\nexport default CreatedBy\n"
  },
  {
    "path": "webapp/src/properties/createdBy/property.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {IntlShape} from 'react-intl'\n\nimport {PropertyType, PropertyTypeEnum, FilterValueType} from '../types'\n\nimport CreatedBy from './createdBy'\n\nexport default class CreatedByProperty extends PropertyType {\n    Editor = CreatedBy\n    name = 'Created By'\n    type = 'createdBy' as PropertyTypeEnum\n    isReadOnly = true\n    displayName = (intl: IntlShape) => intl.formatMessage({id: 'PropertyType.CreatedBy', defaultMessage: 'Created by'})\n    canFilter = true\n    filterValueType = 'person' as FilterValueType\n    canGroup = true\n}\n"
  },
  {
    "path": "webapp/src/properties/createdTime/createdTime.scss",
    "content": ".CreatedTime {\n    display: flex;\n    align-items: center;\n}\n"
  },
  {
    "path": "webapp/src/properties/createdTime/createdTime.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport {useIntl} from 'react-intl'\n\nimport {Utils} from '../../utils'\nimport {PropertyProps} from '../types'\nimport './createdTime.scss'\n\nconst CreatedTime = (props: PropertyProps): JSX.Element => {\n    const intl = useIntl()\n    return (\n        <div className={`CreatedTime ${props.property.valueClassName(true)}`}>\n            {Utils.displayDateTime(new Date(props.card.createAt), intl)}\n        </div>\n    )\n}\n\nexport default CreatedTime\n"
  },
  {
    "path": "webapp/src/properties/createdTime/property.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {IntlShape} from 'react-intl'\n\nimport {Options} from '../../components/calculations/options'\nimport {IPropertyTemplate} from '../../blocks/board'\nimport {Card} from '../../blocks/card'\nimport {Utils} from '../../utils'\n\nimport {DatePropertyType, PropertyTypeEnum} from '../types'\n\nimport CreatedTime from './createdTime'\n\nexport default class CreatedAtProperty extends DatePropertyType {\n    Editor = CreatedTime\n    name = 'Created At'\n    type = 'createdTime' as PropertyTypeEnum\n    isReadOnly = true\n    displayName = (intl: IntlShape) => intl.formatMessage({id: 'PropertyType.CreatedTime', defaultMessage: 'Created time'})\n    calculationOptions = [Options.none, Options.count, Options.countEmpty,\n        Options.countNotEmpty, Options.percentEmpty, Options.percentNotEmpty,\n        Options.countValue, Options.countUniqueValue, Options.earliest,\n        Options.latest, Options.dateRange]\n    displayValue = (_1: string | string[] | undefined, card: Card, _2: IPropertyTemplate, intl: IntlShape) => Utils.displayDateTime(new Date(card.createAt), intl)\n    getDateFrom = (_: string | string[] | undefined, card: Card) => new Date(card.createAt || 0)\n    getDateTo = (_: string | string[] | undefined, card: Card) => new Date(card.createAt || 0)\n}\n"
  },
  {
    "path": "webapp/src/properties/date/__snapshots__/date.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`properties/dateRange cancel set via text input 1`] = `\n<div>\n  <div\n    class=\"DateRange  octo-propertyvalue\"\n  >\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        June 15 → June 20\n      </span>\n    </button>\n  </div>\n</div>\n`;\n\nexports[`properties/dateRange handle clear 1`] = `\n<div>\n  <div\n    class=\"DateRange  octo-propertyvalue\"\n  >\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        June 15\n      </span>\n    </button>\n  </div>\n</div>\n`;\n\nexports[`properties/dateRange returns component with new date after prop change 1`] = `\n<div>\n  <div\n    class=\"DateRange  octo-propertyvalue\"\n  >\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        June 15\n      </span>\n    </button>\n  </div>\n</div>\n`;\n\nexports[`properties/dateRange returns default correctly 1`] = `\n<div>\n  <div\n    class=\"DateRange empty octo-propertyvalue\"\n  >\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span />\n    </button>\n  </div>\n</div>\n`;\n\nexports[`properties/dateRange returns local correctly - es local 1`] = `\n<div>\n  <div\n    class=\"DateRange  octo-propertyvalue\"\n  >\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        15 de junio\n      </span>\n    </button>\n  </div>\n</div>\n`;\n\nexports[`properties/dateRange set via text input 1`] = `\n<div>\n  <div\n    class=\"DateRange  octo-propertyvalue\"\n  >\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        June 15 → June 20\n      </span>\n    </button>\n  </div>\n</div>\n`;\n\nexports[`properties/dateRange set via text input, es locale 1`] = `\n<div>\n  <div\n    class=\"DateRange  octo-propertyvalue\"\n  >\n    <button\n      class=\"Button\"\n      type=\"button\"\n    >\n      <span>\n        15 de junio → 20 de junio\n      </span>\n    </button>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/properties/date/date.scss",
    "content": ".DateRange {\n    .inputContainer {\n        display: flex;\n\n        .Editable {\n            width: 50% !important;\n        }\n    }\n\n    input {\n        cursor: text;\n        overflow: hidden;\n        text-overflow: ellipsis;\n        min-height: 24px;\n        width: 100%;\n        margin: 2px;\n\n        &.active {\n            min-width: 100px;\n        }\n\n        &::placeholder {\n            color: rgba(var(--body-color), 0.4);\n            opacity: 1;\n        }\n\n        &.error {\n            border: 1px solid var(--error-color);\n            border-radius: var(--default-rad);\n        }\n    }\n\n    .Modal {\n        position: absolute;\n        top: 0;\n        left: 0;\n        margin-bottom: 100px;\n    }\n\n    &.empty .Button {\n        min-height: 24px;\n        color: rgba(var(--center-channel-color-rgb), 0.4);\n        padding: 0 3px;\n    }\n\n    .Button {\n        width: calc(100% - 16px);\n        height: 100%;\n        justify-content: left;\n        padding: 0;\n\n        &.--empty {\n            opacity: 0;\n        }\n\n        &:hover {\n            background-color: transparent;\n        }\n    }\n\n    .menu-option {\n        display: flex;\n        flex-direction: row;\n        align-items: center;\n\n        font-size: 14px;\n        line-height: 24px;\n        font-weight: 400;\n        height: 32px;\n        padding: 4px 0;\n        cursor: pointer;\n\n        &:hover {\n            background: rgba(var(--button-bg-rgb), 0.08);\n        }\n\n        >* {\n            margin-left: 6px;\n        }\n\n        >*:first-child {\n            margin-left: 0;\n        }\n\n        >.menu-name {\n            display: flex;\n            flex-grow: 1;\n            white-space: nowrap;\n        }\n\n        >.SubmenuTriangleIcon {\n            fill: rgba(var(--body-color), 0.7);\n        }\n\n        >.Icon {\n            opacity: 0.56;\n            width: 16px;\n            height: 16px;\n            line-height: 16px;\n        }\n\n        >.IconButton .Icon {\n            margin-right: 0;\n        }\n    }\n\n    .DayPicker {\n        font-size: inherit;\n    }\n\n    .DayPickerInput-Overlay {\n        background-color: rgba(var(--center-channel-bg-rgb));\n        box-shadow: 0 0 0 1px hsla(0, 0%, 0%, 0.1), 0 4px 11px hsla(0, 0%, 0%, 0.1);\n        line-height: 100%;\n    }\n\n    .DayPicker-wrapper {\n        padding: 0;\n    }\n\n    .DayPicker-Month {\n        margin: 0;\n        margin-top: 1em;\n        display: flex;\n        flex-direction: column;\n    }\n\n    .DayPicker-Body {\n        display: flex;\n        flex-direction: column;\n    }\n\n    .inputContainer {\n        max-width: 252px;\n    }\n\n    .DayPicker-Weekdays {\n        margin: 0;\n    }\n\n    .DayPicker-WeekdaysRow {\n        display: flex;\n        flex-direction: row;\n    }\n\n    .DayPicker-Weekday {\n        width: 36px;\n        height: 36px;\n    }\n\n    .DayPicker-NavButton {\n        right: 0;\n    }\n\n    .DayPicker-Week {\n        display: flex;\n        flex-direction: row;\n    }\n\n    .DayPicker-Day {\n        display: flex;\n        align-items: center;\n        justify-content: center;\n        padding: 0;\n        width: 36px;\n        height: 36px;\n        border-radius: 50px;\n\n        &::before {\n            content: unset;\n        }\n\n        &:not(.DayPicker-Day--selected) {\n            &:hover {\n                background: rgba(var(--button-bg-rgb), 0.08) !important;\n                color: rgba(var(--button-bg-rgb), 1) !important;\n            }\n        }\n    }\n\n    .DayPicker-Day--today {\n        background: transparent;\n        color: #c74655;\n    }\n\n    .DayPicker-Day--start {\n        border-top-left-radius: 50% !important;\n        border-bottom-left-radius: 50% !important;\n        border-top-right-radius: unset;\n        border-bottom-right-radius: unset;\n    }\n\n    .DayPicker-Day--end {\n        border-top-left-radius: unset;\n        border-bottom-left-radius: unset;\n        border-top-right-radius: 50% !important;\n        border-bottom-right-radius: 50% !important;\n    }\n\n    .DayPicker-Day--selected:not(.DayPicker-Day--disabled):not(.DayPicker-Day--outside) {\n        border-radius: unset;\n        background-color: rgb(var(--button-bg-rgb));\n        color: rgba(var(--button-color-rgb), 1);\n\n        &:hover {\n            background-color: rgb(var(--button-bg-rgb)) !important;\n        }\n    }\n\n    .DayPicker-Day--selected:not(.DayPicker-Day--start):not(.DayPicker-Day--end):not(.DayPicker-Day--outside) {\n        color: rgb(var(--button-bg-rgb));\n        background-color: rgba(var(--button-bg-rgb), 0.08);\n\n        &:hover {\n            background-color: rgba(var(--button-bg-rgb), 0.08) !important;\n        }\n    }\n\n    .DayPicker:not(.DayPicker--interactionDisabled) .DayPicker-Day:not(.DayPicker-Day--disabled):not(.DayPicker-Day--selected):not(.DayPicker-Day--outside):hover {\n        background-color: rgba(var(--body-color), 0.2);\n    }\n}\n"
  },
  {
    "path": "webapp/src/properties/date/date.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {render} from '@testing-library/react'\nimport userEvent from '@testing-library/user-event'\nimport {IntlProvider} from 'react-intl'\nimport {mocked} from 'jest-mock'\n\nimport '@testing-library/jest-dom'\n\nimport {wrapIntl} from '../../testUtils'\nimport {IPropertyTemplate, createBoard} from '../../blocks/board'\nimport {createCard} from '../../blocks/card'\nimport mutator from '../../mutator'\n\nimport DateProperty from './property'\nimport DateProp from './date'\n\njest.mock('../../mutator')\nconst mockedMutator = mocked(mutator, true)\n\n// create Dates for specific days for this year.\nconst June15 = new Date(Date.UTC(new Date().getFullYear(), 5, 15, 12))\nconst June15Local = new Date(new Date().getFullYear(), 5, 15, 12)\nconst June20 = new Date(Date.UTC(new Date().getFullYear(), 5, 20, 12))\n\ndescribe('properties/dateRange', () => {\n    const card = createCard()\n    const board = createBoard()\n    const propertyTemplate: IPropertyTemplate = {\n        id: 'test',\n        name: 'test',\n        type: 'date',\n        options: [],\n    }\n\n    beforeEach(() => {\n        // Quick fix to disregard console error when unmounting a component\n        console.error = jest.fn()\n        document.execCommand = jest.fn()\n        jest.resetAllMocks()\n    })\n\n    test('returns default correctly', () => {\n        const component = wrapIntl(\n            <DateProp\n                property={new DateProperty()}\n                propertyValue=''\n                showEmptyPlaceholder={false}\n                readOnly={false}\n                board={{...board}}\n                card={{...card}}\n                propertyTemplate={propertyTemplate}\n            />,\n        )\n\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('returns local correctly - es local', () => {\n        const component = (\n            <IntlProvider locale='es'>\n                <DateProp\n                    property={new DateProperty()}\n                    propertyValue={June15Local.getTime().toString()}\n                    showEmptyPlaceholder={false}\n                    readOnly={false}\n                    board={{...board}}\n                    card={{...card}}\n                    propertyTemplate={propertyTemplate}\n                />\n            </IntlProvider>\n        )\n\n        const {container, getByText} = render(component)\n        const input = getByText('15 de junio')\n        expect(input).not.toBeNull()\n        expect(container).toMatchSnapshot()\n    })\n\n    test('handles calendar click event', () => {\n        const component = wrapIntl(\n            <DateProp\n                property={new DateProperty()}\n                propertyValue=''\n                showEmptyPlaceholder={true}\n                readOnly={false}\n                board={{...board}}\n                card={{...card}}\n                propertyTemplate={propertyTemplate}\n            />,\n        )\n\n        const date = new Date()\n        const fifteenth = Date.UTC(date.getFullYear(), date.getMonth(), 15, 12)\n\n        const {getByText, getByTitle} = render(component)\n        const dayDisplay = getByText('Empty')\n        userEvent.click(dayDisplay)\n\n        const day = getByText('15')\n        const modal = getByTitle('Close').children[0]\n        userEvent.click(day)\n        userEvent.click(modal)\n\n        expect(mockedMutator.changePropertyValue).toHaveBeenCalledWith(board.id, card, propertyTemplate.id, JSON.stringify({from: fifteenth}))\n    })\n\n    test('handles setting range', () => {\n        const component = wrapIntl(\n            <DateProp\n                property={new DateProperty()}\n                propertyValue={''}\n                showEmptyPlaceholder={true}\n                readOnly={false}\n                board={{...board}}\n                card={{...card}}\n                propertyTemplate={propertyTemplate}\n            />,\n        )\n\n        // open modal\n        const {getByText, getByTitle} = render(component)\n        const dayDisplay = getByText('Empty')\n        userEvent.click(dayDisplay)\n\n        // select start date\n        const date = new Date()\n        const fifteenth = Date.UTC(date.getFullYear(), date.getMonth(), 15, 12)\n        const start = getByText('15')\n        userEvent.click(start)\n\n        // create range\n        const endDate = getByText('End date')\n        userEvent.click(endDate)\n\n        const twentieth = Date.UTC(date.getFullYear(), date.getMonth(), 20, 12)\n\n        const end = getByText('20')\n        const modal = getByTitle('Close').children[0]\n        userEvent.click(end)\n        userEvent.click(modal)\n\n        expect(mockedMutator.changePropertyValue).toHaveBeenCalledWith(board.id, card, propertyTemplate.id, JSON.stringify({from: fifteenth, to: twentieth}))\n    })\n\n    test('handle clear', () => {\n        const component = wrapIntl(\n            <DateProp\n                property={new DateProperty()}\n                propertyValue={June15Local.getTime().toString()}\n                showEmptyPlaceholder={false}\n                readOnly={false}\n                board={{...board}}\n                card={{...card}}\n                propertyTemplate={propertyTemplate}\n            />,\n        )\n\n        const {container, getByText, getByTitle} = render(component)\n        expect(container).toMatchSnapshot()\n\n        // open modal\n        const dayDisplay = getByText('June 15')\n        userEvent.click(dayDisplay)\n\n        const clear = getByText('Clear')\n        const modal = getByTitle('Close').children[0]\n        userEvent.click(clear)\n        userEvent.click(modal)\n\n        expect(mockedMutator.changePropertyValue).toHaveBeenCalledWith(board.id, card, propertyTemplate.id, '')\n    })\n\n    test('set via text input', () => {\n        const component = wrapIntl(\n            <DateProp\n                property={new DateProperty()}\n                propertyValue={'{\"from\": ' + June15.getTime().toString() + ',\"to\": ' + June20.getTime().toString() + '}'}\n                showEmptyPlaceholder={false}\n                readOnly={false}\n                board={{...board}}\n                card={{...card}}\n                propertyTemplate={propertyTemplate}\n            />,\n        )\n\n        const {container, getByRole, getByTitle, getByDisplayValue} = render(component)\n        expect(container).toMatchSnapshot()\n\n        // open modal\n        const dayDisplay = getByRole('button', {name: 'June 15 → June 20'})\n\n        userEvent.click(dayDisplay)\n\n        const fromInput = getByDisplayValue('June 15')\n        const toInput = getByDisplayValue('June 20')\n\n        userEvent.type(fromInput, '{selectall}{delay}07/15/2021{enter}')\n        userEvent.type(toInput, '{selectall}{delay}07/20/2021{enter}')\n\n        const July15 = new Date(Date.UTC(2021, 6, 15, 12))\n        const July20 = new Date(Date.UTC(2021, 6, 20, 12))\n        const modal = getByTitle('Close').children[0]\n\n        userEvent.click(modal)\n\n        // {from: '2021-07-15', to: '2021-07-20'}\n        const retVal = {from: July15.getTime(), to: July20.getTime()}\n        expect(mockedMutator.changePropertyValue).toHaveBeenCalledWith(board.id, card, propertyTemplate.id, JSON.stringify(retVal))\n    })\n\n    test('set via text input, es locale', () => {\n        const component = (\n            <IntlProvider locale='es'>\n                <DateProp\n                    property={new DateProperty()}\n                    propertyValue={'{\"from\": ' + June15.getTime().toString() + ',\"to\": ' + June20.getTime().toString() + '}'}\n                    showEmptyPlaceholder={false}\n                    readOnly={false}\n                    board={{...board}}\n                    card={{...card}}\n                    propertyTemplate={propertyTemplate}\n                />\n            </IntlProvider>\n        )\n        const {container, getByRole, getByTitle, getByDisplayValue} = render(component)\n        expect(container).toMatchSnapshot()\n\n        // open modal\n        const dayDisplay = getByRole('button', {name: '15 de junio → 20 de junio'})\n\n        userEvent.click(dayDisplay)\n\n        const fromInput = getByDisplayValue('15 de junio')\n        const toInput = getByDisplayValue('20 de junio')\n\n        userEvent.type(fromInput, '{selectall}15/07/2021{enter}')\n        userEvent.type(toInput, '{selectall}20/07/2021{enter}')\n\n        const July15 = new Date(Date.UTC(2021, 6, 15, 12))\n        const July20 = new Date(Date.UTC(2021, 6, 20, 12))\n        const modal = getByTitle('Close').children[0]\n\n        userEvent.click(modal)\n\n        // {from: '2021-07-15', to: '2021-07-20'}\n        const retVal = {from: July15.getTime(), to: July20.getTime()}\n        expect(mockedMutator.changePropertyValue).toHaveBeenCalledWith(board.id, card, propertyTemplate.id, JSON.stringify(retVal))\n    })\n\n    test('cancel set via text input', () => {\n        const component = wrapIntl(\n            <DateProp\n                property={new DateProperty()}\n                propertyValue={'{\"from\": ' + June15.getTime().toString() + ',\"to\": ' + June20.getTime().toString() + '}'}\n                showEmptyPlaceholder={false}\n                readOnly={false}\n                board={{...board}}\n                card={{...card}}\n                propertyTemplate={propertyTemplate}\n            />,\n        )\n\n        const {container, getByRole, getByTitle, getByDisplayValue} = render(component)\n        expect(container).toMatchSnapshot()\n\n        // open modal\n        const dayDisplay = getByRole('button', {name: 'June 15 → June 20'})\n        userEvent.click(dayDisplay)\n\n        const fromInput = getByDisplayValue('June 15')\n        const toInput = getByDisplayValue('June 20')\n        userEvent.type(fromInput, '{selectall}07/15/2021{delay}{esc}')\n        userEvent.type(toInput, '{selectall}07/20/2021{delay}{esc}')\n\n        const modal = getByTitle('Close').children[0]\n        userEvent.click(modal)\n\n        // const retVal = {from: '2021-06-15', to: '2021-06-20'}\n        const retVal = {from: June15.getTime(), to: June20.getTime()}\n        expect(mockedMutator.changePropertyValue).toHaveBeenCalledWith(board.id, card, propertyTemplate.id, JSON.stringify(retVal))\n    })\n\n    test('handles `Today` button click event', () => {\n        const component = wrapIntl(\n            <DateProp\n                property={new DateProperty()}\n                propertyValue={''}\n                showEmptyPlaceholder={true}\n                readOnly={false}\n                board={{...board}}\n                card={{...card}}\n                propertyTemplate={propertyTemplate}\n            />,\n        )\n\n        // To see if 'Today' button correctly selects today's date,\n        // we can check it against `new Date()`.\n        // About `Date()`\n        // > \"When called as a function, returns a string representation of the current date and time\"\n        const date = new Date()\n        const today = Date.UTC(date.getFullYear(), date.getMonth(), date.getDate(), 12)\n\n        const {getByText, getByTitle} = render(component)\n        const dayDisplay = getByText('Empty')\n        userEvent.click(dayDisplay)\n\n        const day = getByText('Today')\n        const modal = getByTitle('Close').children[0]\n        userEvent.click(day)\n        userEvent.click(modal)\n\n        expect(mockedMutator.changePropertyValue).toHaveBeenCalledWith(board.id, card, propertyTemplate.id, JSON.stringify({from: today}))\n    })\n\n    test('returns component with new date after prop change', () => {\n        const component = wrapIntl(\n            <DateProp\n                property={new DateProperty()}\n                propertyValue=''\n                showEmptyPlaceholder={false}\n                readOnly={false}\n                board={{...board}}\n                card={{...card}}\n                propertyTemplate={propertyTemplate}\n            />,\n        )\n\n        const {container, rerender} = render(component)\n\n        rerender(\n            wrapIntl(\n                <DateProp\n                    property={new DateProperty()}\n                    propertyValue={'{\"from\": ' + June15.getTime().toString() + '}'}\n                    showEmptyPlaceholder={false}\n                    readOnly={false}\n                    board={{...board}}\n                    card={{...card}}\n                    propertyTemplate={propertyTemplate}\n                />,\n            ),\n        )\n\n        expect(container).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/properties/date/date.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useMemo, useState, useCallback, useEffect} from 'react'\nimport {useIntl} from 'react-intl'\nimport {DateUtils} from 'react-day-picker'\nimport MomentLocaleUtils from 'react-day-picker/moment'\nimport DayPicker from 'react-day-picker/DayPicker'\n\nimport moment from 'moment'\n\nimport mutator from '../../mutator'\n\nimport Editable from '../../widgets/editable'\nimport SwitchOption from '../../widgets/menu/switchOption'\nimport Button from '../../widgets/buttons/button'\n\nimport Modal from '../../components/modal'\nimport ModalWrapper from '../../components/modalWrapper'\nimport {Utils} from '../../utils'\n\nimport 'react-day-picker/lib/style.css'\nimport './date.scss'\n\nimport {PropertyProps} from '../types'\n\nexport type DateProperty = {\n    from?: number\n    to?: number\n    includeTime?: boolean\n    timeZone?: string\n}\n\nexport function createDatePropertyFromString(initialValue: string): DateProperty {\n    let dateProperty: DateProperty = {}\n    if (initialValue) {\n        const singleDate = new Date(Number(initialValue))\n        if (singleDate && DateUtils.isDate(singleDate)) {\n            dateProperty.from = singleDate.getTime()\n        } else {\n            try {\n                dateProperty = JSON.parse(initialValue)\n            } catch {\n                //Don't do anything, return empty dateProperty\n            }\n        }\n    }\n    return dateProperty\n}\n\nfunction datePropertyToString(dateProperty: DateProperty): string {\n    return dateProperty.from || dateProperty.to ? JSON.stringify(dateProperty) : ''\n}\n\nconst loadedLocales: Record<string, moment.Locale> = {}\n\nfunction DateRange(props: PropertyProps): JSX.Element {\n    const {propertyValue, propertyTemplate, showEmptyPlaceholder, readOnly, board, card} = props\n    const [value, setValue] = useState(propertyValue)\n    const intl = useIntl()\n\n    useEffect(() => {\n        if (value !== propertyValue) {\n            setValue(propertyValue)\n        }\n    }, [propertyValue, setValue])\n\n    const onChange = useCallback((newValue) => {\n        if (value !== newValue) {\n            setValue(newValue)\n        }\n    }, [value, board.id, card, propertyTemplate.id])\n\n    const getDisplayDate = (date: Date | null | undefined) => {\n        let displayDate = ''\n        if (date) {\n            displayDate = Utils.displayDate(date, intl)\n        }\n        return displayDate\n    }\n\n    const timeZoneOffset = (date: number): number => {\n        return new Date(date).getTimezoneOffset() * 60 * 1000\n    }\n\n    const dateProperty = useMemo(() => createDatePropertyFromString(value as string), [value])\n    const [showDialog, setShowDialog] = useState(false)\n\n    // Keep dateProperty as UTC,\n    // dateFrom / dateTo will need converted to local time, to ensure date stays consistent\n    // dateFrom / dateTo will be used for input and calendar dates\n    const dateFrom = dateProperty.from ? new Date(dateProperty.from + (dateProperty.includeTime ? 0 : timeZoneOffset(dateProperty.from))) : undefined\n    const dateTo = dateProperty.to ? new Date(dateProperty.to + (dateProperty.includeTime ? 0 : timeZoneOffset(dateProperty.to))) : undefined\n    const [fromInput, setFromInput] = useState<string>(getDisplayDate(dateFrom))\n    const [toInput, setToInput] = useState<string>(getDisplayDate(dateTo))\n\n    const isRange = dateTo !== undefined\n\n    const locale = intl.locale.toLowerCase()\n    if (locale && locale !== 'en' && !loadedLocales[locale]) {\n        // eslint-disable-next-line global-require\n        loadedLocales[locale] = require(`moment/locale/${locale}`)\n    }\n\n    const handleDayClick = (day: Date) => {\n        const range: DateProperty = {}\n        day.setHours(12)\n        if (isRange) {\n            const newRange = DateUtils.addDayToRange(day, {from: dateFrom, to: dateTo})\n            range.from = newRange.from?.getTime()\n            range.to = newRange.to?.getTime()\n        } else {\n            range.from = day.getTime()\n            range.to = undefined\n        }\n        saveRangeValue(range)\n    }\n\n    const onRangeClick = () => {\n        let range: DateProperty = {\n            from: dateFrom?.getTime(),\n            to: dateFrom?.getTime(),\n        }\n        if (isRange) {\n            range = ({\n                from: dateFrom?.getTime(),\n                to: undefined,\n            })\n        }\n        saveRangeValue(range)\n    }\n\n    const onClear = () => {\n        saveRangeValue({})\n    }\n\n    const saveRangeValue = (range: DateProperty) => {\n        const rangeUTC = {...range}\n        if (rangeUTC.from) {\n            rangeUTC.from -= dateProperty.includeTime ? 0 : timeZoneOffset(rangeUTC.from)\n        }\n        if (rangeUTC.to) {\n            rangeUTC.to -= dateProperty.includeTime ? 0 : timeZoneOffset(rangeUTC.to)\n        }\n\n        onChange(datePropertyToString(rangeUTC))\n        setFromInput(getDisplayDate(range.from ? new Date(range.from) : undefined))\n        setToInput(getDisplayDate(range.to ? new Date(range.to) : undefined))\n    }\n\n    let displayValue = ''\n    if (dateFrom) {\n        displayValue = getDisplayDate(dateFrom)\n    }\n    if (dateTo) {\n        displayValue += ' → ' + getDisplayDate(dateTo)\n    }\n\n    const onClose = () => {\n        const newDate = datePropertyToString(dateProperty)\n        onChange(newDate)\n        mutator.changePropertyValue(board.id, card, propertyTemplate.id, newDate)\n        setShowDialog(false)\n    }\n\n    let buttonText = displayValue\n    if (!buttonText && showEmptyPlaceholder) {\n        buttonText = intl.formatMessage({id: 'DateRange.empty', defaultMessage: 'Empty'})\n    }\n\n    const className = props.property.valueClassName(readOnly)\n    if (readOnly) {\n        return <div className={className}>{displayValue}</div>\n    }\n\n    return (\n        <div className={`DateRange ${displayValue ? '' : 'empty'} ` + className}>\n            <Button\n                onClick={() => setShowDialog(true)}\n            >\n                {buttonText}\n            </Button>\n\n            {showDialog &&\n            <ModalWrapper>\n                <Modal\n                    onClose={() => onClose()}\n                >\n                    <div\n                        className={className + '-overlayWrapper'}\n                    >\n                        <div className={className + '-overlay'}>\n                            <div className={'inputContainer'}>\n                                <Editable\n                                    value={fromInput}\n                                    placeholderText={moment.localeData(locale).longDateFormat('L')}\n                                    onFocus={() => {\n                                        if (dateFrom) {\n                                            return setFromInput(Utils.inputDate(dateFrom, intl))\n                                        }\n                                        return undefined\n                                    }}\n                                    onChange={setFromInput}\n                                    onSave={() => {\n                                        const newDate = MomentLocaleUtils.parseDate(fromInput, 'L', intl.locale)\n                                        if (newDate && DateUtils.isDate(newDate)) {\n                                            newDate.setHours(12)\n                                            const range: DateProperty = {\n                                                from: newDate.getTime(),\n                                                to: dateTo?.getTime(),\n                                            }\n                                            saveRangeValue(range)\n                                        } else {\n                                            setFromInput(getDisplayDate(dateFrom))\n                                        }\n                                    }}\n                                    onCancel={() => {\n                                        setFromInput(getDisplayDate(dateFrom))\n                                    }}\n                                />\n                                {dateTo &&\n                                    <Editable\n                                        value={toInput}\n                                        placeholderText={moment.localeData(locale).longDateFormat('L')}\n                                        onFocus={() => {\n                                            if (dateTo) {\n                                                return setToInput(Utils.inputDate(dateTo, intl))\n                                            }\n                                            return undefined\n                                        }}\n                                        onChange={setToInput}\n                                        onSave={() => {\n                                            const newDate = MomentLocaleUtils.parseDate(toInput, 'L', intl.locale)\n                                            if (newDate && DateUtils.isDate(newDate)) {\n                                                newDate.setHours(12)\n                                                const range: DateProperty = {\n                                                    from: dateFrom?.getTime(),\n                                                    to: newDate.getTime(),\n                                                }\n                                                saveRangeValue(range)\n                                            } else {\n                                                setToInput(getDisplayDate(dateTo))\n                                            }\n                                        }}\n                                        onCancel={() => {\n                                            setToInput(getDisplayDate(dateTo))\n                                        }}\n                                    />\n                                }\n                            </div>\n                            <DayPicker\n                                onDayClick={handleDayClick}\n                                initialMonth={dateFrom || new Date()}\n                                showOutsideDays={false}\n                                locale={locale}\n                                localeUtils={MomentLocaleUtils}\n                                todayButton={intl.formatMessage({id: 'DateRange.today', defaultMessage: 'Today'})}\n                                onTodayButtonClick={handleDayClick}\n                                month={dateFrom}\n                                selectedDays={[dateFrom, dateTo ? {from: dateFrom, to: dateTo} : {from: dateFrom, to: dateFrom}]}\n                                modifiers={dateTo ? {start: dateFrom, end: dateTo} : {start: dateFrom, end: dateFrom}}\n                            />\n                            <hr/>\n                            <SwitchOption\n                                key={'EndDateOn'}\n                                id={'EndDateOn'}\n                                name={intl.formatMessage({id: 'DateRange.endDate', defaultMessage: 'End date'})}\n                                isOn={isRange}\n                                onClick={onRangeClick}\n                            />\n                            <hr/>\n                            <div\n                                className='MenuOption menu-option'\n                            >\n                                <Button\n                                    onClick={onClear}\n                                >\n                                    {intl.formatMessage({id: 'DateRange.clear', defaultMessage: 'Clear'})}\n                                </Button>\n                            </div>\n                        </div>\n                    </div>\n                </Modal>\n            </ModalWrapper>\n            }\n        </div>\n    )\n}\n\nexport default DateRange\n"
  },
  {
    "path": "webapp/src/properties/date/property.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {IntlShape} from 'react-intl'\nimport {DateUtils} from 'react-day-picker'\n\nimport {Options} from '../../components/calculations/options'\nimport {IPropertyTemplate} from '../../blocks/board'\nimport {Card} from '../../blocks/card'\nimport {Utils} from '../../utils'\n\nimport {PropertyTypeEnum, DatePropertyType} from '../types'\n\nimport DateComponent, {createDatePropertyFromString} from './date'\n\nconst timeZoneOffset = (date: number): number => {\n    return new Date(date).getTimezoneOffset() * 60 * 1000\n}\n\nexport default class DateProperty extends DatePropertyType {\n    Editor = DateComponent\n    name = 'Date'\n    type = 'date' as PropertyTypeEnum\n    displayName = (intl: IntlShape) => intl.formatMessage({id: 'PropertyType.Date', defaultMessage: 'Date'})\n    calculationOptions = [Options.none, Options.count, Options.countEmpty,\n        Options.countNotEmpty, Options.percentEmpty, Options.percentNotEmpty,\n        Options.countValue, Options.countUniqueValue]\n    displayValue = (propertyValue: string | string[] | undefined, _1: Card, _2: IPropertyTemplate, intl: IntlShape) => {\n        let displayValue = ''\n        if (propertyValue && typeof propertyValue === 'string') {\n            const singleDate = new Date(parseInt(propertyValue, 10))\n            if (singleDate && DateUtils.isDate(singleDate)) {\n                displayValue = Utils.displayDate(new Date(parseInt(propertyValue, 10)), intl)\n            } else {\n                try {\n                    const dateValue = JSON.parse(propertyValue as string)\n                    if (dateValue.from) {\n                        displayValue = Utils.displayDate(new Date(dateValue.from), intl)\n                    }\n                    if (dateValue.to) {\n                        displayValue += ' -> '\n                        displayValue += Utils.displayDate(new Date(dateValue.to), intl)\n                    }\n                } catch {\n                    // do nothing\n                }\n            }\n        }\n        return displayValue\n    }\n\n    getDateFrom = (value: string | string[] | undefined) => {\n        const dateProperty = createDatePropertyFromString(value as string)\n        if (!dateProperty.from) {\n            return undefined\n        }\n\n        // date properties are stored as 12 pm UTC, convert to 12 am (00) UTC for calendar\n        const dateFrom = dateProperty.from ? new Date(dateProperty.from + (dateProperty.includeTime ? 0 : timeZoneOffset(dateProperty.from))) : new Date()\n        dateFrom.setHours(0, 0, 0, 0)\n        return dateFrom\n    }\n\n    getDateTo = (value: string | string[] | undefined) => {\n        const dateProperty = createDatePropertyFromString(value as string)\n        if (!dateProperty.to) {\n            return undefined\n        }\n        const dateToNumber = dateProperty.to + (dateProperty.includeTime ? 0 : timeZoneOffset(dateProperty.to))\n        const dateTo = new Date(dateToNumber)\n        dateTo.setHours(0, 0, 0, 0)\n        return dateTo\n    }\n}\n"
  },
  {
    "path": "webapp/src/properties/email/email.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport {PropertyProps} from '../types'\nimport BaseTextEditor from '../baseTextEditor'\n\nconst Email = (props: PropertyProps): JSX.Element => {\n    return (\n        <BaseTextEditor\n            {...props}\n            validator={() => {\n                const emailRegexp = /^(([^<>()[\\]\\\\.,;:\\s@\"]+(\\.[^<>()[\\]\\\\.,;:\\s@\"]+)*)|(\".+\"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))$/\n                return emailRegexp.test(props.propertyValue as string)\n            }}\n        />\n    )\n}\nexport default Email\n"
  },
  {
    "path": "webapp/src/properties/email/property.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {IntlShape} from 'react-intl'\n\nimport {PropertyType, PropertyTypeEnum, FilterValueType} from '../types'\n\nimport Email from './email'\n\nexport default class EmailProperty extends PropertyType {\n    Editor = Email\n    name = 'Email'\n    type = 'email' as PropertyTypeEnum\n    displayName = (intl: IntlShape) => intl.formatMessage({id: 'PropertyType.Email', defaultMessage: 'Email'})\n    canFilter = true\n    filterValueType = 'text' as FilterValueType\n}\n"
  },
  {
    "path": "webapp/src/properties/index.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {PropertyTypeEnum} from '../blocks/board'\n\nimport CreatedTimeProperty from './createdTime/property'\nimport CreatedByProperty from './createdBy/property'\nimport UpdatedTimeProperty from './updatedTime/property'\nimport UpdatedByProperty from './updatedBy/property'\nimport TextProperty from './text/property'\nimport EmailProperty from './email/property'\nimport PhoneProperty from './phone/property'\nimport NumberProperty from './number/property'\nimport UrlProperty from './url/property'\nimport SelectProperty from './select/property'\nimport MultiSelectProperty from './multiselect/property'\nimport DateProperty from './date/property'\nimport PersonProperty from './person/property'\nimport MultiPersonProperty from './multiperson/property'\nimport CheckboxProperty from './checkbox/property'\nimport UnknownProperty from './unknown/property'\n\nimport {PropertyType} from './types'\n\nclass PropertiesRegistry {\n    properties: {[key: string]: PropertyType} = {}\n    propertiesList: PropertyType[] = []\n    unknownProperty: PropertyType = new UnknownProperty()\n\n    register(prop: PropertyType) {\n        this.properties[prop.type] = prop\n        this.propertiesList.push(prop)\n    }\n\n    unregister(prop: PropertyType) {\n        delete this.properties[prop.type]\n        this.propertiesList = this.propertiesList.filter((p) => p.type === prop.type)\n    }\n\n    list() {\n        return this.propertiesList\n    }\n\n    get(type: PropertyTypeEnum) {\n        return this.properties[type] || this.unknownProperty\n    }\n}\n\nconst registry = new PropertiesRegistry()\nregistry.register(new TextProperty())\nregistry.register(new NumberProperty())\nregistry.register(new EmailProperty())\nregistry.register(new PhoneProperty())\nregistry.register(new UrlProperty())\nregistry.register(new SelectProperty())\nregistry.register(new MultiSelectProperty())\nregistry.register(new DateProperty())\nregistry.register(new PersonProperty())\nregistry.register(new MultiPersonProperty())\nregistry.register(new CheckboxProperty())\nregistry.register(new CreatedTimeProperty())\nregistry.register(new CreatedByProperty())\nregistry.register(new UpdatedTimeProperty())\nregistry.register(new UpdatedByProperty())\n\nexport default registry\n"
  },
  {
    "path": "webapp/src/properties/multiperson/__snapshots__/multiperson.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`properties/multiperson not readonly 1`] = `\n<div>\n  <div\n    class=\"MultiPerson octo-propertyvalue css-b62m3t-container\"\n  >\n    <span\n      class=\"css-1f43avz-a11yText-A11yText\"\n      id=\"react-select-3-live-region\"\n    />\n    <span\n      aria-atomic=\"false\"\n      aria-live=\"polite\"\n      aria-relevant=\"additions text\"\n      class=\"css-1f43avz-a11yText-A11yText\"\n      role=\"log\"\n    />\n    <div\n      class=\"react-select__control css-18140j1-Control\"\n    >\n      <div\n        class=\"react-select__value-container react-select__value-container--is-multi react-select__value-container--has-value css-pcwdi-ValueContainer\"\n      >\n        <div\n          class=\"react-select__multi-value css-1p3m7a8-multiValue\"\n        >\n          <div\n            class=\"react-select__multi-value__label css-wsp0cs-MultiValueGeneric\"\n          >\n            <div\n              class=\"MultiPerson-item\"\n            >\n              username-1\n            </div>\n          </div>\n          <div\n            aria-label=\"Remove [object Object]\"\n            class=\"react-select__multi-value__remove css-12a83d4-MultiValueRemove\"\n            role=\"button\"\n          >\n            <svg\n              aria-hidden=\"true\"\n              class=\"css-tj5bde-Svg\"\n              focusable=\"false\"\n              height=\"14\"\n              viewBox=\"0 0 20 20\"\n              width=\"14\"\n            >\n              <path\n                d=\"M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z\"\n              />\n            </svg>\n          </div>\n        </div>\n        <div\n          class=\"react-select__multi-value css-1p3m7a8-multiValue\"\n        >\n          <div\n            class=\"react-select__multi-value__label css-wsp0cs-MultiValueGeneric\"\n          >\n            <div\n              class=\"MultiPerson-item\"\n            >\n              username-2\n            </div>\n          </div>\n          <div\n            aria-label=\"Remove [object Object]\"\n            class=\"react-select__multi-value__remove css-12a83d4-MultiValueRemove\"\n            role=\"button\"\n          >\n            <svg\n              aria-hidden=\"true\"\n              class=\"css-tj5bde-Svg\"\n              focusable=\"false\"\n              height=\"14\"\n              viewBox=\"0 0 20 20\"\n              width=\"14\"\n            >\n              <path\n                d=\"M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z\"\n              />\n            </svg>\n          </div>\n        </div>\n        <div\n          class=\"react-select__input-container css-1p5v8kp-Input\"\n          data-value=\"\"\n        >\n          <input\n            aria-activedescendant=\"\"\n            aria-autocomplete=\"list\"\n            aria-expanded=\"false\"\n            aria-haspopup=\"true\"\n            autocapitalize=\"none\"\n            autocomplete=\"off\"\n            autocorrect=\"off\"\n            class=\"react-select__input\"\n            id=\"react-select-3-input\"\n            role=\"combobox\"\n            spellcheck=\"false\"\n            style=\"opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;\"\n            tabindex=\"0\"\n            type=\"text\"\n            value=\"\"\n          />\n        </div>\n      </div>\n      <div\n        class=\"react-select__indicators css-1hb7zxy-IndicatorsContainer\"\n      >\n        <div\n          aria-hidden=\"true\"\n          class=\"react-select__indicator react-select__clear-indicator css-31haax-indicatorContainer\"\n        >\n          <svg\n            aria-hidden=\"true\"\n            class=\"css-tj5bde-Svg\"\n            focusable=\"false\"\n            height=\"20\"\n            viewBox=\"0 0 20 20\"\n            width=\"20\"\n          >\n            <path\n              d=\"M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z\"\n            />\n          </svg>\n        </div>\n        <span\n          class=\"react-select__indicator-separator css-1uei4ir-indicatorSeparator\"\n        />\n        <div\n          aria-hidden=\"true\"\n          class=\"react-select__indicator react-select__dropdown-indicator css-uycnsi-indicatorContainer\"\n        >\n          <svg\n            aria-hidden=\"true\"\n            class=\"css-tj5bde-Svg\"\n            focusable=\"false\"\n            height=\"20\"\n            viewBox=\"0 0 20 20\"\n            width=\"20\"\n          >\n            <path\n              d=\"M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z\"\n            />\n          </svg>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`properties/multiperson not readonly not existing user 1`] = `\n<div>\n  <div\n    class=\"MultiPerson octo-propertyvalue css-b62m3t-container\"\n  >\n    <span\n      class=\"css-1f43avz-a11yText-A11yText\"\n      id=\"react-select-2-live-region\"\n    />\n    <span\n      aria-atomic=\"false\"\n      aria-live=\"polite\"\n      aria-relevant=\"additions text\"\n      class=\"css-1f43avz-a11yText-A11yText\"\n      role=\"log\"\n    />\n    <div\n      class=\"react-select__control css-18140j1-Control\"\n    >\n      <div\n        class=\"react-select__value-container react-select__value-container--is-multi css-1gbdvdc-ValueContainer\"\n      >\n        <div\n          class=\"react-select__placeholder css-1jqq78o-placeholder\"\n          id=\"react-select-2-placeholder\"\n        >\n          Empty\n        </div>\n        <div\n          class=\"react-select__input-container css-1p5v8kp-Input\"\n          data-value=\"\"\n        >\n          <input\n            aria-activedescendant=\"\"\n            aria-autocomplete=\"list\"\n            aria-describedby=\"react-select-2-placeholder\"\n            aria-expanded=\"false\"\n            aria-haspopup=\"true\"\n            autocapitalize=\"none\"\n            autocomplete=\"off\"\n            autocorrect=\"off\"\n            class=\"react-select__input\"\n            id=\"react-select-2-input\"\n            role=\"combobox\"\n            spellcheck=\"false\"\n            style=\"opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;\"\n            tabindex=\"0\"\n            type=\"text\"\n            value=\"\"\n          />\n        </div>\n      </div>\n      <div\n        class=\"react-select__indicators css-1hb7zxy-IndicatorsContainer\"\n      >\n        <span\n          class=\"react-select__indicator-separator css-1uei4ir-indicatorSeparator\"\n        />\n        <div\n          aria-hidden=\"true\"\n          class=\"react-select__indicator react-select__dropdown-indicator css-uycnsi-indicatorContainer\"\n        >\n          <svg\n            aria-hidden=\"true\"\n            class=\"css-tj5bde-Svg\"\n            focusable=\"false\"\n            height=\"20\"\n            viewBox=\"0 0 20 20\"\n            width=\"20\"\n          >\n            <path\n              d=\"M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z\"\n            />\n          </svg>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`properties/multiperson readonly view 1`] = `\n<div>\n  <div\n    class=\"MultiPerson octo-propertyvalue octo-propertyvalue--readonly\"\n  >\n    <div\n      class=\"MultiPerson-item\"\n    >\n      username-1\n    </div>\n    <div\n      class=\"MultiPerson-item\"\n    >\n      username-2\n    </div>\n  </div>\n</div>\n`;\n\nexports[`properties/multiperson user dropdown open 1`] = `\n<div>\n  <div\n    class=\"MultiPerson octo-propertyvalue css-b62m3t-container\"\n  >\n    <span\n      class=\"css-1f43avz-a11yText-A11yText\"\n      id=\"react-select-4-live-region\"\n    />\n    <span\n      aria-atomic=\"false\"\n      aria-live=\"polite\"\n      aria-relevant=\"additions text\"\n      class=\"css-1f43avz-a11yText-A11yText\"\n      role=\"log\"\n    >\n      <span\n        id=\"aria-selection\"\n      />\n      <span\n        id=\"aria-focused\"\n      />\n      <span\n        id=\"aria-results\"\n      >\n        1 result available.\n      </span>\n      <span\n        id=\"aria-guidance\"\n      >\n        Use Up and Down to choose options, press Enter to select the currently focused option, press Escape to exit the menu, press Tab to select the option and exit the menu.\n      </span>\n    </span>\n    <div\n      class=\"react-select__control react-select__control--is-focused react-select__control--menu-is-open css-18140j1-Control\"\n    >\n      <div\n        class=\"react-select__value-container react-select__value-container--is-multi react-select__value-container--has-value css-pcwdi-ValueContainer\"\n      >\n        <div\n          class=\"react-select__multi-value css-1p3m7a8-multiValue\"\n        >\n          <div\n            class=\"react-select__multi-value__label css-wsp0cs-MultiValueGeneric\"\n          >\n            <div\n              class=\"MultiPerson-item\"\n            >\n              username-1\n            </div>\n          </div>\n          <div\n            aria-label=\"Remove [object Object]\"\n            class=\"react-select__multi-value__remove css-12a83d4-MultiValueRemove\"\n            role=\"button\"\n          >\n            <svg\n              aria-hidden=\"true\"\n              class=\"css-tj5bde-Svg\"\n              focusable=\"false\"\n              height=\"14\"\n              viewBox=\"0 0 20 20\"\n              width=\"14\"\n            >\n              <path\n                d=\"M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z\"\n              />\n            </svg>\n          </div>\n        </div>\n        <div\n          class=\"react-select__multi-value css-1p3m7a8-multiValue\"\n        >\n          <div\n            class=\"react-select__multi-value__label css-wsp0cs-MultiValueGeneric\"\n          >\n            <div\n              class=\"MultiPerson-item\"\n            >\n              username-2\n            </div>\n          </div>\n          <div\n            aria-label=\"Remove [object Object]\"\n            class=\"react-select__multi-value__remove css-12a83d4-MultiValueRemove\"\n            role=\"button\"\n          >\n            <svg\n              aria-hidden=\"true\"\n              class=\"css-tj5bde-Svg\"\n              focusable=\"false\"\n              height=\"14\"\n              viewBox=\"0 0 20 20\"\n              width=\"14\"\n            >\n              <path\n                d=\"M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z\"\n              />\n            </svg>\n          </div>\n        </div>\n        <div\n          class=\"react-select__input-container css-1p5v8kp-Input\"\n          data-value=\"\"\n        >\n          <input\n            aria-activedescendant=\"react-select-4-option-2\"\n            aria-autocomplete=\"list\"\n            aria-controls=\"react-select-4-listbox\"\n            aria-expanded=\"true\"\n            aria-haspopup=\"true\"\n            autocapitalize=\"none\"\n            autocomplete=\"off\"\n            autocorrect=\"off\"\n            class=\"react-select__input\"\n            id=\"react-select-4-input\"\n            role=\"combobox\"\n            spellcheck=\"false\"\n            style=\"opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;\"\n            tabindex=\"0\"\n            type=\"text\"\n            value=\"\"\n          />\n        </div>\n      </div>\n      <div\n        class=\"react-select__indicators css-1hb7zxy-IndicatorsContainer\"\n      >\n        <div\n          aria-hidden=\"true\"\n          class=\"react-select__indicator react-select__clear-indicator css-3pqe01-indicatorContainer\"\n        >\n          <svg\n            aria-hidden=\"true\"\n            class=\"css-tj5bde-Svg\"\n            focusable=\"false\"\n            height=\"20\"\n            viewBox=\"0 0 20 20\"\n            width=\"20\"\n          >\n            <path\n              d=\"M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z\"\n            />\n          </svg>\n        </div>\n        <span\n          class=\"react-select__indicator-separator css-1uei4ir-indicatorSeparator\"\n        />\n        <div\n          aria-hidden=\"true\"\n          class=\"react-select__indicator react-select__dropdown-indicator css-zngtjc-indicatorContainer\"\n        >\n          <svg\n            aria-hidden=\"true\"\n            class=\"css-tj5bde-Svg\"\n            focusable=\"false\"\n            height=\"20\"\n            viewBox=\"0 0 20 20\"\n            width=\"20\"\n          >\n            <path\n              d=\"M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z\"\n            />\n          </svg>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"react-select__menu css-1slvcut-menu\"\n    >\n      <div\n        aria-multiselectable=\"true\"\n        class=\"react-select__menu-list react-select__menu-list--is-multi css-1d1qzc4-MenuList\"\n        id=\"react-select-4-listbox\"\n        role=\"listbox\"\n      >\n        <div\n          aria-disabled=\"false\"\n          aria-selected=\"false\"\n          class=\"react-select__option react-select__option--is-focused css-63bi6m-option\"\n          id=\"react-select-4-option-2\"\n          role=\"option\"\n          tabindex=\"-1\"\n        >\n          <div\n            class=\"MultiPerson-item\"\n          >\n            username-3\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/properties/multiperson/multiperson.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport {render, waitFor} from '@testing-library/react'\n\nimport configureStore from 'redux-mock-store'\n\nimport {act} from 'react-dom/test-utils'\n\nimport userEvent from '@testing-library/user-event'\n\nimport {wrapIntl} from '../../testUtils'\nimport {IPropertyTemplate, Board} from '../../blocks/board'\nimport {Card} from '../../blocks/card'\n\nimport MultiPersonProperty from './property'\nimport MultiPerson from './multiperson'\n\ndescribe('properties/multiperson', () => {\n    const mockStore = configureStore([])\n\n    const state = {\n        users: {\n            boardUsers: {\n                'user-id-1': {\n                    id: 'user-id-1',\n                    username: 'username-1',\n                    email: 'user-1@example.com',\n                    props: {},\n                    create_at: 1621315184,\n                    update_at: 1621315184,\n                    delete_at: 0,\n                },\n                'user-id-2': {\n                    id: 'user-id-2',\n                    username: 'username-2',\n                    email: 'user-2@example.com',\n                    props: {},\n                    create_at: 1621315184,\n                    update_at: 1621315184,\n                    delete_at: 0,\n                },\n                'user-id-3': {\n                    id: 'user-id-3',\n                    username: 'username-3',\n                    email: 'user-3@example.com',\n                    props: {},\n                    create_at: 1621315184,\n                    update_at: 1621315184,\n                    delete_at: 0,\n                },\n            },\n        },\n        clientConfig: {\n            value: {\n                teammateNameDisplay: 'username',\n            },\n        },\n\n    }\n\n    test('not readonly not existing user', async () => {\n        const store = mockStore(state)\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <MultiPerson\n                    property={new MultiPersonProperty()}\n                    propertyValue={['user-id-4']}\n                    readOnly={false}\n                    showEmptyPlaceholder={false}\n                    propertyTemplate={{\n                        id: 'personPropertyID',\n                        name: 'My Person Property',\n                        type: 'multiPerson',\n                        options: [],\n                    } as IPropertyTemplate}\n                    board={{} as Board}\n                    card={{} as Card}\n                />\n            </ReduxProvider>,\n        )\n\n        const renderResult = render(component)\n        const container = await waitFor(() => {\n            if (!renderResult.container) {\n                return Promise.reject(new Error('container not found'))\n            }\n            return Promise.resolve(renderResult.container)\n        })\n        expect(container).toMatchSnapshot()\n    })\n\n    test('not readonly', async () => {\n        const store = mockStore(state)\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <MultiPerson\n                    property={new MultiPersonProperty()}\n                    propertyValue={['user-id-1', 'user-id-2']}\n                    readOnly={false}\n                    showEmptyPlaceholder={false}\n                    propertyTemplate={{\n                        id: 'personPropertyID',\n                        name: 'My Person Property',\n                        type: 'multiPerson',\n                        options: [],\n                    } as IPropertyTemplate}\n                    board={{} as Board}\n                    card={{} as Card}\n                />\n            </ReduxProvider>,\n        )\n\n        const renderResult = render(component)\n        const container = await waitFor(() => {\n            if (!renderResult.container) {\n                return Promise.reject(new Error('container not found'))\n            }\n            return Promise.resolve(renderResult.container)\n        })\n        expect(container).toMatchSnapshot()\n    })\n\n    test('readonly view', async () => {\n        const store = mockStore(state)\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <MultiPerson\n                    property={new MultiPersonProperty()}\n                    propertyValue={['user-id-1', 'user-id-2']}\n                    readOnly={true}\n                    showEmptyPlaceholder={false}\n                    propertyTemplate={{\n                        id: 'personPropertyID',\n                        name: 'My Person Property',\n                        type: 'multiPerson',\n                        options: [],\n                    } as IPropertyTemplate}\n                    board={{} as Board}\n                    card={{} as Card}\n                />\n            </ReduxProvider>,\n        )\n\n        const renderResult = render(component)\n        const container = await waitFor(() => {\n            if (!renderResult.container) {\n                return Promise.reject(new Error('container not found'))\n            }\n            return Promise.resolve(renderResult.container)\n        })\n        expect(container).toMatchSnapshot()\n    })\n\n    test('user dropdown open', async () => {\n        const store = mockStore(state)\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <MultiPerson\n                    property={new MultiPersonProperty()}\n                    propertyValue={['user-id-1', 'user-id-2']}\n                    readOnly={false}\n                    showEmptyPlaceholder={false}\n                    propertyTemplate={{\n                        id: 'personPropertyID',\n                        name: 'My Person Property',\n                        type: 'multiPerson',\n                        options: [],\n                    } as IPropertyTemplate}\n                    board={{} as Board}\n                    card={{} as Card}\n                />\n            </ReduxProvider>,\n        )\n\n        const renderResult = render(component)\n        const container = await waitFor(() => {\n            if (!renderResult.container) {\n                return Promise.reject(new Error('container not found'))\n            }\n            return Promise.resolve(renderResult.container)\n        })\n\n        if (container) {\n            // this is the actual element where the click event triggers\n            // opening of the dropdown\n            const userProperty = container.querySelector('.MultiPerson > div > div:nth-child(1) > div:nth-child(3) > input')\n            expect(userProperty).not.toBeNull()\n\n            act(() => {\n                userEvent.click(userProperty as Element)\n            })\n            expect(container).toMatchSnapshot()\n        } else {\n            throw new Error('container should have been initialized')\n        }\n    })\n})\n"
  },
  {
    "path": "webapp/src/properties/multiperson/multiperson.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport {PropertyProps} from '../types'\nimport ConfirmPerson from '../person/confirmPerson'\n\nconst MultiPerson = (props: PropertyProps): JSX.Element => {\n    return (\n        <ConfirmPerson\n            {...props}\n            showEmptyPlaceholder={true}\n        />\n    )\n}\n\nexport default MultiPerson\n"
  },
  {
    "path": "webapp/src/properties/multiperson/property.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {IntlShape} from 'react-intl'\n\nimport {PropertyType, PropertyTypeEnum, FilterValueType} from '../types'\n\nimport MultiPerson from './multiperson'\n\nexport default class MultiPersonProperty extends PropertyType {\n    Editor = MultiPerson\n    name = 'MultiPerson'\n    type = 'multiPerson' as PropertyTypeEnum\n    displayName = (intl: IntlShape) => intl.formatMessage({id: 'PropertyType.MultiPerson', defaultMessage: 'Multi person'})\n    canFilter = true\n    filterValueType = 'person' as FilterValueType\n}\n"
  },
  {
    "path": "webapp/src/properties/multiselect/__snapshots__/multiselect.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`properties/multiSelect shows only the selected options when menu is not opened 1`] = `\n<div>\n  <div\n    class=\"octo-propertyvalue octo-propertyvalue--readonly\"\n    data-testid=\"multiselect-non-editable\"\n    tabindex=\"0\"\n  >\n    <span\n      class=\"Label propColorDefault \"\n    >\n      a\n    </span>\n    <span\n      class=\"Label empty \"\n    >\n      b\n    </span>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/properties/multiselect/multiselect.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\nimport {render, screen} from '@testing-library/react'\nimport userEvent from '@testing-library/user-event'\nimport '@testing-library/jest-dom'\nimport {IntlProvider} from 'react-intl'\nimport {mocked} from 'jest-mock'\n\nimport {IPropertyOption, IPropertyTemplate, createBoard} from '../../blocks/board'\nimport {createCard} from '../../blocks/card'\nimport mutator from '../../mutator'\n\nimport MultiSelectProperty from './property'\nimport MultiSelect from './multiselect'\n\njest.mock('../../mutator')\nconst mockedMutator = mocked(mutator, true)\n\nfunction buildMultiSelectPropertyTemplate(options: IPropertyOption[] = []): IPropertyTemplate {\n    return {\n        id: 'multiselect-template-1',\n        name: 'Multi',\n        options: [\n            {\n                color: 'propColorDefault',\n                id: 'multi-option-1',\n                value: 'a',\n            },\n            {\n                color: '',\n                id: 'multi-option-2',\n                value: 'b',\n            },\n            {\n                color: 'propColorDefault',\n                id: 'multi-option-3',\n                value: 'c',\n            },\n            ...options,\n        ],\n        type: 'multiSelect',\n    }\n}\n\ntype WrapperProps = {\n    children?: React.ReactNode\n}\n\nconst Wrapper = ({children}: WrapperProps) => {\n    return <IntlProvider locale='en'>{children}</IntlProvider>\n}\n\ndescribe('properties/multiSelect', () => {\n    const nonEditableMultiSelectTestId = 'multiselect-non-editable'\n\n    const board = createBoard()\n    const card = createCard()\n\n    const expectOptionsMenuToBeVisible = (template: IPropertyTemplate) => {\n        for (const option of template.options) {\n            expect(screen.getByRole('menuitem', {name: option.value})).toBeInTheDocument()\n        }\n    }\n\n    beforeEach(() => {\n        jest.resetAllMocks()\n    })\n\n    it('shows only the selected options when menu is not opened', () => {\n        const propertyTemplate = buildMultiSelectPropertyTemplate()\n        const propertyValue = ['multi-option-1', 'multi-option-2']\n\n        const {container} = render(\n            <MultiSelect\n                property={new MultiSelectProperty()}\n                readOnly={true}\n                showEmptyPlaceholder={false}\n                propertyTemplate={propertyTemplate}\n                propertyValue={propertyValue}\n                board={{...board}}\n                card={{...card}}\n            />,\n            {wrapper: Wrapper},\n        )\n\n        const multiSelectParent = screen.getByTestId(nonEditableMultiSelectTestId)\n\n        expect(multiSelectParent.children.length).toBe(propertyValue.length)\n\n        expect(container).toMatchSnapshot()\n    })\n\n    it('opens editable multi value selector menu when the button/label is clicked', () => {\n        const propertyTemplate = buildMultiSelectPropertyTemplate()\n\n        render(\n            <MultiSelect\n                property={new MultiSelectProperty()}\n                readOnly={false}\n                showEmptyPlaceholder={false}\n                propertyTemplate={propertyTemplate}\n                propertyValue={[]}\n                board={{...board}}\n                card={{...card}}\n            />,\n            {wrapper: Wrapper},\n        )\n\n        userEvent.click(screen.getByTestId(nonEditableMultiSelectTestId))\n\n        expect(screen.getByRole('combobox', {name: /value selector/i})).toBeInTheDocument()\n    })\n\n    it('can select a option', async () => {\n        const propertyTemplate = buildMultiSelectPropertyTemplate()\n        const propertyValue = ['multi-option-1']\n\n        render(\n            <MultiSelect\n                property={new MultiSelectProperty()}\n                readOnly={false}\n                showEmptyPlaceholder={false}\n                propertyTemplate={propertyTemplate}\n                propertyValue={propertyValue}\n                board={{...board}}\n                card={{...card}}\n            />,\n            {wrapper: Wrapper},\n        )\n\n        userEvent.click(screen.getByTestId(nonEditableMultiSelectTestId))\n\n        userEvent.type(screen.getByRole('combobox', {name: /value selector/i}), 'b{enter}')\n\n        expect(mockedMutator.changePropertyValue).toHaveBeenCalledWith(board.id, card, propertyTemplate.id, ['multi-option-1', 'multi-option-2'])\n        expectOptionsMenuToBeVisible(propertyTemplate)\n    })\n\n    it('can unselect a option', async () => {\n        const propertyTemplate = buildMultiSelectPropertyTemplate()\n        const propertyValue = ['multi-option-1', 'multi-option-2']\n\n        render(\n            <MultiSelect\n                property={new MultiSelectProperty()}\n                readOnly={false}\n                showEmptyPlaceholder={false}\n                propertyTemplate={propertyTemplate}\n                propertyValue={propertyValue}\n                board={{...board}}\n                card={{...card}}\n            />,\n            {wrapper: Wrapper},\n        )\n\n        userEvent.click(screen.getByTestId(nonEditableMultiSelectTestId))\n\n        userEvent.click(screen.getAllByRole('button', {name: /clear/i})[0])\n\n        expect(mockedMutator.changePropertyValue).toHaveBeenCalledWith(board.id, card, propertyTemplate.id, ['multi-option-2'])\n        expectOptionsMenuToBeVisible(propertyTemplate)\n    })\n\n    it('can unselect a option via backspace', async () => {\n        const propertyTemplate = buildMultiSelectPropertyTemplate()\n        const propertyValue = ['multi-option-1', 'multi-option-2']\n\n        render(\n            <MultiSelect\n                property={new MultiSelectProperty()}\n                readOnly={false}\n                showEmptyPlaceholder={false}\n                propertyTemplate={propertyTemplate}\n                propertyValue={propertyValue}\n                board={{...board}}\n                card={{...card}}\n            />,\n            {wrapper: Wrapper},\n        )\n\n        userEvent.click(screen.getByTestId(nonEditableMultiSelectTestId))\n\n        userEvent.type(screen.getByRole('combobox', {name: /value selector/i}), '{backspace}')\n\n        expect(mockedMutator.changePropertyValue).toHaveBeenCalledWith(board.id, card, propertyTemplate.id, ['multi-option-1'])\n        expectOptionsMenuToBeVisible(propertyTemplate)\n    })\n\n    it('can close menu on escape', async () => {\n        const propertyTemplate = buildMultiSelectPropertyTemplate()\n        const propertyValue = ['multi-option-1', 'multi-option-2']\n\n        render(\n            <MultiSelect\n                property={new MultiSelectProperty()}\n                readOnly={false}\n                showEmptyPlaceholder={false}\n                propertyTemplate={propertyTemplate}\n                propertyValue={propertyValue}\n                board={{...board}}\n                card={{...card}}\n            />,\n            {wrapper: Wrapper},\n        )\n\n        userEvent.click(screen.getByTestId(nonEditableMultiSelectTestId))\n\n        userEvent.type(screen.getByRole('combobox', {name: /value selector/i}), '{escape}')\n\n        for (const option of propertyTemplate.options) {\n            expect(screen.queryByRole('menuitem', {name: option.value})).toBeNull()\n        }\n    })\n\n    it('can create a new option', async () => {\n        const propertyTemplate = buildMultiSelectPropertyTemplate()\n        const propertyValue = ['multi-option-1', 'multi-option-2']\n\n        render(\n            <MultiSelect\n                property={new MultiSelectProperty()}\n                readOnly={false}\n                showEmptyPlaceholder={false}\n                propertyTemplate={propertyTemplate}\n                propertyValue={propertyValue}\n                board={{...board}}\n                card={{...card}}\n            />,\n            {wrapper: Wrapper},\n        )\n\n        mockedMutator.insertPropertyOption.mockResolvedValue()\n\n        userEvent.click(screen.getByTestId(nonEditableMultiSelectTestId))\n        userEvent.type(screen.getByRole('combobox', {name: /value selector/i}), 'new-value{enter}')\n\n        expect(mockedMutator.insertPropertyOption).toHaveBeenCalledWith(board.id, board.cardProperties, propertyTemplate, expect.objectContaining({value: 'new-value'}), 'add property option')\n        expectOptionsMenuToBeVisible(propertyTemplate)\n    })\n\n    it('can delete a option', () => {\n        const propertyTemplate = buildMultiSelectPropertyTemplate()\n        const propertyValue = ['multi-option-1', 'multi-option-2']\n\n        render(\n            <MultiSelect\n                property={new MultiSelectProperty()}\n                readOnly={false}\n                showEmptyPlaceholder={false}\n                propertyTemplate={propertyTemplate}\n                propertyValue={propertyValue}\n                board={{...board}}\n                card={{...card}}\n            />,\n            {wrapper: Wrapper},\n        )\n\n        userEvent.click(screen.getByTestId(nonEditableMultiSelectTestId))\n\n        userEvent.click(screen.getAllByRole('button', {name: /open menu/i})[0])\n\n        userEvent.click(screen.getByRole('button', {name: /delete/i}))\n\n        const optionToDelete = propertyTemplate.options.find((option: IPropertyOption) => option.id === propertyValue[0])\n\n        expect(mockedMutator.deletePropertyOption).toHaveBeenCalledWith(board.id, board.cardProperties, propertyTemplate, optionToDelete)\n    })\n\n    it('can change color for any option', () => {\n        const propertyTemplate = buildMultiSelectPropertyTemplate()\n        const propertyValue = ['multi-option-1', 'multi-option-2']\n        const newColorKey = 'propColorYellow'\n        const newColorValue = 'yellow'\n\n        render(\n            <MultiSelect\n                property={new MultiSelectProperty()}\n                readOnly={false}\n                showEmptyPlaceholder={false}\n                propertyTemplate={propertyTemplate}\n                propertyValue={propertyValue}\n                board={{...board}}\n                card={{...card}}\n            />,\n            {wrapper: Wrapper},\n        )\n\n        userEvent.click(screen.getByTestId(nonEditableMultiSelectTestId))\n\n        userEvent.click(screen.getAllByRole('button', {name: /open menu/i})[0])\n\n        userEvent.click(screen.getByRole('button', {name: new RegExp(newColorValue, 'i')}))\n\n        const selectedOption = propertyTemplate.options.find((option: IPropertyOption) => option.id === propertyValue[0])\n\n        expect(mockedMutator.changePropertyOptionColor).toHaveBeenCalledWith(board.id, board.cardProperties, propertyTemplate, selectedOption, newColorKey)\n    })\n})\n"
  },
  {
    "path": "webapp/src/properties/multiselect/multiselect.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React, {useState, useCallback} from 'react'\nimport {useIntl} from 'react-intl'\n\nimport {IPropertyOption} from '../../blocks/board'\nimport {Utils, IDType} from '../../utils'\n\nimport mutator from '../../mutator'\n\nimport Label from '../../widgets/label'\nimport ValueSelector from '../../widgets/valueSelector'\n\nimport {PropertyProps} from '../types'\n\nconst MultiSelectProperty = (props: PropertyProps): JSX.Element => {\n    const {propertyTemplate, propertyValue, board, card} = props\n    const isEditable = !props.readOnly && Boolean(board)\n    const [open, setOpen] = useState(false)\n    const intl = useIntl()\n\n    const emptyDisplayValue = props.showEmptyPlaceholder ? intl.formatMessage({id: 'PropertyValueElement.empty', defaultMessage: 'Empty'}) : ''\n\n    const onChange = useCallback((newValue) => mutator.changePropertyValue(board.id, card, propertyTemplate.id, newValue), [board.id, card, propertyTemplate])\n    const onChangeColor = useCallback((option: IPropertyOption, colorId: string) => mutator.changePropertyOptionColor(board.id, board.cardProperties, propertyTemplate, option, colorId), [board, propertyTemplate])\n    const onDeleteOption = useCallback((option: IPropertyOption) => mutator.deletePropertyOption(board.id, board.cardProperties, propertyTemplate, option), [board, propertyTemplate])\n\n    const onDeleteValue = useCallback((valueToDelete: IPropertyOption, currentValues: IPropertyOption[]) => {\n        const newValues = currentValues.\n            filter((currentValue) => currentValue.id !== valueToDelete.id).\n            map((currentValue) => currentValue.id)\n        mutator.changePropertyValue(board.id, card, propertyTemplate.id, newValues)\n    }, [board.id, card, propertyTemplate.id])\n\n    const onCreateValue = useCallback((newValue: string, currentValues: IPropertyOption[]) => {\n        const option: IPropertyOption = {\n            id: Utils.createGuid(IDType.BlockID),\n            value: newValue,\n            color: 'propColorDefault',\n        }\n        currentValues.push(option)\n        mutator.insertPropertyOption(board.id, board.cardProperties, propertyTemplate, option, 'add property option').then(() => {\n            mutator.changePropertyValue(board.id, card, propertyTemplate.id, currentValues.map((v: IPropertyOption) => v.id))\n        })\n    }, [board, board.id, card, propertyTemplate])\n\n    const values = Array.isArray(propertyValue) && propertyValue.length > 0 ? propertyValue.map((v) => propertyTemplate.options.find((o) => o!.id === v)).filter((v): v is IPropertyOption => Boolean(v)) : []\n\n    if (!isEditable || !open) {\n        return (\n            <div\n                className={props.property.valueClassName(!isEditable)}\n                tabIndex={0}\n                data-testid='multiselect-non-editable'\n                onClick={() => setOpen(true)}\n            >\n                {values.map((v) => (\n                    <Label\n                        key={v.id}\n                        color={v.color}\n                    >\n                        {v.value}\n                    </Label>\n                ))}\n                {values.length === 0 && (\n                    <Label\n                        color='empty'\n                    >{emptyDisplayValue}</Label>\n                )}\n            </div>\n        )\n    }\n\n    return (\n        <ValueSelector\n            isMulti={true}\n            emptyValue={emptyDisplayValue}\n            options={propertyTemplate.options}\n            value={values}\n            onChange={onChange}\n            onChangeColor={onChangeColor}\n            onDeleteOption={onDeleteOption}\n            onDeleteValue={(valueToRemove) => onDeleteValue(valueToRemove, values)}\n            onCreate={(newValue) => onCreateValue(newValue, values)}\n            onBlur={() => setOpen(false)}\n        />\n    )\n}\n\nexport default MultiSelectProperty\n"
  },
  {
    "path": "webapp/src/properties/multiselect/property.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {IntlShape} from 'react-intl'\n\nimport {IPropertyTemplate} from '../../blocks/board'\nimport {Card} from '../../blocks/card'\nimport {Utils} from '../../utils'\n\nimport {PropertyType, PropertyTypeEnum, FilterValueType} from '../types'\n\nimport MultiSelect from './multiselect'\n\nexport default class MultiSelectProperty extends PropertyType {\n    Editor = MultiSelect\n    name = 'MultiSelect'\n    type = 'multiSelect' as PropertyTypeEnum\n    canFilter = true\n    filterValueType = 'options' as FilterValueType\n    displayName = (intl: IntlShape) => intl.formatMessage({id: 'PropertyType.MultiSelect', defaultMessage: 'Multi select'})\n    displayValue = (propertyValue: string | string[] | undefined, card: Card, propertyTemplate: IPropertyTemplate) => {\n        if (propertyValue?.length) {\n            const options = propertyTemplate.options.filter((o) => propertyValue.includes(o.id))\n            if (!options.length) {\n                Utils.assertFailure(`Invalid multiSelect option IDs ${propertyValue}, block.title: ${card.title}`)\n            }\n            return options.map((o) => o.value)\n        }\n        return ''\n    }\n\n    exportValue = (value: string | string[] | undefined, card: Card, template: IPropertyTemplate): string => {\n        const displayValue = this.displayValue(value, card, template)\n        return ((displayValue as unknown || []) as string[]).join('|')\n    }\n\n    valueLength = (value: string | string[] | undefined, card: Card, template: IPropertyTemplate, _: IntlShape, fontDescriptor: string, perItemPadding?: number): number => {\n        const displayValue = this.displayValue(value, card, template)\n        if (!displayValue) {\n            return 0\n        }\n        const displayValues = displayValue as string[]\n        let result = 0\n        displayValues.forEach((v) => {\n            result += Utils.getTextWidth(v.toUpperCase(), fontDescriptor) + (perItemPadding || 0)\n        })\n        return result\n    }\n}\n"
  },
  {
    "path": "webapp/src/properties/number/__snapshots__/number.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`properties/number should match snapshot for number with empty value 1`] = `\n<div>\n  <input\n    class=\"Editable octo-propertyvalue\"\n    placeholder=\"\"\n    style=\"width: 100%;\"\n    title=\"\"\n    value=\"\"\n  />\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/properties/number/number.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React, {ComponentProps} from 'react'\nimport {render, screen} from '@testing-library/react'\nimport {mocked} from 'jest-mock'\n\nimport userEvent from '@testing-library/user-event'\n\nimport {wrapIntl} from '../../testUtils'\nimport {TestBlockFactory} from '../../test/testBlockFactory'\nimport mutator from '../../mutator'\n\nimport {Board, IPropertyTemplate} from '../..//blocks/board'\nimport {Card} from '../../blocks/card'\n\nimport NumberProperty from './property'\nimport NumberEditor from './number'\n\njest.mock('../../components/flashMessages')\njest.mock('../../mutator')\n\nconst mockedMutator = mocked(mutator)\n\ndescribe('properties/number', () => {\n    let board: Board\n    let card: Card\n    let propertyTemplate: IPropertyTemplate\n    let baseProps: ComponentProps<typeof NumberEditor>\n\n    beforeEach(() => {\n        board = TestBlockFactory.createBoard()\n        card = TestBlockFactory.createCard()\n        propertyTemplate = board.cardProperties[0]\n\n        baseProps = {\n            property: new NumberProperty(),\n            card,\n            board,\n            propertyTemplate,\n            propertyValue: '',\n            readOnly: false,\n            showEmptyPlaceholder: false,\n        }\n    })\n\n    it('should match snapshot for number with empty value', () => {\n        const {container} = render(\n            wrapIntl((\n                <NumberEditor\n                    {...baseProps}\n                />\n            )),\n        )\n        expect(container).toMatchSnapshot()\n    })\n\n    it('should fire change event when valid number value is entered', async () => {\n        render(\n            wrapIntl(\n                <NumberEditor\n                    {...baseProps}\n                />,\n            ),\n        )\n        const value = '42'\n        const input = screen.getByRole('textbox')\n        userEvent.type(input, `${value}{Enter}`)\n\n        expect(mockedMutator.changePropertyValue).toHaveBeenCalledWith(board.id, card, propertyTemplate.id, `${value}`)\n    })\n})\n"
  },
  {
    "path": "webapp/src/properties/number/number.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport {PropertyProps} from '../types'\nimport BaseTextEditor from '../baseTextEditor'\n\nconst Number = (props: PropertyProps): JSX.Element => {\n    return (\n        <BaseTextEditor\n            {...props}\n            validator={() => props.propertyValue === '' || !isNaN(parseInt(props.propertyValue as string, 10))}\n        />\n    )\n}\nexport default Number\n"
  },
  {
    "path": "webapp/src/properties/number/property.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {IntlShape} from 'react-intl'\n\nimport {Options} from '../../components/calculations/options'\nimport {PropertyType, PropertyTypeEnum} from '../types'\n\nimport NumberProp from './number'\n\nexport default class NumberProperty extends PropertyType {\n    Editor = NumberProp\n    name = 'Number'\n    type = 'number' as PropertyTypeEnum\n    displayName = (intl: IntlShape) => intl.formatMessage({id: 'PropertyType.Number', defaultMessage: 'Number'})\n    calculationOptions = [Options.none, Options.count, Options.countEmpty,\n        Options.countNotEmpty, Options.percentEmpty, Options.percentNotEmpty,\n        Options.countValue, Options.countUniqueValue, Options.sum,\n        Options.average, Options.median, Options.min, Options.max,\n        Options.range]\n\n    exportValue = (value: string | string[] | undefined): string => (value ? Number(value).toString() : '')\n}\n"
  },
  {
    "path": "webapp/src/properties/person/__snapshots__/confirmPerson.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`properties/person select user - cancel 1`] = `\n<div>\n  <div\n    class=\"Person octo-propertyvalue css-b62m3t-container\"\n  >\n    <span\n      class=\"css-1f43avz-a11yText-A11yText\"\n      id=\"react-select-4-live-region\"\n    />\n    <span\n      aria-atomic=\"false\"\n      aria-live=\"polite\"\n      aria-relevant=\"additions text\"\n      class=\"css-1f43avz-a11yText-A11yText\"\n      role=\"log\"\n    />\n    <div\n      class=\"react-select__control css-18140j1-Control\"\n    >\n      <div\n        class=\"react-select__value-container react-select__value-container--has-value css-1gbdvdc-ValueContainer\"\n      >\n        <div\n          class=\"react-select__single-value css-qosd1h-singleValue\"\n        >\n          <div\n            class=\"Person-item\"\n          >\n            username-1\n          </div>\n        </div>\n        <div\n          class=\"react-select__input-container css-1p5v8kp-Input\"\n          data-value=\"\"\n        >\n          <input\n            aria-activedescendant=\"\"\n            aria-autocomplete=\"list\"\n            aria-expanded=\"false\"\n            aria-haspopup=\"true\"\n            autocapitalize=\"none\"\n            autocomplete=\"off\"\n            autocorrect=\"off\"\n            class=\"react-select__input\"\n            id=\"react-select-4-input\"\n            role=\"combobox\"\n            spellcheck=\"false\"\n            style=\"opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;\"\n            tabindex=\"0\"\n            type=\"text\"\n            value=\"\"\n          />\n        </div>\n      </div>\n      <div\n        class=\"react-select__indicators css-1hb7zxy-IndicatorsContainer\"\n      >\n        <div\n          aria-hidden=\"true\"\n          class=\"react-select__indicator react-select__clear-indicator css-31haax-indicatorContainer\"\n        >\n          <svg\n            aria-hidden=\"true\"\n            class=\"css-tj5bde-Svg\"\n            focusable=\"false\"\n            height=\"20\"\n            viewBox=\"0 0 20 20\"\n            width=\"20\"\n          >\n            <path\n              d=\"M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z\"\n            />\n          </svg>\n        </div>\n        <span\n          class=\"react-select__indicator-separator css-1uei4ir-indicatorSeparator\"\n        />\n        <div\n          aria-hidden=\"true\"\n          class=\"react-select__indicator react-select__dropdown-indicator css-uycnsi-indicatorContainer\"\n        >\n          <svg\n            aria-hidden=\"true\"\n            class=\"css-tj5bde-Svg\"\n            focusable=\"false\"\n            height=\"20\"\n            viewBox=\"0 0 20 20\"\n            width=\"20\"\n          >\n            <path\n              d=\"M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z\"\n            />\n          </svg>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`properties/person select user - cancel 2`] = `\n<div>\n  <div\n    class=\"Person octo-propertyvalue css-b62m3t-container\"\n  >\n    <span\n      class=\"css-1f43avz-a11yText-A11yText\"\n      id=\"react-select-4-live-region\"\n    />\n    <span\n      aria-atomic=\"false\"\n      aria-live=\"polite\"\n      aria-relevant=\"additions text\"\n      class=\"css-1f43avz-a11yText-A11yText\"\n      role=\"log\"\n    >\n      <span\n        id=\"aria-selection\"\n      />\n      <span\n        id=\"aria-focused\"\n      />\n      <span\n        id=\"aria-results\"\n      >\n        2 results available.\n      </span>\n      <span\n        id=\"aria-guidance\"\n      >\n        Use Up and Down to choose options, press Enter to select the currently focused option, press Escape to exit the menu, press Tab to select the option and exit the menu.\n      </span>\n    </span>\n    <div\n      class=\"react-select__control react-select__control--is-focused react-select__control--menu-is-open css-18140j1-Control\"\n    >\n      <div\n        class=\"react-select__value-container react-select__value-container--has-value css-1gbdvdc-ValueContainer\"\n      >\n        <div\n          class=\"react-select__single-value css-qosd1h-singleValue\"\n        >\n          <div\n            class=\"Person-item\"\n          >\n            username-1\n          </div>\n        </div>\n        <div\n          class=\"react-select__input-container css-1p5v8kp-Input\"\n          data-value=\"\"\n        >\n          <input\n            aria-activedescendant=\"react-select-4-option-1-0\"\n            aria-autocomplete=\"list\"\n            aria-controls=\"react-select-4-listbox\"\n            aria-expanded=\"true\"\n            aria-haspopup=\"true\"\n            autocapitalize=\"none\"\n            autocomplete=\"off\"\n            autocorrect=\"off\"\n            class=\"react-select__input\"\n            id=\"react-select-4-input\"\n            role=\"combobox\"\n            spellcheck=\"false\"\n            style=\"opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;\"\n            tabindex=\"0\"\n            type=\"text\"\n            value=\"\"\n          />\n        </div>\n      </div>\n      <div\n        class=\"react-select__indicators css-1hb7zxy-IndicatorsContainer\"\n      >\n        <div\n          aria-hidden=\"true\"\n          class=\"react-select__indicator react-select__clear-indicator css-3pqe01-indicatorContainer\"\n        >\n          <svg\n            aria-hidden=\"true\"\n            class=\"css-tj5bde-Svg\"\n            focusable=\"false\"\n            height=\"20\"\n            viewBox=\"0 0 20 20\"\n            width=\"20\"\n          >\n            <path\n              d=\"M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z\"\n            />\n          </svg>\n        </div>\n        <span\n          class=\"react-select__indicator-separator css-1uei4ir-indicatorSeparator\"\n        />\n        <div\n          aria-hidden=\"true\"\n          class=\"react-select__indicator react-select__dropdown-indicator css-zngtjc-indicatorContainer\"\n        >\n          <svg\n            aria-hidden=\"true\"\n            class=\"css-tj5bde-Svg\"\n            focusable=\"false\"\n            height=\"20\"\n            viewBox=\"0 0 20 20\"\n            width=\"20\"\n          >\n            <path\n              d=\"M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z\"\n            />\n          </svg>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"react-select__menu css-1slvcut-menu\"\n    >\n      <div\n        aria-multiselectable=\"false\"\n        class=\"react-select__menu-list css-1d1qzc4-MenuList\"\n        id=\"react-select-4-listbox\"\n        role=\"listbox\"\n      >\n        <div\n          class=\"react-select__group css-syji7d-Group\"\n        >\n          <div\n            class=\"react-select__group-heading css-jtaw72-group\"\n            id=\"react-select-4-group-1-heading\"\n          >\n            Not board members\n          </div>\n          <div>\n            <div\n              aria-disabled=\"false\"\n              aria-selected=\"false\"\n              class=\"react-select__option react-select__option--is-focused css-63bi6m-option\"\n              id=\"react-select-4-option-1-0\"\n              role=\"option\"\n              tabindex=\"-1\"\n            >\n              <div\n                class=\"Person-item\"\n              >\n                username-4\n              </div>\n            </div>\n            <div\n              aria-disabled=\"false\"\n              aria-selected=\"false\"\n              class=\"react-select__option css-1uk8033-option\"\n              id=\"react-select-4-option-1-1\"\n              role=\"option\"\n              tabindex=\"-1\"\n            >\n              <div\n                class=\"Person-item\"\n              >\n                username-5\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`properties/person select user - confirm 1`] = `\n<div>\n  <div\n    class=\"Person octo-propertyvalue css-b62m3t-container\"\n  >\n    <span\n      class=\"css-1f43avz-a11yText-A11yText\"\n      id=\"react-select-2-live-region\"\n    />\n    <span\n      aria-atomic=\"false\"\n      aria-live=\"polite\"\n      aria-relevant=\"additions text\"\n      class=\"css-1f43avz-a11yText-A11yText\"\n      role=\"log\"\n    />\n    <div\n      class=\"react-select__control css-18140j1-Control\"\n    >\n      <div\n        class=\"react-select__value-container react-select__value-container--has-value css-1gbdvdc-ValueContainer\"\n      >\n        <div\n          class=\"react-select__single-value css-qosd1h-singleValue\"\n        >\n          <div\n            class=\"Person-item\"\n          >\n            username-1\n          </div>\n        </div>\n        <div\n          class=\"react-select__input-container css-1p5v8kp-Input\"\n          data-value=\"\"\n        >\n          <input\n            aria-activedescendant=\"\"\n            aria-autocomplete=\"list\"\n            aria-expanded=\"false\"\n            aria-haspopup=\"true\"\n            autocapitalize=\"none\"\n            autocomplete=\"off\"\n            autocorrect=\"off\"\n            class=\"react-select__input\"\n            id=\"react-select-2-input\"\n            role=\"combobox\"\n            spellcheck=\"false\"\n            style=\"opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;\"\n            tabindex=\"0\"\n            type=\"text\"\n            value=\"\"\n          />\n        </div>\n      </div>\n      <div\n        class=\"react-select__indicators css-1hb7zxy-IndicatorsContainer\"\n      >\n        <div\n          aria-hidden=\"true\"\n          class=\"react-select__indicator react-select__clear-indicator css-31haax-indicatorContainer\"\n        >\n          <svg\n            aria-hidden=\"true\"\n            class=\"css-tj5bde-Svg\"\n            focusable=\"false\"\n            height=\"20\"\n            viewBox=\"0 0 20 20\"\n            width=\"20\"\n          >\n            <path\n              d=\"M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z\"\n            />\n          </svg>\n        </div>\n        <span\n          class=\"react-select__indicator-separator css-1uei4ir-indicatorSeparator\"\n        />\n        <div\n          aria-hidden=\"true\"\n          class=\"react-select__indicator react-select__dropdown-indicator css-uycnsi-indicatorContainer\"\n        >\n          <svg\n            aria-hidden=\"true\"\n            class=\"css-tj5bde-Svg\"\n            focusable=\"false\"\n            height=\"20\"\n            viewBox=\"0 0 20 20\"\n            width=\"20\"\n          >\n            <path\n              d=\"M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z\"\n            />\n          </svg>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`properties/person select user - confirm 2`] = `\n<div>\n  <div\n    class=\"Person octo-propertyvalue css-b62m3t-container\"\n  >\n    <span\n      class=\"css-1f43avz-a11yText-A11yText\"\n      id=\"react-select-2-live-region\"\n    />\n    <span\n      aria-atomic=\"false\"\n      aria-live=\"polite\"\n      aria-relevant=\"additions text\"\n      class=\"css-1f43avz-a11yText-A11yText\"\n      role=\"log\"\n    >\n      <span\n        id=\"aria-selection\"\n      />\n      <span\n        id=\"aria-focused\"\n      />\n      <span\n        id=\"aria-results\"\n      >\n        2 results available.\n      </span>\n      <span\n        id=\"aria-guidance\"\n      >\n        Use Up and Down to choose options, press Enter to select the currently focused option, press Escape to exit the menu, press Tab to select the option and exit the menu.\n      </span>\n    </span>\n    <div\n      class=\"react-select__control react-select__control--is-focused react-select__control--menu-is-open css-18140j1-Control\"\n    >\n      <div\n        class=\"react-select__value-container react-select__value-container--has-value css-1gbdvdc-ValueContainer\"\n      >\n        <div\n          class=\"react-select__single-value css-qosd1h-singleValue\"\n        >\n          <div\n            class=\"Person-item\"\n          >\n            username-1\n          </div>\n        </div>\n        <div\n          class=\"react-select__input-container css-1p5v8kp-Input\"\n          data-value=\"\"\n        >\n          <input\n            aria-activedescendant=\"react-select-2-option-1-0\"\n            aria-autocomplete=\"list\"\n            aria-controls=\"react-select-2-listbox\"\n            aria-expanded=\"true\"\n            aria-haspopup=\"true\"\n            autocapitalize=\"none\"\n            autocomplete=\"off\"\n            autocorrect=\"off\"\n            class=\"react-select__input\"\n            id=\"react-select-2-input\"\n            role=\"combobox\"\n            spellcheck=\"false\"\n            style=\"opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;\"\n            tabindex=\"0\"\n            type=\"text\"\n            value=\"\"\n          />\n        </div>\n      </div>\n      <div\n        class=\"react-select__indicators css-1hb7zxy-IndicatorsContainer\"\n      >\n        <div\n          aria-hidden=\"true\"\n          class=\"react-select__indicator react-select__clear-indicator css-3pqe01-indicatorContainer\"\n        >\n          <svg\n            aria-hidden=\"true\"\n            class=\"css-tj5bde-Svg\"\n            focusable=\"false\"\n            height=\"20\"\n            viewBox=\"0 0 20 20\"\n            width=\"20\"\n          >\n            <path\n              d=\"M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z\"\n            />\n          </svg>\n        </div>\n        <span\n          class=\"react-select__indicator-separator css-1uei4ir-indicatorSeparator\"\n        />\n        <div\n          aria-hidden=\"true\"\n          class=\"react-select__indicator react-select__dropdown-indicator css-zngtjc-indicatorContainer\"\n        >\n          <svg\n            aria-hidden=\"true\"\n            class=\"css-tj5bde-Svg\"\n            focusable=\"false\"\n            height=\"20\"\n            viewBox=\"0 0 20 20\"\n            width=\"20\"\n          >\n            <path\n              d=\"M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z\"\n            />\n          </svg>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"react-select__menu css-1slvcut-menu\"\n    >\n      <div\n        aria-multiselectable=\"false\"\n        class=\"react-select__menu-list css-1d1qzc4-MenuList\"\n        id=\"react-select-2-listbox\"\n        role=\"listbox\"\n      >\n        <div\n          class=\"react-select__group css-syji7d-Group\"\n        >\n          <div\n            class=\"react-select__group-heading css-jtaw72-group\"\n            id=\"react-select-2-group-1-heading\"\n          >\n            Not board members\n          </div>\n          <div>\n            <div\n              aria-disabled=\"false\"\n              aria-selected=\"false\"\n              class=\"react-select__option react-select__option--is-focused css-63bi6m-option\"\n              id=\"react-select-2-option-1-0\"\n              role=\"option\"\n              tabindex=\"-1\"\n            >\n              <div\n                class=\"Person-item\"\n              >\n                username-4\n              </div>\n            </div>\n            <div\n              aria-disabled=\"false\"\n              aria-selected=\"false\"\n              class=\"react-select__option css-1uk8033-option\"\n              id=\"react-select-2-option-1-1\"\n              role=\"option\"\n              tabindex=\"-1\"\n            >\n              <div\n                class=\"Person-item\"\n              >\n                username-5\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/properties/person/__snapshots__/person.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`properties/person not readOnly not existing user 1`] = `\n<div>\n  <div\n    class=\"Person octo-propertyvalue css-b62m3t-container\"\n  >\n    <span\n      class=\"css-1f43avz-a11yText-A11yText\"\n      id=\"react-select-2-live-region\"\n    />\n    <span\n      aria-atomic=\"false\"\n      aria-live=\"polite\"\n      aria-relevant=\"additions text\"\n      class=\"css-1f43avz-a11yText-A11yText\"\n      role=\"log\"\n    />\n    <div\n      class=\"react-select__control css-18140j1-Control\"\n    >\n      <div\n        class=\"react-select__value-container css-1gbdvdc-ValueContainer\"\n      >\n        <div\n          class=\"react-select__placeholder css-1jqq78o-placeholder\"\n          id=\"react-select-2-placeholder\"\n        >\n          Empty\n        </div>\n        <div\n          class=\"react-select__input-container css-1p5v8kp-Input\"\n          data-value=\"\"\n        >\n          <input\n            aria-activedescendant=\"\"\n            aria-autocomplete=\"list\"\n            aria-describedby=\"react-select-2-placeholder\"\n            aria-expanded=\"false\"\n            aria-haspopup=\"true\"\n            autocapitalize=\"none\"\n            autocomplete=\"off\"\n            autocorrect=\"off\"\n            class=\"react-select__input\"\n            id=\"react-select-2-input\"\n            role=\"combobox\"\n            spellcheck=\"false\"\n            style=\"opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;\"\n            tabindex=\"0\"\n            type=\"text\"\n            value=\"\"\n          />\n        </div>\n      </div>\n      <div\n        class=\"react-select__indicators css-1hb7zxy-IndicatorsContainer\"\n      >\n        <span\n          class=\"react-select__indicator-separator css-1uei4ir-indicatorSeparator\"\n        />\n        <div\n          aria-hidden=\"true\"\n          class=\"react-select__indicator react-select__dropdown-indicator css-uycnsi-indicatorContainer\"\n        >\n          <svg\n            aria-hidden=\"true\"\n            class=\"css-tj5bde-Svg\"\n            focusable=\"false\"\n            height=\"20\"\n            viewBox=\"0 0 20 20\"\n            width=\"20\"\n          >\n            <path\n              d=\"M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z\"\n            />\n          </svg>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`properties/person not readonly 1`] = `\n<div>\n  <div\n    class=\"Person octo-propertyvalue css-b62m3t-container\"\n  >\n    <span\n      class=\"css-1f43avz-a11yText-A11yText\"\n      id=\"react-select-3-live-region\"\n    />\n    <span\n      aria-atomic=\"false\"\n      aria-live=\"polite\"\n      aria-relevant=\"additions text\"\n      class=\"css-1f43avz-a11yText-A11yText\"\n      role=\"log\"\n    />\n    <div\n      class=\"react-select__control css-18140j1-Control\"\n    >\n      <div\n        class=\"react-select__value-container react-select__value-container--has-value css-1gbdvdc-ValueContainer\"\n      >\n        <div\n          class=\"react-select__single-value css-qosd1h-singleValue\"\n        >\n          <div\n            class=\"Person-item\"\n          >\n            username-1\n          </div>\n        </div>\n        <div\n          class=\"react-select__input-container css-1p5v8kp-Input\"\n          data-value=\"\"\n        >\n          <input\n            aria-activedescendant=\"\"\n            aria-autocomplete=\"list\"\n            aria-expanded=\"false\"\n            aria-haspopup=\"true\"\n            autocapitalize=\"none\"\n            autocomplete=\"off\"\n            autocorrect=\"off\"\n            class=\"react-select__input\"\n            id=\"react-select-3-input\"\n            role=\"combobox\"\n            spellcheck=\"false\"\n            style=\"opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;\"\n            tabindex=\"0\"\n            type=\"text\"\n            value=\"\"\n          />\n        </div>\n      </div>\n      <div\n        class=\"react-select__indicators css-1hb7zxy-IndicatorsContainer\"\n      >\n        <div\n          aria-hidden=\"true\"\n          class=\"react-select__indicator react-select__clear-indicator css-31haax-indicatorContainer\"\n        >\n          <svg\n            aria-hidden=\"true\"\n            class=\"css-tj5bde-Svg\"\n            focusable=\"false\"\n            height=\"20\"\n            viewBox=\"0 0 20 20\"\n            width=\"20\"\n          >\n            <path\n              d=\"M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z\"\n            />\n          </svg>\n        </div>\n        <span\n          class=\"react-select__indicator-separator css-1uei4ir-indicatorSeparator\"\n        />\n        <div\n          aria-hidden=\"true\"\n          class=\"react-select__indicator react-select__dropdown-indicator css-uycnsi-indicatorContainer\"\n        >\n          <svg\n            aria-hidden=\"true\"\n            class=\"css-tj5bde-Svg\"\n            focusable=\"false\"\n            height=\"20\"\n            viewBox=\"0 0 20 20\"\n            width=\"20\"\n          >\n            <path\n              d=\"M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z\"\n            />\n          </svg>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`properties/person not readonly guest user 1`] = `\n<div>\n  <div\n    class=\"Person octo-propertyvalue css-b62m3t-container\"\n  >\n    <span\n      class=\"css-1f43avz-a11yText-A11yText\"\n      id=\"react-select-4-live-region\"\n    />\n    <span\n      aria-atomic=\"false\"\n      aria-live=\"polite\"\n      aria-relevant=\"additions text\"\n      class=\"css-1f43avz-a11yText-A11yText\"\n      role=\"log\"\n    />\n    <div\n      class=\"react-select__control css-18140j1-Control\"\n    >\n      <div\n        class=\"react-select__value-container react-select__value-container--has-value css-1gbdvdc-ValueContainer\"\n      >\n        <div\n          class=\"react-select__single-value css-qosd1h-singleValue\"\n        >\n          <div\n            class=\"Person-item\"\n          >\n            username-1\n            <div\n              class=\"GuestBadge\"\n            >\n              <div\n                class=\"GuestBadge__box\"\n              >\n                Guest\n              </div>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"react-select__input-container css-1p5v8kp-Input\"\n          data-value=\"\"\n        >\n          <input\n            aria-activedescendant=\"\"\n            aria-autocomplete=\"list\"\n            aria-expanded=\"false\"\n            aria-haspopup=\"true\"\n            autocapitalize=\"none\"\n            autocomplete=\"off\"\n            autocorrect=\"off\"\n            class=\"react-select__input\"\n            id=\"react-select-4-input\"\n            role=\"combobox\"\n            spellcheck=\"false\"\n            style=\"opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;\"\n            tabindex=\"0\"\n            type=\"text\"\n            value=\"\"\n          />\n        </div>\n      </div>\n      <div\n        class=\"react-select__indicators css-1hb7zxy-IndicatorsContainer\"\n      >\n        <div\n          aria-hidden=\"true\"\n          class=\"react-select__indicator react-select__clear-indicator css-31haax-indicatorContainer\"\n        >\n          <svg\n            aria-hidden=\"true\"\n            class=\"css-tj5bde-Svg\"\n            focusable=\"false\"\n            height=\"20\"\n            viewBox=\"0 0 20 20\"\n            width=\"20\"\n          >\n            <path\n              d=\"M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z\"\n            />\n          </svg>\n        </div>\n        <span\n          class=\"react-select__indicator-separator css-1uei4ir-indicatorSeparator\"\n        />\n        <div\n          aria-hidden=\"true\"\n          class=\"react-select__indicator react-select__dropdown-indicator css-uycnsi-indicatorContainer\"\n        >\n          <svg\n            aria-hidden=\"true\"\n            class=\"css-tj5bde-Svg\"\n            focusable=\"false\"\n            height=\"20\"\n            viewBox=\"0 0 20 20\"\n            width=\"20\"\n          >\n            <path\n              d=\"M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z\"\n            />\n          </svg>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`properties/person readonly view 1`] = `\n<div>\n  <div\n    class=\"Person octo-propertyvalue octo-propertyvalue--readonly\"\n  >\n    <div\n      class=\"Person-item\"\n    >\n      username-1\n    </div>\n  </div>\n</div>\n`;\n\nexports[`properties/person user dropdown open 1`] = `\n<div>\n  <div\n    class=\"Person octo-propertyvalue css-b62m3t-container\"\n  >\n    <span\n      class=\"css-1f43avz-a11yText-A11yText\"\n      id=\"react-select-5-live-region\"\n    />\n    <span\n      aria-atomic=\"false\"\n      aria-live=\"polite\"\n      aria-relevant=\"additions text\"\n      class=\"css-1f43avz-a11yText-A11yText\"\n      role=\"log\"\n    >\n      <span\n        id=\"aria-selection\"\n      />\n      <span\n        id=\"aria-focused\"\n      />\n      <span\n        id=\"aria-results\"\n      >\n        1 result available.\n      </span>\n      <span\n        id=\"aria-guidance\"\n      >\n        Use Up and Down to choose options, press Enter to select the currently focused option, press Escape to exit the menu, press Tab to select the option and exit the menu.\n      </span>\n    </span>\n    <div\n      class=\"react-select__control react-select__control--is-focused react-select__control--menu-is-open css-18140j1-Control\"\n    >\n      <div\n        class=\"react-select__value-container react-select__value-container--has-value css-1gbdvdc-ValueContainer\"\n      >\n        <div\n          class=\"react-select__single-value css-qosd1h-singleValue\"\n        >\n          <div\n            class=\"Person-item\"\n          >\n            username-1\n          </div>\n        </div>\n        <div\n          class=\"react-select__input-container css-1p5v8kp-Input\"\n          data-value=\"\"\n        >\n          <input\n            aria-activedescendant=\"react-select-5-option-0\"\n            aria-autocomplete=\"list\"\n            aria-controls=\"react-select-5-listbox\"\n            aria-expanded=\"true\"\n            aria-haspopup=\"true\"\n            autocapitalize=\"none\"\n            autocomplete=\"off\"\n            autocorrect=\"off\"\n            class=\"react-select__input\"\n            id=\"react-select-5-input\"\n            role=\"combobox\"\n            spellcheck=\"false\"\n            style=\"opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;\"\n            tabindex=\"0\"\n            type=\"text\"\n            value=\"\"\n          />\n        </div>\n      </div>\n      <div\n        class=\"react-select__indicators css-1hb7zxy-IndicatorsContainer\"\n      >\n        <div\n          aria-hidden=\"true\"\n          class=\"react-select__indicator react-select__clear-indicator css-3pqe01-indicatorContainer\"\n        >\n          <svg\n            aria-hidden=\"true\"\n            class=\"css-tj5bde-Svg\"\n            focusable=\"false\"\n            height=\"20\"\n            viewBox=\"0 0 20 20\"\n            width=\"20\"\n          >\n            <path\n              d=\"M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z\"\n            />\n          </svg>\n        </div>\n        <span\n          class=\"react-select__indicator-separator css-1uei4ir-indicatorSeparator\"\n        />\n        <div\n          aria-hidden=\"true\"\n          class=\"react-select__indicator react-select__dropdown-indicator css-zngtjc-indicatorContainer\"\n        >\n          <svg\n            aria-hidden=\"true\"\n            class=\"css-tj5bde-Svg\"\n            focusable=\"false\"\n            height=\"20\"\n            viewBox=\"0 0 20 20\"\n            width=\"20\"\n          >\n            <path\n              d=\"M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z\"\n            />\n          </svg>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"react-select__menu css-1slvcut-menu\"\n    >\n      <div\n        aria-multiselectable=\"false\"\n        class=\"react-select__menu-list css-1d1qzc4-MenuList\"\n        id=\"react-select-5-listbox\"\n        role=\"listbox\"\n      >\n        <div\n          aria-disabled=\"false\"\n          aria-selected=\"true\"\n          class=\"react-select__option react-select__option--is-focused react-select__option--is-selected css-ad52of-option\"\n          id=\"react-select-5-option-0\"\n          role=\"option\"\n          tabindex=\"-1\"\n        >\n          <div\n            class=\"Person-item\"\n          >\n            username-1\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/properties/person/confirmPerson.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {Provider as ReduxProvider} from 'react-redux'\nimport {mocked} from 'jest-mock'\n\nimport {render, screen, waitFor, within} from '@testing-library/react'\n\nimport configureStore from 'redux-mock-store'\n\nimport {act} from 'react-dom/test-utils'\n\nimport userEvent from '@testing-library/user-event'\n\nimport {TestBlockFactory} from '../../test/testBlockFactory'\n\nimport {wrapIntl} from '../../testUtils'\nimport {IPropertyTemplate} from '../../blocks/board'\n\nimport client from '../../octoClient'\n\nimport mutator from '../../mutator'\n\nimport PersonProperty from './property'\n\n// import {IPropertyTemplate, Board} from '../blocks/board'\n\nimport ConfirmPerson from './confirmPerson'\njest.mock('../../mutator')\njest.mock('../../octoClient')\n\nconst mockedMutator = mocked(mutator, true)\nconst mockedOctoClient = mocked(client, true)\n\nconst board = TestBlockFactory.createBoard()\nboard.teamId = 'team-id-1'\nconst card = TestBlockFactory.createCard(board)\n\ndescribe('properties/person', () => {\n    const mockStore = configureStore([])\n    const state = {\n        boards: {\n            boards: {\n                [board.id]: board,\n            },\n            current: board.id,\n            myBoardMemberships: {\n                [board.id]: {userId: 'user-id-1', schemeAdmin: true},\n            },\n        },\n        users: {\n            me: {\n                id: 'user-id-1',\n                username: 'username_1',\n                roles: 'system_user',\n            },\n            boardUsers: {\n                'user-id-1': {\n                    id: 'user-id-1',\n                    username: 'username-1',\n                    email: 'user-1@example.com',\n                    firstname: 'test',\n                    lastname: 'user',\n                    props: {},\n                    create_at: 1621315184,\n                    update_at: 1621315184,\n                    delete_at: 0,\n                },\n                'user-id-2': {\n                    id: 'user-id-2',\n                    username: 'username-2',\n                    email: 'user-2@example.com',\n                    props: {},\n                    create_at: 1621315184,\n                    update_at: 1621315184,\n                    delete_at: 0,\n                },\n                'user-id-3': {\n                    id: 'user-id-3',\n                    username: 'username-3',\n                    email: 'user-3@example.com',\n                    props: {},\n                    create_at: 1621315184,\n                    update_at: 1621315184,\n                    delete_at: 0,\n                },\n            },\n        },\n        clientConfig: {\n            value: {\n                teammateNameDisplay: 'username',\n            },\n        },\n    }\n    const additionalUsers = [\n        {\n            id: 'user-id-4',\n            username: 'username-4',\n            email: 'user-4@example.com',\n            nickname: '',\n            firstname: '',\n            lastname: '',\n            props: {},\n            create_at: 1621315184,\n            update_at: 1621315184,\n            delete_at: 0,\n            is_bot: false,\n            is_guest: false,\n            roles: 'system_user',\n        },\n        {\n            id: 'user-id-5',\n            username: 'username-5',\n            email: 'user-5@example.com',\n            nickname: '',\n            firstname: '',\n            lastname: '',\n            props: {},\n            create_at: 1621315184,\n            update_at: 1621315184,\n            delete_at: 0,\n            is_bot: false,\n            is_guest: false,\n            roles: 'system_user',\n        },\n    ]\n\n    mockedOctoClient.searchTeamUsers.mockResolvedValue(additionalUsers)\n\n    test('select user - confirm', async () => {\n        const store = mockStore(state)\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <ConfirmPerson\n                    property={new PersonProperty()}\n                    propertyValue={'user-id-1'}\n                    readOnly={false}\n                    showEmptyPlaceholder={false}\n                    propertyTemplate={{} as IPropertyTemplate}\n                    board={board}\n                    card={card}\n                />\n            </ReduxProvider>,\n        )\n        const renderResult = render(component)\n        const container = await waitFor(() => {\n            if (!renderResult.container) {\n                return Promise.reject(new Error('container not found'))\n            }\n            return Promise.resolve(renderResult.container)\n        })\n        expect(container).toMatchSnapshot()\n\n        if (container) {\n            // this is the actual element where the click event triggers\n            // opening of the dropdown\n            const userProperty = container.querySelector('.Person > div > div:nth-child(1) > div:nth-child(2) > input')\n            expect(userProperty).not.toBeNull()\n\n            act(() => {\n                userEvent.click(userProperty as Element)\n            })\n            expect(container).toMatchSnapshot()\n\n            const option = renderResult.getByText('username-4')\n            expect(option).not.toBeNull()\n            act(() => {\n                userEvent.click(option as Element)\n            })\n\n            const confirmDialog = screen.getByTitle('Confirmation Dialog Box')\n            expect(confirmDialog).toBeDefined()\n            const confirmButton = within(confirmDialog).getByRole('button', {name: 'Add to board'})\n            expect(confirmButton).toBeDefined()\n            userEvent.click(confirmButton)\n\n            expect(mockedMutator.createBoardMember).toBeCalled()\n        } else {\n            throw new Error('container should have been initialized')\n        }\n    })\n\n    test('select user - cancel', async () => {\n        mockedMutator.createBoardMember.mockClear()\n        const store = mockStore(state)\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <ConfirmPerson\n                    property={new PersonProperty()}\n                    propertyValue={'user-id-1'}\n                    readOnly={false}\n                    showEmptyPlaceholder={false}\n                    propertyTemplate={{} as IPropertyTemplate}\n                    board={board}\n                    card={card}\n                />\n            </ReduxProvider>,\n        )\n        const renderResult = render(component)\n        const container = await waitFor(() => {\n            if (!renderResult.container) {\n                return Promise.reject(new Error('container not found'))\n            }\n            return Promise.resolve(renderResult.container)\n        })\n        expect(container).toMatchSnapshot()\n\n        if (container) {\n            // this is the actual element where the click event triggers\n            // opening of the dropdown\n            const userProperty = container.querySelector('.Person > div > div:nth-child(1) > div:nth-child(2) > input')\n            expect(userProperty).not.toBeNull()\n\n            act(() => {\n                userEvent.click(userProperty as Element)\n            })\n            expect(container).toMatchSnapshot()\n\n            const option = renderResult.getByText('username-4')\n            expect(option).not.toBeNull()\n            act(() => {\n                userEvent.click(option as Element)\n            })\n\n            const confirmDialog = screen.getByTitle('Confirmation Dialog Box')\n            expect(confirmDialog).toBeDefined()\n            const cancelButton = within(confirmDialog).getByRole('button', {name: 'Cancel'})\n            expect(cancelButton).toBeDefined()\n            userEvent.click(cancelButton)\n\n            expect(mockedMutator.createBoardMember).not.toBeCalled()\n        } else {\n            throw new Error('container should have been initialized')\n        }\n    })\n})\n"
  },
  {
    "path": "webapp/src/properties/person/confirmPerson.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React, {useCallback, useState} from 'react'\nimport {useIntl} from 'react-intl'\n\nimport {ActionMeta, SingleValue, MultiValue} from 'react-select'\n\nimport {IUser} from '../../user'\nimport mutator from '../../mutator'\nimport {useAppSelector} from '../../store/hooks'\nimport {getBoardUsers, getMe} from '../../store/users'\nimport {BoardMember, BoardTypeOpen, MemberRole} from '../../blocks/board'\n\nimport {PropertyProps} from '../types'\nimport {useHasPermissions} from '../../hooks/permissions'\nimport {Permission} from '../../constants'\nimport ConfirmAddUserForNotifications from '../../components/confirmAddUserForNotifications'\nimport PersonSelector from '../../components/personSelector'\n\nconst ConfirmPerson = (props: PropertyProps): JSX.Element => {\n    const {card, board, propertyTemplate, propertyValue, property, readOnly} = props\n    const [confirmAddUser, setConfirmAddUser] = useState<IUser|null>(null)\n    const intl = useIntl()\n\n    const boardUsersById = useAppSelector<{[key: string]: IUser}>(getBoardUsers)\n\n    const me = useAppSelector<IUser|null>(getMe)\n\n    const allowManageBoardRoles = useHasPermissions(board.teamId, board.id, [Permission.ManageBoardRoles])\n    const allowAddUsers = !me?.is_guest && (allowManageBoardRoles || board.type === BoardTypeOpen)\n    const changePropertyValue = useCallback((newValue) => mutator.changePropertyValue(board.id, card, propertyTemplate.id, newValue), [board.id, card, propertyTemplate.id])\n    const emptyDisplayValue = props.showEmptyPlaceholder ? intl.formatMessage({id: 'ConfirmPerson.empty', defaultMessage: 'Empty'}) : ''\n\n    let userIDs: string[] = []\n    if (typeof propertyValue === 'string' && propertyValue !== '') {\n        userIDs.push(propertyValue as string)\n    } else if (Array.isArray(propertyValue) && propertyValue.length > 0) {\n        userIDs = propertyValue\n    }\n\n    const onChange = (items: SingleValue<IUser> | MultiValue<IUser>, action: ActionMeta<IUser>) => {\n        if (Array.isArray(items)) {\n            if (action.action === 'select-option') {\n                const confirmedIds: string[] = []\n                items.forEach((item) => {\n                    if (boardUsersById[item.id]) {\n                        confirmedIds.push(item.id)\n                    } else {\n                        setConfirmAddUser(item)\n                    }\n                })\n                changePropertyValue(confirmedIds)\n            } else if (action.action === 'clear') {\n                changePropertyValue([])\n            } else if (action.action === 'remove-value') {\n                changePropertyValue(items.filter((a) => a.id !== action.removedValue.id).map((b) => b.id) || [])\n            }\n        } else {\n            const item = items as IUser\n            if (action.action === 'select-option') {\n                if (boardUsersById[item?.id || '']) {\n                    changePropertyValue(item?.id || '')\n                } else {\n                    setConfirmAddUser(item)\n                }\n            } else if (action.action === 'clear') {\n                changePropertyValue('')\n            }\n        }\n    }\n\n    const addUser = useCallback(async (userId: string, role: string) => {\n        const newRole = role || MemberRole.Viewer\n        const newMember = {\n            boardId: board.id,\n            userId,\n            roles: role,\n            schemeAdmin: newRole === MemberRole.Admin,\n            schemeEditor: newRole === MemberRole.Admin || newRole === MemberRole.Editor,\n            schemeCommenter: newRole === MemberRole.Admin || newRole === MemberRole.Editor || newRole === MemberRole.Commenter,\n            schemeViewer: newRole === MemberRole.Admin || newRole === MemberRole.Editor || newRole === MemberRole.Commenter || newRole === MemberRole.Viewer,\n        } as BoardMember\n\n        setConfirmAddUser(null)\n        await mutator.createBoardMember(newMember)\n\n        if (propertyTemplate.type === 'multiPerson') {\n            await mutator.changePropertyValue(board.id, card, propertyTemplate.id, [...userIDs, newMember.userId])\n        } else {\n            await mutator.changePropertyValue(board.id, card, propertyTemplate.id, newMember.userId)\n        }\n    }, [board, card, propertyTemplate, userIDs])\n\n    return (\n        <>\n            {confirmAddUser &&\n                <ConfirmAddUserForNotifications\n                    allowManageBoardRoles={allowManageBoardRoles}\n                    minimumRole={board.minimumRole}\n                    user={confirmAddUser}\n                    onConfirm={addUser}\n                    onClose={() => setConfirmAddUser(null)}\n                />}\n            <PersonSelector\n                userIDs={userIDs}\n                allowAddUsers={allowAddUsers}\n                isMulti={propertyTemplate.type === 'multiPerson'}\n                readOnly={readOnly}\n                emptyDisplayValue={emptyDisplayValue}\n                property={property}\n                onChange={onChange}\n            />\n        </>\n    )\n}\n\nexport default ConfirmPerson\n"
  },
  {
    "path": "webapp/src/properties/person/person.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport {render, waitFor} from '@testing-library/react'\n\nimport configureStore from 'redux-mock-store'\n\nimport {act} from 'react-dom/test-utils'\n\nimport userEvent from '@testing-library/user-event'\n\nimport {wrapIntl} from '../../testUtils'\nimport {IPropertyTemplate, Board} from '../../blocks/board'\nimport {Card} from '../../blocks/card'\n\nimport PersonProperty from './property'\nimport Person from './person'\n\ndescribe('properties/person', () => {\n    const mockStore = configureStore([])\n    const state = {\n        users: {\n            boardUsers: {\n                'user-id-1': {\n                    id: 'user-id-1',\n                    username: 'username-1',\n                    email: 'user-1@example.com',\n                    props: {},\n                    create_at: 1621315184,\n                    update_at: 1621315184,\n                    delete_at: 0,\n                },\n            },\n        },\n        clientConfig: {\n            value: {\n                teammateNameDisplay: 'username',\n            },\n        },\n    }\n\n    test('not readOnly not existing user', async () => {\n        const store = mockStore(state)\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <Person\n                    property={new PersonProperty()}\n                    propertyValue={'user-id-2'}\n                    readOnly={false}\n                    showEmptyPlaceholder={false}\n                    propertyTemplate={{} as IPropertyTemplate}\n                    board={{} as Board}\n                    card={{} as Card}\n                />\n            </ReduxProvider>,\n        )\n\n        const renderResult = render(component)\n        const container = await waitFor(() => {\n            if (!renderResult.container) {\n                return Promise.reject(new Error('container not found'))\n            }\n            return Promise.resolve(renderResult.container)\n        })\n        expect(container).toMatchSnapshot()\n    })\n\n    test('not readonly', async () => {\n        const store = mockStore(state)\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <Person\n                    property={new PersonProperty()}\n                    propertyValue={'user-id-1'}\n                    readOnly={false}\n                    showEmptyPlaceholder={false}\n                    propertyTemplate={{} as IPropertyTemplate}\n                    board={{} as Board}\n                    card={{} as Card}\n                />\n            </ReduxProvider>,\n        )\n\n        const renderResult = render(component)\n        const container = await waitFor(() => {\n            if (!renderResult.container) {\n                return Promise.reject(new Error('container not found'))\n            }\n            return Promise.resolve(renderResult.container)\n        })\n        expect(container).toMatchSnapshot()\n    })\n\n    test('not readonly guest user', async () => {\n        const store = mockStore({...state, users: {boardUsers: {'user-id-1': {...state.users.boardUsers['user-id-1'], is_guest: true}}}})\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <Person\n                    property={new PersonProperty()}\n                    propertyValue={'user-id-1'}\n                    readOnly={false}\n                    showEmptyPlaceholder={false}\n                    propertyTemplate={{} as IPropertyTemplate}\n                    board={{} as Board}\n                    card={{} as Card}\n                />\n            </ReduxProvider>,\n        )\n\n        const renderResult = render(component)\n        const container = await waitFor(() => {\n            if (!renderResult.container) {\n                return Promise.reject(new Error('container not found'))\n            }\n            return Promise.resolve(renderResult.container)\n        })\n        expect(container).toMatchSnapshot()\n    })\n\n    test('readonly view', async () => {\n        const store = mockStore(state)\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <Person\n                    property={new PersonProperty()}\n                    propertyValue={'user-id-1'}\n                    readOnly={true}\n                    showEmptyPlaceholder={false}\n                    propertyTemplate={{} as IPropertyTemplate}\n                    board={{} as Board}\n                    card={{} as Card}\n                />\n            </ReduxProvider>,\n        )\n\n        const renderResult = render(component)\n        const container = await waitFor(() => {\n            if (!renderResult.container) {\n                return Promise.reject(new Error('container not found'))\n            }\n            return Promise.resolve(renderResult.container)\n        })\n        expect(container).toMatchSnapshot()\n    })\n\n    test('user dropdown open', async () => {\n        const store = mockStore(state)\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <Person\n                    property={new PersonProperty()}\n                    propertyValue={'user-id-1'}\n                    readOnly={false}\n                    showEmptyPlaceholder={false}\n                    propertyTemplate={{} as IPropertyTemplate}\n                    board={{} as Board}\n                    card={{} as Card}\n                />\n            </ReduxProvider>,\n        )\n\n        const renderResult = render(component)\n        const container = await waitFor(() => {\n            if (!renderResult.container) {\n                return Promise.reject(new Error('container not found'))\n            }\n            return Promise.resolve(renderResult.container)\n        })\n\n        if (container) {\n            // this is the actual element where the click event triggers\n            // opening of the dropdown\n            const userProperty = container.querySelector('.Person > div > div:nth-child(1) > div:nth-child(2) > input')\n            expect(userProperty).not.toBeNull()\n\n            act(() => {\n                userEvent.click(userProperty as Element)\n            })\n            expect(container).toMatchSnapshot()\n        } else {\n            throw new Error('container should have been initialized')\n        }\n    })\n})\n"
  },
  {
    "path": "webapp/src/properties/person/person.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport {PropertyProps} from '../types'\n\nimport ConfirmPerson from './confirmPerson'\n\nconst Person = (props: PropertyProps): JSX.Element => {\n    return (\n        <ConfirmPerson\n            {...props}\n            showEmptyPlaceholder={true}\n        />\n    )\n}\n\nexport default Person\n"
  },
  {
    "path": "webapp/src/properties/person/property.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {IntlShape} from 'react-intl'\n\nimport {PropertyType, PropertyTypeEnum, FilterValueType} from '../types'\n\nimport Person from './person'\n\nexport default class PersonProperty extends PropertyType {\n    Editor = Person\n    name = 'Person'\n    type = 'person' as PropertyTypeEnum\n    displayName = (intl: IntlShape) => intl.formatMessage({id: 'PropertyType.Person', defaultMessage: 'Person'})\n    canFilter = true\n    filterValueType = 'person' as FilterValueType\n    canGroup = true\n}\n"
  },
  {
    "path": "webapp/src/properties/phone/phone.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport {PropertyProps} from '../types'\nimport BaseTextEditor from '../baseTextEditor'\n\nconst Phone = (props: PropertyProps): JSX.Element => {\n    return (\n        <BaseTextEditor\n            {...props}\n            validator={() => true}\n        />\n    )\n}\nexport default Phone\n"
  },
  {
    "path": "webapp/src/properties/phone/property.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {IntlShape} from 'react-intl'\n\nimport {PropertyType, PropertyTypeEnum, FilterValueType} from '../types'\n\nimport Phone from './phone'\n\nexport default class PhoneProperty extends PropertyType {\n    Editor = Phone\n    name = 'Phone'\n    type = 'phone' as PropertyTypeEnum\n    displayName = (intl: IntlShape) => intl.formatMessage({id: 'PropertyType.Phone', defaultMessage: 'Phone'})\n    canFilter = true\n    filterValueType = 'text' as FilterValueType\n}\n"
  },
  {
    "path": "webapp/src/properties/select/__snapshots__/select.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`properties/select shows empty placeholder 1`] = `\n<div>\n  <div\n    class=\"octo-propertyvalue octo-propertyvalue--readonly\"\n    data-testid=\"select-non-editable\"\n    tabindex=\"0\"\n  >\n    <span\n      class=\"Label empty \"\n    >\n      <span\n        class=\"Label-text\"\n      >\n        Empty\n      </span>\n    </span>\n  </div>\n</div>\n`;\n\nexports[`properties/select shows the selected option 1`] = `\n<div>\n  <div\n    class=\"octo-propertyvalue octo-propertyvalue--readonly\"\n    data-testid=\"select-non-editable\"\n    tabindex=\"0\"\n  >\n    <span\n      class=\"Label propColorDefault \"\n    >\n      <span\n        class=\"Label-text\"\n      >\n        one\n      </span>\n    </span>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/properties/select/property.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {IntlShape} from 'react-intl'\n\nimport {IPropertyTemplate} from '../../blocks/board'\nimport {Card} from '../../blocks/card'\nimport {Utils} from '../../utils'\nimport {PropertyType, PropertyTypeEnum, FilterValueType} from '../types'\n\nimport Select from './select'\n\nexport default class SelectProperty extends PropertyType {\n    Editor = Select\n    name = 'Select'\n    type = 'select' as PropertyTypeEnum\n    canGroup = true\n    canFilter = true\n    filterValueType = 'options' as FilterValueType\n\n    displayName = (intl: IntlShape) => intl.formatMessage({id: 'PropertyType.Select', defaultMessage: 'Select'})\n\n    displayValue = (propertyValue: string | string[] | undefined, card: Card, propertyTemplate: IPropertyTemplate) => {\n        if (propertyValue) {\n            const option = propertyTemplate.options.find((o) => o.id === propertyValue)\n            if (!option) {\n                Utils.assertFailure(`Invalid select option ID ${propertyValue}, block.title: ${card.title}`)\n            }\n            return option?.value || '(Unknown)'\n        }\n        return ''\n    }\n\n    valueLength = (value: string | string[] | undefined, card: Card, template: IPropertyTemplate, _: IntlShape, fontDescriptor: string): number => {\n        const displayValue = this.displayValue(value, card, template) || ''\n        return Utils.getTextWidth(displayValue.toString().toUpperCase(), fontDescriptor)\n    }\n}\n"
  },
  {
    "path": "webapp/src/properties/select/select.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\nimport {render, screen} from '@testing-library/react'\nimport '@testing-library/jest-dom'\nimport {mocked} from 'jest-mock'\n\nimport userEvent from '@testing-library/user-event'\n\nimport {IPropertyTemplate, createBoard} from '../../blocks/board'\nimport {createCard} from '../../blocks/card'\n\nimport {wrapIntl} from '../../testUtils'\nimport mutator from '../../mutator'\n\nimport SelectProperty from './property'\nimport Select from './select'\n\njest.mock('../../mutator')\nconst mockedMutator = mocked(mutator, true)\n\nfunction selectPropertyTemplate(): IPropertyTemplate {\n    return {\n        id: 'select-template',\n        name: 'select',\n        type: 'select',\n        options: [\n            {\n                id: 'option-1',\n                value: 'one',\n                color: 'propColorDefault',\n            },\n            {\n                id: 'option-2',\n                value: 'two',\n                color: 'propColorGreen',\n            },\n            {\n                id: 'option-3',\n                value: 'three',\n                color: 'propColorRed',\n            },\n        ],\n    }\n}\n\ndescribe('properties/select', () => {\n    const nonEditableSelectTestId = 'select-non-editable'\n\n    const clearButton = () => screen.queryByRole('button', {name: /clear/i})\n    const board = createBoard()\n    const card = createCard()\n\n    it('shows the selected option', () => {\n        const propertyTemplate = selectPropertyTemplate()\n        const option = propertyTemplate.options[0]\n\n        const {container} = render(wrapIntl(\n            <Select\n                property={new SelectProperty()}\n                board={{...board}}\n                card={{...card}}\n                propertyTemplate={propertyTemplate}\n                propertyValue={option.id}\n                readOnly={true}\n                showEmptyPlaceholder={false}\n            />,\n        ))\n\n        expect(screen.getByText(option.value)).toBeInTheDocument()\n        expect(clearButton()).not.toBeInTheDocument()\n\n        expect(container).toMatchSnapshot()\n    })\n\n    it('shows empty placeholder', () => {\n        const propertyTemplate = selectPropertyTemplate()\n        const emptyValue = 'Empty'\n\n        const {container} = render(wrapIntl(\n            <Select\n                property={new SelectProperty()}\n                board={{...board}}\n                card={{...card}}\n                showEmptyPlaceholder={true}\n                propertyTemplate={propertyTemplate}\n                propertyValue={''}\n                readOnly={true}\n            />,\n        ))\n\n        expect(screen.getByText(emptyValue)).toBeInTheDocument()\n        expect(clearButton()).not.toBeInTheDocument()\n\n        expect(container).toMatchSnapshot()\n    })\n\n    it('shows the menu with options when preview is clicked', () => {\n        const propertyTemplate = selectPropertyTemplate()\n        const selected = propertyTemplate.options[1]\n\n        render(wrapIntl(\n            <Select\n                property={new SelectProperty()}\n                board={{...board}}\n                card={{...card}}\n                propertyTemplate={propertyTemplate}\n                propertyValue={selected.id}\n                showEmptyPlaceholder={false}\n                readOnly={false}\n            />,\n        ))\n\n        userEvent.click(screen.getByTestId(nonEditableSelectTestId))\n\n        // check that all options are visible\n        for (const option of propertyTemplate.options) {\n            const elements = screen.getAllByText(option.value)\n\n            // selected option is rendered twice: in the input and inside the menu\n            const expected = option.id === selected.id ? 2 : 1\n            expect(elements.length).toBe(expected)\n        }\n\n        expect(clearButton()).toBeInTheDocument()\n    })\n\n    it('can select the option from menu', () => {\n        const propertyTemplate = selectPropertyTemplate()\n        const optionToSelect = propertyTemplate.options[2]\n\n        render(wrapIntl(\n            <Select\n                property={new SelectProperty()}\n                board={{...board}}\n                card={{...card}}\n                propertyTemplate={propertyTemplate}\n                propertyValue={''}\n                showEmptyPlaceholder={false}\n                readOnly={false}\n            />,\n        ))\n\n        userEvent.click(screen.getByTestId(nonEditableSelectTestId))\n        userEvent.click(screen.getByText(optionToSelect.value))\n\n        expect(clearButton()).not.toBeInTheDocument()\n        expect(mockedMutator.changePropertyValue).toHaveBeenCalledWith(board.id, card, propertyTemplate.id, optionToSelect.id)\n    })\n\n    it('can clear the selected option', () => {\n        const propertyTemplate = selectPropertyTemplate()\n        const selected = propertyTemplate.options[1]\n\n        render(wrapIntl(\n            <Select\n                property={new SelectProperty()}\n                board={{...board}}\n                card={{...card}}\n                propertyTemplate={propertyTemplate}\n                propertyValue={selected.id}\n                showEmptyPlaceholder={false}\n                readOnly={false}\n            />,\n        ))\n\n        userEvent.click(screen.getByTestId(nonEditableSelectTestId))\n\n        const clear = clearButton()\n        expect(clear).toBeInTheDocument()\n        userEvent.click(clear!)\n\n        expect(mockedMutator.changePropertyValue).toHaveBeenCalledWith(board.id, card, propertyTemplate.id, '')\n    })\n\n    it('can create new option', () => {\n        const propertyTemplate = selectPropertyTemplate()\n        const initialOption = propertyTemplate.options[0]\n        const newOption = 'new-option'\n\n        render(wrapIntl(\n            <Select\n                property={new SelectProperty()}\n                board={{...board}}\n                card={{...card}}\n                propertyTemplate={propertyTemplate}\n                propertyValue={initialOption.id}\n                showEmptyPlaceholder={false}\n                readOnly={false}\n            />,\n        ))\n\n        mockedMutator.insertPropertyOption.mockResolvedValue()\n\n        userEvent.click(screen.getByTestId(nonEditableSelectTestId))\n        userEvent.type(screen.getByRole('combobox', {name: /value selector/i}), `${newOption}{enter}`)\n\n        expect(mockedMutator.insertPropertyOption).toHaveBeenCalledWith(board.id, board.cardProperties, propertyTemplate, expect.objectContaining({value: newOption}), 'add property option')\n        expect(mockedMutator.changePropertyValue).toHaveBeenCalledWith(board.id, card, propertyTemplate.id, 'option-3')\n    })\n})\n"
  },
  {
    "path": "webapp/src/properties/select/select.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React, {useState, useCallback} from 'react'\nimport {useIntl} from 'react-intl'\n\nimport {IPropertyOption} from '../../blocks/board'\n\nimport Label from '../../widgets/label'\nimport {Utils, IDType} from '../../utils'\nimport mutator from '../../mutator'\nimport ValueSelector from '../../widgets/valueSelector'\n\nimport {PropertyProps} from '../types'\n\nconst SelectProperty = (props: PropertyProps) => {\n    const {propertyValue, propertyTemplate, board, card} = props\n    const intl = useIntl()\n\n    const [open, setOpen] = useState(false)\n    const isEditable = !props.readOnly && Boolean(board)\n\n    const onCreate = useCallback((newValue) => {\n        const option: IPropertyOption = {\n            id: Utils.createGuid(IDType.BlockID),\n            value: newValue,\n            color: 'propColorDefault',\n        }\n        mutator.insertPropertyOption(board.id, board.cardProperties, propertyTemplate, option, 'add property option').then(() => {\n            mutator.changePropertyValue(board.id, card, propertyTemplate.id, option.id)\n        })\n    }, [board, board.id, props.card, propertyTemplate.id])\n\n    const emptyDisplayValue = props.showEmptyPlaceholder ? intl.formatMessage({id: 'PropertyValueElement.empty', defaultMessage: 'Empty'}) : ''\n\n    const onChange = useCallback((newValue) => mutator.changePropertyValue(board.id, card, propertyTemplate.id, newValue), [board.id, card, propertyTemplate])\n    const onChangeColor = useCallback((option: IPropertyOption, colorId: string) => mutator.changePropertyOptionColor(board.id, board.cardProperties, propertyTemplate, option, colorId), [board, propertyTemplate])\n    const onDeleteOption = useCallback((option: IPropertyOption) => mutator.deletePropertyOption(board.id, board.cardProperties, propertyTemplate, option), [board, propertyTemplate])\n    const onDeleteValue = useCallback(() => mutator.changePropertyValue(board.id, card, propertyTemplate.id, ''), [card, propertyTemplate.id])\n\n    const option = propertyTemplate.options.find((o: IPropertyOption) => o.id === propertyValue)\n    const propertyColorCssClassName = option?.color || ''\n    const displayValue = option?.value\n    const finalDisplayValue = displayValue || emptyDisplayValue\n\n    if (!isEditable || !open) {\n        return (\n            <div\n                className={props.property.valueClassName(!isEditable)}\n                data-testid='select-non-editable'\n                tabIndex={0}\n                onClick={() => setOpen(true)}\n            >\n                <Label color={displayValue ? propertyColorCssClassName : 'empty'}>\n                    <span className='Label-text'>{finalDisplayValue}</span>\n                </Label>\n            </div>\n        )\n    }\n    return (\n        <ValueSelector\n            emptyValue={emptyDisplayValue}\n            options={propertyTemplate.options}\n            value={propertyTemplate.options.find((p: IPropertyOption) => p.id === propertyValue)}\n            onCreate={onCreate}\n            onChange={onChange}\n            onChangeColor={onChangeColor}\n            onDeleteOption={onDeleteOption}\n            onDeleteValue={onDeleteValue}\n            onBlur={() => setOpen(false)}\n        />\n    )\n}\n\nexport default React.memo(SelectProperty)\n"
  },
  {
    "path": "webapp/src/properties/text/property.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {IntlShape} from 'react-intl'\n\nimport {PropertyType, PropertyTypeEnum, FilterValueType} from '../types'\n\nimport Text from './text'\n\nexport default class TextProperty extends PropertyType {\n    Editor = Text\n    name = 'Text'\n    type = 'text' as PropertyTypeEnum\n    displayName = (intl: IntlShape) => intl.formatMessage({id: 'PropertyType.Text', defaultMessage: 'Text'})\n    canFilter = true\n    filterValueType = 'text' as FilterValueType\n}\n"
  },
  {
    "path": "webapp/src/properties/text/text.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport {PropertyProps} from '../types'\nimport BaseTextEditor from '../baseTextEditor'\n\nconst Text = (props: PropertyProps): JSX.Element => {\n    return (\n        <BaseTextEditor\n            {...props}\n            validator={() => true}\n            spellCheck={true}\n        />\n    )\n}\nexport default Text\n"
  },
  {
    "path": "webapp/src/properties/types.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\nimport {IntlShape} from 'react-intl'\n\nimport {Card} from '../blocks/card'\nimport {Board, IPropertyTemplate, PropertyTypeEnum as BoardPropertyTypeEnum} from '../blocks/board'\nimport {Options} from '../components/calculations/options'\nimport {Utils} from '../utils'\n\nconst hashSignToken = '___hash_sign___'\nfunction encodeText(text: string): string {\n    return text.replace(/\"/g, '\"\"').replace(/#/g, hashSignToken)\n}\n\nexport type PropertyTypeEnum = BoardPropertyTypeEnum\n\nexport type FilterValueType = 'none'|'options'|'boolean'|'text'|'date'|'person'\n\nexport type FilterCondition = {\n    id: string\n    label: string\n}\n\nexport type PropertyProps = {\n    property: PropertyType\n    card: Card\n    board: Board\n    readOnly: boolean\n    propertyValue: string | string[]\n    propertyTemplate: IPropertyTemplate\n    showEmptyPlaceholder: boolean\n}\n\nexport abstract class PropertyType {\n    canGroup = false\n    canFilter = false\n    filterValueType: FilterValueType = 'none'\n    isReadOnly = false\n    calculationOptions = [Options.none, Options.count, Options.countEmpty,\n        Options.countNotEmpty, Options.percentEmpty, Options.percentNotEmpty,\n        Options.countValue, Options.countUniqueValue]\n    displayValue: (value: string | string[] | undefined, card: Card, template: IPropertyTemplate, intl: IntlShape) => string | string[] | undefined\n    valueLength: (value: string | string[] | undefined, card: Card, template: IPropertyTemplate, intl: IntlShape, fontDescriptor: string, perItemPadding?: number) => number\n\n    constructor() {\n        this.displayValue = (value: string | string[] | undefined) => value\n        this.valueLength = (value: string | string[] | undefined, card: Card, template: IPropertyTemplate, intl: IntlShape, fontDescriptor: string): number => {\n            const displayValue = this.displayValue(value, card, template, intl) || ''\n            return Utils.getTextWidth(displayValue.toString(), fontDescriptor)\n        }\n    }\n\n    exportValue = (value: string | string[] | undefined, card: Card, template: IPropertyTemplate, intl: IntlShape): string => {\n        const displayValue = this.displayValue(value, card, template, intl)\n        if (typeof displayValue === 'string') {\n            return `\"${encodeText(displayValue)}\"`\n        } else if (Array.isArray(displayValue)) {\n            return `\"${encodeText((displayValue as string[]).join('|'))}\"`\n        }\n        return ''\n    }\n\n    valueClassName = (readonly: boolean): string => {\n        return `octo-propertyvalue${readonly ? ' octo-propertyvalue--readonly' : ''}`\n    }\n\n    abstract Editor: React.FunctionComponent<PropertyProps>\n    abstract name: string\n    abstract type: PropertyTypeEnum\n    abstract displayName: (intl: IntlShape) => string\n}\n\nexport abstract class DatePropertyType extends PropertyType {\n    canFilter = true\n    filterValueType: FilterValueType = 'date'\n    getDateFrom: (value: string | string[] | undefined, card: Card) => Date | undefined\n    getDateTo: (value: string | string[] | undefined, card: Card) => Date | undefined\n\n    constructor() {\n        super()\n        this.getDateFrom = () => undefined\n        this.getDateTo = () => undefined\n    }\n}\n"
  },
  {
    "path": "webapp/src/properties/unknown/property.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {IntlShape} from 'react-intl'\n\nimport Text from '../text/text'\nimport {PropertyType, PropertyTypeEnum} from '../types'\n\nexport default class UnkownProperty extends PropertyType {\n    Editor = Text\n    name = 'Text'\n    type = 'unknown' as PropertyTypeEnum\n    displayName = (intl: IntlShape) => intl.formatMessage({id: 'PropertyType.Unknown', defaultMessage: 'Unknown'})\n}\n"
  },
  {
    "path": "webapp/src/properties/updatedBy/__snapshots__/updatedBy.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`properties/updatedBy should match snapshot 1`] = `\n<div>\n  <div\n    class=\"Person octo-propertyvalue octo-propertyvalue--readonly\"\n  >\n    <div\n      class=\"Person-item\"\n    >\n      username_1\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/properties/updatedBy/property.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {IntlShape} from 'react-intl'\n\nimport {PropertyType, PropertyTypeEnum, FilterValueType} from '../types'\n\nimport UpdatedBy from './updatedBy'\n\nexport default class UpdatedByProperty extends PropertyType {\n    Editor = UpdatedBy\n    name = 'Last Modified By'\n    type = 'updatedBy' as PropertyTypeEnum\n    isReadOnly = true\n    displayName = (intl: IntlShape) => intl.formatMessage({id: 'PropertyType.UpdatedBy', defaultMessage: 'Last updated by'})\n    canFilter = true\n    filterValueType = 'person' as FilterValueType\n    canGroup = true\n}\n"
  },
  {
    "path": "webapp/src/properties/updatedBy/updatedBy.scss",
    "content": ".LastModifiedBy {\n    display: flex;\n    align-items: center;\n}\n"
  },
  {
    "path": "webapp/src/properties/updatedBy/updatedBy.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport {render} from '@testing-library/react'\nimport configureStore from 'redux-mock-store'\n\nimport {createCard} from '../../blocks/card'\nimport {IUser} from '../../user'\nimport {wrapIntl} from '../../testUtils'\n\nimport {createBoard, IPropertyTemplate} from '../../blocks/board'\n\nimport {createCommentBlock} from '../../blocks/commentBlock'\n\nimport UpdatedByProperty from './property'\nimport UpdatedBy from './updatedBy'\n\ndescribe('properties/updatedBy', () => {\n    test('should match snapshot', () => {\n        const card = createCard()\n        card.id = 'card-id-1'\n        card.modifiedBy = 'user-id-1'\n\n        const board = createBoard()\n        const comment = createCommentBlock()\n        comment.modifiedBy = 'user-id-1'\n        comment.parentId = 'card-id-1'\n\n        const mockStore = configureStore([])\n        const store = mockStore({\n            users: {\n                boardUsers: {\n                    'user-id-1': {username: 'username_1'} as IUser,\n                },\n            },\n            comments: {\n                comments: {\n                    [comment.id]: comment,\n                },\n                commentsByCard: {\n                    [card.id]: [comment],\n                },\n            },\n            clientConfig: {\n                value: {\n                    teammateNameDisplay: 'username',\n                },\n            },\n        })\n\n        const component = (\n            <ReduxProvider store={store}>\n                <UpdatedBy\n                    property={new UpdatedByProperty()}\n                    card={card}\n                    board={board}\n                    propertyTemplate={{} as IPropertyTemplate}\n                    propertyValue={''}\n                    readOnly={false}\n                    showEmptyPlaceholder={false}\n                />\n            </ReduxProvider>\n        )\n\n        const {container} = render(wrapIntl(component))\n        expect(container).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/properties/updatedBy/updatedBy.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport {Block} from '../../blocks/block'\nimport {useAppSelector} from '../../store/hooks'\nimport {getLastCardContent} from '../../store/contents'\nimport {getLastCardComment} from '../../store/comments'\nimport Person from '../person/person'\n\nimport {PropertyProps} from '../types'\n\nconst LastModifiedBy = (props: PropertyProps): JSX.Element => {\n    const lastContent = useAppSelector(getLastCardContent(props.card.id || '')) as Block\n    const lastComment = useAppSelector(getLastCardComment(props.card.id)) as Block\n\n    let latestBlock: Block = props.card\n    if (props.board) {\n        const allBlocks: Block[] = [props.card, lastContent, lastComment]\n        const sortedBlocks = allBlocks.sort((a, b) => b.updateAt - a.updateAt)\n\n        latestBlock = sortedBlocks.length > 0 ? sortedBlocks[0] : latestBlock\n    }\n\n    return (\n        <Person\n            {...props}\n            propertyValue={latestBlock.modifiedBy}\n            readOnly={true} // created by is an immutable property, so will always be readonly\n        />\n    )\n}\n\nexport default LastModifiedBy\n"
  },
  {
    "path": "webapp/src/properties/updatedTime/__snapshots__/updatedTime.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`properties/updatedTime should match snapshot 1`] = `\n<div>\n  <div\n    class=\"UpdatedTime octo-propertyvalue octo-propertyvalue--readonly\"\n  >\n    June 15, 2021 at 4:22 PM\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/properties/updatedTime/property.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {IntlShape} from 'react-intl'\n\nimport {Options} from '../../components/calculations/options'\nimport {IPropertyTemplate} from '../../blocks/board'\nimport {Card} from '../../blocks/card'\nimport {Utils} from '../../utils'\nimport {DatePropertyType, PropertyTypeEnum} from '../types'\n\nimport UpdatedTime from './updatedTime'\n\nexport default class UpdatedTimeProperty extends DatePropertyType {\n    Editor = UpdatedTime\n    name = 'Last Modified At'\n    type = 'updatedTime' as PropertyTypeEnum\n    isReadOnly = true\n    displayName = (intl: IntlShape) => intl.formatMessage({id: 'PropertyType.UpdatedTime', defaultMessage: 'Last updated time'})\n    calculationOptions = [Options.none, Options.count, Options.countEmpty,\n        Options.countNotEmpty, Options.percentEmpty, Options.percentNotEmpty,\n        Options.countValue, Options.countUniqueValue, Options.earliest,\n        Options.latest, Options.dateRange]\n    displayValue = (_1: string | string[] | undefined, card: Card, _2: IPropertyTemplate, intl: IntlShape) => Utils.displayDateTime(new Date(card.updateAt), intl)\n    getDateFrom = (_: string | string[] | undefined, card: Card) => new Date(card.updateAt || 0)\n    getDateTo = (_: string | string[] | undefined, card: Card) => new Date(card.updateAt || 0)\n}\n"
  },
  {
    "path": "webapp/src/properties/updatedTime/updatedTime.scss",
    "content": ".UpdatedTime {\n    display: flex;\n    align-items: center;\n}\n"
  },
  {
    "path": "webapp/src/properties/updatedTime/updatedTime.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {Provider as ReduxProvider} from 'react-redux'\n\nimport {render} from '@testing-library/react'\nimport configureStore from 'redux-mock-store'\n\nimport {createCard} from '../../blocks/card'\nimport {IPropertyTemplate, Board} from '../../blocks/board'\nimport {wrapIntl} from '../../testUtils'\n\nimport {createCommentBlock} from '../../blocks/commentBlock'\n\nimport UpdatedTimeProperty from './property'\nimport UpdatedTime from './updatedTime'\n\ndescribe('properties/updatedTime', () => {\n    test('should match snapshot', () => {\n        const card = createCard()\n        card.id = 'card-id-1'\n        card.modifiedBy = 'user-id-1'\n        card.updateAt = Date.parse('10 Jun 2021 16:22:00')\n\n        const comment = createCommentBlock()\n        comment.modifiedBy = 'user-id-1'\n        comment.parentId = 'card-id-1'\n        comment.updateAt = Date.parse('15 Jun 2021 16:22:00')\n\n        const mockStore = configureStore([])\n        const store = mockStore({\n            comments: {\n                comments: {\n                    [comment.id]: comment,\n                },\n                commentsByCard: {\n                    [card.id]: [comment],\n                },\n            },\n        })\n\n        const component = wrapIntl(\n            <ReduxProvider store={store}>\n                <UpdatedTime\n                    property={new UpdatedTimeProperty()}\n                    card={card}\n                    board={{} as Board}\n                    propertyTemplate={{} as IPropertyTemplate}\n                    propertyValue={''}\n                    readOnly={false}\n                    showEmptyPlaceholder={false}\n                />\n            </ReduxProvider>,\n        )\n\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/properties/updatedTime/updatedTime.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport {useIntl} from 'react-intl'\n\nimport {Block} from '../../blocks/block'\nimport {Utils} from '../../utils'\nimport {useAppSelector} from '../../store/hooks'\nimport {getLastCardContent} from '../../store/contents'\nimport {getLastCardComment} from '../../store/comments'\nimport './updatedTime.scss'\n\nimport {PropertyProps} from '../types'\n\nconst UpdatedTime = (props: PropertyProps): JSX.Element => {\n    const intl = useIntl()\n    const lastContent = useAppSelector(getLastCardContent(props.card.id || '')) as Block\n    const lastComment = useAppSelector(getLastCardComment(props.card.id)) as Block\n\n    let latestBlock: Block = props.card\n    if (props.card) {\n        const allBlocks = [props.card, lastContent, lastComment]\n        const sortedBlocks = allBlocks.sort((a, b) => b.updateAt - a.updateAt)\n\n        latestBlock = sortedBlocks.length > 0 ? sortedBlocks[0] : latestBlock\n    }\n\n    return (\n        <div className={`UpdatedTime ${props.property.valueClassName(true)}`}>\n            {Utils.displayDateTime(new Date(latestBlock.updateAt), intl)}\n        </div>\n    )\n}\n\nexport default UpdatedTime\n"
  },
  {
    "path": "webapp/src/properties/url/__snapshots__/url.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`properties/link should match snapshot for link with empty url 1`] = `\n<div>\n  <div\n    class=\"URLProperty\"\n  >\n    <input\n      class=\"Editable octo-propertyvalue\"\n      placeholder=\"\"\n      style=\"width: 100%;\"\n      title=\"\"\n      value=\"\"\n    />\n  </div>\n</div>\n`;\n\nexports[`properties/link should match snapshot for link with non-empty url 1`] = `\n<div>\n  <div\n    class=\"URLProperty octo-propertyvalue\"\n  >\n    <a\n      class=\"link\"\n      href=\"https://github.com/mattermost/focalboard\"\n      rel=\"noreferrer\"\n      target=\"_blank\"\n    >\n      https://github.com/mattermost/focalboard\n    </a>\n    <button\n      aria-label=\"Edit\"\n      class=\"IconButton Button_Edit\"\n      title=\"Edit\"\n      type=\"button\"\n    >\n      <i\n        class=\"CompassIcon icon-pencil-outline EditIcon\"\n      />\n    </button>\n    <button\n      aria-label=\"Copy\"\n      class=\"IconButton Button_Copy\"\n      title=\"Copy\"\n      type=\"button\"\n    >\n      <i\n        class=\"CompassIcon icon-content-copy content-copy\"\n      />\n    </button>\n  </div>\n</div>\n`;\n\nexports[`properties/link should match snapshot for readonly link with non-empty url 1`] = `\n<div>\n  <div\n    class=\"URLProperty octo-propertyvalue octo-propertyvalue--readonly\"\n  >\n    <a\n      class=\"link\"\n      href=\"https://github.com/mattermost/focalboard\"\n      rel=\"noreferrer\"\n      target=\"_blank\"\n    >\n      https://github.com/mattermost/focalboard\n    </a>\n    <button\n      aria-label=\"Copy\"\n      class=\"IconButton Button_Copy\"\n      title=\"Copy\"\n      type=\"button\"\n    >\n      <i\n        class=\"CompassIcon icon-content-copy content-copy\"\n      />\n    </button>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/properties/url/property.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {IntlShape} from 'react-intl'\n\nimport {PropertyType, PropertyTypeEnum, FilterValueType} from '../types'\n\nimport Url from './url'\n\nexport default class UrlProperty extends PropertyType {\n    Editor = Url\n    name = 'Url'\n    type = 'url' as PropertyTypeEnum\n    displayName = (intl: IntlShape) => intl.formatMessage({id: 'PropertyType.Url', defaultMessage: 'URL'})\n    canFilter = true\n    filterValueType = 'text' as FilterValueType\n}\n"
  },
  {
    "path": "webapp/src/properties/url/url.scss",
    "content": ".URLProperty {\n    display: flex;\n    align-items: center;\n    overflow: hidden;\n    width: 100%;\n\n    .link {\n        flex: 1 1 auto;\n        padding-left: 1px;\n        margin-right: 4px;\n        white-space: nowrap;\n        overflow: hidden;\n        color: rgb(var(--center-channel-color-rgb));\n        text-overflow: ellipsis;\n        text-decoration: underline rgb(var(--center-channel-color-rgb), 0.5);\n    }\n\n    .link:hover {\n        background-color: rgb(var(--center-channel-color-rgb), 0.1);\n    }\n\n    .IconButton {\n        display: none;\n        margin: 0 0 0 4px;\n        flex: 0 0 24px;\n\n        &:hover {\n            background-color: rgb(var(--center-channel-color-rgb), 0.1);\n        }\n    }\n\n    &:hover {\n        .IconButton {\n            display: flex;\n        }\n    }\n}\n\n#focalboard-app .URLProperty .link:visited {\n    color: rgb(var(--center-channel-color-rgb));\n}\n"
  },
  {
    "path": "webapp/src/properties/url/url.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {render, screen} from '@testing-library/react'\n\nimport {mocked} from 'jest-mock'\n\nimport '@testing-library/jest-dom'\nimport userEvent from '@testing-library/user-event'\n\nimport {wrapIntl} from '../../testUtils'\nimport {TestBlockFactory} from '../../test/testBlockFactory'\nimport {Utils} from '../../utils'\nimport {sendFlashMessage} from '../../components/flashMessages'\nimport mutator from '../../mutator'\n\nimport UrlProperty from './property'\nimport Url from './url'\n\njest.mock('../../components/flashMessages')\njest.mock('../../mutator')\n\nconst mockedCopy = jest.spyOn(Utils, 'copyTextToClipboard').mockImplementation(() => true)\nconst mockedSendFlashMessage = mocked(sendFlashMessage, true)\nconst mockedMutator = mocked(mutator, true)\n\ndescribe('properties/link', () => {\n    beforeEach(jest.clearAllMocks)\n\n    const board = TestBlockFactory.createBoard()\n    const card = TestBlockFactory.createCard()\n    const propertyTemplate = board.cardProperties[0]\n    const baseData = {\n        property: new UrlProperty(),\n        card,\n        board,\n        propertyTemplate,\n        readOnly: false,\n        showEmptyPlaceholder: false,\n    }\n\n    it('should match snapshot for link with empty url', () => {\n        const {container} = render(wrapIntl((\n            <Url\n                {...baseData}\n                propertyValue=''\n            />\n        )))\n        expect(container).toMatchSnapshot()\n    })\n\n    it('should match snapshot for link with non-empty url', () => {\n        const {container} = render(wrapIntl((\n            <Url\n                {...baseData}\n                propertyValue='https://github.com/mattermost/focalboard'\n            />\n        )))\n        expect(container).toMatchSnapshot()\n    })\n\n    it('should match snapshot for readonly link with non-empty url', () => {\n        const {container} = render(wrapIntl((\n            <Url\n                {...baseData}\n                propertyValue='https://github.com/mattermost/focalboard'\n                readOnly={true}\n            />\n        )))\n        expect(container).toMatchSnapshot()\n    })\n\n    it('should change to link after entering url', () => {\n        render(\n            wrapIntl(\n                <Url\n                    {...baseData}\n                    propertyValue=''\n                />,\n            ),\n        )\n\n        const url = 'https://mattermost.com'\n        const input = screen.getByRole('textbox')\n        userEvent.type(input, `${url}{enter}`)\n\n        expect(mockedMutator.changePropertyValue).toHaveBeenCalledWith(board.id, card, propertyTemplate.id, url)\n    })\n\n    it('should allow to edit link url', () => {\n        render(\n            wrapIntl(\n                <Url\n                    {...baseData}\n                    propertyValue='https://mattermost.com'\n                />,\n            ),\n        )\n\n        screen.getByRole('button', {name: 'Edit'}).click()\n        const newURL = 'https://github.com/mattermost'\n        const input = screen.getByRole('textbox')\n        userEvent.clear(input)\n        userEvent.type(input, `${newURL}{enter}`)\n        expect(mockedMutator.changePropertyValue).toHaveBeenCalledWith(board.id, card, propertyTemplate.id, newURL)\n    })\n\n    it('should allow to copy url', () => {\n        const url = 'https://mattermost.com'\n        render(\n            wrapIntl(\n                <Url\n                    {...baseData}\n                    propertyValue={url}\n                />,\n            ),\n        )\n        screen.getByRole('button', {name: 'Copy'}).click()\n        expect(mockedCopy).toHaveBeenCalledWith(url)\n        expect(mockedSendFlashMessage).toHaveBeenCalledWith({content: 'Copied!', severity: 'high'})\n    })\n})\n"
  },
  {
    "path": "webapp/src/properties/url/url.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React, {useEffect, useRef, useState, useCallback} from 'react'\nimport {useIntl} from 'react-intl'\n\nimport Editable, {Focusable} from '../../widgets/editable'\n\nimport {Utils} from '../../utils'\nimport mutator from '../../mutator'\nimport EditIcon from '../../widgets/icons/edit'\nimport IconButton from '../../widgets/buttons/iconButton'\nimport DuplicateIcon from '../../widgets/icons/duplicate'\nimport {sendFlashMessage} from '../../components/flashMessages'\n\nimport {PropertyProps} from '../types'\n\nimport './url.scss'\n\nconst URLProperty = (props: PropertyProps): JSX.Element => {\n    if (!props.propertyTemplate) {\n        return <></>\n    }\n\n    const [value, setValue] = useState(props.card.fields.properties[props.propertyTemplate.id || ''] || '')\n    const [isEditing, setIsEditing] = useState(false)\n    const isEmpty = !(props.propertyValue as string)?.trim()\n    const showEditable = !props.readOnly && (isEditing || isEmpty)\n    const editableRef = useRef<Focusable>(null)\n    const intl = useIntl()\n\n    const emptyDisplayValue = props.showEmptyPlaceholder ? intl.formatMessage({id: 'PropertyValueElement.empty', defaultMessage: 'Empty'}) : ''\n\n    const saveTextProperty = useCallback(() => {\n        if (value !== (props.card.fields.properties[props.propertyTemplate?.id || ''] || '')) {\n            mutator.changePropertyValue(props.board.id, props.card, props.propertyTemplate?.id || '', value)\n        }\n    }, [props.board.id, props.card, props.propertyTemplate?.id, value])\n\n    const saveTextPropertyRef = useRef<() => void>(saveTextProperty)\n    if (props.readOnly) {\n        saveTextPropertyRef.current = () => null\n    } else {\n        saveTextPropertyRef.current = saveTextProperty\n    }\n    useEffect(() => {\n        return () => {\n            saveTextPropertyRef.current && saveTextPropertyRef.current()\n        }\n    }, [])\n\n    useEffect(() => {\n        if (isEditing) {\n            editableRef.current?.focus()\n        }\n    }, [isEditing])\n\n    if (showEditable) {\n        return (\n            <div className='URLProperty'>\n                <Editable\n                    className={props.property.valueClassName(props.readOnly)}\n                    ref={editableRef}\n                    placeholderText={emptyDisplayValue}\n                    value={value as string}\n                    autoExpand={true}\n                    readonly={props.readOnly}\n                    onChange={setValue}\n                    onSave={() => {\n                        setIsEditing(false)\n                        saveTextProperty()\n                    }}\n                    onCancel={() => {\n                        setIsEditing(false)\n                        setValue(props.propertyValue || '')\n                    }}\n                    onFocus={() => {\n                        setIsEditing(true)\n                    }}\n                    validator={() => {\n                        if (value === '') {\n                            return true\n                        }\n                        const urlRegexp = /(((.+:(?:\\/\\/)?)?(?:[-;:&=+$,\\w]+@)?[A-Za-z0-9.-]+|(?:www\\.|[-;:&=+$,\\w]+@)[A-Za-z0-9.-]+)((?:\\/[+~%/.\\w\\-_]*)?\\??(?:[-+=&;%@.\\w_]*)#?(?:[.!/\\\\\\w]*))?)/\n                        return urlRegexp.test(value as string)\n                    }}\n                />\n            </div>\n        )\n    }\n\n    return (\n        <div className={`URLProperty ${props.property.valueClassName(props.readOnly)}`}>\n            <a\n                className='link'\n                href={Utils.ensureProtocol((props.propertyValue as string).trim())}\n                target='_blank'\n                rel='noreferrer'\n                onClick={(event) => event.stopPropagation()}\n            >\n                {props.propertyValue}\n            </a>\n            {!props.readOnly &&\n            <IconButton\n                className='Button_Edit'\n                title={intl.formatMessage({id: 'URLProperty.edit', defaultMessage: 'Edit'})}\n                icon={<EditIcon/>}\n                onClick={() => setIsEditing(true)}\n            />}\n            <IconButton\n                className='Button_Copy'\n                title={intl.formatMessage({id: 'URLProperty.copy', defaultMessage: 'Copy'})}\n                icon={<DuplicateIcon/>}\n                onClick={(e) => {\n                    e.stopPropagation()\n                    Utils.copyTextToClipboard(props.propertyValue as string)\n                    sendFlashMessage({content: intl.formatMessage({id: 'URLProperty.copiedLink', defaultMessage: 'Copied!'}), severity: 'high'})\n                }}\n            />\n        </div>\n    )\n}\n\nexport default URLProperty\n"
  },
  {
    "path": "webapp/src/route.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\nimport {\n    Redirect,\n    Route,\n} from 'react-router-dom'\n\nimport {Utils} from './utils'\nimport {getLoggedIn} from './store/users'\nimport {useAppSelector} from './store/hooks'\n\ntype RouteProps = {\n    path: string|string[]\n    exact?: boolean\n    render?: (props: any) => React.ReactElement\n    component?: React.ComponentType\n    children?: React.ReactElement\n    getOriginalPath?: (match: any) => string\n    loginRequired?: boolean\n}\n\nfunction FBRoute(props: RouteProps) {\n    const loggedIn = useAppSelector<boolean|null>(getLoggedIn)\n    let redirect: React.ReactNode = null\n\n    if (redirect === null && loggedIn === false && props.loginRequired) {\n        redirect = ({match}: any) => {\n            if (props.getOriginalPath) {\n                let redirectUrl = '/' + Utils.buildURL(props.getOriginalPath!(match))\n                if (redirectUrl.indexOf('//') === 0) {\n                    redirectUrl = redirectUrl.slice(1)\n                }\n                const loginUrl = `/error?id=not-logged-in&r=${encodeURIComponent(redirectUrl)}`\n                return <Redirect to={loginUrl}/>\n            }\n            return <Redirect to='/error?id=not-logged-in'/>\n        }\n    }\n\n    return (\n        <Route\n            path={props.path}\n            render={props.render}\n            component={props.component}\n            exact={props.exact}\n        >\n            {redirect || props.children}\n        </Route>\n    )\n}\n\nexport default React.memo(FBRoute)\n"
  },
  {
    "path": "webapp/src/router.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useEffect, useMemo} from 'react'\nimport {\n    Router,\n    Switch,\n    useRouteMatch,\n    useHistory,\n    generatePath,\n    useLocation,\n} from 'react-router-dom'\nimport {createBrowserHistory, History} from 'history'\n\nimport BoardPage from './pages/boardPage/boardPage'\nimport ChangePasswordPage from './pages/changePasswordPage'\nimport ErrorPage from './pages/errorPage'\nimport LoginPage from './pages/loginPage'\nimport RegisterPage from './pages/registerPage'\nimport {Utils} from './utils'\nimport octoClient from './octoClient'\nimport {setGlobalError, getGlobalError} from './store/globalError'\nimport {useAppSelector, useAppDispatch} from './store/hooks'\nimport FBRoute from './route'\n\nconst UUID_REGEX = new RegExp(/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/)\n\nfunction WorkspaceToTeamRedirect() {\n    const match = useRouteMatch<{boardId: string, viewId: string, cardId?: string, workspaceId?: string}>()\n    const queryParams = new URLSearchParams(useLocation().search)\n    const history = useHistory()\n    useEffect(() => {\n        octoClient.getBoard(match.params.boardId).then((board) => {\n            if (board) {\n                let newPath = generatePath(match.path.replace('/workspace/:workspaceId', '/team/:teamId'), {\n                    teamId: board?.teamId,\n                    boardId: board?.id,\n                    viewId: match.params.viewId,\n                    cardId: match.params.cardId,\n                })\n                if (queryParams) {\n                    newPath += '?' + queryParams\n                }\n                history.replace(newPath)\n            }\n        })\n    }, [])\n    return null\n}\n\nfunction GlobalErrorRedirect() {\n    const globalError = useAppSelector<string>(getGlobalError)\n    const dispatch = useAppDispatch()\n    const history = useHistory()\n\n    useEffect(() => {\n        if (globalError) {\n            dispatch(setGlobalError(''))\n            history.replace(`/error?id=${globalError}`)\n        }\n    }, [globalError, history])\n\n    return null\n}\n\ntype Props = {\n    history?: History<unknown>\n}\n\nconst FocalboardRouter = (props: Props): JSX.Element => {\n    let browserHistory: History<unknown>\n    if (props.history) {\n        browserHistory = props.history\n    } else {\n        browserHistory = useMemo(() => {\n            return createBrowserHistory({basename: Utils.getFrontendBaseURL()})\n        }, [])\n    }\n\n    return (\n        <Router history={browserHistory}>\n            <GlobalErrorRedirect/>\n            <Switch>\n                <FBRoute path='/error'>\n                    <ErrorPage/>\n                </FBRoute>\n\n                <FBRoute path='/login'>\n                    <LoginPage/>\n                </FBRoute>\n                <FBRoute path='/register'>\n                    <RegisterPage/>\n                </FBRoute>\n                <FBRoute path='/change_password'>\n                    <ChangePasswordPage/>\n                </FBRoute>\n\n                <FBRoute path={['/team/:teamId/new/:channelId']}>\n                    <BoardPage new={true}/>\n                </FBRoute>\n\n                <FBRoute path={['/team/:teamId/shared/:boardId?/:viewId?/:cardId?', '/shared/:boardId?/:viewId?/:cardId?']}>\n                    <BoardPage readonly={true}/>\n                </FBRoute>\n\n                <FBRoute\n                    loginRequired={true}\n                    path='/board/:boardId?/:viewId?/:cardId?'\n                    getOriginalPath={({params: {boardId, viewId, cardId}}) => {\n                        return `/board/${Utils.buildOriginalPath('', boardId, viewId, cardId)}`\n                    }}\n                >\n                    <BoardPage/>\n                </FBRoute>\n                <FBRoute path={['/workspace/:workspaceId/shared/:boardId?/:viewId?/:cardId?', '/workspace/:workspaceId/:boardId?/:viewId?/:cardId?']}>\n                    <WorkspaceToTeamRedirect/>\n                </FBRoute>\n                <FBRoute\n                    loginRequired={true}\n                    path='/team/:teamId/:boardId?/:viewId?/:cardId?'\n                    getOriginalPath={({params: {teamId, boardId, viewId, cardId}}) => {\n                        return `/team/${Utils.buildOriginalPath(teamId, boardId, viewId, cardId)}`\n                    }}\n                >\n                    <BoardPage/>\n                </FBRoute>\n\n                <FBRoute\n                    path='/:boardId?/:viewId?/:cardId?'\n                    loginRequired={true}\n                    getOriginalPath={({params: {boardId, viewId, cardId}}) => {\n                        const boardIdIsValidUUIDV4 = UUID_REGEX.test(boardId || '')\n                        if (boardIdIsValidUUIDV4) {\n                            return `/${Utils.buildOriginalPath('', boardId, viewId, cardId)}`\n                        }\n                        return ''\n                    }}\n                >\n                    <BoardPage/>\n                </FBRoute>\n            </Switch>\n        </Router>\n    )\n}\n\nexport default React.memo(FocalboardRouter)\n"
  },
  {
    "path": "webapp/src/statistics/index.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nexport interface BoardSiteStatistics {\n    board_count: number\n    card_count: number\n}\n"
  },
  {
    "path": "webapp/src/store/attachments.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {createSlice, PayloadAction} from '@reduxjs/toolkit'\n\nimport {AttachmentBlock} from '../blocks/attachmentBlock'\n\nimport {loadBoardData, initialReadOnlyLoad} from './initialLoad'\n\nimport {RootState} from './index'\n\ntype AttachmentsState = {\n    attachments: {[key: string]: AttachmentBlock}\n    attachmentsByCard: {[key: string]: AttachmentBlock[]}\n}\n\nconst attachmentSlice = createSlice({\n    name: 'attachments',\n    initialState: {attachments: {}, attachmentsByCard: {}} as AttachmentsState,\n    reducers: {\n        updateAttachments: (state, action: PayloadAction<AttachmentBlock[]>) => {\n            for (const attachment of action.payload) {\n                if (attachment.deleteAt === 0) {\n                    state.attachments[attachment.id] = attachment\n                    if (!state.attachmentsByCard[attachment.parentId]) {\n                        state.attachmentsByCard[attachment.parentId] = [attachment]\n                        return\n                    }\n                    if (state.attachmentsByCard[attachment.parentId].findIndex((a) => a.id === attachment.id) === -1) {\n                        state.attachmentsByCard[attachment.parentId].push(attachment)\n                    }\n                } else {\n                    const parentId = state.attachments[attachment.id]?.parentId\n                    if (!state.attachmentsByCard[parentId]) {\n                        delete state.attachments[attachment.id]\n                        return\n                    }\n                    for (let i = 0; i < state.attachmentsByCard[parentId].length; i++) {\n                        if (state.attachmentsByCard[parentId][i].id === attachment.id) {\n                            state.attachmentsByCard[parentId].splice(i, 1)\n                        }\n                    }\n                    delete state.attachments[attachment.id]\n                }\n            }\n        },\n        updateUploadPrecent: (state, action: PayloadAction<{blockId: string, uploadPercent: number}>) => {\n            state.attachments[action.payload.blockId].uploadingPercent = action.payload.uploadPercent\n        },\n    },\n    extraReducers: (builder) => {\n        builder.addCase(initialReadOnlyLoad.fulfilled, (state, action) => {\n            state.attachments = {}\n            state.attachmentsByCard = {}\n            for (const block of action.payload.blocks) {\n                if (block.type === 'attachment') {\n                    state.attachments[block.id] = block as AttachmentBlock\n                    state.attachmentsByCard[block.parentId] = state.attachmentsByCard[block.parentId] || []\n                    state.attachmentsByCard[block.parentId].push(block as AttachmentBlock)\n                }\n            }\n            Object.values(state.attachmentsByCard).forEach((arr) => arr.sort((a, b) => a.createAt - b.createAt))\n        })\n        builder.addCase(loadBoardData.fulfilled, (state, action) => {\n            state.attachments = {}\n            state.attachmentsByCard = {}\n            for (const block of action.payload.blocks) {\n                if (block.type === 'attachment') {\n                    state.attachments[block.id] = block as AttachmentBlock\n                    state.attachmentsByCard[block.parentId] = state.attachmentsByCard[block.parentId] || []\n                    state.attachmentsByCard[block.parentId].push(block as AttachmentBlock)\n                }\n            }\n            Object.values(state.attachmentsByCard).forEach((arr) => arr.sort((a, b) => a.createAt - b.createAt))\n        })\n    },\n})\n\nexport const {updateAttachments, updateUploadPrecent} = attachmentSlice.actions\nexport const {reducer} = attachmentSlice\n\nexport function getCardAttachments(cardId: string): (state: RootState) => AttachmentBlock[] {\n    return (state: RootState): AttachmentBlock[] => {\n        return (state.attachments?.attachmentsByCard && state.attachments.attachmentsByCard[cardId]) || []\n    }\n}\n\nexport function getUploadPercent(blockId: string): (state: RootState) => number {\n    return (state: RootState): number => {\n        return (state.attachments.attachments[blockId].uploadingPercent)\n    }\n}\n"
  },
  {
    "path": "webapp/src/store/boards.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {createSlice, PayloadAction, createAsyncThunk, createSelector} from '@reduxjs/toolkit'\n\nimport {default as client} from '../octoClient'\nimport {Board, BoardMember} from '../blocks/board'\nimport {IUser} from '../user'\n\nimport {\n    initialLoad,\n    initialReadOnlyLoad,\n    loadBoardData,\n    loadBoards,\n    loadMyBoardsMemberships,\n} from './initialLoad'\n\nimport {addBoardUsers, removeBoardUsersById, setBoardUsers} from './users'\n\nimport {RootState} from './index'\n\ntype BoardsState = {\n    current: string\n    loadingBoard: boolean\n    linkToChannel: string\n    boards: {[key: string]: Board}\n    templates: {[key: string]: Board}\n    membersInBoards: {[key: string]: {[key: string]: BoardMember}}\n    myBoardMemberships: {[key: string]: BoardMember}\n}\n\nexport const fetchBoardMembers = createAsyncThunk(\n    'boardMembers/fetch',\n    async ({teamId, boardId}: {teamId: string, boardId: string}, thunkAPI: any) => {\n        const members = await client.getBoardMembers(teamId, boardId)\n        const users = [] as IUser[]\n        const userIDs = members.map((member) => member.userId)\n\n        const usersData = await client.getTeamUsersList(userIDs, teamId)\n        users.push(...usersData)\n\n        thunkAPI.dispatch(setBoardUsers(users))\n        return members\n    },\n)\n\nexport const updateMembersEnsuringBoardsAndUsers = createAsyncThunk(\n    'updateMembersEnsuringBoardsAndUsers',\n    async (members: BoardMember[], thunkAPI: any) => {\n        const me = thunkAPI.getState().users.me\n        if (me) {\n            // ensure the boards for the new memberships get loaded or removed\n            const boards = thunkAPI.getState().boards.boards\n            const myMemberships = members.filter((m) => m.userId === me.id)\n            const boardsToUpdate: Board[] = []\n            /* eslint-disable no-await-in-loop */\n            for (const member of myMemberships) {\n                if (!member.schemeAdmin && !member.schemeEditor && !member.schemeViewer && !member.schemeCommenter) {\n                    boardsToUpdate.push({id: member.boardId, deleteAt: 1} as Board)\n                    continue\n                }\n\n                if (boards[member.boardId]) {\n                    continue\n                }\n\n                const board = await client.getBoard(member.boardId)\n                if (board) {\n                    boardsToUpdate.push(board)\n                }\n            }\n            /* eslint-enable no-await-in-loop */\n\n            thunkAPI.dispatch(updateBoards(boardsToUpdate))\n        }\n\n        // ensure the users for the new memberships get loaded\n        const boardUsers = thunkAPI.getState().users.boardUsers\n        members.forEach(async (m) => {\n            const deleted = !m.schemeAdmin && !m.schemeEditor && !m.schemeViewer && !m.schemeCommenter\n            if (deleted) {\n                thunkAPI.dispatch(removeBoardUsersById([m.userId]))\n                return\n            }\n            if (boardUsers[m.userId]) {\n                return\n            }\n\n            const board = await client.getBoard(m.boardId)\n            if (board) {\n                const user = await client.getTeamUsersList([m.userId], board.teamId)\n                if (user) {\n                    thunkAPI.dispatch(addBoardUsers(user))\n                }\n            }\n        })\n\n        return members\n    },\n)\n\nexport const updateMembersHandler = (state: BoardsState, action: PayloadAction<BoardMember[]>) => {\n    if (action.payload.length === 0) {\n        return\n    }\n\n    const boardId = action.payload[0].boardId\n    const boardMembers = state.membersInBoards[boardId] || {}\n\n    for (const member of action.payload) {\n        if (!member.schemeAdmin && !member.schemeEditor && !member.schemeViewer && !member.schemeCommenter) {\n            delete boardMembers[member.userId]\n        } else {\n            boardMembers[member.userId] = member\n        }\n    }\n\n    for (const member of action.payload) {\n        if (state.myBoardMemberships[member.boardId] && state.myBoardMemberships[member.boardId].userId === member.userId) {\n            if (!member.schemeAdmin && !member.schemeEditor && !member.schemeViewer && !member.schemeCommenter) {\n                delete state.myBoardMemberships[member.boardId]\n            } else {\n                state.myBoardMemberships[member.boardId] = member\n            }\n        }\n    }\n}\n\nconst boardsSlice = createSlice({\n    name: 'boards',\n    initialState: {loadingBoard: false, linkToChannel: '', boards: {}, templates: {}, membersInBoards: {}, myBoardMemberships: {}} as BoardsState,\n    reducers: {\n        setCurrent: (state, action: PayloadAction<string>) => {\n            state.current = action.payload\n        },\n        setLinkToChannel: (state, action: PayloadAction<string>) => {\n            state.linkToChannel = action.payload\n        },\n        updateBoards: (state, action: PayloadAction<Board[]>) => {\n            for (const board of action.payload) {\n                if (board.deleteAt !== 0) {\n                    delete state.boards[board.id]\n                    delete state.templates[board.id]\n                } else if (board.isTemplate) {\n                    state.templates[board.id] = board\n                } else {\n                    state.boards[board.id] = board\n                }\n            }\n        },\n        updateMembers: updateMembersHandler,\n        addMyBoardMemberships: (state, action: PayloadAction<BoardMember[]>) => {\n            action.payload.forEach((member) => {\n                if (!member.schemeAdmin && !member.schemeEditor && !member.schemeViewer && !member.schemeCommenter) {\n                    delete state.myBoardMemberships[member.boardId]\n                } else {\n                    state.myBoardMemberships[member.boardId] = member\n                }\n            })\n        },\n    },\n\n    extraReducers: (builder) => {\n        builder.addCase(loadBoardData.pending, (state) => {\n            state.loadingBoard = true\n        })\n        builder.addCase(loadBoardData.fulfilled, (state) => {\n            state.loadingBoard = false\n        })\n        builder.addCase(loadBoardData.rejected, (state) => {\n            state.loadingBoard = false\n        })\n        builder.addCase(initialReadOnlyLoad.fulfilled, (state, action) => {\n            state.boards = {}\n            state.templates = {}\n            if (action.payload.board) {\n                if (action.payload.board.isTemplate) {\n                    state.templates[action.payload.board.id] = action.payload.board\n                } else {\n                    state.boards[action.payload.board.id] = action.payload.board\n                }\n            }\n        })\n        builder.addCase(initialLoad.fulfilled, (state, action) => {\n            state.boards = {}\n            action.payload.boards.forEach((board) => {\n                state.boards[board.id] = board\n            })\n            state.templates = {}\n            action.payload.boardTemplates.forEach((board) => {\n                state.templates[board.id] = board\n            })\n            state.myBoardMemberships = {}\n            action.payload.boardsMemberships.forEach((boardMember) => {\n                state.myBoardMemberships[boardMember.boardId] = boardMember\n            })\n        })\n        builder.addCase(loadBoards.fulfilled, (state, action) => {\n            state.boards = {}\n            action.payload.boards.forEach((board) => {\n                state.boards[board.id] = board\n            })\n        })\n        builder.addCase(loadMyBoardsMemberships.fulfilled, (state, action) => {\n            state.myBoardMemberships = {}\n            action.payload.boardsMemberships.forEach((boardMember) => {\n                state.myBoardMemberships[boardMember.boardId] = boardMember\n            })\n        })\n        builder.addCase(fetchBoardMembers.fulfilled, (state, action) => {\n            if (action.payload.length === 0) {\n                return\n            }\n\n            // all members should belong to the same boardId, so we\n            // get it from the first one\n            const boardId = action.payload[0].boardId\n            const boardMembersMap = action.payload.reduce((acc: {[key: string]: BoardMember}, val: BoardMember) => {\n                acc[val.userId] = val\n                return acc\n            }, {})\n            state.membersInBoards[boardId] = boardMembersMap\n        })\n        builder.addCase(updateMembersEnsuringBoardsAndUsers.fulfilled, updateMembersHandler)\n    },\n})\n\nexport const {updateBoards, setCurrent, setLinkToChannel, updateMembers, addMyBoardMemberships} = boardsSlice.actions\nexport const {reducer} = boardsSlice\n\nexport const getBoards = (state: RootState): {[key: string]: Board} => state.boards?.boards || {}\n\nexport const getMySortedBoards = createSelector(\n    getBoards,\n    (state: RootState): {[key: string]: BoardMember} => state.boards?.myBoardMemberships || {},\n    (boards, myBoardMemberships: {[key: string]: BoardMember}) => {\n        return Object.values(boards).filter((b) => myBoardMemberships[b.id]).\n            sort((a, b) => a.title.localeCompare(b.title))\n    },\n)\n\nexport const getTemplates = (state: RootState): {[key: string]: Board} => state.boards.templates\n\nexport const getSortedTemplates = createSelector(\n    getTemplates,\n    (templates) => {\n        return Object.values(templates).sort((a, b) => a.title.localeCompare(b.title))\n    },\n)\n\nexport function getBoard(boardId: string): (state: RootState) => Board|null {\n    return (state: RootState): Board|null => {\n        if (state.boards.boards && state.boards.boards[boardId]) {\n            return state.boards.boards[boardId]\n        } else if (state.boards.templates && state.boards.templates[boardId]) {\n            return state.boards.templates[boardId]\n        }\n        return null\n    }\n}\n\nexport const isLoadingBoard = (state: RootState): boolean => state.boards.loadingBoard\n\nexport const getCurrentBoardId = (state: RootState): string => state.boards.current || ''\n\nexport const getCurrentBoard = createSelector(\n    getCurrentBoardId,\n    getBoards,\n    getTemplates,\n    (boardId, boards, templates) => {\n        return boards[boardId] || templates[boardId]\n    },\n)\n\nexport const getCurrentBoardMembers = createSelector(\n    (state: RootState): string => state.boards.current,\n    (state: RootState): {[key: string]: {[key: string]: BoardMember}} => state.boards.membersInBoards,\n    (boardId: string, membersInBoards: {[key: string]: {[key: string]: BoardMember}}): {[key: string]: BoardMember} => {\n        return membersInBoards[boardId] || {}\n    },\n)\n\nexport function getMyBoardMembership(boardId: string): (state: RootState) => BoardMember|null {\n    return (state: RootState): BoardMember|null => {\n        return state.boards.myBoardMemberships[boardId] || null\n    }\n}\n\nexport const getCurrentLinkToChannel = (state: RootState): string => state.boards.linkToChannel\n"
  },
  {
    "path": "webapp/src/store/cards.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {createSlice, PayloadAction, createSelector, createAsyncThunk} from '@reduxjs/toolkit'\n\nimport {Card} from '../blocks/card'\nimport {IUser} from '../user'\nimport {Board} from '../blocks/board'\nimport {Block} from '../blocks/block'\nimport {BoardView} from '../blocks/boardView'\nimport {CommentBlock} from '../blocks/commentBlock'\nimport {Utils} from '../utils'\nimport {Constants} from '../constants'\nimport {CardFilter} from '../cardFilter'\nimport {default as client} from '../octoClient'\n\nimport {loadBoardData, initialReadOnlyLoad, initialLoad} from './initialLoad'\nimport {getCurrentBoard} from './boards'\nimport {getBoardUsers} from './users'\nimport {getLastCommentByCard} from './comments'\nimport {getCurrentView} from './views'\nimport {getSearchText} from './searchText'\n\nimport {RootState} from './index'\n\ntype CardsState = {\n    current: string\n    limitTimestamp: number\n    cards: {[key: string]: Card}\n    templates: {[key: string]: Card}\n    cardHiddenWarning: boolean\n}\n\nexport const refreshCards = createAsyncThunk<Block[], number, {state: RootState}>(\n    'refreshCards',\n    async (cardLimitTimestamp: number, thunkAPI) => {\n        const {cards} = thunkAPI.getState().cards\n        const blocksPromises = []\n\n        for (const card of Object.values(cards)) {\n            if (card.limited && card.updateAt >= cardLimitTimestamp) {\n                blocksPromises.push(client.getBlocksWithBlockID(card.id, card.boardId).then((blocks) => blocks.find((b) => b?.type === 'card')))\n            }\n        }\n        const blocks = await Promise.all(blocksPromises)\n\n        return blocks.filter((b: Block|undefined): boolean => Boolean(b)) as Block[]\n    },\n)\n\nconst limitCard = (isBoardTemplate: boolean, limitTimestamp: number, card: Card): Card => {\n    if (isBoardTemplate) {\n        return card\n    }\n    if (card.updateAt >= limitTimestamp) {\n        return card\n    }\n    return {\n        ...card,\n        fields: {\n            icon: card.fields.icon,\n            properties: {},\n            contentOrder: [],\n        },\n        limited: true,\n    }\n}\n\nconst cardsSlice = createSlice({\n    name: 'cards',\n    initialState: {\n        current: '',\n        limitTimestamp: 0,\n        cards: {},\n        templates: {},\n        cardHiddenWarning: false,\n    } as CardsState,\n    reducers: {\n        setCurrent: (state, action: PayloadAction<string>) => {\n            state.current = action.payload\n        },\n        setLimitTimestamp: (state, action: PayloadAction<{timestamp: number, templates: {[key: string]: Board}}>) => {\n            state.limitTimestamp = action.payload.timestamp\n            for (const card of Object.values(state.cards)) {\n                state.cards[card.id] = limitCard(Boolean(action.payload.templates[card.id]), state.limitTimestamp, card)\n            }\n        },\n        addCard: (state, action: PayloadAction<Card>) => {\n            state.cards[action.payload.id] = action.payload\n        },\n        showCardHiddenWarning: (state, action: PayloadAction<boolean>) => {\n            state.cardHiddenWarning = action.payload\n        },\n        addTemplate: (state: CardsState, action: PayloadAction<Card>) => {\n            state.templates[action.payload.id] = action.payload\n        },\n        updateCards: (state: CardsState, action: PayloadAction<Card[]>) => {\n            for (const card of action.payload) {\n                if (card.deleteAt !== 0) {\n                    delete state.cards[card.id]\n                    delete state.templates[card.id]\n                } else if (card.fields.isTemplate) {\n                    state.templates[card.id] = card\n                } else {\n                    state.cards[card.id] = card\n                }\n            }\n        },\n    },\n    extraReducers: (builder) => {\n        builder.addCase(refreshCards.fulfilled, (state, action) => {\n            for (const block of action.payload) {\n                state.cards[block.id] = block as Card\n            }\n        })\n        builder.addCase(initialReadOnlyLoad.fulfilled, (state, action) => {\n            state.cards = {}\n            state.templates = {}\n            for (const block of action.payload.blocks) {\n                if (block.type === 'card' && block.fields.isTemplate) {\n                    state.templates[block.id] = block as Card\n                } else if (block.type === 'card' && !block.fields.isTemplate) {\n                    state.cards[block.id] = block as Card\n                }\n            }\n        })\n        builder.addCase(initialLoad.fulfilled, (state, action) => {\n            state.limitTimestamp = action.payload.limits?.card_limit_timestamp || 0\n        })\n        builder.addCase(loadBoardData.fulfilled, (state, action) => {\n            state.cards = {}\n            state.templates = {}\n            for (const block of action.payload.blocks) {\n                if (block.type === 'card' && block.fields.isTemplate) {\n                    state.templates[block.id] = block as Card\n                } else if (block.type === 'card' && !block.fields.isTemplate) {\n                    state.cards[block.id] = block as Card\n                }\n            }\n        })\n    },\n})\n\nexport const {updateCards, addCard, addTemplate, setCurrent, setLimitTimestamp, showCardHiddenWarning} = cardsSlice.actions\nexport const {reducer} = cardsSlice\n\nexport const getCards = (state: RootState): {[key: string]: Card} => state.cards.cards\n\nexport const getSortedCards = createSelector(\n    getCards,\n    (cards) => {\n        return Object.values(cards).sort((a, b) => a.title.localeCompare(b.title)) as Card[]\n    },\n)\n\nexport const getTemplates = (state: RootState): {[key: string]: Card} => state.cards.templates\n\nexport const getSortedTemplates = createSelector(\n    getTemplates,\n    (templates) => {\n        return Object.values(templates).sort((a, b) => a.title.localeCompare(b.title)) as Card[]\n    },\n)\n\nexport function getCard(cardId: string): (state: RootState) => Card|undefined {\n    return (state: RootState): Card|undefined => {\n        return getCards(state)[cardId] || getTemplates(state)[cardId]\n    }\n}\n\nexport const getCurrentBoardCards = createSelector(\n    (state: RootState) => state.boards.current,\n    getCards,\n    (boardId, cards) => {\n        return Object.values(cards).filter((c) => c.boardId === boardId) as Card[]\n    },\n)\n\nexport const getCurrentBoardTemplates = createSelector(\n    (state: RootState) => state.boards.current,\n    getTemplates,\n    (boardId, templates) => {\n        return Object.values(templates).filter((c) => c.boardId === boardId) as Card[]\n    },\n)\n\nfunction titleOrCreatedOrder(cardA: Card, cardB: Card) {\n    const aValue = cardA.title\n    const bValue = cardB.title\n\n    if (aValue && bValue) {\n        return aValue.localeCompare(bValue)\n    }\n\n    // Always put untitled cards at the bottom\n    if (aValue && !bValue) {\n        return -1\n    }\n    if (bValue && !aValue) {\n        return 1\n    }\n\n    // If both cards are untitled, use the create date\n    return cardA.createAt - cardB.createAt\n}\n\nfunction manualOrder(activeView: BoardView, cardA: Card, cardB: Card) {\n    const indexA = activeView.fields.cardOrder.indexOf(cardA.id)\n    const indexB = activeView.fields.cardOrder.indexOf(cardB.id)\n\n    if (indexA < 0 && indexB < 0) {\n        return titleOrCreatedOrder(cardA, cardB)\n    } else if (indexA < 0 && indexB >= 0) {\n        // If cardA's order is not defined, put it at the end\n        return 1\n    }\n    return indexA - indexB\n}\n\nfunction sortCards(cards: Card[], lastCommentByCard: {[key: string]: CommentBlock}, board: Board, activeView: BoardView, usersById: {[key: string]: IUser}): Card[] {\n    if (!activeView) {\n        return cards\n    }\n    const {sortOptions} = activeView.fields\n\n    if (sortOptions.length < 1) {\n        Utils.log('Manual sort')\n        return cards.sort((a, b) => manualOrder(activeView, a, b))\n    }\n\n    let sortedCards = cards\n    for (const sortOption of sortOptions) {\n        if (sortOption.propertyId === Constants.titleColumnId) {\n            Utils.log('Sort by title')\n            sortedCards = sortedCards.sort((a, b) => {\n                const result = titleOrCreatedOrder(a, b)\n                return sortOption.reversed ? -result : result\n            })\n        } else {\n            const sortPropertyId = sortOption.propertyId\n            const template = board.cardProperties.find((o) => o.id === sortPropertyId)\n            if (!template) {\n                Utils.logError(`Missing template for property id: ${sortPropertyId}`)\n                return sortedCards\n            }\n            Utils.log(`Sort by property: ${template?.name}`)\n            sortedCards = sortedCards.sort((a, b) => {\n                // Always put cards with no titles at the bottom, regardless of sort\n                let aValue = a.fields.properties[sortPropertyId] || ''\n                let bValue = b.fields.properties[sortPropertyId] || ''\n\n                if (template.type === 'createdBy') {\n                    aValue = usersById[a.createdBy]?.username || ''\n                    bValue = usersById[b.createdBy]?.username || ''\n                } else if (template.type === 'updatedBy') {\n                    aValue = usersById[a.modifiedBy]?.username || ''\n                    bValue = usersById[b.modifiedBy]?.username || ''\n                } else if (template.type === 'date') {\n                    aValue = (aValue === '') ? '' : JSON.parse(aValue as string).from\n                    bValue = (bValue === '') ? '' : JSON.parse(bValue as string).from\n                }\n\n                let result = 0\n                if (template.type === 'number' || template.type === 'date') {\n                    // Always put empty values at the bottom\n                    if (aValue && !bValue) {\n                        return -1\n                    }\n                    if (bValue && !aValue) {\n                        return 1\n                    }\n                    if (!aValue && !bValue) {\n                        return titleOrCreatedOrder(a, b)\n                    }\n\n                    result = Number(aValue) - Number(bValue)\n                } else if (template.type === 'createdTime') {\n                    result = a.createAt - b.createAt\n                } else if (template.type === 'updatedTime') {\n                    const aUpdateAt = Math.max(a.updateAt, lastCommentByCard[a.id]?.updateAt || 0)\n                    const bUpdateAt = Math.max(b.updateAt, lastCommentByCard[b.id]?.updateAt || 0)\n                    result = aUpdateAt - bUpdateAt\n                } else {\n                    // Text-based sort\n\n                    if (aValue.length > 0 && bValue.length <= 0) {\n                        return -1\n                    }\n                    if (bValue.length > 0 && aValue.length <= 0) {\n                        return 1\n                    }\n                    if (aValue.length <= 0 && bValue.length <= 0) {\n                        return titleOrCreatedOrder(a, b)\n                    }\n\n                    if (template.type === 'select' || template.type === 'multiSelect') {\n                        aValue = template.options.find((o) => o.id === (Array.isArray(aValue) ? aValue[0] : aValue))?.value || ''\n                        bValue = template.options.find((o) => o.id === (Array.isArray(bValue) ? bValue[0] : bValue))?.value || ''\n                    }\n\n                    if (template.type === 'multiPerson') {\n                        aValue = Array.isArray(aValue) && aValue.length !== 0 && Object.keys(usersById).length > 0 ? aValue.map((id) => {\n                            if (usersById[id] !== undefined) {\n                                return usersById[id].username\n                            }\n                            return ''\n                        }).toString() : aValue\n\n                        bValue = Array.isArray(bValue) && bValue.length !== 0 && Object.keys(usersById).length > 0 ? bValue.map((id) => {\n                            if (usersById[id] !== undefined) {\n                                return usersById[id].username\n                            }\n                            return ''\n                        }).toString() : bValue\n                    }\n\n                    result = (aValue as string).localeCompare(bValue as string)\n                }\n\n                if (result === 0) {\n                    // In case of \"ties\", use the title order\n                    result = titleOrCreatedOrder(a, b)\n                }\n\n                return sortOption.reversed ? -result : result\n            })\n        }\n    }\n\n    return sortedCards\n}\n\nfunction searchFilterCards(cards: Card[], board: Board, searchTextRaw: string): Card[] {\n    const searchText = searchTextRaw.toLocaleLowerCase()\n    if (!searchText) {\n        return cards.slice()\n    }\n\n    return cards.filter((card: Card) => {\n        const searchTextInCardTitle: boolean = card.title?.toLocaleLowerCase().includes(searchText)\n        if (searchTextInCardTitle) {\n            return true\n        }\n\n        for (const [propertyId, propertyValue] of Object.entries(card.fields.properties)) {\n            // TODO: Refactor to a shared function that returns the display value of a property\n            const propertyTemplate = board.cardProperties.find((o) => o.id === propertyId)\n            if (propertyTemplate && propertyValue) {\n                if (propertyTemplate.type === 'select') {\n                    // Look up the value of the select option\n                    const option = propertyTemplate.options.find((o) => o.id === propertyValue)\n                    if (option?.value.toLowerCase().includes(searchText)) {\n                        return true\n                    }\n                } else if (propertyTemplate.type === 'multiSelect') {\n                    // Look up the value of the select option\n                    const options = (Array.isArray(propertyValue) ? propertyValue : [propertyValue]).map((value) => propertyTemplate.options.find((o) => o.id === value)?.value.toLowerCase())\n                    if (options?.includes(searchText)) {\n                        return true\n                    }\n                } else if (propertyTemplate.type !== 'date' && (propertyValue.toString()).toLowerCase().includes(searchText)) {\n                    return true\n                }\n            }\n        }\n\n        return false\n    })\n}\n\nexport const getCurrentViewCardsSortedFilteredAndGroupedWithoutLimit = createSelector(\n    getCurrentBoardCards,\n    getLastCommentByCard,\n    getCurrentBoard,\n    getCurrentView,\n    getSearchText,\n    getBoardUsers,\n    (cards, lastCommentByCard, board, view, searchText, users) => {\n        if (!view || !board || !users || !cards) {\n            return []\n        }\n        let result = cards.filter((c) => !c.limited)\n        if (view.fields.filter) {\n            result = CardFilter.applyFilterGroup(view.fields.filter, board.cardProperties, result)\n        }\n\n        if (searchText) {\n            result = searchFilterCards(result, board, searchText)\n        }\n        result = sortCards(result, lastCommentByCard, board, view, users)\n        return result\n    },\n)\n\nexport const getCurrentViewCardsSortedFilteredAndGrouped = createSelector(\n    getCurrentViewCardsSortedFilteredAndGroupedWithoutLimit,\n    (cards) => cards.filter((c) => !c.limited),\n)\n\nexport const getCurrentBoardHiddenCardsCount = createSelector(\n    getCurrentBoardCards,\n    (cards) => Object.values(cards).filter((c) => c.limited).length,\n)\n\nexport const getCurrentCard = createSelector(\n    (state: RootState) => state.cards.current,\n    getCards,\n    (current, cards) => cards[current],\n)\n\nexport const getCardLimitTimestamp = (state: RootState): number => state.cards.limitTimestamp\nexport const getCardHiddenWarning = (state: RootState): boolean => state.cards.cardHiddenWarning\n"
  },
  {
    "path": "webapp/src/store/channels.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {createSlice, PayloadAction} from '@reduxjs/toolkit'\n\nimport {RootState} from './index'\n\nexport const ChannelTypeOpen = 'O'\nexport const ChannelTypePrivate = 'P'\nexport const ChannelTypeDirectMessage = 'D'\nexport const ChannelTypeGroupMessage = 'G'\nconst channelTypes = [ChannelTypeOpen, ChannelTypePrivate, ChannelTypeDirectMessage, ChannelTypeGroupMessage]\ntype ChannelType = typeof channelTypes[number]\n\nexport interface Channel {\n    id: string\n    name: string\n    display_name: string\n    type: ChannelType\n}\n\ntype ChannelState = {\n    current: Channel | null\n}\n\nconst channelSlice = createSlice({\n    name: 'channels',\n    initialState: {\n        current: null,\n    } as ChannelState,\n    reducers: {\n        setChannel: (state, action: PayloadAction<Channel>) => {\n            const channel = action.payload\n            if (state.current === channel) {\n                return\n            }\n\n            state.current = channel\n        },\n    },\n})\n\nexport const {setChannel} = channelSlice.actions\nexport const {reducer} = channelSlice\n\nexport const getCurrentChannel = (state: RootState): Channel|null => state.channels.current\n"
  },
  {
    "path": "webapp/src/store/clientConfig.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {createSlice, createAsyncThunk, PayloadAction} from '@reduxjs/toolkit'\n\nimport {ClientConfig} from '../config/clientConfig'\n\nimport {default as client} from '../octoClient'\n\nimport {ShowUsername} from '../utils'\n\nimport {RootState} from './index'\n\nexport const fetchClientConfig = createAsyncThunk(\n    'clientConfig/fetchClientConfig',\n    async () => client.getClientConfig(),\n)\n\nconst clientConfigSlice = createSlice({\n    name: 'config',\n    initialState: {value: {telemetry: false, telemetryid: '', enablePublicSharedBoards: false, teammateNameDisplay: ShowUsername, featureFlags: {}, maxFileSize: 0}} as {value: ClientConfig},\n    reducers: {\n        setClientConfig: (state, action: PayloadAction<ClientConfig>) => {\n            state.value = action.payload\n        },\n    },\n    extraReducers: (builder) => {\n        builder.addCase(fetchClientConfig.fulfilled, (state, action) => {\n            state.value = action.payload || {telemetry: false, telemetryid: '', enablePublicSharedBoards: false, teammateNameDisplay: ShowUsername, featureFlags: {}, maxFileSize: 0}\n        })\n    },\n})\n\nexport const {setClientConfig} = clientConfigSlice.actions\nexport const {reducer} = clientConfigSlice\n\nexport function getClientConfig(state: RootState): ClientConfig {\n    return state.clientConfig.value\n}\n"
  },
  {
    "path": "webapp/src/store/comments.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {createSlice, PayloadAction, createSelector} from '@reduxjs/toolkit'\n\nimport {CommentBlock} from '../blocks/commentBlock'\n\nimport {loadBoardData, initialReadOnlyLoad} from './initialLoad'\n\nimport {RootState} from './index'\n\ntype CommentsState = {\n    comments: {[key: string]: CommentBlock}\n    commentsByCard: {[key: string]: CommentBlock[]}\n}\n\nconst commentsSlice = createSlice({\n    name: 'comments',\n    initialState: {comments: {}, commentsByCard: {}} as CommentsState,\n    reducers: {\n        updateComments: (state, action: PayloadAction<CommentBlock[]>) => {\n            for (const comment of action.payload) {\n                if (comment.deleteAt === 0) {\n                    state.comments[comment.id] = comment\n                    if (!state.commentsByCard[comment.parentId]) {\n                        state.commentsByCard[comment.parentId] = [comment]\n                        return\n                    }\n                    for (let i = 0; i < state.commentsByCard[comment.parentId].length; i++) {\n                        if (state.commentsByCard[comment.parentId][i].id === comment.id) {\n                            state.commentsByCard[comment.parentId][i] = comment\n                            return\n                        }\n                    }\n                    state.commentsByCard[comment.parentId].push(comment)\n                } else {\n                    const parentId = state.comments[comment.id]?.parentId\n                    if (!state.commentsByCard[parentId]) {\n                        delete state.comments[comment.id]\n                        return\n                    }\n                    for (let i = 0; i < state.commentsByCard[parentId].length; i++) {\n                        if (state.commentsByCard[parentId][i].id === comment.id) {\n                            state.commentsByCard[parentId].splice(i, 1)\n                        }\n                    }\n                    delete state.comments[comment.id]\n                }\n            }\n        },\n    },\n    extraReducers: (builder) => {\n        builder.addCase(initialReadOnlyLoad.fulfilled, (state, action) => {\n            state.comments = {}\n            state.commentsByCard = {}\n            for (const block of action.payload.blocks) {\n                if (block.type === 'comment') {\n                    state.comments[block.id] = block as CommentBlock\n                    state.commentsByCard[block.parentId] = state.commentsByCard[block.parentId] || []\n                    state.commentsByCard[block.parentId].push(block as CommentBlock)\n                }\n            }\n            Object.values(state.commentsByCard).forEach((comment) => comment.sort((a, b) => a.createAt - b.createAt))\n        })\n        builder.addCase(loadBoardData.fulfilled, (state, action) => {\n            state.comments = {}\n            state.commentsByCard = {}\n            for (const block of action.payload.blocks) {\n                if (block.type === 'comment') {\n                    state.comments[block.id] = block as CommentBlock\n                    state.commentsByCard[block.parentId] = state.commentsByCard[block.parentId] || []\n                    state.commentsByCard[block.parentId].push(block as CommentBlock)\n                }\n            }\n            Object.values(state.commentsByCard).forEach((comment) => comment.sort((a, b) => a.createAt - b.createAt))\n        })\n    },\n})\n\nexport const {updateComments} = commentsSlice.actions\nexport const {reducer} = commentsSlice\n\nexport function getCardComments(cardId: string): (state: RootState) => CommentBlock[] {\n    return (state: RootState): CommentBlock[] => {\n        return (state.comments?.commentsByCard && state.comments.commentsByCard[cardId]) || []\n    }\n}\n\nexport function getLastCardComment(cardId: string): (state: RootState) => CommentBlock|undefined {\n    return (state: RootState): CommentBlock|undefined => {\n        const comments = state.comments?.commentsByCard && state.comments.commentsByCard[cardId]\n        return comments?.[comments?.length - 1]\n    }\n}\n\nexport const getLastCommentByCard = createSelector(\n    (state: RootState) => state.comments?.commentsByCard || null,\n    (commentsByCard: {[key: string]: CommentBlock[]}|null): {[key: string]: CommentBlock} => {\n        const lastCommentByCard: {[key: string]: CommentBlock} = {}\n        Object.keys(commentsByCard || {}).forEach((cardId) => {\n            if (commentsByCard && commentsByCard[cardId]) {\n                const comments = commentsByCard[cardId]\n                lastCommentByCard[cardId] = comments?.[comments?.length - 1]\n            }\n        })\n        return lastCommentByCard\n    },\n)\n"
  },
  {
    "path": "webapp/src/store/contents.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {createSlice, PayloadAction, createSelector} from '@reduxjs/toolkit'\n\nimport {ContentBlock} from '../blocks/contentBlock'\n\nimport {getCards, getTemplates} from './cards'\nimport {loadBoardData, initialReadOnlyLoad} from './initialLoad'\n\nimport {RootState} from './index'\n\ntype ContentsState = {\n    contents: {[key: string]: ContentBlock}\n    contentsByCard: {[key: string]: ContentBlock[]}\n}\n\nconst contentsSlice = createSlice({\n    name: 'contents',\n    initialState: {contents: {}, contentsByCard: {}} as ContentsState,\n    reducers: {\n        updateContents: (state, action: PayloadAction<ContentBlock[]>) => {\n            for (const content of action.payload) {\n                if (content.deleteAt === 0) {\n                    let existsInParent = false\n                    state.contents[content.id] = content\n                    if (!state.contentsByCard[content.parentId]) {\n                        state.contentsByCard[content.parentId] = [content]\n                        return\n                    }\n                    for (let i = 0; i < state.contentsByCard[content.parentId].length; i++) {\n                        if (state.contentsByCard[content.parentId][i].id === content.id) {\n                            state.contentsByCard[content.parentId][i] = content\n                            existsInParent = true\n                            break\n                        }\n                    }\n                    if (!existsInParent) {\n                        state.contentsByCard[content.parentId].push(content)\n                    }\n                } else {\n                    const parentId = state.contents[content.id]?.parentId\n                    if (!state.contentsByCard[parentId]) {\n                        delete state.contents[content.id]\n                        return\n                    }\n                    for (let i = 0; i < state.contentsByCard[parentId].length; i++) {\n                        if (state.contentsByCard[parentId][i].id === content.id) {\n                            state.contentsByCard[parentId].splice(i, 1)\n                        }\n                    }\n                    delete state.contents[content.id]\n                }\n            }\n        },\n    },\n    extraReducers: (builder) => {\n        builder.addCase(initialReadOnlyLoad.fulfilled, (state, action) => {\n            state.contents = {}\n            state.contentsByCard = {}\n            for (const block of action.payload.blocks) {\n                if (block.type !== 'board' && block.type !== 'view' && block.type !== 'comment') {\n                    state.contents[block.id] = block as ContentBlock\n                    state.contentsByCard[block.parentId] = state.contentsByCard[block.parentId] || []\n                    state.contentsByCard[block.parentId].push(block as ContentBlock)\n                    state.contentsByCard[block.parentId].sort((a, b) => a.createAt - b.createAt)\n                }\n            }\n        })\n        builder.addCase(loadBoardData.fulfilled, (state, action) => {\n            state.contents = {}\n            state.contentsByCard = {}\n            for (const block of action.payload.blocks) {\n                if (block.type !== 'board' && block.type !== 'view' && block.type !== 'comment') {\n                    state.contents[block.id] = block as ContentBlock\n                    state.contentsByCard[block.parentId] = state.contentsByCard[block.parentId] || []\n                    state.contentsByCard[block.parentId].push(block as ContentBlock)\n                    state.contentsByCard[block.parentId].sort((a, b) => a.createAt - b.createAt)\n                }\n            }\n        })\n    },\n})\n\nexport const {updateContents} = contentsSlice.actions\nexport const {reducer} = contentsSlice\n\nexport const getContentsById = (state: RootState): {[key: string]: ContentBlock} => state.contents.contents\n\nexport const getContents = createSelector(\n    getContentsById,\n    (contents) => Object.values(contents),\n)\n\nexport function getCardContents(cardId: string): (state: RootState) => Array<ContentBlock|ContentBlock[]> {\n    return createSelector(\n        (state: RootState) => (state.contents?.contentsByCard && state.contents.contentsByCard[cardId]) || [],\n        (state: RootState) => getCards(state)[cardId]?.fields?.contentOrder || getTemplates(state)[cardId]?.fields?.contentOrder,\n        (contents, contentOrder): Array<ContentBlock|ContentBlock[]> => {\n            const result: Array<ContentBlock|ContentBlock[]> = []\n            if (!contents) {\n                return []\n            }\n            if (contentOrder) {\n                for (const contentId of contentOrder) {\n                    if (typeof contentId === 'string') {\n                        const content = contents.find((c) => c.id === contentId)\n                        if (content) {\n                            result.push(content)\n                        }\n                    } else if (typeof contentId === 'object' && contentId) {\n                        const subResult: ContentBlock[] = []\n                        for (const subContentId of contentId) {\n                            if (typeof subContentId === 'string') {\n                                const subContent = contents.find((c) => c.id === subContentId)\n                                if (subContent) {\n                                    subResult.push(subContent)\n                                }\n                            }\n                        }\n                        result.push(subResult)\n                    }\n                }\n            }\n            return result\n        },\n    )\n}\n\nexport function getLastCardContent(cardId: string): (state: RootState) => ContentBlock|undefined {\n    return (state: RootState): ContentBlock|undefined => {\n        const contents = state.contents?.contentsByCard && state.contents?.contentsByCard[cardId]\n        return contents?.[contents?.length - 1]\n    }\n}\n"
  },
  {
    "path": "webapp/src/store/globalError.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {createSlice, PayloadAction} from '@reduxjs/toolkit'\n\nimport {initialLoad, initialReadOnlyLoad} from './initialLoad'\n\nimport {RootState} from './index'\n\nconst globalErrorSlice = createSlice({\n    name: 'globalError',\n    initialState: {value: ''} as {value: string},\n    reducers: {\n        setGlobalError: (state, action: PayloadAction<string>) => {\n            state.value = action.payload\n        },\n    },\n    extraReducers: (builder) => {\n        builder.addCase(initialReadOnlyLoad.rejected, (state, action) => {\n            state.value = action.error.message || ''\n        })\n        builder.addCase(initialLoad.rejected, (state, action) => {\n            state.value = action.error.message || ''\n        })\n    },\n})\n\nexport const {setGlobalError} = globalErrorSlice.actions\nexport const {reducer} = globalErrorSlice\n\nexport const getGlobalError = (state: RootState): string => state.globalError.value\n"
  },
  {
    "path": "webapp/src/store/globalTemplates.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {createSlice, createAsyncThunk} from '@reduxjs/toolkit'\n\nimport {default as client} from '../octoClient'\nimport {Board} from '../blocks/board'\n\nimport {Constants} from '../constants'\n\nimport {RootState} from './index'\n\nexport const fetchGlobalTemplates = createAsyncThunk(\n    'globalTemplates/fetch',\n    async () => {\n        const templates = await client.getTeamTemplates(Constants.globalTeamId)\n        return templates.sort((a, b) => a.title.localeCompare(b.title))\n    },\n)\n\nconst globalTemplatesSlice = createSlice({\n    name: 'globalTemplates',\n    initialState: {value: []} as {value: Board[]},\n    reducers: {},\n    extraReducers: (builder) => {\n        builder.addCase(fetchGlobalTemplates.fulfilled, (state, action) => {\n            state.value = action.payload || []\n        })\n    },\n})\n\nexport const {reducer} = globalTemplatesSlice\n\nexport function getGlobalTemplates(state: RootState): Board[] {\n    return state.globalTemplates.value\n}\n"
  },
  {
    "path": "webapp/src/store/hooks.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {TypedUseSelectorHook, useDispatch, useSelector} from 'react-redux'\n\nimport type {RootState, AppDispatch} from '.'\n\nexport const useAppDispatch = () => useDispatch<AppDispatch>()\nexport const useAppSelector: TypedUseSelectorHook<RootState> = useSelector\n"
  },
  {
    "path": "webapp/src/store/index.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {configureStore} from '@reduxjs/toolkit'\n\nimport {reducer as usersReducer} from './users'\nimport {reducer as teamsReducer} from './teams'\nimport {reducer as channelsReducer} from './channels'\nimport {reducer as languageReducer} from './language'\nimport {reducer as globalTemplatesReducer} from './globalTemplates'\nimport {reducer as boardsReducer} from './boards'\nimport {reducer as viewsReducer} from './views'\nimport {reducer as cardsReducer} from './cards'\nimport {reducer as contentsReducer} from './contents'\nimport {reducer as commentsReducer} from './comments'\nimport {reducer as searchTextReducer} from './searchText'\nimport {reducer as globalErrorReducer} from './globalError'\nimport {reducer as clientConfigReducer} from './clientConfig'\nimport {reducer as sidebarReducer} from './sidebar'\nimport {reducer as limitsReducer} from './limits'\nimport {reducer as attachmentsReducer} from './attachments'\n\nconst store = configureStore({\n    reducer: {\n        users: usersReducer,\n        teams: teamsReducer,\n        channels: channelsReducer,\n        language: languageReducer,\n        globalTemplates: globalTemplatesReducer,\n        boards: boardsReducer,\n        views: viewsReducer,\n        cards: cardsReducer,\n        contents: contentsReducer,\n        comments: commentsReducer,\n        searchText: searchTextReducer,\n        globalError: globalErrorReducer,\n        clientConfig: clientConfigReducer,\n        sidebar: sidebarReducer,\n        limits: limitsReducer,\n        attachments: attachmentsReducer,\n    },\n})\n\nexport default store\n\n// Infer the `RootState` and `AppDispatch` types from the store itself\nexport type RootState = ReturnType<typeof store.getState>\n\n// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}\nexport type AppDispatch = typeof store.dispatch\n"
  },
  {
    "path": "webapp/src/store/initialLoad.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {createAsyncThunk, createSelector} from '@reduxjs/toolkit'\n\nimport {default as client} from '../octoClient'\nimport {Subscription} from '../wsclient'\nimport {ErrorId} from '../errors'\n\nimport {RootState} from './index'\n\nexport const initialLoad = createAsyncThunk(\n    'initialLoad',\n    async () => {\n        const [me, myConfig, team, teams, boards, boardsMemberships, boardTemplates, limits] = await Promise.all([\n            client.getMe(),\n            client.getMyConfig(),\n            client.getTeam(),\n            client.getTeams(),\n            client.getBoards(),\n            client.getMyBoardMemberships(),\n            client.getTeamTemplates(),\n            client.getBoardsCloudLimits(),\n        ])\n\n        // if no me, normally user not logged in\n        if (!me) {\n            throw new Error(ErrorId.NotLoggedIn)\n        }\n\n        // if no team, either bad id, or user doesn't have access\n        if (!team) {\n            throw new Error(ErrorId.TeamUndefined)\n        }\n        return {\n            team,\n            teams,\n            boards,\n            boardsMemberships,\n            boardTemplates,\n            limits,\n            myConfig,\n        }\n    },\n)\n\nexport const initialReadOnlyLoad = createAsyncThunk(\n    'initialReadOnlyLoad',\n    async (boardId: string) => {\n        const [board, blocks] = await Promise.all([\n            client.getBoard(boardId),\n            client.getAllBlocks(boardId),\n        ])\n\n        // if no board, read_token invalid\n        if (!board) {\n            throw new Error(ErrorId.InvalidReadOnlyBoard)\n        }\n\n        return {board, blocks}\n    },\n)\n\nexport const loadBoardData = createAsyncThunk(\n    'loadBoardData',\n    async (boardID: string) => {\n        const blocks = await client.getAllBlocks(boardID)\n        return {\n            blocks,\n        }\n    },\n)\n\nexport const loadBoards = createAsyncThunk(\n    'loadBoards',\n    async () => {\n        const boards = await client.getBoards()\n        return {\n            boards,\n        }\n    },\n)\n\nexport const loadMyBoardsMemberships = createAsyncThunk(\n    'loadMyBoardsMemberships',\n    async () => {\n        const boardsMemberships = await client.getMyBoardMemberships()\n        return {\n            boardsMemberships,\n        }\n    },\n)\n\nexport const getUserBlockSubscriptions = (state: RootState): Subscription[] => state.users.blockSubscriptions\n\nexport const getUserBlockSubscriptionList = createSelector(\n    getUserBlockSubscriptions,\n    (subscriptions) => subscriptions,\n)\n"
  },
  {
    "path": "webapp/src/store/language.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {createSlice, createAsyncThunk, PayloadAction} from '@reduxjs/toolkit'\n\nimport {getCurrentLanguage, storeLanguage as i18nStoreLanguage} from '../i18n'\n\nimport {RootState} from './index'\n\nexport const fetchLanguage = createAsyncThunk(\n    'language/fetch',\n    async () => getCurrentLanguage(),\n)\n\nexport const storeLanguage = createAsyncThunk(\n    'language/store',\n    (lang: string) => {\n        i18nStoreLanguage(lang)\n        return lang\n    },\n)\n\nconst languageSlice = createSlice({\n    name: 'language',\n    initialState: {value: 'en'} as {value: string},\n    reducers: {\n        setLanguage: (state, action: PayloadAction<string>) => {\n            state.value = action.payload\n        },\n    },\n    extraReducers: (builder) => {\n        builder.addCase(fetchLanguage.fulfilled, (state, action) => {\n            state.value = action.payload\n        })\n        builder.addCase(storeLanguage.fulfilled, (state, action) => {\n            state.value = action.payload\n        })\n    },\n})\n\nexport const {reducer} = languageSlice\n\nexport function getLanguage(state: RootState): string {\n    return state.language.value\n}\n"
  },
  {
    "path": "webapp/src/store/limits.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {createSlice, PayloadAction} from '@reduxjs/toolkit'\n\nimport {BoardsCloudLimits} from '../boardsCloudLimits'\n\nimport {initialLoad} from './initialLoad'\n\nimport {RootState} from './index'\n\ntype LimitsState = {\n    limits: BoardsCloudLimits\n}\n\nconst defaultLimits = {\n    cards: 0,\n    used_cards: 0,\n    card_limit_timestamp: 0,\n    views: 0,\n}\n\nconst initialState = {\n    limits: defaultLimits,\n} as LimitsState\n\nconst limitsSlice = createSlice({\n    name: 'limits',\n    initialState,\n    reducers: {\n        setLimits: (state, action: PayloadAction<BoardsCloudLimits>) => {\n            state.limits = action.payload\n        },\n        setCardLimitTimestamp: (state, action: PayloadAction<number>) => {\n            state.limits.card_limit_timestamp = action.payload\n        },\n    },\n    extraReducers: (builder) => {\n        builder.addCase(initialLoad.fulfilled, (state, action) => {\n            state.limits = action.payload.limits || defaultLimits\n        })\n    },\n})\n\nexport const {reducer} = limitsSlice\nexport const {setCardLimitTimestamp} = limitsSlice.actions\n\nexport const getLimits = (state: RootState): BoardsCloudLimits | undefined => state.limits.limits\nexport const getCardLimitTimestamp = (state: RootState): number => state.limits.limits.card_limit_timestamp\n"
  },
  {
    "path": "webapp/src/store/searchText.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {createSlice, PayloadAction} from '@reduxjs/toolkit'\n\nimport {RootState} from './index'\n\nconst searchTextSlice = createSlice({\n    name: 'searchText',\n    initialState: {value: ''} as {value: string},\n    reducers: {\n        setSearchText: (state, action: PayloadAction<string>) => {\n            state.value = action.payload\n        },\n    },\n})\n\nexport const {setSearchText} = searchTextSlice.actions\nexport const {reducer} = searchTextSlice\n\nexport function getSearchText(state: RootState): string {\n    return state.searchText.value\n}\n"
  },
  {
    "path": "webapp/src/store/sidebar.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {createAsyncThunk, createSelector, createSlice, PayloadAction} from '@reduxjs/toolkit'\n\nimport {default as client} from '../octoClient'\n\nimport {Utils} from '../utils'\n\nimport {RootState} from './index'\n\nexport type CategoryType = 'system' | 'custom'\n\ninterface Category {\n    id: string\n    name: string\n    userID: string\n    teamID: string\n    createAt: number\n    updateAt: number\n    deleteAt: number\n    collapsed: boolean\n    sortOrder: number\n    type: CategoryType\n    isNew: boolean\n}\n\ninterface CategoryBoardMetadata {\n    boardID: string\n    hidden: boolean\n}\n\ninterface CategoryBoards extends Category {\n    boardMetadata: CategoryBoardMetadata[]\n}\n\ninterface BoardCategoryWebsocketData {\n    boardID: string\n    categoryID: string\n    hidden: boolean\n}\n\ninterface CategoryBoardsReorderData {\n    categoryID: string\n    boardsMetadata: CategoryBoardMetadata[]\n}\n\nexport const DefaultCategory: CategoryBoards = {\n    id: '',\n    name: 'Boards',\n} as CategoryBoards\n\nexport const fetchSidebarCategories = createAsyncThunk(\n    'sidebarCategories/fetch',\n    async (teamID: string) => {\n        return client.getSidebarCategories(teamID)\n    },\n)\n\ntype Sidebar = {\n    categoryAttributes: CategoryBoards[]\n    hiddenBoardIDs: string[]\n}\n\nconst sidebarSlice = createSlice({\n    name: 'sidebar',\n    initialState: {categoryAttributes: [], hiddenBoardIDs: []} as Sidebar,\n    reducers: {\n        updateCategories: (state, action: PayloadAction<Category[]>) => {\n            action.payload.forEach((updatedCategory) => {\n                const index = state.categoryAttributes.findIndex((c) => c.id === updatedCategory.id)\n\n                // when new category got created,\n                if (index === -1) {\n                    // new categories should always show up on the top\n                    state.categoryAttributes.unshift({\n                        ...updatedCategory,\n                        boardMetadata: [],\n                        isNew: true,\n                    })\n                } else if (updatedCategory.deleteAt) {\n                    // when category is deleted\n                    state.categoryAttributes.splice(index, 1)\n                } else {\n                    // else all, update the category\n                    state.categoryAttributes[index] = {\n                        ...state.categoryAttributes[index],\n                        name: updatedCategory.name,\n                        updateAt: updatedCategory.updateAt,\n                        isNew: false,\n                    }\n                }\n            })\n        },\n        updateBoardCategories: (state, action: PayloadAction<BoardCategoryWebsocketData[]>) => {\n            const updatedCategoryAttributes: CategoryBoards[] = []\n            let updatedHiddenBoardIDs = state.hiddenBoardIDs\n\n            action.payload.forEach((boardCategory) => {\n                for (let i = 0; i < state.categoryAttributes.length; i++) {\n                    const categoryAttribute = state.categoryAttributes[i]\n\n                    if (categoryAttribute.id === boardCategory.categoryID) {\n                        const categoryBoardMetadataIndex = categoryAttribute.boardMetadata.findIndex((boardMetadata) => boardMetadata.boardID === boardCategory.boardID)\n                        if (categoryBoardMetadataIndex >= 0) {\n                            categoryAttribute.boardMetadata[categoryBoardMetadataIndex] = {\n                                ...categoryAttribute.boardMetadata[categoryBoardMetadataIndex],\n                                hidden: boardCategory.hidden,\n                            }\n                        } else {\n                            categoryAttribute.boardMetadata.unshift({boardID: boardCategory.boardID, hidden: boardCategory.hidden})\n                            categoryAttribute.isNew = false\n                        }\n                    } else {\n                        // remove the board from other categories\n                        categoryAttribute.boardMetadata = categoryAttribute.boardMetadata.filter((metadata) => metadata.boardID !== boardCategory.boardID)\n                    }\n\n                    updatedCategoryAttributes[i] = categoryAttribute\n\n                    if (boardCategory.hidden) {\n                        if (updatedHiddenBoardIDs.indexOf(boardCategory.boardID) < 0) {\n                            updatedHiddenBoardIDs.push(boardCategory.boardID)\n                        }\n                    } else {\n                        updatedHiddenBoardIDs = updatedHiddenBoardIDs.filter((hiddenBoardID) => hiddenBoardID !== boardCategory.boardID)\n                    }\n                }\n            })\n\n            if (updatedCategoryAttributes.length > 0) {\n                state.categoryAttributes = updatedCategoryAttributes\n            }\n            state.hiddenBoardIDs = updatedHiddenBoardIDs\n        },\n        updateCategoryOrder: (state, action: PayloadAction<string[]>) => {\n            if (action.payload.length === 0) {\n                return\n            }\n\n            const categoryById = new Map<string, CategoryBoards>()\n            state.categoryAttributes.forEach((categoryBoards: CategoryBoards) => categoryById.set(categoryBoards.id, categoryBoards))\n\n            const newOrderedCategories: CategoryBoards[] = []\n            action.payload.forEach((categoryId) => {\n                const category = categoryById.get(categoryId)\n                if (!category) {\n                    Utils.logError('Category ID from updated category order not found in store. CategoryID: ' + categoryId)\n                    return\n                }\n                newOrderedCategories.push(category)\n            })\n\n            state.categoryAttributes = newOrderedCategories\n        },\n        updateCategoryBoardsOrder: (state, action: PayloadAction<CategoryBoardsReorderData>) => {\n            if (action.payload.boardsMetadata.length === 0) {\n                return\n            }\n\n            const categoryIndex = state.categoryAttributes.findIndex((categoryBoards) => categoryBoards.id === action.payload.categoryID)\n            if (categoryIndex < 0) {\n                Utils.logError('Category ID from updated category boards order not found in store. CategoryID: ' + action.payload.categoryID)\n                return\n            }\n\n            const category = state.categoryAttributes[categoryIndex]\n            const updatedCategory: CategoryBoards = {\n                ...category,\n                boardMetadata: action.payload.boardsMetadata,\n                isNew: false,\n            }\n\n            // creating a new reference of array so redux knows it changed\n            state.categoryAttributes = state.categoryAttributes.map((original, i) => (i === categoryIndex ? updatedCategory : original))\n        },\n    },\n    extraReducers: (builder) => {\n        builder.addCase(fetchSidebarCategories.fulfilled, (state, action) => {\n            state.categoryAttributes = action.payload || []\n            state.hiddenBoardIDs = state.categoryAttributes.flatMap(\n                (ca) => {\n                    return ca.boardMetadata.reduce((collector, m) => {\n                        if (m.hidden) {\n                            collector.push(m.boardID)\n                        }\n\n                        return collector\n                    }, [] as string[])\n                },\n            )\n        })\n    },\n})\n\nexport const getSidebarCategories = createSelector(\n    (state: RootState): CategoryBoards[] => state.sidebar.categoryAttributes,\n    (sidebarCategories) => sidebarCategories,\n)\n\nexport const getHiddenBoardIDs = (state: RootState): string[] => state.sidebar.hiddenBoardIDs\n\nexport function getCategoryOfBoard(boardID: string): (state: RootState) => CategoryBoards | undefined {\n    return createSelector(\n        (state: RootState): CategoryBoards[] => state.sidebar.categoryAttributes,\n        (sidebarCategories) => sidebarCategories.find((category) => category.boardMetadata.findIndex((m) => m.boardID === boardID) >= 0),\n    )\n}\n\nexport const {reducer} = sidebarSlice\n\nexport const {updateCategories, updateBoardCategories, updateCategoryOrder, updateCategoryBoardsOrder} = sidebarSlice.actions\n\nexport {Category, CategoryBoards, BoardCategoryWebsocketData, CategoryBoardsReorderData, CategoryBoardMetadata}\n\n"
  },
  {
    "path": "webapp/src/store/teams.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {createAsyncThunk, createSlice, PayloadAction} from '@reduxjs/toolkit'\n\nimport octoClient from '../octoClient'\n\nimport {Utils} from '../utils'\n\nimport {initialLoad} from './initialLoad'\n\nimport {RootState} from './index'\n\nexport interface Team {\n    id: string\n    title: string\n    signupToken: string\n    modifiedBy: string\n    updateAt: number\n}\n\nexport const fetchTeams = createAsyncThunk(\n    'team/fetch',\n    async () => octoClient.getTeams(),\n)\n\nexport const regenerateSignupToken = createAsyncThunk(\n    'team/regenerateSignupToken',\n    async () => octoClient.regenerateTeamSignupToken(),\n)\n\nexport const refreshCurrentTeam = createAsyncThunk(\n    'team/refreshCurrentTeam',\n    async () => octoClient.getTeam(),\n)\n\ntype TeamState = {\n    currentId: string\n    current: Team | null\n    allTeams: Team[]\n}\n\nconst teamSlice = createSlice({\n    name: 'teams',\n    initialState: {\n        current: null,\n        currentId: '',\n        allTeams: [],\n    } as TeamState,\n    reducers: {\n        setTeam: (state, action: PayloadAction<string>) => {\n            const teamID = action.payload\n            state.currentId = teamID\n            const team = state.allTeams.find((t) => t.id === teamID)\n            if (!team) {\n                Utils.log(`Unable to find team in store. TeamID: ${teamID}`)\n                return\n            }\n\n            if (state.current === team) {\n                return\n            }\n\n            state.current = team\n        },\n    },\n    extraReducers: (builder) => {\n        builder.addCase(initialLoad.fulfilled, (state, action) => {\n            state.current = action.payload.team\n            state.allTeams = action.payload.teams\n            state.allTeams.sort((a: Team, b: Team) => (a.title < b.title ? -1 : 1))\n        })\n        builder.addCase(fetchTeams.fulfilled, (state, action) => {\n            state.allTeams = action.payload\n            state.allTeams.sort((a: Team, b: Team) => (a.title < b.title ? -1 : 1))\n        })\n        builder.addCase(refreshCurrentTeam.fulfilled, (state, action) => {\n            state.current = action.payload\n        })\n    },\n})\n\nexport const {setTeam} = teamSlice.actions\nexport const {reducer} = teamSlice\n\nexport const getCurrentTeamId = (state: RootState): string => state.teams.currentId\nexport const getCurrentTeam = (state: RootState): Team|null => state.teams.current\nexport const getFirstTeam = (state: RootState): Team|null => state.teams.allTeams[0]\nexport const getAllTeams = (state: RootState): Team[] => state.teams.allTeams\n"
  },
  {
    "path": "webapp/src/store/users.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {createSlice, createAsyncThunk, PayloadAction, createSelector} from '@reduxjs/toolkit'\n\nimport {default as client} from '../octoClient'\nimport {IUser, parseUserProps, UserPreference} from '../user'\n\nimport {Subscription} from '../wsclient'\n\nimport {initialLoad} from './initialLoad'\n\nimport {RootState} from './index'\n\nexport const fetchMe = createAsyncThunk(\n    'users/fetchMe',\n    async () => {\n        const [me, myConfig] = await Promise.all([\n            client.getMe(),\n            client.getMyConfig(),\n        ])\n        return {me, myConfig}\n    },\n)\n\nexport const versionProperty = 'version72MessageCanceled'\n\ntype UsersStatus = {\n    me: IUser|null\n    boardUsers: {[key: string]: IUser}\n    loggedIn: boolean|null\n    blockSubscriptions: Subscription[]\n    myConfig: Record<string, UserPreference>\n}\n\nexport const fetchUserBlockSubscriptions = createAsyncThunk(\n    'user/blockSubscriptions',\n    async () => ([]),\n)\n\nconst initialState = {\n    me: null,\n    boardUsers: {},\n    loggedIn: null,\n    userWorkspaces: [],\n    blockSubscriptions: [],\n    myConfig: {},\n} as UsersStatus\n\nconst usersSlice = createSlice({\n    name: 'users',\n    initialState,\n    reducers: {\n        setMe: (state, action: PayloadAction<IUser|null>) => {\n            state.me = action.payload\n            state.loggedIn = Boolean(state.me)\n        },\n        setBoardUsers: (state, action: PayloadAction<IUser[]>) => {\n            state.boardUsers = action.payload.reduce((acc: {[key: string]: IUser}, user: IUser) => {\n                acc[user.id] = user\n                return acc\n            }, {})\n        },\n        addBoardUsers: (state, action: PayloadAction<IUser[]>) => {\n            action.payload.forEach((user: IUser) => {\n                state.boardUsers[user.id] = user\n            })\n        },\n        removeBoardUsersById: (state, action: PayloadAction<string[]>) => {\n            action.payload.forEach((userId: string) => {\n                delete state.boardUsers[userId]\n            })\n        },\n        followBlock: (state, action: PayloadAction<Subscription>) => {\n            state.blockSubscriptions.push(action.payload)\n        },\n        unfollowBlock: (state, action: PayloadAction<Subscription>) => {\n            const oldSubscriptions = state.blockSubscriptions\n            state.blockSubscriptions = oldSubscriptions.filter((subscription) => subscription.blockId !== action.payload.blockId)\n        },\n        patchProps: (state, action: PayloadAction<UserPreference[]>) => {\n            state.myConfig = parseUserProps(action.payload)\n        },\n    },\n    extraReducers: (builder) => {\n        builder.addCase(fetchMe.fulfilled, (state, action) => {\n            state.me = action.payload.me || null\n            state.loggedIn = Boolean(state.me)\n            if (action.payload.myConfig) {\n                state.myConfig = parseUserProps(action.payload.myConfig)\n            }\n        })\n        builder.addCase(fetchMe.rejected, (state) => {\n            state.me = null\n            state.loggedIn = false\n            state.myConfig = {}\n        })\n\n        // TODO: change this when the initial load is complete\n        // builder.addCase(initialLoad.fulfilled, (state, action) => {\n        //     state.boardUsers = action.payload.boardUsers.reduce((acc: {[key: string]: IUser}, user: IUser) => {\n        //         acc[user.id] = user\n        //         return acc\n        //     }, {})\n        // })\n\n        builder.addCase(fetchUserBlockSubscriptions.fulfilled, (state, action) => {\n            state.blockSubscriptions = action.payload\n        })\n\n        builder.addCase(initialLoad.fulfilled, (state, action) => {\n            if (action.payload.myConfig) {\n                state.myConfig = parseUserProps(action.payload.myConfig)\n            }\n        })\n    },\n})\n\nexport const {setMe, setBoardUsers, removeBoardUsersById, addBoardUsers, followBlock, unfollowBlock, patchProps} = usersSlice.actions\nexport const {reducer} = usersSlice\n\nexport const getMe = (state: RootState): IUser|null => state.users.me\nexport const getLoggedIn = (state: RootState): boolean|null => state.users.loggedIn\nexport const getBoardUsers = (state: RootState): {[key: string]: IUser} => state.users.boardUsers\nexport const getMyConfig = (state: RootState): Record<string, UserPreference> => state.users.myConfig || {} as Record<string, UserPreference>\n\nexport const getBoardUsersList = createSelector(\n    getBoardUsers,\n    (boardUsers) => Object.values(boardUsers).sort((a, b) => a.username.localeCompare(b.username)),\n)\n\nexport const getUser = (userId: string): (state: RootState) => IUser|undefined => {\n    return (state: RootState): IUser|undefined => {\n        const users = getBoardUsers(state)\n        return users[userId]\n    }\n}\n\nexport const getOnboardingTourStarted = createSelector(\n    getMyConfig,\n    (myConfig): boolean => {\n        if (!myConfig) {\n            return false\n        }\n\n        return Boolean(myConfig.onboardingTourStarted?.value)\n    },\n)\n\nexport const getOnboardingTourStep = createSelector(\n    getMyConfig,\n    (myConfig): string => {\n        if (!myConfig) {\n            return ''\n        }\n\n        return myConfig.onboardingTourStep?.value\n    },\n)\n\nexport const getOnboardingTourCategory = createSelector(\n    getMyConfig,\n    (myConfig): string => (myConfig.tourCategory ? myConfig.tourCategory.value : ''),\n)\n\nexport const getVersionMessageCanceled = createSelector(\n    getMe,\n    getMyConfig,\n    (me, myConfig): boolean => {\n        if (versionProperty && me) {\n            if (me.id === 'single-user') {\n                return true\n            }\n            return Boolean(myConfig[versionProperty]?.value)\n        }\n        return true\n    },\n)\n\nexport const getCardLimitSnoozeUntil = createSelector(\n    getMyConfig,\n    (myConfig): number => {\n        if (!myConfig) {\n            return 0\n        }\n        try {\n            return parseInt(myConfig.cardLimitSnoozeUntil?.value || '0', 10)\n        } catch (_) {\n            return 0\n        }\n    },\n)\n\nexport const getCardHiddenWarningSnoozeUntil = createSelector(\n    getMyConfig,\n    (myConfig): number => {\n        if (!myConfig) {\n            return 0\n        }\n        try {\n            return parseInt(myConfig.cardHiddenWarningSnoozeUntil?.value || 0, 10)\n        } catch (_) {\n            return 0\n        }\n    },\n)\n"
  },
  {
    "path": "webapp/src/store/views.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {createSlice, PayloadAction, createSelector} from '@reduxjs/toolkit'\nimport isEqual from 'lodash/isEqual'\n\nimport {BoardView, createBoardView} from '../blocks/boardView'\nimport {Utils} from '../utils'\n\nimport {initialReadOnlyLoad, loadBoardData} from './initialLoad'\nimport {getCurrentBoard} from './boards'\n\nimport {RootState} from './index'\n\ntype ViewsState = {\n    current: string\n    views: {[key: string]: BoardView}\n}\n\n// This update ensure that we are not regenerating that fields all the time\nconst smartViewUpdate = (oldView: BoardView, newView: BoardView) => {\n    if (!oldView) {\n        return newView\n    }\n\n    if (isEqual(newView.fields.sortOptions, oldView.fields.sortOptions)) {\n        newView.fields.sortOptions = oldView.fields.sortOptions\n    }\n    if (isEqual(newView.fields.visiblePropertyIds, oldView.fields.visiblePropertyIds)) {\n        newView.fields.visiblePropertyIds = oldView.fields.visiblePropertyIds\n    }\n    if (isEqual(newView.fields.visibleOptionIds, oldView.fields.visibleOptionIds)) {\n        newView.fields.visibleOptionIds = oldView.fields.visibleOptionIds\n    }\n    if (isEqual(newView.fields.hiddenOptionIds, oldView.fields.hiddenOptionIds)) {\n        newView.fields.hiddenOptionIds = oldView.fields.hiddenOptionIds\n    }\n    if (isEqual(newView.fields.collapsedOptionIds, oldView.fields.collapsedOptionIds)) {\n        newView.fields.collapsedOptionIds = oldView.fields.collapsedOptionIds\n    }\n    if (isEqual(newView.fields.filter, oldView.fields.filter)) {\n        newView.fields.filter = oldView.fields.filter\n    }\n    if (isEqual(newView.fields.cardOrder, oldView.fields.cardOrder)) {\n        newView.fields.cardOrder = oldView.fields.cardOrder\n    }\n    if (isEqual(newView.fields.columnWidths, oldView.fields.columnWidths)) {\n        newView.fields.columnWidths = oldView.fields.columnWidths\n    }\n    if (isEqual(newView.fields.columnCalculations, oldView.fields.columnCalculations)) {\n        newView.fields.columnCalculations = oldView.fields.columnCalculations\n    }\n    if (isEqual(newView.fields.kanbanCalculations, oldView.fields.kanbanCalculations)) {\n        newView.fields.kanbanCalculations = oldView.fields.kanbanCalculations\n    }\n    return newView\n}\n\nconst viewsSlice = createSlice({\n    name: 'views',\n    initialState: {views: {}, current: ''} as ViewsState,\n    reducers: {\n        setCurrent: (state, action: PayloadAction<string>) => {\n            state.current = action.payload\n        },\n        updateViews: (state, action: PayloadAction<BoardView[]>) => {\n            for (const view of action.payload) {\n                if (view.deleteAt === 0) {\n                    state.views[view.id] = smartViewUpdate(state.views[view.id], view)\n                } else {\n                    delete state.views[view.id]\n                }\n            }\n        },\n        updateView: (state, action: PayloadAction<BoardView>) => {\n            state.views[action.payload.id] = action.payload\n        },\n    },\n    extraReducers: (builder) => {\n        builder.addCase(initialReadOnlyLoad.fulfilled, (state, action) => {\n            state.views = {}\n            for (const block of action.payload.blocks) {\n                if (block.type === 'view') {\n                    state.views[block.id] = block as BoardView\n                }\n            }\n        })\n        builder.addCase(loadBoardData.fulfilled, (state, action) => {\n            state.views = {}\n            for (const block of action.payload.blocks) {\n                if (block.type === 'view') {\n                    state.views[block.id] = block as BoardView\n                }\n            }\n        })\n    },\n})\n\nexport const {updateViews, setCurrent, updateView} = viewsSlice.actions\nexport const {reducer} = viewsSlice\n\nexport const getViews = (state: RootState): {[key: string]: BoardView} => state.views.views\nexport const getSortedViews = createSelector(\n    getViews,\n    (views) => {\n        return Object.values(views).sort((a, b) => a.title.localeCompare(b.title)).map((v) => createBoardView(v))\n    },\n)\n\nexport const getViewsByBoard = createSelector(\n    getViews,\n    (views) => {\n        const result: {[key: string]: BoardView[]} = {}\n        Object.values(views).forEach((view) => {\n            if (result[view.parentId]) {\n                result[view.parentId].push(view)\n            } else {\n                result[view.parentId] = [view]\n            }\n        })\n        return result\n    },\n)\n\nexport function getView(viewId: string): (state: RootState) => BoardView|null {\n    return (state: RootState): BoardView|null => {\n        return state.views.views[viewId] || null\n    }\n}\n\nexport const getCurrentBoardViews = createSelector(\n    (state: RootState) => state.boards.current,\n    getViews,\n    (boardId, views) => {\n        Utils.log(`getCurrentBoardViews boardId: ${boardId} views: ${views.length}`)\n        return Object.values(views).filter((v) => v.boardId === boardId).sort((a, b) => a.title.localeCompare(b.title)).map((v) => createBoardView(v))\n    },\n)\n\nexport const getCurrentViewId = (state: RootState): string => state.views.current\n\nexport const getCurrentView = createSelector(\n    getViews,\n    getCurrentViewId,\n    (views, viewId) => views[viewId],\n)\n\nexport const getCurrentViewGroupBy = createSelector(\n    getCurrentBoard,\n    getCurrentView,\n    (currentBoard, currentView) => {\n        if (!currentBoard) {\n            return undefined\n        }\n        if (!currentView) {\n            return undefined\n        }\n        return currentBoard.cardProperties.find((o) => o.id === currentView.fields.groupById)\n    },\n)\n\nexport const getCurrentViewDisplayBy = createSelector(\n    getCurrentBoard,\n    getCurrentView,\n    (currentBoard, currentView) => {\n        if (!currentBoard) {\n            return undefined\n        }\n        if (!currentView) {\n            return undefined\n        }\n        return currentBoard.cardProperties.find((o) => o.id === currentView.fields.dateDisplayPropertyId)\n    },\n)\n"
  },
  {
    "path": "webapp/src/styles/_markdown.scss",
    "content": ".markdown__table {\n    margin: 5px 0 10px;\n    background: var(--center-channel-bg);\n    border-collapse: collapse;\n\n    th,\n    td {\n        padding: 6px 13px;\n        border: 1px solid #ddd;\n    }\n\n    a {\n        white-space: nowrap;\n        word-break: initial;\n    }\n\n    tbody tr {\n        background: var(--center-channel-bg);\n    }\n}\n"
  },
  {
    "path": "webapp/src/styles/_modifiers.scss",
    "content": "@charset 'UTF-8';\n$positions: 't b l r';\n\n$positions: 't'top,\n    'b'bottom,\n    'l'left,\n    'r'right;\n\n@for $i from 0 through 20 {\n    @each $label,\n        $position in $positions {\n        .p#{$label}-#{$i} {\n            padding-#{$position}: #{$i*4}px !important;\n        }\n    }\n\n    @each $label,\n        $position in $positions {\n        .m#{$label}-#{$i} {\n            margin-#{$position}: #{$i*4}px !important;\n        }\n    }\n\n    .m-#{$i} {\n        margin: #{$i*4}px !important;\n    }\n\n    .p-#{$i} {\n        padding: #{$i*4}px !important;\n    }\n\n    .px-#{$i} {\n        padding: 0 #{$i*4}px 0 #{$i*4}px !important;\n    }\n\n    .py-#{$i} {\n        padding: #{$i*4}px 0 #{$i*4}px 0 !important;\n    }\n\n    .h-#{$i} {\n        height: #{$i*4}px !important;\n    }\n}\n\n.d-block {\n    display: block;\n}\n\n.w-full {\n    width: 100%;\n}\n\n.a11y--focused {\n    border-radius: 4px;\n    box-shadow: 0 0 1px 3px rgba(var(--link-color-rgb), 0.5), 0 0 0 1px var(--link-color);\n}\n\n.pos-relative {\n    position: relative;\n}\n\n.hidden-label {\n    position: absolute;\n    overflow: hidden;\n    width: 1px;\n    height: 1px;\n    padding: 0;\n    border: 0;\n    margin: -1px;\n}\n\n.overflow--visible {\n    overflow: visible;\n}\n\n.bg--white {\n    background: var(--center-channel-bg);\n}\n"
  },
  {
    "path": "webapp/src/styles/_typography.scss",
    "content": "h1,\nh2,\nh3,\n.title {\n    font-family: Metropolis, sans-serif;\n    font-weight: 600;\n}\n\n.font-semibold {\n    font-weight: 600;\n}\n\n.text-heading8 {\n    font-size: 32px;\n    font-weight: 600;\n}\n\n.text-heading7 {\n    font-size: 28px;\n    font-weight: 600;\n}\n\n.text-heading6 {\n    font-size: 25px;\n    font-weight: 600;\n}\n\n.text-heading5 {\n    font-size: 22px;\n    font-weight: 600;\n}\n\n.text-heading4 {\n    font-size: 20px;\n    font-weight: 600;\n}\n\n.text-heading3 {\n    font-size: 18px;\n    font-weight: 600;\n}\n\n.text-heading2 {\n    font-size: 16px;\n    font-weight: 600;\n}\n\n.text-heading1 {\n    font-size: 14px;\n    font-weight: 600;\n}\n\n.text-base {\n    font-size: 16px;\n}\n\n.text-75 {\n    font-size: 12px;\n    line-height: 16px;\n}\n\n.text-danger {\n    color: var(--error-text);\n}\n\n// Metropolis font definitions\n@font-face {\n    font-family: 'Metropolis';\n    font-style: normal;\n    font-weight: 600;\n    src: url('../fonts/metropolis/Metropolis-SemiBold.woff2') format('woff2');\n}\n\n@font-face {\n    font-family: 'Metropolis';\n    font-style: italic;\n    font-weight: 600;\n    src: url('../fonts/metropolis/Metropolis-SemiBoldItalic.woff2') format('woff2');\n}\n\n@font-face {\n    font-family: 'Metropolis';\n    font-style: normal;\n    font-weight: 400;\n    src: url('../fonts/metropolis/Metropolis-Regular.woff2') format('woff2');\n}\n\n@font-face {\n    font-family: 'Metropolis';\n    font-style: italic;\n    font-weight: 400;\n    src: url('../fonts/metropolis/Metropolis-RegularItalic.woff2') format('woff2');\n}\n\n@font-face {\n    font-family: 'Metropolis';\n    font-style: normal;\n    font-weight: 300;\n    src: url('../fonts/metropolis/Metropolis-Light.woff2') format('woff2');\n}\n\n@font-face {\n    font-family: 'Metropolis';\n    font-style: italic;\n    font-weight: 300;\n    src: url('../fonts/metropolis/Metropolis-LightItalic.woff2') format('woff2');\n}\n"
  },
  {
    "path": "webapp/src/styles/_z-index.scss",
    "content": "// Keep in descending order to aid in visual grepping\n//\n// Usage:\n// z-index: map-get($z-index, <key>)\n// or\n// z-index: z-index(<key>)\n// or\n// @include z-index(<key>)\n\n$z-index: (\n    // key: value\n    modal-permissions-label: 1000,\n    board-template-selector: 1000,\n    notification-box:        1000,\n    calculation-dropdown:     999,\n    flash-messages:           999,\n    tour-tip-backdrop:        999,\n    tour-tip-overlay:         999,\n    confirmation-dialog-box:  300,\n    dialog-back:              200,\n    sidebar-hidden:           105,\n    center-panel:             100,\n    hover-tooltip-body:       100,\n    table-row-action-cell:    100,\n    hover-tooltip-arrow:       99,\n    calendar-options-menu:     30,\n    gallery-options-menu:      30,\n    sub-menu-option-sub-menu:  16,\n    menu:                      15,\n    card-value-selector-menu:  10,\n    modal:                     10,\n    top-bar:                   10,\n    workspace-main-frame:       9,\n    share-board-element:        5,\n    block-hover:                1,\n    kanban-board-header:        1,\n    table-header:               1,\n    value-selector-delete:      1,\n    dialog-backdrop:           -1,\n    unset:                   unset\n);\n\n@function z-index($key) {\n    @return map-get($z-index, $key);\n}\n\n@mixin z-index($key) {\n    z-index: z-index($key); /* stylelint-disable-line property-disallowed-list */\n}\n"
  },
  {
    "path": "webapp/src/styles/focalboard-variables.scss",
    "content": ":root {\n    --link-visited-color-rgb: #551a8b;\n\n    // Label Colors\n    --prop-default: #fff;\n    --prop-gray: #ededed;\n    --prop-brown: #f7ddc3;\n    --prop-orange: #ffd3c1;\n    --prop-yellow: #f7f0b6;\n    --prop-green: #c7eac3;\n    --prop-blue: #b1d1f6;\n    --prop-purple: #e6d0ff;\n    --prop-pink: #ffd6e9;\n    --prop-red: #ffa9a9;\n\n    // Elevations\n    --elevation-1: 0 2px 3px 0 rgba(0, 0, 0, 0.08);\n    --elevation-2: 0 4px 6px 0 rgba(0, 0, 0, 0.12);\n    --elevation-3: 0 6px 14px 0 rgba(0, 0, 0, 0.12);\n    --elevation-4: 0 8px 24px 0 rgba(0, 0, 0, 0.12);\n    --elevation-5: 0 12px 32px 0 rgba(0, 0, 0, 0.12);\n    --elevation-6: 0 20px 32px 0 rgba(0, 0, 0, 0.12);\n\n    // Radius\n    --default-rad: 4px;\n    --modal-rad: 8px;\n}\n"
  },
  {
    "path": "webapp/src/styles/labels.scss",
    "content": ".propColor0,\n.propColorDefault {\n    background-color: var(--prop-default);\n}\n\n.propColor1,\n.propColor1:hover,\n.propColorGray,\n.propColorGray:hover {\n    background-color: var(--prop-gray);\n}\n\n.propColor2,\n.propColor2:hover,\n.propColorBrown,\n.propColorBrown:hover {\n    background-color: var(--prop-brown);\n}\n\n.propColor3,\n.propColor3:hover,\n.propColorOrange,\n.propColorOrange:hover {\n    background-color: var(--prop-orange);\n}\n\n.propColor4,\n.propColor4:hover,\n.propColorYellow,\n.propColorYellow:hover {\n    background-color: var(--prop-yellow);\n}\n\n.propColor5,\n.propColor5:hover,\n.propColorGreen,\n.propColorGreen:hover {\n    background-color: var(--prop-green);\n}\n\n.propColor6,\n.propColor6:hover,\n.propColorBlue,\n.propColorBlue:hover {\n    background-color: var(--prop-blue);\n}\n\n.propColor7,\n.propColor7:hover,\n.propColorPurple,\n.propColorPurple:hover {\n    background-color: var(--prop-purple);\n}\n\n.propColor8,\n.propColor8:hover,\n.propColorPink,\n.propColorPink:hover {\n    background-color: var(--prop-pink);\n}\n\n.propColor9,\n.propColor9:hover,\n.propColorRed,\n.propColorRed:hover {\n    background-color: var(--prop-red);\n}\n"
  },
  {
    "path": "webapp/src/styles/main.scss",
    "content": "@import 'typography';\n@import 'modifiers';\n@import 'z-index';\n\nhtml {\n    height: 100%;\n}\n\n.focalboard-body,\n#focalboard-app {\n    display: flex;\n    flex-direction: column;\n    overflow: hidden;\n\n    width: 100%;\n    height: 100%;\n    color: rgb(var(--center-channel-color-rgb));\n    background: rgb(var(--center-channel-bg-rgb));\n    padding: 0;\n    margin: 0;\n}\n\n.focalboard-plugin-root {\n    height: 100%;\n}\n\n.focalboard-body {\n    --cursor-color: rgb(var(--center-channel-color-rgb));\n\n    padding: 0;\n    margin: 0;\n    font-family: 'Open Sans', sans-serif;\n    /* -webkit-font-smoothing: auto; */\n    font-size: 14px;\n    line-height: 24px;\n\n    * {\n        box-sizing: border-box;\n        outline: 0;\n        user-select: none;\n    }\n\n    .TeamIcon {\n        box-sizing: content-box;\n    }\n\n    input,\n    textarea,\n    select,\n    button {\n        font-size: 100%; /* Normalize */\n    }\n\n    input,\n    textarea {\n        background: rgb(var(--center-channel-bg-rgb));\n        color: rgb(var(--center-channel-color-rgb));\n        border-radius: 4px;\n        user-select: text;\n    }\n\n    a {\n        text-decoration: none;\n        color: rgb(var(--link-color-rgb));\n    }\n\n    hr {\n        width: 100%;\n        height: 1px;\n        border: 0;\n        color: rgba(var(--center-channel-color-rgb), 0.09);\n        background-color: rgba(var(--center-channel-color-rgb), 0.09);\n        margin-bottom: 8px;\n    }\n\n    #focalboard-app {\n        button {\n            &.style--none {\n                background: transparent;\n                border: 0;\n                padding: 0;\n            }\n        }\n    }\n\n    .page-loading {\n        margin: 50px auto;\n    }\n\n    /* App frame */\n\n    #root {\n        flex: 1 1 auto;\n        display: flex;\n        flex-direction: column;\n        overflow: auto;\n    }\n\n    #frame,\n    #main,\n    .BoardPage {\n        flex: 1 1 auto;\n        display: flex;\n        flex-direction: column;\n        overflow: hidden;\n    }\n\n    /* Main app */\n\n    .dragover {\n        background-color: rgba(128, 192, 255, 0.4);\n    }\n\n    .octo-spacer {\n        flex: 1;\n    }\n\n    /* Icons */\n\n    .octo-icon {\n        font-family: 'Apple Color Emoji', 'Segoe UI Emoji', NotoColorEmoji,\n            'Noto Color Emoji', 'Segoe UI Symbol', 'Android Emoji', EmojiSymbols;\n        line-height: 1.2;\n        align-self: baseline;\n        overflow: visible;\n        font-weight: normal;\n    }\n\n    .text-light {\n        color: rgba(var(--center-channel-color-rgb), 0.56);\n    }\n\n    .octo-icontitle {\n        flex: 0 0 auto;\n        display: flex;\n        flex-direction: row;\n        align-items: center;\n        min-width: 0;\n    }\n\n    /*-- Property list --*/\n\n    .octo-propertyvalue {\n        max-width: 100%;\n        font-size: 12px;\n        color: rgb(var(--center-channel-color-rgb));\n\n        &.empty {\n            color: rgba(var(--center-channel-color-rgb), 0.4);\n        }\n\n        .IconButton.delete-value {\n            width: 16px;\n            min-width: 16px;\n            height: 16px;\n\n            i {\n                font-size: 16px;\n            }\n        }\n    }\n\n    /*-- Editable --*/\n\n    .Editable {\n        cursor: text;\n    }\n\n    .Editable.active {\n        min-width: 100px;\n    }\n\n    .octo-placeholder {\n        color: rgba(var(--center-channel-color-rgb), 0.4);\n    }\n\n    [contentEditable='true'] {\n        user-select: text;\n    }\n\n    [contentEditable='true']:empty::before {\n        content: attr(placeholder);\n        display: block;\n        color: rgba(var(--center-channel-color-rgb), 0.4);\n    }\n\n    .octo-propertyvalue.Editable.active,\n    .octo-table-cell .Editable.active {\n        border-radius: 3px;\n        box-shadow: rgba(15, 15, 15, 0.05) 0 0 0 1px,\n            rgba(15, 15, 15, 0.1) 0 3px 6px, rgba(15, 15, 15, 0.2) 0 9px 24px;\n    }\n\n    .octo-block img {\n        max-width: 500px;\n        max-height: 500px;\n        margin: 5px 0;\n        object-fit: contain;\n        flex: none;\n    }\n\n    .octo-content {\n        width: 100%;\n    }\n\n    .octo-block {\n        display: flex;\n        flex-direction: row;\n        align-items: flex-start;\n        width: 100%;\n\n        &:hover {\n            @include z-index(block-hover);\n            position: relative;\n        }\n\n        > * {\n            flex: 1 1 auto;\n        }\n\n        > .octo-block-margin {\n            flex: 0 0 auto;\n        }\n\n        @media screen and (max-width: 975px) {\n            padding-right: 10px;\n        }\n    }\n\n    .octo-block-margin {\n        display: flex;\n        flex-direction: row;\n        align-items: flex-start;\n        justify-content: flex-end;\n\n        @media not screen and (max-width: 975px) {\n            width: 48px;\n        }\n    }\n\n    .emoji-mart-category-list {\n        display: flex;\n        flex-wrap: wrap;\n    }\n}\n"
  },
  {
    "path": "webapp/src/styles/shared-variables.scss",
    "content": ":root {\n    --center-channel-bg-rgb: 255, 255, 255;\n    --center-channel-color-rgb: 63, 67, 80;\n    --sidebar-bg-rgb: 30, 50, 92;\n    --sidebar-text-rgb: 255, 255, 255;\n    --button-color-rgb: 255, 255, 255;\n    --button-bg-rgb: 28, 88, 217;\n    --button-danger-color-rgb: 255, 255, 255;\n    --button-danger-bg-rgb: 210, 75, 78;\n    --link-color-rgb: 56, 111, 229;\n    --error-text-rgb: #d24b4e;\n}\n"
  },
  {
    "path": "webapp/src/styles/variables.scss",
    "content": "@import './shared-variables';\n@import './focalboard-variables';\n"
  },
  {
    "path": "webapp/src/svg/card-skeleton.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\ntype Props = {\n    className?: string\n}\n\nexport default function CardSkeleton(props: Props): JSX.Element {\n    return (\n        <span className={props.className}>\n            <svg\n                width='468'\n                height='521'\n                viewBox='0 0 468 521'\n                fill='none'\n                xmlns='http://www.w3.org/2000/svg'\n            >\n                <rect\n                    width='156'\n                    height='32'\n                    rx='4'\n                    fill='#3F4350'\n                    fillOpacity='0.08'\n                />\n                <rect\n                    x='172'\n                    width='296'\n                    height='32'\n                    rx='4'\n                    fill='#3F4350'\n                    fillOpacity='0.08'\n                />\n                <rect\n                    y='48'\n                    width='156'\n                    height='32'\n                    rx='4'\n                    fill='#3F4350'\n                    fillOpacity='0.08'\n                />\n                <rect\n                    x='172'\n                    y='48'\n                    width='296'\n                    height='32'\n                    rx='4'\n                    fill='#3F4350'\n                    fillOpacity='0.08'\n                />\n                <rect\n                    y='96'\n                    width='156'\n                    height='32'\n                    rx='4'\n                    fill='#3F4350'\n                    fillOpacity='0.08'\n                />\n                <rect\n                    x='172'\n                    y='96'\n                    width='296'\n                    height='32'\n                    rx='4'\n                    fill='#3F4350'\n                    fillOpacity='0.08'\n                />\n                <rect\n                    y='144'\n                    width='156'\n                    height='32'\n                    rx='4'\n                    fill='#3F4350'\n                    fillOpacity='0.08'\n                />\n                <rect\n                    x='172'\n                    y='144'\n                    width='296'\n                    height='32'\n                    rx='4'\n                    fill='#3F4350'\n                    fillOpacity='0.08'\n                />\n                <rect\n                    y='192'\n                    width='468'\n                    height='1'\n                    fill='#3D3C40'\n                    fillOpacity='0.16'\n                />\n                <rect\n                    y='209'\n                    width='468'\n                    height='32'\n                    rx='4'\n                    fill='#3F4350'\n                    fillOpacity='0.08'\n                />\n                <rect\n                    y='257'\n                    width='468'\n                    height='32'\n                    rx='4'\n                    fill='#3F4350'\n                    fillOpacity='0.08'\n                />\n                <rect\n                    y='305'\n                    width='468'\n                    height='1'\n                    fill='#3D3C40'\n                    fillOpacity='0.16'\n                />\n                <rect\n                    y='322'\n                    width='468'\n                    height='199'\n                    rx='4'\n                    fill='#3F4350'\n                    fillOpacity='0.08'\n                />\n            </svg>\n        </span>\n    )\n}\n"
  },
  {
    "path": "webapp/src/svg/error-illustration.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nexport default function ErrorIllustration(): JSX.Element {\n    return (\n        <span>\n            <svg\n                width='355'\n                height='250'\n                viewBox='0 0 355 250'\n                fill='none'\n            >\n                <ellipse\n                    cx='122.596'\n                    cy='247.053'\n                    rx='42.3413'\n                    ry='2.21392'\n                    fill='black'\n                    fillOpacity='0.12'\n                />\n                <path\n                    d='M325.792 41.3701C325.792 35.5825 320.105 29.7949 314.418 29.7949H166.556C160.869 29.7949 155.182 35.5825 155.182 41.3701V151.4H325.792V41.3701Z'\n                    fill='white'\n                />\n                <path\n                    d='M325.792 41.0937C325.792 35.293 320.105 29.4922 314.418 29.4922H166.556C160.869 29.4922 155.182 35.293 155.182 41.0937V148.954H325.792V41.0937Z'\n                    fill='#3F4350'\n                />\n                <path\n                    d='M155.182 148.98V160.475C155.182 166.223 160.869 171.971 166.556 171.971H314.418C320.105 171.971 325.792 166.223 325.792 160.475V148.98H155.182Z'\n                    fill='white'\n                />\n                <path\n                    d='M155.182 148.98V160.475C155.182 166.223 160.869 171.971 166.556 171.971H314.418C320.105 171.971 325.792 166.223 325.792 160.475V148.98H155.182Z'\n                    fill='#DDDFE4'\n                />\n                <path\n                    d='M217.757 177.631C217.757 188.91 206.413 188.91 195.112 188.91H285.862C274.519 188.91 263.218 188.91 263.218 177.631V171.97H217.843L217.757 177.631Z'\n                    fill='white'\n                />\n                <path\n                    d='M217.757 177.631C217.757 188.91 206.413 188.91 195.112 188.91H285.862C274.519 188.91 263.218 188.91 263.218 177.631V171.97H217.843L217.757 177.631Z'\n                    fill='#BABEC9'\n                />\n                <path\n                    d='M240.487 157.45C241.086 157.45 241.671 157.628 242.168 157.96C242.666 158.293 243.053 158.765 243.282 159.318C243.511 159.87 243.571 160.479 243.454 161.065C243.338 161.652 243.05 162.191 242.626 162.614C242.203 163.037 241.664 163.325 241.078 163.442C240.491 163.559 239.883 163.499 239.33 163.27C238.777 163.041 238.305 162.653 237.972 162.156C237.64 161.659 237.462 161.074 237.462 160.475C237.462 159.673 237.781 158.903 238.348 158.336C238.916 157.769 239.685 157.45 240.487 157.45Z'\n                    fill='#A4A9B7'\n                />\n                <path\n                    d='M285.862 188.91H195.112V199.8H285.862V188.91Z'\n                    fill='white'\n                />\n                <path\n                    d='M285.862 188.91H195.112V199.8H285.862V188.91Z'\n                    fill='#A4A9B7'\n                />\n                <g clipPath='url(#clip0_1289_46272)'>\n                    <rect\n                        width='150.04'\n                        height='96.8002'\n                        transform='translate(165.467 40.0801)'\n                        fill='white'\n                    />\n                    <path\n                        d='M301.417 147.598H179.819C179.222 147.597 178.649 147.36 178.227 146.939C177.805 146.518 177.568 145.946 177.567 145.35V93.5184C177.568 92.9224 177.805 92.3511 178.227 91.9297C178.649 91.5083 179.222 91.2712 179.819 91.2705H301.417C302.014 91.2712 302.586 91.5083 303.008 91.9297C303.43 92.3511 303.668 92.9224 303.668 93.5184V145.35C303.668 145.946 303.43 146.518 303.008 146.939C302.586 147.36 302.014 147.597 301.417 147.598ZM179.819 92.1697C179.46 92.1701 179.117 92.3123 178.864 92.5651C178.61 92.818 178.468 93.1608 178.468 93.5184V145.35C178.468 145.708 178.61 146.051 178.864 146.303C179.117 146.556 179.46 146.699 179.819 146.699H301.417C301.775 146.699 302.118 146.556 302.371 146.303C302.625 146.051 302.767 145.708 302.768 145.35V93.5184C302.767 93.1608 302.625 92.818 302.371 92.5651C302.118 92.3123 301.775 92.17 301.417 92.1697L179.819 92.1697Z'\n                        fill='#3D3C40'\n                        fillOpacity='0.16'\n                    />\n                    <rect\n                        x='190.021'\n                        y='100.341'\n                        width='14.944'\n                        height='19.3718'\n                        rx='2.21392'\n                        fill='#3D3C40'\n                        fillOpacity='0.16'\n                    />\n                    <rect\n                        x='211.606'\n                        y='100.341'\n                        width='14.944'\n                        height='45.3854'\n                        rx='2.21392'\n                        fill='#3D3C40'\n                        fillOpacity='0.16'\n                    />\n                    <rect\n                        x='233.192'\n                        y='100.341'\n                        width='14.944'\n                        height='11.0696'\n                        rx='2.21392'\n                        fill='#3D3C40'\n                        fillOpacity='0.16'\n                    />\n                    <rect\n                        x='254.778'\n                        y='100.341'\n                        width='14.944'\n                        height='30.9949'\n                        rx='2.21392'\n                        fill='#3D3C40'\n                        fillOpacity='0.16'\n                    />\n                    <rect\n                        x='276.363'\n                        y='100.341'\n                        width='14.944'\n                        height='24.9066'\n                        rx='2.21392'\n                        fill='#3D3C40'\n                        fillOpacity='0.16'\n                    />\n                    <g clipPath='url(#clip1_1289_46272)'>\n                        <path\n                            d='M226.236 81.8179C224.468 81.8179 223.667 80.5915 224.456 79.0926L238.746 52.0803C239.556 50.5852 240.83 50.5852 241.623 52.0803L255.91 79.0926C256.719 80.5876 255.91 81.8179 254.13 81.8179H226.236Z'\n                            fill='#FFBC1F'\n                        />\n                        <path\n                            d='M237.941 62.0709L239.41 71.5669C239.424 71.7553 239.512 71.9316 239.656 72.0603C239.799 72.1889 239.988 72.2605 240.185 72.2605C240.381 72.2605 240.57 72.1889 240.714 72.0603C240.858 71.9316 240.945 71.7553 240.96 71.5669L242.428 62.0709C242.695 58.3683 237.67 58.3683 237.941 62.0709Z'\n                            fill='#1B1D22'\n                        />\n                        <path\n                            d='M240.183 73.2842C240.628 73.285 241.062 73.4126 241.432 73.6509C241.801 73.8892 242.089 74.2276 242.258 74.6232C242.428 75.0188 242.472 75.4539 242.385 75.8736C242.297 76.2933 242.083 76.6786 241.768 76.981C241.453 77.2833 241.052 77.4891 240.616 77.5723C240.18 77.6555 239.728 77.6124 239.317 77.4485C238.906 77.2845 238.555 77.0071 238.308 76.6512C238.061 76.2953 237.929 75.8769 237.929 75.4489C237.929 75.1643 237.987 74.8825 238.101 74.6196C238.214 74.3567 238.38 74.1179 238.589 73.9168C238.799 73.7158 239.047 73.5564 239.321 73.4478C239.594 73.3393 239.887 73.2837 240.183 73.2842Z'\n                            fill='#1B1D22'\n                        />\n                    </g>\n                </g>\n                <path\n                    d='M83.4508 236.199C83.6867 233.176 81.878 229.472 78.9882 225.026C78.4672 224.226 77.9167 223.396 77.3368 222.557C71.616 214.219 63.0644 203.521 56.9505 190.017C46.2266 166.308 49.0871 127.782 52.2423 106.819C52.2423 106.819 70.4267 100.675 78.8801 109.23C79.2116 109.536 79.5204 109.866 79.804 110.218C83.7357 115.246 83.0871 126.418 79.1455 140.723C72.8055 163.759 72.6119 174.873 75.3445 184.702C77.7036 193.197 82.2908 200.755 86.4192 214.209C86.8713 215.661 87.2842 217.025 87.6675 218.299C87.9624 219.287 88.2377 220.215 88.4933 221.095C90.3412 227.456 91.1767 231.131 91.2946 233.393C88.6898 234.401 86.0654 235.3 83.4508 236.199Z'\n                    fill='#FFBC1F'\n                />\n                <path\n                    d='M88.0697 219.731L86.4282 220.304L85.7401 220.541L80.8254 222.28C79.8404 222.619 78.9736 223.237 78.3287 224.058C72.6473 215.523 63.4076 204.301 56.93 190.017C46.2061 166.308 49.0666 127.782 52.2218 106.819C52.2218 106.819 71.8217 100.191 79.7442 110.267C83.676 115.295 83.0272 126.468 79.0856 140.772C67.2903 183.704 77.4441 185.226 86.3594 214.268C87.0376 216.274 87.5782 218.072 88.0697 219.731Z'\n                    fill='#1C58D9'\n                />\n                <path\n                    d='M88.0697 219.731L86.4282 220.304L85.7401 220.541L80.8254 222.28C79.8404 222.619 78.9736 223.237 78.3287 224.058C72.6473 215.523 63.4076 204.301 56.93 190.017C46.2061 166.308 49.0666 127.782 52.2218 106.819C52.2218 106.819 71.8217 100.191 79.7442 110.267C83.676 115.295 83.0272 126.468 79.0856 140.772C67.2903 183.704 77.4441 185.226 86.3594 214.268C87.0376 216.274 87.5782 218.072 88.0697 219.731Z'\n                    fill='black'\n                    fillOpacity='0.16'\n                />\n                <mask\n                    id='mask0_1289_46272'\n                    maskUnits='userSpaceOnUse'\n                    x='49'\n                    y='104'\n                    width='40'\n                    height='121'\n                >\n                    <path\n                        d='M88.0702 219.732L86.4286 220.305L85.7406 220.542L80.8259 222.281C79.8409 222.62 78.9741 223.237 78.3292 224.059C72.6478 215.524 63.4081 204.302 56.9305 190.018C46.2066 166.309 49.0671 127.783 52.2223 106.82C52.2223 106.82 71.8222 100.192 79.7447 110.268C83.6765 115.296 83.0277 126.469 79.0861 140.773C67.2908 183.705 77.4446 185.226 86.3599 214.269C87.0381 216.275 87.5787 218.073 88.0702 219.732Z'\n                        fill='#1452BD'\n                    />\n                </mask>\n                <g mask='url(#mask0_1289_46272)'>\n                    <rect\n                        x='49.1279'\n                        y='104.414'\n                        width='39.1115'\n                        height='119.663'\n                        fill='url(#pattern0)'\n                    />\n                </g>\n                <path\n                    d='M83.3718 231.517C83.1258 231.6 82.8723 231.66 82.6149 231.694C83.3164 233.634 83.3164 235.761 82.6149 237.701C81.9268 239.864 80.3739 242.719 79.8923 244.171C78.4277 248.567 79.9511 248.053 84.4727 241.8C88.9942 235.547 93.0243 239.716 90.5964 228.958C88.1784 229.808 85.7702 230.66 83.3718 231.517Z'\n                    fill='#1E325C'\n                />\n                <path\n                    d='M137.787 186.313C132.715 178.637 138.672 180.524 123.751 187.133C122.916 187.518 121.992 187.903 121.009 188.308L118.827 189.188C101.134 196.103 85.8488 193.979 75.5279 184.762C73.5111 182.96 71.71 180.928 70.161 178.706C61.0196 165.677 64.686 137.217 78.8797 109.24C80.092 106.85 81.3796 104.472 82.7426 102.108L102.284 89.0586C102.284 89.0586 115.701 103.323 98.3518 139.548C81.8187 174.123 100.367 176.978 118.561 175.644C119.436 175.585 120.33 175.506 121.205 175.427H121.49C140.166 173.668 137.807 171.841 139.92 181.018C142.034 190.195 143.351 194.72 137.787 186.313Z'\n                    fill='#FFBC1F'\n                />\n                <path\n                    d='M122.581 187.656C121.441 188.14 120.193 188.643 118.826 189.187C97.7325 197.396 80.0886 192.842 70.1609 178.696C60.2332 164.55 65.4132 132.197 82.7425 102.097L102.283 89.0479C102.283 89.0479 115.701 103.312 98.3517 139.537C81.1993 175.376 101.792 177.135 120.586 175.465C120.782 176.532 120.979 177.609 121.166 178.686C121.166 178.933 121.254 179.18 121.284 179.427C121.51 180.691 121.716 181.946 121.913 183.21C121.913 183.447 121.992 183.704 122.031 183.941C122.227 185.176 122.404 186.381 122.581 187.656Z'\n                    fill='#1C58D9'\n                />\n                <path\n                    d='M139.92 180.978C138.21 173.549 139.389 173.332 130.091 174.448C130.602 177.346 131.08 180.25 131.526 183.161C136.441 180.198 134.013 180.553 137.817 186.312C143.351 194.719 142.053 190.205 139.92 180.978Z'\n                    fill='#1E325C'\n                />\n                <path\n                    d='M71.9697 51.1739C72.2939 46.0919 73.3262 41.0806 75.0365 36.287C77.0909 31.1007 81.1897 27.0406 82.9983 27.2382C87.1365 27.6729 90.2918 25.9836 91.7761 28.3347C92.9752 30.4487 91.5991 41.8387 90.2623 42.8068C89.2887 43.307 88.2496 43.6662 87.1758 43.8737L85.4557 53.199L71.9697 51.1739Z'\n                    fill='#FFBC1F'\n                />\n                <path\n                    d='M90.3113 32.7155C92.3424 35.192 93.7094 37.0131 93.7094 37.0131C93.8417 38.0498 90.3851 38.5799 90.3851 38.5799L90.3113 32.7155Z'\n                    fill='#FFBC1F'\n                />\n                <path\n                    d='M84.512 43.1423C85.9064 43.5397 87.374 43.604 88.7976 43.33C89.3382 43.2213 87.7065 43.8733 87.1659 43.982C85.5735 44.2981 85.8685 44.4265 84.3056 43.982C83.7453 43.8634 83.9419 43.0237 84.512 43.1423Z'\n                    fill='#CC8F00'\n                />\n                <path\n                    d='M74.8298 25.2623C72.9426 26.675 71.5763 31.1895 70.898 33.4517C70.2198 35.7138 71.5762 37.897 71.8121 40.2481C71.7959 40.7049 71.8851 41.1593 72.0727 41.5757C72.2603 41.9922 72.5412 42.3594 72.8934 42.6486C74.1025 43.3598 75.5768 42.1843 76.2353 40.9396C76.8939 39.6949 77.3264 38.1637 78.5748 37.5216C79.4692 37.0573 80.7373 37.0968 81.2288 36.2078C81.6416 35.4768 81.2288 34.5383 81.4942 33.7381C81.8186 32.6811 83.0668 32.2662 84.1383 32.0193L90.5372 30.498C91.7595 30.294 92.9271 29.8401 93.9677 29.1644C94.5275 28.8512 94.9605 28.3511 95.1917 27.7506C95.4228 27.1501 95.4375 26.4873 95.2332 25.877C95.0289 25.2668 94.6184 24.7478 94.073 24.4099C93.5277 24.0721 92.8818 23.9368 92.2475 24.0275C90.8124 24.0275 89.7508 24.8573 88.4239 25.1635C87.0744 25.3067 85.7137 25.3067 84.3643 25.1635C81.5629 25.1734 77.2085 23.4842 74.8298 25.2623Z'\n                    fill='#66320A'\n                />\n                <path\n                    d='M160.995 29.3122C157.535 29.6777 154.262 34.2514 149.897 42.8952C145.533 51.5389 134.977 62.023 123.182 55.4439C111.387 48.8648 101.733 49.7805 87.333 49.7114C84.8855 49.5039 69.2468 49.4644 65.9245 49.8398C41.518 50.6498 39.8569 75.9883 27.177 74.4769C11.0174 72.5012 13.1208 53.9789 5.3949 49.415C-2.33104 44.8511 -0.188182 48.1802 2.23969 49.336C4.66756 50.4918 2.23962 51.5883 3.39949 57.3771C4.55937 63.166 4.23504 83.6047 19.2937 90.737C30.2929 95.943 43.4801 90.3519 53.339 82.5676C53.28 84.5433 53.4177 86.8357 53.339 89.0485C52.936 100.597 49.8102 108.136 49.7512 112.572C64.2271 112.085 78.3688 112.728 92.808 113.873C96.5137 114.07 104.876 114.715 108.592 114.972C108.592 114.972 107.795 107.896 107.156 98.6498C106.272 85.8966 106.736 79.6039 106.038 72.4518C132.401 80.6016 149.583 64.1242 153.22 56.5572C160.199 41.9468 158.931 36.6618 159.039 34.4589C159.216 30.9125 164.445 28.917 160.995 29.3122Z'\n                    fill='#FFBC1F'\n                />\n                <path\n                    d='M125.432 56.2222C124.901 62.3074 124.066 70.8622 121.972 76.0682C120.891 75.9497 119.888 75.8707 118.895 75.8015L118.168 75.7521L116.497 75.6336L115.77 75.5743C112.823 75.3264 109.912 74.7533 107.09 73.8653C107.283 76.0725 107.355 78.2886 107.306 80.5037C107.306 80.7408 107.306 80.9976 107.306 81.2347C107.306 82.4893 107.247 83.7834 107.208 85.1861C107.208 85.3442 107.208 85.5023 107.208 85.6603C107.208 85.8184 107.208 85.8381 107.208 85.9172C107.208 86.905 107.208 87.8929 107.208 89.0091C107.208 90.1254 107.208 90.9849 107.208 92.0715C107.208 94.0472 107.346 96.2402 107.523 98.7395C107.631 100.439 107.768 102.078 107.906 103.62C107.906 103.866 107.906 104.123 107.975 104.36C108.083 105.645 108.211 106.84 108.329 107.946C108.329 108.193 108.329 108.43 108.398 108.667C108.85 112.737 109.223 115.335 109.223 115.335C105.527 115.079 96.5432 114.022 92.8178 113.824C78.3852 112.68 63.9002 112.35 49.4308 112.836C49.5161 111.188 49.7663 109.554 50.1777 107.956C50.1777 107.709 50.2859 107.452 50.3547 107.196C50.512 106.484 50.6889 105.724 50.8659 104.923L51.0427 104.163C51.2688 103.175 51.4949 102.187 51.7112 101.041C51.9766 99.7274 52.2223 98.3444 52.4484 96.8626C52.7334 94.9462 52.9595 92.9112 53.0873 90.6984C53.0873 90.4613 53.0873 90.2143 53.0873 89.9575C53.0873 89.7006 53.0873 89.3648 53.0873 89.0585V88.209V87.5372C53.1463 85.8776 53.1955 84.2971 53.2348 82.7857L48.9 86.2333L48.3298 86.6877L47.3469 87.4582L46.7866 87.9126C45.1549 89.1968 43.9164 90.1649 43.8378 90.1649C39.0312 85.6801 36.8785 77.5401 35.0601 71.2969C35.9548 70.4419 36.7953 69.5313 37.5765 68.5704C37.773 68.3531 37.9499 68.1259 38.1367 67.8987C38.5987 67.3356 39.0607 66.7528 39.5325 66.1502L40.1222 65.3895C40.2697 65.2018 40.4074 65.0141 40.5646 64.8264C44.6635 59.6106 49.5291 53.7032 57.776 51.1447C58.6901 50.8582 59.6533 50.6112 60.6559 50.4038C61.7025 50.1978 62.7589 50.0461 63.821 49.9493L64.6172 49.8802C64.9711 49.8802 65.3348 49.8802 65.6984 49.8209H65.7869C66.1604 49.8209 66.7698 49.7419 67.3891 49.7024C67.4801 49.867 67.582 50.0254 67.6939 50.1765C74.4368 59.413 84.8462 56.726 91.2845 49.6826H95.3736C97.9685 49.6826 100.436 49.6826 102.814 49.8012C108.381 49.9251 113.878 51.0735 119.033 53.1895L119.731 53.4859C120.33 53.7625 120.93 54.0489 121.53 54.3552L122.228 54.7009C122.572 54.8787 122.906 55.0664 123.21 55.264C123.927 55.6371 124.669 55.9574 125.432 56.2222Z'\n                    fill='white'\n                />\n                <path\n                    d='M125.432 56.2222C124.901 62.3074 124.066 70.8622 121.972 76.0682C120.891 75.9497 119.888 75.8707 118.895 75.8015L118.168 75.7521L116.497 75.6336L115.77 75.5743C112.823 75.3264 109.912 74.7533 107.09 73.8653C107.283 76.0725 107.355 78.2886 107.306 80.5037C107.306 80.7408 107.306 80.9976 107.306 81.2347C107.306 82.4893 107.247 83.7834 107.208 85.1861C107.208 85.3442 107.208 85.5023 107.208 85.6603C107.208 85.8184 107.208 85.8381 107.208 85.9172C107.208 86.905 107.208 87.8929 107.208 89.0091C107.208 90.1254 107.208 90.9849 107.208 92.0715C107.208 94.0472 107.346 96.2402 107.523 98.7395C107.631 100.439 107.768 102.078 107.906 103.62C107.906 103.866 107.906 104.123 107.975 104.36C108.083 105.645 108.211 106.84 108.329 107.946C108.329 108.193 108.329 108.43 108.398 108.667C108.85 112.737 109.223 115.335 109.223 115.335C105.527 115.079 96.5432 114.022 92.8178 113.824C78.3852 112.68 63.9002 112.35 49.4308 112.836C49.5161 111.188 49.7663 109.554 50.1777 107.956C50.1777 107.709 50.2859 107.452 50.3547 107.196C50.512 106.484 50.6889 105.724 50.8659 104.923L51.0427 104.163C51.2688 103.175 51.4949 102.187 51.7112 101.041C51.9766 99.7274 52.2223 98.3444 52.4484 96.8626C52.7334 94.9462 52.9595 92.9112 53.0873 90.6984C53.0873 90.4613 53.0873 90.2143 53.0873 89.9575C53.0873 89.7006 53.0873 89.3648 53.0873 89.0585V88.209V87.5372C53.1463 85.8776 53.1955 84.2971 53.2348 82.7857L48.9 86.2333L48.3298 86.6877L47.3469 87.4582L46.7866 87.9126C45.1549 89.1968 43.9164 90.1649 43.8378 90.1649C39.0312 85.6801 36.8785 77.5401 35.0601 71.2969C35.9548 70.4419 36.7953 69.5313 37.5765 68.5704C37.773 68.3531 37.9499 68.1259 38.1367 67.8987C38.5987 67.3356 39.0607 66.7528 39.5325 66.1502L40.1222 65.3895C40.2697 65.2018 40.4074 65.0141 40.5646 64.8264C44.6635 59.6106 49.5291 53.7032 57.776 51.1447C58.6901 50.8582 59.6533 50.6112 60.6559 50.4038C61.7025 50.1978 62.7589 50.0461 63.821 49.9493L64.6172 49.8802C64.9711 49.8802 65.3348 49.8802 65.6984 49.8209H65.7869C66.1604 49.8209 66.7698 49.7419 67.3891 49.7024C67.4801 49.867 67.582 50.0254 67.6939 50.1765C74.4368 59.413 84.8462 56.726 91.2845 49.6826H95.3736C97.9685 49.6826 100.436 49.6826 102.814 49.8012C108.381 49.9251 113.878 51.0735 119.033 53.1895L119.731 53.4859C120.33 53.7625 120.93 54.0489 121.53 54.3552L122.228 54.7009C122.572 54.8787 122.906 55.0664 123.21 55.264C123.927 55.6371 124.669 55.9574 125.432 56.2222Z'\n                    fill='#3F4350'\n                    fillOpacity='0.08'\n                />\n                <mask\n                    id='mask1_1289_46272'\n                    maskUnits='userSpaceOnUse'\n                    x='35'\n                    y='49'\n                    width='91'\n                    height='67'\n                >\n                    <path\n                        d='M125.432 56.2213C124.901 62.3064 124.066 70.8613 121.972 76.0673C120.891 75.9487 119.888 75.8697 118.895 75.8005L118.168 75.7511L116.497 75.6326L115.77 75.5733C112.823 75.3254 109.912 74.7523 107.09 73.8643C107.283 76.0715 107.355 78.2876 107.306 80.5027C107.306 80.7398 107.306 80.9967 107.306 81.2337C107.306 82.4883 107.247 83.7824 107.208 85.1852C107.208 85.3432 107.208 85.5013 107.208 85.6593C107.208 85.8174 107.208 85.8372 107.208 85.9162C107.208 86.904 107.208 87.8919 107.208 89.0082C107.208 90.1244 107.208 90.9839 107.208 92.0705C107.208 94.0462 107.346 96.2393 107.523 98.7385C107.631 100.438 107.768 102.077 107.906 103.619C107.906 103.866 107.906 104.122 107.975 104.359C108.083 105.644 108.211 106.839 108.329 107.945C108.329 108.192 108.329 108.429 108.398 108.666C108.85 112.736 109.223 115.335 109.223 115.335C105.527 115.078 96.5432 114.021 92.8178 113.823C78.3852 112.679 63.9002 112.349 49.4308 112.835C49.5161 111.187 49.7663 109.553 50.1777 107.955C50.1777 107.708 50.2859 107.451 50.3547 107.195C50.512 106.483 50.6889 105.723 50.8659 104.923L51.0427 104.162C51.2688 103.174 51.4949 102.186 51.7112 101.04C51.9766 99.7264 52.2223 98.3434 52.4484 96.8616C52.7334 94.9452 52.9595 92.9102 53.0873 90.6974C53.0873 90.4603 53.0873 90.2133 53.0873 89.9565C53.0873 89.6997 53.0873 89.3638 53.0873 89.0576V88.208V87.5363C53.1463 85.8767 53.1955 84.2961 53.2348 82.7847L48.9 86.2323L48.3298 86.6867L47.3469 87.4572L46.7866 87.9117C45.1549 89.1959 43.9164 90.164 43.8378 90.164C39.0312 85.6791 36.8785 77.5392 35.0601 71.2959C35.9548 70.4409 36.7953 69.5303 37.5765 68.5694C37.773 68.3521 37.9499 68.1249 38.1367 67.8977C38.5987 67.3346 39.0607 66.7518 39.5325 66.1492L40.1222 65.3885C40.2697 65.2009 40.4074 65.0132 40.5646 64.8255C44.6635 59.6096 49.5291 53.7022 57.776 51.1437C58.6901 50.8572 59.6533 50.6102 60.6559 50.4028C61.7025 50.1968 62.7589 50.0451 63.821 49.9484L64.6172 49.8792C64.9711 49.8792 65.3348 49.8792 65.6984 49.8199H65.7869C66.1604 49.8199 66.7698 49.7409 67.3891 49.7014C67.4801 49.866 67.582 50.0244 67.6939 50.1756C74.4368 59.412 84.8462 56.725 91.2845 49.6816H95.3736C97.9685 49.6816 100.436 49.6817 102.814 49.8002C108.381 49.9241 113.878 51.0725 119.033 53.1885L119.731 53.4849C120.33 53.7615 120.93 54.048 121.53 54.3542L122.228 54.6999C122.572 54.8778 122.906 55.0655 123.21 55.263C123.927 55.6362 124.669 55.9564 125.432 56.2213Z'\n                        fill='white'\n                    />\n                </mask>\n                <g mask='url(#mask1_1289_46272)'>\n                    <rect\n                        x='34.8188'\n                        y='49.3682'\n                        width='90.6243'\n                        height='66.0542'\n                        fill='url(#pattern1)'\n                    />\n                </g>\n                <path\n                    d='M108.358 108.676C88.9354 107.382 69.4732 104.853 50.1387 107.945C50.1387 107.698 50.2467 107.441 50.3155 107.185C69.5714 104.172 88.9354 106.661 108.309 107.955C108.309 108.202 108.339 108.439 108.358 108.676Z'\n                    fill='#3F4350'\n                    fillOpacity='0.16'\n                />\n                <path\n                    d='M107.936 104.39C88.9635 101.957 69.7496 102.14 50.8267 104.933L51.0036 104.172C69.8478 101.42 88.9758 101.244 107.867 103.649C107.887 103.876 107.906 104.133 107.936 104.39Z'\n                    fill='#3F4350'\n                    fillOpacity='0.16'\n                />\n                <path\n                    d='M107.189 85.1851C107.189 85.4321 107.189 85.6692 107.189 85.9162C89.132 84.7999 70.8788 87.7239 53.0679 90.6875C53.0679 90.4504 53.0679 90.2035 53.0679 89.9466C70.8985 86.9732 89.1418 84.0886 107.189 85.1851Z'\n                    fill='#3F4350'\n                    fillOpacity='0.16'\n                />\n                <path\n                    d='M107.306 80.5029C107.306 80.74 107.306 80.9968 107.306 81.2339C89.1202 81.3164 71.0142 83.6623 53.4018 88.2181C53.3286 88.2437 53.249 88.2437 53.1758 88.2181V87.5463C70.8604 82.9595 89.0426 80.5936 107.306 80.5029Z'\n                    fill='#3F4350'\n                    fillOpacity='0.16'\n                />\n                <path\n                    d='M94.6855 49.6621C92.4837 54.957 88.9549 59.5407 82.6739 59.916C75.5083 60.4001 66.9468 56.7253 63.939 49.9387L64.7352 49.8696C67.7528 56.2412 75.8621 59.6592 82.6739 59.185C88.5224 58.7899 91.8251 54.5915 93.9187 49.6621H94.6855Z'\n                    fill='#3F4350'\n                    fillOpacity='0.16'\n                />\n                <path\n                    d='M122.316 54.6807C121.412 61.7603 120.271 68.8004 118.895 75.801L118.168 75.7517C119.583 68.6523 120.733 61.5134 121.618 54.335L122.316 54.6807Z'\n                    fill='#3F4350'\n                    fillOpacity='0.16'\n                />\n                <path\n                    d='M47.4354 87.4482L46.8751 87.9026C42.752 82.02 39.645 75.481 37.6846 68.5604C37.8812 68.3431 38.0582 68.1159 38.2449 67.8887C40.1746 74.8881 43.283 81.5036 47.4354 87.4482Z'\n                    fill='#3F4350'\n                    fillOpacity='0.16'\n                />\n                <path\n                    d='M40.2797 65.3789C41.6298 72.8921 44.621 80.0109 49.0377 86.2227L48.4676 86.6771C44.1048 80.546 41.1107 73.5406 39.6899 66.1396L40.2797 65.3789Z'\n                    fill='#3F4350'\n                    fillOpacity='0.16'\n                />\n                <path\n                    d='M119.819 53.4653C119.161 60.9154 118.051 68.3183 116.497 75.6328L115.77 75.5735C117.343 68.1815 118.462 60.6991 119.121 53.1689L119.819 53.4653Z'\n                    fill='#3F4350'\n                    fillOpacity='0.16'\n                />\n                <path\n                    d='M88.8766 32.4043C87.235 32.4043 85.6525 32.4043 84.355 32.4043C81.8977 32.513 80.7575 32.8982 79.5484 35.1604C79.3322 35.5457 79.981 35.8025 80.1874 35.4074C80.4267 34.7967 80.8283 34.2637 81.3483 33.8666C81.8682 33.4694 82.4866 33.2233 83.1362 33.1551C84.3978 33.0447 85.6641 32.9985 86.9304 33.0168C87.4611 33.0168 88.1001 33.0168 88.7783 33.0168C88.8274 32.8389 88.8471 32.6216 88.8766 32.4043Z'\n                    fill='#3F4350'\n                    fillOpacity='0.16'\n                />\n                <path\n                    d='M89.4267 35.2401C89.0389 34.8908 88.7581 34.4378 88.6171 33.9339C88.4761 33.4301 88.4807 32.8964 88.6304 32.3951C88.9745 30.9331 90.1737 29.965 91.2943 30.2317C92.4149 30.4984 93.0636 31.9111 92.7195 33.3731C92.3755 34.8351 91.1763 35.8032 90.0459 35.5365C89.8211 35.4826 89.61 35.3816 89.4267 35.2401ZM91.5204 31.0911C91.4052 31.0053 91.2752 30.9417 91.137 30.9034C90.3801 30.7256 89.5545 31.4665 89.2989 32.563C89.0433 33.6595 89.4464 34.6869 90.2033 34.8647C90.9601 35.0425 91.7858 34.3017 92.0512 33.215C92.1545 32.846 92.155 32.4556 92.0528 32.0863C91.9505 31.7171 91.7493 31.3831 91.4712 31.1208L91.5204 31.0911Z'\n                    fill='white'\n                />\n                <path\n                    d='M69.1288 34.0546C66.4454 34.0546 64.5286 37.2059 63.6931 40.5547C63.064 43.0639 59.2109 43.5677 56.6847 43.0441C54.0111 42.5008 51.8487 40.1596 49.1259 40.0805C47.1601 40.0114 45.3711 41.1573 43.6117 42.0563C41.8522 42.9552 39.6799 43.6961 37.9499 42.807C35.984 41.8192 35.2566 39.2804 33.3006 38.3716C31.3445 37.4627 28.9362 38.5988 27.3341 40.1201C24.8339 42.5039 23.2708 45.7123 22.9305 49.1589C22.6861 50.9423 22.9987 52.7579 23.825 54.3551C24.2499 55.1442 24.8808 55.802 25.6497 56.258C26.4186 56.714 27.2964 56.9509 28.1892 56.9433C30.4008 56.805 32.298 54.8194 34.4997 55.0762C35.7478 55.3129 36.8589 56.0196 37.6059 57.0519C38.3922 58.0398 38.9918 59.156 39.788 60.134C41.001 61.6202 42.6253 62.7116 44.4554 63.2703C46.2855 63.8289 48.2393 63.8296 50.0699 63.2724C51.9004 62.7152 53.5256 61.6251 54.7397 60.1398C55.9539 58.6546 56.7026 56.8409 56.8912 54.928C57.019 53.6339 56.8912 52.2312 57.6383 51.1643C58.6212 49.7319 60.5871 49.5146 62.2876 49.5442C63.9881 49.5738 65.895 49.6726 67.2023 48.5564C70.0135 46.1657 67.5464 40.7029 70.3183 38.2728C71.0421 37.7376 71.8358 37.3055 72.6772 36.9886C73.7585 36.465 75.7637 34.351 73.8273 33.62C73.1982 33.3928 72.1465 33.8077 71.5174 33.8867C70.7259 33.9968 69.9279 34.0529 69.1288 34.0546Z'\n                    fill='#66320A'\n                />\n                <mask\n                    id='mask2_1289_46272'\n                    maskUnits='userSpaceOnUse'\n                    x='22'\n                    y='33'\n                    width='53'\n                    height='31'\n                >\n                    <path\n                        d='M69.1288 34.0537C66.4454 34.0537 64.5286 37.2049 63.6931 40.5538C63.064 43.0629 59.2109 43.5667 56.6847 43.0431C54.0111 42.4998 51.8487 40.1586 49.1259 40.0796C47.1601 40.0104 45.3711 41.1563 43.6117 42.0553C41.8522 42.9542 39.6799 43.6951 37.9499 42.8061C35.984 41.8182 35.2566 39.2794 33.3006 38.3706C31.3445 37.4618 28.9362 38.5978 27.3341 40.1191C24.8339 42.5029 23.2708 45.7114 22.9305 49.158C22.6861 50.9413 22.9987 52.7569 23.825 54.3541C24.2499 55.1432 24.8808 55.8011 25.6497 56.257C26.4186 56.713 27.2964 56.9499 28.1892 56.9423C30.4008 56.804 32.298 54.8184 34.4997 55.0752C35.7478 55.312 36.8589 56.0186 37.6059 57.0509C38.3922 58.0388 38.9918 59.1551 39.788 60.133C41.001 61.6192 42.6253 62.7107 44.4554 63.2693C46.2855 63.8279 48.2393 63.8287 50.0699 63.2715C51.9004 62.7143 53.5256 61.6241 54.7397 60.1389C55.9539 58.6536 56.7026 56.8399 56.8912 54.927C57.019 53.6329 56.8912 52.2302 57.6383 51.1633C58.6212 49.7309 60.5871 49.5136 62.2876 49.5432C63.9881 49.5729 65.895 49.6717 67.2023 48.5554C70.0135 46.1648 67.5464 40.7019 70.3183 38.2718C71.0421 37.7366 71.8358 37.3045 72.6772 36.9876C73.7585 36.464 75.7637 34.35 73.8273 33.619C73.1982 33.3918 72.1465 33.8067 71.5174 33.8857C70.7259 33.9958 69.9279 34.052 69.1288 34.0537Z'\n                        fill='#66320A'\n                    />\n                </mask>\n                <g mask='url(#mask2_1289_46272)'>\n                    <rect\n                        x='22.417'\n                        y='33.0938'\n                        width='52.4667'\n                        height='30.6338'\n                        fill='url(#pattern2)'\n                    />\n                </g>\n                <path\n                    d='M71.7334 38.5787C71.545 37.1315 71.4792 35.6708 71.5369 34.2124C71.6057 32.3355 68.6962 32.3355 68.5881 34.2124C68.5386 35.9333 68.6372 37.6553 68.8829 39.3591C68.9945 39.7253 69.2439 40.0334 69.578 40.2178C69.912 40.4023 70.3044 40.4486 70.6719 40.347C71.0403 40.2446 71.3557 40.0043 71.5532 39.6754C71.7507 39.3465 71.8151 38.954 71.7334 38.5787Z'\n                    fill='white'\n                />\n                <defs>\n                    <pattern\n                        id='pattern0'\n                        patternContentUnits='objectBoundingBox'\n                        width='1'\n                        height='1'\n                    >\n                        <use\n                            xlinkHref='#image0_1289_46272'\n                            transform='scale(0.0121951 0.004)'\n                        />\n                    </pattern>\n                    <pattern\n                        id='pattern1'\n                        patternContentUnits='objectBoundingBox'\n                        width='1'\n                        height='1'\n                    >\n                        <use\n                            xlinkHref='#image1_1289_46272'\n                            transform='scale(0.00526316 0.00724638)'\n                        />\n                    </pattern>\n                    <pattern\n                        id='pattern2'\n                        patternContentUnits='objectBoundingBox'\n                        width='1'\n                        height='1'\n                    >\n                        <use\n                            xlinkHref='#image2_1289_46272'\n                            transform='scale(0.00909091 0.015625)'\n                        />\n                    </pattern>\n                    <clipPath id='clip0_1289_46272'>\n                        <rect\n                            width='150.04'\n                            height='96.8002'\n                            fill='white'\n                            transform='translate(165.467 40.0801)'\n                        />\n                    </clipPath>\n                    <clipPath id='clip1_1289_46272'>\n                        <rect\n                            width='32.0651'\n                            height='30.8551'\n                            fill='white'\n                            transform='translate(224.152 50.9697)'\n                        />\n                    </clipPath>\n                    <image\n                        id='image0_1289_46272'\n                        width='82'\n                        height='250'\n                        xlinkHref='data:image/png;base64,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'\n                    />\n                    <image\n                        id='image1_1289_46272'\n                        width='190'\n                        height='138'\n                        xlinkHref='data:image/png;base64,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'\n                    />\n                    <image\n                        id='image2_1289_46272'\n                        width='110'\n                        height='64'\n                        xlinkHref='data:image/png;base64,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'\n                    />\n                </defs>\n            </svg>\n        </span>\n    )\n}\n\n"
  },
  {
    "path": "webapp/src/svg/search-illustration.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nexport default function SearchIllustration(): JSX.Element {\n    return (\n        <span>\n            <svg\n                width='160'\n                height='160'\n                viewBox='0 0 211 211'\n                fill='none'\n                xmlns='http://www.w3.org/2000/svg'\n            >\n                <path\n                    opacity='0.5'\n                    d='M30.7386 30.5387C41.9645 19.17 56.1326 13.3142 73.2142 13C89.9817 13.3142 104.064 19.1414 115.461 30.5387C126.83 41.936 132.686 56.0183 133 72.7858C132.686 89.8674 126.859 104.007 115.461 115.261C104.064 126.487 89.9817 132.429 73.2142 133C56.1326 132.4 41.9931 126.487 30.7386 115.261C19.5127 104.007 13.5999 89.8389 13 72.7572C13.5999 55.9898 19.5127 41.9074 30.7386 30.5387Z'\n                    fill='#F4F4F6'\n                />\n                <path\n                    d='M146.832 68.1304C145.633 50.4091 138.441 34.8462 125.285 21.4416C110.302 7.15667 93.0078 0 73.4017 0C53.7956 0 36.5296 7.15667 21.5468 21.4416C7.19177 36.3513 0 53.533 0 73.0435C0 92.5539 7.19177 109.764 21.5468 124.645C35.3024 137.766 51.0273 144.837 68.6928 145.86C86.3583 146.911 102.654 142.054 117.637 131.348L128.852 142.509L143.207 128.224L131.992 117.063C143.065 102.181 148.002 85.8516 146.832 68.1304ZM115.839 115.046C104.452 126.207 90.3822 132.114 73.63 132.682C56.5638 132.086 42.4371 126.207 31.1929 115.046C19.9771 103.885 14.0696 89.7992 13.4703 72.8163C14.0696 56.1458 19.9771 42.1448 31.1929 30.8134C42.4086 19.5104 56.5638 13.6886 73.63 13.3762C90.3822 13.6886 104.452 19.482 115.839 30.8134C127.197 42.1448 133.048 56.1458 133.362 72.8163C133.048 89.7992 127.226 103.885 115.839 115.046Z'\n                    fill='#BABEC9'\n                />\n                <path\n                    d='M32.8537 108.595C45.1718 120.134 59.2946 125.918 75.2222 125.918C91.1498 125.918 105.273 120.134 117.591 108.595C124.208 102.077 128.992 94.685 132 86.39C129.594 97.3371 124.179 107.099 115.786 115.676C103.468 127.216 89.2591 133 73.1883 133C57.1175 133 42.9087 127.216 30.5906 115.676C22.1685 107.099 16.7543 97.3371 14.3766 86.39C11.9703 75.4429 12.7151 64.6368 16.6397 54C14.5485 63.48 14.8349 73.0164 17.5277 82.6375C20.2492 92.2586 25.3483 100.892 32.8537 108.595Z'\n                    fill='#A4A9B7'\n                />\n                <path\n                    d='M192.739 210.413C189.038 211.471 186.297 211.099 184.517 209.298L126.291 145.395C124.511 143.594 123.776 141.278 124.059 138.419C124.341 135.559 126.121 132.357 129.398 128.755C132.958 125.467 136.122 123.579 138.947 123.122C141.773 122.665 144.061 123.494 145.841 125.609L209.407 184.537C211.187 186.339 211.47 189.026 210.283 192.629C209.097 196.231 206.865 199.834 203.616 203.436C200.084 207.039 196.44 209.355 192.739 210.413Z'\n                    fill='#FFBC1F'\n                />\n                <path\n                    d='M197 173L173.197 197L144.5 165.5L165.548 144L197 173Z'\n                    fill='#7A5600'\n                />\n            </svg>\n        </span>\n    )\n}\n\n"
  },
  {
    "path": "webapp/src/telemetry/telemetry.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nexport interface TelemetryHandler {\n    trackEvent: (userId: string, userRoles: string, category: string, event: string, props?: any) => void\n    pageVisited: (userId: string, userRoles: string, category: string, name: string) => void\n}\n"
  },
  {
    "path": "webapp/src/telemetry/telemetryClient.test.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport '@testing-library/jest-dom'\n\nimport TelemetryClient from './telemetryClient'\n\ndescribe('trackEvent', () => {\n    const track = jest.fn()\n    const page = jest.fn()\n    test('should call Rudder\\'s track when a RudderTelemetryHandler is attached to TelemetryClient', () => {\n        TelemetryClient.setTelemetryHandler()\n        TelemetryClient.trackEvent('test', 'onClick')\n        TelemetryClient.pageVisited('focalboard', 'test')\n        expect(track).not.toHaveBeenCalled()\n        expect(page).not.toHaveBeenCalled()\n\n        TelemetryClient.setTelemetryHandler({trackEvent: track, pageVisited: page})\n        TelemetryClient.trackEvent('test', 'onClick')\n        TelemetryClient.pageVisited('focalboard', 'test')\n\n        expect(track).toHaveBeenCalledTimes(1)\n        expect(page).toHaveBeenCalledTimes(1)\n    })\n})\n"
  },
  {
    "path": "webapp/src/telemetry/telemetryClient.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {IUser} from '../user'\n\nimport {TelemetryHandler} from './telemetry'\n\nexport const TelemetryCategory = 'boards'\n\nexport const TelemetryActions = {\n    ClickChannelHeader: 'clickChannelHeader',\n    ClickChannelIntro: 'channelIntro_boardLink',\n    ViewBoard: 'viewBoard',\n    CreateBoard: 'createBoard',\n    DuplicateBoard: 'duplicateBoard',\n    DeleteBoard: 'deleteBoard',\n    DeleteBoardTemplate: 'deleteBoardTemplate',\n    ShareBoard: 'shareBoard',\n    CreateBoardTemplate: 'createBoardTemplate',\n    CreateBoardViaTemplate: 'createBoardViaTemplate',\n    AddTemplateFromBoard: 'AddTemplateFromBoard',\n    CreateBoardView: 'createBoardView',\n    DuplicateBoardView: 'duplicagteBoardView',\n    DeleteBoardView: 'deleteBoardView',\n    EditCardProperty: 'editCardProperty',\n    ViewCard: 'viewCard',\n    CreateCard: 'createCard',\n    CreateCardTemplate: 'createCardTemplate',\n    CreateCardViaTemplate: 'createCardViaTemplate',\n    DuplicateCard: 'duplicateCard',\n    DeleteCard: 'deleteCard',\n    AddTemplateFromCard: 'addTemplateFromCard',\n    ViewSharedBoard: 'viewSharedBoard',\n    ShareBoardOpenModal: 'shareBoard_openModal',\n    ShareBoardLogin: 'shareBoard_login',\n    ShareLinkPublicCopy: 'shareLinkPublic_copy',\n    ShareLinkInternalCopy: 'shareLinkInternal_copy',\n    ImportArchive: 'settings_importArchive',\n    ImportTrello: 'settings_importTrello',\n    ImportAsana: 'settings_importAsana',\n    ImportNotion: 'settings_importNotion',\n    ImportJira: 'settings_importJira',\n    ImportTodoist: 'settings_importTodoist',\n    ExportArchive: 'settings_exportArchive',\n    StartTour: 'welcomeScreen_startTour',\n    SkipTour: 'welcomeScreen_skipTour',\n    CloudMoreInfo: 'cloud_more_info',\n    ViewLimitReached: 'limit_ViewLimitReached',\n    ViewLimitCTAPerformed: 'limit_ViewLimitLinkOpen',\n    LimitCardCTAPerformed: 'limit_CardLimitCTAPerformed',\n    LimitCardLimitReached: 'limit_cardLimitReached',\n    LimitCardLimitLinkOpen: 'limit_cardLimitLinkOpen',\n    VersionMoreInfo: 'version_more_info',\n    ClickChannelsRHSBoard: 'click_board_in_channels_RHS',\n}\n\ninterface IEventProps {\n    channelID?: string\n    teamID?: string\n    board?: string\n    view?: string\n    viewType?: string\n    card?: string\n    cardTemplateId?: string\n    boardTemplateId?: string\n    shareBoardEnabled?: boolean\n}\n\nclass TelemetryClient {\n    public telemetryHandler?: TelemetryHandler\n    public user?: IUser\n\n    setTelemetryHandler(telemetryHandler?: TelemetryHandler): void {\n        this.telemetryHandler = telemetryHandler\n    }\n\n    setUser(user: IUser): void {\n        this.user = user\n    }\n\n    trackEvent(category: string, event: string, props?: IEventProps): void {\n        if (this.telemetryHandler) {\n            const userId = this.user?.id\n            this.telemetryHandler.trackEvent(userId || '', '', category, event, props)\n        }\n    }\n\n    pageVisited(category: string, name: string): void {\n        if (this.telemetryHandler) {\n            const userId = this.user?.id\n            this.telemetryHandler.pageVisited(userId || '', '', category, name)\n        }\n    }\n}\n\nconst telemetryClient = new TelemetryClient()\n\nexport {TelemetryClient}\nexport default telemetryClient\n"
  },
  {
    "path": "webapp/src/test/fetchMock.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nclass FetchMock {\n    static fn = jest.fn(async () => {\n        const response = new Response()\n        return response\n    })\n\n    static async jsonResponse(json: string): Promise<Response> {\n        const response = new Response(json)\n        return response\n    }\n}\n\nexport {FetchMock}\n"
  },
  {
    "path": "webapp/src/test/testBlockFactory.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {Board, IPropertyOption, IPropertyTemplate, createBoard} from '../blocks/board'\nimport {BoardView, createBoardView} from '../blocks/boardView'\nimport {Card, createCard} from '../blocks/card'\nimport {CommentBlock, createCommentBlock} from '../blocks/commentBlock'\nimport {DividerBlock, createDividerBlock} from '../blocks/dividerBlock'\nimport {createFilterClause} from '../blocks/filterClause'\nimport {createFilterGroup} from '../blocks/filterGroup'\nimport {ImageBlock, createImageBlock} from '../blocks/imageBlock'\nimport {TextBlock, createTextBlock} from '../blocks/textBlock'\nimport {Category, CategoryBoards} from '../store/sidebar'\nimport {Utils} from '../utils'\nimport {CheckboxBlock, createCheckboxBlock} from '../blocks/checkboxBlock'\nimport {Block} from '../blocks/block'\nimport {IUser} from '../user'\n\nclass TestBlockFactory {\n    static createBoard(): Board {\n        const board = createBoard()\n        board.title = 'board title'\n        board.description = 'description'\n        board.showDescription = true\n        board.icon = 'i'\n\n        for (let i = 0; i < 3; i++) {\n            const propertyOption: IPropertyOption = {\n                id: 'value1',\n                value: 'value 1',\n                color: 'propColorBrown',\n            }\n            const propertyTemplate: IPropertyTemplate = {\n                id: `property${i + 1}`,\n                name: `Property ${i + 1}`,\n                type: 'select',\n                options: [propertyOption],\n            }\n            board.cardProperties.push(propertyTemplate)\n        }\n\n        return board\n    }\n\n    static createBoardView(board?: Board): BoardView {\n        const view = createBoardView()\n        view.boardId = board ? board.id : 'board'\n        view.title = 'view title'\n        view.fields.viewType = 'board'\n        view.fields.groupById = 'property1'\n        view.fields.hiddenOptionIds = ['value1']\n        view.fields.cardOrder = ['card1', 'card2', 'card3']\n        view.fields.sortOptions = [\n            {\n                propertyId: 'property1',\n                reversed: true,\n            },\n            {\n                propertyId: 'property2',\n                reversed: false,\n            },\n        ]\n        view.fields.columnWidths = {\n            column1: 100,\n            column2: 200,\n        }\n\n        // Filter\n        const filterGroup = createFilterGroup()\n        const filter = createFilterClause()\n        filter.propertyId = 'property1'\n        filter.condition = 'includes'\n        filter.values = ['value1']\n        filterGroup.filters.push(filter)\n        view.fields.filter = filterGroup\n\n        return view\n    }\n\n    static createTableView(board?: Board): BoardView {\n        const view = createBoardView()\n        view.boardId = board ? board.id : 'board'\n        view.title = 'view title'\n        view.fields.viewType = 'table'\n        view.fields.groupById = 'property1'\n        view.fields.hiddenOptionIds = ['value1']\n        view.fields.cardOrder = ['card1', 'card2', 'card3']\n        view.fields.sortOptions = [\n            {\n                propertyId: 'property1',\n                reversed: true,\n            },\n            {\n                propertyId: 'property2',\n                reversed: false,\n            },\n        ]\n        view.fields.columnWidths = {\n            column1: 100,\n            column2: 200,\n        }\n\n        // Filter\n        const filterGroup = createFilterGroup()\n        const filter = createFilterClause()\n        filter.propertyId = 'property1'\n        filter.condition = 'includes'\n        filter.values = ['value1']\n        filterGroup.filters.push(filter)\n        view.fields.filter = filterGroup\n\n        return view\n    }\n\n    static createCard(board?: Board): Card {\n        const card = createCard()\n        card.boardId = board ? board.id : 'board'\n        card.title = 'title'\n        card.fields.icon = 'i'\n        card.fields.properties.property1 = 'value1'\n        return card\n    }\n\n    private static addToCard<BlockType extends Block>(block: BlockType, card: Card, isContent = true): BlockType {\n        block.parentId = card.id\n        block.boardId = card.boardId\n        if (isContent) {\n            card.fields.contentOrder.push(block.id)\n        }\n        return block\n    }\n\n    static createComment(card: Card): CommentBlock {\n        const block = this.addToCard(createCommentBlock(), card, false)\n        block.title = 'title'\n\n        return block\n    }\n\n    static createText(card: Card): TextBlock {\n        const block = this.addToCard(createTextBlock(), card)\n        block.title = 'title'\n        return block\n    }\n\n    static createImage(card: Card): ImageBlock {\n        const block = this.addToCard(createImageBlock(), card)\n        block.fields.fileId = 'fileId'\n        return block\n    }\n\n    static createDivider(card: Card): DividerBlock {\n        const block = this.addToCard(createDividerBlock(), card)\n        block.title = 'title'\n        return block\n    }\n\n    static createCheckbox(card: Card): CheckboxBlock {\n        const block = this.addToCard(createCheckboxBlock(), card)\n        block.title = 'title'\n        return block\n    }\n\n    static createCategory(): Category {\n        const now = Date.now()\n\n        return {\n            id: Utils.createGuid(Utils.blockTypeToIDType('7')),\n            name: 'Category',\n            createAt: now,\n            updateAt: now,\n            deleteAt: 0,\n            userID: '',\n            teamID: '',\n            collapsed: false,\n            type: 'custom',\n            sortOrder: 0,\n            isNew: false,\n        }\n    }\n\n    static createCategoryBoards(): CategoryBoards {\n        return {\n            ...TestBlockFactory.createCategory(),\n            boardMetadata: [],\n        }\n    }\n\n    static createUser(): IUser {\n        return {\n            id: 'user-id-1',\n            username: 'Dwight Schrute',\n            email: 'dwight.schrute@dundermifflin.com',\n            nickname: '',\n            firstname: '',\n            lastname: '',\n            props: {},\n            create_at: Date.now(),\n            update_at: Date.now(),\n            is_bot: false,\n            is_guest: false,\n            roles: 'system_user',\n        }\n    }\n}\n\nexport {TestBlockFactory}\n"
  },
  {
    "path": "webapp/src/testUtils.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {IntlProvider} from 'react-intl'\nimport React from 'react'\nimport {DndProvider} from 'react-dnd'\nimport {HTML5Backend} from 'react-dnd-html5-backend'\nimport configureStore, {MockStoreEnhanced} from 'redux-mock-store'\nimport {Middleware} from 'redux'\n\nimport {DragDropContext, Droppable} from 'react-beautiful-dnd'\n\nimport {Block} from './blocks/block'\n\nexport const wrapIntl = (children?: React.ReactNode): JSX.Element => <IntlProvider locale='en'>{children}</IntlProvider>\nexport const wrapDNDIntl = (children?: React.ReactNode): JSX.Element => {\n    return (\n        <DndProvider backend={HTML5Backend}>\n            {wrapIntl(children)}\n        </DndProvider>\n    )\n}\n\nexport const wrapRBDNDContext = (children?: React.ReactNode): JSX.Element => {\n    return (\n        <DragDropContext onDragEnd={() => {}}>\n            {children}\n        </DragDropContext>\n    )\n}\n\nexport const wrapRBDNDDroppable = (children?: React.ReactNode): JSX.Element => {\n    const draggableComponent = (\n        <Droppable droppableId='droppable_id'>\n            {(provided) => (\n                <div\n                    ref={provided.innerRef}\n                    {...provided.droppableProps}\n                >\n                    {children}\n                </div>\n            )}\n        </Droppable>\n    )\n\n    return wrapRBDNDContext(draggableComponent)\n}\n\nexport function mockDOM(): void {\n    window.focus = jest.fn()\n    document.createRange = () => {\n        const range = new Range()\n        range.getBoundingClientRect = jest.fn()\n        range.getClientRects = () => {\n            return {\n                item: () => null,\n                length: 0,\n                [Symbol.iterator]: jest.fn(),\n            }\n        }\n        return range\n    }\n}\nexport function mockMatchMedia(result: {matches: boolean}): void {\n    // We check if system preference is dark or light theme.\n    // This is required to provide it's definition since\n    // window.matchMedia doesn't exist in Jest.\n    Object.defineProperty(window, 'matchMedia', {\n        writable: true,\n        value: jest.fn().mockImplementation(() => {\n            return result\n\n            // return ({\n            //     matches: true,\n            // })\n        }),\n    })\n}\n\nexport function mockStateStore(middleware: Middleware[], state: unknown): MockStoreEnhanced<unknown, unknown> {\n    const mockStore = configureStore(middleware)\n    return mockStore(state)\n}\n\nexport type BlocksById<BlockType> = {[key: string]: BlockType}\n\nexport function blocksById<BlockType extends Block>(blocks: BlockType[]): BlocksById<BlockType> {\n    return blocks.reduce((res, block) => {\n        res[block.id] = block\n        return res\n    }, {} as BlocksById<BlockType>)\n}\n"
  },
  {
    "path": "webapp/src/theme.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {CSSObject} from '@emotion/serialize'\nimport isEqual from 'lodash/isEqual'\nimport color from 'color'\n\nlet activeThemeName: string\n\nimport {UserSettings} from './userSettings'\n\nexport type Theme = {\n    mainBg: string\n    mainFg: string\n    buttonBg: string\n    buttonFg: string\n    sidebarBg: string\n    sidebarFg: string\n    sidebarTextActiveBorder: string\n    sidebarWhiteLogo: string\n\n    link: string\n    linkVisited: string\n\n    propDefault: string\n    propGray: string\n    propBrown: string\n    propOrange: string\n    propYellow: string\n    propGreen: string\n    propBlue: string\n    propPurple: string\n    propPink: string\n    propRed: string\n}\n\nexport const systemThemeName = 'system-theme'\n\nexport const defaultThemeName = 'default-theme'\n\nexport const defaultTheme = {\n    mainBg: '255, 255, 255',\n    mainFg: '63, 67, 80',\n    buttonBg: '28, 88, 217',\n    buttonFg: '255, 255, 255',\n    sidebarBg: '30, 50, 92',\n    sidebarFg: '255, 255, 255',\n    sidebarTextActiveBorder: '93, 137, 243',\n    sidebarWhiteLogo: 'true',\n\n    link: '93, 137, 234',\n    linkVisited: '#551a8b',\n\n    propDefault: '#fff',\n    propGray: '#EDEDED',\n    propBrown: '#F7DDC3',\n    propOrange: '#ffd3c1',\n    propYellow: '#f7f0b6',\n    propGreen: '#c7eac3',\n    propBlue: '#B1D1F6',\n    propPurple: '#e6d0ff',\n    propPink: '#ffd6e9',\n    propRed: '#ffa9a9',\n}\n\nexport const darkThemeName = 'dark-theme'\n\nexport const darkTheme = {\n    ...defaultTheme,\n\n    mainBg: '55, 53, 47',\n    mainFg: '220, 220, 220',\n    buttonBg: '80, 170, 221',\n    buttonFg: '255, 255, 255',\n    sidebarBg: '75, 73, 67',\n    sidebarFg: '255, 255, 255',\n    sidebarTextActiveBorder: '102, 185, 167',\n    sidebarWhiteLogo: 'true',\n\n    link: '#0090ff',\n    linkVisited: 'hsla(270, 68%, 70%, 1.0)',\n\n    propDefault: 'hsla(0, 100%, 100%, 0.08)',\n    propGray: 'hsla(0, 0%, 70%, 0.4)',\n    propBrown: 'hsla(25, 60%, 40%, 0.4)',\n    propOrange: 'hsla(35, 100%, 50%, 0.4)',\n    propYellow: 'hsla(48, 100%, 70%, 0.4)',\n    propGreen: 'hsla(120, 100%, 70%, 0.4)',\n    propBlue: 'hsla(240, 100%, 70%, 0.4)',\n    propPurple: 'hsla(270, 100%, 64%, 0.4)',\n    propPink: 'hsla(310, 100%, 80%, 0.4)',\n    propRed: 'hsla(4, 100%, 70%, 0.4)',\n}\n\nexport const lightThemeName = 'light-theme'\n\nexport const lightTheme = {\n    ...defaultTheme,\n\n    mainBg: '255, 255, 255',\n    mainFg: '55, 53, 47',\n    buttonBg: '80, 170, 221',\n    buttonFg: '255, 255, 255',\n    sidebarBg: '247, 246, 243',\n    sidebarFg: '55, 53, 47',\n    sidebarTextActiveBorder: '87, 158, 255',\n    sidebarWhiteLogo: 'false',\n}\n\nexport function setTheme(theme: Theme | null): Theme {\n    let consolidatedTheme = defaultTheme\n    if (theme) {\n        consolidatedTheme = {...defaultTheme, ...theme}\n        UserSettings.theme = JSON.stringify(consolidatedTheme)\n    } else {\n        UserSettings.theme = ''\n        const darkThemeMq = window.matchMedia('(prefers-color-scheme: dark)')\n        if (darkThemeMq.matches) {\n            consolidatedTheme = {...defaultTheme, ...darkTheme}\n        }\n    }\n\n    setActiveThemeName(consolidatedTheme, theme)\n\n    // for personal server and desktop, Focalboard is responsible for managing the theme,\n    // so we set all the color variables here.\n    document.documentElement.style.setProperty('--center-channel-bg-rgb', consolidatedTheme.mainBg)\n    document.documentElement.style.setProperty('--center-channel-color-rgb', consolidatedTheme.mainFg)\n    document.documentElement.style.setProperty('--button-bg-rgb', consolidatedTheme.buttonBg)\n    document.documentElement.style.setProperty('--button-color-rgb', consolidatedTheme.buttonFg)\n    document.documentElement.style.setProperty('--sidebar-bg-rgb', consolidatedTheme.sidebarBg)\n    document.documentElement.style.setProperty('--sidebar-text-rgb', consolidatedTheme.sidebarFg)\n    document.documentElement.style.setProperty('--link-color-rgb', consolidatedTheme.link)\n    document.documentElement.style.setProperty('--sidebar-text-active-border-rgb', consolidatedTheme.sidebarTextActiveBorder)\n\n    document.documentElement.style.setProperty('--sidebar-white-logo', consolidatedTheme.sidebarWhiteLogo)\n    document.documentElement.style.setProperty('--link-visited-color-rgb', consolidatedTheme.linkVisited)\n\n    document.documentElement.style.setProperty('--prop-default', consolidatedTheme.propDefault)\n    document.documentElement.style.setProperty('--prop-gray', consolidatedTheme.propGray)\n    document.documentElement.style.setProperty('--prop-brown', consolidatedTheme.propBrown)\n    document.documentElement.style.setProperty('--prop-orange', consolidatedTheme.propOrange)\n    document.documentElement.style.setProperty('--prop-yellow', consolidatedTheme.propYellow)\n    document.documentElement.style.setProperty('--prop-green', consolidatedTheme.propGreen)\n    document.documentElement.style.setProperty('--prop-blue', consolidatedTheme.propBlue)\n    document.documentElement.style.setProperty('--prop-purple', consolidatedTheme.propPurple)\n    document.documentElement.style.setProperty('--prop-pink', consolidatedTheme.propPink)\n    document.documentElement.style.setProperty('--prop-red', consolidatedTheme.propRed)\n\n    return consolidatedTheme\n}\n\nexport function setMattermostTheme(theme: any): Theme {\n    if (!theme) {\n        return setTheme(defaultTheme)\n    }\n\n    document.documentElement.style.setProperty('--center-channel-bg-rgb', color(theme.centerChannelBg).rgb().array().join(', '))\n    document.documentElement.style.setProperty('--center-channel-color-rgb', color(theme.centerChannelColor).rgb().array().join(', '))\n    document.documentElement.style.setProperty('--button-bg-rgb', color(theme.buttonBg).rgb().array().join(', '))\n    document.documentElement.style.setProperty('--button-color-rgb', color(theme.buttonColor).rgb().array().join(', '))\n    document.documentElement.style.setProperty('--sidebar-bg-rgb', color(theme.sidebarBg).rgb().array().join(', '))\n    document.documentElement.style.setProperty('--sidebar-text-rgb', color(theme.sidebarText).rgb().array().join(', '))\n    document.documentElement.style.setProperty('--link-color-rgb', theme.linkColor)\n    document.documentElement.style.setProperty('--sidebar-text-active-border-rgb', color(theme.sidebarTextActiveBorder).rgb().array().join(', '))\n\n    return setTheme({\n        ...defaultTheme,\n        mainBg: color(theme.centerChannelBg).rgb().array().join(', '),\n        mainFg: color(theme.centerChannelColor).rgb().array().join(', '),\n        buttonBg: color(theme.buttonBg).rgb().array().join(', '),\n        buttonFg: color(theme.buttonColor).rgb().array().join(', '),\n        sidebarBg: color(theme.sidebarBg).rgb().array().join(', '),\n        sidebarFg: color(theme.sidebarColor || '#ffffff').rgb().array().join(', '),\n        sidebarTextActiveBorder: color(theme.sidebarTextActiveBorder).rgb().array().join(', '),\n        link: theme.linkColor,\n    })\n}\n\nfunction setActiveThemeName(consolidatedTheme: Theme, theme: Theme | null) {\n    if (theme === null) {\n        activeThemeName = systemThemeName\n    } else if (isEqual(consolidatedTheme, darkTheme)) {\n        activeThemeName = darkThemeName\n    } else if (isEqual(consolidatedTheme, lightTheme)) {\n        activeThemeName = lightThemeName\n    } else {\n        activeThemeName = defaultThemeName\n    }\n}\n\nexport function loadTheme(): Theme {\n    const themeStr = UserSettings.theme\n    if (themeStr) {\n        try {\n            const theme = JSON.parse(themeStr)\n            const consolidatedTheme = setTheme(theme)\n            setActiveThemeName(consolidatedTheme, theme)\n            return consolidatedTheme\n        } catch (e) {\n            return setTheme(null)\n        }\n    } else {\n        return setTheme(null)\n    }\n}\n\nexport function initThemes(): void {\n    const darkThemeMq = window.matchMedia('(prefers-color-scheme: dark)')\n    const changeHandler = () => {\n        const themeStr = UserSettings.theme\n        if (!themeStr) {\n            setTheme(null)\n        }\n    }\n    if (darkThemeMq.addEventListener) {\n        darkThemeMq.addEventListener('change', changeHandler)\n    } else if (darkThemeMq.addListener) {\n        // Safari and Mac app support\n        darkThemeMq.addListener(changeHandler)\n    }\n    loadTheme()\n}\n\nexport function getSelectBaseStyle() {\n    return {\n        dropdownIndicator: (provided: CSSObject): CSSObject => ({\n            ...provided,\n            display: 'none !important',\n        }),\n        indicatorSeparator: (provided: CSSObject): CSSObject => ({\n            ...provided,\n            display: 'none',\n        }),\n        loadingIndicator: (provided: CSSObject): CSSObject => ({\n            ...provided,\n            display: 'none',\n        }),\n        clearIndicator: (provided: CSSObject): CSSObject => ({\n            ...provided,\n            display: 'none',\n        }),\n        menu: (provided: CSSObject): CSSObject => ({\n            ...provided,\n            width: 'unset',\n            background: 'rgb(var(--center-channel-bg-rgb))',\n        }),\n        option: (provided: CSSObject, state: { isFocused: boolean }): CSSObject => ({\n            ...provided,\n            background: state.isFocused ? 'rgba(var(--center-channel-color-rgb), 0.1)' : 'rgb(var(--center-channel-bg-rgb))',\n            color: state.isFocused ? 'rgb(var(--center-channel-color-rgb))' : 'rgb(var(--center-channel-color-rgb))',\n            padding: '2px 8px',\n        }),\n        control: (): CSSObject => ({\n            border: 0,\n            width: '100%',\n            margin: '4px 0 0 0',\n\n            // display: 'flex',\n            // marginTop: 0,\n        }),\n        valueContainer: (provided: CSSObject): CSSObject => ({\n            ...provided,\n            padding: '0 5px',\n            overflow: 'unset',\n        }),\n        singleValue: (provided: CSSObject): CSSObject => ({\n            ...provided,\n            color: 'rgb(var(--center-channel-color-rgb))',\n            overflow: 'unset',\n            maxWidth: 'calc(100% - 20px)',\n        }),\n        input: (provided: CSSObject): CSSObject => ({\n            ...provided,\n            paddingBottom: 0,\n            paddingTop: 0,\n            marginBottom: 0,\n            marginTop: 0,\n        }),\n        menuList: (provided: CSSObject): CSSObject => ({\n            ...provided,\n            overflowY: 'auto',\n            overflowX: 'hidden',\n        }),\n    }\n}\n\nexport function getActiveThemeName(): string {\n    return activeThemeName || defaultThemeName\n}\n"
  },
  {
    "path": "webapp/src/tsconfig.json",
    "content": "{\n    \"extends\": \"../tsconfig.json\",\n    \"compilerOptions\": {\n\t\t\"baseUrl\": \".\",\n\t\t\"paths\": {\n\t\t\t\"*\": [\n\t\t\t\t\"../node_modules/*\",\n\t\t\t\t\"../@custom_types/*\"\n\t\t\t]\n\t\t}\n\t},\n\t\"include\": [\n\t\t\"./**/*.ts\",\n\t\t\"./**/*.tsx\"\n\t]\n}\n"
  },
  {
    "path": "webapp/src/types/images.d.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\ndeclare module '*.jpg';\ndeclare module '*.png';\ndeclare module '*.gif';\ndeclare module '*.apng';\n"
  },
  {
    "path": "webapp/src/types/index.d.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\ntype TelemetryProps = {\n    trackingLocation: string\n}\nexport interface IAppWindow extends Window {\n    baseURL?: string\n    frontendBaseURL?: string\n    isFocalboardPlugin?: boolean\n    getCurrentTeamId?: () => string\n    msCrypto: Crypto\n    openInNewBrowser?: ((href: string) => void) | null\n    webkit?: {messageHandlers: {nativeApp?: {postMessage: <T>(message: T) => void}}}\n    openPricingModal?: () => (telemetry: TelemetryProps) => void\n}\n\n// SuiteWindow documents all custom properties\n// which may be defined on global\n// window object when operating in\n// the Mattermost suite environment\nexport type SuiteWindow = Window & {\n    getCurrentTeamId?: () => string\n    baseURL?: string\n    frontendBaseURL?: string\n    isFocalboardPlugin?: boolean\n    WebappUtils?: any\n}\n"
  },
  {
    "path": "webapp/src/undoManager.test.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport undoManager from './undomanager'\n\ntest('Basic undo/redo', async () => {\n    expect(undoManager.canUndo).toBe(false)\n    expect(undoManager.canRedo).toBe(false)\n    expect(undoManager.currentCheckpoint).toBe(0)\n\n    const values: string[] = []\n\n    await undoManager.perform(\n        async () => {\n            values.push('a')\n        },\n        async () => {\n            values.pop()\n        },\n        'test',\n    )\n\n    expect(undoManager.canUndo).toBe(true)\n    expect(undoManager.canRedo).toBe(false)\n    expect(undoManager.currentCheckpoint).toBeGreaterThan(0)\n    expect(values).toEqual(['a'])\n    expect(undoManager.undoDescription).toBe('test')\n    expect(undoManager.redoDescription).toBe(undefined)\n\n    await undoManager.undo()\n    expect(undoManager.canUndo).toBe(false)\n    expect(undoManager.canRedo).toBe(true)\n    expect(values).toEqual([])\n    expect(undoManager.undoDescription).toBe(undefined)\n    expect(undoManager.redoDescription).toBe('test')\n\n    await undoManager.redo()\n    expect(undoManager.canUndo).toBe(true)\n    expect(undoManager.canRedo).toBe(false)\n    expect(values).toEqual(['a'])\n\n    await undoManager.clear()\n    expect(undoManager.canUndo).toBe(false)\n    expect(undoManager.canRedo).toBe(false)\n    expect(undoManager.currentCheckpoint).toBe(0)\n    expect(undoManager.undoDescription).toBe(undefined)\n    expect(undoManager.redoDescription).toBe(undefined)\n})\n\ntest('Basic undo/redo response dependant', async () => {\n    expect(undoManager.canUndo).toBe(false)\n    expect(undoManager.canRedo).toBe(false)\n    expect(undoManager.currentCheckpoint).toBe(0)\n\n    const blockIds = [2, 1]\n    const blocks: Record<string, any> = {}\n\n    const newBlock = await undoManager.perform(\n        async () => {\n            const responseId = blockIds.pop() // every time we run the action a new ID is obtained\n            const block: Record<string, any> = {id: responseId, title: 'Sample'}\n            blocks[block.id] = block\n            return block\n        },\n        async (block: Record<string, any>) => {\n            delete blocks[block.id]\n        },\n        'test',\n    )\n\n    // should insert the block and return the new block for its use\n    expect(undoManager.canUndo).toBe(true)\n    expect(undoManager.canRedo).toBe(false)\n    expect(blocks).toHaveProperty('1')\n    expect(blocks[1]).toEqual(newBlock)\n\n    // should correctly remove the block based on the info gathered in\n    // the redo function\n    await undoManager.undo()\n    expect(undoManager.canUndo).toBe(false)\n    expect(undoManager.canRedo).toBe(true)\n    expect(blocks).not.toHaveProperty('1')\n\n    // when redoing, as the function has side effects the new id will\n    // be different\n    await undoManager.redo()\n    expect(undoManager.canUndo).toBe(true)\n    expect(undoManager.canRedo).toBe(false)\n    expect(blocks).not.toHaveProperty('1')\n    expect(blocks).toHaveProperty('2')\n    expect(blocks[2].id).toEqual(2)\n\n    // when undoing, the undo manager has saved the new id internally\n    // and it removes the right block\n    await undoManager.undo()\n    expect(undoManager.canUndo).toBe(false)\n    expect(undoManager.canRedo).toBe(true)\n    expect(blocks).not.toHaveProperty('2')\n\n    await undoManager.clear()\n})\n\ntest('Grouped undo/redo', async () => {\n    expect(undoManager.canUndo).toBe(false)\n    expect(undoManager.canRedo).toBe(false)\n\n    const values: string[] = []\n    const groupId = 'the group id'\n\n    await undoManager.perform(\n        async () => {\n            values.push('a')\n        },\n        async () => {\n            values.pop()\n        },\n        'insert a',\n    )\n\n    expect(undoManager.canUndo).toBe(true)\n    expect(undoManager.canRedo).toBe(false)\n    expect(values).toEqual(['a'])\n    expect(undoManager.undoDescription).toBe('insert a')\n    expect(undoManager.redoDescription).toBe(undefined)\n\n    await undoManager.perform(\n        async () => {\n            values.push('b')\n        },\n        async () => {\n            values.pop()\n        },\n        'insert b',\n        groupId,\n    )\n\n    expect(undoManager.canUndo).toBe(true)\n    expect(undoManager.canRedo).toBe(false)\n    expect(values).toEqual(['a', 'b'])\n    expect(undoManager.undoDescription).toBe('insert b')\n    expect(undoManager.redoDescription).toBe(undefined)\n\n    await undoManager.perform(\n        async () => {\n            values.push('c')\n        },\n        async () => {\n            values.pop()\n        },\n        'insert c',\n        groupId,\n    )\n\n    expect(undoManager.canUndo).toBe(true)\n    expect(undoManager.canRedo).toBe(false)\n    expect(values).toEqual(['a', 'b', 'c'])\n    expect(undoManager.undoDescription).toBe('insert c')\n    expect(undoManager.redoDescription).toBe(undefined)\n\n    await undoManager.undo()\n    expect(undoManager.canUndo).toBe(true)\n    expect(undoManager.canRedo).toBe(true)\n    expect(values).toEqual(['a'])\n    expect(undoManager.undoDescription).toBe('insert a')\n    expect(undoManager.redoDescription).toBe('insert b')\n\n    await undoManager.redo()\n    expect(undoManager.canUndo).toBe(true)\n    expect(undoManager.canRedo).toBe(false)\n    expect(values).toEqual(['a', 'b', 'c'])\n    expect(undoManager.undoDescription).toBe('insert c')\n    expect(undoManager.redoDescription).toBe(undefined)\n\n    await undoManager.clear()\n    expect(undoManager.canUndo).toBe(false)\n    expect(undoManager.canRedo).toBe(false)\n    expect(undoManager.undoDescription).toBe(undefined)\n    expect(undoManager.redoDescription).toBe(undefined)\n})\n"
  },
  {
    "path": "webapp/src/undomanager.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\ninterface UndoCommand {\n    checkpoint: number\n    undo: (value?: any) => Promise<void>\n    redo: () => Promise<void>\n    description?: string\n    groupId?: string\n    value?: any\n}\n\n//\n// General-purpose undo manager\n//\nclass UndoManager {\n    onStateDidChange?: () => void\n\n    private commands: UndoCommand[] = []\n    private index = -1\n    private limit = 0\n    private isExecuting = false\n\n    get currentCheckpoint() {\n        if (this.index < 0) {\n            return 0\n        }\n        return this.commands[this.index].checkpoint\n    }\n\n    get undoDescription(): string | undefined {\n        const command = this.commands[this.index]\n        if (!command) {\n            return undefined\n        }\n\n        return command.description\n    }\n\n    get redoDescription(): string | undefined {\n        const command = this.commands[this.index + 1]\n        if (!command) {\n            return undefined\n        }\n\n        return command.description\n    }\n\n    private async execute(command: UndoCommand, action: 'undo' | 'redo') {\n        if (!command || typeof command[action] !== 'function') {\n            return this\n        }\n        this.isExecuting = true\n\n        if (action === 'redo') {\n            command.value = await command[action]()\n        } else {\n            await command[action](command.value)\n        }\n\n        this.isExecuting = false\n        return this\n    }\n\n    async perform(\n        redo: () => Promise<any>,\n        undo: (value?: any) => Promise<void>,\n        description?: string,\n        groupId?: string,\n        isDiscardable = false,\n    ): Promise<any> {\n        const value = await redo()\n        this.registerUndo({undo, redo}, description, groupId, value, isDiscardable)\n        return value\n    }\n\n    registerUndo(\n        command: {\n            undo: (value?: any) => Promise<void>\n            redo: () => Promise<void>\n        },\n        description?: string,\n        groupId?: string,\n        value?: any,\n        isDiscardable = false,\n    ): UndoManager {\n        if (this.isExecuting) {\n            return this\n        }\n\n        // If we are here after having called undo, invalidate items higher on the stack\n        this.commands.splice(this.index + 1, this.commands.length - this.index)\n\n        let checkpoint: number\n        if (isDiscardable) {\n            checkpoint =\n                this.commands.length > 1 ? this.commands[this.commands.length - 1].checkpoint : 0\n        } else {\n            checkpoint = Date.now()\n        }\n\n        const internalCommand = {\n            checkpoint,\n            undo: command.undo,\n            redo: command.redo,\n            description,\n            groupId,\n            value,\n        }\n        this.commands.push(internalCommand)\n\n        // If limit is set, remove items from the start\n        if (this.limit && this.commands.length > this.limit) {\n            this.commands = this.commands.splice(\n                0,\n                this.commands.length - this.limit,\n            )\n        }\n\n        // Set the current index to the end\n        this.index = this.commands.length - 1\n\n        if (this.onStateDidChange) {\n            this.onStateDidChange()\n        }\n\n        return this\n    }\n\n    async undo() {\n        if (this.isExecuting) {\n            return this\n        }\n        let command = this.commands[this.index]\n        if (!command) {\n            return this\n        }\n\n        const currentGroupId = command.groupId\n        if (currentGroupId) {\n            do {\n                // eslint-disable-next-line no-await-in-loop\n                await this.execute(command, 'undo')\n                this.index -= 1\n                command = this.commands[this.index]\n            } while (this.index >= 0 && currentGroupId === command.groupId)\n        } else {\n            await this.execute(command, 'undo')\n            this.index -= 1\n        }\n\n        if (this.onStateDidChange) {\n            this.onStateDidChange()\n        }\n\n        return this\n    }\n\n    async redo() {\n        if (this.isExecuting) {\n            return this\n        }\n        let command = this.commands[this.index + 1]\n        if (!command) {\n            return this\n        }\n\n        const currentGroupId = command.groupId\n        if (currentGroupId) {\n            do {\n                // eslint-disable-next-line no-await-in-loop\n                await this.execute(command, 'redo')\n                this.index += 1\n                command = this.commands[this.index + 1]\n            } while (this.index < this.commands.length - 1 && currentGroupId === command.groupId)\n        } else {\n            await this.execute(command, 'redo')\n            this.index += 1\n        }\n\n        if (this.onStateDidChange) {\n            this.onStateDidChange()\n        }\n\n        return this\n    }\n\n    clear() {\n        const prevSize = this.commands.length\n\n        this.commands = []\n        this.index = -1\n\n        if (this.onStateDidChange && prevSize > 0) {\n            this.onStateDidChange()\n        }\n    }\n\n    get canUndo() {\n        return this.index !== -1\n    }\n\n    get canRedo() {\n        return this.index < this.commands.length - 1\n    }\n}\n\nconst undoManager = new UndoManager()\nexport default undoManager\n"
  },
  {
    "path": "webapp/src/user.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\ninterface IUser {\n    id: string\n    username: string\n    email: string\n    nickname: string\n    firstname: string\n    lastname: string\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    props: Record<string, any>\n    create_at: number\n    update_at: number\n    is_bot: boolean\n    is_guest: boolean\n    permissions?: string[]\n    roles: string\n}\n\ninterface UserWorkspace {\n    id: string\n    title: string\n    boardCount: number\n}\n\ninterface UserConfigPatch {\n    updatedFields?: Record<string, string>\n    deletedFields?: string[]\n}\n\nfunction parseUserProps(props: UserPreference[]): Record<string, UserPreference> {\n    const processedProps: Record<string, UserPreference> = {}\n\n    props.forEach((prop) => {\n        const processedProp = prop\n        if (prop.name === 'hiddenBoardIDs') {\n            const hiddenBoardIDs = JSON.parse(processedProp.value)\n            processedProp.value = {}\n            hiddenBoardIDs.forEach((boardID: string) => {\n                processedProp.value[boardID] = true\n            })\n        }\n        processedProps[processedProp.name] = processedProp\n    })\n\n    return processedProps\n}\n\ninterface UserPreference {\n    user_id: string\n    category: string\n    name: string\n    value: any\n}\n\nexport {\n    IUser,\n    UserWorkspace,\n    UserConfigPatch,\n    parseUserProps,\n    UserPreference,\n}\n"
  },
  {
    "path": "webapp/src/userSettings.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {notifySettingsChanged} from './nativeApp'\nimport {Utils} from './utils'\n\n// eslint-disable-next-line no-shadow\nexport enum UserSettingKey {\n    Language = 'language',\n    Theme = 'theme',\n    LastTeamId = 'lastTeamId',\n    LastBoardId = 'lastBoardId',\n    LastViewId = 'lastViewId',\n    EmojiMartSkin = 'emoji-mart.skin',\n    EmojiMartLast = 'emoji-mart.last',\n    EmojiMartFrequently = 'emoji-mart.frequently',\n    RandomIcons = 'randomIcons',\n    MobileWarningClosed = 'mobileWarningClosed',\n    WelcomePageViewed = 'welcomePageViewed',\n    NameFormat = 'nameFormat'\n}\n\nexport class UserSettings {\n    static get(key: UserSettingKey): string | null {\n        return localStorage.getItem(key)\n    }\n\n    static set(key: UserSettingKey, value: string | null): void {\n        if (!Object.values(UserSettingKey).includes(key)) {\n            return\n        }\n        if (value === null) {\n            localStorage.removeItem(key)\n        } else {\n            localStorage.setItem(key, value)\n        }\n        notifySettingsChanged(key)\n    }\n\n    static get language(): string | null {\n        return UserSettings.get(UserSettingKey.Language)\n    }\n\n    static set language(newValue: string | null) {\n        UserSettings.set(UserSettingKey.Language, newValue)\n    }\n\n    static get theme(): string | null {\n        return UserSettings.get(UserSettingKey.Theme)\n    }\n\n    static set theme(newValue: string | null) {\n        UserSettings.set(UserSettingKey.Theme, newValue)\n    }\n\n    static get lastTeamId(): string | null {\n        return UserSettings.get(UserSettingKey.LastTeamId)\n    }\n\n    static set lastTeamId(newValue: string | null) {\n        UserSettings.set(UserSettingKey.LastTeamId, newValue)\n    }\n\n    // maps last board ID for each team\n    // maps teamID -> board ID\n    static get lastBoardId(): {[key: string]: string} {\n        let rawData = UserSettings.get(UserSettingKey.LastBoardId) || '{}'\n        if (rawData[0] !== '{') {\n            rawData = '{}'\n        }\n\n        let mapping: {[key: string]: string}\n        try {\n            mapping = JSON.parse(rawData)\n        } catch {\n            // revert to empty data if JSON conversion fails.\n            // This will happen when users run the new code for the first time\n            mapping = {}\n        }\n\n        return mapping\n    }\n\n    static setLastTeamID(teamID: string | null): void {\n        UserSettings.set(UserSettingKey.LastTeamId, teamID)\n    }\n\n    static setLastBoardID(teamID: string, boardID: string | null): void {\n        const data = this.lastBoardId\n        if (boardID === null) {\n            delete data[teamID]\n        } else {\n            data[teamID] = boardID\n        }\n        UserSettings.set(UserSettingKey.LastBoardId, JSON.stringify(data))\n    }\n\n    static get lastViewId(): {[key: string]: string} {\n        const rawData = UserSettings.get(UserSettingKey.LastViewId) || '{}'\n        let mapping: {[key: string]: string}\n        try {\n            mapping = JSON.parse(rawData)\n        } catch {\n            // revert to empty data if JSON conversion fails.\n            // This will happen when users run the new code for the first time\n            mapping = {}\n        }\n\n        return mapping\n    }\n\n    static setLastViewId(boardID: string, viewID: string | null): void {\n        const data = this.lastViewId\n        if (viewID === null) {\n            delete data[boardID]\n        } else {\n            data[boardID] = viewID\n        }\n        UserSettings.set(UserSettingKey.LastViewId, JSON.stringify(data))\n    }\n\n    static get prefillRandomIcons(): boolean {\n        return UserSettings.get(UserSettingKey.RandomIcons) !== 'false'\n    }\n\n    static set prefillRandomIcons(newValue: boolean) {\n        UserSettings.set(UserSettingKey.RandomIcons, JSON.stringify(newValue))\n    }\n\n    static getEmojiMartSetting(key: string): any {\n        const prefixed = `emoji-mart.${key}`\n        Utils.assert((Object as any).values(UserSettingKey).includes(prefixed))\n        const json = UserSettings.get(prefixed as UserSettingKey)\n        return json ? JSON.parse(json) : null\n    }\n\n    static setEmojiMartSetting(key: string, value: any): void {\n        const prefixed = `emoji-mart.${key}`\n        Utils.assert((Object as any).values(UserSettingKey).includes(prefixed))\n        UserSettings.set(prefixed as UserSettingKey, JSON.stringify(value))\n    }\n\n    static get mobileWarningClosed(): boolean {\n        return UserSettings.get(UserSettingKey.MobileWarningClosed) === 'true'\n    }\n\n    static set mobileWarningClosed(newValue: boolean) {\n        UserSettings.set(UserSettingKey.MobileWarningClosed, String(newValue))\n    }\n\n    static get nameFormat(): string | null {\n        return UserSettings.get(UserSettingKey.NameFormat)\n    }\n\n    static set nameFormat(newValue: string | null) {\n        UserSettings.set(UserSettingKey.NameFormat, newValue)\n    }\n}\n\nexport function exportUserSettingsBlob(): string {\n    return window.btoa(exportUserSettings())\n}\n\nfunction exportUserSettings(): string {\n    const keys = Object.values(UserSettingKey)\n    const settings = Object.fromEntries(keys.map((key) => [key, localStorage.getItem(key)]))\n    settings.timestamp = `${Date.now()}`\n    return JSON.stringify(settings)\n}\n\nexport function importUserSettingsBlob(blob: string): string[] {\n    return importUserSettings(window.atob(blob))\n}\n\nfunction importUserSettings(json: string): string[] {\n    const settings = parseUserSettings(json)\n    if (!settings) {\n        return []\n    }\n    const timestamp = settings.timestamp\n    const lastTimestamp = localStorage.getItem('timestamp')\n    if (!timestamp || (lastTimestamp && Number(timestamp) <= Number(lastTimestamp))) {\n        return []\n    }\n    const importedKeys = []\n    for (const [key, value] of Object.entries(settings)) {\n        if (Object.values(UserSettingKey).includes(key as UserSettingKey)) {\n            if (value) {\n                localStorage.setItem(key, value as string)\n            } else {\n                localStorage.removeItem(key)\n            }\n            importedKeys.push(key)\n        }\n    }\n    return importedKeys\n}\n\nfunction parseUserSettings(json: string): any {\n    try {\n        return JSON.parse(json)\n    } catch (e) {\n        return undefined\n    }\n}\n"
  },
  {
    "path": "webapp/src/utils.test.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {createIntl} from 'react-intl'\n\nimport {createMemoryHistory} from 'history'\n\nimport {match as routerMatch} from 'react-router-dom'\n\nimport {Utils, IDType, ShowFullName, ShowNicknameFullName, ShowUsername} from './utils'\nimport {IUser} from './user'\n\nimport {IAppWindow} from './types'\n\ndeclare let window: IAppWindow\n\ndescribe('utils', () => {\n    describe('assureProtocol', () => {\n        test('should passthrough on valid short protocol', () => {\n            expect(Utils.ensureProtocol('https://focalboard.com')).toBe('https://focalboard.com')\n        })\n        test('should passthrough on valid long protocol', () => {\n            expect(Utils.ensureProtocol('somecustomprotocol://focalboard.com')).toBe('somecustomprotocol://focalboard.com')\n        })\n\n        test('should passthrough on valid short protocol', () => {\n            expect(Utils.ensureProtocol('x://focalboard.com')).toBe('x://focalboard.com')\n        })\n\n        test('should add a https for empty protocol', () => {\n            expect(Utils.ensureProtocol('focalboard.com')).toBe('https://focalboard.com')\n        })\n    })\n\n    describe('createGuid', () => {\n        test('should create 27 char random id for workspace', () => {\n            expect(Utils.createGuid(IDType.Workspace)).toMatch(/^w[ybndrfg8ejkmcpqxot1uwisza345h769]{26}$/)\n        })\n        test('should create 27 char random id for board', () => {\n            expect(Utils.createGuid(IDType.Board)).toMatch(/^b[ybndrfg8ejkmcpqxot1uwisza345h769]{26}$/)\n        })\n        test('should create 27 char random id for card', () => {\n            expect(Utils.createGuid(IDType.Card)).toMatch(/^c[ybndrfg8ejkmcpqxot1uwisza345h769]{26}$/)\n        })\n        test('should create 27 char random id', () => {\n            expect(Utils.createGuid(IDType.None)).toMatch(/^7[ybndrfg8ejkmcpqxot1uwisza345h769]{26}$/)\n        })\n    })\n\n    describe('htmlFromMarkdown', () => {\n        test('should not allow XSS on links href on the webapp', () => {\n            expect(Utils.htmlFromMarkdown('[](\"xss-attack=\"true\"other=\"whatever)')).toBe('<p><a target=\"_blank\" rel=\"noreferrer\" href=\"%22xss-attack=%22true%22other=%22whatever\" title=\"\" onclick=\"\"></a></p>')\n        })\n\n        test('should not allow XSS on links href on the desktop app', () => {\n            window.openInNewBrowser = () => null\n            const expectedHtml = '<p><a target=\"_blank\" rel=\"noreferrer\" href=\"%22xss-attack=%22true%22other=%22whatever\" title=\"\" onclick=\" openInNewBrowser && openInNewBrowser(event.target.href);\"></a></p>'\n            expect(Utils.htmlFromMarkdown('[](\"xss-attack=\"true\"other=\"whatever)')).toBe(expectedHtml)\n            window.openInNewBrowser = null\n        })\n\n        test('should encode links', () => {\n            expect(Utils.htmlFromMarkdown('https://example.com?title=August<1>2022')).toBe('<p><a target=\"_blank\" rel=\"noreferrer\" href=\"https://example.com?title=August&lt;1%3E2022\" title=\"\" onclick=\"\">https://example.com?title=August&lt;1&gt;2022</a></p>')\n            expect(Utils.htmlFromMarkdown('[Duck Duck Go](https://duckduckgo.com \"The best search engine\\'s for <privacy>\")')).toBe('<p><a target=\"_blank\" rel=\"noreferrer\" href=\"https://duckduckgo.com\" title=\"The best search engine&#39;s for &lt;privacy&gt;\" onclick=\"\">Duck Duck Go</a></p>')\n        })\n\n        test('should not double encode title and href', () => {\n            expect(Utils.htmlFromMarkdown('https://example.com?title=August%201%20-%202022')).toBe('<p><a target=\"_blank\" rel=\"noreferrer\" href=\"https://example.com?title=August%201%20-%202022\" title=\"\" onclick=\"\">https://example.com?title=August%201%20-%202022</a></p>')\n            expect(Utils.htmlFromMarkdown('[Duck Duck Go](https://duckduckgo.com \"The best search engine#39;s for &lt;privacy&gt;\")')).toBe('<p><a target=\"_blank\" rel=\"noreferrer\" href=\"https://duckduckgo.com\" title=\"The best search engine#39;s for &lt;privacy&gt;\" onclick=\"\">Duck Duck Go</a></p>')\n        })\n    })\n\n    describe('countCheckboxesInMarkdown', () => {\n        test('should count checkboxes', () => {\n            const text = `\n                ## Header\n                - [x] one\n                - [ ] two\n                - [x] three\n            `.replace(/\\n\\s+/gm, '\\n')\n            const checkboxes = Utils.countCheckboxesInMarkdown(text)\n            expect(checkboxes.total).toBe(3)\n            expect(checkboxes.checked).toBe(2)\n        })\n    })\n\n    describe('test - buildURL', () => {\n        test('buildURL, no base', () => {\n            (global as any).isFocalboardPlugin = true\n            expect(Utils.buildURL('test', true)).toBe('http://localhost/test')\n            expect(Utils.buildURL('/test', true)).toBe('http://localhost/test')\n\n            expect(Utils.buildURL('test')).toBe('/test')\n            expect(Utils.buildURL('/test')).toBe('/test')\n        })\n\n        test('buildURL, base no slash', () => {\n            window.baseURL = 'base'\n\n            expect(Utils.buildURL('test', true)).toBe('http://localhost/base/test')\n            expect(Utils.buildURL('/test', true)).toBe('http://localhost/base/test')\n\n            expect(Utils.buildURL('test')).toBe('base/test')\n            expect(Utils.buildURL('/test')).toBe('base/test')\n        })\n\n        test('buildUrl, base with slash', () => {\n            window.baseURL = '/base/'\n\n            expect(Utils.buildURL('test', true)).toBe('http://localhost/base/test')\n            expect(Utils.buildURL('/test', true)).toBe('http://localhost/base/test')\n\n            expect(Utils.buildURL('test')).toBe('base/test')\n            expect(Utils.buildURL('/test')).toBe('base/test')\n        })\n    })\n\n    describe('display date', () => {\n        const intl = createIntl({locale: 'en-us'})\n\n        it('should show month and day for current year', () => {\n            const currentYear = new Date().getFullYear()\n            const date = new Date(currentYear, 6, 9)\n            expect(Utils.displayDate(date, intl)).toBe('July 09')\n        })\n\n        it('should show month, day and year for previous year', () => {\n            const currentYear = new Date().getFullYear()\n            const previousYear = currentYear - 1\n            const date = new Date(previousYear, 6, 9)\n            expect(Utils.displayDate(date, intl)).toBe(`July 09, ${previousYear}`)\n        })\n    })\n\n    describe('input date', () => {\n        const currentYear = new Date().getFullYear()\n        const date = new Date(currentYear, 6, 9)\n\n        it('should show mm/dd/yyyy for current year', () => {\n            const intl = createIntl({locale: 'en-us'})\n            expect(Utils.inputDate(date, intl)).toBe(`07/09/${currentYear}`)\n        })\n\n        it('should show dd/mm/yyyy for current year, es local', () => {\n            const intl = createIntl({locale: 'es-es'})\n            expect(Utils.inputDate(date, intl)).toBe(`09/07/${currentYear}`)\n        })\n    })\n\n    describe('display date and time', () => {\n        const intl = createIntl({locale: 'en-us'})\n\n        it('should show month, day and time for current year', () => {\n            const currentYear = new Date().getFullYear()\n            const date = new Date(currentYear, 6, 9, 15, 20)\n            expect(Utils.displayDateTime(date, intl)).toBe('July 09 at 3:20 PM')\n        })\n\n        it('should show month, day, year and time for previous year', () => {\n            const currentYear = new Date().getFullYear()\n            const previousYear = currentYear - 1\n            const date = new Date(previousYear, 6, 9, 5, 35)\n            expect(Utils.displayDateTime(date, intl)).toBe(`July 09, ${previousYear} at 5:35 AM`)\n        })\n    })\n\n    describe('compare versions', () => {\n        it('should return one if b > a', () => {\n            expect(Utils.compareVersions('0.9.4', '0.10.0')).toBe(1)\n        })\n\n        it('should return zero if a = b', () => {\n            expect(Utils.compareVersions('1.2.3', '1.2.3')).toBe(0)\n        })\n\n        it('should return minus one if b < a', () => {\n            expect(Utils.compareVersions('10.9.4', '10.9.2')).toBe(-1)\n        })\n    })\n\n    describe('showBoard test', () => {\n        it('should switch boards', () => {\n            const match = {\n                params: {\n                    boardId: 'board_id_1',\n                    viewId: 'view_id_1',\n                    cardId: 'card_id_1',\n                    teamId: 'team_id_1',\n                },\n                path: '/team/:teamId/:boardId?/:viewId?/:cardId?',\n            } as unknown as routerMatch<{boardId: string, viewId?: string, cardId?: string, teamId?: string}>\n\n            const history = createMemoryHistory()\n            history.push = jest.fn()\n\n            Utils.showBoard('board_id_2', match, history)\n\n            expect(history.push).toBeCalledWith('/team/team_id_1/board_id_2')\n        })\n    })\n\n    describe('getUserDisplayName test', () => {\n        const user: IUser = {\n            id: 'user-id-1',\n            username: 'username_1',\n            email: 'test@email.com',\n            nickname: 'nickname',\n            firstname: 'firstname',\n            lastname: 'lastname',\n            props: {},\n            create_at: 0,\n            update_at: 0,\n            is_bot: false,\n            is_guest: false,\n            roles: 'system_user',\n        }\n\n        it('should display username, by default', () => {\n            const displayName = Utils.getUserDisplayName(user, '')\n            expect(displayName).toEqual('username_1')\n        })\n        it('should display nickname', () => {\n            const displayName = Utils.getUserDisplayName(user, ShowNicknameFullName)\n            expect(displayName).toEqual('nickname')\n        })\n        it('should display fullname', () => {\n            const displayName = Utils.getUserDisplayName(user, ShowFullName)\n            expect(displayName).toEqual('firstname lastname')\n        })\n        it('should display username', () => {\n            const displayName = Utils.getUserDisplayName(user, ShowUsername)\n            expect(displayName).toEqual('username_1')\n        })\n        it('should display full name, no nickname', () => {\n            user.nickname = ''\n            const displayName = Utils.getUserDisplayName(user, ShowNicknameFullName)\n            expect(displayName).toEqual('firstname lastname')\n        })\n        it('should display username, no nickname, no full name', () => {\n            user.nickname = ''\n            user.firstname = ''\n            user.lastname = ''\n            const displayName = Utils.getUserDisplayName(user, ShowNicknameFullName)\n            expect(displayName).toEqual('username_1')\n        })\n    })\n})\n"
  },
  {
    "path": "webapp/src/utils.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport {marked} from 'marked'\nimport {IntlShape} from 'react-intl'\nimport moment from 'moment'\n\nimport {generatePath, match as routerMatch} from 'react-router-dom'\n\nimport {History} from 'history'\n\nimport {IUser} from './user'\n\nimport {Block} from './blocks/block'\nimport {Board as BoardType, BoardMember, createBoard} from './blocks/board'\nimport {createBoardView} from './blocks/boardView'\nimport {createCard} from './blocks/card'\nimport {createCommentBlock} from './blocks/commentBlock'\nimport {IAppWindow} from './types'\nimport {ChangeHandlerType, WSMessage} from './wsclient'\nimport {BoardCategoryWebsocketData, Category} from './store/sidebar'\nimport {UserSettings} from './userSettings'\n\ndeclare let window: IAppWindow\n\nconst imageURLForUser = typeof window === 'undefined' ? undefined : (window as any).Components?.imageURLForUser\nconst IconClass = 'octo-icon'\nconst OpenButtonClass = 'open-button'\nconst SpacerClass = 'octo-spacer'\nconst HorizontalGripClass = 'HorizontalGrip'\nconst base32Alphabet = 'ybndrfg8ejkmcpqxot1uwisza345h769'\n\nexport const SYSTEM_ADMIN_ROLE = 'system_admin'\nexport const TEAM_ADMIN_ROLE = 'team_admin'\nexport type CategoryOrder = string[]\n\nexport type WSMessagePayloads = Block | Category | BoardCategoryWebsocketData[] | BoardType | BoardMember | null | CategoryOrder\n\n// eslint-disable-next-line no-shadow\nenum IDType {\n    None = '7',\n    Workspace = 'w',\n    Board = 'b',\n    Card = 'c',\n    View = 'v',\n    Session = 's',\n    User = 'u',\n    Token = 'k',\n    BlockID = 'a',\n}\n\nexport const KeyCodes: Record<string, [string, number]> = {\n    ENTER: ['Enter', 13],\n    COMPOSING: ['Composing', 229],\n}\n\nexport const ShowUsername = 'username'\nexport const ShowNicknameFullName = 'nickname_full_name'\nexport const ShowFullName = 'full_name'\n\nclass Utils {\n    static createGuid(idType: IDType): string {\n        const data = Utils.randomArray(16)\n        return idType + Utils.base32encode(data, false)\n    }\n\n    static blockTypeToIDType(blockType: string | undefined): IDType {\n        let ret: IDType = IDType.None\n        switch (blockType) {\n        case 'workspace':\n            ret = IDType.Workspace\n            break\n        case 'board':\n            ret = IDType.Board\n            break\n        case 'card':\n            ret = IDType.Card\n            break\n        case 'view':\n            ret = IDType.View\n            break\n        }\n        return ret\n    }\n\n    static getProfilePicture(userId?: string): string {\n        const defaultImageUrl = 'data:image/svg+xml,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 100\" style=\"fill: rgb(192, 192, 192);\"><rect width=\"100\" height=\"100\" /></svg>'\n\n        return imageURLForUser && userId ? imageURLForUser(userId) : defaultImageUrl\n    }\n\n    static getUserDisplayName(user: IUser, configNameFormat: string): string {\n        let nameFormat = configNameFormat\n        if (UserSettings.nameFormat) {\n            nameFormat = UserSettings.nameFormat\n        }\n\n        // default nameFormat = 'username'\n        let displayName = user.username\n\n        if (nameFormat === ShowNicknameFullName) {\n            if (user.nickname === '') {\n                const fullName = Utils.getFullName(user)\n                if (fullName !== '') {\n                    displayName = fullName\n                }\n            } else {\n                displayName = user.nickname\n            }\n        } else if (nameFormat === ShowFullName) {\n            const fullName = Utils.getFullName(user)\n            if (fullName !== '') {\n                displayName = fullName\n            }\n        }\n        return displayName\n    }\n\n    static getFullName(user: IUser): string {\n        if (user.firstname !== '' && user.lastname !== '') {\n            return user.firstname + ' ' + user.lastname\n        } else if (user.firstname !== '') {\n            return user.firstname\n        } else if (user.lastname !== '') {\n            return user.lastname\n        }\n        return ''\n    }\n\n    static randomArray(size: number): Uint8Array {\n        const crypto = window.crypto || window.msCrypto\n        const rands = new Uint8Array(size)\n        if (crypto && crypto.getRandomValues) {\n            crypto.getRandomValues(rands)\n        } else {\n            for (let i = 0; i < size; i++) {\n                rands[i] = Math.floor((Math.random() * 255))\n            }\n        }\n        return rands\n    }\n\n    static base32encode(data: Int8Array | Uint8Array | Uint8ClampedArray, pad: boolean): string {\n        const dview = new DataView(data.buffer, data.byteOffset, data.byteLength)\n        let bits = 0\n        let value = 0\n        let output = ''\n\n        // adapted from https://github.com/LinusU/base32-encode\n        for (let i = 0; i < dview.byteLength; i++) {\n            value = (value << 8) | dview.getUint8(i)\n            bits += 8\n\n            while (bits >= 5) {\n                output += base32Alphabet[(value >>> (bits - 5)) & 31]\n                bits -= 5\n            }\n        }\n        if (bits > 0) {\n            output += base32Alphabet[(value << (5 - bits)) & 31]\n        }\n        if (pad) {\n            while ((output.length % 8) !== 0) {\n                output += '='\n            }\n        }\n        return output\n    }\n\n    // general purpose (non-secure) hash\n    static hashCode(s: string) {\n        let h = 0\n        for (let i = 0; i < s.length; i++) {\n            h = Math.imul(31, h) + s.charCodeAt(i) | 0\n        }\n        return h\n    }\n\n    static htmlToElement(html: string): HTMLElement {\n        const template = document.createElement('template')\n        template.innerHTML = html.trim()\n        return template.content.firstChild as HTMLElement\n    }\n\n    static getElementById(elementId: string): HTMLElement {\n        const element = document.getElementById(elementId)\n        Utils.assert(element, `getElementById \"${elementId}$`)\n        return element!\n    }\n\n    static htmlEncode(text: string): string {\n        return String(text).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;')\n    }\n\n    static htmlDecode(text: string): string {\n        return String(text).replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&quot;/g, '\"')\n    }\n\n    // re-use canvas object for better performance\n    static canvas: HTMLCanvasElement | undefined\n    static getTextWidth(displayText: string, fontDescriptor: string): number {\n        if (displayText !== '') {\n            if (!Utils.canvas) {\n                Utils.canvas = document.createElement('canvas') as HTMLCanvasElement\n            }\n            const context = Utils.canvas.getContext('2d')\n            if (context) {\n                context.font = fontDescriptor\n                const metrics = context.measureText(displayText)\n                return Math.ceil(metrics.width)\n            }\n        }\n        return 0\n    }\n\n    static getFontAndPaddingFromCell = (cell: Element): {fontDescriptor: string, padding: number} => {\n        const style = getComputedStyle(cell)\n        const padding = Utils.getTotalHorizontalPadding(style)\n        return Utils.getFontAndPaddingFromChildren(cell.children, padding)\n    }\n\n    // recursive routine to determine the padding and font from its children\n    // specifically for the table view\n    static getFontAndPaddingFromChildren = (children: HTMLCollection, pad: number): {fontDescriptor: string, padding: number} => {\n        const myResults = {\n            fontDescriptor: '',\n            padding: pad,\n        }\n        Array.from(children).forEach((element) => {\n            const style = getComputedStyle(element)\n            if (element.tagName === 'svg') {\n                // clientWidth already includes padding\n                myResults.padding += element.clientWidth\n                myResults.padding += Utils.getHorizontalBorder(style)\n                myResults.padding += Utils.getHorizontalMargin(style)\n                myResults.fontDescriptor = Utils.getFontString(style)\n            } else {\n                switch (element.className) {\n                case IconClass:\n                case HorizontalGripClass:\n                    myResults.padding += element.clientWidth\n                    break\n                case SpacerClass:\n                case OpenButtonClass:\n                    break\n                default: {\n                    myResults.fontDescriptor = Utils.getFontString(style)\n                    myResults.padding += Utils.getTotalHorizontalPadding(style)\n                    const childResults = Utils.getFontAndPaddingFromChildren(element.children, myResults.padding)\n                    if (childResults.fontDescriptor !== '') {\n                        myResults.fontDescriptor = childResults.fontDescriptor\n                        myResults.padding = childResults.padding\n                    }\n                }\n                }\n            }\n        })\n        return myResults\n    }\n\n    private static getFontString(style: CSSStyleDeclaration): string {\n        if (style.font) {\n            return style.font\n        }\n        const {fontStyle, fontVariant, fontWeight, fontSize, lineHeight, fontFamily} = style\n        const props = [fontStyle, fontVariant, fontWeight]\n        if (fontSize) {\n            props.push(lineHeight ? `${fontSize} / ${lineHeight}` : fontSize)\n        }\n        props.push(fontFamily)\n        return props.join(' ')\n    }\n\n    private static getHorizontalMargin(style: CSSStyleDeclaration): number {\n        return parseInt(style.marginLeft, 10) + parseInt(style.marginRight, 10)\n    }\n\n    private static getHorizontalBorder(style: CSSStyleDeclaration): number {\n        return parseInt(style.borderLeftWidth, 10) + parseInt(style.borderRightWidth, 10)\n    }\n\n    private static getHorizontalPadding(style: CSSStyleDeclaration): number {\n        return parseInt(style.paddingLeft, 10) + parseInt(style.paddingRight, 10)\n    }\n\n    private static getTotalHorizontalPadding(style: CSSStyleDeclaration): number {\n        return Utils.getHorizontalPadding(style) + Utils.getHorizontalMargin(style) + Utils.getHorizontalBorder(style)\n    }\n\n    // Markdown\n\n    static htmlFromMarkdown(text: string): string {\n        // HACKHACK: Somehow, marked doesn't encode angle brackets\n        const renderer = new marked.Renderer()\n        renderer.link = (href, title, contents) => {\n            return '<a ' +\n                'target=\"_blank\" ' +\n                'rel=\"noreferrer\" ' +\n                `href=\"${encodeURI(decodeURI(href || ''))}\" ` +\n                `title=\"${title || ''}\" ` +\n                `onclick=\"${(window.openInNewBrowser ? ' openInNewBrowser && openInNewBrowser(event.target.href);' : '')}\"` +\n            '>' + contents + '</a>'\n        }\n\n        renderer.table = (header, body) => {\n            return `<div class=\"table-responsive\"><table class=\"markdown__table\"><thead>${header}</thead><tbody>${body}</tbody></table></div>`\n        }\n\n        return this.htmlFromMarkdownWithRenderer(text, renderer)\n    }\n\n    static htmlFromMarkdownWithRenderer(text: string, renderer: marked.Renderer): string {\n        const html = marked(text.replace(/</g, '&lt;'), {renderer, breaks: true})\n        return html.trim()\n    }\n\n    static countCheckboxesInMarkdown(text: string): {total: number, checked: number} {\n        let total = 0\n        let checked = 0\n        const renderer = new marked.Renderer()\n        renderer.checkbox = (isChecked) => {\n            ++total\n            if (isChecked) {\n                ++checked\n            }\n            return ''\n        }\n        this.htmlFromMarkdownWithRenderer(text, renderer)\n        return {total, checked}\n    }\n\n    // Date and Time\n    private static yearOption(date: Date) {\n        const isCurrentYear = date.getFullYear() === new Date().getFullYear()\n        return isCurrentYear ? undefined : 'numeric'\n    }\n\n    static displayDate(date: Date, intl: IntlShape): string {\n        return intl.formatDate(date, {\n            year: Utils.yearOption(date),\n            month: 'long',\n            day: '2-digit',\n        })\n    }\n\n    static inputDate(date: Date, intl: IntlShape): string {\n        return intl.formatDate(date, {\n            year: 'numeric',\n            month: '2-digit',\n            day: '2-digit',\n        })\n    }\n\n    static displayDateTime(date: Date, intl: IntlShape): string {\n        return intl.formatDate(date, {\n            year: Utils.yearOption(date),\n            month: 'long',\n            day: '2-digit',\n            hour: 'numeric',\n            minute: 'numeric',\n        })\n    }\n\n    static relativeDisplayDateTime(date: Date, intl: IntlShape): string {\n        return moment(date).locale(intl.locale.toLowerCase()).fromNow()\n    }\n\n    static sleep(miliseconds: number): Promise<void> {\n        return new Promise((resolve) => setTimeout(resolve, miliseconds))\n    }\n\n    // Errors\n\n    static assertValue(valueObject: any): void {\n        const name = Object.keys(valueObject)[0]\n        const value = valueObject[name]\n        if (!value) {\n            Utils.logError(`ASSERT VALUE [${name}]`)\n        }\n    }\n\n    static assert(condition: any, tag = ''): void {\n        /// #!if ENV !== \"production\"\n        if (!condition) {\n            Utils.logError(`ASSERT [${tag ?? new Error().stack}]`)\n        }\n\n        /// #!endif\n    }\n\n    static assertFailure(tag = ''): void {\n        /// #!if ENV !== \"production\"\n        Utils.assert(false, tag)\n\n        /// #!endif\n    }\n\n    static log(message: string): void {\n        /// #!if ENV !== \"production\"\n        const timestamp = (Date.now() / 1000).toFixed(2)\n        // eslint-disable-next-line no-console\n        console.log(`[${timestamp}] ${message}`)\n\n        /// #!endif\n    }\n\n    static logError(message: string): void {\n        /// #!if ENV !== \"production\"\n        const timestamp = (Date.now() / 1000).toFixed(2)\n        // eslint-disable-next-line no-console\n        console.error(`[${timestamp}] ${message}`)\n\n        /// #!endif\n    }\n\n    static logWarn(message: string): void {\n        /// #!if ENV !== \"production\"\n        const timestamp = (Date.now() / 1000).toFixed(2)\n        // eslint-disable-next-line no-console\n        console.warn(`[${timestamp}] ${message}`)\n\n        /// #!endif\n    }\n\n    // favicon\n\n    static setFavicon(icon?: string): void {\n        if (!icon) {\n            document.querySelector(\"link[rel*='icon']\")?.remove()\n            return\n        }\n        const link = document.createElement('link') as HTMLLinkElement\n        link.type = 'image/x-icon'\n        link.rel = 'shortcut icon'\n        link.href = `data:image/svg+xml,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 100\"><text y=\".9em\" font-size=\"90\">${icon}</text></svg>`\n        document.querySelectorAll(\"link[rel*='icon']\").forEach((n) => n.remove())\n        document.getElementsByTagName('head')[0].appendChild(link)\n    }\n\n    // URL\n\n    static replaceUrlQueryParam(paramName: string, value?: string): void {\n        const queryString = new URLSearchParams(window.location.search)\n        const currentValue = queryString.get(paramName) || ''\n        if (currentValue !== value) {\n            const newUrl = new URL(window.location.toString())\n            if (value) {\n                newUrl.searchParams.set(paramName, value)\n            } else {\n                newUrl.searchParams.delete(paramName)\n            }\n            window.history.pushState({}, document.title, newUrl.toString())\n        }\n    }\n\n    static ensureProtocol(url: string): string {\n        return url.match(/^.+:\\/\\//) ? url : `https://${url}`\n    }\n\n    // File names\n\n    static sanitizeFilename(filename: string): string {\n        // TODO: Use an industrial-strength sanitizer\n        let sanitizedFilename = filename\n        const illegalCharacters = ['\\\\', '/', '?', ':', '<', '>', '*', '|', '\"', '.']\n        illegalCharacters.forEach((character) => {\n            sanitizedFilename = sanitizedFilename.replace(character, '')\n        })\n        return sanitizedFilename\n    }\n\n    // File picker\n\n    static selectLocalFile(onSelect?: (file: File) => void, accept = '.jpg,.jpeg,.png'): void {\n        const input = document.createElement('input')\n        input.type = 'file'\n        input.accept = accept\n        input.onchange = async () => {\n            const file = input.files![0]\n            onSelect?.(file)\n        }\n\n        input.style.display = 'none'\n        document.body.appendChild(input)\n        input.click()\n\n        // TODO: Remove or reuse input\n    }\n\n    // Arrays\n\n    static arraysEqual(a: readonly any[], b: readonly any[]): boolean {\n        if (a === b) {\n            return true\n        }\n        if (a === null || b === null) {\n            return false\n        }\n        if (a === undefined || b === undefined) {\n            return false\n        }\n        if (a.length !== b.length) {\n            return false\n        }\n\n        for (let i = 0; i < a.length; ++i) {\n            if (a[i] !== b[i]) {\n                return false\n            }\n        }\n        return true\n    }\n\n    static arrayMove(arr: any[], srcIndex: number, destIndex: number): void {\n        arr.splice(destIndex, 0, arr.splice(srcIndex, 1)[0])\n    }\n\n    // Clipboard\n\n    static copyTextToClipboard(text: string): boolean {\n        const textField = document.createElement('textarea')\n        textField.innerText = text\n        textField.style.position = 'fixed'\n        textField.style.opacity = '0'\n\n        document.body.appendChild(textField)\n        textField.select()\n\n        let result = false\n        try {\n            result = document.execCommand('copy')\n        } catch (err) {\n            Utils.logError(`copyTextToClipboard ERROR: ${err}`)\n            result = false\n        }\n        textField.remove()\n\n        return result\n    }\n\n    static isMobile(): boolean {\n        const toMatch = [\n            /Android/i,\n            /webOS/i,\n            /iPhone/i,\n            /iPad/i,\n            /iPod/i,\n            /BlackBerry/i,\n            /Windows Phone/i,\n        ]\n\n        return toMatch.some((toMatchItem) => {\n            return navigator.userAgent.match(toMatchItem)\n        })\n    }\n\n    static getBaseURL(absolute?: boolean): string {\n        let baseURL = window.baseURL || ''\n        baseURL = baseURL.replace(/\\/+$/, '')\n        if (baseURL.indexOf('/') === 0) {\n            baseURL = baseURL.slice(1)\n        }\n        if (absolute) {\n            return window.location.origin + '/' + baseURL\n        }\n        return baseURL\n    }\n\n    static getFrontendBaseURL(absolute?: boolean): string {\n        let frontendBaseURL = window.frontendBaseURL || Utils.getBaseURL()\n        frontendBaseURL = frontendBaseURL.replace(/\\/+$/, '')\n        if (frontendBaseURL.indexOf('/') === 0) {\n            frontendBaseURL = frontendBaseURL.slice(1)\n        }\n        if (absolute) {\n            return window.location.origin + '/' + frontendBaseURL\n        }\n        return frontendBaseURL\n    }\n\n    static buildURL(path: string, absolute?: boolean): string {\n        const baseURL = Utils.getBaseURL()\n        let finalPath = baseURL + path\n        if (path.indexOf('/') !== 0) {\n            finalPath = baseURL + '/' + path\n        }\n        if (absolute) {\n            if (finalPath.indexOf('/') === 0) {\n                finalPath = finalPath.slice(1)\n            }\n            return window.location.origin + '/' + finalPath\n        }\n        return finalPath\n    }\n\n    static roundTo(num: number, decimalPlaces: number): number {\n        return Math.round(num * Math.pow(10, decimalPlaces)) / Math.pow(10, decimalPlaces)\n    }\n\n    // this is a temporary solution while we're using legacy routes\n    // for shared boards as a way to check if we're accessing the\n    // legacy routes inside the plugin\n    static isFocalboardLegacy(): boolean {\n        return window.location.pathname.includes('/plugins/focalboard')\n    }\n\n    static fixWSData(message: WSMessage): [WSMessagePayloads, ChangeHandlerType] {\n        if (message.block) {\n            return [this.fixBlock(message.block), 'block']\n        } else if (message.board) {\n            return [this.fixBoard(message.board), 'board']\n        } else if (message.category) {\n            return [message.category, 'category']\n        } else if (message.blockCategories) {\n            return [message.blockCategories, 'blockCategories']\n        } else if (message.member) {\n            return [message.member, 'boardMembers']\n        } else if (message.categoryOrder) {\n            return [message.categoryOrder, 'categoryOrder']\n        }\n        return [null, 'block']\n    }\n\n    static fixBlock(block: Block): Block {\n        switch (block.type) {\n        case 'view':\n            return createBoardView(block)\n        case 'card':\n            return createCard(block)\n        case 'comment':\n            return createCommentBlock(block)\n        default:\n            return block\n        }\n    }\n\n    static fixBoard(board: BoardType): BoardType {\n        return createBoard(board)\n    }\n\n    static userAgent(): string {\n        return window.navigator.userAgent\n    }\n\n    static isDesktopApp(): boolean {\n        return Utils.userAgent().indexOf('Mattermost') !== -1 && Utils.userAgent().indexOf('Electron') !== -1\n    }\n\n    static getDesktopVersion(): string {\n        // use if the value window.desktop.version is not set yet\n        const regex = /Mattermost\\/(\\d+\\.\\d+\\.\\d+)/gm\n        const match = regex.exec(window.navigator.appVersion)?.[1] || ''\n        return match\n    }\n\n    /**\n     * Function to check how a version compares to another\n     *\n     * eg.  versionA = 4.16.0, versionB = 4.17.0 returns  1\n     *      versionA = 4.16.1, versionB = 4.16.1 returns  0\n     *      versionA = 4.16.1, versionB = 4.15.0 returns -1\n     */\n    static compareVersions(versionA: string, versionB: string): number {\n        if (versionA === versionB) {\n            return 0\n        }\n\n        // We only care about the numbers\n        const versionANumber = (versionA || '').split('.').filter((x) => (/^[0-9]+$/).exec(x) !== null)\n        const versionBNumber = (versionB || '').split('.').filter((x) => (/^[0-9]+$/).exec(x) !== null)\n\n        for (let i = 0; i < Math.max(versionANumber.length, versionBNumber.length); i++) {\n            const a = parseInt(versionANumber[i], 10) || 0\n            const b = parseInt(versionBNumber[i], 10) || 0\n            if (a > b) {\n                return -1\n            }\n\n            if (a < b) {\n                return 1\n            }\n        }\n\n        // If all components are equal, then return true\n        return 0\n    }\n\n    static isDesktop(): boolean {\n        return Utils.isDesktopApp() && (Utils.compareVersions(Utils.getDesktopVersion(), '5.0.0') <= 0)\n    }\n\n    static getReadToken(): string {\n        const queryString = new URLSearchParams(window.location.search)\n        const readToken = queryString.get('r') || ''\n        return readToken\n    }\n\n    static generateClassName(conditions: Record<string, boolean>): string {\n        return Object.entries(conditions).map(([className, condition]) => (condition ? className : '')).filter((className) => className !== '').join(' ')\n    }\n\n    static buildOriginalPath(teamID = '', boardId = '', viewId = '', cardId = ''): string {\n        let originalPath = ''\n\n        if (teamID) {\n            originalPath += `${teamID}/`\n        }\n\n        if (boardId) {\n            originalPath += `${boardId}/`\n        }\n\n        if (viewId) {\n            originalPath += `${viewId}/`\n        }\n\n        if (cardId) {\n            originalPath += `${cardId}/`\n        }\n\n        return originalPath\n    }\n\n    static uuid(): string {\n        return (window as any).URL.createObjectURL(new Blob([])).substr(-36)\n    }\n\n    static isKeyPressed(event: KeyboardEvent, key: [string, number]): boolean {\n        // There are two types of keyboards\n        // 1. English with different layouts(Ex: Dvorak)\n        // 2. Different language keyboards(Ex: Russian)\n        if (event.keyCode === KeyCodes.COMPOSING[1]) {\n            return false\n        }\n\n        // checks for event.key for older browsers and also for the case of different English layout keyboards.\n        if (typeof event.key !== 'undefined' && event.key !== 'Unidentified' && event.key !== 'Dead') {\n            const isPressedByCode = event.key === key[0] || event.key === key[0].toUpperCase()\n            if (isPressedByCode) {\n                return true\n            }\n        }\n\n        // used for different language keyboards to detect the position of keys\n        return event.keyCode === key[1]\n    }\n\n    static isMac() {\n        return navigator.platform.toUpperCase().indexOf('MAC') >= 0\n    }\n\n    static cmdOrCtrlPressed(e: KeyboardEvent, allowAlt = false) {\n        if (allowAlt) {\n            return (Utils.isMac() && e.metaKey) || (!Utils.isMac() && e.ctrlKey)\n        }\n        return (Utils.isMac() && e.metaKey) || (!Utils.isMac() && e.ctrlKey && !e.altKey)\n    }\n\n    static getBoardPagePath(currentPath: string) {\n        if (currentPath === '/team/:teamId/new/:channelId') {\n            return '/team/:teamId/:boardId?/:viewId?/:cardId?'\n        }\n        return currentPath\n    }\n\n    static showBoard(\n        boardId: string,\n        match: routerMatch<{boardId: string, viewId?: string, cardId?: string, teamId?: string}>,\n        history: History,\n    ) {\n        // if the same board, reuse the match params\n        // otherwise remove viewId and cardId, results in first view being selected\n        const params = {...match.params, boardId: boardId || ''}\n        if (boardId !== match.params.boardId) {\n            params.viewId = undefined\n            params.cardId = undefined\n        }\n        const newPath = generatePath(Utils.getBoardPagePath(match.path), params)\n        history.push(newPath)\n    }\n\n    static humanFileSize(bytesParam: number, si = false, dp = 1): string {\n        let bytes = bytesParam\n        const thresh = si ? 1000 : 1024\n\n        if (Math.abs(bytes) < thresh) {\n            return bytes + ' B'\n        }\n\n        const units = si ? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] : ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']\n        let u = -1\n        const r = 10 ** dp\n\n        do {\n            bytes /= thresh\n            ++u\n        } while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1)\n\n        return bytes.toFixed(dp) + ' ' + units[u]\n    }\n\n    static spaceSeparatedStringIncludes(item: string, spaceSeparated?: string): boolean {\n        if (spaceSeparated) {\n            const items = spaceSeparated?.split(' ')\n            return items.includes(item)\n        }\n        return false\n    }\n\n    static isSystemAdmin(roles: string): boolean {\n        return Utils.spaceSeparatedStringIncludes(SYSTEM_ADMIN_ROLE, roles)\n    }\n\n    static isTeamAdmin(roles: string): boolean {\n        return Utils.spaceSeparatedStringIncludes(TEAM_ADMIN_ROLE, roles)\n    }\n\n    static isAdmin(roles: string): boolean {\n        return Utils.isSystemAdmin(roles) || Utils.isTeamAdmin(roles)\n    }\n}\n\nexport {Utils, IDType}\n"
  },
  {
    "path": "webapp/src/widgets/__snapshots__/guestBadge.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`widgets/guestBadge should match the snapshot on show 1`] = `\n<div>\n  <div\n    class=\"GuestBadge\"\n  >\n    <div\n      class=\"GuestBadge__box\"\n    >\n      Guest\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/widgets/__snapshots__/propertyMenu.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`widgets/PropertyMenu should match snapshot 1`] = `\n<div>\n  <div\n    class=\"Menu noselect bottom \"\n  >\n    <div\n      class=\"menu-contents\"\n    >\n      <div\n        class=\"menu-options\"\n      >\n        <div>\n          <input\n            class=\"PropertyMenu menu-textbox menu-option\"\n            spellcheck=\"true\"\n            title=\"test-property\"\n            type=\"text\"\n            value=\"test-property\"\n          />\n        </div>\n        <div>\n          <div\n            class=\"MenuOption SubMenuOption menu-option menu-option-active\"\n            id=\"type\"\n          >\n            <div\n              class=\"noicon\"\n            />\n            <div\n              class=\"menu-name\"\n            >\n              Type: Text\n            </div>\n            <i\n              class=\"CompassIcon icon-chevron-right\"\n            />\n            <div\n              class=\"SubMenu Menu noselect bottom\"\n            >\n              <div\n                class=\"menu-contents\"\n              >\n                <div\n                  class=\"menu-options\"\n                >\n                  <div\n                    class=\"MenuOption LabelOption menu-option\"\n                  >\n                    <div\n                      class=\"noicon\"\n                    />\n                    <div\n                      class=\"menu-name\"\n                    >\n                      <b>\n                        Change property type\n                      </b>\n                    </div>\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                  <div\n                    class=\"MenuOption MenuSeparator menu-separator\"\n                  />\n                  <div\n                    aria-label=\"Text\"\n                    class=\"MenuOption TextOption menu-option\"\n                    role=\"button\"\n                  >\n                    <div\n                      class=\"d-flex\"\n                    >\n                      <div\n                        class=\"noicon\"\n                      />\n                    </div>\n                    <div\n                      class=\"menu-option__content\"\n                    >\n                      <div\n                        class=\"menu-name\"\n                      >\n                        Text\n                      </div>\n                    </div>\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                  <div\n                    aria-label=\"Number\"\n                    class=\"MenuOption TextOption menu-option\"\n                    role=\"button\"\n                  >\n                    <div\n                      class=\"d-flex\"\n                    >\n                      <div\n                        class=\"noicon\"\n                      />\n                    </div>\n                    <div\n                      class=\"menu-option__content\"\n                    >\n                      <div\n                        class=\"menu-name\"\n                      >\n                        Number\n                      </div>\n                    </div>\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                  <div\n                    aria-label=\"Email\"\n                    class=\"MenuOption TextOption menu-option\"\n                    role=\"button\"\n                  >\n                    <div\n                      class=\"d-flex\"\n                    >\n                      <div\n                        class=\"noicon\"\n                      />\n                    </div>\n                    <div\n                      class=\"menu-option__content\"\n                    >\n                      <div\n                        class=\"menu-name\"\n                      >\n                        Email\n                      </div>\n                    </div>\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                  <div\n                    aria-label=\"Phone\"\n                    class=\"MenuOption TextOption menu-option\"\n                    role=\"button\"\n                  >\n                    <div\n                      class=\"d-flex\"\n                    >\n                      <div\n                        class=\"noicon\"\n                      />\n                    </div>\n                    <div\n                      class=\"menu-option__content\"\n                    >\n                      <div\n                        class=\"menu-name\"\n                      >\n                        Phone\n                      </div>\n                    </div>\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                  <div\n                    aria-label=\"URL\"\n                    class=\"MenuOption TextOption menu-option\"\n                    role=\"button\"\n                  >\n                    <div\n                      class=\"d-flex\"\n                    >\n                      <div\n                        class=\"noicon\"\n                      />\n                    </div>\n                    <div\n                      class=\"menu-option__content\"\n                    >\n                      <div\n                        class=\"menu-name\"\n                      >\n                        URL\n                      </div>\n                    </div>\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                  <div\n                    aria-label=\"Select\"\n                    class=\"MenuOption TextOption menu-option\"\n                    role=\"button\"\n                  >\n                    <div\n                      class=\"d-flex\"\n                    >\n                      <div\n                        class=\"noicon\"\n                      />\n                    </div>\n                    <div\n                      class=\"menu-option__content\"\n                    >\n                      <div\n                        class=\"menu-name\"\n                      >\n                        Select\n                      </div>\n                    </div>\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                  <div\n                    aria-label=\"Multi select\"\n                    class=\"MenuOption TextOption menu-option\"\n                    role=\"button\"\n                  >\n                    <div\n                      class=\"d-flex\"\n                    >\n                      <div\n                        class=\"noicon\"\n                      />\n                    </div>\n                    <div\n                      class=\"menu-option__content\"\n                    >\n                      <div\n                        class=\"menu-name\"\n                      >\n                        Multi select\n                      </div>\n                    </div>\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                  <div\n                    aria-label=\"Date\"\n                    class=\"MenuOption TextOption menu-option\"\n                    role=\"button\"\n                  >\n                    <div\n                      class=\"d-flex\"\n                    >\n                      <div\n                        class=\"noicon\"\n                      />\n                    </div>\n                    <div\n                      class=\"menu-option__content\"\n                    >\n                      <div\n                        class=\"menu-name\"\n                      >\n                        Date\n                      </div>\n                    </div>\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                  <div\n                    aria-label=\"Person\"\n                    class=\"MenuOption TextOption menu-option\"\n                    role=\"button\"\n                  >\n                    <div\n                      class=\"d-flex\"\n                    >\n                      <div\n                        class=\"noicon\"\n                      />\n                    </div>\n                    <div\n                      class=\"menu-option__content\"\n                    >\n                      <div\n                        class=\"menu-name\"\n                      >\n                        Person\n                      </div>\n                    </div>\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                  <div\n                    aria-label=\"Multi person\"\n                    class=\"MenuOption TextOption menu-option\"\n                    role=\"button\"\n                  >\n                    <div\n                      class=\"d-flex\"\n                    >\n                      <div\n                        class=\"noicon\"\n                      />\n                    </div>\n                    <div\n                      class=\"menu-option__content\"\n                    >\n                      <div\n                        class=\"menu-name\"\n                      >\n                        Multi person\n                      </div>\n                    </div>\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                  <div\n                    aria-label=\"Checkbox\"\n                    class=\"MenuOption TextOption menu-option\"\n                    role=\"button\"\n                  >\n                    <div\n                      class=\"d-flex\"\n                    >\n                      <div\n                        class=\"noicon\"\n                      />\n                    </div>\n                    <div\n                      class=\"menu-option__content\"\n                    >\n                      <div\n                        class=\"menu-name\"\n                      >\n                        Checkbox\n                      </div>\n                    </div>\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                  <div\n                    aria-label=\"Created time\"\n                    class=\"MenuOption TextOption menu-option\"\n                    role=\"button\"\n                  >\n                    <div\n                      class=\"d-flex\"\n                    >\n                      <div\n                        class=\"noicon\"\n                      />\n                    </div>\n                    <div\n                      class=\"menu-option__content\"\n                    >\n                      <div\n                        class=\"menu-name\"\n                      >\n                        Created time\n                      </div>\n                    </div>\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                  <div\n                    aria-label=\"Created by\"\n                    class=\"MenuOption TextOption menu-option\"\n                    role=\"button\"\n                  >\n                    <div\n                      class=\"d-flex\"\n                    >\n                      <div\n                        class=\"noicon\"\n                      />\n                    </div>\n                    <div\n                      class=\"menu-option__content\"\n                    >\n                      <div\n                        class=\"menu-name\"\n                      >\n                        Created by\n                      </div>\n                    </div>\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                  <div\n                    aria-label=\"Last updated time\"\n                    class=\"MenuOption TextOption menu-option\"\n                    role=\"button\"\n                  >\n                    <div\n                      class=\"d-flex\"\n                    >\n                      <div\n                        class=\"noicon\"\n                      />\n                    </div>\n                    <div\n                      class=\"menu-option__content\"\n                    >\n                      <div\n                        class=\"menu-name\"\n                      >\n                        Last updated time\n                      </div>\n                    </div>\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                  <div\n                    aria-label=\"Last updated by\"\n                    class=\"MenuOption TextOption menu-option\"\n                    role=\"button\"\n                  >\n                    <div\n                      class=\"d-flex\"\n                    >\n                      <div\n                        class=\"noicon\"\n                      />\n                    </div>\n                    <div\n                      class=\"menu-option__content\"\n                    >\n                      <div\n                        class=\"menu-name\"\n                      >\n                        Last updated by\n                      </div>\n                    </div>\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                </div>\n                <div\n                  class=\"menu-spacer hideOnWidescreen\"\n                />\n                <div\n                  class=\"menu-options hideOnWidescreen\"\n                >\n                  <div\n                    aria-label=\"Cancel\"\n                    class=\"MenuOption TextOption menu-option menu-cancel\"\n                    role=\"button\"\n                  >\n                    <div\n                      class=\"d-flex\"\n                    >\n                      <div\n                        class=\"noicon\"\n                      />\n                    </div>\n                    <div\n                      class=\"menu-option__content\"\n                    >\n                      <div\n                        class=\"menu-name\"\n                      >\n                        Cancel\n                      </div>\n                    </div>\n                    <div\n                      class=\"noicon\"\n                    />\n                  </div>\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n        <div>\n          <div\n            aria-label=\"Delete\"\n            class=\"MenuOption TextOption menu-option\"\n            role=\"button\"\n          >\n            <div\n              class=\"d-flex\"\n            >\n              <div\n                class=\"noicon\"\n              />\n            </div>\n            <div\n              class=\"menu-option__content\"\n            >\n              <div\n                class=\"menu-name\"\n              >\n                Delete\n              </div>\n            </div>\n            <div\n              class=\"noicon\"\n            />\n          </div>\n        </div>\n      </div>\n      <div\n        class=\"menu-spacer hideOnWidescreen\"\n      />\n      <div\n        class=\"menu-options hideOnWidescreen\"\n      >\n        <div\n          aria-label=\"Cancel\"\n          class=\"MenuOption TextOption menu-option menu-cancel\"\n          role=\"button\"\n        >\n          <div\n            class=\"d-flex\"\n          >\n            <div\n              class=\"noicon\"\n            />\n          </div>\n          <div\n            class=\"menu-option__content\"\n          >\n            <div\n              class=\"menu-name\"\n            >\n              Cancel\n            </div>\n          </div>\n          <div\n            class=\"noicon\"\n          />\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/widgets/adminBadge/__snapshots__/adminBadge.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`widgets/adminBadge should match the snapshot for Admin 1`] = `\n<div>\n  <div\n    class=\"AdminBadge\"\n  >\n    <div\n      class=\"AdminBadge__box\"\n    >\n      Admin\n    </div>\n  </div>\n</div>\n`;\n\nexports[`widgets/adminBadge should match the snapshot for TeamAdmin 1`] = `\n<div>\n  <div\n    class=\"AdminBadge\"\n  >\n    <div\n      class=\"AdminBadge__box\"\n    >\n      Team Admin\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/widgets/adminBadge/adminBadge.scss",
    "content": ".AdminBadge {\n    display: inline-flex;\n    align-items: center;\n    margin: 0 10px 0 4px;\n}\n\n.AdminBadge__box {\n    padding: 2px 4px;\n    border: 0;\n    background: rgba(var(--center-channel-color-rgb), 0.16);\n    border-radius: 2px;\n    font-family: inherit;\n    font-size: 10px;\n    font-weight: 600;\n    line-height: 14px;\n}\n"
  },
  {
    "path": "webapp/src/widgets/adminBadge/adminBadge.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {render} from '@testing-library/react'\nimport '@testing-library/jest-dom'\n\nimport {wrapIntl} from '../../testUtils'\n\nimport AdminBadge from './adminBadge'\n\ndescribe('widgets/adminBadge', () => {\n    test('should match the snapshot for TeamAdmin', () => {\n        const {container} = render(wrapIntl(<AdminBadge permissions={['manage_team']}/>))\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match the snapshot for Admin', () => {\n        const {container} = render(wrapIntl(<AdminBadge permissions={['manage_team', 'manage_system']}/>))\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match the snapshot for empty', () => {\n        const {container} = render(wrapIntl(<AdminBadge permissions={[]}/>))\n        expect(container).toMatchInlineSnapshot('<div />')\n    })\n\n    test('should match the snapshot for invalid permission', () => {\n        const {container} = render(wrapIntl(<AdminBadge permissions={['invalid_permission']}/>))\n        expect(container).toMatchInlineSnapshot('<div />')\n    })\n})\n"
  },
  {
    "path": "webapp/src/widgets/adminBadge/adminBadge.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React, {memo} from 'react'\nimport {useIntl} from 'react-intl'\n\nimport './adminBadge.scss'\n\ntype Props = {\n    permissions?: string[]\n}\n\nconst AdminBadge = (props: Props) => {\n    const intl = useIntl()\n\n    if (!props.permissions) {\n        return null\n    }\n    let text = ''\n    if (props.permissions?.find((s) => s === 'manage_system')) {\n        text = intl.formatMessage({id: 'AdminBadge.SystemAdmin', defaultMessage: 'Admin'})\n    } else if (props.permissions?.find((s) => s === 'manage_team')) {\n        text = intl.formatMessage({id: 'AdminBadge.TeamAdmin', defaultMessage: 'Team Admin'})\n    } else {\n        return null\n    }\n    return (\n        <div className='AdminBadge'>\n            <div className='AdminBadge__box'>\n                {text}\n            </div>\n        </div>\n    )\n}\n\nexport default memo(AdminBadge)\n"
  },
  {
    "path": "webapp/src/widgets/buttons/button.scss",
    "content": ".Button {\n    --danger-button-bg-rgb: 247, 67, 67;\n    font-family: 'Open Sans', sans-serif;\n    display: flex;\n    flex: 0 0 auto;\n    align-items: center;\n    text-align: center;\n    justify-content: center;\n    border-radius: var(--default-rad);\n    min-width: 20px;\n    cursor: pointer;\n    overflow: hidden;\n    background: transparent;\n    border: 0;\n    transition: all 100ms ease-out 0s;\n    color: inherit;\n    height: 32px;\n    padding: 0 10px;\n    font-weight: 600;\n\n    &:hover {\n        background-color: rgba(var(--center-channel-color-rgb), 0.08);\n    }\n\n    .CompassIcon {\n        align-items: center;\n        display: flex;\n        height: 18px;\n        justify-content: center;\n        margin-right: 0;\n        width: 18px;\n        font-size: 18px;\n\n        &:first-child {\n            margin-right: 8px;\n        }\n\n        &:last-child {\n            margin-left: 8px;\n        }\n    }\n\n    &.filled,\n    &.emphasis--primary {\n        color: #fff;\n        background-color: rgb(var(--button-bg-rgb));\n        padding: 6px 12px;\n\n        &:hover {\n            background-color: rgb(var(--button-bg-rgb), 0.8);\n        }\n\n        &.danger {\n            background: linear-gradient(rgb(var(--danger-button-bg-rgb)), rgb(var(--danger-button-bg-rgb)));\n\n            &:hover {\n                background: linear-gradient(rgba(0, 0, 0, 0.08), rgba(0, 0, 0, 0.08)), linear-gradient(rgb(var(--danger-button-bg-rgb)), rgb(var(--danger-button-bg-rgb)));\n            }\n\n            &:active {\n                background: linear-gradient(rgba(0, 0, 0, 0.16), rgba(0, 0, 0, 0.16)), linear-gradient(rgb(var(--danger-button-bg-rgb)), rgb(var(--danger-button-bg-rgb)));\n            }\n        }\n    }\n\n    &.emphasis--secondary {\n        border: 1px solid rgb(var(--button-bg-rgb));\n        color: rgb(var(--button-bg-rgb));\n\n        &:hover {\n            background-color: rgb(var(--button-bg-rgb), 0.08);\n        }\n\n        &:active {\n            background-color: rgb(var(--button-bg-rgb), 0.16);\n        }\n    }\n\n    &.emphasis--tertiary {\n        background: rgba(var(--button-bg-rgb), 0.08);\n        color: rgb(var(--button-bg-rgb));\n\n        &:hover {\n            background-color: rgb(var(--button-bg-rgb), 0.12);\n        }\n\n        &:active {\n            background-color: rgb(var(--button-bg-rgb), 0.16);\n        }\n    }\n\n    &.emphasis--quaternary {\n        background: transparent;\n        color: rgb(var(--button-bg-rgb));\n\n        &:hover {\n            background: rgba(var(--button-bg-rgb), 0.08);\n        }\n\n        &:active {\n            background-color: rgb(var(--button-bg-rgb), 0.12);\n        }\n    }\n\n    &.emphasis--gray {\n        background: transparent;\n        color: rgba(var(--center-channel-color-rgb), 0.64);\n\n        &:hover {\n            color: rgba(var(--center-channel-color-rgb), 0.72);\n            background: rgba(var(--center-channel-color-rgb), 0.08);\n        }\n\n        &:active {\n            color: rgb(var(--button-bg-rgb));\n            background-color: rgb(var(--button-bg-rgb), 0.12);\n        }\n    }\n\n    &.emphasis--danger {\n        color: rgb(var(--button-danger-color-rgb));\n        background-color: rgb(var(--button-danger-bg-rgb));\n\n        &:hover {\n            background-color: rgb(var(--button-danger-bg-rgb), 0.8);\n        }\n    }\n\n    &.emphasis--default {\n        &:hover {\n            color: rgba(var(--center-channel-color-rgb), 0.72);\n        }\n    }\n\n    &.emphasis--link {\n        color: rgb(var(--button-bg-rgb));\n    }\n\n    &.active {\n        background: rgba(var(--button-bg-rgb), 0.08);\n        color: rgb(var(--button-bg-rgb));\n    }\n\n    &.size--xsmall {\n        font-size: 12px;\n        padding: 0 10px;\n        height: 24px;\n\n        .CompassIcon {\n            font-size: 14.4px;\n            width: 12px;\n            height: 12px;\n\n            &:first-child {\n                margin-right: 6px;\n            }\n\n            &:last-child {\n                margin-left: 6px;\n            }\n        }\n    }\n\n    &.size--small {\n        font-size: 12px;\n        padding: 0 16px;\n        height: 32px;\n\n        .CompassIcon {\n            font-size: 14.4px;\n            width: 12px;\n            height: 12px;\n\n            &:first-child {\n                margin-right: 6px;\n            }\n\n            &:last-child {\n                margin-left: 6px;\n            }\n        }\n    }\n\n    &.size--medium {\n        font-size: 14px;\n        font-weight: 600;\n        padding: 0 20px;\n        height: 40px;\n    }\n\n    &.size--large {\n        font-size: 16px;\n        height: 48px;\n        padding: 0 24px;\n\n        .CompassIcon {\n            font-size: 24px;\n            width: 20px;\n            height: 20px;\n\n            &:first-child {\n                margin-right: 10px;\n            }\n\n            &:last-child {\n                margin-left: 10px;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "webapp/src/widgets/buttons/button.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\n\nimport './button.scss'\nimport {Utils} from '../../utils'\n\ntype Props = {\n    onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void\n    onMouseOver?: (e: React.MouseEvent<HTMLButtonElement>) => void\n    onMouseLeave?: (e: React.MouseEvent<HTMLButtonElement>) => void\n    onBlur?: (e: React.FocusEvent<HTMLButtonElement>) => void\n    children?: React.ReactNode\n    title?: string\n    icon?: React.ReactNode\n    filled?: boolean\n    active?: boolean\n    submit?: boolean\n    emphasis?: string\n    size?: string\n    danger?: boolean\n    className?: string\n    rightIcon?: boolean\n    disabled?: boolean\n}\n\nfunction Button(props: Props): JSX.Element {\n    const classNames: Record<string, boolean> = {\n        Button: true,\n        active: Boolean(props.active),\n        filled: Boolean(props.filled),\n        danger: Boolean(props.danger),\n    }\n    classNames[`emphasis--${props.emphasis}`] = Boolean(props.emphasis)\n    classNames[`size--${props.size}`] = Boolean(props.size)\n    classNames[`${props.className}`] = Boolean(props.className)\n\n    return (\n        <button\n            type={props.submit ? 'submit' : 'button'}\n            onClick={props.onClick}\n            onMouseOver={props.onMouseOver}\n            onMouseLeave={props.onMouseLeave}\n            className={Utils.generateClassName(classNames)}\n            title={props.title}\n            onBlur={props.onBlur}\n            disabled={props?.disabled}\n        >\n            {!props.rightIcon && props.icon}\n            <span>{props.children}</span>\n            {props.rightIcon && props.icon}\n        </button>\n    )\n}\n\nexport default React.memo(Button)\n"
  },
  {
    "path": "webapp/src/widgets/buttons/buttonWithMenu.scss",
    "content": ".ButtonWithMenu {\n    display: flex;\n    align-items: stretch;\n    min-width: 20px;\n    cursor: pointer;\n    overflow: visible;\n    padding: 0;\n\n    .button-text {\n        padding: 3px 10px;\n        background-color: rgb(var(--button-bg-rgb));\n        color: rgb(var(--button-color-rgb));\n        border-radius: 5px 0 0 5px;\n        transition: background 100ms ease-out 0s;\n        height: 100%;\n\n        &:hover {\n            background-color: rgba(var(--button-bg-rgb), 0.8);\n        }\n    }\n\n    .button-dropdown {\n        background-color: rgb(var(--button-bg-rgb));\n        border-left: 1px solid rgba(var(--button-color-rgb), 0.2);\n        display: flex;\n        padding: 0 3px;\n        align-items: center;\n        border-radius: 0 5px 5px 0;\n        transition: background 100ms ease-out 0s;\n        height: 100%;\n\n        &:hover {\n            background-color: rgba(var(--button-bg-rgb), 0.8);\n        }\n\n        .DropdownIcon {\n            color: rgb(var(--button-color-rgb));\n        }\n    }\n}\n"
  },
  {
    "path": "webapp/src/widgets/buttons/buttonWithMenu.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\n\nimport DropdownIcon from '../icons/dropdown'\nimport MenuWrapper from '../menuWrapper'\n\nimport './buttonWithMenu.scss'\n\ntype Props = {\n    onClick?: (e: React.MouseEvent<HTMLDivElement>) => void\n    children?: React.ReactNode\n    title?: string\n    text: React.ReactNode\n}\n\nfunction ButtonWithMenu(props: Props): JSX.Element {\n    return (\n        <div\n            onClick={props.onClick}\n            className='ButtonWithMenu'\n            title={props.title}\n        >\n            <div className='button-text'>\n                {props.text}\n            </div>\n            <MenuWrapper stopPropagationOnToggle={true}>\n                <div className='button-dropdown'>\n                    <DropdownIcon/>\n                </div>\n                {props.children}\n            </MenuWrapper>\n        </div>\n    )\n}\n\nexport default React.memo(ButtonWithMenu)\n"
  },
  {
    "path": "webapp/src/widgets/buttons/iconButton.scss",
    "content": ".IconButton {\n    cursor: pointer;\n    border-radius: 4px;\n    flex: 0 0 24px;\n    height: 24px;\n    width: 24px;\n    padding: 0;\n    margin: 0;\n    background: transparent;\n    border: 0;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    transition: all 100ms ease-out 0s;\n    color: rgba(var(--center-channel-color-rgb), 0.56);\n\n    &:hover {\n        color: rgba(var(--center-channel-color-rgb), 0.72);\n        background-color: rgba(var(--center-channel-color-rgb), 0.08);\n    }\n\n    &:active {\n        background-color: rgba(var(--button-bg-rgb), 0.08);\n        color: rgba(var(--button-bg-rgb), 1);\n    }\n\n    &.style--inverted {\n        color: rgba(var(--sidebar-text-rgb), 0.64);\n\n        &:hover {\n            color: rgba(var(--sidebar-text-rgb), 1);\n            background-color: rgba(var(--sidebar-text-rgb), 0.08);\n        }\n\n        &:active {\n            color: rgba(var(--sidebar-text-rgb), 1);\n            background-color: rgba(var(--sidebar-text-rgb), 0.16);\n        }\n    }\n\n    .Icon {\n        height: 24px;\n        width: 24px;\n        padding: 0;\n        margin: 0;\n    }\n\n    &.size--large {\n        flex: 0 0 48px;\n        width: 48px;\n        height: 48px;\n        font-size: 31.2px;\n    }\n\n    &.size--medium {\n        flex: 0 0 40px;\n        width: 40px;\n        height: 40px;\n        font-size: 24px;\n    }\n\n    &.size--small {\n        flex: 0 0 32px;\n        width: 32px;\n        height: 32px;\n        font-size: 18px;\n    }\n\n    &.margin-left {\n        margin-left: 5px;\n    }\n}\n"
  },
  {
    "path": "webapp/src/widgets/buttons/iconButton.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\n\nimport './iconButton.scss'\nimport {Utils} from '../../utils'\n\ntype Props = {\n    onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void\n    title?: string\n    icon?: React.ReactNode\n    className?: string\n    size?: string\n    inverted?: boolean\n    onMouseDown?: (e: React.MouseEvent<HTMLButtonElement>) => void\n}\n\nfunction IconButton(props: Props): JSX.Element {\n    const classNames: Record<string, boolean> = {\n        IconButton: true,\n        'style--inverted': Boolean(props.inverted),\n    }\n    classNames[`${props.className}`] = Boolean(props.className)\n    classNames[`size--${props.size}`] = Boolean(props.size)\n\n    return (\n        <button\n            type='button'\n            onClick={props.onClick}\n            onMouseDown={props.onMouseDown}\n            className={Utils.generateClassName(classNames)}\n            title={props.title}\n            aria-label={props.title}\n        >\n            {props.icon}\n        </button>\n    )\n}\n\nexport default React.memo(IconButton)\n"
  },
  {
    "path": "webapp/src/widgets/editable.scss",
    "content": ".Editable {\n    cursor: text;\n    overflow: hidden;\n    text-overflow: ellipsis;\n    border: 1px solid transparent;\n    min-height: 24px;\n\n    &.active {\n        min-width: 100px;\n    }\n\n    &::placeholder {\n        color: rgba(var(--center-channel-color-rgb), 0.4);\n        opacity: 1;\n    }\n\n    &.error {\n        border: 1px solid rgb(var(--error-text-rgb));\n        border-radius: var(--default-rad);\n    }\n\n    &.readonly {\n        background-color: transparent;\n        flex: 1;\n    }\n}\n"
  },
  {
    "path": "webapp/src/widgets/editable.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {forwardRef, useImperativeHandle, useLayoutEffect, useRef} from 'react'\n\nimport './editable.scss'\n\nexport type EditableProps = {\n    onChange: (value: string) => void\n    value?: string\n    placeholderText?: string\n    className?: string\n    saveOnEsc?: boolean\n    readonly?: boolean\n    spellCheck?: boolean\n    autoExpand?: boolean\n\n    validator?: (value: string) => boolean\n    onCancel?: () => void\n    onSave?: (saveType: 'onEnter'|'onEsc'|'onBlur') => void\n    onFocus?: () => void\n}\n\nexport type Focusable = {\n    focus: (selectAll?: boolean) => void\n}\n\nexport type ElementType = HTMLInputElement | HTMLTextAreaElement\n\nexport type ElementProps = {\n    className: string\n    placeholder?: string\n    onChange: (e: React.ChangeEvent<HTMLTextAreaElement|HTMLInputElement>) => void\n    value?: string\n    title?: string\n    onBlur: () => void\n    onKeyDown: (e: React.KeyboardEvent<HTMLTextAreaElement|HTMLInputElement>) => void\n    readOnly?: boolean\n    spellCheck?: boolean\n    onFocus?: () => void\n}\n\nexport function useEditable(\n    props: EditableProps,\n    focusableRef: React.Ref<Focusable>,\n    elementRef: React.RefObject<ElementType>): ElementProps {\n    const saveOnBlur = useRef<boolean>(true)\n\n    const save = (saveType: 'onEnter'|'onEsc'|'onBlur'): void => {\n        if (props.validator && !props.validator(props.value || '')) {\n            return\n        }\n        if (!props.onSave) {\n            return\n        }\n        if (saveType === 'onBlur' && !saveOnBlur.current) {\n            return\n        }\n        if (saveType === 'onEsc' && !props.saveOnEsc) {\n            return\n        }\n        props.onSave(saveType)\n    }\n\n    useImperativeHandle(focusableRef, () => ({\n        focus: (selectAll = false): void => {\n            if (elementRef.current) {\n                const valueLength = elementRef.current.value.length\n                elementRef.current.focus()\n                if (selectAll) {\n                    elementRef.current.setSelectionRange(0, valueLength)\n                } else {\n                    elementRef.current.setSelectionRange(valueLength, valueLength)\n                }\n            }\n        },\n    }))\n\n    const blur = (): void => {\n        saveOnBlur.current = false\n        elementRef.current?.blur()\n        saveOnBlur.current = true\n    }\n\n    const {value, onChange, className, placeholderText, readonly} = props\n    let error = false\n    if (props.validator) {\n        error = !props.validator(value || '')\n    }\n    return {\n        className: 'Editable ' + (error ? 'error ' : '') + (readonly ? 'readonly ' : '') + (className || ''),\n        placeholder: placeholderText,\n        onChange: (e: React.ChangeEvent<ElementType>) => {\n            onChange(e.target.value)\n        },\n        value,\n        title: value,\n        onBlur: () => save('onBlur'),\n        onKeyDown: (e: React.KeyboardEvent<HTMLTextAreaElement|HTMLInputElement>): void => {\n            if (e.keyCode === 27 && !(e.metaKey || e.ctrlKey) && !e.shiftKey && !e.altKey) { // ESC\n                e.preventDefault()\n                if (props.saveOnEsc) {\n                    save('onEsc')\n                } else {\n                    props.onCancel?.()\n                }\n                blur()\n            } else if (e.keyCode === 13 && !(e.metaKey || e.ctrlKey) && !e.shiftKey && !e.altKey) { // Return\n                e.preventDefault()\n                save('onEnter')\n                blur()\n            }\n        },\n        readOnly: readonly,\n        spellCheck: props.spellCheck,\n        onFocus: props.onFocus,\n    }\n}\n\nconst Editable = (props: EditableProps, ref: React.Ref<Focusable>): JSX.Element => {\n    const elementRef = useRef<HTMLInputElement>(null)\n    const elementProps = useEditable(props, ref, elementRef)\n\n    useLayoutEffect(() => {\n        if (props.autoExpand && elementRef.current) {\n            const input = elementRef.current\n            input.style.width = '100%'\n        }\n    })\n\n    return (\n        <input\n            {...elementProps}\n            ref={elementRef}\n        />\n    )\n}\n\nexport default forwardRef(Editable)\n"
  },
  {
    "path": "webapp/src/widgets/editableArea.scss",
    "content": ".EditableAreaWrap {\n    width: 100%;\n}\n\n.EditableArea {\n    resize: none;\n}\n\n.EditableAreaContainer {\n    height: 0;\n    overflow: hidden;\n}\n\n.EditableAreaReference {\n    height: auto;\n    width: 100%;\n}\n"
  },
  {
    "path": "webapp/src/widgets/editableArea.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {forwardRef, useEffect, useRef} from 'react'\n\nimport {EditableProps, Focusable, useEditable} from './editable'\n\nimport './editableArea.scss'\n\nfunction getBorderWidth(style: CSSStyleDeclaration): number {\n    return parseInt(style.borderTopWidth || '0', 10) + parseInt(style.borderBottomWidth || '0', 10)\n}\n\nconst EditableArea = (props: EditableProps, ref: React.Ref<Focusable>): JSX.Element => {\n    const elementRef = useRef<HTMLTextAreaElement>(null)\n    const referenceRef = useRef<HTMLTextAreaElement>(null)\n    const heightRef = useRef(0)\n    const elementProps = useEditable(props, ref, elementRef)\n\n    useEffect(() => {\n        if (!elementRef.current || !referenceRef.current) {\n            return\n        }\n\n        const height = referenceRef.current.scrollHeight\n        const textarea = elementRef.current\n\n        if (height > 0 && height !== heightRef.current) {\n            const style = getComputedStyle(textarea)\n            const borderWidth = getBorderWidth(style)\n\n            // Directly change the height to avoid circular rerenders\n            textarea.style.height = String(height + borderWidth) + 'px'\n\n            heightRef.current = height\n        }\n    })\n\n    const heightProps = {\n        height: heightRef.current,\n        rows: 1,\n    }\n\n    return (\n        <div className={'EditableAreaWrap'}>\n            <textarea\n                {...elementProps}\n                {...heightProps}\n                ref={elementRef}\n                className={'EditableArea ' + elementProps.className}\n            />\n            <div className={'EditableAreaContainer'}>\n                <textarea\n                    ref={referenceRef}\n                    className={'EditableAreaReference ' + elementProps.className}\n                    dir='auto'\n                    disabled={true}\n                    rows={1}\n                    value={elementProps.value}\n                    aria-hidden={true}\n                />\n            </div>\n        </div>\n    )\n}\n\nexport default forwardRef(EditableArea)\n"
  },
  {
    "path": "webapp/src/widgets/editableDayPicker.scss",
    "content": ".EditableDayPicker {\n    .DayPickerInput {\n        input {\n            cursor: text;\n            overflow: hidden;\n            text-overflow: ellipsis;\n            border: 1px solid transparent;\n            min-height: 24px;\n\n            &.active {\n                min-width: 100px;\n            }\n\n            &::placeholder {\n                color: rgba(var(--center-channel-color-rgb), 0.4);\n                opacity: 1;\n            }\n\n            &.error {\n                border: 1px solid rgb(var(--error-text-rgb));\n                border-radius: var(--default-rad);\n            }\n        }\n    }\n\n    .DayPickerInput-Overlay {\n        background-color: rgba(var(--center-channel-bg-rgb));\n        box-shadow: 0 0 0 1px hsla(0, 0%, 0%, 0.1),0 4px 11px hsla(0, 0%, 0%, 0.1);\n        line-height: 100%;\n    }\n\n    .DayPicker-Day--today {\n        color: #c74655;\n    }\n\n    .DayPicker:not(.DayPicker--interactionDisabled)\n    .DayPicker-Day:not(.DayPicker-Day--disabled):not(.DayPicker-Day--selected):not(.DayPicker-Day--outside):hover {\n        background-color: rgba(var(--center-channel-color-rgb), 0.2);\n    }\n}\n"
  },
  {
    "path": "webapp/src/widgets/editableDayPicker.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useState} from 'react'\nimport {IntlShape, useIntl} from 'react-intl'\nimport DayPickerInput from 'react-day-picker/DayPickerInput'\nimport MomentLocaleUtils from 'react-day-picker/moment'\n\nimport {Utils} from '../utils'\n\nimport 'react-day-picker/lib/style.css'\nimport './editableDayPicker.scss'\n\ntype Props = {\n    className: string\n    value: string\n    onChange: (value: string | undefined) => void\n}\n\nconst loadedLocales: Record<string, moment.Locale> = {}\n\nconst updateLocales = (locale: string) => {\n    if (locale && locale !== 'en' && !loadedLocales[locale]) {\n        // eslint-disable-next-line global-require\n        loadedLocales[locale] = require(`moment/locale/${locale}`)\n    }\n}\n\nconst parseValue = (value: string): Date | undefined => {\n    return value ? new Date(parseInt(value, 10)) : undefined\n}\n\nconst displayDate = (date: Date | undefined, intl: IntlShape): string | undefined => {\n    if (date === undefined) {\n        return undefined\n    }\n    return Utils.displayDate(date, intl)\n}\n\nconst dateFormat = 'MM/DD/YYYY'\n\nfunction EditableDayPicker(props: Props): JSX.Element {\n    const {className, onChange} = props\n    const intl = useIntl()\n    const [value, setValue] = useState(() => parseValue(props.value))\n    const [dayPickerVisible, setDayPickerVisible] = useState(false)\n\n    const locale = intl.locale.toLowerCase()\n    updateLocales(locale)\n\n    const saveSelection = () => {\n        onChange(value?.getTime().toString())\n    }\n\n    const inputValue = dayPickerVisible ? value : displayDate(value, intl)\n\n    const parseDate = (str: string, format: string, withLocale: string) => {\n        if (str === inputValue) {\n            return value\n        }\n        return MomentLocaleUtils.parseDate(str, format, withLocale)\n    }\n\n    return (\n        <div className={'EditableDayPicker ' + className}>\n            <DayPickerInput\n                value={inputValue}\n                onDayChange={(day: Date) => setValue(day)}\n                onDayPickerShow={() => setDayPickerVisible(true)}\n                onDayPickerHide={() => {\n                    setDayPickerVisible(false)\n                    saveSelection()\n                }}\n                inputProps={{\n                    onKeyUp: (e: KeyboardEvent) => {\n                        if (e.key === 'Enter') {\n                            saveSelection()\n                        }\n                    },\n                }}\n                dayPickerProps={{\n                    locale,\n                    localeUtils: MomentLocaleUtils,\n                    todayButton: intl.formatMessage({id: 'EditableDayPicker.today', defaultMessage: 'Today'}),\n                }}\n                formatDate={MomentLocaleUtils.formatDate}\n                parseDate={parseDate}\n                format={dateFormat}\n                placeholder={dateFormat}\n            />\n        </div>\n    )\n}\n\nexport default EditableDayPicker\n"
  },
  {
    "path": "webapp/src/widgets/emojiPicker.scss",
    "content": ".EmojiPicker {\n    .emoji-mart {\n        color: rgb(var(--center-channel-color-rgb));\n        background: rgb(var(--center-channel-bg-rgb));\n        border-color: rgba(var(--center-channel-color-rgb), 0.16);\n\n        .emoji-mart-bar {\n            border: 0 solid rgba(var(--center-channel-color-rgb), 0.16);\n            border-bottom-width: 1px;\n        }\n\n        .emoji-mart-search input {\n            border: 1px solid rgba(var(--center-channel-color-rgb), 0.16);\n        }\n\n        .emoji-mart-category-label span {\n            background: rgb(var(--center-channel-bg-rgb));\n        }\n\n        .emoji-mart-search-icon svg {\n            fill: rgb(var(--center-channel-color-rgb));\n        }\n    }\n}\n"
  },
  {
    "path": "webapp/src/widgets/emojiPicker.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {FC} from 'react'\n\nimport 'emoji-mart/css/emoji-mart.css'\nimport {Picker, BaseEmoji} from 'emoji-mart'\n\nimport {Utils} from '../utils'\nimport './emojiPicker.scss'\n\nimport emojiSpirit from '../../static/emoji_spirit.png'\n\ntype Props = {\n    onSelect: (emoji: string) => void\n}\n\nconst EmojiPicker: FC<Props> = (props: Props): JSX.Element => (\n    <div\n        className='EmojiPicker'\n        onClick={(e) => e.stopPropagation()}\n    >\n        <Picker\n            onSelect={(emoji: BaseEmoji) => props.onSelect(emoji.native)}\n            backgroundImageFn={() => Utils.buildURL(emojiSpirit, true)}\n        />\n    </div>\n)\n\nexport default EmojiPicker\n"
  },
  {
    "path": "webapp/src/widgets/guestBadge.scss",
    "content": ".GuestBadge {\n    display: inline-flex;\n    align-items: center;\n    margin: 0 10px 0 4px;\n}\n\n.GuestBadge__box {\n    padding: 2px 4px;\n    border: 0;\n    background: rgba(var(--center-channel-color-rgb), 0.16);\n    border-radius: 2px;\n    font-family: inherit;\n    font-size: 10px;\n    font-weight: 600;\n    line-height: 14px;\n}\n"
  },
  {
    "path": "webapp/src/widgets/guestBadge.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {render} from '@testing-library/react'\nimport '@testing-library/jest-dom'\n\nimport {wrapIntl} from '../testUtils'\n\nimport GuestBadge from './guestBadge'\n\ndescribe('widgets/guestBadge', () => {\n    test('should match the snapshot on show', () => {\n        const {container} = render(wrapIntl(<GuestBadge show={true}/>))\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match the snapshot on hide', () => {\n        const {container} = render(wrapIntl(<GuestBadge show={false}/>))\n        expect(container).toMatchInlineSnapshot('<div />')\n    })\n})\n"
  },
  {
    "path": "webapp/src/widgets/guestBadge.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React, {memo} from 'react'\nimport {FormattedMessage} from 'react-intl'\n\nimport './guestBadge.scss'\n\ntype Props = {\n    show?: boolean\n}\n\nconst GuestBadge = (props: Props) => {\n    if (!props.show) {\n        return null\n    }\n    return (\n        <div className='GuestBadge'>\n            <div className='GuestBadge__box'>\n                <FormattedMessage\n                    id='badge.guest'\n                    defaultMessage='Guest'\n                />\n            </div>\n        </div>\n    )\n}\n\nexport default memo(GuestBadge)\n"
  },
  {
    "path": "webapp/src/widgets/icons/HandRight.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport CompassIcon from './compassIcon'\n\nexport default function HandRight(): JSX.Element {\n    return (\n        <CompassIcon\n            icon='hand-right'\n            className='HandRightIcon'\n        />\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/Link.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport CompassIcon from './compassIcon'\n\nimport './link.scss'\n\nexport default function LinkIcon(): JSX.Element {\n    return (\n        <CompassIcon\n            icon='link-variant'\n            className='LinkIcon'\n        />\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/add.scss",
    "content": ""
  },
  {
    "path": "webapp/src/widgets/icons/add.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport CompassIcon from './compassIcon'\n\nimport './add.scss'\n\nexport default function AddIcon(): JSX.Element {\n    return (\n        <CompassIcon\n            icon='plus'\n            className='AddIcon'\n        />\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/alert.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport CompassIcon from './compassIcon'\n\nexport default function AlertIcon(): JSX.Element {\n    return (\n        <CompassIcon\n            icon='alert-outline'\n            className='AlertIcon'\n        />\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/apps.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport CompassIcon from './compassIcon'\n\nexport default function Apps(): JSX.Element {\n    return (\n        <CompassIcon\n            icon='apps'\n            className='AppsIcon'\n        />\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/board.scss",
    "content": ".BoardIcon {\n    stroke-width: 8px;\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/board.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport './board.scss'\n\nexport default function BoardIcon(): JSX.Element {\n    return (\n        <svg\n            width='24'\n            height='24'\n            viewBox='0 0 24 24'\n            fill='currentColor'\n            xmlns='http://www.w3.org/2000/svg'\n            className='BoardIcon Icon'\n        >\n            <g opacity='0.8'>\n                <path\n                    fillRule='evenodd'\n                    clipRule='evenodd'\n                    d='M4 4H20V20H4V4ZM2 4C2 2.89543 2.89543 2 4 2H20C21.1046 2 22 2.89543 22 4V20C22 21.1046 21.1046 22 20 22H4C2.89543 22 2 21.1046 2 20V4ZM8 6H6V12H8V6ZM11 6H13V16H11V6ZM18 6H16V9H18V6Z'\n                    fill='currentColor'\n                />\n            </g>\n        </svg>\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/brokenFile.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport CompassIcon from './compassIcon'\n\nexport default function BrokenFile(): JSX.Element {\n    return (\n        <CompassIcon\n            icon='file-image-broken-outline'\n            className='BrokenFile'\n        />\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/calendar.scss",
    "content": ".CalendarIcon {\n    stroke-width: 8px;\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/calendar.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport './calendar.scss'\n\nexport default function CalendarIcon(): JSX.Element {\n    return (\n        <svg\n            width='24'\n            height='24'\n            viewBox='0 0 24 24'\n            fill='currentColor'\n            xmlns='http://www.w3.org/2000/svg'\n            className='CalendarIcon Icon'\n        >\n            <g opacity='0.8'>\n                <path\n                    fillRule='evenodd'\n                    clipRule='evenodd'\n                    d='M4 4H20V7L4 7V4ZM4 9L4 20H20V9L4 9ZM2 4C2 2.89543 2.89543 2 4 2H20C21.1046 2 22 2.89543 22 4V20C22 21.1046 21.1046 22 20 22H4C2.89543 22 2 21.1046 2 20V4ZM6 11H8V13H6V11ZM8 17V15H6V17H8ZM13 11V13H11V11H13ZM13 17V15H11V17H13ZM18 11V13H16V11H18ZM18 17V15H16V17H18Z'\n                    fill='currentColor'\n                />\n            </g>\n        </svg>\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/card.scss",
    "content": ".CardIcon {\n    stroke-width: 6px;\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/card.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport './card.scss'\n\nexport default function CardIcon(): JSX.Element {\n    return (\n        <svg\n            xmlns='http://www.w3.org/2000/svg'\n            className='CardIcon Icon'\n            viewBox='0 0 100 100'\n        >\n            <rect\n                x='20'\n                y='30'\n                width='60'\n                height='40'\n                rx='3'\n                ry='3'\n            />\n        </svg>\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/check.scss",
    "content": ".CheckIcon {\n    stroke: currentColor;\n    stroke-width: 8px;\n    fill: none;\n    width: 1em;\n    height: 1em;\n    line-height: 1em;\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/check.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport './check.scss'\n\nexport default function CheckIcon(): JSX.Element {\n    return (\n        <svg\n            xmlns='http://www.w3.org/2000/svg'\n            className='CheckIcon Icon'\n            viewBox='0 0 100 100'\n        >\n            <polyline points='20,60 40,80 80,40'/>\n        </svg>\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/checkIcon.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport CompassIcon from './compassIcon'\n\n// TODO use this icon instead of check.tsx\nexport default function Check(): JSX.Element {\n    return (\n        <CompassIcon\n            icon='check'\n            className='CheckIconCompass'\n        />\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/chevronDown.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport CompassIcon from './compassIcon'\n\nexport default function ChevronDown(): JSX.Element {\n    return (\n        <CompassIcon\n            icon='chevron-down'\n            className='ChevronDownIcon'\n        />\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/chevronRight.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport CompassIcon from './compassIcon'\n\nexport default function ChevronRight(): JSX.Element {\n    return (\n        <CompassIcon\n            icon='chevron-right'\n            className='ChevronRightIcon'\n        />\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/chevronUp.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport CompassIcon from './compassIcon'\n\nexport default function ChevronUp(): JSX.Element {\n    return (\n        <CompassIcon\n            icon='chevron-up'\n            className='ChevronUpIcon'\n        />\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/close.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport CompassIcon from './compassIcon'\n\nexport default function CloseIcon(): JSX.Element {\n    return (\n        <CompassIcon\n            icon='close'\n            className='CloseIcon'\n        />\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/closeCircle.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport CompassIcon from './compassIcon'\n\nimport './add.scss'\n\nexport default function CloseCircle(): JSX.Element {\n    return (\n        <CompassIcon\n            icon='close-circle'\n            className='CloseCircle'\n        />\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/compassIcon.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\ntype Props = {\n    icon: string\n    className?: string\n}\n\nexport default function CompassIcon(props: Props): JSX.Element {\n    // All compass icon classes start with icon,\n    // so not expecting that prefix in props.\n    return (\n        <i className={`CompassIcon icon-${props.icon}${props.className === undefined ? '' : ` ${props.className}`}`}/>\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/delete.scss",
    "content": ".DeleteIcon {\n    fill: rgba(var(--center-channel-color-rgb), 0.5);\n    stroke: none;\n    width: 24px;\n    height: 24px;\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/delete.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport CompassIcon from './compassIcon'\n\nimport './delete.scss'\n\nexport default function DeleteIcon(): JSX.Element {\n    return (\n        <CompassIcon\n            icon='trash-can-outline'\n            className='DeleteIcon trash-can-outline'\n        />\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/disclosureTriangle.scss",
    "content": ".DisclosureTriangleIcon {\n    fill: currentColor;\n    stroke: none;\n    width: 24px;\n    height: 24px;\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/disclosureTriangle.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport './disclosureTriangle.scss'\n\nexport default function DisclosureTriangle(): JSX.Element {\n    return (\n        <svg\n            xmlns='http://www.w3.org/2000/svg'\n            className='DisclosureTriangleIcon Icon'\n            viewBox='0 0 100 100'\n        >\n            <polygon points='37,35 37,65 63,50'/>\n        </svg>\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/divider.scss",
    "content": ".DividerIcon {\n    fill: currentColor;\n    stroke: none;\n    width: 24px;\n    height: 24px;\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/divider.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport './divider.scss'\n\nexport default function DividerIcon(): JSX.Element {\n    return (\n        <svg\n            xmlns='http://www.w3.org/2000/svg'\n            className='DividerIcon Icon'\n            viewBox='0 0 448 512'\n        >\n            <path\n                d='M 432,224 H 16 c -8.836556,0 -16,7.16344 -16,16 v 32 c 0,8.83656 7.163444,16 16,16 h 416 c 8.83656,0 16,-7.16344 16,-16 v -32 c 0,-8.83656 -7.16344,-16 -16,-16 z'\n            />\n        </svg>\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/dot.scss",
    "content": ".DotIcon {\n    fill: rgba(var(--center-channel-color-rgb), 0.5);\n    width: 24px;\n    height: 24px;\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/dot.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport './dot.scss'\n\nexport default function DotIcon(): JSX.Element {\n    return (\n        <svg\n            xmlns='http://www.w3.org/2000/svg'\n            className='DotIcon Icon'\n            viewBox='0 0 100 100'\n        >\n            <circle\n                cx='50'\n                cy='50'\n                r='5'\n            />\n        </svg>\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/dropdown.scss",
    "content": ".DropdownIcon {\n    color: rgb(var(--center-channel-color-rgb));\n    font-size: 16px;\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/dropdown.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport CompassIcon from './compassIcon'\n\nimport './dropdown.scss'\n\nexport default function DropdownIcon(): JSX.Element {\n    return (\n        <CompassIcon\n            icon='chevron-down'\n            className='DropdownIcon'\n        />\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/duplicate.scss",
    "content": ".DuplicateIcon {\n    fill: currentColor;\n    stroke: none;\n    width: 24px;\n    height: 24px;\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/duplicate.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport CompassIcon from './compassIcon'\n\nimport './duplicate.scss'\n\nexport default function DuplicateIcon(): JSX.Element {\n    return (\n        <CompassIcon\n            icon='content-copy'\n            className='content-copy'\n        />\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/edit.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport CompassIcon from './compassIcon'\n\nexport default function EditIcon(): JSX.Element {\n    return (\n        <CompassIcon\n            className='EditIcon'\n            icon='pencil-outline'\n        />\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/emoji.scss",
    "content": ".EmojiIcon {\n    fill: currentColor;\n    stroke: none;\n    width: 24px;\n    height: 24px;\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/emoji.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport './emoji.scss'\n\nexport default function EmojiIcon(): JSX.Element {\n    return (\n        <svg\n            xmlns='http://www.w3.org/2000/svg'\n            className='EmojiIcon Icon'\n            viewBox='0 0 496 512'\n        >\n            <path\n                d='M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 448c-110.3 0-200-89.7-200-200S137.7 56 248 56s200 89.7 200 200-89.7 200-200 200zm-80-216c17.7 0 32-14.3 32-32s-14.3-32-32-32-32 14.3-32 32 14.3 32 32 32zm160 0c17.7 0 32-14.3 32-32s-14.3-32-32-32-32 14.3-32 32 14.3 32 32 32zm4 72.6c-20.8 25-51.5 39.4-84 39.4s-63.2-14.3-84-39.4c-8.5-10.2-23.7-11.5-33.8-3.1-10.2 8.5-11.5 23.6-3.1 33.8 30 36 74.1 56.6 120.9 56.6s90.9-20.6 120.9-56.6c8.5-10.2 7.1-25.3-3.1-33.8-10.1-8.4-25.3-7.1-33.8 3.1z'\n            />\n        </svg>\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/focalboard_logo.scss",
    "content": ".FocalboardLogoIcon {\n    width: 32px;\n    height: 32px;\n    fill: rgba(var(--sidebar-text-rgb), 0.7);\n}\n\n"
  },
  {
    "path": "webapp/src/widgets/icons/focalboard_logo.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport './focalboard_logo.scss'\n\nexport default function FocalboardLogoIcon(): JSX.Element {\n    return (\n        <svg\n            className='FocalboardLogoIcon Icon'\n            version='1.1'\n            x='0px'\n            y='0px'\n            viewBox='0 0 52.589677 64'\n        >\n            <path\n                d='m 33.071077,12.069805 c -12.663,-3.4670001 -27.0530002,3.289 -31.6760002,16.943 -4.655,13.75 2.719,28.67 16.4690002,33.325 13.75,4.655 28.67,-2.719 33.326,-16.469 3.804,-11.235 -0.462,-22.701 -8.976,-29.249 l -0.46,4.871 h -0.001 c 4.631,4.896 6.709,11.941 4.325,18.985 -3.362,9.931 -14.447,15.151 -24.76,11.66 -10.313,-3.49 -15.9480002,-14.37 -12.5870002,-24.301 2.9750002,-8.788 11.9980002,-13.715 20.7430002,-12.625 v -10e-4 z m -6.175,16.488 c 3.456,-0.665 6.986,2.754 5.762,6.37 -0.854,2.522 -3.67,3.85 -6.291,2.962 -2.62,-0.887 -4.052,-3.651 -3.197,-6.174 0.573,-1.697 2.034,-2.852 3.726,-3.158 z m -1.285,-4.944 c -1.786,0.323 -3.45,1.104 -4.812,2.258 -1.299,1.101 -2.319,2.545 -2.898,4.258 -0.879,2.597 -0.579,5.323 0.617,7.632 1.206,2.329 3.325,4.234 6.07,5.164 2.744,0.929 5.584,0.701 7.959,-0.417 2.352,-1.107 4.246,-3.091 5.125,-5.688 0.555,-1.639 0.633,-3.254 0.344,-4.761 -0.21,-1.093 -0.615,-2.134 -1.174,-3.091 l 1.019,-5.107 c 0.189,0.187 0.374,0.378 0.552,0.574 1.75,1.919 3.008,4.283 3.508,6.877 0.415,2.154 0.304,4.457 -0.484,6.784 -1.239,3.661 -3.898,6.453 -7.193,8.005 -3.273,1.541 -7.175,1.858 -10.93,0.588 -3.754,-1.271 -6.661,-3.895 -8.326,-7.108 -1.674,-3.233 -2.09,-7.065 -0.851,-10.728 0.819,-2.419 2.26,-4.46 4.097,-6.016 1.88,-1.593 4.181,-2.673 6.656,-3.125 l -0.001,-0.004 c 1.759,-0.339 3.522,-0.313 5.213,0.016 l -3.583,3.761 c -0.294,0.028 -0.588,0.071 -0.883,0.127 h -0.025 z'\n            />\n            <polygon\n                points='26.057,32.594 37.495,11.658 36.79,8.44 41.066,0.207 43.683,4.611 48.803,4.434 44.185,12.48 40.902,13.697 29.542,34.491 '\n                transform='translate(7.6780426e-5,-0.21919512)'\n            />\n        </svg>\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/folder.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport CompassIcon from './compassIcon'\n\nexport default function Folder(): JSX.Element {\n    return (\n        <CompassIcon\n            icon='folder-outline'\n            className='Folder'\n        />\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/gallery.scss",
    "content": ".GalleryIcon {\n    fill: currentColor;\n    stroke: none;\n    width: 24px;\n    height: 24px;\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/gallery.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport './gallery.scss'\n\nexport default function GalleryIcon(): JSX.Element {\n    return (\n        <svg\n            width='24'\n            height='24'\n            viewBox='0 0 24 24'\n            fill='currentColor'\n            xmlns='http://www.w3.org/2000/svg'\n            className='GalleryIcon Icon'\n        >\n            <g opacity='0.8'>\n                <path\n                    fillRule='evenodd'\n                    clipRule='evenodd'\n                    d='M4 4H20V16.4462L16.3273 10.3784C15.9432 9.74384 15.0262 9.7336 14.6281 10.3594L10.6479 16.6154L8.83356 14.2458C8.43849 13.7299 7.66396 13.7219 7.25832 14.2296L4 18.3077V4ZM2 4C2 2.89543 2.89543 2 4 2H20C21.1046 2 22 2.89543 22 4V20C22 21.1046 21.1046 22 20 22H4C2.89543 22 2 21.1046 2 20V4ZM8.04507 11.7014C9.06719 11.7014 9.89577 10.8728 9.89577 9.8507C9.89577 8.82859 9.06719 8 8.04507 8C7.02296 8 6.19437 8.82859 6.19437 9.8507C6.19437 10.8728 7.02296 11.7014 8.04507 11.7014Z'\n                    fill='currentColor'\n                />\n            </g>\n        </svg>\n\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/globe.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport CompassIcon from './compassIcon'\n\nexport default function Globe(): JSX.Element {\n    return (\n        <CompassIcon\n            icon='globe'\n            className='GlobeIcon'\n        />\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/grip.scss",
    "content": ".GripIcon {\n    fill: rgb(var(--center-channel-color-rgb), 0.5);\n    stroke: none;\n    width: 24px;\n    height: 24px;\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/grip.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport './grip.scss'\n\nexport default function GripIcon(): JSX.Element {\n    return (\n        <svg\n            xmlns='http://www.w3.org/2000/svg'\n            className='GripIcon Icon'\n            viewBox='0 0 24 24'\n        >\n            <path\n                d='M0 0h24v24H0V0z'\n                fill='none'\n            />\n            <path d='M11 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm-2-8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z'/>\n        </svg>\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/hamburger.scss",
    "content": ".HamburgerIcon {\n    stroke: rgba(var(--center-channel-color-rgb), 0.5);\n    stroke-width: 6px;\n    fill: none;\n    width: 24px;\n    height: 24px;\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/hamburger.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport './hamburger.scss'\n\nexport default function HamburgerIcon(): JSX.Element {\n    return (\n        <svg\n            xmlns='http://www.w3.org/2000/svg'\n            className='HamburgerIcon Icon'\n            viewBox='0 0 100 100'\n        >\n            <polyline points='20,25 80,25'/>\n            <polyline points='20,50 80,50'/>\n            <polyline points='20,75 80,75'/>\n        </svg>\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/help.scss",
    "content": ".HelpIcon {\n    color: rgba(var(--sidebar-text-rgb), 0.7);\n    font-size: 16px;\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/help.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\n\nimport CompassIcon from './compassIcon'\n\nimport './help.scss'\n\nexport default function HelpIcon(): JSX.Element {\n    return (\n        <CompassIcon\n            icon='help-circle-outline'\n            className='HelpIcon'\n        />\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/hide.scss",
    "content": ".HideIcon {\n    fill: currentColor;\n    stroke: none;\n    width: 24px;\n    height: 24px;\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/hide.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport './hide.scss'\n\nexport default function HideIcon(): JSX.Element {\n    return (\n        <svg\n            xmlns='http://www.w3.org/2000/svg'\n            className='HideIcon Icon'\n            viewBox='0 0 640 512'\n        >\n            <path\n                d='M634 471L36 3.51A16 16 0 0 0 13.51 6l-10 12.49A16 16 0 0 0 6 41l598 467.49a16 16 0 0 0 22.49-2.49l10-12.49A16 16 0 0 0 634 471zM296.79 146.47l134.79 105.38C429.36 191.91 380.48 144 320 144a112.26 112.26 0 0 0-23.21 2.47zm46.42 219.07L208.42 260.16C210.65 320.09 259.53 368 320 368a113 113 0 0 0 23.21-2.46zM320 112c98.65 0 189.09 55 237.93 144a285.53 285.53 0 0 1-44 60.2l37.74 29.5a333.7 333.7 0 0 0 52.9-75.11 32.35 32.35 0 0 0 0-29.19C550.29 135.59 442.93 64 320 64c-36.7 0-71.71 7-104.63 18.81l46.41 36.29c18.94-4.3 38.34-7.1 58.22-7.1zm0 288c-98.65 0-189.08-55-237.93-144a285.47 285.47 0 0 1 44.05-60.19l-37.74-29.5a333.6 333.6 0 0 0-52.89 75.1 32.35 32.35 0 0 0 0 29.19C89.72 376.41 197.08 448 320 448c36.7 0 71.71-7.05 104.63-18.81l-46.41-36.28C359.28 397.2 339.89 400 320 400z'\n            />\n        </svg>\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/hideSidebar.scss",
    "content": ".HideSidebarIcon {\n    stroke: rgba(var(--center-channel-color-rgb), 0.5);\n    stroke-width: 6px;\n    fill: none;\n    width: 24px;\n    height: 24px;\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/hideSidebar.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport './hideSidebar.scss'\n\nexport default function HideSidebarIcon(): JSX.Element {\n    return (\n        <svg\n            xmlns='http://www.w3.org/2000/svg'\n            className='HideSidebarIcon Icon'\n            viewBox='0 0 100 100'\n        >\n            <polyline points='80,20 50,50 80,80'/>\n            <polyline points='50,20 20,50, 50,80'/>\n        </svg>\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/image.scss",
    "content": ".ImageIcon {\n    fill: currentColor;\n    stroke: none;\n    width: 24px;\n    height: 24px;\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/image.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport './image.scss'\n\nexport default function ImageIcon(): JSX.Element {\n    return (\n        <svg\n            xmlns='http://www.w3.org/2000/svg'\n            className='ImageIcon Icon'\n            viewBox='0 0 512 512'\n        >\n            <path\n                d='M464 64H48C21.49 64 0 85.49 0 112v288c0 26.51 21.49 48 48 48h416c26.51 0 48-21.49 48-48V112c0-26.51-21.49-48-48-48zm-6 336H54a6 6 0 0 1-6-6V118a6 6 0 0 1 6-6h404a6 6 0 0 1 6 6v276a6 6 0 0 1-6 6zM128 152c-22.091 0-40 17.909-40 40s17.909 40 40 40 40-17.909 40-40-17.909-40-40-40zM96 352h320v-80l-87.515-87.515c-4.686-4.686-12.284-4.686-16.971 0L192 304l-39.515-39.515c-4.686-4.686-12.284-4.686-16.971 0L96 304v48z'\n            />\n        </svg>\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/link.scss",
    "content": "\n.LinkIcon {\n    color: rgba(var(--body-color), 0.5);\n    font-size: 18px;\n    width: 16px;\n\n    &::before {\n        margin: 0 !important;\n    }\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/lockOutline.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport CompassIcon from './compassIcon'\n\nexport default function LockOutline(): JSX.Element {\n    return (\n        <CompassIcon\n            icon='lock-outline'\n            className='LockOutlineIcon'\n        />\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/logo.scss",
    "content": ".LogoIcon {\n    font-size: 16px;\n    color: rgba(var(--sidebar-text-rgb), 0.7);\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/logo.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport './logo.scss'\nimport CompassIcon from './compassIcon'\n\nexport default function LogoIcon(): JSX.Element {\n    return (\n        <CompassIcon\n            icon='product-boards'\n            className='boards-rhs-icon'\n        />\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/logoWithName.scss",
    "content": ".LogoWithNameIcon {\n    width: 150px;\n    height: 32px;\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/logoWithName.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport './logoWithName.scss'\n\nexport default function LogoWithNameIcon(): JSX.Element {\n    return (\n        <svg\n            className='LogoWithNameIcon Icon'\n            version='1.1'\n            x='0px'\n            y='0px'\n            viewBox='0 0 300 64'\n            enableBackground='new 0 0 300 64'\n        >\n            <g\n                id='Layer_2'\n                display='none'\n            >\n                <rect\n                    x='0'\n                    y='0.16'\n                    display='inline'\n                    fillRule='evenodd'\n                    clipRule='evenodd'\n                    fill='#FFFFFF'\n                    width='300'\n                    height='63.84'\n                />\n            </g>\n            <g id='Layer_1'>\n                <g>\n                    <g>\n                        <path\n                            fillRule='evenodd'\n                            clipRule='evenodd'\n                            fill='#0058CC'\n                            d='M33.071,12.289C20.408,8.822,6.018,15.578,1.395,29.232\n                        c-4.655,13.75,2.719,28.67,16.469,33.325c13.75,4.655,28.67-2.719,33.326-16.469c3.804-11.235-0.462-22.701-8.976-29.249\n                        l-0.46,4.871l-0.001,0c4.631,4.896,6.709,11.941,4.325,18.985c-3.362,9.931-14.447,15.151-24.76,11.66\n                        C11.005,48.865,5.37,37.985,8.731,28.054c2.975-8.788,11.998-13.715,20.743-12.625v-0.001L33.071,12.289L33.071,12.289z\n                         M26.896,28.777c3.456-0.665,6.986,2.754,5.762,6.37c-0.854,2.522-3.67,3.85-6.291,2.962c-2.62-0.887-4.052-3.651-3.197-6.174\n                        C23.743,30.238,25.204,29.083,26.896,28.777L26.896,28.777z M25.611,23.833c-1.786,0.323-3.45,1.104-4.812,2.258\n                        c-1.299,1.101-2.319,2.545-2.898,4.258c-0.879,2.597-0.579,5.323,0.617,7.632c1.206,2.329,3.325,4.234,6.07,5.164\n                        c2.744,0.929,5.584,0.701,7.959-0.417c2.352-1.107,4.246-3.091,5.125-5.688c0.555-1.639,0.633-3.254,0.344-4.761\n                        c-0.21-1.093-0.615-2.134-1.174-3.091l1.019-5.107c0.189,0.187,0.374,0.378,0.552,0.574c1.75,1.919,3.008,4.283,3.508,6.877\n                        c0.415,2.154,0.304,4.457-0.484,6.784c-1.239,3.661-3.898,6.453-7.193,8.005c-3.273,1.541-7.175,1.858-10.93,0.588\n                        c-3.754-1.271-6.661-3.895-8.326-7.108c-1.674-3.233-2.09-7.065-0.851-10.728c0.819-2.419,2.26-4.46,4.097-6.016\n                        c1.88-1.593,4.181-2.673,6.656-3.125l-0.001-0.004c1.759-0.339,3.522-0.313,5.213,0.016l-3.583,3.761\n                        c-0.294,0.028-0.588,0.071-0.883,0.127H25.611z'\n                        />\n                        <polygon\n                            fillRule='evenodd'\n                            clipRule='evenodd'\n                            fill='#0058CC'\n                            points='37.495,11.658 36.79,8.44 41.066,0.207 43.683,4.611\n                        48.803,4.434 44.185,12.48 40.902,13.697 29.542,34.491 26.057,32.594 \t\t\t'\n                        />\n                    </g>\n                    <path\n                        fillRule='evenodd'\n                        clipRule='evenodd'\n                        fill='#222C3B'\n                        d='M68.736,52.902V21.449h19.827v5.982H75.828v7.284H86.73v5.983\n                    H75.828v12.204H68.736L68.736,52.902z M102.842,53.481c-1.513,0-2.976-0.282-4.391-0.845c-1.415-0.563-2.661-1.374-3.739-2.437\n                    c-1.077-1.061-1.937-2.371-2.58-3.932c-0.644-1.56-0.965-3.336-0.965-5.33c0-1.993,0.321-3.771,0.965-5.33\n                    c0.643-1.561,1.503-2.87,2.58-3.933c1.078-1.061,2.325-1.872,3.739-2.435c1.415-0.563,2.878-0.844,4.391-0.844\n                    c1.511,0,2.966,0.281,4.365,0.844c1.398,0.563,2.637,1.375,3.714,2.435c1.078,1.063,1.938,2.372,2.581,3.933\n                    c0.644,1.559,0.965,3.337,0.965,5.33c0,1.994-0.321,3.771-0.965,5.33c-0.643,1.561-1.503,2.871-2.581,3.932\n                    c-1.077,1.063-2.316,1.874-3.714,2.437C105.809,53.199,104.353,53.481,102.842,53.481L102.842,53.481z M102.842,47.74\n                    c1.447,0,2.54-0.611,3.279-1.832c0.74-1.224,1.11-2.879,1.11-4.97c0-2.09-0.371-3.746-1.11-4.969\n                    c-0.739-1.222-1.832-1.834-3.279-1.834c-1.48,0-2.581,0.612-3.305,1.834c-0.724,1.223-1.086,2.879-1.086,4.969\n                    c0,2.091,0.362,3.746,1.086,4.97C100.261,47.129,101.362,47.74,102.842,47.74L102.842,47.74z M129.761,53.481\n                    c-1.673,0-3.225-0.282-4.656-0.845c-1.43-0.563-2.677-1.374-3.738-2.437c-1.062-1.061-1.897-2.371-2.509-3.932\n                    c-0.611-1.56-0.916-3.336-0.916-5.33c0-1.993,0.337-3.771,1.013-5.33c0.676-1.561,1.584-2.87,2.726-3.933\n                    c1.142-1.061,2.468-1.872,3.979-2.435c1.512-0.563,3.088-0.844,4.728-0.844c1.479,0,2.789,0.241,3.931,0.723\n                    c1.142,0.482,2.163,1.093,3.063,1.833l-3.329,4.583c-1.126-0.932-2.203-1.399-3.232-1.399c-1.737,0-3.104,0.612-4.101,1.834\n                    c-0.997,1.223-1.496,2.879-1.496,4.969c0,2.091,0.507,3.746,1.521,4.97c1.013,1.221,2.292,1.832,3.834,1.832\n                    c0.772,0,1.52-0.169,2.244-0.506c0.724-0.338,1.406-0.748,2.05-1.231l2.798,4.632c-1.19,1.029-2.477,1.762-3.859,2.196\n                    C132.43,53.264,131.079,53.481,129.761,53.481L129.761,53.481z M147.416,53.481c-1.093,0-2.082-0.185-2.966-0.556\n                    c-0.884-0.369-1.632-0.884-2.244-1.543c-0.61-0.659-1.084-1.423-1.423-2.291c-0.337-0.868-0.505-1.817-0.505-2.847\n                    c0-2.509,1.061-4.471,3.184-5.886c2.123-1.414,5.547-2.363,10.276-2.846c-0.065-1.062-0.379-1.905-0.941-2.533\n                    c-0.563-0.627-1.503-0.94-2.822-0.94c-1.029,0-2.05,0.193-3.063,0.579c-1.013,0.386-2.099,0.916-3.257,1.592l-2.556-4.68\n                    c1.542-0.932,3.159-1.688,4.848-2.267c1.688-0.579,3.481-0.868,5.378-0.868c3.089,0,5.443,0.876,7.067,2.628\n                    s2.437,4.479,2.437,8.176v13.701h-5.789l-0.481-2.46h-0.194c-1.028,0.899-2.105,1.632-3.231,2.194S148.767,53.481,147.416,53.481\n                    L147.416,53.481z M149.828,47.981c0.804,0,1.495-0.177,2.074-0.53s1.189-0.853,1.834-1.496v-4.196\n                    c-2.509,0.354-4.245,0.868-5.21,1.544c-0.965,0.675-1.447,1.479-1.447,2.411c0,0.772,0.25,1.344,0.748,1.713\n                    C148.325,47.797,148.992,47.981,149.828,47.981L149.828,47.981z M173.321,53.481c-1.223,0-2.252-0.192-3.088-0.579\n                    s-1.503-0.924-2.002-1.616c-0.498-0.691-0.859-1.527-1.085-2.508c-0.226-0.981-0.338-2.083-0.338-3.305V19.086h7.092v26.677\n                    c0,0.74,0.137,1.254,0.409,1.543c0.273,0.29,0.555,0.435,0.845,0.435c0.16,0,0.297,0,0.41,0c0.112,0,0.266-0.032,0.458-0.096\n                    l0.868,5.258c-0.386,0.161-0.876,0.297-1.472,0.41C174.824,53.426,174.125,53.481,173.321,53.481L173.321,53.481z M193.147,53.481\n                    c-1.062,0-2.13-0.266-3.208-0.796c-1.077-0.53-2.082-1.312-3.016-2.341h-0.192l-0.578,2.558h-5.548V19.086h7.092v8.297\n                    l-0.194,3.666c0.934-0.836,1.938-1.487,3.016-1.954c1.078-0.467,2.179-0.699,3.305-0.699c1.447,0,2.75,0.289,3.907,0.868\n                    c1.158,0.579,2.147,1.39,2.967,2.436c0.82,1.045,1.447,2.316,1.881,3.811c0.435,1.495,0.652,3.16,0.652,4.992\n                    c0,2.06-0.282,3.892-0.845,5.499c-0.563,1.608-1.318,2.968-2.268,4.078c-0.948,1.108-2.026,1.953-3.231,2.532\n                    C195.681,53.191,194.434,53.481,193.147,53.481L193.147,53.481z M191.508,47.692c1.222,0,2.268-0.555,3.136-1.664\n                    s1.303-2.902,1.303-5.38c0-4.309-1.384-6.463-4.149-6.463c-1.415,0-2.781,0.724-4.1,2.171v9.792\n                    c0.643,0.579,1.285,0.98,1.929,1.207C190.269,47.579,190.896,47.692,191.508,47.692L191.508,47.692z M218.522,53.481\n                    c-1.512,0-2.975-0.282-4.391-0.845c-1.414-0.563-2.66-1.374-3.738-2.437c-1.077-1.061-1.938-2.371-2.58-3.932\n                    c-0.644-1.56-0.965-3.336-0.965-5.33c0-1.993,0.321-3.771,0.965-5.33c0.643-1.561,1.503-2.87,2.58-3.933\n                    c1.078-1.061,2.324-1.872,3.738-2.435c1.416-0.563,2.879-0.844,4.391-0.844s2.967,0.281,4.366,0.844\n                    c1.398,0.563,2.637,1.375,3.714,2.435c1.078,1.063,1.938,2.372,2.581,3.933c0.644,1.559,0.965,3.337,0.965,5.33\n                    c0,1.994-0.321,3.771-0.965,5.33c-0.643,1.561-1.503,2.871-2.581,3.932c-1.077,1.063-2.315,1.874-3.714,2.437\n                    C221.489,53.199,220.034,53.481,218.522,53.481L218.522,53.481z M218.522,47.74c1.447,0,2.54-0.611,3.28-1.832\n                    c0.739-1.224,1.109-2.879,1.109-4.97c0-2.09-0.37-3.746-1.109-4.969c-0.74-1.222-1.833-1.834-3.28-1.834\n                    c-1.479,0-2.581,0.612-3.305,1.834c-0.724,1.223-1.086,2.879-1.086,4.969c0,2.091,0.362,3.746,1.086,4.97\n                    C215.941,47.129,217.043,47.74,218.522,47.74L218.522,47.74z M240.52,53.481c-1.093,0-2.082-0.185-2.966-0.556\n                    c-0.885-0.369-1.632-0.884-2.244-1.543c-0.61-0.659-1.085-1.423-1.423-2.291s-0.506-1.817-0.506-2.847\n                    c0-2.509,1.061-4.471,3.184-5.886c2.123-1.414,5.548-2.363,10.275-2.846c-0.065-1.062-0.379-1.905-0.941-2.533\n                    c-0.562-0.627-1.503-0.94-2.821-0.94c-1.029,0-2.051,0.193-3.063,0.579s-2.099,0.916-3.257,1.592l-2.556-4.68\n                    c1.543-0.932,3.159-1.688,4.848-2.267c1.688-0.579,3.481-0.868,5.378-0.868c3.089,0,5.444,0.876,7.068,2.628\n                    s2.437,4.479,2.437,8.176v13.701h-5.789l-0.482-2.46h-0.193c-1.029,0.899-2.106,1.632-3.232,2.194\n                    C243.109,53.199,241.871,53.481,240.52,53.481L240.52,53.481z M242.933,47.981c0.803,0,1.494-0.177,2.074-0.53\n                    c0.578-0.354,1.189-0.853,1.833-1.496v-4.196c-2.508,0.354-4.245,0.868-5.21,1.544c-0.966,0.675-1.447,1.479-1.447,2.411\n                    c0,0.772,0.249,1.344,0.747,1.713C241.43,47.797,242.096,47.981,242.933,47.981L242.933,47.981z M259.913,52.902V28.975h5.789\n                    l0.481,4.197h0.192c0.869-1.641,1.914-2.846,3.137-3.618c1.222-0.773,2.444-1.158,3.665-1.158c0.676,0,1.231,0.04,1.666,0.121\n                    c0.434,0.08,0.827,0.2,1.182,0.361l-1.158,6.126c-0.45-0.128-0.868-0.225-1.255-0.29c-0.385-0.063-0.836-0.096-1.351-0.096\n                    c-0.899,0-1.841,0.321-2.822,0.964c-0.98,0.645-1.792,1.786-2.436,3.426v13.894H259.913L259.913,52.902z M287.169,53.481\n                    c-1.48,0-2.823-0.29-4.028-0.868c-1.207-0.579-2.236-1.408-3.089-2.484c-0.851-1.078-1.511-2.389-1.977-3.932\n                    c-0.466-1.544-0.7-3.297-0.7-5.259s0.281-3.714,0.845-5.258c0.563-1.545,1.303-2.854,2.219-3.932s1.979-1.906,3.184-2.484\n                    c1.206-0.579,2.437-0.868,3.691-0.868c1.351,0,2.46,0.224,3.328,0.674c0.869,0.451,1.72,1.062,2.557,1.833l-0.29-3.666v-8.152H300\n                    v33.816h-5.789l-0.481-2.363h-0.193c-0.836,0.835-1.817,1.535-2.943,2.098S288.326,53.481,287.169,53.481L287.169,53.481z\n                     M289.001,47.692c0.772,0,1.471-0.161,2.1-0.482c0.626-0.322,1.229-0.884,1.808-1.688v-9.793c-0.643-0.579-1.302-0.98-1.977-1.206\n                    c-0.676-0.225-1.336-0.337-1.979-0.337c-1.126,0-2.123,0.538-2.991,1.615s-1.303,2.758-1.303,5.042\n                    c0,2.379,0.379,4.116,1.134,5.21C286.549,47.146,287.619,47.692,289.001,47.692z'\n                    />\n                </g>\n            </g>\n        </svg>\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/logoWithNameWhite.scss",
    "content": ".LogoWithNameWhiteIcon {\n    width: 150px;\n    height: 32px;\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/logoWithNameWhite.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport './logoWithNameWhite.scss'\n\nexport default function LogoWithNameWhiteIcon(): JSX.Element {\n    return (\n        <svg\n            className='LogoWithNameWhiteIcon Icon'\n            version='1.1'\n            x='0px'\n            y='0px'\n            viewBox='0 0 300 64'\n            enableBackground='new 0 0 300 64'\n        >\n            <g\n                id='Layer_2'\n                display='none'\n            >\n                <rect\n                    x='0'\n                    y='0.16'\n                    display='inline'\n                    fillRule='evenodd'\n                    clipRule='evenodd'\n                    fill='#FFFFFF'\n                    width='300'\n                    height='63.84'\n                />\n            </g>\n            <g id='Layer_1'>\n                <g>\n                    <g>\n                        <path\n                            fillRule='evenodd'\n                            clipRule='evenodd'\n                            fill='#FFFFFF'\n                            d='M33.071,12.289C20.408,8.822,6.018,15.578,1.395,29.232\n                        c-4.655,13.75,2.719,28.67,16.469,33.325c13.75,4.655,28.67-2.719,33.326-16.469c3.804-11.235-0.462-22.701-8.976-29.249\n                        l-0.46,4.871l-0.001,0c4.631,4.896,6.709,11.941,4.325,18.985c-3.362,9.931-14.447,15.151-24.76,11.66\n                        C11.005,48.865,5.37,37.985,8.731,28.054c2.975-8.788,11.998-13.715,20.743-12.625v-0.001L33.071,12.289L33.071,12.289z\n                         M26.896,28.777c3.456-0.665,6.986,2.754,5.762,6.37c-0.854,2.522-3.67,3.85-6.291,2.962c-2.62-0.887-4.052-3.651-3.197-6.174\n                        C23.743,30.238,25.204,29.083,26.896,28.777L26.896,28.777z M25.611,23.833c-1.786,0.323-3.45,1.104-4.812,2.258\n                        c-1.299,1.101-2.319,2.545-2.898,4.258c-0.879,2.597-0.579,5.323,0.617,7.632c1.206,2.329,3.325,4.234,6.07,5.164\n                        c2.744,0.929,5.584,0.701,7.959-0.417c2.352-1.107,4.246-3.091,5.125-5.688c0.555-1.639,0.633-3.254,0.344-4.761\n                        c-0.21-1.093-0.615-2.134-1.174-3.091l1.019-5.107c0.189,0.187,0.374,0.378,0.552,0.574c1.75,1.919,3.008,4.283,3.508,6.877\n                        c0.415,2.154,0.304,4.457-0.484,6.784c-1.239,3.661-3.898,6.453-7.193,8.005c-3.273,1.541-7.175,1.858-10.93,0.588\n                        c-3.754-1.271-6.661-3.895-8.326-7.108c-1.674-3.233-2.09-7.065-0.851-10.728c0.819-2.419,2.26-4.46,4.097-6.016\n                        c1.88-1.593,4.181-2.673,6.656-3.125l-0.001-0.004c1.759-0.339,3.522-0.313,5.213,0.016l-3.583,3.761\n                        c-0.294,0.028-0.588,0.071-0.883,0.127H25.611z'\n                        />\n                        <polygon\n                            fillRule='evenodd'\n                            clipRule='evenodd'\n                            fill='#FFFFFF'\n                            points='37.495,11.658 36.79,8.44 41.066,0.207 43.683,4.611\n                        48.803,4.434 44.185,12.48 40.902,13.697 29.542,34.491 26.057,32.594 \t\t\t'\n                        />\n                    </g>\n                    <path\n                        fillRule='evenodd'\n                        clipRule='evenodd'\n                        fill='#FFFFFF'\n                        d='M68.736,52.902V21.449h19.827v5.982H75.828v7.284H86.73v5.983\n                    H75.828v12.204H68.736L68.736,52.902z M102.842,53.481c-1.513,0-2.976-0.282-4.391-0.845c-1.415-0.563-2.661-1.374-3.739-2.437\n                    c-1.077-1.061-1.937-2.371-2.58-3.932c-0.644-1.56-0.965-3.336-0.965-5.33c0-1.993,0.321-3.771,0.965-5.33\n                    c0.643-1.561,1.503-2.87,2.58-3.933c1.078-1.061,2.325-1.872,3.739-2.435c1.415-0.563,2.878-0.844,4.391-0.844\n                    c1.511,0,2.966,0.281,4.365,0.844c1.398,0.563,2.637,1.375,3.714,2.435c1.078,1.063,1.938,2.372,2.581,3.933\n                    c0.644,1.559,0.965,3.337,0.965,5.33c0,1.994-0.321,3.771-0.965,5.33c-0.643,1.561-1.503,2.871-2.581,3.932\n                    c-1.077,1.063-2.316,1.874-3.714,2.437C105.809,53.199,104.353,53.481,102.842,53.481L102.842,53.481z M102.842,47.74\n                    c1.447,0,2.54-0.611,3.279-1.832c0.74-1.224,1.11-2.879,1.11-4.97c0-2.09-0.371-3.746-1.11-4.969\n                    c-0.739-1.222-1.832-1.834-3.279-1.834c-1.48,0-2.581,0.612-3.305,1.834c-0.724,1.223-1.086,2.879-1.086,4.969\n                    c0,2.091,0.362,3.746,1.086,4.97C100.261,47.129,101.362,47.74,102.842,47.74L102.842,47.74z M129.761,53.481\n                    c-1.673,0-3.225-0.282-4.656-0.845c-1.43-0.563-2.677-1.374-3.738-2.437c-1.062-1.061-1.897-2.371-2.509-3.932\n                    c-0.611-1.56-0.916-3.336-0.916-5.33c0-1.993,0.337-3.771,1.013-5.33c0.676-1.561,1.584-2.87,2.726-3.933\n                    c1.142-1.061,2.468-1.872,3.979-2.435c1.512-0.563,3.088-0.844,4.728-0.844c1.479,0,2.789,0.241,3.931,0.723\n                    c1.142,0.482,2.163,1.093,3.063,1.833l-3.329,4.583c-1.126-0.932-2.203-1.399-3.232-1.399c-1.737,0-3.104,0.612-4.101,1.834\n                    c-0.997,1.223-1.496,2.879-1.496,4.969c0,2.091,0.507,3.746,1.521,4.97c1.013,1.221,2.292,1.832,3.834,1.832\n                    c0.772,0,1.52-0.169,2.244-0.506c0.724-0.338,1.406-0.748,2.05-1.231l2.798,4.632c-1.19,1.029-2.477,1.762-3.859,2.196\n                    C132.43,53.264,131.079,53.481,129.761,53.481L129.761,53.481z M147.416,53.481c-1.093,0-2.082-0.185-2.966-0.556\n                    c-0.884-0.369-1.632-0.884-2.244-1.543c-0.61-0.659-1.084-1.423-1.423-2.291c-0.337-0.868-0.505-1.817-0.505-2.847\n                    c0-2.509,1.061-4.471,3.184-5.886c2.123-1.414,5.547-2.363,10.276-2.846c-0.065-1.062-0.379-1.905-0.941-2.533\n                    c-0.563-0.627-1.503-0.94-2.822-0.94c-1.029,0-2.05,0.193-3.063,0.579c-1.013,0.386-2.099,0.916-3.257,1.592l-2.556-4.68\n                    c1.542-0.932,3.159-1.688,4.848-2.267c1.688-0.579,3.481-0.868,5.378-0.868c3.089,0,5.443,0.876,7.067,2.628\n                    s2.437,4.479,2.437,8.176v13.701h-5.789l-0.481-2.46h-0.194c-1.028,0.899-2.105,1.632-3.231,2.194S148.767,53.481,147.416,53.481\n                    L147.416,53.481z M149.828,47.981c0.804,0,1.495-0.177,2.074-0.53s1.189-0.853,1.834-1.496v-4.196\n                    c-2.509,0.354-4.245,0.868-5.21,1.544c-0.965,0.675-1.447,1.479-1.447,2.411c0,0.772,0.25,1.344,0.748,1.713\n                    C148.325,47.797,148.992,47.981,149.828,47.981L149.828,47.981z M173.321,53.481c-1.223,0-2.252-0.192-3.088-0.579\n                    s-1.503-0.924-2.002-1.616c-0.498-0.691-0.859-1.527-1.085-2.508c-0.226-0.981-0.338-2.083-0.338-3.305V19.086h7.092v26.677\n                    c0,0.74,0.137,1.254,0.409,1.543c0.273,0.29,0.555,0.435,0.845,0.435c0.16,0,0.297,0,0.41,0c0.112,0,0.266-0.032,0.458-0.096\n                    l0.868,5.258c-0.386,0.161-0.876,0.297-1.472,0.41C174.824,53.426,174.125,53.481,173.321,53.481L173.321,53.481z M193.147,53.481\n                    c-1.062,0-2.13-0.266-3.208-0.796c-1.077-0.53-2.082-1.312-3.016-2.341h-0.192l-0.578,2.558h-5.548V19.086h7.092v8.297\n                    l-0.194,3.666c0.934-0.836,1.938-1.487,3.016-1.954c1.078-0.467,2.179-0.699,3.305-0.699c1.447,0,2.75,0.289,3.907,0.868\n                    c1.158,0.579,2.147,1.39,2.967,2.436c0.82,1.045,1.447,2.316,1.881,3.811c0.435,1.495,0.652,3.16,0.652,4.992\n                    c0,2.06-0.282,3.892-0.845,5.499c-0.563,1.608-1.318,2.968-2.268,4.078c-0.948,1.108-2.026,1.953-3.231,2.532\n                    C195.681,53.191,194.434,53.481,193.147,53.481L193.147,53.481z M191.508,47.692c1.222,0,2.268-0.555,3.136-1.664\n                    s1.303-2.902,1.303-5.38c0-4.309-1.384-6.463-4.149-6.463c-1.415,0-2.781,0.724-4.1,2.171v9.792\n                    c0.643,0.579,1.285,0.98,1.929,1.207C190.269,47.579,190.896,47.692,191.508,47.692L191.508,47.692z M218.522,53.481\n                    c-1.512,0-2.975-0.282-4.391-0.845c-1.414-0.563-2.66-1.374-3.738-2.437c-1.077-1.061-1.938-2.371-2.58-3.932\n                    c-0.644-1.56-0.965-3.336-0.965-5.33c0-1.993,0.321-3.771,0.965-5.33c0.643-1.561,1.503-2.87,2.58-3.933\n                    c1.078-1.061,2.324-1.872,3.738-2.435c1.416-0.563,2.879-0.844,4.391-0.844s2.967,0.281,4.366,0.844\n                    c1.398,0.563,2.637,1.375,3.714,2.435c1.078,1.063,1.938,2.372,2.581,3.933c0.644,1.559,0.965,3.337,0.965,5.33\n                    c0,1.994-0.321,3.771-0.965,5.33c-0.643,1.561-1.503,2.871-2.581,3.932c-1.077,1.063-2.315,1.874-3.714,2.437\n                    C221.489,53.199,220.034,53.481,218.522,53.481L218.522,53.481z M218.522,47.74c1.447,0,2.54-0.611,3.28-1.832\n                    c0.739-1.224,1.109-2.879,1.109-4.97c0-2.09-0.37-3.746-1.109-4.969c-0.74-1.222-1.833-1.834-3.28-1.834\n                    c-1.479,0-2.581,0.612-3.305,1.834c-0.724,1.223-1.086,2.879-1.086,4.969c0,2.091,0.362,3.746,1.086,4.97\n                    C215.941,47.129,217.043,47.74,218.522,47.74L218.522,47.74z M240.52,53.481c-1.093,0-2.082-0.185-2.966-0.556\n                    c-0.885-0.369-1.632-0.884-2.244-1.543c-0.61-0.659-1.085-1.423-1.423-2.291s-0.506-1.817-0.506-2.847\n                    c0-2.509,1.061-4.471,3.184-5.886c2.123-1.414,5.548-2.363,10.275-2.846c-0.065-1.062-0.379-1.905-0.941-2.533\n                    c-0.562-0.627-1.503-0.94-2.821-0.94c-1.029,0-2.051,0.193-3.063,0.579s-2.099,0.916-3.257,1.592l-2.556-4.68\n                    c1.543-0.932,3.159-1.688,4.848-2.267c1.688-0.579,3.481-0.868,5.378-0.868c3.089,0,5.444,0.876,7.068,2.628\n                    s2.437,4.479,2.437,8.176v13.701h-5.789l-0.482-2.46h-0.193c-1.029,0.899-2.106,1.632-3.232,2.194\n                    C243.109,53.199,241.871,53.481,240.52,53.481L240.52,53.481z M242.933,47.981c0.803,0,1.494-0.177,2.074-0.53\n                    c0.578-0.354,1.189-0.853,1.833-1.496v-4.196c-2.508,0.354-4.245,0.868-5.21,1.544c-0.966,0.675-1.447,1.479-1.447,2.411\n                    c0,0.772,0.249,1.344,0.747,1.713C241.43,47.797,242.096,47.981,242.933,47.981L242.933,47.981z M259.913,52.902V28.975h5.789\n                    l0.481,4.197h0.192c0.869-1.641,1.914-2.846,3.137-3.618c1.222-0.773,2.444-1.158,3.665-1.158c0.676,0,1.231,0.04,1.666,0.121\n                    c0.434,0.08,0.827,0.2,1.182,0.361l-1.158,6.126c-0.45-0.128-0.868-0.225-1.255-0.29c-0.385-0.063-0.836-0.096-1.351-0.096\n                    c-0.899,0-1.841,0.321-2.822,0.964c-0.98,0.645-1.792,1.786-2.436,3.426v13.894H259.913L259.913,52.902z M287.169,53.481\n                    c-1.48,0-2.823-0.29-4.028-0.868c-1.207-0.579-2.236-1.408-3.089-2.484c-0.851-1.078-1.511-2.389-1.977-3.932\n                    c-0.466-1.544-0.7-3.297-0.7-5.259s0.281-3.714,0.845-5.258c0.563-1.545,1.303-2.854,2.219-3.932s1.979-1.906,3.184-2.484\n                    c1.206-0.579,2.437-0.868,3.691-0.868c1.351,0,2.46,0.224,3.328,0.674c0.869,0.451,1.72,1.062,2.557,1.833l-0.29-3.666v-8.152H300\n                    v33.816h-5.789l-0.481-2.363h-0.193c-0.836,0.835-1.817,1.535-2.943,2.098S288.326,53.481,287.169,53.481L287.169,53.481z\n                     M289.001,47.692c0.772,0,1.471-0.161,2.1-0.482c0.626-0.322,1.229-0.884,1.808-1.688v-9.793c-0.643-0.579-1.302-0.98-1.977-1.206\n                    c-0.676-0.225-1.336-0.337-1.979-0.337c-1.126,0-2.123,0.538-2.991,1.615s-1.303,2.758-1.303,5.042\n                    c0,2.379,0.379,4.116,1.134,5.21C286.549,47.146,287.619,47.692,289.001,47.692z'\n                    />\n                </g>\n            </g>\n        </svg>\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/message.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport CompassIcon from './compassIcon'\n\nexport default function MessageIcon(): JSX.Element {\n    return (\n        <CompassIcon\n            icon='message-text-outline'\n            className='MessageIcon'\n        />\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/newFolder.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport CompassIcon from './compassIcon'\n\nexport default function CreateNewFolder(): JSX.Element {\n    return (\n        <CompassIcon\n            icon='folder-plus-outline'\n            className='CreateNewFolderIcon'\n        />\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/options.scss",
    "content": ""
  },
  {
    "path": "webapp/src/widgets/icons/options.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport CompassIcon from './compassIcon'\n\nimport './options.scss'\n\nexport default function OptionsIcon(): JSX.Element {\n    return (\n        <CompassIcon\n            icon='dots-horizontal'\n            className='OptionsIcon'\n        />\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/random.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport CompassIcon from './compassIcon'\n\nexport default function RandomIcon(): JSX.Element {\n    return (\n        <CompassIcon\n            icon='shuffle-variant'\n        />\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/search.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport CompassIcon from './compassIcon'\n\nexport default function Search(): JSX.Element {\n    return (\n        <CompassIcon\n            icon='magnify'\n            className='MagnifyIcon'\n        />\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/settings.scss",
    "content": ""
  },
  {
    "path": "webapp/src/widgets/icons/settings.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport CompassIcon from './compassIcon'\n\nimport './settings.scss'\n\nexport default function SettingsIcon(): JSX.Element {\n    return (\n        <CompassIcon\n            icon='settings-outline'\n            className='SettingsIcon'\n        />\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/show.scss",
    "content": ".ShowIcon {\n    fill: currentColor;\n    stroke: none;\n    width: 24px;\n    height: 24px;\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/show.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport './show.scss'\n\nexport default function ShowIcon(): JSX.Element {\n    return (\n        <svg\n            xmlns='http://www.w3.org/2000/svg'\n            className='ShowIcon Icon'\n            viewBox='0 0 576 512'\n        >\n            <path\n                d='M288 144a110.94 110.94 0 0 0-31.24 5 55.4 55.4 0 0 1 7.24 27 56 56 0 0 1-56 56 55.4 55.4 0 0 1-27-7.24A111.71 111.71 0 1 0 288 144zm284.52 97.4C518.29 135.59 410.93 64 288 64S57.68 135.64 3.48 241.41a32.35 32.35 0 0 0 0 29.19C57.71 376.41 165.07 448 288 448s230.32-71.64 284.52-177.41a32.35 32.35 0 0 0 0-29.19zM288 400c-98.65 0-189.09-55-237.93-144C98.91 167 189.34 112 288 112s189.09 55 237.93 144C477.1 345 386.66 400 288 400z'\n            />\n        </svg>\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/showSidebar.scss",
    "content": ".ShowSidebarIcon {\n    stroke: rgba(var(--center-channel-color-rgb), 0.5);\n    stroke-width: 6px;\n    fill: none;\n    width: 24px;\n    height: 24px;\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/showSidebar.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport './showSidebar.scss'\n\nexport default function ShowSidebarIcon(): JSX.Element {\n    return (\n        <svg\n            xmlns='http://www.w3.org/2000/svg'\n            className='ShowSidebarIcon Icon'\n            viewBox='0 0 100 100'\n        >\n            <polyline points='20,20 50,50 20,80'/>\n            <polyline points='50,20 80,50, 50,80'/>\n        </svg>\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/sortDown.scss",
    "content": ".SortDownIcon {\n    stroke: rgba(var(--center-channel-color-rgb), 0.5);\n    stroke-width: 8px;\n    fill: none;\n    width: 24px;\n    height: 24px;\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/sortDown.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport './sortDown.scss'\n\nexport default function SortDownIcon(): JSX.Element {\n    return (\n        <svg\n            xmlns='http://www.w3.org/2000/svg'\n            className='SortDownIcon Icon'\n            viewBox='0 0 100 100'\n        >\n            <polyline points='50,20 50,80'/>\n            <polyline points='30,60 50,80 70,60'/>\n        </svg>\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/sortUp.scss",
    "content": ".SortUpIcon {\n    stroke: currentColor;\n    stroke-width: 8px;\n    fill: none;\n    width: 24px;\n    height: 24px;\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/sortUp.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport './sortUp.scss'\n\nexport default function SortUpIcon(): JSX.Element {\n    return (\n        <svg\n            xmlns='http://www.w3.org/2000/svg'\n            className='SortUpIcon Icon'\n            viewBox='0 0 100 100'\n        >\n            <polyline points='50,20 50,80'/>\n            <polyline points='30,40 50,20 70,40'/>\n        </svg>\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/submenuTriangle.scss",
    "content": ".SubmenuTriangleIcon {\n    fill: currentColor;\n    stroke: none;\n    width: 24px;\n    height: 24px;\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/submenuTriangle.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport './submenuTriangle.scss'\n\nexport default function SubmenuTriangleIcon(): JSX.Element {\n    return (\n        <svg\n            xmlns='http://www.w3.org/2000/svg'\n            className='SubmenuTriangleIcon Icon'\n            viewBox='0 0 100 100'\n        >\n            <polygon points='50,35 75,50 50,65'/>\n        </svg>\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/table.scss",
    "content": ".TableIcon {\n    stroke-width: 8px;\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/table.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport './table.scss'\n\nexport default function TableIcon(): JSX.Element {\n    return (\n        <svg\n            width='24'\n            height='24'\n            viewBox='0 0 24 24'\n            fill='currentColor'\n            xmlns='http://www.w3.org/2000/svg'\n            className='TableIcon Icon'\n        >\n            <g opacity='0.8'>\n                <path\n                    fillRule='evenodd'\n                    clipRule='evenodd'\n                    d='M20 4H10V8L20 8V4ZM8 4V8H4V4H8ZM4 14L4 10H8V14H4ZM4 16L4 20H8V16H4ZM10 16V20H20V16L10 16ZM20 14V10L10 10V14L20 14ZM4 2C2.89543 2 2 2.89543 2 4V20C2 21.1046 2.89543 22 4 22H20C21.1046 22 22 21.1046 22 20V4C22 2.89543 21.1046 2 20 2H4Z'\n                    fill='currentColor'\n                />\n            </g>\n        </svg>\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/text.scss",
    "content": ".TextIcon {\n    stroke: none;\n    width: 1em;\n    height: 1em;\n    line-height: 1em;\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/text.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport './text.scss'\n\nexport default function TextIcon(): JSX.Element {\n    return (\n        <svg\n            xmlns='http://www.w3.org/2000/svg'\n            className='TextIcon Icon'\n            viewBox='0 0 448 512'\n        >\n            <path\n                d='M432 416H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-128H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-128H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-128H16A16 16 0 0 0 0 48v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16V48a16 16 0 0 0-16-16z'\n            />\n        </svg>\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/icons/update.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\n\nimport CompassIcon from './compassIcon'\n\nexport default function Update(): JSX.Element {\n    return (\n        <CompassIcon\n            icon='update'\n            className='UpdateIcon'\n        />\n    )\n}\n"
  },
  {
    "path": "webapp/src/widgets/label.scss",
    "content": ".Label {\n    display: inline-flex;\n    align-items: center;\n    padding: 2px 8px;\n    margin: 4px 4px 0 0;\n    border-radius: 3px;\n    line-height: 20px;\n    color: rgba(var(--center-channel-color-rgb), 0.8);\n    white-space: nowrap;\n    text-transform: uppercase;\n    overflow: hidden;\n    font-weight: 600;\n    font-size: 13px;\n    max-width: 100%;\n\n    &.empty {\n        color: rgba(var(--center-channel-color-rgb), 0.4);\n        padding: 1px;\n        text-transform: none;\n        font-weight: normal;\n    }\n\n    input {\n        line-height: 20px;\n        color: rgba(var(--center-channel-color-rgb), 0.8);\n        font-weight: 600;\n        font-size: 13px;\n    }\n}\n"
  },
  {
    "path": "webapp/src/widgets/label.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\n\nimport {Constants} from '../constants'\n\nimport './label.scss'\n\ntype Props = {\n    color?: string\n    title?: string\n    children: React.ReactNode\n    className?: string\n}\n\n// Switch is an on-off style switch / checkbox\nfunction Label(props: Props): JSX.Element {\n    let color = 'empty'\n    if (props.color && props.color in Constants.menuColors) {\n        color = props.color\n    }\n    return (\n        <span\n            className={`Label ${color} ${props.className ? props.className : ''}`}\n            title={props.title}\n        >\n            {props.children}\n        </span>\n    )\n}\n\nexport default React.memo(Label)\n"
  },
  {
    "path": "webapp/src/widgets/menu/colorOption.scss",
    "content": ".ColorOption {\n    .menu-colorbox {\n        display: inline-block;\n        margin-right: 8px;\n        vertical-align: middle;\n        width: 18px;\n        height: 18px;\n        border-radius: 3px;\n        box-shadow: rgba(15, 15, 15, 0.1) 0 0 0 1px inset;\n    }\n}\n"
  },
  {
    "path": "webapp/src/widgets/menu/colorOption.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\nimport {useIntl} from 'react-intl'\n\nimport {MenuOptionProps} from './menuItem'\n\nimport './colorOption.scss'\n\ntype ColorOptionProps = MenuOptionProps & {\n    icon?: React.ReactNode\n}\n\nfunction ColorOption(props: ColorOptionProps): JSX.Element {\n    const {id, name, icon} = props\n    const intl = useIntl()\n    return (\n        <div\n            role='button'\n            aria-label={intl.formatMessage({id: 'ColorOption.selectColor', defaultMessage: 'Select {color} Color'}, {color: name})}\n            className='MenuOption ColorOption menu-option'\n            onClick={(e: React.MouseEvent): void => {\n                e.target.dispatchEvent(new Event('menuItemClicked'))\n                props.onClick(props.id)\n                e.stopPropagation()\n            }}\n        >\n            {icon ?? <div className='noicon'/>}\n            <div className='menu-name'>{name}</div>\n            <div className={`menu-colorbox ${id}`}/>\n        </div>\n    )\n}\n\nexport default React.memo(ColorOption)\n"
  },
  {
    "path": "webapp/src/widgets/menu/index.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport Menu from './menu'\n\nexport default Menu\n"
  },
  {
    "path": "webapp/src/widgets/menu/labelOption.scss",
    "content": ".Menu {\n    .LabelOption.menu-option {\n        cursor: auto;\n        pointer-events: none;\n    }\n}\n"
  },
  {
    "path": "webapp/src/widgets/menu/labelOption.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\n\nimport './labelOption.scss'\n\ntype LabelOptionProps = {\n    icon?: string\n    children: React.ReactNode\n}\n\nfunction LabelOption(props: LabelOptionProps): JSX.Element {\n    return (\n        <div className='MenuOption LabelOption menu-option'>\n            {props.icon ?? <div className='noicon'/>}\n            <div className='menu-name'>{props.children}</div>\n            <div className='noicon'/>\n        </div>\n    )\n}\n\nexport default React.memo(LabelOption)\n"
  },
  {
    "path": "webapp/src/widgets/menu/menu.scss",
    "content": "@import '../../styles/z-index';\n\n.Menu.noselect {\n    @include z-index(menu);\n    display: flex;\n    flex-direction: column;\n    position: absolute;\n    background-color: rgb(var(--center-channel-bg-rgb));\n    color: rgb(var(--center-channel-color-rgb));\n    border: 1px solid rgba(var(--center-channel-color-rgb), 0.16);\n    border-radius: var(--default-rad);\n    box-shadow: var(--elevation-4);\n    max-width: 320px;\n    cursor: default;\n\n    &.fixed {\n        position: fixed;\n    }\n\n    .menu-contents {\n        display: flex;\n        flex-direction: column;\n        padding: 8px 0;\n        min-width: 240px;\n        max-width: 320px;\n    }\n\n    .view-list {\n        overflow: hidden;\n    }\n\n    .menu-options {\n        display: flex;\n        flex-direction: column;\n\n        flex-grow: 1;\n        position: relative;\n\n        list-style: none;\n        padding: 0;\n        margin: 0;\n\n        color: rgb(var(--center-channel-color-rgb));\n\n        .CompassIcon {\n            font-size: 18px;\n            opacity: 0.56;\n            width: 18px;\n\n            &::before {\n                margin: 0;\n            }\n        }\n\n        .menu-option__content {\n            overflow: hidden;\n            flex: 1;\n        }\n\n        .menu-option {\n            display: flex;\n            flex-direction: row;\n            align-items: center;\n            justify-content: flex-start;\n            position: relative;\n            font-size: 14px;\n            line-height: 24px;\n            font-weight: 400;\n            height: 32px;\n            padding: 4px 20px;\n            white-space: nowrap;\n            cursor: pointer;\n\n            &.menu-option--disabled {\n                cursor: not-allowed;\n                pointer-events: none;\n                opacity: 0.32;\n            }\n\n            &.menu-option--with-subtext {\n                height: auto;\n                align-items: initial;\n            }\n\n            &:hover {\n                background: rgba(var(--button-bg-rgb), 0.08);\n            }\n\n            &-active {\n                background: rgba(var(--button-bg-rgb), 0.08);\n            }\n\n            .empty-icon {\n                width: 18px;\n            }\n\n            .noicon {\n                &:empty {\n                    display: none;\n                }\n            }\n\n            > *:first-child {\n                margin-left: 0;\n            }\n\n            > .menu-content {\n                display: block;\n            }\n\n            .menu-name {\n                overflow: hidden;\n                text-overflow: ellipsis;\n                flex-grow: 1;\n                white-space: nowrap;\n                text-align: left;\n            }\n\n            > .menu-subtext {\n                font-size: 10px;\n                text-align: left;\n            }\n\n            > .SubmenuTriangleIcon {\n                fill: currentColor;\n            }\n\n            .Icon {\n                opacity: 0.56;\n                width: 18px;\n                height: 18px;\n                flex: 0 0 18px;\n            }\n\n            .IconButton .Icon {\n                margin-right: 0;\n            }\n        }\n\n        * > .menu-option.bold-menu-text {\n            font-weight: bold;\n        }\n    }\n\n    .menu-option__icon {\n        display: flex;\n        align-items: center;\n        margin-right: 12px;\n    }\n\n    .menu-option__check {\n        color: rgba(var(--button-bg-rgb), 1);\n\n        svg {\n            stroke: currentColor;\n        }\n    }\n\n    .menu-spacer {\n        height: 20px;\n        width: 20px;\n        flex: 0 0 auto;\n    }\n\n    @media not screen and (max-width: 430px) {\n        &.top {\n            bottom: 100%;\n        }\n\n        &.left {\n            right: 0;\n        }\n    }\n}\n\n.Menu.noselect,\n.SubMenuOption .SubMenu {\n    @media screen and (max-width: 430px) {\n        position: fixed;\n        top: 0;\n        left: 0;\n        right: 0;\n        bottom: 0;\n        min-width: 0;\n        background-color: rgba(var(--center-channel-color-rgb), 0.5);\n        border-radius: 0;\n        padding: 10px;\n\n        display: block;\n        overflow-y: auto;\n\n        .menu-contents {\n            justify-content: flex-end;\n            min-height: 100%;\n        }\n\n        .menu-options {\n            align-items: center;\n            border-radius: 10px;\n            overflow: hidden;\n\n            flex: 0 0 auto;\n\n            .menu-option {\n                min-height: 44px;\n                width: 100%;\n                padding: 0 20px;\n                background-color: rgb(var(--center-channel-bg-rgb));\n\n                > * {\n                    flex: 0 0 auto;\n                }\n\n                > .noicon {\n                    width: 16px;\n                    height: 16px;\n                    margin-right: 12px;\n                }\n\n                > .menu-name {\n                    font-size: 16px;\n                    justify-content: center;\n                }\n            }\n        }\n    }\n\n    @media not screen and (max-width: 430px) {\n        .hideOnWidescreen {\n            /* Hide controls (e.g. close button) on larger screens */\n            display: none !important;\n        }\n    }\n}\n"
  },
  {
    "path": "webapp/src/widgets/menu/menu.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {CSSProperties} from 'react'\n\nimport SeparatorOption from './separatorOption'\nimport SwitchOption from './switchOption'\nimport TextOption from './textOption'\nimport ColorOption from './colorOption'\nimport SubMenuOption, {HoveringContext} from './subMenuOption'\nimport LabelOption from './labelOption'\n\nimport './menu.scss'\nimport textInputOption from './textInputOption'\nimport MenuUtil from './menuUtil'\n\ntype Props = {\n    children: React.ReactNode\n    position?: 'top' | 'bottom' | 'left' | 'right' | 'auto'\n    fixed?: boolean\n    parentRef?: React.RefObject<any>\n}\n\nexport default class Menu extends React.PureComponent<Props> {\n    static Color = ColorOption\n    static SubMenu = SubMenuOption\n    static Switch = SwitchOption\n    static Separator = SeparatorOption\n    static Text = TextOption\n    static TextInput = textInputOption\n    static Label = LabelOption\n\n    menuRef: React.RefObject<HTMLDivElement>\n\n    constructor(props: Props) {\n        super(props)\n\n        this.menuRef = React.createRef<HTMLDivElement>()\n    }\n\n    public state = {\n        hovering: null,\n        menuStyle: {},\n    }\n\n    public render(): JSX.Element {\n        const {position, fixed, children} = this.props\n\n        let style: CSSProperties = {}\n        if (this.props.parentRef) {\n            const forceBottom = position ? ['bottom', 'left', 'right'].includes(position) : false\n            style = MenuUtil.openUp(this.props.parentRef, forceBottom).style\n        }\n\n        return (\n            <div\n                className={`Menu noselect ${position || 'bottom'} ${fixed ? ' fixed' : ''}`}\n                style={style}\n                ref={this.menuRef}\n            >\n                <div className='menu-contents'>\n                    <div className='menu-options'>\n                        {React.Children.map(children, (child) => (\n                            <div\n                                onMouseEnter={() => this.setState({hovering: child})}\n                            >\n                                <HoveringContext.Provider value={child === this.state.hovering}>\n                                    {child}\n                                </HoveringContext.Provider>\n                            </div>))}\n                    </div>\n\n                    <div className='menu-spacer hideOnWidescreen'/>\n\n                    <div className='menu-options hideOnWidescreen'>\n                        <Menu.Text\n                            id='menu-cancel'\n                            name={'Cancel'}\n                            className='menu-cancel'\n                            onClick={this.onCancel}\n                        />\n                    </div>\n                </div>\n            </div>\n        )\n    }\n\n    private onCancel = () => {\n        // No need to do anything, as click bubbled up to MenuWrapper, which closes\n    }\n}\n"
  },
  {
    "path": "webapp/src/widgets/menu/menuItem.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nexport type MenuOptionProps = {\n    id: string\n    name: string\n    onClick: (id: string) => void\n}\n"
  },
  {
    "path": "webapp/src/widgets/menu/menuUtil.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {CSSProperties} from 'react'\n\n/**\n * Calculates the position where the menu should be open, aligning it with the\n * `anchorRef` and positioning it down or up around the ref.\n * This should be used to make sure the menues are always aligned regardless of\n * the scroll position and fullly visible in cases when opening them close to\n * the edges of screen.\n * @param anchorRef ref of the element with respect to which the menu position is to be calculated.\n * @param forceBottom forces the element to be aligned at the bottom of the ref\n * @param menuMargin a safe margin value to be ensured around the menu in the calculations.\n *  this ensures the menu stick to the edges of the screen ans has some space around for ease of use.\n */\nfunction openUp(anchorRef: React.RefObject<HTMLElement>, forceBottom = false, menuMargin = 40): {openUp: boolean, style: CSSProperties} {\n    const ret = {\n        openUp: false,\n        style: {} as CSSProperties,\n    }\n    if (!anchorRef.current) {\n        return ret\n    }\n\n    const boundingRect = anchorRef.current.getBoundingClientRect()\n    const y = typeof boundingRect?.y === 'undefined' ? boundingRect?.top : boundingRect.y\n    const windowHeight = window.innerHeight\n    const totalSpace = windowHeight - menuMargin\n    const spaceOnTop = y || 0\n    const spaceOnBottom = totalSpace - spaceOnTop\n    ret.openUp = spaceOnTop > spaceOnBottom\n    if (!forceBottom && ret.openUp) {\n        ret.style.bottom = spaceOnBottom + menuMargin\n    } else {\n        ret.style.top = spaceOnTop + menuMargin\n    }\n\n    return ret\n}\n\nexport default {\n    openUp,\n}\n"
  },
  {
    "path": "webapp/src/widgets/menu/separatorOption.scss",
    "content": ".MenuSeparator {\n    border-bottom: solid 1px rgba(var(--center-channel-color-rgb), 0.16);\n    margin: 8px 0;\n}\n"
  },
  {
    "path": "webapp/src/widgets/menu/separatorOption.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {FC} from 'react'\n\nimport './separatorOption.scss'\n\nconst SeparatorOption: FC = (): JSX.Element => (\n    <div className='MenuOption MenuSeparator menu-separator'/>\n)\n\nexport default SeparatorOption\n"
  },
  {
    "path": "webapp/src/widgets/menu/subMenuOption.scss",
    "content": "@import '../../styles/z-index';\n\n.Menu .menu-options .SubMenuOption {\n    &.menu-option {\n        padding-right: 16px;\n    }\n}\n\n.SubMenuOption {\n    position: relative;\n\n    .SubMenu {\n        @include z-index(sub-menu-option-sub-menu);\n        position: absolute;\n        min-width: 180px;\n        background-color: rgb(var(--center-channel-bg-rgb));\n        color: rgb(var(--center-channel-color-rgb));\n        margin: 0 !important;\n\n        border-radius: var(--default-rad);\n        box-shadow: var(--elevation-4);\n        left: 100%;\n\n        &.top {\n            bottom: 0;\n        }\n\n        &.bottom {\n            top: 0;\n        }\n\n        &.left {\n            left: -100%;\n            right: 100%;\n        }\n\n        &.left-bottom {\n            left: -100%;\n            right: 100%;\n            top: 0;\n        }\n    }\n\n    @media screen and (max-width: 430px) {\n        .SubMenu {\n            background-color: rgba(var(--center-channel-color-rgb), 0.8) !important;\n        }\n    }\n}\n"
  },
  {
    "path": "webapp/src/widgets/menu/subMenuOption.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useEffect, useState, useContext, CSSProperties, useRef} from 'react'\n\nimport CompassIcon from '../../widgets/icons/compassIcon'\n\nimport MenuUtil from './menuUtil'\n\nimport Menu from '.'\n\nimport './subMenuOption.scss'\n\nexport const HoveringContext = React.createContext(false)\n\ntype SubMenuOptionProps = {\n    id: string\n    name: string\n    position?: 'bottom' | 'top' | 'left' | 'left-bottom' | 'auto'\n    icon?: React.ReactNode\n    children: React.ReactNode\n    className?: string\n}\n\nfunction SubMenuOption(props: SubMenuOptionProps): JSX.Element {\n    const [isOpen, setIsOpen] = useState(false)\n    const isHovering = useContext(HoveringContext)\n\n    const openLeftClass = props.position === 'left' || props.position === 'left-bottom' ? ' open-left' : ''\n\n    useEffect(() => {\n        if (isHovering !== undefined) {\n            setIsOpen(isHovering)\n        }\n    }, [isHovering])\n\n    const ref = useRef<HTMLDivElement>(null)\n\n    const styleRef = useRef<CSSProperties>({})\n\n    useEffect(() => {\n        const newStyle: CSSProperties = {}\n        if (props.position === 'auto' && ref.current) {\n            const openUp = MenuUtil.openUp(ref)\n            if (openUp.openUp) {\n                newStyle.bottom = 0\n            } else {\n                newStyle.top = 0\n            }\n        }\n\n        styleRef.current = newStyle\n    }, [ref.current])\n\n    return (\n        <div\n            id={props.id}\n            className={`MenuOption SubMenuOption menu-option${openLeftClass}${isOpen ? ' menu-option-active' : ''}${props.className ? ' ' + props.className : ''}`}\n            onClick={(e: React.MouseEvent) => {\n                e.preventDefault()\n                e.stopPropagation()\n                setIsOpen((open) => !open)\n            }}\n            ref={ref}\n        >\n            {props.icon ? <div className='menu-option__icon'>{props.icon}</div> : <div className='noicon'/>}\n            <div className='menu-name'>{props.name}</div>\n            <CompassIcon icon='chevron-right'/>\n            {isOpen &&\n                <div\n                    className={'SubMenu Menu noselect ' + (props.position || 'bottom')}\n                    style={styleRef.current}\n                >\n                    <div className='menu-contents'>\n                        <div className='menu-options'>\n                            {props.children}\n                        </div>\n                        <div className='menu-spacer hideOnWidescreen'/>\n\n                        <div className='menu-options hideOnWidescreen'>\n                            <Menu.Text\n                                id='menu-cancel'\n                                name={'Cancel'}\n                                className='menu-cancel'\n                                onClick={() => undefined}\n                            />\n                        </div>\n                    </div>\n\n                </div>\n            }\n        </div>\n    )\n}\n\nexport default React.memo(SubMenuOption)\n"
  },
  {
    "path": "webapp/src/widgets/menu/switchOption.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\n\nimport Switch from '../switch'\n\nimport {MenuOptionProps} from './menuItem'\n\ntype SwitchOptionProps = MenuOptionProps & {\n    isOn: boolean\n    icon?: React.ReactNode\n    suppressItemClicked?: boolean\n}\n\nfunction SwitchOption(props: SwitchOptionProps): JSX.Element {\n    const {name, icon, isOn, suppressItemClicked} = props\n\n    return (\n        <div\n            className='MenuOption SwitchOption menu-option'\n            role='button'\n            aria-label={name}\n            onClick={(e: React.MouseEvent) => {\n                if (!suppressItemClicked) {\n                    e.target.dispatchEvent(new Event('menuItemClicked'))\n                }\n                props.onClick(props.id)\n                e.stopPropagation()\n            }}\n        >\n            {icon ? <div className='menu-option__icon'>{icon}</div> : <div className='noicon'/>}\n            <div className='menu-name'>{name}</div>\n            <Switch\n                isOn={isOn}\n                onChanged={() => {}}\n            />\n        </div>\n    )\n}\n\nexport default React.memo(SwitchOption)\n"
  },
  {
    "path": "webapp/src/widgets/menu/textInputOption.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React, {useState, useRef, useEffect} from 'react'\n\ntype TextInputOptionProps = {\n    initialValue: string\n    onConfirmValue: (value: string) => void\n    onValueChanged: (value: string) => void\n}\n\nfunction TextInputOption(props: TextInputOptionProps): JSX.Element {\n    const nameTextbox = useRef<HTMLInputElement>(null)\n    const [value, setValue] = useState(props.initialValue)\n\n    useEffect(() => {\n        nameTextbox.current?.focus()\n        nameTextbox.current?.setSelectionRange(0, value.length)\n    }, [])\n\n    return (\n        <input\n            ref={nameTextbox}\n            type='text'\n            className='PropertyMenu menu-textbox menu-option'\n            onClick={(e) => e.stopPropagation()}\n            onChange={(e) => {\n                setValue(e.target.value)\n                props.onValueChanged(value)\n            }}\n            value={value}\n            title={value}\n            onBlur={() => props.onConfirmValue(value)}\n            onKeyDown={(e) => {\n                if (e.key === 'Enter' || e.key === 'Escape') {\n                    props.onConfirmValue(value)\n                    e.stopPropagation()\n                    if (e.key === 'Enter') {\n                        e.target.dispatchEvent(new Event('menuItemClicked'))\n                    }\n                }\n            }}\n            spellCheck={true}\n        />\n    )\n}\n\nexport default React.memo(TextInputOption)\n"
  },
  {
    "path": "webapp/src/widgets/menu/textOption.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\n\nimport {MenuOptionProps} from './menuItem'\n\ntype TextOptionProps = MenuOptionProps & {\n    check?: boolean\n    icon?: React.ReactNode\n    rightIcon?: React.ReactNode\n    className?: string\n    subText?: string\n    disabled?: boolean\n}\n\nfunction TextOption(props: TextOptionProps): JSX.Element {\n    const {name, icon, rightIcon, check, subText, disabled} = props\n    let className = 'MenuOption TextOption menu-option'\n    if (props.className) {\n        className += ' ' + props.className\n    }\n    if (subText) {\n        className += ' menu-option--with-subtext'\n    }\n    if (disabled) {\n        className += ' menu-option--disabled'\n    }\n\n    return (\n        <div\n            role='button'\n            aria-label={name}\n            className={className}\n            onClick={(e: React.MouseEvent) => {\n                e.target.dispatchEvent(new Event('menuItemClicked'))\n                props.onClick(props.id)\n                e.stopPropagation()\n            }}\n        >\n            <div className={`${check ? 'd-flex menu-option__check' : 'd-flex'}`}>{icon ? <div className='menu-option__icon'>{icon}</div> : <div className='noicon'/>}</div>\n            <div className='menu-option__content'>\n                <div className='menu-name'>{name}</div>\n                {subText && <div className='menu-subtext text-75 mt-1'>{subText}</div>}\n            </div>\n            {rightIcon ?? <div className='noicon'/>}\n        </div>\n    )\n}\n\nexport default React.memo(TextOption)\n"
  },
  {
    "path": "webapp/src/widgets/menuWrapper.scss",
    "content": ".MenuWrapper {\n    position: relative;\n    cursor: default;\n\n    &.disabled {\n        cursor: default;\n    }\n\n    &.override.menuOpened {\n        display: block;\n    }\n\n    *:first-child {\n        /* stylelint-disable property-no-vendor-prefix*/\n        -webkit-user-select: text; /* Chrome all / Safari all */\n        -moz-user-select: text; /* Firefox all */\n        -ms-user-select: text; /* IE 10+ */\n        user-select: text;\n        /* stylelint-enable property-no-vendor-prefix*/\n    }\n}\n"
  },
  {
    "path": "webapp/src/widgets/menuWrapper.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React, {useRef, useState, useEffect, useCallback} from 'react'\n\nimport './menuWrapper.scss'\n\ntype Props = {\n    children?: React.ReactNode\n    stopPropagationOnToggle?: boolean\n    className?: string\n    disabled?: boolean\n    isOpen?: boolean\n    onToggle?: (open: boolean) => void\n    label?: string\n}\n\nconst MenuWrapper = (props: Props) => {\n    const node = useRef<HTMLDivElement>(null)\n    const [open, setOpen] = useState(Boolean(props.isOpen))\n\n    if (!Array.isArray(props.children) || props.children.length !== 2) {\n        throw new Error('MenuWrapper needs exactly 2 children')\n    }\n\n    const close = useCallback((): void => {\n        if (open) {\n            setOpen(false)\n            props.onToggle && props.onToggle(false)\n        }\n    }, [props.onToggle, open])\n\n    const closeOnBlur = useCallback((e: Event) => {\n        if (e.target && node.current?.contains(e.target as Node)) {\n            return\n        }\n\n        close()\n    }, [close])\n\n    const keyboardClose = useCallback((e: KeyboardEvent) => {\n        if (e.key === 'Escape') {\n            close()\n        }\n\n        if (e.key === 'Tab') {\n            closeOnBlur(e)\n        }\n    }, [close, closeOnBlur])\n\n    const toggle = useCallback((e: React.MouseEvent<HTMLDivElement, MouseEvent>): void => {\n        if (props.disabled) {\n            return\n        }\n\n        /**\n         * This is only here so that we can toggle the menus in the sidebar, because the default behavior of the mobile\n         * version (ie the one that uses a modal) needs propagation to close the modal after selecting something\n         * We need to refactor this so that the modal is explicitly closed on toggle, but for now I am aiming to preserve the existing logic\n         * so as to not break other things\n        **/\n        if (props.stopPropagationOnToggle) {\n            e.preventDefault()\n            e.stopPropagation()\n        }\n        setOpen(!open)\n        props.onToggle && props.onToggle(!open)\n    }, [props.onToggle, open, props.disabled])\n\n    useEffect(() => {\n        if (open) {\n            document.addEventListener('menuItemClicked', close, true)\n            document.addEventListener('click', closeOnBlur, true)\n            document.addEventListener('keyup', keyboardClose, true)\n        }\n        return () => {\n            if (open) {\n                document.removeEventListener('menuItemClicked', close, true)\n                document.removeEventListener('click', closeOnBlur, true)\n                document.removeEventListener('keyup', keyboardClose, true)\n            }\n        }\n    }, [open, close, closeOnBlur, keyboardClose])\n\n    const {children} = props\n    let className = 'MenuWrapper'\n    if (props.disabled) {\n        className += ' disabled'\n    }\n    if (open) {\n        className += ' override menuOpened'\n    }\n    if (props.className) {\n        className += ' ' + props.className\n    }\n\n    return (\n        <div\n            role='button'\n            aria-label={props.label || 'menuwrapper'}\n            className={className}\n            onClick={toggle}\n            ref={node}\n        >\n            {children ? Object.values(children)[0] : null}\n            {children && !props.disabled && open ? Object.values(children)[1] : null}\n        </div>\n    )\n}\n\nexport default React.memo(MenuWrapper)\n"
  },
  {
    "path": "webapp/src/widgets/notificationBox/__snapshots__/notificationBox.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`widgets/NotificationBox should match snapshot with close with tooltip 1`] = `\n<div>\n  <div\n    class=\"NotificationBox\"\n  >\n    <div\n      class=\"content\"\n    >\n      <p\n        class=\"title\"\n      >\n        title\n      </p>\n      CONTENT\n    </div>\n    <div\n      class=\"octo-tooltip tooltip-top\"\n      data-tooltip=\"tooltip\"\n    >\n      <button\n        class=\"IconButton\"\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-close CloseIcon\"\n        />\n      </button>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`widgets/NotificationBox should match snapshot with close without tooltip 1`] = `\n<div>\n  <div\n    class=\"NotificationBox\"\n  >\n    <div\n      class=\"content\"\n    >\n      <p\n        class=\"title\"\n      >\n        title\n      </p>\n      CONTENT\n    </div>\n    <button\n      class=\"IconButton\"\n      type=\"button\"\n    >\n      <i\n        class=\"CompassIcon icon-close CloseIcon\"\n      />\n    </button>\n  </div>\n</div>\n`;\n\nexports[`widgets/NotificationBox should match snapshot with icon 1`] = `\n<div>\n  <div\n    class=\"NotificationBox\"\n  >\n    <div\n      class=\"NotificationBox__icon\"\n    >\n      ICON\n    </div>\n    <div\n      class=\"content\"\n    >\n      <p\n        class=\"title\"\n      >\n        title\n      </p>\n      CONTENT\n    </div>\n  </div>\n</div>\n`;\n\nexports[`widgets/NotificationBox should match snapshot with icon and close with tooltip 1`] = `\n<div>\n  <div\n    class=\"NotificationBox\"\n  >\n    <div\n      class=\"NotificationBox__icon\"\n    >\n      ICON\n    </div>\n    <div\n      class=\"content\"\n    >\n      <p\n        class=\"title\"\n      >\n        title\n      </p>\n      CONTENT\n    </div>\n    <div\n      class=\"octo-tooltip tooltip-top\"\n      data-tooltip=\"tooltip\"\n    >\n      <button\n        class=\"IconButton\"\n        type=\"button\"\n      >\n        <i\n          class=\"CompassIcon icon-close CloseIcon\"\n        />\n      </button>\n    </div>\n  </div>\n</div>\n`;\n\nexports[`widgets/NotificationBox should match snapshot without icon and close 1`] = `\n<div>\n  <div\n    class=\"NotificationBox\"\n  >\n    <div\n      class=\"content\"\n    >\n      <p\n        class=\"title\"\n      >\n        title\n      </p>\n      CONTENT\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "webapp/src/widgets/notificationBox/notificationBox.scss",
    "content": "@import '../../styles/z-index';\n\n.NotificationBox {\n    @include z-index(notification-box);\n    position: fixed;\n    bottom: 52px;\n    right: 32px;\n    border-radius: 4px;\n    background: rgb(var(--center-channel-bg-rgb));\n    box-shadow: rgba(var(--center-channel-color-rgb), 0.1) 0 0 0 1px,\n        rgba(var(--center-channel-color-rgb), 0.1) 0 2px 4px;\n    display: flex;\n    padding: 22px;\n    width: 400px;\n\n    .NotificationBox__icon {\n        margin-right: 10px;\n    }\n\n    .content {\n        font-size: 14px;\n        font-weight: 400;\n\n        .title {\n            font-size: 14px;\n            font-weight: 600;\n            margin-bottom: 0;\n            line-height: 25px;\n        }\n    }\n\n    .IconButton {\n        margin-left: auto;\n    }\n\n    .octo-tooltip {\n        font-size: 12px;\n        font-weight: 600;\n\n        .IconButton {\n            font-size: 14px;\n            font-weight: 400;\n        }\n    }\n}\n"
  },
  {
    "path": "webapp/src/widgets/notificationBox/notificationBox.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {render} from '@testing-library/react'\nimport '@testing-library/jest-dom'\n\nimport {wrapIntl} from '../../testUtils'\n\nimport NotificationBox from './notificationBox'\n\ndescribe('widgets/NotificationBox', () => {\n    beforeEach(() => {\n        // Quick fix to disregard console error when unmounting a component\n        console.error = jest.fn()\n        document.execCommand = jest.fn()\n    })\n\n    test('should match snapshot without icon and close', () => {\n        const component = wrapIntl(\n            <NotificationBox\n                title='title'\n            >\n                {'CONTENT'}\n            </NotificationBox>,\n        )\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot with icon', () => {\n        const component = wrapIntl(\n            <NotificationBox\n                title='title'\n                icon='ICON'\n            >\n                {'CONTENT'}\n            </NotificationBox>,\n        )\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot with close without tooltip', () => {\n        const component = wrapIntl(\n            <NotificationBox\n                title='title'\n                onClose={() => null}\n            >\n                {'CONTENT'}\n            </NotificationBox>,\n        )\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot with close with tooltip', () => {\n        const component = wrapIntl(\n            <NotificationBox\n                title='title'\n                onClose={() => null}\n                closeTooltip='tooltip'\n            >\n                {'CONTENT'}\n            </NotificationBox>,\n        )\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n\n    test('should match snapshot with icon and close with tooltip', () => {\n        const component = wrapIntl(\n            <NotificationBox\n                title='title'\n                icon='ICON'\n                onClose={() => null}\n                closeTooltip='tooltip'\n            >\n                {'CONTENT'}\n            </NotificationBox>,\n        )\n        const {container} = render(component)\n        expect(container).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/widgets/notificationBox/notificationBox.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\n\nimport {Utils} from '../../utils'\n\nimport IconButton from '../buttons/iconButton'\nimport CloseIcon from '../icons/close'\nimport Tooltip from '../tooltip'\n\nimport './notificationBox.scss'\n\ntype Props = {\n    title: string\n    icon?: React.ReactNode\n    children?: React.ReactNode\n    onClose?: () => void\n    closeTooltip?: string\n    className?: string\n}\n\nfunction renderClose(onClose?: () => void, closeTooltip?: string) {\n    if (!onClose) {\n        return null\n    }\n\n    if (closeTooltip) {\n        return (\n            <Tooltip title={closeTooltip}>\n                <IconButton\n                    icon={<CloseIcon/>}\n                    onClick={onClose}\n                />\n            </Tooltip>\n        )\n    }\n\n    return (\n        <IconButton\n            icon={<CloseIcon/>}\n            onClick={onClose}\n        />\n    )\n}\n\nfunction NotificationBox(props: Props): JSX.Element {\n    const className = Utils.generateClassName({\n        NotificationBox: true,\n        [props.className || '']: Boolean(props.className),\n    })\n\n    return (\n        <div className={className}>\n            {props.icon &&\n                <div className='NotificationBox__icon'>\n                    {props.icon}\n                </div>}\n            <div className='content'>\n                <p className='title'>{props.title}</p>\n                {props.children}\n            </div>\n            {renderClose(props.onClose, props.closeTooltip)}\n        </div>\n    )\n}\n\nexport default React.memo(NotificationBox)\n"
  },
  {
    "path": "webapp/src/widgets/propertyMenu.scss",
    "content": ".PropertyMenu {\n    &.menu-textbox {\n        font-weight: 400;\n        padding: 2px 10px;\n        cursor: text;\n        touch-action: none;\n        border: solid 1px #909090;\n        border-radius: 3px;\n        margin: 5px;\n    }\n}\n"
  },
  {
    "path": "webapp/src/widgets/propertyMenu.test.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport React from 'react'\nimport {fireEvent, render} from '@testing-library/react'\nimport '@testing-library/jest-dom'\n\nimport {wrapIntl} from '../testUtils'\nimport propsRegistry from '../properties'\n\nimport PropertyMenu from './propertyMenu'\n\ndescribe('widgets/PropertyMenu', () => {\n    beforeEach(() => {\n        // Quick fix to disregard console error when unmounting a component\n        console.error = jest.fn()\n        document.execCommand = jest.fn()\n    })\n\n    test('should display the type of property', () => {\n        const callback = jest.fn()\n        const component = wrapIntl(\n            <PropertyMenu\n                propertyId={'id'}\n                propertyName={'email of a person'}\n                propertyType={propsRegistry.get('email')}\n                onTypeAndNameChanged={callback}\n                onDelete={callback}\n            />,\n        )\n        const {getByText} = render(component)\n        expect(getByText('Type: Email')).toBeVisible()\n    })\n\n    test('handles delete event', () => {\n        const callback = jest.fn()\n        const component = wrapIntl(\n            <PropertyMenu\n                propertyId={'id'}\n                propertyName={'email of a person'}\n                propertyType={propsRegistry.get('email')}\n                onTypeAndNameChanged={callback}\n                onDelete={callback}\n            />,\n        )\n        const {getByText} = render(component)\n        fireEvent.click(getByText(/delete/i))\n        expect(callback).toHaveBeenCalledWith('id')\n    })\n\n    test('handles name change event', () => {\n        const callback = jest.fn()\n        const component = wrapIntl(\n            <PropertyMenu\n                propertyId={'id'}\n                propertyName={'test-property'}\n                propertyType={propsRegistry.get('text')}\n                onTypeAndNameChanged={callback}\n                onDelete={callback}\n            />,\n        )\n        const {getByDisplayValue} = render(component)\n        const input = getByDisplayValue(/test-property/i)\n        fireEvent.change(input, {target: {value: 'changed name'}})\n        fireEvent.blur(input)\n        expect(callback).toHaveBeenCalledWith(propsRegistry.get('text'), 'changed name')\n    })\n\n    test('handles type change event', async () => {\n        const callback = jest.fn()\n        const component = wrapIntl(\n            <PropertyMenu\n                propertyId={'id'}\n                propertyName={'test-property'}\n                propertyType={propsRegistry.get('text')}\n                onTypeAndNameChanged={callback}\n                onDelete={callback}\n            />,\n        )\n        const {getByText} = render(component)\n        const menuOpen = getByText(/Type: Text/i)\n        fireEvent.click(menuOpen)\n        fireEvent.click(getByText('Select'))\n        setTimeout(() => expect(callback).toHaveBeenCalledWith('select', 'test-property'), 2000)\n    })\n\n    test('handles name and type change event', () => {\n        const callback = jest.fn()\n        const component = wrapIntl(\n            <PropertyMenu\n                propertyId={'id'}\n                propertyName={'test-property'}\n                propertyType={propsRegistry.get('text')}\n                onTypeAndNameChanged={callback}\n                onDelete={callback}\n            />,\n        )\n        const {getByDisplayValue, getByText} = render(component)\n        const input = getByDisplayValue(/test-property/i)\n        fireEvent.change(input, {target: {value: 'changed name'}})\n\n        const menuOpen = getByText(/Type: Text/i)\n        fireEvent.click(menuOpen)\n        fireEvent.click(getByText('Select'))\n        setTimeout(() => expect(callback).toHaveBeenCalledWith('select', 'changed name'), 2000)\n    })\n\n    test('should match snapshot', () => {\n        const callback = jest.fn()\n        const component = wrapIntl(\n            <PropertyMenu\n                propertyId={'id'}\n                propertyName={'test-property'}\n                propertyType={propsRegistry.get('text')}\n                onTypeAndNameChanged={callback}\n                onDelete={callback}\n            />,\n        )\n        const {container, getByText} = render(component)\n        const menuOpen = getByText(/Type: Text/i)\n        fireEvent.click(menuOpen)\n        expect(container).toMatchSnapshot()\n    })\n})\n"
  },
  {
    "path": "webapp/src/widgets/propertyMenu.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\nimport {useIntl, IntlShape} from 'react-intl'\n\nimport Menu from '../widgets/menu'\nimport propsRegistry from '../properties'\nimport {PropertyType} from '../properties/types'\nimport './propertyMenu.scss'\n\ntype Props = {\n    propertyId: string\n    propertyName: string\n    propertyType: PropertyType\n    onTypeAndNameChanged: (newType: PropertyType, newName: string) => void\n    onDelete: (id: string) => void\n}\n\nfunction typeMenuTitle(intl: IntlShape, type: PropertyType): string {\n    return `${intl.formatMessage({id: 'PropertyMenu.typeTitle', defaultMessage: 'Type'})}: ${type.displayName(intl)}`\n}\n\ntype TypesProps = {\n    label: string\n    onTypeSelected: (type: PropertyType) => void\n}\n\nexport const PropertyTypes = (props: TypesProps): JSX.Element => {\n    const intl = useIntl()\n    return (\n        <>\n            <Menu.Label>\n                <b>{props.label}</b>\n            </Menu.Label>\n\n            <Menu.Separator/>\n\n            {\n                propsRegistry.list().map((p) => (\n                    <Menu.Text\n                        key={p.type}\n                        id={p.type}\n                        name={p.displayName(intl)}\n                        onClick={() => props.onTypeSelected(p)}\n                    />\n                ))\n            }\n        </>\n    )\n}\n\nconst PropertyMenu = (props: Props) => {\n    const intl = useIntl()\n    let currentPropertyName = props.propertyName\n\n    const deleteText = intl.formatMessage({\n        id: 'PropertyMenu.Delete',\n        defaultMessage: 'Delete',\n    })\n\n    return (\n        <Menu>\n            <Menu.TextInput\n                initialValue={props.propertyName}\n                onConfirmValue={(n) => {\n                    props.onTypeAndNameChanged(props.propertyType, n)\n                    currentPropertyName = n\n                }}\n                onValueChanged={(n) => {\n                    currentPropertyName = n\n                }}\n            />\n            <Menu.SubMenu\n                id='type'\n                name={typeMenuTitle(intl, props.propertyType)}\n            >\n                <PropertyTypes\n                    label={intl.formatMessage({id: 'PropertyMenu.changeType', defaultMessage: 'Change property type'})}\n                    onTypeSelected={(type: PropertyType) => props.onTypeAndNameChanged(type, currentPropertyName)}\n                />\n            </Menu.SubMenu>\n            <Menu.Text\n                id='delete'\n                name={deleteText}\n                onClick={() => props.onDelete(props.propertyId)}\n            />\n        </Menu>\n    )\n}\n\nexport default React.memo(PropertyMenu)\n"
  },
  {
    "path": "webapp/src/widgets/switch.scss",
    "content": ".Switch.override-main {\n    display: flex;\n    flex-shrink: 0;\n    align-items: center;\n    box-sizing: content-box;\n    height: 14px;\n    width: 26px;\n    border-radius: 44px;\n    padding: 2px;\n    background-color: rgba(var(--center-channel-color-rgb), 0.24);\n    transition: background 200ms ease 0s, box-shadow 200ms ease 0s;\n\n    &:not(.readonly) {\n        cursor: pointer;\n    }\n\n    &.on {\n        background-color: rgb(var(--button-bg-rgb));\n\n        .octo-switch-inner {\n            transform: translateX(12px) translateY(0);\n        }\n    }\n\n    .octo-switch-inner {\n        width: 14px;\n        height: 14px;\n        border-radius: 44px;\n        background-color: rgb(var(--center-channel-bg-rgb));\n        transition: transform 200ms ease-out 0s, background 200ms ease-out 0s;\n        transform: translateX(0) translateY(0);\n    }\n\n    &.size--medium {\n        width: 36px;\n        height: 20px;\n\n        .octo-switch-inner {\n            width: 20px;\n            height: 20px;\n        }\n\n        &.on {\n            .octo-switch-inner {\n                transform: translateX(16px) translateY(0);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "webapp/src/widgets/switch.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\n\nimport './switch.scss'\n\ntype Props = {\n    onChanged: (isOn: boolean) => void\n    isOn: boolean\n    readOnly?: boolean\n    size?: string\n}\n\n// Switch is an on-off style switch / checkbox\nfunction Switch(props: Props): JSX.Element {\n    const switchSize = `size--${props.size === 'medium' ? 'medium' : 'small'}`\n    const switchIsOn = props.isOn ? ' on' : ''\n    const switchIsReadonly = props.readOnly ? ' readonly' : ''\n    const className = `Switch override-main ${switchSize}${switchIsOn}${switchIsReadonly}`\n    return (\n        <div\n            className={className}\n            onClick={() => {\n                if (!props.readOnly) {\n                    props.onChanged(!props.isOn)\n                }\n            }}\n        >\n            <div className='octo-switch-inner'/>\n        </div>\n    )\n}\n\nexport default React.memo(Switch)\n"
  },
  {
    "path": "webapp/src/widgets/tooltip.scss",
    "content": "@import '../styles/z-index';\n\n// Tooltip arrow width\n$tooltip-arrow-width: 6px;\n// Space between element after transform\n$tooltop-horizontal-offset: 2px;\n$tooltop-vertical-offset: 2px;\n\n%hover-tooltip-body {\n    @include z-index(hover-tooltip-body);\n    position: absolute;\n    content: attr(data-tooltip);\n    padding: 2px 4px;\n    line-height: 1.2;\n    font-size: 1em;\n    text-align: center;\n    color: rgb(255, 255, 255);\n    background: rgb(0, 0, 0);\n    // opacity: 0.8;\n    border: 4px solid rgb(0, 0, 0);\n    border-radius: 4px;\n    text-shadow: rgba(0, 0, 0, 0.098) 1px 1px 1px;\n    box-shadow: rgba(0, 0, 0, 0.1) 1px 1px 2px 0;\n    min-width: 2em;\n    max-width: 21em;\n    white-space: nowrap;\n    overflow: hidden;\n    text-overflow: ellipsis;\n    display: block;\n}\n\n%hover-tooltip-arrow {\n    @include z-index(hover-tooltip-arrow);\n    content: '';\n    position: absolute;\n    width: 0;\n    height: 0;\n    border-width: $tooltip-arrow-width;\n    border-style: solid;\n    border-color: #000 transparent transparent;\n}\n\n.octo-tooltip {\n    display: inline;\n    // Tooltip itself needs to be relative so\n    // content and arrow can be absolute to it\n    position: relative;\n    // Arrow gets added before content\n    &:hover::before {\n        @extend %hover-tooltip-arrow; }\n    // Tooltip message gets added after content\n    &:hover::after {\n        @extend %hover-tooltip-body; }\n\n    // Top tooltip arrow style\n    &.tooltip-top:hover::before {\n        left: 50%;\n        bottom: calc(100% - 2px);\n        transform: translate(-50%, #{$tooltop-horizontal-offset});\n    }\n    // Top tooltip body style\n    &.tooltip-top:hover::after {\n        bottom: calc(100% + 10px);\n        left: 50%;\n        transform: translate(-50%, #{$tooltop-horizontal-offset});\n    }\n\n    // Right tooltip arrow style\n    &.tooltip-right:hover::before {\n        border-color: transparent #000 transparent transparent;\n        top: 50%;\n        right: calc(0em - 2px);\n        transform: translate(#{$tooltop-vertical-offset}, -50%);\n    }\n    // Right tooltip body style\n    &.tooltip-right:hover::after {\n        top: 50%;\n        left: calc(100% + 2px);\n        transform: translate(#{$tooltop-vertical-offset}, -50%);\n    }\n\n    // Left tooltip arrow style\n    &.tooltip-left:hover::before {\n        top: 50%;\n        left: calc(0em - 2px);\n        border-color: transparent transparent transparent #000;\n        transform: translate(-#{$tooltop-vertical-offset}, -50%);\n    }\n    // Left tooltip body style\n    &.tooltip-left:hover::after {\n        top: 50%;\n        right: calc(100% + 2px);\n        transform: translate(-#{$tooltop-vertical-offset}, -50%);\n    }\n\n    // Bottom tooltip arrow style\n    &.tooltip-bottom:hover::before {\n        left: 50%;\n        top: calc(100% - 8px);\n        border-color: transparent transparent #000;\n        transform: translate(-50%, -#{$tooltop-horizontal-offset});\n    }\n    // Bottom tooltip body style\n    &.tooltip-bottom:hover::after {\n        left: 50%;\n        top: calc(100% + 4px);\n        transform: translate(-50%, -#{$tooltop-horizontal-offset});\n    }\n}\n"
  },
  {
    "path": "webapp/src/widgets/tooltip.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\n\nimport './tooltip.scss'\n\ntype Props = {\n    title: string\n    children: React.ReactNode\n    placement?: 'top'|'left'|'right'|'bottom'\n}\n\n// Adds tooltip div over children elements, the popup will\n// be positioned based on the specified placement\n// Default position is 'top'\nfunction Tooltip(props: Props): JSX.Element {\n    const placement = props.placement || 'top'\n    const className = `octo-tooltip tooltip-${placement}`\n    return (\n        <div\n            className={className}\n            data-tooltip={props.title}\n        >\n            {props.children}\n        </div>\n    )\n}\n\nexport default React.memo(Tooltip)\n"
  },
  {
    "path": "webapp/src/widgets/valueSelector.scss",
    "content": "@import '../styles/z-index';\n\n.ValueSelector {\n    width: 100%;\n    border-radius: var(--default-rad);\n    color: rgb(var(--center-channel-color-rgb));\n    display: flex;\n    min-width: 180px;\n\n    > .Label {\n        margin: 0 10px;\n        max-width: calc(100% - 10px);\n\n        &.empty {\n            color: rgba(var(--center-channel-color-rgb), 0.6);\n        }\n    }\n\n    .Label {\n        display: flex;\n        text-overflow: ellipsis;\n        overflow: hidden;\n        border-radius: var(--default-rad);\n        max-width: 100%;\n\n        .Label-text {\n            flex-grow: 1;\n        }\n\n        .IconButton.delete-value {\n            @include z-index(value-selector-delete);\n            width: 16px;\n            height: 16px;\n            flex: 0 0 auto;\n\n            i {\n                font-size: 16px;\n            }\n        }\n    }\n\n    .Label-no-padding {\n        padding-top: 0;\n        padding-bottom: 0;\n    }\n\n    .Label-no-margin {\n        margin: 0;\n    }\n\n    .Label-single-select {\n        margin-bottom: 0;\n    }\n\n    .Label-text {\n        display: inline-block;\n        text-overflow: ellipsis;\n        overflow: hidden;\n        max-width: 250px;\n    }\n\n    .value-menu-option {\n        display: flex;\n        width: 100%;\n        justify-content: center;\n        align-items: center;\n\n        .label-container {\n            flex-grow: 1;\n            display: flex;\n        }\n\n        .MenuWrapper {\n            display: flex;\n            align-self: flex-end;\n\n            .IconButton {\n                background-color: unset;\n\n                &:hover {\n                    background-color: rgba(var(--center-channel-color-rgb), 0.1);\n                }\n            }\n        }\n    }\n\n    .ValueSelector__menu-list {\n        overflow-y: unset;\n        overflow-x: unset;\n    }\n}\n\n.label-container > .Label {\n    max-width: 600px;\n    overflow: hidden;\n    text-overflow: ellipsis;\n    display: inline-block;\n\n\n}\n\n.octo-property-value > .Label {\n    max-width: 200px;\n    overflow: hidden;\n    text-overflow: ellipsis;\n    display: inline-block;\n}\n"
  },
  {
    "path": "webapp/src/widgets/valueSelector.tsx",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nimport React from 'react'\nimport {useIntl} from 'react-intl'\nimport {ActionMeta, OnChangeValue} from 'react-select'\nimport {FormatOptionLabelMeta} from 'react-select/base'\nimport CreatableSelect from 'react-select/creatable'\n\nimport {CSSObject} from '@emotion/serialize'\n\nimport {IPropertyOption} from '../blocks/board'\nimport {Constants} from '../constants'\n\nimport {getSelectBaseStyle} from '../theme'\n\nimport Menu from './menu'\nimport MenuWrapper from './menuWrapper'\nimport IconButton from './buttons/iconButton'\nimport OptionsIcon from './icons/options'\nimport DeleteIcon from './icons/delete'\nimport CloseIcon from './icons/close'\nimport Label from './label'\n\nimport './valueSelector.scss'\n\ntype Props = {\n    options: IPropertyOption[]\n    value?: IPropertyOption | IPropertyOption[]\n    emptyValue: string\n    onCreate: (value: string) => void\n    onChange: (value: string | string[]) => void\n    onChangeColor: (option: IPropertyOption, color: string) => void\n    onDeleteOption: (option: IPropertyOption) => void\n    isMulti?: boolean\n    onDeleteValue?: (value: IPropertyOption) => void\n    onBlur?: () => void\n}\n\ntype LabelProps = {\n    option: IPropertyOption\n    meta: FormatOptionLabelMeta<IPropertyOption>\n    onChangeColor: (option: IPropertyOption, color: string) => void\n    onDeleteOption: (option: IPropertyOption) => void\n    onDeleteValue?: (value: IPropertyOption) => void\n    isMulti?: boolean\n}\n\nconst ValueSelectorLabel = (props: LabelProps): JSX.Element => {\n    const {option, onDeleteValue, meta, isMulti} = props\n    const intl = useIntl()\n    if (meta.context === 'value') {\n        let className = onDeleteValue ? 'Label-no-padding' : 'Label-single-select'\n        if (!isMulti) {\n            className += ' Label-no-margin'\n        }\n        return (\n            <Label\n                color={option.color}\n                className={className}\n            >\n                <span className='Label-text'>{option.value}</span>\n                {onDeleteValue &&\n                    <IconButton\n                        onClick={() => onDeleteValue(option)}\n                        icon={<CloseIcon/>}\n                        title='Clear'\n                        className='margin-left delete-value'\n                    />\n                }\n            </Label>\n        )\n    }\n    return (\n        <div\n            className='value-menu-option'\n            role='menuitem'\n        >\n            <div className='label-container'>\n                <Label color={option.color}>{option.value}</Label>\n            </div>\n            <MenuWrapper stopPropagationOnToggle={true}>\n                <IconButton\n                    title={intl.formatMessage({id: 'ValueSelectorLabel.openMenu', defaultMessage: 'Open menu'})}\n                    icon={<OptionsIcon/>}\n                />\n                <Menu position='left'>\n                    <Menu.Text\n                        id='delete'\n                        icon={<DeleteIcon/>}\n                        name={intl.formatMessage({id: 'BoardComponent.delete', defaultMessage: 'Delete'})}\n                        onClick={() => props.onDeleteOption(option)}\n                    />\n                    <Menu.Separator/>\n                    {Object.entries(Constants.menuColors).map(([key, color]: [string, string]) => (\n                        <Menu.Color\n                            key={key}\n                            id={key}\n                            name={color}\n                            onClick={() => props.onChangeColor(option, key)}\n                        />\n                    ))}\n                </Menu>\n            </MenuWrapper>\n        </div>\n    )\n}\n\nconst valueSelectorStyle = {\n    ...getSelectBaseStyle(),\n    option: (provided: CSSObject, state: {isFocused: boolean}): CSSObject => ({\n        ...provided,\n        background: state.isFocused ? 'rgba(var(--center-channel-color-rgb), 0.1)' : 'rgb(var(--center-channel-bg-rgb))',\n        color: state.isFocused ? 'rgb(var(--center-channel-color-rgb))' : 'rgb(var(--center-channel-color-rgb))',\n        padding: '8px',\n    }),\n    control: (): CSSObject => ({\n        border: 0,\n        width: '100%',\n        margin: '0',\n    }),\n    valueContainer: (provided: CSSObject): CSSObject => ({\n        ...provided,\n        padding: '0 8px',\n        overflow: 'unset',\n    }),\n    singleValue: (provided: CSSObject): CSSObject => ({\n        ...provided,\n        position: 'static',\n        top: 'unset',\n        transform: 'unset',\n    }),\n    placeholder: (provided: CSSObject): CSSObject => ({\n        ...provided,\n        color: 'rgba(var(--center-channel-color-rgb), 0.4)',\n    }),\n    multiValue: (provided: CSSObject): CSSObject => ({\n        ...provided,\n        margin: 0,\n        padding: 0,\n        backgroundColor: 'transparent',\n    }),\n    multiValueLabel: (provided: CSSObject): CSSObject => ({\n        ...provided,\n        display: 'flex',\n        paddingLeft: 0,\n        padding: 0,\n    }),\n    multiValueRemove: (): CSSObject => ({\n        display: 'none',\n    }),\n    menu: (provided: CSSObject): CSSObject => ({\n        ...provided,\n        width: 'unset',\n        background: 'rgb(var(--center-channel-bg-rgb))',\n        minWidth: '260px',\n    }),\n}\n\nfunction ValueSelector(props: Props): JSX.Element {\n    const intl = useIntl()\n    return (\n        <CreatableSelect\n            noOptionsMessage={() => intl.formatMessage({id: 'ValueSelector.noOptions', defaultMessage: 'No options. Start typing to add the first one!'})}\n            aria-label={intl.formatMessage({id: 'ValueSelector.valueSelector', defaultMessage: 'Value selector'})}\n            captureMenuScroll={true}\n            maxMenuHeight={1200}\n            isMulti={props.isMulti}\n            isClearable={true}\n            styles={valueSelectorStyle}\n            formatOptionLabel={(option: IPropertyOption, meta: FormatOptionLabelMeta<IPropertyOption>) => (\n                <ValueSelectorLabel\n                    option={option}\n                    meta={meta}\n                    isMulti={props.isMulti}\n                    onChangeColor={props.onChangeColor}\n                    onDeleteOption={props.onDeleteOption}\n                    onDeleteValue={props.onDeleteValue}\n                />\n            )}\n            className='ValueSelector'\n            classNamePrefix='ValueSelector'\n            options={props.options}\n            getOptionLabel={(o: IPropertyOption) => o.value}\n            getOptionValue={(o: IPropertyOption) => o.id}\n            onChange={(value: OnChangeValue<IPropertyOption, true | false>, action: ActionMeta<IPropertyOption>): void => {\n                if (action.action === 'select-option' || action.action === 'pop-value') {\n                    if (Array.isArray(value)) {\n                        props.onChange((value as IPropertyOption[]).map((option) => option.id))\n                    } else {\n                        props.onChange((value as IPropertyOption).id)\n                        props.onBlur?.()\n                    }\n                } else if (action.action === 'clear') {\n                    props.onChange('')\n                }\n            }}\n            onKeyDown={(event) => {\n                if (event.key === 'Escape') {\n                    props.onBlur?.()\n                }\n            }}\n            onBlur={props.onBlur}\n            onCreateOption={props.onCreate}\n            autoFocus={true}\n            value={props.value || null}\n            closeMenuOnSelect={!props.isMulti}\n            placeholder={props.emptyValue}\n            hideSelectedOptions={false}\n            defaultMenuIsOpen={true}\n            menuIsOpen={props.isMulti}\n            blurInputOnSelect={!props.isMulti}\n        />\n    )\n}\n\nexport default React.memo(ValueSelector)\n"
  },
  {
    "path": "webapp/src/wsclient.ts",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\n\nimport {ClientConfig} from './config/clientConfig'\n\nimport {CategoryOrder, Utils, WSMessagePayloads} from './utils'\nimport {Block} from './blocks/block'\nimport {Board, BoardMember} from './blocks/board'\nimport {OctoUtils} from './octoUtils'\nimport {BoardCategoryWebsocketData, Category} from './store/sidebar'\n\n// These are outgoing commands to the server\ntype WSCommand = {\n    action: string\n    teamId?: string\n    readToken?: string\n    blockIds?: string[]\n}\n\n// These are messages from the server\nexport type WSMessage = {\n    action?: string\n    block?: Block\n    board?: Board\n    category?: Category\n    blockCategories?: BoardCategoryWebsocketData[]\n    error?: string\n    teamId?: string\n    member?: BoardMember\n    timestamp?: number\n    categoryOrder?: string[]\n}\n\nexport const ACTION_UPDATE_BOARD = 'UPDATE_BOARD'\nexport const ACTION_UPDATE_MEMBER = 'UPDATE_MEMBER'\nexport const ACTION_DELETE_MEMBER = 'DELETE_MEMBER'\nexport const ACTION_UPDATE_BLOCK = 'UPDATE_BLOCK'\nexport const ACTION_AUTH = 'AUTH'\nexport const ACTION_SUBSCRIBE_BLOCKS = 'SUBSCRIBE_BLOCKS'\nexport const ACTION_SUBSCRIBE_TEAM = 'SUBSCRIBE_TEAM'\nexport const ACTION_UNSUBSCRIBE_TEAM = 'UNSUBSCRIBE_TEAM'\nexport const ACTION_UNSUBSCRIBE_BLOCKS = 'UNSUBSCRIBE_BLOCKS'\nexport const ACTION_UPDATE_CLIENT_CONFIG = 'UPDATE_CLIENT_CONFIG'\nexport const ACTION_UPDATE_CATEGORY = 'UPDATE_CATEGORY'\nexport const ACTION_UPDATE_BOARD_CATEGORY = 'UPDATE_BOARD_CATEGORY'\nexport const ACTION_UPDATE_SUBSCRIPTION = 'UPDATE_SUBSCRIPTION'\nexport const ACTION_UPDATE_CARD_LIMIT_TIMESTAMP = 'UPDATE_CARD_LIMIT_TIMESTAMP'\nexport const ACTION_REORDER_CATEGORIES = 'REORDER_CATEGORIES'\n\ntype WSSubscriptionMsg = {\n    action?: string\n    subscription?: Subscription\n    error?: string\n}\n\nexport interface Subscription {\n    blockId: string\n    subscriberId: string\n    blockType: string\n    subscriberType: string\n    notifiedAt?: number\n    createAt?: number\n    deleteAt?: number\n}\n\n// The Mattermost websocket client interface\nexport interface MMWebSocketClient {\n    conn: WebSocket | null\n    sendMessage(action: string, data: any, responseCallback?: () => void): void /* eslint-disable-line @typescript-eslint/no-explicit-any */\n    addFirstConnectListener(callback: () => void): void\n    addReconnectListener(callback: () => void): void\n    addErrorListener(callback: (event: Event) => void): void\n    addCloseListener(callback: (connectFailCount: number) => void): void\n}\n\ntype OnChangeHandler = (client: WSClient, items: any[]) => void\ntype OnReconnectHandler = (client: WSClient) => void\ntype OnStateChangeHandler = (client: WSClient, state: 'init' | 'open' | 'close') => void\ntype OnErrorHandler = (client: WSClient, e: Event) => void\ntype OnConfigChangeHandler = (client: WSClient, clientConfig: ClientConfig) => void\ntype OnCardLimitTimestampChangeHandler = (client: WSClient, timestamp: number) => void\ntype FollowChangeHandler = (client: WSClient, subscription: Subscription) => void\n\nexport type ChangeHandlerType = 'block' | 'category' | 'blockCategories' | 'board' | 'boardMembers' | 'categoryOrder'\n\ntype UpdatedData = {\n    Blocks: Block[]\n    Categories: Category[]\n    BoardCategories: BoardCategoryWebsocketData[]\n    Boards: Board[]\n    BoardMembers: BoardMember[]\n    CategoryOrder: string[]\n}\n\ntype ChangeHandlers = {\n    Block: OnChangeHandler[]\n    Category: OnChangeHandler[]\n    BoardCategory: OnChangeHandler[]\n    Board: OnChangeHandler[]\n    BoardMember: OnChangeHandler[]\n    CategoryReorder: OnChangeHandler[]\n}\n\ntype Subscriptions = {\n    Teams: Record<string, number>\n}\n\nclass WSClient {\n    ws: WebSocket|null = null\n    client: MMWebSocketClient|null = null\n    onPluginReconnect: null|(() => void) = null\n    token = ''\n    pluginId = ''\n    pluginVersion = ''\n    teamId = ''\n    onAppVersionChangeHandler: ((versionHasChanged: boolean) => void) | null = null\n    clientPrefix = ''\n    serverUrl: string | undefined\n    state: 'init'|'open'|'close' = 'init'\n    onStateChange: OnStateChangeHandler[] = []\n    onReconnect: OnReconnectHandler[] = []\n    onChange: ChangeHandlers = {Block: [], Category: [], BoardCategory: [], Board: [], BoardMember: [], CategoryReorder: []}\n    onError: OnErrorHandler[] = []\n    onConfigChange: OnConfigChangeHandler[] = []\n    onCardLimitTimestampChange: OnCardLimitTimestampChangeHandler[] = []\n    onFollowBlock: FollowChangeHandler = () => {}\n    onUnfollowBlock: FollowChangeHandler = () => {}\n    private notificationDelay = 100\n    private reopenDelay = 3000\n    private reopenRetryCount = 0\n    private reopenMaxRetries = 10\n    private updatedData: UpdatedData = {Blocks: [], Categories: [], BoardCategories: [], Boards: [], BoardMembers: [], CategoryOrder: []}\n    private updateTimeout?: NodeJS.Timeout\n    private errorPollId?: NodeJS.Timeout\n    private subscriptions: Subscriptions = {Teams: {}}\n\n    private logged = false\n\n    // this need to be a function rather than a const because\n    // one of the global variable (`window.baseURL`) is set at runtime\n    // after the first instance of OctoClient is created.\n    // Avoiding the race condition becomes more complex than making\n    // the base URL dynamic though a function\n    private getBaseURL(): string {\n        const baseURL = (this.serverUrl || Utils.getBaseURL(true)).replace(/\\/$/, '')\n\n        // Logging this for debugging.\n        // Logging just once to avoid log noise.\n        if (!this.logged) {\n            Utils.log(`WSClient serverUrl: ${baseURL}`)\n            this.logged = true\n        }\n\n        return baseURL\n    }\n\n    constructor(serverUrl?: string) {\n        this.serverUrl = serverUrl\n    }\n\n    initPlugin(pluginId: string, pluginVersion: string, client: MMWebSocketClient): void {\n        this.pluginId = pluginId\n        this.pluginVersion = pluginVersion\n        this.clientPrefix = `custom_${pluginId}_`\n        this.client = client\n        Utils.log(`WSClient initialised for plugin id \"${pluginId}\"`)\n    }\n\n    resetSubscriptions() {\n        this.subscriptions = {Teams: {}} as Subscriptions\n    }\n\n    // this function sends the necessary commands for the connection\n    // to subscribe to all registered subscriptions\n    subscribe() {\n        Utils.log('Sending commands for the registered subscriptions')\n        Object.keys(this.subscriptions.Teams).forEach((teamId) => this.sendSubscribeToTeamCommand(teamId))\n    }\n\n    sendCommand(command: WSCommand): void {\n        try {\n            if (this.client !== null) {\n                const {action, ...data} = command\n                this.client.sendMessage(this.clientPrefix + action, data)\n                return\n            }\n\n            this.ws?.send(JSON.stringify(command))\n        } catch (e) {\n            Utils.logError(`WSClient failed to send command ${command.action}: ${e}`)\n        }\n    }\n\n    sendAuthenticationCommand(token: string): void {\n        const command = {action: ACTION_AUTH, token}\n\n        this.sendCommand(command)\n    }\n\n    sendSubscribeToTeamCommand(teamId: string): void {\n        const command: WSCommand = {\n            action: ACTION_SUBSCRIBE_TEAM,\n            teamId,\n        }\n\n        this.sendCommand(command)\n    }\n\n    sendUnsubscribeToTeamCommand(teamId: string): void {\n        const command: WSCommand = {\n            action: ACTION_UNSUBSCRIBE_TEAM,\n            teamId,\n        }\n\n        this.sendCommand(command)\n    }\n\n    addOnChange(handler: OnChangeHandler, type: ChangeHandlerType): void {\n        switch (type) {\n        case 'block':\n            this.onChange.Block.push(handler)\n            break\n        case 'category':\n            this.onChange.Category.push(handler)\n            break\n        case 'blockCategories':\n            this.onChange.BoardCategory.push(handler)\n            break\n        case 'board':\n            this.onChange.Board.push(handler)\n            break\n        case 'boardMembers':\n            this.onChange.BoardMember.push(handler)\n            break\n        case 'categoryOrder':\n            this.onChange.CategoryReorder.push(handler)\n            break\n        }\n    }\n\n    removeOnChange(needle: OnChangeHandler, type: ChangeHandlerType): void {\n        let haystack = []\n        switch (type) {\n        case 'block':\n            haystack = this.onChange.Block\n            break\n        case 'blockCategories':\n            haystack = this.onChange.BoardCategory\n            break\n        case 'board':\n            haystack = this.onChange.Board\n            break\n        case 'boardMembers':\n            haystack = this.onChange.BoardMember\n            break\n        case 'category':\n            haystack = this.onChange.Category\n            break\n        case 'categoryOrder':\n            haystack = this.onChange.CategoryReorder\n            break\n        }\n\n        if (!haystack) {\n            return\n        }\n\n        const index = haystack.indexOf(needle)\n        if (index !== -1) {\n            haystack.splice(index, 1)\n        }\n    }\n\n    addOnReconnect(handler: OnReconnectHandler): void {\n        this.onReconnect.push(handler)\n    }\n\n    removeOnReconnect(handler: OnReconnectHandler): void {\n        const index = this.onReconnect.indexOf(handler)\n        if (index !== -1) {\n            this.onReconnect.splice(index, 1)\n        }\n    }\n\n    addOnStateChange(handler: OnStateChangeHandler): void {\n        this.onStateChange.push(handler)\n    }\n\n    removeOnStateChange(handler: OnStateChangeHandler): void {\n        const index = this.onStateChange.indexOf(handler)\n        if (index !== -1) {\n            this.onStateChange.splice(index, 1)\n        }\n    }\n\n    addOnError(handler: OnErrorHandler): void {\n        this.onError.push(handler)\n    }\n\n    removeOnError(handler: OnErrorHandler): void {\n        const index = this.onError.indexOf(handler)\n        if (index !== -1) {\n            this.onError.splice(index, 1)\n        }\n    }\n\n    addOnConfigChange(handler: OnConfigChangeHandler): void {\n        this.onConfigChange.push(handler)\n    }\n\n    removeOnConfigChange(handler: OnConfigChangeHandler): void {\n        const index = this.onConfigChange.indexOf(handler)\n        if (index !== -1) {\n            this.onConfigChange.splice(index, 1)\n        }\n    }\n\n    addOnCardLimitTimestampChange(handler: OnCardLimitTimestampChangeHandler): void {\n        this.onCardLimitTimestampChange.push(handler)\n    }\n\n    removeOnCardLimitTimestampChange(handler: OnCardLimitTimestampChangeHandler): void {\n        const index = this.onCardLimitTimestampChange.indexOf(handler)\n        if (index !== -1) {\n            this.onCardLimitTimestampChange.splice(index, 1)\n        }\n    }\n\n    open(): void {\n        if (this.client !== null) {\n            // configure the Mattermost websocket client callbacks\n            const onConnect = () => {\n                Utils.log('WSClient in plugin mode, reusing Mattermost WS connection')\n\n                // if there are any subscriptions set by the\n                // components, send their subscribe messages\n                this.subscribe()\n\n                for (const handler of this.onStateChange) {\n                    handler(this, 'open')\n                }\n                this.state = 'open'\n            }\n\n            const onReconnect = () => {\n                Utils.logWarn('WSClient reconnected')\n\n                onConnect()\n                for (const handler of this.onReconnect) {\n                    handler(this)\n                }\n            }\n            this.onPluginReconnect = onReconnect\n\n            const onClose = (connectFailCount: number) => {\n                Utils.logError(`WSClient has been closed, connect fail count: ${connectFailCount}`)\n\n                for (const handler of this.onStateChange) {\n                    handler(this, 'close')\n                }\n                this.state = 'close'\n\n                // there is no way to react to a reconnection with the\n                // reliable websockets schema, so we poll the raw\n                // websockets client for its state directly until it\n                // reconnects\n                if (!this.errorPollId) {\n                    this.errorPollId = setInterval(() => {\n                        Utils.logWarn(`Polling websockets connection for state: ${this.client?.conn?.readyState}`)\n                        if (this.client?.conn?.readyState === 1) {\n                            onReconnect()\n                            clearInterval(this.errorPollId!)\n                            this.errorPollId = undefined\n                        }\n                    }, 500)\n                }\n            }\n\n            const onError = (event: Event) => {\n                Utils.logError(`WSClient websocket onerror. data: ${JSON.stringify(event)}`)\n\n                for (const handler of this.onError) {\n                    handler(this, event)\n                }\n            }\n\n            this.client.addFirstConnectListener(onConnect)\n            this.client.addErrorListener(onError)\n            this.client.addCloseListener(onClose)\n            this.client.addReconnectListener(onReconnect)\n\n            return\n        }\n\n        const url = new URL(this.getBaseURL())\n        const protocol = (url.protocol === 'https:') ? 'wss:' : 'ws:'\n        const wsServerUrl = `${protocol}//${url.host}${url.pathname.replace(/\\/$/, '')}/ws`\n        Utils.log(`WSClient open: ${wsServerUrl}`)\n        const ws = new WebSocket(wsServerUrl)\n        this.ws = ws\n\n        ws.onopen = () => {\n            Utils.log('WSClient webSocket opened.')\n            this.state = 'open'\n            this.reopenRetryCount = 0\n\n            // if has a token defined when connecting, authenticate\n            if (this.token) {\n                this.sendAuthenticationCommand(this.token)\n            }\n\n            // if there are any subscriptions set by the components,\n            // send their subscribe messages\n            this.subscribe()\n\n            for (const handler of this.onStateChange) {\n                handler(this, 'open')\n            }\n        }\n\n        ws.onerror = (e) => {\n            Utils.logError(`WSClient websocket onerror. data: ${e}`)\n            for (const handler of this.onError) {\n                handler(this, e)\n            }\n        }\n\n        ws.onclose = (e) => {\n            Utils.log(`WSClient websocket onclose, code: ${e.code}, reason: ${e.reason}`)\n            if (ws === this.ws) {\n                // Unexpected close, re-open\n                Utils.logError('Unexpected WSClient close')\n                for (const handler of this.onStateChange) {\n                    handler(this, 'close')\n                }\n                this.state = 'close'\n\n                if (this.reopenRetryCount < this.reopenMaxRetries) {\n                    setTimeout(() => {\n                        this.reopenRetryCount++\n                        Utils.log(`Reopening websocket connection, count: ${this.reopenRetryCount}`)\n\n                        this.open()\n                        for (const handler of this.onReconnect) {\n                            handler(this)\n                        }\n                    }, this.reopenDelay)\n                } else {\n                    Utils.logError('Reached max websocket re-opening attempts')\n                }\n            }\n        }\n\n        ws.onmessage = (e) => {\n            if (ws !== this.ws) {\n                Utils.log('Ignoring closed ws')\n                return\n            }\n\n            try {\n                const message = JSON.parse(e.data) as WSMessage\n                if (message.error) {\n                    Utils.logError(`Listener websocket error: ${message.error}`)\n                    return\n                }\n\n                switch (message.action) {\n                case ACTION_UPDATE_BOARD:\n                    this.updateHandler(message)\n                    break\n                case ACTION_UPDATE_MEMBER:\n                    this.updateHandler(message)\n                    break\n                case ACTION_DELETE_MEMBER:\n                    this.updateHandler(message)\n                    break\n                case ACTION_UPDATE_BLOCK:\n                    this.updateHandler(message)\n                    break\n                case ACTION_UPDATE_CATEGORY:\n                    this.updateHandler(message)\n                    break\n                case ACTION_UPDATE_BOARD_CATEGORY:\n                    this.updateHandler(message)\n                    break\n                case ACTION_UPDATE_SUBSCRIPTION:\n                    this.updateSubscriptionHandler(message)\n                    break\n                case ACTION_REORDER_CATEGORIES:\n                    this.updateHandler(message)\n                    break\n                default:\n                    Utils.logError(`Unexpected action: ${message.action}`)\n                }\n            } catch (err) {\n                Utils.log('message is not an object')\n            }\n        }\n    }\n\n    hasConn(): boolean {\n        return this.ws?.readyState === 1 || this.client !== null\n    }\n\n    updateHandler(message: WSMessage): void {\n        // if messages are directed to a team, process only the ones\n        // for the current team\n        if (message.teamId && message.teamId !== this.teamId) {\n            return\n        }\n\n        const [data, type] = Utils.fixWSData(message)\n        if (data) {\n            this.queueUpdateNotification(data, type)\n        }\n    }\n\n    setOnFollowBlock(handler: FollowChangeHandler): void {\n        this.onFollowBlock = handler\n    }\n\n    setOnUnfollowBlock(handler: FollowChangeHandler): void {\n        this.onUnfollowBlock = handler\n    }\n\n    updateClientConfigHandler(config: ClientConfig): void {\n        for (const handler of this.onConfigChange) {\n            handler(this, config)\n        }\n    }\n\n    updateCardLimitTimestampHandler(action: {action: string, timestamp: number}): void {\n        for (const handler of this.onCardLimitTimestampChange) {\n            handler(this, action.timestamp)\n        }\n    }\n\n    updateSubscriptionHandler(message: WSSubscriptionMsg): void {\n        Utils.log('updateSubscriptionHandler: ' + message.action + '; blockId=' + message.subscription?.blockId)\n\n        if (!message.subscription) {\n            return\n        }\n\n        const handler = message.subscription.deleteAt ? this.onUnfollowBlock : this.onFollowBlock\n        handler(this, message.subscription)\n    }\n\n    setOnAppVersionChangeHandler(fn: (versionHasChanged: boolean) => void): void {\n        this.onAppVersionChangeHandler = fn\n    }\n\n    pluginStatusesChangedHandler(data: any): void {\n        if (this.pluginId === '' || !this.onAppVersionChangeHandler) {\n            return\n        }\n\n        const focalboardStatusChange = data.plugin_statuses.find((s: any) => s.plugin_id === this.pluginId)\n        if (focalboardStatusChange) {\n            // if the plugin version is greater than the current one,\n            // show the new version banner\n            if (Utils.compareVersions(this.pluginVersion, focalboardStatusChange.version) > 0) {\n                Utils.log('Boards plugin has been updated')\n                this.onAppVersionChangeHandler(true)\n            }\n\n            // if the plugin version is greater or equal, trigger a\n            // reconnect to resubscribe in case the interface hasn't\n            // been reloaded\n            if (Utils.compareVersions(this.pluginVersion, focalboardStatusChange.version) >= 0) {\n                // this is a temporal solution that leaves a second\n                // between the message and the reconnect so the server\n                // has time to register the WS handler\n                setTimeout(() => {\n                    if (this.onPluginReconnect) {\n                        Utils.log('Reconnecting after plugin update')\n                        this.onPluginReconnect()\n                    }\n                }, 1000)\n            }\n        }\n    }\n\n    authenticate(token: string): void {\n        if (!token) {\n            Utils.assertFailure('WSClient trying to authenticate without a token')\n            return\n        }\n\n        if (this.hasConn()) {\n            this.sendAuthenticationCommand(token)\n        }\n\n        this.token = token\n    }\n\n    subscribeToTeam(teamId: string): void {\n        if (!this.subscriptions.Teams[teamId]) {\n            Utils.log(`First component subscribing to team ${teamId}`)\n\n            // only send command if the WS connection has already been\n            // stablished. If not, the connect or reconnect functions\n            // will do\n            if (this.hasConn()) {\n                this.sendSubscribeToTeamCommand(teamId)\n            }\n\n            this.teamId = teamId\n            this.subscriptions.Teams[teamId] = 1\n            return\n        }\n\n        this.subscriptions.Teams[teamId] += 1\n    }\n\n    unsubscribeToTeam(teamId: string): void {\n        if (!this.subscriptions.Teams[teamId]) {\n            Utils.logError('Component trying to unsubscribe to a team when no subscriptions are registered. Doing nothing')\n            return\n        }\n\n        this.subscriptions.Teams[teamId] -= 1\n        if (this.subscriptions.Teams[teamId] === 0) {\n            Utils.log(`Last subscription to team ${teamId} being removed`)\n            if (this.hasConn()) {\n                this.sendUnsubscribeToTeamCommand(teamId)\n            }\n\n            if (teamId === this.teamId) {\n                this.teamId = ''\n            }\n            delete this.subscriptions.Teams[teamId]\n        }\n    }\n\n    subscribeToBlocks(teamId: string, blockIds: string[], readToken = ''): void {\n        if (!this.hasConn()) {\n            Utils.assertFailure('WSClient.subscribeToBlocks: ws is not open')\n            return\n        }\n\n        const command: WSCommand = {\n            action: ACTION_SUBSCRIBE_BLOCKS,\n            blockIds,\n            teamId,\n            readToken,\n        }\n\n        this.sendCommand(command)\n    }\n\n    unsubscribeFromBlocks(teamId: string, blockIds: string[], readToken = ''): void {\n        if (!this.hasConn()) {\n            Utils.assertFailure('WSClient.removeBlocks: ws is not open')\n            return\n        }\n\n        const command: WSCommand = {\n            action: ACTION_UNSUBSCRIBE_BLOCKS,\n            blockIds,\n            teamId,\n            readToken,\n        }\n\n        this.sendCommand(command)\n    }\n\n    private queueUpdateNotification(data: WSMessagePayloads, type: ChangeHandlerType) {\n        if (!data) {\n            return\n        }\n\n        // Remove existing queued update\n        if (type === 'block') {\n            this.updatedData.Blocks = this.updatedData.Blocks.filter((o) => o.id !== (data as Block).id)\n            this.updatedData.Blocks.push(OctoUtils.hydrateBlock(data as Block))\n        } else if (type === 'category') {\n            this.updatedData.Categories = this.updatedData.Categories.filter((c) => c.id !== (data as Category).id)\n            this.updatedData.Categories.push(data as Category)\n        } else if (type === 'blockCategories') {\n            this.updatedData.BoardCategories = this.updatedData.BoardCategories.filter((b) => !(data as BoardCategoryWebsocketData[]).find((boardCategory) => boardCategory.boardID === b.boardID))\n            this.updatedData.BoardCategories.push(...(data as BoardCategoryWebsocketData[]))\n        } else if (type === 'board') {\n            this.updatedData.Boards = this.updatedData.Boards.filter((b) => b.id !== (data as Board).id)\n            this.updatedData.Boards.push(data as Board)\n        } else if (type === 'boardMembers') {\n            this.updatedData.BoardMembers = this.updatedData.BoardMembers.filter((m) => m.userId !== (data as BoardMember).userId || m.boardId !== (data as BoardMember).boardId)\n            this.updatedData.BoardMembers.push(data as BoardMember)\n        } else if (type === 'categoryOrder') {\n            // Since each update contains the whole state of all\n            // categories, we don't need to keep accumulating all updates.\n            // Only the very latest one is sufficient to describe the\n            // latest state of all sidebar categories.\n            this.updatedData.CategoryOrder = (data as CategoryOrder)\n        }\n\n        if (this.updateTimeout) {\n            clearTimeout(this.updateTimeout)\n            this.updateTimeout = undefined\n        }\n\n        this.updateTimeout = setTimeout(() => {\n            this.flushUpdateNotifications()\n        }, this.notificationDelay)\n    }\n\n    private logUpdateNotification() {\n        for (const block of this.updatedData.Blocks) {\n            Utils.log(`WSClient flush update block: ${block.id}`)\n        }\n\n        for (const category of this.updatedData.Categories) {\n            Utils.log(`WSClient flush update category: ${category.id}`)\n        }\n\n        for (const blockCategories of this.updatedData.BoardCategories) {\n            Utils.log(`WSClient flush update blockCategory: ${blockCategories.boardID} ${blockCategories.categoryID}`)\n        }\n\n        for (const board of this.updatedData.Boards) {\n            Utils.log(`WSClient flush update board: ${board.id}`)\n        }\n\n        for (const boardMember of this.updatedData.BoardMembers) {\n            Utils.log(`WSClient flush update boardMember: ${boardMember.userId} ${boardMember.boardId}`)\n        }\n\n        Utils.log(`WSClient flush update categoryOrder: ${this.updatedData.CategoryOrder}`)\n    }\n\n    private flushUpdateNotifications() {\n        this.logUpdateNotification()\n\n        for (const handler of this.onChange.Block) {\n            handler(this, this.updatedData.Blocks)\n        }\n\n        for (const handler of this.onChange.Category) {\n            handler(this, this.updatedData.Categories)\n        }\n\n        for (const handler of this.onChange.BoardCategory) {\n            handler(this, this.updatedData.BoardCategories)\n        }\n\n        for (const handler of this.onChange.Board) {\n            handler(this, this.updatedData.Boards)\n        }\n\n        for (const handler of this.onChange.BoardMember) {\n            handler(this, this.updatedData.BoardMembers)\n        }\n\n        for (const handler of this.onChange.CategoryReorder) {\n            handler(this, this.updatedData.CategoryOrder)\n        }\n\n        this.updatedData = {\n            Blocks: [],\n            Categories: [],\n            BoardCategories: [],\n            Boards: [],\n            BoardMembers: [],\n            CategoryOrder: [],\n        }\n    }\n\n    close(): void {\n        if (!this.hasConn()) {\n            return\n        }\n\n        Utils.log(`WSClient close: ${this.ws?.url}`)\n\n        // Use this sequence so the onclose method doesn't try to re-open\n        const ws = this.ws\n        this.ws = null\n        this.onChange = {Block: [], Category: [], BoardCategory: [], Board: [], BoardMember: [], CategoryReorder: []}\n        this.onReconnect = []\n        this.onStateChange = []\n        this.onError = []\n\n        // if running in plugin mode, nothing else needs to be done\n        if (this.client) {\n            return\n        }\n\n        try {\n            ws?.close()\n        } catch {\n            try {\n                (ws as any)?.websocket?.close()\n            } catch {\n                Utils.log('WSClient unable to close the websocket')\n            }\n        }\n    }\n}\n\nconst wsClient = new WSClient()\n\nexport {WSClient}\nexport default wsClient\n"
  },
  {
    "path": "webapp/tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n        \"jsx\": \"react\",\n        \"target\": \"es2019\",\n        \"module\": \"commonjs\",\n        \"esModuleInterop\": true,\n        \"noImplicitAny\": true,\n        \"strict\": true,\n        \"strictNullChecks\": true,\n        \"forceConsistentCasingInFileNames\": true,\n        \"sourceMap\": true,\n        \"allowJs\": true,\n        \"resolveJsonModule\": true,\n        \"incremental\": false,\n        \"baseUrl\": \"src\",\n        \"outDir\": \"./dist\",\n        \"moduleResolution\": \"node\"\n    },\n    \"include\": [\n        \".\"\n    ],\n    \"exclude\": [\n        \".git\",\n        \"**/node_modules/*\",\n        \"dist\",\n        \"pack\"\n    ]\n}\n"
  },
  {
    "path": "webapp/tslint.json",
    "content": "{\n\t\"extends\": \"tslint:recommended\",\n\t\"rules\": {\n\t\t\"new-parens\": true,\n\t\t\"no-arg\": true,\n\t\t\"no-bitwise\": true,\n\t\t\"no-conditional-assignment\": true,\n\t\t\"no-consecutive-blank-lines\": true,\n\t\t\"indent\": [false, \"tabs\"],\n\t\t\"member-access\": [true, \"no-public\"],\n\t\t\"semicolon\": [true, \"never\"],\n\t\t\"variable-name\": [\n\t\t\ttrue,\n\t\t\t\"ban-keywords\",\n\t\t\t\"check-format\",\n\t\t\t\"allow-pascal-case\",\n\t\t\t\"allow-leading-underscore\"\n\t\t],\n\t\t\"max-line-length\": [false, { \"limit\": 150, \"ignore-pattern\": \"\\\";?$\" }],\n\t\t\"trailing-comma\": false,\n\t\t\"object-literal-sort-keys\": false,\n\t\t\"member-ordering\": false,\n\t\t\"interface-name\": false,\n\t\t\"arrow-parens\": false,\n\t\t\"no-console\": false,\n\t\t\"align\": false\n\t},\n\t\"jsRules\": {\n\t\t\"max-line-length\": {\n\t\t\t\"options\": [150]\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "webapp/webpack.common.js",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nconst path = require('path');\n\nconst tsTransformer = require('@formatjs/ts-transformer');\nconst CopyPlugin = require('copy-webpack-plugin');\nvar HtmlWebpackPlugin = require('html-webpack-plugin');\n\nconst outpath = path.resolve(__dirname, 'pack');\n\nfunction makeCommonConfig() {\n    const commonConfig = {\n        target: 'web',\n        mode: 'development',\n        node: {\n            __dirname: false,\n            __filename: false,\n        },\n        module: {\n            rules: [\n                {\n                    test: /\\.m?js/,\n                    resolve: {\n                        fullySpecified: false,\n                    },\n                },\n                {\n                    test: /\\.tsx?$/,\n                    use: {\n                        loader: 'ts-loader',\n                        options: {\n                            getCustomTransformers: {\n                                before: [\n                                    tsTransformer.transform({\n                                        overrideIdFn: '[sha512:contenthash:base64:6]',\n                                        ast: true,\n                                    }),\n                                ],\n                            },\n                        },\n                    },\n                    exclude: [/node_modules/],\n\n                },\n                {\n                    test: /\\.html$/,\n                    type: 'asset/resource',\n                },\n                {\n                    test: /\\.s[ac]ss$/i,\n                    use: [\n                        'style-loader',\n                        'css-loader',\n                        'sass-loader',\n                    ],\n                },\n                {\n                    test: /\\.css$/i,\n                    use: [\n                        'style-loader',\n                        'css-loader',\n                    ],\n                },\n                {\n                    test: /\\.(tsx?|js|jsx|mjs|html)$/,\n                    use: [\n                    ],\n                    exclude: [/node_modules/],\n                },\n                {\n                    test: /\\.(eot|tiff|svg|woff2|woff|ttf|png|jpg|jpeg|gif)$/,\n                    type: 'asset/resource',\n                    generator: {\n                        filename: 'static/[name][ext]',\n                    },\n                },\n            ],\n        },\n        resolve: {\n            modules: [\n                'node_modules',\n                path.resolve(__dirname),\n            ],\n            fullySpecified: false,\n            extensions: ['.js', '.jsx', '.ts', '.tsx'],\n        },\n        plugins: [\n            new CopyPlugin({\n                patterns: [\n                    {from: path.resolve(__dirname, 'static'), to: 'static'},\n                ],\n            }),\n            new HtmlWebpackPlugin({\n                inject: true,\n                title: 'Focalboard',\n                chunks: ['main'],\n                template: 'html-templates/page.ejs',\n                filename: 'index.html',\n                publicPath: '{{.BaseURL}}/',\n                hash: true,\n            }),\n        ],\n        entry: ['./src/main.tsx', './src/userSettings.ts'],\n        output: {\n            library: 'Focalboard',\n            filename: 'static/[name].js',\n            path: outpath,\n        },\n    };\n\n    return commonConfig;\n}\n\nmodule.exports = makeCommonConfig;\n"
  },
  {
    "path": "webapp/webpack.dev.js",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nconst merge = require('webpack-merge');\n\nconst makeCommonConfig = require('./webpack.common.js');\n\nconst commonConfig = makeCommonConfig();\n\nconst config = merge.merge(commonConfig, {\n    mode: 'development',\n    devtool: 'inline-source-map',\n    optimization: {\n        minimize: false,\n    },\n});\n\nmodule.exports = [\n    merge.merge(config, {\n        devtool: 'source-map',\n        output: {\n            devtoolNamespace: 'focalboard',\n        },\n    }),\n];\n"
  },
  {
    "path": "webapp/webpack.editor.js",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nconst merge = require('webpack-merge');\nconst path = require('path');\nconst CopyPlugin = require('copy-webpack-plugin');\nconst HtmlWebpackPlugin = require('html-webpack-plugin');\n\nconst makeCommonConfig = require('./webpack.common.js');\n\nconst commonConfig = makeCommonConfig();\n\nconst config = merge.merge(commonConfig, {\n    mode: 'development',\n    devtool: 'inline-source-map',\n    optimization: {\n        minimize: false,\n    },\n    devServer: {\n        port: 9000,\n        open: \"/editor.html\",\n    },\n    entry: ['./src/components/blocksEditor/devmain.tsx'],\n    plugins: [\n        new CopyPlugin({\n            patterns: [\n                {from: path.resolve(__dirname, 'static'), to: 'static'},\n            ],\n        }),\n        new HtmlWebpackPlugin({\n            inject: true,\n            title: 'Focalboard',\n            chunks: ['main'],\n            template: 'html-templates/deveditor.ejs',\n            filename: 'editor.html',\n            publicPath: '/',\n            hash: true,\n        }),\n    ],\n});\n\nmodule.exports = [\n    merge.merge(config, {\n        devtool: 'source-map',\n        output: {\n            devtoolNamespace: 'focalboard',\n        },\n    }),\n];\n"
  },
  {
    "path": "webapp/webpack.prod.js",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nconst merge = require('webpack-merge');\nconst TerserPlugin = require('terser-webpack-plugin');\n\nconst makeCommonConfig = require('./webpack.common.js');\n\nconst commonConfig = makeCommonConfig();\n\nconst config = merge.merge(commonConfig, {\n    mode: 'production',\n    optimization: {\n        minimize: true,\n        minimizer: [new TerserPlugin({extractComments: false})],\n    },\n});\n\nmodule.exports = [\n    merge.merge(config, {\n    }),\n];\n"
  },
  {
    "path": "website/.editorconfig",
    "content": "# http://editorconfig.org/\n\nroot = true\n\n[*]\nend_of_line = lf\ninsert_final_newline = true\ntrim_trailing_whitespace = true\ncharset = utf-8\n\n[*.{md,css,html}]\nindent_style = space\nindent_size = 4\n\n[*.toml]\nindent_style = space\nindent_size = 2\n\n[*.go]\nindent_style = tab\n\n[Makefile,*.mk]\nindent_style = tab\n\n[*.md]\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "website/.gitignore",
    "content": "# build artifacts\ndist\n\n# os artifacts\n*.swp\n.DS_Store\n\n# IDE artifacts\n.idea/\n"
  },
  {
    "path": "website/Makefile",
    "content": "\nBASE_URL?=https://www.focalboard.com\n\n.PHONY: dist\ndist:\n\trm -rf ./dist\n\thugo -s site --destination ../dist/html -b$(BASE_URL)\n\n.PHONY: run\nrun:\n\thugo server --buildDrafts --disableFastRender -F -s site\n"
  },
  {
    "path": "website/README.md",
    "content": "# Focalboard website\n\nThis folder is used to manage the website at [focalboard.com](https://www.focalboard.com/). It was built using [Hugo](https://gohugo.io/).\n\n- The main page for https://focalboard.com can be found at [/site/layouts/partials/hero.htm](site/layouts/partials).\n- Other content can be found in the [/site/content](site/content) folder.\n\n## How to contribute\n\nThe documentation for Focalboard (known as Boards in Mattermost) has moved to the [Mattermost Product Documentation](https://docs.mattermost.com/guides/boards.html) site.\n\nWe welcome pull requests for typos, minor corrections, content additions, troubleshooting tips, install guides, and any other incremental improvements. If you have any questions about the contribution process, take a look at the [README](https://github.com/mattermost/docs/blob/master/README.md) file or join the [Documentation Working Group channel](https://community.mattermost.com/core/channels/dwg-documentation-working-group) on our Community server.\n\nFor larger changes, such as adding an install guide for a different platform, please create a [feature request](https://github.com/mattermost/focalboard/issues/new?assignees=&labels=enhancement&template=enhancement.md&title=Feature+Request%3A+) ticket to discuss.\n\n## How to build locally\n\nIf you're interested in building the website locally from this repo using Hugo, please use the following steps:\n\n1. Follow [Hugo documentation](https://gohugo.io/getting-started/installing/) to install Hugo\n\n```bash\n# Eg. for Mac OS X\nbrew install hugo\n```\n\n\n2. Start the development server\n\n```bash\nmake run\n```\n\n3. Go to http://localhost:1313 to see the running server\n\n## Production Notes\n\n### For Assets (stylesheets, scripts, images)\n\nIf there are changes, please make sure that you change files names or update the version in the asset URL’s query string to help override the browser cache.\nThe main stylesheet (styles.css) can change quite frequently, so to update the version across all pages, you can go into `layouts/partials/head.html` and adjust the `version` query param."
  },
  {
    "path": "website/site/archetypes/default.md",
    "content": "---\ntitle: \"{{ replace .TranslationBaseName \"-\" \" \" | title }}\"\ndate: {{ .Date }}\ndraft: true\n---\n"
  },
  {
    "path": "website/site/config.toml",
    "content": "# Page settings\nbaseURL = \"https://www.focalboard.com/\"\ncanonifyURLs = true\n#relativeURLs = true\n\ntitle = \"Focalboard: Open source alternative to Trello, Asana, and Notion\"\nlanguageCode = \"en-us\"\npublishDir = \"../docs\"\npygmentsCodeFences = true\npygmentsStyle = \"manni\"\n\n[taxonomies]\n  category = \"categories\"\n\n[params]\n  # Meta\n  author = \"\"\n  description = \"\"\n  email = \"\"\n  ghrepo = \"https://github.com/mattermost/focalboard/\"\n\n  [params.mailinglist]\n    enable = false\n\n  [params.notification]\n    enable = false\n    url = \"https://mattermost.com/careers\"\n    text = \"We're hiring!\"\n\n  [params.search]\n    enable = false\n\n  [[params.sidebar.item]]\n    name = \"docs\"\n    displayName = \"Docs\"\n    draft = false\n\n  [[params.sidebar.item]]\n    name = \"guide\"\n    displayName = \"User's Guide\"\n    draft = false\n\n  [params.sidebar]\n  [[params.sidebar.item]]\n    name = \"contribute\"\n    displayName = \"Contribute\"\n    draft = false\n\n  # Navigation\n  [params.navigation]\n    brand = \"Focalboard\"\n    home = \"Home\"\n\n    # You can add custom links before or after the default links\n    # Assign a weight to define the order\n\n    [[menu.postpend]]\n      url = \"https://developers.mattermost.com/contribute/focalboard/\"\n      name = \"Contribute\"\n      weight = 1\n\n    [[menu.postpend]]\n      url = \"https://github.com/mattermost/focalboard\"\n      name = \"GitHub\"\n      weight = 2\n\n    [[menu.postpend]]\n      url = \"https://github.com/mattermost/focalboard/#readme\"\n      name = \"Help Docs\"\n      weight = 3\n\n    [[menu.postpend]]\n      url = \"/feedback\"\n      name = \"Feedback\"\n      weight = 4\n\n    # postpended links\n    [[menu.postpend]]\n      url = \"/download\"\n      name = \"Download\"\n      weight = 6\n\n    # Workaround to add draft status to menu items\n    [[params.navigation.drafts]]\n      Download = false\n      \"User's Guide\" = false\n      Contribute = false\n      Integrate = false\n      Extend = false\n      Blog = false\n      Feedback = false\n      Internal = false\n      'Admin Docs' = false\n\n  # Hero section\n  [params.hero]\n    title = \"Get Focalboard\"\n    subtitle = ''\n\n  # Intro section\n  # Available icons: http://simplelineicons.com/\n  [params.intro]\n  [[params.intro.item]]\n    title = \"Download\"\n    description = \"Download Focalboard here.\"\n    url = \"/docs/personal-edition\"\n    button = \"Download Now\"\n    icon = \"/img/download-icon.svg\"\n    draft = false\n\n  [[params.intro.item]]\n    title = \"Read Guide\"\n    description = \"Read the User's Guide to get the most out of Focalboard.\"\n    url = \"guide/user\"\n    button = \"User's Guide\"\n    icon = \"/img/use-icon.svg\"\n    draft = false\n\n  [[params.intro.item]]\n    title = \"Contribute\"\n    description = \"Help build the future of productivity and submit code directly to the Focalboard open-source project.\"\n    url = \"https://developers.mattermost.com/contribute/focalboard/\"\n    button = \"Start Contributing\"\n    icon = \"/img/contribute-icon.svg\"\n    draft = false\n\n  # Footer section\n  [params.footer]\n    enable = true\n  \ttwitter = 'https://twitter.com/Focalboard'\n  \tfacebook = 'https://www.facebook.com/Mattermost-2300985916642531/'\n  \tyoutube = 'https://www.youtube.com/channel/UCNR05H72hi692y01bWaFXNA'\n    copyright = '&copy; Mattermost, Inc. All Rights Reserved.'\n\n# Allows html in Hugo >= v0.60.0.  See Github issue #506.\n[markup]\n  [markup.goldmark]\n    [markup.goldmark.renderer]\n      unsafe = true\n"
  },
  {
    "path": "website/site/content/blog/2021-1-7-hello.md",
    "content": "---\ntitle: Hello World\nslug: hello\ndate: 2021-01-07\ncategories:\n    - \"general\"\nauthor: Chen Lim\ngithub: chenilim\ncommunity: chen-i.lim\n---\n\n> \"I long to accomplish a great and noble task, but it is my chief duty to accomplish small tasks as if they were great and noble.\"\n> -- Helen Keller\n"
  },
  {
    "path": "website/site/content/blog/2021-4-21-Focalboard v0.6.5 release.md",
    "content": "---\ntitle: \"Focalboard v0.6.5: Gallery View, Docker install,10 translations and more\"\nslug: focalboard-v0.6.5-release\ndate: 2021-04-21\ncategories:\n    - \"releases\"\nauthor: Chen Lim\ngithub: chenilim\ncommunity: chen-i.lim\n---\n\n<img src=\"https://user-images.githubusercontent.com/46905241/115785976-0d071080-a375-11eb-8ee1-59a1e2975686.jpg\" />\n\nThis is our first-ever release announcement for Focalboard, an open source alternative to Trello, Notion, and Asana.\n\nIt's been an incredible month. Not long ago we were at 40 GitHub Stars, and after trending on Hacker News, Reddit and Product Hunt, our open source project is at over 3,200 stars and growing.\n\nFocalboard 0.6.5 is the best release we've ever shipped and it's made possible by so many new contributors to the project.\n\nWe can't thank everyone enough for their contributions, each of which makes a last difference in the history of the Focalboard open source project.\n\nWe’re so excited to share some highlights:\n\n## Focalboard is now available in DockerHub with over 10,000 Pulls!!!\n\n<img src=\"https://user-images.githubusercontent.com/46905241/115785989-12645b00-a375-11eb-927d-fe4285dc8b32.png\" style=\"max-height: 300px;\" />\n\nCheck out Focalboard on Docker Hub at: https://hub.docker.com/r/mattermost/focalboard.\n\nUse Docker? Install Focalboard with one line: `docker pull mattermost/focalboard`\n\nHuge thanks to @jwilander and @obbardc! Plus, thanks to @proffalken for adding a Dockerfile to run service in a container (#76) and to @jbutler992 for adding docker-compose to run the whole service in containers (#105).\n\n## Focalboard is now available in 10 languages!\n\n<img src=\"https://user-images.githubusercontent.com/46905241/115786018-1a23ff80-a375-11eb-927f-d12988f5ad41.png\" style=\"max-height: 300px;\" />\n\nLast month, we had only English and Spanish. This month we’ve added eight new languages: German (Thanks @svelle!); Japanese (Thanks @kaakaa!); French (Thanks @CyrilLD!), Occitan (Thanks Quentin PAGÈS!); Dutch (Thanks Tom De Moor!); Turkish (Thanks Abdullah Musab!); Simplified Chinese (Thanks Yao Xie and toto6038!); and Russian (Thanks Edward Smirnov!).\n\nThanks to everyone for making Focalboard an inclusive, international community from the very start of the project.\n\n## Translations contributions can now be made at translate.mattermost.com\n\n<img src=\"https://user-images.githubusercontent.com/46905241/115786040-227c3a80-a375-11eb-8299-1e337e9100a8.png\" style=\"max-height: 300px;\" />\n\nThanks to @jespino, anyone can now go to https://translate.mattermost.com/ to contribute language translations to the project!\n\n## New “Gallery View”\n\n<img src=\"https://user-images.githubusercontent.com/46905241/115786031-1f814a00-a375-11eb-8178-c62c05f928db.png\" style=\"max-height: 300px;\" />\n\nFocalboard 0.6.5 gives you more flexibility in organizing tickets with a new “Gallery View” which displays tickets with image elements in a neat rectangularly formatted grid.\n\nJust like existing Kanban Board and Table Views, you can customize your new Gallery Views to filter, sort, and display different properties, plus search through the content.\n\n## New Checkbox Content Type\n\n<img src=\"https://user-images.githubusercontent.com/46905241/115786054-28721b80-a375-11eb-8720-fea7e7c0dcf1.png\" style=\"max-height: 300px;\" />\n\nFocalboard 0.6.5 makes tickets even more versatile by adding a new “checkbox” property type option.\n\nJust like text, number, email, URL, and selection list types, you can add the new checkbox property to tickets within a grid, as well as in templates.\n\n## Shortcuts and Infrastructure\n\nFocalboard 0.6.5 also includes a new keyboard shortcut for search (CTRL+SHIFT+F) and duplicating selected cards (CTRL+D).\n\nAlso many thanks to @renjithgr for contributing more frontend unit test coverage (#126) and adding an email property type (#84).\n\n## Want to try the latest release?\n\nHere’s now how to start with different editions:\n\n**Personal Desktop Edition** - A standalone desktop app for your todos and personal projects\n* **Mac App Store**: Download or upgrade from the [Mac App Store](https://apps.apple.com/app/apple-store/id1556908618?pt=2114704&ct=website&mt=8)\n* **Microsoft Store**: Download from the [Microsoft Store](https://www.microsoft.com/store/apps/9NLN2T0SX9VF?cid=website)\n* **Windows Install**: Download `focalboard-win.zip` from the [release](https://github.com/mattermost/focalboard/releases)\n* **Linux Desktop Install**: Download `focalboard-linux.tar.gz` from the [release](https://github.com/mattermost/focalboard/releases)\n\n**Personal Server Edition** - A self-hosted server for your team to collaborate\n* **Linux Server Install**: See the [setup](../../download/personal-edition/ubuntu/) or [upgrade guide](../../download/personal-edition/ubuntu-upgrade/).\n* **Docker Install**: See the [docker install guide](../../download/personal-edition/docker/)\n\nSee the [download page for more details](../../download/personal-edition/).\n\n## Want to contribute?\n\nWe’d love your help!\n\nHelp shape the future of Focalboard by contributing ideas, bug reports, and code. Check out our community page or GitHub repo to get started.\n\nYou can also browse GitHub tickets with a “help wanted” tag to see what projects are available.\n\nLike what you see? Please consider giving Focalboard a GitHub Star!\n"
  },
  {
    "path": "website/site/content/blog/2021-4-27-Mattermost-Focalboard-early-preview.md",
    "content": "---\ntitle: \"Mattermost-Focalboard Early Preview\"\nslug: mattermost-focalboard-early-preview\ndate: 2021-04-27\ncategories:\n    - \"roadmap\"\nauthor: Chen Lim\ngithub: chenilim\ncommunity: chen-i.lim\n---\n\nThis is a preview of how Focalboard could initially integrate with Mattermost: Where each channel (public and private) has an associated Focalboard workspace accessible to members of that channel.\n\n## 🎯 To open a Focalboard workspace:\n\n1. Log into the [Mattermost community server](https://community.mattermost.com/core/channels/focalboard) via the web, or create an account\n2. Visit the [Focalboard channel](https://community.mattermost.com/core/channels/focalboard)\n3. Click on the Focalboard icon in the channel header:\n\n![Focalboard channel button](https://user-images.githubusercontent.com/46905241/116278459-5cfe2280-a73b-11eb-923b-039f15c54622.png)\n\n**Note**: Mattermost-Focalboard Early Preview is only available via a web browser, not via the Desktop or Mobile apps at this time.\n\nYou can then create or visit any other channel and click the Focalboard button there to open its workspace. **Tip**: Create a private channel or DM yourself to use a private workspace.\n\n## Attention: You are entering the **bleeding edge**! :)\n\nThis feature is currently in Early Preview. Please do not store any critical or sensitive data. You have been warned.\n\nThis is also not the final form of the integration, but meant to start us thinking about how an integration should work. Have an idea or feedback? Let us know on the [Focalboard channel](https://community.mattermost.com/core/channels/focalboard).\n\nSeriously though, it should be functional for basic project management:\nAll available features should work (please [file a bug](https://github.com/mattermost/focalboard/issues/new/choose) if they don’t)\nIt should be secure (you should not be able to access workspaces in channels you don’t belong to)\n\n### Please [file bugs](https://github.com/mattermost/focalboard/issues/new/choose) for any issues. 🐞\n\n## Tips and Tricks:\n1. Check out the [Focalboard user’s guide](https://www.focalboard.com/guide/user/) to get started\n2. [Export the archive](https://www.focalboard.com/guide/user/#archives) to backup all the boards in a workspace\n  a. You can import them back later, to a different server or Personal Desktop\n  b. Binary (image) file export / migration is not supported yet\n3. Want to assign a card to someone?\n  a. Create a Select property, and enter each person’s name as text\n  b. In the future, we plan a user-select property type\n4. If you see a sign-in or “no_workspace” error\n  a. Your session token may have expired\n  b. Click the button to re-login, then\n  c. Close the tab, and click the Focalboard button to reopen\n  d. Press Ctrl+Shift+R to force a full reload, close and reopen\n  e. Try deleting the sessionId entry in local storage, close and reopen\n\n## Feedback?\nPlease [get in touch](https://www.focalboard.com/feedback/)!\n\n## Want to Contribute?\nWe’d love your help!\n\nHelp shape the future of Focalboard by contributing ideas, bug reports, and code. Check out our [community page](https://mattermost.github.io/focalboard/) or [GitHub repo](https://github.com/mattermost/focalboard) to get started.\n\nYou can also browse GitHub tickets with a “[help wanted](https://github.com/mattermost/focalboard/issues?q=is%3Aissue+is%3Aopen+label%3A%22Up+for+grabs%22)” tag to see what projects are available.\n\nLike what you see? Please consider giving Focalboard a [GitHub Star](https://github.com/mattermost/focalboard)!"
  },
  {
    "path": "website/site/content/blog/2021-5-07-meeting-agenda-template.md",
    "content": "---\ntitle: \"Template: Meeting agenda\"\nslug: meeting-agenda-template\ndate: 2021-05-07\ncategories:\n    - \"templates\"\nauthor: Chen Lim\ngithub: chenilim\ncommunity: chen-i.lim\n---\n\n### Meeting Agenda Template\n\nWe've been using Focalboard on the [community server](https://github.com/mattermost/focalboard/discussions/349) for a few weeks now, and a few unexpected use-cases have emerged. One of them is using Focalboard for recurring meeting agendas, like team meetings and 1:1s.\n\n<img width=\"846\" alt=\"Meeting Agenda Template\" src=\"https://user-images.githubusercontent.com/46905241/117484632-35b60b00-af1c-11eb-82c7-89e753a160cf.png\">\n\nFor my own meetings, I started with the project tasks template, but have found for following columns work better:\n* To Discuss\n* Revisit Later\n* Done / Archived\n\nMeeting participants queue new items under **To Discuss**. As we go through them, most are moved to done, but a few need followup. We move those to the **Revisit Later** column. The owner then moves it back to **To Discuss** for a future meeting.\n\nHere's [the archive of the template](https://community.mattermost.com/core/pl/5qo11pe837ykue15bne9ke1snc) if you would like to try this out yourself:\n* Download the .focalboard archive file from the thread above\n* Select \"Import Archive\" under Settings at the lower left\n* Pick the archive file\n* (You may need to refresh the browser)\n* Click on **+ Add Board**, and pick \"Meeting Agenda\"\n\nWhat do you think?"
  },
  {
    "path": "website/site/content/blog/2021-5-13-Focalboard-the-road-to-v1.md",
    "content": "---\ntitle: \"Focalboard: The road to v1.0\"\nslug: focalboard-the-road-to-v1\ndate: 2021-05-13\ncategories:\n    - \"roadmap\"\nauthor: Chen Lim\ngithub: chenilim\ncommunity: chen-i.lim\n---\n\nThank you all so much for the enthusiasm and energy you've shown for Focalboard! To be perfectly honest, your enthusiasm is what enables us, as Mattermost, to direct more resources towards Focalboard. It wouldn't happen without you.\n\nI want to give an update on our current plans for taking Focalboard to a \"v1\". As it says on the tin, we want Focalboard to be a compelling **open source, self-hosted alternative to Trello, Notion, and Asana**. Our immediate goal is to build out the common features in these reference products, that is, features that \"80% of users\" care about.\n\nWe're not going to stop there of course. One of our main differentiators is going to be **tight integration with Mattermost**. You can check out the [Early Preview of that here](../mattermost-focalboard-early-preview). The immediate benefit of this is gaining the security and access-control systems in Mattermost, e.g. the ability to specify per-workspace permissions.\n\nWe anticipate many other integrations over time, e.g.\n* [Unfurled card links](https://github.com/mattermost/focalboard/issues/386)\n* [Slash commands to create cards](https://github.com/mattermost/focalboard/issues/382)\n\nIn addition to that, here is an outline of the other things we anticipate completing before declaring a \"v1\", based on the goals above, and feedback to date. Standard disclaimers apply - no promises, this is the current plan until the plan changes, and many of these are at the exploratory stage. :)\n\n### Complete / update existing views\n* Board view\n\t* [Export / import board](https://github.com/mattermost/focalboard/issues/261)\n\t* [Keyboard support](https://github.com/mattermost/focalboard/issues/29)\n* Table view\n\t* [Grouped table / list view](https://github.com/mattermost/focalboard/issues/408)\n\t* [Keyboard support](https://github.com/mattermost/focalboard/issues/30)\n* Gallery view\n\t* Various bug fixes\n* Card view\n\t* Various bug fixes\n\n### Additional views\n* List view\n\t* Current plan is to extend the Table view to support [groups](https://github.com/mattermost/focalboard/issues/408)\n* [Calendar view](https://github.com/mattermost/focalboard/issues/338)\n* Pages\n\t* Basic implementation is the same as the content blocks on a card\n\t* [Advanced content editor](https://github.com/mattermost/focalboard/issues/166)\n\n### Additional property types\n* [Date property](https://github.com/mattermost/focalboard/issues/38)\n* [User property](https://github.com/mattermost/focalboard/issues/185)\n* [Multi-select properties](https://github.com/mattermost/focalboard/issues/110)\n\n### Additional content types\n* [Link to another card](https://github.com/mattermost/focalboard/issues/324)\n\n### Improved sidebar\n* [Manual sorting](https://github.com/mattermost/focalboard/issues/299)\n* [Keyboard navigation](https://github.com/mattermost/focalboard/issues/28)\n\n### Personal Dashboard\n* [Status page](https://github.com/mattermost/focalboard/issues/305)\n\n## We need your feedback!\n\nContinuing to get feedback from you, our community is vital to us. Focalboard (and Mattermost) will continue to be open source projects supported by a vibrant community. We want you to be part of the ongoing conversation. Please continue to keep those [feature ideas](https://github.com/mattermost/focalboard/issues/new/choose), [bug reports](https://github.com/mattermost/focalboard/issues/new/choose), and [other input](https://www.focalboard.com/feedback/) coming.\n\n## Want to contribute?\n\nWe’d love your help!\n\nHelp shape the future of Focalboard by contributing ideas, bug reports, and code. Check out our [community page](https://mattermost.github.io/focalboard/) or [GitHub repo](https://github.com/mattermost/focalboard) to get started.\n\nYou can also browse GitHub tickets with a “[up for grabs](https://github.com/mattermost/focalboard/issues?q=is%3Aissue+is%3Aopen+label%3A%22Up+for+grabs%22)” tag to see what projects are available.\n\nLike what you see? Please consider giving Focalboard a [GitHub Star](https://github.com/mattermost/focalboard)!"
  },
  {
    "path": "website/site/content/blog/2021-6-18-Mattermost-Focalboard-release.md",
    "content": "---\ntitle: \"Focalboard now ships with Mattermost v5.36 and later\"\nslug: mattermost-focalboard-release\ndate: 2021-06-18\ncategories:\n    - \"release\"\nauthor: Chen Lim\ngithub: chenilim\ncommunity: chen-i.lim\n---\n\n![Mattermost-Focalboard](https://user-images.githubusercontent.com/46905241/121941290-ee355280-cd03-11eb-9b9f-f6f524e4103e.gif)\n\nThis week, Focalboard got a massive upgrade by also [shipping as part of Mattermost](https://mattermost.com/blog/mattermost-release-v5-36/)! For those new to Mattermost, it's the leading open source, self-hostable collaboration platform for developers, and an alternative to messaging chat tools like Slack and Microsoft Teams.\n\n### Now Enterprise-Grade\n\nThe immediate benefit is gaining the hundreds of developer-years of enterprise-grade security, scalability, and operability that's built into Mattermost. Things like multi-factor authentication, user management, and system administration. It also now supports [channel-based permissions](/download/mattermost/#focalboard-permissions), which limits access to members of the associated (public or private) channel.\n\nThe bigger picture is deeper integration with chat-based messaging workflows, which we are building out in the coming months. This is where we believe Focalboard will be able to deliver a much better solution compared to what's currently out there. Stay tuned.\n\nDon't worry, Focalboard Personal Editions are still available. Personal Desktop is the solution for individual personal use. If you are currently using Focalboard Personal Server, this might be the big upgrade you have been waiting for. You can simply export and re-import your boards into Mattermost-Focalboard.\n\n### Check out Mattermost-Focalboard today!\n\nIf you are new to Mattermost, follow these [install steps](https://docs.mattermost.com/guides/install-deploy-upgrade-scale.html#mattermost-server), which are very similar to the steps for Focalboard Personal Server.\n\nIf you already have Mattermost, simply upgrade to [v5.36](https://mattermost.com/blog/mattermost-release-v5-36/) or newer.\n\nThen follow [these steps](/download/mattermost/) to enable Focalboard.\n"
  },
  {
    "path": "website/site/content/docs/personal-edition/_index.md",
    "content": "---\ntitle: \"Personal Edition\"\ndate: \"2020-12-15T12:01:23-04:00\"\nsection: \"docs\"\nweight: 1\n---\n\nIf you are new to Focalboard, [Personal Desktop](desktop) is the fastest way to try it out.\n\nTo use it with your team, use [Mattermost Boards](../mattermost).\nYou can import boards from Personal Desktop to Mattermost Boards.\n\nYou can also set up the standalone Development or Personal Server on [Ubuntu](ubuntu) or with [Docker](docker).\n"
  },
  {
    "path": "website/site/content/docs/personal-edition/desktop.md",
    "content": "---\ntitle: \"Personal Desktop\"\ndate: \"2020-12-15T12:02:23-04:00\"\nsubsection: Personal Edition\nweight: 1\n---\n\nPersonal Desktop is a fully contained, standalone app meant for a single user, that is quick to install on Mac, Windows, or Linux.\n\n#### macOS\n\nDownload Focalboard from the [Mac App Store](https://apps.apple.com/app/apple-store/id1556908618?pt=2114704&ct=website&mt=8).\n\n<a href=\"https://apps.apple.com/app/apple-store/id1556908618?pt=2114704&ct=website&mt=8\"><img src=\"/img/mac-app-store.svg\" style=\"max-height: 40px;\" /></a>\n\n#### Windows\n\nDownload Focalboard from the [Microsoft App Store](https://www.microsoft.com/store/apps/9NLN2T0SX9VF?cid=website).\n\n<a href=\"https://www.microsoft.com/store/apps/9NLN2T0SX9VF?cid=website\"><img src=\"/img/ms-app-store.svg\" style=\"max-height: 40px;\" /></a>\n\nOr download `focalboard-win.zip` from the latest [release on GitHub](https://github.com/mattermost/focalboard/releases).\n\n#### Linux Desktop\n\nTo install Personal Desktop for Linux\n1. Download `focalboard-linux.tar.gz` from the latest [release on GitHub](https://github.com/mattermost/focalboard/releases)\n2. Unpack the .tar.gz archive\n4. Open `focalboard-app` from within the `focalboard-app` folder\n"
  },
  {
    "path": "website/site/content/docs/personal-edition/docker.md",
    "content": "---\ntitle: \"Personal Server (Docker)\"\ndate: \"2020-12-15T12:02:23-04:00\"\nsubsection: Personal Edition\nweight: 4\n---\n\nYou can download and run the latest version of Focalboard Personal Server with a single-line <a href=\"https://www.docker.com/\">Docker</a> command:\n\n```\ndocker run -it -p 80:8000 mattermost/focalboard\n```\n\nThen open a browser to <a href=\"http://localhost\">http://localhost</a>.\n\nor, to specify a port number:\n\n```\ndocker run -it -p <port>:8000 mattermost/focalboard\n```\n\n## To set up manually\n\nFollow the steps in the [setup guide](../ubuntu) to configure Focalboard manually.\n"
  },
  {
    "path": "website/site/content/docs/personal-edition/ubuntu-upgrade.md",
    "content": "---\ntitle: \"Upgrading Personal Server\"\ndate: \"2021-04-13T12:02:23-08:00\"\nsubsection: Personal Edition\nweight: 3\n---\n\nFollow these steps to upgrade an existing Personal Server installation that was previously set up with the [setup guide here](../ubuntu).\n\nUse the URL of the Ubuntu archive package, `focalboard-server-linux-amd64.tar.gz`, from the appropriate [release in GitHub](https://github.com/mattermost/focalboard/releases).\n\nCreate and use a clean directory, or delete any existing packages first, then run:\n\n```\n# Download the new version (e.g. 0.9.2 here, check the release for the latest one)\nwget https://github.com/mattermost/focalboard/releases/download/v0.9.2/focalboard-server-linux-amd64.tar.gz\ntar -xvzf focalboard-server-linux-amd64.tar.gz\n\n# Stop the server\nsudo systemctl stop focalboard.service\n\n# Back up the old version\nsudo mv /opt/focalboard /opt/focalboard-old\nsudo mv focalboard /opt\n\n# Copy config and move uploaded files over\nsudo mv /opt/focalboard-old/files /opt/focalboard\nsudo cp /opt/focalboard-old/config.json /opt/focalboard\n\n# Start the server\nsudo systemctl start focalboard.service\n\n# (Optional) delete the backup after verifying\nsudo rm -rf /opt/focalboard-old\n```\n"
  },
  {
    "path": "website/site/content/docs/personal-edition/ubuntu.md",
    "content": "---\ntitle: \"Personal Server (Ubuntu)\"\ndate: \"2020-12-15T12:02:23-04:00\"\nsubsection: Personal Edition\nweight: 2\n---\n\nFocalboard Personal Server is a standalone server for development and personal use. For team use, check out [Mattermost Boards](../../mattermost/), which supports private boards, team communication, and more.\n\nFollow these steps it up on an Ubuntu server. To upgrade an existing installation, see [the upgrade guide](../ubuntu-upgrade).\n\n## Set up Ubuntu Server 18.04\n\nPopular hosted options include:\n* [Digital Ocean](https://www.digitalocean.com/community/tutorials/initial-server-setup-with-ubuntu-18-04)\n* [Amazon EC2](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EC2_GetStarted.html)\n* [Linode](https://www.linode.com/docs/products/compute/)\n\n## Install Focalboard\n\nDownload the Ubuntu archive package from the appropriate [release in GitHub](https://github.com/mattermost/focalboard/releases). The example below uses the link for **v0.15.0**, but you're encouraged to use the latest version in the release list:\n\n```\nwget https://github.com/mattermost/focalboard/releases/download/v0.15.0/focalboard-server-linux-amd64.tar.gz\ntar -xvzf focalboard-server-linux-amd64.tar.gz\nsudo mv focalboard /opt\n```\n\n## Install NGINX\n\nBy default, the Focalboard server runs on port 8000 (specified in config.json). We recommend running NGINX as a web proxy to forward http and websocket requests from port 80 to it. To install NGINX, run:\n\n```\nsudo apt update\nsudo apt install nginx\n```\n\nYou may need to adjust your firewall settings depending on the host, e.g.\n* [Digital Ocean](https://www.digitalocean.com/community/tutorials/how-to-install-nginx-on-ubuntu-18-04)\n* [EC2](https://docs.nginx.com/nginx/deployment-guides/amazon-web-services/ec2-instances-for-nginx/)\n* [Linode](https://www.linode.com/docs/guides/how-to-install-nginx-ubuntu-18-04/)\n\n### Configure NGINX\n\nCreate a new site config:\n\n```\nsudo nano /etc/nginx/sites-available/focalboard\n```\n\nCopy and paste this configuration:\n\n```\nupstream focalboard {\n   server localhost:8000;\n   keepalive 32;\n}\n\nserver {\n   listen 80 default_server;\n\n   server_name focalboard.example.com;\n\n   location ~ /ws/* {\n       proxy_set_header Upgrade $http_upgrade;\n       proxy_set_header Connection \"upgrade\";\n       client_max_body_size 50M;\n       proxy_set_header Host $http_host;\n       proxy_set_header X-Real-IP $remote_addr;\n       proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n       proxy_set_header X-Forwarded-Proto $scheme;\n       proxy_set_header X-Frame-Options SAMEORIGIN;\n       proxy_buffers 256 16k;\n       proxy_buffer_size 16k;\n       client_body_timeout 60;\n       send_timeout 300;\n       lingering_timeout 5;\n       proxy_connect_timeout 1d;\n       proxy_send_timeout 1d;\n       proxy_read_timeout 1d;\n       proxy_pass http://focalboard;\n   }\n\n   location / {\n       client_max_body_size 50M;\n       proxy_set_header Connection \"\";\n       proxy_set_header Host $http_host;\n       proxy_set_header X-Real-IP $remote_addr;\n       proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n       proxy_set_header X-Forwarded-Proto $scheme;\n       proxy_set_header X-Frame-Options SAMEORIGIN;\n       proxy_buffers 256 16k;\n       proxy_buffer_size 16k;\n       proxy_read_timeout 600s;\n       proxy_cache_revalidate on;\n       proxy_cache_min_uses 2;\n       proxy_cache_use_stale timeout;\n       proxy_cache_lock on;\n       proxy_http_version 1.1;\n       proxy_pass http://focalboard;\n   }\n}\n```\n\nIf there is a default site, you may need to delete it\n\n```\nsudo rm /etc/nginx/sites-enabled/default\n```\n\nEnable the Focalboard site, test the config, and reload NGINX:\n\n```\nsudo ln -s /etc/nginx/sites-available/focalboard /etc/nginx/sites-enabled/focalboard\nsudo nginx -t\nsudo /etc/init.d/nginx reload\n```\n\n## Set up TLS on NGINX\n\nFor a production server, it's important to set up TLS to encrypt web traffic. Without this, your login passwords and data are unprotected. Refer to the [NGINX TLS guide](https://docs.nginx.com/nginx/admin-guide/security-controls/terminating-ssl-http/) and [Let's Encrypt Certbot guide](https://certbot.eff.org/lets-encrypt/ubuntubionic-nginx) on setting this up.\n\n## Install Postgresql (Recommended)\n\nFocalboard stores data in a SQLite database by default, but we recommend running against Postgres in production (we've tested against Postgres 10.15). To install, run:\n\n```\nsudo apt install postgresql postgresql-contrib\n```\n\nThen run as the postgres user to create a new database:\n\n```\nsudo --login --user postgres\npsql\n```\n\nOn the psql prompt, run the following commands (**change the user/password** to your own values):\n\n```\nCREATE DATABASE boards;\nCREATE USER <b>boardsuser</b> WITH PASSWORD '<b>boardsuser-password</b>';\n\\q\n```\n\nExit the postgres user session:\n\n```\nexit\n```\n\nEdit the Focalboard config.json:\n\n```\nnano /opt/focalboard/config.json\n```\n\nChange the dbconfig setting to use the postgres database you created:\n\n```\n\"dbtype\": \"postgres\",\n\"dbconfig\": \"postgres://boardsuser:boardsuser-password@localhost/boards?sslmode=disable&connect_timeout=10\",\n```\n\n## Install MySQL\n\nAs an alternative to Postgres, you also can store your data in a MySQL database. To install, run:\n\n```\nsudo apt-get install mysql-server\n```\n\nLog in as `root` in your database:\n\n```\nsudo mysql\n```\n\nAt the MySQL prompt, run the following commands (change `user/password` to your own values):\n\n```\nCREATE DATABASE boards;\nGRANT ALL on boards.* to 'boardsuser'@'localhost' identified by 'boardsuser-password';\n```\n\nExit the mysql-prompt:\n\n```\nexit\n```\n\nEdit the Focalboard `config.json`:\n\n```\nnano /opt/focalboard/config.json\n```\n\nChange the dbconfig setting to use the MySQL database you created:\n\nWhen MySQL is being used, using collation is recommended over using charset.\n\nUsing a variant of `utf8mb4` collation is required. For example, `utf8mb4_general_ci`\nis used by default when no collation is specified.\n\nIf you're using Focalboard as a Mattermost Plugin prior to version 0.9 with MySQL,\nplease ensure the collations of focalboard tables (tables with the prefix `focalboard_`)\nis the same as the collation of mattermost tables.\n\n```\n\"dbtype\": \"mysql\",\n\"dbconfig\": \"boardsuser:boardsuser-password@tcp(127.0.0.1:3306)/boards\",\n```\n\n## Configure Focalboard to run as a service\n\nThis will keep the server running across reboots. First, create a new service config file:\n\n```\nsudo nano /lib/systemd/system/focalboard.service\n```\n\nPaste in the following:\n\n```\n[Unit]\nDescription=Focalboard server\n\n[Service]\nType=simple\nRestart=always\nRestartSec=5s\nExecStart=/opt/focalboard/bin/focalboard-server\nWorkingDirectory=/opt/focalboard\n\n[Install]\nWantedBy=multi-user.target\n```\n\nMake systemd reload the new unit, and start it on machine reboot:\n\n```\nsudo systemctl daemon-reload\nsudo systemctl start focalboard.service\nsudo systemctl enable focalboard.service\n```\n\n## Test the server\n\nAt this point, the Focalboard server should be running.\n\nTest that it's running locally with:\n\n```\ncurl localhost:8000\ncurl localhost\n```\n\nThe first command checks that the server is running on port 8000 (default), and the second checks that NGINX is proxying requests successfully. Both commands should return the same snippet of HTML.\n\nTo access the server remotely, open a browser to its IP address or domain.\n\n## Set up the server\n\nRefer to the [server setup guide](/guide/server-setup/) to complete server setup.\n"
  },
  {
    "path": "website/site/content/download/index.html",
    "content": "---\ntitle: \"Download\"\nlayout: \"page\"\nweight: 0\n---\n\n<div class=\"homepage__section homepage__threecol\">\n    <div class=\"threecol__single\">\n        <div class=\"threecol__image\">\n            <img src=\"../img/teams-icon.svg\" alt=\"Teams Icon\">\n        </div>\n        <div class=\"threecol__copy\">\n            <h4>For Teams</h4>\n            <p>\n                Install the <a href=\"https://github.com/mattermost/focalboard/releases\" target='_blank'>latest version</a> of the Focalboard plugin in your existing Mattermost server\n            </p>\n        </div>\n    </div>\n    <div class=\"threecol__single\">\n        <div class=\"threecol__image\">\n            <img src=\"../img/developers-icon.svg\" alt=\"Developers Icon\">\n        </div>\n        <div class=\"threecol__copy\">\n            <h4>For Developers</h4>\n            <p>\n                Fork the source code on <a href=\"https://github.com/mattermost/focalboard\" target=\"_blank\">GitHub</a> and install <a href=\"/docs/personal-edition/ubuntu/\">personal server</a> for\n                development.\n            </p>\n        </div>\n    </div>\n    <div class=\"threecol__single\">\n        <div class=\"threecol__image\">\n            <img src=\"../img/single-users-icon.svg\" alt=\"Single User Icon\">\n        </div>\n        <div class=\"threecol__copy\">\n            <h4>For Single Users</h4>\n            <p>\n                Get <a href=\"/docs/personal-edition/desktop/\">Personal Desktop</a>, a stand-alone single-user app for <a href=\"/docs/personal-edition/desktop/#macos\">Mac</a>, <a href=\"/docs/personal-edition/desktop/#windows\">Windows</a>, and <a href=\"/docs/personal-edition/desktop/#linux-desktop\">Linux</a>.\n            </p>\n        </div>\n    </div>\n</div>\n"
  },
  {
    "path": "website/site/content/feedback/_index.md",
    "content": "---\ntitle: \"We love your feedback!\"\ndate: \"2021-03-03T12:01:23-04:00\"\nsection: \"feedback\"\nweight: 1\n---\n\nPlease contact us via one of the following:\n* [File a GitHub issue](https://github.com/mattermost/focalboard/issues) for bugs or feature ideas\n* [Start a GitHub discussion](https://github.com/mattermost/focalboard/discussions)\n\nFocalboard is an open source project that is in early access. Help shape its future by sharing what you'd like to see with the community.\n\nThank you for your input!\n"
  },
  {
    "path": "website/site/content/fwlink/doc-boards.html",
    "content": "<html>\n    <head>\n        <meta http-equiv=\"refresh\" content=\"0;url=https://docs.mattermost.com/guides/boards.html\" />\n        <title>Page Moved</title>\n        <script type=\"text/javascript\">\n            window.location.replace(\"https://docs.mattermost.com/guides/boards.html\");\n        </script>\n    </head>\n    <body>\n        This page has moved. Click <a href=\"https://docs.mattermost.com/guides/boards.html\">here</a> to go to the new page.\n    </body>\n</html>\n"
  },
  {
    "path": "website/site/content/fwlink/feedback-boards.html",
    "content": "<html>\n    <head>\n        <title>Page Moved</title>\n        <script type=\"text/javascript\">\n            var urlParams = new URLSearchParams(window.location.search);\n            var version = urlParams.get('v');\n            var url = 'https://docs.google.com/forms/d/e/1FAIpQLSfNLwUsG6zazDYxt_fgCV-ThH5_R9ycPTTgRTJ1DVnXuuyD7Q/viewform?usp=pp_url&entry.797568877=' + version;\n\n            var link = document.getElementById(\"mainLink\");\n            if (link && link.href) {\n                link.href = url;\n            }\n\n            window.location.replace(url);\n        </script>\n    </head>\n    <body>\n        This page has moved. Click <a id=\"mainLink\" href=\"https://docs.google.com/forms/d/e/1FAIpQLSfNLwUsG6zazDYxt_fgCV-ThH5_R9ycPTTgRTJ1DVnXuuyD7Q/viewform\">here</a> to go to the new page.\n    </body>\n</html>\n"
  },
  {
    "path": "website/site/content/fwlink/feedback-focalboard.html",
    "content": "<html>\n    <head>\n        <title>Page Moved</title>\n        <script type=\"text/javascript\">\n            var urlParams = new URLSearchParams(window.location.search);\n            var version = urlParams.get('v');\n            var url = 'https://docs.google.com/forms/d/e/1FAIpQLSdTq7M69Pdlz71CwucaSEG0FCK1M_WRvIbZbPr2imfT2QvUCQ/viewform?usp=pp_url&entry.1003590493=' + version;\n\n            var link = document.getElementById(\"mainLink\");\n            if (link && link.href) {\n                link.href = url;\n            }\n\n            window.location.replace(url);\n        </script>\n    </head>\n    <body>\n        This page has moved. Click <a id=\"mainLink\" href=\"https://docs.google.com/forms/d/e/1FAIpQLSdTq7M69Pdlz71CwucaSEG0FCK1M_WRvIbZbPr2imfT2QvUCQ/viewform\">here</a> to go to the new page.\n    </body>\n</html>\n"
  },
  {
    "path": "website/site/content/fwlink/plugin-setup.html",
    "content": "<html>\n    <head>\n        <meta http-equiv=\"refresh\" content=\"0;url=https://www.focalboard.com/download/mattermost/\" />\n        <title>Page Moved</title>\n        <script type=\"text/javascript\">\n            window.location.replace(\"https://www.focalboard.com/download/mattermost/\");\n        </script>\n    </head>\n    <body>\n        This page has moved. Click <a href=\"https://www.focalboard.com/download/mattermost/\">here</a> to go to the new page.\n    </body>\n</html>\n"
  },
  {
    "path": "website/site/content/fwlink/setup-536.html",
    "content": "<html>\n    <head>\n        <meta http-equiv=\"refresh\" content=\"0;url=https://www.focalboard.com/download/mattermost/\" />\n        <title>Page Moved</title>\n        <script type=\"text/javascript\">\n            window.location.replace(\"https://www.focalboard.com/download/mattermost/\");\n        </script>\n    </head>\n    <body>\n        This page has moved. Click <a href=\"https://www.focalboard.com/download/mattermost/\">here</a> to go to the new page.\n    </body>\n</html>\n"
  },
  {
    "path": "website/site/content/fwlink/v1-focalboard.html",
    "content": "<html>\n    <head>\n        <meta http-equiv=\"refresh\" content=\"0;url=https://focalboard-community.octo.mattermost.com/workspace/qgsck6cts3fwpqwyjiupjm5cde/shared?id=47aa9bb4-6967-4a96-83c7-11bd6b20f1eb&v=ca1a5441-4c1c-4d0e-856e-88cc744576c8&r=762db9c6-305c-4d13-8ce6-9f11e5232c24\" />\n        <title>Page Moved</title>\n        <script type=\"text/javascript\">\n            window.location.replace(\"https://focalboard-community.octo.mattermost.com/workspace/qgsck6cts3fwpqwyjiupjm5cde/shared?id=47aa9bb4-6967-4a96-83c7-11bd6b20f1eb&v=ca1a5441-4c1c-4d0e-856e-88cc744576c8&r=762db9c6-305c-4d13-8ce6-9f11e5232c24\");\n        </script>\n    </head>\n    <body>\n        This page has moved. Click <a href=\"https://focalboard-community.octo.mattermost.com/workspace/qgsck6cts3fwpqwyjiupjm5cde/shared?id=47aa9bb4-6967-4a96-83c7-11bd6b20f1eb&v=ca1a5441-4c1c-4d0e-856e-88cc744576c8&r=762db9c6-305c-4d13-8ce6-9f11e5232c24\">here</a> to go to the new page.\n    </body>\n</html>\n"
  },
  {
    "path": "website/site/content/fwlink/websocket-connect-error.html",
    "content": "<html>\n    <head>\n        <meta http-equiv=\"refresh\" content=\"0;url=https://www.focalboard.com/guide/websocket-errors/\" />\n        <title>Page Moved</title>\n        <script type=\"text/javascript\">\n            window.location.replace(\"https://www.focalboard.com/guide/websocket-errors/\");\n        </script>\n    </head>\n    <body>\n        This page has moved. Click <a href=\"https://www.focalboard.com/guide/websocket-errors/\">here</a> to go to the new page.\n    </body>\n</html>\n"
  },
  {
    "path": "website/site/content/guide/admin/_index.md",
    "content": "---\ntitle: \"Administrator's Guide\"\ndate: \"2020-12-15T12:01:23-04:00\"\nsection: \"guide\"\nweight: 3\n---\n\n## Personal server configuration\n\nPersonal server settings are stored in `config.json` and are read when the server is launched. The contents are:\n\n| Key      | Description | Example |\n|----------|-------------|---------|\n| serverRoot    | Root URL of the server        | http://localhost:8000\n| port          | Server port                   | 8000\n| dbtype        | Type of database. `sqlite3`, `postgres`, or `mysql` | sqlite3\n| dbconfig      | Database connection string    | `postgres://user:pass@localhost/boards?sslmode=disable&connect_timeout=10`\n| useSSL        | Enable or disable SSL         | false\n| webpath       | Path to web files             | `./webapp/pack`\n| filespath     | Path to uploaded files folder | `./files`\n| telemetry     | Enable health diagnostics telemetry | `true`\n| prometheus_address | Enables Prometheus metrics, if it's empty is disabled | `:9092`\n| session_expire_time | Session expiration time in seconds | 2592000\n| session_refresh_time | Session refresh time in seconds   | 18000\n| localOnly | Only allow connections from localhost        | `false`\n| enableLocalMode | Enable admin APIs on local Unix port   | `true`\n| localModeSocketLocation | Location of local Unix port    | `/var/tmp/focalboard_local.socket`\n| enablePublicSharedBoards | Enable publishing boards for public access | `false`\n\n## Resetting passwords\n\nBy default, personal server exposes admin APIs on a local Unix socket at `/var/tmp/focalboard_local.socket`. This is configurable using the `enableLocalMode` and `localModeSocketLocation` settings in `config.json`.\n\nNote that if you're using a version of Mattermost Boards up to v7.1, you need to use v1 of the API. From v7.2 onwards, you need to use v2 of the API.\n\nTo reset a user's password, you can use the following `reset-password.sh` script:\n\n```\n#!/bin/bash\n\nif [[ $# < 2 ]] ; then\n    echo 'reset-password.sh <username> <new password>'\n    exit 1\nfi\n\ncurl --unix-socket /var/tmp/focalboard_local.socket http://localhost/api/v2/admin/users/$1/password -X POST -H 'Content-Type: application/json' -d '{ \"password\": \"'$2'\" }'\n```\n\nAfter resetting a user's password (e.g. if they forgot it), direct them to change it from the user menu, by clicking on their username at the top of the sidebar.\n"
  },
  {
    "path": "website/site/content/guide/server-setup/_index.md",
    "content": "---\ntitle: \"Server Setup Guide\"\ndate: \"2021-01-21T12:01:23-04:00\"\nsection: \"guide\"\nweight: 1\n---\n\nIf you haven't already, first follow the [install guide](/download/personal-edition/ubuntu/) to install the Focalboard server.\n\n## Registering the first user\n\nAfter installing the server, open a browser to the domain you used (or `http://localhost:8000` for local installs). You should be redirected to the login screen. Click the link to register a new user instead, and complete the registration.\n\nThe first user registration will always be permitted, but **subsequent registrations will require an invite link which includes a code**. You can invite additional users by clicking on your username in the top left, then selecting \"Invite users\".\n\nYou are now ready to use Focalboard. Please refer to the [user's guide](../user/) for details.\n"
  },
  {
    "path": "website/site/content/guide/user/_index.md",
    "content": "---\ntitle: \"User's Guide\"\ndate: \"2020-12-15T12:01:23-04:00\"\nsection: \"guide\"\nweight: 2\n---\n\nThis guide is an overview of the basic features to get you started. Focalboard is an open source project that is being updated continuously.\n\n[Let us know](/feedback/) if there's a feature you are looking for, or [connect with our vibrant community](https://community.mattermost.com/core/channels/focalboard) to help bring it to life.\n\n## Adding new Boards\n\n1. Select **+ Add Board** in the bottom left of the sidebar to add a new board to Focalboard.\n2. Pick a template, such as **Project Tasks**, from the list.\n3. This shows the first view of the new board, which is a table of all tasks.\n\n![image](./all%20tasks.png)\n\n4. Click on \"By Status\" in the sidebar to see a board view.\n\n![image](./by%20status.png)\n\n5. Click on the properties menu, and turn on \"Priority\" and \"Date created\". This will add those properties to the card display.\n\n![image](./by%20status%20properties.png)\n\n6. You can likewise change the **Group By**, **Filter**, and **Sort** settings of the view.\n7. In general, all changes you make are saved immediately and are visible to all other users.\n\n## Renaming Boards\n\nClick on the board title to edit it. You can also set an icon on boards and cards.\n\n## Adding new Views\n\nClick on the dropdown next to the view name, and click **Add view** to add a new view.\n\n![image](./add%20view.png)\n\n### Dragging cards\n\nDrag cards from one column to another to change their group-by property. For example, drag a card to the **Completed** column to mark it as completed.\n\nWhen a board is unsorted, you can drag a card to a specific row in a column. For sorted boards, dragging a card to a column with auto-sort it using the specified sort settings.\n\n### Editing cards\n\nSelect a card to edit it. A card consists of:\n\n1. **A set of properties:** Properties are common to all cards in a board. Board views can group cards by \"Select\" type properties into different columns.\n2. **A list of comments:** Comments are useful for noting important changes or milestones.\n3. **A set of content:** The content of a card can consist of markdown text and images. Use this to record detailed specs or design decisions for an item for example.\n\nPress ESC or click outside the card editor dialog to close the card editor.\n\n### Table Views\n\nEach column corresponds to a card property. Cells can be edited directly, or you can open the card editor for a row by hovering over the title and clicking \"open\".\n\nClick on headers to sort them, or insert new properties.\n\n![image](./table%20header%20menu.png)\n\n### Card templates\n\nEach board can have a set of card templates. To create a new card template, click on the dropdown by the \"New\" button and select \"+ New template\".\n\nA card editor opens with a banner indicating this is a template.\n\nYou can new create new cards using this new template.\n\nAlternatively, you can turn any card into a template:\n\n1. Open the card editor.\n2. Select the options menu (**...** at the top right), then select **New template from card**.\n\n### Board templates\n\nSimilarly, you can create board templates from the \"+ Add board\" menu. To turn an existing board into a template:\n\n1. Hover over the board title in the sidebar.\n2. Select the options menu (**...**), then select **New template from board**.\n\n![image](./board%20sidebar%20menu.png)\n\n### Sharing boards\n\n1. Hover over the right-hand side of the board menu.\n2. Click on the options menu (**...**) and select **Share board**.\n![image](./share%20board.png)\n\n3. Turn the switch on which says **Publish to web and share this board with anyone**.\n![image](./share%20board%20menu.png)\n\n4. Copy the given link.\n5. You can now share the copied link with others. Anyone with the link will be able to view the board)\n6. Select **Regenerate Token** if you want to invalidate all the previously shared links.\n7. A message asking **This will invalidate previously shared links. Continue?** will pop up. Select **OK** and the token will be regenerated.\n\n### Archives\n\nYou can export an archive of all your boards from the settings menu.\n\n![image](./settings%20menu.png)\n\nThis is handy to quickly back-up snapshots, or to transfer them to different installations, for example from a Personal Desktop to a server install. Note that the archive does not (currently) include image and file attachments, such as images included in a card.\n\nAlso note that importing an archive will overwrite changes to items, but will not affect additional items (e.g. boards and cards) that are not part of the archive.\n"
  },
  {
    "path": "website/site/content/guide/websocket-errors/_index.md",
    "content": "---\ntitle: \"Troubleshooting: Websocket errors\"\ndate: \"2021-06-14T12:01:23-04:00\"\nsection: \"guide\"\nweight: 4\n---\n\nIf the websocket persistently fails to connect to the server, check that the web proxy is configured correctly:\n* [If running Focalboard with Mattermost](/download/mattermost/)\n* [If running Focalboard Personal Server](/download/personal-edition/ubuntu/#configure-nginx)\n"
  },
  {
    "path": "website/site/layouts/404.html",
    "content": "\n"
  },
  {
    "path": "website/site/layouts/_default/_markup/render-link.html",
    "content": "<a href=\"{{ .Destination | safeURL }}\"{{ with .Title}} title=\"{{ . }}\"{{ end }}{{ if strings.HasPrefix .Destination \"http\" }} target=\"_blank\" rel=\"noopener noreferrer\"{{ end }}>{{ htmlUnescape .Text }}</a>"
  },
  {
    "path": "website/site/layouts/_default/list.html",
    "content": "<!DOCTYPE html>\n<!--[if lt IE 7]>      <html class=\"no-js lt-ie9 lt-ie8 lt-ie7\"> <![endif]-->\n<!--[if IE 7]>         <html class=\"no-js lt-ie9 lt-ie8\"> <![endif]-->\n<!--[if IE 8]>         <html class=\"no-js lt-ie9\"> <![endif]-->\n<!--[if gt IE 8]>      <html class=\"no-js\"> <![endif]-->\n\n<html>\n    <head>\n        {{ partial \"head.html\" .}}\n    </head>\n    <body>\n        {{ partial \"nav.html\" .}}\n        <div id=\"wrapper\">\n            <div class=\"container-fluid\">\n                <div class=\"row\">\n                    {{ partial \"sidebar.html\" .}}\n                    <div class=\"col-lg-9 doc-content\">\n                        {{ partial \"page-edit.html\" . }}\n                        <h1 class=\"mt-0 doc-title\">{{ .Title }}</h1>\n                        {{ partial \"hanchor.html\" .Content }}\n                    </div>\n                </div>\n            </div>\n        </div>\n        {{ partial \"footer.html\" .}}\n    </body>\n</html>\n"
  },
  {
    "path": "website/site/layouts/_default/page.html",
    "content": "<!DOCTYPE html>\n<!--[if lt IE 7]>      <html class=\"no-js lt-ie9 lt-ie8 lt-ie7\"> <![endif]-->\n<!--[if IE 7]>         <html class=\"no-js lt-ie9 lt-ie8\"> <![endif]-->\n<!--[if IE 8]>         <html class=\"no-js lt-ie9\"> <![endif]-->\n<!--[if gt IE 8]>      <html class=\"no-js\"> <![endif]-->\n\n<html>\n    <head>\n        {{ partial \"head.html\" .}}\n    </head>\n    <body>\n        {{ partial \"nav.html\" .}}\n        <div id=\"wrapper\">\n            <div class=\"container-fluid\">\n                {{ partial \"hanchor.html\" .Content }}\n            </div>\n        </div>\n        {{ partial \"footer.html\" .}}\n    </body>\n</html>\n"
  },
  {
    "path": "website/site/layouts/_default/single.html",
    "content": "<!DOCTYPE html>\n<!--[if lt IE 7]>      <html class=\"no-js lt-ie9 lt-ie8 lt-ie7\"> <![endif]-->\n<!--[if IE 7]>         <html class=\"no-js lt-ie9 lt-ie8\"> <![endif]-->\n<!--[if IE 8]>         <html class=\"no-js lt-ie9\"> <![endif]-->\n<!--[if gt IE 8]>      <html class=\"no-js\"> <![endif]-->\n\n<html>\n    <head>\n        {{ partial \"head.html\" .}}\n    </head>\n    <body>\n        {{ partial \"nav.html\" .}}\n        <div id=\"wrapper\">\n            <div class=\"container-fluid\">\n                <div class=\"row\">\n                    {{ partial \"sidebar.html\" .}}\n                    <div class=\"col-lg-9 doc-content\">\n                        {{ partial \"page-edit.html\" . }}\n                        <h1 class=\"mt-0 doc-title\">{{ .Title }}</h1>\n                        {{ partial \"hanchor.html\" .Content }}\n                    </div>\n                </div>\n            </div>\n        </div>\n        {{ partial \"footer.html\" .}}\n    </body>\n</html>\n"
  },
  {
    "path": "website/site/layouts/_default/taxonomy.html",
    "content": "<!DOCTYPE html>\n<!--[if lt IE 7]>      <html class=\"no-js lt-ie9 lt-ie8 lt-ie7\"> <![endif]-->\n<!--[if IE 7]>         <html class=\"no-js lt-ie9 lt-ie8\"> <![endif]-->\n<!--[if IE 8]>         <html class=\"no-js lt-ie9\"> <![endif]-->\n<!--[if gt IE 8]>      <html class=\"no-js\"> <![endif]-->\n\n<html>\n    <head>\n        {{ partial \"head.html\" .}}\n    </head>\n<body>\n    {{ partial \"nav.html\" .}}\n    <div class=\"container\">\n        <div class=\"row\">\n            <div class=\"col-md-9 doc-content\">\n                <div class=\"well well-sm\">\n                    <h2>Items in category <code>{{ .Title | lower }}</code></h2>\n                    <ul class=\"list-unstyled\" style=\"margin-top: 20px\">\n                        {{ range .Data.Pages }}\n                            {{ .Render \"li\" }}\n                        {{ end}}\n                    </ul>\n                </div>\n            </div>\n        </div>\n    </div>\n    {{ partial \"footer.html\" .}}\n</body>\n</html>\n"
  },
  {
    "path": "website/site/layouts/blog/li.html",
    "content": "<li>\n    <h4>- <a href=\"{{ .Permalink }}\">{{ .Title}}</a><br>\n    <small>posted on {{ .Date.Format \"January 2, 2006\" }}</small></h4>\n</li>\n"
  },
  {
    "path": "website/site/layouts/blog/list.html",
    "content": "<!DOCTYPE html>\n<!--[if lt IE 7]>      <html class=\"no-js lt-ie9 lt-ie8 lt-ie7\"> <![endif]-->\n<!--[if IE 7]>         <html class=\"no-js lt-ie9 lt-ie8\"> <![endif]-->\n<!--[if IE 8]>         <html class=\"no-js lt-ie9\"> <![endif]-->\n<!--[if gt IE 8]>      <html class=\"no-js\"> <![endif]-->\n\n<html>\n    <head>\n        {{ partial \"head.html\" .}}\n    </head>\n<body class=\"blog-list\">\n    {{ partial \"nav.html\" .}}\n    <div class=\"container\">\n        <div class=\"row\">\n            <div class=\"col-md-9 doc-content\">\n                <h3>Focalboard Developer Blog</h3>\n                {{ range (.Paginate .Data.Pages).Pages.ByDate.Reverse }}\n                    {{ .Render \"summary\" }}\n                {{ end }}\n                <div class=\"blog-pagination\">\n                    {{ if .Paginator.HasNext }}\n                        <a class=\"btn btn-primary btn-sm\" href=\"{{ .Paginator.Next.URL }}\"> &laquo; Previous Posts</a>\n                    {{ end }}\n                    {{ if .Paginator.HasPrev }}\n                        <a class=\"btn btn-primary btn-sm\" href=\"{{ .Paginator.Prev.URL }}\"> Next Posts &raquo;</a>\n                    {{ end }}\n                </div>\n            </div>\n        </div>\n    </div>\n    {{ partial \"footer.html\" .}}\n</body>\n</html>\n"
  },
  {
    "path": "website/site/layouts/blog/single.html",
    "content": "<!DOCTYPE html>\n<!--[if lt IE 7]>      <html class=\"no-js lt-ie9 lt-ie8 lt-ie7\"> <![endif]-->\n<!--[if IE 7]>         <html class=\"no-js lt-ie9 lt-ie8\"> <![endif]-->\n<!--[if IE 8]>         <html class=\"no-js lt-ie9\"> <![endif]-->\n<!--[if gt IE 8]>      <html class=\"no-js\"> <![endif]-->\n\n<html>\n\n<head>\n    {{ partial \"head.html\" .}}\n</head>\n\n<body class=\"blog-single\">\n    {{ partial \"nav.html\" .}}\n    <div class=\"container\">\n        <div class=\"row\">\n            <div class=\"col-md-9 doc-content\">\n                <div class=\"well well-sm\">\n                    <div class=\"blog-item__header\">\n                        <div class=\"blog-item__title\">{{ .Title }}<br> <small>{{ .Description }}</small></div>\n                        <div class=\"blog-item__info\">\n                            {{ .Date.Format \"January 2, 2006\" }}\n                            <small class=\"blog-item__count\">{{ .WordCount }} words</small>\n                        </div>\n                    </div>\n                    <hr>\n                    {{ partial \"hanchor.html\" .Content }}\n                    <hr>\n                    {{ if and (and .Params.author .Params.github) .Params.community }}\n                    Written by\n                    {{ if and (and .Params.author_2 .Params.github_2) .Params.community_2 }}\n                    :\n                    <ul>\n                        <li>\n                            {{ partial \"blogauthor\" (dict \"name\" .Params.author \"community\" .Params.community \"github\" .Params.github) }}\n                        </li>\n                        <li>\n                            {{ partial \"blogauthor\" (dict \"name\" .Params.author_2 \"community\" .Params.community_2 \"github\" .Params.github_2) }}\n                        </li>\n                    </ul>\n                    {{ else }}\n                    {{ partial \"blogauthor\" (dict \"name\" .Params.author \"community\" .Params.community \"github\" .Params.github) }}\n                    {{ end }}\n                    {{ end }}\n                    <br />\n                    <br />\n                    Join us on <a href=\"https://community.mattermost.com/signup_user_complete\"\n                        target=\"_blank\">community.mattermost.com</a>!\n                </div>\n            </div>\n\n            <!-- Sidebar -->\n            <div class=\"col-md-3 doc-content tags-sidebar\">\n                <div class=\"well well-sm\">\n{{ if .Params.toc }}\n                <h6>Table Of Contents</h6>\n                    {{ .TableOfContents }}\n{{ end }}\n                    {{ partial \"series_link.html\" . }}\n\n                    <h6>Other Posts</h6>\n                    <ul class=\"list-unstyled\">\n                        {{ range first 15 (where .Site.Pages \".Section\" \"blog\" )}}\n                        {{ if and (.IsPage) (ne .Title $.Title)}}\n                        <li class=\"blog-item__sidebar\">- <a href=\"{{ .Permalink }}\">{{ .Title }}</a></li>\n                        {{ end }}\n                        {{ end }}\n                    </ul>\n                    <h6>Categories</h6>\n                    <ul class=\"list-unstyled\">\n                        {{ range .Params.categories }}\n                    <li>- <a href=\"{{ $.Site.BaseURL }}categories/{{ . | urlize }}\">{{ . }}</a></li>\n                        {{ end }}\n                    </ul>\n                </div>\n            </div>\n        </div>\n    </div>\n    {{ partial \"footer.html\" .}}\n</body>\n\n</html>\n"
  },
  {
    "path": "website/site/layouts/blog/summary.html",
    "content": "<div class=\"well well-sm\">\n    <h5>\n        <a href=\"{{ .Permalink }}\">{{ .Title }}</a>\n        <small class=\"blog-item__date pull-right\">\n            Posted\n            on {{ .Date.Format \"Jan 2, 2006\" }}\n            {{ if .Params.author }}\n                {{ if .Params.author_2 }}\n                    <br>\n                    by {{ .Params.author }} and {{ .Params.author_2 }}\n                {{ else }}\n                    by {{ .Params.author }}\n                {{ end }}\n            {{ end }}\n        </small>\n    </h5>\n    <small>{{ .Description }}</small>\n    <hr>\n    <p>{{ .Summary }}</p>\n    <a class=\"btn btn-primary btn-sm\" href=\"{{ .Permalink }}\">Read More <span class=\"fa fa-angle-right\"></span></a>\n</div>"
  },
  {
    "path": "website/site/layouts/index.html",
    "content": "<!DOCTYPE html>\n<!--[if lt IE 7]>      <html class=\"no-js lt-ie9 lt-ie8 lt-ie7\"> <![endif]-->\n<!--[if IE 7]>         <html class=\"no-js lt-ie9 lt-ie8\"> <![endif]-->\n<!--[if IE 8]>         <html class=\"no-js lt-ie9\"> <![endif]-->\n<!--[if gt IE 8]>      <html class=\"no-js\"> <![endif]-->\n<html>\n    <head>\n        {{ partial \"head.html\" .}}\n        <script src=\"https://cdn.jsdelivr.net/gh/kevinbatdorf/alpine-auto-interval@0.x.x/dist/index.js\"></script>\n        <script defer src=\"https://unpkg.com/alpinejs@3.10.5/dist/cdn.min.js\"></script>\n        <script src=\"https://unpkg.com/aos@2.3.1/dist/aos.js\"></script>\n        <link href=\"https://unpkg.com/aos@2.3.1/dist/aos.css\" rel=\"stylesheet\">\n\n    </head>\n    <body\n        class=\"homepage\"\n        x-data='{\n            template: 1,\n            boardView: 1,\n            roadmapItem: 1,\n            viewsInterval: null,\n            roadmapInterval: null,\n        }'\n        x-init='viewsInterval = setInterval(function () {boardView > 3 ? boardView = 1 : boardView++}, 3000); roadmapInterval = setInterval(function () {roadmapItem > 2 ? roadmapItem = 1 : roadmapItem++}, 3000);'\n    >\n        {{ partial \"nav.html\" .}}\n\n\n        <div id=\"wrapper\">\n            {{ partial \"hero.html\" .}}\n\n            {{ partial \"intro.html\" .}}\n\n            {{ partial \"mailinglist.html\" .}}\n        </div>\n\n        {{ partial \"footer.html\" .}}\n\n        <script>\n            document.addEventListener('alpine:init', () => {\n                Alpine.data('dropdown', () => ({\n                    open: false,\n\n                    toggle() {\n                        this.open = ! this.open\n                    }\n                }))\n            })\n\n            AOS.init();\n\n            AOS.init({\n                // Global settings:\n                disable: false, // accepts following values: 'phone', 'tablet', 'mobile', boolean, expression or function\n                startEvent: 'DOMContentLoaded', // name of the event dispatched on the document, that AOS should initialize on\n                initClassName: 'aos-init', // class applied after initialization\n                animatedClassName: 'aos-animate', // class applied on animation\n                useClassNames: false, // if true, will add content of `data-aos` as classes on scroll\n                disableMutationObserver: false, // disables automatic mutations' detections (advanced)\n                debounceDelay: 50, // the delay on debounce used while resizing window (advanced)\n                throttleDelay: 99, // the delay on throttle used while scrolling the page (advanced)\n\n\n                // Settings that can be overridden on per-element basis, by `data-aos-*` attributes:\n                delay: 0, // values from 0 to 3000, with step 50ms\n                duration: 1000, // values from 0 to 3000, with step 50ms\n                easing: 'ease', // default easing for AOS animations\n                once: false, // whether animation should happen only once - while scrolling down\n                mirror: false, // whether elements should animate out while scrolling past them\n                anchorPlacement: 'top-bottom', // defines which position of the element regarding to window should trigger the animation\n                });\n        </script>\n    </body>\n</html>\n"
  },
  {
    "path": "website/site/layouts/indexes/tag.html",
    "content": "<!DOCTYPE html>\n<!--[if lt IE 7]>      <html class=\"no-js lt-ie9 lt-ie8 lt-ie7\"> <![endif]-->\n<!--[if IE 7]>         <html class=\"no-js lt-ie9 lt-ie8\"> <![endif]-->\n<!--[if IE 8]>         <html class=\"no-js lt-ie9\"> <![endif]-->\n<!--[if gt IE 8]>      <html class=\"no-js\"> <![endif]-->\n\n<html>\n    <head>\n        {{ partial \"head.html\" .}}\n    </head>\n<body>\n    {{ partial \"nav.html\" .}}\n    <div class=\"container\">\n        <div class=\"row\">\n            <div class=\"col-md-9\">\n                <div class=\"well well-sm\">\n                    <strong>Items with tag <code>{{ .Title | lower }}</code></strong>\n                    <ul class=\"list-unstyled\" style=\"margin-top: 20px\">\n                        {{ range .Data.Pages }}\n                            {{ .Render \"li\" }}\n                        {{ end}}\n                    </ul>\n                </div>\n            </div>\n        </div>\n    </div>\n</body>\n</html>\n"
  },
  {
    "path": "website/site/layouts/partials/blogauthor.html",
    "content": "{{ .name }}\n-\n<a href=\"https://community.mattermost.com/core/messages/@{{ .community }}\" target=\"_blank\">\n    @{{ .community }}\n</a>\non\n<a href=\"https://community.mattermost.com/signup_user_complete\" target=\"_blank\">\n    community.mattermost.com\n</a>\nand\n<a href=\"https://github.com/{{ .github  }}\" target=\"_blank\">\n    @{{ .github  }}\n</a>\non GitHub\n"
  },
  {
    "path": "website/site/layouts/partials/footer.html",
    "content": "<footer class='py-5'>\n    <div class=\"container\">\n        <div class='d-flex justify-content-between'>\n            <a href=\"/\"><img class='header__logo' src=\"/img/logo.svg?version=v2\" alt=\"Logo\" width=\"180\"></a>\n            <div>\n                <a href=\"https://github.com/mattermost/focalboard#readme-ov-file\" target='_blank'>\n                    Read me\n                </a>\n                |\n                <a href=\"https://github.com/mattermost/focalboard/blob/main/LICENSE.txt\" target='_blank'>\n                    License\n                </a>\n            </div>\n        </div>\n    </div>\n</footer>\n\n<!-- JS -->\n<script src=\"https://code.jquery.com/jquery-3.5.1.min.js\"\n    integrity=\"sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=\" crossorigin=\"anonymous\"></script>\n<script src=\"{{ \"js/tabs.js\" | absURL }}\"></script>\n<script src=\"{{ \"js/main.js\" | absURL }}\"></script>\n"
  },
  {
    "path": "website/site/layouts/partials/hanchor.html",
    "content": "{{ . | replaceRE \"(<h[1-9] id=\\\"([^\\\"]+)\\\".+)(</h[1-9]+>)\" `${1}&nbsp;<a class=\"hanchor fa fa-link\" ariaLabel=\"Anchor\" href=\"#${2}\"></i></a> ${3}` | safeHTML }}\n"
  },
  {
    "path": "website/site/layouts/partials/head.html",
    "content": "<meta charset=\"utf-8\">\n<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n<title> {{ .Title }}</title>\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"> {{ with .Site.Params.author }}\n<meta name=\"author\" content=\"{{ . }}\">{{ end }} {{ with .Site.Params.description }}\n<meta name=\"description\" content=\"{{ . }}\">{{ end }} {{ with .Site.LanguageCode }}\n<meta http-equiv=\"content-language\" content=\"{{ . }}\" />{{ end }}\n\n<!-- canonical link tag -->\n{{ if .Params.canonicalUrl }}\n<link rel=\"canonical\" href=\"{{ .Params.canonicalUrl }}\">\n{{ else }}\n<link rel=\"canonical\" href=\"{{ .Permalink }}\">\n{{ end }}\n\n<link rel=\"shortcut icon\" type=\"image/png\" href=\"{{ \"img/favicon-32x32.png\" | absURL }}\" />\n\n<link href=\"https://fonts.googleapis.com/css2?family=Fira+Mono:wght@400;700&family=Noto+Sans:wght@300;400&display=swap\" rel=\"stylesheet\">\n\n<!-- Lottie player -->\n<script src=\"https://unpkg.com/@lottiefiles/lottie-player@latest/dist/lottie-player.js\"></script>\n\n<!-- CSS  -->\n<link rel=\"stylesheet\" href=\"{{ \"css/bootstrap.min.css\" | absURL }}\">\n<link rel=\"stylesheet\" href=\"{{ \"css/tabs.css\" | absURL }}\">\n<link rel=\"stylesheet\" href=\"{{ \"css/bar.css\" | absURL }}\">\n<link rel=\"stylesheet\" href=\"{{ \"css/styles.css\" | absURL }}?version=v9\">\n<link rel=\"stylesheet\" href=\"{{ \"css/code.css\" | absURL }}\">\n<link rel=\"stylesheet\" href=\"{{ \"css/note.css\" | absURL }}\">\n\n<script async defer src=\"https://buttons.github.io/buttons.js\"></script>\n<script async src=\"//cdn.bizible.com/scripts/bizible.js\"></script>\n<!-- Global site tag (gtag.js) - Google Analytics -->\n<script async src=\"https://www.googletagmanager.com/gtag/js?id=UA-64458817-2\"></script>\n<script>\n    window.dataLayer = window.dataLayer || [];\n    function gtag() {dataLayer.push(arguments);}\n    gtag('js', new Date());\n    gtag('config', 'UA-64458817-2');\n</script>\n\n<!-- Lucky Orange Tracking -->\n<script async defer src=\"https://tools.luckyorange.com/core/lo.js?site-id=62fa07bd\"></script>\n\n<!-- Marketo Munchkin Tracking code for Marketing Team -->\n<script type=\"text/javascript\">\n    (function() {\n    var didInit = false;\n    function initMunchkin() {\n    if(didInit === false) {\n    didInit = true;\n    Munchkin.init('161-FBE-733');\n    }\n    }\n    var s = document.createElement('script');\n    s.type = 'text/javascript';\n    s.async = true;\n    s.src = '//munchkin.marketo.net/munchkin.js';\n    s.onreadystatechange = function() {\n    if (this.readyState == 'complete' || this.readyState == 'loaded') {\n    initMunchkin();\n    }\n    };\n    s.onload = initMunchkin;\n    document.getElementsByTagName('head')[0].appendChild(s);\n    })();\n</script>\n\n<!-- Engagio Tracking code for Marketing Team -->\n<script type=\"text/javascript\" charset=\"utf-8\">\n    var _eiq = _eiq || [];\n    var _engagio_settings = {\n      accountId: \"cb6a404b72e9141b70d1f82abc04db92b4e56238\"\n    };\n    (function() {\n      var ei = document.createElement('script'); ei.type = 'text/javascript'; ei.async = true;\n      ei.src = ('https:' == document.location.protocol ? 'https://' : 'http://') + 'web-analytics.engagio.com/js/ei.js';\n      var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ei, s);\n    })();\n  </script>\n"
  },
  {
    "path": "website/site/layouts/partials/hero.html",
    "content": "<div class=\"homepage__intro homepage__section\">\n\t<div class=\"container-fluid\">\n\t\t<div class=\"homepage-intro__text\" data-aos=\"fade-left\">\n\t\t\t<h1>\n\t\t\t\tOpen source <br> project management for <span style='color: #ffbc1f'>technical teams</span>\n\t\t\t</h1>\n\t\t\t<p>\n                Keep everything and everyone on track.\n\t\t\t</p>\n\n            <a href=\"https://github.com/mattermost/focalboard\" target='_blank'><button class='btn btn-primary btn-xl'>Get started</button></a>\n\n            <div class='homepage-intro__github'><i class=\"fa fa-star\"></i> Over 13k stars on <a target='_blank' href=\"https://github.com/mattermost/focalboard\" class='mx-1 text-white'>GitHub</a> and counting!</div>\n\t\t</div>\n\n        <div class='homepage-intro__video' data-aos=\"fade-right\">\n            <lottie-player src=\"lottie/intro-section.json\" background=\"transparent\" speed=\"1\"  style=\"width: 957.5px\" loop autoplay></lottie-player>\n        </div>\n\t</div>\n</div>\n"
  },
  {
    "path": "website/site/layouts/partials/intro.html",
    "content": "<div class='section-half'>\n    <div class=\"section-half__container\">\n        <div data-aos=\"fade-right\">\n            <img src=\"img/3d-icons/rocket.svg\" alt=\"Rocket Icon\">\n            <h2>Accelerate productivity</h2>\n            <p>\n                Centralize, plan, and track all your work in one place. Focalboard helps your organization maintain a single source of truth, so your teams stay aligned to complete tasks, reach milestones, and achieve their goals.\n            </p>\n        </div>\n        <div class=\"section-half__visual\" data-aos=\"fade-left\">\n            <video src=\"video/accelerate1.mp4\" autoplay loop muted width='640px'></video>\n        </div>\n    </div>\n</div>\n\n<div class='section-half section-half--reverse'>\n    <img src=\"img/bgroundedleft.svg\" alt=\"Background rounded left Element\" class='bgElementLeft'>\n    <div class=\"section-half__container\">\n        <div data-aos=\"fade-right\">\n            <img src=\"img/3d-icons/gasp.svg\" alt=\"Wow Icon\">\n            <h2>Organize and visualize work, your way</h2>\n            <p>\n                Work in the way that suits you best. Manage all your tasks on a Kanban, table, gallery, and calendar view. Focus on the highest priority items with board filters, and save an unlimited number of filtered views for quick access later.\n            </p>\n        </div>\n        <div class=\"section-half__visual\" data-aos=\"fade-left\">\n            <div class=\"view-tabs\">\n                <button class='btn btn-yellow btn-lg' x-bind:class=\"{ 'active': boardView == 1 }\" @click='boardView = 1, clearInterval(viewsInterval)'>Board</button>\n                <button class='btn btn-yellow btn-lg' x-bind:class=\"{ 'active': boardView == 2 }\" @click='boardView = 2, clearInterval(viewsInterval)'>List</button>\n                <button class='btn btn-yellow btn-lg' x-bind:class=\"{ 'active': boardView == 3 }\" @click='boardView = 3, clearInterval(viewsInterval)'>Calendar</button>\n                <button class='btn btn-yellow btn-lg' x-bind:class=\"{ 'active': boardView == 4 }\" @click='boardView = 4, clearInterval(viewsInterval)'>Gallery</button>\n            </div>\n            <div class='view-content'>\n                <img x-cloak x-transition.duration.300 x-show='boardView == 1' src=\"img/views/boardview.png\" alt=\"Board View\">\n                <img x-cloak x-transition.duration.300 x-show='boardView == 2' src=\"img/views/listview.png\" alt=\"List View\">\n                <img x-cloak x-transition.duration.300 x-show='boardView == 3' src=\"img/views/calendarview.png\" alt=\"Calendar View\">\n                <img x-cloak x-transition.duration.300 x-show='boardView == 4' src=\"img/views/galleryview.png\" alt=\"Gallery View\">\n            </div>\n        </div>\n    </div>\n</div>\n\n<div class='section-half'>\n    <img src=\"img/bgroundedright.svg\" alt=\"Background rounded right Element\" class='bgElementRight'>\n    <div class=\"section-half__container\" data-aos=\"fade-right\">\n        <div>\n            <img src=\"img/3d-icons/handshake.svg\" alt=\"Handshake Icon\">\n            <h2>Align your teams with real-time collaboration</h2>\n            <p>\n                Keep everyone in sync with card comments, @mention teammates to get their attention, and set board permissions to share your board with the entire team or specific individuals.\n            </p>\n        </div>\n        <div class=\"section-half__visual\"  data-aos=\"fade-left\">\n            <video src=\"video/comment-animation.mp4\" class='align-video' autoplay loop muted></video>\n        </div>\n    </div>\n</div>\n\n<div class='template-picker-container' data-aos=\"zoom-in\">\n    <img src=\"img/bgroundedcenter.svg\" alt=\"Background rounded center Element\" class='bgElementCenter'>\n    <div class=\"container-wide\">\n        <h2 class='mb-4'>\n            <span>Tons of templates to get you started</span>\n            <img class='ml-3 position-relative' style='top: -6px' src=\"img/3d-icons/raisedhands.svg\" alt=\"Raised hands icon\">\n        </h2>\n        <p>Get started fast with our pre-built templates or create a fully custom board from scratch.</p>\n\n        <div class=\"template-picker\">\n            <div class=\"template-picker__sidebar\">\n                <div class=\"template-picker-item\" x-bind:class=\"{ 'active': template == 1 }\" @click='template = 1'>\n                    <span class='template-picker-item__emoji'>🎯</span>\n                    <span class='template-picker-item__title'>Project Tasks</span>\n                </div>\n                <div class=\"template-picker-item\" x-bind:class=\"{ 'active': template == 2 }\" @click='template = 2'>\n                    <span class='template-picker-item__emoji'>📅</span>\n                    <span class='template-picker-item__title'>Content Calendar</span>\n                </div>\n                <div class=\"template-picker-item\" x-bind:class=\"{ 'active': template == 3 }\" @click='template = 3'>\n                    <span class='template-picker-item__emoji'>⛳</span>\n                    <span class='template-picker-item__title'>Company Goals & OKRs</span>\n                </div>\n                <div class=\"template-picker-item\" x-bind:class=\"{ 'active': template == 4 }\" @click='template = 4'>\n                    <span class='template-picker-item__emoji'>🗺️</span>\n                    <span class='template-picker-item__title'>Roadmap</span>\n                </div>\n                <div class=\"template-picker-item\" x-bind:class=\"{ 'active': template == 5 }\" @click='template = 5'>\n                    <span class='template-picker-item__emoji'>🍩</span>\n                    <span class='template-picker-item__title'>Meeting Agenda</span>\n                </div>\n                <div class=\"template-picker-item\" x-bind:class=\"{ 'active': template == 6 }\" @click='template = 6'>\n                    <span class='template-picker-item__emoji'>👋</span>\n                    <span class='template-picker-item__title'>Welcome to Focalboard</span>\n                </div>\n            </div>\n            <div class=\"template-picker__preview\">\n                <img x-cloak x-transition.duration.500 x-show='template == 1' src=\"img/templates/project-tasks.png\" alt=\"Project Tasks Template\">\n                <img x-cloak x-transition.duration.500 x-show='template == 2' src=\"img/templates/content-calendar.png\" alt=\"Content Calendar Template\">\n                <img x-cloak x-transition.duration.500 x-show='template == 3' src=\"img/templates/company-goals.png\" alt=\"Company Goals Template\">\n                <img x-cloak x-transition.duration.500 x-show='template == 4' src=\"img/templates/roadmap.png\" alt=\"Roadmap Template\">\n                <img x-cloak x-transition.duration.500 x-show='template == 5' src=\"img/templates/meeting-agenda.png\" alt=\"Meeting Agenda Template\">\n                <img x-cloak x-transition.duration.500 x-show='template == 6' src=\"img/templates/welcome.png?version=v3\" alt=\"Welcome to Boards Template\">\n            </div>\n        </div>\n    </div>\n\n</div>\n\n<div class='section-full text-center' data-aos=\"fade-up\">\n    <div class=\"container-fluid\">\n        <h2>Feature Highlights</h2>\n        <div class='feature-bullets'>\n            <div>Unlimited boards</div>\n            <div>Group, filter, and sort tasks</div>\n            <div>File sharing</div>\n            <div>Unlimited custom attributes</div>\n            <div>Customizable templates</div>\n            <div>Meeting notes</div>\n            <div>Project cards & tasks</div>\n            <div>Archiving & back-up snapshots</div>\n            <div>Priority labeling</div>\n            <div>User permissions</div>\n            <div>Team and direct messaging</div>\n            <div>Multi-team views</div>\n        </div>\n    </div>\n</div>\n\n<div class='mmtrust-banner mentioned-in'  data-aos=\"fade-up\">\n    <div class=\"container-wide\">\n        <div class=\"title\">MENTIONED IN</div>\n        <div class='mmtrust_banner__logos align-items-center d-flex'>\n            <a target=\"_blank\" href=\"https://www.producthunt.com/products/focalboard\"><img src=\"img/mentioned/producthunt.png\" alt=\"\"></a>\n            <a target=\"_blank\" href=\"https://venturebeat.com/software/mattermost-moves-beyond-messaging-to-unify-developer-collaboration-and-productivity/\"><img src=\"img/mentioned/venturebeat.png\" alt=\"\"></a>\n            <a target=\"_blank\" href=\"https://www.techrepublic.com/article/focalboard-kanban-tool-better-task-management/\"><img src=\"img/mentioned/techrepublic.png\" alt=\"\"></a>\n            <a target=\"_blank\" href=\"https://news.ycombinator.com/item?id=26499062\"><img src=\"img/mentioned/ycombinator.png\" alt=\"\"></a>\n            <a target=\"_blank\" href=\"https://thenewstack.io/how-to-build-an-open-source-community/\"><img src=\"img/mentioned/newstack.png\" alt=\"\"></a>\n        </div>\n    </div>\n</div>\n\n<div class='section-half section-half--reverse'>\n    <div class=\"section-half__container\">\n        <div data-aos=\"fade-left\">\n            <img src=\"img/3d-icons/development.svg\" alt=\"Open source Icon\">\n            <h2>Focalboard is 100% open source</h2>\n            <p>\n                Focalboard is an open source alternative to tools like Asana, Trello, and Notion. Check out the <a target='_blank' href=\"https://github.com/mattermost/focalboard\">source code on GitHub</a>, and contribute to the future of this project. Join the discussion on the <a href=\"https://community.mattermost.com/core/channels/focalboard\" target=\"_blank\">Mattermost community</a> to interact with other Mattermost and Focalboard contributors and users.\n            </p>\n        </div>\n        <div class=\"section-half__visual\" data-aos=\"fade-right\">\n            <img src=\"https://user-images.githubusercontent.com/10912003/164514752-7ec8cbbd-844d-47b3-964f-dcf2e18e60b5.png\" alt=\"Open source image\">\n        </div>\n    </div>\n</div>\n\n{{/*  <div class='section-half roadmap-section'>\n    <div class=\"section-half__container\">\n        <div data-aos=\"fade-left\">\n            <img src=\"img/3d-icons/firstbump.svg\" alt=\"Fistbump Icon\">\n            <h2>What’s next for Boards</h2>\n            <p>\n                Here are some things that we have on our roadmap and plan to release soon!\n            </p>\n            <div class=\"view-tabs\">\n                <button class='btn btn-yellow btn-lg' x-bind:class=\"{ 'active': roadmapItem == 1 }\" @click='roadmapItem = 1, clearInterval(roadmapInterval)'>Standard Properties</button>\n                <button class='btn btn-yellow btn-lg' x-bind:class=\"{ 'active': roadmapItem == 2 }\" @click='roadmapItem = 2, clearInterval(roadmapInterval)'>Card Dependencies</button>\n                <button class='btn btn-yellow btn-lg' x-bind:class=\"{ 'active': roadmapItem == 3 }\" @click='roadmapItem = 3, clearInterval(roadmapInterval)'>Analytics and Reporting</button>\n            </div>\n        </div>\n        <div class=\"section-half__visual\" data-aos=\"fade-right\">\n            <img x-cloak x-transition.duration.500\n            x-show='roadmapItem == 1' src=\"img/roadmap/standard-properties.png\" alt=\"Standard Properties Image\">\n            <img x-cloak x-transition.duration.500\n            x-show='roadmapItem == 2' src=\"img/roadmap/card-dependencies.png\" class='image-shadow' alt=\"Card Dependencies Image\">\n            <img x-cloak x-transition.duration.500\n            x-show='roadmapItem == 3' src=\"img/roadmap/analytics.png\" alt=\"Analytics and Reporting Image\" width='800px'>\n        </div>\n    </div>\n</div>  */}}\n\n<div class=\"help-section\" data-aos=\"fade-up\">\n    <div class='section-half'>\n        <div class=\"section-half__container\">\n            <div>\n                <h2>Need help?</h2>\n                <p>\n                    We have tons of documentation to help you make the most out of focalboard, head over to our documentation site to learn more about focalboard.\n                </p>\n                <a href='https://github.com/mattermost/focalboard/#readme' target='_blank'>\n                    <button class='btn btn-primary btn-xl'>\n                        <span>Go to docs</span>\n                        <img src=\"img/arrow-right.svg\" alt=\"Arrow right\">\n                    </button>\n                </a>\n            </div>\n            <div class=\"section-half__visual\">\n                <img src='img/needhelp.svg' alt='Get Help Image'>\n            </div>\n        </div>\n    </div>\n</div>\n"
  },
  {
    "path": "website/site/layouts/partials/mailinglist.html",
    "content": "{{ if .Site.Params.mailinglist.enable }}\n<section style=\"min-width:800px;width:50%;margin:auto;padding-bottom:100px;\">\n<iframe frameborder=\"0\" scrolling=\"no\" marginheight=\"0\" marginwidth=\"0\" src=\"https://app.mailjet.com/widget/iframe/2mC4/4u5\" width=\"100%\" height=\"242\"></iframe>\n</section>\n{{ end }}\n"
  },
  {
    "path": "website/site/layouts/partials/nav.html",
    "content": "{{ if .Site.Params.notification.enable }}\n<header class=\"with-notification-bar\">\n{{ else }}\n<header>\n{{ end }}\n    <div class=\"container\">\n        <a href=\"/\"><img class='header__logo' src=\"/img/logo.svg?version=v3\" alt=\"Logo\" width=\"180\"></a>\n        <div class=\"header__menu-toggle\">\n            <div class=\"menu-bar\"></div>\n            <div class=\"menu-bar\"></div>\n            <div class=\"menu-bar\"></div>\n        </div>\n        <ul class=\"header__links--right\">\n            {{ range .Site.Menus.postpend }}\n                <li><a class=\"external\" rel=\"noopener\" href=\"{{ .URL }}\">{{ .Name }}</a></li>\n            {{ end }}\n            <li class='header__feedback'>\n                <a target='_blank' href='https://github.com/mattermost/focalboard'><button class='btn btn-primary'>Get started</button></a>\n            </li>\n        </ul>\n    </div>\n</header>\n\n{{ partial \"notification.html\" .}}\n"
  },
  {
    "path": "website/site/layouts/partials/notification.html",
    "content": "{{ if .Site.Params.notification.enable }}\n<div class='notification-bar sticky-top'>\n    <div class=\"notification-bar__content\">\n        <a class=\"notification-bar__close\">\n            <span aria-hidden=\"true\">×</span>\n        </a>\n        <a href=\"{{ .Site.Params.notification.url | absURL }}\">\n            {{ .Site.Params.notification.text }}\n        </a>\n    </div>\n</div>\n\n{{ end }}\n"
  },
  {
    "path": "website/site/layouts/partials/page-edit.html",
    "content": "{{ if .File }}\n<a\n    href=\"{{.Site.Params.ghrepo}}edit/main/website/site/content/{{ .File.Path }}\"\n    class=\"float-right edit-github\"\n    target=\"_blank\"\n>\n    Edit on GitHub\n</a>\n{{ end }}\n"
  },
  {
    "path": "website/site/layouts/partials/series_link.html",
    "content": "{{ if .Params.series }}\n    {{ $cur_page_num := 1 }}\n    {{ $total_page_num := 0 }}\n    {{ $prev_link := \"\" }}\n    {{ $prev_title := \"\" }}\n    {{ $next_link := \"\" }}\n    {{ $next_title := \"\" }}\n\n    {{ range where .Site.RegularPages.ByDate \"Params.series\" .Params.series }}\n        {{ $total_page_num = add $total_page_num 1 }}\n        {{ if gt $.Date.Unix .Date.Unix }}\n            {{ $cur_page_num = add $cur_page_num 1 }}\n            {{ $prev_link = .Permalink }}\n            {{ $prev_title = .Title }}\n        {{ end }}\n    {{ end }}\n    {{ range where .Site.RegularPages.ByDate.Reverse \"Params.series\" .Params.series }}\n        {{ if lt $.Date.Unix .Date.Unix }}\n            {{ $next_link = .Permalink }}\n            {{ $next_title = .Title }}\n        {{ end }}\n    {{ end }}\n    {{ if or $next_link $prev_link }}\n        <p>\n            Part {{ $cur_page_num }} of {{ $total_page_num }} in the <b>{{ .Params.series }}</b> series.\n        </p>\n        <p>\n        {{ if $prev_link }}\n            <a href=\"{{ $prev_link }}\"><i class=\"fa fa-angle-double-left\"></i>&nbsp;Part {{ sub $cur_page_num 1 }}</a>\n        {{ end }}\n        {{ if and $next_link $prev_link }}\n                |\n        {{ end }}\n        {{ if $next_link }}\n            <a href=\"{{ $next_link }}\">Part {{ add $cur_page_num 1 }}&nbsp;<i class=\"fa fa-angle-double-right\"></i></a></p>\n        {{ end }}\n    {{ end }}\n{{ end }}\n"
  },
  {
    "path": "website/site/layouts/partials/sidebar.html",
    "content": "<div class=\"col-lg-3 sidebar\">\n    <div class=\"sidebar__menu-toggle\">\n        {{ .Title }}\n        <i class=\"fa fa-bars pull-right\"></i>\n    </div>\n    <div class=\"row sidebar__links\">\n        {{ range where .Site.Params.sidebar.item \"name\" $.Section }}\n            {{ range where $.Site.Pages \".Params.section\" .name }}\n                {{ $active := (eq $.Title .Title) }}\n                {{ $expanded := or (eq $.Params.subsection .Title) $active (eq $.Parent.Params.subsection .Title) }}\n\n                {{ $.Scratch.Set \"icon\" \"fa-plus-square-o\" }}\n                {{ if $expanded }}\n                    {{ $.Scratch.Set \"icon\" \"fa-minus-square-o\" }}\n                {{ end }}\n\n                {{if eq (len (.Pages)) 0}}\n                    {{ $.Scratch.Set \"iconstyle\" \"display: none\" }}\n                {{ else }}\n                    {{ $.Scratch.Set \"iconstyle\" \"\" }}\n                {{ end }}\n\n                <span class=\"item {{ if $active }}active{{ end }}\">\n                    <i class=\"sub-menu__toggle fa {{$.Scratch.Get \"icon\"}}\" aria-hidden=\"true\" style=\"{{$.Scratch.Get \"iconstyle\"}}\"></i>\n                    {{ if $active }}\n                        <a class=\"active\" href=\"#\"> {{ .Title }}</a>\n                    {{ else }}\n                        <a href=\"{{ .Permalink }}\"> {{ .Title }}</a>\n                    {{ end }}\n                </span>\n                <ul class=\"sub-menu\" {{ if $expanded }}style=\"display: block\"{{ end }}>\n                    {{ range .Pages.ByWeight }}\n                        {{ $subactive := (eq $.Title .Title) }}\n                        {{ $subexpanded := or (eq .Title $.Params.subsection) $subactive }}\n\n                        {{ $.Scratch.Set \"subicon\" \"fa-plus-square-o\" }}\n                        {{ if $subexpanded }}\n                            {{ $.Scratch.Set \"subicon\" \"fa-minus-square-o\" }}\n                        {{ end }}\n\n                        {{if eq (len (.Pages)) 0}}\n                            {{ $.Scratch.Set \"iconstyle\" \"display: none\" }}\n                        {{ else }}\n                            {{ $.Scratch.Set \"iconstyle\" \"\" }}\n                        {{ end }}\n\n                        <span class=\"item {{ if $subactive }}active{{ end }}\">\n                            <i class=\"sub-menu__toggle fa {{$.Scratch.Get \"subicon\"}}\" aria-hidden=\"true\" style=\"{{$.Scratch.Get \"iconstyle\"}}\"></i>\n                            {{ if $subactive }}\n                                <a class=\"active\" href=\"{{ .Permalink }}\"> {{ .Title }}</a></span>\n                            {{ else }}\n                                <a href=\"{{ .Permalink }}\"> {{ .Title }}</a></span>\n                            {{ end }}\n                        </span>\n                        <ul class=\"sub-menu\" {{ if $subexpanded }}style=\"display: block\"{{ end }}>\n                            {{ range .Pages.ByWeight }}\n                                <a class=\"item {{ if eq $.Title .Title }}active{{ end }}\" href=\"{{ .Permalink }}\">{{ .Title }}</a>\n                            {{ end }}\n                        </ul>\n                    {{ end }}\n                </ul>\n            {{ end }}\n        {{ end }}\n    </div>\n</div>\n"
  },
  {
    "path": "website/site/layouts/shortcodes/baseurl.html",
    "content": "{{ .Page.Site.BaseURL }}\n"
  },
  {
    "path": "website/site/layouts/shortcodes/bignumber.html",
    "content": "<div class=\"steps-row\">\n    <div class=\"steps-number\">{{ .Get \"number\" }}</div>\n    <div>\n        <div>\n            <h4 class=\"steps-header\">{{ .Get \"title\" | markdownify }}</h4>\n        </div>\n        <div class=\"content\">\n            {{ .Get \"content\" | markdownify }}\n        </div>\n    </div>\n</div>\n"
  },
  {
    "path": "website/site/layouts/shortcodes/blogurl.html",
    "content": "{{ .Page.Site.BaseURL }}blog\n"
  },
  {
    "path": "website/site/layouts/shortcodes/content.html",
    "content": "{{ .Get 0 | readFile | markdownify }}\n"
  },
  {
    "path": "website/site/layouts/shortcodes/md.html",
    "content": "{{ .Inner }}\n"
  },
  {
    "path": "website/site/layouts/shortcodes/note.html",
    "content": "<aside class=\"note\">\n    <div class=\"note-content\">\n        <strong>{{ .Get 0 }}</strong><br>\n        {{ .Inner | markdownify }}\n    </div>\n</aside>\n"
  },
  {
    "path": "website/site/static/css/bar.css",
    "content": ".notification-bar {\n    text-align: center;\n    display: flex;\n    width: 100%;\n    align-items: center;\n    justify-content: center;\n    height: 64px;\n    white-space: nowrap;\n    background-image: -webkit-gradient(linear,left top,right top,from(#f0f6ff),to(#d7e7ff));\n    background-image: linear-gradient(90deg,#f0f6ff 0,#d7e7ff 100%);\n    position: fixed;\n    z-index: 6;\n}\n\n.notification-bar.closed {\n    display: none;\n}\n\n.notification-bar__content {\n    padding: 2rem 2.4rem;\n    width: 1080px;\n    font-size: 16px;\n    font-weight: bold;\n    line-height: 1.4;\n}\n\n.notification-bar a {\n    color: #0058cc;\n}\n\n.notification-bar__close {\n    font-size: 36px;\n    font-weight: 300;\n    position: absolute;\n    cursor: pointer;\n    right: 0px;\n    opacity: .3;\n    top: 0;\n    width: 64px;\n    line-height: 64px;\n}\n\n.notification-bar-hide:hover {\n    opacity: .5;\n}\n"
  },
  {
    "path": "website/site/static/css/code.css",
    "content": "code {\n    padding: .2rem;\n}\n"
  },
  {
    "path": "website/site/static/css/markdown.css",
    "content": "table {\n    padding: 0;\n}\n\ntable tr {\n    border-top: 1px solid #cccccc;\n    background-color: white;\n    margin: 0;\n    padding: 0;\n}\n\ntable tr:nth-child(2n) {\n    background-color: #f8f8f8;\n}\n\ntable tr th {\n    font-weight: bold;\n    border: 1px solid #cccccc;\n    text-align: left;\n    margin: 0;\n    padding: 6px 13px;\n}\n\ntable tr td {\n    border: 1px solid #cccccc;\n    text-align: left;\n    margin: 0;\n    padding: 6px 13px;\n}\n\ntable tr th :first-child, table tr td :first-child {\n    margin-top: 0;\n}\n\ntable tr th :last-child, table tr td :last-child {\n    margin-bottom: 0;\n}\n"
  },
  {
    "path": "website/site/static/css/note.css",
    "content": ".note {\n    border-left-width: 3px;\n    border-left-style: solid;\n    position: relative;\n    border-color: #0594CB;\n\n    display: block;\n}\n.note #exclamation-icon,\n.warning #exclamation-icon {\n\n    fill: #0594CB;\n    position: absolute;\n    top: 35%;\n    left: -12px;\n    /*background-color: white;*/\n}\n\n.note-content {\n    display: block;\n    margin: 2px;\n    padding: .125em 1em;\n    /*margin-left: 1em;*/\n    margin-top: 1em;\n    margin-bottom: 1em;\n    overflow-x: auto;\n    /*font-size: .9375em;*/\n    background-color: rgba(0, 0, 0, .05);\n}\n"
  },
  {
    "path": "website/site/static/css/partials/banners.css",
    "content": ".mmtrust-banner {\n    margin-top: 80px;\n    text-align: center;\n}\n\n.mmtrust-banner .title {\n    font-size: 14px;\n    font-weight: bold;\n    opacity: .6;\n}\n\n.mmtrust_banner__logos {\n    display: flex;\n    flex-wrap: wrap;\n    justify-content: center;\n    margin-top: 40px;\n    gap: 120px;\n}\n\n.mmtrust-banner.mentioned-in {\n    margin-top: 140px;\n}\n\n.mmtrust-banner.mentioned-in img {\n    opacity: 1;\n    filter: none;\n    -webkit-filter: grayscale(0%);\n    -moz-filter:    grayscale(0%);\n    -ms-filter:     grayscale(0%);\n    -o-filter:      grayscale(0%);;\n}\n\n.mmtrust_banner__logos img {\n    transition: all ease 0.25s;\n    opacity: 0.4;\n    filter: none;\n    -webkit-filter: grayscale(100%);\n    -moz-filter:    grayscale(100%);\n    -ms-filter:     grayscale(100%);\n    -o-filter:      grayscale(100%);;\n}\n\n.mmtrust_banner__logos img:hover {\n    opacity: 1;\n    filter: none;\n    -webkit-filter: grayscale(0%);\n    -moz-filter:    grayscale(0%);\n    -ms-filter:     grayscale(0%);\n    -o-filter:      grayscale(0%);\n}\n\n@media (max-width: 992px) {\n    .mmtrust_banner__logos {\n        gap: 40px;\n    }\n}\n"
  },
  {
    "path": "website/site/static/css/partials/base.css",
    "content": "html {\n    font-size: 10px;\n}\n\nbody {\n    font-size: 1.5rem;\n    display: flex;\n    flex-direction: column;\n    min-height: 100vh;\n}\n\nimg {\n    max-width: 100%;\n}\n\nh1,\nh2,\nh3,\nh4 {\n    font-family: var(--tradeGothic);\n}\n\nh1 {\n    font-size: 32px;\n    font-weight: 800;\n}\n\nh2 {\n    font-weight: 800;\n    font-size: 28px;\n    margin-bottom: 0.8rem;\n}\n\nh3 {\n    font-size: 24px;\n}\n\nh4 {\n    font-size: 22px;\n}\n\nh1:first-child,\nh2:first-child,\nh3:first-child,\nh4:first-child,\nh5:first-child,\nh6:first-child {\n    margin-top: 0;\n}\n\n\n.sr-only {\n    position: absolute;\n    width: 1px;\n    height: 1px;\n    padding: 0;\n    margin: -1px;\n    overflow: hidden;\n    clip: rect(0, 0, 0, 0);\n    border: 0;\n}\n\n.sr-only-focusable:active,\n.sr-only-focusable:focus {\n    position: static;\n    width: auto;\n    height: auto;\n    margin: 0;\n    overflow: visible;\n    clip: auto;\n}\n\n.weight--300 {\n    font-weight: 300;\n}\n\n.box-dropshadow {\n    -moz-box-shadow: 0 40px 77px 0 rgba(0, 0, 0, 0.1), 0 27px 24px 0 rgba(0, 0, 0, 0.1);\n    -webkit-box-shadow: 0 40px 77px 0 rgba(0, 0, 0, 0.1), 0 27px 24px 0 rgba(0, 0, 0, 0.1);\n    box-shadow: 0 40px 77px 0 rgba(0, 0, 0, 0.1), 0 27px 24px 0 rgba(0, 0, 0, 0.1);\n}\n\n.btn-dropshadow {\n    -moz-box-shadow: 0 17px 50px 0 rgba(0, 0, 0, 0.1), 0 12px 15px 0 rgba(0, 0, 0, 0.1);\n    -webkit-box-shadow: 0 17px 50px 0 rgba(0, 0, 0, 0.1), 0 12px 15px 0 rgba(0, 0, 0, 0.1);\n    box-shadow: 0 17px 50px 0 rgba(0, 0, 0, 0.1), 0 12px 15px 0 rgba(0, 0, 0, 0.1);\n}\n\na {\n    color: #186CDA;\n}\n\na:hover {\n    text-decoration: none;\n}\n\n.container-fluid {\n    max-width: 1140px;\n    padding-left: 32px;\n    padding-right: 32px;\n}\n\n.container-wide {\n    max-width: 1384px;\n    margin: 0 auto;\n    padding: 0 32px;\n}\n\n.container {\n    padding: 0 32px;\n    max-width: 100%;\n}\n\n@media (min-width: 1200px) {\n    .container,\n    .container-fluid {\n        max-width: 1170px;\n    }\n}\n\n#wrapper {\n    flex: 1;\n    padding-bottom: 3em;\n}\n\np {\n    margin-bottom: 24px;\n}\n\np,\na,\nli {\n    font-family: var(--notoSans);\n}\n\np + ul {\n    margin-top: -24px;\n}\n\ncode {\n    font-family: var(--firaMono);\n}\n\n.hanchor {\n    font-size: 70%;\n    visibility: hidden;\n    color: silver;\n}\n\nh1:hover a,\nh2:hover a,\nh3:hover a,\nh4:hover a {\n    visibility: visible\n}\n\ntable {\n    width: 100%;\n    max-width: 100%;\n    margin-bottom: 20px;\n}\n\ntable>thead>tr>th,\ntable>thead>tr>td,\ntable>tbody>tr>th,\ntable>tbody>tr>td,\ntable>tfoot>tr>th,\ntable>tfoot>tr>td {\n    padding: 8px;\n    line-height: 1.42857;\n    vertical-align: top;\n    border-top: 1px solid #ddd;\n    font-family: var(--notoSans);\n}\n\ntable>thead>tr>th {\n    vertical-align: bottom;\n    border-bottom: 2px solid #ddd;\n}\n\ntable>caption+thead>tr:first-child>th,\ntable>caption+thead>tr:first-child>td,\ntable>colgroup+thead>tr:first-child>th,\ntable>colgroup+thead>tr:first-child>td,\ntable>thead:first-child>tr:first-child>th,\ntable>thead:first-child>tr:first-child>td {\n    border-top: 0;\n}\n\ntable>tbody+tbody {\n    border-top: 2px solid #ddd;\n}\n\ntable>tbody>tr:nth-of-type(odd) {\n    background-color: #f9f9f9;\n}\n\n[x-cloak] {\n    display: none !important;\n}\n"
  },
  {
    "path": "website/site/static/css/partials/blog.css",
    "content": ".blog-list .well-sm {\n    margin: 4rem 0;\n}\n\n.blog-item__header {}\n\n.blog-item__sidebar {\n    margin: 4px 0 4px 0;\n}\n\n.blog-item__title {\n    font-size: 1.5rem;\n    margin-bottom: .6rem;\n}\n\n.doc-content .doc-title {\n    margin-bottom: 24px;\n}\n\n\n.blog-item__count {\n    margin: 0 0 0 5px;\n    opacity: .6;\n}\n\n.blog-single .tags-sidebar {\n    font-size: .9rem;\n}\n\n.doc-content,\n.sidebar {\n    -webkit-font-smoothing: antialiased;\n\t-moz-osx-font-smoothing: grayscale;\n}\n\n.doc-content {\n    margin: 50px 0 0;\n}\n\n.doc-content h2,\n.doc-content h3 {\n    margin-top: 3rem;\n}\n\n.doc-content h3 {\n    font-size: 2.2rem;\n    font-weight: 700;\n}\n\n.doc-content img  {\n    border: 1px solid var(--black01);\n}\n\n.doc-content .edit-github {\n    padding-top: 0.9rem;\n}\n\n.doc-content .doc-title {\n    display: inline-block;\n}\n\n.sidebar+.doc-content {\n    padding-left: 16px;\n}\n\n.doc-content img {\n    margin: 1em 0;\n    max-width: 100%;\n}\n\n.doc-content pre {\n    background: #f0f3f3;\n    border-radius: 5px;\n    padding: 14px;\n    margin-bottom: 1.6rem;\n}\n"
  },
  {
    "path": "website/site/static/css/partials/buttons.css",
    "content": ".btn {\n    transition: var(--transition-btn);\n    font-family: var(--firaMono);\n    cursor: pointer;\n    padding: 0 20px;\n    font-size: 14px;\n    border-radius: 50px;\n    height: 40px;\n}\n\n.btn-rounded {\n    border-radius: 12px;\n}\n\n.btn.btn-xs {\n    padding: .8em 2em;\n}\n\n.btn .fa {\n    margin-left: 5px;\n}\n\n.btn-full {\n    width: 100%;\n    height: 100%;\n}\n\n.btn-white {\n    background: #fff;\n    color: #186CDA;\n}\n\n.btn-white:hover,\n.btn-white:focus,\n.btn-white:active {\n    background: rgba(255, 255, 255, 0.9);\n}\n\n.btn-xl {\n    font-size: 18px;\n    padding: 0 36px;\n    height: 64px;\n}\n\n.btn-lg {\n    height: 52px;\n    padding: 0 20px;\n    font-size: 16px;\n    font-weight: 600;\n}\n\n.btn-primary {\n    border: none;\n    background-color: var(--denimBtn);\n}\n\n.btn-tertiary {\n    background: rgba(var(--denimBtnRgb), 0.08);\n    color: #1C58D9;\n}\n\n.btn-tertiary:hover {\n    background: rgba(var(--denimBtnRgb), 0.12);\n}\n\n.btn-tertiary:active {\n    background: rgba(var(--denimBtnRgb), 0.16);\n}\n\n.btn-yellow {\n    background-color: rgb(var(--yellow));\n    border-radius: 12px;\n}\n"
  },
  {
    "path": "website/site/static/css/partials/fontawesome.css",
    "content": "/*!\n*  Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome\n*  License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)\n*/\n\n/* FONT PATH\n* -------------------------- */\n\n@import url(\"https://use.typekit.net/cng2uup.css\");\n\n@font-face {\n    font-family: 'FontAwesome';\n    src: url(\"../../fonts/fontawesome-webfont.eot?v=4.7.0\");\n    src: url(\"../../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0\") format(\"embedded-opentype\"), url(\"../../fonts/fontawesome-webfont.woff2?v=4.7.0\") format(\"woff2\"), url(\"../../fonts/fontawesome-webfont.woff?v=4.7.0\") format(\"woff\"), url(\"../../fonts/fontawesome-webfont.ttf?v=4.7.0\") format(\"truetype\"), url(\"../../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular\") format(\"svg\");\n    font-weight: normal;\n    font-style: normal;\n}\n\n.fa {\n    display: inline-block;\n    font: normal normal normal 14px/1 FontAwesome;\n    font-size: inherit;\n    text-rendering: auto;\n    -webkit-font-smoothing: antialiased;\n    -moz-osx-font-smoothing: grayscale;\n}\n\n/* makes the font 33% larger relative to the icon container */\n\n.fa-lg {\n    font-size: 1.33333333em;\n    line-height: 0.75em;\n    vertical-align: -15%;\n}\n\n.fa-2x {\n    font-size: 2em;\n}\n\n.fa-3x {\n    font-size: 3em;\n}\n\n.fa-4x {\n    font-size: 4em;\n}\n\n.fa-5x {\n    font-size: 5em;\n}\n\n.fa-fw {\n    width: 1.28571429em;\n    text-align: center;\n}\n\n.fa-ul {\n    padding-left: 0;\n    margin-left: 2.14285714em;\n    list-style-type: none;\n}\n\n.fa-ul>li {\n    position: relative;\n}\n\n.fa-li {\n    position: absolute;\n    left: -2.14285714em;\n    width: 2.14285714em;\n    top: 0.14285714em;\n    text-align: center;\n}\n\n.fa-li.fa-lg {\n    left: -1.85714286em;\n}\n\n.fa-border {\n    padding: .2em .25em .15em;\n    border: solid 0.08em #eeeeee;\n    border-radius: .1em;\n}\n\n.fa-pull-left {\n    float: left;\n}\n\n.fa-pull-right {\n    float: right;\n}\n\n.fa.fa-pull-left {\n    margin-right: .3em;\n}\n\n.fa.fa-pull-right {\n    margin-left: .3em;\n}\n\n/* Deprecated as of 4.4.0 */\n\n.pull-right {\n    float: right;\n}\n\n.pull-left {\n    float: left;\n}\n\n.fa.pull-left {\n    margin-right: .3em;\n}\n\n.fa.pull-right {\n    margin-left: .3em;\n}\n\n.fa-spin {\n    -webkit-animation: fa-spin 2s infinite linear;\n    animation: fa-spin 2s infinite linear;\n}\n\n.fa-pulse {\n    -webkit-animation: fa-spin 1s infinite steps(8);\n    animation: fa-spin 1s infinite steps(8);\n}\n\n@-webkit-keyframes fa-spin {\n    0% {\n        -webkit-transform: rotate(0deg);\n        transform: rotate(0deg);\n    }\n\n    100% {\n        -webkit-transform: rotate(359deg);\n        transform: rotate(359deg);\n    }\n}\n\n@keyframes fa-spin {\n    0% {\n        -webkit-transform: rotate(0deg);\n        transform: rotate(0deg);\n    }\n\n    100% {\n        -webkit-transform: rotate(359deg);\n        transform: rotate(359deg);\n    }\n}\n\n.fa-rotate-90 {\n    -ms-filter: \"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)\";\n    -webkit-transform: rotate(90deg);\n    -ms-transform: rotate(90deg);\n    transform: rotate(90deg);\n}\n\n.fa-rotate-180 {\n    -ms-filter: \"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)\";\n    -webkit-transform: rotate(180deg);\n    -ms-transform: rotate(180deg);\n    transform: rotate(180deg);\n}\n\n.fa-rotate-270 {\n    -ms-filter: \"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)\";\n    -webkit-transform: rotate(270deg);\n    -ms-transform: rotate(270deg);\n    transform: rotate(270deg);\n}\n\n.fa-flip-horizontal {\n    -ms-filter: \"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)\";\n    -webkit-transform: scale(-1, 1);\n    -ms-transform: scale(-1, 1);\n    transform: scale(-1, 1);\n}\n\n.fa-flip-vertical {\n    -ms-filter: \"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)\";\n    -webkit-transform: scale(1, -1);\n    -ms-transform: scale(1, -1);\n    transform: scale(1, -1);\n}\n\n:root .fa-rotate-90,\n:root .fa-rotate-180,\n:root .fa-rotate-270,\n:root .fa-flip-horizontal,\n:root .fa-flip-vertical {\n    filter: none;\n}\n\n.fa-stack {\n    position: relative;\n    display: inline-block;\n    width: 2em;\n    height: 2em;\n    line-height: 2em;\n    vertical-align: middle;\n}\n\n.fa-stack-1x,\n.fa-stack-2x {\n    position: absolute;\n    left: 0;\n    width: 100%;\n    text-align: center;\n}\n\n.fa-stack-1x {\n    line-height: inherit;\n}\n\n.fa-stack-2x {\n    font-size: 2em;\n}\n\n.fa-inverse {\n    color: #ffffff;\n}\n\n/*\nFont Awesome uses the Unicode Private Use Area (PUA) to ensure screen\nreaders do not read off random characters that represent icons\n*/\n\n.fa-glass:before {\n    content: \"\\f000\";\n}\n\n.fa-music:before {\n    content: \"\\f001\";\n}\n\n.fa-search:before {\n    content: \"\\f002\";\n}\n\n.fa-envelope-o:before {\n    content: \"\\f003\";\n}\n\n.fa-heart:before {\n    content: \"\\f004\";\n}\n\n.fa-star:before {\n    content: \"\\f005\";\n}\n\n.fa-star-o:before {\n    content: \"\\f006\";\n}\n\n.fa-user:before {\n    content: \"\\f007\";\n}\n\n.fa-film:before {\n    content: \"\\f008\";\n}\n\n.fa-th-large:before {\n    content: \"\\f009\";\n}\n\n.fa-th:before {\n    content: \"\\f00a\";\n}\n\n.fa-th-list:before {\n    content: \"\\f00b\";\n}\n\n.fa-check:before {\n    content: \"\\f00c\";\n}\n\n.fa-remove:before,\n.fa-close:before,\n.fa-times:before {\n    content: \"\\f00d\";\n}\n\n.fa-search-plus:before {\n    content: \"\\f00e\";\n}\n\n.fa-search-minus:before {\n    content: \"\\f010\";\n}\n\n.fa-power-off:before {\n    content: \"\\f011\";\n}\n\n.fa-signal:before {\n    content: \"\\f012\";\n}\n\n.fa-gear:before,\n.fa-cog:before {\n    content: \"\\f013\";\n}\n\n.fa-trash-o:before {\n    content: \"\\f014\";\n}\n\n.fa-home:before {\n    content: \"\\f015\";\n}\n\n.fa-file-o:before {\n    content: \"\\f016\";\n}\n\n.fa-clock-o:before {\n    content: \"\\f017\";\n}\n\n.fa-road:before {\n    content: \"\\f018\";\n}\n\n.fa-download:before {\n    content: \"\\f019\";\n}\n\n.fa-arrow-circle-o-down:before {\n    content: \"\\f01a\";\n}\n\n.fa-arrow-circle-o-up:before {\n    content: \"\\f01b\";\n}\n\n.fa-inbox:before {\n    content: \"\\f01c\";\n}\n\n.fa-play-circle-o:before {\n    content: \"\\f01d\";\n}\n\n.fa-rotate-right:before,\n.fa-repeat:before {\n    content: \"\\f01e\";\n}\n\n.fa-refresh:before {\n    content: \"\\f021\";\n}\n\n.fa-list-alt:before {\n    content: \"\\f022\";\n}\n\n.fa-lock:before {\n    content: \"\\f023\";\n}\n\n.fa-flag:before {\n    content: \"\\f024\";\n}\n\n.fa-headphones:before {\n    content: \"\\f025\";\n}\n\n.fa-volume-off:before {\n    content: \"\\f026\";\n}\n\n.fa-volume-down:before {\n    content: \"\\f027\";\n}\n\n.fa-volume-up:before {\n    content: \"\\f028\";\n}\n\n.fa-qrcode:before {\n    content: \"\\f029\";\n}\n\n.fa-barcode:before {\n    content: \"\\f02a\";\n}\n\n.fa-tag:before {\n    content: \"\\f02b\";\n}\n\n.fa-tags:before {\n    content: \"\\f02c\";\n}\n\n.fa-book:before {\n    content: \"\\f02d\";\n}\n\n.fa-bookmark:before {\n    content: \"\\f02e\";\n}\n\n.fa-print:before {\n    content: \"\\f02f\";\n}\n\n.fa-camera:before {\n    content: \"\\f030\";\n}\n\n.fa-font:before {\n    content: \"\\f031\";\n}\n\n.fa-bold:before {\n    content: \"\\f032\";\n}\n\n.fa-italic:before {\n    content: \"\\f033\";\n}\n\n.fa-text-height:before {\n    content: \"\\f034\";\n}\n\n.fa-text-width:before {\n    content: \"\\f035\";\n}\n\n.fa-align-left:before {\n    content: \"\\f036\";\n}\n\n.fa-align-center:before {\n    content: \"\\f037\";\n}\n\n.fa-align-right:before {\n    content: \"\\f038\";\n}\n\n.fa-align-justify:before {\n    content: \"\\f039\";\n}\n\n.fa-list:before {\n    content: \"\\f03a\";\n}\n\n.fa-dedent:before,\n.fa-outdent:before {\n    content: \"\\f03b\";\n}\n\n.fa-indent:before {\n    content: \"\\f03c\";\n}\n\n.fa-video-camera:before {\n    content: \"\\f03d\";\n}\n\n.fa-photo:before,\n.fa-image:before,\n.fa-picture-o:before {\n    content: \"\\f03e\";\n}\n\n.fa-pencil:before {\n    content: \"\\f040\";\n}\n\n.fa-map-marker:before {\n    content: \"\\f041\";\n}\n\n.fa-adjust:before {\n    content: \"\\f042\";\n}\n\n.fa-tint:before {\n    content: \"\\f043\";\n}\n\n.fa-edit:before,\n.fa-pencil-square-o:before {\n    content: \"\\f044\";\n}\n\n.fa-share-square-o:before {\n    content: \"\\f045\";\n}\n\n.fa-check-square-o:before {\n    content: \"\\f046\";\n}\n\n.fa-arrows:before {\n    content: \"\\f047\";\n}\n\n.fa-step-backward:before {\n    content: \"\\f048\";\n}\n\n.fa-fast-backward:before {\n    content: \"\\f049\";\n}\n\n.fa-backward:before {\n    content: \"\\f04a\";\n}\n\n.fa-play:before {\n    content: \"\\f04b\";\n}\n\n.fa-pause:before {\n    content: \"\\f04c\";\n}\n\n.fa-stop:before {\n    content: \"\\f04d\";\n}\n\n.fa-forward:before {\n    content: \"\\f04e\";\n}\n\n.fa-fast-forward:before {\n    content: \"\\f050\";\n}\n\n.fa-step-forward:before {\n    content: \"\\f051\";\n}\n\n.fa-eject:before {\n    content: \"\\f052\";\n}\n\n.fa-chevron-left:before {\n    content: \"\\f053\";\n}\n\n.fa-chevron-right:before {\n    content: \"\\f054\";\n}\n\n.fa-plus-circle:before {\n    content: \"\\f055\";\n}\n\n.fa-minus-circle:before {\n    content: \"\\f056\";\n}\n\n.fa-times-circle:before {\n    content: \"\\f057\";\n}\n\n.fa-check-circle:before {\n    content: \"\\f058\";\n}\n\n.fa-question-circle:before {\n    content: \"\\f059\";\n}\n\n.fa-info-circle:before {\n    content: \"\\f05a\";\n}\n\n.fa-crosshairs:before {\n    content: \"\\f05b\";\n}\n\n.fa-times-circle-o:before {\n    content: \"\\f05c\";\n}\n\n.fa-check-circle-o:before {\n    content: \"\\f05d\";\n}\n\n.fa-ban:before {\n    content: \"\\f05e\";\n}\n\n.fa-arrow-left:before {\n    content: \"\\f060\";\n}\n\n.fa-arrow-right:before {\n    content: \"\\f061\";\n}\n\n.fa-arrow-up:before {\n    content: \"\\f062\";\n}\n\n.fa-arrow-down:before {\n    content: \"\\f063\";\n}\n\n.fa-mail-forward:before,\n.fa-share:before {\n    content: \"\\f064\";\n}\n\n.fa-expand:before {\n    content: \"\\f065\";\n}\n\n.fa-compress:before {\n    content: \"\\f066\";\n}\n\n.fa-plus:before {\n    content: \"\\f067\";\n}\n\n.fa-minus:before {\n    content: \"\\f068\";\n}\n\n.fa-asterisk:before {\n    content: \"\\f069\";\n}\n\n.fa-exclamation-circle:before {\n    content: \"\\f06a\";\n}\n\n.fa-gift:before {\n    content: \"\\f06b\";\n}\n\n.fa-leaf:before {\n    content: \"\\f06c\";\n}\n\n.fa-fire:before {\n    content: \"\\f06d\";\n}\n\n.fa-eye:before {\n    content: \"\\f06e\";\n}\n\n.fa-eye-slash:before {\n    content: \"\\f070\";\n}\n\n.fa-warning:before,\n.fa-exclamation-triangle:before {\n    content: \"\\f071\";\n}\n\n.fa-plane:before {\n    content: \"\\f072\";\n}\n\n.fa-calendar:before {\n    content: \"\\f073\";\n}\n\n.fa-random:before {\n    content: \"\\f074\";\n}\n\n.fa-comment:before {\n    content: \"\\f075\";\n}\n\n.fa-magnet:before {\n    content: \"\\f076\";\n}\n\n.fa-chevron-up:before {\n    content: \"\\f077\";\n}\n\n.fa-chevron-down:before {\n    content: \"\\f078\";\n}\n\n.fa-retweet:before {\n    content: \"\\f079\";\n}\n\n.fa-shopping-cart:before {\n    content: \"\\f07a\";\n}\n\n.fa-folder:before {\n    content: \"\\f07b\";\n}\n\n.fa-folder-open:before {\n    content: \"\\f07c\";\n}\n\n.fa-arrows-v:before {\n    content: \"\\f07d\";\n}\n\n.fa-arrows-h:before {\n    content: \"\\f07e\";\n}\n\n.fa-bar-chart-o:before,\n.fa-bar-chart:before {\n    content: \"\\f080\";\n}\n\n.fa-twitter-square:before {\n    content: \"\\f081\";\n}\n\n.fa-facebook-square:before {\n    content: \"\\f082\";\n}\n\n.fa-camera-retro:before {\n    content: \"\\f083\";\n}\n\n.fa-key:before {\n    content: \"\\f084\";\n}\n\n.fa-gears:before,\n.fa-cogs:before {\n    content: \"\\f085\";\n}\n\n.fa-comments:before {\n    content: \"\\f086\";\n}\n\n.fa-thumbs-o-up:before {\n    content: \"\\f087\";\n}\n\n.fa-thumbs-o-down:before {\n    content: \"\\f088\";\n}\n\n.fa-star-half:before {\n    content: \"\\f089\";\n}\n\n.fa-heart-o:before {\n    content: \"\\f08a\";\n}\n\n.fa-sign-out:before {\n    content: \"\\f08b\";\n}\n\n.fa-linkedin-square:before {\n    content: \"\\f08c\";\n}\n\n.fa-thumb-tack:before {\n    content: \"\\f08d\";\n}\n\n.fa-external-link:before {\n    content: \"\\f08e\";\n}\n\n.fa-sign-in:before {\n    content: \"\\f090\";\n}\n\n.fa-trophy:before {\n    content: \"\\f091\";\n}\n\n.fa-github-square:before {\n    content: \"\\f092\";\n}\n\n.fa-upload:before {\n    content: \"\\f093\";\n}\n\n.fa-lemon-o:before {\n    content: \"\\f094\";\n}\n\n.fa-phone:before {\n    content: \"\\f095\";\n}\n\n.fa-square-o:before {\n    content: \"\\f096\";\n}\n\n.fa-bookmark-o:before {\n    content: \"\\f097\";\n}\n\n.fa-phone-square:before {\n    content: \"\\f098\";\n}\n\n.fa-twitter:before {\n    content: \"\\f099\";\n}\n\n.fa-facebook-f:before,\n.fa-facebook:before {\n    content: \"\\f09a\";\n}\n\n.fa-github:before {\n    content: \"\\f09b\";\n}\n\n.fa-unlock:before {\n    content: \"\\f09c\";\n}\n\n.fa-credit-card:before {\n    content: \"\\f09d\";\n}\n\n.fa-feed:before,\n.fa-rss:before {\n    content: \"\\f09e\";\n}\n\n.fa-hdd-o:before {\n    content: \"\\f0a0\";\n}\n\n.fa-bullhorn:before {\n    content: \"\\f0a1\";\n}\n\n.fa-bell:before {\n    content: \"\\f0f3\";\n}\n\n.fa-certificate:before {\n    content: \"\\f0a3\";\n}\n\n.fa-hand-o-right:before {\n    content: \"\\f0a4\";\n}\n\n.fa-hand-o-left:before {\n    content: \"\\f0a5\";\n}\n\n.fa-hand-o-up:before {\n    content: \"\\f0a6\";\n}\n\n.fa-hand-o-down:before {\n    content: \"\\f0a7\";\n}\n\n.fa-arrow-circle-left:before {\n    content: \"\\f0a8\";\n}\n\n.fa-arrow-circle-right:before {\n    content: \"\\f0a9\";\n}\n\n.fa-arrow-circle-up:before {\n    content: \"\\f0aa\";\n}\n\n.fa-arrow-circle-down:before {\n    content: \"\\f0ab\";\n}\n\n.fa-globe:before {\n    content: \"\\f0ac\";\n}\n\n.fa-wrench:before {\n    content: \"\\f0ad\";\n}\n\n.fa-tasks:before {\n    content: \"\\f0ae\";\n}\n\n.fa-filter:before {\n    content: \"\\f0b0\";\n}\n\n.fa-briefcase:before {\n    content: \"\\f0b1\";\n}\n\n.fa-arrows-alt:before {\n    content: \"\\f0b2\";\n}\n\n.fa-group:before,\n.fa-users:before {\n    content: \"\\f0c0\";\n}\n\n.fa-chain:before,\n.fa-link:before {\n    content: \"\\f0c1\";\n}\n\n.fa-cloud:before {\n    content: \"\\f0c2\";\n}\n\n.fa-flask:before {\n    content: \"\\f0c3\";\n}\n\n.fa-cut:before,\n.fa-scissors:before {\n    content: \"\\f0c4\";\n}\n\n.fa-copy:before,\n.fa-files-o:before {\n    content: \"\\f0c5\";\n}\n\n.fa-paperclip:before {\n    content: \"\\f0c6\";\n}\n\n.fa-save:before,\n.fa-floppy-o:before {\n    content: \"\\f0c7\";\n}\n\n.fa-square:before {\n    content: \"\\f0c8\";\n}\n\n.fa-navicon:before,\n.fa-reorder:before,\n.fa-bars:before {\n    content: \"\\f0c9\";\n}\n\n.fa-list-ul:before {\n    content: \"\\f0ca\";\n}\n\n.fa-list-ol:before {\n    content: \"\\f0cb\";\n}\n\n.fa-strikethrough:before {\n    content: \"\\f0cc\";\n}\n\n.fa-underline:before {\n    content: \"\\f0cd\";\n}\n\n.fa-table:before {\n    content: \"\\f0ce\";\n}\n\n.fa-magic:before {\n    content: \"\\f0d0\";\n}\n\n.fa-truck:before {\n    content: \"\\f0d1\";\n}\n\n.fa-pinterest:before {\n    content: \"\\f0d2\";\n}\n\n.fa-pinterest-square:before {\n    content: \"\\f0d3\";\n}\n\n.fa-google-plus-square:before {\n    content: \"\\f0d4\";\n}\n\n.fa-google-plus:before {\n    content: \"\\f0d5\";\n}\n\n.fa-money:before {\n    content: \"\\f0d6\";\n}\n\n.fa-caret-down:before {\n    content: \"\\f0d7\";\n}\n\n.fa-caret-up:before {\n    content: \"\\f0d8\";\n}\n\n.fa-caret-left:before {\n    content: \"\\f0d9\";\n}\n\n.fa-caret-right:before {\n    content: \"\\f0da\";\n}\n\n.fa-columns:before {\n    content: \"\\f0db\";\n}\n\n.fa-unsorted:before,\n.fa-sort:before {\n    content: \"\\f0dc\";\n}\n\n.fa-sort-down:before,\n.fa-sort-desc:before {\n    content: \"\\f0dd\";\n}\n\n.fa-sort-up:before,\n.fa-sort-asc:before {\n    content: \"\\f0de\";\n}\n\n.fa-envelope:before {\n    content: \"\\f0e0\";\n}\n\n.fa-linkedin:before {\n    content: \"\\f0e1\";\n}\n\n.fa-rotate-left:before,\n.fa-undo:before {\n    content: \"\\f0e2\";\n}\n\n.fa-legal:before,\n.fa-gavel:before {\n    content: \"\\f0e3\";\n}\n\n.fa-dashboard:before,\n.fa-tachometer:before {\n    content: \"\\f0e4\";\n}\n\n.fa-comment-o:before {\n    content: \"\\f0e5\";\n}\n\n.fa-comments-o:before {\n    content: \"\\f0e6\";\n}\n\n.fa-flash:before,\n.fa-bolt:before {\n    content: \"\\f0e7\";\n}\n\n.fa-sitemap:before {\n    content: \"\\f0e8\";\n}\n\n.fa-umbrella:before {\n    content: \"\\f0e9\";\n}\n\n.fa-paste:before,\n.fa-clipboard:before {\n    content: \"\\f0ea\";\n}\n\n.fa-lightbulb-o:before {\n    content: \"\\f0eb\";\n}\n\n.fa-exchange:before {\n    content: \"\\f0ec\";\n}\n\n.fa-cloud-download:before {\n    content: \"\\f0ed\";\n}\n\n.fa-cloud-upload:before {\n    content: \"\\f0ee\";\n}\n\n.fa-user-md:before {\n    content: \"\\f0f0\";\n}\n\n.fa-stethoscope:before {\n    content: \"\\f0f1\";\n}\n\n.fa-suitcase:before {\n    content: \"\\f0f2\";\n}\n\n.fa-bell-o:before {\n    content: \"\\f0a2\";\n}\n\n.fa-coffee:before {\n    content: \"\\f0f4\";\n}\n\n.fa-cutlery:before {\n    content: \"\\f0f5\";\n}\n\n.fa-file-text-o:before {\n    content: \"\\f0f6\";\n}\n\n.fa-building-o:before {\n    content: \"\\f0f7\";\n}\n\n.fa-hospital-o:before {\n    content: \"\\f0f8\";\n}\n\n.fa-ambulance:before {\n    content: \"\\f0f9\";\n}\n\n.fa-medkit:before {\n    content: \"\\f0fa\";\n}\n\n.fa-fighter-jet:before {\n    content: \"\\f0fb\";\n}\n\n.fa-beer:before {\n    content: \"\\f0fc\";\n}\n\n.fa-h-square:before {\n    content: \"\\f0fd\";\n}\n\n.fa-plus-square:before {\n    content: \"\\f0fe\";\n}\n\n.fa-angle-double-left:before {\n    content: \"\\f100\";\n}\n\n.fa-angle-double-right:before {\n    content: \"\\f101\";\n}\n\n.fa-angle-double-up:before {\n    content: \"\\f102\";\n}\n\n.fa-angle-double-down:before {\n    content: \"\\f103\";\n}\n\n.fa-angle-left:before {\n    content: \"\\f104\";\n}\n\n.fa-angle-right:before {\n    content: \"\\f105\";\n}\n\n.fa-angle-up:before {\n    content: \"\\f106\";\n}\n\n.fa-angle-down:before {\n    content: \"\\f107\";\n}\n\n.fa-desktop:before {\n    content: \"\\f108\";\n}\n\n.fa-laptop:before {\n    content: \"\\f109\";\n}\n\n.fa-tablet:before {\n    content: \"\\f10a\";\n}\n\n.fa-mobile-phone:before,\n.fa-mobile:before {\n    content: \"\\f10b\";\n}\n\n.fa-circle-o:before {\n    content: \"\\f10c\";\n}\n\n.fa-quote-left:before {\n    content: \"\\f10d\";\n}\n\n.fa-quote-right:before {\n    content: \"\\f10e\";\n}\n\n.fa-spinner:before {\n    content: \"\\f110\";\n}\n\n.fa-circle:before {\n    content: \"\\f111\";\n}\n\n.fa-mail-reply:before,\n.fa-reply:before {\n    content: \"\\f112\";\n}\n\n.fa-github-alt:before {\n    content: \"\\f113\";\n}\n\n.fa-folder-o:before {\n    content: \"\\f114\";\n}\n\n.fa-folder-open-o:before {\n    content: \"\\f115\";\n}\n\n.fa-smile-o:before {\n    content: \"\\f118\";\n}\n\n.fa-frown-o:before {\n    content: \"\\f119\";\n}\n\n.fa-meh-o:before {\n    content: \"\\f11a\";\n}\n\n.fa-gamepad:before {\n    content: \"\\f11b\";\n}\n\n.fa-keyboard-o:before {\n    content: \"\\f11c\";\n}\n\n.fa-flag-o:before {\n    content: \"\\f11d\";\n}\n\n.fa-flag-checkered:before {\n    content: \"\\f11e\";\n}\n\n.fa-terminal:before {\n    content: \"\\f120\";\n}\n\n.fa-code:before {\n    content: \"\\f121\";\n}\n\n.fa-mail-reply-all:before,\n.fa-reply-all:before {\n    content: \"\\f122\";\n}\n\n.fa-star-half-empty:before,\n.fa-star-half-full:before,\n.fa-star-half-o:before {\n    content: \"\\f123\";\n}\n\n.fa-location-arrow:before {\n    content: \"\\f124\";\n}\n\n.fa-crop:before {\n    content: \"\\f125\";\n}\n\n.fa-code-fork:before {\n    content: \"\\f126\";\n}\n\n.fa-unlink:before,\n.fa-chain-broken:before {\n    content: \"\\f127\";\n}\n\n.fa-question:before {\n    content: \"\\f128\";\n}\n\n.fa-info:before {\n    content: \"\\f129\";\n}\n\n.fa-exclamation:before {\n    content: \"\\f12a\";\n}\n\n.fa-superscript:before {\n    content: \"\\f12b\";\n}\n\n.fa-subscript:before {\n    content: \"\\f12c\";\n}\n\n.fa-eraser:before {\n    content: \"\\f12d\";\n}\n\n.fa-puzzle-piece:before {\n    content: \"\\f12e\";\n}\n\n.fa-microphone:before {\n    content: \"\\f130\";\n}\n\n.fa-microphone-slash:before {\n    content: \"\\f131\";\n}\n\n.fa-shield:before {\n    content: \"\\f132\";\n}\n\n.fa-calendar-o:before {\n    content: \"\\f133\";\n}\n\n.fa-fire-extinguisher:before {\n    content: \"\\f134\";\n}\n\n.fa-rocket:before {\n    content: \"\\f135\";\n}\n\n.fa-maxcdn:before {\n    content: \"\\f136\";\n}\n\n.fa-chevron-circle-left:before {\n    content: \"\\f137\";\n}\n\n.fa-chevron-circle-right:before {\n    content: \"\\f138\";\n}\n\n.fa-chevron-circle-up:before {\n    content: \"\\f139\";\n}\n\n.fa-chevron-circle-down:before {\n    content: \"\\f13a\";\n}\n\n.fa-html5:before {\n    content: \"\\f13b\";\n}\n\n.fa-css3:before {\n    content: \"\\f13c\";\n}\n\n.fa-anchor:before {\n    content: \"\\f13d\";\n}\n\n.fa-unlock-alt:before {\n    content: \"\\f13e\";\n}\n\n.fa-bullseye:before {\n    content: \"\\f140\";\n}\n\n.fa-ellipsis-h:before {\n    content: \"\\f141\";\n}\n\n.fa-ellipsis-v:before {\n    content: \"\\f142\";\n}\n\n.fa-rss-square:before {\n    content: \"\\f143\";\n}\n\n.fa-play-circle:before {\n    content: \"\\f144\";\n}\n\n.fa-ticket:before {\n    content: \"\\f145\";\n}\n\n.fa-minus-square:before {\n    content: \"\\f146\";\n}\n\n.fa-minus-square-o:before {\n    content: \"\\f147\";\n}\n\n.fa-level-up:before {\n    content: \"\\f148\";\n}\n\n.fa-level-down:before {\n    content: \"\\f149\";\n}\n\n.fa-check-square:before {\n    content: \"\\f14a\";\n}\n\n.fa-pencil-square:before {\n    content: \"\\f14b\";\n}\n\n.fa-external-link-square:before {\n    content: \"\\f14c\";\n}\n\n.fa-share-square:before {\n    content: \"\\f14d\";\n}\n\n.fa-compass:before {\n    content: \"\\f14e\";\n}\n\n.fa-toggle-down:before,\n.fa-caret-square-o-down:before {\n    content: \"\\f150\";\n}\n\n.fa-toggle-up:before,\n.fa-caret-square-o-up:before {\n    content: \"\\f151\";\n}\n\n.fa-toggle-right:before,\n.fa-caret-square-o-right:before {\n    content: \"\\f152\";\n}\n\n.fa-euro:before,\n.fa-eur:before {\n    content: \"\\f153\";\n}\n\n.fa-gbp:before {\n    content: \"\\f154\";\n}\n\n.fa-dollar:before,\n.fa-usd:before {\n    content: \"\\f155\";\n}\n\n.fa-rupee:before,\n.fa-inr:before {\n    content: \"\\f156\";\n}\n\n.fa-cny:before,\n.fa-rmb:before,\n.fa-yen:before,\n.fa-jpy:before {\n    content: \"\\f157\";\n}\n\n.fa-ruble:before,\n.fa-rouble:before,\n.fa-rub:before {\n    content: \"\\f158\";\n}\n\n.fa-won:before,\n.fa-krw:before {\n    content: \"\\f159\";\n}\n\n.fa-bitcoin:before,\n.fa-btc:before {\n    content: \"\\f15a\";\n}\n\n.fa-file:before {\n    content: \"\\f15b\";\n}\n\n.fa-file-text:before {\n    content: \"\\f15c\";\n}\n\n.fa-sort-alpha-asc:before {\n    content: \"\\f15d\";\n}\n\n.fa-sort-alpha-desc:before {\n    content: \"\\f15e\";\n}\n\n.fa-sort-amount-asc:before {\n    content: \"\\f160\";\n}\n\n.fa-sort-amount-desc:before {\n    content: \"\\f161\";\n}\n\n.fa-sort-numeric-asc:before {\n    content: \"\\f162\";\n}\n\n.fa-sort-numeric-desc:before {\n    content: \"\\f163\";\n}\n\n.fa-thumbs-up:before {\n    content: \"\\f164\";\n}\n\n.fa-thumbs-down:before {\n    content: \"\\f165\";\n}\n\n.fa-youtube-square:before {\n    content: \"\\f166\";\n}\n\n.fa-youtube:before {\n    content: \"\\f167\";\n}\n\n.fa-xing:before {\n    content: \"\\f168\";\n}\n\n.fa-xing-square:before {\n    content: \"\\f169\";\n}\n\n.fa-youtube-play:before {\n    content: \"\\f16a\";\n}\n\n.fa-dropbox:before {\n    content: \"\\f16b\";\n}\n\n.fa-stack-overflow:before {\n    content: \"\\f16c\";\n}\n\n.fa-instagram:before {\n    content: \"\\f16d\";\n}\n\n.fa-flickr:before {\n    content: \"\\f16e\";\n}\n\n.fa-adn:before {\n    content: \"\\f170\";\n}\n\n.fa-bitbucket:before {\n    content: \"\\f171\";\n}\n\n.fa-bitbucket-square:before {\n    content: \"\\f172\";\n}\n\n.fa-tumblr:before {\n    content: \"\\f173\";\n}\n\n.fa-tumblr-square:before {\n    content: \"\\f174\";\n}\n\n.fa-long-arrow-down:before {\n    content: \"\\f175\";\n}\n\n.fa-long-arrow-up:before {\n    content: \"\\f176\";\n}\n\n.fa-long-arrow-left:before {\n    content: \"\\f177\";\n}\n\n.fa-long-arrow-right:before {\n    content: \"\\f178\";\n}\n\n.fa-apple:before {\n    content: \"\\f179\";\n}\n\n.fa-windows:before {\n    content: \"\\f17a\";\n}\n\n.fa-android:before {\n    content: \"\\f17b\";\n}\n\n.fa-linux:before {\n    content: \"\\f17c\";\n}\n\n.fa-dribbble:before {\n    content: \"\\f17d\";\n}\n\n.fa-skype:before {\n    content: \"\\f17e\";\n}\n\n.fa-foursquare:before {\n    content: \"\\f180\";\n}\n\n.fa-trello:before {\n    content: \"\\f181\";\n}\n\n.fa-female:before {\n    content: \"\\f182\";\n}\n\n.fa-male:before {\n    content: \"\\f183\";\n}\n\n.fa-gittip:before,\n.fa-gratipay:before {\n    content: \"\\f184\";\n}\n\n.fa-sun-o:before {\n    content: \"\\f185\";\n}\n\n.fa-moon-o:before {\n    content: \"\\f186\";\n}\n\n.fa-archive:before {\n    content: \"\\f187\";\n}\n\n.fa-bug:before {\n    content: \"\\f188\";\n}\n\n.fa-vk:before {\n    content: \"\\f189\";\n}\n\n.fa-weibo:before {\n    content: \"\\f18a\";\n}\n\n.fa-renren:before {\n    content: \"\\f18b\";\n}\n\n.fa-pagelines:before {\n    content: \"\\f18c\";\n}\n\n.fa-stack-exchange:before {\n    content: \"\\f18d\";\n}\n\n.fa-arrow-circle-o-right:before {\n    content: \"\\f18e\";\n}\n\n.fa-arrow-circle-o-left:before {\n    content: \"\\f190\";\n}\n\n.fa-toggle-left:before,\n.fa-caret-square-o-left:before {\n    content: \"\\f191\";\n}\n\n.fa-dot-circle-o:before {\n    content: \"\\f192\";\n}\n\n.fa-wheelchair:before {\n    content: \"\\f193\";\n}\n\n.fa-vimeo-square:before {\n    content: \"\\f194\";\n}\n\n.fa-turkish-lira:before,\n.fa-try:before {\n    content: \"\\f195\";\n}\n\n.fa-plus-square-o:before {\n    content: \"\\f196\";\n}\n\n.fa-space-shuttle:before {\n    content: \"\\f197\";\n}\n\n.fa-slack:before {\n    content: \"\\f198\";\n}\n\n.fa-envelope-square:before {\n    content: \"\\f199\";\n}\n\n.fa-wordpress:before {\n    content: \"\\f19a\";\n}\n\n.fa-openid:before {\n    content: \"\\f19b\";\n}\n\n.fa-institution:before,\n.fa-bank:before,\n.fa-university:before {\n    content: \"\\f19c\";\n}\n\n.fa-mortar-board:before,\n.fa-graduation-cap:before {\n    content: \"\\f19d\";\n}\n\n.fa-yahoo:before {\n    content: \"\\f19e\";\n}\n\n.fa-google:before {\n    content: \"\\f1a0\";\n}\n\n.fa-reddit:before {\n    content: \"\\f1a1\";\n}\n\n.fa-reddit-square:before {\n    content: \"\\f1a2\";\n}\n\n.fa-stumbleupon-circle:before {\n    content: \"\\f1a3\";\n}\n\n.fa-stumbleupon:before {\n    content: \"\\f1a4\";\n}\n\n.fa-delicious:before {\n    content: \"\\f1a5\";\n}\n\n.fa-digg:before {\n    content: \"\\f1a6\";\n}\n\n.fa-pied-piper-pp:before {\n    content: \"\\f1a7\";\n}\n\n.fa-pied-piper-alt:before {\n    content: \"\\f1a8\";\n}\n\n.fa-drupal:before {\n    content: \"\\f1a9\";\n}\n\n.fa-joomla:before {\n    content: \"\\f1aa\";\n}\n\n.fa-language:before {\n    content: \"\\f1ab\";\n}\n\n.fa-fax:before {\n    content: \"\\f1ac\";\n}\n\n.fa-building:before {\n    content: \"\\f1ad\";\n}\n\n.fa-child:before {\n    content: \"\\f1ae\";\n}\n\n.fa-paw:before {\n    content: \"\\f1b0\";\n}\n\n.fa-spoon:before {\n    content: \"\\f1b1\";\n}\n\n.fa-cube:before {\n    content: \"\\f1b2\";\n}\n\n.fa-cubes:before {\n    content: \"\\f1b3\";\n}\n\n.fa-behance:before {\n    content: \"\\f1b4\";\n}\n\n.fa-behance-square:before {\n    content: \"\\f1b5\";\n}\n\n.fa-steam:before {\n    content: \"\\f1b6\";\n}\n\n.fa-steam-square:before {\n    content: \"\\f1b7\";\n}\n\n.fa-recycle:before {\n    content: \"\\f1b8\";\n}\n\n.fa-automobile:before,\n.fa-car:before {\n    content: \"\\f1b9\";\n}\n\n.fa-cab:before,\n.fa-taxi:before {\n    content: \"\\f1ba\";\n}\n\n.fa-tree:before {\n    content: \"\\f1bb\";\n}\n\n.fa-spotify:before {\n    content: \"\\f1bc\";\n}\n\n.fa-deviantart:before {\n    content: \"\\f1bd\";\n}\n\n.fa-soundcloud:before {\n    content: \"\\f1be\";\n}\n\n.fa-database:before {\n    content: \"\\f1c0\";\n}\n\n.fa-file-pdf-o:before {\n    content: \"\\f1c1\";\n}\n\n.fa-file-word-o:before {\n    content: \"\\f1c2\";\n}\n\n.fa-file-excel-o:before {\n    content: \"\\f1c3\";\n}\n\n.fa-file-powerpoint-o:before {\n    content: \"\\f1c4\";\n}\n\n.fa-file-photo-o:before,\n.fa-file-picture-o:before,\n.fa-file-image-o:before {\n    content: \"\\f1c5\";\n}\n\n.fa-file-zip-o:before,\n.fa-file-archive-o:before {\n    content: \"\\f1c6\";\n}\n\n.fa-file-sound-o:before,\n.fa-file-audio-o:before {\n    content: \"\\f1c7\";\n}\n\n.fa-file-movie-o:before,\n.fa-file-video-o:before {\n    content: \"\\f1c8\";\n}\n\n.fa-file-code-o:before {\n    content: \"\\f1c9\";\n}\n\n.fa-vine:before {\n    content: \"\\f1ca\";\n}\n\n.fa-codepen:before {\n    content: \"\\f1cb\";\n}\n\n.fa-jsfiddle:before {\n    content: \"\\f1cc\";\n}\n\n.fa-life-bouy:before,\n.fa-life-buoy:before,\n.fa-life-saver:before,\n.fa-support:before,\n.fa-life-ring:before {\n    content: \"\\f1cd\";\n}\n\n.fa-circle-o-notch:before {\n    content: \"\\f1ce\";\n}\n\n.fa-ra:before,\n.fa-resistance:before,\n.fa-rebel:before {\n    content: \"\\f1d0\";\n}\n\n.fa-ge:before,\n.fa-empire:before {\n    content: \"\\f1d1\";\n}\n\n.fa-git-square:before {\n    content: \"\\f1d2\";\n}\n\n.fa-git:before {\n    content: \"\\f1d3\";\n}\n\n.fa-y-combinator-square:before,\n.fa-yc-square:before,\n.fa-hacker-news:before {\n    content: \"\\f1d4\";\n}\n\n.fa-tencent-weibo:before {\n    content: \"\\f1d5\";\n}\n\n.fa-qq:before {\n    content: \"\\f1d6\";\n}\n\n.fa-wechat:before,\n.fa-weixin:before {\n    content: \"\\f1d7\";\n}\n\n.fa-send:before,\n.fa-paper-plane:before {\n    content: \"\\f1d8\";\n}\n\n.fa-send-o:before,\n.fa-paper-plane-o:before {\n    content: \"\\f1d9\";\n}\n\n.fa-history:before {\n    content: \"\\f1da\";\n}\n\n.fa-circle-thin:before {\n    content: \"\\f1db\";\n}\n\n.fa-header:before {\n    content: \"\\f1dc\";\n}\n\n.fa-paragraph:before {\n    content: \"\\f1dd\";\n}\n\n.fa-sliders:before {\n    content: \"\\f1de\";\n}\n\n.fa-share-alt:before {\n    content: \"\\f1e0\";\n}\n\n.fa-share-alt-square:before {\n    content: \"\\f1e1\";\n}\n\n.fa-bomb:before {\n    content: \"\\f1e2\";\n}\n\n.fa-soccer-ball-o:before,\n.fa-futbol-o:before {\n    content: \"\\f1e3\";\n}\n\n.fa-tty:before {\n    content: \"\\f1e4\";\n}\n\n.fa-binoculars:before {\n    content: \"\\f1e5\";\n}\n\n.fa-plug:before {\n    content: \"\\f1e6\";\n}\n\n.fa-slideshare:before {\n    content: \"\\f1e7\";\n}\n\n.fa-twitch:before {\n    content: \"\\f1e8\";\n}\n\n.fa-yelp:before {\n    content: \"\\f1e9\";\n}\n\n.fa-newspaper-o:before {\n    content: \"\\f1ea\";\n}\n\n.fa-wifi:before {\n    content: \"\\f1eb\";\n}\n\n.fa-calculator:before {\n    content: \"\\f1ec\";\n}\n\n.fa-paypal:before {\n    content: \"\\f1ed\";\n}\n\n.fa-google-wallet:before {\n    content: \"\\f1ee\";\n}\n\n.fa-cc-visa:before {\n    content: \"\\f1f0\";\n}\n\n.fa-cc-mastercard:before {\n    content: \"\\f1f1\";\n}\n\n.fa-cc-discover:before {\n    content: \"\\f1f2\";\n}\n\n.fa-cc-amex:before {\n    content: \"\\f1f3\";\n}\n\n.fa-cc-paypal:before {\n    content: \"\\f1f4\";\n}\n\n.fa-cc-stripe:before {\n    content: \"\\f1f5\";\n}\n\n.fa-bell-slash:before {\n    content: \"\\f1f6\";\n}\n\n.fa-bell-slash-o:before {\n    content: \"\\f1f7\";\n}\n\n.fa-trash:before {\n    content: \"\\f1f8\";\n}\n\n.fa-copyright:before {\n    content: \"\\f1f9\";\n}\n\n.fa-at:before {\n    content: \"\\f1fa\";\n}\n\n.fa-eyedropper:before {\n    content: \"\\f1fb\";\n}\n\n.fa-paint-brush:before {\n    content: \"\\f1fc\";\n}\n\n.fa-birthday-cake:before {\n    content: \"\\f1fd\";\n}\n\n.fa-area-chart:before {\n    content: \"\\f1fe\";\n}\n\n.fa-pie-chart:before {\n    content: \"\\f200\";\n}\n\n.fa-line-chart:before {\n    content: \"\\f201\";\n}\n\n.fa-lastfm:before {\n    content: \"\\f202\";\n}\n\n.fa-lastfm-square:before {\n    content: \"\\f203\";\n}\n\n.fa-toggle-off:before {\n    content: \"\\f204\";\n}\n\n.fa-toggle-on:before {\n    content: \"\\f205\";\n}\n\n.fa-bicycle:before {\n    content: \"\\f206\";\n}\n\n.fa-bus:before {\n    content: \"\\f207\";\n}\n\n.fa-ioxhost:before {\n    content: \"\\f208\";\n}\n\n.fa-angellist:before {\n    content: \"\\f209\";\n}\n\n.fa-cc:before {\n    content: \"\\f20a\";\n}\n\n.fa-shekel:before,\n.fa-sheqel:before,\n.fa-ils:before {\n    content: \"\\f20b\";\n}\n\n.fa-meanpath:before {\n    content: \"\\f20c\";\n}\n\n.fa-buysellads:before {\n    content: \"\\f20d\";\n}\n\n.fa-connectdevelop:before {\n    content: \"\\f20e\";\n}\n\n.fa-dashcube:before {\n    content: \"\\f210\";\n}\n\n.fa-forumbee:before {\n    content: \"\\f211\";\n}\n\n.fa-leanpub:before {\n    content: \"\\f212\";\n}\n\n.fa-sellsy:before {\n    content: \"\\f213\";\n}\n\n.fa-shirtsinbulk:before {\n    content: \"\\f214\";\n}\n\n.fa-simplybuilt:before {\n    content: \"\\f215\";\n}\n\n.fa-skyatlas:before {\n    content: \"\\f216\";\n}\n\n.fa-cart-plus:before {\n    content: \"\\f217\";\n}\n\n.fa-cart-arrow-down:before {\n    content: \"\\f218\";\n}\n\n.fa-diamond:before {\n    content: \"\\f219\";\n}\n\n.fa-ship:before {\n    content: \"\\f21a\";\n}\n\n.fa-user-secret:before {\n    content: \"\\f21b\";\n}\n\n.fa-motorcycle:before {\n    content: \"\\f21c\";\n}\n\n.fa-street-view:before {\n    content: \"\\f21d\";\n}\n\n.fa-heartbeat:before {\n    content: \"\\f21e\";\n}\n\n.fa-venus:before {\n    content: \"\\f221\";\n}\n\n.fa-mars:before {\n    content: \"\\f222\";\n}\n\n.fa-mercury:before {\n    content: \"\\f223\";\n}\n\n.fa-intersex:before,\n.fa-transgender:before {\n    content: \"\\f224\";\n}\n\n.fa-transgender-alt:before {\n    content: \"\\f225\";\n}\n\n.fa-venus-double:before {\n    content: \"\\f226\";\n}\n\n.fa-mars-double:before {\n    content: \"\\f227\";\n}\n\n.fa-venus-mars:before {\n    content: \"\\f228\";\n}\n\n.fa-mars-stroke:before {\n    content: \"\\f229\";\n}\n\n.fa-mars-stroke-v:before {\n    content: \"\\f22a\";\n}\n\n.fa-mars-stroke-h:before {\n    content: \"\\f22b\";\n}\n\n.fa-neuter:before {\n    content: \"\\f22c\";\n}\n\n.fa-genderless:before {\n    content: \"\\f22d\";\n}\n\n.fa-facebook-official:before {\n    content: \"\\f230\";\n}\n\n.fa-pinterest-p:before {\n    content: \"\\f231\";\n}\n\n.fa-whatsapp:before {\n    content: \"\\f232\";\n}\n\n.fa-server:before {\n    content: \"\\f233\";\n}\n\n.fa-user-plus:before {\n    content: \"\\f234\";\n}\n\n.fa-user-times:before {\n    content: \"\\f235\";\n}\n\n.fa-hotel:before,\n.fa-bed:before {\n    content: \"\\f236\";\n}\n\n.fa-viacoin:before {\n    content: \"\\f237\";\n}\n\n.fa-train:before {\n    content: \"\\f238\";\n}\n\n.fa-subway:before {\n    content: \"\\f239\";\n}\n\n.fa-medium:before {\n    content: \"\\f23a\";\n}\n\n.fa-yc:before,\n.fa-y-combinator:before {\n    content: \"\\f23b\";\n}\n\n.fa-optin-monster:before {\n    content: \"\\f23c\";\n}\n\n.fa-opencart:before {\n    content: \"\\f23d\";\n}\n\n.fa-expeditedssl:before {\n    content: \"\\f23e\";\n}\n\n.fa-battery-4:before,\n.fa-battery:before,\n.fa-battery-full:before {\n    content: \"\\f240\";\n}\n\n.fa-battery-3:before,\n.fa-battery-three-quarters:before {\n    content: \"\\f241\";\n}\n\n.fa-battery-2:before,\n.fa-battery-half:before {\n    content: \"\\f242\";\n}\n\n.fa-battery-1:before,\n.fa-battery-quarter:before {\n    content: \"\\f243\";\n}\n\n.fa-battery-0:before,\n.fa-battery-empty:before {\n    content: \"\\f244\";\n}\n\n.fa-mouse-pointer:before {\n    content: \"\\f245\";\n}\n\n.fa-i-cursor:before {\n    content: \"\\f246\";\n}\n\n.fa-object-group:before {\n    content: \"\\f247\";\n}\n\n.fa-object-ungroup:before {\n    content: \"\\f248\";\n}\n\n.fa-sticky-note:before {\n    content: \"\\f249\";\n}\n\n.fa-sticky-note-o:before {\n    content: \"\\f24a\";\n}\n\n.fa-cc-jcb:before {\n    content: \"\\f24b\";\n}\n\n.fa-cc-diners-club:before {\n    content: \"\\f24c\";\n}\n\n.fa-clone:before {\n    content: \"\\f24d\";\n}\n\n.fa-balance-scale:before {\n    content: \"\\f24e\";\n}\n\n.fa-hourglass-o:before {\n    content: \"\\f250\";\n}\n\n.fa-hourglass-1:before,\n.fa-hourglass-start:before {\n    content: \"\\f251\";\n}\n\n.fa-hourglass-2:before,\n.fa-hourglass-half:before {\n    content: \"\\f252\";\n}\n\n.fa-hourglass-3:before,\n.fa-hourglass-end:before {\n    content: \"\\f253\";\n}\n\n.fa-hourglass:before {\n    content: \"\\f254\";\n}\n\n.fa-hand-grab-o:before,\n.fa-hand-rock-o:before {\n    content: \"\\f255\";\n}\n\n.fa-hand-stop-o:before,\n.fa-hand-paper-o:before {\n    content: \"\\f256\";\n}\n\n.fa-hand-scissors-o:before {\n    content: \"\\f257\";\n}\n\n.fa-hand-lizard-o:before {\n    content: \"\\f258\";\n}\n\n.fa-hand-spock-o:before {\n    content: \"\\f259\";\n}\n\n.fa-hand-pointer-o:before {\n    content: \"\\f25a\";\n}\n\n.fa-hand-peace-o:before {\n    content: \"\\f25b\";\n}\n\n.fa-trademark:before {\n    content: \"\\f25c\";\n}\n\n.fa-registered:before {\n    content: \"\\f25d\";\n}\n\n.fa-creative-commons:before {\n    content: \"\\f25e\";\n}\n\n.fa-gg:before {\n    content: \"\\f260\";\n}\n\n.fa-gg-circle:before {\n    content: \"\\f261\";\n}\n\n.fa-tripadvisor:before {\n    content: \"\\f262\";\n}\n\n.fa-odnoklassniki:before {\n    content: \"\\f263\";\n}\n\n.fa-odnoklassniki-square:before {\n    content: \"\\f264\";\n}\n\n.fa-get-pocket:before {\n    content: \"\\f265\";\n}\n\n.fa-wikipedia-w:before {\n    content: \"\\f266\";\n}\n\n.fa-safari:before {\n    content: \"\\f267\";\n}\n\n.fa-chrome:before {\n    content: \"\\f268\";\n}\n\n.fa-firefox:before {\n    content: \"\\f269\";\n}\n\n.fa-opera:before {\n    content: \"\\f26a\";\n}\n\n.fa-internet-explorer:before {\n    content: \"\\f26b\";\n}\n\n.fa-tv:before,\n.fa-television:before {\n    content: \"\\f26c\";\n}\n\n.fa-contao:before {\n    content: \"\\f26d\";\n}\n\n.fa-500px:before {\n    content: \"\\f26e\";\n}\n\n.fa-amazon:before {\n    content: \"\\f270\";\n}\n\n.fa-calendar-plus-o:before {\n    content: \"\\f271\";\n}\n\n.fa-calendar-minus-o:before {\n    content: \"\\f272\";\n}\n\n.fa-calendar-times-o:before {\n    content: \"\\f273\";\n}\n\n.fa-calendar-check-o:before {\n    content: \"\\f274\";\n}\n\n.fa-industry:before {\n    content: \"\\f275\";\n}\n\n.fa-map-pin:before {\n    content: \"\\f276\";\n}\n\n.fa-map-signs:before {\n    content: \"\\f277\";\n}\n\n.fa-map-o:before {\n    content: \"\\f278\";\n}\n\n.fa-map:before {\n    content: \"\\f279\";\n}\n\n.fa-commenting:before {\n    content: \"\\f27a\";\n}\n\n.fa-commenting-o:before {\n    content: \"\\f27b\";\n}\n\n.fa-houzz:before {\n    content: \"\\f27c\";\n}\n\n.fa-vimeo:before {\n    content: \"\\f27d\";\n}\n\n.fa-black-tie:before {\n    content: \"\\f27e\";\n}\n\n.fa-fonticons:before {\n    content: \"\\f280\";\n}\n\n.fa-reddit-alien:before {\n    content: \"\\f281\";\n}\n\n.fa-edge:before {\n    content: \"\\f282\";\n}\n\n.fa-credit-card-alt:before {\n    content: \"\\f283\";\n}\n\n.fa-codiepie:before {\n    content: \"\\f284\";\n}\n\n.fa-modx:before {\n    content: \"\\f285\";\n}\n\n.fa-fort-awesome:before {\n    content: \"\\f286\";\n}\n\n.fa-usb:before {\n    content: \"\\f287\";\n}\n\n.fa-product-hunt:before {\n    content: \"\\f288\";\n}\n\n.fa-mixcloud:before {\n    content: \"\\f289\";\n}\n\n.fa-scribd:before {\n    content: \"\\f28a\";\n}\n\n.fa-pause-circle:before {\n    content: \"\\f28b\";\n}\n\n.fa-pause-circle-o:before {\n    content: \"\\f28c\";\n}\n\n.fa-stop-circle:before {\n    content: \"\\f28d\";\n}\n\n.fa-stop-circle-o:before {\n    content: \"\\f28e\";\n}\n\n.fa-shopping-bag:before {\n    content: \"\\f290\";\n}\n\n.fa-shopping-basket:before {\n    content: \"\\f291\";\n}\n\n.fa-hashtag:before {\n    content: \"\\f292\";\n}\n\n.fa-bluetooth:before {\n    content: \"\\f293\";\n}\n\n.fa-bluetooth-b:before {\n    content: \"\\f294\";\n}\n\n.fa-percent:before {\n    content: \"\\f295\";\n}\n\n.fa-gitlab:before {\n    content: \"\\f296\";\n}\n\n.fa-wpbeginner:before {\n    content: \"\\f297\";\n}\n\n.fa-wpforms:before {\n    content: \"\\f298\";\n}\n\n.fa-envira:before {\n    content: \"\\f299\";\n}\n\n.fa-universal-access:before {\n    content: \"\\f29a\";\n}\n\n.fa-wheelchair-alt:before {\n    content: \"\\f29b\";\n}\n\n.fa-question-circle-o:before {\n    content: \"\\f29c\";\n}\n\n.fa-blind:before {\n    content: \"\\f29d\";\n}\n\n.fa-audio-description:before {\n    content: \"\\f29e\";\n}\n\n.fa-volume-control-phone:before {\n    content: \"\\f2a0\";\n}\n\n.fa-braille:before {\n    content: \"\\f2a1\";\n}\n\n.fa-assistive-listening-systems:before {\n    content: \"\\f2a2\";\n}\n\n.fa-asl-interpreting:before,\n.fa-american-sign-language-interpreting:before {\n    content: \"\\f2a3\";\n}\n\n.fa-deafness:before,\n.fa-hard-of-hearing:before,\n.fa-deaf:before {\n    content: \"\\f2a4\";\n}\n\n.fa-glide:before {\n    content: \"\\f2a5\";\n}\n\n.fa-glide-g:before {\n    content: \"\\f2a6\";\n}\n\n.fa-signing:before,\n.fa-sign-language:before {\n    content: \"\\f2a7\";\n}\n\n.fa-low-vision:before {\n    content: \"\\f2a8\";\n}\n\n.fa-viadeo:before {\n    content: \"\\f2a9\";\n}\n\n.fa-viadeo-square:before {\n    content: \"\\f2aa\";\n}\n\n.fa-snapchat:before {\n    content: \"\\f2ab\";\n}\n\n.fa-snapchat-ghost:before {\n    content: \"\\f2ac\";\n}\n\n.fa-snapchat-square:before {\n    content: \"\\f2ad\";\n}\n\n.fa-pied-piper:before {\n    content: \"\\f2ae\";\n}\n\n.fa-first-order:before {\n    content: \"\\f2b0\";\n}\n\n.fa-yoast:before {\n    content: \"\\f2b1\";\n}\n\n.fa-themeisle:before {\n    content: \"\\f2b2\";\n}\n\n.fa-google-plus-circle:before,\n.fa-google-plus-official:before {\n    content: \"\\f2b3\";\n}\n\n.fa-fa:before,\n.fa-font-awesome:before {\n    content: \"\\f2b4\";\n}\n\n.fa-handshake-o:before {\n    content: \"\\f2b5\";\n}\n\n.fa-envelope-open:before {\n    content: \"\\f2b6\";\n}\n\n.fa-envelope-open-o:before {\n    content: \"\\f2b7\";\n}\n\n.fa-linode:before {\n    content: \"\\f2b8\";\n}\n\n.fa-address-book:before {\n    content: \"\\f2b9\";\n}\n\n.fa-address-book-o:before {\n    content: \"\\f2ba\";\n}\n\n.fa-vcard:before,\n.fa-address-card:before {\n    content: \"\\f2bb\";\n}\n\n.fa-vcard-o:before,\n.fa-address-card-o:before {\n    content: \"\\f2bc\";\n}\n\n.fa-user-circle:before {\n    content: \"\\f2bd\";\n}\n\n.fa-user-circle-o:before {\n    content: \"\\f2be\";\n}\n\n.fa-user-o:before {\n    content: \"\\f2c0\";\n}\n\n.fa-id-badge:before {\n    content: \"\\f2c1\";\n}\n\n.fa-drivers-license:before,\n.fa-id-card:before {\n    content: \"\\f2c2\";\n}\n\n.fa-drivers-license-o:before,\n.fa-id-card-o:before {\n    content: \"\\f2c3\";\n}\n\n.fa-quora:before {\n    content: \"\\f2c4\";\n}\n\n.fa-free-code-camp:before {\n    content: \"\\f2c5\";\n}\n\n.fa-telegram:before {\n    content: \"\\f2c6\";\n}\n\n.fa-thermometer-4:before,\n.fa-thermometer:before,\n.fa-thermometer-full:before {\n    content: \"\\f2c7\";\n}\n\n.fa-thermometer-3:before,\n.fa-thermometer-three-quarters:before {\n    content: \"\\f2c8\";\n}\n\n.fa-thermometer-2:before,\n.fa-thermometer-half:before {\n    content: \"\\f2c9\";\n}\n\n.fa-thermometer-1:before,\n.fa-thermometer-quarter:before {\n    content: \"\\f2ca\";\n}\n\n.fa-thermometer-0:before,\n.fa-thermometer-empty:before {\n    content: \"\\f2cb\";\n}\n\n.fa-shower:before {\n    content: \"\\f2cc\";\n}\n\n.fa-bathtub:before,\n.fa-s15:before,\n.fa-bath:before {\n    content: \"\\f2cd\";\n}\n\n.fa-podcast:before {\n    content: \"\\f2ce\";\n}\n\n.fa-window-maximize:before {\n    content: \"\\f2d0\";\n}\n\n.fa-window-minimize:before {\n    content: \"\\f2d1\";\n}\n\n.fa-window-restore:before {\n    content: \"\\f2d2\";\n}\n\n.fa-times-rectangle:before,\n.fa-window-close:before {\n    content: \"\\f2d3\";\n}\n\n.fa-times-rectangle-o:before,\n.fa-window-close-o:before {\n    content: \"\\f2d4\";\n}\n\n.fa-bandcamp:before {\n    content: \"\\f2d5\";\n}\n\n.fa-grav:before {\n    content: \"\\f2d6\";\n}\n\n.fa-etsy:before {\n    content: \"\\f2d7\";\n}\n\n.fa-imdb:before {\n    content: \"\\f2d8\";\n}\n\n.fa-ravelry:before {\n    content: \"\\f2d9\";\n}\n\n.fa-eercast:before {\n    content: \"\\f2da\";\n}\n\n.fa-microchip:before {\n    content: \"\\f2db\";\n}\n\n.fa-snowflake-o:before {\n    content: \"\\f2dc\";\n}\n\n.fa-superpowers:before {\n    content: \"\\f2dd\";\n}\n\n.fa-wpexplorer:before {\n    content: \"\\f2de\";\n}\n\n.fa-meetup:before {\n    content: \"\\f2e0\";\n}\n"
  },
  {
    "path": "website/site/static/css/partials/footer.css",
    "content": "/*New Footer*/\n\nfooter .container {\n    padding: 4rem 0;\n}\n\nfooter #footer-logo {\n    grid-column: 1 / -1;\n    max-width: 18.8rem;\n    margin-bottom: 1.5rem;\n}\n\nfooter.site-footer {\n    background: linear-gradient(180deg, rgba(19, 30, 57, 0.9) 0%, var(--denim05) 20%);\n    padding: 5rem 0rem;\n    -webkit-font-smoothing: antialiased;\n\t-moz-osx-font-smoothing: grayscale;\n}\n\n@media (min-width: 992px) {\n    footer #footer-logo {\n        margin-bottom: 6.8rem;\n    }\n    footer.site-footer {\n        padding: 10rem 0rem 5rem;\n    }\n}\n\nfooter.site-footer .container {\n    display: grid;\n    grid-template-columns: repeat(2, minmax(0, 1fr));\n}\n\n@media (max-width: 1199.98px) {\n    footer.site-footer .container {\n        row-gap: 3rem;\n        column-gap: 5rem;\n    }\n}\n\n@media (min-width: 992px) {\n    footer.site-footer .container {\n        grid-template-columns: repeat(5, minmax(0, 1fr));\n        column-gap: 3rem;\n    }\n}\n\nfooter.site-footer ul {\n    list-style-type: none;\n    padding: 0;\n    margin: 0;\n}\n\nfooter.site-footer .footer-menu__heading {\n    color: var(--marigold03);\n    font-family: var(--firaMono);\n    font-size: 1.6rem;\n}\n\nfooter.site-footer .footer-menu__single a {\n    font-family: var(--tradeGothic);\n}\n\nfooter.site-footer .footer-menu__single a,\nfooter.site-footer .footer-menu__single a:visited {\n    color: #FFFFFF;\n    font-size: 1.5rem;\n    line-height: 2.7rem;\n}\n\nfooter.site-footer .footer-menu__single a:hover,\nfooter.site-footer .footer-menu__single a:focus {\n    color: var(--denim02);\n}\n\nfooter.site-footer .footer-menu__single li {\n    margin-bottom: 1rem;\n}\n\nfooter.site-footer .footer-menu__single ul {\n    margin-top: 1rem;\n}\n\nfooter.site-footer a {\n    text-decoration: none;\n}\n\nfooter.site-footer small {\n    font-family: var(--tradeGothic);\n}\n\nfooter.site-footer small,\nfooter.site-footer small a {\n    color: var(--denim01);\n}\n\nfooter.site-footer .secondary-footer__copy {\n    font-size: 1.5rem;\n}\n\nfooter.site-footer .secondary-footer__copy a:hover,\nfooter.site-footer .secondary-footer__copy a:focus {\n    color: var(--marigold03);\n}\n\nfooter.site-footer .secondary-footer__copy small {\n    display: block;\n}\n\nfooter.site-footer small.footer__recaptcha {\n    color: var(--denim01);\n    line-height: 1;\n}\n\nfooter.site-footer .secondary-footer__social li {\n    display: inline-block;\n}\n\nfooter.site-footer .secondary-footer__social li:hover svg path,\nfooter.site-footer .secondary-footer__social li:focus svg path {\n    fill: var(--denim02);\n}\n\n@media (max-width: 991.98px) {\n    footer.site-footer .secondary-footer__social li {\n        width: 45%;\n        margin-bottom: 2.5rem;\n    }\n\n    footer.site-footer .secondary-footer__social li img {\n        width: 5rem;\n    }\n}\n\n@media (max-width: 991.98px) {\n    footer.site-footer .secondary-footer__copy {\n        order: 6;\n        grid-column: 1/span 2;\n    }\n\n    footer.site-footer .mm-copyright {\n        display: block;\n    }\n}\n\n@media (min-width: 992px) {\n\n    footer.site-footer .secondary-footer__copy,\n    footer.site-footer .secondary-footer__social {\n        margin-top: 5rem;\n    }\n\n    footer.site-footer .secondary-footer__copy {\n        grid-column: 1/span 3;\n        place-self: end start;\n    }\n\n    footer.site-footer .secondary-footer__social {\n        grid-column: 4/span 2;\n        place-self: end end;\n    }\n\n    footer.site-footer .secondary-footer__social li {\n        margin-left: 2.5rem;\n    }\n}\n"
  },
  {
    "path": "website/site/static/css/partials/header.css",
    "content": "header {\n    background: #FAFAFA;\n    font-weight: 300;\n    font-size: 15px;\n    height: 100px;\n    line-height: 100px;\n    width: 100%;\n    position: relative;\n    z-index: 5;\n}\n\nheader .container {\n    display: flex;\n    position: relative;\n}\n\nheader.with-notification-bar {\n    margin-top: 64px;\n}\n\n.header__logo {\n    margin-top: -8px;\n}\n\n.header__feedback {\n    position: absolute;\n    right: 32px;\n    top: 0;\n}\n\n.header__links--right {\n    list-style: none;\n    padding: 0;\n    z-index: 5;\n    color: var(--black04);\n    font-weight: 600;\n    margin: 0;\n    margin-left: 24px;\n}\n\n.header__links--right li {\n    display: inline-block;\n}\n\n.header__links--right li.active a {\n    color: #186CDA;\n}\n\n.header__links--right a {\n    font-family: var(--tradeGothic);\n    color: inherit;\n    padding: 0 16px;\n}\n\n.homepage .header__links--right a:hover {\n    color: var(--marigold03) !important;\n}\n\n.header__links--right a:hover,\n.header__links--right a:focus {\n    color: #186CDA;\n}\n\n.header__menu-toggle {\n    cursor: pointer;\n    display: none;\n    position: relative;\n    top: 4rem;\n    position: absolute;\n    right: 16px;\n    top: 32px;\n    height: 48px;\n    width: 48px;\n    align-items: center;\n    justify-content: center;\n    flex-direction: column;\n}\n\n.header__menu-toggle .menu-bar {\n    height: 3px;\n    background-color: var(--denim02);\n    width: 28px;\n    margin-bottom: 6px;\n}\n\n.logo__white {\n    display: none;\n}\n\n@media (max-width: 992px) {\n    .header__links--right {\n        margin-left: 0;\n        background: #fff;\n        border-bottom: 1px solid #ddd;\n        display: none;\n        float: none;\n        line-height: 50px;\n        position: fixed;\n        top: 100px;\n        width: 100%;\n        left: 0;\n        padding-bottom: 12px;\n    }\n\n    .header__links--right li {\n        display: block;\n    }\n\n    .header__links--right li.active {\n        background: #f5f5f5;\n    }\n\n    .header__links--right li.active a {\n        color: #000 !important;\n    }\n\n    .header__links--right a {\n        display: block;\n    }\n\n    .header__menu-toggle {\n        display: flex;\n    }\n\n    .header__feedback {\n        position: relative;\n        left: 16px;\n    }\n}\n"
  },
  {
    "path": "website/site/static/css/partials/homepage.css",
    "content": ".homepage {\n    color: var(--denim05);\n    font-size: 1.8rem;\n}\n\n.homepage header {\n    position: fixed;\n    z-index: 555;\n}\n\n.homepage #wrapper {\n    overflow: hidden;\n}\n\n.homepage,\n.homepage p {\n    font-family: var(--tradeGothic);\n}\n\n.homepage__intro {\n    overflow: hidden;\n    background-color: var(--denim05);\n    color: white;\n    padding: 160px 0 64px;\n    display: flex;\n    align-items: center;\n}\n\n.homepage__intro .container-fluid {\n    display: flex;\n    align-items: center;\n}\n\n.homepage h1 {\n    font-size: 56px;\n    margin: 0 0 24px;\n}\n\n.homepage h2 {\n    font-size: 48px;\n    margin: 12px 0 24px;\n}\n\n.homepage-intro__text p {\n    font-size: 18px;\n}\n\n.homepage-intro__text,\n.homepage-intro__video {\n    width: 50%;\n}\n\n.homepage-intro__video {\n    padding-left: 80px;\n}\n\n.focalboard-tag {\n    font-size: 18px;\n    padding: 8px 12px;\n    background-color: rgba(255, 255, 255, 0.1);\n    border-radius: 8px;\n    display: inline-flex;\n    align-items: center;\n    font-weight: 600;\n    font-family: var(--tradeGothic);\n}\n\n.homepage-intro__github {\n    margin-top: 24px;\n    font-weight: bold;\n    font-size: 14px;\n    display: flex;\n    align-items: center;\n    font-weight: 400;\n}\n\n.homepage-intro__github i {\n    font-size: 20px;\n    margin-right: 6px;\n    color: var(--marigold02);\n}\n\n/* Homepage Sections */\n\n.section-half, .section-full {\n    margin-top: 140px;\n    position: relative;\n}\n\n.bgElementLeft {\n    position: absolute;\n    left: 0;\n    top: 100px;\n}\n\n.bgElementRight {\n    position: absolute;\n    right: 0;\n    top: 100px;\n}\n\n.bgElementCenter {\n    position: absolute;\n    top: 320px;\n    margin-left: -360px;\n}\n\n@media (min-width: 1024px) {\n    .section-half--reverse .section-half__container {\n        flex-direction: row-reverse;\n    }\n\n    .section-half--reverse .section-half__visual {\n        margin: 0 120px 0 0;\n    }\n\n    .homepage__intro {\n        height: 970px;\n    }\n}\n\n.section-half__container {\n    max-width: 1384px;\n    margin: 0 auto;\n    display: flex;\n    align-items: center;\n    padding: 0 32px;\n}\n\n.section-half__visual {\n    position: relative;\n    z-index: 5;\n    margin-left: 120px;\n}\n\n.section-half__container > div:first-child {\n    width: 40%;\n    flex: 0 0 40%;\n}\n\n.section-half p {\n    font-size: 18px;\n    color: var(--black03);\n}\n\n.section-half video {\n    border-radius: 8px;\n    max-width: 720px;\n    max-width: 100%;\n}\n\n@media (max-width: 1024px) {\n    .section-half__container {\n        flex-direction: column;\n        width: 100%;\n    }\n\n    .bgElementLeft, .bgElementRight, .bgElementCenter {\n        display: none;\n    }\n\n    .homepage__intro .container-fluid {\n        flex-direction: column;\n    }\n\n    .homepage-intro__text,\n    .homepage-intro__video {\n        width: 100%;\n    }\n\n    .homepage-intro__video lottie-player {\n        width: 600px !important;\n        margin-top: 48px;\n    }\n\n    .section-half__container {\n        gap: 40px;\n    }\n\n    .section-half__visual {\n        margin-left: 0;\n    }\n\n    .section-half__container div, .section-half__container div:first-child {\n        width: 100%;\n    }\n}\n\n\n/* Tabs */\n\n.image-shadow {\n    box-shadow: var(--shadow-image);\n}\n\n.tabs-shadow {\n    box-shadow: 0px 24px 48px rgba(0, 0, 0, 0.08);\n    width: 720px;\n}\n\n\n/* Help Section */\n\n.help-section {\n    margin: 140px 0 0;\n    background-color: #F6F8FD;\n    padding: 140px 0;\n    margin-bottom: -5.5rem;\n}\n\n.help-section .section-half {\n    margin: 0;\n}\n\n\n/* Feature Bullets */\n\n.feature-bullets {\n    display: grid;\n    grid-template-columns: auto auto auto;\n    gap: 16px;\n    font-weight: bold;\n    margin-top: 48px;\n}\n\n.feature-bullets div {\n    text-align: left;\n    padding: 10px 24px;\n    background-image: url('../../img/check.svg');\n    background-position: left center;\n    background-repeat: no-repeat;\n}\n\n@media (max-width: 992px) {\n    .feature-bullets {\n        display: grid;\n        grid-template-columns: auto auto;\n    }\n}\n\n@media (max-width: 640px) {\n    .feature-bullets {\n        display: grid;\n        grid-template-columns: auto;\n    }\n}\n\n/* View Tabs */\n\n.view-tabs {\n    display: flex;\n    gap: 16px;\n    flex-wrap: wrap;\n    margin-bottom: 24px;\n}\n\n.view-tabs button:not(.active) {\n    background: rgba(var(--yellow), 0.12);\n    color: var(--black03);\n}\n\n.view-content {\n    background-color: white;\n    border-radius: 12px;\n    padding: 32px 32px 0;\n    box-shadow: var(--shadow-image);\n    height: 472px;\n    overflow: hidden;\n}\n\n.view-content img {\n    margin-bottom: 32px;\n}\n\n@media (max-width: 1024px) {\n    .view-content {\n        height: auto;\n    }\n}\n\n/* video */\n\n.align-video {\n    box-shadow: var(--shadow-image);\n    border-radius: 8px;\n    overflow: hidden;\n}\n\n/* Roadmap Section */\n\n.roadmap-section .section-half__container {\n    background: rgba(246, 192, 34, 0.08);\n    border-radius: 24px;\n    padding-left: 100px;\n    min-height: 812px;\n}\n\n.roadmap-section .section-half__visual img {\n    max-width: initial;\n    position: absolute;\n    left: 0;\n    top: -320px;\n}\n\n@media (max-width: 1024px) {\n    .roadmap-section .section-half__container {\n        padding: 64px 32px;\n    }\n\n    .roadmap-section .section-half__visual img {\n        max-width: 100%;\n        position: relative;\n        top: initial;\n    }\n}\n\n.homepage__threecol {\n    margin-top: 80px;\n    gap: 24px;\n    display: grid;\n    grid-template-columns: repeat(3, 1fr);\n}\n\n@media (max-width: 992px) {\n    .homepage__threecol {\n        grid-template-columns: repeat(1, 1fr);\n    }\n}\n\n.homepage__threecol h4 {\n    margin: 2rem 0;\n    font-weight: 700;\n}\n\n@media (max-width: 1280px) {\n    .homepage__threecol {\n        flex-wrap: wrap;\n    }\n}\n"
  },
  {
    "path": "website/site/static/css/partials/root.css",
    "content": ":root {\n    --denim01:\t\t#C5D2EC;\n    --denim02:\t\t#7E96C8;\n    --denim03:\t\t#4A69AC;\n    --denim04:\t\t#2C4987;\n    --denim05:\t\t#1E325C;\n    --denim06:\t\t#162645;\n    --denimBtn:\t\t#1C58D9;\n    --denimBtnRgb:\t28, 88, 217;\n    --marigold01:\t#FFE099;\n    --marigold02:\t#FFD470;\n    --marigold03:\t#FFBC1F;\n    --marigold04:\t#CC8F00;\n    --marigold05:\t#BB8D21;\n    --marigold06:\t#A37200;\n    --yellow:\t    246, 192, 34;\n    --brown01:\t\t#674600;\n    --brown02:\t\t#5B3515;\n    --brown03:\t\t#1D1400;\n    --black01:\t\t#EBEBEF;\n    --black02:\t\t#BABEC9;\n    --black03:\t\t#818698;\n    --black04:\t\t#5A6072;\n    --black05:\t\t#363A45;\n    --black06:\t\t#1B1D22;\n    --tradeGothic: 'trade-gothic-next', sans-serif;\n    --notoSans: 'Noto Sans', Helvetica, Arial, sans-serif;\n    --firaMono: \"Fira Mono\", monospace;\n    --shadow-image: 0px 24px 48px rgb(0 0 0 / 8%);\n    --transition-btn: all 0.15s;\n}\n"
  },
  {
    "path": "website/site/static/css/partials/sidebar.css",
    "content": ".sidebar {\n    line-height: 40px;\n    margin: 50px 0;\n}\n\n.sidebar .item {\n    border-radius: 3px;\n    display: block;\n    margin: 5px 0;\n    padding: 0 15px 0 33px;\n    width: 100%;\n}\n\n.sidebar .active,\n.sidebar .item:hover {\n    background: #f5f5f5;\n}\n\n.sidebar a {\n    font-family: var(--tradeGothic);\n}\n\n.sidebar a.active,\n.sidebar a:hover {\n    background: #f5f5f5;\n}\n\n.sidebar a.active {\n    color: var(--black05);\n    font-weight: 800;\n}\n\n.sidebar .fa {\n    margin-left: -24px;\n    text-align: center;\n    width: 20px;\n}\n\n.sidebar .sub-menu {\n    display: none;\n    font-size: .95em;\n    line-height: 35px;\n    margin: 0 0 5px;\n    padding-left: 20px;\n    width: 100%;\n}\n\n.sidebar__menu-toggle {\n    background: #f5f5f5;\n    cursor: pointer;\n    display: none;\n    height: 50px;\n    line-height: 50px;\n    margin: 0 -15px;\n    opacity: .6;\n    padding: 0 15px;\n    font-family: var(--notoSans);\n}\n\n.sidebar__menu-toggle .fa {\n    font-size: 20px;\n    margin-top: 15px;\n}\n"
  },
  {
    "path": "website/site/static/css/partials/template-picker.css",
    "content": "/* Template Picker */\n\n.template-picker-container {\n    text-align: center;\n    margin-top: 180px;\n    position: relative;\n}\n\n.template-picker {\n    position: relative;\n    z-index: 5;\n    display: flex;\n    overflow: hidden;\n    margin-top: 48px;\n    border-radius: 12px;\n    background-color: white;\n    box-shadow: var(--shadow-image);\n}\n\n.template-picker__sidebar {\n    background: #F7F7F8;\n    flex: 0 0 292px;\n    text-align: left;\n    padding: 16px;\n}\n\n.template-picker__preview {\n    height: 740px;\n}\n\n.template-picker-item {\n    margin-bottom: 8px;\n    cursor: pointer;\n    font-size: 15px;\n    font-weight: 600;\n    opacity: 0.72;\n    gap: 10px;\n    padding: 0 10px;\n    height: 40px;\n    display: flex;\n    align-items: center;\n    border-radius: 4px;\n    transition: var(--transition-btn);\n}\n\n.template-picker-item:hover {\n    background-color: rgba(0, 0, 0, 0.06);\n}\n\n.template-picker-item.active {\n    background-color: rgba(var(--denimBtnRgb), 0.08);\n    color: var(--denimBtn);\n}\n\n@media (max-width: 1280px) {\n    .template-picker {\n        display: block;\n    }\n\n    .template-picker__sidebar {\n        display: flex;\n        flex-wrap: wrap;\n        justify-content: center;\n        gap: 0 40px;\n        background-color: transparent;\n    }\n\n    .template-picker__preview {\n        height: auto;\n    }\n}\n"
  },
  {
    "path": "website/site/static/css/styles.css",
    "content": "@import url(\"partials/root.css\");\n@import url(\"partials/fontawesome.css\");\n@import url(\"partials/base.css\");\n@import url(\"partials/buttons.css\");\n@import url(\"partials/header.css\");\n@import url(\"partials/footer.css\");\n@import url(\"partials/blog.css\");\n@import url(\"partials/sidebar.css\");\n@import url(\"partials/homepage.css\");\n@import url(\"partials/banners.css\");\n@import url(\"partials/template-picker.css\");\n\n\n.tags-sidebar .list-unstyled {\n    padding-top: .5rem;\n}\n\n.label {\n    display: inline;\n    padding: .2em .6em .3em;\n    font-size: 85%;\n    line-height: 1;\n    color: #fff;\n    text-align: center;\n    white-space: nowrap;\n    vertical-align: baseline;\n    border-radius: .25em;\n    background: #999;\n}\n\n.subscribe-box {\n    border: 1px solid rgba(221, 221, 221, 0.3);\n    padding: 50px 50px 40px;\n}\n\n.subscribe-form__input {\n    background: #F7F8F9;\n    height: 60px;\n}\n\n@media (max-width: 992px) {\n    .sidebar__menu-toggle {\n        display: block;\n    }\n\n    .sidebar {\n        border-radius: 3px;\n        border: 1px solid #ddd;\n        margin: 50px 16px 0;\n    }\n\n    .sidebar__links {\n        background: #fff;\n        display: none;\n    }\n}\n\n@media (max-width: 768px) {\n\n    .doc-content {\n        padding: 0 20px;\n    }\n\n    .homepage-boxes__box {\n        border-bottom: 1px solid #ddd;\n        margin: 0 auto;\n    }\n\n    .homepage-boxes {\n        margin-top: -13em;\n    }\n}\n\n.steps-row {\n    display: flex;\n    margin: 3rem 0;\n}\n\n.steps-number {\n    width: 50px;\n    height: 50px;\n    border-radius: 25px;\n    border: 1px solid #007bff;\n    display: flex;\n    flex-shrink: 0;\n    justify-content: center;\n    align-items: center;\n    margin-right: 35px;\n    color: #007bff;\n}\n\n.steps-header {\n    margin-bottom: 10px;\n}\n\n.alert {\n    font-size: 95%;\n}\n\n@media (max-width: 768px) {\n    .blog-list .blog-item__date {\n        float: none;\n        display: block;\n        margin-top: 1.5rem;\n    }\n}\n\nblockquote {\n    margin: 0 0.5rem;\n    padding: 0 0.5rem;\n    border-left: 1px solid grey;\n}\n\npre {\n    padding: 0.5rem;\n}\n\nol>li+li {\n    margin-top: 1rem;\n}\n\n"
  },
  {
    "path": "website/site/static/css/tabs.css",
    "content": "div.tab {\n    overflow: hidden;\n    border: 1px solid #ccc;\n    background-color: #f1f1f1;\n}\n\ndiv.tab button {\n    background-color: inherit;\n    float: left;\n    border: none;\n    outline: none;\n    cursor: pointer;\n    padding: 14px 16px;\n    transition: 0.3s;\n}\n\ndiv.tab button:hover {\n    background-color: #ddd;\n}\n\ndiv.tab button.active {\n    background-color: #ccc;\n}\n\n.tabcontent {\n    display: none;\n    padding: 25px 12px;\n    border: 1px solid #ccc;\n    border-top: none;\n}\n\n.tabcontent {\n    -webkit-animation: fadeEffect 1s;\n    animation: fadeEffect 1s; /* Fading effect takes 1 second */\n}\n\n@-webkit-keyframes fadeEffect {\n    from {opacity: 0;}\n    to {opacity: 1;}\n}\n\n@keyframes fadeEffect {\n    from {opacity: 0;}\n    to {opacity: 1;}\n}\n"
  },
  {
    "path": "website/site/static/js/main.js",
    "content": "$(document).ready(function(){\n    $('.header__menu-toggle, .sidebar__menu-toggle').on('click', function(){\n        $(this).next().slideToggle();\n    })\n\n    $('.sub-menu__toggle').on('click', function(){\n        $(this).parent().next('.sub-menu').toggle();\n        $(this).toggleClass('fa-plus-square-o fa-minus-square-o');\n    });\n\n    let hideBannerExist = document.cookie.split(';').filter(item => { return item.indexOf('hideBanner=') >= 0}).length\n\n    if(hideBannerExist){\n        $(\".notification-bar\").addClass(\"closed\");\n        $(\"header\").removeClass(\"with-notification-bar\");\n    }\n\n    $('.notification-bar__close').on('click', function(){\n        if(!hideBannerExist){\n            $(\".notification-bar\").addClass(\"closed\");\n            $(\"header\").removeClass(\"with-notification-bar\");\n        }\n        document.cookie = 'hideBanner=true'\n    });\n});\n"
  },
  {
    "path": "website/site/static/js/tabs.js",
    "content": "function openTab(evt, tabName) {\n    // Declare all variables\n    var i, tabcontent, tablinks;\n\n    // Get all elements with class=\"tabcontent\" and hide them\n    tabcontent = document.getElementsByClassName(\"tabcontent\");\n    for (i = 0; i < tabcontent.length; i++) {\n        tabcontent[i].style.display = \"none\";\n    }\n\n    // Get all elements with class=\"tablinks\" and remove the class \"active\"\n    tablinks = document.getElementsByClassName(\"tablinks\");\n    for (i = 0; i < tablinks.length; i++) {\n        tablinks[i].className = tablinks[i].className.replace(\" active\", \"\");\n    }\n\n    // Show the current tab, and add an \"active\" class to the button that opened the tab\n    document.getElementById(tabName).style.display = \"block\";\n    evt.currentTarget.className += \" active\";\n}\n"
  },
  {
    "path": "website/site/static/lottie/intro-section.json",
    "content": "{\"assets\":[{\"id\":\"tl8GDiDql8LN-0CPe8g0K\",\"layers\":[{\"ddd\":0,\"ind\":13,\"ty\":4,\"nm\":\"\",\"ln\":\"342:103880\",\"sr\":1,\"ks\":{\"a\":{\"a\":0,\"k\":[0,0]},\"o\":{\"a\":0,\"k\":10},\"p\":{\"a\":0,\"k\":[3830,3830]},\"r\":{\"a\":0,\"k\":0},\"s\":{\"a\":0,\"k\":[100,100]},\"sk\":{\"a\":0,\"k\":0},\"sa\":{\"a\":0,\"k\":0}},\"ao\":0,\"ip\":0,\"op\":480,\"st\":0,\"bm\":0,\"shapes\":[{\"ty\":\"gr\",\"hd\":false,\"bm\":0,\"it\":[{\"ty\":\"el\",\"hd\":false,\"p\":{\"a\":0,\"k\":[0,0]},\"s\":{\"a\":0,\"k\":[1112,1112]},\"d\":1},{\"ty\":\"fl\",\"hd\":false,\"bm\":0,\"c\":{\"a\":0,\"k\":[1,1,1]},\"r\":1,\"o\":{\"a\":0,\"k\":100}},{\"ty\":\"tr\",\"nm\":\"Transform\",\"a\":{\"a\":0,\"k\":[0,0]},\"o\":{\"a\":0,\"k\":100},\"p\":{\"a\":0,\"k\":[0,0]},\"r\":{\"a\":0,\"k\":0},\"s\":{\"a\":0,\"k\":[100,100]},\"sk\":{\"a\":0,\"k\":0},\"sa\":{\"a\":0,\"k\":0}}],\"np\":0}]},{\"ddd\":0,\"ind\":14,\"ty\":4,\"nm\":\"\",\"ln\":\"342:103879\",\"sr\":1,\"ks\":{\"a\":{\"a\":0,\"k\":[0,0]},\"o\":{\"a\":0,\"k\":6},\"p\":{\"a\":0,\"k\":[3830,3830]},\"r\":{\"a\":0,\"k\":0},\"s\":{\"a\":0,\"k\":[100,100]},\"sk\":{\"a\":0,\"k\":0},\"sa\":{\"a\":0,\"k\":0}},\"ao\":0,\"ip\":0,\"op\":480,\"st\":0,\"bm\":0,\"shapes\":[{\"ty\":\"gr\",\"hd\":false,\"bm\":0,\"it\":[{\"ty\":\"el\",\"hd\":false,\"p\":{\"a\":0,\"k\":[0,0]},\"s\":{\"a\":0,\"k\":[1352,1352]},\"d\":1},{\"ty\":\"st\",\"hd\":false,\"bm\":0,\"c\":{\"a\":0,\"k\":[1,1,1]},\"lc\":2,\"lj\":1,\"o\":{\"a\":0,\"k\":100},\"w\":{\"a\":0,\"k\":8}},{\"ty\":\"tr\",\"nm\":\"Transform\",\"a\":{\"a\":0,\"k\":[0,0]},\"o\":{\"a\":0,\"k\":100},\"p\":{\"a\":0,\"k\":[0,0]},\"r\":{\"a\":0,\"k\":0},\"s\":{\"a\":0,\"k\":[100,100]},\"sk\":{\"a\":0,\"k\":0},\"sa\":{\"a\":0,\"k\":0}}],\"np\":0}]}]},{\"id\":\"hgNDtZ3SPZxtuempkhwbe\",\"layers\":[{\"ddd\":0,\"ind\":15,\"ty\":4,\"ln\":\"layer_15\",\"sr\":1,\"ks\":{\"a\":{\"a\":0,\"k\":[13.5,14]},\"o\":{\"a\":0,\"k\":100},\"p\":{\"a\":0,\"k\":[3830.5,3830.6]},\"r\":{\"a\":0,\"k\":0},\"s\":{\"a\":0,\"k\":[137.04,128.57]},\"sk\":{\"a\":0,\"k\":0},\"sa\":{\"a\":0,\"k\":0}},\"ao\":0,\"ip\":0,\"op\":480,\"st\":0,\"bm\":0,\"shapes\":[{\"ty\":\"gr\",\"nm\":\"surface36\",\"it\":[{\"ty\":\"gr\",\"it\":[{\"ty\":\"sh\",\"d\":1,\"ks\":{\"a\":0,\"k\":{\"c\":true,\"i\":[[0,0],[0,0],[0.45,-0.45],[0,-0.63],[0,0],[0,0],[0,0],[-0.43,0.43],[0,0.61],[0,0],[0.45,0.43],[0.63,0]],\"o\":[[0,0],[-0.61,0],[-0.45,0.43],[0,0],[0,0],[0,0],[0.63,0],[0.45,-0.45],[0,0],[0,-0.63],[-0.43,-0.45],[0,0]],\"v\":[[22.49,2.99],[4.51,2.99],[2.91,3.67],[2.24,5.26],[2.24,25.51],[6.75,21],[22.49,21],[24.09,20.35],[24.76,18.76],[24.76,5.26],[24.09,3.67],[22.49,2.99]]}}},{\"ty\":\"sh\",\"d\":1,\"ks\":{\"a\":0,\"k\":{\"c\":true,\"i\":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],\"o\":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],\"v\":[[18.57,9.74],[12.37,15.93],[8.45,12.01],[10.02,10.42],[12.37,12.77],[16.98,8.18]]}}},{\"ty\":\"fl\",\"c\":{\"a\":0,\"k\":[1,1,1,1]},\"r\":1,\"o\":{\"a\":0,\"k\":100}},{\"ty\":\"tr\",\"nm\":\"Transform\",\"a\":{\"a\":0,\"k\":[0,0]},\"o\":{\"a\":0,\"k\":100},\"p\":{\"a\":0,\"k\":[0,0]},\"r\":{\"a\":0,\"k\":0},\"s\":{\"a\":0,\"k\":[100,100]},\"sk\":{\"a\":0,\"k\":0},\"sa\":{\"a\":0,\"k\":0}}]},{\"ty\":\"tr\",\"nm\":\"Transform\",\"a\":{\"a\":0,\"k\":[0,0]},\"o\":{\"a\":0,\"k\":100},\"p\":{\"a\":0,\"k\":[0,0]},\"r\":{\"a\":0,\"k\":0},\"s\":{\"a\":0,\"k\":[100,100]},\"sk\":{\"a\":0,\"k\":0},\"sa\":{\"a\":0,\"k\":0}}]}]}]},{\"id\":\"5FNAOrXP6rp64bE1SY5nR\",\"layers\":[{\"ddd\":0,\"ind\":5,\"ty\":0,\"nm\":\"\",\"ln\":\"precomp_I342:103891;1915:1875\",\"sr\":1,\"ks\":{\"a\":{\"a\":0,\"k\":[3830,3830]},\"o\":{\"a\":0,\"k\":100},\"p\":{\"a\":0,\"k\":[3830,3830]},\"r\":{\"a\":0,\"k\":0},\"s\":{\"a\":0,\"k\":[100,100]},\"sk\":{\"a\":0,\"k\":0},\"sa\":{\"a\":0,\"k\":0}},\"ao\":0,\"w\":7660,\"h\":7660,\"ip\":0,\"op\":480,\"st\":0,\"bm\":0,\"refId\":\"hgNDtZ3SPZxtuempkhwbe\"}]},{\"id\":\"5ZyVIkmClEMYgN_sjgBH-\",\"layers\":[{\"ddd\":0,\"ind\":4,\"ty\":0,\"nm\":\"\",\"ln\":\"precomp_342:1038914\",\"sr\":1,\"ks\":{\"a\":{\"a\":0,\"k\":[3830,3830]},\"o\":{\"a\":0,\"k\":100},\"p\":{\"a\":0,\"k\":[40,40]},\"r\":{\"a\":0,\"k\":0},\"s\":{\"a\":0,\"k\":[100,100]},\"sk\":{\"a\":0,\"k\":0},\"sa\":{\"a\":0,\"k\":0}},\"ao\":0,\"w\":7660,\"h\":7660,\"ip\":0,\"op\":480,\"st\":0,\"bm\":0,\"refId\":\"5FNAOrXP6rp64bE1SY5nR\"},{\"ddd\":0,\"ind\":6,\"ty\":2,\"nm\":\"\",\"ln\":\"image_342:106120\",\"sr\":1,\"ks\":{\"a\":{\"a\":0,\"k\":[80,80]},\"o\":{\"a\":0,\"k\":100},\"p\":{\"a\":0,\"k\":[40,40]},\"r\":{\"a\":0,\"k\":0},\"s\":{\"a\":0,\"k\":[50,50]},\"sk\":{\"a\":0,\"k\":0},\"sa\":{\"a\":0,\"k\":0}},\"ao\":0,\"ip\":0,\"op\":480,\"st\":0,\"bm\":0,\"refId\":\"MiP8hF_GKoeyJDEFJnuyT\"}]},{\"id\":\"N8_ysg5IzHBHgCLskwGY1\",\"layers\":[{\"ddd\":0,\"ind\":16,\"ty\":4,\"ln\":\"layer_16\",\"sr\":1,\"ks\":{\"a\":{\"a\":0,\"k\":[13.5,14]},\"o\":{\"a\":0,\"k\":100},\"p\":{\"a\":0,\"k\":[3830.5,3830.6]},\"r\":{\"a\":0,\"k\":0},\"s\":{\"a\":0,\"k\":[137.04,128.57]},\"sk\":{\"a\":0,\"k\":0},\"sa\":{\"a\":0,\"k\":0}},\"ao\":0,\"ip\":0,\"op\":480,\"st\":0,\"bm\":0,\"shapes\":[{\"ty\":\"gr\",\"nm\":\"surface31\",\"it\":[{\"ty\":\"gr\",\"it\":[{\"ty\":\"sh\",\"d\":1,\"ks\":{\"a\":0,\"k\":{\"c\":true,\"i\":[[0,0],[0,0],[0.43,-0.43],[0.63,0],[0.45,0.45],[0,0.61]],\"o\":[[0,0],[0,0.61],[-0.43,0.45],[-0.61,0],[-0.43,-0.43],[0,0]],\"v\":[[11.26,24.38],[15.74,24.38],[15.09,25.94],[13.5,26.62],[11.91,25.94],[11.26,24.38]]}}},{\"ty\":\"sh\",\"d\":1,\"ks\":{\"a\":0,\"k\":{\"c\":true,\"i\":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[-0.46,1.01],[-0.85,0.74],[-1.11,0.34],[0,0],[-0.43,0.43],[-0.61,0],[-0.43,-0.45],[0,-0.63],[0,0],[-0.84,-0.76],[-0.47,-1.02],[0,-1.15],[0,0]],\"o\":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,-1.15],[0.47,-1.02],[0.86,-0.76],[0,0],[0,-0.63],[0.45,-0.45],[0.61,0],[0.45,0.43],[0,0],[1.11,0.34],[0.86,0.74],[0.46,1.01],[0,0],[0,0]],\"v\":[[23.63,22.13],[23.63,23.24],[3.38,23.24],[3.38,22.13],[5.62,19.87],[5.62,13.12],[6.32,9.88],[8.29,7.23],[11.26,5.58],[11.26,5.26],[11.91,3.67],[13.5,2.99],[15.07,3.67],[15.74,5.26],[15.74,5.58],[18.68,7.23],[20.68,9.88],[21.38,13.12],[21.38,19.87]]}}},{\"ty\":\"sh\",\"d\":1,\"ks\":{\"a\":0,\"k\":{\"c\":true,\"i\":[[0,0],[0.5,0.86],[0.86,0.48],[1.04,0],[0.87,-0.5],[0.5,-0.86],[0,-1.03],[0,0],[0,0]],\"o\":[[0,-1.03],[-0.48,-0.86],[-0.85,-0.5],[-1.04,0],[-0.84,0.48],[-0.48,0.86],[0,0],[0,0],[0,0]],\"v\":[[19.12,13.12],[18.36,10.28],[16.34,8.26],[13.5,7.5],[10.64,8.26],[8.61,10.28],[7.88,13.12],[7.88,21],[19.12,21]]}}},{\"ty\":\"fl\",\"c\":{\"a\":0,\"k\":[1,1,1,1]},\"r\":1,\"o\":{\"a\":0,\"k\":100}},{\"ty\":\"tr\",\"nm\":\"Transform\",\"a\":{\"a\":0,\"k\":[0,0]},\"o\":{\"a\":0,\"k\":100},\"p\":{\"a\":0,\"k\":[0,0]},\"r\":{\"a\":0,\"k\":0},\"s\":{\"a\":0,\"k\":[100,100]},\"sk\":{\"a\":0,\"k\":0},\"sa\":{\"a\":0,\"k\":0}}]},{\"ty\":\"tr\",\"nm\":\"Transform\",\"a\":{\"a\":0,\"k\":[0,0]},\"o\":{\"a\":0,\"k\":100},\"p\":{\"a\":0,\"k\":[0,0]},\"r\":{\"a\":0,\"k\":0},\"s\":{\"a\":0,\"k\":[100,100]},\"sk\":{\"a\":0,\"k\":0},\"sa\":{\"a\":0,\"k\":0}}]}]}]},{\"id\":\"xd6OfK8WthoUviS3Bdz-L\",\"layers\":[{\"ddd\":0,\"ind\":9,\"ty\":0,\"nm\":\"\",\"ln\":\"precomp_I342:103889;1915:1879\",\"sr\":1,\"ks\":{\"a\":{\"a\":0,\"k\":[3830,3830]},\"o\":{\"a\":0,\"k\":100},\"p\":{\"a\":0,\"k\":[3830,3830]},\"r\":{\"a\":0,\"k\":0},\"s\":{\"a\":0,\"k\":[100,100]},\"sk\":{\"a\":0,\"k\":0},\"sa\":{\"a\":0,\"k\":0}},\"ao\":0,\"w\":7660,\"h\":7660,\"ip\":0,\"op\":480,\"st\":0,\"bm\":0,\"refId\":\"N8_ysg5IzHBHgCLskwGY1\"}]},{\"id\":\"VOuaAEdkBzNNKU0gWKJ-r\",\"layers\":[{\"ddd\":0,\"ind\":8,\"ty\":0,\"nm\":\"\",\"ln\":\"precomp_342:1038898\",\"sr\":1,\"ks\":{\"a\":{\"a\":0,\"k\":[3830,3830]},\"o\":{\"a\":0,\"k\":100},\"p\":{\"a\":0,\"k\":[40,40]},\"r\":{\"a\":0,\"k\":0},\"s\":{\"a\":0,\"k\":[100,100]},\"sk\":{\"a\":0,\"k\":0},\"sa\":{\"a\":0,\"k\":0}},\"ao\":0,\"w\":7660,\"h\":7660,\"ip\":0,\"op\":480,\"st\":0,\"bm\":0,\"refId\":\"xd6OfK8WthoUviS3Bdz-L\"},{\"ddd\":0,\"ind\":17,\"ty\":4,\"nm\":\"\",\"ln\":\"342:103888\",\"sr\":1,\"ks\":{\"a\":{\"a\":0,\"k\":[0,0]},\"o\":{\"a\":0,\"k\":100},\"p\":{\"a\":0,\"k\":[40,40]},\"r\":{\"a\":0,\"k\":0},\"s\":{\"a\":0,\"k\":[100,100]},\"sk\":{\"a\":0,\"k\":0},\"sa\":{\"a\":0,\"k\":0}},\"ao\":0,\"ip\":0,\"op\":480,\"st\":0,\"bm\":0,\"shapes\":[{\"ty\":\"gr\",\"hd\":false,\"bm\":0,\"it\":[{\"ty\":\"rc\",\"hd\":false,\"p\":{\"a\":0,\"k\":[0,0]},\"r\":{\"a\":0,\"k\":1110},\"s\":{\"a\":0,\"k\":[80,80]}},{\"ty\":\"fl\",\"hd\":false,\"bm\":0,\"c\":{\"a\":0,\"k\":[0.91,0.23,0.24]},\"r\":1,\"o\":{\"a\":0,\"k\":100}},{\"ty\":\"tr\",\"nm\":\"Transform\",\"a\":{\"a\":0,\"k\":[0,0]},\"o\":{\"a\":0,\"k\":100},\"p\":{\"a\":0,\"k\":[0,0]},\"r\":{\"a\":0,\"k\":0},\"s\":{\"a\":0,\"k\":[100,100]},\"sk\":{\"a\":0,\"k\":0},\"sa\":{\"a\":0,\"k\":0}}],\"np\":0}]}]},{\"id\":\"n3_n5wvl-IucVyppudovb\",\"layers\":[{\"ddd\":0,\"ind\":18,\"ty\":4,\"ln\":\"layer_18\",\"sr\":1,\"ks\":{\"a\":{\"a\":0,\"k\":[170,12]},\"o\":{\"a\":0,\"k\":100},\"p\":{\"a\":0,\"k\":[3830,3830]},\"r\":{\"a\":0,\"k\":0},\"s\":{\"a\":0,\"k\":[133.24,133.33]},\"sk\":{\"a\":0,\"k\":0},\"sa\":{\"a\":0,\"k\":0}},\"ao\":0,\"ip\":0,\"op\":480,\"st\":0,\"bm\":0,\"shapes\":[{\"ty\":\"gr\",\"nm\":\"surface36\",\"it\":[{\"ty\":\"gr\",\"it\":[{\"ty\":\"sh\",\"d\":1,\"ks\":{\"a\":0,\"k\":{\"c\":true,\"i\":[[0,0],[0.13,-0.54],[0.26,-0.43],[0.39,-0.26],[0.54,0],[0.34,0.26],[0.09,0.39],[0,0],[0.41,-0.26],[0.6,0],[0.54,0.62],[0,1.03],[-0.33,0.61],[-0.6,0.35],[-0.82,0],[-0.54,-0.1],[-0.32,-0.11],[0,0],[0,-0.12],[0,-0.05],[-0.18,-0.2],[-0.25,0],[-0.22,0.29],[-0.12,0.48],[0,0.59],[0.43,0.74],[0.75,0.39],[0.96,0],[0.91,-0.54],[0.47,-0.95],[0,-1.23],[-0.9,-0.92],[-1.71,0],[-0.71,0.15],[-0.57,0.21],[0,0],[0.67,-0.14],[0.78,0],[1.03,0.53],[0.54,0.99],[0,1.39],[-0.35,0.94],[-0.67,0.7],[-0.94,0.39],[-1.18,0],[-0.98,-0.52],[-0.56,-0.96],[0,-1.32]],\"o\":[[0,0.56],[-0.12,0.53],[-0.27,0.43],[-0.39,0.25],[-0.49,0],[-0.33,-0.26],[0,0],[-0.23,0.39],[-0.41,0.26],[-1,0],[-0.53,-0.63],[0,-0.8],[0.33,-0.61],[0.6,-0.35],[0.55,0],[0.55,0.09],[0,0],[-0.01,0.17],[0,0.12],[0,0.57],[0.18,0.19],[0.33,0],[0.23,-0.3],[0.12,-0.49],[0,-1.06],[-0.43,-0.75],[-0.75,-0.4],[-1.32,0],[-0.9,0.54],[-0.47,0.95],[0,1.71],[0.91,0.91],[0.7,0],[0.71,-0.16],[0,0],[-0.55,0.23],[-0.67,0.14],[-1.45,0],[-1.03,-0.53],[-0.55,-1],[0,-1.11],[0.36,-0.95],[0.67,-0.7],[0.95,-0.39],[1.27,0],[0.98,0.52],[0.56,0.95],[0,0]],\"v\":[[53.65,12.32],[53.45,13.96],[52.87,15.41],[51.88,16.45],[50.48,16.82],[49.23,16.43],[48.61,15.45],[48.49,15.45],[47.52,16.43],[46,16.82],[43.7,15.89],[42.9,13.41],[43.39,11.3],[44.79,9.86],[46.93,9.33],[48.57,9.48],[49.87,9.79],[49.69,13.4],[49.67,13.84],[49.67,14.08],[49.94,15.23],[50.58,15.51],[51.41,15.08],[51.93,13.92],[52.1,12.31],[51.45,9.6],[49.68,7.89],[47.12,7.29],[43.78,8.11],[41.72,10.34],[41.02,13.61],[42.37,17.55],[46.3,18.93],[48.42,18.7],[50.34,18.14],[50.34,19.55],[48.52,20.11],[46.34,20.31],[42.62,19.52],[40.25,17.25],[39.44,13.66],[39.96,10.59],[41.5,8.13],[43.92,6.49],[47.11,5.91],[50.48,6.69],[52.8,8.91],[53.65,12.32]]}}},{\"ty\":\"sh\",\"d\":1,\"ks\":{\"a\":0,\"k\":{\"c\":true,\"i\":[[0,0],[-0.29,-0.33],[-0.5,0],[-0.28,0.47],[-0.05,0.77],[0,0],[0.21,0.02],[0.23,0],[0.36,-0.27],[0.16,-0.43],[0,-0.47]],\"o\":[[0,0.72],[0.29,0.32],[0.64,0],[0.28,-0.48],[0,0],[-0.15,-0.04],[-0.2,-0.03],[-0.59,0],[-0.36,0.27],[-0.16,0.42],[0,0]],\"v\":[[44.57,13.45],[45.01,15.02],[46.2,15.51],[47.59,14.8],[48.09,12.92],[48.19,10.8],[47.64,10.71],[47,10.66],[45.58,11.07],[44.81,12.11],[44.57,13.45]]}}},{\"ty\":\"sh\",\"d\":1,\"ks\":{\"a\":0,\"k\":{\"c\":true,\"i\":[[0,0],[0.24,0.04],[0.18,0.06],[0,0],[-0.22,-0.03],[-0.25,0],[-0.26,0.13],[-0.16,0.32],[0,0.58],[0,0],[0,0],[0,0],[0.67,-0.64],[1.11,0]],\"o\":[[-0.32,0],[-0.25,-0.04],[0,0],[0.19,0.05],[0.21,0.04],[0.32,0],[0.27,-0.12],[0.16,-0.32],[0,0],[0,0],[0,0],[0,1.44],[-0.67,0.64],[0,0]],\"v\":[[54.71,22.33],[53.87,22.27],[53.23,22.12],[53.23,20.35],[53.84,20.47],[54.54,20.53],[55.41,20.34],[56.04,19.67],[56.28,18.31],[56.28,5.9],[58.39,5.9],[58.39,18.24],[57.38,21.36],[54.71,22.33]]}}},{\"ty\":\"sh\",\"d\":1,\"ks\":{\"a\":0,\"k\":{\"c\":true,\"i\":[[0,0],[0.83,-0.89],[1.43,0],[0.68,0.39],[0.39,0.75],[0,1.08],[-0.82,0.88],[-1.43,0],[-0.69,-0.4],[-0.39,-0.75],[0,-1.07]],\"o\":[[0,1.61],[-0.82,0.88],[-0.89,0],[-0.69,-0.4],[-0.38,-0.75],[0,-1.61],[0.83,-0.87],[0.9,0],[0.69,0.39],[0.39,0.74],[0,0]],\"v\":[[70.11,13.86],[68.87,17.61],[65.49,18.93],[63.13,18.34],[61.5,16.61],[60.93,13.86],[62.16,10.14],[65.55,8.84],[67.92,9.43],[69.53,11.15],[70.11,13.86]]}}},{\"ty\":\"sh\",\"d\":1,\"ks\":{\"a\":0,\"k\":{\"c\":true,\"i\":[[0,0],[-0.39,-0.59],[-0.87,0],[-0.39,0.59],[0,1.07],[0.39,0.58],[0.86,0],[0.39,-0.58],[0,-1.07]],\"o\":[[0,1.07],[0.39,0.59],[0.86,0],[0.39,-0.59],[0,-1.06],[-0.39,-0.58],[-0.86,0],[-0.39,0.57],[0,0]],\"v\":[[63.05,13.86],[63.63,16.36],[65.52,17.24],[67.39,16.35],[67.98,13.86],[67.39,11.4],[65.52,10.53],[63.63,11.4],[63.05,13.86]]}}},{\"ty\":\"sh\",\"d\":1,\"ks\":{\"a\":0,\"k\":{\"c\":true,\"i\":[[0,0],[0.7,-0.5],[1.28,0],[0.49,0.1],[0.43,0.2],[0,0],[-0.58,-0.16],[-0.52,0],[-0.3,0.21],[0,0.36],[0.12,0.16],[0.33,0.18],[0.63,0.25],[0.42,0.25],[0.22,0.35],[0,0.55],[-0.68,0.45],[-1.12,0],[-0.53,-0.12],[-0.51,-0.23],[0,0],[0.44,0.13],[0.46,0],[0,-0.63],[-0.14,-0.16],[-0.33,-0.16],[-0.59,-0.23],[-0.42,-0.25],[-0.23,-0.36],[0,-0.55]],\"o\":[[0,0.96],[-0.69,0.5],[-0.68,0],[-0.49,-0.09],[0,0],[0.46,0.23],[0.58,0.15],[0.68,0],[0.3,-0.21],[0,-0.21],[-0.12,-0.17],[-0.32,-0.18],[-0.62,-0.24],[-0.42,-0.25],[-0.21,-0.34],[0,-0.86],[0.69,-0.45],[0.6,0],[0.53,0.12],[0,0],[-0.45,-0.19],[-0.44,-0.13],[-1.09,0],[0,0.23],[0.14,0.16],[0.33,0.16],[0.58,0.22],[0.42,0.25],[0.23,0.35],[0,0]],\"v\":[[79.02,15.97],[77.98,18.17],[75.02,18.93],[73.26,18.78],[71.88,18.34],[71.88,16.55],[73.43,17.13],[75.08,17.35],[76.54,17.04],[76.99,16.18],[76.81,15.61],[76.14,15.09],[74.71,14.44],[73.15,13.71],[72.19,12.82],[71.88,11.47],[72.89,9.51],[75.61,8.84],[77.3,9.02],[78.86,9.54],[78.21,11.09],[76.89,10.62],[75.54,10.43],[73.91,11.38],[74.11,11.96],[74.8,12.44],[76.19,13.02],[77.7,13.72],[78.67,14.62],[79.02,15.97]]}}},{\"ty\":\"sh\",\"d\":1,\"ks\":{\"a\":0,\"k\":{\"c\":true,\"i\":[[0,0],[0,0],[0.02,-0.34],[0.02,-0.19],[0,0],[-0.52,0.25],[-0.63,0],[-0.61,-0.57],[0,-1.25],[0,0],[0,0],[0,0],[1.27,0],[0.37,-0.59],[0,-1.09],[0,0],[0,0],[0,0]],\"o\":[[0,0],[0,0.36],[-0.02,0.33],[0,0],[0.3,-0.5],[0.51,-0.25],[1.11,0],[0.62,0.56],[0,0],[0,0],[0,0],[0,-1.51],[-0.94,0],[-0.36,0.59],[0,0],[0,0],[0,0],[0,0]],\"v\":[[83.32,5.07],[83.32,8.51],[83.29,9.57],[83.22,10.35],[83.34,10.35],[84.57,9.22],[86.29,8.84],[88.88,9.7],[89.8,12.41],[89.8,18.75],[87.74,18.75],[87.74,12.79],[85.83,10.52],[83.86,11.4],[83.32,13.92],[83.32,18.75],[81.25,18.75],[81.25,5.07]]}}},{\"ty\":\"fl\",\"c\":{\"a\":0,\"k\":[0.11,0.35,0.85,1]},\"r\":1,\"o\":{\"a\":0,\"k\":100}},{\"ty\":\"tr\",\"nm\":\"Transform\",\"a\":{\"a\":0,\"k\":[0,0]},\"o\":{\"a\":0,\"k\":100},\"p\":{\"a\":0,\"k\":[0,0]},\"r\":{\"a\":0,\"k\":0},\"s\":{\"a\":0,\"k\":[100,100]},\"sk\":{\"a\":0,\"k\":0},\"sa\":{\"a\":0,\"k\":0}}]},{\"ty\":\"gr\",\"it\":[{\"ty\":\"gr\",\"it\":[{\"ty\":\"sh\",\"d\":1,\"ks\":{\"a\":0,\"k\":{\"c\":true,\"i\":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],\"o\":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],\"v\":[[12.08,18.75],[9.98,18.75],[9.98,12.95],[4.05,12.95],[4.05,18.75],[1.94,18.75],[1.94,5.9],[4.05,5.9],[4.05,11.16],[9.98,11.16],[9.98,5.9],[12.08,5.9]]}}},{\"ty\":\"sh\",\"d\":1,\"ks\":{\"a\":0,\"k\":{\"c\":true,\"i\":[[0,0],[-0.74,-0.79],[0,-1.37],[0,0],[0,0],[-0.48,-0.51],[-0.87,0],[-0.5,0.12],[-0.53,0.24],[0,0],[0.5,-0.11],[0.7,0],[0.72,0.37],[0.41,0.73],[0,1.1],[-0.81,0.89],[-1.34,0]],\"o\":[[1.3,0],[0.75,0.79],[0,0],[0,0],[0.02,0.94],[0.48,0.51],[0.61,0],[0.5,-0.12],[0,0],[-0.49,0.23],[-0.5,0.11],[-0.95,0],[-0.73,-0.37],[-0.4,-0.74],[0,-1.64],[0.82,-0.89],[0,0]],\"v\":[[19.14,8.84],[22.19,10.02],[23.31,13.25],[23.31,14.36],[16.81,14.36],[17.57,16.54],[19.61,17.3],[21.28,17.13],[22.82,16.59],[22.82,18.27],[21.32,18.77],[19.52,18.93],[17,18.37],[15.3,16.71],[14.7,13.95],[15.91,10.17],[19.14,8.84]]}}},{\"ty\":\"sh\",\"d\":1,\"ks\":{\"a\":0,\"k\":{\"c\":true,\"i\":[[0,0],[0.4,-0.42],[0.07,-0.82],[0,0],[0.35,0.46],[0.73,0]],\"o\":[[-0.65,0],[-0.4,0.42],[0,0],[-0.01,-0.73],[-0.34,-0.47],[0,0]],\"v\":[[19.14,10.39],[17.56,11.02],[16.86,12.88],[21.29,12.88],[20.75,11.09],[19.14,10.39]]}}},{\"ty\":\"sh\",\"d\":1,\"ks\":{\"a\":0,\"k\":{\"c\":true,\"i\":[[0,0],[0,0],[0,0],[-0.1,-0.35],[-0.05,-0.35],[0,0],[-0.11,0.38],[-0.14,0.39],[0,0],[0,0],[0,0],[0.62,-0.52],[0.91,0],[0.22,0.04],[0.15,0.04],[0,0],[-0.18,-0.02],[-0.2,0],[-0.34,0.33],[-0.19,0.52],[0,0]],\"o\":[[0,0],[0,0],[0.13,0.39],[0.1,0.35],[0,0],[0.06,-0.3],[0.11,-0.37],[0,0],[0,0],[0,0],[-0.36,0.96],[-0.61,0.52],[-0.29,0],[-0.21,-0.03],[0,0],[0.12,0.04],[0.19,0.02],[0.55,0],[0.34,-0.32],[0,0],[0,0]],\"v\":[[24.17,9.02],[26.42,9.02],[28.41,14.57],[28.76,15.68],[28.98,16.74],[29.04,16.74],[29.29,15.72],[29.66,14.57],[31.57,9.02],[33.79,9.02],[29.62,20.09],[28.16,22.29],[25.88,23.07],[25.11,23.02],[24.56,22.93],[24.56,21.27],[25.02,21.35],[25.6,21.39],[26.93,20.89],[27.71,19.64],[28.05,18.72]]}}},{\"ty\":\"sh\",\"d\":1,\"ks\":{\"a\":0,\"k\":{\"c\":true,\"i\":[[0,0],[0,0],[0.28,-0.76],[0.29,-0.65],[0,0],[-0.16,0.8],[-0.09,0.64]],\"o\":[[0,0],[-0.16,0.64],[-0.28,0.76],[0,0],[0.18,-0.71],[0.17,-0.8],[0,0]],\"v\":[[94.78,16.66],[94.9,16.86],[94.23,18.95],[93.38,21.07],[91.86,21.07],[92.37,18.8],[92.77,16.66]]}}},{\"ty\":\"sh\",\"d\":1,\"ks\":{\"a\":0,\"k\":{\"c\":true,\"i\":[[0,0],[0.81,0.8],[0,1.71],[-0.4,0.75],[-0.7,0.36],[-0.92,0],[-0.47,-0.12],[-0.33,-0.16],[0,0],[0.38,0.1],[0.34,0],[0,-2.24],[-0.41,-0.55],[-0.82,0],[-0.42,0.12],[-0.37,0.21],[0,0],[0.42,-0.1],[0.61,0]],\"o\":[[-1.38,0],[-0.8,-0.81],[0,-1.18],[0.4,-0.76],[0.7,-0.36],[0.57,0],[0.47,0.12],[0,0],[-0.36,-0.14],[-0.37,-0.11],[-1.7,0],[0,1.08],[0.42,0.56],[0.54,0],[0.42,-0.13],[0,0],[-0.37,0.22],[-0.42,0.1],[0,0]],\"v\":[[106.2,18.93],[102.91,17.71],[101.71,13.93],[102.3,11.04],[103.96,9.37],[106.39,8.84],[107.96,9.01],[109.17,9.43],[108.55,11.08],[107.45,10.72],[106.38,10.56],[103.83,13.92],[104.46,16.37],[106.31,17.2],[107.75,17.02],[108.94,16.51],[108.94,18.3],[107.75,18.78],[106.2,18.93]]}}},{\"ty\":\"sh\",\"d\":1,\"ks\":{\"a\":0,\"k\":{\"c\":true,\"i\":[[0,0],[-0.63,-0.54],[0,-1.16],[0,0],[0,0],[0,0],[0,0],[0.47,-0.25],[0.82,0],[0.57,0.48],[0,1.02],[-3,0.11],[0,0],[0,0],[0.32,0.29],[0.56,0],[0.46,-0.14],[0.43,-0.21],[0,0],[-0.6,0.16],[-0.66,0]],\"o\":[[1.23,0],[0.63,0.55],[0,0],[0,0],[0,0],[0,0],[-0.41,0.53],[-0.47,0.25],[-0.88,0],[-0.57,-0.48],[0,-1.95],[0,0],[0,0],[0,-0.67],[-0.32,-0.3],[-0.49,0],[-0.46,0.14],[0,0],[0.48,-0.25],[0.61,-0.16],[0,0]],\"v\":[[115.04,8.84],[117.83,9.65],[118.77,12.2],[118.77,18.75],[117.31,18.75],[116.91,17.37],[116.84,17.37],[115.52,18.55],[113.57,18.93],[111.39,18.2],[110.54,15.95],[115.05,12.86],[116.73,12.8],[116.73,12.29],[116.26,10.86],[114.95,10.42],[113.52,10.63],[112.19,11.15],[111.52,9.69],[113.14,9.08],[115.04,8.84]]}}},{\"ty\":\"sh\",\"d\":1,\"ks\":{\"a\":0,\"k\":{\"c\":true,\"i\":[[0,0],[0,0],[0.41,-0.32],[0,-0.55],[-0.29,-0.21],[-0.47,0],[-0.47,0.41],[0,0.79]],\"o\":[[0,0],[-1.04,0.04],[-0.41,0.32],[0,0.48],[0.29,0.22],[0.71,0],[0.47,-0.41],[0,0]],\"v\":[[116.73,14.1],[115.47,14.14],[113.29,14.68],[112.68,15.97],[113.11,17.03],[114.24,17.35],[116.02,16.75],[116.73,14.95]]}}},{\"ty\":\"sh\",\"d\":1,\"ks\":{\"a\":0,\"k\":{\"c\":true,\"i\":[[0,0],[-0.61,-0.57],[0,-1.25],[0,0],[0,0],[0,0],[1.27,0],[0.37,-0.59],[0,-1.1],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[-0.54,0.25],[-0.63,0]],\"o\":[[1.09,0],[0.61,0.56],[0,0],[0,0],[0,0],[0,-1.51],[-0.95,0],[-0.36,0.58],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0.31,-0.5],[0.55,-0.25],[0,0]],\"v\":[[126.74,8.84],[129.3,9.69],[130.22,12.41],[130.22,18.75],[128.15,18.75],[128.15,12.79],[126.25,10.52],[124.28,11.4],[123.73,13.92],[123.73,18.75],[121.67,18.75],[121.67,9.02],[123.27,9.02],[123.57,10.34],[123.68,10.34],[124.96,9.21],[126.74,8.84]]}}},{\"ty\":\"sh\",\"d\":1,\"ks\":{\"a\":0,\"k\":{\"c\":true,\"i\":[[0,0],[0,0],[0,0],[-0.1,-0.35],[-0.05,-0.35],[0,0],[-0.11,0.38],[-0.13,0.39],[0,0],[0,0],[0,0],[0.61,-0.52],[0.91,0],[0.22,0.04],[0.16,0.04],[0,0],[-0.18,-0.02],[-0.2,0],[-0.34,0.33],[-0.19,0.52],[0,0]],\"o\":[[0,0],[0,0],[0.13,0.39],[0.1,0.35],[0,0],[0.06,-0.3],[0.11,-0.37],[0,0],[0,0],[0,0],[-0.36,0.96],[-0.61,0.52],[-0.29,0],[-0.21,-0.03],[0,0],[0.13,0.04],[0.19,0.02],[0.55,0],[0.34,-0.32],[0,0],[0,0]],\"v\":[[136.32,9.02],[138.57,9.02],[140.56,14.57],[140.91,15.68],[141.14,16.74],[141.19,16.74],[141.43,15.72],[141.8,14.57],[143.72,9.02],[145.94,9.02],[141.77,20.09],[140.31,22.29],[138.02,23.07],[137.26,23.02],[136.7,22.93],[136.7,21.27],[137.16,21.35],[137.75,21.39],[139.08,20.89],[139.86,19.64],[140.2,18.72]]}}},{\"ty\":\"sh\",\"d\":1,\"ks\":{\"a\":0,\"k\":{\"c\":true,\"i\":[[0,0],[0.82,-0.89],[1.43,0],[0.68,0.39],[0.39,0.75],[0,1.08],[-0.82,0.88],[-1.44,0],[-0.68,-0.4],[-0.39,-0.75],[0,-1.07]],\"o\":[[0,1.61],[-0.83,0.88],[-0.89,0],[-0.69,-0.4],[-0.38,-0.75],[0,-1.61],[0.82,-0.87],[0.89,0],[0.68,0.39],[0.39,0.74],[0,0]],\"v\":[[156.03,13.86],[154.79,17.61],[151.41,18.93],[149.05,18.34],[147.43,16.61],[146.85,13.86],[148.08,10.14],[151.47,8.84],[153.84,9.43],[155.45,11.15],[156.03,13.86]]}}},{\"ty\":\"sh\",\"d\":1,\"ks\":{\"a\":0,\"k\":{\"c\":true,\"i\":[[0,0],[-0.39,-0.59],[-0.87,0],[-0.39,0.59],[0,1.07],[0.39,0.58],[0.87,0],[0.39,-0.58],[0,-1.07]],\"o\":[[0,1.07],[0.39,0.59],[0.86,0],[0.39,-0.59],[0,-1.06],[-0.39,-0.58],[-0.86,0],[-0.39,0.57],[0,0]],\"v\":[[148.98,13.86],[149.55,16.36],[151.45,17.24],[153.32,16.35],[153.91,13.86],[153.32,11.4],[151.43,10.53],[149.55,11.4],[148.98,13.86]]}}},{\"ty\":\"sh\",\"d\":1,\"ks\":{\"a\":0,\"k\":{\"c\":true,\"i\":[[0,0],[0,0],[0,0],[0,0],[0,0],[0.55,-0.25],[0.64,0],[0.61,0.56],[0,1.24],[0,0],[0,0],[0,0],[-1.26,0],[-0.37,0.58],[0,1.09],[0,0]],\"o\":[[0,0],[0,0],[0,0],[0,0],[-0.3,0.5],[-0.55,0.24],[-1.1,0],[-0.61,-0.56],[0,0],[0,0],[0,0],[0,1.51],[0.94,0],[0.37,-0.58],[0,0],[0,0]],\"v\":[[166.89,9.02],[166.89,18.75],[165.26,18.75],[164.98,17.44],[164.86,17.44],[163.58,18.57],[161.8,18.93],[159.24,18.08],[158.33,15.38],[158.33,9.02],[160.4,9.02],[160.4,14.99],[162.3,17.25],[164.26,16.38],[164.81,13.86],[164.81,9.02]]}}},{\"ty\":\"sh\",\"d\":1,\"ks\":{\"a\":0,\"k\":{\"c\":true,\"i\":[[0,0],[0,0],[0.02,-0.34],[0.02,-0.19],[0,0],[-0.51,0.25],[-0.63,0],[-0.62,-0.57],[0,-1.25],[0,0],[0,0],[0,0],[1.27,0],[0.37,-0.59],[0,-1.09],[0,0],[0,0],[0,0]],\"o\":[[0,0],[0,0.36],[-0.02,0.33],[0,0],[0.3,-0.5],[0.52,-0.25],[1.11,0],[0.62,0.56],[0,0],[0,0],[0,0],[0,-1.51],[-0.95,0],[-0.37,0.59],[0,0],[0,0],[0,0],[0,0]],\"v\":[[176.59,5.07],[176.59,8.51],[176.55,9.57],[176.49,10.35],[176.61,10.35],[177.84,9.22],[179.56,8.84],[182.14,9.7],[183.08,12.41],[183.08,18.75],[181.01,18.75],[181.01,12.79],[179.11,10.52],[177.14,11.4],[176.59,13.92],[176.59,18.75],[174.52,18.75],[174.52,5.07]]}}},{\"ty\":\"sh\",\"d\":1,\"ks\":{\"a\":0,\"k\":{\"c\":true,\"i\":[[0,0],[-0.63,-0.54],[0,-1.16],[0,0],[0,0],[0,0],[0,0],[0.47,-0.25],[0.82,0],[0.57,0.48],[0,1.02],[-3.01,0.11],[0,0],[0,0],[0.32,0.29],[0.56,0],[0.46,-0.14],[0.43,-0.21],[0,0],[-0.6,0.16],[-0.66,0]],\"o\":[[1.23,0],[0.63,0.55],[0,0],[0,0],[0,0],[0,0],[-0.41,0.53],[-0.47,0.25],[-0.88,0],[-0.57,-0.48],[0,-1.95],[0,0],[0,0],[0,-0.67],[-0.32,-0.3],[-0.49,0],[-0.46,0.14],[0,0],[0.48,-0.25],[0.61,-0.16],[0,0]],\"v\":[[189.78,8.84],[192.57,9.65],[193.52,12.2],[193.52,18.75],[192.05,18.75],[191.65,17.37],[191.58,17.37],[190.26,18.55],[188.32,18.93],[186.14,18.2],[185.28,15.95],[189.79,12.86],[191.48,12.8],[191.48,12.29],[191,10.86],[189.68,10.42],[188.26,10.63],[186.93,11.15],[186.27,9.69],[187.88,9.08],[189.78,8.84]]}}},{\"ty\":\"sh\",\"d\":1,\"ks\":{\"a\":0,\"k\":{\"c\":true,\"i\":[[0,0],[0,0],[0.41,-0.32],[0,-0.55],[-0.29,-0.21],[-0.47,0],[-0.47,0.41],[0,0.79]],\"o\":[[0,0],[-1.04,0.04],[-0.41,0.32],[0,0.48],[0.29,0.22],[0.71,0],[0.48,-0.41],[0,0]],\"v\":[[191.47,14.1],[190.21,14.14],[188.04,14.68],[187.42,15.97],[187.85,17.03],[188.98,17.35],[190.76,16.75],[191.47,14.95]]}}},{\"ty\":\"sh\",\"d\":1,\"ks\":{\"a\":0,\"k\":{\"c\":true,\"i\":[[0,0],[0,0],[0,0],[0,0],[-0.11,-0.42],[-0.04,-0.29],[0,0],[-0.12,0.41],[-0.12,0.36],[0,0],[0,0],[0,0]],\"o\":[[0,0],[0,0],[0,0],[0.12,0.36],[0.11,0.42],[0,0],[0.04,-0.3],[0.12,-0.42],[0,0],[0,0],[0,0],[0,0]],\"v\":[[198.63,18.75],[194.93,9.02],[197.12,9.02],[199.13,14.73],[199.47,15.91],[199.7,16.97],[199.77,16.97],[200,15.9],[200.36,14.73],[202.37,9.02],[204.55,9.02],[200.85,18.75]]}}},{\"ty\":\"sh\",\"d\":1,\"ks\":{\"a\":0,\"k\":{\"c\":true,\"i\":[[0,0],[-0.74,-0.79],[0,-1.37],[0,0],[0,0],[-0.48,-0.51],[-0.87,0],[-0.5,0.12],[-0.53,0.24],[0,0],[0.5,-0.11],[0.7,0],[0.73,0.37],[0.41,0.73],[0,1.1],[-0.81,0.89],[-1.34,0]],\"o\":[[1.3,0],[0.75,0.79],[0,0],[0,0],[0.02,0.94],[0.48,0.51],[0.61,0],[0.5,-0.12],[0,0],[-0.49,0.23],[-0.5,0.11],[-0.95,0],[-0.73,-0.37],[-0.4,-0.74],[0,-1.64],[0.81,-0.89],[0,0]],\"v\":[[209.9,8.84],[212.95,10.02],[214.07,13.25],[214.07,14.36],[207.57,14.36],[208.33,16.54],[210.37,17.3],[212.04,17.13],[213.57,16.59],[213.57,18.27],[212.08,18.77],[210.28,18.93],[207.76,18.37],[206.06,16.71],[205.45,13.95],[206.67,10.17],[209.9,8.84]]}}},{\"ty\":\"sh\",\"d\":1,\"ks\":{\"a\":0,\"k\":{\"c\":true,\"i\":[[0,0],[0.4,-0.42],[0.07,-0.82],[0,0],[0.35,0.46],[0.73,0]],\"o\":[[-0.65,0],[-0.4,0.42],[0,0],[-0.01,-0.73],[-0.34,-0.47],[0,0]],\"v\":[[209.9,10.39],[208.32,11.02],[207.61,12.88],[212.05,12.88],[211.51,11.09],[209.9,10.39]]}}},{\"ty\":\"sh\",\"d\":1,\"ks\":{\"a\":0,\"k\":{\"c\":true,\"i\":[[0,0],[-0.62,-0.54],[0,-1.16],[0,0],[0,0],[0,0],[0,0],[0.47,-0.25],[0.82,0],[0.57,0.48],[0,1.02],[-3,0.11],[0,0],[0,0],[0.31,0.29],[0.56,0],[0.46,-0.14],[0.43,-0.21],[0,0],[-0.61,0.16],[-0.66,0]],\"o\":[[1.23,0],[0.63,0.55],[0,0],[0,0],[0,0],[0,0],[-0.42,0.53],[-0.47,0.25],[-0.88,0],[-0.57,-0.48],[0,-1.95],[0,0],[0,0],[0,-0.67],[-0.32,-0.3],[-0.49,0],[-0.45,0.14],[0,0],[0.47,-0.25],[0.6,-0.16],[0,0]],\"v\":[[224.89,8.84],[227.67,9.65],[228.62,12.2],[228.62,18.75],[227.15,18.75],[226.76,17.37],[226.69,17.37],[225.36,18.55],[223.42,18.93],[221.24,18.2],[220.39,15.95],[224.89,12.86],[226.58,12.8],[226.58,12.29],[226.11,10.86],[224.79,10.42],[223.36,10.63],[222.04,11.15],[221.37,9.69],[222.99,9.08],[224.89,8.84]]}}},{\"ty\":\"sh\",\"d\":1,\"ks\":{\"a\":0,\"k\":{\"c\":true,\"i\":[[0,0],[0,0],[0.41,-0.32],[0,-0.55],[-0.29,-0.21],[-0.47,0],[-0.47,0.41],[0,0.79]],\"o\":[[0,0],[-1.04,0.04],[-0.41,0.32],[0,0.48],[0.29,0.22],[0.71,0],[0.48,-0.41],[0,0]],\"v\":[[226.57,14.1],[225.32,14.14],[223.14,14.68],[222.52,15.97],[222.95,17.03],[224.09,17.35],[225.86,16.75],[226.57,14.95]]}}},{\"ty\":\"sh\",\"d\":1,\"ks\":{\"a\":0,\"k\":{\"c\":true,\"i\":[[0,0],[0,0],[0,0],[0,0]],\"o\":[[0,0],[0,0],[0,0],[0,0]],\"v\":[[238.26,18.75],[236.19,18.75],[236.19,5.07],[238.26,5.07]]}}},{\"ty\":\"sh\",\"d\":1,\"ks\":{\"a\":0,\"k\":{\"c\":true,\"i\":[[0,0],[0.83,-0.89],[1.43,0],[0.69,0.39],[0.39,0.75],[0,1.08],[-0.82,0.88],[-1.43,0],[-0.69,-0.4],[-0.39,-0.75],[0,-1.07]],\"o\":[[0,1.61],[-0.82,0.88],[-0.89,0],[-0.68,-0.4],[-0.39,-0.75],[0,-1.61],[0.83,-0.87],[0.9,0],[0.69,0.39],[0.39,0.74],[0,0]],\"v\":[[249.83,13.86],[248.59,17.61],[245.21,18.93],[242.84,18.34],[241.23,16.61],[240.64,13.86],[241.88,10.14],[245.27,8.84],[247.64,9.43],[249.25,11.15],[249.83,13.86]]}}},{\"ty\":\"sh\",\"d\":1,\"ks\":{\"a\":0,\"k\":{\"c\":true,\"i\":[[0,0],[-0.39,-0.59],[-0.87,0],[-0.39,0.59],[0,1.07],[0.39,0.58],[0.87,0],[0.39,-0.58],[0,-1.07]],\"o\":[[0,1.07],[0.39,0.59],[0.86,0],[0.39,-0.59],[0,-1.06],[-0.39,-0.58],[-0.86,0],[-0.39,0.57],[0,0]],\"v\":[[242.77,13.86],[243.35,16.36],[245.24,17.24],[247.11,16.35],[247.7,13.86],[247.11,11.4],[245.23,10.53],[243.35,11.4],[242.77,13.86]]}}},{\"ty\":\"sh\",\"d\":1,\"ks\":{\"a\":0,\"k\":{\"c\":true,\"i\":[[0,0],[0.82,-0.89],[1.43,0],[0.69,0.39],[0.39,0.75],[0,1.08],[-0.82,0.88],[-1.43,0],[-0.69,-0.4],[-0.39,-0.75],[0,-1.07]],\"o\":[[0,1.61],[-0.83,0.88],[-0.89,0],[-0.69,-0.4],[-0.39,-0.75],[0,-1.61],[0.83,-0.87],[0.89,0],[0.68,0.39],[0.39,0.74],[0,0]],\"v\":[[260.82,13.86],[259.58,17.61],[256.19,18.93],[253.83,18.34],[252.21,16.61],[251.63,13.86],[252.86,10.14],[256.25,8.84],[258.63,9.43],[260.23,11.15],[260.82,13.86]]}}},{\"ty\":\"sh\",\"d\":1,\"ks\":{\"a\":0,\"k\":{\"c\":true,\"i\":[[0,0],[-0.38,-0.59],[-0.87,0],[-0.39,0.59],[0,1.07],[0.39,0.58],[0.87,0],[0.39,-0.58],[0,-1.07]],\"o\":[[0,1.07],[0.39,0.59],[0.86,0],[0.39,-0.59],[0,-1.06],[-0.39,-0.58],[-0.86,0],[-0.38,0.57],[0,0]],\"v\":[[253.76,13.86],[254.34,16.36],[256.23,17.24],[258.1,16.35],[258.69,13.86],[258.1,11.4],[256.22,10.53],[254.34,11.4],[253.76,13.86]]}}},{\"ty\":\"sh\",\"d\":1,\"ks\":{\"a\":0,\"k\":{\"c\":true,\"i\":[[0,0],[0,0],[0.03,-0.36],[0.02,-0.31],[0,0],[-0.21,0.27],[-0.19,0.21],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],\"o\":[[0,0],[0,0.3],[-0.02,0.35],[0,0],[0.15,-0.2],[0.21,-0.28],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],\"v\":[[265.25,5.07],[265.25,11.68],[265.21,12.67],[265.15,13.66],[265.19,13.66],[265.74,12.95],[266.35,12.22],[269.32,9.02],[271.7,9.02],[267.8,13.2],[271.95,18.75],[269.52,18.75],[266.41,14.48],[265.25,15.47],[265.25,18.75],[263.19,18.75],[263.19,5.07]]}}},{\"ty\":\"sh\",\"d\":1,\"ks\":{\"a\":0,\"k\":{\"c\":true,\"i\":[[0,0],[-0.63,-0.54],[0,-1.16],[0,0],[0,0],[0,0],[0,0],[0.47,-0.25],[0.83,0],[0.57,0.48],[0,1.02],[-3.01,0.11],[0,0],[0,0],[0.32,0.29],[0.56,0],[0.46,-0.14],[0.43,-0.21],[0,0],[-0.6,0.16],[-0.66,0]],\"o\":[[1.23,0],[0.63,0.55],[0,0],[0,0],[0,0],[0,0],[-0.41,0.53],[-0.47,0.25],[-0.88,0],[-0.57,-0.48],[0,-1.95],[0,0],[0,0],[0,-0.67],[-0.32,-0.3],[-0.49,0],[-0.46,0.14],[0,0],[0.48,-0.25],[0.61,-0.16],[0,0]],\"v\":[[282,8.84],[284.79,9.65],[285.73,12.2],[285.73,18.75],[284.27,18.75],[283.87,17.37],[283.8,17.37],[282.47,18.55],[280.53,18.93],[278.35,18.2],[277.5,15.95],[282.01,12.86],[283.7,12.8],[283.7,12.29],[283.22,10.86],[281.9,10.42],[280.48,10.63],[279.15,11.15],[278.48,9.69],[280.1,9.08],[282,8.84]]}}},{\"ty\":\"sh\",\"d\":1,\"ks\":{\"a\":0,\"k\":{\"c\":true,\"i\":[[0,0],[0,0],[0.42,-0.32],[0,-0.55],[-0.29,-0.21],[-0.47,0],[-0.47,0.41],[0,0.79]],\"o\":[[0,0],[-1.04,0.04],[-0.41,0.32],[0,0.48],[0.29,0.22],[0.71,0],[0.48,-0.41],[0,0]],\"v\":[[283.68,14.1],[282.43,14.14],[280.25,14.68],[279.63,15.97],[280.06,17.03],[281.2,17.35],[282.97,16.75],[283.68,14.95]]}}},{\"ty\":\"sh\",\"d\":1,\"ks\":{\"a\":0,\"k\":{\"c\":true,\"i\":[[0,0],[-0.27,0.05],[-0.22,0.08],[0,0],[0.36,-0.07],[0.39,0],[0.45,0.18],[0.26,0.45],[0,0.8],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[-0.24,-0.24],[-0.41,0]],\"o\":[[0.27,0],[0.26,-0.05],[0,0],[-0.23,0.1],[-0.36,0.07],[-0.55,0],[-0.44,-0.19],[-0.25,-0.45],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0.49],[0.25,0.24],[0,0]],\"v\":[[292.25,17.25],[293.05,17.19],[293.77,17],[293.77,18.57],[292.88,18.82],[291.74,18.93],[290.25,18.65],[289.2,17.7],[288.83,15.82],[288.83,10.59],[287.5,10.59],[287.5,9.67],[288.92,8.94],[289.6,6.86],[290.9,6.86],[290.9,9.02],[293.69,9.02],[293.69,10.59],[290.9,10.59],[290.9,15.8],[291.26,16.89],[292.25,17.25]]}}},{\"ty\":\"sh\",\"d\":1,\"ks\":{\"a\":0,\"k\":{\"c\":true,\"i\":[[0,0],[-0.26,0.05],[-0.21,0.08],[0,0],[0.36,-0.07],[0.39,0],[0.44,0.18],[0.26,0.45],[0,0.8],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[-0.24,-0.24],[-0.41,0]],\"o\":[[0.27,0],[0.27,-0.05],[0,0],[-0.23,0.1],[-0.36,0.07],[-0.55,0],[-0.44,-0.19],[-0.25,-0.45],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0.49],[0.25,0.24],[0,0]],\"v\":[[304.04,17.25],[304.84,17.19],[305.56,17],[305.56,18.57],[304.67,18.82],[303.54,18.93],[302.05,18.65],[301,17.7],[300.62,15.82],[300.62,10.59],[299.29,10.59],[299.29,9.67],[300.72,8.94],[301.39,6.86],[302.7,6.86],[302.7,9.02],[305.48,9.02],[305.48,10.59],[302.7,10.59],[302.7,15.8],[303.05,16.89],[304.04,17.25]]}}},{\"ty\":\"sh\",\"d\":1,\"ks\":{\"a\":0,\"k\":{\"c\":true,\"i\":[[0,0],[0,0],[0.02,-0.34],[0.02,-0.19],[0,0],[-0.52,0.25],[-0.63,0],[-0.62,-0.57],[0,-1.25],[0,0],[0,0],[0,0],[1.27,0],[0.37,-0.59],[0,-1.09],[0,0],[0,0],[0,0]],\"o\":[[0,0],[0,0.36],[-0.02,0.33],[0,0],[0.3,-0.5],[0.52,-0.25],[1.11,0],[0.62,0.56],[0,0],[0,0],[0,0],[0,-1.51],[-0.95,0],[-0.36,0.59],[0,0],[0,0],[0,0],[0,0]],\"v\":[[309.61,5.07],[309.61,8.51],[309.57,9.57],[309.51,10.35],[309.62,10.35],[310.85,9.22],[312.57,8.84],[315.16,9.7],[316.09,12.41],[316.09,18.75],[314.02,18.75],[314.02,12.79],[312.12,10.52],[310.15,11.4],[309.61,13.92],[309.61,18.75],[307.54,18.75],[307.54,5.07]]}}},{\"ty\":\"sh\",\"d\":1,\"ks\":{\"a\":0,\"k\":{\"c\":true,\"i\":[[0,0],[-0.23,-0.17],[0,-0.41],[0.23,-0.18],[0.32,0],[0.23,0.17],[0,0.41],[-0.22,0.17],[-0.33,0]],\"o\":[[0.32,0],[0.23,0.17],[0,0.41],[-0.23,0.17],[-0.33,0],[-0.22,-0.18],[0,-0.41],[0.23,-0.17],[0,0]],\"v\":[[320.03,5.29],[320.84,5.55],[321.2,6.43],[320.84,7.31],[320.03,7.56],[319.19,7.31],[318.86,6.43],[319.19,5.55],[320.03,5.29]]}}},{\"ty\":\"sh\",\"d\":1,\"ks\":{\"a\":0,\"k\":{\"c\":true,\"i\":[[0,0],[0,0],[0,0],[0,0]],\"o\":[[0,0],[0,0],[0,0],[0,0]],\"v\":[[321.05,9.02],[321.05,18.75],[318.98,18.75],[318.98,9.02]]}}},{\"ty\":\"sh\",\"d\":1,\"ks\":{\"a\":0,\"k\":{\"c\":true,\"i\":[[0,0],[0.7,-0.5],[1.29,0],[0.49,0.1],[0.43,0.2],[0,0],[-0.58,-0.16],[-0.52,0],[-0.3,0.21],[0,0.36],[0.13,0.16],[0.33,0.18],[0.63,0.25],[0.42,0.25],[0.21,0.35],[0,0.55],[-0.68,0.45],[-1.12,0],[-0.53,-0.12],[-0.51,-0.23],[0,0],[0.44,0.13],[0.46,0],[0,-0.63],[-0.14,-0.16],[-0.33,-0.16],[-0.59,-0.23],[-0.43,-0.25],[-0.23,-0.36],[0,-0.55]],\"o\":[[0,0.96],[-0.7,0.5],[-0.68,0],[-0.49,-0.09],[0,0],[0.46,0.23],[0.58,0.15],[0.68,0],[0.3,-0.21],[0,-0.21],[-0.12,-0.17],[-0.32,-0.18],[-0.62,-0.24],[-0.42,-0.25],[-0.21,-0.34],[0,-0.86],[0.69,-0.45],[0.6,0],[0.54,0.12],[0,0],[-0.45,-0.19],[-0.44,-0.13],[-1.09,0],[0,0.23],[0.13,0.16],[0.34,0.16],[0.58,0.22],[0.42,0.25],[0.23,0.35],[0,0]],\"v\":[[330.55,15.97],[329.5,18.17],[326.54,18.93],[324.78,18.78],[323.4,18.34],[323.4,16.55],[324.96,17.13],[326.6,17.35],[328.07,17.04],[328.52,16.18],[328.33,15.61],[327.66,15.09],[326.24,14.44],[324.68,13.71],[323.72,12.82],[323.4,11.47],[324.42,9.51],[327.14,8.84],[328.82,9.02],[330.39,9.54],[329.74,11.09],[328.41,10.62],[327.07,10.43],[325.43,11.38],[325.64,11.96],[326.33,12.44],[327.72,13.02],[329.22,13.72],[330.2,14.62],[330.55,15.97]]}}},{\"ty\":\"sh\",\"d\":1,\"ks\":{\"a\":0,\"k\":{\"c\":true,\"i\":[[0,0],[0,0],[-0.23,0.42],[-0.61,0.47],[-0.25,0.23],[-0.11,0.23],[0,0.32],[0.34,0.27],[0.61,0],[0.48,-0.15],[0.46,-0.23],[0,0],[-0.62,0.18],[-0.75,0],[-0.66,-0.58],[0,-0.95],[0.17,-0.37],[0.31,-0.32],[0.45,-0.35],[0.2,-0.21],[0.07,-0.22],[0,-0.3],[0,0]],\"o\":[[0,0],[0,-0.64],[0.23,-0.42],[0.4,-0.32],[0.25,-0.23],[0.11,-0.24],[0,-0.49],[-0.34,-0.26],[-0.55,0],[-0.47,0.15],[0,0],[0.52,-0.29],[0.63,-0.18],[1.19,0],[0.66,0.58],[0,0.52],[-0.16,0.37],[-0.32,0.31],[-0.37,0.29],[-0.19,0.21],[-0.07,0.21],[0,0],[0,0]],\"v\":[[333.78,14.83],[333.78,14.28],[334.13,12.68],[335.38,11.34],[336.36,10.5],[336.89,9.8],[337.05,8.97],[336.54,7.83],[335.13,7.44],[333.6,7.67],[332.2,8.24],[331.47,6.69],[333.2,5.99],[335.27,5.71],[338.04,6.59],[339.02,8.88],[338.77,10.22],[338.05,11.25],[336.89,12.24],[336.03,13],[335.64,13.64],[335.53,14.43],[335.53,14.83]]}}},{\"ty\":\"sh\",\"d\":1,\"ks\":{\"a\":0,\"k\":{\"c\":true,\"i\":[[0,0],[-0.25,0.2],[-0.37,0],[-0.25,-0.2],[0,-0.47],[0.25,-0.21],[0.35,0],[0.25,0.21],[0,0.47]],\"o\":[[0,-0.47],[0.25,-0.2],[0.35,0],[0.25,0.2],[0,0.47],[-0.25,0.21],[-0.37,0],[-0.25,-0.21],[0,0]],\"v\":[[333.45,17.66],[333.82,16.65],[334.74,16.35],[335.64,16.65],[336.02,17.66],[335.64,18.68],[334.74,18.99],[333.82,18.68],[333.45,17.66]]}}},{\"ty\":\"fl\",\"c\":{\"a\":0,\"k\":[0.25,0.26,0.31,1]},\"r\":1,\"o\":{\"a\":0,\"k\":100}},{\"ty\":\"tr\",\"nm\":\"Transform\",\"a\":{\"a\":0,\"k\":[0,0]},\"o\":{\"a\":0,\"k\":100},\"p\":{\"a\":0,\"k\":[0,0]},\"r\":{\"a\":0,\"k\":0},\"s\":{\"a\":0,\"k\":[100,100]},\"sk\":{\"a\":0,\"k\":0},\"sa\":{\"a\":0,\"k\":0}}]},{\"ty\":\"tr\",\"nm\":\"Transform\",\"a\":{\"a\":0,\"k\":[0,0]},\"o\":{\"a\":0,\"k\":100},\"p\":{\"a\":0,\"k\":[0,0]},\"r\":{\"a\":0,\"k\":0},\"s\":{\"a\":0,\"k\":[100,100]},\"sk\":{\"a\":0,\"k\":0},\"sa\":{\"a\":0,\"k\":0}}]},{\"ty\":\"tr\",\"nm\":\"Transform\",\"a\":{\"a\":0,\"k\":[0,0]},\"o\":{\"a\":0,\"k\":100},\"p\":{\"a\":0,\"k\":[0,0]},\"r\":{\"a\":0,\"k\":0},\"s\":{\"a\":0,\"k\":[100,100]},\"sk\":{\"a\":0,\"k\":0},\"sa\":{\"a\":0,\"k\":0}}]}]},{\"ddd\":0,\"ind\":19,\"ty\":4,\"nm\":\"\",\"ln\":\"342:103893\",\"sr\":1,\"ks\":{\"a\":{\"a\":0,\"k\":[0,0]},\"o\":{\"a\":0,\"k\":100},\"p\":{\"a\":0,\"k\":[3830,3830]},\"r\":{\"a\":0,\"k\":0},\"s\":{\"a\":0,\"k\":[100,100]},\"sk\":{\"a\":0,\"k\":0},\"sa\":{\"a\":0,\"k\":0}},\"ao\":0,\"ip\":0,\"op\":480,\"st\":0,\"bm\":0,\"shapes\":[{\"ty\":\"gr\",\"hd\":false,\"bm\":0,\"it\":[{\"ty\":\"rc\",\"hd\":false,\"p\":{\"a\":0,\"k\":[0,0]},\"r\":{\"a\":0,\"k\":16},\"s\":{\"a\":0,\"k\":[485,64]}},{\"ty\":\"fl\",\"hd\":false,\"bm\":0,\"c\":{\"a\":0,\"k\":[1,1,1]},\"r\":1,\"o\":{\"a\":0,\"k\":100}},{\"ty\":\"tr\",\"nm\":\"Transform\",\"a\":{\"a\":0,\"k\":[0,0]},\"o\":{\"a\":0,\"k\":100},\"p\":{\"a\":0,\"k\":[0,0]},\"r\":{\"a\":0,\"k\":0},\"s\":{\"a\":0,\"k\":[100,100]},\"sk\":{\"a\":0,\"k\":0},\"sa\":{\"a\":0,\"k\":0}}],\"np\":0}]}]},{\"id\":\"D5x4S3KigyY39QhXclbk-\",\"layers\":[{\"ddd\":0,\"ind\":1,\"ty\":0,\"nm\":\"\",\"ln\":\"precomp_342:1038931\",\"sr\":1,\"ks\":{\"a\":{\"a\":0,\"k\":[3830,3830]},\"o\":{\"a\":1,\"k\":[{\"t\":0,\"s\":[0],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":201,\"s\":[0],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":202,\"s\":[2.48],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":203,\"s\":[8.5],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":204,\"s\":[14.26],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":205,\"s\":[19.78],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":206,\"s\":[25.06],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":207,\"s\":[30.1],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":208,\"s\":[34.9],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":209,\"s\":[39.49],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":210,\"s\":[43.85],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":211,\"s\":[48],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":212,\"s\":[51.93],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":213,\"s\":[55.67],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":214,\"s\":[59.2],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":215,\"s\":[62.55],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":216,\"s\":[65.7],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":217,\"s\":[68.67],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":218,\"s\":[71.47],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":219,\"s\":[74.09],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":220,\"s\":[76.55],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":221,\"s\":[78.85],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":222,\"s\":[80.99],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":223,\"s\":[82.98],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":224,\"s\":[84.83],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":225,\"s\":[86.54],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":226,\"s\":[88.11],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":227,\"s\":[89.56],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":228,\"s\":[90.89],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":229,\"s\":[92.1],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":230,\"s\":[93.19],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":231,\"s\":[94.18],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":232,\"s\":[95.07],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":233,\"s\":[95.86],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":234,\"s\":[96.57],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":235,\"s\":[97.19],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":236,\"s\":[97.73],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":237,\"s\":[98.19],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":238,\"s\":[98.59],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":239,\"s\":[98.92],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":240,\"s\":[99.2],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":241,\"s\":[99.42],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":242,\"s\":[99.6],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":243,\"s\":[99.74],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":244,\"s\":[99.84],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":245,\"s\":[99.91],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":246,\"s\":[99.96],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":247,\"s\":[99.98],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":248,\"s\":[100],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":433,\"s\":[100],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":434,\"s\":[99.98],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":435,\"s\":[99.96],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":436,\"s\":[99.91],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":437,\"s\":[99.84],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":438,\"s\":[99.74],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":439,\"s\":[99.6],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":440,\"s\":[99.42],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":441,\"s\":[99.2],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":442,\"s\":[98.92],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":443,\"s\":[98.59],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":444,\"s\":[98.19],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":445,\"s\":[97.73],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":446,\"s\":[97.19],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":447,\"s\":[96.57],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":448,\"s\":[95.86],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":449,\"s\":[95.07],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":450,\"s\":[94.18],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":451,\"s\":[93.19],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":452,\"s\":[92.1],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":453,\"s\":[90.89],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":454,\"s\":[89.56],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":455,\"s\":[88.11],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":456,\"s\":[86.54],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":457,\"s\":[84.83],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":458,\"s\":[82.98],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":459,\"s\":[80.99],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":460,\"s\":[78.85],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":461,\"s\":[76.55],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":462,\"s\":[74.09],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":463,\"s\":[71.47],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":464,\"s\":[68.67],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":465,\"s\":[65.7],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":466,\"s\":[62.55],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":467,\"s\":[59.2],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":468,\"s\":[55.67],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":469,\"s\":[51.93],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":470,\"s\":[48],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":471,\"s\":[43.85],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":472,\"s\":[39.49],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":473,\"s\":[34.9],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":474,\"s\":[30.1],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":475,\"s\":[25.06],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":476,\"s\":[19.78],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":477,\"s\":[14.26],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":478,\"s\":[8.5],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":479,\"s\":[2.48],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}}]},\"p\":{\"a\":0,\"k\":[1302.5,1256]},\"r\":{\"a\":0,\"k\":0},\"s\":{\"a\":1,\"k\":[{\"t\":0,\"s\":[50,50],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":201,\"s\":[50,50],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":202,\"s\":[51.24,51.24],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":203,\"s\":[54.25,54.25],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":204,\"s\":[57.13,57.13],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":205,\"s\":[59.89,59.89],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":206,\"s\":[62.53,62.53],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":207,\"s\":[65.05,65.05],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":208,\"s\":[67.45,67.45],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":209,\"s\":[69.74,69.74],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":210,\"s\":[71.92,71.92],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":211,\"s\":[74,74],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":212,\"s\":[75.97,75.97],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":213,\"s\":[77.83,77.83],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":214,\"s\":[79.6,79.6],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":215,\"s\":[81.27,81.27],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":216,\"s\":[82.85,82.85],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":217,\"s\":[84.34,84.34],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":218,\"s\":[85.73,85.73],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":219,\"s\":[87.05,87.05],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":220,\"s\":[88.27,88.27],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":221,\"s\":[89.42,89.42],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":222,\"s\":[90.49,90.49],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":223,\"s\":[91.49,91.49],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":224,\"s\":[92.41,92.41],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":225,\"s\":[93.27,93.27],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":226,\"s\":[94.06,94.06],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":227,\"s\":[94.78,94.78],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":228,\"s\":[95.44,95.44],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":229,\"s\":[96.05,96.05],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":230,\"s\":[96.6,96.6],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":231,\"s\":[97.09,97.09],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":232,\"s\":[97.54,97.54],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":233,\"s\":[97.93,97.93],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":234,\"s\":[98.28,98.28],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":235,\"s\":[98.59,98.59],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":236,\"s\":[98.86,98.86],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":237,\"s\":[99.1,99.1],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":238,\"s\":[99.29,99.29],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":239,\"s\":[99.46,99.46],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":240,\"s\":[99.6,99.6],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":241,\"s\":[99.71,99.71],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":242,\"s\":[99.8,99.8],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":243,\"s\":[99.87,99.87],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":244,\"s\":[99.92,99.92],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":245,\"s\":[99.96,99.96],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":246,\"s\":[99.98,99.98],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":247,\"s\":[99.99,99.99],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":248,\"s\":[100,100],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":433,\"s\":[100,100],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":434,\"s\":[99.99,99.99],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":435,\"s\":[99.98,99.98],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":436,\"s\":[99.96,99.96],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":437,\"s\":[99.92,99.92],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":438,\"s\":[99.87,99.87],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":439,\"s\":[99.8,99.8],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":440,\"s\":[99.71,99.71],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":441,\"s\":[99.6,99.6],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":442,\"s\":[99.46,99.46],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":443,\"s\":[99.29,99.29],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":444,\"s\":[99.1,99.1],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":445,\"s\":[98.86,98.86],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":446,\"s\":[98.59,98.59],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":447,\"s\":[98.28,98.28],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":448,\"s\":[97.93,97.93],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":449,\"s\":[97.54,97.54],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":450,\"s\":[97.09,97.09],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":451,\"s\":[96.6,96.6],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":452,\"s\":[96.05,96.05],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":453,\"s\":[95.44,95.44],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":454,\"s\":[94.78,94.78],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":455,\"s\":[94.06,94.06],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":456,\"s\":[93.27,93.27],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":457,\"s\":[92.41,92.41],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":458,\"s\":[91.49,91.49],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":459,\"s\":[90.49,90.49],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":460,\"s\":[89.42,89.42],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":461,\"s\":[88.27,88.27],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":462,\"s\":[87.05,87.05],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":463,\"s\":[85.73,85.73],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":464,\"s\":[84.34,84.34],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":465,\"s\":[82.85,82.85],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":466,\"s\":[81.27,81.27],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":467,\"s\":[79.6,79.6],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":468,\"s\":[77.83,77.83],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":469,\"s\":[75.97,75.97],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":470,\"s\":[74,74],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":471,\"s\":[71.92,71.92],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":472,\"s\":[69.74,69.74],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":473,\"s\":[67.45,67.45],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":474,\"s\":[65.05,65.05],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":475,\"s\":[62.53,62.53],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":476,\"s\":[59.89,59.89],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":477,\"s\":[57.13,57.13],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":478,\"s\":[54.25,54.25],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":479,\"s\":[51.24,51.24],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}}]},\"sk\":{\"a\":0,\"k\":0},\"sa\":{\"a\":0,\"k\":0}},\"ao\":0,\"w\":7660,\"h\":7660,\"ip\":0,\"op\":480,\"st\":0,\"bm\":0,\"refId\":\"n3_n5wvl-IucVyppudovb\"}]},{\"h\":192,\"id\":\"1Da0nr0h0jPcTBwhSZmHH\",\"p\":\"data:image/png;base64,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\",\"u\":\"\",\"w\":192,\"e\":1},{\"h\":160,\"id\":\"MiP8hF_GKoeyJDEFJnuyT\",\"p\":\"data:image/png;base64,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\",\"u\":\"\",\"w\":160,\"e\":1},{\"h\":2004,\"id\":\"hF2XvXiETHHsaM0OLSNM8\",\"p\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAADIQAAAfUCAYAAACSdycZAAAAAXNSR0IArs4c6QAAIABJREFUeF7s3QucXGWdJ/zuXLpzIeGWQIBwRxC8AHHEgRFhveF64XVAFMVxXBnXzyrOOiszOvui86rsrOuI64zojKs4eEFRBnRREBFv6HBzDISRm3InECCBXEnSnU7n/fTznD9QJ12p6qrq7qqub30++qSqznnOc77POVXdzfmdf29PnY/Vq1efWCx6ZNEeVeeqFiMw6QKDg4MVY+jr65v0MRkAAQKVAuvXr08vzJ07N7XTpk1DRIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQGDKCwwPDz8wspPTpk37+Ui76667prbWo7fWAvG+QEi9UpZrRwGBkHacFWMiUCkgEOKIIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQ6EaBlgdCnhUA+ZsCNCqEdKOvfe5wgYGBgYo96O/v7/A9MnwCU09g/foNaadmz56d2hkzpk+9nbRHBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBGoL3NfT07Otp6fnzJFFq1UMqVohRCCktrAlOkdAIKRz5spIu1dAIKR7596eEyBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIVAg0FghZvXp1VAT5/4ASmCoCAiFTZSbtx1QWEAiZyrNr3wgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBBoQGKkSMvK/j4+su+uuu37s2X1sVyFEIKQBYqu0vYBASNtPkQES6BEIcRAQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQqBCoLxCyatWqd46sNn369H8GSGCqCQiETLUZtT9TUUAgZCrOqn0iQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBBogUAKhkyfPv1dI33Nnz//qyPt0xVCBEJaQKyLthUQCGnbqTEwAk8LCIQ4GAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAiMKjB6IGT16tUHFIv/rGjjOUcCU0ZAIGTKTKUdmcICAiFTeHLtGgECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQCsEVo9UCunp6Tl4pLNegZBWmOqj3QUEQtp9hoyPQE+PQIijgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBRgVGroTpbXRl6xEgQIAAAQIECBAgQIAAgc4RqAyErFq16p0jY58+ffo/d84+GCmBsQkIhIzNy9IEJkNAIGQy1G2TAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECg0wSGh4c/NjLmXoGQTps6421EQCCkETXrEJhYAYGQifW2NQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgc4UeDoQsnr16p8Vu3BiZ+6KUROoLSAQUtvIEgQmW0AgZLJnwPYJECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQ6RGAkB7KtVyCkQ6bLMJsSEAhpis/KBCZEQCBkQphthAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAoPMFng6ErC72ZZfO3yd7QGB0AYEQRwaB9hcQCGn/OTJCAmWBTZs2p5f6+/tSO23aNEgECLSpwIwZM9LI+vqcr206RW09rMHBwTS+LVu2pHbbtm1tPV6DI0CAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAh0gcBIDiRVCBEI6YLZ7vZdFAjp9iPA/neCgEBIJ8ySMRKoFBAIcUQQ6BwBgZDOmat2HKlASDvOijERIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECXS7wdCDErT27/Ejoht0XCOmGWbaPnS4gENLpM2j83SgQ5+3cuXPS7qsQ0o1HgX1ud4FZs2alIUbb7uM1vvYWGB4eTgPcsGFDauN5e4/a6AgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQAsERq64721BP7og0DqBkaMyVQgRCGkdqp7aVEAgpE0nxrAIPEtAIMThQKDzBARCOm/OjLj7BARCum/Ox3OPBULGU1ffBAgQIECA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ECBAgAABAgQIECBAgAABAgQIECBAgAABAgRaINDZgZAWAOiiiwQmKxCy4tHHk/IPr/rJpGjvtNPctN2oEDIpg7BRAnUKCITUCWUxAm0kIBDS2GTcdvvvKlY85OD90/P+/v7GOrQWgVEEBEIcFhMhIBAyEcq2QYAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAYVUAgxIHRPQICIblCiAeBdhYQCGnn2TE2AqMLCIQ0dmQIhDTmZq2xCQiEjM3L0o0JCIQ05mYtAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEALBARCWoCoiw4RmKxASPBEpZANG56aULH991ucttfXN3NCt2tjBBoREAhpRM06BCZXQCBkcv1tncCOBARCHB8TISAQMhHKtkGAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQGFVAIMSB0T0CAiECId1ztHfungqEdO7cGXn3CgiEdO/c2/P2FxAIaf85mgojFAiZCrNoHwgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIEOFRAI6dCJM+wGBCY7ENLAkK1CoOsEBEK6bsrt8BQQEAiZApNoF6asgEDIlJ3attoxgZC2mg6DIUCAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBLpLQCCku+a7u/dWIKS759/ed4aAQEhnzJNREni2gECI44FA+woIhLTv3EylkQmETKXZtC8ECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIBAhwkIhHTYhBluEwICIU3gWZXABAkIhEwQtM0QaKGAQEgLMXVFoMUCAiEtBtXdqAICIQ4MAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAl0mMHLpcW+X7bPdJUCAQPsKCIS079wYWasFBEJaLao/Aq0XEAhpvakeCYy3gEDIeAvrn0DjAgIhjdtZs34BgZD6rSxJgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEGixgEBIi0F118YCAiFtPDmGRqAQEAhxKBDoPAGBkM6bMyPuHoF58+alnZ0+fXr37LQ9nXABgZAJJ7dBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEAICIQ4FrpHQCCke+bannaugEBI586dkXevgEBI9869PW9/AYGQ9p+jqTBCgZCpMIv2gQABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEOhQAYGQDp04w25AQCCkATSrEJhgAYGQCQa3OQItEBAIaQGiLgiMk8CsWbNSz9GO02Z026UCw8PDac/XrVvXpQJ2mwABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECAw6QICIZM+BQYwYQICIRNGbUMEGhYQCGmYzooEJk1AIGTS6G2YQE0BgZCaRBZoQkAgpAk8qxIgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEWiMgENIaR710goBASCfMkjF2u4BASLcfAfa/EwUEQjpx1oy5WwR6e3vTrs6bNy+106ZN65Zdt58TILBx48a0lcHBwQnYmk0QIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAiMIiAQ4rDoHgGBkO6Za3vauQICIZ07d0beCoGRn8vyxdud9BAI6aTZMtZuExAI6bYZn9j9FQiZWG9bI0CAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAiMIiAQ4rDoHgGBkO6Za3vauQICIZ07d0bevQICId079/a8cwSiMsisWbPSoPv6+jpn8EbaNgLbto38/aCnZ9OmTalVGaRtpsZACBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAge4VEAjp3rnvvj0XCOm+ObfHnScgENJ5c2bEBARCHAME2l9AIKT956gTRigQ0gmzZIwECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIBAlwkIhHTZhHf17gqEdPX02/kOERAI6ZCJMkwCzxIYGtqans2YMZ0LAQIdIhABkWg7ZNiGOUkCEQTZujV/3nsQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAi0jYBASNtMhYGMu4BAyLgT2wCBpgUEQpom1AGBCRcQCJlwchsk0LSAQEjThF3VgUBIV023nSVAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQ6S0AgpLPmy2ibERAIaUbPugQmRkAgZGKcbYUAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQKDjBQRCOn4K7UDdAgIhdVNZkMCkCQiETBq9DRMgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECHSWgEBIZ82X0TYjIBDSjJ51CUyMgEDIxDjbCgECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQMcLCIR0/BTagboFBELqprIggUkTEAiZNHobJkCAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQ6CwBgZDOmi+jbUZAIKQZPesSmBgBgZCJcbYVAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBjhcQCOn4KbQDdQsIhNRNZUECkyYgEDJp9DZMgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECDQWQICIZ01X0bbjIBASDN61iUwMQICIRPjbCsECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIdLyAQ0vFTaAfqFhAIqZvKggQmTUAgZNLobZgAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQKCzBHIgZOXKlSP/8CAwpQUGBgYr9q+/v29K76+dI9CJAhs2PJWGPXv2rNROnz69E3fDmAkQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAiMt4BAyHgL6799BARC2mcujIRANQGBEMcGAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgboEciDk+huXqhBSl5eFOllgcLCyQkhfnwohnTyfxj41BTZu3JR2LCr4qBAyNefZXhEgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECDQtIBDSNKEOOkZAIKRjpspAu1hAIKSLJ9+uEyBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIjEUgB0IWHPgyFULGwmZZAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgMDkCQiETJ69LRMgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEGhIQCGmIzUoECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgckTEAiZPHtbJkCAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAg0JCAQ0hCblQgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECkycgEDJ59rZMgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEGhIQCCkITYrESBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQmT0AgZPLsbZkAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAg0JCAQEhDbFYiQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECEyegEDI5NnbMgECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECgIQGBkIbYrESAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQmDwBgZDJs7dlAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEBDAgIhDbFZiQABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECAweQICIZNnb8sECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgYYEBEIaYrMSAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQGDyBARCJs/elgkQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECDQkIhDTEZiUCBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECg5QK927akPvumR9cjuYeenuGeaandMjyj5dvs0A4FQjp04gybAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAhMOQGBkLqnVCCkbioLEiBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAk0JTOvNFT92mjGQ2rkzh1L7xMqHU531Hr0AACAASURBVDt9em9qd99l19Ru2LAhb683VwiZO3tWag8/8qX5+R77p/aGX9+S2sceX9XU+DpoZYGQDposQyVAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAh0tIBDSsukTCGkZpY4IECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEOhqgZH7GOd7HXsQIECAAAECBAgQIBACO82dm/55+MF7pfbxe3+T2nlzcqWPwcEtqV27fn1qn9q8KbVbt+bKIRufeip3VfywPX3mzPR0n0WLUvuiFx+X2v/w6pNTe/fyx1P77UuvyNtb+UQMZaq1AiFTbUbtDwECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQaBcBgZBxmwmBkHGjnYId77rLvLRXu+w8v2LvNm8eSM9Xr82JrHg+BQnsEgECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAjUIbBoz4Vpqc9+8qOpvfoHF6f2lhv/NbWPPLoitTP7+lK7bXhraoeGhlO7y667pXbB7rmfuUWlkd6iVMj9D9yXXn/s8cdSe+B+B6X25f/xjand/7kvTEt+9nMXpOf33v9gHaPuqEUEQjpquiZ5sAIhkzwBNk+AAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIEOERAIGfeJEggZd+IO3MCB+++TRr3kyOem9vBDc1Jq1qz+uvbmvgceTsstvfWO1N687M7U7rJzrjBy5p+cktrLvn9NamP5ujq3EAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAi0rcDBB+6XxvaZv/1IaufMmZXaj/7Ve1O7ccP61C7eZ//Uzp07J7Uz+/L16vN23qXYt97Ubi0qh/SOxB96enqGi+ebBzan5zdc/4vUbtrwVGr7Zs1O7fs+eE5qD3vBUblSyD98OT3/6S+uK/rv+EYgpOOncBx2QCBkHFB1SYAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgS4QEAiZsEkWCJkw6jbeUFTuOPXkV6ZRRiAkhhwVPFY8uiq9tGbtuoq9icohe+25oGL9eP2i71yZXt9rUX7/5S87JrWXff8nqV26LFcS8SBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBzhTYc498vfj5n/l4aufPm5va1avydej/86N/kdoDDj4itUtefFxqh4aGUrt5Y67wsXVrfr5169YKiN7eXDFkaGhLsfyG1N57z12pvfnmf0vtuvX5evdXnXRyat/7wf83tQMDQz3btvX0fOxvz0vPb7vj9xX9d+ATgZAOnLSWD1kgpOWkOiRAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEBXCQiETPh0C4RMOHkbbTAqerzttNelUe26y7zU3nHXvam97qZlqY0KIfUOPSqDHH7ogWmV+x94OLWnlCqQXPD176bXZ/X3p3bzwEBD26t3XJYj0E4C5SDWLjvPH9Pw4ny57PJr0norHsvJSQ8CBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECgewXiOt4lL3xuQjhw/31Su2jPhRUocR3io4+uTK8vvfXO1I71uuGpLn3Gm1+b/fao9Iv9/ukvb0z/vHlZ9uv2x4Vf/HQi2KOoFNLbM5JX6On57N9+NLUPPXBPal/3x2ekdt8DDknt6iceT+2jjzyY2mm903I7LVcEGRgYTG1/cd15VA6Z1jOcXn/4oQdS++NrfpjawS15+T86/hWp/cBf54olg4O58sj6tet7tm3b1vOhv/lker7qidWp7cCHQEgHTlrLhiwQ0jJKHREYs4BAyJjJrECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAjUEBAIae0hIhAyNk+BkLF5tWDpzgyERJAhPrACYvPmXGEi7pTf6uVaAN4WXcSF6Ge9+/SK8Vx6+U/S8zt+lyuElB9RQeSA/XJSMB5r1q5P/6yVCDzn7Hen5crzVt7OOeee31KnSDYuOTInHet9bNqUk2E333pHalVgqFeucrnjjjkyvXDsMUelNo6vK6/+ZWMdTpG1Xv6yY9KeRBvHV3yO1drNOK5j+ai4M9WP05kzZySaLVtyQtODAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQ6OmJ6zXjusS4XjeuM1xdXO8bz+P9uN46DFc8uir985uXXJHaWK9bjT941p+mXZ89qy+1cZ1mXMd53Y3L0utX/ri7r4s94y1vTA5nnJ7bbcO5csfWov1fH/lgej44sDG1b3nH+1K7y+57pLavf2Zq77v7d6ld92SuGLKtJ1cKmT4tt5s3b0rtU+vXpra3qCCyeVN+/XuXXZyX25JzBfvsvW9Pb29vz2e++M30fHjL1tQODeXrUP/1hn9L7flf+npqO/AhEDIyafUGRzpwgkcdskBIfTMpEFKfU62lBEJGFxIIqXXkjP6+QEhjbtYiQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgaktIBAyPvMrEFKfq0BIfU7jsFRnBkLO/JM/ThaRrAqYqOQQlS5avdw4TMCEdhlJvvf9Wa4MEhU/Pv+lnIQqVxY4/LCD0utx4Xo5AVgefCQGf3rtTemt627Kibf4gnntq4+vWGX1mlxZ5I67ckWSqBxRq9LIWNGOfmGuDHLqya8c66oVy8d+RdtUZ120cjn4UD5Pu4iiYlfLLued/9X0fpwXtVzOPeesikWmeqWQfffeM+3vcw7ZP7U3/Dp/vmzclBOcHgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAoBsFytcjrl6zLjFcX1zHu/TWO9PzuM6wbBQ3mo9+lhx5eMXyF3z9u+l5+TrjqWJ9+KH5eum4jjn2K1zOfn+uEBLXO190yZXp+Tlnv7vC5bLLr0nPo6JKuQJLq6+Pbhf/PfdYkIbylX/8VGqjIsi2bSM5hZ6eDevy8fi//iZXCJndPyu1b3zrmam989ZcoeOpDRtS+5zDn5/a3RbundonHluRl7stL3f33XeldsFuebtRMeTAQ/L14jfemCu13PW7fNwf/7JXjdQR6fmrv/lkej64eTC1W4sKIWvWru0ZGek//OOF6fXf3XN/ajvoIRAyMln1Bkc6aGJHHapAiEDIZBzDAiGjqwuEjO1oFAgZm5elCRAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQKA7BARCmptngZDm/ARCmvNrwdoCISOI3RIIiQodUbHjyqtzAioqecQBVV4uEoFLl+Wk1IrHVlYce7Nn9afni/bMSasrf/yr1JZLT5UrbJSTb5EcrJZAbPSAL1cIif6vKPa/Wr8vf9lL0ltRSSWWO/fTX0r/bPU4G92/dl9PIGT0GWp1ICS2MlUrhSzeJ1cIOer5h6V246bNqVUppN0/AYyPAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBMZDoHwdYlyHGxU94nrCuF53yQtzBYVddp6f2hWP5+uBby6uD44xHrj/PumfZ5z22ophf/5LF6fnUQFjPPZpIvs8+sjsceob8g3no7LKo4+tSs8PPyxXDolH+brr97379PTWXsX107H+mrXr0+vhGOuf97mvTim/2K9Xvfyl6Z//9X3vSu1QUXmjKBDSM7RlKL2+7Nf/mtrvfTs7vPH0vPyWgU2pXf7gvaldu+bJ1L7uj9+W2jt+uyy//8DvU3vT9fk69b5ZudLIc5/7vNTO22nn1D700H2pvfa6X6T25Decltq3/9mfp3Z4aGtucwGTntVP5vPgVzfektpL/u9V+Y3OeQiEjMyVQEg+UeIhEJIlBEJa80kmEDK6o0DI2I4vgZCxeVmaAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBKa2gEBIc/MrENKcX6wtENIaxyZ6EQgZwZvqgZBddp6XjpGz3/+nqY0E2nnnf63i2Cl/Mdz3wMPp/YsuuTK19VbEOOUNr0jLLzny8NQuXXZHai/7/k92uL1qFUuaOMDTquUKIWvW5OTdp8/PCbNqj0hEnnP2uysWieRk+JTXj/WiQkok/+L1NWvWpVWiMkskMmvtZyQFlxSJxEhoxnrRb1RiqZbAjC+wg4oE573FPN9xV07Elccd818eX/RzxKE5AVnevxjH0cVxEMfXzbfm4+HSyyuPh+i/Wr9x/N3+u5wALCdSa/m12/vjFQiJ/VQppN1m3HgIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEDzAnFd8FlFhYpNmwdSp5//8rdTG9cPlpeL6zzLI1haXNd5Wem6zqiQEZVC7rgrX79Z7brS5vdsYnoIlzP/5JRig0WpiJ7e9HzXXfJ113F9b1zfXO264bhOuXwj+lg/riOO9eM65InZ2/Hfyic/8aG0kRcccVhqtxQVQoaGhtPzqMix6amn0vN/+NQ5qX3bu/5Lardtzf7XXPW91D72SL5+/Y1v/U/5eF6fr/tefv/deV4eXZ7auXPzPC3cY8/U9vXPSe1D9+dKIj+/9qepfcXLX53afQ/KFWGOfnGuaLKtKBHy5JOPp+fD23p7tm3b1vPhj//v9LyDHgIhI5MlEJIPWYGQylNXIGRBAhEIGZ+PdIGQxlxVCmnMzVoECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgMDUEBEKam8dmAyEjsZGIkIyMRCBEIKS5I7LptTszEBIX6s+e1VchsGnzYHoeybZWL9c09yR1UC5pdOnl16SR3Hzrnamtt4JIeB5+6IFpvfuLyhKbBrJ7BGsiyRbJtkiylRNu0d/ZZ70jrV9evlVcjVYIKbvEeM77XK4sUq7AUf6CiIRgrf2IShrRlpcvBwdq9VerMkS5gkskDqPiSIy7WiWV8vrVxhPjiP4jKVqtQshrX3186ioqlNTaz05PmjYbCPngWbniT7VHrXms5dvu7wuGtPsMGR8BAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIjIdA+XrL8nXBsc33FRVE4vrduJ5380CuKHLsi49M7ZIjD0/tpd8vri9elq8vjkf5xvvnfvpL6a24TnQ89nE8+yxfBxsVQK67aVna7IH775PauK45nsf1rXG97f0P5EoVcf1vhEQ2F9dVh0/ZL+ahWsWR8dz38ej7yu9emLrdNpwrggxt3ZrawcGh/HwwH28b1q7Lx9k38/HzH17zhtTuvnCv1N571+2p7Z02PbVHv/jY1N752zwvjzx0Xx7+tryd6TNmpnbeLrvn7W3enNo1T6xI7Q3X/TK1f3DMcantn5Mriiw57sS8naKf1U+sTM9nzZ7bs21bT88nPv1P6fkTq9fk7bX/QyAkTeCs/jRVtQIm7T+fo49QICSX+Dn15FfmE31NLh306fNzsKPaQyBkdCeBkNZ8EgiENOcoENKcn7UJECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECAwFQXK1Rum4j4KhDQ3qwIhzfmV1xYIaa1nA711ZiAkkmoR5IgdjyRVJLJavVwDwG2xyhmnvTaNIyo0lJN55Q+2cgWMapUvzjn3/NRvVCqIigSr1+QE11e+/t283ecelNqXH39Maj99/tdSG/NV3n6rk4PlCiGx3Tt+d+8O5+fwQ/O44zirVsmjmk9UsIj1IlEZ/cYXcgyinDCM12P7kSyMChuri2BLbP+MN78urVKuxBJJwuivWqAjzpsYx5oiiRfbKQeLor/YvxhXjDfmO467WL5cISSWP+fsd1fMR4w7xhGJypjPn117U1q+XKmlLU66OgbRbCCk1ibivBz54TafdzsOQNXqr13fFwxp15kxLgIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAYD4Go/DGrvy91f15xXW55W1GZIipRxPWesVz5+s3y9Z2xXFwHGtcjV7vedTz2dTz6jP059Q2vSN1Xu064fN1s+Tr1qBwSr3/+Sxen/jYVFUKi/9he+frqTr3+NebkoAP3S//8wmfPTe3g4GBqR6ps5OdbUrtlc64Qsm7tE6m9965cgWbfYv39Dso3/r/zt7emdtq0aak9/PnPT+2/3XhDap987JGin9WpfWLVo6k9+DkvTO3M/lwg4slVebkH7v197m96rjhy1B/8UWr3PyRXxIntPLEy9zNv3i6p/eZlP0ztjb/J4+mAh0DIyCTVGxzpgAkddYgCIZUVQgRC8hdYlPiKg0YgZGLPcIGQ1ngLhLTGUS8ECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAg0BkCAiHNzZNASHN+sbZASGscW9BLZwZCIrEWyaqAKCfTWr1cC8AnpYv44O8pElef/3JOoMUjKjNEJYjy++UKIJdefk1a9Y7f3Zfas896R2o3FQmuqAwSybWobBEVMS76zpXF+rlCR/n9qAwRicRm0coVQsbaXwRIYlwRnIh+ygnASPBVS1zGeuX9ju1EhZRq44xKLPH+ps05URfBpjjuq/VXrhBSb+KwfD7dvOyOtN1Lv/+THZKWj596K4REgjQqrXR6ErKM1O2BkDmzZyWS3Xbbeayn5KjLL1q4e3p90Z4LUrtx0+bU3vDrZcXznDD1IECAAAECBAgQmAyBbijKPBmutkmAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQDcKnHvOWWm34/rKiy7J1+WO9VGu/HHdjfl6uyt//MuKrnbZeX56fvb78/XC1SqJjHX7k738LjvPS0M4692np7Z8HXVc/9pTXIBdvj46rmOP62vD77qbsmN4xfXQMU9xfe9k73+z23/h8/MN+//nxz+U2q1bt6Z2W1EiZGAgVwjZuqWoFLJlKD3/1c/y8fr8570gtS8+7qWp/dGV+fXhrXm5WXPmpHbattzvr36er1dev+7J1E7vHbkWoadnW2+uKHLQIXk8w8N5+SeeWJXaPRftk9rDnrcktf2zZ+f1Z8xI7arHV6R23vxdU3vxd69KrQohiWH8Hq0OetTb3/jt0fj2LBBSWSFkrNoCIVlMIGSsR86OlxcIEQhp7RGlNwIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBDoBgGBkNbMskBIc44CIc35tXBtFUJGMKd6ICQSamvWrkvHTlS6iAOp2hfDdom+UkWIM057beoiKjlEBYdZs/rT6xGkKCfgLisqSiwt+itX8Ci/3+wBX+4/xlUrERkVN6KySbVgSDhEUjI8IuFXbfzhFBVaYrnzzv9q+mckDcvjKFfGifVi+XIFkXPOPb9iCOUKIfUmRMuVPuqt5BJ+URGlWjK0vFzZLRKSS2/NlUluXnZns4fGpK5fbyCkfD7FoHctkqnVKqfEfOX8Y0/Pp4vjalJ3+lkb372oDHLsi48c1yE98eSa1P/1v751XLejcwIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgMBECcd1vo5U6jj4y32j9da86PrVxHWJcF1qtgkWz250Im0a2Ue0669jfuH61fP11bKt8HfYVV/8qvTXVKqqUbcuBkKGhXNljJJ0w8hjakit1DA0Mpnbz5s2pXbf6idT29uYlX3B0vo70e9/5dmr3XLRX7qCoAPKbG36Rnq5fl6+Dnz07Vw55ePmDeXtFRZGDnnN4ej5z5szUDg/n/l/0Ry8vXs/Xtxfd9kyfPj09f2zF8tTuuuvuqb34e1en9sal/57H0f4PgZCRORIIGb10lEDIgnQKC4TkTzKBkNZ+oguE7JxABUJae1zpjQABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgSmtkCzwQyBkMrjQyCksfNFIKQxt3FYSyBkBFUgJAdCyknBcgWLcsIt3o8KFqe84ZUV/fz02pvS87FWCLnoO1em9e743b0tOebLFULWrFmf+q23YkK5ckW5oka54ka9FUJi5+KLOZ5HhZBtRUTurHefnt4K75uLyir3PvBwen12UZFl0Z45wLLkyJxwi0etCiH1JkTLgZB656neCiEx3qi0cvihB6aXjjjsoIr9j+WiUshll/+kJcfJRHdSKxASpcjO/JNT0tCuv+mW1EYSNyrTXPr9a9Lr5Yop7V4hZOaMGWnc8+fPbQn9gfvtk/qJ82DTppwkve7Xy1K7adNAS7ajEwIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgMJkC5YoUF12Sr7ut9YjrEs9+/5+mRVc8tiq1tSqDxHXC7yuuZ73uxnxd3pU//mWtTbb1+3F9c1yPuXlzrmQR1xfH9eWx/5//0sXp/biO87hjcmWLuE42XOK63PAK529+54qK9dsap47BHXTgfmmp8z/z8dRu3ZorgmzZsqV4ni+E3jqYn2/atCk/H87LbduavffdL/fzve98I7V33nlbak967f+T2v7+Wal96MH7UvvAvffk/p7K14PPnZOvQ53ZPzu1T61fm9q9D3hOfr6lL7V33Xt/ap9cnd/vTf+fBpKaFx35gtT+27LbU3v3fbkCSQc8BEJGJkkgRCBkRyerQEjWEQhp7Ue6QIhASGuPKL0RIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQDcICIS0ZpYFQppzFAhpzq+Fa3dmICQqJcyelRM78dhUJLM2b853gm/1ci2En9CuImE2u78/bbdcGSMu9A/Pcz/9pYrxlQMzUaEivJcc+dwK7/M+99X0vFoCLpKEUXGkfGF8+f1msZqtEFKuAFKulFJO+JXfrzb+KLl1alFZZfWadWnR887/WmrH2u8uO89P6539/ndUbLJVFULKx0FUgIm22n6WgyT1ViQp91f2iPfjeI3zvtnjZaLWrxYIicowURmkpycnJL/y9e+mdtNATkSWk6flSiHtXiGkVc6HHrx/6urQQ3KrMkirZPVDgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAu0oUL5+sHzdb7Uxl69bjIoXUcGi2nrl610vvfyatOjNt97Zjjw1x1S+HjWuP72iqHhy87K8XxEYCe/oePWaXJli113mpTau/43rPOP66bJ3LPfNoqJLLfeaOzLJC8ydOyeN4F8u+sfUDg0N5XZr0W4ZTs+jQsjmzZvT802bNqZ2Y1HhY8GC3dLzLVvy9bHr1m1I7YxpuYbHrUtvSu2Tqx5L7W67L0ztzL5cOWRwIFceiX6vvznP38NP5sokg1vyeKbwQyBkZHLrDY506oEgEJIDK6ee/MrUrik+iMvBmGrzKxCSZQRCWvsJIBDSGk+BkNY46oUAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEOkNAIKS5eRIIac4v1hYIaY1jC3rpzEDIXnsuSPseQY6AiIRWJKZavVwLwCeli3KgoVxRoVaFjl12zgm21736+NQefthBFfsR7pde/pP0+h2/u7fi/ahUEJUOogJGLFQOGrS64kO1CiFRUaE8KbOKSip7LcrHWfjEclEh5dLv5/2N4/B9f3Z6eh6Jvzvuyg5XXv3L1EZlh0gMnvqGV1SsX05Mlscdx3VUUClXxHhtMT/xRRXjbVWFkPJ4ov/Yv6WlpGe4lcdTrhASfmec9trU5dJb70jt/fc/nNpISlZLWrb6eJmok7RaIOSUomJM7O9F37ly1PMqzsuz3/+nFUOOCj3v+pNT0us5H7l9ZaCJ2s/x2o4gyHjJ6pcAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIE2lmgfP1hXA8blS2qjX3X4nrgXXaZnxaJ61LL16OW148b08d12XGdYlzf2c5Wo42t7PfTa3MFiutuWpbaJS/MN6KP64DjOtdYL56veHRVWu6nv8zrh2+8f98D+TrY8nXSne5XNr3w/5yXXlqw+66p3bp1a2oHBnKFji2bB3K7JT+PSh6Dm3Nlj9VPPJ7aI//gxal9amOuJDJvfr5+/fqf/Ti1d/52ad7OHotSO3NmrhAS/V3+s1vS8yfX5Qok8dh5Tr6Sdl5evGfWzIq3e7bk4fasKVZbu3EkX9FRD4GQkemqNzjSUVP7rMEKhIxeIUQg5PB0lJQDGtWOc4GQ1n4CCIQ05ykQ0pyftQkQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECgMwUEQpqbN4GQ5vzKawuEtNazgd46MxBSTkrFjpcvbG/1cg0At8Uq5coKkWSLNioNnPXuXOFiU5HE+srXv5uelxN8sfzs2f3p/Ui4lXe2/IEZlSQiQVeucBBJuKiA0Sq8akGGsfYfSchvfueKUV2iosjb3vS69H5UCqm1nfJ8xPLVKo+sXrMuLbJm7frURqCpvF48b1WFkOgvKnmUK8WU9zMSozGvsXz5PI3Xo99aXvH+dTfmJOaVP84VWDrtUS0Qsq0IFp5ZtlugGwAAIABJREFUVPiYPasv7VqcF3E+lku+lRO+UZlnqlUIEQTptCPdeAkQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECglQJxfenZZ72jotvPf+ni9Lxa5Y5q1y2uXpOvRy0/jnvJkeml177q+NTevOyO1F76/Z+0cncmvK/y9blxnWYMJN6P63WjAki5Akssd9wx2Sl8o5+4jjaWi/Wr3dB+wiFatMEz3vLG1NNb33xyaqNCyODgUHoeFUIGBnKlkI0bN6R2KCqGFM/32Xf/9PrwtlyyY/OmwdTesSxXYFn12COpnbNTrhwyXFQiufTHv0nPV6/PFUdmTs87tnj3aaktVwSptdtRMWT5E/mC3s1b2r5iiEDIyETVGxypdQC06/sCIZUVQhqdJ4GQLCcQ0ugRVLmeQEhjjgIhjblZiwABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgSmhoBASHPzKBDSnF95bYGQ1no20JtAyAjaVA+EVPvg//T5X0vHTCTQIqH22lfnJF8k26788a/S8zvuuneHx1hs59Q3vCItF5UfqlX+OKVYbsmRh6flL738mtTefOudDRzL1VeJQMySI3MwZKyP2++8L+//73a8/9FvVD457pij0ktROWSXneen548+tjK1USklfKqNK/qLAMFeixZWLBrzsrRIXr7ihGMq3r/08sokZlRMOeiAfdJy997/cEPu0U/MX+zf/Q8uz/tXVPCI14947oE73F4ttzVrc2WU2N/wG+t8tsvytZK24RGVQqKySgSTIphTrgwS+zfVKoQIgrTLkWsc7STQ25trAG2L0kLF4Mb6+lj3KSoPNZp7rja+8jiq7kexYHn75eULnp4ST83drXd8tcZba/u1tlNef+zL5xHW2v9a46wJNkELxP7H5uK4r+lS5XipNexqx3k1r0Ydax23jfZba/9qHr91n2eFVHGgbX9eVh6Hrdqf7dyqjbf0es3jpcrn6lg9Y/nqx8uOP797erJkrfO32rhq7Wet8W13fDR4Hk3U+Bqdn+rjKx+3o8/XWB1b5TrW7/l6j4dajr7/Rxeq9Tle6/N2+/ebO/7q/Zytd7lax8V4v+/7f3yF6/4+LX0/Pj0vvv9HnSDf//V9Xtb7PTy+Z8EzvTf7+1e942z0+9T3f6XwWL9/m/25tNb3ea3v1Vo/jzV7/NXafrM/t9Z7fLdqOd//rZKs7/O4+t8/Kn8P8v2/43nx/V/f8eb7f2znt+9/3//PFtj+54XKvx+02/k1tqO9p8f3/1jFxra83/+zV62fy8emOtLf6Odhrc9vf/+P+ajvc2ys81Jr+WZ//6rV/9O/fxX/GOt/T691/NR7XUC942x2vM/8vrnj/45Sa7n6fy9p7Lhp9Pwf698fam2n2ePP7//F50ej51eV9abKf/+P6zhPPfmVaU/XrM2VPi74+mX5eVH5Iz6X4nraaOM6zIGBXIkh/kP1sUXFi/L1xF/5+vdyv+vy9ZyN/nftap8PVT8XSvNY+7yr7/Op7BfX80Z79Avz9c277pIrUoTXlVf/Mj0/692np3bRngtSG9d3PvrYqvT8gP3ydbpRgeTzX/p2er66uB42HKq1tfbzGceK6avabaN/p641vj0W7p4WueAfP5XaqBCypSi1ERVCNm3KFTw2bXoqtVEhZONTuWLIwYdl73Vrn0zt5k0bU/v4I/k650eX35/aLcXxet0td6Xndz20OrUzZ+Q93H9BbqNSyDNO9R0XsfzW4fyvB1blfwxsiXcq20ZdW/j9LxAyMiUCIbkEj0DI6CeqQMjoLgIho7vU+6pASL1SeTmBkLF5Wbo7BFr4A+GYwBr9AXasP1hX3b+iI4GQ0i8WpT88+4NQ9mn0eBUIyX61zzOBkB19gPoPQlX+EFJn8Kbez7ExfYnVsXCzf5CvYxPNfT4JhFYQj/U/yFSbn3r/kFpef6zbr7WdZo+/es+beper93ger+VcEDJessXPSeWgR90/Z/v+9/0/9mOz1uffM78vFj+HjvWKibEPqfR9UrndWuNt9HO04d9PfP/7/n+WgAtCR/+AaPj8qvr9N/rnUcPn/3YBy/LnzsR8/rkgdPSfg5r8GnFBaBXAWt+nvv93fOT5+3+lz1h///b7f3zeTcz3S/Ofo/FNHuPN3/e1Pkd8/9cn7/vf9/8O/45R5efh+o6u7Zeqdd76/vf9PyKwXbCl6u9l5aB6Y99r9R6X5dkZ688ftbbj7/+j/3wXr7ohZKOfvHk9gZAqfy+q879LC4Q0d/zF559ASPE9NUbOFv7+LxAyYj/VAyFxfJUDH1HB4cof56RateXi9agYcv+DOWkVj6gAsVeRcItKIVHJ4dLv5woVUYkkKh+c/f4/Ta9Hv+cVFUvGeD5YnEBHCtQKhMROxfkU588z511OnEait4ww1SqEzJ83N+3iUS84LLW/vvm21G7alANtHgQIVL8TTatsxnpnhO3+oFEMpNp1PU//ub/KHXUa3Y96/4NAeblm97fR8VZbr9YFB09fMFm6U3PNPzzFBrcLlIwtEd/o/tY7P+X+wyMSC3UHF57uqLICQ71Ota5LqzVPjTo9PU11/sFie6/R57NW8CUiNeU/zNbaj5qe210I09zxVv0X5DzSWnckadW8VfvDcPk43X65KvPTpNP2n2tjc641jzWPgyqf+/X2O9bP4aqfnzW+f2rtR7PvP3NBe3OVXxr9fmh+/KOfR/XOT63P+bb8/u/t6dlW3Onlmc/fsTk0615ru+Xvhfigi++pWudZNfda67Vsvxo8L33/1/pJpHKGxh6wrvzB0Pd/fUe87//Scef7Pwn4/i++N6ucRm35/T/K7w31/rxT36dF/UvV+v3E7/87/r4rB0rLd2Cu9fNOrZ9f6/05rf4ZH33JWuOs/vuB3/93ZF/r/Kp33nz/+/4f7Vjx/e/7v97PkNGPn9H/7uD3/9GPq2p/X33G1t//0+8lVX8/8/t/I+er73/f/77/t78eodbvT37/3/GnTa3fT/z+7/f/Hf9+O/rv/+XrEeN6w6W33pm6u+GmZal9ck2u7FF+RMWQ6CeeR4WRC77x3bRKXO9b/nm13mBPre/iyfrv/3stKip8PJore8Q44nrnD571jvR6XBd90SVXpufnnnNWaqOiyFe+kSuolP87y96LFqbXH3l0ZUX/9f73mE757////a+yx4uXHJnaLVtySY2hoqLHU0UlkMHBfN3n0JZcmWbtmlwR5P9n717g5SjqRI+fk/fjnBCSIAkPEcgDwq6AQQEJKIqiKHdVRFdXYcFV11VRP3rxCauyPnB3ddV170oUXHWvJsh6lZWXKKsERJSV4Mr7KeQBJCQhh5wkhJz7OVVdM+marvn/q7tnpufM73w++XRmurq6+lvVXd09Xf1feOhzzXTawBQz3bLRfn/7bbb9Pv7YGuu4eq2Z/ucNNkKI+9tnz3Hmv3tMS30d/WFX8rPcuAQ+CXTSd9+j7ve6yv3+z4CQ0VpmQAgDQqL3dhZAoKAAA0LiABkQEudF6t4UkG4YFFWJfRCCASFFxdPLS/XLDSHfy39jTPoNYzwQYr34QShxKGkgGj8Iefth8lEasBU6Wsb2O8F8AuUo9ygdzo0HQqwNA0LztTj6/+zzIQaE6toTA0KaO0n7l0658Y21oYFLDAjVDUyl/9e1vLLab1nnYVz/6+pNm0qqX67/uf5v1pbo/+n/zf2eyBeKxKb3lcP3l+j/tcd+TTqpf9Dk0fz4kVy/exfw2vMzHggtVgNS/dL/0//T/2dEFFDe15f2L+3ey/1/bz8M3HfXnldo+xepfqT+R1q+6Hzu/3P/v0gbko5P9P/0/3n6fwaEWDVp/wrZMiBEuK+i7P8ZEFKkd8jffpOf5Ef65xx4Qtwr5oqVt/DS7o35U6dMSuU1vM2OFHIj28pOV7jgFcnAHwDz81/ebErmpq6YbmRbfcTffmbWnjNtZAL357zXPmpHxrl83Ig3P7+3vfV15itXf19b9n3zeePmLRURohgItF7Aj9izcVO57d/tp24//OZ37Ahd/hBAoHcEtDfcJJFWvblDXK/yzfhFb/TFXgiGbvhKEQjc9obfZOANYEgWCD5A2+BjF9CWQ/QX1i8ur7wRL+eTHsihfTOClG/e+VJ7kec3/yFcWt4vd972H7qB2JC/cj/M6xlaTtrP8m63VE7pQc3Y+pG2z82X2rX/httQ+sY34brjgj2SxB5/3J0y6U0uoQdfRe/Acaaoc608Lv/AgTTvevK2P+368uYv9TNSfYSOL27kWFn9i1QOaT+sbWeb+ynpuBS7XaH6kuq/aDnEcrbZtazjpLRdRedL+688n/5fUwdS+5b2D806stJIxx2pfrXr1fbn/vFBOl+g/7dC9P+6B26l9sr1f1pIOi6JnoEHshvyFfrfouUQy0n/n0kkHf/l+fT/Utuzx2//hR52KXf9Qf+ffUFL/0//n7X/aPa5zPPh2gloev/Lm592Oek83CtW8EUT0vqk47W/vHRckteXffyn/8+Wi71OkvyLzpfaizyf/l9TB9J+Rv9P/9+sHXH9z/W/7jgTd14jHZekdcb+/sbv//z+3/Q4l/M+jf/7/yGLDjI5nfKy48009Lyve97an+/K+LtVd5j//uSnK83UPScs7Rex59nd0v9/8D1npjzd857O78YkAssV16Rf0B/ycN+Ptd//99prttm0v//Mx83UPce/feuw+Tw8vDWZ2s+TJ00w07tuv81MlzzfttsJk+34gBnuefXkAPrg/feZ76/56S/M9EeJ95RJtiUduFf6OBPbXtdvsSvavNVOD97bRhxxf6ufsN9v2aaLEFK0fUc8/9edEULKHuihzS+2YVQ1PQNCqlozlKuXBBgQ0ku1zbYi0BmBiBPCpgXkgZA0DzeEdO1Z+mFEl0v9wQCXXnpwXptv3nTSdsnz+UFIYy/tZ0UvmENlkH6IlupXs22jaWJ/6NSm54EQWwP8IMQPQpp9MXZ/lo5L0jr5QUgSyj7fov/PdsvbD/o/COl/eCznuCK1Amk/y7vd8ett/mCqlJ90nqFt1/T/cdL0/+Xsp1z/c/0ft+elz7+LDiDWHvfylDHPMtL5ojyf63+NO/1/vvNgrv+5/t/9/lLR+6X0//T/muO1n0bqB7V50v83l8p7Hcz1f+B+ivACrE61a+1+QP9P/0//r+1d4t+sLl2XSGvm/r8klO+6Jy7X/Kml4788v5rX/wwIcf2Gf7+/2P1/BoTozl8ZEOId95KPeaN2RDz/150DQvIfwllyVMANgHn3X/25AXEj1EKRQnw1fwCNFNngwAP2NVn8xemnmKlb/rLLrzWff7fqTioGgZ4TcPvBKS9bmuyHM0o1cCNy3YhTIvCUyktmCIxpAf+CVnpA2mFIF8L1dHEPqkSc2GbWi/ZGat7ySY0hr0tjPdg1FX7AQrjQiPUO3YDuS97Zpi2vdr31B738Ef3u0in0BrZaDSeOoTcsFXPW7i9Su/Hbo6v42laKP1zo3qyS/welok7Fltf65U1X/yFcuoHn3TAK7F8hZ+kHnHSrdXuVfqti61d7vNKXIJ1Sv59L7un2o73RLpVbW75QPlrvWOdW9Qf+dkg/eITKrXXTpiurP67vX8o3swTe4C21m2B7SBqE1A/mdY8tl9Q+89ZPYzl03rH1TP9vxaTzDO15r9QeirbrVi0f2+5j09P/x4rp0muPL1I66To19riSN31D/xk4/2vsZ+POf+n/8/00Rv/vnX8L7VPa70Lni/T/un5Zd5Sspwo9uEn/HysZl57+P85Lm1p7fJHS0f/HnT/krR/teYdUX3nPr7T5hq8z0lc4sdfhWrei1zlc/+t+F+L6X3eewfV/sT2X/r+Yn3Q8lgYqSsd9+n/6f00L5fqf6/9RAf+8IRxhodh+JV2Xa9rs7mk6df1/aBIx5MBn2+d4582dkyq6e85w7aPrzfd33HW/mbrP0nbG/v4p5Vf2/LL6/1NebiNXzNs77efK+6skQsgddz+Q2gS/feb+nSbJVXv3tNX1IvXrL156jCnxO896k5nufOYZM500caKZjp9gI4NMnGC36D++9x0zXXK0dZ4xay8znTNnlplOmDjeTO+9+x4z/crXv22md9/3sJnOGrCye+9hp1L5TKLd/lxkkMeftOXZZ0+bzx7T0s/fbLYBTvrWbNyVrMd+lq5H3aqk60O/XBGfGRASgTVmkjIgZMxUJRvSxQIMCOniyqPoCIxxAelGW/jBW90JbuwJd2x6v3q0N8ZDJ95Fq1t7gSWd8GvzkcorXVjGevu+9fXzQOioRaxnsP0mV44MCJFaeDnzy7ohVDuuJP/xb8yE9p9evSGkPQ5L/ZQ2n9D+Jv1gFWpl0vG1Xi5dfxlKX1Z/IG0/D4QUO55o64n+nwdCslpap34Qklq9tl0Hj5PKgVJSOVo1n/6/NbLa82EpHf1/3PmDtjbz9kNSfXXsfKz2JIDuelS7HXpPXT3lddeWQ7oOKVo/jeXQeceulwdCrZj04In2vpf2eqXxOkHXrun/k/pKILj+157n80KI3fcd7XV4bH/A9X9agP5ft3/6/Tb9v65fjt0/uf6PFSsnPdf/5ThK/Uv4/JD+n/6/xN9PlS8opP+n/x/d77QPakvHt6LtqVP9PwNCkpoVXhDXcB/Gu85nQEjc9VW+ASEjfUuOPsGsiAEh0edtDAiJJhtDC8zcY9BszV+84VVm6kaubdz0pPn88+tvNtMHH1xtptoIAy4iyEtOeIFZzn12Iwkv+/HPzPd33G1HEvKHAAIIIIAAAuUJFL0AdSUJD/zIvmEQm77oFpe9Pu0DbqEf2qXl/R/epfR5ffK+wV1+ICXuBm3e8muX077xI5RfsP0kC9QHXqRzKOtGkXY76/tj83LE1nu9HbfmwR2/3M4t9ABK4/4R92a/2voa/mO/CNWb1O5Dx8NW7b9Su9A+wFN0//DLoV9vXL1p/UPtSeqvJM/Y+dKDqFL/W6+XdLsM3fgtWr7Y5SXPcL+bvZ9JHu0qn7Qe7f5M/9+ayFpS/TQcj+q/2CfHd+07kGxO9P9xx2npuBCqv4bjZZKw4cFI7/vc5yf0/0n7TkPkHeDoc0p7WWy90f+nj0d+JMC8x8W81ynSfk7/n37zm9uvpPMHrv9jW3Lz9EWvb+j/6f+zzgO191Gk/b3c1l7PTX8dTv/fijrg+j90/5/r/1GB4H3GhuubbK+ibVZ7/Gq8nk7Xq9TOQ9fjRc87tds/tvr/UfvmV1ah+4b18/W4/S/2Ok26LuD6X9tyi6Wj/49r58W0G5eWjovS/W7u/5dTI/z+n+1I/69rX1z/c/3P9b9uXzFO3vVL+Hwvrl2F7v+f/lr7fPqb3vBnZtrfP85MJ09yEUJsCb71L1810/0PXGCnBy0y0xkz7HPue+29t5nu2DZsph/82N+Z6d33/dFMwxE9ak8AmXR7TEu2ONm89UlEkPVb7PfzZtrpzOm2nP7vLVt32Pl/XJ8+z6/fP06/IU36nSUpTcMkx30pBoSEMHvhewaE9EIts40IIIAAAr0mIN2Q0nrE3nCJTa8tR/jEN25girQ+7Yk0D4TyQKjUlrLmF90v+UFIp+49J1xbiAEheW8xWMJ23xAK15ctjz+/6P6la1310NO1AU7eG2SkcvCDkFa6ebqyzzfo/3kgJOs4J/W72tYsHRekfKRyxP4QyAMhSb/WcPzW9S/a44VUr7Hz9f2wyznujX/B661khnQWEduuGBCSbocMCNHtEfT/2U6x/UDtKCEcB6X+q93Hw7H1QKjc5un/ZaPdz99qF6yu3wp0XNr+R5tOV8r8qej/dedn+YWl6870+hvbhe7FOW4t/n0E+n9dzdH/0/8nZ85mEjvgnQdC4x7cC50nSnsrL4SQhOLm0//T/zdrMWVdh8X2r3mvO+Nafz11bPmk9Wjd+P2f3/+ltpQ1X7p/IuXJ9b8ktPvZ4G4/WHP9n+v82NfWn3fEnVcyIMQeT2v9SnJDQPqdJbQ3aPux3ZZnQIju0NIbqY587iFmQ19ywtFmuudMO7LK/a19dL35r4v0sWmzjSQyd++9bPok4siUKZNTy91xl40EcsU115upNtJIb6izlQgggAACCJQroH0gJ3Rjo9zSNOZW9MLYzzF8I8qlLOeBqFpu6YHcteK0+oaY9oIstv7Kyte/YVF74VXQq5x60T4YkvdGXqjey3IL1VewPSUL5L1gjG4fkQ+8a7dHexzwB3ho30CX48I4lkaVvtXtRFWIiESxbtrt09a3VNR6e4h7U6W2XUrp3HypHYZctF7+cUe9Xm+HCf1QLt1gluoh7/zY9iWtJ+hcc7A5SPUlrYf+v7kQ/b+2BenS0f9bJ/p/XXspK1Xs8Vnbn9H/p9uz9vw9tp/WXw+lW0xZ/aPUDmPbl5hfkqAh4hD9v0SXOr5q26P2/JjrfxV/MBH9P/1/sRaUb+nY4zP9f7Yz1/8Bl8D94HytNfyiktjzJu36pd8bWvWiEu1+pt2O2v2MwPlTdD7e+Rb9f6xgOj39P/1/sRaUb2n6/+b9lpsrXS/T/9P/m/uX3u+n+fZK/QvZYvMv67yC+/+x8s3T0//T/5fbonS5Va3//7NXv9wU/Mw3v85MpybPmbsIIJd+9xvm+y2bbaiOPfeaa6Zz9ppnprPmPCt1hXPhl5eZz/c9+IiZzp1pj4B7Tk9fwLgIII9vsXdG95llI388vdNm9/iTu+z3yfJ7eMv72rUIIY/b5bT3W7XP87n1aZ/r2y0dA0L8yurlzwwI6eXaZ9sRQAABBMaKgPYEUnviWLZLWTdI6ifA9n+NP8TUUiTztafgzbeYH4SyfbghVO6ewg0h68kDoeW2Kym3qt0Q8svLgBBd/+T2HAaEZJ8fSPtBaD79P/3/7v1SOWeVjab0//T/eY9RRZaj/9cd33ggJF8ri21f0loYECoJCeeLyeyi/RjX/8XqoeE6J/QijZLqS1va2AgI2vNm7X1Arv+1NVVOutjjs/aBMm19S1vB9T/X/7sL0P9Lewz9v7le905Q3H0x7fHOT6c97uWtHa7/uf7P23aKLKfdH9w6tPsB/X+6PWuvt2IHNvJCiMSZF0KoDgPa/VfKjOt/SShuPv0//X9ciyknddX6fwaEpI/Q4u8OgeucxvuLtQ6SASHl7DpjMxc3QGTxooPMBs6cOcNM5+09J7XBLmKIiyDywEOrzXwXGcR9PzaV2CoEEEAAAQQ6I6C9UeSHouvL+caKhhviBd/0VfQGYfyDmbaepBPq2o3OnNun/eEg7w/dDSf2yRfaG4ytaq2xF5J5y1G43XgVXFao+djt8QduaR/g1rbf2PKUlb7V+2Xecvo3Pstqr6XlU2uXuuNUWev1Vqt+c4V0nIy90Vx4v/aO12XnF2p3RdeTuz1rb/x4/UPedhNbn9J25S1H3nYXu77Y9KFySW+KcyPe8h7Xy64Xqd7C+4HuuJU3/7qv3eLYfts/zuU/Dy62fn876u8Kap5v3vZY1Dt2efr/WDGbPvZ6oOz2kPc4UtYPl0X70cbr02L7qda3aLnztZbwA25+fmWd9+ZtH63qL2LbnbY+pfMLqb64/peEis0vur9JD5RLpSu6fvr/tHDsfinVT/B4U9J1UMPxNed9wtBxWns9ULZb3uN77HE43B+U218X3U+1vkXXk7s9c/2fXAd6xxNvP8/bn2vrXzoecP2ft4VnL1d0f6P/L7c+pPYv7X9597PYrSjrOki7vXnLR/+f75dMrv+z3cpq93nPD7n+twINEUxrB8Zkfr5mX7tvmHPx2MNUMH3bjuM5n1/xuEdvoCXnj3FyRc8/uP7n+j9rJ+L+f/P9ce6z9jJs7/3rM8z0RccfbaZPbXnSTH/4/e+a6bbhYTOdMGmSmU6cONlMx4+fYKY/+a/fmukDq9eb6cxpdr3z9qzVQKp6/EghbqaLDDJzwEYOkX6PfGLIHmce3Rx3vPHbin/94uYHX/yYJHBrzThOMyDER+ZzXYABIbQGBBBAAAEEqivg/R4UHOjAgBBbh7E3LGLTu5bCAyGt3WeK3pDhB6FW14/NPxSxR/t92aUs68Z4wwU6D4Rk17d3I0Kqz8L7NQNCMonLavf8ICS14OzzDB4I0blpUxU+TngnatKN3Mbjfb4fkkL5MCAk+3xB2x606co6DtL/pwV4ILSc44G2HdcPX+kfsAr8IKRaNf2/iqnhPgP9v85Nm4r+XyvVmXQMCM3nzgMhxfpRHgjlgdDRPS/Y30bep+P+f9xxLK9X3FrqA6Fjr9tr581c/8eSR6Wn/4/iqiWm/6f/j2k5oYhHDfenki+aPBCqWi3X/yomrv+LPe8sInP9LxJ1NAH9fz5++n8GhGhaDgNCNEqkQQABBBBAAAEEekAg9GB73hvzeZfTUjeeyNo7B3nfjK1dr3SDLG8+Dfl6d8yK3oDLW27tQKFQ+cuqD+kGYt72lnc5qZ6l8sZ6+e8bCL0hRrp/FlsuaTuZbwVib1wVdYttt/X9WPdDSWz+oeOL9s05ZbVLaTul9Ujz89Zbt+Rb9Aa582nV9ob8272+Vm9nrR0nB3T6fyse6t/yHq/yLicdB2Lbo1QO+n9JvLPz6f/T/qHryVYfv+n/i+0H9P9xfrHHeW3u9P/ZxxP6/+zzIG071KbTtlPScf2f1Qbo/+OOX3n3o7L3Z/r/uJoo2792X4H7/6mKkJyl6+fgdUfkwB5t65DK23C/VCgH1/9a+c6k4/qf6/9mLS/2eKBtxWXnS/+vlU/O+5Pk0u+ucbnWf090N7y5/5993eufL8U+b5D3vEGqz9j9UioH/b8k3tn59P/d3f8/74jDzAa8+hUvMdMlRyw201+v/IWZbnj00dQBf/v2bebzrXf+0U7vWm2m422Aj775c+1/xvk7bsLkIoVMHG+/2GN6IGGgWd/76C4zZ+dOm6Bo/xN7v6bJ3kaEkCY4zEIAAQSxUp/JAAAgAElEQVQQQAABBCorEDohlC5UQxuUdzktkF9ed0oce0NAu77gdiYzip6Q+/nzQEhaRLrBkre95V1OajdSeUP1HWq/3BCSxDs7nxtC2fsrA0KsS+zxQNuay86XH4S08i2uVx4ISVWE1M7z9uN5l5NaiVRe+n9JsLvm0//T/zdrsbHHA23rLztf+n+tPP1/0/Ze8oOWUjvP24/nXU5qJVJ56f8lwe6aT/9P/0//XxeIPf4V3dvbvT5X3latl/v/3P/fXYD7/0WPEK1dnv6f/p/+n/6/rKMM/T/9P/1/WXtT6/Oh/+/u/p8BIUn9JQ05b8TEZGzKSP+cA08o+5m41u/FrAEBBBBAAAEEEEAgKCA9KBL7w3reH1L85aT1SvNrP+wo39BU1klu7HZITbMhv8AvVlL5Q/lIy0nlK2u+X59521He8tQHItkc3ACO2BsCRcsdep9A0XrS7i9+83Kf62+ysSUMXVhqtz+2PKF6Da1PKoc0X2pHZbVXbfsqy0varqLz85YzdNz036RUuHxJBrX27H328y/aTrTlDbbjQP9VVrny1pd2u/Kmk7ZPW24pnbSe2PLT/8eK2fRlHU/zrb0+sMuN8KL/T0vS/6c9ymqv9P/J/u/3y96BtOj5b+CySRzQWXb/EH0eS/+fSSb167X65vrf9q+R5715+9G8y5V1PM29fu8AQf9P/9+sLZXVXun/6f+zjs9S/1XWeYm2H817XM27nLR92nJL6aT1xJaf6/9YMa7/dxfj/n9c++H+f5xXq1NLx1vt9W/owfmi5Y+9Diq7f9BuP/2/FSgaOVJqj2XXL/1/vj20rOupfGvn/r/v5p+HcP8/LVRWe+X6vzPX/0uO/FPbv+zcbgvwzA4zGTfORgB5ZN0GM123fnOqH9pzwCafu0cSMiTvAcdbbvNWu4et2Zju8bTtI28xIvpHIoTkRWY5BBBAAAEEEECgygIMCLG107IHjoQHUqS2EbqB6T/hIZU/9kaoVK6y55d1gZ23XLUbIF59xV6QFb3ByA9CcTXID0JxXq1OLd1gCK2fASHZ/VDw+JNASsd9qb7z1peUb9H50nFUW24pnbSe2O3gB6FYMZue/j9xCPC1ez/nB6Hm7bis9qo9v5SOY/n2uvKXyltO+n/6/91bo9Qva9uZlE5aT+weQv8fK0b/n9XufUX6/+x2xfV/vv2tVUtJx1uu/yPbMQNCM8G07UxKR//fqiNBXL5lXU/FrbWemvv/aTmu/7n+z7MvScdb+n/6f027kvplbTuT0knr0ZS12X0Laf1S/vz+LwmVM5/+n/4/piWVdb7K/f/k/l+CXx94lP6i6P0vV7duP2dASPZ9V38f2K3/YUBIzAGCtAgggAACCCCAQNUEpIEftRPm5AqlQGi5XJsulS80v1UPYLiNiL0QKfsGU/AGqnfH3r3RMhd+GxbSumjTtaHImauoevna5YJDtrR0Azh0gzf2OOPf4PCXl8qRt51Uvd615dOmk46/nTruxpZfm76ezo+hkrfF6JbTlq/JDSszK28+/v5E/6+rN20qbb1o02nXW3a6qpev7O0NHv+SGXn7rXaVs93rkfpd+v/W1oh2/9Smo//3Hw1rTf3lrY9W7U/0/+XWs7Z+tenKLZ0+t6qXT78lxVLiwPV/sRbUmqW17VKbjv6f/j+mpRZtV9p1ueuM2nlKxS/EtC7adFqnstNVvXxlby/X/3GiXP/HeZWdWrt/atPR/9P/x7TRou1Kuy76f61UuenaVb/llrr83HDg+r/8VlU8R2271KYLlWjvmTaHWdOL9Y/bnrZreGj9LjPdZSfiX2z5tembnL8yIESsFRIggAACCCCAAAIVFpAGXLiia9OVvanSehkQkhbnhlDZLVCXn/bCSpdb96bCIbvu+EGos21a2y616UJbI9VzqxViy69NX0/HgJDROox9zkLrXLR90P8XFcy3fLvqN1/p2rcUDvT/7Wtt+jVp26U2Hf1/sR+8tDWXtz785fLmU7v/4RWY/l9bg83TaetFm66cUsXnUvXyxW9RviVwoP/P13Jau5S2XWrT0f/T/8e02KLtSrsurv+1UuWma1f9llvq8nPDgf6//FZVPEdtu9Smo/+n/49plUXblXZd9P9aqXLTtat+yy11+bnhQP9ffqsqnqO2XWrThUrEgJDidUUOCCCAAAIIIIAAAmNAwA3UcJviRxYJh0S0p+ShSCTSABGfzr9tFfsgh8tPf6HQ/IFZKR9pfn374h7MLcsh3DTT5Wncjrjylr8LFKuX4uXx198QuyFZRbudQuXKd8NX335rR4bkP+n1aUOvxq8vcMPG0w8dR+TjR7vrr7lj/dF1z1fY3vj2rt1ubTppu3QlDLWP2HajT9/8OBjOJ9YltP2x+cSmV7p7GzrSMIRCOr5I5ZKOW3Z+fy02iC23vP/qtk/bD8YeR/K2M6nU9P/0/9ltRNqPpJaVnq9vv/T/cbJS6lD7zv4+vp7yrt9fTjquF02va4+x269PT/9v+l36/8wdRmpH0vx6pnH7Ef0//T/9f7gPk/Y7aX7e/VLqVfXz6f+zrLj+l1pQXD8i5ebm0/9n39+QjiPS/LzHGfp/+n/6f/r/2vE5+U9590O1/Yg2XfP7Q+p+KLCd+uOszUCfnut/rv93bzG6+1Hx+2XcfkT/T/9P/0//H3+c0fa02uORNl139/97TLNH3L0Gx5npxAnNHZ/ZZV02PmXTbRiyn7WRQbS11IJ0owUd6Z9z4AnlnUu2oJRkiQACCCCAAAIIINBeAQaE8IPQaIurXyTEXgiW3V65IcQNIW4IcUPICuh/4IlNzw9CxpcHQjMPNlK7k+bXM43rT/lBiP6f/p/+n/4/tj+PTU//T/+/+xlm+pgj9e/SfPr/vPcF6P/p/+n/6f9j+/PY9PT/9P/0/43nMXH3K/L28uHl6P/p/+n/6f9j+/PY9PT/9P/0//T/fl/jn3/4j0/7YtnPj7T+vKjYeZr+/pXbkuz18ULI2JrmhRBZYp16IQQDQmLbL+kRQAABBBBAAAEEOioQfyGX3CjzYpSGInvE3oiUyiPN9zFrl9fJf0aS66fgBYOXrp5f7AVz9o0A9+Zyf8BE6Pv6I8zSjQZtM5JuSPg1ps3Xv9BP5xN+MDV0w0T7KGvoBkre7dSOdw+VT/t9yDVU7pCvVF+xvtobVFI5m9/4CW29fv9uvv5wPrH7cfZ+V49Y1Hz/qJcjbr2NywWPbGaGfnuzf7hobF02Xf24FHfjKT4mg/ZGqLYc0vE39ANOaP8P7RfS8ULan6TjlNYl6ZeTiqxH5pD2KO2ts+ztCLfv2O2K2z/k9pV209aefj+Sjm9S+0svT/8fu59o+z3pDFR7ntH8+BuKyBT+nv4/S0A6WjWej2e3g/j9WHuem/SLyfWPu56Qz2fijm/0/9L+IZ1nS8df+n//jCEtLvWYdj79f9z5KNf/tpX59z8ar2C0/TLX/+n9OPt6Ve8rnYc1XinGrV97vSz1x1z/7y6kP9/h+t8ef+j/6f8b7yLo9yOu/7OP0NnH5XBvHnt/WroOl/on6b61dD8vdN9Bex5YrF8L3/eSzoOkfl1bD5KvdP5B/y+1gOzrqubtXn/cov+n/9ecOXL9n97jYo/v0v2n9HGU+//a/on+P7v/oP/XHNXif4/JPp/h93/t9Y/mbGf3Mx4pvfY4LB1/23P9P22y3S+nTLTbNS753ejpZ+z6t2yz3+9KIoXI9+l0e7/2KkbSDs3vtzsSEULyArIcAggggAACCCBQFQH9A1jpEkuRQEKX7dKJqlQeaX5ove7JFQaESA/qOsHQDQap5fKDUFoo7kHA8E+ULtfQHsQNod3d9T+QSO3Zn88Dobr2LR3pg0dqoUK664aQfINHOh7HHYf1D2j6x3ltOaQbaaE9T3vDX7u9UvtK5yP9vCVpxD/wL92QTB/P+UFI2z60xw2pPYfOc6R2JS0nPagu9Td5H1ih/6f/z3pcqPmDPtKDWgwIDfUM2ivR2OOalL758YP+P/v4TP8fun6Uztek/ir7+qj+rc1fOs7EnydL9yno/62Q9kFPbTqpHYXag+68XntUle6TcP3v11P2EVDaL+n/6f+tQPP2I7+AQjqvKXqftPl1If2/dNyWrju15wHc/09LFW3X4SN0ej1c/3P9z/V/+Doi+d0m6ccYEBo6r9Ge76Sv6+j/pfu+fvtL98fc/9eeHzY/z6vP5fqf639Nf6g9r/X2V14IlYBIv8drfXX3h/S/w0q/v0q/Y4eOM6H7ZNnbyYAQbf2TDgEEEEAAAQQQQKAtAl5gj9o6awMlahc62gfjdMWuj6h3N0bSl6u6XPSp9D8sa/OUPOJuaIR/aE2vR75RJF2ghG7cSTcEQy5aWe1PcNINSL8c0oMLoXqSfljR/mDe/Aev+o1RqV1J7SV9AyJcGzafeqmkdlrOhW49F92NUHejvvGBNW15dB6NTtr2apf098vG/TS2vKH267f72O2L26762tL1Fb6RL7VPaT/0tydfeZvFPnE11nxP0x4PQv7ScSzWSXKR9qei7aT5D8fyA0BSe87ePxr3e6k9hOZrf/iOrc9QK9IeT6Xyhvr15v03/T/9f7PjW7h9SOcffv8jnUeGz0DSc3QDEMLnSdrjad7ta74c/X/Rfl3qv7TH01D7DJ1/xR6/pX5Eu39o26vWRUpH/59d09ofJqX6ij2/kdpp9nkB1/95r6Ok45P2fF96YCR936S+13H9n6456XhV1g/vscc9ab/MPorQ/0v7V9HrV/p/K5jXOXY/0PZ37r68u58Y6k9j+8f0cVR+cFTab6XzttDxSDqfiz1/lPqv5ttB/y/5aetZe92ar100LkX/T/+/u4D2+Bo6bkvtMnRcku6fNj+/aTyf1d7npv8vt/+W2o/2ulbbTrj/n11/9P8hgezjvXQeLx1P6P917bC5M/f/pVYb109y/Z/3ulTqp+LqoV6r0nKh/ky6TpauL6TrxbxORc+fpO0KHVel/cTOl7R3y2UUgAghOlZSIYAAAggggAAC1RFgQEjeush7AyT7BJ4fhLghxA2hrH1Re4Ncux9HXOIyIKTAgwrcEErfUgnd+OnUDSHpAQ8eCMk+okj9fujGYfPv6f/p/+n/6f+bvTu/+RlO3HlN/WiU/iGEAaG+su5B8fIe6NSe75b14BAPhOiOu9nX7eF9MrQ/MiAkfV4sDdjQnadKbwJurCdp/8neD3kgVLrODrk292RAqH99UNbxRjproP/Pvk4P+UsPthXtv4sur90/dcdVXgghnQ+GvOn/0zLSfsP1v+48lAdC0y1Fez9Oui+XvR/zQGjeBx25/8/9//D9vHpvEDuAL3Q/JtRP57vOC59FaY839P/0/6N3BfO2F+0D6r6yXa6+Vu36Q/1j7H1lrv9153HS9SXX/6EzsrRv6LohdASX7jdK13vSdUzR6/eiy2uv/6V06bMXxVGEASE6UlIhgAACCCCAAALdKeAGjrjIIbXLxxZFEIlVkk7TGy93pRuW7oTYXWDr0ofKLUcWkC5sit44ixVV3iCQflfMu1ppOXG92hsZ2hsYUoFi55dVPunGgnRDX3GpF7tpqfTJdtZDDiVzpfUGfGqbo/UL7VfSjQHpQUAJRToi5S2/tF9K7SE0X9oeab1lewXK07Df++1Lux3tTRc+/hdtB+Eex86JbedSfn77yVt+7XLadNr9XJlf8Dgj/cCU7Uf/X/L+Rv/vgUrHX6k/Krl+atnR/1uKsv21Pxz4/Tb9f6taerN86f+1/XPedNrl6P/j2r9w3BL74bi1qVOL61XWc+24XNZ5rXYLyipf6Hgu9Tfa9Wu3R7hu4Po/AdLex5Pc6f9T51W19iW5dWY+/b+2f86bTruc8rjH9X8CSv/fmiOGsh2K5yf0/3H1I7mH5kvLSccf6XwsbivC9xOk9sD1f6x0Genp/6X9Q7h+UD9Y7/IpuB/T/9P/GwHpd+q8RwdtfxL6vUn6HUrqb7Trz7t93n7I9T/X/02bkrI98vu/coeUPP3jh5+tbuDlbmthQIiyZkiGAAIIIIAAAgh0pQADQhgQYhuud6EhPpjRouYurle6IPJvHPo3yrXL590+bf7cEEoJMyBEuFHrt2Opnce2X+lGo78+7Q1V5f7ADSFlhUme0nx/NdJxSFvPse0jtpxS/sr8+EFIOM4kQGI/rGyuscnE9SrrWXzgIrZdazekrPJJx/vQA4ja9Wu3J5QuWQ8/CCVAPBCa3VKU7ZH+X7lDSp7SfPp/e7kbOt8NHXdDrdvmE//GyNjr0th6lZqTcL4v9sNS/jnni+vVOpR9XqvdnrLKR/+vFbfpJPfQfGm50PGyVedfscelUDuP09MPbC3qFSgX/b+ywiR/aT79P/1/1vHSazdiP6xsrrHJxPVq2zf9fyx9XHqu/7PPO7TtM6RN/59yZUCo11Ck9iXNp/+n/6f/Z0BIXG/fmJr+n/5f04aU/RHX/xrMiPtcoewYEKKEJhkCCCCAAAIIINAbAsrT9RpGeABJ2suPOBKrKZWrcX6rHvCLLXnohluLyif+gNKh8ku/lzcUy7/BkCSQxuv4v/sU3dyGB4Zdhl79SeVyi0WWL/jGobLrWcxP2AODyweWE9YX/6b9ohUtHWGE9hd6ACV0vS15Bx9Ul8rR3KHBNXq/DKxfKm/R6gksH9ye0GG3ReXIna3ufkw4e6kd5S5YzgXLLk/Z+eXcLHGxqpSz5eVQHid9sOjjDP3/KCH9f4vOk0Pnm9Kb4vKe5/jnf9J+KvWn0vKx/aX03Il6f855fBAPsNkJ6P8Tl8jripzc8mI522Uw47Lzk7cgX4qqlLPl5ci5f9P/23YVuZ/S/9P/24ajvN8kHL24/s93eG/ZUlz/N6dteX9WUs1WpZwtLwf9f9TxuOD+Tf9P/x/V3uj/QzcKSjrQl5xNweND7bQw8rqi5K2oZ1d2/1N2fq3a8KqUs+XloP+POh4X3L/p/+n/o9ob/f8Y7f9zHneD7UEaIO8WbOhQRr8Y6Z9z4AmRe+boCiMXaVVnTb4IIIAAAggggAAC4mmi9syNASGxjansE3tv/dwQiq0Q5Q+SoQuoZPGQOzeEPCDBK5nNgBAHkW9ATahR80BIuYeHwrkVPD7wg1DhGigng5b3u8pitrwcOc9feCDUVmDkD7f8IKS9ElHuH0WvfIL7l9BPu/VqB17U1kP/36xmGRBSO2EuugOUs3zZ/U/Z+ZWzlY25VKWcLS8H/b+tfK9f4vo/cH6Tr//i+p/rf82hmv6f/l/TTlqepuX9rnILWl4O+n/6/4y2mPe6OHA/hP6f/l9zxKP/p//XtJOWp2l5v6vcgpaXg/6f/p/+v/F3HO7/29+3ch4flIc3P1nv9P9lu7Z9QEjOGmYxBBBAAAEEEEAAgVIE/IEb/ckXI4HQHaGBHqUURhHoruh6wo/vSw+Y5Tzxlh54jL1Ro31QOG++kQ8mBusjdv0uI+kBirLKFyp4q8sdamba7dL65E2Xd/tD9Rf7g5BfL7Feebe7aHsoe71FD3Rl16NUL9r2G7td0nbkddcuJ5VXOr5Ly0vzpe2Xjpva+aH6lepV6xhycuuV1iM5FZ0fe5zRrk9qH9r69etRcsubb1n1ELt+qZ3mzU9bT9L6pXxi9wM/P6177Hqk+7tS+5S2Wzpu0P9bIe2Aj1jvoucteZfXXocU3R5pv8+7P2iXk8pf9v4j7U9F66vocUZbPsmlXe1Hqr9Qvyr1r7H5hvZ/qX2HvKXy5c1X2z6k7Y9dv9T/5s1PKqe2PUv5aI8nRc8zY9dD/29rTjoe5T2uxrbbqrdjaXtCx8uyjhux+2Pe/UG7XOx+X3b9avOT0knzY92ldiLtb/T/2cclbXuj/5ek8s2P3U/y7gd+6bTHT+1xK2+6vNsfcuD638pw/d+a/bHsdh7b/qV+Lt9W15fSlkdKJ82n/8+uKe1xWXv9ULS9aM+bWlXf2vYcu/7YflRbjth0rS431/9JjSifp4n1ytsfaPff2HRF9/fY9ivtR9r8pP1AexzSri/WVdpOyV3bTqTyS+uRlpfmS/UgOUj5B99Ep92wWrrR/+SJECKWkAQIIIAAAggggAACLRRgQIh/Rh3CVl7A+otL59XaE/5QMUM3rPLmW/QGWNELlLIu1PLuM7Fu0vaG6t8vn9Zd65M3Xd7tDzkE81PuT9wQyteSy67H0HHNfa9tv7FbI21H2e1cWl/s8T12e6X8Q/lJ5Zbml71ebb/X6vaj9Y89zsTmywOhOjHt/qzLLT5V7H5C/582lvb7Wmr6//jGGbFE3nYstefY65CIImcmlbZDe7zQtktpfVI/Gbu85KPNT0onzZe2K/a8Q/L2+9tWnT9KvqH2XNZ5idZBu/1at1bVd6yndruk407s9mjLmbfda5fj+t9KNbQD+v+8TVS1XNH9RVpeexxSFbZJIm05yhr4JK1P2u9jl5d8tPlJ6aT50nbR/0s1lT2f/j/OTXs+H5erPnXsfhJ73lL0PpPWJ2+6vNsfcgjmR/+vb5Q5UpZdj6H+oazrpNj+Je9+Jw3MiXWTju85qi61iLY8UjppPv1/dk3FXr9KjkXbi/a8u1X1rW3PseuP3Z+15YhN1+py0/8nNUL/H9s0o9LnbcfSfujPp/+3AtJ5RVTl7ZZYW4/adA3l0D5AJl5QjCYY6X/WwS8KHeLyErAcAggggAACCCCAQBsFpAghtfP/5DwyEEik8bTTS99w/izM1xNIV9yhOzL6NTRNGXvDJ/eJfKAU4nl7i7azpGx7Ppuy2k+72kGowqR2HbudRRuGtNvHXtBLN2a12yelkxx9l9j0RV2l5aXtk5av6vyqO0tura6Xsn2ij2fJAiPJjh27f0t+2uOeO0GqhUz2Fiy7HqKdcm5o2fWbsxhjbrHY9hBb3+2qN2k9sdtZtKLp/4sK5lu+3fWcr5TxS0ntOz7HYkvElqfV9RJbHmnrY49zLiQA/b+VbVd9SPXI/OYCsftl7H5RdjvQngdL16mtLhf9f2f2vNj23JlSxq+11e01tkSx5Wl1vcSWR9re2OMc/X9atF31IdUj8+n/s85Hy26f2vMNly72/qArr7+8+6w9vkrpYl1i07d6f5S2r9Xrb1X+VXeWtrvV9VK2D/2/VKPNr/PbVR+6UpJKe90s1VvsfiHlV1bNSOtp9fGH/r+smiyWT7vruVhp9UtL7VufUzkpY8sTmz444qWW0eh/GBBSTm2SCwIIIIAAAggg0DkBBoQUtI+9AIo+MRfKF3uDIO/mll3uvOUYa8uV1X7a1Q60N7a0N2iKvpFHW57QDzrads0PQro9L7Y963LtfCptO2lXSWPL0+p6iS2P5BR9PEsW4IFQK9uu+pDqkfnNBWL3y9j9oux2oO1v6f+tQFlvss7r3u79L7Y9t7t8edfXrv1IW77Y8rS6XmLLI21n7HGOB0LTou2qD6kemU//n3U+Wnb71J5vuHQ8ENqaPbPV/UxrSi3n2ur2Kpeg2PG91fVStg/9v65FRDvpsm1IVXb95izGmFssdr+Mre921Zu0ntjtLFrRofXR/xeVLfd8trWlKS93qX2XtyZdTrHlafX+F1seaStjj3Nc/xc7P8xbH9JyzC/3eBm7X5S9X4a2RlpPq48/XP9XY09rdz23a6ul9t2ucrj1xJYnNr12QMi8Q052WbebgPV1scC4CVOS0o/r4q2g6AgggAACCHSXgBv4IZfaP73LfmJ7JHkTdj3fwBmnW3yXl697g3btjdpJyZJkDaXwiuEW+8A7X2sW3G+fveRN66IUI8kNrv6GJ8q6aCMoarRAu+vdrc8VlPYWXWUsYJ4/t0dsuf1E35nAdwwJ6NtJZza6KuWrSjk6Uwu9u9Z21zv9f++2tTK3XN9u6f/LdO+2vPTtpDNbVpXyVaUcnamF3l1ru+ud/r9321qZW65vt/T/Zbp3W176dtKZLatK+apSjs7UQu+utd31Tv/fu22tzC3Xt1v6/zLduy0vfTvpzJZVpXxVKUdnaqF319rueqf/7922VuaW69st/X+Z7h3Ma7QiR/oZENLBKujiVTMgpIsrj6IjgAACCHStAANCuqvq9BdY3bVdlLa5QLvrnRtCtMgyBPTtlhtCZXh3ax76dtKZLaxK+apSjs7UQu+utd31Tv/fu22tzC3Xt1v6/zLduy0vfTvpzJZVpXxVKUdnaqF319rueqf/7922VuaW69st/X+Z7t2Wl76ddGbLqlK+qpSjM7XQu2ttd73T//duWytzy/Xtlv6/TPduy0vfTjqzZVUpX1XK0Zla6N21trve6f97t62VueX6dkv/X6Z7B/NKBoQsPpUIIR2shW5d9fhJ003Rx42b0K2bQLkRQAABBBAYcwIuAEfsyZ0L9OFAXOSOeKDYNds1LL/o42Z6zJJD41fJEggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgj0ngADQnqvzsvbYgaElGdJTggggAACCJQlwICQsiTJBwEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAIFKCzAgpNLVU/HCMSCk4hVE8RBAAAEEekogNBDERf4IRfyQBpD0JxmMBEOG+KEDlRFCvGS1CCFHESGkpxouG4sAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIKAUGP2Bzf26p1yEZAgggAACCCCAAAIIIDDWBRgQMtZruJXbx4CQVuqSNwIIIIAAAnECDAiJ8yI1AggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAl0uwICQLq/AjhafASEd5WflCCCAAAIIZAr4EUFCEUKKRg4J8wsRQvyAIklGtQghS4gQQtNGAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAQCHAgBAFEkkCApmDCmEAACAASURBVAwIoWkggAACCCBQPQEGhFSvTigRAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAi0QYEBIC1B7JksGhPRMVbOhCCCAAAJjQKA/GSkyMiJE8Mi9rZH5unIk5SJCSG54FkQAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEOhNAQaE9Ga9l7PVDAgpx5FcEEAAAQQQaIcAA0Laocw6EEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEChBYPSRrv4S8iELBBBAAAEEEEAAAQQQGOsCDAgZ6zXcyu1jQEgrdckbAQQQQACBfAJJwI2+lgUCaSiWiwzi7kgHIoWEkiWLESEkX32zFAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAQM8KMCCkZ6u+hA1nQEgJiGSBAAIIIIBAyQIMCCkZlOwQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQqKYAA0KqWS/dUSoGhHRHPVFKBBBAAAEEsgSEeB4KtEAkkNCStZAl6TW7XFYs+4RZ8pglhyrWTRIEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEel6AASE93wQKADAgpAAeiyKAAAIIINBhAQaEdLgCWD0CCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACxQQYEFLMr7eXZkBIb9c/W48AAggg0F0C/ckIkFqgjkDx+72E9TggsRFB3Arccs2HoCwnQkh3NShKiwACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggg0GkBBoR0uga6ef0MCOnm2qPsCCCAAAK9JsCAkF6rcbYXAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEBgjAswIGSMV3BLN48BIS3lJXMEEEAAAQSaCvgDPFz8jb5kxogXCiQ0IKR53I7RIigjgzQEAmm+nJ98+UWfMNt7zFGHUvMIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIICALMCBENiJFSIABIbQNBBBAAAEEOifAgJDO2bNmBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBCogwICQClRC1xaBASFdW3UUHAEEEEAAgQaB0iOFBCKLhOKGrCBCCK0SAQQQSKIy1WI+IYIAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAQDMBBoTQPvILMCAkvx1LIoAAAgggUDUBBoRUrUYoDwIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAJNBao5IGTipGmm1DP3XmSmg7P2N9MpA3N6oj63Da0327nliYfNdGjjH810eGhDpbafASGVqg4KgwACCCDQowL9yUiOkVDojdJcAivwv66NLNEVaCSJJLLiovNMSY856tDSSkxGCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCIxhAQaEVLFyGRBSxVqhTAgggAACCFRTgAEh1awXSoUAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIBAiwWqMSBkzyQSyP6LX2a2d8+5NjKIixTSYoSuyX7jurtMWdfce4OZrr3PTjv1R4SQTsmzXgQQQAABBPr6aoE4AhjlDRTxIn0EI4K4gngJAgWpp7L/W3HRJ8z0mKMWU70IIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIICALMCBENqpOCgaEVKcuKAkCCCCAAAKdFmBASKdrgPUjgAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggg0FGBzgwIcZE/Djz8z8zWP3vxSR1V6NaVr00ihdy/6kdmE4aHNrR1U4gQ0lZuVoYAAggggEBTAT8QhzRgRM8phgSxWY346bLXMNKXHXFkxbLzzALHHHWovmikRAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQKB3BRgQ0s11z4CQbq49yo4AAggggEC5AgwIKdeT3BBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBCouEB7B4RMHZhjPJ574nvMdHDW/iqfbUPrTbrhZKpaqIsTTZg0LfF5tmornMt/X/2FxKk9kUKIEKKqHhIhgAACCCDQZQKBSB/uaxd6xG1VLXnyH39kSmDra5FCksWIENJlzYTiIoAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIdFqAASGdroGs9TMgpIq1QpkQQAABBBDoFQEGhPRKTbOdCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACXS3QngEhLjLI807+30bLffbpnt6x1Xz18B0/NdM1995gptuG2hPxompVOSWJqLLn3EWmaAcd/r+a+u1M/H59+SdNuuEWuxEhpGothvIggAACCPSigDIgRwONC/TROPzDDwXipWhYIDCAJFljY27JN95iy5d9wixx7FGLe7Ea2WYEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEYgUYEBIr1s70DAhppzbrQgABBBBAoDsFGBDSnfVGqRFAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBAoKNCeASGHHXe2Kee8+cdllnfLEw+b71dd91Uz7dWIIFJlugEiLlLIPgHP4aH1JqubL/+UmbrIK1L+sfOJEBIrRnoEEEAAAQTKE+hPRoKMjKRDbbjIH33C/OZxPUbLKaRoCP1hvwjHF8mODOKWWL7sPLM8EULKayPkhAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggMKYFGBDSTdXLgJBuqi3KigACCCCAQGsFGBDSWl9yRwABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQKDiAq0dEDLvYBsR5LClNkKI/+cig9xy9RfMrJ07tlbcq1rFW/j8PzcFevbil2UW7P5bf2S+v3/Vj1tS8MpFCBl9Jbr8uvOWWJApAggggAACnRIIDQwJlScJHNLnBRbpc5FFRho60/ock6ff1yazXaQSL3VtgYbva/nY/xAhpFMtiPUigAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgh0qQADQrq04kyxGRDSzbVH2RFAAAEEEChHgAEh5TiSCwIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAJdJtDaASHHnXah8Zg6MCflMjy03nx2kUG2DW3IdNs+8zDz/fC8E810R/K5/mpq7xXV/quu/VdYi/NdMVy+/iuwk89iPqFy6fIbv936TH3sRjOd9vivmrarJSefa+bvOXdRKp2LuHLDZR823z9dcgSWykUI6bK9j+IigAACCCDQSgFpoEgokkffbrFCmpav4XwondpFGulP8qtFHmmI5kWEkFa2A/JGAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBAYswIMCElVbe0BRQaEaJo8A0I0SqRBAAEEEECgMwIMCOmMO2tFAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAoE0CrRkQsufeNlLFklfYyBX+3x9uuNh8tfbeGzLnbznwjeb7oQPfYOcHI3K4iB0uG28gR8MAjyRdLT8pYkdovnZ9eSOFpLdr+rqfmRXOePDSTC8XGcRFCvET3X/rj8xX96/6cantquoDQiZPmmi2d+LECWY6Yfy4UrefzBBAAAEEOivguvNndu0yBdm2fYeZ7tz5TGcL1ua1N0b6KFoAd/6izNk7T2tcuiEkSHJ+lz6fWr7sPPPFsUctLroBLI8AAggggAACCCCAAAIIIIAAAggg0CqB0Vs97rZRq9ZBvggggAACCCCAAAIIIIAAAggggAACCCCAAAJaAQaEpKXcQAwpQohbShqAwoAQbUtsRToGhLRClTwRQACB6ggwIMTWhXLYRkTFMSAkAoukCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCHRKoDUDQg477myzQfPmH5fasOGh9ebzDZd9OHODt8470Xy/+dD32PmhyCB5I4a4tSbL1x+gVA7saIg4IkQo8dZX32jl+rzlp69NIoU89INMPxchxEUMcYk2rrvL/PeWq79QakOraoSQ6dOmmO10A0JK3WgyQwABBBCovMDwNhspZHjb9sqXtZoFDET0kAqbnF8FI4Q0zEivhwghEjDzEUAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEgJMCBklGOkTzlAgwEhqdbDgBAOJwgggAACVRRgQEjRWmFASFFBlkcAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEECgDQKtGRBy9Kl/a8o+OOvZqW1Yc+8N5vPtN1ycuW2PvfD/mO+fmbyXN98fsOE/qBiK1OGycfO9fGqzA/m5gSKhgSANyxcsh7g+m//c3/5vM+3fuTXltP+hJ5nPi17wpkzfa//tbaW2qaoNCHERQVyEkFI3lswQQAABBLpO4Mkh20/u3PlM15W9SIH7kxBoDQHVvEwb03nnQ7WIHvWYaiaLhvMim3EwMoh3OrZbBqkSESGkSK2zLAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAQA8KMCDEVHrDE5PKiCGuxdSWZ0BIJ3ciBoR0Up91I4AAAtUTYEBI8zphQEj12iwlQgABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQCBCoDUDQl70pq+aMkycNC1Vlrtv/p75/Mc7rk19//TAc8zn9c//B6/sfmQPN7vggI1aNoGIIWKkjiQDfyBIQ76R5VVGItnj/m+bjKc9/uuU1+Cs/c3no0/9ZGYbWHnZueb7bUMbItpIOGnVIoTsMWjb2/jx40vZPjJBAAEEEOhuge07njYb8NTWbd29IYHSe3E7+vqTER4j3kDX4PfudCYjtkdTMCn0SEO+tS/Mf+oDUdIRSYgQMiabKRuFAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCDQOgEGhFjb9AOJtc/KARoNy+eNOKJcHwNCsvcIBoS07khBzggggEA3CjAgxNYaA0K6sfVSZgQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQREgdYMCDnpzG9mrvn2lfb7NffdmJq/feZh5vMTR34q+b6kyCChSB/JgI2Z0+y7tU85bJyZHjLXTg+cbYvxxFN2+r4Vw+ntESODhAaYRH7v1uqtb/CRK8ycgdV26v6mDMwx/1162oWZ/mM9QsismYOZ282XCCCAAAK9KfD0zp1mw7cMef34GOFojBBiNywUwCM0MKQ+sNXlWDsBSTJMPjfMtuc1/tlNvVzenIaBr+mKIELIGGmYbAYCCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggEC7BBgQMirNgJB87W38pOlmwXHjJuTLoOSlGBBSMijZIYAAAl0uwICQdAUyIKTLGzTFRwABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQCAtUI0BIcNzX2yKtenQd9viNbzaOm/EELe1dvkXzbcRQD70MjuA4eRDx2c2iIc27DLfv+zLW830weRz/R3YNr8XL7T5/Oo++wby7Tvdq6+V5Q1FMKkV28sv+X7wkZ+Y/w2svjJVfiKEECGEIxwCCCCAQF1gzA8I6bexOEZCIUHUjcGdb/gxR7wM/GTJ7Mb1u/Mgm6A2EKV23uPHFLHpiBCirjASIoAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIDAqwICQrHbAgBDd3kGEEJ0TqRBAAAEEOiPAgBCtOwNCtFKkQwABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQKBCAtUYELI1iRCy+ZC/8Wz8N0gLkTe8N2QfMMtm97U3TjTTlwcigmzaavO94g820sffXr7dTB/a8ExmeRbtbSON/PfHbUSKQ87fbKYPb7SRRWqRRIKRTly22RFA6hFSsucPPHKFyWCQCCGp+pk1kwghFTq4UBQEEECg4wJjfUCIFjgJJFI/PQku6J13+adhtQAi6RmNgUPsN7XToIbAI0QI0dYd6RBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBoIsCAkFEcBoTk20mIEJLPjaUQQAABBNojwIAQ68yAkPa0N9aCAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAQJsFqjEgZDiJELKpFiFEGRmk9urpdCSNly6yETy+9dZJZrr3DPdqasu7brNN/7c/sZFA/v1mGxlkx04X4SOpBi//A2bbfH/6vgEzfU7yeeF5m8znB11EkaQ4b3r+ZPP9QXvZ9X/mJ0959eu/iVsXMWQgiQxChJA0JxFC2nz4YHUIIIBAxQUYEBJbQdmRO+q5JOdTI+58Kfnc7yK4+evzIoXUZmef5y1fdr5JcexRi2MLTnoEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEelGAASGjtc6AkHxtnwgh+dxYCgEEEECgPQIMCIl1ZkBIrBjpEUAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEOigQNUGhLwrbRGIAFJLVJtvvzlqf/um6qveayODzJiSjgyybKWNBPLxH9vIIJu2ujdc+2+2Tj8QOS558/VNHx40yx2x//hUORd+YqP5XIsQksw95U8mmv/9v3fvYaaf+H9DZvqFq7xIIQ3bmWQQ+H5g9RUmweDqq1LlmDIwx3xeetqFmW1q5WXnmu+3DW3InB/7Za8NCJk8cYIhmjd3tplOmmQ/bxkaNtO1jz4RS0h6BBBAAIEWCjAgJBY3MCCk4evkC3eaFQoQ0heKhOaXy6YjQkhsfZEeAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEECgxwUYEGIbAANC8uwIDAhhQEiedsMyCCCAQLsEGBASK82AkFgx0iOAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCDQQYFqDAjZOvdFxmDzIX9jLUIRM2oDN9wDi3Z6wCz7iurffXSymQ5MTkcGueDKHeb7C35ip7UBIMJAEJfuH0+bapZ670ts/v7fwk/YyBAPbkhHHDl8PxtJ5Dcfn5Va5I1f32Q+//B327K3t/Y8pv9gpv08sPpKMyVCSLomZs20EVzK+hucbuv9xccdbqb7JJFBQvm7SCF33fewSXLLqnvKKgr5IIAAAgjkEOiVASFeoI6+/uQLL5BaE0F3vuHnlCziz3Y5JSuon61IA0oC85PzMSKE5GjkLIIAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIINDLAgwIMbUvDMBgQEj2PjLWI4QwIKSXj41sOwIIjAUBBoRoa5EBIVop0iGAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCBQIYGKDQhZ9K7EJh0BpAbW8Kprm+6uv51ipgfOTkcGufF+G7HjxV/cqsw3/ebqD59s873gf9lp6O9P/najmXX3YzttEm+AyTfOsJErznihjTjx6JO2XEs+vd5MH9vyTLp8ge106x94JIkQsuaqVJGmDMwxn5eedmFmUVdedq75ftvQhqbbo505VgeEuIEgp558rKEYHLD1tmaddbvrvkdSRJMnTTSf//TQ5yTpp6XSX37NTVrSnk43e890hJcNG7eoPKZNsZF7Zs2yyz+yxu5Xvf6HS6+3ALZ/VKDqA0LOeMNJpqIeevhRM/3Fr37flooLxAHZ/QTGliM4TiQ70kfDt+58prZCIoS0pYJZCQIIIIAAAggggAACCCBQMYHRq8H0nfuKFZDiIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAALdK8CAEFN3tQEYDAiJacsMCLFaDAiJaTXhtAwIKcfR5cKAkHI9ya07BRgQkl1vDAjpzvZMqRFAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBDwBKoxIGR47otMuTYt+uuM8oUHbJx1zHiT/utvnpRazo3vWPTJp8z3D26wETl2C92RfMyORHLCApvvte9PRywINZ8Xft5GCPntg09nJpk9YL++/dM2gsee08aZ6YrfDJvpW5bZ5RvK517N7UUcGVhtI4MMEiEk5T1rpq6+QvXovn/z615ifZPIINdc91vz+YHkDe7S8kcdvtAkWXL4AjO97Y4HzPRXv7ldWrTQ/MMXH2SWf+5hBzbNx0XQuO6GVSbdKSe9wEzdAIIf/Of1hcqRd+G3nv5Ss+jd968201/fcqcqqwUH7WvSvSrZjn+66Ieq5cZ6oqOXHGI28dglh5opLmO9xtm+LIGqDwg5/4N/YYq96n/uM9MfXV1ORKn+fjvkY6Qh4pi2nXiRPBpDf6Qy8mfXA4L4c4gQoq0B0iGAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCgEGBAiEVKP6DIgBBF0+nr6xurEUIYEMKAEN0eUO1UDAipdv1QuvYIMCAkNABD8mdAiCTEfAQQQAABBBBAAAEEEECgpwRGLxPd6P+e2nA2FgEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBCovEA1BoRsnXuCkdq86F2JWPIgYu3N1v6Difbz3Z+aYqbPmWUjbri/7/7aRuo4+zvbvPxciuzIIAfMsr9q/eKDNtLEPjPT+Yaq8+R/2mRmXXfnjvT6aquz6zvv1TZUyHmn2um2p+338z+yzkwf2+JFMvEig7iBK/UIIVenijRlwEYgWXrahZlFXXnZuXa9QxtCmxL1/VgbELLw4P3M9p943OFmeuNvbESP3ycRPqJw+vr6Tn35MWaRfebONtNvfd/W1/YdO2OzUqV3kT5m72nb702BCBvD27ab+S5SiEs/derk1PeqlZaYiAghJWL29fUxIKRcT3LrToFeHRDiaqsWqSP5wv9cr9VgjI/MineRR/xngUa8Abb1j8LAlOR8b/k3zjfrO/aoxd3Z4Cg1AggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAu0VYEDI7t4MCIlrfQwIae7FgJC49sSAkDgvKTUDQiQh5veCAANCbC274RgMCOmFVs82IoAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAII9JBAxQaELPzrxD47gkdf8gbp1xwx3qRb8TYb0cD/e/1Fw+arH99mI4XU/moRR9w36Ugkd356DzPjoDm6yCAul9P+z2bz38tXuYgkbjPS2zFzmn0U87EvzU0V68Irt5jP5/3Q5tNYXldOO2dwzVVmOrCGCCG7c82aaSNj5P1zAzjmzJphsrjk+9fkzcost8/eNjLIqSfbSCE3/uYPZvr7Ox4slG9oYRchxM2/4tqbVetxEUJc4g0bbXt0f/vtYyPPuIgiCw7a13yelUQiGR62kXFuu/3+puubNsXur3962IGpdPfev9p8duW/O/n860CEE38lrjyvOukFZtZF374itR5XvtVrHzff+9vn5yeVU1reec5PnFz+bjtDy0vOt/3hAZOVi/Di8vXr44mk/lz9HLvkUJP0ny76YWpT3Xbum9SvX59ar6aVzkwEOizQ6wNC4vnT5xt9oRAjLuOG8yo7ozFSSChCSPr75cuIEBJfZyyBAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCDQwwIMCDGVnzzQyICQuF1hrEUIYUCIrX8GhDQfuMKAkLjjBKkR6KQAA0Ji9RkQEitGegQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQ6KFCNASHDc08wBpsWvtOzSEfwcDO/8ZZJ5r9nHD0h027B+UPm+4ee2GXn195g7b2hOvn+xEU2n6vfly/CxFu/aSN7LP9NEiFEWN/tFzzLpJ+/t13vbY/YSCZHffpRVXldZJBBFVfSKwAAIABJREFUIoSk6r9ohJA3v+5Ek9+WIRth5vJrbiq0bw4OTDXLv/l1LzHT399hIzzc+JvbC+UbWjhvhBBpuU9/+AyzytVr15vphidsBJGt27ab6TFLDjHTe+5fY6bfufTaVBFdxIz3veO1STobEcQNrDh88UE2ffIm+p+vvNV8jI0Q8tbTX2qWcxEyXESTqVPt8eLww+x6XKQMN98V1pXjtFOXmq9cRA5/O/9r5Soz/+c32Kn7c9v5ltNPMl+tXmO93PIvWXq4+fz1JILJPUkkFLe8c3bfz9rTRqp5YuOTZuoivji317/6ePO9ixCyKonQ4soxdaod2OIigfgRQvz1uXzd8u6zNtJMCoMPCFREYKwPCGkI4JF84U5D/Pl+tTTOD0TyqH0divSRzrkWIUS9nE1IhJCK7DgUAwEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAoFsEGBAyWlMMCMnXXsdahBAGhNh24A8AYEBIeuALA0LyHS9YCoFOCDAgxKqHhnEwIKQTrZJ1IoAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIFCaQDUGhGzd20YI2bzoHXbLahE2/A21jzT+5iM28sLh+45LJXj6Gftx+jn2jfr1RyCzI4O4+R995RST+lOnTssle2YSIeR7N9vIEg2PXnoRQ1a8a7ZJ9Zoj7Xrd38x3P2L+u3VHEtnEzfCWd5FBBtZck1p+ysAc83npaRdmbsfKy841328b2pBrO/2FxuqAkO07bMSWy/5zZSGnOUmEh9NOtZEc2hUhxEV4uOmWOzPL7yJOuAgQ2ggh1yWRMa7zImPsN8+2u4++78/N9N0f+efUel3kDvfldy79WWr+1Ck2ksUFH7GRSC69/HozjY0Q8v4kAsn5F/6bWd5tn1uZ286FB+1rvgpFzHDl8yN4uEgc7zzjFLP8eZ//tpkOJ5FSpMZyYhIhxHn5Dn7EDn++y9+Vw7l+/svLzSwXicSlc/NnJ+3Qba9rH66+PvTJZVLRmY9A1wp0akDIGW+wkYKkv+fsv7dJsunJp+x0s41wFvp76GEbSewXv/p9ZpJQRJB+L3JI48L2PKk/CdVUi/DhErqAbcnQkn4hw4YIIQ0Fc+dl6fMzIoRILYb5CCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCKQEGBAyysGAkHy7BQNCmrsxIMT6MCDEOjAgJN9xhqUQKCLAgBCrx4CQIq2IZRFAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBCorEA1BoQMJxFCNrkIIckbqN3ERdwY12/fJP3kFwfMdNKENOy2nfbzHu/bbKYNb7iu5Zt+I/V7TrQRCr54+vRcNXXs52zEjVse3JFe3ovs4WZ+86yZ5r9vPTa9vqP/bp35/ncPJfkElh9cfbVJN7CWCCG7g8+aOZir/txCp778GPPfObNmmOn//Y+fm+n2HUnDisx94cH7mSVOPO5wM/2vJLLGXffZSDBl/7kIGMcsOcRk7UfIcOv79S13mP+6CCLaCCGhyBku3699/j3mv36EkPclkTvc9q/6w/2Zm/6Ot9rIG6tut/NjI4S4gSfnX2gjd/h/ocgYLmLH+9/5WrOIFDHDRfJwEWT87ZmWRDyZn0QimTp1ksnXRSZx5QpFCJGcXX3tv4+NzPL1b1+Rub1HJ+3g2CWHmvl+RBRXL9Om2uPfdStvTbWLzEz5EoEuE+jUgJBz3v4aldTMGfY8YNt22+9v224jVIX+7rz3YTPrmutuMVNpoEdovovwMSJEZAsWpHYaZf9TP6vyIrIlGdQChDSsjwghqoZCIgQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQSyBRgQMurCgJB8+8dYixDCgBDbDq649uZUg3ADIKSBCgwIsWwMCMl3PGEpBFohwIAQq+qPw2BASCtaG3kigAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggg0HaBig0IWfj2RMB/w7T9vM8M+47pBz/TPJLHQR9/0qR7ZNMum18g0oZ7p/XBc8abZH/4lI3cMc69ylqoj4c2PGNSLPjYY17KpPyBN2H/5H32zf4vO2xKarmX/f2j5vMv7trW1GFgjY0MMphMXSZTBmy+S0+7MLPkKy8713y/bchGNCn6N9YGhCxKInq8OInocdsdDxiiX/3m9iiqyRNt6JrTTj0hVY+XX3OT+bxlaDgqP21iKdJHKB9pOTcg5MsX/dBkEYo8EhoQoh1Q4iJ83H3/arOe2Aghrz/1eLPc5778/cxNdRFCPv3hM818F8lkQRLJQ4ow4jINRTzxI7Rcl0SE2Tq83SxadoQQtz1+pBFXTrddrzrpBeYrP0KIS+ciyhydRBJx+f7g8pUmyW1JxBZtOyQdAlUS6NSAEK3B+R/8C5N01f/cZ6Y/utr2E3n/apE4lBnUB4Yk50tuOXcaFjofcuc33gprkdn807h6iJBkDdmRRJYvO9/MP/aoxcotIBkCCCCAAAIIIIAAAggggAACCCCAAAIIIIBAU4HR27HK376RRAABBBBAAAEEEEAAAQQQQAABBBDoSgEGhIxWGwNC8jVeBoRkuzEg5J9TMAwIYUBIviMMSyFQXIABIc0NGRBSvI2RAwIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIdFKjKgBD7Zv9NC9+RWGRH2NjXBvDoe+DvBpqanfovQ2b+1f/zdDq/2lLZ+b//pTZixxde3zz/jU/ZN2m/4stPmOnvHvLWU3vxtftPen03n7e3We6IZ09KbcdJf7/OfP5lLUKIm51evhYhZO1PU8sTIWSwlH3p1JcfY/LZZ+5sM/3tqrvN9JZV9zTNf3D6VDP/5BOPMtPZs2Yky9vlbknyKaWQGZlIkT5C65WWKxoh5B1vPcWs+p4k8oeLnOGXx0XeuOmWO8ys2Agh7zzDrudDn1yWuamhSCBTp0w26f/hkzZCkVt+eJsdyOH/uXQu4sYja9abJC5Cyue+YiOUuO/d8icuPdz8d795NpKPH9lDO3DG5XP44oNMPqHIH9p0/vYdfpjN97RXLzXT8y/8dqYDXyLQDQI9NyAkectbQ4Cy6MryIng0BPTIjvDhVjMSiJDm5tcGouxKRyZZ/g0ihERXFQsggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgj0sgADQnavfQaExO0LYy1CiNt6BoTcnGoIDAhJ7xcMCIk7TpAagU4KMCAkrz4DQvLKsRwCCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACbRSoxoCQrXvbCCGbF9o39Pf1uYgYjsJ+njDOTtf/g40EMW1S8ipsT+zrv7Rv9n/v97d6+flvtPYjeNjkB8weZ6ZvW2ojhuwz035+/En7JusvXG0jkGx86pn0moORQWyy58web6Z3f36eV2L78cQLbYSQG+7Z1rTcRAjJ5OubNbOcCCGDAzbShxsYMjgwzXzeMjRspg8+bOtp+w4bGWbGdDv/gGfbyC+TJ01MFdAtd/nVv7L5PGXzKftPivQRWp+0XNEBIccsOcSs2q3nyxf90HzesHGLmbrIHe9IInz84PLrzfexEULc8lf+1A5o+fkNq8x0WhIB5C1veKn5/MQTdr0/+E+7HvfnIpSsXmsjfrhyuPmu/G57/MgZbqDI1799hVnERUSZvadtly5/933eCCEuoskFHznD5OtHKnHb+5H3vdFub+Ls0rnlZ8+y5fIjmbjtO3HpEWb+575sI57wh0A3CvTagBBtHdUidNQiedgTmP4+e1414p+HudMtZeiRxgghrmSh8zA7f/kyIoRo65B0CCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCQDL0b65y0+1X9Cr5DOSWd+M3P521fa79fcd2NqPgNCLAcDQgo1OwaEnPSCFOAV16YjfYR0GRBiZRgQYh0YEFLsOMTS1RJgQEh2fTAgpFrtlNIggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgjEC4wOgMgOLxGfVxcvUbEIIQv+ylo2vIk6HTHk9+cPmGSL9raRO/y/zcM2/REXbDbT1RtdJI/Am6kb3njtpfPeoF1bX+j72pu10/lc8No9zKIfPmVGZrmnv/NB8/3TO73IJfUVmv8NrPmpmQ6utVP3N2Vgjvnv0tMuzMx/5WXnmu+3DW3InB/75fhJ080i48ZNiF20JenLihDiF+6owxearxYevK91TyKG+OnWrLOut6y6J0lnI428+LjDzbTVkUJOXGrX4/6uW2kjZEh/0nJuoMR3L73WZOUie/j5ukgifuQMl84NPHHrGx62kXxu+8MDNt9NT5qpi2ix6g/3S0U38/ebZ9v9MUsONdOt22y+bmCDy8Stx48M4ua7yBmvP3Wp+eq5hx1kpq6cbrtDDn4kFJevW+66JGLJwgNtOwpFKHEDeVwkkRCCK59zdZFIXMQPl89LknbhIpe47XQRVfbbx/o5961JvVyWRFDxI4ioKoVECFREoFcHhNROo6LrwT//8TPw5vcnEUWS8yH/xLoxUkho/LH9nggh0RXGAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAr0twIAQU/8MCMm1GzAgJM3GgJAzDAgDQtKRWhgQkuvwwkIIlCLAgJBYRgaExIqRHgEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAIEOClRlQIh9I//mBW9PLAIRMpKBG198/RST7j0nTm5q9+8320gBZ12yxUvnIo4EIobUIny4xZqXp555dr4HzB5vktx03lwznT09Hdnk1j/uMN+/4FOrve331p9s/0ASGWRwrY3Y4P6IEDLYln1pcPq01Hp2PG3rb/uOnZnrX3Twfub7dkUKaQsCK0EAAQS6QKDqA0LOeftrjOKd9z5sptdcd0uHVQMRPBrGiaS/qEckiRxQ4p1vLf/G+Wb7jz1qcYcdWD0CCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACXSHAgJB0NXkDP2rPNTIgJKs590qEEH/bGRDSFQc3CokAAgj0MSAkthEwICRWjPQIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIdFCgIgNCnpVECFn4V4lF8wgeL14wwaS75v0DKrsPrHjKpPvadVtt+iTSRn1h/43W0gCQQGQRL9/xySuzr/zgXmZVL15kI5v4fx+99Anz1T9etSk9q5Zfen1ECMmu9lkz2xMhRNXoMhK5SCHHPt+++fzBhx810/+6YVXeLFkOAQQQQKCJQNUHhJRdef3JeYd/mhP6vnH9UoQPf4nsASQjDZHW3HLZ50+uvCuIEFJ2kyA/BLpSYPRI4SIPdeUGUGgEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBNonwIAQa82AkDxtrlcjhOSxGl2GASF55VgOAQQQyCfAgBDrxoCQfO2HpRBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBCouEDVBoS8Le0ViJDRnwzgeOAze5j0+8wcp3L+yH8MmXRfvCaJFNKigSDjklfa/usZe5r1/eXS7EgmbvMWfuSPJt1D63cm29E8QsrA2mtNusFk6jZ+ysAc89+lp12Y6bHysnPN99uGNqi8pEQMCJGEsucPDkw1M3bseNpMt+9w9Z4vP5ZCAAEEEMgW6LUBIcXbQXbEj1q+DRHW7By3VP2N/vabxuShCGs2n+VECCleheSAAAIIjCGBe++912zNvffY6RFHHmGmc+fOHUNbyaYggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAKFBBgQkuILDECpP9EYepAx/T0DQgo1ytwLz5o5mHvZdi7IgJB2arMuBBDoZQEGhMTWPgNCYsVIjwACCCDQOgEGhLTOlpwRQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAYMwIVHRAiD8wo/Z8ovuPnb596WQz/dqbp0fVyLduGDbpP3vFU2b6oB+Zw+UWGiDi3oXtvfr6eQdMNEv+y1ttZJDnHWDLF/r79g02YslfXfxYkqR5ZBD3Du6BNUmEkHU/S2VNhJDuGBAS1VhJjAACCCCQW4ABIZbORe5oHO7hx/YIDAhp+Lr5wJGR2nlSUnW1AoSWs98vX3a+mR571OLcdc6CCBQVcJEI3IPoa9euM1kODNqIf/OSyAQuUsHAQHYkwKLl6NTy69bZ7b3yiqsyi3D6G15vPcbYdnfKe6yv954kssfK61dmbupZZ/9l5veuHZ591tvN/KEhe93s/r558TLz3wUL5o91QrYPAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBCQBBgQMirEgBCpnWTPHz/JDsQZN25CvgxKXqpbIoSUvNlkhwACCCAQEGBAiIVhQAi7CAJ6AQaEMCBE31pIKQkwIEQSYj4CCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIFBYoGIDQhac7W2RHzEj/Wbp8f3289Xvn2GmJyywETq0f2s37zJJf3r7djP9zo1bzfQXd9nPLiJHPT+7voHJ9tHK4+ZPMtMPnmzXv3Sh/TxhnHv0MrskDyURSV7yhTUmwcMbnk5W5785Ox0Rxb1we3CtjRAyQISQFDADQrQtn3QIIIBAbwgwIKR5PfcnpytewLOM8x93WpQdUaR+9uLO2/z1No8M4s5vln+DCCG9sWdWYytdxIFLL73MFOjSFT8wUz8SgVTapccvNUnOPutMM53f5RELbv3drWY7zjnnA5mbvmLF98z3c+fNlWiY3yMCo0f40NXvlVfaSDOf++yFmRq/vP66zO+l5c5K9rdQhJEeoWczEUAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQACBUQEGhIwqMCAk395AhJB8biyFAAIIINAeAQaENHdmQEh72iFrqaYAA0Ky64UBIdVsr91aKmlgBwNCurVmKTcCCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIVEigGgNChp9l36y7acFZno0fISSZXXuVtZ1/wOzxZnr9uXuY6dwZ4woZ70pW+8cndtp8ks8u8MezZ08olP8r/3GtWf5nt9uIJH0Nr+b237CdjhQysPZnZrFBIoSk6oEIIYWaJQsjgAACY06AASHaKvUjeLh3vWdH/BhJToxCb4R38935U2PENf90z66HCCHa+iJdEYHrr19pFncRC2Ijgkjrfv3przdJzj7bRgwZGBiQFqnUfAaEVKo6ur4weQeErFu3zu5HZ73dTN1+6vaniy9ZZr6fO5dINV3fSNgABBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQyCVw6S82m+Wu/u2QmT65dVeufFgIgU4InH6Cfc732MVTzXS/vSZ2ohjR6/zV7cOp/e72h7ZF58ECCCCgE+jW44Ru61qSigEhWawMCNE1NiKE6JxIhQACCCDQGQEGhGjdGRCilSJd9wswIKR5HTIgpPvbeJW2gAEhVaoNyoIAAggggAACCCCAAAIIIIAAAggggAACCCCAAAJjSYABIWOpNntvW7r1QW8GhPReW2WLOyfQrceJzomZVzeP9M9bfKr/JGChMp105jczl799pf1+zX03puYPP+s483nT/LOT79MRMeqJAxFDkldQL3m2HSl45ftmmOnMacUihRRCyFj4g9/bYL796rWb0ttZ02++fe4N2/UIIT9PrWXKwBzzeelpF2YWfeVl55rvtw3ZchT9Y0BIUUGWRwABBBBopUCvDwjx4nxkULsTED/Wh3da6CUb8SKb1ddjEzbk1hAJLV0Ul9+Kb5xvZhx71OJWNgvyLigwvNNG0Lt763Yzfe7gNFvv/aGYMQVXWNLil1zybyanSy7+lirH+fPnm3R+hI97773XfC9FFpm/wC7/la98KTMfVSE6kIgBIR1AH8OrzDsgxJG4SCG/+92t5qsFyX7l9s8xTMemIYAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIpAfOUZ19f359f8LD5/uHH7e+2b3vlTDPdo2LPSVJ9CGQJbE4i2VzzGxvZ5uH1O80zJivO2898rmqkkC9dtsHsf1/6QTnP3dI6EEBAL+COC5dW/Dih36KWpWRASMtoMzJmQEhrtWfNHGztCsgdAQQQQKCrBBgQYqsrPOqXASFd1aArUlgGhDAgZLQpzp03tyItkmJUWYABIVWuHcqGAAIIIIAAAggggAACCCCAAAIIIIAAAggggAAC3STAgJBuqi3KGhJgQAhtAwEEYgUYEKIWq8aAkK1JhJDN889KSu6/mTouYshz9x1v8vnB39gRsM+ZbT+3+29TMqLx3OV2ZOC3Vj5pi+BHBHFfNLxBO3u7iRCSXZMzBuwbqidM6Ex9t7t9sT4EEEAAgeYC23c8bRI8tXVbT1K5gA3+6UXo++DQkYYRJfaL2nCSJMN65JB0pJD6+gNDU5IEy4kQ0hXt9EM33GLKeesmG/HuG8c930zHJ/W430wbqa8qEUOuv36lKc/HP3ZeU9/TTz/NzD/r7L80Uz8yiL+we9D9kott5BEXycBPt/T4pearz372gq6oXyKEdEU1dU0hiw4I6ZoNpaAIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAQIsFRiMUjP5d+ssn+0Z/or/yMweYzzOmj2vxmskegdYIjD5B8qlvP24yv/2hbSZSyPLz9m/NynLm+oeHtveNPgrxyo8+lDMHFkMAgbIEjl081WS1omLHibK2r4R8GBBSAmIwCwaEtFK3MW8GhLTXm7UhgAACVRdgQIitIQaEVL2ldlf5GBBi64sBIUQI6a49tzOlZUBIZ9xZKwIIIIAAAggggAACCCCAAAIIIIAAAggggAACCIw9AQaEjL067fUtYkBIr7cAth+BOAEGhIheVRkQ8kJT0nqEEFfw5E3SocgZoUgbyeLzZoyOG+zr+/RrBsz0zBfaCBKt/rvxHvsm8rMvfsxM73/MvqG84YnM2ru13Xbqtntg3c9NwsFk6paaMjDH/HfpaRdmbuLKy841328bsiOGi/6NnzTdZDFu3ISiWZWyvIsM4gaGlJIpmSCAAAIIdJ2Ai1SxectTpuy7dgUiU3TdlrW7wN75iT2tqv9552f107KQd/Pvly873+R97FGL272hrE8hcOcGGxHknF/eZKY7dtrz29fMmWWmc+baAQJvOfRgRW7tS/KG099kVhaK4PHRj37YzH/lKa/IVSiX7znv/UDz9XwsWc8rs9czNDRkll+ZRDTxC+MijbjIJS6SxxVXXp1ar5t//PHHme+PPPIIM52b1I+0kbERQtz2X5mU45577jWrcNszf/588/l5Rx5upm47pHJo57v1uIEH99xzX2Y9uHKckLgckbho1yOlc2733Gu3XyrHKa882aSbv8D6aP+ct1ufv9wrvPYVqp9z3vtu2y7mNR/YI/m69nZkUr/HJxFxXHvLOyBE2k5/f/AdrrryqkxSV++ufM7xl9ffYNLfm9SfW/jII2y7dccH7X4Uqk+tZ2j/deW7N9nP3HpcPZS9f2nbJekQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEECgdQKjP8uP/lv6/vtNBIV/+Gv7+86xi9vzHGTrtoycEejre3LrLtO+X3iObd/LPrhPZdr36BMuH/zXdaY8l/7iSaoLAQQqIrDivP0qc5yoCIkrBgNCWlEhDAhphaqcJwNCZCNSIIAAAr0gwICQsmqZASFlSY6FfBgQkl2LDAixN8AYEHKrcWBAyFLjwICQ7OMFA0LGQm/INiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggg0F4BBoS015u1tVeAASHt9WZtCIwFAQaEBGuxIgNC9koihCw4Kylpscgg9Ugc6TdRHzJvvMn/Qy+3EUPe8PypZjplov/Ka12z35m8cXzl3TYiyGcut29O/sWdw8rt8N+UbT+70rgHWvu8SCL1CCHXpQra6xFCHMbUKZPMf6dOmayrSFIhgAACCIwJAddvPjm01WzPM8/sGhPb1fqNECKouNne6ZLzrp23JAXtT85bGgK81c5n/C2yKyBCSOtrusga/vWm/zaLr3jgITPdunmzmT5/5kwzvfC0V5nptIkTi6ymtGWlyARnnXWmWddZZ/9lKet0A0POPuvtJj/34LfL3D0gv+LS72Wuzy3vIpr4iT6aRBhxAy9CESL85dx6P/PZC8ysBUJECm2EkCuvspFJLrn4W1F+rjxf+eqXzHJ5Iy5ceullqfX73lKhXMSQz37OusSW45JL/s0sd+mKH2TWt7R+N99F9DjnHBuxw0V6CC0vtetfXm+vD135QvWzYoVth6EIIS4Sxcc+ep5JF4qw45fTlf+ss+3+5T5/7rPZESxdef18pHb45a/Y9uMi4PjLn3D8iZmE7z3nPeZ7t31XXpEdSSS0Xe9N6umVgUg/4Xqz+8tXv/LPUe3F9xzaYiMJufp165OOL9r2SDoEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBAsD6YWAAAgAElEQVSonsDDjz1tI4R84IG+/v6+vof+fWH1CkmJECggMPrEyBsveNjkcPoJe9jpi2YUyLGcRXcv169ud88Dl5M3uSCAQH6BLyaRsqpwnMi/FS1ZkgEho6wMCMnXuMZPmm4WHDduQr4MWrQUA0JaBEu2CCCAQMUFGBCSt4IYEJJXrpeWY0BI89pmQIhub2BASNqJASHpFxw4HQaEWAkGhOiOK6RCAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQGAsCzAgZCzXLts2KsCAENoBAgjECDAgJKhVjQEh22YdaUq4cdG7vJK6BxSFiCFuqdqrqL0HGwPfJ4Ek+l54kI0o8YID7RuN5z8rebNxPVSH+X7t5mfM9LaHd5jpT//HvoF801b7fe0vVA73ZuyGV2bryts3Yt90PrDOPjgz+CgRQpodCMaNG2dmT5hgI8OMH5cvEkyzdTAPAQQQQKDzAruSiF07nt5pClOPsNX5srWyBH5kjvzrUg4ISVYwkpzP+L1qPZCI/V8t18ZQIbWc7H9sTsuX2TfRH3vU4vyb0oNLPnXzzWar133nu2Y6dJP9vP1JG8Gjb2DQTKYusm+Kmf3qV5vprJefZKZT5szJVHt6p92f7t6w0Uz/+fZ7zPTOP9o3c6y7+y4z3ffAA830Tw4+2EyX7LuPmb7uOfuZ6fRJnRk47CJt+JENWv0mfReRwX+Dv0MORTaQIoS4B8NjI2G49brlL75kmfkqFBFDehD/lae8wiyvjayQ2bh2W39spJDPfc5Gmii6ft/F1YsUQcUtd845HzD/1UZqCTm475cev9T897NJJJdQeilCiIt4I0VuCUUIcZEzznmv3b687c2V/4gjj2jq1O4IIUX3I7dd37zY7kdSe7n+/7P3LuBWVeX+/2ZvLiJ3xAsqXgjT8KRQZKIhap1ke/6lqJh6OppYnS6KiseTaKJZWlaKYZoJonYzQFPrl+CpVEKj1EQr0YJAUgRF5La53/7PfMd8N2uOvcYac64959pzrf1Zz9Mz9pxzjHe84zPGvGDzO79zn5Z611xt7m+t/el5W+nrWmvzpj0EIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAALlE0AQUj47WlYHAQQh1TFPZAmBvBBAEOKcCQQhARoEIeWdqnl1CNHRIAgpb15pBQEIQKDaCCAIae2MIQhpLcG2bI8gpDh9BCFRLghCiq8T5YIgxPBBEBLvao4gJB4nakEAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEINB6AghCWs+QCPkmgCAk3/NDdhDIGwEEIc4ZyUYQMvLc26XHTp33jPT8j2cfkO1/vfLbyP5t3QbI9jtHh1/OTOj0UZe0/u5vVps87PaxHTx0GHGdTNTpxNXO3m9th3n1fv1hOdD13fkRjn32PVy2Pzjqf4vO+NMPmf2bm1alco7mXRCSyiAJAgEIQAACEMg5gQ4djMNGuc4oLZ1GHAKR3ZYfJYmog4j9uFXgGRK2jz4XTZ86UfbjEBJvwa26736p+PoN35By6ybjXNftQx+SstdJJ0lZ36O7lJveWCblu+EX67dv3izbh155hZS9Tz1VyjlLlkr53DYzPyv69DHtQ0O8135unEgaOhgnti2djNPeju6mn85du0r5/Y+dIOXgffeWslI/n9PGJZd8WVIZc/ZZmaSkjgqnNn6iaPwxY86U/ZeMuzhy3Je3Vh40aFCYv4kzZIhxYND26kzicq5w9a/xfQ4h9qAuvPACk0foBKECCxUW3DvNrFPb0UDjaLvJkyeVnA8dl8v5wsdl9qzZEn/WrMeL9hNXMKON589/Uf5U5wflquOxnSMWLlwk9W+//Q4pF4XbdjIuBxmt53MIsePp+mg89RQ5pJxczhYuIVXzfIXrbczZZv1pPJ1fH2c7v0o7hGj/6rSh4/jIR4xDS9xx+NatxlGnFef6T4ln1s5HJU9ODkIAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIJApAQQhmeIleA4IIAjJwSSQAgSqiACCEOdkIQgRNAhCyjqdEYSUhY1GEIAABCAAgVQJIAhJFWfVBEMQUnyqfMIKBCHFBSlKE0HIFEGhL9i7LggIQhCEBGvDJWRCEFI1t1IShQAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQjkngCCkNxPEQm2kgCCkFYCpDkE2hkBBCHOCc9GEPLhT1wnPfboe1Ck5zcXPSPbC56ZVjSjtz9wk+zf0aWvOd7iS9TWF6vLdQZxOoCk4+Cxe3Aaz/7SduvGsc8rt0oXDVvXRDgOeN/HZPvwY84tyve391+U6qmPICRVnASDAAQgAAEItAmB3Q4hDmcQzSp8frJrtXAY0ees8EDz8RbPX+HjXrh/Bg4hsea/6R//kHr/HDFSyi3hhPS//FLZHjDhKik3hvXW//kF89zY1zxfd/u3I6Vc89xzUi785s1S/r9rzPP7mve8V8r6/vtIuWpdk5RNCxZI+aV+vaU8YsABUr7dZI53CZ1C3rP3XrLdp1vUKVB2VuDnEzT4HBjSStHltDBixPHSxY03GWcX/fmELCpQmHavESyoo4Ur37FjPyeHbCcKn5OAj5/2N2HCV+TPxlNHxUI2btzlUs/lXOKaFx8X5TnharPufVzUqePSMB91dNFB+Jwf7MFqe1+/2k7r6/qw+29sNE4eOh67v7gOIUnnxxdXnWAuHPuZWPOtTi7q7OJqVGmHEF3/k283jjQ+4Y9v3T4261dF113aPH3xfOd1rEmjEgQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAArkkgCAkl9NCUikSQBCSIkxCQaAdEEAQ4pxkBCEGjb7aiCAkyfUAQUgSWtSFAAQgAAEI5JMAgpB8zosrKwQhpefLJ2hAELKfAJwx84GiIH38tFFSwYHvxXoEIYYsghDDwbcOfefxCSNOKrq+EYRU1/2ObCEAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEKirQxDCKqh1AghCan2GGR8E0iWAIMTJMxtByHs/dI70eNDgf4/0vH3rRtl+6oFLima0ue8Q2b/6vV8Ij1tCDW1VMWeQ5g7NHy5nkbQdR5q/rB11GOnx1pOSRvcVTxXl98FT/lf299nv8Mjx1Stele0/P/6dVM8sBCGp4iQYBCAAAQhAIBGBDqGSw2G8kShW+KBTvE0Lx7Zotd2OIbYDmh2uuGOa5o9DSLwpmz/yRKm4Y4F5vus9+nQpB919l5RvTLpNyte+bZ77Gnr3kbK+j3H22Pivf0l54CXmebzHaYOlvPId4wiybS9Trlm92iT05jIpzu/XS8rzTvyI2Z/TX2tfJE9rWC6HkCFDzL931KFA+/M5YfhegLfznjnjQdl1++13FB1Suc4MPsGCi59vfC4HiptuMg42s2fNjoRO+mK/nZfPwcLl/BB3faxYvkKq7tffCHDs3+2Tvy+7Zs58qOi4XIIdn1NEufPjEuy01nnCdR7ooMtdh77zwSUIueSSL0vXY84+K9ZUzp//otRTRxm70YwZRlhlz3NWPLOKGwsGlSAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEGgTAghC2gQ7nVaQAIKQCsKmKwjUAAEEIc5JRBBi0LicQRRcVJixG6dnvzOupz8EITVw2WEIEIAABCAAgWwJIAjJlm9eoyMIKT0zCEIMHwQhpdcJgpAon6yEBghCjKBEf60V2GQ1T3m935EXBCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACOISwBmqfAIKQ2p9jRgiBNAkgCHHSzEYQog4V6lhhd//y09Nk1/J/PlM0s6YD/z/Zv/7AU6PHy3UG0Sh2e6ezR3MD80faziDOfKx+Q0FJt3fmyYGey6JfxtXaXbv3kz+PP9N8QbcF72dC3ouK8y73ZMMhpFxytIMABCAAAQhkT6BDqBjZ5bQQUceO0GpEhaxWatrerqXb9kh2tRDE2s834baV1/SpE+XA8GHGsYJflMCbs2bJjre/8CUpt23aJOVhP/mJlF0OPkjKP58wUsoDL75YygETjIPcji1bpdy88m0p//HV66Ts+WET97vbz5Ry0QDzHN7w5htSXvD+I6X89EkjpMz7zycIufGmr8sQRozI1unk1MZPSD9NTU0RZCNGHC/bN970jch+n4OGzxHBnhcfh3KdGVrLz/VCu8vZYuyFnzPrctGiyBDLdcLQIDovOk82vwlXf0V2NTaOKrnk7733fjmuThLK3W406LBBsmvQIFNq/0/PfTpS1ScY8DmETJs2xfQT9hf3fHUJN8aMMdeFS8aZ60nSn8sJReOUuw5954PLISTuvGp+vvPS5RDi4uk6/+NydQmZfOsmbnzqQQACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgkD8COITkb07IKF0CCELS5Uk0CNQ6AQQhzhlGEBJFoy9G6t6MnEGaw2t/LscQsx9BSK1fohgfBCAAAQhAIH0CCELSZ9qWERGExKPvE0K0VtAQL4u6OgQhxUkhCEEQUrgyEITEvaKYeghCkvGiNgQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCECgFgggCKmFWWQMpQggCGF9QAACSQggCHHSykYQot19+BPmy8M9+povFutv+9aN8ueffnW9lJuaVhXNcEeXvWT/+gOMU8j2bgdIubOha1jfFlJoGJfQwjrerP+w6jdXcwhEmrP1CEZaxPEITsJPbXdev0Ra7rl6vpSdm14rud7VGUSdQuzKTz9kvgy92cG5ZPASB3EIKZcc7SAAAQhAAAKVIBD19AgNQwqMz+znEssDpIWBSLR+S38RO541Ro9TyfQpOISUWhX/vOarcrjp0Uel3PLWSinf99j/M9vLl0v5989+XsphfzHPkf+68y5T3m2cAzoePEDK997zBbP/l8ZJbnvddikX7vOfJo2Fy6Q4/1s3SlmvC8gcze3P5/zQWscD38B9jgIXXniBhLhw7GcioXztfI4Idl4+YUy5L+KX60Ch+X3zpm/Jn7NmPR5JeciQIbI9+fZJkf0up4WkTg+ueXPF983T1ROulZC2c4lvffiO+5wefA4hrnn19ZuWo4bdT7n5+tav73xIazy+8zKpQ0hrnW0QhPhWMschAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAArVHAEFI7c0pI4oSQBDCioAABJIQQBDipIUgxKBBEJLkhNK6CELKoUYbCEAAAhCAQKUIIAipFOlK9IMgJB5lBCGGk++FegQhhhOCEMMhLQGFfZYiCFkRQYIgJN51nFoQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQjsJoAghNVQ6wQQhNT6DDM+CKRLAEGIk2e2gpA++x0uPX/wFONQYf/Wv/u67Hrh8W9LuS10Dkl3+ms32pHHj5XB9R90fNFBLn7JfEF68Yu/zAQCgpBMsBIUAhCAAAQg0CoCls9Hglg+hw8NZeq5/EX0SEtDkNLOaziElJ6qV7/wRamw5Y9/lHLzsjelfO+P7pdyZ9h8wQXGeeKYV16W8tWLL5Hy7d89KWXTwcYJZK+jTpay1yfGSLnkum9I+YFfzJRy/n+cJuWIh8x23yOPLJ1gzo6OG3e5ZKTCCE2ve/fu8ueMmQ9IqdtppX/TTTdLqNmzZhcN6XI28DkR+BwR7M6yEoQkzcPO6+qrjbPG03OfjhxK6hByySVflvZjzj6rVVN3auMnpL0KiTSYK74rf22nDjSDDhsku9TxQ+d30cJFsn/u3Gek1P3avq0cQpJyiAt95owHpertt99RtEm5wiTfOkxL4OI7L10OIb7rz2OzfhUXYaSea/351k1ZndEIAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIACBXBBAEJKLaSCJDAkgCMkQLqEhUIMEEIQ4JxVBSDWvdwQh1Tx75A4BCEAAAhDIhgCCkGy4tnVUBCHJZsD3QjaCECMQsn8+IYnvRXzfLCEIQRBSuEYQhPjOmOhxBCHJeFEbAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIFALBBCE1MIsMoZSBBCEsD4gAIEkBBCEOGllKwjRbn3ChU1N70hVdQrZ1LQqyfy2m7qdOu8pYz3qpIulVAcWG4DyfOahr2TKBoeQTPESHAIQgAAEIFAhAsa5o0OdkZLsauH9Eaahlh+h4sTa3N2qhTVIaeeRXWH9GVMnSkfDhw2u0Lirq5tXrrxSEl73ZPgi/2vGaW+/cea58IDLLpXyT0cNlfKgq0z9Dr16S7ngS6bezo4dpVxz5BFSHnLd1VL+4bwLpRxy7xQpl33ru1K+/6wzzf6LjSNDtfxmhQ4d3wwdO+y8zxpjnCXGjUtnXD4ngUGDjGPEtJCvnY+vfVIhhk/YUe6L+K115jh7zLkydNsZQ501LgnXs/JxCkiGDpEqkydPKmtJJuW9aJERcoy98HNF+0vK5d5p90mce+81Dj/68zk9+Na1a159kFzz0th4ijSdcPVVvhBFj/scc8pdh77zoa0dQlzzq5AuCa87Y8LrkA+ub/351o0vPschAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCFQ7gbnP/EmGsGzZcin/o/GjUvbo0aPah0b+EKhDEMIiqHUCCEJqfYYZHwTSJYAgxMkTQUi6Sy3baAhCsuVLdAhAAAIQgED7JIAgpBrmHUFIslnyvTiPIKQ8h5Ckwgd71hCEGCIIQgwHBCFGmOb7IQjxEeI4BCAAAQhAAAIQgAAEIAABCEAAAhCAAAQg0N4JIAhp7yugtsePIKS255fRBR8trav71NfNBzHHnNDLlCN7tjmawrzmLdjU5vmQAAQgYAggCHGuhMoIQjqGzhYfPOV/JZMefQeUXJvLFz0jx98My9Vv/b1druU9uu0l495/0EekHDD4Y1KqMMSGos4gf37823Joc8ZOKziEtMtlyaAhAAEIQCDnBDp0CJ0+Wjh1uBJXB4/Q+sN2CLEMPlwOIrtbhw1ahC3uFIJDSLwFtfR+4yTwl+uMk8o+W7ZL2dCvn5RHz50j5fL7fyTlkm/dLOXQp34n5Y7Nm83+6TOk7HXOGVKu//tCKX99qXEUGTz5NhNn6j1SDjzqKClHfdvEq7bf2LHG0WHRQuPwYP8uHPsZ2XXhhReUNTR9UfvqCddKe9v5QoP6HA2SOlb4ks3KIUSdCNTppHv37r5U5LhPoDPhauNs2Ng4KhJv5owHZfv22+8o2o+Pqys5l/OIy2lh7tynJdQ1V5t5tn9JnTnyJgi5ffL3ZUgzZz4UGZrOr8638vFNum89a/taFYQ0NTXJEFUApds2N73+qBOL8tX6Oh96Hrji4BDiW5EchwAEIAABCEAAAhCAAAQgAAEIQAACEIAABGqdwJRpP5EhbtmyVcoePcz/f3HGaY3hNk4htb4Ganl8CEJqeXYZW0AAQQjrAAIQSEIAQYiTFoKQJAup0nURhFSaOP1BAAIQgAAEqp8AgpDqn8NiI0AQUt68Igi5vCi4cl/ERxCCICTOmYggBEFInHVCHQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACaRFAEJIWSeLkkQCCkDzOCjmlSQBBSJo0iQWB2ieAIMQ5x5URhGj3e3QPv2B80sWyy+cUou22bd0ofza9a6yhavWnTip7dDfOIC4nEHv8lXYG0f5xCKnVlci4IAABCECgGgjYwo/QGKQutjFI8yCLO3e4Hx+j9XdvFY/T7BzSIrFo/elTjPPF8GGDqwF/xXPc+u670ucD+x8g5aCBA00Ob78jxd7nnWv2T54k5T8uuUzKFT/9qZT9zvmUlN0+bhznVv/1L1K+fv/PpKwffISUe1w2TsoF15gX3w877ngpT73lO6a/KvstDJ1BLh1nhBG+L+xfHTpVDBk6pOhIVyxfIftnzX5cSnV6cGFR5xF1AnDV871An9QJIyuHEM1/VOjkMW7cl2WXyylE81BHDpu/Ckwm327Wre1AofXHXmicXmwHFu1XHUZGjDDOivZP49x7r3HaUccFu94ll5jxjDn7rMghn0NI3PnR/MddYtajPR6f04PPaSWpU4kOcv78F+VPPU9sLr550vq+8dlxyxUm+XifMOKkouvA5URT7nk5Y8YDZt32369oiJkzQ4ebycUdblz9uvbrPCRdN0n7oT4EIAABCEAAAhCAAAQgAAEIQAACEIAABCAAgWojMPeZP0rKL/1lQSR1nELadibfWbVKEnjxpZel7NK5s5RDjj5Syh49cG6JM0MIQuJQok41E0AQkp/ZG/6+rpJMz271Ur6xcruULy/dkp8kyaTdE0AQ4lwCCELydHYgCMnTbJALBCAAAQhAIN8EEITke37Szg5BSHlEEYSk6xCis4AgJLoefQIFre0TTCAIMaR8giYfbwQh5V0vaQUBCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAoFoJIAjJ58whCElnXmpJEBK8qaoflEyHDlFqgQCCkPzMIoKQ/MwFmbgJIAhxsqmsIMROY+CQT8qugUefxvotg8Db/5ovrRY8M03K7aGTShmhymqCQ0hZ2GgEAQhAAAIQSJVAXGeQZqcOq/fd+y2HD9vwY7fVRySC7RDSIfxPOLvqwiPNFeyAOISUsxDmXWZe8F94333mObpnLyl3NW2QcsCVV5hywv9KueInxgFk6e13Srl20UIpdzQ0SNn7ZPMl/ffcYBxa3vjTc1LOu+oaKYd9xcQ5btwl5aSbmzZxhSF2wvrl/6b1TXLI5TBit2tsPEV2Tbj6qlgMqsUhRB05lIMKGAYdNkjGqcd1PPpivwtCXAeVuPOnzi6208jTc58uOX9DhhhHGHUqsfPV8Z49xjjxuJxOLhx7gRw/bJDhob+5Tz8jf6ozic+pZsZM4zxh/7JyCNF+brrpZvlz9qzZJdftR0InFnu+F4WOPDo+e73YQWvdIUTHq05C6lAT66JQUEnPk+Z4odONbvuEREn7oz4EIAABCEAAAhCAAAQgAAEIQAACEIAABCAAgWoh8Oxz5r2pZ583peuHU0hlZ3Td+vXS4fSZj0q5ZcvWSAIH7G+ct0efdmplE6vS3mpJEFKlU0DaGRNAEJIx4BLhjzy4ixydesX+Uh64d6eitR9/3rwvcf2P3pZSnUPaLnN6bs8EEIQ4Zx9BSDWfGAhCqnn2yB0CEIAABCCQDgEEIelwrJYoCELKm6m4ggI7OoKQKBEEIQhCghWBIMScFzNmGOGOXidcVycEIeVdt2kFAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQMBHAEGIj1DbHEcQki53BCHp8iRa/gggCGm7OUEQ0nbs6bl8AghCnOzaVhCiae3RvZ/8OfBo4xiy90FDpezUec/yZ70GW65e8aqMavFLv5Ry9Yq/t+kocQhpU/x0DgEIQAAC7ZyAy/FDsfiO78ZnO3c4wDqdPqL1d+2y4jmcRZpbhfWnTzUOFcOHDW7nM1t6+FvXrJEKMz44TMrODQZw/83bpNy5YaOU/U43DnwDrrpSyi7vGSjl5uXLTb2wm4a+veWvNc/+WcpXrzSOFqtWrpTy47/9Pyn3es97amJe1Lli3CXGaUW3Wzs4FUrol/zHnH1WopDV4hBy401fl3F9M3SSiOuYYsOI6wxit1NhzzVXX5vK/I0YcbzEUScXnUfX5M2c+aAcun3yHYnm1648KHQQWbRoUeSQz+kha4cQnc9x48z5oY4fSQer41CnHJczRntxCFF+ep6rQGThwn/KIfs8UseaxlON09DQocbBxiUs8a2bpPNHfQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACeSfgEoIcccRhkvqQo8z/3/jwo7OkVIcKnEIqM7Ov/n2hdPTbJ+aW7PDiL46tTEJV3guCkHQn0HY2OHDvjpEO1m0w/0/6uo2m7LlnvSm7mZJf+gQQhKTP1BdRnUAe/+bBkar3zF4t26+vNO+fDH+feX97zMieUs5bYN5HOfvrb/i6yPXxA/uZ817Pcy1znTTJNRNAEOJcDAhCquk8QRBSTbNFrhCAAAQgAIFsCfgEH77ju7NDEJLtTKUbHUFI63giCDH8yn0RH0EIgpA4ZyCCkOKUEITEWT3UgQAEIAABCEAAAhCAAAQgAAEIQAACEIAABCDgJ4AgxM+oLWsgCEmXPoKQdHkiCEmXZxrREISkQTFZDAQhCEKSrZh81UYQ4pyPfAhCXOn12e9wOdS97wApO3VqH44h27YZJd3mplVSqhPI9q1mf15+OITkZSbIAwIQgAAE2iOBDqr4qDN/tHDm8EKJKQTROGH1XXXmj+buw+O6v0W3TmeRaP/Tp+AQ4p2yggpr/mm+LP/Tf/+4lB03b5Zy4LbtZjvEu7PezFSff/+YlN2Oer+J0rmzFE3/MA4Fyx95RMqd9Q1SHvDFL0j5wa9enSStqqurjguzHntccn/xxRdjjUGdJMaMOVPqqyOIz2HCFbxaHEJmzHjADCG8ANx0482xuLXWQcXmZr9YP3fuM2Y9NzWVnD/beaGxcVSs+bYr6bq5d9r9csjnNKP9Xjj2AsNrvllntnOGz+kha4cQe5zqSDFz5kMl+er8quPKJeMulvpz5z4tpTrK2PHLFSZ9b/IkCaXOGXbcE0acVHReJ1z9Fdkfd95956WeD/v136+sdZS00U2hM8/sWbMjTdVxZtq9U5KGpD4EIAABCEAAAhCAAAQgAAEIQAACEIAABCAAgaoi4BOCfOykEZHxvLPKvHeFU0hlp3nZm8sj3O3e1anlgk+fXdnEqrS3aheEvBE6DcxbsCnWDPTsZv7/6lOGdYtVP24leUu2rq7unK+/Lk3++MqmuuCdi6U/fW8kxIyn1kq9K3/4lvxfot/9gvn/gdQhIW5/1ItPAEFIfFZp1bRfqB911VIJ/fLSLUW7uP78vWX/RUz8HfwAACAASURBVI19pDw7PI/intdp5a1x1LlnzMhesuvIg837L6+vNO/LLFhq3p/R8dhCsNcfMOe95q/jSTtP4mVDAEGIkyuCkGyWXOuiIghpHT9aQwACEIAABNoDAQQh7WGW3WNEEJLO/CMIiXJUocK4cZcXBYwgxGBBEBJdHghCEISkc0UmCgQgAAEIQAACEIAABCAAAQhAAAIQgAAEIACB0gQQhFTHCkEQku48IQhJhyeCkHQ4ZhEFQUgWVEvHRBCCIKTyqy69HhGEOFnmWxCS3hIgUhYEcAjJgioxIQABCEAAAvEIqCBkV+gEoY4dLt8P93GPU4gebg7gcxYxxzWvZiuR5h3W+ML906fiEBJv5qO13l5kHD5++KlzpFwdOoccFU5T/47G6rI+LHeGC2dXWOr2lnCi9jrPxPnIzd8y7RrMF1jay08dJhYuNFx1W194VweH/hVyBKgW7uqksHz5igg35XXYYYMqMpT5ofOG3VnW82avF1036txQK+tF+er4bK7lOuRUZHG0YSfqlKKOJkk5zZplHIy+eZO5Ltu/xsZTZNeEq69qw1HSNQQgAAEIQAACEIAABCAAAQhAAAIQgAAEIACB7AgkFYLYmeAUkt3clIq8eIn54vxLf3lZyi5dzBfcRxx/rJTqFNI22VVPr9UuCFHHjf/54VuxoA/Yp5M4dzxz26Gx6sethCAkLqnK10MQUnnmM649UDodsHcnKYePW1IyiQPDevMmm/Py1geNA9ekh0xZqZ86B936hf7SZc9u9SW7djmA4BBSqRnLph8EIU6uCEKyWXLtIyqCkPYxz4wSAhCAAATySQBBSD7npdJZIQhJlziCkPJ4IgiJCogQhJS3jmq1FYKQWp1ZxgUBCEAAAhCAAAQgAAEIQAACEIAABCAAAQhUigCCkEqRTrcfBCHp8EQQkg5HBCHpcMwiCoKQLKiWjokgBIeQyq+69HpEEOJkiSAkvWXW/iIhCGl/c86IIQABCEAgvwRUIKIZugw53CMwlhIdQqeIZh+QFhYkxR1CdtVpe9NDc7PQWWQXDiGZLh7l+/SPfyz9zL79dinXLlwoZZ+dZn661xvHj12h80fngeYLDqffdpuUA48/LtM8CQ4BCECgvRBQodTZY86VIaujykdGfES2TxhxfGS/clkUOj/9fu4z5no+a3ZJZDNmPlA0TnvhzDghAAEIQAACEIAABCAAAQhAAAIQgAAEIACB6iOwfn2TJK2CgWVvLpftteua5P+p3LJ1q2x36WwcJd5Z9W5kkEcccZhsf+ykEYkGj1NIIlxUzgmBWhOEXHf+PkJ21LBuJQmrI0Fa04AgJC2S6cdBEJI+U1/EeZMHhlXMeyTV4hAy+5sHSb5HHrKHlGd//XUp1Qmk557GMWT44K5Srtu4M3JcueAQ4lsh+T6OIMQ5PwhC8r10850dgpB8zw/ZQQACEIBA+yKAIKR9zbc9WgQh7Xv+GT0EIJA/AghC8jcnZAQBCEAAAhCAAAQgAAEIQAACEIAABCAAAQjkg4BLELJunRGKIAjJxzyRRT4IIAhJZx4QhKTDMYsoCEKyoFo6JoIQHEIqv+rS6xFBiJMlgpD0lln7i4QgpP3NOSOGAAQgAIFaJmA5f+hm6PDRPHLb6UOVKC0cQEyAFk4jNsKw3fSpE+XI8GGDaxlyxce2/J//lD7fXrJEym3btknZf9AgKQ84zHxBiR8EIAABCKRL4PbJ35eAM2c+lG7gMNqECV+RvxpPHZVJfIJCAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEEibwKt/N872v31iblmhy3UGsTt79rkXZNezz78YOdSjR3fZPuO0Ril79OhRVp40gkCaBGpFEHLlD98SB6DvfmE/wTNmZM9EmNZtMF/6v2f2ailnzFkbaX/kwV1k+/Iz95JSHQS0UlaCkEkPrZIuHn/OCNrWbtwh5YC9O5lxntCr6Hhnzlkn+2f+3oxD8x4+eM/IuOx6pwwz16mLGvtE6r2xcpu8m3HFXStkv6teIugVqowgJHvQw99nHDOODdfXZ3X9hO8DTX3MnFeuX89uxnlD281bsFG21ZlD1/EbK7dnOpiXp5r3XNaF55nP2cSVjO0Qcs8s40Sm55Wev6+vNO/XjA/PK9f4lK9e1+zzWOPo9UK5aX7qbDL1iv1l19RZa6Rcv8FcTy4/y1zXNK9TrloacjDXRXVUuqixt2yPCq8Tej1a8NoW2X9reL3Kep4yXQTBOMq8j2SdVw7iIwjJwSRUbQoIQqp26kgcAhCAAAQgUIQAgpBaXBYIQmpxVhkTBCBQDQQQhFTDLJEjBCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgUEkCCEIqSZu+aoUAghAzkwhCjHAEQUjlzuxCoYr9An/lskinJwQhUY4IQrIV7qSzat1REIQ42SAIyXrx1XJ8BCG1PLuMDQIQgAAEqoWAGnRovi2MOmIPxBKENAe0AxSvt2u3F4hp0KKao12Y8AwcQmLPFBUhAAEIQCD/BFasMF9gunfafVLOmvV4q5Lebz/zxawJVxtnkKFDh7QqHo0hAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCFSagEsQ0qVLZ0llxPEfjqS0ZetW2d6yxZTHDBvaqpRfedU4lPzuydIOJdrPMR9qXX+tSrYGGr/0l5dlFM8+P1/Kzp3NPP/HqI9K2a+f+eI5v9IE2rsgZG3TzrrglYLGa14TUPple33BXb+MP+8V41iwbOV2cSL5+bUHyrZ+qT9th5Cv/WilxJ86yzgruPLRfK8/f2+ppw4Es59rknF9ftKbsv+68Lg6MNj5qiAhGG/wjsgfvndoZOHMnLNW4l0ROrHcUkVf0MchJLurYPP5MTm6XtLuMXCokfNg3JK0Q0fizbDO6+vD8/Ce8DyM27kKQtZtNA4b+n7T488bp5+e3RqkPGVYNym13vBLFke2tT/b4eflpZsjqahjh8a149jzpHmoAEyvI2tDx5BRE4xDiLabGXLpuafJe0boPKRJnB06Famziu0wEpdbXuohCHHOBIKQvCzSaswDQUg1zho5QwACEIBArRFAEFJrM8p4IAABCECgFgggCKmFWWQMEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAQJoEEISkSTP/sRCEpDNHCEIQhAQrCUFIOudTkii14BCCIKT4jCMIMYKSZkFMkhMjB3URhDgnAUFIDtZn1aaAIKRqp47EIQABCECghgkEX7sIfg6/D/kaRvS4q2Y8SLuaLUl8DiPR49pOBS3Tp0yUDocPGxyvY2pBAAIQgAAEqoiACkTmz39Rsl60cJGUCxf+MzKK7t27y3b//vtK+ZERH5ESR5AqmmxShQAEIAABCEAAAhCAAAQgAAEIQAACEIAABEoSUMeIZ58zzhH6O+LwQfLnx04+IVWCLmeQfnv1lX5GfMQ4k6iAYcTxx8p2jx7mv9fyS0Zg2ZvGQfvhRx8r2lAdYT439tPJArfT2rUiCPmfH74lMzhmZE8ph79vz6IzeuDeHc3xweb4jKfWyrsP2l6/xD/+rKjDjHJqvHppXfBSxEGhk8ZjNx0scdJyCPnDy5vEieOcG1+XuGef0FPewfjuF43Tu/7WbgiFLFcbZ5P1G3eKs8cztw3cnc+uurr3f878/0XHDe4aOpsMkG0dz/GXGccF5TZzzrrQIcTEUV4X3WKcRv7v+aaIg4gKAorCzslOHEKym4gxJ5jz7dZwfV7/o7dlWx0oWtvz9efvIyHUyWLAuf9obciS7Y88uIscn3rF/uH67ySlOpTc+uAq2Z75+3Ul46ggRCsNH2ecP9SJQ/fbwoPxPzD3N198u3N1BlKnIDuOS7hjOxHZcW1nofF3hfnNiY7f7r9cZ5VMJzdBcAQhTlgIQhKsI6paBBCEsCQgAAEIQAAC+SOAICR/c0JGEIAABCAAAQQhrAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAoYAgpDaXgkIQtKdXwQhZQhC6urqDtqnkwgjEISYF+bz/EMQkt3sIAgpzhZByEoBc8+s1dktvgwjIwhxwkUQkuG6q/nQCEJqfooZIAQgAAEIVAGBDqHFxm6njqRJJ3QICR1BtJUtQOkQepM0G4c0V7D7CbfDYvpUHEKSzhz1IQABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIFCtBLIWhvicQUafdqqgU8eKauWYt7xf/ftCSem3T8wtmZo6hMC/9AzWmiDEt14Dx43gFzhuBK8SnPN148TxxwWbxEHjsW8eJNtHHrJHJJTLAUQdOQ7Yu2M03iubRDCy9KfvjcRRR5Irf/iWcf74gnH+UIeOWx5cJXEmP7RKjt8dOhWoQ4IG03xu+JF58XrarNWR+h8f1j2Sz4KlWyIOIrOfWy/Hr7z7rVDYcpA4kzRO+JfEs50BjrzIOI0EDgpBXtMnGqeRavghCMlulmxBSLkOF64M7Rfzs3YI0TzUUWP8mcYpSM+/nt3qZVsdQ8aE1w/b+UMFIS8v3SL1R121tOgQk/JTB5PBoZOJBlXHI72O+BxCfHlp3BnXHih/anyNaw+mV8jlutDRZeactVJl/F3GuanafghCnDOGIKTaFnOe8kUQkqfZIJfkBILH34QvQSfvhBYQgAAEMieAICRzxHQAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQikTABBSMpAcxIOQUi6E4EgBEGIcTpBEJLumeWPVihUmbdgk79BDmskFTQkHQKCkCgxBCFJV1B59RGEOLkhCClvSdEqIIAghHUAAQhAAAIQaDsCLZw5wh3NzhwxU9sdxxLJ2Zo5rahxrY52V486f7RMwxxXR5PQ4KRu+hQcQmJOGdUgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgUDME0haG4AzStktjy5atksDPZz4i5fr1TZGEjj7qSNkecfyH2zbRKum91gQh+oX6UcO6FZ2Bnns2yP4e3eqLOnqo48eBe3eMtFdHjv/5wQrZ/+DcdRHHjawcQn5ufaHfHpTtKOJzHNF4U2etkVBvvrMtdAg5WBxCRk0wTgb6xf/Lz+wn22eHTgiXh44J488yzgnV8MMhJLtZqlVBiE1MHUPOHmkchvQ8mLdgY3h+vBFpog4hKvTR88eO6+On/d76hX2lqTp1qCPJ6yu3hSHNe1K2k8fM36+T/Rpn3uRDZduXl+Y5b/LAsL25HtpOKK6VNfv59XLoa6GDUXYrMJvICEKcXBGEZLPk2kdUBCHtY54ZJQQgAAEI5JMAgpB8zgtZQQACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAAC8QkgCInPqhpqIghJd5YQhIQOIa9sigg8EITUy0JDEJLu+VYYDYcQP9u2cgixM0MQgiDEv1rbRQ0EIe1imjMaJIKQjMASFgIQgAAEIJACAXXecDuG2BYgdqeW5MQRqIUziB2muYLtQBLdnj4Vh5AUpp0QEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEKhKAq0VhuAMkq9pXxc6gyx5zTga9OjeXcqBhx6cr0Rznk2tCEKu/OFbdcEbCLZDhgu/On587pY3pcpv/twUSxByTuiU8cdQQPLXuweZ9edwHFn60/dGUpjx1FpxJnHlO2XWajn+jR+tLDkezf+G8Av802atlvp3X7G/9HfKMHM+/OHljeL8cc6NxsHguvP3lnLSg6ukHDWsu+nni/tJv/pF/5lz1srxy0MnkGB/UG+6x7Ekj8sdh5DsZsXncNHanvMiCLHHMfubB8muIw/ZQ8oB5/4jUiUthxA9Xz/b2Efi6/k5ddbqSH++eSjXIcQe55EXLZJ+123c2dqpzXV7HEKc04MgJNcrN+fJIQjJ+QSRHgQgAAEItGsCCELa9fQzeAhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhUFQEEIVU1Xd5kEYR4EcWqgCAEQQiCkFinSuqVcAjxI0UQYgRcCEL8ayXNGghCnDQRhKS50LKO1bNHN+niU6d/VErdXrd+g2zP/t0fpXz9zbezTkXiIwipCGY6gQAEIAABCFSIgMcxpMXh4vV3yXcp6uo6yPcn6up2NTuLROvv3m+GNwOHkArNM91AAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAIL8EHn70MUlu2ZsrIkkefdRg2R5x/LFFk//5jEdk/zur3pWy3159pRx92qlSdunSOb+DJjMIOAi0V0GI4lBHjq+HThufbewth647f58IMeV0/GVLZP+AfTqFjiKHyrY6dtgOIkkdQmxHj+MGd5U3I35+7YBIPmubdorzR+M1r5nr2TvbI/moI0BQJ/jf+z9nvux/7OA9pfzN801h3ANle3i4f94C4yjyqW8YR5EjD+4i5esrt0n8v001jijV9MMhJLvZ8jlTtLbnSglCeu5ZH1nv817ZVDR1Pa9mhk45Wmn4OHNd0F9aDiFTLcef4eMWSxdvrNwe6c/mNP4H5vlm5u/XSVmuQ8jlZ+4l7ceHTkG3hs5Ckx4yDkO1+kMQ4pxZBCHVtOgRhFTTbJErBCAAAQhAoNoIIAipthkjXwhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQjUGgEEIbU2o4ynNQQQhKwWMQeCECMUQRDSmrMpWVscQvy8EITsL5BOGdZdSgQh/jXTqhqB+m5XXR2CECfFfAtC6jvslMw7dtgmZUOHHa1aD3lvvGNXg6S4dadRburvyCOMUvWh+26SUoUhrvHccucDcuiWO0yZ1Q+HkKzIEhcCEIAABCBQPoHgqw/Br9mYI3aouIKQ0s4gzd3FdBQx3+Koq5s+ZaKUw4eZL/vwgwAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAE2g+BZ5+bL4N99nlTun7HDBsqh475kCn1ZzuEuOq1H6L5GOk77xjHli1bt0p5wP775SOxKsmivQtC1m4wThvnfON1mbGXl26R8pRh3aTs2c28b/n4c01Srt+4U5w17ra+3J+WQ4jGuSF0LLln1mrpV506Bh9i3vsMhBvBT50C7C/56/LTeP9jOQYE730E/7MdTNZtNDyOC50Igm3lEYx7yhUHVMnK3p0mDiHZTVmtOITYDhpvrDTvkwfOOMFPz4Ph7zPCqZ7djKOIOmWoc4aSTsshxD6vNa8Zc4zzx/DBXaUcsHcnKXUcaTmEqHPK4986OBL/8efN9VD59AodVk4Z1sNcLyYY5yLbySS7lZhuZAQhTp4IQtJdaq2LhiCkdfxoDQEIQAACEICA+Y8CwQ9BCKsBAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCoFgIIQqplppLliSAkGS+7NoIQBCGFawJBSOvOpyStcQjx06qUQwiCkOJzgSCkp3+Rtq8a+RKENHTYLvi7ddwgZdd6U9Z38HyxukYmbc22PjKSTTuMUm3A/vtI+ZtffE9KnzOIjeGya0y7GY/8LhNCOIRkgpWgEIAABCAAgQoT0OesUEkSOna0SKL5cay0Q0iLKE5lSjQODiEVnna6gwAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgEAOCLiEIP326ivZDRxovnyt9TRl2wHksVm/lUOLX/uXlDiEtO3k2o4trnlr2yzz33u1C0L0S/X3PGacNC4/ay8phw8270fG/akDwIw5a6XJ/4WOIPoF/CNDZ46LGs37l3b83c4eb8vxV17bIk4iP584IJJC0nxnhvnMDB0B7Hz0i/xjRpZ+cdnu98hD9pC8rjt/7xaIgrFcETqKqCPBRaeacZ8yrHtcpLmph0NIdlNRKw4hKnwYM7JXZJ3r/l6hU5A6CN0zyzhTzVuwqSjcGdea8/7lpZul/Fro+GNXVieiixrN88ikh94pGnd8eF37+AfN+af5zPy9uV5NDa9/U0MHHzuOCl5UYOPLy85T21/U2FsOHXmwuX6oM8naDTtM3q8Y56JJD66SUq+r2a3AbCLjEOLkiiAkmyVXXlQEIeVxoxUEIAABCEAAAq0hgCCkNfRoCwEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIlE8AQUj57PLcEkFIOrODIMRwRBCyez0hCEnn3PJFwSHER6iurlIOIQhCSs8FghD/Wm0nNfIhCNmzwSiPenRcI2V7cQSxF9nbW/aTXTt2NUh5242XSnn26R8taz2+/OpiaffvZ15WVntfIxxCfIQ4DgEIQAACEMiOgDpxaA+2b0eHsIIadLj9P6yWtj7E7qA5UGmnkOaROx1CooEDh5Ag9LHDBmcHjcgQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgEAuCPiEIKNPO1Xy7NKls5TPPj/flM+ZUn/qBLLszeWya9mbK6TEIaRtpnnxkqXS8WOzf1cygYu/OLZtEqyyXqtdEFJluEm3DQjgEJIddNshRB1tXM4ZSTNR5xt15Blw7j+ShqA+BBITwCHEiQxBSOLVlGEDBCEZwiU0BCAAAQhAoMYI1KIgJJii4QhCamylMhwIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEItCSAIKQ2V0W7FYQEr2Ha/yd+ClOMICQFiITINQEEIdlND4KQ7NgSue0IIAhxsm9bQYg6g/TqtLpNVseu8IvVWnYIP6XdubNRlnfs2FFKPb5t2zbZ1lKTrq+vTyX/5ZsPiMT5v4e+J9v/dsShrYp/xLHnSvt16ze0Ko7dGIeQVHESDAIQgAAEINDGBBxOIZpVCwWKqb+7lad9QU0TMqwfFtOnTpS9CELaeBnQPQQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQyJJBUCGKn4nIKseud2vhR2TXwkIMzHA2hbQLq1PLwo7OKwunRo7vsv+DTZwMvBgEEITEgUaWqCSAIyW76eu5p3mueN3mglD27pfOes53xGyvNe9XDxy3JbjBEhkBIAEGIcykgCAnQIAgp71qBIKQ8brSCAAQgAAEI5JMAgpB8zgtZQQACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQKB2CCAIqZ25LDYSBCHpzi+CkHR5Ei1/BBCEZDcnCEKyY0vktiOAIMTJvm0EIQ0dtktG+3R5qyKrQgUfXbt2lf6GDh0q5bBhw6Q84ogjpNx3332lVIcQ2/lj+3aTd1NTk5QLFy6U8sUXX4yUS5cuLWtcWTmE7H/kJ8vKx9cIQYiPEMchAAEIQAAC+SWghh+7mp07wj2hg1oLO9tmvUhUOOJyCOkQBmiOr3GbkYQOI2GAGTiE5HexkBkEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEWkngnXdWSYSfz3w0EqnfXn1le/Rpp0rZpUvnWD35nELUgUIdKWIFpVJqBOY+8yeJ9dJfXo7M60eO/7Bsv+/ww1Lrq5YDIQip5dllbAEBBCHZr4MD9+4knQwfbN6fTvv3+HPmfep1G3emHZp4EGhBAEGIc1EgCAnQIAgp76qBIKQ8brSCAAQgAAEI5IEAgpA8zAI5QAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQKB9EEAQ0j7mWUeJICSd+UYQkg5HouSXAIKQ7OcGQUj2jOmhcgQQhDhZt40gpHen1ZJR14aNmayCnTuN0mzgwIFSjho1SspjjjlGyn79+kmpDiDqINKhg74aWTotV/1t27ZJQ3UMeeKJJ2T7T38yiuetW7eWDPz2FuNQsmNXRymv+NK5pvyyKZP+5j37V2ly5oXXJG0aqz6CkFiYqAQBCEAAAhDIOYGo44cv2ebnoBYVo44fLR1G7H5wCPGx5jgEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEaoXAb5/4vQzl1b8vkvKA/feT8tRRH5MyrjOIzcN2Chl46EGRuLXCr1rHsWVL9H25cue5Wsff2rwRhLSWIO3zTgBBSN5niPwgkC8CCEKc84EgJECDIKS8ExZBSHncaAUBCEAAAhDIFwEEIfmaD7KBAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAQO0RQBBSe3MaZ0QIQuJQctdBENI6frTOPwEEIfmfIzKEQJ4IIAhxzkZlBSENHbZLJvt0eSuT9dG3b1+Je+65xlHjxBNPlLJr166Z9OcLqkKTZcuWSdU77rhDyr/97W9S6nGNs357D/mzaXtPKXv26Cblb35xm5QD9jcOInF/Z37maqk67znTX9o/BCFpEyUeBCAAAQhAIA8ELIFI86b+oY5q0Xr20bq64kIT+/lHt2dMnSiDHz5scB4gkAMEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIJACARUEvPqPhRLtgP7GGaRfv71SiL47xLI3l5u4e5m4OFGkipdgbUQAQUgbgafbihFAEFIx1HQEgZoggCDEOY0IQrJc4QhCsqRLbAhAAAIQgAAEsiGAICQbrkSFAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAQPsjgCCk/c05I06PAIKQ9FgSKZ8EEITkc17ICgJ5JYAgxDkzlRWEdGtokkx6dlqbylppaGiQOMcee6yUX/7yl038nsZhI2+/HTt2SEpPPPGElPfee6+U69atk3JnXb2UK7cYJ5Cdu8z2gAP2kfLB+2402w6nkHXrN8jxy64xjiKzf/enTBHgEJIpXoJDAAIQgAAEyiKg/h3auLhPR2Fof41I7V2mfkufEJeQxJVJWD8spuMQUtZ80wgCEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIYIW4pgAAIABJREFUQAACEIAABGqTAIKQ2pxXRrWbAIIQVgMEIJCEAIIQJy0EIUkWUmvrIghpLUHaQwACEIAABCDgI4AgxEeI4xCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAIH8E0AQkv85IsPWEUAQ0jp+tIZAeyOAIMQ545UVhPTstEYy6dZgnCzK/XXp0kWafvzjH5fyc5/7nJT19cZRo1p+f/nLXyTV22+/Xcrly5dLuW1XJynf3dpPSnUK0XEN/9C/yZ8DDjBOIq8ve0vKl19dIqU6hWTNAYeQrAkTHwIQgAAEIOAn0MGy6rD9PvR4aOxRIqDD4cNWmNgRrMC76qLOH7utRFxOJGb/9CkTpRw+bLB/0NSAgIfAI488IjUOOeQQKYcMGQKzdkDgxRdflFGuWWP+3XniiSe2g1EzRAjEI3DfffdJxdNPP13K3r17x2tIrZogoNdFvT9Weh1ov3o/1vtzTcBlEBBoIwJtfV630bC93fLvAC8iKkAAAhCAAAQgAAEIQAACEIAABCBQZQQQhFTZhJFuYgIIQhIjowEE2jUBBCHO6UcQ0pZnBoKQtqRP3xCAAAQgAIHaIIAgpDbmkVGkS4AXwdLlWS3REIRUy0yRZ1sQQBDSFtTz02dbvziOICQ/a4FMaodAW5/XeSXJvwPyOjPkBQEIQAACEIAABCAAAQhAAAIQgEC5BBCElEuOdtVCAEFItcwUeUIgHwQQhDjnobKCkF6dVksmezZsLGtl7Aq/QP2Zz3xG2o8ZM6asOHlppON54403JKWvfvWrUq5atUrKHbsazHboFLJjV8e8pC55VKtDSN/ePST/QYfuH4vnX195Tept2rylaP1jhh4u+zXunHl/jVX/3dXrpd6i194smYedryufrnsY55xjhr5XSt3W4MuWm3X119BJxtXpAf33kkMH7Gccap6d//eS+fnq+3hv2rRV4i9b8Y6U764xXOyf3Y9y6LpHZ6kadz7tuDo+X552O9c8DDrErCs7Hx3XoiVmvl3jLAmbgxCAAARiEIjvCGIHs5w9XH11KO4k0uwM4mrnsigJ90+fikNIjOmtu+yyy6Ta0KFDpbzgggviNKu7//77pZ6+MD9p0qRY7aq10kknnSSpq0PEddddV9ZQnnrqKWmn8ebPny/bOI6UhTPzRl/72tekD523J598MvM+6QAC1UKgQ/iAoOcFDjrVMnPp5Pnaa+a/axx66KF1wVpYvHixbKtTx6OPPloX/Deq0aNHFz3emiyCuAMHDpQQ119/faLnl9b0S1sI1DqBwvM6GOuSJcY5ur078KT174BaXz+MDwIQgAAEIAABCEAAAhCAAAQgAIHqIYAgpHrmikzLI4AgpDxutIJAeyWAIMQ58whC2vKkQBDSNvTTevFfs0cQUlpA4uONIKRtzgN6hQAEapcAgpDandtgZAhC4s1vWi+CIQiJxzsvtRCE5GUmyCOPBBCE5HFWKpcTgpDKsaYnCFSKAIKQ4qTT+ndApeaRfiAAAQhAAAIQgAAEIAABCEAAAhCAgI8AghAfIY5XOwEEIdU+g+QPgcoSQBDi5F0dghAVTnzyk5+Ukfz3f/93ZVdQxr3p+JYvXy49XX755VJu2LAh0vPWncYBYstO44igjiENHbbLdscOO6Rcs61Pxhmb8NXqEKIODhdfZNaTOnW4oH1/2i9NPYdzxcTx/ynH+/YxziOzn3jelE+a0v5dPNb0q/VvuOWnJefrvDPMF67VccKur4KU0aceF4nz7uqmyLY6bKhTiI7Ldj4ZddIwaTfqZFNedu1dJfPz1ffxVg7aycOPPSN/qtOK7rf7UQ5du5rz4qJzT4nk2bWrOU/UKcU1zzfcavgrR+WddF3Y+Wn7TZuNA4ryV0eSn/2CL1aXXFgchAAEYhPooDXDP9SIwy8MUacPjWBtNwcyHejzSnN/zf1GO252Cmk2ErEcRVqMzByfPgWHkDiTXu4LTrXyovzpp58umG677TYpXV9ALpeTPQdr1qyRXfp8rk4jlfrysgpSgi+3B79ad3aJcw6UqlOpdX7fffdJGr1795ZS12Vr8y+3vb4QquO/9957yw1Vk+0Cx4Pg9/DDD9fk+OIOCkFIXFK1Wc8nCAnud8Gz3vjx48UhZNq0aamBwCEkNZQEgkCEAIKQ4gsirX8HsNwgAAEIQAACEIAABCAAAQhAAAIQgEBeCCAIyctMkEdWBBCEZEWWuBCoTQIIQpzziiAkD0seQUhlZ8EnULCzQRCCIKTYCrXXBYKQyp7H9AYBCOwmgCCkfa2Gcl9wqtSL8lnPBoKQSVkjrur4lVrnCEKqa5kgCDHzhSCkutZt2tkiCEmbKPEg0PYEEIQUn4Ny/73U9jNKBhCAAAQgAAEIQAACEIAABCAAAQhAoDgBBCGsjFongCCk1meY8UEgXQIIQpw8q0MQ8qEPfUhGMHGi+XK0vsiQ7jIpP1rzF7P1U9zlh5KWTz/9tJQ333yzlBrfF3bHrgap8vaW/XxVUzneWocQFWYseu3NWPkkre8KagtC9MX+RUvi5aFxbUeJv76yRA6pI4XGtfMYOfz9smv0qcdL6epf43zzmgulnu0s0be3cSSZeIVxKFEHk+/c8aBs284fI487yvTbaJxEXE4mPscPezy++j7eOo6Lzhslofv26S7lhBujXzZ2OYS4nFvs+VFHkbj1k66L277+haLzpLx0PnXbnp9YJwGVIAABCBQQ8DuAlIvLcvRwGIk0O4E0d+No5zxu6murGVNxCIkzY+W+4FSpF+XjjKGcOurU0aePccJbssQ8d2XtEFJOrmm2UWcSHT/OD6XpVmqdqzBJhQYXXHBBmtOeOJYKVO6//35p++STONEFHNRh58ILzb+n9LqRGHCNNEAQUiMTWeYwfIKQMsPGaoZDSCxMVIJAYgJZCUI0bqUc8RIP3NOg3H8vpZ0H8SAAAQhAAAIQgAAEIAABCEAAAhCAQFoEEISkRZI4eSWAICSvM0NeEMgnAQQhznlBEJLGkkUQ0rEsjEkFHknru5LyCRTiDgZBiCGFIMRwQBAS98yhHgQgkBYBBCFpkayuOOW+4FSpF+WzookgZI2gRRBSeoVVap0jCMnqTE83LoKQKE8EIemur2qLhiCk2maMfCHgJ4AgpDijcv+95CdODQhAAAIQgAAEIAABCEAAAhCAAAQg0DYEEIS0DXd6rRwBBCGVY01PEKgFAghCnLOYb0FIx45GaHDPPfdIuddee6W6Hrdv3y7xGhqMs0Z9fb2UO3fulNJ2ItFtbbd27Vqp9/bbb0t5+OGHF22XNGkVmEyZMkWa/vKXv4wVolocQmwhxc9+Yb5gqw4Y9mBtwcF37pwpVZYtXxWLi10pLUHIxWM/GQk9+8nnZVv3x3X+cDl12JzseHGdRuzxa34H9Dfn0w23/ExKdazwCTx883PZtXdFqsTlfd7ok6TdMR8w55EdJ68OIT4nl7IWKY0gAAEItAkBy+HDlcOuqLNHh+Z6USuRZgeRFg4jtpOI2Z6OQ0isWS/3BSffi/Ivvvii9K+OFPoitSalXwi+/vrrZZftiKAvpOmX+O32Q4YMkXYPP/ywlHG/ODxnzhypry/gqzDEbn/ZZZdJvUsvvVRK5XT00UfLtj7Hq5OCHWfSpEmRfnTc9gvl6ryg/fvG3bt3bwn1mc98RkrtxzfZ6jzxyCOPSFWNo6W2tx0PfPmceOKJ0lSFJTbH733ve3Lc/jK0zrvNTedT58mup/nq/Fx33XWRoWs/Ol8az7UObY42D9861/yVr6573e/LV/PUdWHPi/K0HTq0vo5L+1UYvnG51ouuZ9tJxp5X5arnob1ObA7aXvOy5823fvW4OpboutB+9Xhrryuu9aznjV6PXNcN13nvGp/Oo73O7PH4rifaXutpnuWuC4132223SSqu65uue12fyi/ufOp60/7s+QzWV3Ct/cUvfiEhXdf5OByDOC+88ELk+qfj0uuUPV691uv1zb5PBesi+G8tN9xwg8S1eQc8gn6nTZtWNP9gvEH7M844o7lesD127NhIPPs6EuRRWM++Pwb3t8J+C69rQTudNz0f9Xpb7H5WyF/Pd51fnyAkyEvzDOIsXry4xfW61DgK73dB+1tvvbW5faFDiM6LjqPY80IhD3scvvVqr6/guaDUvMe9/gbrLoij698+z+zrZXA8qH/yySfL/CoP+7zTeh/4wAdkaKWuuxqvsF4ch6rCdvqcpOeRzd/3nKD8fc+N+tym1znX84Y9n/ZzxPz58yPXAft6ad+/db24nLJc10tdv3be9nPGo48+KvnofUb7S/r8o+OOcz0M6rruK2kJQmwuOm6XU4iuOx23jkfXlc1Dj/vON51Pba/PEbo/bhzfv5d8eeq86v0k6XXId53iOAQgAAEIQAACEIAABCAAAQhAAAIQSEoAQUhSYtSvNgIIQqptxsgXAm1LAEGIkz+CkAANgpDyTtCGzt2kYX19MocQBCFdhNs3r7lQSgQhZv0hCCnvPKQVBCAAgfQIIAhJj2V2kXwvOLl69r0o73uxr7UvbiMIQRASrE0EIZfLKVrui//2+Y0gpLjACUGIcRTS67a+SIwgBEFIsC4OPfRQESio4EPXCYIQIwy1BQB63UUQEr0D+Z4bEYQYQbBLEKs0EYQYTvpDEJLdvyGJDAEIQAACEIAABCAAAQhAAAIQgEB1E0AQUt3zR/Z+AghC/IyoAQEI7CaAIMS5GvIpCFGHjIsvvlgyb2xsTHU9b9myReJdeeWVUqozyEEHHSTbRxxxhJTq/PHmm2/K9sqVK6XU/4Nq48aNsq1fHFZHjx49eqSS74YNGySOfnFSt13Bq8UhRPM/74zQEWKocYSwnUJsRwifk0hc6LZjhcZdtMTMs/17d836yK6+vc38TrziP4vmrUKPZ1/4hxx/eNYzReOqU0ffPibeDbf8NFLvovNGyfZuJ4/ocZ+AwsXDdhb5zh2h48oK47hSaYeQA/YzTiU6XnUq+c6dD0aGUGmHEHWseXd1dP41vznz/hrJz17P2v7ZF/4u9Ra9Vnx9xV231IMABCBgE+gQWnOEhh0tAKlzh1/m4anR4rCrftQ5pK65Wun6mvj0KRPlz+HDBjPZJQhkJQgJXhANfvrlaH2BTVPRF9b0xVH7S8/afuTIkdJEv9yuL1bql371i7zqbOF68VL71edu+wVv26nDdmpQTvrCogoR9MvEGl+FMvpivZ2Xjlvj6XEdv77wqP8e0C8Jaz7av35hWev7FrnW13nQ+LbDiO1Uonmedtpp0oX95WZbGKRf3rZ5aDtdD3YczUu/8K7jtR1gdB2og4XNz/6ytQqHdJz6pXLbScTlxOETPul60i9O6xeuNX97vm0nBTsP5WPPqz0vQ4cOFcS6DpWfxtMX9TUP15fN7XVjvzj50ksvSRVdh1rfPj/0fLW/gG1z0Lzs9eBbv5pXnz59pKqeX/aX7F3XFZuzbz3r9UHXlz3Pug5tri7nHdf4bCcV5Rf3OmevL9cXyLUfPZ9dLxTb1y+dd/u8sZ2bWusQoutb+1GHg0LnjeCa9cQTT0RQqsPGwIEDI/cb+/qi4y90Ugj+G5GOI7juFDpIaD1tp/NhCx3UqUEdQGznKV2nwfWhmEOJnb86omg8nc9gvQX5jh8/XsYZ5FPo3FE4P0E9dYYoNs+Fzg56Xuh5VHifLnTACMZRKPiwv/TvE4QoJ3XODcZQmIeOR8cd535X6BASrJdiTia6jnRdB9ezwnnwXXcKn1fUySXYF6yDYo4jwXopHJfrOld4nrnWXaETiN6/1Bkm4Bn8dL0UOqfo9bFwvQT7gn7efffdyJCVT9++fSNc4jgX2PyDwHq/tq8XtpOV7dBhX5/t9ajXX9u5yr7+Kle9futgbUcR27FKnZrt5y3NS0vbiSXu9dJ3P7aFmLajip4POi6fEK+195XWOoTYzk8uJzx9jlHu9nNPWnx1HWheytf1vOty1HH9e8n3nKfrV+tp/75/r8S9PlEPAhCAAAQgAAEIQAACEIAABCAAAQiUSwBBSLnkaFctBBCEVMtMkScE8kEAQYhzHhCEBGgQhJR3opbrEKK9IQj5pKBAEIIgpLwzkFYQgEB7J4AgpH2uAAQhxmENQYhZ/whCnoxcCBCEIAgJFgSCEOOcoi8KIwh5qvnF/8IX+hGELI7cR9QhBEHIpUXvKyq0USFMoRAJQchuJwwEIcbxAkHIvcLBFhz5BDd68iEIaZ//zmXUEIAABCAAAQhAAAIQgAAEIAABCLgJIAhhddQ6AQQhtT7DjA8C6RJAEOLkmU9BiDp1fOc735HMu3XrlsqKUOeRBQsWSLyrrrpKSt2vndjb+kVeVxJa/6abbpIqRx11lJS+dnEH9fOf/1yq/vjHPy4Zt9ocQnT8tjDkr68skUPvf5/5UnVaziDan+0Q4poHdQaxnTuOCR1NNG89rvVHNx4nIY/5gHE+mXCj+T9C7Z8d5/vTfilVli03Th3qNDLnD8aJwnYaUUeN97/vEDl+2bV3xVpSrn7VISVrhxBXktq/zrftzFJphxCdB3UE0bw1L83THs+ok4eZ+R9i5l8FP7quH37sD7LfHl+syaMSBCAAgSIE4juB+PD5nELC45YSRVvtzsOK43QKCQ+ExfSpOIT4Zig4npUgRJ9bk345/tFHH5W09Qu9q1evLjkMdQzQL/bbjgGuxj6nDrudctL9LscF28lAv4StX9z29asvFKvjgu3gEWdOS9XRF7m1ju38oPvtLzyrU4Id2x6vPd8qqND5cc2nzruO37duXPNuf9na5SSh47DnQ/PTLzf7BCFx50O/hK3rWp1AtL3Ot35R3rWOtb1+0Vy/yG7n4RuXL++44/atZ7ufuOvBbmfPq31e+caT1nqOG8eXT9zjSa8nvvNGv7CvPO31o+tQnSLsdeo6b3z9xh2vXS+4LgT/bSR44Te4p9gOE3o9W7p0qRy3HURc/aqTQ+CkUegYEdcZQR0abKeIYv0F+QcODMHPdowodAgJ8tDrcbHzv9CRIXghulCIUthvoTNEEL/QOSOoV+hgEWz7uPnyD2KU6xCiL3Jre9vpotS6icsjiGHPtzplxP1Cvy1s0XVmO0YoX3XkVb4q+NDj6mhT6npfOI9B/oXzpM5owX210LlF4yvX4LkliBNcd4s5yuj5FVwX7Di+c9bmH9R3PU/4nhNsBxHX85XPucK+v+h9wnag07Hp84Y+T7jyd7GI+/ypjh36HGDfv/S+otcz2/nM7l/jaP7qFOabMz3uu6/4OPv60XFoP67nFH2u1vmx+ZfLt9z7ke1AY+fj+veS7cDlW3c+fhyHAAQgAAEIQAACEIAABCAAAQhAAAKVIrB2w866Xbvq6o76/KK64P+qf+Y248R94N4dK5UC/UAgUwLB+h41Yan0Mf4s82HpU4Z1z7TPOMGDV1k+e8ubUvX/nm+K04Q6EIBABQhMvWL/3FwnKjDcJF0gCAloIQhJsmZ2122tQ4hGQhBykqBAEGIeoBCElHc+0goCEGi/BBCEtK+5RxBinpt8L+AhCDECa/vne9EzrgAAQYgRriMIKX79RRDSR8D4BGa+F2ERhJj1hSDk5OYTDUGI/5kPQUhLRghC6urKFSwgCDHrCUGI/9pDDQhAAAIQgAAEIAABCEAAAhCAAAQgkCUBBCFZ0iV2HgggCMnDLJADBKqHAIIQ51zlSxCiwozzzz9fMv7Upz6V6irTLx9///vfl7iPPfaYlGk5eTQ2Nkq8L3/5y6nmvWHDBomnX27buHFj0fjV6hCig7GFIWk7g2g/tkPIw489I4eWrTDOHPpTZwh1itD9E8f/p/ypzg/vrl4fade1a2fZ7rpHFylV6KEOGFpZj0+84jzZpU4g6hyhPL5zx8yi+Z032rwQaTuR2I4W9mKxnTbs+Fk7hNi8LzrvFEnx2Rf+LuXDs4yDhi9v25nFru9zcvHVd81b0eRK7Bx5nHEMUucYXQcaP2k86kMAAhBwEVDjjrqo8UYCYA6HkBa7XU4i0f0tjUHsdtHt6VNwCIkzWVkJQvTFdhUEqLOBfvm82Je1g3y1vn4xWdu5xqLOE/pF+7hfSk7qbJCUk8shxdevfmlYv/CtX062+cWZ22J14jqE6JenX3rpJQnjmwedb9uRo1ynCdupwx6Ly1HDFg74XpD3fSk7bv7ar85v4FhQ+LPXs71O4zqE2MIk13mk49LzI6mjRtxxa71HHnlEhqv9uNan/aV0nxDLjqOCBh1X3OuKrnudH9961vi281BWDiGal55vOn96HqgjgL2ekzrBuObVJ+yy5yHpeea7XrnGr+vj/vvvL+oQEjh8BL+kX8oP5lEdGIJrtTpG+PIMjhe+CO9zUtH6LscI2yHkhRdekBSKOZUk6bdwnm3BR6FDSFyHE70fBS/AFzqTFK6DQocJvW/ZQopCh5dgnMF1I8hH+ei449zvkvCwOS9evFg4u5wj7HXgG4dd38W/MI/C+6orD71eqkOI5m2Pp9DxRB1ddN7VISToT50lgvtPsF+vi0Fdl+OM65wo5K/z5Xv+cjkQ6X1Nr0N6Prv61ucNlyOZXk9cTiMa137e0v0a/7TTTpNdtpOMz/HDlbdLQJL0vhL3ebTc+0prHUL0Pq39u54LdD3Y66dcvr7nKF1f+rxiP6fpfr0OxXUI0fnWeVF++nym50Xc602c+xB1IAABCEAAAhCAAAQgAAEIQAACEIBAGgTk7c66urpzvv66hDvyYPM+2nXn75NGeGJAoE0JzFuwSdb3FXetqAs+hjrj2gMlnwP37tSmeWnnU2etlvxu+NHKXORDEhBozwT0ujAzZ9eJHM0JgpBgMhCElLck03II0d4RhPxVUCAIQRBS3hlJKwhAoL0TQBDSPlZA3BfLbBq+F8YRhBivHd8L3PYL8QhCjEAZQcgFRS9ACEIuFy4IQk4UDghCEIQUu1AgCIlSQRDyNQGSpSAkeOKZeN11JR+cEYQYp6fWCg3jPrcjCIk+RyEIaR//rmWUEIAABCAAAQhAAAIQgAAEIAABCMQngCAkPitqVh8BBCHVN2dkDIG2IoAgxEs+X4KQzp2Ns8LkyZOlPPBAo/hL67d9+3YJpQ4k69atSyu0xNl3332lvPvuu6VsaGhIJb4KViZNmiTxfvvb3xaNW+0OITqoQYfuL3/ajhqpwKyrq7MdQuI6QRzQv5+kcOWXzpLy2fnG0cJ2CNE8Rx73fvlTHUZcjhDq9HFA/71MvDVNUuq2OmHY47cdMOI6qqjDichqAwXrLT+NhB453OQ9+tTjI8dVqGLncdF5o2TXoEP7SznhxnsjVXy8bUcS13zY9arFIURh6DwPGmjWt2te01rnxIEABGqfgApAAvvMYj/fcfMdkRI/6/Auq354G2kOYB9vGb60swgOIfHWbNwXy+xoPkGI1tcv5apAZM6cOZFQKpjQL+cmdQjRYCNHjpQ/XY4Jdv4+pw67flJO5TqE2Nw0T+Wix/U53vclbXscSR1C9AvKcfvRL2rrF5bjrhN7PtTp0bWK03II0fiu+fLlr8ftFzztL/zbTjnlOoTouPV8ibve1Zkn7heqfeNWbpV2CHFdVwIHicLzX7/srePVda+Cq3LXc9IvufuuwvYXxe3zR9tr/j6Bme+8cc1ruY4frvPGN259Qf/kk0+WqjpPOi+9evWS/eqYEpxfQV+FDhNJHCLsfArnsdDZwpd3cLzQgUGvw3p+FWtf6IARjLvQsSOJUKFwvHo/dfVbLYKQOPe7gNett94qVQvP2yQ8knAuNodZOYSow4ztQGHnoMcvvfTS5kPF1mFQL9gfODQUrmtd7+q088QTT0ScbnzrybWuBw4cKIfiOOUE9VSQYjv6xBU62nnY97W49y07ju3wZT9v2eNL+vyo/bmeW5LeV3zPo629r7TWIUTbax76PKSl8tN69r8DynUScwmO9PlA51Gfm7TU+43W0/tRUocQnWcdX+F5FxxTJxS9b/jO+zj3I+pAAAIQgAAEIAABCEAAAhCAAAQgAIE0CLz+9ra64N2AxquN8/1nTzUfNhlzQk8p8+KokMZYiVG7BNZt2CmDm/fKRuMM8oMVsn35Wea9yc82mnWdp5+cdxPMeffy0i15So1cINCuCFx3/t65vU7kZCIQhKQ5EQhC0qGJIARBSLCSbEEQgpB0zi+iQAACtUPAJ/jwHUcQUp1rwfdimWtUcV+8QxDypCDUF8/ivkjoe0ERQcihwtV+kTXtF9t96xxBiPny/COPPCLl/PnzS14Iy33R0w5qX1cQhBhnHQQhu1+YL7UQEYTsFuIEwoFp06Y5cQVravTo0XK8T58+IjTQ+oXX20IBgr5QHVdIUep+hyCkrg5BSPHliSDEOEXZAlMEIZcJFxVQIQipzn+fkjUEIAABCEAAAhCAAAQgAAEIQAAClSOAIKRyrOkpOwIIQrJjS2QI1DoBBCHeGc6XIOT97zfOBDfddJOU+gVL7zBiVliyZInU/NKXviRlWg4e2n3Hjh3lz5tvvlnKww8/PGZmpavpiyLBlwGDn35x0eZTKw4hqUArEcTnWOFqet4Z5sUddebwOVSMbjxO6o887igp1Tlj0+aoUlQFMBeP/aTUU8eROfP+Ittz5v21aEpd9+gi+9WxpGtX47Dz8GN/kNIWVKjzh+bjchRRZ5IrvzQmEufhx56R7U2bt0qpjikXnXeKbKtjisbVpOPyVucSjf+dO2dGxl1pQYhyXLb8naL8l61YJfv79u4upS1k0nH07d1Djuv6UacVl2NM0c7YCQEIQKAEAXXq8Ph9lIigLcNIajliB27ejvbUst9wj3Vgt0BFD5hSu5sxdaJsDx82mPkuQUC/1By86Bn87C/iupqW285+kVsdKPQLwd/73vekS3Vc0OfttCcxrjBD+00qnGmtQ4hrvCqAUE7KJ+6XhuM6hARfGA9++gK/zk/SefAJKjReVg4hDz/8sHRxmysmAAAgAElEQVThcoSwBQoqaPA5nCQVnri+jK7jd3053Oat49D6KgxKOi+++nHnTV+s1/WyevXqkqHtedb6cdevK7jOh764ri+k6/yntZ6Tfsndla/NzXUer1mzRkLo9TkrhxC7n6TnjZ1XqUUQ/LeI8ePHN19fCh0z7HalBAVBnA984APSJBDcFQoHfOv70UcfFfFMsF6Cdu+++640ibMOC50p9Ivv9gvZhf0H9dUJJbiuFOaZxLkiiSNGXIeQYLxBPr/4xS+cyHz5Bw2D61FrBCHFOg/61XEE97vC+OqEoQ4VPoeLJJyL5RJX2KJtXfyD86zQ2UP/e1xcpyd7Xel5pOev/nc+Fe7of+ez+33hhRckj+D8KTZvvvMnOF64HvW+5Lsf6XXMdtSK+1ziyyvufcsXR49rnvb9TXnrfTju9U+fC+3ra9L7iut51L6v6POM7Qzmu6+01iFE+en1Ua+r+lyl27ru7euuLz/X/Ln46jypgEivF3Yc3zpM+u8AjW87psS5b8Rdo9SDAAQgAAEIQAACEIAABCAAAQhAAAJpEnh95TZxVvif0Flh3iub0gxPLAhUhEDPPeuln+vP30fKMSON001ef+s27pTz7mv3vy0pzvz9urymSl4QqBkC1XadyAF4BCFpTgKCkDRpZhcrrkDBzgBBCIKQwjWBICS7c5TIEIBAMgIIQpLxqvba5Qo7ym2HIOQpWTL6Ypm+CG6/sOdaVwhCDBmXgMJ+kTHpi+0IQgzfuC/WIggxH4hI+kMQgiAkWDNJhAoIQjrULV68WE41BCFGkIEgJHrljXvfinu9RhBinNiSPqcqXwQhhgSCkLhnHPUgAAEIQAACEIAABCAAAQhAAAIQaGsCCELaegboPw0C1faiN4KQNGadGBBIRqDarhPJRpdJ7XwIQvTLeOeff76M8pxzzpFS97d26BrngQcekFA//vGPWxuyZPuLLrpIjp911lmp9qNfwvz0pz8tcevrjVJSfziExMNdriBEHSyWrTCOEff87PGSHdrOH+qwYTt+qNPHxCvOk3i67XMg0c5tBwrt105OnUlmP/m8HJrzh+LOI9pOnVDUmaNvH+N0Yf/UiUR5tHBAOWR/aXLxRcYBRZ0xbAcTm9fsJ0yemm+lHUJ8q0nnp2vXqFOLq52OVx1U1CnE1w/HIQABCMQlUL4wxOMtYh/WjuT7B7sdPnb3bzVQCxDnQEz96VNwCIkz13G/KKyxkr5w78pBHS60f30R3/4ysB7XL/vGGVOcOkm/gJz0y8BZOYTYjhZJHRZ8X0BWdraTgy2UiMM4qBP3Bc2sHEL0hUSX840enzNnjgzJdqRx5W/Pg+uFSXudqaDHdhRQBxH9YrbrS+t63mgc1xfA485P0vPTrm9/KV0FOK4v3ivvtWvXSiit39p8tb1rvuz1FfeL7nZeSQUwPr4+JySd78suu0xCZeUQonnqdU4Faq7zxpdXqfkM/lvK2LFjpUqwflwOFUE9vV7df//9Um/nzp2R0DrfAcfgeOB8EPx8Ajt1TFCHCeVbyumjsGPtN1gPhf3aX7q3BR+2I0RbC0KC61gpbnHyD7hk4RASxA3yK3SyKHRySSKQScK52NpNyyEkiF3ouBJsl3LI8V0XbWcJdXxRZ5DC63Bhv7pOAy6F8+/rr/B4IX/dr/cj+zzQPPV8tu+XjzzyiITQ4+Xe1+I+b2i+ev9yOQPZ92/7OUivl+p84btv6/Xe5pSWQ4j9fOBy7PJdv5M+H9vrJu7zkW+9Jb0fufi6nGnsdWA/h9n3v6T/DrDH53J403WozykuRzsfL45DAAIQgAAEIAABCEAAAhCAAAQgAIG0CazbYP57/LqNO9IOTTwIZEbgwL07ZRa7koHfWLmtkt3RFwTaFYFauU5UcNIQhGQBG0FIFlTTi4kgBEFIsdWkAhh1gvGtOAQhPkIchwAEKk0AQUilibdNfwhC4n0BOemLYAhCzHqO+4ImgpChwgtBSOuugwhCSgsyfedj0hdwXUKVUrOIIGT3eZ5EqJBEAFE4z7bgoFAYgCDkkFgXHAQhLTEhCNnt9IYgJLo+EIQUv6wgCIl1uaUSBCAAAQhAAAIQgAAEIAABCEAAAjkigCAkR5NBKrEJ1MqL3ghCYk85FSGQmECtXCcSD7z8BvkShHz729+WoQwePFhKfTGr/PFFW27dulV27NiRrSK2Y8eO0k+nTtkoGfWLy6tWrYoMEIeQZCtFnTU2bTbrwna2sKMlra/t47ZTZ5Cue3SWpuU6SGh/6uih43p3dVOscboo2s4jy5ab9efjlpSD5q/tlENSPq2t71tNrvmxOb27en2r5tOXB8chAAEIKIHyBSEawfFiaovdpV9g3RU6h2jRIUxst/ObnSkOIeWs4ssvv1ya6Rd29cUlfdFNvxisX3K+9NJLpb46Fdh9Bl8MD36nnXaalPqFXv1icfDF9+DniqN5aF52PhpHnR3UacD3ZXhXnrpfHQw0jj4nV0oQov3rv1vs8Sj/kSNHRuYr7pzbX+rW/nr16hWZT/1its6vflFa50HzsteF/UVq3wvomndWghAVWOh4dD3rF8p1Her6sb/M7Mtf17m9fuz1qf1rPrYTQaHTQRBLX7TX65x9ntnOJvZ5pvOiL2bqF8njrhN7Plznha4D+3y162s8zUudLuKer9pOz0PfdUXzsZ2FlKPytvPUfjRf2zHGdkTR+dR1peelyyFF+eu86Hjs80qP63n56KOPSlPbgSXpeeNbz768dF0vXbpU8tH+fc4w9roLrkOFwhDXejn44IOlqcshRJ0+Tj75ZKmn86PrQ/t96aWX5L8FTZs2TXYVrtsgj/Hjx8t+131P51fvV65+dRzF7nNB//Z539aCEOWj696Vf3AeBfnbDgiFTgLB8cWLF0f4lhJSFApTdD7s64Her4L5tPtPIpBJwrnYNTJNQUgQX/PRdeu6fwTrNvi57hu6Dvv27dvMvXAe7LEE9/GAmz5XBfeZoL46isS9PwT1Cvnr/U3Pv1LnQdDW9dyoeelzjo7bft5w3dd81zd7fHq+6/OWnudaT/PQbfs+at+XtL2Weh3Q5yfXc0ZaDiGajzpd2PcV+36mz8/qhKG8W+sQorw0D50v+/zWdaN5Frs+BrFsJxYXXx2Hrj/7vNLx2utT9x999NHSRNdDXIcQnT99nrTv/8pd7+O2A46uW9v5zV6PSc5P6kIAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIACBlAggCEkJZCQMgpAsqKYfM65QQ3tOWj9pu6QCBhcRBCGGTFKedn3fikMQ4iPEcQhAoNIEEIRUmnjb9ocgxPBHEHK9cEAQYtaD7wVTBCHmC/sIQoYIBwQhJ8a6kSEIMZiSCBWSCCDiOoToZCEIKb1sEYS05IMgxJy/wS+uYAFBSNSRB0GI4YEgJNZjA5UgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhBoGwL5EITo2O+55x75c999980Uh365tbUOJNpe46UV1zX4+vp6OXTNNddIaX/xDoeQTJcNwSEAAQhAAAJVTWC3U4drGKWdP9Txo273HxJodyurffOmK250//QpEyXe8GHGKY5fPAL6xXf9Yq6+8KZfqtUv6Pq+gO+Ko18IjvvitPavX97V7aRxXKO34+sXnfWLxTpOHY/tNOGKq18Mtr9wrV/Q1i9Paz8a15WP9qPxbCeLeLO7u5bmp19uVmGDOipoPtqi3HWh8XVcrrxtLvYXnu3xKT9dl/YXxHU8+u8bzUNLdaxRJwHXl5h9+duOEnreaDzlqfV0HK7+7HnRL2zbThcaRznol+R1/er4456vrvWj49e8tJ46mNhf/HatX9/6irt+7fG21fXJNU4Xl6TXH/s81/PPdlJIet741rPm6ZtHdcpwnYdx51PHZX+J377+6vrzXRfs88c+31ztXfcZPU/1OuFytHH1a4/D5uK7H8S97tn1Ss1zMWcO/TK/3mf1OuK73/jy961P3/3Oxy/u+vPl6VuvvnEk4V9Y175/6LxpnbjXTdf541tv9vOJj0Ph8WICJb3f2PcL3zza/Wb9vGFf59S5wXbQ8K1/O47mbT9X+a4ftqOX7/nO9zzqOq/s54m49xX7OdW3TlQgYz9/2M+VmqctSLefd1znie1E6HMccz336vxofjp/9r9zfNw1vjpo2c8nrudr7U+dZPR67BuPbx44DgEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAARSIIAgpDUQEYR0E3z19R1bg5G2EIAABCAAAQhUgACCkApAboMuyn0Rz04VQch9ggRByIvCAUGI+ZK4/hCE9C7r6oYgxHxRXF8UzfpFdVsgoC/gIgiJLl8EIY8IEPvFcd/6RBBi7gu2kEBXF4KQOZHnB58wOa7gTfnq+kMQUvq+giDErBgEIWU9ttEIAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIACB6iaQD0FIQ0ODYPzRj34kZa9evVqF1Xbq2Lp1q8TT/wNx4cKFsr1o0SIpm5qapFy/fr2UGzZskFLbaTJdunSRP7t1M0KIHj16SKlfJjviiCNk+6CDDoqU2m7nzp2yv7XOJN/97ncljv4fXJofDiGtWjY0hgAEIAABCNQEAVuwqoNyC0I8ziAawHL80M0O4fHd29F4uzxOIfrcNmMqDiE1sQAZBAQSENB/n+mLtE8++aS09jnaJOiCqhCAAASqloDtEBI8402bNq1qx0PibUOgmEOIy8mqbTKk10oT0P+erA4h+u8xXx5aX5/T1CHD147jEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACmRNAEBIgRhBS3kJr6IxDSHnkaAUBCEAAAhDIjgCCkOzYEhkCEEiXAIKQdHkSDQIQqC0CCEJqaz7bajQIQtqKfH77RRCS37khMwhAAAIQgAAEIAABCEAAAhDImEDwipx+6S7jrggPAQhAAAIQgAAEIACBChPIhyCkY8eOMu77779fyrgOIbYTyObNm6X93/72NymfeeYZKf/85z9LuXbtWim3b98e4dxaxw77S2r19fUSv1+/flIec8wxUuoX1A477DDZ1nEnnfS77rpLmvzqV7+SUvPHISQpSepDAAIQgAAEap+A7eDhH7HlGOJx+LDj7aoLG7QwHrF3RD1Gpk+5VkINHzbYnyI1IACBmiCAIKQmppFBQAACGRFAEJIR2HYWFkFIO5vwGMN98cUXpdbQoUOl9Dm0PfLII1Jv9OjRUs6fP1/KIUOGxOiNKhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhUggCAkgIwgpLylhkNIedxoBQEIQAACEKgkAQQhlaRNXxCAQBICCEKS0KIuBCDQ3gggCGlvM57NeBGEZMO1mqMiCKmW2eOztdUyU+QJAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEMgBgXwIQhoaGoTFfffdJ2WfPn1isdm6davUmz17tpS/+93vpFQnENu5I1bQDCup8ESdQxobG6U3dQ5RDr4U7rzzTqny61//WkocQnzEOA4BCEAAAhCAQEthSAsLj9KQdkXr261th+Xdz2GufqJOItOnTpT+cQhhrUKg/RBAENJ+5pqRQgACyQkgCEnOjBYtCSAIYVW4CFx//fVySB277Xpr1qyRXYcccoiU1113nZSnn346UCEAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABPJFAEFIJecDQUgladMXBCAAAQhAAAKFBBCEsB4gAIG8EUAQkrcZIR8IQCBPBBCE5Gk2qjcXBCHVO3dZZ44gJGvCxIcABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACFSOQD0GICiXuvvtuGXn//v1jEdi5c6fU0zJWowwqaf/19fVlRe/YsWOsdsrpW9/6ltSfO3dupN2OXcZp5e0t+8WK19pKDZ27SYj6+nj5t7Y/2kMAAhCAAAQg4CbQUvARl5bHKaTF4eL1d9VZ+5s3fU4k5vj0KTiExJ0x6kGg1gioMKR3794yNC1rbZyMp3YJBHcy2ymrdkfLyCpJQL/Qr31yfawk/drpi/ts7cxlliPRdWJfb7juZEmd2BCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAgFQIIQtLAiCAEQUga64gYEIAABCAAgdYQQBDSGnq0hQAE2pIAL6q2JX36hgAE8kwAQUieZ6d6cuM+Wz1z1ZaZIghpS/r0DQEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAARaRSAfgpBdu8yXoSdNmiTlYYcdJqU6YrRqiHV1dRr/qaeeklBPPPGElNu2bZNyyJAhUo4ePVrKLl26lOzynXfekeO//vWvpXz55Zel7Nq1ayTO0Ucfneo41IFk/PjxEvfVV1+N5IlDSGtXCu0hAAEIQAAC1UOgQ6gACR+jWpG47eBhBW6hNDH1tZX9RfQWTiGamSvRcP/0qTiEtGISaQoBCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAAC7Y8AgpBgzhGElLfyGzp3k4b19TiElEeQVhCAAAQgAIHyCSAIKZ8dLSEAAQhAAAIQgAAEIACBNiAQ/KdoW1HeBmnQJQQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAARqiEC+BCGf//znhe0nP/lJKdNyCLnqqqsk3qZNm6Q8++yzpVQnkN/85jey/c9//lNKdSrp0aOHbKvDyJIlS2T7yiuvlPKEE06IlGvWrJHtn/zkJ1Kq0OSSSy5JZc1s375d4px11llS6rYGxyEkFcwEgQAEIAABCFQlgdYLRGynkBBD8+7o8dgOIY72uz1GTD/Tp+AQUpULj6QhAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAoK0IIAgJyCMIKW/94RBSHjdaQQACEIAABLIggCAkC6rEhAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEI5JZAPgQhiueYY46RPydONF+ILvenjh5z5syRED/72c+kvPPOO6VsaGiQUh1Idu7cKds33nhj5HifPn1ku2vXrlK+8MILUn7wgx+U8r/+67+krK+vl1L7ff3112V7/PjxUk6ePFnK/v37R/pNOr6FCxdKk3HjxkXy1Dg4hCQlSn0IQAACEIAABFoSCC09bMOQDlbNXcUdQ2znjzqnQ0gYL4wzfSoOIaxGCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIBAAgIIQgJYCEISLJmCqjiElMeNVhCAAAQgAIF8E0AQku/5ITsIQAACEIAABCAAAQhAAAIQqEYCwb+27W8tVOM4yBkCEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAIFcEciXIKRXr15C5wc/+IGUPXv2bBWtb3zjG9L+2GOPlfKjH/2olOoMYgdXB44rr7xSDp1xxhlSNjU1STlr1iwpH3roISk7d+5cMr877rhDjh9yyCH/P3t3HidFdS58fIZlWAcVhsUBjSg4CMryikkIYERuIphPokZBjVv04tWYG42oMZKoMSrGmCBokisvSxISVEATl1cB70UNaFAhEbhRUTZlF2ZQGLYBmXk/85x6ZqwzXVPVPd3VVd2//sNDV586y/ec6m6kn3qkPPfccxvt36sxzTzy5JNPSpXZs2cnrEqGkCZtF05GAAEEEEAgpwT0+45+j9DJ6Y9P6vN76J8aviLnWBk+7NrabsNWrBQjVkYRbVgPzyVDSN3+40dCOXUpMhkEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAIFMCBIR8XpaAkOT2GRlCkvOiNgIIIIAAAmEKEBASpjZ9IYAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIBC6QLQCQqqrq0Xge9/7npTf/OY3myTy4IMPyvkDBgyQ8pxzzpHSK0PI6tWr5fWpU6dKOWnSJCkPHz4s5cUXXyzlE088IWXr1q0bHd/kyZPl9bKyMilHjRrVaP9+k73++uulyubNmxNWJUOInyCvI4AAAgggkDsCdd9nnBQbVj6OBhMtdBKANEjUUVfTpwXrRLu25hexM3/UD8Q+w3nuFHPIEJI7m5OZIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIhCFAQMjnlQkISW7PkSEkOS9qI4AAAgggkE4BAkLSqUlbCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCMROIFoBIcqnGTV+/etfN0l08eLFcv5TTz0l5UMPPSRlUVGRq139QeWUKVPkeMeOHaW84oorXPVuvfVWef71r3/dVdZYd8zevn27vH777bdLec8990jZs2fPpOaj7a5atUrOmzBhgpReGU7IEJIUL5URQAABBBDIaQH/jCD29DWDh0cqkbrDpl59RhB3O/b3Is0Y4o1t2psz7S4phwzum9PrwuQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEMglgdr/y+/1bwa5NE/mggACCCCAAAIIIIAAAggggEBEBQgIqV0YAkJS255kCEnNjbMQQAABBBAIQ4CAkDCU6QMBBBBAAAEEEEAAAQQQaJoAP55rmh9nI4AAAggggAACCCCAAAIIIIAAAggggAACCCCAAAJ5LhDNgBBdlDvvvFP++KUvfSmldTpy5Iicp+0cOHBAnl9yySVStm7dWsqXXnpJyi1btkh5113mDtWaKUQ7X7dunfxRM3987Wtfk+dDhw6VUjODzJ49W55/8YtflPL666+X0iuzh9fk9A7b48ePlypr1qxp1CHsDCHtjj7OzKt5c9e4qj+rkudHDhtvHggggAACCCCQSQG975aV4aOuSz0edAxO/Rq7Xb/zzXn6/UW/9wTNGPLE1J/K+WQI8XPmdQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQREgICQWgUCQlK7HAgISc2NsxBAAAEEEEivAAEh6fWkNQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQRiIRDtgJBu3bqJ4tSpU6VsbmWiCEq8b98+qbpkyRJXqRlE+vXrJ8fPO+88KTt06NBo05opRNv717/+JfU7deok5VlnnSWlZghJddx///vfpZ2JEycGmmrYGUJOP+dHMq5jupW5xvfx2lfk+Y51rwYaN5UQQAABBBBAIIsCNVYGESe+RA9ruImOsEYCihM87HacKnX1ndMK69p3tzNj8q1yxuCB7u8VWZShawQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQSiLEBASO3qEBCS2h4lICQ1N85CAAEEEEAgUgIEhERqORgMAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgEFoh0QUuP8QPGSSy6R+VxxxRUB55W4mrZX6NyaWks9nmzj9vlNbU/7//TTT+WPN998s5Q7d+4MNDQyhARiohICCCCAAAI5LaAZPTzyeDQyd+sM52n9UZ8Wne9tWqvQySRSd1bDPzhjMS/MmHyblGQIyentyeQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQTSJ0BASK0lASGp7SgyhKTmxlkIIIAAAghkUoCAkEzq0jYCCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACkRGIdkCIzXTLLbfIoREjRkRGMJ0DqaqqkubuueceKVetWpVU82QISYqLyggggAACCOSFgH8Gs8YzgziJ1WojaMWrPgOI4avRIw0SiCTOKNIwEJcMIXmxEZkkAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIBAugUICEm3aFPaIyCkKXqciwACCCCAAAKJBAgIYV8ggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggkJMC8QoIad68uazCQw89JOXJJ5+cE6ty8OBBmceUKVOkXLx4sZT6A86gkyRDSFAp6iGAAAIIIJA7AprBw0ngkcLEnEwedkKPQqepupcTZxKpzxmSuOuGGUQSZw6ZMflWaWDwwLIU5sApCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCOSdAAEhUVhyAkKisAqMAQEEEEAAgXgKEBASz3Vj1AgggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgg0USBeASE62ZYtW8ofH3jgASnLysydpJPNqNFEvJRPr3Fu4V1VVSVt/OY3v5HylVdeadI8yBCS8pJwIgIIIIAAAggUuDN31GcccR+vSxyihws9Mow4on4ZQvR70cwpt8kZZAhhKyKAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAk0TqP01h/7Go2ktcTYCCCCAQMQFCAjJxgIREJINdfpEAAEEEEAAgcYFCAhhhyCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCAQI4FwA0I6tPxUbNo135cWo6KiImlnzJgxUl500UVSagaRtHSSgUYqKodzY20AACAASURBVCqk1fvuu0/KDz74QMqmZjipqjYeuw51zsCoGzZ5+jk/koPHdDMZWvTx8VqT6WTHuldDGQedIIAAAggggEBTBNyBIFaikALN8FGXGcTuqu50K6CkQUNWP3XtmOMzJpMhpCmryLkIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAJ5J0BASDaWnICQbKjTJwIIIIAAAggkFiAghJ2BAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggECsBWp/AqR3fI31RBg8AggkKRBuQEib5vtlfEe3/CTJcTZevabG/JBxxIgRUn73u9+VsqSkJK39pNrYkSNH5NQ33nhDymnTpklZXl6eapMJz9t3pL0c33P4qLS269UYGUJCYaYTBBBAAAEEAgl4ZvAIdHZtJfN9yvlaVf/3Q6dh/b7l3ZxXYInTrp5oZRTRdmdOIUNI4KWiIgIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAALmh381hcf2/ab1C77M2BAQQkBIZnYWrSKAAAIIIIAAASHsAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQTySiDcgJBmhdWi27lou5TNCjMTh9KmTRtp/2tf+5qUF154oZQdO3aUsrDQ/GRS70itz1NderudQ4cOSVPvvfeelJoR5KOPPnL1m2p/XueVV3WRlw7XtEx30wnbI0NIKMx0ggACCCCAQEoCzted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TzXnfs9bc2774X2/n7fRr37XQdfjuq7Trj5tLsP277c/XT+H1rrdru+rWj9r33O1H8TX2p/017u+T2ufA23XH13XY7/t9zsvw37/D7p+h9WP2udk7fV+v1eWu3/tc7Hf/g66XpZ7nO//Ayev9DuP5boa9PjaccN639WuI2vrvN/Pra77D3t8g36+de3vsNqvff/325+u/Vop7+V+zratz/I6ue3xoF6OI0CAAAECBAgQIEBgLAQUhIzFNI1oJxWEjOjE6BYBAgQIEFhCQEGIZUGAAAECBAgQIDBNAl1/+BgmtR981F4ftm2/5xv2D2X6/SHdcvvb9YekXZ377c9KtTusftR+AFd7vW18/fav6/61H8r1299+12P5vo7HXddZbZy1/tSO77reap9PNedBz9PmVZ6v33ks+zPo8bXjVsq/dBm2f9f5Gvb4auusa79q+y2337XvOQUhB56BrtdFtc+38iz9zmu/7dfW1XL707X9QcfZ9XunrR+1z7va677/s0DXeajNc2391o7vut5qn8vD/v7pd1yDrru279GuLrXzrpS/7/+uM7T0fsudl9r3/6C9q/Wr9vqg5x3253Lb9XW///0z7PFojwABAgQIECBAgACBNRFQELIm7BNyUgUhEzKRhkGAAAECYykw21R4zLUEeyz8j67eHRbv354ZkmDmX156vzIhZB5z0e5t55EQMpYLUKcJECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAYK0FFISs9QyM8/kVhIzz7Ok7AQIECIy7gIKQcZ9B/SdAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAssSUBCyLL4pP1hByJQvAMMnQIAAgZEWKAtGmkCRhcCP+d5HckfLHtWEkLzD7Ew+vi3C+15RI82Z83GRWHLxzu3p8batp420q84RIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIEREVAQMiITMZbdUBAyltOm0wQIECAwJQIKQqZkog2TAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBKZVQEHItM78MMatIGQYitogQIAAAQJrI7CQB9I1IST6OR8Z0tLx3uSPxckgS7eza4eEkLVZCc5KgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAmMqoCBkTCduJLqtIGQkpkEnCBAgQIDAQAIKQgZicxABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEBgVAQUhozIT49gPBSHjOGv6TIAAAQLTIjA725R8zDWJHYsG3pb00XvcYq+lj1t4tvnbouCR8ol9uenm6V07JYRMy9o0TgIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAYioCCkKEwTmkjCkKmdOINmwABAgTGQkBByFhMk04SIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIFBBRSEDCrnuJkZBSFWAQECBAgQGH2BIijkAB0ukz2KpJDm4UyTOBINlbkfEflR7LYQBVL2oNlRQsjoryU9JECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIDCz/wdD8VsiHAQIrLWAgpC1noFxPr+CkHGePX0nQIAAgWkRUBAyLTNtnAQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECAwZQIKQqZswoc6XAUhQ+XUGAECBAgQGA2BxdEeuV8tCSHR6UgKKRNEFgY1v0d6aq44z8U7t6fnt209bTQc9IIAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECIy2gIKQ0Z6f0e6dgpDRnh+9I0CAAAECAwkoCBmIzUEECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQGCVBRSErDL4RJ1OQchETafBECBAgMCECswHezTjKx/PzPQmd8wzxNOLDlh6/7mynZZmF87Xu8OuHRJCJnQJGhYBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAmMqsP9XXvETsjEdwqR3W0HIpM/wSo5PQchK6mqbAAECBAgMR0BByHActUKAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBEZMQEHIiE3IWHVHQchYTZfOEiBAgMCECcw2lR5zLUkctdcXc3RNCsn7LQ4Q6T1+vhBl/um2yJD8vISQCVughkOAAAECBAgQIECAAAEC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ECBAgAABAgQIzCgIsQgGF1AQMridIwkQIECAwHIFFIQsV9DxBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIEBgrAUUhIz19K1x5xWErPEEOD0BAgQIEOhDoAkUiWCPmYXHB04GiVPMJ4Q0T8RRs/OPK+20Jpnk43btkBDSx3TalQABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIKAixBgYXUBAyuJ0jCRAgQIDAagsoCFltcecjQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIrKqAgZEV5J7zxaS8IWbgj+oEn+uzT857fvemgtP3GNfcsecBBTYP7ut2ofcJXl+ERIECAQE1gtqnwiOSOcv+yAKTW3sxMxy+glqSPueb4+e/HRc21tS8hpD439iBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAosEFIRYFIMLKAjJdrWfzyoIGXyNOZIAAQIE2gUUhFgdBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIEBgqgUUhEz19C9z8NNaEHL0pgy3/RcPSdtvX31X2r7t/TkBJP484Jj8+if+8760vfH2Q9P2cb+cE0L27r27Z/8/fOOR6fHffuMHaXvhRXvSdl8+3B8CBAgQILDCAi0ljvF0S/THopfbSiVbkkVmmud37dyexrdt62krPE7NEyBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBiRBQEDIR07hGg1AQoiBkjZae0xIgQIDAiggoCFkRVo0SIECAAAECBAgQIECAAAECBAgQIECAAIG1Etj/T/9xw6e16oPzEiBAgAABAgQIECBAgAABAispoCBkJXUnve1pKwg5Kgd4zPzZb+dkj/tuyUkf3/1OTgh54ivyDnED9At+8qj0+BUvPy8/v/uqtP2FX70sbT/yxTvS9vgtOVnkU+/J0SP77s7JIF/9+vfT9lm/mgtP2m6sPunrzPgIECBA4MACs7P5/+TMFV8UzdPzB9e/R4qCkNb6kPxC+f+P5ppkkNnmlehP9KPs38Kocnu7dkgIsdYJECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBA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xirTBgECBAi0CSgIsTYIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgMBUCygImerpX+bgFYQcGPDfnHZd2uHpp343bedmDkrbD/7dfdP2o1ceu8wZcDgBAgQIEOhfYLapJIlkjsUtNJEekewxHzWydJLI4t2aZyrHLZw3779rx/a03bb1tP4H5QgCBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIDB9AgpCpm/OhzdiBSEHtlQQMry1piUCBAgQGJ6AgpDhWWqJAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAisoYCCkDXEH/tTKwgZ+yk0AAIECBCYQIEo+NifTbX/z9zSwR73Gnl1h552Zpt2S7q5eH5RZEjeswkmmVmcTCIhZAKXoSERIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAwMoLKAhZeePJPYOCkMmdWyMjQIAAgfEVUBAyvnOn5wQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAoA8BBSF9YNm1EFAQYkkQIECAAIHxE5htujyf6DGTn1n8fMvYKpEji5JC5hNFFs6wVMu7drw+Pb1t62njh6rHBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIEBg9QUUhKy++eScUUHI5MylkRAgQIDA9AgoCJmeuTZSAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEJhoAQUhEz29Kzw4BSErDKx5AgQIECCwJgL7rw9nZuaDPaIP85UkzetF3xYSR1o6vShZpPc8u3ZuTwdKCFmTSXdSAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEBg/AQUh4zdno9NjBSGjMxd6QoAAAQIEhiegIGR4lloiQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgkAX2/yon7snKhAABAkMSUBAyJMipbEZByFROu0ETIECAwIgKzM72/ufi3KJEjlrHi+SPRUEgvU/Eo/ngkCZSZLb5z9b58y9KFlk4Iv2HbtPPiyWE1CbI6wQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBA4N4CCkKsh8EFFIQMbudIAgQIECAwbAEFIcMW1R4BAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEBhpAQUhIz09I945BSEjPkG6R4AAAQJTJRABIWUwSG8ex71JFmd8LAk2HwzS/KU5USR7lAkhi9ooj2/ZYdeO7emVbVtPm6p5M1gCBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIDCggIKQAeEcNjMzoyDEMiBAgAABAqMjoCBkdOZCTwgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAwCoIKAhZBeSJPYWCkImdWgMjQIAAgakS6DcpJHDycQtH9z6ef2Hecj4qpHmmedxsdu2UEDJVy85gCRAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQGC5AgpClis4zccrCJnm2Td2AgQIEJgcAQUhkzOXRkKAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECUySgIGSKJnvoQ1UQMnRSDRIgQIAAgaEJzM7Oprbm5spkjrZTtOy36OkyGSS3N1dGgrQc13b2XTten17atvW0oRloiAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACB/8ve/QdLdhX2ge/WSBYaQHL0i58aUYkE4oHN2noxfmB5I8cRARcVsVBZlxF2Ehs5W2WbihInOGWeyloqYpegjbBTDnovtcEGr3vK/PBuIoJTQYkRBOIV3mBAGGEHJGRjaUYWYI00SJreeufc060+/e70j9c/7o/P++dOd9977jmfc98MP973fQkQIECAAAEC7RXY+wGe+DNEvggQaKyAQEhjt3YFCxMIWQGyWxAgQIAAgTkFBELmhHMZAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBCoh4BASD32qZqzFAip5r6YFQEC9RPYy2BP2+FQv9WZ8bIEUuAjjV/WBFIUhXTKi0KmfPqK01ITSPnvDigaRAbDlo0/+n5vZzssRUPIsp4Y4xIgQIAAgRoL+MVFNd48UydAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBJQoIhCwRt/FDC4Q0fostkAABAgQqLCAQUuHNMTUCBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQILB8AYGQ5Rs39w4CIc3dWysjQIAAgfoIpKaOKXs+nrKwdEU+QvF6rFKkOL+oHEmNJONXFw0hOWFpRUk8X0NIfZ45MyVAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBSggIhFRiG2o6CYGQmm6caRMgQIBAowQEQhq1nRZDgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgSmFRAImVbKeeMCAiGeCgIECBAg0ASBCd0iYx+PvjHWM5KaQAZJlbImkmiXmkaO7m6H11ubG01AtQYCBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQILBsAYGQZQs3eXyBkCbvrrURIECAQHsEBELas9dWSoAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQINEhAIadBmrnwpAiErJ3dDAgQIECAwtUC3aOgYK+yYOEJJQCQNlK7Pxh8MW3rDsuBJfL+3oyFk4tY4gQABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIDAUEQjwN8wsIhMxv50oCBAgQILBsAYGQZQsbnwABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIrFVAIGSt/DW/uUBIzTfQ9AkQIECgEQIp+NEpCjjKejjKFzvlFYOGkKIaJN0wG7g/NpEJ4xfj9nY1hDTigbQIAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEFiVgEDIqqSbeB+BkCbuqjURIECAQN0EBEJWs2N7/6k5RWFWc0d3IUCAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECpxUQCPGAzC8gEDK/nSsJECBAgMBBBU7f0zEML0zZ/9HplDR+jM1zMODoyINmkHRByXn5eP2iIeSohpCDPhKuJ0CAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIF2CQiEtGu/F7tagZDFehqNAAECBAjMIiAQMouWcwkQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQOMEBEIat6UrXJBAyAqx3YoAAQIECGQC3W4WCSkaOaZvBCkjzQYaS57kzSBxnOFp2QwmNoXEE3o72+G4tblhrwkQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIHJAgIhk42cUSYgEOLZIECAAAEC6xMQCFmfvTsTIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIEKCAiEVGATajsFgZDabp2JEyBAgEADBFIgpN8/eCdI5JgwTvq4qALJ75saQhLt+LzKxtcQ0oDH0RIIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAYPUCAiGrN2/OHQVCmrOXVkKAAAEC9RMQCKnfnpkxAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBBYoIBAyAIxWzeUQEjrttyCCRAgQKCRAlM2jBRNJFlRyJhIPzWNjA2rIaSRj49FESBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgMC6BARC1iXfhPsKhDRhF62BAAECBAgIhHgGCBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIBADQUEQmq4aZWZskBIZbbCRAgQIECAQKlAt9sNn/WLho/4qpN6PJ5y3YRgSHF92Y0GzSDphLxKZOz6eEJ6++judni9tblhNwkQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIHJAgIhk42cUSYgEOLZIECAAAEC1RcQCKn+HpkhAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBCYQ0AgZA40lxQCAiEeBQIECBAgUB2BseBHUQUyodhj366QkVXlTR/Fh+ONI1nDyOBlWfPI6Pu9HQ0h1XmazIQAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEaiAgEFKDTarsFAVCKrs1JkaAAAECLRQQCGnhplsyAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECLRZQCCk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HP/1b/z7cPyN3/70TPdb9MmLagh53WteGaZ21ctfGo4XXhAbWI4djw0MH//058PxQ7d/4rRLOPK8i8Pnb3zDD4Xjpc+7KBwPH35aON77tQeKcT4Zjnd99p54v/Pj/V519WY4piaIC4p5PHoi+t/12S+H4/s/8LFwPPFo/MH/9DVrIOSjd9w1cv1VL4/PW/qadL90XmqISI5p3fk4ye/YQ98cuU96kfx/4PvjPpT5pXl//NOf23ecW37pp8P76X79IiHyP7067nPa37//8+8ecczX8eLLLwmf5/v2U2+KTRyPFk0cN9wYGzum/UrNGkeeH5+XG7bfMzKPNM7hc84Of/xX7/y5cPxq8fy87R3vHbnVG19/dXh9SfH8vXvnwyPXLSowla9v3oaQRe1z/n2bf7989f7491X6fkn7OG0gJB//gx+J3/93fio+d9N+v6d923nfR6Z9RBp5Xh7UKmuo2vu3KHyVFYYMBoonjJ9WUiWUXZeQ07936ePeroaQRj6AFkWAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECyxIQCFmWbBvGFQhZZCBkGDARCBEI2fv7QyBEIOR0/44IhEwXABMIiU+RQEgb/lOZNRIgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIBACwUEQlq46QtbctMDIdddGxsj3vj6vxGOl1wSf0A9NYSk37D++OOPh/e3t+NvNv/BH/zBcLzmmmvC8cMfjo0BX/ziF8PxrW99azimxo9bb701vP7xH//xcLziiivCMW8IOX78eHi/9+HYUPGr7/tPC9vLeQY6aEPIG18fmzxedfWV4fiFL90bjn/45fvC8YILzoueRXPI3ffE92++9TdHppsaPt7+1p8I76ffWH/nf43NIieKho8rLov79/4P3hGOqbEgNUHc9E+i/+9/7o/i5/fFRpEXvTBel+aRmi8+dHtsGklfszaEpAaUB4omlLTuFxXz3HhhbKQpW3dqMnnL9deG89I4v/8HscnknKLh4srvviy8To0a/6zwy5tCUnNDuv9nikaUtL60TxcV+5L2Ic0vnZeei7v+W2xgufJll4fjV+77sziPolnl5nf3wuu0f7fcFJtFHin2K+1fGvd7vyuuI90/+c3aEHJN8bxdVzx/qcFivLElNqWkfb3t12PD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LLkhZDKAMwgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQJ0EBELqtFtVm6tASNV2xHwIECBAoE0CAiFt2m1rJUCAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECYwICIR6K+QUEQua3cyUBAgQIEFiVwLDBo+yOg06R4oSypo/Tn9fvlDSAlFSUDBpGiglqCFnVE+E+BAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQINAQAYGQhmzkWpYhELIWdjclQIAAAQIzCQiEzMTlZAIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgUBcBgZC67FQV5ykQUsVdMScCBAgQIDCvQFnDRzFeSpak4cuaP/KmkMGwp28Q6e1uh5G3NjfmXYDrCBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIBAmwQEQtq024teq0DIokWNR4AAAQIE1ikgELJOffcmQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIzCgiEzAjm9KcICIR4HAgQIECAwPoEukVjRyrqyF/PP7MsGDJoAkkVIfHzdNZYcchYQ0hJ0CRNsBhfQ8j8O+ZKAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEGilgEBIK7d9QYsWCFkQpGEIECBAgMAcAgIhc6C5hAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQINEdAIKQ5e7n6lQiErN7cHQkQIECAQJnA7A0hecfHpCaPdOf9z+sXzSCjPSJ7VSJl446+39vZDjfY2tywyQQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgMBkAYGQyUbOKBMQCPFsECBAgACB6ggIhFRnL8yEAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAisQEAgZAXIjb2FQEhjt9bCCBAgQKDGAmMNHRPXUtLgkReIpHGKxo+yjztFU8jg9Oy64XTiCKlA5OiuhpCJW+UEAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECAwFBAI8TTMLyAQMr+dKwkQIECAwLIEBEKWJWtcAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBQKQGBkEptR80mIxBSsw0zXQIECBBohcD8gZCSKwcFIvs3iQzfLf5UUjiSN4cMNyNe0NvRENKKB9QiCRAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQGBRAgIhi5Js4zgCIW3cdWsmQIAAgaoLCIRUfYfMjwABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQILERAIGQhjC0dRCCkpRtv2QQIECBQSYGyIEi3+KBf2tyRLydr+hgbYHSg9Gpw/7Ebldy4OC99enRXQ0glHyyTIkCAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIGqCgiEVHVn6jAvgZA67JI5EiBAgEBbBARC2rLT1kmAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBIKAQIgHYX4BgZD57VxJgAABAgSWJdAtGj36RQNHWVBk/P5lTR7pzNNXjAw/zRpGBjcquz6+39vRELKsZ8K4BAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQINBIAYGQRm7rihYlELIiaLchQIAAAQIzCAiEzIDlVAIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgUF8BgZD67t36Zy4Qsv49MAMCBAgQIDCtQHlTyOmbPwbjF40jZfcbawhJJ5ZcN2gwKSamIWTanXQeAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAIAgIhHoT5BQRC5rdzJQECBAgQWLWAQMiqxd2PAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgsVUAgZKm8DR9cIKThG2x5BAgQIFBLgW6R/JhQ6PGUtaVuj/LISDi5rOmjGCldHQPH+51e1kQS39cQUsvHzaQJECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAYH0CAiHrs6//nQVC6r+HVkCAAAECzRMQCGnenloRAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBDYR0AgxGMxv4BAyPx2riRAgAABAtURKGvumDDDojFkvF+kZLyxhpHRJpGju9vhhlubG9WhMRMCBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIFBdAYGQ6u5N9WcmEFL9PTJDAgQIECAwWUAgZLKRMwgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQOUEBEIqtyU1mpBASI02y1QJECBAoPUC3W4kGBZ17B8E6Xbiif1O9vlYw8co6fDs4k+DNyYFTuLnvR0NIa1/SAEQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAwCwCAiGzaDl3VEAgxBNBgAABAgTqIyAQUp+9MlMCBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIDCFgEDIFEhOKREQCPFoECBAgACB9QnkAY+iAKTTKT7oZ40e44GQsrnnzSD5efs3fow3ipz+ujS/NO/eroaQ9T1N7kyAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECNRQQCKnhplVmygIhldkKEyFAgACBFgoIhLRw0y2ZAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgMBQRCPA3zCwiEzG/nSgIECBAgUF2B/RtAOsXbqQlk0EhSLGR41eDEbIn5uKPnaQip7hPRhpntPY35M92GdVsjAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQK1FhAIqfX2rXnyAiFr3gC3J0CAAAECSxEQCFkKq0EJECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgMBiBQRCFuvZrtEEQtq131ZLgAABAtUU6Ba1Bv2SHMfkWU95YUlDyFgzSLrh2LD736dfTPzo7na4cmtzY/KUnUGAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgIhHgG5hcQCJnfzpUECBAgQGBRAgIhi5I0DgECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIEaCez9GHjxy2RrNGtTJUBgsQICIYv1bNdoAiHt2m+rJUCAAIFqCaT/LlfW7zE5KDJbM0inE8/PrxrOY1AhMgo1OKHsfvH93o6GkGo9YWZDgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAhUXEAip+AZVenoCIZXeHpMjQIAAgYYLCIQ0fIMtjwABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQInF5AIMQTMr+AQMj8dq4kQIAAAQKLFsgbQSYFRob3n9AUUloJMvpBP+8OGRs2f2O0UaS3qyFk0c+E8QgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECg0QICIY3e3iUvTiBkycCGJ0CAAAECMwgIhMyA5VQCBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIFB/AYGQ+u/h+lYgELI+e3cmQIAAAQKLF8gaPMZexjdS80h+/3T6oJmkX9Y8kt7XELL4PTQiAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECLRIQCCkRZu98KUKhCyc1IAECBAgQGCNAgIha8R3awIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgMKuAQMisYs4fCgiEeBoIECBAgMD6BbpFJUdpIcfEKZY1eex/YX9i80d23YTz08dHd7fDhVubGxNn7AQCBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECgIxDiIZhfQCBkfjtXEiBAgACBRQkIhCxK0jgECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBBoocBepKD4xcQtXH11lzzdvgiEVHcHqz8zgZDq75EZEiBAgEBzBbpFEiRv7Bj85/IJn4/3gpQ0heRvpxuUNH/0Q+D4KV+Dl/lA8bWGkOY+o1ZGgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAksVEAhZKm/DBxcIafgGWx4BAgQIVFpAIKTS22NyBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIEBg2QICIcsWbvL4AiFN3l1rI0CAAIG6CJQFQ8rmPyj4GJwQmzq6ReffWMNHPlBR6ZH6Pooikk7eVDIsCilpHsmaRHo72+EELCwSAAAgAElEQVROW5sbdaE3zyUITNdyuIQbG5IAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQL1ExAIqd+eVWfGAiHV2QszIUCAAIH2CgiEtHfvrZwAAQIECBCokoBYY5V2w1wIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECDQEgGBkJZs9FKWKRCyFFaDEiBAgACBhQgMmzv2H66sKWSvKyR+Fc0eYwUfo28MmkIGt4nvFEUiT7l5PtDoaw0hC9l2gxAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIBAewQEQtqz14tfqUDI4k2NSIAAAQJtFtgLYoylL+YGEQiZm86FBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECgDgICIXXYparOUSCkqjtjXgQIECDQJoHxpo95Vz9lGCWr/sgbQvrF5+OBlLLx4/saQubdN9cRIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQEsFBEJauvELWbZAyEIYDUKAAAECBA4kIBByID4XEyBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBugoIhNR156owb4GQKuyCORAgQIAAgYMKjHV87D/goOBj/6aP4cfxT8OGkJJmkKxppLe7Ha7b2tw46IJcT4AAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIE2iAgENKGXV7WGgVCliVrXAIECBAgsEoBgZBVarsXAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBBYkIBAyIIgWzmMQEgrt92iCRAgQKDiAt1ifiW9HIPZj5+XXTHIiWRnZs0ew6uKZpB0/7EJlM0ovt/b0RBS8UfL9AgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECgWgICIdXaj3rNRiCkXvtltgQIECDQDgGBkHbss1USIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQOsFBEJa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QIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgsHwBgZDlGzf3DgIhzd1bKyNAgACBNgoIhLRx162ZAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBGorIBBS262rwMQFQiqwCaZAgAABAgQmCKTmjvG4R97tURIIGXv79MGRfqr8GBu+7Lr4fm9nOxy3NjfsKQECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIDBZQCBkspEzygQEQjwbBAgQIECg+gICIdXfIzMkQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQJzCAiEzIHmkkJAIMSjQIAAAQIE6ivQLZIi/WkbQAYnjkZMhpcXf5p2vKxJpLerIaS+T5OZEyBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgMAaBARC1oDemFsKhDRmKy2EAAECBFooIBDSwk23ZAIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBBokoBASJN2c9VrEQhZtbj7ESBAgACBZQjklR6jDSCpyCPduV+8kc7KZ5Q+H143VhkyeknRPKIhZBl7a0wCBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQaLCAQEiDN3fpSxMIWTqxGxAgQIAAgRUICISsANktCBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIDAogUEQhYt2qbxBELatNvWSoAAAQJ1E8h6PvaZfgqC5F0fWUAkO61fNHqkAYf3iSeOjZadn08kjXd0dzt8tLW5UTdq8yVAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAusQEAhZh3pT7ikQ0pSdtA4CBAgQaKKAQEgTd9WaCBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIDAQEAgxMMwv4BAyPx2riRAgAABAssW6BaJkLygo+z9TidrBkkTHHs7vjEoDikGHDaHjDaFDO9fNn58v6chZNmPhPEJECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAoFkCAiHN2s/VrkYgZLXe7kaAAAECBGYREAiZRcu5BAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECgdgICIbXbsgpNWCCkQpthKgQIECBAYOECRaPHoAoku0FWPTLo/8grSQaXlTSEFF0jvZ3tcObW5sbCV2JAAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECDQQAGBkAZu6sqWJBCyMmo3IkCAAAECaxAQCFkDulsSIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIFpBQRCppVy3riAQIinggABAgQINEGgrLmjWFtJQ0i/aALpjp0WLxgvCtEQ0oSnxRoIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAoDICAiGV2YoaTkQgpIabZsoECBAgQGBMQCDEQ0GAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBGooIBBSw02rzJQFQiqzFSZCgAABAi0UyJs55ieYMhBS3KDfieen+6f7DotEioaQwQenbwZJI/V23hau2NrcmH8priRAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAu0REAhpz14vfqUCIYs3NSIBAgQIEJhWQCBkWinnESBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBRgoIhDRyW1e0KIGQFUG7DQECBAgQOI1AtxujIf3+hKaPkjHGgyUl4wzePv19UoNIUSTylLvm1xWvi0NvdzucqyHE406AAAECBAgQIECAAAECBAgQIECAAAECBGoosPe/9+f14jVchikTIECAAAECBAgQIECgMgL+e1ZltqLiExEIqfgGVXp6AiGV3h6TI0CAAIGWCAiEtGSjLZMAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECIwKCIR4IuYXEAiZ386VBAgQIECgKgLDhpAJDSNFA0l+1ljDSGoqKT4YfF7SYJKaTY5qCKnKI2EeBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIFAPAYGQeuxTNWcpEFLNfTErAgQIECAwi4BAyCxaziVAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABApUREAipzFbUcCICITXcNFMmQIAAgcYIdIskR0nxxhzrLGkIGby9/+fDd7PPx07P34iv0/w1hMyxZS4hQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgTYLCIS0efcPunaBkIMKup4AAQIECMwvIBAyv50rCRAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIBAAwQEQhqwiWtbgkDI2ujdmAABAgQITBToFomRfmmFSGrsKKpGOiUNIMX1+VnpdT6RfhpnUkNINq/e7nYYamtzY+LanECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAiEH9jrd5+z8dr9fwKQEIHTCAiEeDwIECBAgEB1BQRCqrs3ZkaAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBBYgIBCyAMTWDiEQ0tqtt3ACBAgQqIXAaKdHURjSGRZz5HngrANkrEBk9PzxfpEJ+eIJTSW9HQ0htXisTJIAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEqiIgEFKVnajjPARC6rhr5kyAAAEC7REQCGnPXlspAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECLRQQCCkhZu+sCULhCyM0kAECBAgQGBhAlnPxwzjTmr4SEPF88r6RdIn44Ug+RWjrzWEzLBVTiVAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAvEH9vrd52y8dsJPALIiMC4gEOKpIECAAAEC1RMQCKnenpgRAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBBYgoBAyBJQWzOkQEhrttpCCRAgQKDRAjEX3O3EKEl/rPujWHyq/CgSJ9nL4VVj1SCnzx33i/OP7m6HG21tbjRa2+IIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgMCCBARCFgTZymEEQlq57RZNgAABAo0TEAhp3JZaEAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAi0QUAgpA27vKw1CoQsS9a4BAgQIEBgfoFut2j6GGvqKBszNXgU1R95Q0hW8FHWIDK8urhgbNj9m0I0hMy/164kQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgVYLCIS0evsPuHiBkAMCupwAAQIECCxBQCBkCaiGJECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAEC1RMQCKnentRnRgIh9dkrMyVAgACB5gnkwY+iGKQzdTHIgGT/5o5SsewGw6v3H2fQHDI2sdHzezvb4ZZbmxvN2ywrIkCAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECixcQCFm8aXtGFAhpz15bKQECBAhUT0AgpHp7YkYECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQGCFAgIhK8Ru3K0EQhq3pRZEgAABAjUUmLYZZNDUka1x+H7W8JEXfgyrPkZGyBtCup14Yr9TfDI4IR9QQ0gNHzdTJnAggb3v+vRXyYEGcjEBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAnsCAiGeg/kFBELmt3MlAQIECBBYlIBAyKIkjUOAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBGolIBBSq+2q2GQFQiq2IaZDgAABAq0SKGv8SAiTPh9i5c0dJYylTR+j5/f72XglzSKDq4rze7vb4a2tzY1W7aPFEiBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBOQUEQuaEc1mn0xEI8RgQIECAAIH1CUwKfEz6fDhzgZD17aI7EyBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBuQUEQuamc6FAiGeAAAECBAisUaCbEh+d+IexZo6Jc5syCJLGKU7vd+IfBrcvPk/vj922tFlk9P69HQ0hE7fMCQQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAYCggEOJpmF9AQ8j8dq4kQIAAAQIHFRAIOaig6wkQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQK0FBEJqvX1rnrxAyJo3wO0JECBAoNUCKRDSL4o2UmNHWe9H+ecTmkLSx4MBJjWLxM/TvAZVIoM3sm0r3u/taghp9QNt8QQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECAwq4BAyKxizh8KCIR4GggQIECAwPoEBELWZ6Wn4Y0AACAASURBVO/OBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECgAgICIRXYhNpOQSCktltn4gQIECDQQIEUEElLKyvkKF96bPboFpUegx6QsQqS/RtC+p10fbzD4LKiWaSvIaSBT50lESBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgMAaBQRC1ohf+1sLhNR+Cy2AAAECBBokIBDSoM20FAIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgMFlAIGSykTPKBARCPBsECBAgQKBJAlnzR3pZNHwMVpo3faQkylgDSBxgrGkkJyuu6+1uh0+2NjeahGotBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIEBgWQICIcuSbcO4AiFt2GVrJECAAIH2CAiEtGevrZQAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEGiAgENKATVzbEgRC1kbvxgQIECBAYCCQCjrSG2NFHVNbZYGQwYD5APuf1x92gcQLxk4rua6Y8FENIVPvlBMJECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEDxk3r97nM2XlvyE4CQCJQLCIR4OggQIECAwPoFBELWvwdmQIAAAQIECBAgQIAAAQIECBBousDe/5HYbfoirY8AAQIECBAgQIBA5QX8J/PKb5EJEiBAgAABAgRWL6AhZPXmzbmjQEhz9tJKCBAgQKA5Aun/mC9L+45/frBccH9QSTKpYWT083RdCrT0drbDJmxtbjRnM6yEAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgsT0AgZHm2zR9ZIKT5e2yFBAgQIFA/AYGQ+u2ZGRMgQIAAAQIEaiPgFxDWZqtMlAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBVggIhLRim5e0SIGQJcEalgABAgQIzCDQLSo2hk0dM1wcTp2xIaRoBElX5QGUbjHeoDhkcEJ+n+J1cejtagiZdeecT4AAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQKtFhAIafX2H3DxAiEHBHQ5AQIECBBYgIBAyAIQDUGAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBOonIBBSvz2rzowFQqqzF2ZCgAABAu0TGGvmKN4YNHNMSTIcJ2vwyAs90olp3OxGw9NHmz/GpxE/T40mRcFJp7ejIWTKLXMaAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBDYExAI8RzMLyAQMr+dKwkQIECAwEEFBEIOKuh6AgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBQawGBkFpv35onLxCy5g1wewIECBAgcBqB1LxR3hiSV4Dkg2WRk5KBxppB8mEGJ+QNJKOve7saQjzQBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIEBgBgGBkBmwnJoJCIR4JAgQIECAQHUFBEKquzdmRoAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEFiAgELIAxNYOIRDS2q23cAIECBBopMCExpCxj/c/v9+J73c7sWGkP2gWGT1/+H7EPKohpJFPlUURIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAwNIEBEKWRtuCgQVCWrDJlkiAAAECLRIQCGnRZlsqAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECNRfQCCk/nu4vhUIhKzP3p0JECBAgECZQDcWc3QGxRxTU00bCDl9M8jgdlM2inSKRpHezna4dGtzY+oZO5EAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECLRYQCCkxZt/4KULhByY0AAECBAgQGDhAgIhCyc1IAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQqKKAQEgVd6UucxIIqctOmScBAgQIEDidQKryKKpFisaOsSsGjR+nbwgZG6W0qmR0HA0hnlICBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIDCTgEDITFxOHhEQCPFAECBAgACBJggIhDRhF62BAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBFonIBDSui1f4IIFQhaIaSgCBAgQIDCjQGriSJflvR3d4oRU0FHe/5FdmedD8hsMBjp9U8hgOaUNIaMDawiZ8QFwOgECBAgQIECAAAECBAgQIECAAAEC/397d7Mby3aWAbhro4CEGARGJ0igDIOZBMkDPABFIsMTiUsgFwEjtglwA4ipDUgwat8EXAKQMXAJzEGkUHevVe1a1eXqLlfXz1rPkSK7q1atn+erRPvs+PVHgAABAgQIECBAgAABAgQIECBAgEDpAgIhpb8Bnzm/QMhn9DxLgAABAgQ+JyAQ8jk/TxMgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgY0LCIRsvICLbl8gZFF+ixMgQIAAgYkFejqFxFU6CZTT+PNTA8+/G3maMowPX/avz8erT48PE5/LdAQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAIEsBgZAsyzrToQRCZoK2DAECBAgQmEVAIGQWZosQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIFpBARCpnEscxaBkDLr7tQECBAgkIdAbPhRN507wpU6BEM6HUHiudvBkb4OIdXuNEEzf5y34QsdRsIEbzqE5PFiOQUBAusXOPzvbvq/8evftR0SIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAh0BQRCvBXjBQRCxtt5kgABAgQILC0gELJ0BaxPgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQ+JSAQ8im+wh8WCCn8BXB8AgQIEMhEoN3xY+hQdej00f3l8u2OH53fPq9DyBCt+wQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBA4BYBgZBbtIxtCwiEeCMIECBAgEAOAgIhOVTRGQgQ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CBDYgIBAyAaKtNotCoSstjQ2RoAAAQIFCgiEFFh0RyZAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBkgUEQkqu/mfPLhDyWUHPEyBAgACBNQqEzh1pQ48q2Wt9uWPIbpc82NshJMwX5tm/6hCyxrfBnggQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIEBgtQICIastzQY2JhCygSLZIgECBAgQuFlAIORmMg8QIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIH5BQRC5jfPZ0WBkHxq6SQECBAgkJ9AVZ1aetRJJ4/Y6OPcxyN+171zmiDanL5JR7fv7nbnWdJOIWnLkTBfuPymQ0h+L6ETESBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgMA9BQRC7qmb+9wCIblX2PkIECBAYMsCAiFbrp69EyBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBQQGBkEEiA3oFBEK8HAQIECBAYDmBGPjYhQ4gaf+NdGehYUgcfmHjAzMknUbS0bEzSOwhkgx/32okrB1mCF/2OoQs9zJZmQABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgS2KCAQssWqrWXPAiFrqYR9ECBAgECJAgIhJVbdmQkQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQCMgEOJlGC8gEDLezpMECBAgQODeAsMdQdIdxJ4foddH2uKjuXwad+4I0p6n7rQGGepdcrq/f3k+fn16fLg3jfkJECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEAOAgIhOVRxqTMIhCwlb10CBAgQIDAsIBAybGQEAQIECKxD4PA3E31Bw3Xs0C4IECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAqsUEAhZZVk2simBkI0UyjYJECBA4EaBw4+kDnW1uHHKVQ5POoKkZ+4Q9JiE5Mm5M0i7g8i5YUj6fPuzDiGrfElsigABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgTWKyAQst7a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AAECBAgQIECAAAECBAgQILCogEDIovwbX1wgZOMFtH0CBAgQ2LRAVbWjHp3GHKNPN9AhpAr3k2Hnj+37cZvn/bUfrMONNx1CRlfMgwQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBQpIBASJFln+jQAiETQZqGAAECBAiMEBAIGYHmEQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgkI+AQEg+tZz/JAIh85tbkQABAgQIDAl0O3IMPRHv93QG6dy+PK7eXW4Zct7P5c4g8f7+5fm40tPjw7UbNo4AAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECJQsIBBScvU/e3aBkM8Kep4AAQIECEwvIBAyvakZCRAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIDACgUEQlZYlM1sSSBkM6WyUQIECBAoUCAGQ2LjjoH+H++EwsjOA+FCT+Kkr0PIeeJ0wvY6+1cdQgp8TR2ZAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBMYLCISMt/OkQIh3gAABAgQIrFdAIGS9tbEzAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECAwgYBAyASIxU4hEFJs6R2cAAECBFYgkDbq6GncccNOe3qINJfb99PRVVgpdgo5f443dAi5oRiGEiBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBIQGBkCEh9/sFBEK8HQQIECBAYDkBgZDl7K1MgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgRWICAQsoIibHYLAiGbLZ2NEyBAgEAGAlVIhNT1qfPG+A4hPZ1BolG8HVp+xPXi7bQzSEPb01nkTN9ed//yfLz19PiQQXUcgQABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQI3F1AIOTuxBkvIBCScXEdjQABAgRWLyAQsvoS2SABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAYHKBw0/+xl/gOfnkJiRAgACBrQkIhGytYmvar0DImqphLwQIECBQmkAaCInnP3fsuCzSvT/QISROk3QK2YXOJOfbPfMMdQoJ8+xfdQgp7R12XgIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBD4lIBAyKf4Cn9YIGSOF+DwY7tX/qDuHNuxBgECBAisRkAgZDWlsBECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQILCEgEDIEuq5rCkQkkslnYMAAQIEShL4dIeQEFRM44ppK9I6dhBpFuwLOJ6u7190CCnpPXRWAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEPi0gEDIpwkLnkAgpODiOzoBAgQIbFZAIGSzpbNxAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECDwXkAgxPswXkAgZLydJwkQIECAwNICVUiGxE4e1e50oQ4dQJr9xcYeSQuQpgNIOjAd3zQGaXcIadYN8+oQsvQbYX0CBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQ2JiAQMjGCraq7QqErKocNkOAAAECBG4SEAi5ictgAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECCwNgGBkLVVZEv7EQjZUrXslQABAgRKE6hi4iN0/KjbDTo+4EgGho9p55CkYUh/Z5G040iz8mniuK+31+fj56fHh9JK5bwECBAgQIAAAQIECBAgQIAAAQIEFhI4/B1d+jd9C23FsgQIECBAgAABAgQIECBAgAABAgQIjBEQCBmj5pmTgECIN4EAAQIECKxXQCBkvbWxMwIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgMIGAQMgEiMVOIRBSbOkdnAABAgSyFBhoIdJpHNK+EH+PYN3biiSdv2k9ctTc6xCS5VvlUAQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBwNwGBkLvRFjCxQEgBRXZEAgQIEChIQCCkoGI7KgECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAhsX0AgZPs1XO4EAiHL2VuZAAECBAhU1aknR39HjpNR07mjl6wnCBIvn1t/JDO0Zz7PMjDf7nKnkP3L83H+p8cHxd2UwKGe8V3Y1MZtlgABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAlsXEAjZegWX3L9AyJL61iZAgACB0gUEQkp/A5yfAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQOYCfjdZ5gV2PAIECBAgQIAAgQkEBEImQCx2CoGQYkvv4AQIECCQpcBtnT3SBiKRpI4dQDrTXe4MEp/TISTLl8qhCBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQOB+AgIh97PNf2aBkPxr7IQECBAgUJKAQEhJ1XZWAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIENi8gEDI5ku44AEEQhbEtzQBAgQIEBgpUIXnuvGPoUBIXPDyuG7HkNOVuhl+uUNIvP/2+nwc//T4MPJkHiNAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAkUJCIQUVe6JDysQMjGo6QgQIECAwAwCAiEzIFuCAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAjcX0Ag5P7G+a4gEJJvbZ2MAAECBLYnUIWkx7kjx9AZ0p4ePR1C4jS9nT7a69S7MLAzXd/8p+v7Fx1ChirmPgECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQeCcgEOJ1GC8gEDLezpMECBAgQGBqAYGQqUXNR4AAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEVi0gELLq8qx8cwIhKy+Q7REgQIBAEQK3B0HaLKGxSOzrsdu9+641stN65PRk7AhynufGDiFh3v2rDiFFvLAOSYAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQJTCQiETCVZ4jwCISVW3ZkJECBAYG0CAiFrq4j9ECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBWQQEQmZhznQRgZBMC+tYBAgQIFCoQOjsMXT6pFNI96kbO4SEjiT7Fx1ChujdJ0CAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAEC7wQEQrwO4wUEQsbbeZIAAQIECKxPQCBkfTWxIwIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAg0CsgEOLlGC8gEDLezpMECBAgQGAqgSpMFOMcVbiQNPK4YrmeQEgzcVzo4+BIHTp+NAt2hscL7Rs6hFxRIkMIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgMBZQCDE2zBeQCBkvJ0nCRAgQIDAVAICIVNJmocAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECGxKQCBkU+Va2WYFQlZWENshQIAAgSIF0kBIRKhCq5D66lYhAx1CwsSxA0gniNLcjztI5ms+Xl5n//L1+ODT40ORdXRoAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBwo4BAyI1ghr8TEAjxOhAgQIAAgeUFBEKWr4EdECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACBBQQEQhZAz2ZJgZBsSukgBAgQIFC0QOzY0RMt6dxud/jo3u7pNLL7+Pr+5flYBR1Cin4ZHZ4AAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIErhcQCLneyshUQCDEO0GAAAECBHIQEAjJoYrOQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQLFCZwCIb/5uz/578PR63r33eIIHHi0gEDIaDoPEiBAgACBTwv09PPozHvtuF1vB4+erdanIEm370f7SrN+czl94vRZh5BPvxImIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAIGyBA45EIGQsmo+3WkFQqazNBMBAgQIELhV4Nqgx7XjBEJurYDxBAgQIECAAAECBLYocAhkx39L2OL+7ZkAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgXcCp0DI937n238+XqyqH+EhcK2AQMi1UsYRIECAAIHpBarq9ENcdejUEVcIlw+d30b+c+WDvQsknUM6012ef//y9bjfp8eHkfv2GAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQKErgkAMRCCmq5BMeViBkQkxTESBAgACBGwUEQm4EM5wAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECOQlcAqEfPPw7c8O56p21V/kdT6nuaeAQMg9dc1NgAABAgSmFTj1E9nt+vt/DHQGSW7XYaY4b7rbtHPJ+b4OIdNW1mwECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgUJjA4QfxDv/56eHcAiGFVX+q4wqETCVpHgIECBAgcH8BgZD7G1uBAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAjMIPA+EFJX3//hH3/3sOj//O///efha13vfn2GTVhi4wICIRsvoO0TIECAQN4CVYiA1AOdPzoKPeM7l4fmPd1vlu9NpJzGvb18PX79/ceHvOvidAQIECBAgAABAgQIECBAgACBIgUOfwfU12u2SBCHJkCAAAECBAgQIJC3gH8FyLu+TkeAAAECBAisQeC/QoeQPzps5n0g5D8OF+p69xtr2KU9rFtAIGTd9bE7AgQIEChcQCCk8BfA8QkQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIEAgU4FjI5DdbncKhMRDfvODn/zJ8cKX3T9kenDHmlBAIGRCTFMRIECAAIGRAjH3sQsNO4b6dgwvk0zU09mjDgv2Nv6IGwoLNuOaDbZ3ug8dQp50CBkukREECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgUKLA4QfvDv/56eHwVVX94/FrlBAIKfGdGH9mgZDxdp4kQIAAAQJTCQiETCVpHgIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgsGqBX4TdHQIhdVVV/3T43ARC4ta/efj2Z6cb1XO41hmz6mPa3CwCAiGzMFuEAAECBAhcFIhBkPrzLUHC/HGi/p4fx4Fhwb7RnafjBtMbyef9y58fp9chxAtPgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgRaArEzyF8drlZV9Zfv734UCPkaBn4BSiAVEAjxThAgQIAAgeUEBEKWs7cyAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBCYUSB2BjkEQg6dQY7BkPhPb/ePb37w7Y8Og6qq+vvj4Gr3/fCQjiEzVm+tSwmErLUy9kWAAAECJQkM9PO4geLGViPN8PZz50+n76rQjK7ehTs9z+1fTjlkHUJuKJmhBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIJCTQPqDfP8SDhc7g8TPrTMPB0K+VH8XnoiBEB1DcnptRp5FIGQknMcIECBAgMCEAgIhE2KaigABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQILCcQO4HEHcQAyF8fLlRVdVsgJD1H7Biy+7I7dg7Z7arfXu6sVl5a4Du//KvHLXz58ktLb8X6BAgQIECgHIG+WG78Y+C1sd30j41RMD6fztc3vk9+YPwvQseQuNzr3/7ZcaYf/8EPy6mlkxIgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgbPAv4Zv//3wtS8AkoL1dghJB3YDIbvfol+uwHd+5deOh/9SXf0KlYvl5AQIECBAYCqBrQRC0sZ1yfnTvMnr3/zpccSP//D3ppIyDwECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQ2JLAv+12x9+2/PPDpq8NhPw/dEGvq9Nty/YAAAAASUVORK5CYII=\",\"u\":\"\",\"w\":3204,\"e\":1},{\"h\":1572,\"id\":\"_W0-LMkQeoucVkRQawwsW\",\"p\":\"data:image/png;base64,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\",\"u\":\"\",\"w\":1660,\"e\":1}],\"ddd\":0,\"fr\":60,\"h\":1493,\"ip\":0,\"layers\":[{\"ddd\":0,\"ind\":20,\"ty\":0,\"nm\":\"\",\"ln\":\"precomp_342:10389320\",\"sr\":1,\"ks\":{\"a\":{\"a\":0,\"k\":[0,0]},\"o\":{\"a\":0,\"k\":100},\"p\":{\"a\":0,\"k\":[0,0]},\"r\":{\"a\":0,\"k\":0},\"s\":{\"a\":0,\"k\":[100,100]},\"sk\":{\"a\":0,\"k\":0},\"sa\":{\"a\":0,\"k\":0}},\"ao\":0,\"ef\":[{\"ty\":25,\"nm\":\"Drop Shadow\",\"ix\":0,\"ef\":[{\"ty\":2,\"nm\":\"\",\"ix\":0,\"v\":{\"a\":0,\"k\":[0,0,0]}},{\"ty\":0,\"nm\":\"\",\"ix\":0,\"v\":{\"a\":0,\"k\":20.4}},{\"ty\":1,\"nm\":\"\",\"ix\":0,\"v\":{\"a\":0,\"k\":180}},{\"ty\":0,\"nm\":\"\",\"ix\":0,\"v\":{\"a\":0,\"k\":24}},{\"ty\":0,\"nm\":\"\",\"ix\":0,\"v\":{\"a\":0,\"k\":96}}],\"en\":1}],\"w\":7660,\"h\":7660,\"ip\":0,\"op\":480,\"st\":0,\"bm\":0,\"refId\":\"D5x4S3KigyY39QhXclbk-\"},{\"ddd\":0,\"ind\":2,\"ty\":2,\"nm\":\"\",\"ln\":\"image_342:103892\",\"sr\":1,\"ks\":{\"a\":{\"a\":0,\"k\":[96,96]},\"o\":{\"a\":1,\"k\":[{\"t\":0,\"s\":[0],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":153,\"s\":[0],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":154,\"s\":[2.48],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":155,\"s\":[8.5],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":156,\"s\":[14.26],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":157,\"s\":[19.78],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":158,\"s\":[25.06],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":159,\"s\":[30.1],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":160,\"s\":[34.9],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":161,\"s\":[39.49],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":162,\"s\":[43.85],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":163,\"s\":[48],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":164,\"s\":[51.93],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":165,\"s\":[55.67],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":166,\"s\":[59.2],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":167,\"s\":[62.55],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":168,\"s\":[65.7],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":169,\"s\":[68.67],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":170,\"s\":[71.47],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":171,\"s\":[74.09],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":172,\"s\":[76.55],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":173,\"s\":[78.85],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":174,\"s\":[80.99],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":175,\"s\":[82.98],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":176,\"s\":[84.83],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":177,\"s\":[86.54],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":178,\"s\":[88.11],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":179,\"s\":[89.56],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":180,\"s\":[90.89],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":181,\"s\":[92.1],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":182,\"s\":[93.19],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":183,\"s\":[94.18],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":184,\"s\":[95.07],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":185,\"s\":[95.86],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":186,\"s\":[96.57],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":187,\"s\":[97.19],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":188,\"s\":[97.73],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":189,\"s\":[98.19],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":190,\"s\":[98.59],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":191,\"s\":[98.92],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":192,\"s\":[99.2],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":193,\"s\":[99.42],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":194,\"s\":[99.6],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":195,\"s\":[99.74],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":196,\"s\":[99.84],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":197,\"s\":[99.91],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":198,\"s\":[99.96],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":199,\"s\":[99.98],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":200,\"s\":[100],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":433,\"s\":[100],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":434,\"s\":[99.98],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":435,\"s\":[99.96],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":436,\"s\":[99.91],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":437,\"s\":[99.84],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":438,\"s\":[99.74],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":439,\"s\":[99.6],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":440,\"s\":[99.42],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":441,\"s\":[99.2],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":442,\"s\":[98.92],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":443,\"s\":[98.59],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":444,\"s\":[98.19],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":445,\"s\":[97.73],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":446,\"s\":[97.19],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":447,\"s\":[96.57],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":448,\"s\":[95.86],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":449,\"s\":[95.07],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":450,\"s\":[94.18],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":451,\"s\":[93.19],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":452,\"s\":[92.1],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":453,\"s\":[90.89],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":454,\"s\":[89.56],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":455,\"s\":[88.11],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":456,\"s\":[86.54],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":457,\"s\":[84.83],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":458,\"s\":[82.98],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":459,\"s\":[80.99],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":460,\"s\":[78.85],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":461,\"s\":[76.55],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":462,\"s\":[74.09],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":463,\"s\":[71.47],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":464,\"s\":[68.67],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":465,\"s\":[65.7],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":466,\"s\":[62.55],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":467,\"s\":[59.2],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":468,\"s\":[55.67],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":469,\"s\":[51.93],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":470,\"s\":[48],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":471,\"s\":[43.85],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":472,\"s\":[39.49],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":473,\"s\":[34.9],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":474,\"s\":[30.1],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":475,\"s\":[25.06],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":476,\"s\":[19.78],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":477,\"s\":[14.26],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":478,\"s\":[8.5],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":479,\"s\":[2.48],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}}]},\"p\":{\"a\":0,\"k\":[1040,1304]},\"r\":{\"a\":0,\"k\":0},\"s\":{\"a\":1,\"k\":[{\"t\":0,\"s\":[25,25],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":153,\"s\":[25,25],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":154,\"s\":[25.62,25.62],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":155,\"s\":[27.12,27.12],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":156,\"s\":[28.57,28.57],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":157,\"s\":[29.95,29.95],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":158,\"s\":[31.26,31.26],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":159,\"s\":[32.52,32.52],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":160,\"s\":[33.73,33.73],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":161,\"s\":[34.87,34.87],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":162,\"s\":[35.96,35.96],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":163,\"s\":[37,37],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":164,\"s\":[37.98,37.98],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":165,\"s\":[38.92,38.92],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":166,\"s\":[39.8,39.8],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":167,\"s\":[40.64,40.64],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":168,\"s\":[41.42,41.42],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":169,\"s\":[42.17,42.17],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":170,\"s\":[42.87,42.87],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":171,\"s\":[43.52,43.52],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":172,\"s\":[44.14,44.14],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":173,\"s\":[44.71,44.71],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":174,\"s\":[45.25,45.25],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":175,\"s\":[45.75,45.75],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":176,\"s\":[46.21,46.21],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":177,\"s\":[46.63,46.63],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":178,\"s\":[47.03,47.03],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":179,\"s\":[47.39,47.39],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":180,\"s\":[47.72,47.72],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":181,\"s\":[48.02,48.02],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":182,\"s\":[48.3,48.3],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":183,\"s\":[48.55,48.55],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":184,\"s\":[48.77,48.77],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":185,\"s\":[48.97,48.97],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":186,\"s\":[49.14,49.14],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":187,\"s\":[49.3,49.3],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":188,\"s\":[49.43,49.43],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":189,\"s\":[49.55,49.55],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":190,\"s\":[49.65,49.65],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":191,\"s\":[49.73,49.73],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":192,\"s\":[49.8,49.8],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":193,\"s\":[49.86,49.86],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":194,\"s\":[49.9,49.9],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":195,\"s\":[49.94,49.94],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":196,\"s\":[49.96,49.96],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":197,\"s\":[49.98,49.98],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":198,\"s\":[49.99,49.99],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":199,\"s\":[50,50],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":434,\"s\":[50,50],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":435,\"s\":[49.99,49.99],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":436,\"s\":[49.98,49.98],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":437,\"s\":[49.96,49.96],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":438,\"s\":[49.94,49.94],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":439,\"s\":[49.9,49.9],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":440,\"s\":[49.86,49.86],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":441,\"s\":[49.8,49.8],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":442,\"s\":[49.73,49.73],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":443,\"s\":[49.65,49.65],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":444,\"s\":[49.55,49.55],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":445,\"s\":[49.43,49.43],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":446,\"s\":[49.3,49.3],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":447,\"s\":[49.14,49.14],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":448,\"s\":[48.97,48.97],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":449,\"s\":[48.77,48.77],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":450,\"s\":[48.55,48.55],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":451,\"s\":[48.3,48.3],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":452,\"s\":[48.02,48.02],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":453,\"s\":[47.72,47.72],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":454,\"s\":[47.39,47.39],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":455,\"s\":[47.03,47.03],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":456,\"s\":[46.63,46.63],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":457,\"s\":[46.21,46.21],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":458,\"s\":[45.75,45.75],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":459,\"s\":[45.25,45.25],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":460,\"s\":[44.71,44.71],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":461,\"s\":[44.14,44.14],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":462,\"s\":[43.52,43.52],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":463,\"s\":[42.87,42.87],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":464,\"s\":[42.17,42.17],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":465,\"s\":[41.42,41.42],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":466,\"s\":[40.64,40.64],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":467,\"s\":[39.8,39.8],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":468,\"s\":[38.92,38.92],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":469,\"s\":[37.98,37.98],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":470,\"s\":[37,37],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":471,\"s\":[35.96,35.96],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":472,\"s\":[34.87,34.87],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":473,\"s\":[33.73,33.73],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":474,\"s\":[32.52,32.52],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":475,\"s\":[31.26,31.26],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":476,\"s\":[29.95,29.95],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":477,\"s\":[28.57,28.57],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":478,\"s\":[27.12,27.12],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":479,\"s\":[25.62,25.62],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}}]},\"sk\":{\"a\":0,\"k\":0},\"sa\":{\"a\":0,\"k\":0}},\"ao\":0,\"ip\":0,\"op\":480,\"st\":0,\"bm\":0,\"refId\":\"1Da0nr0h0jPcTBwhSZmHH\"},{\"ddd\":0,\"ind\":3,\"ty\":0,\"nm\":\"\",\"ln\":\"precomp_342:1038903\",\"sr\":1,\"ks\":{\"a\":{\"a\":0,\"k\":[40,40]},\"o\":{\"a\":1,\"k\":[{\"t\":0,\"s\":[0],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":105,\"s\":[0],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":106,\"s\":[2.48],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":107,\"s\":[8.5],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":108,\"s\":[14.26],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":109,\"s\":[19.78],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":110,\"s\":[25.06],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":111,\"s\":[30.1],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":112,\"s\":[34.9],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":113,\"s\":[39.49],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":114,\"s\":[43.85],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":115,\"s\":[48],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":116,\"s\":[51.93],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":117,\"s\":[55.67],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":118,\"s\":[59.2],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":119,\"s\":[62.55],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":120,\"s\":[65.7],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":121,\"s\":[68.67],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":122,\"s\":[71.47],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":123,\"s\":[74.09],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":124,\"s\":[76.55],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":125,\"s\":[78.85],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":126,\"s\":[80.99],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":127,\"s\":[82.98],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":128,\"s\":[84.83],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":129,\"s\":[86.54],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":130,\"s\":[88.11],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":131,\"s\":[89.56],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":132,\"s\":[90.89],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":133,\"s\":[92.1],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":134,\"s\":[93.19],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":135,\"s\":[94.18],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":136,\"s\":[95.07],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":137,\"s\":[95.86],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":138,\"s\":[96.57],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":139,\"s\":[97.19],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":140,\"s\":[97.73],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":141,\"s\":[98.19],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":142,\"s\":[98.59],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":143,\"s\":[98.92],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":144,\"s\":[99.2],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":145,\"s\":[99.42],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":146,\"s\":[99.6],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":147,\"s\":[99.74],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":148,\"s\":[99.84],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":149,\"s\":[99.91],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":150,\"s\":[99.96],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":151,\"s\":[99.98],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":152,\"s\":[100],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":433,\"s\":[100],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":434,\"s\":[99.98],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":435,\"s\":[99.96],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":436,\"s\":[99.91],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":437,\"s\":[99.84],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":438,\"s\":[99.74],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":439,\"s\":[99.6],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":440,\"s\":[99.42],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":441,\"s\":[99.2],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":442,\"s\":[98.92],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":443,\"s\":[98.59],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":444,\"s\":[98.19],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":445,\"s\":[97.73],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":446,\"s\":[97.19],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":447,\"s\":[96.57],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":448,\"s\":[95.86],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":449,\"s\":[95.07],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":450,\"s\":[94.18],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":451,\"s\":[93.19],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":452,\"s\":[92.1],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":453,\"s\":[90.89],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":454,\"s\":[89.56],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":455,\"s\":[88.11],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":456,\"s\":[86.54],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":457,\"s\":[84.83],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":458,\"s\":[82.98],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":459,\"s\":[80.99],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":460,\"s\":[78.85],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":461,\"s\":[76.55],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":462,\"s\":[74.09],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":463,\"s\":[71.47],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":464,\"s\":[68.67],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":465,\"s\":[65.7],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":466,\"s\":[62.55],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":467,\"s\":[59.2],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":468,\"s\":[55.67],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":469,\"s\":[51.93],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":470,\"s\":[48],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":471,\"s\":[43.85],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":472,\"s\":[39.49],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":473,\"s\":[34.9],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":474,\"s\":[30.1],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":475,\"s\":[25.06],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":476,\"s\":[19.78],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":477,\"s\":[14.26],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":478,\"s\":[8.5],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":479,\"s\":[2.48],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}}]},\"p\":{\"a\":0,\"k\":[40,786]},\"r\":{\"a\":0,\"k\":0},\"s\":{\"a\":1,\"k\":[{\"t\":0,\"s\":[50,50],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":105,\"s\":[50,50],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":106,\"s\":[51.24,51.24],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":107,\"s\":[54.25,54.25],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":108,\"s\":[57.13,57.13],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":109,\"s\":[59.89,59.89],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":110,\"s\":[62.53,62.53],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":111,\"s\":[65.05,65.05],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":112,\"s\":[67.45,67.45],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":113,\"s\":[69.74,69.74],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":114,\"s\":[71.92,71.92],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":115,\"s\":[74,74],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":116,\"s\":[75.97,75.97],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":117,\"s\":[77.83,77.83],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":118,\"s\":[79.6,79.6],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":119,\"s\":[81.27,81.27],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":120,\"s\":[82.85,82.85],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":121,\"s\":[84.34,84.34],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":122,\"s\":[85.73,85.73],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":123,\"s\":[87.05,87.05],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":124,\"s\":[88.27,88.27],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":125,\"s\":[89.42,89.42],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":126,\"s\":[90.49,90.49],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":127,\"s\":[91.49,91.49],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":128,\"s\":[92.41,92.41],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":129,\"s\":[93.27,93.27],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":130,\"s\":[94.06,94.06],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":131,\"s\":[94.78,94.78],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":132,\"s\":[95.44,95.44],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":133,\"s\":[96.05,96.05],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":134,\"s\":[96.6,96.6],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":135,\"s\":[97.09,97.09],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":136,\"s\":[97.54,97.54],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":137,\"s\":[97.93,97.93],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":138,\"s\":[98.28,98.28],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":139,\"s\":[98.59,98.59],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":140,\"s\":[98.86,98.86],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":141,\"s\":[99.1,99.1],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":142,\"s\":[99.29,99.29],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":143,\"s\":[99.46,99.46],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":144,\"s\":[99.6,99.6],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":145,\"s\":[99.71,99.71],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":146,\"s\":[99.8,99.8],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":147,\"s\":[99.87,99.87],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":148,\"s\":[99.92,99.92],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":149,\"s\":[99.96,99.96],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":150,\"s\":[99.98,99.98],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":151,\"s\":[99.99,99.99],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":152,\"s\":[100,100],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":433,\"s\":[100,100],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":434,\"s\":[99.99,99.99],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":435,\"s\":[99.98,99.98],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":436,\"s\":[99.96,99.96],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":437,\"s\":[99.92,99.92],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":438,\"s\":[99.87,99.87],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":439,\"s\":[99.8,99.8],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":440,\"s\":[99.71,99.71],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":441,\"s\":[99.6,99.6],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":442,\"s\":[99.46,99.46],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":443,\"s\":[99.29,99.29],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":444,\"s\":[99.1,99.1],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":445,\"s\":[98.86,98.86],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":446,\"s\":[98.59,98.59],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":447,\"s\":[98.28,98.28],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":448,\"s\":[97.93,97.93],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":449,\"s\":[97.54,97.54],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":450,\"s\":[97.09,97.09],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":451,\"s\":[96.6,96.6],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":452,\"s\":[96.05,96.05],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":453,\"s\":[95.44,95.44],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":454,\"s\":[94.78,94.78],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":455,\"s\":[94.06,94.06],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":456,\"s\":[93.27,93.27],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":457,\"s\":[92.41,92.41],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":458,\"s\":[91.49,91.49],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":459,\"s\":[90.49,90.49],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":460,\"s\":[89.42,89.42],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":461,\"s\":[88.27,88.27],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":462,\"s\":[87.05,87.05],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":463,\"s\":[85.73,85.73],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":464,\"s\":[84.34,84.34],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":465,\"s\":[82.85,82.85],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":466,\"s\":[81.27,81.27],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":467,\"s\":[79.6,79.6],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":468,\"s\":[77.83,77.83],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":469,\"s\":[75.97,75.97],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":470,\"s\":[74,74],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":471,\"s\":[71.92,71.92],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":472,\"s\":[69.74,69.74],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":473,\"s\":[67.45,67.45],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":474,\"s\":[65.05,65.05],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":475,\"s\":[62.53,62.53],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":476,\"s\":[59.89,59.89],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":477,\"s\":[57.13,57.13],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":478,\"s\":[54.25,54.25],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":479,\"s\":[51.24,51.24],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}}]},\"sk\":{\"a\":0,\"k\":0},\"sa\":{\"a\":0,\"k\":0}},\"ao\":0,\"w\":80,\"h\":80,\"ip\":0,\"op\":480,\"st\":0,\"bm\":0,\"refId\":\"5ZyVIkmClEMYgN_sjgBH-\"},{\"ddd\":0,\"ind\":7,\"ty\":0,\"nm\":\"\",\"ln\":\"precomp_342:1038887\",\"sr\":1,\"ks\":{\"a\":{\"a\":0,\"k\":[40,40]},\"o\":{\"a\":1,\"k\":[{\"t\":0,\"s\":[0],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":57,\"s\":[0],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":58,\"s\":[2.48],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":59,\"s\":[8.5],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":60,\"s\":[14.26],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":61,\"s\":[19.78],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":62,\"s\":[25.06],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":63,\"s\":[30.1],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":64,\"s\":[34.9],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":65,\"s\":[39.49],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":66,\"s\":[43.85],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":67,\"s\":[48],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":68,\"s\":[51.93],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":69,\"s\":[55.67],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":70,\"s\":[59.2],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":71,\"s\":[62.55],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":72,\"s\":[65.7],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":73,\"s\":[68.67],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":74,\"s\":[71.47],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":75,\"s\":[74.09],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":76,\"s\":[76.55],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":77,\"s\":[78.85],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":78,\"s\":[80.99],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":79,\"s\":[82.98],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":80,\"s\":[84.83],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":81,\"s\":[86.54],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":82,\"s\":[88.11],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":83,\"s\":[89.56],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":84,\"s\":[90.89],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":85,\"s\":[92.1],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":86,\"s\":[93.19],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":87,\"s\":[94.18],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":88,\"s\":[95.07],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":89,\"s\":[95.86],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":90,\"s\":[96.57],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":91,\"s\":[97.19],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":92,\"s\":[97.73],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":93,\"s\":[98.19],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":94,\"s\":[98.59],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":95,\"s\":[98.92],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":96,\"s\":[99.2],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":97,\"s\":[99.42],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":98,\"s\":[99.6],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":99,\"s\":[99.74],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":100,\"s\":[99.84],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":101,\"s\":[99.91],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":102,\"s\":[99.96],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":103,\"s\":[99.98],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":104,\"s\":[100],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":433,\"s\":[100],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":434,\"s\":[99.98],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":435,\"s\":[99.96],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":436,\"s\":[99.91],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":437,\"s\":[99.84],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":438,\"s\":[99.74],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":439,\"s\":[99.6],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":440,\"s\":[99.42],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":441,\"s\":[99.2],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":442,\"s\":[98.92],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":443,\"s\":[98.59],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":444,\"s\":[98.19],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":445,\"s\":[97.73],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":446,\"s\":[97.19],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":447,\"s\":[96.57],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":448,\"s\":[95.86],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":449,\"s\":[95.07],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":450,\"s\":[94.18],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":451,\"s\":[93.19],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":452,\"s\":[92.1],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":453,\"s\":[90.89],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":454,\"s\":[89.56],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":455,\"s\":[88.11],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":456,\"s\":[86.54],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":457,\"s\":[84.83],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":458,\"s\":[82.98],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":459,\"s\":[80.99],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":460,\"s\":[78.85],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":461,\"s\":[76.55],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":462,\"s\":[74.09],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":463,\"s\":[71.47],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":464,\"s\":[68.67],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":465,\"s\":[65.7],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":466,\"s\":[62.55],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":467,\"s\":[59.2],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":468,\"s\":[55.67],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":469,\"s\":[51.93],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":470,\"s\":[48],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":471,\"s\":[43.85],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":472,\"s\":[39.49],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":473,\"s\":[34.9],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":474,\"s\":[30.1],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":475,\"s\":[25.06],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":476,\"s\":[19.78],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":477,\"s\":[14.26],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":478,\"s\":[8.5],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":479,\"s\":[2.48],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}}]},\"p\":{\"a\":0,\"k\":[570,40]},\"r\":{\"a\":0,\"k\":0},\"s\":{\"a\":1,\"k\":[{\"t\":0,\"s\":[50,50],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":57,\"s\":[50,50],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":58,\"s\":[51.24,51.24],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":59,\"s\":[54.25,54.25],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":60,\"s\":[57.13,57.13],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":61,\"s\":[59.89,59.89],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":62,\"s\":[62.53,62.53],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":63,\"s\":[65.05,65.05],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":64,\"s\":[67.45,67.45],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":65,\"s\":[69.74,69.74],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":66,\"s\":[71.92,71.92],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":67,\"s\":[74,74],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":68,\"s\":[75.97,75.97],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":69,\"s\":[77.83,77.83],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":70,\"s\":[79.6,79.6],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":71,\"s\":[81.27,81.27],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":72,\"s\":[82.85,82.85],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":73,\"s\":[84.34,84.34],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":74,\"s\":[85.73,85.73],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":75,\"s\":[87.05,87.05],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":76,\"s\":[88.27,88.27],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":77,\"s\":[89.42,89.42],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":78,\"s\":[90.49,90.49],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":79,\"s\":[91.49,91.49],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":80,\"s\":[92.41,92.41],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":81,\"s\":[93.27,93.27],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":82,\"s\":[94.06,94.06],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":83,\"s\":[94.78,94.78],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":84,\"s\":[95.44,95.44],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":85,\"s\":[96.05,96.05],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":86,\"s\":[96.6,96.6],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":87,\"s\":[97.09,97.09],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":88,\"s\":[97.54,97.54],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":89,\"s\":[97.93,97.93],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":90,\"s\":[98.28,98.28],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":91,\"s\":[98.59,98.59],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":92,\"s\":[98.86,98.86],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":93,\"s\":[99.1,99.1],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":94,\"s\":[99.29,99.29],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":95,\"s\":[99.46,99.46],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":96,\"s\":[99.6,99.6],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":97,\"s\":[99.71,99.71],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":98,\"s\":[99.8,99.8],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":99,\"s\":[99.87,99.87],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":100,\"s\":[99.92,99.92],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":101,\"s\":[99.96,99.96],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":102,\"s\":[99.98,99.98],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":103,\"s\":[99.99,99.99],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":104,\"s\":[100,100],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":433,\"s\":[100,100],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":434,\"s\":[99.99,99.99],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":435,\"s\":[99.98,99.98],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":436,\"s\":[99.96,99.96],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":437,\"s\":[99.92,99.92],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":438,\"s\":[99.87,99.87],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":439,\"s\":[99.8,99.8],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":440,\"s\":[99.71,99.71],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":441,\"s\":[99.6,99.6],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":442,\"s\":[99.46,99.46],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":443,\"s\":[99.29,99.29],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":444,\"s\":[99.1,99.1],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":445,\"s\":[98.86,98.86],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":446,\"s\":[98.59,98.59],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":447,\"s\":[98.28,98.28],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":448,\"s\":[97.93,97.93],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":449,\"s\":[97.54,97.54],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":450,\"s\":[97.09,97.09],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":451,\"s\":[96.6,96.6],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":452,\"s\":[96.05,96.05],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":453,\"s\":[95.44,95.44],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":454,\"s\":[94.78,94.78],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":455,\"s\":[94.06,94.06],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":456,\"s\":[93.27,93.27],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":457,\"s\":[92.41,92.41],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":458,\"s\":[91.49,91.49],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":459,\"s\":[90.49,90.49],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":460,\"s\":[89.42,89.42],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":461,\"s\":[88.27,88.27],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":462,\"s\":[87.05,87.05],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":463,\"s\":[85.73,85.73],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":464,\"s\":[84.34,84.34],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":465,\"s\":[82.85,82.85],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":466,\"s\":[81.27,81.27],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":467,\"s\":[79.6,79.6],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":468,\"s\":[77.83,77.83],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":469,\"s\":[75.97,75.97],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":470,\"s\":[74,74],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":471,\"s\":[71.92,71.92],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":472,\"s\":[69.74,69.74],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":473,\"s\":[67.45,67.45],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":474,\"s\":[65.05,65.05],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":475,\"s\":[62.53,62.53],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":476,\"s\":[59.89,59.89],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":477,\"s\":[57.13,57.13],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":478,\"s\":[54.25,54.25],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":479,\"s\":[51.24,51.24],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}}]},\"sk\":{\"a\":0,\"k\":0},\"sa\":{\"a\":0,\"k\":0}},\"ao\":0,\"w\":80,\"h\":80,\"ip\":0,\"op\":480,\"st\":0,\"bm\":0,\"refId\":\"VOuaAEdkBzNNKU0gWKJ-r\"},{\"ddd\":0,\"ind\":10,\"ty\":2,\"nm\":\"\",\"ln\":\"image_342:106119\",\"sr\":1,\"ks\":{\"a\":{\"a\":0,\"k\":[830,786]},\"o\":{\"a\":1,\"k\":[{\"t\":0,\"s\":[0],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":268,\"s\":[0],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":269,\"s\":[3.41],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":270,\"s\":[7.56],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":271,\"s\":[11.59],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":272,\"s\":[15.5],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":273,\"s\":[19.3],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":274,\"s\":[22.97],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":275,\"s\":[26.54],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":276,\"s\":[29.99],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":277,\"s\":[33.34],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":278,\"s\":[36.57],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":279,\"s\":[39.7],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":280,\"s\":[42.73],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":281,\"s\":[45.65],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":282,\"s\":[48.47],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":283,\"s\":[51.19],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":284,\"s\":[53.81],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":285,\"s\":[56.34],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":286,\"s\":[58.77],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":287,\"s\":[61.12],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":288,\"s\":[63.37],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":289,\"s\":[65.53],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":290,\"s\":[67.61],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":291,\"s\":[69.6],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":292,\"s\":[71.5],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":293,\"s\":[73.33],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":294,\"s\":[75.08],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":295,\"s\":[76.75],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":296,\"s\":[78.34],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":297,\"s\":[79.86],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":298,\"s\":[81.3],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":299,\"s\":[82.68],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":300,\"s\":[83.98],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":301,\"s\":[85.22],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":302,\"s\":[86.39],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":303,\"s\":[87.5],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":304,\"s\":[88.55],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":305,\"s\":[89.53],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":306,\"s\":[90.46],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":307,\"s\":[91.33],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":308,\"s\":[92.15],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":309,\"s\":[92.91],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":310,\"s\":[93.63],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":311,\"s\":[94.29],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":312,\"s\":[94.91],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":313,\"s\":[95.48],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":314,\"s\":[96],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":315,\"s\":[96.48],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":316,\"s\":[96.93],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":317,\"s\":[97.33],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":318,\"s\":[97.7],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":319,\"s\":[98.03],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":320,\"s\":[98.33],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":321,\"s\":[98.59],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":322,\"s\":[98.83],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":323,\"s\":[99.04],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":324,\"s\":[99.22],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":325,\"s\":[99.38],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":326,\"s\":[99.51],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":327,\"s\":[99.63],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":328,\"s\":[99.72],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":329,\"s\":[99.8],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":330,\"s\":[99.86],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":331,\"s\":[99.91],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":332,\"s\":[99.94],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":333,\"s\":[99.97],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":334,\"s\":[99.98],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":335,\"s\":[99.99],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":336,\"s\":[100],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":433,\"s\":[100],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":434,\"s\":[99.98],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":435,\"s\":[99.96],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":436,\"s\":[99.91],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":437,\"s\":[99.84],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":438,\"s\":[99.74],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":439,\"s\":[99.6],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":440,\"s\":[99.42],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":441,\"s\":[99.2],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":442,\"s\":[98.92],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":443,\"s\":[98.59],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":444,\"s\":[98.19],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":445,\"s\":[97.73],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":446,\"s\":[97.19],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":447,\"s\":[96.57],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":448,\"s\":[95.86],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":449,\"s\":[95.07],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":450,\"s\":[94.18],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":451,\"s\":[93.19],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":452,\"s\":[92.1],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":453,\"s\":[90.89],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":454,\"s\":[89.56],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":455,\"s\":[88.11],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":456,\"s\":[86.54],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":457,\"s\":[84.83],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":458,\"s\":[82.98],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":459,\"s\":[80.99],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":460,\"s\":[78.85],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":461,\"s\":[76.55],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":462,\"s\":[74.09],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":463,\"s\":[71.47],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":464,\"s\":[68.67],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":465,\"s\":[65.7],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":466,\"s\":[62.55],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":467,\"s\":[59.2],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":468,\"s\":[55.67],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":469,\"s\":[51.93],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":470,\"s\":[48],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":471,\"s\":[43.85],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":472,\"s\":[39.49],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":473,\"s\":[34.9],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":474,\"s\":[30.1],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":475,\"s\":[25.06],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":476,\"s\":[19.78],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":477,\"s\":[14.26],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":478,\"s\":[8.5],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":479,\"s\":[2.48],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}}]},\"p\":{\"a\":0,\"k\":[504,1100]},\"r\":{\"a\":0,\"k\":0},\"s\":{\"a\":1,\"k\":[{\"t\":0,\"s\":[25,25],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":268,\"s\":[25,25],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":269,\"s\":[25.85,25.85],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":270,\"s\":[26.89,26.89],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":271,\"s\":[27.9,27.9],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":272,\"s\":[28.88,28.88],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":273,\"s\":[29.82,29.82],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":274,\"s\":[30.74,30.74],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":275,\"s\":[31.63,31.63],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":276,\"s\":[32.5,32.5],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":277,\"s\":[33.33,33.33],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":278,\"s\":[34.14,34.14],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":279,\"s\":[34.93,34.93],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":280,\"s\":[35.68,35.68],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":281,\"s\":[36.41,36.41],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":282,\"s\":[37.12,37.12],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":283,\"s\":[37.8,37.8],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":284,\"s\":[38.45,38.45],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":285,\"s\":[39.09,39.09],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":286,\"s\":[39.69,39.69],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":287,\"s\":[40.28,40.28],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":288,\"s\":[40.84,40.84],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":289,\"s\":[41.38,41.38],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":290,\"s\":[41.9,41.9],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":291,\"s\":[42.4,42.4],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":292,\"s\":[42.88,42.88],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":293,\"s\":[43.33,43.33],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":294,\"s\":[43.77,43.77],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":295,\"s\":[44.19,44.19],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":296,\"s\":[44.58,44.58],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":297,\"s\":[44.96,44.96],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":298,\"s\":[45.33,45.33],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":299,\"s\":[45.67,45.67],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":300,\"s\":[46,46],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":301,\"s\":[46.3,46.3],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":302,\"s\":[46.6,46.6],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":303,\"s\":[46.88,46.88],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":304,\"s\":[47.14,47.14],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":305,\"s\":[47.38,47.38],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":306,\"s\":[47.62,47.62],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":307,\"s\":[47.83,47.83],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":308,\"s\":[48.04,48.04],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":309,\"s\":[48.23,48.23],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":310,\"s\":[48.41,48.41],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":311,\"s\":[48.57,48.57],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":312,\"s\":[48.73,48.73],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":313,\"s\":[48.87,48.87],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":314,\"s\":[49,49],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":315,\"s\":[49.12,49.12],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":316,\"s\":[49.23,49.23],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":317,\"s\":[49.33,49.33],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":318,\"s\":[49.42,49.42],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":319,\"s\":[49.51,49.51],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":320,\"s\":[49.58,49.58],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":321,\"s\":[49.65,49.65],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":322,\"s\":[49.71,49.71],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":323,\"s\":[49.76,49.76],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":324,\"s\":[49.81,49.81],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":325,\"s\":[49.84,49.84],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":326,\"s\":[49.88,49.88],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":327,\"s\":[49.91,49.91],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":328,\"s\":[49.93,49.93],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":329,\"s\":[49.95,49.95],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":330,\"s\":[49.96,49.96],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":331,\"s\":[49.98,49.98],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":332,\"s\":[49.99,49.99],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":333,\"s\":[49.99,49.99],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":334,\"s\":[50,50],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":434,\"s\":[50,50],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":435,\"s\":[49.99,49.99],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":436,\"s\":[49.98,49.98],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":437,\"s\":[49.96,49.96],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":438,\"s\":[49.94,49.94],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":439,\"s\":[49.9,49.9],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":440,\"s\":[49.86,49.86],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":441,\"s\":[49.8,49.8],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":442,\"s\":[49.73,49.73],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":443,\"s\":[49.65,49.65],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":444,\"s\":[49.55,49.55],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":445,\"s\":[49.43,49.43],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":446,\"s\":[49.3,49.3],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":447,\"s\":[49.14,49.14],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":448,\"s\":[48.97,48.97],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":449,\"s\":[48.77,48.77],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":450,\"s\":[48.55,48.55],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":451,\"s\":[48.3,48.3],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":452,\"s\":[48.02,48.02],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":453,\"s\":[47.72,47.72],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":454,\"s\":[47.39,47.39],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":455,\"s\":[47.03,47.03],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":456,\"s\":[46.63,46.63],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":457,\"s\":[46.21,46.21],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":458,\"s\":[45.75,45.75],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":459,\"s\":[45.25,45.25],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":460,\"s\":[44.71,44.71],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":461,\"s\":[44.14,44.14],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":462,\"s\":[43.52,43.52],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":463,\"s\":[42.87,42.87],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":464,\"s\":[42.17,42.17],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":465,\"s\":[41.42,41.42],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":466,\"s\":[40.64,40.64],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":467,\"s\":[39.8,39.8],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":468,\"s\":[38.92,38.92],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":469,\"s\":[37.98,37.98],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":470,\"s\":[37,37],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":471,\"s\":[35.96,35.96],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":472,\"s\":[34.87,34.87],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":473,\"s\":[33.73,33.73],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":474,\"s\":[32.52,32.52],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":475,\"s\":[31.26,31.26],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":476,\"s\":[29.95,29.95],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":477,\"s\":[28.57,28.57],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":478,\"s\":[27.12,27.12],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}},{\"t\":479,\"s\":[25.62,25.62],\"i\":{\"x\":0,\"y\":0},\"o\":{\"x\":1,\"y\":1}}]},\"sk\":{\"a\":0,\"k\":0},\"sa\":{\"a\":0,\"k\":0}},\"ao\":0,\"ip\":0,\"op\":480,\"st\":0,\"bm\":0,\"refId\":\"_W0-LMkQeoucVkRQawwsW\"},{\"ddd\":0,\"ind\":11,\"ty\":2,\"nm\":\"\",\"ln\":\"image_342:106118\",\"sr\":1,\"ks\":{\"a\":{\"a\":0,\"k\":[1602,1002]},\"o\":{\"a\":0,\"k\":100},\"p\":{\"a\":0,\"k\":[1114,692]},\"r\":{\"a\":0,\"k\":0},\"s\":{\"a\":0,\"k\":[50,50]},\"sk\":{\"a\":0,\"k\":0},\"sa\":{\"a\":0,\"k\":0}},\"ao\":0,\"ip\":0,\"op\":480,\"st\":0,\"bm\":0,\"refId\":\"hF2XvXiETHHsaM0OLSNM8\"},{\"ddd\":0,\"ind\":12,\"ty\":0,\"nm\":\"\",\"ln\":\"precomp_342:10387812\",\"sr\":1,\"ks\":{\"a\":{\"a\":0,\"k\":[3830,3830]},\"o\":{\"a\":0,\"k\":100},\"p\":{\"a\":0,\"k\":[712,706]},\"r\":{\"a\":0,\"k\":0},\"s\":{\"a\":0,\"k\":[100,100]},\"sk\":{\"a\":0,\"k\":0},\"sa\":{\"a\":0,\"k\":0}},\"ao\":0,\"w\":7660,\"h\":7660,\"ip\":0,\"op\":480,\"st\":0,\"bm\":0,\"refId\":\"tl8GDiDql8LN-0CPe8g0K\"}],\"meta\":{\"g\":\"https://jitter.video\"},\"nm\":\"Focalboard.com-Marketing-Site:-Right-content-Animation---raster\",\"op\":480,\"v\":\"5.7.4\",\"w\":1915}"
  },
  {
    "path": "website/site/static/robots.txt",
    "content": "User-Agent: *\nDisallow: /cgi-bin/\nDisallow: /&utm_source\nDisallow: /&utm_medium\nDisallow: /&utm_campaign\nDisallow: /?utm_\n\nSitemap: https://www.focalboard.com/sitemap.xml\n"
  },
  {
    "path": "website/site/themes/archetypes/default.md",
    "content": "---\ntitle: \"{{ replace .TranslationBaseName \"-\" \" \" | title }}\"\ndate: {{ .Date }}\ndraft: true\n---\n"
  },
  {
    "path": "website/site/themes/config.toml",
    "content": "# Page settings\nbaseurl = \"localhost:1313\"\ntitle = \"Focalboard\"\nlanguageCode = \"en-us\"\ntheme = \"hugo-elate-theme\"\n\n[params]\n\n  custom_css = [\"css/custom1.css\", \"css/custom2.css\"]\n\n  # Meta\n  author = \"\"\n  description = \"\"\n  email = \"\"\n  # Navigation\n  [params.navigation]\n    brand = \"Focalboard\"\n    home = \"Home\"\n\n    # You can add custom links before or after the default links\n    # Assign a weight to define the order\n\n    # prepended links\n    #[[menu.prepend]]\n    #  url = \"http://gohugo.io\"\n    #  name = \"Hugo\"\n    #  weight = 10\n\n    # postpended links\n    [[menu.postpend]]\n      url = \"https://developers.mattermost.com/contribute/focalboard/\"\n      name = \"Contribute\"\n      weight = 10\n\n  # Hero section\n  [params.hero]\n    title = \"Get Focalboard\"\n    subtitle = ''\n\n  # Intro section\n  # Available icons: http://simplelineicons.com/\n  [params.intro]\n\n  [[params.intro.item]]\n    title = \"Contribute\"\n    description = \"Join the hunrdeds of contributors and submit code directly to the Focalboard open-source project.\"\n    url = \"https://developers.mattermost.com/contribute/focalboard/\"\n    button = \"Start Contributing\"\n    icon = \"icon-bulb\"\n    img = \"img_7.jpg\"\n\n  [[params.intro.item]]\n    title = \"Integrate\"\n    description = \"Integrate with Mattermsot through incoming and outgoing webhooks, slash commands, OAuth 2.0 and the REST API.\"\n    url = \"/integrate\"\n    button = \"Start Integrating\"\n    icon = \"icon-wrench\"\n    img = \"img_8.jpg\"\n\n  [[params.intro.item]]\n    title = \"Extend\"\n    description = \"Extend Focalboard to fit your purposes.\"\n    url = \"#\"\n    button = \"Start Extending\"\n    icon = \"icon-rocket\"\n    img = \"img_10.jpg\"\n\n  #[params.intro.video]\n  #  enable = true\n  #  title = \"Watch the video\"\n  #  icon = \"icon-play2\"\n  #  url = \"https://vimeo.com/channels/staffpicks/93951774\"\n\n  # Footer section\n  [params.footer]\n    enable = true\n    twitter = 'https://twitter.com/mattermost'\n    facebook = 'https://www.facebook.com/Mattermost-2300985916642531/'\n    youtube = 'https://www.youtube.com/channel/UCNR05H72hi692y01bWaFXNA'\n    copyright = '&copy; Mattermost, Inc. All Rights Reserved.'\n"
  },
  {
    "path": "website/site/themes/content/contribute/_index.md",
    "content": "---\ntitle: \"Contribute\"\ndate: \"2017-08-19T12:01:23-04:00\"\n---\n\n# heading\n\ntext!\n"
  },
  {
    "path": "website/site/themes/layouts/404.html",
    "content": "\n"
  },
  {
    "path": "website/site/themes/layouts/_default/list.html",
    "content": "<!DOCTYPE html>\n<!--[if lt IE 7]>      <html class=\"no-js lt-ie9 lt-ie8 lt-ie7\"> <![endif]-->\n<!--[if IE 7]>         <html class=\"no-js lt-ie9 lt-ie8\"> <![endif]-->\n<!--[if IE 8]>         <html class=\"no-js lt-ie9\"> <![endif]-->\n<!--[if gt IE 8]>      <html class=\"no-js\"> <![endif]-->\n\n<html>\n    <head>\n        {{ partial \"head.html\" .}}\n    </head>\n<body>\n    {{ partial \"nav.html\" .}}\n    {{ .Content }}\n</body>\n</html>\n"
  },
  {
    "path": "website/site/themes/layouts/_default/single.html",
    "content": "<html>\n<body>\n{{ .Content }}\n</body>\n</html>\n"
  },
  {
    "path": "website/site/themes/layouts/index.html",
    "content": "<!DOCTYPE html>\n<!--[if lt IE 7]>      <html class=\"no-js lt-ie9 lt-ie8 lt-ie7\"> <![endif]-->\n<!--[if IE 7]>         <html class=\"no-js lt-ie9 lt-ie8\"> <![endif]-->\n<!--[if IE 8]>         <html class=\"no-js lt-ie9\"> <![endif]-->\n<!--[if gt IE 8]>      <html class=\"no-js\"> <![endif]-->\n<html>\n    <head>\n        {{ partial \"head.html\" .}}\n    </head>\n    <body>\n        {{ partial \"nav.html\" .}}\n\n        {{ partial \"hero.html\" .}}\n\n        {{ partial \"intro.html\" .}}\n\n        {{ if .Site.Params.work.enable }}\n                {{ partial \"work.html\" .}}\n            {{ end }}\n\n        {{ if .Site.Params.testimonials.enable }}\n            {{ partial \"testimonials.html\" .}}\n        {{ end }}\n\n        {{ if .Site.Params.services.enable }}\n            {{ partial \"services.html\" .}}\n        {{ end }}\n\n        {{ if .Site.Params.about.enable }}\n            {{ partial \"about.html\" .}}\n        {{ end }}\n\n        {{ if .Site.Params.counters.enable }}\n            {{ partial \"counters.html\" .}}\n        {{ end }}\n\n        {{ if .Site.Params.contact.enable }}\n            {{ partial \"contact.html\" .}}\n        {{ end }}\n\n        {{ if .Site.Params.footer.enable }}\n            {{ partial \"footer.html\" .}}\n        {{ end }}\n\n        {{ partial \"js.html\" .}}\n    </body>\n</html>\n"
  },
  {
    "path": "website/site/themes/layouts/partials/about.html",
    "content": "    <section id=\"fh5co-about\" data-section=\"about\">\n        <div class=\"container\">\n            <div class=\"row\">\n                <div class=\"col-md-12 section-heading text-center\">\n                    <h2 class=\"to-animate\">{{ with .Site.Params.about.title }}{{ . | markdownify }}{{ end }}</h2>\n                    <div class=\"row\">\n                        <div class=\"col-md-8 col-md-offset-2 subtext to-animate\">\n                            <h3>{{ with .Site.Params.about.description }}{{ . | markdownify }}{{ end }}</h3>\n                        </div>\n                    </div>\n                </div>\n            </div>\n            <div class=\"row\">\n            {{ range .Site.Params.about.item }}\n                <div class=\"col-md-4\">\n                    <div class=\"fh5co-person text-center to-animate\">\n                        <figure><img src=\"images/{{ .img }}\" alt=\"{{ .alt }}\"></figure>\n                        <h3>{{ .name | markdownify }}</h3>\n                        <span class=\"fh5co-position\">{{ .position | markdownify }}</span>\n                        <p>{{ .description | markdownify }}</p>\n                        <ul class=\"social social-circle\">\n                        {{ range .social }}\n                            <li><a href=\"{{ index . 1 }}\"><i class=\"{{ index .  0 }}\"></i></a></li>\n                        {{ end }}\n                        </ul>\n                    </div>\n                </div>\n            {{ end }}\n            </div>\n        </div>\n    </section>\n"
  },
  {
    "path": "website/site/themes/layouts/partials/contact.html",
    "content": "    <section id=\"fh5co-contact\" data-section=\"contact\">\n        <div class=\"container\">\n            <div class=\"row\">\n                <div class=\"col-md-12 section-heading text-center\">\n                    <h2 class=\"to-animate\">{{ with .Site.Params.contact.title }}{{ . | markdownify }}{{ end }}</h2>\n                    <div class=\"row\">\n                        <div class=\"col-md-8 col-md-offset-2 subtext to-animate\">\n                            <h3>{{ with .Site.Params.contact.description }}{{ . | markdownify }}{{ end }}</h3>\n                        </div>\n                    </div>\n                </div>\n            </div>\n            <div class=\"row row-bottom-padded-md\">\n                <div class=\"col-md-6 to-animate\">\n                    <h3>{{ with .Site.Params.contact.info }}{{ . | markdownify }}{{ end }}</h3>\n                    <ul class=\"fh5co-contact-info\">\n                    {{ range .Site.Params.contact.details }}\n                        <li class=\"fh5co-contact-address \"><i class=\"{{ with index . 0}}{{ . }}{{ end }}\"></i>{{ with index . 1}}{{ . | markdownify }}{{ end }}</li>\n                    {{ end }}\n                    </ul>\n                </div>\n\n                <div class=\"col-md-6 to-animate\">\n                    <h3>{{ with .Site.Params.contact.form }}{{ . | markdownify }}{{ end }}</h3>\n                    <form method=\"post\" action=\"//formspree.io/{{ with .Site.Params.email }}{{.}}{{ end }}\">\n                    <div class=\"form-group \">\n                        {{ with .Site.Params.contact.name }}\n                        <label for=\"name\" class=\"sr-only\">{{ . | markdownify }}</label>\n                        <input id=\"name\" class=\"form-control\" name=\"name\" placeholder=\"{{ . }}\" type=\"text\">\n                        {{ end }}\n                    </div>\n                    <div class=\"form-group \">\n                        {{ with .Site.Params.contact.email }}\n                        <label for=\"email\" class=\"sr-only\">{{ . | markdownify }}</label>\n                        <input id=\"email\" class=\"form-control\" name=\"_replyto\" placeholder=\"{{ . }}\" type=\"email\">\n                        {{ end }}\n                    </div>\n                    <div class=\"form-group \">\n                        {{ with .Site.Params.contact.phone }}\n                        <label for=\"phone\" class=\"sr-only\">{{ . | markdownify }}</label>\n                        <input id=\"phone\" class=\"form-control\" name=\"phone\" placeholder=\"{{ . }}\" type=\"text\">\n                        {{ end }}\n                    </div>\n                    <div class=\"form-group \">\n                        {{ with .Site.Params.contact.message }}\n                        <label for=\"message\" class=\"sr-only\">{{ . | markdownify }}</label>\n                        <textarea id=\"message\" cols=\"30\" rows=\"5\" class=\"form-control\" name=\"message\" placeholder=\"{{ . }}\"></textarea>\n                        {{ end }}\n                    </div>\n                    <div class=\"form-group \">\n                        <input class=\"btn btn-primary btn-lg\" value=\"{{ .Site.Params.contact.button | markdownify }}\" type=\"submit\">\n                    </div>\n                    </form>\n                </div>\n\n            </div>\n        </div>\n        {{ if .Site.Params.contact.map }}\n            <div id=\"map\" class=\"to-animate\"></div>\n        {{ end }}\n    </section>\n"
  },
  {
    "path": "website/site/themes/layouts/partials/counters.html",
    "content": "    <section id=\"fh5co-counters\" style=\"background-image: url(images/{{ .Site.Params.counters.background }});\" data-stellar-background-ratio=\"0.5\">\n        <div class=\"fh5co-overlay\"></div>\n        <div class=\"container\">\n            <div class=\"row\">\n                <div class=\"col-md-12 section-heading text-center to-animate\">\n                    <h2>{{ .Site.Params.counters.title | markdownify }}</h2>\n                </div>\n            </div>\n            <div class=\"row\">\n            {{ range .Site.Params.counters.item }}\n                <div class=\"col-md-3 col-sm-6 col-xs-12\">\n                    <div class=\"fh5co-counter to-animate\">\n                        <i class=\"fh5co-counter-icon {{ .icon }} to-animate-2\"></i>\n                        <span class=\"fh5co-counter-number js-counter\" data-from=\"{{ .from }}\" data-to=\"{{ .to }}\" data-speed=\"{{ .speed }}\" data-refresh-interval=\"{{ .interval }}\">{{ .to }}</span>\n                        <span class=\"fh5co-counter-label\">{{ .description }}</span>\n                    </div>\n                </div>\n            {{ end }}\n            </div>\n        </div>\n    </section>\n"
  },
  {
    "path": "website/site/themes/layouts/partials/footer.html",
    "content": "    <footer id=\"footer\" role=\"contentinfo\">\n        <a href=\"#\" class=\"gotop js-gotop\"><i class=\"icon-arrow-up2\"></i></a>\n        <div class=\"container\">\n            <div class=\"\">\n                <div class=\"col-md-12 text-center\">\n                    <p>{{ with .Site.Params.footer.copyright }}{{ . | markdownify }}{{ end }}</p>\n\n                </div>\n            </div>\n            <div class=\"row\">\n                <div class=\"col-md-12 text-center\">\n                    <ul class=\"social social-circle\">\n                        <li><a href=\"{{ .Site.Params.footer.twitter }}\" target=\"_blank\"><i class=\"icon-twitter\"></i></a></li>\n                        <li><a href=\"{{ .Site.Params.footer.facebook }}\" target=\"_blank\"><i class=\"icon-facebook\"></i></a></li>\n                        <li><a href=\"{{ .Site.Params.footer.youtube }}\" target=\"_blank\"><i class=\"icon-youtube\"></i></a></li>\n                    </ul>\n                </div>\n            </div>\n        </div>\n    </footer>\n"
  },
  {
    "path": "website/site/themes/layouts/partials/head.html",
    "content": "    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <title> {{ .Title }}</title>\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <!--\n    <meta name=\"description\" content=\"Free HTML5 Template by FREEHTML5.CO - Hugo port by Pieter Saey\" />\n    <meta name=\"keywords\" content=\"free html5, free template, free bootstrap, html5, css3, mobile first, responsive, hugo, static site\" />\n    <meta name=\"author\" content=\"FREEHTML5.CO / Hugo port \" />\n    -->\n    {{ with .Site.Params.author }}<meta name=\"author\" content=\"{{ . }}\">{{ end }}\n    {{ with .Site.Params.description }}<meta name=\"description\" content=\"{{ . }}\">{{ end }}\n    {{ with .Site.LanguageCode }}<meta http-equiv=\"content-language\" content=\"{{ . }}\" />{{ end }}\n\n    <!--\n    //////////////////////////////////////////////////////\n\n    FREE HTML5 TEMPLATE\n    DESIGNED & DEVELOPED by FREEHTML5.CO\n\n    Website:        http://freehtml5.co/\n    Email:          info@freehtml5.co\n    Twitter:        http://twitter.com/fh5co\n    Facebook:       https://www.facebook.com/fh5co\n\n    HUGO TEMPLATE PORTED BY Pieter Saey\n\n    Website:        http://saey55.gitlab.io/pietercv\n    Email:          saeypieter@gmail.com\n    Twitter:        http://twitter.com/saey55\n\n\n    //////////////////////////////////////////////////////\n    -->\n\n    <!-- Facebook and Twitter integration -->\n    {{ if not .Site.Params.OpenGraph.hide }}\n    <meta name=\"og:site_name\" content=\"{{ .Site.Title }}\">\n    <meta name=\"og:title\" content=\"{{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ .Title }}{{ end }}\">\n    <meta name=\"og:url\" content=\"{{ .Permalink }}\">\n    <meta name=\"og:image\" content=\"{{ .Site.BaseURL }}images/{{ .Site.Params.sidebar.avatar }}\">\n    <meta name=\"twitter:title\" content=\"{{ .Site.Title }}\" />\n    <meta name=\"twitter:url\" content=\"{{ .Permalink }}\" />\n    <meta name=\"twitter:image\" content=\"{{ .Site.BaseURL }}images/{{ .Site.Params.sidebar.avatar }}\" />\n    <meta name=\"twitter:card\" content=\"\" />\n    {{ end }}\n    <!--\n    <meta property=\"og:title\" content=\"\"/>\n    <meta property=\"og:image\" content=\"\"/>\n    <meta property=\"og:url\" content=\"\"/>\n    <meta property=\"og:site_name\" content=\"\"/>\n    <meta property=\"og:description\" content=\"\"/>\n    <meta name=\"twitter:title\" content=\"\" />\n    <meta name=\"twitter:image\" content=\"\" />\n    <meta name=\"twitter:url\" content=\"\" />\n    <meta name=\"twitter:card\" content=\"\" />\n    -->\n\n    <!-- canonical link tag -->\n    <link rel=\"canonical\" href=\"{{ .Permalink }}\"/>\n\n    <!-- Place favicon.ico and apple-touch-icon.png in the root directory -->\n    <link rel=\"shortcut icon\" href=\"favicon.ico\">\n\n    <link href='https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,300,600,400italic,700' rel='stylesheet' type='text/css'>\n\n    <!-- Animate.css -->\n    <link rel=\"stylesheet\" href=\"/css/animate.css\">\n    <!-- Icomoon Icon Fonts-->\n    <link rel=\"stylesheet\" href=\"/css/icomoon.css\">\n    <!-- Simple Line Icons -->\n    <link rel=\"stylesheet\" href=\"/css/simple-line-icons.css\">\n    <!-- Magnific Popup -->\n    <link rel=\"stylesheet\" href=\"/css/magnific-popup.css\">\n    <!-- Bootstrap  -->\n    <link rel=\"stylesheet\" href=\"/css/bootstrap.css\">\n\n    <!--\n    Default Theme Style\n    You can change the style.css (default color purple) to one of these styles\n\n    1. pink.css\n    2. blue.css\n    3. turquoise.css\n    4. orange.css\n    5. lightblue.css\n    6. brown.css\n    7. green.css\n\n    -->\n    <link rel=\"stylesheet\" href=\"/css/style.css\">\n\n    <!--\n        custom css files can be configured in config.toml:\n        [params]\n        custom_css = [\"css/foo.css\", \"css/bar.css\"]\n    -->\n    {{ range .Site.Params.custom_css }}\n    <link rel=\"stylesheet\" href=\"{{ . }}\">\n    {{ end }}\n\n    <!-- Modernizr JS -->\n    <script src=\"/js/modernizr-2.6.2.min.js\"></script>\n    <!-- FOR IE9 below -->\n    <!--[if lt IE 9]>\n    <script src=\"js/respond.min.js\"></script>\n    <![endif]-->\n"
  },
  {
    "path": "website/site/themes/layouts/partials/hero.html",
    "content": "    <section id=\"fh5co-home\" data-section=\"home\" style=\"background-image: url(images/full_image_2.jpg);\" data-stellar-background-ratio=\"0.5\">\n        <div class=\"gradient\"></div>\n        <div class=\"container\">\n            <div class=\"text-wrap\">\n                <div class=\"text-inner\">\n                    <div class=\"row\">\n                        <div class=\"col-md-8 col-md-offset-2\">\n                            <h1 class=\"to-animate\">{{ with .Site.Params.hero.title }}{{ . | markdownify }}{{ end }}</h1>\n                            <h2 class=\"to-animate\">{{ with .Site.Params.hero.subtitle }}{{ . | markdownify }}{{ end }}</h2>\n                        </div>\n                    </div>\n                </div>\n            </div>\n        </div>\n        <div class=\"slant\"></div>\n    </section>\n"
  },
  {
    "path": "website/site/themes/layouts/partials/intro.html",
    "content": "    <section id=\"fh5co-intro\">\n        <div class=\"container\">\n            <div class=\"row row-bottom-padded-lg\">\n                {{ range .Site.Params.intro.item }}\n                <div class=\"fh5co-block to-animate\" style=\"background-image: url(images/{{ .img }});\">\n                    <div class=\"overlay-darker\"></div>\n                    <div class=\"overlay\"></div>\n                    <div class=\"fh5co-text\">\n                        <i class=\"fh5co-intro-icon {{ .icon }}\"></i>\n                        <h2>{{ .title }}</h2>\n                        <p>{{ .description }}</p>\n                        <p><a href=\"{{ .url }}\" class=\"btn btn-primary\">{{ .button}}</a></p>\n                    </div>\n                </div>\n                {{ end }}\n            </div>\n            {{ if .Site.Params.intro.video.enable }}\n            <div class=\"row watch-video text-center to-animate\">\n                <!-- <span>{{ with .Site.Params.intro.video }}{{ . }}{{ end }}</span> -->\n                <span>{{.Site.Params.intro.video.title | markdownify }}</span>\n\n                <a href=\"{{ .Site.Params.intro.video.url }}\" class=\"popup-vimeo btn-video\"><i class=\"{{ .Site.Params.intro.video.icon | markdownify }}\"></i></a>\n            </div>\n            {{ end }}\n        </div>\n    </section>\n"
  },
  {
    "path": "website/site/themes/layouts/partials/js.html",
    "content": "    <!-- jQuery -->\n    <script src=\"/js/jquery.min.js\"></script>\n    <!-- jQuery Easing -->\n    <script src=\"/js/jquery.easing.1.3.js\"></script>\n    <!-- Bootstrap -->\n    <script src=\"/js/bootstrap.min.js\"></script>\n    <!-- Waypoints -->\n    <script src=\"/js/jquery.waypoints.min.js\"></script>\n    <!-- Stellar Parallax -->\n    <script src=\"/js/jquery.stellar.min.js\"></script>\n    <!-- Counter -->\n    <script src=\"/js/jquery.countTo.js\"></script>\n    <!-- Magnific Popup -->\n    <script src=\"/js/jquery.magnific-popup.min.js\"></script>\n    <script src=\"js/magnific-popup-options.js\"></script>\n    <!-- Google Map -->\n    <script src=\"https://maps.googleapis.com/maps/api/js?key=AIzaSyCefOgb1ZWqYtj7raVSmN4PL2WkTrc-KyA&sensor=false\"></script>\n    <script src=\"/js/google_map.js\"></script>\n    <script src=\"/js/main.js\"></script>\n"
  },
  {
    "path": "website/site/themes/layouts/partials/nav.html",
    "content": "    <header role=\"banner\" id=\"fh5co-header\">\n        <div class=\"container\">\n            <!-- <div class=\"row\"> -->\n            <nav class=\"navbar navbar-default\">\n                <div class=\"navbar-header\">\n                    <!-- Mobile Toggle Menu Button -->\n                    <a href=\"#\" class=\"js-fh5co-nav-toggle fh5co-nav-toggle\" data-toggle=\"collapse\" data-target=\"#navbar\" aria-expanded=\"false\" aria-controls=\"navbar\"><i></i></a>\n                    <a class=\"navbar-brand\" href=\"index.html\">{{ with .Site.Params.navigation.brand }}{{ . | markdownify }}{{ end }}</a>\n                </div>\n                <div id=\"navbar\" class=\"navbar-collapse collapse\">\n                <ul class=\"nav navbar-nav navbar-right\">\n\n                    {{ range .Site.Menus.prepend }}\n                        <li><a class=\"external\" href=\"{{ .Permalink }}\"><span>{{ .Name | markdownify }}</span></a></li>\n                    {{ end }}\n\n                    <li class=\"active\"><a href=\"#\" data-nav-section=\"home\"><span>{{ with .Site.Params.navigation.home }}{{ . | markdownify }}{{ end }}</span></a></li>\n\n                    {{ range .Site.Menus.postpend }}\n                        <li><a class=\"external\" href=\"{{ .Permalink }}\"><span>{{ .Name | markdownify }}</span></a></li>\n                    {{ end }}\n                </ul>\n            </div>\n        </nav>\n        <!-- </div> -->\n    </div>\n</header>\n"
  },
  {
    "path": "website/site/themes/layouts/partials/nav2.html",
    "content": "<nav class=\"navbar navbar-default\">\n    <div class=\"container-fluid\">\n        <div class=\"navbar-header\">\n        <a class=\"navbar-brand\" href=\"#\">\n            {{ .Site.Params.navigation.brand }}\n        </a>\n        </div>\n        <div class=\"collapse navbar-collapse\" id=\"bs-example-navbar-collapse-1\">\n        <ul class=\"nav navbar-nav\">\n            <li><a href=\"https://github.com/mattermost/focalboard\" target='_blank'>View Source</a></li>\n        </ul>\n        </div>\n    </div>\n</nav>\n"
  },
  {
    "path": "website/site/themes/layouts/partials/services.html",
    "content": "    <section id=\"fh5co-services\" data-section=\"services\">\n        <div class=\"container\">\n            <div class=\"row\">\n                <div class=\"col-md-12 section-heading text-left\">\n                    <h2 class=\" left-border to-animate\">{{ with .Site.Params.services.title }}{{ . | markdownify }}{{ end }}</h2>\n                    <div class=\"row\">\n                        <div class=\"col-md-8 subtext to-animate\">\n                            <h3>{{ with .Site.Params.services.description }}{{ . | markdownify }}{{ end }}</h3>\n                        </div>\n                    </div>\n                </div>\n            </div>\n            <div class=\"row\">\n            {{ range .Site.Params.services.item }}\n                <div class=\"col-md-6 col-sm-6 fh5co-service to-animate\">\n                    <i class=\"icon to-animate-2 {{ .icon | markdownify }}\"></i>\n                    <h3>{{ .title | markdownify }}</h3>\n                    <p>{{ .description | markdownify }}</p>\n                </div>\n                <div class=\"clearfix visible-sm-block\"></div>\n            {{ end }}\n            </div>\n        </div>\n    </section>\n"
  },
  {
    "path": "website/site/themes/layouts/partials/testimonials.html",
    "content": "    <section id=\"fh5co-testimonials\" data-section=\"testimonials\">\n        <div class=\"container\">\n            <div class=\"row\">\n                <div class=\"col-md-12 section-heading text-center\">\n                    <h2 class=\"to-animate\">{{ with .Site.Params.testimonials.title }}{{ . | markdownify }}{{ end }}</h2>\n                    <div class=\"row\">\n                        <div class=\"col-md-8 col-md-offset-2 subtext to-animate\">\n                            <h3>{{ with .Site.Params.testimonials.description }}{{ . | markdownify }}{{ end }}</h3>\n                        </div>\n                    </div>\n                </div>\n            </div>\n            <div class=\"row\">\n            {{ range .Site.Params.testimonials.item }}\n                <div class=\"col-md-4\">\n                    <div class=\"box-testimony\">\n                        <blockquote class=\"to-animate-2\">\n                            <p>{{ .quote | markdownify}}</p>\n                        </blockquote>\n                        <div class=\"author to-animate\">\n                            <figure><img src=\"images/{{ .img }}\" alt=\"{{ .alt }}\"></figure>\n                            <p>{{ .person | markdownify }}</p>\n                        </div>\n                    </div>\n                </div>\n            {{ end }}\n            </div>\n        </div>\n    </section>\n"
  },
  {
    "path": "website/site/themes/layouts/partials/work.html",
    "content": "    <section id=\"fh5co-work\" data-section=\"work\">\n        <div class=\"container\">\n            <div class=\"row\">\n                <div class=\"col-md-12 section-heading text-center\">\n                    <h2 class=\"to-animate\">{{ with .Site.Params.work.title }}{{ . }}{{ end }}</h2>\n                    <div class=\"row\">\n                        <div class=\"col-md-8 col-md-offset-2 subtext to-animate\">\n                            <h3>{{ with .Site.Params.work.description }}{{ . | markdownify }}{{ end }}</h3>\n                        </div>\n                    </div>\n                </div>\n            </div>\n            <div class=\"row row-bottom-padded-sm\">\n            {{ range .Site.Params.work.row }}\n                <div class=\"col-md-4 col-sm-6 col-xxs-12\">\n                    <a href=\"images/{{ .img }}\" class=\"fh5co-project-item image-popup to-animate\">\n                        <img src=\"images/{{ .img }}\" alt=\"{{ .alt }}\" class=\"img-responsive\">\n                        <div class=\"fh5co-text\">\n                        <h2>{{ .title }}</h2>\n                        <span>{{ .subtitle }}</span>\n                        </div>\n                    </a>\n                </div>\n\n                <div class=\"clearfix visible-sm-block\"></div>\n            {{ end }}\n            </div>\n            <div class=\"row\">\n                <div class=\"col-md-12 text-center to-animate\">\n                    <p>{{ .Site.Params.work.footertext | markdownify }}</p>\n                </div>\n            </div>\n        </div>\n    </section>\n"
  },
  {
    "path": "win-wpf/.gitignore",
    "content": "packages\nobj\nmsix\ntemp\ndist\n*.msix\n*.suo\n*.csproj.user\n"
  },
  {
    "path": "win-wpf/AppxManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Package xmlns=\"http://schemas.microsoft.com/appx/manifest/foundation/windows10\"\n    xmlns:uap=\"http://schemas.microsoft.com/appx/manifest/uap/windows10\"\n    xmlns:rescap=\"http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities\">\n    <Identity Name=\"MattermostInc.Focalboard\" Publisher=\"CN=5C59534D-C37D-4D0C-8464-26AA0D0B6EFE\" Version=\"7.0.0.0\" ProcessorArchitecture=\"x64\" />\n    <Properties>\n        <DisplayName>Focalboard</DisplayName>\n        <PublisherDisplayName>Mattermost, Inc.</PublisherDisplayName>\n        <Description>Focalboard Desktop Edition</Description>\n        <Logo>Assets\\StoreLogo.png</Logo>\n    </Properties>\n    <Resources>\n        <Resource Language=\"en-us\" />\n    </Resources>\n    <Dependencies>\n        <TargetDeviceFamily Name=\"Windows.Desktop\" MinVersion=\"10.0.17763.0\" MaxVersionTested=\"10.0.19041.1\" />\n    </Dependencies>\n    <Capabilities>\n        <rescap:Capability Name=\"runFullTrust\"/>\n    </Capabilities>\n    <Applications>\n        <Application Id=\"Focalboard\" Executable=\"Focalboard.exe\" EntryPoint=\"Windows.FullTrustApplication\">\n            <uap:VisualElements BackgroundColor=\"#ffffff\" DisplayName=\"Focalboard\" Square150x150Logo=\"Assets\\icon150.png\" Square44x44Logo=\"Assets\\icon44.png\" Description=\"Focalboard Desktop Edition\" />\n        </Application>\n    </Applications>\n</Package>\n"
  },
  {
    "path": "win-wpf/Focalboard/App.config",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<configuration>\n    <configSections>\n        <sectionGroup name=\"userSettings\" type=\"System.Configuration.UserSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\">\n            <section name=\"Focalboard.Properties.Settings\" type=\"System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\" allowExeDefinition=\"MachineToLocalUser\" requirePermission=\"false\"/>\n        </sectionGroup>\n    </configSections>\n    <startup> \n        <supportedRuntime version=\"v4.0\" sku=\".NETFramework,Version=v4.8\"/>\n    </startup>\n<userSettings>\n        <Focalboard.Properties.Settings>\n                <setting name=\"WindowPosition\" serializeAs=\"String\">\n                        <value>50, 20</value>\n                </setting>\n                <setting name=\"WindowSize\" serializeAs=\"String\">\n                        <value>1024, 800</value>\n                </setting>\n                <setting name=\"WindowMaximized\" serializeAs=\"String\">\n                        <value>False</value>\n                </setting>\n        </Focalboard.Properties.Settings>\n    </userSettings>\n</configuration>\n"
  },
  {
    "path": "win-wpf/Focalboard/App.xaml",
    "content": "﻿<Application x:Class=\"Focalboard.App\"\n             xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n             xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n             xmlns:local=\"clr-namespace:Focalboard\"\n             StartupUri=\"MainWindow.xaml\">\n    <Application.Resources>\n         \n    </Application.Resources>\n</Application>\n"
  },
  {
    "path": "win-wpf/Focalboard/App.xaml.cs",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nusing System;\nusing System.Diagnostics;\nusing System.IO;\nusing System.Net;\nusing System.Net.Sockets;\nusing System.Runtime.InteropServices;\nusing System.Security.Cryptography;\nusing System.Text;\nusing System.Threading;\nusing System.Windows;\nusing Windows.Storage;\n\nnamespace Focalboard {\n    /// <summary>\n    /// Interaction logic for App.xaml\n    /// </summary>\n    public partial class App : Application {\n        public string sessionToken = \"\";\n        public int port;\n\n        private Mutex mutex;\n\n        App() {\n            SingleInstanceCheck();\n\n            Startup += App_Startup;\n        }\n\n        public void SingleInstanceCheck() {\n            bool isOnlyInstance = false;\n            mutex = new Mutex(true, @\"Focalboard\", out isOnlyInstance);\n            if (!isOnlyInstance) {\n                ShowExistingWindow();\n                Shutdown();\n            }\n        }\n\n        [DllImport(\"User32.dll\")]\n        private static extern bool SetForegroundWindow(IntPtr hWnd);\n\n        [DllImport(\"user32.dll\")]\n        private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);\n\n        // shows the window of the single-instance that is already open\n        private void ShowExistingWindow() {\n            var currentProcess = Process.GetCurrentProcess();\n            var processes = Process.GetProcessesByName(currentProcess.ProcessName);\n            foreach (var process in processes) {\n                // the single-instance already open should have a MainWindowHandle\n                if (process.MainWindowHandle != IntPtr.Zero) {\n                    // restores the window in case it was minimized\n                    const int SW_SHOWNORMAL = 1;\n                    ShowWindow(process.MainWindowHandle, SW_SHOWNORMAL);\n\n                    // brings the window to the foreground\n                    SetForegroundWindow(process.MainWindowHandle);\n\n                    return;\n                }\n            }\n        }\n\n        private void App_Startup(object sender, StartupEventArgs e) {\n            Debug.WriteLine($\"App_Startup()\");\n\n            try {\n                InitServer();\n            } catch (Exception ex) {\n                MessageBox.Show($\"InitServer ERROR: {ex.ToString()}\", \"Focalboard\");\n                Shutdown();\n            }\n        }\n\n        private void InitServer() {\n            port = FindFreePort();\n            Debug.WriteLine(\"port: {0}\", port);\n\n            sessionToken = CreateSessionToken();\n\n            // Need to set CWD so the server can read the config file\n\t\t\tvar appFolder = Utils.GetAppFolder();\n\t\t\tDirectory.SetCurrentDirectory(appFolder);\n\n\t\t\tstring appDataFolder;\n            try {\n                appDataFolder = ApplicationData.Current.LocalFolder.Path;\n            } catch {\n                var documentsFolder = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);\n                appDataFolder = Path.Combine(documentsFolder, \"Focalboard\");\n                Directory.CreateDirectory(appDataFolder);\n                // Not a UWP app, store in Documents\n\n                // FIXUP code: Copy from old DB location\n                var oldDBPath = Path.Combine(documentsFolder, \"focalboard.db\");\n                var newDBPath = Path.Combine(appDataFolder, \"focalboard.db\");\n                if (!File.Exists(newDBPath) && File.Exists(oldDBPath)) {\n                    Debug.WriteLine($\"Moving DB file from: {oldDBPath} to {newDBPath}\");\n                    File.Move(oldDBPath, newDBPath);\n\t\t\t\t}\n            }\n\n            var dbPath = Path.Combine(appDataFolder, \"focalboard.db\");\n            Debug.WriteLine($\"dbPath: {dbPath}\");\n\n            var filesPath = Path.Combine(appDataFolder, \"files\");\n            Debug.WriteLine($\"filesPath: {filesPath}\");\n\n            var cwd = Directory.GetCurrentDirectory();\n            var webFolder = Path.Combine(cwd, @\"pack\");\n            webFolder = webFolder.Replace(@\"\\\", @\"/\");\n            filesPath = filesPath.Replace(@\"\\\", @\"/\");\n            dbPath = dbPath.Replace(@\"\\\", @\"/\");\n            byte[] webFolderBytes = Encoding.UTF8.GetBytes(webFolder);\n            byte[] filesPathBytes = Encoding.UTF8.GetBytes(filesPath);\n            byte[] sessionTokenBytes = Encoding.UTF8.GetBytes(sessionToken);\n            byte[] dbPathBytes = Encoding.UTF8.GetBytes(dbPath);\n            byte[] configFilePathBytes = Encoding.UTF8.GetBytes(\"\");\n            GoFunctions.StartServer(webFolderBytes, filesPathBytes, port, sessionTokenBytes, dbPathBytes, configFilePathBytes);\n\n            Debug.WriteLine(\"Server started\");\n        }\n\n        private string CreateSessionToken() {\n            using (RandomNumberGenerator rng = new RNGCryptoServiceProvider()) {\n                byte[] tokenData = new byte[32];\n                rng.GetBytes(tokenData);\n\n                string token = Convert.ToBase64String(tokenData);\n                return token;\n            }\n        }\n\n        private int FindFreePort() {\n            int port = 0;\n            Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);\n            try {\n                var localEP = new IPEndPoint(IPAddress.Any, 0);\n                socket.Bind(localEP);\n                localEP = (IPEndPoint)socket.LocalEndPoint;\n                port = localEP.Port;\n            } finally {\n                socket.Close();\n            }\n            return port;\n        }\n    }\n\n    static class GoFunctions {\n        [DllImport(@\"focalboard-server.dll\", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]\n        public static extern void StartServer(byte[] webPath, byte[] filesPath, int port, byte[] singleUserToken, byte[] dbConfigString, byte[] configFilePath);\n\n        [DllImport(@\"focalboard-server.dll\", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]\n        public static extern void StopServer();\n    }\n}\n"
  },
  {
    "path": "win-wpf/Focalboard/Focalboard.csproj",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"15.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <Import Project=\"$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props\" Condition=\"Exists('$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props')\" />\n  <PropertyGroup>\n    <Configuration Condition=\" '$(Configuration)' == '' \">Debug</Configuration>\n    <Platform Condition=\" '$(Platform)' == '' \">AnyCPU</Platform>\n    <ProjectGuid>{7B3F5C74-96AC-4521-9268-28BF2D91FCF4}</ProjectGuid>\n    <OutputType>WinExe</OutputType>\n    <RootNamespace>Focalboard</RootNamespace>\n    <AssemblyName>Focalboard</AssemblyName>\n    <TargetFrameworkVersion>v4.8</TargetFrameworkVersion>\n    <FileAlignment>512</FileAlignment>\n    <ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>\n    <WarningLevel>4</WarningLevel>\n    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>\n    <Deterministic>true</Deterministic>\n    <NuGetPackageImportStamp>\n    </NuGetPackageImportStamp>\n    <PublishUrl>publish\\</PublishUrl>\n    <Install>true</Install>\n    <InstallFrom>Disk</InstallFrom>\n    <UpdateEnabled>false</UpdateEnabled>\n    <UpdateMode>Foreground</UpdateMode>\n    <UpdateInterval>7</UpdateInterval>\n    <UpdateIntervalUnits>Days</UpdateIntervalUnits>\n    <UpdatePeriodically>false</UpdatePeriodically>\n    <UpdateRequired>false</UpdateRequired>\n    <MapFileExtensions>true</MapFileExtensions>\n    <ApplicationRevision>0</ApplicationRevision>\n    <ApplicationVersion>1.0.0.%2a</ApplicationVersion>\n    <IsWebBootstrapper>false</IsWebBootstrapper>\n    <UseApplicationTrust>false</UseApplicationTrust>\n    <BootstrapperEnabled>true</BootstrapperEnabled>\n    <TargetFrameworkProfile />\n  </PropertyGroup>\n  <PropertyGroup Condition=\" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' \">\n    <PlatformTarget>AnyCPU</PlatformTarget>\n    <DebugSymbols>true</DebugSymbols>\n    <DebugType>full</DebugType>\n    <Optimize>false</Optimize>\n    <OutputPath>bin\\Debug\\</OutputPath>\n    <DefineConstants>DEBUG;TRACE</DefineConstants>\n    <ErrorReport>prompt</ErrorReport>\n    <WarningLevel>4</WarningLevel>\n  </PropertyGroup>\n  <PropertyGroup Condition=\" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' \">\n    <PlatformTarget>AnyCPU</PlatformTarget>\n    <DebugType>pdbonly</DebugType>\n    <Optimize>true</Optimize>\n    <OutputPath>bin\\Release\\</OutputPath>\n    <DefineConstants>TRACE</DefineConstants>\n    <ErrorReport>prompt</ErrorReport>\n    <WarningLevel>4</WarningLevel>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)' == 'Debug|x64'\">\n    <DebugSymbols>true</DebugSymbols>\n    <OutputPath>bin\\x64\\Debug\\</OutputPath>\n    <DefineConstants>DEBUG;TRACE</DefineConstants>\n    <DebugType>full</DebugType>\n    <PlatformTarget>x64</PlatformTarget>\n    <LangVersion>7.3</LangVersion>\n    <ErrorReport>prompt</ErrorReport>\n    <Prefer32Bit>true</Prefer32Bit>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)' == 'Release|x64'\">\n    <OutputPath>bin\\x64\\Release\\</OutputPath>\n    <DefineConstants>TRACE</DefineConstants>\n    <Optimize>true</Optimize>\n    <DebugType>pdbonly</DebugType>\n    <PlatformTarget>x64</PlatformTarget>\n    <LangVersion>7.3</LangVersion>\n    <ErrorReport>prompt</ErrorReport>\n    <Prefer32Bit>true</Prefer32Bit>\n  </PropertyGroup>\n  <PropertyGroup>\n    <RunPostBuildEvent>Always</RunPostBuildEvent>\n  </PropertyGroup>\n  <PropertyGroup>\n    <ApplicationIcon>focalboard.ico</ApplicationIcon>\n  </PropertyGroup>\n  <ItemGroup>\n    <Reference Include=\"Microsoft.Web.WebView2.Core, Version=1.0.705.50, Culture=neutral, PublicKeyToken=2a8ab48044d2601e, processorArchitecture=MSIL\">\n      <HintPath>..\\packages\\Microsoft.Web.WebView2.1.0.705.50\\lib\\net45\\Microsoft.Web.WebView2.Core.dll</HintPath>\n    </Reference>\n    <Reference Include=\"Microsoft.Web.WebView2.WinForms, Version=1.0.705.50, Culture=neutral, PublicKeyToken=2a8ab48044d2601e, processorArchitecture=MSIL\">\n      <HintPath>..\\packages\\Microsoft.Web.WebView2.1.0.705.50\\lib\\net45\\Microsoft.Web.WebView2.WinForms.dll</HintPath>\n    </Reference>\n    <Reference Include=\"Microsoft.Web.WebView2.Wpf, Version=1.0.705.50, Culture=neutral, PublicKeyToken=2a8ab48044d2601e, processorArchitecture=MSIL\">\n      <HintPath>..\\packages\\Microsoft.Web.WebView2.1.0.705.50\\lib\\net45\\Microsoft.Web.WebView2.Wpf.dll</HintPath>\n    </Reference>\n    <Reference Include=\"System\" />\n    <Reference Include=\"System.Data\" />\n    <Reference Include=\"System.Drawing\" />\n    <Reference Include=\"System.Runtime.WindowsRuntime, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL\">\n      <SpecificVersion>False</SpecificVersion>\n      <HintPath>$(MSBuildProgramFiles32)\\Reference Assemblies\\Microsoft\\Framework\\.NETCore\\v4.5\\System.Runtime.WindowsRuntime.dll</HintPath>\n      <Private>False</Private>\n    </Reference>\n    <Reference Include=\"System.Xml\" />\n    <Reference Include=\"Microsoft.CSharp\" />\n    <Reference Include=\"System.Core\" />\n    <Reference Include=\"System.Xml.Linq\" />\n    <Reference Include=\"System.Data.DataSetExtensions\" />\n    <Reference Include=\"System.Net.Http\" />\n    <Reference Include=\"System.Xaml\">\n      <RequiredTargetFramework>4.0</RequiredTargetFramework>\n    </Reference>\n    <Reference Include=\"Windows\">\n      <HintPath>$(MSBuildProgramFiles32)\\Windows Kits\\10\\UnionMetadata\\10.0.19041.0\\Windows.winmd</HintPath>\n      <Private>False</Private>\n    </Reference>\n    <Reference Include=\"WindowsBase\" />\n    <Reference Include=\"PresentationCore\" />\n    <Reference Include=\"PresentationFramework\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ApplicationDefinition Include=\"App.xaml\">\n      <Generator>MSBuild:Compile</Generator>\n      <SubType>Designer</SubType>\n    </ApplicationDefinition>\n    <Compile Include=\"Utils.cs\" />\n    <Compile Include=\"Webview2Installer.cs\" />\n    <Page Include=\"MainWindow.xaml\">\n      <Generator>MSBuild:Compile</Generator>\n      <SubType>Designer</SubType>\n    </Page>\n    <Compile Include=\"App.xaml.cs\">\n      <DependentUpon>App.xaml</DependentUpon>\n      <SubType>Code</SubType>\n    </Compile>\n    <Compile Include=\"MainWindow.xaml.cs\">\n      <DependentUpon>MainWindow.xaml</DependentUpon>\n      <SubType>Code</SubType>\n    </Compile>\n  </ItemGroup>\n  <ItemGroup>\n    <Compile Include=\"Properties\\AssemblyInfo.cs\">\n      <SubType>Code</SubType>\n    </Compile>\n    <Compile Include=\"Properties\\Resources.Designer.cs\">\n      <AutoGen>True</AutoGen>\n      <DesignTime>True</DesignTime>\n      <DependentUpon>Resources.resx</DependentUpon>\n    </Compile>\n    <Compile Include=\"Properties\\Settings.Designer.cs\">\n      <AutoGen>True</AutoGen>\n      <DependentUpon>Settings.settings</DependentUpon>\n      <DesignTimeSharedInput>True</DesignTimeSharedInput>\n    </Compile>\n    <EmbeddedResource Include=\"Properties\\Resources.resx\">\n      <Generator>ResXFileCodeGenerator</Generator>\n      <LastGenOutput>Resources.Designer.cs</LastGenOutput>\n    </EmbeddedResource>\n    <None Include=\"packages.config\" />\n    <None Include=\"Properties\\Settings.settings\">\n      <Generator>SettingsSingleFileGenerator</Generator>\n      <LastGenOutput>Settings.Designer.cs</LastGenOutput>\n    </None>\n  </ItemGroup>\n  <ItemGroup>\n    <None Include=\"App.config\" />\n  </ItemGroup>\n  <ItemGroup>\n    <BootstrapperPackage Include=\".NETFramework,Version=v4.7.2\">\n      <Visible>False</Visible>\n      <ProductName>Microsoft .NET Framework 4.7.2 %28x86 and x64%29</ProductName>\n      <Install>true</Install>\n    </BootstrapperPackage>\n    <BootstrapperPackage Include=\"Microsoft.Net.Framework.3.5.SP1\">\n      <Visible>False</Visible>\n      <ProductName>.NET Framework 3.5 SP1</ProductName>\n      <Install>false</Install>\n    </BootstrapperPackage>\n  </ItemGroup>\n  <ItemGroup>\n    <Resource Include=\"focalboard.ico\" />\n  </ItemGroup>\n  <Import Project=\"$(MSBuildToolsPath)\\Microsoft.CSharp.targets\" />\n  <Import Project=\"..\\packages\\Microsoft.Web.WebView2.1.0.705.50\\build\\Microsoft.Web.WebView2.targets\" Condition=\"Exists('..\\packages\\Microsoft.Web.WebView2.1.0.705.50\\build\\Microsoft.Web.WebView2.targets')\" />\n  <Target Name=\"EnsureNuGetPackageBuildImports\" BeforeTargets=\"PrepareForBuild\">\n    <PropertyGroup>\n      <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>\n    </PropertyGroup>\n    <Error Condition=\"!Exists('..\\packages\\Microsoft.Web.WebView2.1.0.705.50\\build\\Microsoft.Web.WebView2.targets')\" Text=\"$([System.String]::Format('$(ErrorText)', '..\\packages\\Microsoft.Web.WebView2.1.0.705.50\\build\\Microsoft.Web.WebView2.targets'))\" />\n  </Target>\n  <PropertyGroup>\n    <PostBuildEvent>copy \"$(ProjectDir)..\\..\\bin\\win-dll\\*\"  \"$(TargetDir)\"\ncopy \"$(ProjectDir)..\\..\\app-config.json\"  \"$(TargetDir)config.json\"\nrd /s /q \"$(TargetDir)pack\"\nmd \"$(TargetDir)pack\"\nxcopy /E /I /Y \"$(ProjectDir)..\\..\\webapp\\pack\"  \"$(TargetDir)pack\\\"</PostBuildEvent>\n  </PropertyGroup>\n</Project>"
  },
  {
    "path": "win-wpf/Focalboard/MainWindow.xaml",
    "content": "<Window x:Class=\"Focalboard.MainWindow\"\n        xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n        xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n        xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n        xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n        xmlns:wv2=\"clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf\"\n        xmlns:local=\"clr-namespace:Focalboard\"\n        mc:Ignorable=\"d\"\n        Title=\"Focalboard\" Height=\"800\" Width=\"1024\">\n    <DockPanel>\n        <Label Name=\"installingLabel\" Visibility=\"Collapsed\" DockPanel.Dock=\"Top\">Downloading webview2...</Label>\n        <wv2:WebView2\n            Name=\"webView\"\n        />\n    </DockPanel>\n</Window>\n"
  },
  {
    "path": "win-wpf/Focalboard/MainWindow.xaml.cs",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nusing System;\nusing System.Diagnostics;\nusing System.IO;\nusing System.Windows;\nusing System.Windows.Input;\nusing Microsoft.Web.WebView2.Core;\n\nnamespace Focalboard {\n\t/// <summary>\n\t/// Interaction logic for MainWindow.xaml\n\t/// </summary>\n\tpublic partial class MainWindow : Window {\n\t\tprivate int port {\n\t\t\tget { return ((App)Application.Current).port; }\n\t\t}\n\n\t\tprivate string sessionToken {\n\t\t\tget { return ((App)Application.Current).sessionToken; }\n\t\t}\n\n\t\tpublic MainWindow() {\n\t\t\tDebug.WriteLine($\"MainWindow()\");\n\n\t\t\tInitializeComponent();\n\n\t\t\tRestoreWindowsState();\n\n\t\t\tthis.Loaded += MainWindow_Loaded;\n\t\t\tthis.Closing += MainWindow_Closing;\n\n\t\t\tInitializeWebView();\n\t\t}\n\n\t\tprivate void MainWindow_Loaded(object sender, RoutedEventArgs e) {\n\t\t\tActivate();\n\t\t}\n\n\t\tprivate void PromptToInstallWebview2() {\n\t\t\tvar dialogResult = MessageBox.Show(\n\t\t\t\t\"Focalboard requires the WebView2 runtime to be downloaded and installed. Install now?\",\n\t\t\t\t\"Focalboard\",\n\t\t\t\tMessageBoxButton.YesNo,\n\t\t\t\tMessageBoxImage.Information,\n\t\t\t\tMessageBoxResult.OK,\n\t\t\t\tMessageBoxOptions.DefaultDesktopOnly);\n\n\t\t\tif (dialogResult == MessageBoxResult.Yes) {\n\t\t\t\tinstallingLabel.Visibility = Visibility.Visible;\n\t\t\t\twebView.Visibility = Visibility.Collapsed;\n\n\t\t\t\tvar installer = new Webview2Installer();\n\t\t\t\tinstaller.InstallProgress += Installer_InstallProgress;\n\t\t\t\tinstaller.InstallCompleted += Installer_InstallCompleted;\n\t\t\t\tinstaller.DownloadAndInstall();\n\t\t\t}\n\t\t}\n\n\t\tprivate void Installer_InstallProgress(Webview2Installer sender, EventArgs e) {\n\t\t\tApplication.Current.Dispatcher.Invoke(() => {\n\t\t\t\tif (sender.downloadProgress < 100) {\n\t\t\t\t\tinstallingLabel.Content = $\"Downloading Webview2: {sender.downloadProgress}%\";\n\t\t\t\t} else {\n\t\t\t\t\tinstallingLabel.Content = \"Installing Webview2...\";\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\tprivate void Installer_InstallCompleted(Webview2Installer sender, EventArgs e) {\n\t\t\tApplication.Current.Dispatcher.Invoke(() => {\n\t\t\t\tinstallingLabel.Content = \"Webview2 install completed\";\n\t\t\t\tActivate();\n\n\t\t\t\tif (sender.exitCode != 0) {\n\t\t\t\t\tvar message = $\"Webview2 install FAILED with code {sender.exitCode}. Try again.\";\n\t\t\t\t\tMessageBox.Show(message, \"Install failed\");\n\t\t\t\t}\n\n\t\t\t\t// Reopen window\n\t\t\t\tvar window = new MainWindow();\n\t\t\t\twindow.Show();\n\t\t\t\tClose();\n\t\t\t});\n\t\t}\n\n\t\tprivate void SaveWindowState() {\n\t\t\ttry {\n\t\t\t\tProperties.Settings.Default.WindowPosition = new System.Drawing.Point(\n\t\t\t\t\tConvert.ToInt32(RestoreBounds.Location.X),\n\t\t\t\t\tConvert.ToInt32(RestoreBounds.Location.Y));\n\n\t\t\t\tProperties.Settings.Default.WindowSize = new System.Drawing.Size(\n\t\t\t\t\tConvert.ToInt32(RestoreBounds.Size.Width),\n\t\t\t\t\tConvert.ToInt32(RestoreBounds.Size.Height));\n\n\t\t\t\tProperties.Settings.Default.WindowMaximized = (WindowState == WindowState.Maximized);\n\t\t\t\tProperties.Settings.Default.Save();\n\t\t\t} catch {\n\t\t\t\t// Ignore errors, e.g. overflow\n\t\t\t}\n\t\t}\n\n\t\tprivate void RestoreWindowsState() {\n\t\t\tthis.Left = Properties.Settings.Default.WindowPosition.X;\n\t\t\tthis.Top = Properties.Settings.Default.WindowPosition.Y;\n\t\t\tthis.Width = Math.Max(300, Properties.Settings.Default.WindowSize.Width);\n\t\t\tthis.Height = Math.Max(200, Properties.Settings.Default.WindowSize.Height);\n\n\t\t\tif (Properties.Settings.Default.WindowMaximized) {\n\t\t\t\tWindowState = WindowState.Maximized;\n\t\t\t}\n\t\t}\n\n\t\tprivate void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e) {\n\t\t\tSaveWindowState();\n\t\t}\n\n\t\tasync void InitializeWebView() {\n\t\t\tstring version = GetWebView2Version();\n\t\t\tvar isAltKeyPressed = (Keyboard.IsKeyDown(Key.LeftAlt) || Keyboard.IsKeyDown(Key.RightAlt));\n\t\t\tif (version == \"\" || isAltKeyPressed) {\n\t\t\t\tPromptToInstallWebview2();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tthis.Title = $\"Focalboard (port {port} WebView {version})\";\n\n\t\t\t// must create a data folder if running out of a secured folder that can't write like Program Files\n\t\t\tvar env = await CoreWebView2Environment.CreateAsync(\n\t\t\t\tuserDataFolder: Path.Combine(Path.GetTempPath(), \"Focalboard\")\n\t\t\t);\n\t\t\tawait webView.EnsureCoreWebView2Async(env);\n\n\t\t\twebView.ContentLoading += WebView_ContentLoading;\n\n\t\t\tvar url = String.Format(\"http://localhost:{0}\", port);\n\t\t\twebView.Source = new Uri(url);\n\t\t}\n\n\t\tprivate static string GetWebView2Version() {\n\t\t\ttry {\n\t\t\t\treturn CoreWebView2Environment.GetAvailableBrowserVersionString();\n\t\t\t} catch (Exception) { return \"\"; }\n\t\t}\n\n\t\tprivate void WebView_ContentLoading(object sender, CoreWebView2ContentLoadingEventArgs e) {\n\t\t\t// Set focalboardSessionId\n\t\t\tstring script = $\"localStorage.setItem('focalboardSessionId', '{sessionToken}');\";\n\t\t\twebView.ExecuteScriptAsync(script);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "win-wpf/Focalboard/Properties/AssemblyInfo.cs",
    "content": "using System.Reflection;\nusing System.Resources;\nusing System.Runtime.CompilerServices;\nusing System.Runtime.InteropServices;\nusing System.Windows;\n\n// General Information about an assembly is controlled through the following\n// set of attributes. Change these attribute values to modify the information\n// associated with an assembly.\n[assembly: AssemblyTitle(\"Focalboard\")]\n[assembly: AssemblyDescription(\"Focalboard Windows App\")]\n[assembly: AssemblyConfiguration(\"\")]\n[assembly: AssemblyCompany(\"\")]\n[assembly: AssemblyProduct(\"Focalboard\")]\n[assembly: AssemblyCopyright(\"Copyright © Mattermost, Inc. 2021\")]\n[assembly: AssemblyTrademark(\"\")]\n[assembly: AssemblyCulture(\"\")]\n\n// Setting ComVisible to false makes the types in this assembly not visible\n// to COM components.  If you need to access a type in this assembly from\n// COM, set the ComVisible attribute to true on that type.\n[assembly: ComVisible(false)]\n\n//In order to begin building localizable applications, set\n//<UICulture>CultureYouAreCodingWith</UICulture> in your .csproj file\n//inside a <PropertyGroup>.  For example, if you are using US english\n//in your source files, set the <UICulture> to en-US.  Then uncomment\n//the NeutralResourceLanguage attribute below.  Update the \"en-US\" in\n//the line below to match the UICulture setting in the project file.\n\n//[assembly: NeutralResourcesLanguage(\"en-US\", UltimateResourceFallbackLocation.Satellite)]\n\n\n[assembly: ThemeInfo(\n    ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located\n                                     //(used if a resource is not found in the page,\n                                     // or application resource dictionaries)\n    ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located\n                                              //(used if a resource is not found in the page,\n                                              // app, or any theme specific resource dictionaries)\n)]\n\n\n// Version information for an assembly consists of the following four values:\n//\n//      Major Version\n//      Minor Version\n//      Build Number\n//      Revision\n//\n// You can specify all the values or you can default the Build and Revision Numbers\n// by using the '*' as shown below:\n// [assembly: AssemblyVersion(\"1.0.*\")]\n[assembly: AssemblyVersion(\"1.0.0.0\")]\n[assembly: AssemblyFileVersion(\"1.0.0.0\")]\n"
  },
  {
    "path": "win-wpf/Focalboard/Properties/Resources.Designer.cs",
    "content": "﻿//------------------------------------------------------------------------------\n// <auto-generated>\n//     This code was generated by a tool.\n//     Runtime Version:4.0.30319.42000\n//\n//     Changes to this file may cause incorrect behavior and will be lost if\n//     the code is regenerated.\n// </auto-generated>\n//------------------------------------------------------------------------------\n\nnamespace Focalboard.Properties {\n    using System;\n    \n    \n    /// <summary>\n    ///   A strongly-typed resource class, for looking up localized strings, etc.\n    /// </summary>\n    // This class was auto-generated by the StronglyTypedResourceBuilder\n    // class via a tool like ResGen or Visual Studio.\n    // To add or remove a member, edit your .ResX file then rerun ResGen\n    // with the /str option, or rebuild your VS project.\n    [global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"System.Resources.Tools.StronglyTypedResourceBuilder\", \"16.0.0.0\")]\n    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]\n    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]\n    internal class Resources {\n        \n        private static global::System.Resources.ResourceManager resourceMan;\n        \n        private static global::System.Globalization.CultureInfo resourceCulture;\n        \n        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute(\"Microsoft.Performance\", \"CA1811:AvoidUncalledPrivateCode\")]\n        internal Resources() {\n        }\n        \n        /// <summary>\n        ///   Returns the cached ResourceManager instance used by this class.\n        /// </summary>\n        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]\n        internal static global::System.Resources.ResourceManager ResourceManager {\n            get {\n                if (object.ReferenceEquals(resourceMan, null)) {\n                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager(\"Focalboard.Properties.Resources\", typeof(Resources).Assembly);\n                    resourceMan = temp;\n                }\n                return resourceMan;\n            }\n        }\n        \n        /// <summary>\n        ///   Overrides the current thread's CurrentUICulture property for all\n        ///   resource lookups using this strongly typed resource class.\n        /// </summary>\n        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]\n        internal static global::System.Globalization.CultureInfo Culture {\n            get {\n                return resourceCulture;\n            }\n            set {\n                resourceCulture = value;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "win-wpf/Focalboard/Properties/Resources.resx",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" msdata:Ordinal=\"1\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n</root>"
  },
  {
    "path": "win-wpf/Focalboard/Properties/Settings.Designer.cs",
    "content": "﻿//------------------------------------------------------------------------------\n// <auto-generated>\n//     This code was generated by a tool.\n//     Runtime Version:4.0.30319.42000\n//\n//     Changes to this file may cause incorrect behavior and will be lost if\n//     the code is regenerated.\n// </auto-generated>\n//------------------------------------------------------------------------------\n\nnamespace Focalboard.Properties {\n    \n    \n    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]\n    [global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator\", \"16.8.1.0\")]\n    internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {\n        \n        private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));\n        \n        public static Settings Default {\n            get {\n                return defaultInstance;\n            }\n        }\n        \n        [global::System.Configuration.UserScopedSettingAttribute()]\n        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]\n        [global::System.Configuration.DefaultSettingValueAttribute(\"50, 20\")]\n        public global::System.Drawing.Point WindowPosition {\n            get {\n                return ((global::System.Drawing.Point)(this[\"WindowPosition\"]));\n            }\n            set {\n                this[\"WindowPosition\"] = value;\n            }\n        }\n        \n        [global::System.Configuration.UserScopedSettingAttribute()]\n        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]\n        [global::System.Configuration.DefaultSettingValueAttribute(\"1024, 800\")]\n        public global::System.Drawing.Size WindowSize {\n            get {\n                return ((global::System.Drawing.Size)(this[\"WindowSize\"]));\n            }\n            set {\n                this[\"WindowSize\"] = value;\n            }\n        }\n        \n        [global::System.Configuration.UserScopedSettingAttribute()]\n        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]\n        [global::System.Configuration.DefaultSettingValueAttribute(\"False\")]\n        public bool WindowMaximized {\n            get {\n                return ((bool)(this[\"WindowMaximized\"]));\n            }\n            set {\n                this[\"WindowMaximized\"] = value;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "win-wpf/Focalboard/Properties/Settings.settings",
    "content": "﻿<?xml version='1.0' encoding='utf-8'?>\n<SettingsFile xmlns=\"http://schemas.microsoft.com/VisualStudio/2004/01/settings\" CurrentProfile=\"(Default)\" GeneratedClassNamespace=\"Focalboard.Properties\" GeneratedClassName=\"Settings\">\n  <Profiles />\n  <Settings>\n    <Setting Name=\"WindowPosition\" Type=\"System.Drawing.Point\" Scope=\"User\">\n      <Value Profile=\"(Default)\">50, 20</Value>\n    </Setting>\n    <Setting Name=\"WindowSize\" Type=\"System.Drawing.Size\" Scope=\"User\">\n      <Value Profile=\"(Default)\">1024, 800</Value>\n    </Setting>\n    <Setting Name=\"WindowMaximized\" Type=\"System.Boolean\" Scope=\"User\">\n      <Value Profile=\"(Default)\">False</Value>\n    </Setting>\n  </Settings>\n</SettingsFile>"
  },
  {
    "path": "win-wpf/Focalboard/Utils.cs",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nusing System.IO;\n\nnamespace Focalboard {\n    static class Utils {\n        public static string GetAppFolder() {\n            string appFolder;\n\n            try {\n                appFolder = Windows.Application­Model.Package.Current.Installed­Location.Path;\n            } catch {\n                // Not a UWP app\n                string appPath = System.Reflection.Assembly.GetExecutingAssembly().Location;\n                appFolder = Path.GetDirectoryName(appPath);\n            }\n\n            return appFolder;\n        }\n    }\n}\n"
  },
  {
    "path": "win-wpf/Focalboard/Webview2Installer.cs",
    "content": "// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n// See LICENSE.txt for license information.\nusing System;\nusing System.ComponentModel;\nusing System.Diagnostics;\nusing System.IO;\nusing System.Net;\nusing System.Windows;\nusing Windows.Storage;\n\nnamespace Focalboard {\n    class Webview2Installer {\n        public int exitCode = -1;\n        public int downloadProgress = 0;\n\n        public delegate void InstallerHandler(Webview2Installer sender, EventArgs e);\n        public event InstallerHandler InstallProgress;\n        public event InstallerHandler InstallCompleted;\n\n        private string filePath;\n\n        public Webview2Installer() {\n            var filename = $\"{Guid.NewGuid().ToString()} MicrosoftEdgeWebview2Setup.exe\";\n            filePath = Path.Combine(System.IO.Path.GetTempPath(), filename);\n            Debug.WriteLine($\"Webview2Installer.filePath: {filePath}\");\n        }\n\n        public void DownloadAndInstall() {\n            const string url = \"https://go.microsoft.com/fwlink/p/?LinkId=2124703\";\n            var uri = new Uri(url);\n\n            try {\n                if (File.Exists(filePath)) {\n                    File.Delete(filePath);\n                }\n\n                WebClient wc = new WebClient();\n                wc.DownloadFileAsync(uri, filePath);\n                wc.DownloadProgressChanged += new DownloadProgressChangedEventHandler(wc_DownloadProgressChanged);\n                wc.DownloadFileCompleted += new AsyncCompletedEventHandler(wc_DownloadFileCompleted);\n            } catch (Exception ex) {\n                MessageBox.Show($\"Webview2 download ERROR: {ex.Message}\", \"Download error\");\n            }\n        }\n\n        private void wc_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e) {\n            downloadProgress = e.ProgressPercentage;\n            InstallProgress?.Invoke(this, e);\n        }\n\n        private void wc_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e) {\n            if (e.Error == null) {\n                var proc = Process.Start(filePath);\n                proc.EnableRaisingEvents = true;\n                proc.Exited += Proc_Exited;\n            } else {\n                MessageBox.Show($\"Unable to download webview2 installer, please check your Internet connection. ERROR: {e.Error.Message}\", \"Download failed\");\n            }\n        }\n\n        private void Proc_Exited(object sender, EventArgs e) {\n            // Delete downloaded installer\n            try {\n                if (File.Exists(filePath)) {\n                    File.Delete(filePath);\n                }\n            } catch (Exception ex) {\n                Debug.WriteLine($\"Delete file failed. Error: {ex.Message}, filePath: {filePath}\");\n            }\n\n            var proc = (Process)sender;\n            exitCode = proc.ExitCode;\n            InstallCompleted?.Invoke(this, e);\n        }\n    }\n}\n"
  },
  {
    "path": "win-wpf/Focalboard/packages.config",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<packages>\n  <package id=\"Microsoft.Web.WebView2\" version=\"1.0.705.50\" targetFramework=\"net472\" />\n</packages>"
  },
  {
    "path": "win-wpf/Focalboard.sln",
    "content": "﻿\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio Version 16\nVisualStudioVersion = 16.0.30907.101\nMinimumVisualStudioVersion = 10.0.40219.1\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"Focalboard\", \"Focalboard\\Focalboard.csproj\", \"{7B3F5C74-96AC-4521-9268-28BF2D91FCF4}\"\nEndProject\nGlobal\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n\t\tDebug|Any CPU = Debug|Any CPU\n\t\tDebug|x64 = Debug|x64\n\t\tRelease|Any CPU = Release|Any CPU\n\t\tRelease|x64 = Release|x64\n\tEndGlobalSection\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n\t\t{7B3F5C74-96AC-4521-9268-28BF2D91FCF4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{7B3F5C74-96AC-4521-9268-28BF2D91FCF4}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{7B3F5C74-96AC-4521-9268-28BF2D91FCF4}.Debug|x64.ActiveCfg = Debug|x64\n\t\t{7B3F5C74-96AC-4521-9268-28BF2D91FCF4}.Debug|x64.Build.0 = Debug|x64\n\t\t{7B3F5C74-96AC-4521-9268-28BF2D91FCF4}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{7B3F5C74-96AC-4521-9268-28BF2D91FCF4}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{7B3F5C74-96AC-4521-9268-28BF2D91FCF4}.Release|x64.ActiveCfg = Release|x64\n\t\t{7B3F5C74-96AC-4521-9268-28BF2D91FCF4}.Release|x64.Build.0 = Release|x64\n\tEndGlobalSection\n\tGlobalSection(SolutionProperties) = preSolution\n\t\tHideSolutionNode = FALSE\n\tEndGlobalSection\n\tGlobalSection(ExtensibilityGlobals) = postSolution\n\t\tSolutionGuid = {8583DA40-AB6E-4AC4-89EC-F2419CE044D1}\n\tEndGlobalSection\nEndGlobal\n"
  },
  {
    "path": "win-wpf/README.md",
    "content": "# Focalboard Windows Personal Desktop\n\nThis folder contains the code for the Windows Personal Desktop. It packages a lightweight C# Windows App with the Windows build of the server, and the webapp. The server is run in a single-user mode.\n\n## Debugging in Visual Studio\n\nOpen `Focalboard.sln` in Visual Studio to debug it.\n\n### Testing the single-user server\n\nYou can also run the server in single-user mode and connect to it via a browser:\n\n1. Run `FOCALBOARD_SINGLE_USER_TOKEN=testtest make watch-single-user`\n  * This runs the server with the `-single-user` flag\n  * Alternatively, select `Go: Launch Single-user Server` from VSCode's run and debug options\n2. Open a browser to `http://localhost:8000`\n3. Open the browser developer tools to Application \\ Local Storage \\ localhost:8000\n4. Set `focalboardSessionId` to `testtest`\n5. Navigate to `http://localhost:8000`\n"
  },
  {
    "path": "win-wpf/build.bat",
    "content": "@echo off\n\nWHERE msbuild.exe > nul 2>&1\nIF %ERRORLEVEL% NEQ 0 set PATH=%PATH%;C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\MSBuild\\Current\\Bin\n\nWHERE msbuild.exe > nul 2>&1\nIF %ERRORLEVEL% NEQ 0 set PATH=%PATH%;C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Enterprise\\MSBuild\\Current\\Bin\n\nWHERE msbuild.exe > nul\nIF %ERRORLEVEL% NEQ 0 echo msbuild.exe not found; exit /b 1\n\necho Building...\n\nmsbuild.exe Focalboard.sln /t:Rebuild /p:Configuration=Release  /p:Platform=\"x64\" /p:DebugSymbols=false /p:DebugType=None\n"
  },
  {
    "path": "win-wpf/package-zip.bat",
    "content": "@echo off\n\nif exist dist\\focalboard-win.zip del /q dist\\focalboard-win.zip\nif not exist dist mkdir dist\n\nif exist temp del /s /f /q temp\nrmdir /s /q temp\nif not exist temp mkdir temp\nxcopy /e /i /y Focalboard\\bin\\x64\\Release temp\ncopy ..\\build\\MIT-COMPILED-LICENSE.md temp\ncopy ..\\NOTICE.txt temp\ncopy ..\\webapp\\NOTICE.txt temp\\webapp-NOTICE.txt\n\necho --- Contents of temp ---\ndir /s /b temp\necho ---\n\npowershell Compress-Archive -Path temp\\* -DestinationPath dist\\focalboard-win.zip\n"
  },
  {
    "path": "win-wpf/package.bat",
    "content": "@echo off\n\nWHERE makeappx.exe > nul 2>&1\nIF %ERRORLEVEL% NEQ 0 set PATH=%PATH%;C:\\Program Files (x86)\\Windows Kits\\10\\App Certification Kit\n\nWHERE makeappx.exe > nul\nIF %ERRORLEVEL% NEQ 0 echo makeappx.exe not found; exit /b 1\n\necho Packaging...\n\nrd /s /q msix\nmkdir msix\nxcopy /e /i /y Focalboard\\bin\\x64\\Release msix\nmkdir msix\\Assets\ncopy art\\StoreLogo.png msix\\Assets\ncopy art\\icon150.png msix\\Assets\ncopy art\\icon44.png msix\\Assets\ncopy AppxManifest.xml msix\nmakeappx.exe pack /o /v /d msix /p Focalboard.msix\n"
  }
]